|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(function(){ |
|
|
'use strict'; |
|
|
|
|
|
|
|
|
|
|
|
const storage = (function(NS){ |
|
|
|
|
|
|
|
|
|
|
|
'use strict'; |
|
|
NS = NS||{}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const tryStorage = function f(obj){ |
|
|
if(!f.key) f.key = 'storage.access.check'; |
|
|
try{ |
|
|
obj.setItem(f.key, 'f'); |
|
|
const x = obj.getItem(f.key); |
|
|
obj.removeItem(f.key); |
|
|
if(x!=='f') throw new Error(f.key+" failed") |
|
|
return obj; |
|
|
}catch(e){ |
|
|
return undefined; |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
const $storage = |
|
|
tryStorage(window.localStorage) |
|
|
|| tryStorage(window.sessionStorage) |
|
|
|| tryStorage({ |
|
|
|
|
|
$$$:{}, |
|
|
setItem: function(k,v){this.$$$[k]=v}, |
|
|
getItem: function(k){ |
|
|
return this.$$$.hasOwnProperty(k) ? this.$$$[k] : undefined; |
|
|
}, |
|
|
removeItem: function(k){delete this.$$$[k]}, |
|
|
clear: function(){this.$$$={}} |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const $storageHolder = $storage.hasOwnProperty('$$$') ? $storage.$$$ : $storage; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const storageKeyPrefix = ( |
|
|
$storageHolder===$storage |
|
|
? ( |
|
|
(NS.config ? |
|
|
(NS.config.projectCode || NS.config.projectName |
|
|
|| NS.config.shortProjectName) |
|
|
: false) |
|
|
|| window.location.pathname |
|
|
)+'::' : ( |
|
|
'' |
|
|
) |
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
NS.storage = { |
|
|
storageKeyPrefix: storageKeyPrefix, |
|
|
|
|
|
|
|
|
set: (k,v)=>$storage.setItem(storageKeyPrefix+k,v), |
|
|
|
|
|
setJSON: (k,v)=>$storage.setItem(storageKeyPrefix+k,JSON.stringify(v)), |
|
|
|
|
|
|
|
|
get: (k,dflt)=>$storageHolder.hasOwnProperty( |
|
|
storageKeyPrefix+k |
|
|
) ? $storage.getItem(storageKeyPrefix+k) : dflt, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
getBool: function(k,dflt){ |
|
|
return 'true'===this.get(k,''+(!!dflt)); |
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
getJSON: function f(k,dflt){ |
|
|
try { |
|
|
const x = this.get(k,f); |
|
|
return x===f ? dflt : JSON.parse(x); |
|
|
} |
|
|
catch(e){return dflt} |
|
|
}, |
|
|
|
|
|
|
|
|
contains: (k)=>$storageHolder.hasOwnProperty(storageKeyPrefix+k), |
|
|
|
|
|
remove: function(k){ |
|
|
$storage.removeItem(storageKeyPrefix+k); |
|
|
return this; |
|
|
}, |
|
|
|
|
|
clear: function(){ |
|
|
this.keys().forEach((k)=>$storage.removeItem(k)); |
|
|
return this; |
|
|
}, |
|
|
|
|
|
keys: ()=>Object.keys($storageHolder).filter((v)=>(v||'').startsWith(storageKeyPrefix)), |
|
|
|
|
|
|
|
|
|
|
|
isTransient: ()=>$storageHolder!==$storage, |
|
|
|
|
|
storageImplName: function(){ |
|
|
if($storage===window.localStorage) return 'localStorage'; |
|
|
else if($storage===window.sessionStorage) return 'sessionStorage'; |
|
|
else return 'transient'; |
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
storageHelpDescription: function(){ |
|
|
return { |
|
|
localStorage: "Browser-local persistent storage with an "+ |
|
|
"unspecified long-term lifetime (survives closing the browser, "+ |
|
|
"but maybe not a browser upgrade).", |
|
|
sessionStorage: "Storage local to this browser tab, "+ |
|
|
"lost if this tab is closed.", |
|
|
"transient": "Transient storage local to this invocation of this page." |
|
|
}[this.storageImplName()]; |
|
|
} |
|
|
}; |
|
|
return NS.storage; |
|
|
})({}); |
|
|
|
|
|
|
|
|
|
|
|
const configStorageKey = 'sqlite3-fiddle-config'; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const SF |
|
|
= window.SqliteFiddle = { |
|
|
|
|
|
config: { |
|
|
|
|
|
|
|
|
|
|
|
autoScrollOutput: true, |
|
|
|
|
|
|
|
|
autoClearOutput: false, |
|
|
|
|
|
|
|
|
|
|
|
echoToConsole: false, |
|
|
|
|
|
sideBySide: true, |
|
|
|
|
|
swapInOut: false |
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
echo: function f(text) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if(!f._){ |
|
|
f._ = document.getElementById('output'); |
|
|
f._.value = ''; |
|
|
} |
|
|
if(arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' '); |
|
|
else if(1===arguments.length && Array.isArray(text)) text = text.join(' '); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if(null===text){ |
|
|
f._.value = ''; |
|
|
return; |
|
|
}else if(this.echo._clearPending){ |
|
|
delete this.echo._clearPending; |
|
|
f._.value = ''; |
|
|
} |
|
|
if(this.config.echoToConsole) console.log(text); |
|
|
if(this.jqTerm) this.jqTerm.echo(text); |
|
|
f._.value += text + "\n"; |
|
|
if(this.config.autoScrollOutput){ |
|
|
f._.scrollTop = f._.scrollHeight; |
|
|
} |
|
|
}, |
|
|
_msgMap: {}, |
|
|
|
|
|
|
|
|
addMsgHandler: function f(type,callback){ |
|
|
if(Array.isArray(type)){ |
|
|
type.forEach((t)=>this.addMsgHandler(t, callback)); |
|
|
return this; |
|
|
} |
|
|
(this._msgMap.hasOwnProperty(type) |
|
|
? this._msgMap[type] |
|
|
: (this._msgMap[type] = [])).push(callback); |
|
|
return this; |
|
|
}, |
|
|
|
|
|
runMsgHandlers: function(msg){ |
|
|
const list = (this._msgMap.hasOwnProperty(msg.type) |
|
|
? this._msgMap[msg.type] : false); |
|
|
if(!list){ |
|
|
console.warn("No handlers found for message type:",msg); |
|
|
return false; |
|
|
} |
|
|
|
|
|
list.forEach((f)=>f(msg)); |
|
|
return true; |
|
|
}, |
|
|
|
|
|
clearMsgHandlers: function(type){ |
|
|
delete this._msgMap[type]; |
|
|
return this; |
|
|
}, |
|
|
|
|
|
wMsg: function(type,data,transferables){ |
|
|
this.worker.postMessage({type, data}, transferables || []); |
|
|
return this; |
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
resetDb: function(){ |
|
|
if(window.confirm("Really destroy all content and tables " |
|
|
+"in the (transient) db?")){ |
|
|
this.wMsg('db-reset'); |
|
|
} |
|
|
return this; |
|
|
}, |
|
|
|
|
|
storeConfig: function(){ |
|
|
storage.setJSON(configStorageKey,this.config); |
|
|
} |
|
|
}; |
|
|
|
|
|
if(1){ |
|
|
const storedConfig = storage.getJSON(configStorageKey); |
|
|
if(storedConfig){ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Object.keys(SF.config).forEach(function(k){ |
|
|
if(storedConfig.hasOwnProperty(k)){ |
|
|
SF.config[k] = storedConfig[k]; |
|
|
} |
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
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(' '); |
|
|
a.setAttribute('href', |
|
|
|
|
|
'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); |
|
|
}); |
|
|
SF.addMsgHandler('wasm-info', (ev)=>{ |
|
|
const v = ev.data; |
|
|
SF.e.wasmInfo.innerText = 'WASM: '+( |
|
|
4===v.pointerSize ? 32 : 64 |
|
|
)+'-bit' |
|
|
|
|
|
|
|
|
; |
|
|
}); |
|
|
|
|
|
|
|
|
const EAll = function(){ |
|
|
return (arguments.length>1 ? arguments[0] : document) |
|
|
.querySelectorAll(arguments[arguments.length-1]); |
|
|
}; |
|
|
|
|
|
const E = function(){ |
|
|
return (arguments.length>1 ? arguments[0] : document) |
|
|
.querySelector(arguments[arguments.length-1]); |
|
|
}; |
|
|
|
|
|
|
|
|
SF.addMsgHandler('module', function f(ev){ |
|
|
ev = ev.data; |
|
|
if('status'!==ev.type){ |
|
|
console.warn("Unexpected module-type message:",ev); |
|
|
return; |
|
|
} |
|
|
if(!f.ui){ |
|
|
f.ui = { |
|
|
status: E('#module-status'), |
|
|
progress: E('#module-progress'), |
|
|
spinner: E('#module-spinner') |
|
|
}; |
|
|
} |
|
|
const msg = ev.data; |
|
|
if(f.ui.progres){ |
|
|
progress.value = msg.step; |
|
|
progress.max = msg.step + 1; |
|
|
} |
|
|
if(1==msg.step){ |
|
|
f.ui.progress.classList.remove('hidden'); |
|
|
f.ui.spinner.classList.remove('hidden'); |
|
|
} |
|
|
if(msg.text){ |
|
|
f.ui.status.classList.remove('hidden'); |
|
|
f.ui.status.innerText = msg.text; |
|
|
}else{ |
|
|
if(f.ui.progress){ |
|
|
f.ui.progress.remove(); |
|
|
f.ui.spinner.remove(); |
|
|
delete f.ui.progress; |
|
|
delete f.ui.spinner; |
|
|
} |
|
|
f.ui.status.classList.add('hidden'); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
SF.addMsgHandler('fiddle-ready', function(){ |
|
|
SF.clearMsgHandlers('fiddle-ready'); |
|
|
self.onSFLoaded(); |
|
|
}); |
|
|
|
|
|
SF.e ={ |
|
|
about: E('#view-about'), |
|
|
split: E('#view-split'), |
|
|
wasmInfo: E('#opt-wasm-info'), |
|
|
terminal: E('#view-terminal'), |
|
|
hideInTerminal: EAll('.hide-in-terminal' |
|
|
) |
|
|
}; |
|
|
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(); |
|
|
}; |
|
|
|
|
|
|
|
|
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 ); |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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){ |
|
|
|
|
|
f.extra += callee.bottom - callee.top; |
|
|
} |
|
|
return f.extra; |
|
|
}; |
|
|
} |
|
|
f.extra = 0; |
|
|
f.measure(e,0); |
|
|
return f.extra; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 ; |
|
|
|
|
|
SF.ForceResizeKludge = (function(){ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const eVisibles = EAll('.app-view'); |
|
|
const elemsToCount = [ |
|
|
|
|
|
|
|
|
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",")" |
|
|
|
|
|
|
|
|
|
|
|
].join(''); |
|
|
}); |
|
|
}; |
|
|
resized.$disabled = true; |
|
|
window.addEventListener('resize', debounce(resized, 250), false); |
|
|
return resized; |
|
|
})(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self.onSFLoaded = function(){ |
|
|
delete this.onSFLoaded; |
|
|
|
|
|
|
|
|
EAll('.initially-hidden').forEach((e)=>e.classList.remove('initially-hidden')); |
|
|
SF.e.main = EAll('.app-view:not(.hidden)')[0] |
|
|
|
|
|
; |
|
|
if( (new URL(self.location.href).searchParams).has('about') ){ |
|
|
SF.toggleAbout() ; |
|
|
} |
|
|
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'); |
|
|
btnClearIn.addEventListener('click',function(){ |
|
|
taInput.value = ''; |
|
|
selectExamples.selectedIndex = 0; |
|
|
},false); |
|
|
|
|
|
taInput.addEventListener('keydown',function(ev){ |
|
|
if((ev.ctrlKey || ev.shiftKey) && 13 === ev.keyCode){ |
|
|
ev.preventDefault(); |
|
|
ev.stopPropagation(); |
|
|
btnShellExec.click(); |
|
|
} |
|
|
}, false); |
|
|
const taOutput = E('#output'); |
|
|
const btnClearOut = E('#btn-clear-output'); |
|
|
btnClearOut.addEventListener('click',function(){ |
|
|
taOutput.value = ''; |
|
|
if(SF.jqTerm) SF.jqTerm.clear(); |
|
|
},false); |
|
|
const btnShellExec = E('#btn-shell-exec'); |
|
|
btnShellExec.addEventListener('click',function(ev){ |
|
|
let sql; |
|
|
ev.preventDefault(); |
|
|
if(taInput.selectionStart<taInput.selectionEnd){ |
|
|
sql = taInput.value.substring(taInput.selectionStart,taInput.selectionEnd).trim(); |
|
|
}else{ |
|
|
sql = taInput.value.trim(); |
|
|
} |
|
|
if(sql) SF.dbExec(sql); |
|
|
},false); |
|
|
|
|
|
const btnInterrupt = E("#btn-interrupt"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const preStartWork = function f(){ |
|
|
if(!f._){ |
|
|
const title = E('title'); |
|
|
f._ = { |
|
|
btnLabel: btnShellExec.innerText, |
|
|
pageTitle: title, |
|
|
pageTitleOrig: title.innerText |
|
|
}; |
|
|
} |
|
|
f._.pageTitle.innerText = "[working...] "+f._.pageTitleOrig; |
|
|
btnShellExec.setAttribute('disabled','disabled'); |
|
|
btnInterrupt.removeAttribute('disabled','disabled'); |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
SF.dbExec = function f(sql){ |
|
|
if(null!==sql && this.config.autoClearOutput){ |
|
|
this.echo._clearPending = true; |
|
|
} |
|
|
preStartWork(); |
|
|
this.wMsg('shellExec',sql); |
|
|
}; |
|
|
|
|
|
SF.addMsgHandler('working',function f(ev){ |
|
|
switch(ev.data){ |
|
|
case 'start': ; return; |
|
|
case 'end': |
|
|
preStartWork._.pageTitle.innerText = preStartWork._.pageTitleOrig; |
|
|
btnShellExec.innerText = preStartWork._.btnLabel; |
|
|
btnShellExec.removeAttribute('disabled'); |
|
|
btnInterrupt.setAttribute('disabled','disabled'); |
|
|
return; |
|
|
} |
|
|
console.warn("Unhandled 'working' event:",ev.data); |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
EAll('input[type=checkbox][data-csstgt]') |
|
|
.forEach(function(e){ |
|
|
const tgt = E(e.dataset.csstgt); |
|
|
const cssClass = e.dataset.cssclass || 'error'; |
|
|
e.checked = tgt.classList.contains(cssClass); |
|
|
e.addEventListener('change', function(){ |
|
|
tgt.classList[ |
|
|
this.checked ? 'add' : 'remove' |
|
|
](cssClass) |
|
|
}, false); |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
EAll('input[type=checkbox][data-config]') |
|
|
.forEach(function(e){ |
|
|
const confVal = !!SF.config[e.dataset.config]; |
|
|
if(e.checked !== confVal){ |
|
|
|
|
|
|
|
|
e.checked = confVal; |
|
|
e.dispatchEvent(new Event('change')); |
|
|
} |
|
|
e.addEventListener('change', function(){ |
|
|
SF.config[this.dataset.config] = this.checked; |
|
|
SF.storeConfig(); |
|
|
}, false); |
|
|
}); |
|
|
|
|
|
|
|
|
const cmdClick = function(){SF.dbExec(this.dataset.cmd);}; |
|
|
EAll('button[data-cmd]').forEach( |
|
|
e => e.addEventListener('click', cmdClick, false) |
|
|
); |
|
|
|
|
|
btnInterrupt.addEventListener('click',function(){ |
|
|
SF.wMsg('interrupt'); |
|
|
}); |
|
|
|
|
|
|
|
|
const btnExport = E('#btn-export'); |
|
|
const eLoadDb = E('#load-db'); |
|
|
const btnLoadDb = E('#btn-load-db'); |
|
|
btnLoadDb.addEventListener('click', ()=>eLoadDb.click()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const enableMutatingElements = function f(enable){ |
|
|
if(!f._elems){ |
|
|
f._elems = [ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
btnShellExec, btnExport, eLoadDb |
|
|
]; |
|
|
} |
|
|
f._elems.forEach( enable |
|
|
? (e)=>e.removeAttribute('disabled') |
|
|
: (e)=>e.setAttribute('disabled','disabled') ); |
|
|
}; |
|
|
btnExport.addEventListener('click',function(){ |
|
|
enableMutatingElements(false); |
|
|
SF.wMsg('db-export'); |
|
|
}); |
|
|
SF.addMsgHandler('db-export', function(ev){ |
|
|
enableMutatingElements(true); |
|
|
ev = ev.data; |
|
|
if(ev.error){ |
|
|
SF.echo("Export failed:",ev.error); |
|
|
return; |
|
|
} |
|
|
const blob = new Blob([ev.buffer], |
|
|
{type:"application/x-sqlite3"}); |
|
|
const a = document.createElement('a'); |
|
|
document.body.appendChild(a); |
|
|
a.href = window.URL.createObjectURL(blob); |
|
|
a.download = ev.filename; |
|
|
a.addEventListener('click',function(){ |
|
|
setTimeout(function(){ |
|
|
SF.echo("Exported (possibly auto-downloaded):",ev.filename); |
|
|
window.URL.revokeObjectURL(a.href); |
|
|
a.remove(); |
|
|
},500); |
|
|
}); |
|
|
a.click(); |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
eLoadDb.addEventListener('change',function(){ |
|
|
const f = this.files[0]; |
|
|
const r = new FileReader(); |
|
|
const status = {loaded: 0, total: 0}; |
|
|
enableMutatingElements(false); |
|
|
r.addEventListener('loadstart', function(){ |
|
|
SF.echo("Loading",f.name,"..."); |
|
|
}); |
|
|
r.addEventListener('progress', function(ev){ |
|
|
SF.echo("Loading progress:",ev.loaded,"of",ev.total,"bytes."); |
|
|
}); |
|
|
const that = this; |
|
|
r.addEventListener('load', function(){ |
|
|
enableMutatingElements(true); |
|
|
SF.echo("Loaded",f.name+". Opening db..."); |
|
|
SF.wMsg('open',{ |
|
|
filename: f.name, |
|
|
buffer: this.result |
|
|
}, [this.result]); |
|
|
}); |
|
|
r.addEventListener('error',function(){ |
|
|
enableMutatingElements(true); |
|
|
SF.echo("Loading",f.name,"failed for unknown reasons."); |
|
|
}); |
|
|
r.addEventListener('abort',function(){ |
|
|
enableMutatingElements(true); |
|
|
SF.echo("Cancelled loading of",f.name+"."); |
|
|
}); |
|
|
r.readAsArrayBuffer(f); |
|
|
}); |
|
|
|
|
|
EAll('fieldset.collapsible').forEach(function(fs){ |
|
|
const btnToggle = E(fs,'legend > .fieldset-toggle'), |
|
|
content = EAll(fs,':scope > div'); |
|
|
btnToggle.addEventListener('click', function(){ |
|
|
fs.classList.toggle('collapsed'); |
|
|
content.forEach((d)=>d.classList.toggle('hidden')); |
|
|
}, false); |
|
|
}); |
|
|
|
|
|
|
|
|
(function(){ |
|
|
const xElem = E('#select-examples'); |
|
|
const examples = [ |
|
|
{name: "Help", sql: [ |
|
|
"-- ================================================\n", |
|
|
"-- Use ctrl-enter or shift-enter to execute sqlite3\n", |
|
|
"-- shell commands and SQL.\n", |
|
|
"-- If a subset of the text is currently selected,\n", |
|
|
"-- only that part is executed.\n", |
|
|
"-- ================================================\n", |
|
|
".help\n" |
|
|
]}, |
|
|
|
|
|
|
|
|
{name: "Box Mode", sql: ".mode box"}, |
|
|
{name: "Setup table T", sql:[ |
|
|
".nullvalue NULL\n", |
|
|
"CREATE TABLE t(a,b);\n", |
|
|
"INSERT INTO t(a,b) VALUES('abc',123),('def',456),(NULL,789),('ghi',012);\n", |
|
|
"SELECT * FROM t;\n" |
|
|
]}, |
|
|
{name: "sqlite_schema", sql: "select * from sqlite_schema"}, |
|
|
{name: "Mandelbrot", sql:[ |
|
|
"WITH RECURSIVE", |
|
|
" xaxis(x) AS (VALUES(-2.0) UNION ALL SELECT x+0.05 FROM xaxis WHERE x<1.2),\n", |
|
|
" yaxis(y) AS (VALUES(-1.0) UNION ALL SELECT y+0.1 FROM yaxis WHERE y<1.0),\n", |
|
|
" m(iter, cx, cy, x, y) AS (\n", |
|
|
" SELECT 0, x, y, 0.0, 0.0 FROM xaxis, yaxis\n", |
|
|
" UNION ALL\n", |
|
|
" SELECT iter+1, cx, cy, x*x-y*y + cx, 2.0*x*y + cy FROM m \n", |
|
|
" WHERE (x*x + y*y) < 4.0 AND iter<28\n", |
|
|
" ),\n", |
|
|
" m2(iter, cx, cy) AS (\n", |
|
|
" SELECT max(iter), cx, cy FROM m GROUP BY cx, cy\n", |
|
|
" ),\n", |
|
|
" a(t) AS (\n", |
|
|
" SELECT group_concat( substr(' .+*#', 1+min(iter/7,4), 1), '') \n", |
|
|
" FROM m2 GROUP BY cy\n", |
|
|
" )\n", |
|
|
"SELECT group_concat(rtrim(t),x'0a') as Mandelbrot FROM a;\n", |
|
|
]}, |
|
|
{name: "JSON pretty-print", |
|
|
sql: "select json_pretty(json_object('ex',json('[52,3.14159]')))" |
|
|
}, |
|
|
{name: "JSON pretty-print (with tabs)", |
|
|
sql: "select json_pretty(json_object('ex',json('[52,3.14159]')),char(0x09))" |
|
|
} |
|
|
]; |
|
|
const newOpt = function(lbl,val){ |
|
|
const o = document.createElement('option'); |
|
|
if(Array.isArray(val)) val = val.join(''); |
|
|
o.value = val; |
|
|
if(!val) o.setAttribute('disabled',true); |
|
|
o.appendChild(document.createTextNode(lbl)); |
|
|
xElem.appendChild(o); |
|
|
}; |
|
|
newOpt("Examples (replaces input!)"); |
|
|
examples.forEach((o)=>newOpt(o.name, o.sql)); |
|
|
|
|
|
xElem.selectedIndex = 0; |
|
|
xElem.addEventListener('change', function(){ |
|
|
taInput.value = '-- ' + |
|
|
this.selectedOptions[0].innerText + |
|
|
'\n' + this.value; |
|
|
SF.dbExec(this.value); |
|
|
}); |
|
|
})(); |
|
|
|
|
|
|
|
|
if(window.jQuery && window.jQuery.terminal && SF.e.terminal){ |
|
|
|
|
|
const jqeTerm = window.jQuery(SF.e.terminal).empty(); |
|
|
SF.jqTerm = jqeTerm.terminal(SF.dbExec.bind(SF),{ |
|
|
prompt: 'sqlite> ', |
|
|
greetings: false |
|
|
}); |
|
|
EAll('.unhide-if-terminal-available').forEach(e=>{ |
|
|
e.classList.remove('hidden'); |
|
|
}); |
|
|
EAll('.remove-if-terminal-available').forEach(e=>{ |
|
|
e.parentElement.removeChild(e); |
|
|
}); |
|
|
|
|
|
const ePlaceholder = E('#terminal-button-placeholder'); |
|
|
ePlaceholder.classList.add('labeled-input'); |
|
|
ePlaceholder.classList.remove('hidden'); |
|
|
const btnToggleView = document.createElement('button'); |
|
|
btnToggleView.innerText = "Toggle view"; |
|
|
ePlaceholder.appendChild( btnToggleView ); |
|
|
btnToggleView.addEventListener('click',function f(){ |
|
|
const terminalOn = document.body.classList.toggle('terminal-mode'); |
|
|
SF.setMainView( terminalOn ? SF.e.terminal : SF.e.split ); |
|
|
}, false); |
|
|
btnToggleView.click(); |
|
|
} |
|
|
const urlParams = new URL(self.location.href).searchParams; |
|
|
SF.dbExec(urlParams.get('sql') || null); |
|
|
delete SF.ForceResizeKludge.$disabled; |
|
|
SF.ForceResizeKludge(); |
|
|
}; |
|
|
})(); |
|
|
|