diff options
Diffstat (limited to 'ext/wasm/fiddle')
-rw-r--r-- | ext/wasm/fiddle/fiddle-worker.js | 9 | ||||
-rw-r--r-- | ext/wasm/fiddle/fiddle.js | 298 | ||||
-rw-r--r-- | ext/wasm/fiddle/index.html | 186 |
3 files changed, 324 insertions, 169 deletions
diff --git a/ext/wasm/fiddle/fiddle-worker.js b/ext/wasm/fiddle/fiddle-worker.js index 27d915eb2..9c6cddb0f 100644 --- a/ext/wasm/fiddle/fiddle-worker.js +++ b/ext/wasm/fiddle/fiddle-worker.js @@ -163,9 +163,11 @@ fiddleModule.isDead = true; return false; } - stdout("SQLite version", capi.sqlite3_libversion(), - capi.sqlite3_sourceid().substr(0,19)); - stdout('Welcome to the "fiddle" shell.'); + wMsg('sqlite-version', { + lib: capi.sqlite3_libversion(), + srcId: capi.sqlite3_sourceid() + }); + stdout('Welcome to the "fiddle" shell. Tap the About button for more info.'); if(capi.sqlite3_vfs_find("opfs")){ stdout("\nOPFS is available. To open a persistent db, use:\n\n", " .open file:name?vfs=opfs\n\nbut note that some", @@ -281,6 +283,7 @@ stderr("'open' expects {buffer:Uint8Array} containing an uploaded db."); return; } + buffer.set([1,1], 18)/*force db out of WAL mode*/; const fn = ( opt.filename ? opt.filename.split(/[/\\]/).pop().replace('"','_') diff --git a/ext/wasm/fiddle/fiddle.js b/ext/wasm/fiddle/fiddle.js index f0a89f25d..877a87772 100644 --- a/ext/wasm/fiddle/fiddle.js +++ b/ext/wasm/fiddle/fiddle.js @@ -329,6 +329,21 @@ SF.worker = new Worker('fiddle-worker.js'+self.location.search); SF.worker.onmessage = (ev)=>SF.runMsgHandlers(ev.data); SF.addMsgHandler(['stdout', 'stderr'], (ev)=>SF.echo(ev.data)); + SF.addMsgHandler('sqlite-version', (ev)=>{ + const v = ev.data; + const a = E('#sqlite-version-link'); + const li = v.srcId.split(' ')/*DATE TIME HASH*/; + a.setAttribute('href', + //'https://sqlite.org/src/timeline/?c='+li[2].substr(0,20) + 'https://sqlite.org/src/info/'+li[2].substr(0,20) + ); + a.setAttribute('target', '_blank'); + a.innerText = [ + v.lib, + v.srcId.substr(0,34) + ].join(' '); + SF.echo("SQLite version",a.innerText); + }); /* querySelectorAll() proxy */ const EAll = function(/*[element=document,] cssSelector*/){ @@ -391,6 +406,143 @@ self.onSFLoaded(); }); + SF.e ={ + about: E('#view-about'), + split: E('#view-split'), + terminal: E('#view-terminal'), + hideInTerminal: EAll('.hide-in-terminal' + /* Elements to hide when in terminal mode */) + }; + SF.eViews = EAll('.app-view'); + SF.setMainView = function(eMain){ + if( SF.e.main === eMain ) return; + SF.eViews.forEach((e)=>{ + if( e===eMain ) e.classList.remove('hidden'); + else e.classList.add('hidden'); + }); + SF.e.hideInTerminal.forEach(e=>{ + if( eMain === SF.e.terminal ) e.classList.add('hidden'); + else e.classList.remove('hidden'); + }); + SF.e.main = eMain; + SF.ForceResizeKludge(); + }; + + /** Toggle the "About" view on and off. */ + SF.toggleAbout = function(){ + if( SF.e.about.classList.contains('hidden') ){ + SF.e.about.$returnTo = SF.e.main; + SF.setMainView( SF.e.about ); + }else{ + const e = SF.e.about.$returnTo; + delete SF.e.about.$returnTo; + SF.setMainView( e ); + } + }; + + /** + Given a DOM element, this routine measures its "effective + height", which is the bounding top/bottom range of this element + and all of its children, recursively. For some DOM structure + cases, a parent may have a reported height of 0 even though + children have non-0 sizes. + + Returns 0 if !e or if the element really has no height. + */ + const effectiveHeight = function f(e){ + if(!e) return 0; + if(!f.measure){ + f.measure = function callee(e, depth){ + if(!e) return; + const m = e.getBoundingClientRect(); + if(0===depth){ + callee.top = m.top; + callee.bottom = m.bottom; + }else{ + callee.top = m.top ? Math.min(callee.top, m.top) : callee.top; + callee.bottom = Math.max(callee.bottom, m.bottom); + } + Array.prototype.forEach.call(e.children,(e)=>callee(e,depth+1)); + if(0===depth){ + //console.debug("measure() height:",e.className, callee.top, callee.bottom, (callee.bottom - callee.top)); + f.extra += callee.bottom - callee.top; + } + return f.extra; + }; + } + f.extra = 0; + f.measure(e,0); + return f.extra; + }; + + /** + Returns a function, that, as long as it continues to be invoked, + will not be triggered. The function will be called after it stops + being called for N milliseconds. If `immediate` is passed, call + the callback immediately and hinder future invocations until at + least the given time has passed. + + If passed only 1 argument, or passed a falsy 2nd argument, + the default wait time set in this function's $defaultDelay + property is used. + + Source: underscore.js, by way of https://davidwalsh.name/javascript-debounce-function + */ + const debounce = function f(func, wait, immediate) { + var timeout; + if(!wait) wait = f.$defaultDelay; + return function() { + const context = this, args = Array.prototype.slice.call(arguments); + const later = function() { + timeout = undefined; + if(!immediate) func.apply(context, args); + }; + const callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if(callNow) func.apply(context, args); + }; + }; + debounce.$defaultDelay = 500 /*arbitrary*/; + + SF.ForceResizeKludge = (function(){ + /* Workaround for Safari mayhem regarding use of vh CSS + units.... We cannot use vh units to set the main view + size because Safari chokes on that, so we calculate + that height here. Larger than ~95% is too big for + Firefox on Android, causing the input area to move + off-screen. */ + const eVisibles = EAll('.app-view'); + const elemsToCount = [ + /* Elements which we need to always count in the + visible body size. */ + E('body > header'), + E('body > footer'), + E('body > fieldset.options') + ]; + const resized = function f(){ + if(f.$disabled) return; + const wh = window.innerHeight; + var ht; + var extra = 0; + elemsToCount.forEach((e)=>e ? extra += effectiveHeight(e) : false); + ht = wh - extra; + eVisibles.forEach(function(e){ + e.style.height = + e.style.maxHeight = [ + "calc(", (ht>=100 ? ht : 100), "px", + " - 2em"/*fudge value*/,")" + /* ^^^^ hypothetically not needed, but both + Chrome/FF on Linux will force scrollbars on the + body if this value is too small. */ + ].join(''); + }); + }; + resized.$disabled = true/*gets deleted when setup is finished*/; + window.addEventListener('resize', debounce(resized, 250), false); + return resized; + })(); + /** Performs all app initialization which must wait until after the worker module is loaded. This function removes itself when it's @@ -398,9 +550,19 @@ */ self.onSFLoaded = function(){ delete this.onSFLoaded; + // Unhide all elements which start out hidden EAll('.initially-hidden').forEach((e)=>e.classList.remove('initially-hidden')); + SF.e.main = EAll('.app-view:not(.hidden)')[0] + /** The main view widget. Initially the first non-hidden + .app-view element. */; + if( (new URL(self.location.href).searchParams).has('about') ){ + SF.toggleAbout() /* for use while editing the About page */; + } E('#btn-reset').addEventListener('click',()=>SF.resetDb()); + EAll('#btn-about, #btn-about-close').forEach((e)=>{ + e.addEventListener('click',()=>SF.toggleAbout()) + }); const taInput = E('#input'); const btnClearIn = E('#btn-clear'); const selectExamples = E('#select-examples'); @@ -618,108 +780,6 @@ }, false); }); - /** - Given a DOM element, this routine measures its "effective - height", which is the bounding top/bottom range of this element - and all of its children, recursively. For some DOM structure - cases, a parent may have a reported height of 0 even though - children have non-0 sizes. - - Returns 0 if !e or if the element really has no height. - */ - const effectiveHeight = function f(e){ - if(!e) return 0; - if(!f.measure){ - f.measure = function callee(e, depth){ - if(!e) return; - const m = e.getBoundingClientRect(); - if(0===depth){ - callee.top = m.top; - callee.bottom = m.bottom; - }else{ - callee.top = m.top ? Math.min(callee.top, m.top) : callee.top; - callee.bottom = Math.max(callee.bottom, m.bottom); - } - Array.prototype.forEach.call(e.children,(e)=>callee(e,depth+1)); - if(0===depth){ - //console.debug("measure() height:",e.className, callee.top, callee.bottom, (callee.bottom - callee.top)); - f.extra += callee.bottom - callee.top; - } - return f.extra; - }; - } - f.extra = 0; - f.measure(e,0); - return f.extra; - }; - - /** - Returns a function, that, as long as it continues to be invoked, - will not be triggered. The function will be called after it stops - being called for N milliseconds. If `immediate` is passed, call - the callback immediately and hinder future invocations until at - least the given time has passed. - - If passed only 1 argument, or passed a falsy 2nd argument, - the default wait time set in this function's $defaultDelay - property is used. - - Source: underscore.js, by way of https://davidwalsh.name/javascript-debounce-function - */ - const debounce = function f(func, wait, immediate) { - var timeout; - if(!wait) wait = f.$defaultDelay; - return function() { - const context = this, args = Array.prototype.slice.call(arguments); - const later = function() { - timeout = undefined; - if(!immediate) func.apply(context, args); - }; - const callNow = immediate && !timeout; - clearTimeout(timeout); - timeout = setTimeout(later, wait); - if(callNow) func.apply(context, args); - }; - }; - debounce.$defaultDelay = 500 /*arbitrary*/; - - const ForceResizeKludge = (function(){ - /* Workaround for Safari mayhem regarding use of vh CSS - units.... We cannot use vh units to set the main view - size because Safari chokes on that, so we calculate - that height here. Larger than ~95% is too big for - Firefox on Android, causing the input area to move - off-screen. */ - const appViews = EAll('.app-view'); - const elemsToCount = [ - /* Elements which we need to always count in the - visible body size. */ - E('body > header'), - E('body > footer') - ]; - const resized = function f(){ - if(f.$disabled) return; - const wh = window.innerHeight; - var ht; - var extra = 0; - elemsToCount.forEach((e)=>e ? extra += effectiveHeight(e) : false); - ht = wh - extra; - appViews.forEach(function(e){ - e.style.height = - e.style.maxHeight = [ - "calc(", (ht>=100 ? ht : 100), "px", - " - 2em"/*fudge value*/,")" - /* ^^^^ hypothetically not needed, but both - Chrome/FF on Linux will force scrollbars on the - body if this value is too small. */ - ].join(''); - }); - }; - resized.$disabled = true/*gets deleted when setup is finished*/; - window.addEventListener('resize', debounce(resized, 250), false); - return resized; - })(); - /** Set up a selection list of examples */ (function(){ const xElem = E('#select-examples'); @@ -790,33 +850,35 @@ })()/* example queries */; //SF.echo(null/*clear any output generated by the init process*/); - if(window.jQuery && window.jQuery.terminal){ + if(window.jQuery && window.jQuery.terminal && SF.e.terminal){ /* Set up the terminal-style view... */ - const eTerm = window.jQuery('#view-terminal').empty(); - SF.jqTerm = eTerm.terminal(SF.dbExec.bind(SF),{ + const jqeTerm = window.jQuery(SF.e.terminal).empty(); + SF.jqTerm = jqeTerm.terminal(SF.dbExec.bind(SF),{ prompt: 'sqlite> ', greetings: false /* note that the docs incorrectly call this 'greeting' */ }); + EAll('.unhide-if-terminal-available').forEach(e=>{ + e.classList.remove('hidden'); + }); + EAll('.remove-if-terminal-available').forEach(e=>{ + e.parentElement.removeChild(e); + }); /* Set up a button to toggle the views... */ - const head = E('header#titlebar'); + const ePlaceholder = E('#terminal-button-placeholder'); + ePlaceholder.classList.add('labeled-input'); + ePlaceholder.classList.remove('hidden'); const btnToggleView = document.createElement('button'); - btnToggleView.appendChild(document.createTextNode("Toggle View")); - head.appendChild(btnToggleView); + btnToggleView.innerText = "Toggle view"; + ePlaceholder.appendChild( btnToggleView ); btnToggleView.addEventListener('click',function f(){ - EAll('.app-view').forEach(e=>e.classList.toggle('hidden')); - if(document.body.classList.toggle('terminal-mode')){ - ForceResizeKludge(); - } + const terminalOn = document.body.classList.toggle('terminal-mode'); + SF.setMainView( terminalOn ? SF.e.terminal : SF.e.split ); }, false); btnToggleView.click()/*default to terminal view*/; } - SF.echo('This experimental app is provided in the hope that it', - 'may prove interesting or useful but is not an officially', - 'supported deliverable of the sqlite project. It is subject to', - 'any number of changes or outright removal at any time.\n'); const urlParams = new URL(self.location.href).searchParams; SF.dbExec(urlParams.get('sql') || null); - delete ForceResizeKludge.$disabled; - ForceResizeKludge(); + delete SF.ForceResizeKludge.$disabled; + SF.ForceResizeKludge(); }/*onSFLoaded()*/; })(); diff --git a/ext/wasm/fiddle/index.html b/ext/wasm/fiddle/index.html index ca6788ef0..7f79b754b 100644 --- a/ext/wasm/fiddle/index.html +++ b/ext/wasm/fiddle/index.html @@ -5,16 +5,27 @@ <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>SQLite3 Fiddle</title> <link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon"> - <!-- to add a toggleable terminal-style view, uncomment the following - two lines and ensure that these files are on the web server. --> + <!-- + To add a terminal-style view using jquery.terminal[^1], + uncomment the following two HTML lines and ensure that these + files are on the web server. + + jquery-bundle.min.js is a concatenation of jquery.min.js from + [^2] and jquery.terminal.min.js from [^1]. + jquery.terminal.min.css is from [^1]. + + [^1]: https://github.com/jcubic/jquery.terminal + [^2]: https://jquery.com + --> <!--script src="jqterm/jqterm-bundle.min.js"></script> - <link rel="stylesheet" href="jqterm/jquery.terminal.min.css"/--> + <link rel="stylesheet" href="jqterm/jquery.terminal.min.css"--> <style> /* The following styles are for app-level use. */ :root { --sqlite-blue: #044a64; - --textarea-color1: #044a64; + --textarea-color1: #000 /*044a64 is nice too*/; --textarea-color2: white; + --size: 1.25 /* used by jqterm to calculate font size and the default is too tiny.*/; } textarea { font-family: monospace; @@ -170,6 +181,17 @@ display: flex; flex-direction: column-reverse; } + #view-about { + flex: auto; + overflow: auto; + } + #view-about h1:first-child { + display: flex; + } + #view-about h1:first-child > button { + align-self: center; + margin-left: 1em; + } /* emcscript-related styling, used during the module load/intialization processes... */ .emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; } @@ -200,8 +222,10 @@ <body> <header id='titlebar'> <span>SQLite3 Fiddle</span> - <span class='powered-by'>Powered by - <a href='https://sqlite.org'>SQLite3</a></span> + <span id='titlebar-buttons'> + <span class='powered-by'>Powered by + <a href='https://sqlite.org'>SQLite3</a></span> + </span> </header> <!-- emscripten bits --> <figure id="module-spinner"> @@ -215,7 +239,7 @@ </figure> <div class="emscripten" id="module-status">Downloading...</div> <div class="emscripten"> - <progress value="0" max="100" id="module-progress" hidden='1'></progress> + <progress value="0" max="100" id="module-progress" hidden='1'></progress> </div><!-- /emscripten bits --> <div id='view-terminal' class='app-view hidden initially-hidden'> @@ -224,45 +248,6 @@ </div> <div id='view-split' class='app-view initially-hidden'> - <fieldset class='options collapsible'> - <legend><button class='fieldset-toggle'>Options</button></legend> - <div class=''> - <span class='labeled-input'> - <input type='checkbox' id='opt-cb-sbs' - data-csstgt='#main-wrapper' - data-cssclass='side-by-side' - data-config='sideBySide'> - <label for='opt-cb-sbs'>Side-by-side</label> - </span> - <span class='labeled-input'> - <input type='checkbox' id='opt-cb-swapio' - data-csstgt='#main-wrapper' - data-cssclass='swapio' - data-config='swapInOut'> - <label for='opt-cb-swapio'>Swap in/out</label> - </span> - <span class='labeled-input'> - <input type='checkbox' id='opt-cb-autoscroll' - data-config='autoScrollOutput'> - <label for='opt-cb-autoscroll'>Auto-scroll output</label> - </span> - <span class='labeled-input'> - <input type='checkbox' id='opt-cb-autoclear' - data-config='autoClearOutput'> - <label for='opt-cb-autoclear'>Auto-clear output</label> - </span> - <span class='labeled-input'> - <input type='file' id='load-db' class='hidden'/> - <button id='btn-load-db'>Load DB...</button> - </span> - <span class='labeled-input'> - <button id='btn-export'>Download DB</button> - </span> - <span class='labeled-input'> - <button id='btn-reset'>Reset DB</button> - </span> - </div> - </fieldset><!-- .options --> <div id='main-wrapper' class=''> <fieldset class='zone-wrapper input'> <legend><div class='button-bar'> @@ -295,8 +280,113 @@ <div><textarea id="output" readonly placeholder="Shell output."></textarea></div> </fieldset> - </div> + </div><!-- #main-wrapper --> </div> <!-- #view-split --> - <script src="fiddle.js"></script> + +<div class='hidden app-view' id='view-about'> + <h1>About SQLite Fiddle <button id='btn-about-close'>close</button></h1> + + <p>Fiddle is a JavaScript application wrapping a <a href='https://webassembly.org'>WebAssembly</a> + build of <a href="https://sqlite.org/cli.html">the SQLite CLI shell</a>, slightly + modified to account for browser-based user input. Aside from the different layout, + it works just like the CLI shell. This copy was built with SQLite version + <a id='sqlite-version-link'></a>. + </p> + + <p>This app is provided in the hope that it may prove interesting or useful + but it is not an officially-supported deliverable of the SQLite project. + It is subject to any number of changes or outright removal at any time. + That said, for as long as it's online we do respond to support requests + in <a href="https://sqlite.org/forum">the SQLite forum</a>. + </p> + + <p>This app runs on your device. After loading, it does not interact + with the remote server at all. Similarly, this app does not use any + HTTP cookies.</p> + + <p>Fiddle databases are transient in-memory databases unless they + specifically use a persistent storage option (if available, help + text in the SQL result output area will indicate how to use + persistent storage when this app starts up). + </p> + + <h1>Usage Summary</h1> + + <ul> + <li class='hidden unhide-if-terminal-available'>In "terminal + mode" it accepts input just like the CLI shell does.</li> + <li>In split-view mode: + <ul> + <li>Input can be executed with either the Run + button or tapping one of Ctrl-enter or Shift-enter from within + the text input field. If a portion of the input field is + selected, only that portion will be run. + </li> + <li>The various toggle checkboxes can be used to tweak the layout + and behaviors. Those toggles are persistent if the JS environment + allows it. + </li> + </ul> + </li> + <li class='remove-if-terminal-available'>"Terminal mode" is + not available in this deployment. + </li> + <li>Databases can be imported and exported using the buttons in + the Options toolbar. No specific limit for imported database + sizes is imposed, but large databases may cause it to fail with + an out-of-memory error.</li> + <!--li></li--> + </ul> + +</div><!-- #view-about --> + +<fieldset class='options'> + <legend>Options</legend> + <div class=''> + <span class='labeled-input'> + <input type='file' id='load-db' class='hidden'/> + <button id='btn-load-db'>Load DB...</button> + </span> + <span class='labeled-input'> + <button id='btn-export'>Download DB</button> + </span> + <span class='labeled-input'> + <button id='btn-reset'>Reset DB</button> + </span> + <span id='terminal-button-placeholder' class='hidden'></span> + <span class='labeled-input'> + <button id='btn-about'>About...</button> + </span> + <span class='labeled-input hide-in-terminal'> + <input type='checkbox' id='opt-cb-sbs' + data-csstgt='#main-wrapper' + data-cssclass='side-by-side' + data-config='sideBySide' + > + <label for='opt-cb-sbs'>Side-by-side</label> + </span> + <span class='labeled-input hide-in-terminal'> + <input type='checkbox' id='opt-cb-swapio' + data-csstgt='#main-wrapper' + data-cssclass='swapio' + data-config='swapInOut' + > + <label for='opt-cb-swapio'>Swap in/out</label> + </span> + <span class='labeled-input hide-in-terminal'> + <input type='checkbox' id='opt-cb-autoscroll' + data-config='autoScrollOutput' + > + <label for='opt-cb-autoscroll'>Auto-scroll output</label> + </span> + <span class='labeled-input hide-in-terminal'> + <input type='checkbox' id='opt-cb-autoclear' + data-config='autoClearOutput'> + <label for='opt-cb-autoclear'>Auto-clear output</label> + </span> + </div> +</fieldset><!-- .options --> + +<script src="fiddle.js"></script> </body> </html> |