aboutsummaryrefslogtreecommitdiff
path: root/ext/wasm/fiddle
diff options
context:
space:
mode:
Diffstat (limited to 'ext/wasm/fiddle')
-rw-r--r--ext/wasm/fiddle/fiddle-worker.js9
-rw-r--r--ext/wasm/fiddle/fiddle.js298
-rw-r--r--ext/wasm/fiddle/index.html186
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>