Last active
May 15, 2022 11:34
-
-
Save nksaraf/62e7c180412386bcf02f2fc457cd70d1 to your computer and use it in GitHub Desktop.
sqlite-ui
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8" /> | |
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> | |
<link | |
rel="icon" | |
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🛢</text></svg>" | |
/> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
<title>Document</title> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.28.0/prism.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.28.0/components/prism-sql.min.js"></script> | |
<style> | |
/* Please see the article */ | |
body, | |
html { | |
margin: 0; | |
} | |
* { | |
box-sizing: border-box; | |
} | |
#editing, | |
#highlighting { | |
/* Both elements need the same text and space styling so they are directly on top of each other */ | |
padding: 10px; | |
border: 0; | |
margin-top: 10px; | |
border-radius: 12px; | |
/* width: calc(100% - 32px); */ | |
width: 100%; | |
height: 150px; | |
} | |
#editing, | |
#highlighting, | |
#highlighting * { | |
/* Also add text styles to highlighing tokens */ | |
font-size: 12pt; | |
font-family: monospace; | |
line-height: 16pt; | |
tab-size: 2; | |
} | |
#editing, | |
#highlighting { | |
/* In the same place */ | |
position: absolute; | |
top: 0; | |
left: 0; | |
} | |
/* Move the textarea in front of the result */ | |
#editing { | |
z-index: 1; | |
} | |
#highlighting { | |
z-index: 0; | |
} | |
/* Make textarea almost completely transparent */ | |
#editing { | |
color: transparent; | |
background: transparent; | |
caret-color: white; /* Or choose your favourite color */ | |
} | |
/* Can be scrolled */ | |
#editing, | |
#highlighting { | |
overflow: auto; | |
white-space: nowrap; /* Allows textarea to scroll horizontally */ | |
} | |
/* No resize on textarea */ | |
#editing { | |
resize: none; | |
} | |
/* Paragraphs; First Image */ | |
* { | |
font-family: "Fira Code", monospace; | |
} | |
p code { | |
border-radius: 2px; | |
background-color: #eee; | |
color: #111; | |
} | |
/* Syntax Highlighting from prism.js starts below, partly modified: */ | |
/* PrismJS 1.23.0 | |
https://prismjs.com/download.html#themes=prism-funky&languages=markup */ | |
/** | |
* prism.js Funky theme | |
* Based on “Polyfilling the gaps” talk slides http://lea.verou.me/polyfilling-the-gaps/ | |
* @author Lea Verou | |
*/ | |
code[class*="language-"], | |
pre[class*="language-"] { | |
font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace; | |
font-size: 1em; | |
text-align: left; | |
white-space: pre; | |
word-spacing: normal; | |
word-break: normal; | |
word-wrap: normal; | |
line-height: 1.5; | |
-moz-tab-size: 4; | |
-o-tab-size: 4; | |
tab-size: 4; | |
-webkit-hyphens: none; | |
-moz-hyphens: none; | |
-ms-hyphens: none; | |
hyphens: none; | |
} | |
/* Code blocks */ | |
pre[class*="language-"] { | |
padding: 0.4em 0.8em; | |
margin: 0.5em 0; | |
overflow: auto; | |
/* background: url('data:image/svg+xml;charset=utf-8,<svg%20version%3D"1.1"%20xmlns%3D"http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg"%20width%3D"100"%20height%3D"100"%20fill%3D"rgba(0%2C0%2C0%2C.2)">%0D%0A<polygon%20points%3D"0%2C50%2050%2C0%200%2C0"%20%2F>%0D%0A<polygon%20points%3D"0%2C100%2050%2C100%20100%2C50%20100%2C0"%20%2F>%0D%0A<%2Fsvg>'); | |
background-size: 1em 1em; - WebCoder49*/ | |
background: black; /* - WebCoder49 */ | |
} | |
code[class*="language-"] { | |
background: black; | |
color: white; | |
box-shadow: -0.3em 0 0 0.3em black, 0.3em 0 0 0.3em black; | |
} | |
/* Inline code */ | |
:not(pre) > code[class*="language-"] { | |
padding: 0.2em; | |
border-radius: 0.3em; | |
box-shadow: none; | |
white-space: normal; | |
} | |
.token.comment, | |
.token.prolog, | |
.token.doctype, | |
.token.cdata { | |
color: #aaa; | |
} | |
.token.punctuation { | |
color: #999; | |
} | |
.token.namespace { | |
opacity: 0.7; | |
} | |
.token.property, | |
.token.tag, | |
.token.boolean, | |
.token.number, | |
.token.constant, | |
.token.symbol { | |
color: #0cf; | |
} | |
.token.selector, | |
.token.attr-name, | |
.token.string, | |
.token.char, | |
.token.builtin { | |
color: yellow; | |
} | |
.token.operator, | |
.token.entity, | |
.token.url, | |
.language-css .token.string, | |
.token.variable, | |
.token.inserted { | |
color: yellowgreen; | |
} | |
.token.atrule, | |
.token.attr-value, | |
.token.keyword { | |
color: deeppink; | |
} | |
.token.regex, | |
.token.important { | |
color: orange; | |
} | |
.token.important, | |
.token.bold { | |
font-weight: bold; | |
} | |
.token.italic { | |
font-style: italic; | |
} | |
.token.entity { | |
cursor: help; | |
} | |
.token.deleted { | |
color: red; | |
} | |
/* Plugin styles: Diff Highlight */ | |
pre.diff-highlight.diff-highlight > code .token.deleted:not(.prefix), | |
pre > code.diff-highlight.diff-highlight .token.deleted:not(.prefix) { | |
background-color: rgba(255, 0, 0, 0.3); | |
display: inline; | |
} | |
pre.diff-highlight.diff-highlight > code .token.inserted:not(.prefix), | |
pre > code.diff-highlight.diff-highlight .token.inserted:not(.prefix) { | |
background-color: rgba(0, 255, 128, 0.3); | |
display: inline; | |
} | |
/* End of prism.js syntax highlighting*/ | |
</style> | |
</head> | |
<body> | |
<div id="app"></div> | |
<script></script> | |
<script type="module"> | |
import { render } from "https://esm.sh/solid-js/web"; | |
import html from "https://esm.sh/solid-js/html"; | |
import { createSignal, For, Show } from "https://esm.sh/solid-js"; | |
function Button(props) { | |
return html`<button class="btn-primary" ...${props} />`; | |
} | |
function TextArea(props) { | |
function update(text) { | |
let result_element = document.querySelector("#highlighting-content"); | |
// Handle final newlines (see article) | |
if (text.charCodeAt(text.length - 1) == 10) { | |
text += " "; | |
} | |
// Update code | |
result_element.innerHTML = text | |
.replace(new RegExp("&", "g"), "&") | |
.replace(new RegExp("<", "g"), "<"); /* Global RegExp */ | |
// Syntax Highlight | |
Prism.highlightElement(result_element); | |
props.onChange?.(text); | |
} | |
function sync_scroll(element) { | |
/* Scroll result to scroll coords of event - sync with textarea */ | |
let result_element = document.querySelector("#highlighting"); | |
// Get and set x and y | |
result_element.scrollTop = element.scrollTop; | |
result_element.scrollLeft = element.scrollLeft; | |
} | |
function check_tab(element, event) { | |
let code = element.value; | |
if (event.key == "Tab") { | |
/* Tab key pressed */ | |
event.preventDefault(); // stop normal | |
let before_tab = code.slice(0, element.selectionStart); // text before tab | |
let after_tab = code.slice( | |
element.selectionEnd, | |
element.value.length | |
); // text after tab | |
let cursor_pos = element.selectionEnd + 1; // where cursor moves after tab - moving forward by 1 char to after tab | |
element.value = before_tab + "\t" + after_tab; // add tab char | |
// move cursor | |
element.selectionStart = cursor_pos; | |
element.selectionEnd = cursor_pos; | |
update(element.value); // Update text to include indent | |
} | |
} | |
return html` | |
<div style="padding: 12px"> | |
<div style="position: relative; height: 200px; width: 100%"> | |
<textarea | |
placeholder="Enter SQL" | |
id="editing" | |
autocomplete="on" | |
name="sql" | |
spellcheck="false" | |
oninput=${(ev) => { | |
let target = ev.target; | |
update(target.value); | |
sync_scroll(target); | |
}} | |
onscroll=${(ev) => { | |
let target = ev.target; | |
sync_scroll(target); | |
}} | |
onkeydown=${(ev) => { | |
let target = ev.target; | |
check_tab(target, ev); | |
}} | |
></textarea> | |
<pre id="highlighting" aria-hidden="true"> | |
<code class="language-sql" id="highlighting-content"></code> | |
</pre> | |
</div> | |
</div> | |
`; | |
} | |
function App() { | |
const [sql, setSql] = createSignal(""); | |
const [data, setData] = createSignal([]); | |
const increment = async (e) => { | |
let method = sql().includes("SELECT") ? "query" : "execute"; | |
try { | |
let result = await fetch(`/main/${method}`, { | |
method: "POST", | |
body: JSON.stringify({ | |
sql: sql(), | |
}), | |
}); | |
let data = await result.json(); | |
console.log(data); | |
if (data.error) { | |
alert(data.error); | |
} else { | |
setData(data); | |
} | |
} catch (e) { | |
console.log(e); | |
} | |
}; | |
let cols = () => (data().length > 0 ? Object.keys(data()[0]) : []); | |
return html` | |
<> | |
<${TextArea} onChange=${setSql} /> | |
<${Button} type="button" onClick=${increment}>Submit<//> | |
<${Show} when=${() => data()}> | |
<table> | |
<thead> | |
<tr> | |
<${For} each=${cols}> | |
${(col) => html`<th>${col}</th>`} | |
<//> | |
</tr> | |
</thead> | |
<tbody> | |
<${For} each=${() => data()}> | |
${(item) => | |
html`<tr> | |
<${For} each=${cols}> | |
${(col) => html`<td>${item[col]}</td>`} | |
<//> | |
</tr>`} | |
<//> | |
</tbody> | |
</table> | |
<//> | |
</> | |
`; | |
} | |
render(App, document.getElementById("app")); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment