xref: /sqlite-3.40.0/ext/wasm/fiddle/fiddle.js (revision eb97743c)
18c3b7501Sstephan/*
28c3b7501Sstephan  2022-05-20
38c3b7501Sstephan
48c3b7501Sstephan  The author disclaims copyright to this source code.  In place of a
58c3b7501Sstephan  legal notice, here is a blessing:
68c3b7501Sstephan
78c3b7501Sstephan  *   May you do good and not evil.
88c3b7501Sstephan  *   May you find forgiveness for yourself and forgive others.
98c3b7501Sstephan  *   May you share freely, never taking more than you give.
108c3b7501Sstephan
118c3b7501Sstephan  ***********************************************************************
128c3b7501Sstephan
138c3b7501Sstephan  This is the main entry point for the sqlite3 fiddle app. It sets up the
148c3b7501Sstephan  various UI bits, loads a Worker for the db connection, and manages the
158c3b7501Sstephan  communication between the UI and worker.
168c3b7501Sstephan*/
178c3b7501Sstephan(function(){
188c3b7501Sstephan  'use strict';
198c3b7501Sstephan  /* Recall that the 'self' symbol, except where locally
208c3b7501Sstephan     overwritten, refers to the global window or worker object. */
218c3b7501Sstephan
228c3b7501Sstephan  const storage = (function(NS/*namespace object in which to store this module*/){
238c3b7501Sstephan    /* Pedantic licensing note: this code originated in the Fossil SCM
248c3b7501Sstephan       source tree, where it has a different license, but the person who
258c3b7501Sstephan       ported it into sqlite is the same one who wrote it for fossil. */
268c3b7501Sstephan    'use strict';
278c3b7501Sstephan    NS = NS||{};
288c3b7501Sstephan
298c3b7501Sstephan    /**
308c3b7501Sstephan       This module provides a basic wrapper around localStorage
318c3b7501Sstephan       or sessionStorage or a dummy proxy object if neither
328c3b7501Sstephan       of those are available.
338c3b7501Sstephan    */
348c3b7501Sstephan    const tryStorage = function f(obj){
358c3b7501Sstephan      if(!f.key) f.key = 'storage.access.check';
368c3b7501Sstephan      try{
378c3b7501Sstephan        obj.setItem(f.key, 'f');
388c3b7501Sstephan        const x = obj.getItem(f.key);
398c3b7501Sstephan        obj.removeItem(f.key);
408c3b7501Sstephan        if(x!=='f') throw new Error(f.key+" failed")
418c3b7501Sstephan        return obj;
428c3b7501Sstephan      }catch(e){
438c3b7501Sstephan        return undefined;
448c3b7501Sstephan      }
458c3b7501Sstephan    };
468c3b7501Sstephan
478c3b7501Sstephan    /** Internal storage impl for this module. */
488c3b7501Sstephan    const $storage =
498c3b7501Sstephan          tryStorage(window.localStorage)
508c3b7501Sstephan          || tryStorage(window.sessionStorage)
518c3b7501Sstephan          || tryStorage({
528c3b7501Sstephan            // A basic dummy xyzStorage stand-in
538c3b7501Sstephan            $$$:{},
548c3b7501Sstephan            setItem: function(k,v){this.$$$[k]=v},
558c3b7501Sstephan            getItem: function(k){
568c3b7501Sstephan              return this.$$$.hasOwnProperty(k) ? this.$$$[k] : undefined;
578c3b7501Sstephan            },
588c3b7501Sstephan            removeItem: function(k){delete this.$$$[k]},
598c3b7501Sstephan            clear: function(){this.$$$={}}
608c3b7501Sstephan          });
618c3b7501Sstephan
628c3b7501Sstephan    /**
638c3b7501Sstephan       For the dummy storage we need to differentiate between
648c3b7501Sstephan       $storage and its real property storage for hasOwnProperty()
658c3b7501Sstephan       to work properly...
668c3b7501Sstephan    */
678c3b7501Sstephan    const $storageHolder = $storage.hasOwnProperty('$$$') ? $storage.$$$ : $storage;
688c3b7501Sstephan
698c3b7501Sstephan    /**
708c3b7501Sstephan       A prefix which gets internally applied to all storage module
718c3b7501Sstephan       property keys so that localStorage and sessionStorage across the
728c3b7501Sstephan       same browser profile instance do not "leak" across multiple apps
738c3b7501Sstephan       being hosted by the same origin server. Such cross-polination is
748c3b7501Sstephan       still there but, with this key prefix applied, it won't be
758c3b7501Sstephan       immediately visible via the storage API.
768c3b7501Sstephan
778c3b7501Sstephan       With this in place we can justify using localStorage instead of
788c3b7501Sstephan       sessionStorage.
798c3b7501Sstephan
808c3b7501Sstephan       One implication of using localStorage and sessionStorage is that
818c3b7501Sstephan       their scope (the same "origin" and client application/profile)
828c3b7501Sstephan       allows multiple apps on the same origin to use the same
838c3b7501Sstephan       storage. Thus /appA/foo could then see changes made via
848c3b7501Sstephan       /appB/foo. The data do not cross user- or browser boundaries,
858c3b7501Sstephan       though, so it "might" arguably be called a
868c3b7501Sstephan       feature. storageKeyPrefix was added so that we can sandbox that
878c3b7501Sstephan       state for each separate app which shares an origin.
888c3b7501Sstephan
898c3b7501Sstephan       See: https://fossil-scm.org/forum/forumpost/4afc4d34de
908c3b7501Sstephan
918c3b7501Sstephan       Sidebar: it might seem odd to provide a key prefix and stick all
928c3b7501Sstephan       properties in the topmost level of the storage object. We do that
938c3b7501Sstephan       because adding a layer of object to sandbox each app would mean
948c3b7501Sstephan       (de)serializing that whole tree on every storage property change.
958c3b7501Sstephan       e.g. instead of storageObject.projectName.foo we have
968c3b7501Sstephan       storageObject[storageKeyPrefix+'foo']. That's soley for
978c3b7501Sstephan       efficiency's sake (in terms of battery life and
988c3b7501Sstephan       environment-internal storage-level effort).
998c3b7501Sstephan    */
1008c3b7501Sstephan    const storageKeyPrefix = (
1018c3b7501Sstephan      $storageHolder===$storage/*localStorage or sessionStorage*/
1028c3b7501Sstephan        ? (
1038c3b7501Sstephan          (NS.config ?
1048c3b7501Sstephan           (NS.config.projectCode || NS.config.projectName
1058c3b7501Sstephan            || NS.config.shortProjectName)
1068c3b7501Sstephan           : false)
1078c3b7501Sstephan            || window.location.pathname
1088c3b7501Sstephan        )+'::' : (
1098c3b7501Sstephan          '' /* transient storage */
1108c3b7501Sstephan        )
1118c3b7501Sstephan    );
1128c3b7501Sstephan
1138c3b7501Sstephan    /**
1148c3b7501Sstephan       A proxy for localStorage or sessionStorage or a
1158c3b7501Sstephan       page-instance-local proxy, if neither one is availble.
1168c3b7501Sstephan
1178c3b7501Sstephan       Which exact storage implementation is uses is unspecified, and
1188c3b7501Sstephan       apps must not rely on it.
1198c3b7501Sstephan    */
1208c3b7501Sstephan    NS.storage = {
1218c3b7501Sstephan      storageKeyPrefix: storageKeyPrefix,
1228c3b7501Sstephan      /** Sets the storage key k to value v, implicitly converting
1238c3b7501Sstephan          it to a string. */
1248c3b7501Sstephan      set: (k,v)=>$storage.setItem(storageKeyPrefix+k,v),
1258c3b7501Sstephan      /** Sets storage key k to JSON.stringify(v). */
1268c3b7501Sstephan      setJSON: (k,v)=>$storage.setItem(storageKeyPrefix+k,JSON.stringify(v)),
1278c3b7501Sstephan      /** Returns the value for the given storage key, or
1288c3b7501Sstephan          dflt if the key is not found in the storage. */
1298c3b7501Sstephan      get: (k,dflt)=>$storageHolder.hasOwnProperty(
1308c3b7501Sstephan        storageKeyPrefix+k
1318c3b7501Sstephan      ) ? $storage.getItem(storageKeyPrefix+k) : dflt,
1328c3b7501Sstephan      /** Returns true if the given key has a value of "true".  If the
1338c3b7501Sstephan          key is not found, it returns true if the boolean value of dflt
1348c3b7501Sstephan          is "true". (Remember that JS persistent storage values are all
1358c3b7501Sstephan          strings.) */
1368c3b7501Sstephan      getBool: function(k,dflt){
1378c3b7501Sstephan        return 'true'===this.get(k,''+(!!dflt));
1388c3b7501Sstephan      },
1398c3b7501Sstephan      /** Returns the JSON.parse()'d value of the given
1408c3b7501Sstephan          storage key's value, or dflt is the key is not
1418c3b7501Sstephan          found or JSON.parse() fails. */
1428c3b7501Sstephan      getJSON: function f(k,dflt){
1438c3b7501Sstephan        try {
1448c3b7501Sstephan          const x = this.get(k,f);
1458c3b7501Sstephan          return x===f ? dflt : JSON.parse(x);
1468c3b7501Sstephan        }
1478c3b7501Sstephan        catch(e){return dflt}
1488c3b7501Sstephan      },
1498c3b7501Sstephan      /** Returns true if the storage contains the given key,
1508c3b7501Sstephan          else false. */
1518c3b7501Sstephan      contains: (k)=>$storageHolder.hasOwnProperty(storageKeyPrefix+k),
1528c3b7501Sstephan      /** Removes the given key from the storage. Returns this. */
1538c3b7501Sstephan      remove: function(k){
1548c3b7501Sstephan        $storage.removeItem(storageKeyPrefix+k);
1558c3b7501Sstephan        return this;
1568c3b7501Sstephan      },
1578c3b7501Sstephan      /** Clears ALL keys from the storage. Returns this. */
1588c3b7501Sstephan      clear: function(){
1598c3b7501Sstephan        this.keys().forEach((k)=>$storage.removeItem(/*w/o prefix*/k));
1608c3b7501Sstephan        return this;
1618c3b7501Sstephan      },
1628c3b7501Sstephan      /** Returns an array of all keys currently in the storage. */
1638c3b7501Sstephan      keys: ()=>Object.keys($storageHolder).filter((v)=>(v||'').startsWith(storageKeyPrefix)),
1648c3b7501Sstephan      /** Returns true if this storage is transient (only available
1658c3b7501Sstephan          until the page is reloaded), indicating that fileStorage
1668c3b7501Sstephan          and sessionStorage are unavailable. */
1678c3b7501Sstephan      isTransient: ()=>$storageHolder!==$storage,
1688c3b7501Sstephan      /** Returns a symbolic name for the current storage mechanism. */
1698c3b7501Sstephan      storageImplName: function(){
1708c3b7501Sstephan        if($storage===window.localStorage) return 'localStorage';
1718c3b7501Sstephan        else if($storage===window.sessionStorage) return 'sessionStorage';
1728c3b7501Sstephan        else return 'transient';
1738c3b7501Sstephan      },
1748c3b7501Sstephan
1758c3b7501Sstephan      /**
1768c3b7501Sstephan         Returns a brief help text string for the currently-selected
1778c3b7501Sstephan         storage type.
1788c3b7501Sstephan      */
1798c3b7501Sstephan      storageHelpDescription: function(){
1808c3b7501Sstephan        return {
1818c3b7501Sstephan          localStorage: "Browser-local persistent storage with an "+
1828c3b7501Sstephan            "unspecified long-term lifetime (survives closing the browser, "+
1838c3b7501Sstephan            "but maybe not a browser upgrade).",
1848c3b7501Sstephan          sessionStorage: "Storage local to this browser tab, "+
1858c3b7501Sstephan            "lost if this tab is closed.",
1868c3b7501Sstephan          "transient": "Transient storage local to this invocation of this page."
1878c3b7501Sstephan        }[this.storageImplName()];
1888c3b7501Sstephan      }
1898c3b7501Sstephan    };
1908c3b7501Sstephan    return NS.storage;
1918c3b7501Sstephan  })({})/*storage API setup*/;
1928c3b7501Sstephan
1938c3b7501Sstephan
1948c3b7501Sstephan  /** Name of the stored copy of SqliteFiddle.config. */
1958c3b7501Sstephan  const configStorageKey = 'sqlite3-fiddle-config';
1968c3b7501Sstephan
1978c3b7501Sstephan  /**
1988c3b7501Sstephan     The SqliteFiddle object is intended to be the primary
1998c3b7501Sstephan     app-level object for the main-thread side of the sqlite
2008c3b7501Sstephan     fiddle application. It uses a worker thread to load the
2018c3b7501Sstephan     sqlite WASM module and communicate with it.
2028c3b7501Sstephan  */
2038c3b7501Sstephan  const SF/*local convenience alias*/
2048c3b7501Sstephan        = window.SqliteFiddle/*canonical name*/ = {
2058c3b7501Sstephan          /* Config options. */
2068c3b7501Sstephan          config: {
2078c3b7501Sstephan            /* If true, SqliteFiddle.echo() will auto-scroll the
2088c3b7501Sstephan               output widget to the bottom when it receives output,
2098c3b7501Sstephan               else it won't. */
2108c3b7501Sstephan            autoScrollOutput: true,
2118c3b7501Sstephan            /* If true, the output area will be cleared before each
2128c3b7501Sstephan               command is run, else it will not. */
2138c3b7501Sstephan            autoClearOutput: false,
2148c3b7501Sstephan            /* If true, SqliteFiddle.echo() will echo its output to
2158c3b7501Sstephan               the console, in addition to its normal output widget.
2168c3b7501Sstephan               That slows it down but is useful for testing. */
2178c3b7501Sstephan            echoToConsole: false,
2188c3b7501Sstephan            /* If true, display input/output areas side-by-side. */
2198c3b7501Sstephan            sideBySide: true,
2208c3b7501Sstephan            /* If true, swap positions of the input/output areas. */
2218c3b7501Sstephan            swapInOut: false
2228c3b7501Sstephan          },
2238c3b7501Sstephan          /**
2248c3b7501Sstephan             Emits the given text, followed by a line break, to the
2258c3b7501Sstephan             output widget.  If given more than one argument, they are
2268c3b7501Sstephan             join()'d together with a space between each. As a special
2278c3b7501Sstephan             case, if passed a single array, that array is used in place
2288c3b7501Sstephan             of the arguments array (this is to facilitate receiving
2298c3b7501Sstephan             lists of arguments via worker events).
2308c3b7501Sstephan          */
2318c3b7501Sstephan          echo: function f(text) {
2328c3b7501Sstephan            /* Maintenance reminder: we currently require/expect a textarea
2338c3b7501Sstephan               output element. It might be nice to extend this to behave
2348c3b7501Sstephan               differently if the output element is a non-textarea element,
2358c3b7501Sstephan               in which case it would need to append the given text as a TEXT
2368c3b7501Sstephan               node and add a line break. */
2378c3b7501Sstephan            if(!f._){
2388c3b7501Sstephan              f._ = document.getElementById('output');
2398c3b7501Sstephan              f._.value = ''; // clear browser cache
2408c3b7501Sstephan            }
2418c3b7501Sstephan            if(arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' ');
2428c3b7501Sstephan            else if(1===arguments.length && Array.isArray(text)) text = text.join(' ');
2438c3b7501Sstephan            // These replacements are necessary if you render to raw HTML
2448c3b7501Sstephan            //text = text.replace(/&/g, "&");
2458c3b7501Sstephan            //text = text.replace(/</g, "&lt;");
2468c3b7501Sstephan            //text = text.replace(/>/g, "&gt;");
2478c3b7501Sstephan            //text = text.replace('\n', '<br>', 'g');
2488c3b7501Sstephan            if(null===text){/*special case: clear output*/
2498c3b7501Sstephan              f._.value = '';
2508c3b7501Sstephan              return;
2518c3b7501Sstephan            }else if(this.echo._clearPending){
2528c3b7501Sstephan              delete this.echo._clearPending;
2538c3b7501Sstephan              f._.value = '';
2548c3b7501Sstephan            }
2558c3b7501Sstephan            if(this.config.echoToConsole) console.log(text);
2568c3b7501Sstephan            if(this.jqTerm) this.jqTerm.echo(text);
2578c3b7501Sstephan            f._.value += text + "\n";
2588c3b7501Sstephan            if(this.config.autoScrollOutput){
2598c3b7501Sstephan              f._.scrollTop = f._.scrollHeight;
2608c3b7501Sstephan            }
2618c3b7501Sstephan          },
2628c3b7501Sstephan          _msgMap: {},
2638c3b7501Sstephan          /** Adds a worker message handler for messages of the given
2648c3b7501Sstephan              type. */
2658c3b7501Sstephan          addMsgHandler: function f(type,callback){
2668c3b7501Sstephan            if(Array.isArray(type)){
2678c3b7501Sstephan              type.forEach((t)=>this.addMsgHandler(t, callback));
2688c3b7501Sstephan              return this;
2698c3b7501Sstephan            }
2708c3b7501Sstephan            (this._msgMap.hasOwnProperty(type)
2718c3b7501Sstephan             ? this._msgMap[type]
2728c3b7501Sstephan             : (this._msgMap[type] = [])).push(callback);
2738c3b7501Sstephan            return this;
2748c3b7501Sstephan          },
2758c3b7501Sstephan          /** Given a worker message, runs all handlers for msg.type. */
2768c3b7501Sstephan          runMsgHandlers: function(msg){
2778c3b7501Sstephan            const list = (this._msgMap.hasOwnProperty(msg.type)
2788c3b7501Sstephan                          ? this._msgMap[msg.type] : false);
2798c3b7501Sstephan            if(!list){
2808c3b7501Sstephan              console.warn("No handlers found for message type:",msg);
2818c3b7501Sstephan              return false;
2828c3b7501Sstephan            }
2838c3b7501Sstephan            //console.debug("runMsgHandlers",msg);
2848c3b7501Sstephan            list.forEach((f)=>f(msg));
2858c3b7501Sstephan            return true;
2868c3b7501Sstephan          },
2878c3b7501Sstephan          /** Removes all message handlers for the given message type. */
2888c3b7501Sstephan          clearMsgHandlers: function(type){
2898c3b7501Sstephan            delete this._msgMap[type];
2908c3b7501Sstephan            return this;
2918c3b7501Sstephan          },
2928c3b7501Sstephan          /* Posts a message in the form {type, data} to the db worker. Returns this. */
293395012e5Sstephan          wMsg: function(type,data,transferables){
294395012e5Sstephan            this.worker.postMessage({type, data}, transferables || []);
2958c3b7501Sstephan            return this;
2968c3b7501Sstephan          },
2978c3b7501Sstephan          /**
2988c3b7501Sstephan             Prompts for confirmation and, if accepted, deletes
2998c3b7501Sstephan             all content and tables in the (transient) database.
3008c3b7501Sstephan          */
3018c3b7501Sstephan          resetDb: function(){
3028c3b7501Sstephan            if(window.confirm("Really destroy all content and tables "
3038c3b7501Sstephan                              +"in the (transient) db?")){
3048c3b7501Sstephan              this.wMsg('db-reset');
3058c3b7501Sstephan            }
3068c3b7501Sstephan            return this;
3078c3b7501Sstephan          },
3088c3b7501Sstephan          /** Stores this object's config in the browser's storage. */
3098c3b7501Sstephan          storeConfig: function(){
3108c3b7501Sstephan            storage.setJSON(configStorageKey,this.config);
3118c3b7501Sstephan          }
3128c3b7501Sstephan        };
3138c3b7501Sstephan
3148c3b7501Sstephan  if(1){ /* Restore SF.config */
3158c3b7501Sstephan    const storedConfig = storage.getJSON(configStorageKey);
3168c3b7501Sstephan    if(storedConfig){
3178c3b7501Sstephan      /* Copy all properties to SF.config which are currently in
3188c3b7501Sstephan         storedConfig. We don't bother copying any other
3198c3b7501Sstephan         properties: those have been removed from the app in the
3208c3b7501Sstephan         meantime. */
3218c3b7501Sstephan      Object.keys(SF.config).forEach(function(k){
3228c3b7501Sstephan        if(storedConfig.hasOwnProperty(k)){
3238c3b7501Sstephan          SF.config[k] = storedConfig[k];
3248c3b7501Sstephan        }
3258c3b7501Sstephan      });
3268c3b7501Sstephan    }
3278c3b7501Sstephan  }
3288c3b7501Sstephan
3296110a5d0Sstephan  SF.worker = new Worker('fiddle-worker.js'+self.location.search);
3308c3b7501Sstephan  SF.worker.onmessage = (ev)=>SF.runMsgHandlers(ev.data);
3318c3b7501Sstephan  SF.addMsgHandler(['stdout', 'stderr'], (ev)=>SF.echo(ev.data));
3328c3b7501Sstephan
3338c3b7501Sstephan  /* querySelectorAll() proxy */
3348c3b7501Sstephan  const EAll = function(/*[element=document,] cssSelector*/){
3358c3b7501Sstephan    return (arguments.length>1 ? arguments[0] : document)
3368c3b7501Sstephan      .querySelectorAll(arguments[arguments.length-1]);
3378c3b7501Sstephan  };
3388c3b7501Sstephan  /* querySelector() proxy */
3398c3b7501Sstephan  const E = function(/*[element=document,] cssSelector*/){
3408c3b7501Sstephan    return (arguments.length>1 ? arguments[0] : document)
3418c3b7501Sstephan      .querySelector(arguments[arguments.length-1]);
3428c3b7501Sstephan  };
3438c3b7501Sstephan
344*eb97743cSstephan  /** Handles status updates from the Emscripten Module object. */
3458c3b7501Sstephan  SF.addMsgHandler('module', function f(ev){
3468c3b7501Sstephan    ev = ev.data;
3478c3b7501Sstephan    if('status'!==ev.type){
3488c3b7501Sstephan      console.warn("Unexpected module-type message:",ev);
3498c3b7501Sstephan      return;
3508c3b7501Sstephan    }
3518c3b7501Sstephan    if(!f.ui){
3528c3b7501Sstephan      f.ui = {
3538c3b7501Sstephan        status: E('#module-status'),
3548c3b7501Sstephan        progress: E('#module-progress'),
3558c3b7501Sstephan        spinner: E('#module-spinner')
3568c3b7501Sstephan      };
3578c3b7501Sstephan    }
3588c3b7501Sstephan    const msg = ev.data;
3598c3b7501Sstephan    if(f.ui.progres){
3608c3b7501Sstephan      progress.value = msg.step;
3618c3b7501Sstephan      progress.max = msg.step + 1/*we don't know how many steps to expect*/;
3628c3b7501Sstephan    }
3638c3b7501Sstephan    if(1==msg.step){
3648c3b7501Sstephan      f.ui.progress.classList.remove('hidden');
3658c3b7501Sstephan      f.ui.spinner.classList.remove('hidden');
3668c3b7501Sstephan    }
3678c3b7501Sstephan    if(msg.text){
3688c3b7501Sstephan      f.ui.status.classList.remove('hidden');
3698c3b7501Sstephan      f.ui.status.innerText = msg.text;
3708c3b7501Sstephan    }else{
3718c3b7501Sstephan      if(f.ui.progress){
3728c3b7501Sstephan        f.ui.progress.remove();
3738c3b7501Sstephan        f.ui.spinner.remove();
3748c3b7501Sstephan        delete f.ui.progress;
3758c3b7501Sstephan        delete f.ui.spinner;
3768c3b7501Sstephan      }
3778c3b7501Sstephan      f.ui.status.classList.add('hidden');
3788c3b7501Sstephan      /* The module can post messages about fatal problems,
3798c3b7501Sstephan         e.g. an exit() being triggered or assertion failure,
3808c3b7501Sstephan         after the last "load" message has arrived, so
3818c3b7501Sstephan         leave f.ui.status and message listener intact. */
3828c3b7501Sstephan    }
3838c3b7501Sstephan  });
3848c3b7501Sstephan
3858c3b7501Sstephan  /**
3868c3b7501Sstephan     The 'fiddle-ready' event is fired (with no payload) when the
3878c3b7501Sstephan     wasm module has finished loading. Interestingly, that happens
3888c3b7501Sstephan     _before_ the final module:status event */
3898c3b7501Sstephan  SF.addMsgHandler('fiddle-ready', function(){
3908c3b7501Sstephan    SF.clearMsgHandlers('fiddle-ready');
3918c3b7501Sstephan    self.onSFLoaded();
3928c3b7501Sstephan  });
3938c3b7501Sstephan
3948c3b7501Sstephan  /**
3958c3b7501Sstephan     Performs all app initialization which must wait until after the
3968c3b7501Sstephan     worker module is loaded. This function removes itself when it's
3978c3b7501Sstephan     called.
3988c3b7501Sstephan  */
3998c3b7501Sstephan  self.onSFLoaded = function(){
4008c3b7501Sstephan    delete this.onSFLoaded;
4018c3b7501Sstephan    // Unhide all elements which start out hidden
4028c3b7501Sstephan    EAll('.initially-hidden').forEach((e)=>e.classList.remove('initially-hidden'));
4038c3b7501Sstephan    E('#btn-reset').addEventListener('click',()=>SF.resetDb());
4048c3b7501Sstephan    const taInput = E('#input');
4058c3b7501Sstephan    const btnClearIn = E('#btn-clear');
4068c3b7501Sstephan    btnClearIn.addEventListener('click',function(){
4078c3b7501Sstephan      taInput.value = '';
4088c3b7501Sstephan    },false);
4098c3b7501Sstephan    // Ctrl-enter and shift-enter both run the current SQL.
4108c3b7501Sstephan    taInput.addEventListener('keydown',function(ev){
4118c3b7501Sstephan      if((ev.ctrlKey || ev.shiftKey) && 13 === ev.keyCode){
4128c3b7501Sstephan        ev.preventDefault();
4138c3b7501Sstephan        ev.stopPropagation();
4148c3b7501Sstephan        btnShellExec.click();
4158c3b7501Sstephan      }
4168c3b7501Sstephan    }, false);
4178c3b7501Sstephan    const taOutput = E('#output');
4188c3b7501Sstephan    const btnClearOut = E('#btn-clear-output');
4198c3b7501Sstephan    btnClearOut.addEventListener('click',function(){
4208c3b7501Sstephan      taOutput.value = '';
4218c3b7501Sstephan      if(SF.jqTerm) SF.jqTerm.clear();
4228c3b7501Sstephan    },false);
4238c3b7501Sstephan    const btnShellExec = E('#btn-shell-exec');
4248c3b7501Sstephan    btnShellExec.addEventListener('click',function(ev){
4258c3b7501Sstephan      let sql;
4268c3b7501Sstephan      ev.preventDefault();
4278c3b7501Sstephan      if(taInput.selectionStart<taInput.selectionEnd){
4288c3b7501Sstephan        sql = taInput.value.substring(taInput.selectionStart,taInput.selectionEnd).trim();
4298c3b7501Sstephan      }else{
4308c3b7501Sstephan        sql = taInput.value.trim();
4318c3b7501Sstephan      }
4328c3b7501Sstephan      if(sql) SF.dbExec(sql);
4338c3b7501Sstephan    },false);
4348c3b7501Sstephan
4358c3b7501Sstephan    const btnInterrupt = E("#btn-interrupt");
4368c3b7501Sstephan    //btnInterrupt.classList.add('hidden');
4378c3b7501Sstephan    /** To be called immediately before work is sent to the
4388c3b7501Sstephan        worker. Updates some UI elements. The 'working'/'end'
4398c3b7501Sstephan        event will apply the inverse, undoing the bits this
4408c3b7501Sstephan        function does. This impl is not in the 'working'/'start'
4418c3b7501Sstephan        event handler because that event is given to us
4428c3b7501Sstephan        asynchronously _after_ we need to have performed this
4438c3b7501Sstephan        work.
4448c3b7501Sstephan    */
4458c3b7501Sstephan    const preStartWork = function f(){
4468c3b7501Sstephan      if(!f._){
4478c3b7501Sstephan        const title = E('title');
4488c3b7501Sstephan        f._ = {
4498c3b7501Sstephan          btnLabel: btnShellExec.innerText,
4508c3b7501Sstephan          pageTitle: title,
4518c3b7501Sstephan          pageTitleOrig: title.innerText
4528c3b7501Sstephan        };
4538c3b7501Sstephan      }
4548c3b7501Sstephan      f._.pageTitle.innerText = "[working...] "+f._.pageTitleOrig;
4558c3b7501Sstephan      btnShellExec.setAttribute('disabled','disabled');
4568c3b7501Sstephan      btnInterrupt.removeAttribute('disabled','disabled');
4578c3b7501Sstephan    };
4588c3b7501Sstephan
4598c3b7501Sstephan    /* Sends the given text to the db module to evaluate as if it
4608c3b7501Sstephan       had been entered in the sqlite3 CLI shell. If it's null or
46160d9aa7cSstephan       empty, this is a no-op. */
4628c3b7501Sstephan    SF.dbExec = function f(sql){
46360d9aa7cSstephan      if(null!==sql && this.config.autoClearOutput){
4648c3b7501Sstephan        this.echo._clearPending = true;
4658c3b7501Sstephan      }
4668c3b7501Sstephan      preStartWork();
4678c3b7501Sstephan      this.wMsg('shellExec',sql);
4688c3b7501Sstephan    };
4698c3b7501Sstephan
4708c3b7501Sstephan    SF.addMsgHandler('working',function f(ev){
4718c3b7501Sstephan      switch(ev.data){
4728c3b7501Sstephan          case 'start': /* See notes in preStartWork(). */; return;
4738c3b7501Sstephan          case 'end':
4748c3b7501Sstephan            preStartWork._.pageTitle.innerText = preStartWork._.pageTitleOrig;
4758c3b7501Sstephan            btnShellExec.innerText = preStartWork._.btnLabel;
4768c3b7501Sstephan            btnShellExec.removeAttribute('disabled');
4778c3b7501Sstephan            btnInterrupt.setAttribute('disabled','disabled');
4788c3b7501Sstephan            return;
4798c3b7501Sstephan      }
4808c3b7501Sstephan      console.warn("Unhandled 'working' event:",ev.data);
4818c3b7501Sstephan    });
4828c3b7501Sstephan
4838c3b7501Sstephan    /* For each checkbox with data-csstgt, set up a handler which
4848c3b7501Sstephan       toggles the given CSS class on the element matching
4858c3b7501Sstephan       E(data-csstgt). */
4868c3b7501Sstephan    EAll('input[type=checkbox][data-csstgt]')
4878c3b7501Sstephan      .forEach(function(e){
4888c3b7501Sstephan        const tgt = E(e.dataset.csstgt);
4898c3b7501Sstephan        const cssClass = e.dataset.cssclass || 'error';
4908c3b7501Sstephan        e.checked = tgt.classList.contains(cssClass);
4918c3b7501Sstephan        e.addEventListener('change', function(){
4928c3b7501Sstephan          tgt.classList[
4938c3b7501Sstephan            this.checked ? 'add' : 'remove'
4948c3b7501Sstephan          ](cssClass)
4958c3b7501Sstephan        }, false);
4968c3b7501Sstephan      });
4978c3b7501Sstephan    /* For each checkbox with data-config=X, set up a binding to
4988c3b7501Sstephan       SF.config[X]. These must be set up AFTER data-csstgt
4998c3b7501Sstephan       checkboxes so that those two states can be synced properly. */
5008c3b7501Sstephan    EAll('input[type=checkbox][data-config]')
5018c3b7501Sstephan      .forEach(function(e){
5028c3b7501Sstephan        const confVal = !!SF.config[e.dataset.config];
5038c3b7501Sstephan        if(e.checked !== confVal){
5048c3b7501Sstephan          /* Ensure that data-csstgt mappings (if any) get
5058c3b7501Sstephan             synced properly. */
5068c3b7501Sstephan          e.checked = confVal;
5078c3b7501Sstephan          e.dispatchEvent(new Event('change'));
5088c3b7501Sstephan        }
5098c3b7501Sstephan        e.addEventListener('change', function(){
5108c3b7501Sstephan          SF.config[this.dataset.config] = this.checked;
5118c3b7501Sstephan          SF.storeConfig();
5128c3b7501Sstephan        }, false);
5138c3b7501Sstephan      });
5148c3b7501Sstephan    /* For each button with data-cmd=X, map a click handler which
5158c3b7501Sstephan       calls SF.dbExec(X). */
5168c3b7501Sstephan    const cmdClick = function(){SF.dbExec(this.dataset.cmd);};
5178c3b7501Sstephan    EAll('button[data-cmd]').forEach(
5188c3b7501Sstephan      e => e.addEventListener('click', cmdClick, false)
5198c3b7501Sstephan    );
5208c3b7501Sstephan
5218c3b7501Sstephan    btnInterrupt.addEventListener('click',function(){
5228c3b7501Sstephan      SF.wMsg('interrupt');
5238c3b7501Sstephan    });
5248c3b7501Sstephan
5258c3b7501Sstephan    /** Initiate a download of the db. */
5268c3b7501Sstephan    const btnExport = E('#btn-export');
5278c3b7501Sstephan    const eLoadDb = E('#load-db');
5288c3b7501Sstephan    const btnLoadDb = E('#btn-load-db');
5298c3b7501Sstephan    btnLoadDb.addEventListener('click', ()=>eLoadDb.click());
5308c3b7501Sstephan    /**
5318c3b7501Sstephan       Enables (if passed true) or disables all UI elements which
5328c3b7501Sstephan       "might," if timed "just right," interfere with an
5338c3b7501Sstephan       in-progress db import/export/exec operation.
5348c3b7501Sstephan    */
5358c3b7501Sstephan    const enableMutatingElements = function f(enable){
5368c3b7501Sstephan      if(!f._elems){
5378c3b7501Sstephan        f._elems = [
5388c3b7501Sstephan          /* UI elements to disable while import/export are
5398c3b7501Sstephan             running. Normally the export is fast enough
5408c3b7501Sstephan             that this won't matter, but we really don't
5418c3b7501Sstephan             want to be reading (from outside of sqlite) the
5428c3b7501Sstephan             db when the user taps btnShellExec. */
5438c3b7501Sstephan          btnShellExec, btnExport, eLoadDb
5448c3b7501Sstephan        ];
5458c3b7501Sstephan      }
5468c3b7501Sstephan      f._elems.forEach( enable
5478c3b7501Sstephan                        ? (e)=>e.removeAttribute('disabled')
5488c3b7501Sstephan                        : (e)=>e.setAttribute('disabled','disabled') );
5498c3b7501Sstephan    };
5508c3b7501Sstephan    btnExport.addEventListener('click',function(){
5518c3b7501Sstephan      enableMutatingElements(false);
5528c3b7501Sstephan      SF.wMsg('db-export');
5538c3b7501Sstephan    });
5548c3b7501Sstephan    SF.addMsgHandler('db-export', function(ev){
5558c3b7501Sstephan      enableMutatingElements(true);
5568c3b7501Sstephan      ev = ev.data;
5578c3b7501Sstephan      if(ev.error){
5588c3b7501Sstephan        SF.echo("Export failed:",ev.error);
5598c3b7501Sstephan        return;
5608c3b7501Sstephan      }
561395012e5Sstephan      const blob = new Blob([ev.buffer],
562395012e5Sstephan                            {type:"application/x-sqlite3"});
5638c3b7501Sstephan      const a = document.createElement('a');
5648c3b7501Sstephan      document.body.appendChild(a);
5658c3b7501Sstephan      a.href = window.URL.createObjectURL(blob);
5668c3b7501Sstephan      a.download = ev.filename;
5678c3b7501Sstephan      a.addEventListener('click',function(){
5688c3b7501Sstephan        setTimeout(function(){
5698c3b7501Sstephan          SF.echo("Exported (possibly auto-downloaded):",ev.filename);
5708c3b7501Sstephan          window.URL.revokeObjectURL(a.href);
5718c3b7501Sstephan          a.remove();
5728c3b7501Sstephan        },500);
5738c3b7501Sstephan      });
5748c3b7501Sstephan      a.click();
5758c3b7501Sstephan    });
5768c3b7501Sstephan    /**
5778c3b7501Sstephan       Handle load/import of an external db file.
5788c3b7501Sstephan    */
5798c3b7501Sstephan    eLoadDb.addEventListener('change',function(){
5808c3b7501Sstephan      const f = this.files[0];
5818c3b7501Sstephan      const r = new FileReader();
5828c3b7501Sstephan      const status = {loaded: 0, total: 0};
5838c3b7501Sstephan      enableMutatingElements(false);
5848c3b7501Sstephan      r.addEventListener('loadstart', function(){
5858c3b7501Sstephan        SF.echo("Loading",f.name,"...");
5868c3b7501Sstephan      });
5878c3b7501Sstephan      r.addEventListener('progress', function(ev){
5888c3b7501Sstephan        SF.echo("Loading progress:",ev.loaded,"of",ev.total,"bytes.");
5898c3b7501Sstephan      });
5908c3b7501Sstephan      const that = this;
5918c3b7501Sstephan      r.addEventListener('load', function(){
5928c3b7501Sstephan        enableMutatingElements(true);
5938c3b7501Sstephan        SF.echo("Loaded",f.name+". Opening db...");
5948c3b7501Sstephan        SF.wMsg('open',{
5958c3b7501Sstephan          filename: f.name,
5968c3b7501Sstephan          buffer: this.result
597395012e5Sstephan        }, [this.result]);
5988c3b7501Sstephan      });
5998c3b7501Sstephan      r.addEventListener('error',function(){
6008c3b7501Sstephan        enableMutatingElements(true);
6018c3b7501Sstephan        SF.echo("Loading",f.name,"failed for unknown reasons.");
6028c3b7501Sstephan      });
6038c3b7501Sstephan      r.addEventListener('abort',function(){
6048c3b7501Sstephan        enableMutatingElements(true);
6058c3b7501Sstephan        SF.echo("Cancelled loading of",f.name+".");
6068c3b7501Sstephan      });
6078c3b7501Sstephan      r.readAsArrayBuffer(f);
6088c3b7501Sstephan    });
6098c3b7501Sstephan
6108c3b7501Sstephan    EAll('fieldset.collapsible').forEach(function(fs){
6118c3b7501Sstephan      const btnToggle = E(fs,'legend > .fieldset-toggle'),
6128c3b7501Sstephan            content = EAll(fs,':scope > div');
6138c3b7501Sstephan      btnToggle.addEventListener('click', function(){
6148c3b7501Sstephan        fs.classList.toggle('collapsed');
6158c3b7501Sstephan        content.forEach((d)=>d.classList.toggle('hidden'));
6168c3b7501Sstephan      }, false);
6178c3b7501Sstephan    });
6188c3b7501Sstephan
6198c3b7501Sstephan    /**
6208c3b7501Sstephan       Given a DOM element, this routine measures its "effective
6218c3b7501Sstephan       height", which is the bounding top/bottom range of this element
6228c3b7501Sstephan       and all of its children, recursively. For some DOM structure
6238c3b7501Sstephan       cases, a parent may have a reported height of 0 even though
6248c3b7501Sstephan       children have non-0 sizes.
6258c3b7501Sstephan
6268c3b7501Sstephan       Returns 0 if !e or if the element really has no height.
6278c3b7501Sstephan    */
6288c3b7501Sstephan    const effectiveHeight = function f(e){
6298c3b7501Sstephan      if(!e) return 0;
6308c3b7501Sstephan      if(!f.measure){
6318c3b7501Sstephan        f.measure = function callee(e, depth){
6328c3b7501Sstephan          if(!e) return;
6338c3b7501Sstephan          const m = e.getBoundingClientRect();
6348c3b7501Sstephan          if(0===depth){
6358c3b7501Sstephan            callee.top = m.top;
6368c3b7501Sstephan            callee.bottom = m.bottom;
6378c3b7501Sstephan          }else{
6388c3b7501Sstephan            callee.top = m.top ? Math.min(callee.top, m.top) : callee.top;
6398c3b7501Sstephan            callee.bottom = Math.max(callee.bottom, m.bottom);
6408c3b7501Sstephan          }
6418c3b7501Sstephan          Array.prototype.forEach.call(e.children,(e)=>callee(e,depth+1));
6428c3b7501Sstephan          if(0===depth){
6438c3b7501Sstephan            //console.debug("measure() height:",e.className, callee.top, callee.bottom, (callee.bottom - callee.top));
6448c3b7501Sstephan            f.extra += callee.bottom - callee.top;
6458c3b7501Sstephan          }
6468c3b7501Sstephan          return f.extra;
6478c3b7501Sstephan        };
6488c3b7501Sstephan      }
6498c3b7501Sstephan      f.extra = 0;
6508c3b7501Sstephan      f.measure(e,0);
6518c3b7501Sstephan      return f.extra;
6528c3b7501Sstephan    };
6538c3b7501Sstephan
6548c3b7501Sstephan    /**
6558c3b7501Sstephan       Returns a function, that, as long as it continues to be invoked,
6568c3b7501Sstephan       will not be triggered. The function will be called after it stops
6578c3b7501Sstephan       being called for N milliseconds. If `immediate` is passed, call
6588c3b7501Sstephan       the callback immediately and hinder future invocations until at
6598c3b7501Sstephan       least the given time has passed.
6608c3b7501Sstephan
6618c3b7501Sstephan       If passed only 1 argument, or passed a falsy 2nd argument,
6628c3b7501Sstephan       the default wait time set in this function's $defaultDelay
6638c3b7501Sstephan       property is used.
6648c3b7501Sstephan
6658c3b7501Sstephan       Source: underscore.js, by way of https://davidwalsh.name/javascript-debounce-function
6668c3b7501Sstephan    */
6678c3b7501Sstephan    const debounce = function f(func, wait, immediate) {
6688c3b7501Sstephan      var timeout;
6698c3b7501Sstephan      if(!wait) wait = f.$defaultDelay;
6708c3b7501Sstephan      return function() {
6718c3b7501Sstephan        const context = this, args = Array.prototype.slice.call(arguments);
6728c3b7501Sstephan        const later = function() {
6738c3b7501Sstephan          timeout = undefined;
6748c3b7501Sstephan          if(!immediate) func.apply(context, args);
6758c3b7501Sstephan        };
6768c3b7501Sstephan        const callNow = immediate && !timeout;
6778c3b7501Sstephan        clearTimeout(timeout);
6788c3b7501Sstephan        timeout = setTimeout(later, wait);
6798c3b7501Sstephan        if(callNow) func.apply(context, args);
6808c3b7501Sstephan      };
6818c3b7501Sstephan    };
6828c3b7501Sstephan    debounce.$defaultDelay = 500 /*arbitrary*/;
6838c3b7501Sstephan
6848c3b7501Sstephan    const ForceResizeKludge = (function(){
6858c3b7501Sstephan      /* Workaround for Safari mayhem regarding use of vh CSS
6868c3b7501Sstephan         units....  We cannot use vh units to set the main view
6878c3b7501Sstephan         size because Safari chokes on that, so we calculate
6888c3b7501Sstephan         that height here. Larger than ~95% is too big for
6898c3b7501Sstephan         Firefox on Android, causing the input area to move
6908c3b7501Sstephan         off-screen. */
6918c3b7501Sstephan      const appViews = EAll('.app-view');
6928c3b7501Sstephan      const elemsToCount = [
6938c3b7501Sstephan        /* Elements which we need to always count in the
6948c3b7501Sstephan           visible body size. */
6958c3b7501Sstephan        E('body > header'),
6968c3b7501Sstephan        E('body > footer')
6978c3b7501Sstephan      ];
6988c3b7501Sstephan      const resized = function f(){
6998c3b7501Sstephan        if(f.$disabled) return;
7008c3b7501Sstephan        const wh = window.innerHeight;
7018c3b7501Sstephan        var ht;
7028c3b7501Sstephan        var extra = 0;
7038c3b7501Sstephan        elemsToCount.forEach((e)=>e ? extra += effectiveHeight(e) : false);
7048c3b7501Sstephan        ht = wh - extra;
7058c3b7501Sstephan        appViews.forEach(function(e){
7068c3b7501Sstephan          e.style.height =
7078c3b7501Sstephan            e.style.maxHeight = [
7088c3b7501Sstephan              "calc(", (ht>=100 ? ht : 100), "px",
7098c3b7501Sstephan              " - 2em"/*fudge value*/,")"
7108c3b7501Sstephan              /* ^^^^ hypothetically not needed, but both
7118c3b7501Sstephan                 Chrome/FF on Linux will force scrollbars on the
7128c3b7501Sstephan                 body if this value is too small. */
7138c3b7501Sstephan            ].join('');
7148c3b7501Sstephan        });
7158c3b7501Sstephan      };
7168c3b7501Sstephan      resized.$disabled = true/*gets deleted when setup is finished*/;
7178c3b7501Sstephan      window.addEventListener('resize', debounce(resized, 250), false);
7188c3b7501Sstephan      return resized;
7198c3b7501Sstephan    })();
7208c3b7501Sstephan
7218c3b7501Sstephan    /** Set up a selection list of examples */
7228c3b7501Sstephan    (function(){
7238c3b7501Sstephan      const xElem = E('#select-examples');
7248c3b7501Sstephan      const examples = [
725*eb97743cSstephan        {name: "Help", sql: [
726*eb97743cSstephan          "-- ================================================\n",
727*eb97743cSstephan          "-- Use ctrl-enter or shift-enter to execute sqlite3\n",
728*eb97743cSstephan          "-- shell commands and SQL.\n",
729*eb97743cSstephan          "-- If a subset of the text is currently selected,\n",
730*eb97743cSstephan          "-- only that part is executed.\n",
731*eb97743cSstephan          "-- ================================================\n",
732*eb97743cSstephan          ".help\n"
733*eb97743cSstephan        ]},
734429899ddSstephan              //{name: "Timer on", sql: ".timer on"},
735429899ddSstephan              // ^^^ re-enable if emscripten re-enables getrusage()
736*eb97743cSstephan        {name: "Setup table T", sql:[
737*eb97743cSstephan          ".nullvalue NULL\n",
738*eb97743cSstephan          "CREATE TABLE t(a,b);\n",
739*eb97743cSstephan          "INSERT INTO t(a,b) VALUES('abc',123),('def',456),(NULL,789),('ghi',012);\n",
740*eb97743cSstephan          "SELECT * FROM t;\n"
741*eb97743cSstephan        ]},
7428c3b7501Sstephan        {name: "Table list", sql: ".tables"},
7438c3b7501Sstephan        {name: "Box Mode", sql: ".mode box"},
7448c3b7501Sstephan        {name: "JSON Mode", sql: ".mode json"},
745*eb97743cSstephan        {name: "Mandlebrot", sql:[
746*eb97743cSstephan          "WITH RECURSIVE",
747*eb97743cSstephan          "  xaxis(x) AS (VALUES(-2.0) UNION ALL SELECT x+0.05 FROM xaxis WHERE x<1.2),\n",
748*eb97743cSstephan          "  yaxis(y) AS (VALUES(-1.0) UNION ALL SELECT y+0.1 FROM yaxis WHERE y<1.0),\n",
749*eb97743cSstephan          "  m(iter, cx, cy, x, y) AS (\n",
750*eb97743cSstephan          "    SELECT 0, x, y, 0.0, 0.0 FROM xaxis, yaxis\n",
751*eb97743cSstephan          "    UNION ALL\n",
752*eb97743cSstephan          "    SELECT iter+1, cx, cy, x*x-y*y + cx, 2.0*x*y + cy FROM m \n",
753*eb97743cSstephan          "     WHERE (x*x + y*y) < 4.0 AND iter<28\n",
754*eb97743cSstephan          "  ),\n",
755*eb97743cSstephan          "  m2(iter, cx, cy) AS (\n",
756*eb97743cSstephan          "    SELECT max(iter), cx, cy FROM m GROUP BY cx, cy\n",
757*eb97743cSstephan          "  ),\n",
758*eb97743cSstephan          "  a(t) AS (\n",
759*eb97743cSstephan          "    SELECT group_concat( substr(' .+*#', 1+min(iter/7,4), 1), '') \n",
760*eb97743cSstephan          "    FROM m2 GROUP BY cy\n",
761*eb97743cSstephan          "  )\n",
762*eb97743cSstephan          "SELECT group_concat(rtrim(t),x'0a') as Mandelbrot FROM a;\n",
763*eb97743cSstephan        ]}
7648c3b7501Sstephan      ];
7658c3b7501Sstephan      const newOpt = function(lbl,val){
7668c3b7501Sstephan        const o = document.createElement('option');
767*eb97743cSstephan        if(Array.isArray(val)) val = val.join('');
7688c3b7501Sstephan        o.value = val;
7698c3b7501Sstephan        if(!val) o.setAttribute('disabled',true);
7708c3b7501Sstephan        o.appendChild(document.createTextNode(lbl));
7718c3b7501Sstephan        xElem.appendChild(o);
7728c3b7501Sstephan      };
7738c3b7501Sstephan      newOpt("Examples (replaces input!)");
7748c3b7501Sstephan      examples.forEach((o)=>newOpt(o.name, o.sql));
7758c3b7501Sstephan      //xElem.setAttribute('disabled',true);
7768c3b7501Sstephan      xElem.selectedIndex = 0;
7778c3b7501Sstephan      xElem.addEventListener('change', function(){
7788c3b7501Sstephan        taInput.value = '-- ' +
7798c3b7501Sstephan          this.selectedOptions[0].innerText +
7808c3b7501Sstephan          '\n' + this.value;
7818c3b7501Sstephan        SF.dbExec(this.value);
7828c3b7501Sstephan      });
7838c3b7501Sstephan    })()/* example queries */;
7848c3b7501Sstephan
785a579f440Sstephan    //SF.echo(null/*clear any output generated by the init process*/);
7868c3b7501Sstephan    if(window.jQuery && window.jQuery.terminal){
7878c3b7501Sstephan      /* Set up the terminal-style view... */
7888c3b7501Sstephan      const eTerm = window.jQuery('#view-terminal').empty();
7898c3b7501Sstephan      SF.jqTerm = eTerm.terminal(SF.dbExec.bind(SF),{
7908c3b7501Sstephan        prompt: 'sqlite> ',
7918c3b7501Sstephan        greetings: false /* note that the docs incorrectly call this 'greeting' */
7928c3b7501Sstephan      });
7938c3b7501Sstephan      /* Set up a button to toggle the views... */
7948c3b7501Sstephan      const head = E('header#titlebar');
7958c3b7501Sstephan      const btnToggleView = document.createElement('button');
7968c3b7501Sstephan      btnToggleView.appendChild(document.createTextNode("Toggle View"));
7978c3b7501Sstephan      head.appendChild(btnToggleView);
7988c3b7501Sstephan      btnToggleView.addEventListener('click',function f(){
7998c3b7501Sstephan        EAll('.app-view').forEach(e=>e.classList.toggle('hidden'));
8008c3b7501Sstephan        if(document.body.classList.toggle('terminal-mode')){
8018c3b7501Sstephan          ForceResizeKludge();
8028c3b7501Sstephan        }
8038c3b7501Sstephan      }, false);
8048c3b7501Sstephan      btnToggleView.click()/*default to terminal view*/;
8058c3b7501Sstephan    }
8068c3b7501Sstephan    SF.echo('This experimental app is provided in the hope that it',
8078c3b7501Sstephan            'may prove interesting or useful but is not an officially',
8088c3b7501Sstephan            'supported deliverable of the sqlite project. It is subject to',
8098c3b7501Sstephan            'any number of changes or outright removal at any time.\n');
810395012e5Sstephan    const urlParams = new URL(self.location.href).searchParams;
811395012e5Sstephan    SF.dbExec(urlParams.get('sql') || null);
8128c3b7501Sstephan    delete ForceResizeKludge.$disabled;
8138c3b7501Sstephan    ForceResizeKludge();
8148c3b7501Sstephan  }/*onSFLoaded()*/;
8158c3b7501Sstephan})();
816