Jump to content

Leaderboard


Popular Content

Showing content with the highest reputation since 08/05/2018 in all areas

  1. 7 points
    Updated 5/2019 to add: Now online here: Paintball Net --- Back in the mid-90's, my brother created what he called an "action MUD". He called it "Paintball Net". PBN used ANSI text terminal commands to create a combined roll-and-scroll and animated text experience. ! (exclamation points) were trees, _ (underscores) were grass, ^ (up carets) were mountains, and more. Your "avatar" was a "Y" (uppercase y) and enemies were * (asterisks). You used MUD-like commands to move around the world and target your paintball gun and buy and sell and more. I wish I had a screenshot of what the first version looked like, but you'll just have to imagine. πŸ˜ƒ He asked me to create the GUI for the game. That was 1996, and I had just purchased Delphi 2 and wrapped up my first "learn Delphi" project. This sounded like a good next step, and "PBTerm" was born. I *do* have screenshots of that. Oh, yeah. Love those 16-color Windows graphics. πŸ˜ƒ During the years it was online, the game evolved and upgraded to look more like this: We were never going to win any awards, but we had a very devoted player community. πŸ˜ƒ Paintball Net was online from the summer of 1996 through the summer of 2000, when we took it offline to focus on other projects. The game was never huge, but we had thousands of players come through over that period. And since then, every year at least a few of the players have reached out and asked/begged/pleaded/demanded if we were going to put it back online. There really wasn't much chance of the original version going back online. The original server, written in ANSI C for Linux had proven rather fragile, and was a big part of why we took the game offline. It was taking hours every day just to keep it up and running, in addition to time spent managing the community of players. On top of that, a hard drive incident in 2008 had cost me the source code of a number of core third-party/modified components for PBTerm. This past summer, though, I realized I might be able to make the game live again using Smart Mobile Studio. I would do a straight port of the ANSI C server to Smart Pascal using Node.JS and WebSocket, and I would rebuild the PBTerm client as a browser-based client. I'm not going to say it was *easy*, but it has been a lot of fun. ANSI C converts to Pascal without a lot of painful gyrations. And Node.JS seems a LOT more flexible, stable, and powerful than the TCP sockets approach we were using before. Also, game development is a lot easier when you already own all the graphic and audio resources. πŸ˜ƒ This is the server "in action"... Recreating the client has been more complicated. 20+ years of Delphi habits had to be adapted to the new reality of SMS and a browser-based UI. But that's coming together too. It might be obvious, but I'm not targeting this game at mobile. Paintball Net needed a mouse and keyboard in 1996, it's gonna need a mouse and keyboard in 2018. Also, I've made as few modifications to the gameplay as possible. I really wanted to bring back the original as much as I could. Today I got the handful of sound effects integrated, which was easier than I expected. I'm planning to start testing soon. I just need to line up a server to use and find a few volunteers. I'm excited. This would never have happened without Smart Mobile Studio. So I figured I would share. Merry Xmas! -David
  2. 5 points
    jarto

    Development updates

    New update available: RTL: TW3ListMenu: Add Items-property, so items can created in Object Inspector. Add OnSelected-event. IDE: Bug fix to renaming of units Show form source instead of designer when form is selected from Project Manager
  3. 5 points
    jarto

    Smart Mobile Studio 3.0.1 is released!

    Smart Mobile Studio 3.0.1 is released This is the first release since 3.0. Biggest new feature is TW3LeafletMap, which lets you use OpenStreetMap. As it does not need API keys (like TW3GoogleMaps), it’s really fast and easy to use: – Create a project – Add a TW3LeafletMap -control on the form – Set AutoCreateMap to true on the map control Changes since 3.0 8.11.2018 RTL: – EventManager: – Add procedure AllowDefaultAction, that can be called from OnClick when the default action should happen. For example: To let the virtual keyboard to pop up from OnTouch. – Bug fixes: – Native scrolling was prevented if scrolling was done from an unknown element. – Prevent an extra OnClick from being fired on mobile devices. – TW3ListView: Bug fix to resizing of items. – Bug fixes to GeoLocation. Also update the Geolocation demo. – Deprecate PhoneGapAPI’s JGeolocation functions. SmartCL.GeoLocation.pas should be used instead. – Fix slider so that OnMouseDown, OnMouseUp and OnMOuseMove can be used. – TW3TabControl Tab’s OnShow was sent twice for the first tab – SmartCL.RegEx moved to System.RegEx. Also fixed TW3RegEx.Exec to return a proper empty array instead of nil. – Bug fix to Chart: TSeriesData.Refresh now also updates the X axis – TW3Grid: – Added TW3ImageColumn – Add Alignment-property to TW3ColumnHeader – Added a new OnCellControlCreated-event, which fires when a button, toggle, progress bar or image is created. Makes it possible to change properties of the control easily. – Added support for OpenStreetMap using the Leaflet library: – New control: TW3LeafletMap – New featured demo: LeafletMap IDE/Compiler: – Fixed search path compilation issues – Relative and absolute paths are working now – Compiler is updated when search path is modified in options – $I will look for include file in the project folder first – $R supports absolute paths, wildcards, folder name extension and ($Libraries) macro – Fix exceptions in Search – Upgrade to UPX 3.95 23.7.2018 – SmartCL.Effects: Properly handle padding and margins while doing effects. 22.7.2018 – Fix to css-files for selected text in TW3Edit and TW3Memo Release notes and installers: https://smartmobilestudio.com/2018/11/08/smart-mobile-studio-3-0-1-released/ Note that you can also use SmartUpdate to keep your portable installation up to date. Instructions on using that are in the link above.
  4. 4 points
    jarto

    Development updates

    New update available: RTL: New controls: TW3SpinButton, TW3ArrowUpButton, TW3ArrowDownButton, TW3ArrowLeftButton and TW3ArrowRightButton. Add 1px margin to TW3ButtonBorder to prevent the border from being clipped. Bug fixes to Tween.Effects IDE: Bug fixes and changes to renaming of forms: Renaming in Project Manager will not change form class any more. Form class name can be renamed in Object Inspector. Improvements to Project Statistics: Only count Total time when Smart Mobile Studio IDE has focus. Only Count Design time when changes are made in Form Designer. Add clear button. Component Palette: Added new controls New icons for many existing controls
  5. 4 points
    lynkfs

    Quantum computing with Smart

    Is it possible to do a bit of quantum computing using SMS ? The answer looks to be yes, using IBM's quantum computer resources which are partly made available for developers Basics first : IBM has released it's SDK to program these quantum computers using a visual composer, Python, a proprietary assembler QASM format and some run and execute utilities. There is however a second way of using its quantum resources, using the entry-points of is Q-experience REST server. Most if not all of the visual composer functions can be done by ajax-calls ! To demonstrate : the first thing is to acquire an api-token. (Create an account on https://quantumexperience.ng.bluemix.net/qx/experience. Log in and navigate to the Composer. Username > My Account, and then Advanced on the upper right. Then generate API Token. Api Tokens are personal and are not supposed to be shared.) The next thing is to generate a session token (access token). This can be done in code. This screenshot shows a couple of these REST server calls and its results : The first button (get access) generates a session token The second button queries which quantum computers are available, how many qubits can be used, if it is online and what type of system it is. Current results show 3 quantum systems - 2 real ones and 1 simulator The third button (current Temp) gives some processor info of the selected system. In this case the current temperature in Kelvin : the Melbourne processor is cooled to just above the absolute minimum. Next : the next button to implement will be to submit some code to one of the available processors for processing. Below is the project code for these REST calls unit Form1; interface uses System.Types, System.Types.Convert, System.Objects, System.Time, SmartCL.System, SmartCL.Time, SmartCL.Graphics, SmartCL.Components, SmartCL.FileUtils, SmartCL.Forms, SmartCL.Fonts, SmartCL.Theme, SmartCL.Borders, SmartCL.Application, SmartCL.Net.Http, ECMA.Json, SmartCL.Controls.Button, SmartCL.Controls.Label, HTMLTableElement, SmartCL.Controls.ScrollBox, SmartCL.Controls.Panel, SmartCL.Controls.ComboBox; type TForm1 = class(TW3Form) procedure W3Button3Click(Sender: TObject); procedure W3Button2Click(Sender: TObject); procedure W3Button1Click(Sender: TObject); private {$I 'Form1:intf'} protected procedure InitializeForm; override; procedure InitializeObject; override; procedure Resize; override; DBRows : integer := 0; FHttp : TW3HttpRequest; smscursor: variant; procedure GetAccessToken(Sender: TW3HttpRequest); procedure ListAvailableSystems(Sender: TW3HttpRequest); procedure GetTempKelvin(Sender: TW3HttpRequest); AccessToken : string; end; implementation { TForm1 } procedure TForm1.InitializeForm; begin inherited; // this is a good place to initialize components end; procedure TForm1.InitializeObject; begin inherited; {$I 'Form1:impl'} end; { Button1 } procedure TForm1.W3Button1Click(Sender: TObject); begin //Get Access Token var FHttp := TW3HttpRequest.Create; FHttp.OnDataReady := GetAccessToken; FHttp.open("POST",'https://quantumexperience.ng.bluemix.net/api/users/loginWithToken'); FHttp.RequestHeaders.Add("Content-type","application/json"); var api : variant := new JObject; api.apiToken := TString.encodeURIComponent('a59d26ebf........1d6d6d4a28c430b5336aba'); FHttp.send(json.stringify(api)); end; procedure TForm1.GetAccessToken(Sender: TW3HttpRequest); begin smscursor := JSON.parse(Sender.ResponseText); AccessToken := smscursor.id; W3Label1.Caption:= 'Access Token : ' + AccessToken; end; { Button2 } procedure TForm1.W3Button2Click(Sender: TObject); begin //List available Q systems var FHttp := TW3HttpRequest.Create; FHttp.OnDataReady := ListAvailableSystems; FHttp.open("GET",'https://quantumexperience.ng.bluemix.net/api/Backends?access_token=' + AccessToken); FHttp.RequestHeaders.Add("Content-type","application/json"); FHttp.send; end; procedure TForm1.ListAvailableSystems(Sender: TW3HttpRequest); begin var W3TableElement1 : TW3TableElement := TW3TableElement.Create(W3Panel1); //add 5 columns to the grid W3TableElement1.AddColumn('Name',200); //title, width W3TableElement1.AddColumn('Description',200); W3TableElement1.AddColumn('Qubits',50); W3TableElement1.AddColumn('System type',150); W3TableElement1.AddColumn('Status',50); smscursor := JSON.parse(Sender.ResponseText); W3ComboBox1.Clear; for var i := 0 to smscursor.length -1 do begin W3TableElement1.AddCell(i+1,1,smscursor[i].name); W3TableElement1.AddCell(i+1,2,smscursor[i].description); W3TableElement1.AddCell(i+1,3,smscursor[i].nQubits); if smscursor[i].simulator = true then W3TableElement1.AddCell(i+1,4,'quantum simulator') else W3TableElement1.AddCell(i+1,4,'quantum system'); W3TableElement1.AddCell(i+1,5,smscursor[i].status); W3ComboBox1.add(smscursor[i].name); end; W3Panel1.NativeScrolling := true; W3ComboBox1.OnChanged := procedure (Sender: TObject) begin writeln(W3ComboBox1.Items[W3ComboBox1.SelectedIndex]); end; end; { Button3 } procedure TForm1.W3Button3Click(Sender: TObject); begin //Get Temp var FHttp := TW3HttpRequest.Create; FHttp.OnDataReady := GetTempKelvin; //'https://quantumexperience.ng.bluemix.net/api/Backends/NAME/parameters?access_token=ACCESS-TOKEN'; FHttp.open("GET",'https://quantumexperience.ng.bluemix.net/api/Backends/' + W3ComboBox1.Items[W3ComboBox1.SelectedIndex] + '/parameters?access_token=' + AccessToken); FHttp.RequestHeaders.Add("Content-type","application/json"); FHttp.send; end; procedure TForm1.GetTempKelvin(Sender: TW3HttpRequest); begin If Sender.ResponseText = '{}' then begin W3Label3.Caption := 'Simulators are not cooled to (close to) absolute minimum'; end else begin smscursor := JSON.parse(Sender.ResponseText); if smscursor.fridgeParameters.Temperature.value = '' then W3Label3.Caption := 'Processor does not support Temp reading' else W3Label3.Caption:= 'Current Temp : ' + smscursor.fridgeParameters.Temperature.value + ' Kelvin'; end; end; procedure TForm1.Resize; begin inherited; end; initialization Forms.RegisterForm({$I %FILE%}, TForm1); end. In the end hopefully it will be possible to recreate the classic Battleships game using quantum computing, as described here
  6. 4 points
    warleyalex

    ecma.promise

    The unit ECMA.Promise is broken. I use this definition Using Promises in SMS unit uPromises; interface uses W3C.Console, W3C.DOM, W3C.XMLHttpRequest; type TVariantDynArray = array of Variant; JDeferred = class; TJPromiseCallback = procedure(Value: Variant); TJDeferredObject_fn = function(d: TJPromiseCallback): Variant; TJDeferredObject = procedure(resolve: TJPromiseCallback; reject: TJPromiseCallback); TJPromiseCallback_fn = function(Value: Variant): Variant; TJDeferredEventHandler = function(event: Variant): Variant; JPromise = class external 'Promise' constructor Create(fn: TJDeferredObject_fn { = nil}); overload; constructor Create(resolve: TJDeferredObject_fn; reject: TJDeferredObject_fn); overload; constructor Create(fn: TJDeferredObject); overload; function always(alwaysCallbacks: TVariantDynArray): JPromise; function done(doneCallbacks: TVariantDynArray): JPromise; overload; function done(doneCallbacks: Variant): JPromise; overload; function fail(failCallbacks: TVariantDynArray): JPromise; function progress(progressCallbacks: TVariantDynArray): JPromise; function state(): string; function &then(doneCallbacks: Variant; failCallbacks: Variant{ = undefined}; progressCallbacks: Variant { = undefined}): JPromise; external 'then'; function &then(onFulfilled: TJPromiseCallback_fn = nil): JPromise; overload; external 'then'; function &then(onFulfilled: TJPromiseCallback_fn; onRejected: TJPromiseCallback_fn): JPromise; overload; external 'then'; function &then(onFulfilled: TJPromiseCallback; onRejected: TJPromiseCallback): JPromise; overload; external 'then'; function catch(rejecTJPromiseCallback: Variant = nil): JPromise; overload; function catch(rejecTJPromiseCallback: TJPromiseCallback_fn): JPromise; overload; class function promise(target: Variant): JPromise; end; type JDeferred = class external 'Promise'(JPromise) function notify(args: TVariantDynArray): JDeferred; function notifyWith(context: Variant; args: TVariantDynArray): JDeferred; function reject(args: TVariantDynArray): JDeferred; overload; function reject(args: Variant): JDeferred; overload; function reject(args: TJDeferredEventHandler): JDeferred; overload; function rejectWith(context: Variant; args: TVariantDynArray): JDeferred; function resolve(args: TVariantDynArray): JDeferred; overload; function resolve(value: Variant = nil): JPromise; overload; function resolveWith(context: Variant; args: TVariantDynArray): JDeferred; function all(iterable: TVariantDynArray): JPromise; overload; function race(iterable: TVariantDynArray): JPromise; end; { global external functions } function Promise : JDeferred; external 'Promise' property; function queue: JPromise; external 'Promise.resolve()'; function Error(message: variant): variant; external 'Error'; function document: variant; external "document" property; function window : Variant; external 'window' property; function &typeof(obj:variant): variant; overload; external "typeof"; function wait(ms: Integer): JPromise; function getURI(url: string): Variant; function getFile(url: string): variant; //function myRequire(url: string): Variant; implementation function wait(ms: Integer): JPromise; function setTimeout(ahandler : TJPromiseCallback; aTimeout : Integer): Integer; external 'window.setTimeout'; begin result := JPromise.Create( procedure (resolve, reject: TJPromiseCallback) begin setTimeout(resolve, ms); end); end; function getURI(url: string): Variant; var request: JXMLHttpRequest; procedure p(resolve: TJPromiseCallback; reject: TJPromiseCallback); // Standard XHR to load an image procedure doOnLoad; begin // This is called even on 404 etc // so check the status if (request.status = 200) then begin // If successful, resolve the promise by passing back the request response resolve(request.response); end else begin // Otherwise reject with the status text // which will hopefully be a meaningful error reject(Error('File didn''t load successfully; error code: ' + request.statusText)); end; end; procedure doOnError; begin // Also deal with the case when the entire request fails to begin with // This is probably a network error, so reject the promise with an appropriate message reject(Error('There was a network error.')); end; Begin request := JXMLHttpRequest.Create; request.open('GET', url); // When the request loads, check whether it was successful request.addEventListener('load', @doOnLoad); // Handle network errors request.addEventListener('abort', @doOnError); // Send the request request.send(); End; begin // Create new promise with the Promise() constructor; // This has as its argument a function // with two parameters, resolve and reject Result := JPromise.Create(@p); end; function getFile(url: string): variant; begin // Create new promise with the Promise() constructor; // This has as its argument a function // with two parameters, resolve and reject Result := JPromise.create( procedure(resolve: TJPromiseCallback; reject: TJPromiseCallback) // Standard XHR to load an image var request: JXMLHttpRequest; begin request := new JXMLHttpRequest(); request.open('GET', url); // When the request loads, check whether it was successful request.onload := lambda begin // This is called even on 404 etc // so check the status if (request.status = 200) then begin // If successful, resolve the promise by passing back the request response resolve(request.response); end else begin // Otherwise reject with the status text // which will hopefully be a meaningful error reject(Error("File didn't load successfully; error code: " + request.statusText)); end; end; end; // Handle network errors request.onerror := lambda // Also deal with the case when the entire request fails to begin with // This is probably a network error, so reject the promise with an appropriate message reject(Error('There was a network error.')); end; // Send the request request.send(); end); end; /* function myRequire( url: string): Variant; function ev(win: Variant; arr: array of Variant): Variant; external 'eval.apply'; var ajax: JXMLHttpRequest; function onReady(event: JEvent): Variant; var script: Variant; begin script := ajax.response; // ?? ajax.responseText; if (ajax.readyState = 4) then begin case( (ajax.status)) of 200: begin //eval.apply( window, [script] ); ev( window, [script] ); console.log('script loaded: '+ url); end else console.log('ERROR: script not loaded: '+ url); end; end; end; begin ajax := JXMLHttpRequest.Create; ajax.open( 'GET', url, false ); // <-- the 'false' makes it synchronous ajax.onreadystatechange := @onReady; ajax.send(null); end;*/ end.
  7. 4 points
    lynkfs

    wysiwyg

    Was looking for an easy to make wysiwyg editor. This one is based on 'document.designMode'. Mozilla: "When an HTML document has been switched to designMode, its document object exposes an execCommand method to run commands that manipulate the current editable region" IFrame's have an innate document element which can be used for that purpose. Have a form with an IFrameHtmlElement and 2 buttons ('bold' and 'italic') : procedure TForm1.InitializeForm; begin inherited; // this is a good place to initialize components W3IframeHtmlElement1.handle.contentDocument.designMode := 'on'; W3IframeHtmlElement1.handle.contentDocument.body.innerHTML := 'this is some text'; W3IframeHtmlElement1.handle.focus(); end; procedure TForm1.W3Button1Click(Sender: TObject); //bold begin W3IframeHtmlElement1.handle.contentDocument.execCommand('bold', false, null); W3IframeHtmlElement1.handle.focus(); end; procedure TForm1.W3Button2Click(Sender: TObject); //italic begin W3IframeHtmlElement1.handle.contentDocument.execCommand('italic', false, null); W3IframeHtmlElement1.handle.focus(); end; Besides 'bold' and 'italic' execCommand supports a host of other edit commands as well. see https://codepen.io/chrisdavidmills/full/gzYjag/
  8. 4 points
    lynkfs

    lighthouse

    Google has implemented a new service : web.dev Basically this measures some indicators of any url using their LightHouse tool. These indicators are grouped into Performance, Accessibility, Best Practices and SEO I created a minimal SMS project with only a single image on a form and run the test : https://web.dev/measure and url : https://www.lynkfs.com/Experiments/lighthouse/www/ This gives scores of 95 / 27 / 77 / 89 for Performance, Accessibility, Best Practices and SEO respectively There are a couple of really simple things which will up these scores considerably. Scores were upped to 96 / 74 / 92 / 100 when doing this : Best Practices from 77 to 92 : 1) in project options/linker unclick 'generate cache manifest' (deprecated) Performance from 95 to 96 : 1) in project options/linker unclick 'store CSS as a separate file' (95 to 96) 2) add async to <script async src="lib/polyfill.custom.events.js" type="text/javascript"></script> in the index.html template SEO from 89 to 100 : 1) add meta description tag to the head of index.html <meta name="Description" content="Put your description here."> Accessability from 27 to 74 : 1) add alt attribute to images, even if only '' : W3Image1.handle.setAttribute('alt', ''); (27 to 58) 2) add lang="en" to html element in index.html template : <html lang="en"> (58 to 74) 3) tabindex=1 for TW3Display and TW3DisplayView. Should be 0 ? run test again on url : https://www.lynkfs.com/Experiments/lighthouse2/www/ to see these results All of the above could be made part of the standard settings for a new visual project
  9. 4 points
    lynkfs

    console override

    redirecting 'console.log' or 'writeln' output proves to be very simple : have a form with a Memo component : procedure TForm1.InitializeObject; begin inherited; {$I 'Form1:impl'} W3Memo1.Text := 'Console.log : ' + #10; browserapi.console.log := procedure(text:variant) begin W3Memo1.Text := W3Memo1.Text + #10 + text; end; writeln('aaa'); browserapi.console.log('bbb'); end; all subsequent calls to writeln and/or console.log are now redirected to the Memo component.
  10. 4 points
    lynkfs

    console override

    This mechanism can be used to redefine more built-in functions. Funny. The code below redefines 'ShowMessage' to redirect its contents to a Memo : procedure TForm1.InitializeObject; begin inherited; {$I 'Form1:impl'} W3Memo1.Text := 'ShowMessage : ' + #10; browserapi.window.alert := procedure(text:variant) begin W3Memo1.Text := W3Memo1.Text + #10 + text; end; ShowMessage('ccc'); browserapi.window.alert('ddd'); end; or combine the two and redefine 'ShowMessage' to redirect its contents to the 'Writeln' function, which in its turn is redirected to the Memo : procedure TForm1.InitializeObject; begin inherited; {$I 'Form1:impl'} W3Memo1.Text := 'Console.log : ' + #10; //WriteLn redirects to Memo browserapi.console.log := procedure(text:variant) begin W3Memo1.Text := W3Memo1.Text + #10 + text; end; //ShowMessage redirects to WriteLn browserapi.window.alert := procedure(text:variant) begin writeln(text); end; writeln('aaa'); browserapi.console.log('bbb'); ShowMessage('ccc'); browserapi.window.alert('ddd'); end;
  11. 4 points
    jarto

    Development updates

    17.11.2018 RTL: Changes to handling of Cursor: Style definitions moved from basic html elements to control styles GetCursor and SetCursor can now be overridden Bug fixes to how many controls handle cursor. Especially TW3Label. Themes: Add missing styles TW3CheckBox, TW3CheckMark, TW3RadioButton, TW3RadioToggle and TW3RadioGroup Two new backgrounds: TW3DecorativeListItemBackground and TW3TransparentBackground RTL optimizations to creation of controls, GetBoundsRect, SetBounds, MoveTo and SetSize. Bug fix to SmartCL.Graphics.pas: Changing of canvas font, size and styles did not work. Bug fix to System.DateUtils.DecodeDate. IDE: Delete key works now Search dialog and other dialogs. Bug fix: Internal Browser Window showed only a white screen if Execute was clicked while it was open. Compiler: Now(), EncodeDate() and EncodeTime() returns now the same values as Delphi and FPC All time/date -functions fixed to work with the new TDateTime-values
  12. 4 points
    jarto

    DayOf reporting false value

    I dug around and found out that I actually can fix those "buggy" functions in the compiler without having to upgrade to a newer version of DWScript. I only have IncMonth to fix any more. The root cause of the problem is the way javascript dates work. When you build a date from day, month and year, the result depends on your time zone. I guess it builds the date as UTC and then views it from your location. So, while it's 01.12.2018 at 00:00:00 in London, it's still 30.11.2018 in USA. And when you call DayOf on that, you get 30 instead of 1. This affects about 10 other functions as well, including FormatDateTime and DateTimeToStr and TimeToStr. I'll make sure to test this all properly and then release an update. Once it's done, I'm super happy to get rid of these problems.
  13. 4 points
    jarto

    Yikes!!!

    I'm sad to see Jon leave his baby like this, but I do respect his decision. What I can say is, that SMS is not dying here. I've been waiting a few weeks already to push a new update, but have not been able to get any answers from Jon, if he'd also want to include some changes. I believe things look worse to you guys than they really are. A lot of RTL work and especially bug fixes was done by me during the last year and almost all the IDE improvements and bug fixes by Primoz. Jon did pretty much all the NodeJS work, so we'll miss him most there. If I look at this optimistically, Jon's donating his shares to two young and talented Delphi programmers is a very nice thing and may give them a possibility to spend more time on SMS. But hey, hang on there and lets hope that this divorce will not be too ugly.
  14. 3 points
    jarto

    Smart Mobile Studio 3.0.2 is released!

    Smart Mobile Studio 3.0.2 is released. This version contains all the fixes and improvements from the development-channel. There are lots of bug fixes and improvements to the IDE thanks to all the help from our users here. The Smart Mobile Studio team wants to with everybody a Merry Christmas and Happy New Year. Release notes: https://smartmobilestudio.com/2018/12/21/smart-mobile-studio-3-0-2-released/
  15. 3 points
    lynkfs

    printing

    Working on a component which involves printing. I've used external libraries like jsPDF, which generate printable content and they generally work fine. They also have some drawbacks though. So this component is going to rely on the browsers native 'print' command to print a complete page/form, or part thereof. 1) The print command is tied to a window object (window.print), so the possible implementations are limited to manipulating either the current window an iFrame element, which encapsulates a window object a new pop-up window (or new tab) Just to try these out, button 1-3 in the code below correspond to these possible implementations. var Button1 : TW3Button := TW3Button.Create(self); Button1.SetBounds(20,470,120,30); Button1.Caption := 'this window'; Button1.OnClick := procedure(sender:TObject) begin var originalContents := browserapi.document.body.innerHTML; browserapi.document.body.innerHTML := SrcDoc; browserapi.window.print(); browserapi.document.body.innerHTML = originalContents; end; var Button2 : TW3Button := TW3Button.Create(self); Button2.SetBounds(150,470,120,30); Button2.Caption := 'iframe window'; var IFrame1 := TW3IFrameHtmlElement.Create(self); IFrame1.SetBounds(20,20,400,400); Button2.OnClick := procedure(sender:TObject) begin IFrame1.handle.srcdoc := SrcDoc; IFrame1.handle.contentWindow.print(); end; var Button3 : TW3Button := TW3Button.Create(self); Button3.SetBounds(280,470,120,30); Button3.Caption := 'other window'; Button3.OnClick := procedure(sender:TObject) begin asm var mywindow = window.open('', 'PRINT', 'height=400,width=600'); mywindow.document.write(@SrcDoc); mywindow.setTimeout(function(){ mywindow.print(); mywindow.close(); }, 1000); //mywindow.print(); //mywindow.close(); end; end; //SrcDoc content : see below All of these approaches work. However if printable content contains images or other sizable resources, the actual printing must be delayed until all these resources have been downloaded - so either preload or set an appropriate Timeout, as in Button3 2) Screen and print layout differ in a couple of aspects : screens are continuous, paper is not and some elements like tables and images should not be cut in half over page breaks readability of fonts is different on screens and print output hints like blue links on webpages don't translate well to printing many web page elements like menus should not appear on print and more. A good overview is given in this article A separate print stylesheet can handle some/most/all of these concerns. A generic print stylesheet based on @media print is included below. Probably not the solution for each and every situation, but it handles most of the aspects above var SrcDoc : string := #' <!DOCTYPE html> <HTML> <HEAD> <style> @media print { @page { margin: 2cm } body { font: 13pt Georgia, "Times New Roman", Times, serif; line-height: 1.3; background: #fff !important; color: #000; } h1 { font-size: 24pt; } h2, h3, h4 { font-size: 14pt; margin-top: 25px; } a { page-break-inside:avoid } blockquote { page-break-inside: avoid; } h1, h2, h3, h4, h5, h6 { page-break-after:avoid; page-break-inside:avoid } img { page-break-inside:avoid; page-break-after:avoid; } table, pre { page-break-inside:avoid } ul, ol, dl { page-break-before:avoid } a:link, a:visited, a { background: transparent; color: #520; font-weight: bold; text-decoration: underline; text-align: left; } a { page-break-inside:avoid } a[href^=http]:after { content:" <" attr(href) "> "; } $a:after > img { content: ""; } article a[href^="#"]:after { content: ""; } a:not(:local-link):after { content:" <" attr(href) "> "; } nav, .sidebar, .heading { display: none; } p, address, li, dt, dd, blockquote { font-size: 100% } code, pre { font-family: "Courier New", Courier, mono} ul, ol { list-style: square; margin-left: 18pt; margin-bottom: 20pt; } li { line-height: 1.6em; } } </style> </HEAD> <BODY BGCOLOR="FFFFFF"> <CENTER><IMG SRC="res/logo.png"> </CENTER> <HR> <H1>Header H1</H1> <H2>Header H2</H2> <P>Paragraph <P><B>Bold paragraph</B> <BR><BR><B><I>This is a sentence with some length, longer than the iframe width, in bold italic.</I></B> <HR> </BODY> </HTML>'; This generates a print preview in all major browsers, with options to set paper size, select printer, orientation etc. see https://www.lynkfs.com/Experiments/ReportWriter/www/reportwriter.htmlο»Ώ Next thing to tackle is scaling.
  16. 3 points
    DavidRM

    NodeJS file utils and native app

    Here's my example: Files := TW3NodeStorageDevice.Create(nil); Files.Mount(nil, procedure (Sender: TW3StorageDevice; Success: boolean) begin if Success then Writeln(PBNRS_FILE_SYSTEM_MOUNTED) else Writeln(Format(PBNRS_ERROR_MOUNTING_FILE_SYSTEM, [Files.LastError])); end ); And reading a file: Files.Load(PBNConfig.HelpFile, procedure (Sender: TW3StorageDevice; Filename: string; Data: TStream; Success: boolean) begin if Success then begin ... end else ... end
  17. 3 points
    jarto

    Development updates

    New update available: RTL: TW3TabControl: AnimateTabs-property to control how tabs are changed. TW3ListBox: Prevent an exception if TW3Image is used as a line control and OnShowItem is not set. TW3ListMenu, TW3HeaderControl and TW3SimpleLabel: Don't set default caption to classname during initialization. DWScript: Capitalize day and month names correctly (January instead of january, Sunday instead of sunday) IDE: Improvements to the way the IDE reacts to a changed external file. Use caption while drawing generic controls instead of component name.
  18. 3 points
    jarto

    CSS Resource Files

    @DavidRM Now I was able to reproduce the problem and it explains the "caching" too. When the IDE gets a signal, that something has changed, it reacts to it while the file is still being saved -> It can't access the file. I did probably not get this bug myself as the css file I was testing with was so small. Thank you. Now I can fix this problem πŸ™‚
  19. 3 points
    jarto

    New to Node

    Actually, the communication part between a NodeJS server and a Smart client app is pretty easy. Client: You read data from the server using simple http/https get. When you want to write something to the server, you do it with http/https post. Server: You can start with the NodeJS http server example. When your client sends a get or post, it comes to TServer.HandleRequest where you handle the request. You can start by adding some WriteLn to TServer.HandleRequest to print out what the headers and the content is. Then you can send GET and POST requests to it by using a browser (http://localhost:1881/whatever/url/you/want/to/send/it?params=whatever) or curl. With Curl you can easily send post commands. For example: curl -i -X POST -H 'Content-Type: application/json' -d '{"score": "Player": "10000"}' http://localhost:1881 When you can see what's going on in the server, you can also make the client app and start sending those http get/post with TW3HttpRequest (SmartCL.Net.Http) I have done this earlier on the client side with Smart Mobile Studio and it worked nicely. There were hundreds of thousands of users on iOS and Android. The server was in Delphi but it can really be written in any language. I'd probably write my next server in Smart Mobile Studio using NodeJS.
  20. 3 points
    jarto

    component creation

    Let me start by commenting on Jon's blog post. For the most part it's accurate, but he makes a small mistake with the "common mistake": ObjectReady() calls ReadySync() which actually does check if the child elements are ready. So, when you override ObjectReady(), the child controls you created in InitializeObject should also be ready. And when ObjectReady() happens, it also triggers the first Resize for the component. Let me comment on these three lines: Users don't need to write as complex checking routines as ReadySync() is. Let the RTL take care of that. It does it very well. However, do always check your controls in Resize(). Minimum is to check that they are not nil. And if you want to be extra sure, check the ready state too. And by far the best way to do this all is pretty simple: In your Resize() procedure, always check if your controls are ready If they are not, call Self.Invalidate(). Do not call Resize() directly or via TW3Dispatch as Jon suggests. Think about Invalidate() as a request for a new Resize(). It calls an internal procedure in the RTL called ResizeWhenReady() and it does exactly what it says: It will call Resize() with a short delay and it will check that any new child components you added after the previous resize are also ready. The delay helps to minimize the number of Resize()-calls and it simply makes sense to not let a Resize() happen before your child components are ready. I think Jon is explaining how the RTL did work before I got involved. It makes sense to rely on ObjectReady() when you have a simple, static control but when you have a control like TW3ListBox, it does not work at all as the TW3ListBox dynamically creates new child components while you use the control.
  21. 3 points
    jarto

    Development updates

    Now it should be possible for everyone to use SmartUpdate with the development-channel.
  22. 3 points
    jarto

    Smart Mobile Studio 3.0.1 is released!

    Couldn't be easier ?
  23. 3 points
    lynkfs

    Calendar

    I would have a look at fully developed open source calendar js libraries which can be embedded in your sms app as is (i.e. https://fullcalendar.io/) make your own based on an opensource css-only component. These components usually have no js component at all, just html and css. The css bit you can copy into your stylesheet if you like the look and feel and the html can be used to recreate the logic behind it all. (i.e. https://freefrontend.com/css-calendars/) make your own from scratch my personal approach would be the middle one (unless you find a js library which does exactly what you need) cheers
  24. 3 points
    lynkfs

    Send an email ?

    This is a 2-step process 1) see this post and follow at least the first steps. Basically establish the link between your app, googles api's and your gmail account. For option 2a(below) you need at least an api-key and clientId, for option 2b you need the full oauth2 gamut including tokens 2) then do one of the following 2a) make use of Googles javascript client library for gmail <!DOCTYPE html> <html> <head> <title>Send email using gmail API</title> <meta charset='utf-8' /> </head> <body> <p>Send email using gmail API.</p> <!--Add buttons to initiate auth sequence and sign out--> <button id="authorize-button" style="display: none;">Authorize</button> <button id="signout-button" style="display: none;">Sign Out</button> <div id="content"></div> <script type="text/javascript"> var apiKey = '********2qOeiRlg2humU4YifLyNqt2TrWR2kGk'; var discoveryDocs = ["https://www.googleapis.com/discovery/v1/apis/gmail/v1/rest"]; var clientId = '********7354-7kp9br0phbsdfkcsilu660u7uq9p6tqs.apps.googleusercontent.com'; var scopes = 'https://www.googleapis.com/auth/gmail.send'; var authorizeButton = document.getElementById('authorize-button'); var signoutButton = document.getElementById('signout-button'); function handleClientLoad() { // Load the API client and auth2 library gapi.load('client:auth2', initClient); } function initClient() { gapi.client.init({ apiKey: apiKey, discoveryDocs: discoveryDocs, clientId: clientId, scope: scopes }).then(function () { // Listen for sign-in state changes. gapi.auth2.getAuthInstance().isSignedIn.listen(updateSigninStatus); // Handle the initial sign-in state. updateSigninStatus(gapi.auth2.getAuthInstance().isSignedIn.get()); authorizeButton.onclick = handleAuthClick; signoutButton.onclick = handleSignoutClick; }); } function updateSigninStatus(isSignedIn) { if (isSignedIn) { authorizeButton.style.display = 'none'; signoutButton.style.display = 'block'; makeApiCall(); } else { authorizeButton.style.display = 'block'; signoutButton.style.display = 'none'; } } function handleAuthClick(event) { gapi.auth2.getAuthInstance().signIn(); } function handleSignoutClick(event) { gapi.auth2.getAuthInstance().signOut(); } // Load the API and make an API call. Display the results function makeApiCall() { const message = "From: lynk@gmail.com\r\n" + "To: nico@hotmail.com\r\n" + "Subject: Subject of this email\r\n\r\n" + "with body text here"; // The body needs to be base64url encoded. const encodedMessage = btoa(message) //but google style, replacing a couple of characters extra const reallyEncodedMessage = encodedMessage.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '') gapi.client.gmail.users.messages.send({ userId: 'me', resource: { raw: reallyEncodedMessage } }).then(function () { console.log("done!")}); } </script> <script async defer src="https://apis.google.com/js/api.js" onload="this.onload=function(){};handleClientLoad()" onreadystatechange="if (this.readyState === 'complete') this.onload()"> </script> </body> </html> 2b) or make an ajax call (post) to https://www.googleapis.com/upload/gmail/v1/users/userId/messages/send and attach a "raw" parameter in the request body with the base64 encoded email as above. I'll "Smart" both of these up a bit when time permits
  25. 3 points
    lynkfs

    OpenStreetMap component?

    you can use a simple IFrameHtmlElement on a form and set src to something like this W3IFrameHtmlElement1.Src := "//www.openstreetmap.org/export/embed.html?bbox=10.970685482025146%2C49.5968664515866%2C10.98160743713379%2C49.603138573344914&layer=mapnik&marker=49.60000261331038%2C10.976146459579468";
×