Files
sqlite/ext/fiddle/fiddle.in.html
T
stephan b8a2b20509 Added an option to auto-clear the output area before submitting the input. Experimentally inverted the colors in the input/output areas.
FossilOrigin-Name: 1a1e4e7fdbd0ec61218c3a311164086316d825181f3fc1c1ec235b63488746ef
2022-05-19 09:22:16 +00:00

349 lines
14 KiB
HTML

<!doctype html>
<html lang="en-us">
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>sqlite3 fiddle</title>
<style>
.emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; }
textarea {
font-family: monospace;
flex: 1 1 auto;
}
div.emscripten { text-align: center; }
div.emscripten_border { border: 1px solid black; }
.spinner {
height: 50px;
width: 50px;
margin: 0px auto;
animation: rotation 0.8s linear infinite;
border-left: 10px solid rgb(0,150,240);
border-right: 10px solid rgb(0,150,240);
border-bottom: 10px solid rgb(0,150,240);
border-top: 10px solid rgb(100,0,200);
border-radius: 100%;
background-color: rgb(200,100,250);
}
@keyframes rotation {
from {transform: rotate(0deg);}
to {transform: rotate(360deg);}
}
header {
font-size: 130%;
font-weight: bold;
}
#main-wrapper {
display: flex;
flex-direction: column;
}
#main-wrapper.side-by-side {
flex-direction: row;
}
.ta-wrapper{
display: flex;
flex-direction: column;
align-items: stretch;
flex: 1 1 auto;
margin: 0.25em;
}
.ta-wrapper textarea {
font-size: 110%;
filter: invert(100%);
}
.button-bar {
display: flex;
justify-content: center;
}
.button-bar button {
margin: 0.25em 1em;
}
label[for] {
cursor: pointer;
}
fieldset {
border-radius: 0.5em;
}
.error {
color: red;
background-color: yellow;
}
.hidden {
position: absolute !important;
opacity: 0 !important;
pointer-events: none !important;
display: none !important;
}
fieldset.options {
font-size: 75%;
}
fieldset > legend {
padding: 0 0.5em;
}
span.labeled-input {
padding: 0.25em;
margin: 0.25em 0.5em;
border: 1px inset;
border-radius: 0.25em;
white-space: nowrap;
}
#notes-caveats {
border-top: 1px dotted;
padding-top: 0.25em;
margin-top: 0.5em;
}
</style>
</head>
<body>
<header>sqlite3 fiddle</header>
<figure style="overflow:visible;" id="spinner"><div class="spinner"></div><center style="margin-top:0.5em"><strong>emscripten</strong></center></figure>
<div class="emscripten" id="status">Downloading...</div>
<div class="emscripten">
<progress value="0" max="100" id="progress" hidden='1'></progress>
</div>
<fieldset class='options'>
<legend>Options</legend>
<div class=''>
<span class='labeled-input'>
<input type='checkbox' id='opt-cb-sbs'>
<label for='opt-cb-sbs'>Side-by-side</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>
</div>
</fieldset>
<div id='main-wrapper'>
<div class='ta-wrapper'>
<textarea id="input" rows="8"
placeholder="Shell input. Ctrl-enter/shift-enter runs it.">
-- Use ctrl-enter or shift-enter to execute SQL
.nullvalue NULL
.mode box
CREATE TABLE t(a,b);
INSERT INTO t(a,b) VALUES('abc',123),('def',456),(NULL,789),('ghi',012);
SELECT * FROM t;</textarea>
<div class='button-bar'>
<button id='btn-run' disabled>Run</button>
<button id='btn-clear' disabled>Clear</button>
<button data-cmd='.help' disabled>Help</button>
</div>
</div>
<div class='ta-wrapper'>
<textarea id="output" rows="18" readonly
placeholder="Shell output."></textarea>
<div class='button-bar'>
<button id='btn-clear-output' disabled>Clear</button>
</div>
</div>
</div>
<div id='notes-caveats'>
<header>
Notes and Caveats
<button id='btn-notes-caveats'>Remove</button>
</header>
<p>
This JavaScript application runs a C application which has been
compiled into WASM (Web Assembly). As such, it has certain
limitations. Those include, but are not limited to:
</p>
<ul>
<li>It <strong>cannot recover after a call to
<code>exit()</code></strong>. If the native code triggers
an exit, reloading the page is the only way to restart
the application. (Making the app restartable without reloading
the page would require significant surgery in the C code.)
</li>
<li>It <strong>cannot perform any file I/O</strong>, and running
any command which attempts to do so might trigger an
<code>exit()</code>.
</li>
<li>A number of dot-commands available in the CLI shell are
explicitly removed from this version of the shell.
</li>
</ul>
</div><!-- #notes-caveats -->
<script type='text/javascript'>
(function(){
/* An object for propagating certain config state between our
JS code and the WASM module. */
const config = {
/* If true, the Module.print() impl will auto-scroll
the output widget to the bottom when it receives output,
else it won't. */
autoScrollOutput: true,
/* If true, the output area will be cleared before each
command is run, else it will not. */
autoClearOutput: false
};
/**
Callback for the emscripten module init process, gets
passed the module object after all parts of the module
have been loaded and initialized.
*/
const doAppSetup = function(Module) {
const taInput = document.querySelector('#input');
const btnClearIn = document.querySelector('#btn-clear');
document.querySelectorAll('button').forEach(function(e){
e.removeAttribute('disabled');
});
btnClearIn.addEventListener('click',function(){
taInput.value = '';
},false);
// Ctrl-enter and shift-enter both run the current SQL.
taInput.addEventListener('keydown',function(ev){
if((ev.ctrlKey || ev.shiftKey) && 13 === ev.keyCode){
ev.preventDefault();
ev.stopPropagation();
btnRun.click();
}
}, false);
const taOutput = document.querySelector('#output');
const btnClearOut = document.querySelector('#btn-clear-output');
btnClearOut.addEventListener('click',function(){
taOutput.value = '';
},false);
/* Sends the given text to the shell. If it's null or empty, this
is a no-op except that the very first call will initialize the
db and output an informational header. */
const doExec = function f(sql){
if(!f._) f._ = Module.cwrap('fiddle_exec', null, ['string']);
if(Module._isDead){
Module.printErr("shell module has exit()ed. Cannot run SQL.");
return;
}
if(config.autoClearOutput) taOutput.value='';
f._(sql);
};
const btnRun = document.querySelector('#btn-run');
btnRun.addEventListener('click',function(){
const sql = taInput.value.trim();
if(sql){
doExec(sql);
}
},false);
doExec(null)/*sets up the db and outputs the header*/;
document.querySelector('#opt-cb-sbs')
.addEventListener('change', function(){
document.querySelector('#main-wrapper').classList[
this.checked ? 'add' : 'remove'
]('side-by-side');
}, false);
document.querySelector('#btn-notes-caveats')
.addEventListener('click', function(){
document.querySelector('#notes-caveats').remove();
}, false);
/* For each checkbox with data-config=X, set up a binding to
config[X]. */
document.querySelectorAll('input[type=checkbox][data-config]')
.forEach(function(e){
e.checked = !!config[e.dataset.config];
e.addEventListener('change', function(){
config[this.dataset.config] = this.checked;
}, false);
});
/* For each button with data-cmd=X, map a click handler which
calls doExec(X). */
const cmdClick = function(){doExec(this.dataset.cmd);};
document.querySelectorAll('button[data-cmd]').forEach(
e => e.addEventListener('click', cmdClick, false)
);
};
/**
What follows is part of the emscripten core setup. Do not
modify without understanding what it's doing...
*/
const statusElement = document.getElementById('status');
const progressElement = document.getElementById('progress');
const spinnerElement = document.getElementById('spinner');
const Module = window.Module = {
preRun: [],
postRun: [],
onRuntimeInitialized: function(){
doAppSetup(this);
},
print: (function f() {
if(!f._){
f._ = document.getElementById('output');
}
f._.value = ''; // clear browser cache
return function(text) {
if(arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' ');
// These replacements are necessary if you render to raw HTML
//text = text.replace(/&/g, "&amp;");
//text = text.replace(/</g, "&lt;");
//text = text.replace(/>/g, "&gt;");
//text = text.replace('\n', '<br>', 'g');
//console.log("arguments",arguments);
console.log(text);
f._.value += text + "\n";
if(config.autoScrollOutput){
f._.scrollTop = f._.scrollHeight;
}
};
})(),
setStatus: function f(text) {
if(!f.last) f.last = { time: Date.now(), text: '' };
if(text === f.last.text) return;
const m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/);
const now = Date.now();
if(m && now - f.last.time < 30) return; // if this is a progress update, skip it if too soon
f.last.time = now;
f.last.text = text;
if(m) {
text = m[1];
progressElement.value = parseInt(m[2])*100;
progressElement.max = parseInt(m[4])*100;
progressElement.hidden = false;
spinnerElement.hidden = false;
} else {
progressElement.value = null;
progressElement.max = null;
progressElement.hidden = true;
if(!text) spinnerElement.hidden = true;
}
statusElement.innerHTML = text;
},
totalDependencies: 0,
monitorRunDependencies: function(left) {
this.totalDependencies = Math.max(this.totalDependencies, left);
this.setStatus(left
? ('Preparing... (' + (this.totalDependencies-left)
+ '/' + this.totalDependencies + ')')
: 'All downloads complete.');
}
};
Module.printErr = Module.print/*capture stderr output*/;
Module.setStatus('Downloading...');
window.onerror = function(/*message, source, lineno, colno, error*/) {
const err = arguments[4];
if(err && 'ExitStatus'==err.name){
Module._isDead = true;
Module.printErr("FATAL ERROR:", err.message);
Module.printErr("Restarting the app requires reloading the page.");
const taOutput = document.querySelector('#output');
if(taOutput) taOutput.classList.add('error');
}
Module.setStatus('Exception thrown, see JavaScript console');
spinnerElement.style.display = 'none';
Module.setStatus = function(text) {
if(text) console.error('[post-exception status] ' + text);
};
};
})();
</script>
{{{ SCRIPT }}}
</body>
</html>