Skip to content

Instantly share code, notes, and snippets.

@nksaraf
Last active May 15, 2022 11:34
Show Gist options
  • Save nksaraf/62e7c180412386bcf02f2fc457cd70d1 to your computer and use it in GitHub Desktop.
Save nksaraf/62e7c180412386bcf02f2fc457cd70d1 to your computer and use it in GitHub Desktop.
sqlite-ui
<!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"), "&amp;")
.replace(new RegExp("<", "g"), "&lt;"); /* 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