Jump to content


  • Content Count

  • Joined

  • Last visited

  • Days Won


lynkfs last won the day on November 12

lynkfs had the most liked content!

About lynkfs

  • Rank

Profile Information

  • Gender
  • Location

Recent Profile Visitors

2,862 profile views
  1. lynkfs


    and this works too var html : variant; asm @html = katex.renderToString("c = \\pm\\sqrt{a^3 + b^3}", { throwOnError: false }); end; W3Panel1.InnerHTML := html;
  2. lynkfs


    A quick solution : have a form with a panel and a button in the custom template add these lines <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/katex.css" integrity="sha384-bsHo4/LA+lkZv61JspMDQB9QP1TtO4IgOf2yYS+J6VdAYLVyx1c3XKcsHh0Vy8Ws" crossorigin="anonymous"> <script defer src="https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/katex.js" integrity="sha384-4z8mjH4yIpuK9dIQGR1JwbrfYsStrNK6MP+2Enhue4eyo0XlBDXOIPc8b6ZU0ajz" crossorigin="anonymous"></script> in the onclick handler of the button add this code procedure TForm1.W3Button1Click(Sender: TObject); begin var katexelement : variant; katexelement := W3Panel1.handle; asm katex.render("c = \\pm\\sqrt{a^2 + b^2}", @katexelement, { throwOnError: false }); end; end; which results in this : This code can be improved a bit. F.i. I like to load the js library under program control, so at least you know when it is ready
  3. lynkfs

    HTML programming

    A bit of a weird topic name for a product which elevates Object Pascal using javascript. I could have named it 'Web Components' (as I did a while ago) or maybe better 'Api Programming'. There are a number of organisations which look after the web api's. W3C is the most known one, but there are others looking after specific parts (WhatWG, Khronos etc.). What they have in common is that they publish the web api's they're looking after in a specific formal format (WebIDL). Smart has transformed these specifications in corresponding pascal classes, contained in the APIs directory in the RTL. These classes basically map directly onto functions and data available in the browser itself, and using them is sort of similar to for instance incorporating Microsoft Office functions in a Delphi program (although technically very different from using COM). The RTL uses some of these api's under the hood, this post is about a mini-rtl if you like consisting of just 1 component. I use this approach a lot for non-form based projects, single-page-apps (or multiple) and websites. Demo of a semi-static SPA The inheritance chain of the DOM looks like - EventTarget - Node - Element - HTMLElement with all the various html elements underneath : HTMLDivElement for a <div>, HTMLAnchorElement for a <a> etc. The code below wraps the HTMLElement, but allows its constructor to create any of the specialised sub-elements. unit JElement; interface uses W3C.HTML5, W3C.DOM, W3C.CSS; type THTMLElement = class private FHTMLElement: JHTMLElement; public constructor Create(element: String; parent: THTMLElement; className: String; innerHTML : String); end; implementation { THTMLElement } constructor THTMLElement.Create(element: String; parent: THTMLElement; className: String; innerHTML : String); begin // cache element and set innerHTML and className FHTMLElement := JHTMLElement(Document.createElement(element)); FHTMLElement.innerHTML := innerHTML; FHTMLElement.className := ClassName; If parent = nil then document.body.appendChild(FHTMLElement) else parent.FHTMLElement.appendchild(FHTMLElement); end; HTMLElements have as specifiers 'attributes' and 'properties'. Properties are any of the values in the 'style' attribute : in the html expression style="left=10px; color=white;" href="#" both 'left' and 'color' are properties, and both 'style' and 'href' are attributes. So we need to add the setters for these parameters : Procedure THTMLElement.SetProperty(s1: String; S2: String); begin var FElementStyle := JElementCSSInlineStyle(FHTMLElement).style; FElementStyle.setProperty(S1, S2); end; Procedure THTMLElement.SetAttribute(S1: String; S2: String); begin FHTMLElement.setAttribute(S1, S2); end; The first part of the demo app is an image, which fills the complete viewport on any device and orientation, a slightly darker tinted bar on top, and a button in the bar linking to an email form further down. This header can be constructed as per below : unit JHeader; interface uses JHTMLElement, smartcl.system; type THeader = class(THTMLElement) public constructor Create(props: Variant); end; implementation { THeader } constructor THeader.Create(props: Variant); begin //hero image var hdr0 := THTMLElement.Create('div', nil, 'div'); hdr0.setProperty('width', '100%'); hdr0.setProperty('height', '100%'); hdr0.setProperty('min-height', '100vh'); hdr0.setProperty('background-image','url('+props['header-img']+')'); hdr0.setProperty('background-size','cover'); //top bar 100px var hdr11 := THTMLElement.Create('div', hdr0, 'div'); hdr11.setProperty('width', '100%'); hdr11.setProperty('height', '100px'); hdr11.setProperty('background-color','rgb(21,21,21,0.2)'); //contact button var hdr21 := THTMLElement.Create('a', hdr11, 'div', 'Contact'); hdr21.SetBounds(20,30,75,35); hdr21.setProperty('color', 'white'); hdr21.SetAttribute('type', 'button'); hdr21.setAttribute('href', '#contact'); hdr21.SetProperty('background-color', 'rgb(21,21,21,0.2)'); which can be instantiated as in (with a variable for the image) //THeader props['header-img'] := 'assets/img/blueprint.jpg'; THeader.Create(props); and which on execution time resolves in <div class="div" style="width: 100%; height: 100%; min-height: 100vh; background-image: url(; background-size: cover;"> <div class="div" style="width: 100%; height: 100px;"> <a class="div" type="button" href="#contact" style="visibility: visible; display: inline-block; position: absolute; overflow: auto; left: 20px; top: 30px; width: 75px; height: 35px; color: white;"> Contact </a> </div> </div> Hence the title of this post : HTML programming. The advantage of this approach is that once there are a number of these multi-purpose constructs like THeader available, making up another webpage is as easy as providing values for constructor parameters and stringing them together. At some stage I created the constructs listed here They need to be updated and streamlined a bit.
  4. lynkfs

    Expand Combobox using code

    Basically the <select> element where the combobox is based on, is under control of the browser. And can't be manipulated from pascal/js. There is a hack (there is a hack for everything) to fake it : procedure TForm1.InitializeForm; begin inherited; // this is a good place to initialize components var W3ComboBox1 : Tw3ComboBox := TW3ComboBox.Create(self); W3ComboBox1.SetBounds(50,70,250,30); W3ComboBox1.Add('line 1'); W3ComboBox1.Add('line 2'); W3ComboBox1.Add('line 3'); W3ComboBox1.handle.style['opacity'] := 0; var W3Panel1 : TW3Panel := TW3Panel.Create(self); W3Panel1.SetBounds(50,70,250,30); W3Panel1.handle.style['z-index'] := 0; W3Panel1.handle.setAttribute('class',''); //get rid of all styling var W3Button1 : TW3Button := TW3Button.Create(W3Panel1); W3Button1.Caption := 'MyButton'; W3Button1.SetBounds(0,0,250,30); W3ComboBox1.OnChanged := procedure (Sender: TObject) begin W3Button1.Caption := W3ComboBox1.Items[W3ComboBox1.SelectedIndex]; end; end; works on Chrome and other browsers. Works on mobile too Demo Wouldn't recommend it though .... edited : as per @IElite suggestion: it would also be possible to make the combobox invisible, read its items on the click of a button and display them in a listbox or memo. Better, in my opinion and if really necessary, is to create a dedicated control which does exactly what you want, based on a listbox rather than on a <select> htmlelement
  5. This link is from feb 2019 (nasa related), so may work : https://www.jakenherman.com/articles/2019-02/push-notifi-cordova-firebase (there are more links as a result of googling 'cordova push notifications plugin 2019', which may be of relevance)
  6. lynkfs

    Waiting for controls to be ready

    could the 'readyexecute' method be repurposed so that code changes to existing projects could be avoided ?
  7. lynkfs


    For this app I used the HOP-H58 bluetooth printer. It comes with bluetooth and usb connectivity, and even a socket for a cash drawer. At an unbeatable price of $26 USD delivered (!), this gives me a mobile POS (invoice print) solution, where only the printer and a mobile phone is needed. Amazing.
  8. lynkfs


    How to connect webapp to a bluetooth enabled printer. Basically there are 2 avenues to do that : a) going native and b) the web bluetooth api Going native This involves using PhoneGap or Cordova, plus installing a plugin to make the bluetooth capacities of the mobile target platform available in the js world. I found this plugin on github, a cordova plugin, so used cordova rather than phonegap. The install sequence for a clean install of everything looks something like see https://cordova.apache.org/docs/en/latest/guide/cli/ install node install npm install cordova : npm install -g cordova install pre-requisites : see https://cordova.apache.org/docs/en/latest/guide/platforms/android/index.html#requirements-and-support java development kit (jdk) gradle android studio / android sdk set environment and path variables JAVA_HOME ANDROID_HOME PATH create app structure create app : cordova create bluetoothprinter com.smartmobilestudio.bluetoothprinter BluetoothPrinter install plugin cordova plugin add https://github.com/srehanuddin/Cordova-Plugin-Bluetooth-Printer.git create remainder of app add platform : cordova platform add android create SMS project copy SMS project output into the cordova app structure (www directory) build app : cordova build android install apk file on android Amazingly this all works out. Some gotcha's : - environment and path variables don't allow spaces, so for instance .../Program Files/... has to be shortened to .../Progra~1/... - cordova now requires plugins to have their own package.json file. The selected plugin doesn't have that, so I had to download the git repository, create a package.json file and install the plugin from local (cordova plugin add cordova-plugin-bluetooth-printer --save --searchpath ../plugin) - the SMS project needs these entries in its template index file <script type="text/javascript" charset="utf-8" src="cordova.js"></script> <script type="text/javascript" charset="utf-8" src="BluetoothPrinter.js"></script> and compile may end up in errors. However the cordova build process takes care of that and produces a viable apk file. It is a bit of a process to go through, but works out well in the end.
  9. lynkfs

    kodi grid

    @lennart posted a reference to a scrollable grid in Delphi Developer. See post here. Since he produced this grid in Smart Pascal, it might be of interest to forum members. This grid basically takes items of irregular dimensions and determines how to best fit those items in rows/columns. The code he posted doesn't work in my (latest) version of Smart, but with a few tweaks here and there I got it going. At least for strings that is. Icons/images are not fully implemented in the source he posted, but I'll take another look. Won't be too difficult. I like the marching ants selection. See demo. kodi := TWbIconView.Create(self); kodi.setbounds(10,10,500,500); //by way of demo : just split any sentence into words and use these as variable length cell items var s : string := #'This grid basically takes items of irregular dimensions and determines how to best fit those items in rows/columns.'; var a : array of string := StrSplit(s,' '); for var i := 0 to a.length-1 do begin kodi.AddText(a[i]); end;
  10. lynkfs

    Combobox items work like "read-only" mode

    don't use the items object : procedure TForm1.InitializeForm; begin inherited; // this is a good place to initialize components W3ComboBox1.add('Line 1'); W3ComboBox1.add('Line 2'); W3ComboBox1.add('Line 3'); end; procedure TForm1.W3Button1Click(Sender: TObject); begin W3ComboBox1.Clear; end;
  11. lynkfs


    it is a bit slow timing for this example : start: 0.0849609375ms init tensorflow : 0.936767578125ms tensorflow loaded : 1017.56591796875ms init coco-ssd : 1017.623779296875ms coco-ssd loaded : 1421.75390625ms get image : 1422.751708984375ms Predictions: 45869.56982421875ms Firefox has the same sort of response times, Edge doesn't support console timers. It is basically the time to load the mobilenet model itself from the google servers, which is responsible for the large timegap between get image and the resulting predictions. GET "https://storage.googleapis.com/tfjs-models/savedmodel/ssdlite_mobilenet_v2/model.json" GET "https://storage.googleapis.com/tfjs-models/savedmodel/ssdlite_mobilenet_v2/group1-shard5of5" GET "https://storage.googleapis.com/tfjs-models/savedmodel/ssdlite_mobilenet_v2/group1-shard4of5" GET "https://storage.googleapis.com/tfjs-models/savedmodel/ssdlite_mobilenet_v2/group1-shard3of5" GET "https://storage.googleapis.com/tfjs-models/savedmodel/ssdlite_mobilenet_v2/group1-shard2of5" GET "https://storage.googleapis.com/tfjs-models/savedmodel/ssdlite_mobilenet_v2/group1-shard1of5" It should be possible to cache all of these files, or store them on a SBC
  12. lynkfs


    edited Been a while since I had a look at Googles Tensorflow deep learning library (js version) In the meantime this library has expanded quite a bit, most notably it enables using pre-built models for a variety of tasks. This one takes any image and detects up to 90 categories of objects in the image (persons, dogs, traffic lights etc) The library correctly identifies 2 objects of type "person" in the above image (image 620*408 pixels, bbox coordinates x,y,width,height) Usage is incredibly simple : load the libraries, display an image in the browser and issue a detect command var Script := document.createElement('script'); Script.src := 'https://cdn.jsdelivr.net/npm/@tensorflow/tfjs'; console.log('init tensorflow'); document.head.appendChild(Script); Script.onload := procedure begin console.log('tensorflow loaded'); var Script := document.createElement('script'); Script.src := 'https://cdn.jsdelivr.net/npm/@tensorflow-models/coco-ssd'; console.log('init coco-ssd'); document.head.appendChild(Script); Script.onload := procedure begin console.log('coco-ssd loaded'); var Image1 := TW3Image.Create(Self); Image1.SetBounds(0, 0, 620, 408); Image1.src := 'puydesancy.jpg'; var img := Image1.handle; asm //quick and dirty : cocoSsd.load().then(model => { // detect objects in the image. model.detect(@img).then(predictions => { console.log('Predictions: ', predictions); }); }); end; end; end; (should have checked for image1.onload). Anyway, demo here. Works from server only due to cors constraints, otherwise results in 'tainted image' errors. edited and another random image
  13. lynkfs


    I recently revisited the console object, available in all browsers, and discovered some newbies I didn't know about. The methods and properties below may be sometimes useful for debugging purposes. The console object is tied to the global window object, so access either by browserapi.window.console.log('whatever'); or define a reference to the window object : var window external 'window': variant; and call like window.console.dirxml(self); Some console methods are wrapped in SmartCL.System (like writeln ea) and SmartCL.Console The console.dirxml() above gives an xml representation of any object, which is way better than the 'object Object' result of a console.log(some object) instruction For performance testing, console also offers native timers console.time() console.timeLog() console.timeEnd() The above respectively initiate, report and destroy a named timer. The maximum nr of concurrent timers is 10,000 (!) To see how this works I created a stock new visual project with a single form and no visual controls. A timer was inserted at the beginning of the generated js projectfile, at the initialization of the form and at the ApplicationStarting, InitializeForm, InitializeObject and Resize events. Results : console.timeLog top of main.js : 0.0009765625 ms Initialization 10.574951171875 ms applicationstarting 17.234130859375 ms InitializeObject 21.23095703125 ms ReSize 62.385986328125 ms ReSize 101.25 ms InitializeForm 225.994140625 ms ReSize 226.757080078125 ms Wondering about the multiple ReSize calls and the relatively large time lag between InitializeObject and InitializeForm. There is another feature built into browsers re performance : the window.performance object with its own api's The most useful methods of this object are performance.now(), which gives a high res timestamp, and performance.toJSON() which gives a json representation of the attributes of the performance object Below the 'console.timer' and 'performance.now' results of a minimum project with 1 form and no visual controls in the native framework : console.timeLog performance.now() top of main.js : 0.001953125 ms / 118.28000005334616 timestamps Initialization 2.16186523437 ms / 120.43999996967614 InitializeForm 3.61083984375 ms / 121.92000006325543 InitializeObject 4.32080078125 ms / 122.65000003390014 and some values for the attributes of the performance object itself (console.log(window.JSON.stringify(window.performance.toJSON()));) timeOrigin : 1564383720413.3972 timestamps navigationStart: 1564383720413 redirectStart: 0 redirectEnd: 0 fetchStart: 1564383720414 domainLookupStart: 1564383720418 domainLookupEnd: 1564383720418 connectStart: 1564383720418 connectEnd: 1564383720418 secureConnectionStart: 0 requestStart: 1564383720418 responseStart: 1564383720423 responseEnd: 1564383720426 unloadEventStart: 1564383720432 unloadEventEnd: 1564383720432 domLoading: 1564383720435 domInteractive: 0 domContentLoadedEventStart:0 domContentLoadedEventEnd: 0 domComplete: 0 loadEventStart: 0 loadEventEnd: 0
  14. lynkfs

    Waiting for controls to be ready

    Some interesting decisions to be made. side note : the native/shoestring rtl uses MutationObservers exclusively, which worked well for me. Interested to see what the speed difference is
  15. lynkfs

    Waiting for controls to be ready

    To make this work using MutationObservers I would probably do something like this: demo, project Essentially the TestPanel is put here under the surveillance of a MutationObserver, which triggers when all changes to the panel have been applied (including the original sizing). After that it is ready to accept the label etc. procedure TTestPanel.InitializeObject; begin inherited; WriteLn('InitializeObject called -> Creating Label'); self.observe; self.handle.addEventListener('readytogo', procedure(e:variant) begin writeln('heard readytogo'); var Lab:=TW3Label.Create(Self); Lab.Caption:='Can you see me?'; Lab.SetBounds(10,10,150,30); WriteLn('InitializeObject done'); end); end; The code in this demo works, but is a bit ugly. It would be better to include the observe method standard in a constructor, put it in the TControlHandle rather than the TPanel, rewrite the eventlistener similar to the standard ReadyExecute method etc. But hey, demo. As a matter of fact, using the standard ReadyExecute method instead of MutationObservers works here as well. I think the latter is a bit better though as observers don't rely on timeouts and a single observer does handle multiple changes. /* self.Handle.ReadyExecute(procedure() begin var Lab:=TW3Label.Create(Self); Lab.Caption:='Can you see me?'; Lab.SetBounds(10,10,150,30); WriteLn('InitializeObject done'); end); */ Maybe the rtl could be made to handle all child inserts in this manner