sqlight: sqlite playground
This commit is contained in:
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
/target
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
2335
Cargo.lock
generated
Normal file
2335
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
37
Cargo.toml
Normal file
37
Cargo.toml
Normal file
@@ -0,0 +1,37 @@
|
||||
[package]
|
||||
name = "sqlight"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = [
|
||||
"crates/aceditor",
|
||||
"crates/floating-ui",
|
||||
"crates/istyles",
|
||||
"crates/split-grid"
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
console_error_panic_hook = "0.1.7"
|
||||
leptos = { version = "0.8.1", features = ["csr", "nightly"] }
|
||||
sqlite-wasm-rs = { git = "https://github.com/Spxg/sqlite-wasm-rs.git", default-features = false, features = ["precompiled"] }
|
||||
istyles = { path = "crates/istyles" }
|
||||
aceditor = { path = "crates/aceditor" }
|
||||
split-grid = { path = "crates/split-grid" }
|
||||
floating-ui = { path = "crates/floating-ui" }
|
||||
reactive_stores = "0.2.2"
|
||||
thiserror = "2.0.12"
|
||||
serde = "1.0.219"
|
||||
serde_json = "1.0.140"
|
||||
web-sys = { version = "0.3.77", features = ["Clipboard", "DedicatedWorkerGlobalScope", "File", "FileList", "HtmlSelectElement", "MediaQueryList", "Navigator", "Storage", "Worker", "WorkerOptions", "WorkerType"] }
|
||||
serde-wasm-bindgen = "0.6.5"
|
||||
parking_lot = "0.12.3"
|
||||
once_cell = "1.21.3"
|
||||
tokio = { version = "1.45.0", features = ["sync"] }
|
||||
wasm-bindgen = "0.2.100"
|
||||
js-sys = "0.3.77"
|
||||
wasm-bindgen-futures = "0.4.50"
|
||||
console_log = "1.0.0"
|
||||
log = "0.4.27"
|
||||
fragile = "2.0.1"
|
||||
135
assets/module.css/button_set.module.css
Normal file
135
assets/module.css/button_set.module.css
Normal file
@@ -0,0 +1,135 @@
|
||||
$width: 1px;
|
||||
$radius: 4px;
|
||||
|
||||
.-buttonReset {
|
||||
color: var(--font-color);
|
||||
border: none;
|
||||
background: inherit;
|
||||
background-color: transparent; /* IE 11 */
|
||||
padding: 0;
|
||||
font: inherit;
|
||||
line-height: inherit;
|
||||
text-align: inherit;
|
||||
}
|
||||
|
||||
.set {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.-border {
|
||||
border-width: $width 0;
|
||||
border-style: solid;
|
||||
|
||||
&:first-child {
|
||||
border-top-left-radius: $radius;
|
||||
border-bottom-left-radius: $radius;
|
||||
border-left-width: $width;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-top-right-radius: $radius;
|
||||
border-bottom-right-radius: $radius;
|
||||
border-right-width: $width;
|
||||
}
|
||||
}
|
||||
|
||||
.-button {
|
||||
--button-gap: 0.5em;
|
||||
--button-side-padding: 1.25em;
|
||||
--button-height: 3em;
|
||||
--button-side-icon-nudge: 0.25em;
|
||||
|
||||
composes: -buttonReset;
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
gap: var(--button-gap);
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
padding: 0 var(--button-side-padding);
|
||||
height: var(--button-height);
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
white-space: nowrap;
|
||||
|
||||
&:enabled {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.small {
|
||||
--button-gap: 0.25em;
|
||||
--button-side-padding: 0.75em;
|
||||
--button-height: 2.5em;
|
||||
--button-side-icon-nudge: 0.125em;
|
||||
}
|
||||
|
||||
.primary {
|
||||
composes: -border -button;
|
||||
background-color: var(--button-primary-bg-color);
|
||||
border-color: var(--button-primary-border-color);
|
||||
color: var(--button-primary-color);
|
||||
font-weight: 700;
|
||||
|
||||
&:disabled {
|
||||
background-color: var(--button-primary-bg-color-light);
|
||||
border-color: var(--button-primary-border-color-light);
|
||||
}
|
||||
|
||||
&:hover:enabled {
|
||||
background-color: var(--button-primary-border-color);
|
||||
}
|
||||
|
||||
&:active:enabled {
|
||||
box-shadow: inset 0 0 5px var(--button-primary-active-color);
|
||||
}
|
||||
}
|
||||
|
||||
.secondary {
|
||||
composes: -border -button;
|
||||
background: var(--button-secondary-bg-color);
|
||||
border-color: var(--button-secondary-border-color);
|
||||
color: var(--button-secondary-color);
|
||||
|
||||
&:disabled {
|
||||
background: inherit;
|
||||
background-color: var(--button-secondary-bg-color-light);
|
||||
border-color: var(--button-secondary-border-color-light);
|
||||
color: var(--button-secondary-color-light);
|
||||
}
|
||||
|
||||
&:visited {
|
||||
color: var(--button-secondary-color);
|
||||
}
|
||||
|
||||
&:hover:enabled {
|
||||
background: inherit;
|
||||
background-color: var(--button-secondary-border-color);
|
||||
}
|
||||
|
||||
&:active:enabled {
|
||||
box-shadow: inset 0 0 5px var(--button-secondary-active-color);
|
||||
}
|
||||
}
|
||||
|
||||
.iconLeft {
|
||||
transform: translate(calc(-1 * var(--button-side-icon-nudge)), 0);
|
||||
}
|
||||
|
||||
.iconRight {
|
||||
transform: translate(var(--button-side-icon-nudge), 0);
|
||||
}
|
||||
|
||||
.rule {
|
||||
composes: -border;
|
||||
background-color: var(--button-secondary-border-color);
|
||||
width: 1px;
|
||||
border-color: var(--button-secondary-border-color);
|
||||
}
|
||||
|
||||
.icon {
|
||||
composes: secondary;
|
||||
padding: 0;
|
||||
aspect-ratio: 1/1;
|
||||
justify-items: center;
|
||||
}
|
||||
75
assets/module.css/config_element.module.css
Normal file
75
assets/module.css/config_element.module.css
Normal file
@@ -0,0 +1,75 @@
|
||||
.container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.name {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.notDefault {
|
||||
composes: name;
|
||||
color: var(--header-tint);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.value {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.select {
|
||||
width: 100%;
|
||||
background: var(--button-secondary-bg-color);
|
||||
border-color: var(--button-secondary-border-color);
|
||||
color: var(--button-secondary-color);
|
||||
|
||||
& option {
|
||||
background-color: var(--button-secondary-bg-color-top);
|
||||
border-color: var(--button-secondary-border-color);
|
||||
color: var(--button-secondary-color);
|
||||
}
|
||||
}
|
||||
|
||||
.toggle {
|
||||
display: flex;
|
||||
|
||||
& label {
|
||||
$border: 1px solid #bbb;
|
||||
|
||||
flex: 1;
|
||||
cursor: pointer;
|
||||
border: $border;
|
||||
border-right-width: 0;
|
||||
border-top-left-radius: var(--header-border-radius);
|
||||
border-bottom-left-radius: var(--header-border-radius);
|
||||
padding: 0 1em;
|
||||
color: #777;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
|
||||
& ~ label {
|
||||
border-left: $border;
|
||||
border-right-width: 1px;
|
||||
border-radius: 0 var(--header-border-radius) var(--header-border-radius) 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: hsl(208deg 100% 43% / 10%);
|
||||
}
|
||||
}
|
||||
|
||||
& input {
|
||||
display: none;
|
||||
|
||||
&:checked + label {
|
||||
border-color: var(--header-tint);
|
||||
background: var(--header-tint);
|
||||
color: #fff;
|
||||
|
||||
& ~ label {
|
||||
border-left-width: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
42
assets/module.css/editor.module.css
Normal file
42
assets/module.css/editor.module.css
Normal file
@@ -0,0 +1,42 @@
|
||||
.-autoSize {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.-bodyMonospace {
|
||||
font-size: inherit;
|
||||
line-height: 1.25;
|
||||
|
||||
/* http://code.stephenmorley.org/html-and-css/fixing-browsers-broken-monospace-font-handling/
|
||||
* ACE uses Monaco, Menlo, "Ubuntu Mono", Consolas, source-code-pro, monospace;
|
||||
*/
|
||||
font-family: 'Source Code Pro', monospace;
|
||||
font-optical-sizing: auto;
|
||||
}
|
||||
|
||||
.container {
|
||||
composes: -autoSize;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.-advanced {
|
||||
composes: -bodyMonospace -autoSize;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.ace {
|
||||
composes: -advanced;
|
||||
}
|
||||
|
||||
.monaco {
|
||||
composes: -advanced;
|
||||
}
|
||||
|
||||
.simple {
|
||||
composes: -advanced;
|
||||
border: none;
|
||||
color: inherit;
|
||||
background-color: inherit;
|
||||
}
|
||||
12
assets/module.css/header.module.css
Normal file
12
assets/module.css/header.module.css
Normal file
@@ -0,0 +1,12 @@
|
||||
.container,
|
||||
.left,
|
||||
.right {
|
||||
display: flex;
|
||||
gap: 0.5em;
|
||||
}
|
||||
|
||||
.container {
|
||||
font-size: var(--secondary-font-size);
|
||||
padding: 1.25em 0;
|
||||
justify-content: space-between;
|
||||
}
|
||||
4
assets/module.css/icon.module.css
Normal file
4
assets/module.css/icon.module.css
Normal file
@@ -0,0 +1,4 @@
|
||||
.icon {
|
||||
fill: currentcolor;
|
||||
display: block;
|
||||
}
|
||||
231
assets/module.css/index.module.css
Normal file
231
assets/module.css/index.module.css
Normal file
@@ -0,0 +1,231 @@
|
||||
/* Non-theme variables */
|
||||
:root {
|
||||
/* Fonts */
|
||||
--primary-font: 'Open Sans', sans-serif;
|
||||
--primary-font-size: 18px;
|
||||
--secondary-font-size: calc(0.75 * var(--primary-font-size));
|
||||
|
||||
/* Header */
|
||||
--header-transition: 0.2s ease-in-out;
|
||||
--header-border-radius: 4px;
|
||||
|
||||
/* Border */
|
||||
--border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
@define-mixin light-theme-vars {
|
||||
--positive-luminance: white;
|
||||
--negative-luminance: black;
|
||||
|
||||
/* Fonts */
|
||||
--font-color: #444;
|
||||
--font-color-high-contrast: var(--negative-luminance);
|
||||
|
||||
/* Links */
|
||||
--link-color: #00e;
|
||||
--link-color-visited: #551a8b;
|
||||
|
||||
/* Background */
|
||||
--background-color: #e1e1db;
|
||||
--background-color-high-contrast: var(--positive-luminance);
|
||||
|
||||
/* Code */
|
||||
--code-background-color: #eee;
|
||||
|
||||
/* Border */
|
||||
--border-color: #bbb;
|
||||
|
||||
/* Header */
|
||||
--header-link-color: var(--negative-luminance);
|
||||
--header-main-border: #dedede;
|
||||
--header-tint: #428bca;
|
||||
--header-accent-border: #bdbdbd;
|
||||
|
||||
/* The big red button */
|
||||
--button-primary-color: var(--positive-luminance);
|
||||
--button-primary-bg-color: #a42;
|
||||
--button-primary-border-color: #80331a;
|
||||
--button-primary-bg-color-light: color-mix(
|
||||
in hsl,
|
||||
var(--button-primary-bg-color),
|
||||
var(--positive-luminance)
|
||||
);
|
||||
--button-primary-border-color-light: color-mix(
|
||||
in hsl,
|
||||
var(--button-primary-border-color),
|
||||
var(--positive-luminance)
|
||||
);
|
||||
|
||||
/* Clicked */
|
||||
--button-primary-active-color: color-mix(
|
||||
in hsl,
|
||||
var(--positive-luminance),
|
||||
var(--negative-luminance) 30%
|
||||
);
|
||||
|
||||
/* Not the big red button */
|
||||
--button-secondary-color: #444;
|
||||
--button-secondary-bg-color-top: #fff;
|
||||
--button-secondary-bg-color-bottom: #f9f9f9;
|
||||
--button-secondary-bg-color: linear-gradient(
|
||||
to bottom,
|
||||
var(--button-secondary-bg-color-top),
|
||||
var(--button-secondary-bg-color-bottom)
|
||||
);
|
||||
--button-secondary-border-color: color-mix(
|
||||
in hsl,
|
||||
var(--button-secondary-bg-color-bottom),
|
||||
var(--negative-luminance) 20%
|
||||
);
|
||||
|
||||
/* Disabled */
|
||||
--button-secondary-bg-color-light: color-mix(
|
||||
in hsl,
|
||||
var(--button-secondary-bg-color-bottom),
|
||||
var(--positive-luminance)
|
||||
);
|
||||
--button-secondary-border-color-light: color-mix(
|
||||
in hsl,
|
||||
var(--button-secondary-border-color),
|
||||
var(--positive-luminance)
|
||||
);
|
||||
--button-secondary-color-light: color-mix(
|
||||
in hsl,
|
||||
var(--button-secondary-color),
|
||||
var(--positive-luminance)
|
||||
);
|
||||
|
||||
/* Clicked */
|
||||
--button-secondary-active-color: color-mix(
|
||||
in hsl,
|
||||
var(--negative-luminance),
|
||||
var(--positive-luminance) 30%
|
||||
);
|
||||
|
||||
/* Output tabs */
|
||||
--output-background-tab: #fcfcfc;
|
||||
--output-current-tab: #f9ffff;
|
||||
|
||||
/* Output compiler highlighting */
|
||||
--output-highlight-warning-color: #f79a06;
|
||||
--output-highlight-warning-bg-color: inherit;
|
||||
--output-highlight-error-color: #bf1b1b;
|
||||
--output-highlight-error-bg-color: inherit;
|
||||
}
|
||||
|
||||
@define-mixin dark-theme-vars {
|
||||
--positive-luminance: black;
|
||||
--negative-luminance: white;
|
||||
|
||||
/* Fonts */
|
||||
--font-color: #dcdbd8;
|
||||
--font-color-high-contrast: #dcdbd8;
|
||||
|
||||
/* Links */
|
||||
--link-color: #b2dcff;
|
||||
--link-color-visited: #eecaff;
|
||||
|
||||
/* Background */
|
||||
--background-color: #444;
|
||||
--background-color-high-contrast: #181818;
|
||||
|
||||
/* Code */
|
||||
--code-background-color: #3c3c3c;
|
||||
|
||||
/* Border */
|
||||
--border-color: #5e5e5e;
|
||||
|
||||
/* Header */
|
||||
--header-main-border: #363b3d;
|
||||
--header-accent-border: #bdbdbd;
|
||||
|
||||
/* The big red button */
|
||||
--button-primary-color: #dcdbd8;
|
||||
--button-primary-bg-color: #5d2310;
|
||||
--button-primary-border-color: #612714;
|
||||
|
||||
/* Not the big red button */
|
||||
--button-secondary-color: #dcdbd8;
|
||||
--button-secondary-bg-color-top: #17191a;
|
||||
--button-secondary-bg-color-bottom: #1a1c1d;
|
||||
--button-secondary-border-color: color-mix(
|
||||
in hsl,
|
||||
var(--button-secondary-bg-color-bottom),
|
||||
var(--negative-luminance) 40%
|
||||
);
|
||||
|
||||
/* Output tabs */
|
||||
--output-background-tab: #343434;
|
||||
--output-current-tab: #191b1c;
|
||||
|
||||
/* Output compiler highlighting */
|
||||
--output-highlight-warning-color: #ffe800;
|
||||
--output-highlight-warning-bg-color: #330;
|
||||
--output-highlight-error-color: #f5f5f5;
|
||||
--output-highlight-error-bg-color: #820000;
|
||||
}
|
||||
|
||||
:root,
|
||||
[data-theme='light']:root {
|
||||
@mixin light-theme-vars;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
@mixin dark-theme-vars;
|
||||
}
|
||||
}
|
||||
|
||||
[data-theme='dark']:root {
|
||||
@mixin dark-theme-vars;
|
||||
}
|
||||
|
||||
/* Modify normalized styles */
|
||||
button,
|
||||
input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
font-family: var(--primary-font);
|
||||
font-optical-sizing: auto;
|
||||
}
|
||||
|
||||
html {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--link-color);
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: var(--link-color-visited);
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
body {
|
||||
color: var(--font-color);
|
||||
background-color: var(--background-color);
|
||||
padding: 0 1em;
|
||||
font-family: var(--primary-font);
|
||||
font-optical-sizing: auto;
|
||||
font-size: var(--primary-font-size);
|
||||
}
|
||||
|
||||
/* stylelint-disable-next-line selector-class-pattern */
|
||||
:global(.language-rust_errors) {
|
||||
& :global(.warning) {
|
||||
color: var(--output-highlight-warning-color);
|
||||
background-color: var(--output-highlight-warning-bg-color);
|
||||
}
|
||||
|
||||
& :global(.error) {
|
||||
color: var(--output-highlight-error-color);
|
||||
background-color: var(--output-highlight-error-bg-color);
|
||||
}
|
||||
}
|
||||
23
assets/module.css/loader.module.css
Normal file
23
assets/module.css/loader.module.css
Normal file
@@ -0,0 +1,23 @@
|
||||
@keyframes loader-fade {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
75% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.dot {
|
||||
opacity: 0;
|
||||
animation: loader-fade 1s;
|
||||
animation-iteration-count: infinite;
|
||||
|
||||
&:nth-child(2) {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
}
|
||||
22
assets/module.css/menu_group.module.css
Normal file
22
assets/module.css/menu_group.module.css
Normal file
@@ -0,0 +1,22 @@
|
||||
.container {
|
||||
padding: 0.75em 1em 0;
|
||||
width: 27em;
|
||||
line-height: normal;
|
||||
|
||||
&:last-child {
|
||||
padding-bottom: 0.75em;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0;
|
||||
border-bottom: 1px solid var(--header-main-border);
|
||||
padding-bottom: 10px;
|
||||
font-weight: 700;
|
||||
font-size: var(--secondary-font-size);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 1em 0.25em;
|
||||
}
|
||||
7
assets/module.css/menu_item.module.css
Normal file
7
assets/module.css/menu_item.module.css
Normal file
@@ -0,0 +1,7 @@
|
||||
.container {
|
||||
margin-bottom: 1em;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
53
assets/module.css/output.module.css
Normal file
53
assets/module.css/output.module.css
Normal file
@@ -0,0 +1,53 @@
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.tab {
|
||||
color: var(--font-color);
|
||||
flex: 1 1 auto;
|
||||
cursor: pointer;
|
||||
border: var(--border);
|
||||
border-right: none;
|
||||
background-color: var(--output-background-tab);
|
||||
line-height: 1.5;
|
||||
|
||||
&:last-of-type {
|
||||
border-right: var(--border);
|
||||
}
|
||||
}
|
||||
|
||||
.tabSelected {
|
||||
composes: tab;
|
||||
cursor: default;
|
||||
border-bottom: none;
|
||||
background-color: var(--output-current-tab);
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
.tabClose {
|
||||
composes: tab;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.body {
|
||||
border: var(--border);
|
||||
border-top: none;
|
||||
background-color: var(--output-current-tab);
|
||||
padding: 0.5em;
|
||||
height: 100%;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
.stdin {
|
||||
margin-top: 0.25em;
|
||||
}
|
||||
32
assets/module.css/output/execute.module.css
Normal file
32
assets/module.css/output/execute.module.css
Normal file
@@ -0,0 +1,32 @@
|
||||
.-buttonReset {
|
||||
color: var(--font-color);
|
||||
border: none;
|
||||
background: inherit;
|
||||
background-color: transparent; /* IE 11 */
|
||||
padding: 0;
|
||||
font: inherit;
|
||||
line-height: inherit;
|
||||
text-align: inherit;
|
||||
}
|
||||
|
||||
.-buttonAsLink {
|
||||
composes: -buttonReset;
|
||||
cursor: pointer;
|
||||
color: var(--link-color);
|
||||
user-select: text;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.addMain {
|
||||
composes: -buttonAsLink;
|
||||
}
|
||||
|
||||
.table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.tdAndTh {
|
||||
border: 1px solid #dddddd;
|
||||
text-align: left;
|
||||
padding: 8px;
|
||||
}
|
||||
21
assets/module.css/output/header.module.css
Normal file
21
assets/module.css/output/header.module.css
Normal file
@@ -0,0 +1,21 @@
|
||||
.container {
|
||||
display: flex;
|
||||
color: var(--font-color);
|
||||
white-space: nowrap;
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
flex: 1 1 auto;
|
||||
margin: auto;
|
||||
border-top: var(--border);
|
||||
content: '';
|
||||
}
|
||||
|
||||
&::before {
|
||||
margin-right: 2%;
|
||||
}
|
||||
|
||||
&::after {
|
||||
margin-left: 2%;
|
||||
}
|
||||
}
|
||||
14
assets/module.css/output/section.module.css
Normal file
14
assets/module.css/output/section.module.css
Normal file
@@ -0,0 +1,14 @@
|
||||
.-bodyMonospace {
|
||||
font-size: inherit;
|
||||
line-height: 1.25;
|
||||
|
||||
/* http://code.stephenmorley.org/html-and-css/fixing-browsers-broken-monospace-font-handling/
|
||||
* ACE uses Monaco, Menlo, "Ubuntu Mono", Consolas, source-code-pro, monospace;
|
||||
*/
|
||||
font-family: 'Source Code Pro', monospace;
|
||||
font-optical-sizing: auto;
|
||||
}
|
||||
|
||||
.code {
|
||||
composes: -bodyMonospace;
|
||||
}
|
||||
51
assets/module.css/output/share.module.css
Normal file
51
assets/module.css/output/share.module.css
Normal file
@@ -0,0 +1,51 @@
|
||||
.-buttonReset {
|
||||
color: var(--font-color);
|
||||
border: none;
|
||||
background: inherit;
|
||||
background-color: transparent; /* IE 11 */
|
||||
padding: 0;
|
||||
font: inherit;
|
||||
line-height: inherit;
|
||||
text-align: inherit;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
|
||||
--copied-duration: 1s ease-in-out;
|
||||
}
|
||||
|
||||
.button {
|
||||
composes: -buttonReset;
|
||||
transition: color var(--copied-duration);
|
||||
cursor: pointer;
|
||||
margin: 0 0.25em 0 0.5em;
|
||||
}
|
||||
|
||||
.text {
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transition:
|
||||
visibility var(--copied-duration),
|
||||
opacity var(--copied-duration),
|
||||
color var(--copied-duration);
|
||||
}
|
||||
|
||||
.active {
|
||||
composes: container;
|
||||
|
||||
& .button {
|
||||
transition: color 0s;
|
||||
color: green;
|
||||
}
|
||||
|
||||
& .text {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
transition:
|
||||
visibility 0s,
|
||||
opacity 0s,
|
||||
color 0s;
|
||||
color: green;
|
||||
}
|
||||
}
|
||||
69
assets/module.css/playground.module.css
Normal file
69
assets/module.css/playground.module.css
Normal file
@@ -0,0 +1,69 @@
|
||||
.-autoSize {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-bottom: 1em;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.-resizeableArea {
|
||||
composes: -autoSize;
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.resizeableAreaRowOutputUnfocused {
|
||||
composes: -resizeableArea;
|
||||
grid-template-rows: 1fr auto;
|
||||
}
|
||||
|
||||
.resizeableAreaRowOutputFocused {
|
||||
composes: -resizeableArea;
|
||||
grid-template-rows: 1fr 12px 1fr;
|
||||
}
|
||||
|
||||
.resizeableAreaColumnOutputUnfocused {
|
||||
composes: -resizeableArea;
|
||||
grid-template-columns: 1fr auto;
|
||||
}
|
||||
|
||||
.resizeableAreaColumnOutputFocused {
|
||||
composes: -resizeableArea;
|
||||
grid-template-columns: 1fr 12px 1fr;
|
||||
}
|
||||
|
||||
.-gutter {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.splitRowsGutter {
|
||||
composes: -gutter;
|
||||
cursor: row-resize;
|
||||
}
|
||||
|
||||
.splitRowsGutterHandle {
|
||||
transform: rotate(90deg);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.splitColumnsGutter {
|
||||
composes: -gutter;
|
||||
cursor: col-resize;
|
||||
}
|
||||
|
||||
.editor {
|
||||
composes: -autoSize;
|
||||
border: 4px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.output {
|
||||
composes: -autoSize;
|
||||
}
|
||||
35
assets/module.css/pop_button.module.css
Normal file
35
assets/module.css/pop_button.module.css
Normal file
@@ -0,0 +1,35 @@
|
||||
$arrow-height: 10px;
|
||||
$arrow-width: 20px;
|
||||
|
||||
.container {
|
||||
z-index: 10;
|
||||
font-size: var(--secondary-font-size);
|
||||
|
||||
& button:enabled {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.arrow {
|
||||
fill: var(--background-color-high-contrast);
|
||||
}
|
||||
|
||||
.-content {
|
||||
margin: $arrow-height;
|
||||
box-shadow:
|
||||
5px 5px 20px -3px rgb(0 0 0 / 25%),
|
||||
0 0 5px -2px rgb(0 0 0 / 90%);
|
||||
border-radius: var(--header-border-radius);
|
||||
background: var(--background-color-high-contrast);
|
||||
color: var(--font-color);
|
||||
}
|
||||
|
||||
.contentBottom {
|
||||
composes: -content;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.contentTop {
|
||||
composes: -content;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
64
assets/module.css/selectable_menu_item.module.css
Normal file
64
assets/module.css/selectable_menu_item.module.css
Normal file
@@ -0,0 +1,64 @@
|
||||
.-buttonReset {
|
||||
color: var(--font-color);
|
||||
border: none;
|
||||
background: inherit;
|
||||
background-color: transparent; /* IE 11 */
|
||||
padding: 0;
|
||||
font: inherit;
|
||||
line-height: inherit;
|
||||
text-align: inherit;
|
||||
}
|
||||
|
||||
.-menuItemFullButton {
|
||||
composes: -buttonReset;
|
||||
transition: color var(--header-transition);
|
||||
width: 100%;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
.-menuItemTitle {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.container {
|
||||
composes: -menuItemFullButton;
|
||||
|
||||
&:hover {
|
||||
color: var(--header-tint);
|
||||
}
|
||||
}
|
||||
|
||||
.selected {
|
||||
composes: container;
|
||||
color: var(--header-tint);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.name {
|
||||
composes: -menuItemTitle;
|
||||
}
|
||||
|
||||
.description {
|
||||
padding-left: 2em;
|
||||
}
|
||||
|
||||
.checkmark {
|
||||
opacity: 0;
|
||||
transition: opacity 0.15s ease-in-out;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
.selected .checkmark,
|
||||
.selected:hover .checkmark {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.container:hover .checkmark {
|
||||
opacity: 0.5;
|
||||
color: var(--header-tint);
|
||||
}
|
||||
128
assets/module.postcss/button_set.module.css
Normal file
128
assets/module.postcss/button_set.module.css
Normal file
File diff suppressed because one or more lines are too long
1
assets/module.postcss/button_set.module.css.map
Normal file
1
assets/module.postcss/button_set.module.css.map
Normal file
@@ -0,0 +1 @@
|
||||
{"-buttonReset":"_-buttonReset_14uvt_4","set":"_set_14uvt_15","-border":"_-border_14uvt_19","-button":"_-button_14uvt_4 _-buttonReset_14uvt_4","small":"_small_14uvt_60","primary":"_primary_14uvt_67 _-border_14uvt_19 _-button_14uvt_4 _-buttonReset_14uvt_4","secondary":"_secondary_14uvt_88 _-border_14uvt_19 _-button_14uvt_4 _-buttonReset_14uvt_4","iconLeft":"_iconLeft_14uvt_115","iconRight":"_iconRight_14uvt_119","rule":"_rule_14uvt_123 _-border_14uvt_19","icon":"_icon_14uvt_115 _secondary_14uvt_88 _-border_14uvt_19 _-button_14uvt_4 _-buttonReset_14uvt_4"}
|
||||
75
assets/module.postcss/config_element.module.css
Normal file
75
assets/module.postcss/config_element.module.css
Normal file
@@ -0,0 +1,75 @@
|
||||
._container_e4oy5_1 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
._name_e4oy5_6 {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
._notDefault_e4oy5_10 {
|
||||
color: var(--header-tint);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
._value_e4oy5_16 {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
._select_e4oy5_20 {
|
||||
width: 100%;
|
||||
background: var(--button-secondary-bg-color);
|
||||
border-color: var(--button-secondary-border-color);
|
||||
color: var(--button-secondary-color);
|
||||
|
||||
& option {
|
||||
background-color: var(--button-secondary-bg-color-top);
|
||||
border-color: var(--button-secondary-border-color);
|
||||
color: var(--button-secondary-color);
|
||||
}
|
||||
}
|
||||
|
||||
._toggle_e4oy5_33 {
|
||||
display: flex;
|
||||
|
||||
& label {
|
||||
|
||||
flex: 1;
|
||||
cursor: pointer;
|
||||
border: 1px solid #bbb;
|
||||
border-right-width: 0;
|
||||
border-top-left-radius: var(--header-border-radius);
|
||||
border-bottom-left-radius: var(--header-border-radius);
|
||||
padding: 0 1em;
|
||||
color: #777;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
|
||||
& ~ label {
|
||||
border-left: 1px solid #bbb;
|
||||
border-right-width: 1px;
|
||||
border-radius: 0 var(--header-border-radius) var(--header-border-radius) 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: hsl(208deg 100% 43% / 10%);
|
||||
}
|
||||
}
|
||||
|
||||
& input {
|
||||
display: none;
|
||||
|
||||
&:checked + label {
|
||||
border-color: var(--header-tint);
|
||||
background: var(--header-tint);
|
||||
color: #fff;
|
||||
|
||||
& ~ label {
|
||||
border-left-width: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL21vZHVsZS5jc3MvY29uZmlnX2VsZW1lbnQubW9kdWxlLmNzcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTtFQUNFLGFBQWE7RUFDYixtQkFBbUI7QUFDckI7O0FBRUE7RUFDRSxPQUFPO0FBQ1Q7O0FBRUE7RUFFRSx5QkFBeUI7RUFDekIsZ0JBQWdCO0FBQ2xCOztBQUVBO0VBQ0UsT0FBTztBQUNUOztBQUVBO0VBQ0UsV0FBVztFQUNYLDRDQUE0QztFQUM1QyxrREFBa0Q7RUFDbEQsb0NBQW9DOztFQUVwQztJQUNFLHNEQUFzRDtJQUN0RCxrREFBa0Q7SUFDbEQsb0NBQW9DO0VBQ3RDO0FBQ0Y7O0FBRUE7RUFDRSxhQUFhOztFQUViOztJQUdFLE9BQU87SUFDUCxlQUFlO0lBQ2Ysc0JBQWU7SUFDZixxQkFBcUI7SUFDckIsbURBQW1EO0lBQ25ELHNEQUFzRDtJQUN0RCxjQUFjO0lBQ2QsV0FBVztJQUNYLGdCQUFnQjtJQUNoQixrQkFBa0I7SUFDbEIseUJBQXlCOztJQUV6QjtNQUNFLDJCQUFvQjtNQUNwQix1QkFBdUI7TUFDdkIsMEVBQTBFO0lBQzVFOztJQUVBO01BQ0Usc0NBQXNDO0lBQ3hDO0VBQ0Y7O0VBRUE7SUFDRSxhQUFhOztJQUViO01BQ0UsZ0NBQWdDO01BQ2hDLDhCQUE4QjtNQUM5QixXQUFXOztNQUVYO1FBQ0Usb0JBQW9CO01BQ3RCO0lBQ0Y7RUFDRjtBQUNGIiwiZmlsZSI6ImNvbmZpZ19lbGVtZW50Lm1vZHVsZS5jc3MiLCJzb3VyY2VzQ29udGVudCI6WyIuY29udGFpbmVyIHtcbiAgZGlzcGxheTogZmxleDtcbiAgYWxpZ24taXRlbXM6IGNlbnRlcjtcbn1cblxuLm5hbWUge1xuICBmbGV4OiAxO1xufVxuXG4ubm90RGVmYXVsdCB7XG4gIGNvbXBvc2VzOiBuYW1lO1xuICBjb2xvcjogdmFyKC0taGVhZGVyLXRpbnQpO1xuICBmb250LXdlaWdodDogNjAwO1xufVxuXG4udmFsdWUge1xuICBmbGV4OiAxO1xufVxuXG4uc2VsZWN0IHtcbiAgd2lkdGg6IDEwMCU7XG4gIGJhY2tncm91bmQ6IHZhcigtLWJ1dHRvbi1zZWNvbmRhcnktYmctY29sb3IpO1xuICBib3JkZXItY29sb3I6IHZhcigtLWJ1dHRvbi1zZWNvbmRhcnktYm9yZGVyLWNvbG9yKTtcbiAgY29sb3I6IHZhcigtLWJ1dHRvbi1zZWNvbmRhcnktY29sb3IpO1xuXG4gICYgb3B0aW9uIHtcbiAgICBiYWNrZ3JvdW5kLWNvbG9yOiB2YXIoLS1idXR0b24tc2Vjb25kYXJ5LWJnLWNvbG9yLXRvcCk7XG4gICAgYm9yZGVyLWNvbG9yOiB2YXIoLS1idXR0b24tc2Vjb25kYXJ5LWJvcmRlci1jb2xvcik7XG4gICAgY29sb3I6IHZhcigtLWJ1dHRvbi1zZWNvbmRhcnktY29sb3IpO1xuICB9XG59XG5cbi50b2dnbGUge1xuICBkaXNwbGF5OiBmbGV4O1xuXG4gICYgbGFiZWwge1xuICAgICRib3JkZXI6IDFweCBzb2xpZCAjYmJiO1xuXG4gICAgZmxleDogMTtcbiAgICBjdXJzb3I6IHBvaW50ZXI7XG4gICAgYm9yZGVyOiAkYm9yZGVyO1xuICAgIGJvcmRlci1yaWdodC13aWR0aDogMDtcbiAgICBib3JkZXItdG9wLWxlZnQtcmFkaXVzOiB2YXIoLS1oZWFkZXItYm9yZGVyLXJhZGl1cyk7XG4gICAgYm9yZGVyLWJvdHRvbS1sZWZ0LXJhZGl1czogdmFyKC0taGVhZGVyLWJvcmRlci1yYWRpdXMpO1xuICAgIHBhZGRpbmc6IDAgMWVtO1xuICAgIGNvbG9yOiAjNzc3O1xuICAgIGZvbnQtd2VpZ2h0OiA2MDA7XG4gICAgdGV4dC1hbGlnbjogY2VudGVyO1xuICAgIHRleHQtdHJhbnNmb3JtOiB1cHBlcmNhc2U7XG5cbiAgICAmIH4gbGFiZWwge1xuICAgICAgYm9yZGVyLWxlZnQ6ICRib3JkZXI7XG4gICAgICBib3JkZXItcmlnaHQtd2lkdGg6IDFweDtcbiAgICAgIGJvcmRlci1yYWRpdXM6IDAgdmFyKC0taGVhZGVyLWJvcmRlci1yYWRpdXMpIHZhcigtLWhlYWRlci1ib3JkZXItcmFkaXVzKSAwO1xuICAgIH1cblxuICAgICY6aG92ZXIge1xuICAgICAgYmFja2dyb3VuZDogaHNsKDIwOGRlZyAxMDAlIDQzJSAvIDEwJSk7XG4gICAgfVxuICB9XG5cbiAgJiBpbnB1dCB7XG4gICAgZGlzcGxheTogbm9uZTtcblxuICAgICY6Y2hlY2tlZCArIGxhYmVsIHtcbiAgICAgIGJvcmRlci1jb2xvcjogdmFyKC0taGVhZGVyLXRpbnQpO1xuICAgICAgYmFja2dyb3VuZDogdmFyKC0taGVhZGVyLXRpbnQpO1xuICAgICAgY29sb3I6ICNmZmY7XG5cbiAgICAgICYgfiBsYWJlbCB7XG4gICAgICAgIGJvcmRlci1sZWZ0LXdpZHRoOiAwO1xuICAgICAgfVxuICAgIH1cbiAgfVxufVxuIl19 */
|
||||
1
assets/module.postcss/config_element.module.css.map
Normal file
1
assets/module.postcss/config_element.module.css.map
Normal file
@@ -0,0 +1 @@
|
||||
{"container":"_container_e4oy5_1","name":"_name_e4oy5_6","notDefault":"_notDefault_e4oy5_10 _name_e4oy5_6","value":"_value_e4oy5_16","select":"_select_e4oy5_20","toggle":"_toggle_e4oy5_33"}
|
||||
39
assets/module.postcss/editor.module.css
Normal file
39
assets/module.postcss/editor.module.css
Normal file
@@ -0,0 +1,39 @@
|
||||
._-autoSize_1m78w_1 {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
._-bodyMonospace_1m78w_8 {
|
||||
font-size: inherit;
|
||||
line-height: 1.25;
|
||||
|
||||
/* http://code.stephenmorley.org/html-and-css/fixing-browsers-broken-monospace-font-handling/
|
||||
* ACE uses Monaco, Menlo, "Ubuntu Mono", Consolas, source-code-pro, monospace;
|
||||
*/
|
||||
font-family: 'Source Code Pro', monospace;
|
||||
font-optical-sizing: auto;
|
||||
}
|
||||
|
||||
._container_1m78w_19 {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
._-advanced_1m78w_24 {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
._ace_1m78w_29 {
|
||||
}
|
||||
|
||||
._monaco_1m78w_33 {
|
||||
}
|
||||
|
||||
._simple_1m78w_37 {
|
||||
border: none;
|
||||
color: inherit;
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL21vZHVsZS5jc3MvZWRpdG9yLm1vZHVsZS5jc3MiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7RUFDRSxXQUFXO0VBQ1gsWUFBWTtFQUNaLFlBQVk7RUFDWixhQUFhO0FBQ2Y7O0FBRUE7RUFDRSxrQkFBa0I7RUFDbEIsaUJBQWlCOztFQUVqQjs7SUFFRTtFQUNGLHlDQUF5QztFQUN6Qyx5QkFBeUI7QUFDM0I7O0FBRUE7RUFFRSxrQkFBa0I7QUFDcEI7O0FBRUE7RUFFRSxrQkFBa0I7QUFDcEI7O0FBRUE7QUFFQTs7QUFFQTtBQUVBOztBQUVBO0VBRUUsWUFBWTtFQUNaLGNBQWM7RUFDZCx5QkFBeUI7QUFDM0IiLCJmaWxlIjoiZWRpdG9yLm1vZHVsZS5jc3MiLCJzb3VyY2VzQ29udGVudCI6WyIuLWF1dG9TaXplIHtcbiAgd2lkdGg6IDEwMCU7XG4gIG1pbi13aWR0aDogMDtcbiAgaGVpZ2h0OiAxMDAlO1xuICBtaW4taGVpZ2h0OiAwO1xufVxuXG4uLWJvZHlNb25vc3BhY2Uge1xuICBmb250LXNpemU6IGluaGVyaXQ7XG4gIGxpbmUtaGVpZ2h0OiAxLjI1O1xuXG4gIC8qIGh0dHA6Ly9jb2RlLnN0ZXBoZW5tb3JsZXkub3JnL2h0bWwtYW5kLWNzcy9maXhpbmctYnJvd3NlcnMtYnJva2VuLW1vbm9zcGFjZS1mb250LWhhbmRsaW5nL1xuICAgKiBBQ0UgdXNlcyBNb25hY28sIE1lbmxvLCBcIlVidW50dSBNb25vXCIsIENvbnNvbGFzLCBzb3VyY2UtY29kZS1wcm8sIG1vbm9zcGFjZTtcbiAgICovXG4gIGZvbnQtZmFtaWx5OiAnU291cmNlIENvZGUgUHJvJywgbW9ub3NwYWNlO1xuICBmb250LW9wdGljYWwtc2l6aW5nOiBhdXRvO1xufVxuXG4uY29udGFpbmVyIHtcbiAgY29tcG9zZXM6IC1hdXRvU2l6ZTtcbiAgcG9zaXRpb246IHJlbGF0aXZlO1xufVxuXG4uLWFkdmFuY2VkIHtcbiAgY29tcG9zZXM6IC1ib2R5TW9ub3NwYWNlIC1hdXRvU2l6ZTtcbiAgcG9zaXRpb246IGFic29sdXRlO1xufVxuXG4uYWNlIHtcbiAgY29tcG9zZXM6IC1hZHZhbmNlZDtcbn1cblxuLm1vbmFjbyB7XG4gIGNvbXBvc2VzOiAtYWR2YW5jZWQ7XG59XG5cbi5zaW1wbGUge1xuICBjb21wb3NlczogLWFkdmFuY2VkO1xuICBib3JkZXI6IG5vbmU7XG4gIGNvbG9yOiBpbmhlcml0O1xuICBiYWNrZ3JvdW5kLWNvbG9yOiBpbmhlcml0O1xufVxuIl19 */
|
||||
1
assets/module.postcss/editor.module.css.map
Normal file
1
assets/module.postcss/editor.module.css.map
Normal file
@@ -0,0 +1 @@
|
||||
{"-autoSize":"_-autoSize_1m78w_1","-bodyMonospace":"_-bodyMonospace_1m78w_8","container":"_container_1m78w_19 _-autoSize_1m78w_1","-advanced":"_-advanced_1m78w_24 _-bodyMonospace_1m78w_8 _-autoSize_1m78w_1","ace":"_ace_1m78w_29 _-advanced_1m78w_24 _-bodyMonospace_1m78w_8 _-autoSize_1m78w_1","monaco":"_monaco_1m78w_33 _-advanced_1m78w_24 _-bodyMonospace_1m78w_8 _-autoSize_1m78w_1","simple":"_simple_1m78w_37 _-advanced_1m78w_24 _-bodyMonospace_1m78w_8 _-autoSize_1m78w_1"}
|
||||
14
assets/module.postcss/header.module.css
Normal file
14
assets/module.postcss/header.module.css
Normal file
@@ -0,0 +1,14 @@
|
||||
._container_qgdhb_1,
|
||||
._left_qgdhb_2,
|
||||
._right_qgdhb_3 {
|
||||
display: flex;
|
||||
gap: 0.5em;
|
||||
}
|
||||
|
||||
._container_qgdhb_1 {
|
||||
font-size: var(--secondary-font-size);
|
||||
padding: 1.25em 0;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL21vZHVsZS5jc3MvaGVhZGVyLm1vZHVsZS5jc3MiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7OztFQUdFLGFBQWE7RUFDYixVQUFVO0FBQ1o7O0FBRUE7RUFDRSxxQ0FBcUM7RUFDckMsaUJBQWlCO0VBQ2pCLDhCQUE4QjtBQUNoQyIsImZpbGUiOiJoZWFkZXIubW9kdWxlLmNzcyIsInNvdXJjZXNDb250ZW50IjpbIi5jb250YWluZXIsXG4ubGVmdCxcbi5yaWdodCB7XG4gIGRpc3BsYXk6IGZsZXg7XG4gIGdhcDogMC41ZW07XG59XG5cbi5jb250YWluZXIge1xuICBmb250LXNpemU6IHZhcigtLXNlY29uZGFyeS1mb250LXNpemUpO1xuICBwYWRkaW5nOiAxLjI1ZW0gMDtcbiAganVzdGlmeS1jb250ZW50OiBzcGFjZS1iZXR3ZWVuO1xufVxuIl19 */
|
||||
1
assets/module.postcss/header.module.css.map
Normal file
1
assets/module.postcss/header.module.css.map
Normal file
@@ -0,0 +1 @@
|
||||
{"container":"_container_qgdhb_1","left":"_left_qgdhb_2","right":"_right_qgdhb_3"}
|
||||
6
assets/module.postcss/icon.module.css
Normal file
6
assets/module.postcss/icon.module.css
Normal file
@@ -0,0 +1,6 @@
|
||||
._icon_imjug_1 {
|
||||
fill: currentcolor;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL21vZHVsZS5jc3MvaWNvbi5tb2R1bGUuY3NzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBO0VBQ0Usa0JBQWtCO0VBQ2xCLGNBQWM7QUFDaEIiLCJmaWxlIjoiaWNvbi5tb2R1bGUuY3NzIiwic291cmNlc0NvbnRlbnQiOlsiLmljb24ge1xuICBmaWxsOiBjdXJyZW50Y29sb3I7XG4gIGRpc3BsYXk6IGJsb2NrO1xufVxuIl19 */
|
||||
1
assets/module.postcss/icon.module.css.map
Normal file
1
assets/module.postcss/icon.module.css.map
Normal file
@@ -0,0 +1 @@
|
||||
{"icon":"_icon_imjug_1"}
|
||||
240
assets/module.postcss/index.module.css
Normal file
240
assets/module.postcss/index.module.css
Normal file
File diff suppressed because one or more lines are too long
1
assets/module.postcss/index.module.css.map
Normal file
1
assets/module.postcss/index.module.css.map
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
25
assets/module.postcss/loader.module.css
Normal file
25
assets/module.postcss/loader.module.css
Normal file
@@ -0,0 +1,25 @@
|
||||
@keyframes _loader-fade_jthdv_1 {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
75% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
._dot_jthdv_11 {
|
||||
opacity: 0;
|
||||
animation: _loader-fade_jthdv_1 1s;
|
||||
animation-iteration-count: infinite;
|
||||
|
||||
&:nth-child(2) {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL21vZHVsZS5jc3MvbG9hZGVyLm1vZHVsZS5jc3MiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7RUFDRTtJQUNFLFVBQVU7RUFDWjs7RUFFQTtJQUNFLFVBQVU7RUFDWjtBQUNGOztBQUVBO0VBQ0UsVUFBVTtFQUNWLGtDQUF5QjtFQUN6QixtQ0FBbUM7O0VBRW5DO0lBQ0UscUJBQXFCO0VBQ3ZCOztFQUVBO0lBQ0UscUJBQXFCO0VBQ3ZCO0FBQ0YiLCJmaWxlIjoibG9hZGVyLm1vZHVsZS5jc3MiLCJzb3VyY2VzQ29udGVudCI6WyJAa2V5ZnJhbWVzIGxvYWRlci1mYWRlIHtcbiAgMCUge1xuICAgIG9wYWNpdHk6IDA7XG4gIH1cblxuICA3NSUge1xuICAgIG9wYWNpdHk6IDE7XG4gIH1cbn1cblxuLmRvdCB7XG4gIG9wYWNpdHk6IDA7XG4gIGFuaW1hdGlvbjogbG9hZGVyLWZhZGUgMXM7XG4gIGFuaW1hdGlvbi1pdGVyYXRpb24tY291bnQ6IGluZmluaXRlO1xuXG4gICY6bnRoLWNoaWxkKDIpIHtcbiAgICBhbmltYXRpb24tZGVsYXk6IDAuMnM7XG4gIH1cblxuICAmOm50aC1jaGlsZCgzKSB7XG4gICAgYW5pbWF0aW9uLWRlbGF5OiAwLjRzO1xuICB9XG59XG4iXX0= */
|
||||
1
assets/module.postcss/loader.module.css.map
Normal file
1
assets/module.postcss/loader.module.css.map
Normal file
@@ -0,0 +1 @@
|
||||
{"dot":"_dot_jthdv_11","loader-fade":"_loader-fade_jthdv_1"}
|
||||
24
assets/module.postcss/menu_group.module.css
Normal file
24
assets/module.postcss/menu_group.module.css
Normal file
@@ -0,0 +1,24 @@
|
||||
._container_18py9_1 {
|
||||
padding: 0.75em 1em 0;
|
||||
width: 27em;
|
||||
line-height: normal;
|
||||
|
||||
&:last-child {
|
||||
padding-bottom: 0.75em;
|
||||
}
|
||||
}
|
||||
|
||||
._title_18py9_11 {
|
||||
margin: 0;
|
||||
border-bottom: 1px solid var(--header-main-border);
|
||||
padding-bottom: 10px;
|
||||
font-weight: 700;
|
||||
font-size: var(--secondary-font-size);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
._content_18py9_20 {
|
||||
padding: 1em 0.25em;
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL21vZHVsZS5jc3MvbWVudV9ncm91cC5tb2R1bGUuY3NzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBO0VBQ0UscUJBQXFCO0VBQ3JCLFdBQVc7RUFDWCxtQkFBbUI7O0VBRW5CO0lBQ0Usc0JBQXNCO0VBQ3hCO0FBQ0Y7O0FBRUE7RUFDRSxTQUFTO0VBQ1Qsa0RBQWtEO0VBQ2xELG9CQUFvQjtFQUNwQixnQkFBZ0I7RUFDaEIscUNBQXFDO0VBQ3JDLHlCQUF5QjtBQUMzQjs7QUFFQTtFQUNFLG1CQUFtQjtBQUNyQiIsImZpbGUiOiJtZW51X2dyb3VwLm1vZHVsZS5jc3MiLCJzb3VyY2VzQ29udGVudCI6WyIuY29udGFpbmVyIHtcbiAgcGFkZGluZzogMC43NWVtIDFlbSAwO1xuICB3aWR0aDogMjdlbTtcbiAgbGluZS1oZWlnaHQ6IG5vcm1hbDtcblxuICAmOmxhc3QtY2hpbGQge1xuICAgIHBhZGRpbmctYm90dG9tOiAwLjc1ZW07XG4gIH1cbn1cblxuLnRpdGxlIHtcbiAgbWFyZ2luOiAwO1xuICBib3JkZXItYm90dG9tOiAxcHggc29saWQgdmFyKC0taGVhZGVyLW1haW4tYm9yZGVyKTtcbiAgcGFkZGluZy1ib3R0b206IDEwcHg7XG4gIGZvbnQtd2VpZ2h0OiA3MDA7XG4gIGZvbnQtc2l6ZTogdmFyKC0tc2Vjb25kYXJ5LWZvbnQtc2l6ZSk7XG4gIHRleHQtdHJhbnNmb3JtOiB1cHBlcmNhc2U7XG59XG5cbi5jb250ZW50IHtcbiAgcGFkZGluZzogMWVtIDAuMjVlbTtcbn1cbiJdfQ== */
|
||||
1
assets/module.postcss/menu_group.module.css.map
Normal file
1
assets/module.postcss/menu_group.module.css.map
Normal file
@@ -0,0 +1 @@
|
||||
{"container":"_container_18py9_1","title":"_title_18py9_11","content":"_content_18py9_20"}
|
||||
9
assets/module.postcss/menu_item.module.css
Normal file
9
assets/module.postcss/menu_item.module.css
Normal file
@@ -0,0 +1,9 @@
|
||||
._container_1i3fr_1 {
|
||||
margin-bottom: 1em;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL21vZHVsZS5jc3MvbWVudV9pdGVtLm1vZHVsZS5jc3MiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7RUFDRSxrQkFBa0I7O0VBRWxCO0lBQ0UsZ0JBQWdCO0VBQ2xCO0FBQ0YiLCJmaWxlIjoibWVudV9pdGVtLm1vZHVsZS5jc3MiLCJzb3VyY2VzQ29udGVudCI6WyIuY29udGFpbmVyIHtcbiAgbWFyZ2luLWJvdHRvbTogMWVtO1xuXG4gICY6bGFzdC1jaGlsZCB7XG4gICAgbWFyZ2luLWJvdHRvbTogMDtcbiAgfVxufVxuIl19 */
|
||||
1
assets/module.postcss/menu_item.module.css.map
Normal file
1
assets/module.postcss/menu_item.module.css.map
Normal file
@@ -0,0 +1 @@
|
||||
{"container":"_container_1i3fr_1"}
|
||||
53
assets/module.postcss/output.module.css
Normal file
53
assets/module.postcss/output.module.css
Normal file
@@ -0,0 +1,53 @@
|
||||
._container_d1av0_1 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
._tabs_d1av0_8 {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
._tab_d1av0_8 {
|
||||
color: var(--font-color);
|
||||
flex: 1 1 auto;
|
||||
cursor: pointer;
|
||||
border: var(--border);
|
||||
border-right: none;
|
||||
background-color: var(--output-background-tab);
|
||||
line-height: 1.5;
|
||||
|
||||
&:last-of-type {
|
||||
border-right: var(--border);
|
||||
}
|
||||
}
|
||||
|
||||
._tabSelected_d1av0_26 {
|
||||
cursor: default;
|
||||
border-bottom: none;
|
||||
background-color: var(--output-current-tab);
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
._tabClose_d1av0_37 {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
._body_d1av0_42 {
|
||||
border: var(--border);
|
||||
border-top: none;
|
||||
background-color: var(--output-current-tab);
|
||||
padding: 0.5em;
|
||||
height: 100%;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
._stdin_d1av0_51 {
|
||||
margin-top: 0.25em;
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL21vZHVsZS5jc3Mvb3V0cHV0Lm1vZHVsZS5jc3MiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7RUFDRSxhQUFhO0VBQ2Isc0JBQXNCO0VBQ3RCLFdBQVc7RUFDWCxZQUFZO0FBQ2Q7O0FBRUE7RUFDRSxhQUFhO0FBQ2Y7O0FBRUE7RUFDRSx3QkFBd0I7RUFDeEIsY0FBYztFQUNkLGVBQWU7RUFDZixxQkFBcUI7RUFDckIsa0JBQWtCO0VBQ2xCLDhDQUE4QztFQUM5QyxnQkFBZ0I7O0VBRWhCO0lBQ0UsMkJBQTJCO0VBQzdCO0FBQ0Y7O0FBRUE7RUFFRSxlQUFlO0VBQ2YsbUJBQW1CO0VBQ25CLDJDQUEyQzs7RUFFM0M7SUFDRSxhQUFhO0VBQ2Y7QUFDRjs7QUFFQTtFQUVFLGNBQWM7QUFDaEI7O0FBRUE7RUFDRSxxQkFBcUI7RUFDckIsZ0JBQWdCO0VBQ2hCLDJDQUEyQztFQUMzQyxjQUFjO0VBQ2QsWUFBWTtFQUNaLGdCQUFnQjtBQUNsQjs7QUFFQTtFQUNFLGtCQUFrQjtBQUNwQiIsImZpbGUiOiJvdXRwdXQubW9kdWxlLmNzcyIsInNvdXJjZXNDb250ZW50IjpbIi5jb250YWluZXIge1xuICBkaXNwbGF5OiBmbGV4O1xuICBmbGV4LWRpcmVjdGlvbjogY29sdW1uO1xuICB3aWR0aDogMTAwJTtcbiAgaGVpZ2h0OiAxMDAlO1xufVxuXG4udGFicyB7XG4gIGRpc3BsYXk6IGZsZXg7XG59XG5cbi50YWIge1xuICBjb2xvcjogdmFyKC0tZm9udC1jb2xvcik7XG4gIGZsZXg6IDEgMSBhdXRvO1xuICBjdXJzb3I6IHBvaW50ZXI7XG4gIGJvcmRlcjogdmFyKC0tYm9yZGVyKTtcbiAgYm9yZGVyLXJpZ2h0OiBub25lO1xuICBiYWNrZ3JvdW5kLWNvbG9yOiB2YXIoLS1vdXRwdXQtYmFja2dyb3VuZC10YWIpO1xuICBsaW5lLWhlaWdodDogMS41O1xuXG4gICY6bGFzdC1vZi10eXBlIHtcbiAgICBib3JkZXItcmlnaHQ6IHZhcigtLWJvcmRlcik7XG4gIH1cbn1cblxuLnRhYlNlbGVjdGVkIHtcbiAgY29tcG9zZXM6IHRhYjtcbiAgY3Vyc29yOiBkZWZhdWx0O1xuICBib3JkZXItYm90dG9tOiBub25lO1xuICBiYWNrZ3JvdW5kLWNvbG9yOiB2YXIoLS1vdXRwdXQtY3VycmVudC10YWIpO1xuXG4gICY6Zm9jdXMge1xuICAgIG91dGxpbmU6IG5vbmU7XG4gIH1cbn1cblxuLnRhYkNsb3NlIHtcbiAgY29tcG9zZXM6IHRhYjtcbiAgZmxleDogMCAwIGF1dG87XG59XG5cbi5ib2R5IHtcbiAgYm9yZGVyOiB2YXIoLS1ib3JkZXIpO1xuICBib3JkZXItdG9wOiBub25lO1xuICBiYWNrZ3JvdW5kLWNvbG9yOiB2YXIoLS1vdXRwdXQtY3VycmVudC10YWIpO1xuICBwYWRkaW5nOiAwLjVlbTtcbiAgaGVpZ2h0OiAxMDAlO1xuICBvdmVyZmxvdzogc2Nyb2xsO1xufVxuXG4uc3RkaW4ge1xuICBtYXJnaW4tdG9wOiAwLjI1ZW07XG59XG4iXX0= */
|
||||
1
assets/module.postcss/output.module.css.map
Normal file
1
assets/module.postcss/output.module.css.map
Normal file
@@ -0,0 +1 @@
|
||||
{"container":"_container_d1av0_1","tabs":"_tabs_d1av0_8","tab":"_tab_d1av0_8","tabSelected":"_tabSelected_d1av0_26 _tab_d1av0_8","tabClose":"_tabClose_d1av0_37 _tab_d1av0_8","body":"_body_d1av0_42","stdin":"_stdin_d1av0_51"}
|
||||
32
assets/module.postcss/output/execute.module.css
Normal file
32
assets/module.postcss/output/execute.module.css
Normal file
@@ -0,0 +1,32 @@
|
||||
._-buttonReset_16ln6_1 {
|
||||
color: var(--font-color);
|
||||
border: none;
|
||||
background: inherit;
|
||||
background-color: transparent; /* IE 11 */
|
||||
padding: 0;
|
||||
font: inherit;
|
||||
line-height: inherit;
|
||||
text-align: inherit;
|
||||
}
|
||||
|
||||
._-buttonAsLink_16ln6_12 {
|
||||
cursor: pointer;
|
||||
color: var(--link-color);
|
||||
user-select: text;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
._addMain_16ln6_20 {
|
||||
}
|
||||
|
||||
._table_16ln6_24 {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
._tdAndTh_16ln6_28 {
|
||||
border: 1px solid #dddddd;
|
||||
text-align: left;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uL21vZHVsZS5jc3Mvb3V0cHV0L2V4ZWN1dGUubW9kdWxlLmNzcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTtFQUNFLHdCQUF3QjtFQUN4QixZQUFZO0VBQ1osbUJBQW1CO0VBQ25CLDZCQUE2QixFQUFFLFVBQVU7RUFDekMsVUFBVTtFQUNWLGFBQWE7RUFDYixvQkFBb0I7RUFDcEIsbUJBQW1CO0FBQ3JCOztBQUVBO0VBRUUsZUFBZTtFQUNmLHdCQUF3QjtFQUN4QixpQkFBaUI7RUFDakIsMEJBQTBCO0FBQzVCOztBQUVBO0FBRUE7O0FBRUE7RUFDRSx5QkFBeUI7QUFDM0I7O0FBRUE7RUFDRSx5QkFBeUI7RUFDekIsZ0JBQWdCO0VBQ2hCLFlBQVk7QUFDZCIsImZpbGUiOiJleGVjdXRlLm1vZHVsZS5jc3MiLCJzb3VyY2VzQ29udGVudCI6WyIuLWJ1dHRvblJlc2V0IHtcbiAgY29sb3I6IHZhcigtLWZvbnQtY29sb3IpO1xuICBib3JkZXI6IG5vbmU7XG4gIGJhY2tncm91bmQ6IGluaGVyaXQ7XG4gIGJhY2tncm91bmQtY29sb3I6IHRyYW5zcGFyZW50OyAvKiBJRSAxMSAqL1xuICBwYWRkaW5nOiAwO1xuICBmb250OiBpbmhlcml0O1xuICBsaW5lLWhlaWdodDogaW5oZXJpdDtcbiAgdGV4dC1hbGlnbjogaW5oZXJpdDtcbn1cblxuLi1idXR0b25Bc0xpbmsge1xuICBjb21wb3NlczogLWJ1dHRvblJlc2V0O1xuICBjdXJzb3I6IHBvaW50ZXI7XG4gIGNvbG9yOiB2YXIoLS1saW5rLWNvbG9yKTtcbiAgdXNlci1zZWxlY3Q6IHRleHQ7XG4gIHRleHQtZGVjb3JhdGlvbjogdW5kZXJsaW5lO1xufVxuXG4uYWRkTWFpbiB7XG4gIGNvbXBvc2VzOiAtYnV0dG9uQXNMaW5rO1xufVxuXG4udGFibGUge1xuICBib3JkZXItY29sbGFwc2U6IGNvbGxhcHNlO1xufVxuXG4udGRBbmRUaCB7XG4gIGJvcmRlcjogMXB4IHNvbGlkICNkZGRkZGQ7XG4gIHRleHQtYWxpZ246IGxlZnQ7XG4gIHBhZGRpbmc6IDhweDtcbn1cbiJdfQ== */
|
||||
1
assets/module.postcss/output/execute.module.css.map
Normal file
1
assets/module.postcss/output/execute.module.css.map
Normal file
@@ -0,0 +1 @@
|
||||
{"-buttonReset":"_-buttonReset_16ln6_1","-buttonAsLink":"_-buttonAsLink_16ln6_12 _-buttonReset_16ln6_1","addMain":"_addMain_16ln6_20 _-buttonAsLink_16ln6_12 _-buttonReset_16ln6_1","table":"_table_16ln6_24","tdAndTh":"_tdAndTh_16ln6_28"}
|
||||
23
assets/module.postcss/output/header.module.css
Normal file
23
assets/module.postcss/output/header.module.css
Normal file
@@ -0,0 +1,23 @@
|
||||
._container_knjdr_1 {
|
||||
display: flex;
|
||||
color: var(--font-color);
|
||||
white-space: nowrap;
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
flex: 1 1 auto;
|
||||
margin: auto;
|
||||
border-top: var(--border);
|
||||
content: '';
|
||||
}
|
||||
|
||||
&::before {
|
||||
margin-right: 2%;
|
||||
}
|
||||
|
||||
&::after {
|
||||
margin-left: 2%;
|
||||
}
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uL21vZHVsZS5jc3Mvb3V0cHV0L2hlYWRlci5tb2R1bGUuY3NzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBO0VBQ0UsYUFBYTtFQUNiLHdCQUF3QjtFQUN4QixtQkFBbUI7O0VBRW5COztJQUVFLGNBQWM7SUFDZCxZQUFZO0lBQ1oseUJBQXlCO0lBQ3pCLFdBQVc7RUFDYjs7RUFFQTtJQUNFLGdCQUFnQjtFQUNsQjs7RUFFQTtJQUNFLGVBQWU7RUFDakI7QUFDRiIsImZpbGUiOiJoZWFkZXIubW9kdWxlLmNzcyIsInNvdXJjZXNDb250ZW50IjpbIi5jb250YWluZXIge1xuICBkaXNwbGF5OiBmbGV4O1xuICBjb2xvcjogdmFyKC0tZm9udC1jb2xvcik7XG4gIHdoaXRlLXNwYWNlOiBub3dyYXA7XG5cbiAgJjo6YmVmb3JlLFxuICAmOjphZnRlciB7XG4gICAgZmxleDogMSAxIGF1dG87XG4gICAgbWFyZ2luOiBhdXRvO1xuICAgIGJvcmRlci10b3A6IHZhcigtLWJvcmRlcik7XG4gICAgY29udGVudDogJyc7XG4gIH1cblxuICAmOjpiZWZvcmUge1xuICAgIG1hcmdpbi1yaWdodDogMiU7XG4gIH1cblxuICAmOjphZnRlciB7XG4gICAgbWFyZ2luLWxlZnQ6IDIlO1xuICB9XG59XG4iXX0= */
|
||||
1
assets/module.postcss/output/header.module.css.map
Normal file
1
assets/module.postcss/output/header.module.css.map
Normal file
@@ -0,0 +1 @@
|
||||
{"container":"_container_knjdr_1"}
|
||||
15
assets/module.postcss/output/section.module.css
Normal file
15
assets/module.postcss/output/section.module.css
Normal file
@@ -0,0 +1,15 @@
|
||||
._-bodyMonospace_1qg95_1 {
|
||||
font-size: inherit;
|
||||
line-height: 1.25;
|
||||
|
||||
/* http://code.stephenmorley.org/html-and-css/fixing-browsers-broken-monospace-font-handling/
|
||||
* ACE uses Monaco, Menlo, "Ubuntu Mono", Consolas, source-code-pro, monospace;
|
||||
*/
|
||||
font-family: 'Source Code Pro', monospace;
|
||||
font-optical-sizing: auto;
|
||||
}
|
||||
|
||||
._code_1qg95_12 {
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uL21vZHVsZS5jc3Mvb3V0cHV0L3NlY3Rpb24ubW9kdWxlLmNzcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTtFQUNFLGtCQUFrQjtFQUNsQixpQkFBaUI7O0VBRWpCOztJQUVFO0VBQ0YseUNBQXlDO0VBQ3pDLHlCQUF5QjtBQUMzQjs7QUFFQTtBQUVBIiwiZmlsZSI6InNlY3Rpb24ubW9kdWxlLmNzcyIsInNvdXJjZXNDb250ZW50IjpbIi4tYm9keU1vbm9zcGFjZSB7XG4gIGZvbnQtc2l6ZTogaW5oZXJpdDtcbiAgbGluZS1oZWlnaHQ6IDEuMjU7XG5cbiAgLyogaHR0cDovL2NvZGUuc3RlcGhlbm1vcmxleS5vcmcvaHRtbC1hbmQtY3NzL2ZpeGluZy1icm93c2Vycy1icm9rZW4tbW9ub3NwYWNlLWZvbnQtaGFuZGxpbmcvXG4gICAqIEFDRSB1c2VzIE1vbmFjbywgTWVubG8sIFwiVWJ1bnR1IE1vbm9cIiwgQ29uc29sYXMsIHNvdXJjZS1jb2RlLXBybywgbW9ub3NwYWNlO1xuICAgKi9cbiAgZm9udC1mYW1pbHk6ICdTb3VyY2UgQ29kZSBQcm8nLCBtb25vc3BhY2U7XG4gIGZvbnQtb3B0aWNhbC1zaXppbmc6IGF1dG87XG59XG5cbi5jb2RlIHtcbiAgY29tcG9zZXM6IC1ib2R5TW9ub3NwYWNlO1xufVxuIl19 */
|
||||
1
assets/module.postcss/output/section.module.css.map
Normal file
1
assets/module.postcss/output/section.module.css.map
Normal file
@@ -0,0 +1 @@
|
||||
{"-bodyMonospace":"_-bodyMonospace_1qg95_1","code":"_code_1qg95_12 _-bodyMonospace_1qg95_1"}
|
||||
51
assets/module.postcss/output/share.module.css
Normal file
51
assets/module.postcss/output/share.module.css
Normal file
@@ -0,0 +1,51 @@
|
||||
._-buttonReset_ccgtz_1 {
|
||||
color: var(--font-color);
|
||||
border: none;
|
||||
background: inherit;
|
||||
background-color: transparent; /* IE 11 */
|
||||
padding: 0;
|
||||
font: inherit;
|
||||
line-height: inherit;
|
||||
text-align: inherit;
|
||||
}
|
||||
|
||||
._container_ccgtz_12 {
|
||||
display: flex;
|
||||
|
||||
--copied-duration: 1s ease-in-out;
|
||||
}
|
||||
|
||||
._button_ccgtz_18 {
|
||||
transition: color var(--copied-duration);
|
||||
cursor: pointer;
|
||||
margin: 0 0.25em 0 0.5em;
|
||||
}
|
||||
|
||||
._text_ccgtz_25 {
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transition:
|
||||
visibility var(--copied-duration),
|
||||
opacity var(--copied-duration),
|
||||
color var(--copied-duration);
|
||||
}
|
||||
|
||||
._active_ccgtz_34 {
|
||||
|
||||
& ._button_ccgtz_18 {
|
||||
transition: color 0s;
|
||||
color: green;
|
||||
}
|
||||
|
||||
& ._text_ccgtz_25 {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
transition:
|
||||
visibility 0s,
|
||||
opacity 0s,
|
||||
color 0s;
|
||||
color: green;
|
||||
}
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uL21vZHVsZS5jc3Mvb3V0cHV0L3NoYXJlLm1vZHVsZS5jc3MiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7RUFDRSx3QkFBd0I7RUFDeEIsWUFBWTtFQUNaLG1CQUFtQjtFQUNuQiw2QkFBNkIsRUFBRSxVQUFVO0VBQ3pDLFVBQVU7RUFDVixhQUFhO0VBQ2Isb0JBQW9CO0VBQ3BCLG1CQUFtQjtBQUNyQjs7QUFFQTtFQUNFLGFBQWE7O0VBRWIsaUNBQWlDO0FBQ25DOztBQUVBO0VBRUUsd0NBQXdDO0VBQ3hDLGVBQWU7RUFDZix3QkFBd0I7QUFDMUI7O0FBRUE7RUFDRSxrQkFBa0I7RUFDbEIsVUFBVTtFQUNWOzs7Z0NBRzhCO0FBQ2hDOztBQUVBOztFQUdFO0lBQ0Usb0JBQW9CO0lBQ3BCLFlBQVk7RUFDZDs7RUFFQTtJQUNFLG1CQUFtQjtJQUNuQixVQUFVO0lBQ1Y7OztjQUdVO0lBQ1YsWUFBWTtFQUNkO0FBQ0YiLCJmaWxlIjoic2hhcmUubW9kdWxlLmNzcyIsInNvdXJjZXNDb250ZW50IjpbIi4tYnV0dG9uUmVzZXQge1xuICBjb2xvcjogdmFyKC0tZm9udC1jb2xvcik7XG4gIGJvcmRlcjogbm9uZTtcbiAgYmFja2dyb3VuZDogaW5oZXJpdDtcbiAgYmFja2dyb3VuZC1jb2xvcjogdHJhbnNwYXJlbnQ7IC8qIElFIDExICovXG4gIHBhZGRpbmc6IDA7XG4gIGZvbnQ6IGluaGVyaXQ7XG4gIGxpbmUtaGVpZ2h0OiBpbmhlcml0O1xuICB0ZXh0LWFsaWduOiBpbmhlcml0O1xufVxuXG4uY29udGFpbmVyIHtcbiAgZGlzcGxheTogZmxleDtcblxuICAtLWNvcGllZC1kdXJhdGlvbjogMXMgZWFzZS1pbi1vdXQ7XG59XG5cbi5idXR0b24ge1xuICBjb21wb3NlczogLWJ1dHRvblJlc2V0O1xuICB0cmFuc2l0aW9uOiBjb2xvciB2YXIoLS1jb3BpZWQtZHVyYXRpb24pO1xuICBjdXJzb3I6IHBvaW50ZXI7XG4gIG1hcmdpbjogMCAwLjI1ZW0gMCAwLjVlbTtcbn1cblxuLnRleHQge1xuICB2aXNpYmlsaXR5OiBoaWRkZW47XG4gIG9wYWNpdHk6IDA7XG4gIHRyYW5zaXRpb246XG4gICAgdmlzaWJpbGl0eSB2YXIoLS1jb3BpZWQtZHVyYXRpb24pLFxuICAgIG9wYWNpdHkgdmFyKC0tY29waWVkLWR1cmF0aW9uKSxcbiAgICBjb2xvciB2YXIoLS1jb3BpZWQtZHVyYXRpb24pO1xufVxuXG4uYWN0aXZlIHtcbiAgY29tcG9zZXM6IGNvbnRhaW5lcjtcblxuICAmIC5idXR0b24ge1xuICAgIHRyYW5zaXRpb246IGNvbG9yIDBzO1xuICAgIGNvbG9yOiBncmVlbjtcbiAgfVxuXG4gICYgLnRleHQge1xuICAgIHZpc2liaWxpdHk6IHZpc2libGU7XG4gICAgb3BhY2l0eTogMTtcbiAgICB0cmFuc2l0aW9uOlxuICAgICAgdmlzaWJpbGl0eSAwcyxcbiAgICAgIG9wYWNpdHkgMHMsXG4gICAgICBjb2xvciAwcztcbiAgICBjb2xvcjogZ3JlZW47XG4gIH1cbn1cbiJdfQ== */
|
||||
1
assets/module.postcss/output/share.module.css.map
Normal file
1
assets/module.postcss/output/share.module.css.map
Normal file
@@ -0,0 +1 @@
|
||||
{"-buttonReset":"_-buttonReset_ccgtz_1","container":"_container_ccgtz_12","button":"_button_ccgtz_18 _-buttonReset_ccgtz_1","text":"_text_ccgtz_25","active":"_active_ccgtz_34 _container_ccgtz_12"}
|
||||
62
assets/module.postcss/playground.module.css
Normal file
62
assets/module.postcss/playground.module.css
Normal file
@@ -0,0 +1,62 @@
|
||||
._-autoSize_1uxa0_1 {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
._container_1uxa0_8 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-bottom: 1em;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
._-resizeableArea_1uxa0_15 {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
._resizeableAreaRowOutputUnfocused_1uxa0_20 {
|
||||
grid-template-rows: 1fr auto;
|
||||
}
|
||||
|
||||
._resizeableAreaRowOutputFocused_1uxa0_25 {
|
||||
grid-template-rows: 1fr 12px 1fr;
|
||||
}
|
||||
|
||||
._resizeableAreaColumnOutputUnfocused_1uxa0_30 {
|
||||
grid-template-columns: 1fr auto;
|
||||
}
|
||||
|
||||
._resizeableAreaColumnOutputFocused_1uxa0_35 {
|
||||
grid-template-columns: 1fr 12px 1fr;
|
||||
}
|
||||
|
||||
._-gutter_1uxa0_40 {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
._splitRowsGutter_1uxa0_46 {
|
||||
cursor: row-resize;
|
||||
}
|
||||
|
||||
._splitRowsGutterHandle_1uxa0_51 {
|
||||
transform: rotate(90deg);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
._splitColumnsGutter_1uxa0_56 {
|
||||
cursor: col-resize;
|
||||
}
|
||||
|
||||
._editor_1uxa0_61 {
|
||||
border: 4px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
._output_1uxa0_67 {
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL21vZHVsZS5jc3MvcGxheWdyb3VuZC5tb2R1bGUuY3NzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBO0VBQ0UsV0FBVztFQUNYLFlBQVk7RUFDWixZQUFZO0VBQ1osYUFBYTtBQUNmOztBQUVBO0VBQ0UsYUFBYTtFQUNiLHNCQUFzQjtFQUN0QixtQkFBbUI7RUFDbkIsYUFBYTtBQUNmOztBQUVBO0VBRUUsYUFBYTtBQUNmOztBQUVBO0VBRUUsNEJBQTRCO0FBQzlCOztBQUVBO0VBRUUsZ0NBQWdDO0FBQ2xDOztBQUVBO0VBRUUsK0JBQStCO0FBQ2pDOztBQUVBO0VBRUUsbUNBQW1DO0FBQ3JDOztBQUVBO0VBQ0UsYUFBYTtFQUNiLHVCQUF1QjtFQUN2QixtQkFBbUI7QUFDckI7O0FBRUE7RUFFRSxrQkFBa0I7QUFDcEI7O0FBRUE7RUFDRSx3QkFBd0I7RUFDeEIsb0JBQW9CO0FBQ3RCOztBQUVBO0VBRUUsa0JBQWtCO0FBQ3BCOztBQUVBO0VBRUUscUNBQXFDO0VBQ3JDLGtCQUFrQjtBQUNwQjs7QUFFQTtBQUVBIiwiZmlsZSI6InBsYXlncm91bmQubW9kdWxlLmNzcyIsInNvdXJjZXNDb250ZW50IjpbIi4tYXV0b1NpemUge1xuICB3aWR0aDogMTAwJTtcbiAgbWluLXdpZHRoOiAwO1xuICBoZWlnaHQ6IDEwMCU7XG4gIG1pbi1oZWlnaHQ6IDA7XG59XG5cbi5jb250YWluZXIge1xuICBkaXNwbGF5OiBmbGV4O1xuICBmbGV4LWRpcmVjdGlvbjogY29sdW1uO1xuICBwYWRkaW5nLWJvdHRvbTogMWVtO1xuICBoZWlnaHQ6IDEwMHZoO1xufVxuXG4uLXJlc2l6ZWFibGVBcmVhIHtcbiAgY29tcG9zZXM6IC1hdXRvU2l6ZTtcbiAgZGlzcGxheTogZ3JpZDtcbn1cblxuLnJlc2l6ZWFibGVBcmVhUm93T3V0cHV0VW5mb2N1c2VkIHtcbiAgY29tcG9zZXM6IC1yZXNpemVhYmxlQXJlYTtcbiAgZ3JpZC10ZW1wbGF0ZS1yb3dzOiAxZnIgYXV0bztcbn1cblxuLnJlc2l6ZWFibGVBcmVhUm93T3V0cHV0Rm9jdXNlZCB7XG4gIGNvbXBvc2VzOiAtcmVzaXplYWJsZUFyZWE7XG4gIGdyaWQtdGVtcGxhdGUtcm93czogMWZyIDEycHggMWZyO1xufVxuXG4ucmVzaXplYWJsZUFyZWFDb2x1bW5PdXRwdXRVbmZvY3VzZWQge1xuICBjb21wb3NlczogLXJlc2l6ZWFibGVBcmVhO1xuICBncmlkLXRlbXBsYXRlLWNvbHVtbnM6IDFmciBhdXRvO1xufVxuXG4ucmVzaXplYWJsZUFyZWFDb2x1bW5PdXRwdXRGb2N1c2VkIHtcbiAgY29tcG9zZXM6IC1yZXNpemVhYmxlQXJlYTtcbiAgZ3JpZC10ZW1wbGF0ZS1jb2x1bW5zOiAxZnIgMTJweCAxZnI7XG59XG5cbi4tZ3V0dGVyIHtcbiAgZGlzcGxheTogZmxleDtcbiAganVzdGlmeS1jb250ZW50OiBjZW50ZXI7XG4gIGFsaWduLWl0ZW1zOiBjZW50ZXI7XG59XG5cbi5zcGxpdFJvd3NHdXR0ZXIge1xuICBjb21wb3NlczogLWd1dHRlcjtcbiAgY3Vyc29yOiByb3ctcmVzaXplO1xufVxuXG4uc3BsaXRSb3dzR3V0dGVySGFuZGxlIHtcbiAgdHJhbnNmb3JtOiByb3RhdGUoOTBkZWcpO1xuICBwb2ludGVyLWV2ZW50czogbm9uZTtcbn1cblxuLnNwbGl0Q29sdW1uc0d1dHRlciB7XG4gIGNvbXBvc2VzOiAtZ3V0dGVyO1xuICBjdXJzb3I6IGNvbC1yZXNpemU7XG59XG5cbi5lZGl0b3Ige1xuICBjb21wb3NlczogLWF1dG9TaXplO1xuICBib3JkZXI6IDRweCBzb2xpZCB2YXIoLS1ib3JkZXItY29sb3IpO1xuICBib3JkZXItcmFkaXVzOiA0cHg7XG59XG5cbi5vdXRwdXQge1xuICBjb21wb3NlczogLWF1dG9TaXplO1xufVxuIl19 */
|
||||
1
assets/module.postcss/playground.module.css.map
Normal file
1
assets/module.postcss/playground.module.css.map
Normal file
@@ -0,0 +1 @@
|
||||
{"-autoSize":"_-autoSize_1uxa0_1","container":"_container_1uxa0_8","-resizeableArea":"_-resizeableArea_1uxa0_15 _-autoSize_1uxa0_1","resizeableAreaRowOutputUnfocused":"_resizeableAreaRowOutputUnfocused_1uxa0_20 _-resizeableArea_1uxa0_15 _-autoSize_1uxa0_1","resizeableAreaRowOutputFocused":"_resizeableAreaRowOutputFocused_1uxa0_25 _-resizeableArea_1uxa0_15 _-autoSize_1uxa0_1","resizeableAreaColumnOutputUnfocused":"_resizeableAreaColumnOutputUnfocused_1uxa0_30 _-resizeableArea_1uxa0_15 _-autoSize_1uxa0_1","resizeableAreaColumnOutputFocused":"_resizeableAreaColumnOutputFocused_1uxa0_35 _-resizeableArea_1uxa0_15 _-autoSize_1uxa0_1","-gutter":"_-gutter_1uxa0_40","splitRowsGutter":"_splitRowsGutter_1uxa0_46 _-gutter_1uxa0_40","splitRowsGutterHandle":"_splitRowsGutterHandle_1uxa0_51","splitColumnsGutter":"_splitColumnsGutter_1uxa0_56 _-gutter_1uxa0_40","editor":"_editor_1uxa0_61 _-autoSize_1uxa0_1","output":"_output_1uxa0_67 _-autoSize_1uxa0_1"}
|
||||
32
assets/module.postcss/pop_button.module.css
Normal file
32
assets/module.postcss/pop_button.module.css
Normal file
@@ -0,0 +1,32 @@
|
||||
._container_1chxk_4 {
|
||||
z-index: 10;
|
||||
font-size: var(--secondary-font-size);
|
||||
|
||||
& button:enabled {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
._arrow_1chxk_13 {
|
||||
fill: var(--background-color-high-contrast);
|
||||
}
|
||||
|
||||
._-content_1chxk_17 {
|
||||
margin: 10px;
|
||||
box-shadow:
|
||||
5px 5px 20px -3px rgb(0 0 0 / 25%),
|
||||
0 0 5px -2px rgb(0 0 0 / 90%);
|
||||
border-radius: var(--header-border-radius);
|
||||
background: var(--background-color-high-contrast);
|
||||
color: var(--font-color);
|
||||
}
|
||||
|
||||
._contentBottom_1chxk_27 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
._contentTop_1chxk_32 {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL21vZHVsZS5jc3MvcG9wX2J1dHRvbi5tb2R1bGUuY3NzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUdBO0VBQ0UsV0FBVztFQUNYLHFDQUFxQzs7RUFFckM7SUFDRSxlQUFlO0VBQ2pCO0FBQ0Y7O0FBRUE7RUFDRSwyQ0FBMkM7QUFDN0M7O0FBRUE7RUFDRSxZQUFxQjtFQUNyQjs7aUNBRStCO0VBQy9CLDBDQUEwQztFQUMxQyxpREFBaUQ7RUFDakQsd0JBQXdCO0FBQzFCOztBQUVBO0VBRUUsYUFBYTtBQUNmOztBQUVBO0VBRUUsZ0JBQWdCO0FBQ2xCIiwiZmlsZSI6InBvcF9idXR0b24ubW9kdWxlLmNzcyIsInNvdXJjZXNDb250ZW50IjpbIiRhcnJvdy1oZWlnaHQ6IDEwcHg7XG4kYXJyb3ctd2lkdGg6IDIwcHg7XG5cbi5jb250YWluZXIge1xuICB6LWluZGV4OiAxMDtcbiAgZm9udC1zaXplOiB2YXIoLS1zZWNvbmRhcnktZm9udC1zaXplKTtcblxuICAmIGJ1dHRvbjplbmFibGVkIHtcbiAgICBjdXJzb3I6IHBvaW50ZXI7XG4gIH1cbn1cblxuLmFycm93IHtcbiAgZmlsbDogdmFyKC0tYmFja2dyb3VuZC1jb2xvci1oaWdoLWNvbnRyYXN0KTtcbn1cblxuLi1jb250ZW50IHtcbiAgbWFyZ2luOiAkYXJyb3ctaGVpZ2h0O1xuICBib3gtc2hhZG93OlxuICAgIDVweCA1cHggMjBweCAtM3B4IHJnYigwIDAgMCAvIDI1JSksXG4gICAgMCAwIDVweCAtMnB4IHJnYigwIDAgMCAvIDkwJSk7XG4gIGJvcmRlci1yYWRpdXM6IHZhcigtLWhlYWRlci1ib3JkZXItcmFkaXVzKTtcbiAgYmFja2dyb3VuZDogdmFyKC0tYmFja2dyb3VuZC1jb2xvci1oaWdoLWNvbnRyYXN0KTtcbiAgY29sb3I6IHZhcigtLWZvbnQtY29sb3IpO1xufVxuXG4uY29udGVudEJvdHRvbSB7XG4gIGNvbXBvc2VzOiAtY29udGVudDtcbiAgbWFyZ2luLXRvcDogMDtcbn1cblxuLmNvbnRlbnRUb3Age1xuICBjb21wb3NlczogLWNvbnRlbnQ7XG4gIG1hcmdpbi1ib3R0b206IDA7XG59XG4iXX0= */
|
||||
1
assets/module.postcss/pop_button.module.css.map
Normal file
1
assets/module.postcss/pop_button.module.css.map
Normal file
@@ -0,0 +1 @@
|
||||
{"container":"_container_1chxk_4","arrow":"_arrow_1chxk_13","-content":"_-content_1chxk_17","contentBottom":"_contentBottom_1chxk_27 _-content_1chxk_17","contentTop":"_contentTop_1chxk_32 _-content_1chxk_17"}
|
||||
62
assets/module.postcss/selectable_menu_item.module.css
Normal file
62
assets/module.postcss/selectable_menu_item.module.css
Normal file
@@ -0,0 +1,62 @@
|
||||
._-buttonReset_11oz3_1 {
|
||||
color: var(--font-color);
|
||||
border: none;
|
||||
background: inherit;
|
||||
background-color: transparent; /* IE 11 */
|
||||
padding: 0;
|
||||
font: inherit;
|
||||
line-height: inherit;
|
||||
text-align: inherit;
|
||||
}
|
||||
|
||||
._-menuItemFullButton_11oz3_12 {
|
||||
transition: color var(--header-transition);
|
||||
width: 100%;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
._-menuItemTitle_11oz3_19 {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
._container_11oz3_23 {
|
||||
|
||||
&:hover {
|
||||
color: var(--header-tint);
|
||||
}
|
||||
}
|
||||
|
||||
._selected_11oz3_31 {
|
||||
color: var(--header-tint);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
._header_11oz3_37 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
._name_11oz3_42 {
|
||||
}
|
||||
|
||||
._description_11oz3_46 {
|
||||
padding-left: 2em;
|
||||
}
|
||||
|
||||
._checkmark_11oz3_50 {
|
||||
opacity: 0;
|
||||
transition: opacity 0.15s ease-in-out;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
._selected_11oz3_31 ._checkmark_11oz3_50,
|
||||
._selected_11oz3_31:hover ._checkmark_11oz3_50 {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
._container_11oz3_23:hover ._checkmark_11oz3_50 {
|
||||
opacity: 0.5;
|
||||
color: var(--header-tint);
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL21vZHVsZS5jc3Mvc2VsZWN0YWJsZV9tZW51X2l0ZW0ubW9kdWxlLmNzcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTtFQUNFLHdCQUF3QjtFQUN4QixZQUFZO0VBQ1osbUJBQW1CO0VBQ25CLDZCQUE2QixFQUFFLFVBQVU7RUFDekMsVUFBVTtFQUNWLGFBQWE7RUFDYixvQkFBb0I7RUFDcEIsbUJBQW1CO0FBQ3JCOztBQUVBO0VBRUUsMENBQTBDO0VBQzFDLFdBQVc7RUFDWCxpQkFBaUI7QUFDbkI7O0FBRUE7RUFDRSxnQkFBZ0I7QUFDbEI7O0FBRUE7O0VBR0U7SUFDRSx5QkFBeUI7RUFDM0I7QUFDRjs7QUFFQTtFQUVFLHlCQUF5QjtFQUN6QixnQkFBZ0I7QUFDbEI7O0FBRUE7RUFDRSxhQUFhO0VBQ2IsbUJBQW1CO0FBQ3JCOztBQUVBO0FBRUE7O0FBRUE7RUFDRSxpQkFBaUI7QUFDbkI7O0FBRUE7RUFDRSxVQUFVO0VBQ1YscUNBQXFDO0VBQ3JDLG1CQUFtQjtBQUNyQjs7QUFFQTs7RUFFRSxVQUFVO0FBQ1o7O0FBRUE7RUFDRSxZQUFZO0VBQ1oseUJBQXlCO0FBQzNCIiwiZmlsZSI6InNlbGVjdGFibGVfbWVudV9pdGVtLm1vZHVsZS5jc3MiLCJzb3VyY2VzQ29udGVudCI6WyIuLWJ1dHRvblJlc2V0IHtcbiAgY29sb3I6IHZhcigtLWZvbnQtY29sb3IpO1xuICBib3JkZXI6IG5vbmU7XG4gIGJhY2tncm91bmQ6IGluaGVyaXQ7XG4gIGJhY2tncm91bmQtY29sb3I6IHRyYW5zcGFyZW50OyAvKiBJRSAxMSAqL1xuICBwYWRkaW5nOiAwO1xuICBmb250OiBpbmhlcml0O1xuICBsaW5lLWhlaWdodDogaW5oZXJpdDtcbiAgdGV4dC1hbGlnbjogaW5oZXJpdDtcbn1cblxuLi1tZW51SXRlbUZ1bGxCdXR0b24ge1xuICBjb21wb3NlczogLWJ1dHRvblJlc2V0O1xuICB0cmFuc2l0aW9uOiBjb2xvciB2YXIoLS1oZWFkZXItdHJhbnNpdGlvbik7XG4gIHdpZHRoOiAxMDAlO1xuICB1c2VyLXNlbGVjdDogdGV4dDtcbn1cblxuLi1tZW51SXRlbVRpdGxlIHtcbiAgZm9udC13ZWlnaHQ6IDYwMDtcbn1cblxuLmNvbnRhaW5lciB7XG4gIGNvbXBvc2VzOiAtbWVudUl0ZW1GdWxsQnV0dG9uO1xuXG4gICY6aG92ZXIge1xuICAgIGNvbG9yOiB2YXIoLS1oZWFkZXItdGludCk7XG4gIH1cbn1cblxuLnNlbGVjdGVkIHtcbiAgY29tcG9zZXM6IGNvbnRhaW5lcjtcbiAgY29sb3I6IHZhcigtLWhlYWRlci10aW50KTtcbiAgZm9udC13ZWlnaHQ6IDYwMDtcbn1cblxuLmhlYWRlciB7XG4gIGRpc3BsYXk6IGZsZXg7XG4gIGFsaWduLWl0ZW1zOiBjZW50ZXI7XG59XG5cbi5uYW1lIHtcbiAgY29tcG9zZXM6IC1tZW51SXRlbVRpdGxlO1xufVxuXG4uZGVzY3JpcHRpb24ge1xuICBwYWRkaW5nLWxlZnQ6IDJlbTtcbn1cblxuLmNoZWNrbWFyayB7XG4gIG9wYWNpdHk6IDA7XG4gIHRyYW5zaXRpb246IG9wYWNpdHkgMC4xNXMgZWFzZS1pbi1vdXQ7XG4gIG1hcmdpbi1yaWdodDogMC41ZW07XG59XG5cbi5zZWxlY3RlZCAuY2hlY2ttYXJrLFxuLnNlbGVjdGVkOmhvdmVyIC5jaGVja21hcmsge1xuICBvcGFjaXR5OiAxO1xufVxuXG4uY29udGFpbmVyOmhvdmVyIC5jaGVja21hcmsge1xuICBvcGFjaXR5OiAwLjU7XG4gIGNvbG9yOiB2YXIoLS1oZWFkZXItdGludCk7XG59XG4iXX0= */
|
||||
@@ -0,0 +1 @@
|
||||
{"-buttonReset":"_-buttonReset_11oz3_1","-menuItemFullButton":"_-menuItemFullButton_11oz3_12 _-buttonReset_11oz3_1","-menuItemTitle":"_-menuItemTitle_11oz3_19","container":"_container_11oz3_23 _-menuItemFullButton_11oz3_12 _-buttonReset_11oz3_1","selected":"_selected_11oz3_31 _container_11oz3_23 _-menuItemFullButton_11oz3_12 _-buttonReset_11oz3_1","header":"_header_11oz3_37","name":"_name_11oz3_42 _-menuItemTitle_11oz3_19","description":"_description_11oz3_46","checkmark":"_checkmark_11oz3_50"}
|
||||
47
assets/shared.module.css
Normal file
47
assets/shared.module.css
Normal file
@@ -0,0 +1,47 @@
|
||||
.-bodyMonospace {
|
||||
font-size: inherit;
|
||||
line-height: 1.25;
|
||||
|
||||
/* http://code.stephenmorley.org/html-and-css/fixing-browsers-broken-monospace-font-handling/
|
||||
* ACE uses Monaco, Menlo, "Ubuntu Mono", Consolas, source-code-pro, monospace;
|
||||
*/
|
||||
font-family: 'Source Code Pro', monospace;
|
||||
font-optical-sizing: auto;
|
||||
}
|
||||
|
||||
.-autoSize {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.-buttonReset {
|
||||
color: var(--font-color);
|
||||
border: none;
|
||||
background: inherit;
|
||||
background-color: transparent; /* IE 11 */
|
||||
padding: 0;
|
||||
font: inherit;
|
||||
line-height: inherit;
|
||||
text-align: inherit;
|
||||
}
|
||||
|
||||
.-buttonAsLink {
|
||||
composes: -buttonReset;
|
||||
cursor: pointer;
|
||||
color: var(--link-color);
|
||||
user-select: text;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.-menuItemTitle {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.-menuItemFullButton {
|
||||
composes: -buttonReset;
|
||||
transition: color var(--header-transition);
|
||||
width: 100%;
|
||||
user-select: text;
|
||||
}
|
||||
13
crates/aceditor/Cargo.toml
Normal file
13
crates/aceditor/Cargo.toml
Normal file
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "aceditor"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
js-sys = "0.3.77"
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
serde-wasm-bindgen = "0.6.5"
|
||||
thiserror = "2.0.12"
|
||||
tokio = { version = "1.45.0", features = ["sync"] }
|
||||
wasm-bindgen = "0.2.100"
|
||||
web-sys = { version = "0.3.77", features = ["HtmlElement"] }
|
||||
174
crates/aceditor/src/lib.rs
Normal file
174
crates/aceditor/src/lib.rs
Normal file
@@ -0,0 +1,174 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use js_sys::{Object, Reflect};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use wasm_bindgen::{JsCast, JsValue, prelude::Closure};
|
||||
|
||||
// <https://ajaxorg.github.io/ace-api-docs/index.html>
|
||||
mod bindgen {
|
||||
use js_sys::Object;
|
||||
use wasm_bindgen::{
|
||||
JsValue,
|
||||
prelude::{Closure, wasm_bindgen},
|
||||
};
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(js_namespace = ace, js_name = edit, catch)]
|
||||
pub fn edit(element: &str, options: Option<Object>) -> Result<Editor, JsValue>;
|
||||
|
||||
#[wasm_bindgen(js_namespace = ["ace", "config"], js_name = loadModule)]
|
||||
pub fn load_module(module: &str, callback: &Closure<dyn FnMut(JsValue)>);
|
||||
|
||||
#[wasm_bindgen(js_namespace = ace, js_name = require, catch)]
|
||||
pub fn require(module: &str) -> Result<JsValue, JsValue>;
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
pub type Editor;
|
||||
|
||||
#[wasm_bindgen(method, js_name = setTheme, catch)]
|
||||
pub fn set_theme(this: &Editor, theme: &str) -> Result<(), JsValue>;
|
||||
|
||||
#[wasm_bindgen(method, js_name = setKeyboardHandler, catch)]
|
||||
pub fn set_keyboard_handler(this: &Editor, handler: &str) -> Result<(), JsValue>;
|
||||
|
||||
#[wasm_bindgen(method, js_name = getValue)]
|
||||
pub fn get_value(this: &Editor) -> String;
|
||||
}
|
||||
}
|
||||
|
||||
type Result<T> = std::result::Result<T, EditorError>;
|
||||
|
||||
#[derive(Default, Serialize, Deserialize)]
|
||||
pub struct EditorOptionsBuilder(EditorOptions);
|
||||
|
||||
impl EditorOptionsBuilder {
|
||||
pub fn mode(mut self, value: &str) -> Self {
|
||||
self.0.mode = value.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn theme(mut self, value: &str) -> Self {
|
||||
self.0.theme = value.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn keyboard(mut self, value: &str) -> Self {
|
||||
self.0.keyboard_handler = value.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn value(mut self, value: &str) -> Self {
|
||||
self.0.value = value.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> EditorOptions {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EditorOptions {
|
||||
pub mode: String,
|
||||
pub theme: String,
|
||||
pub keyboard_handler: String,
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
impl Default for EditorOptions {
|
||||
fn default() -> Self {
|
||||
EditorOptions {
|
||||
mode: "ace/mode/text".into(),
|
||||
theme: "ace/theme/textmate".into(),
|
||||
keyboard_handler: "ace/keyboard/ace".into(),
|
||||
value: String::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EditorOptions {
|
||||
pub fn to_js(&self) -> Object {
|
||||
serde_wasm_bindgen::to_value(self).unwrap().into()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum EditorError {
|
||||
#[error(transparent)]
|
||||
Serde(#[from] serde_wasm_bindgen::Error),
|
||||
#[error("Failed to open an editor")]
|
||||
Open(JsValue),
|
||||
#[error("Failed to set theme")]
|
||||
SetTheme(JsValue),
|
||||
#[error("Failed to set keyboard handler")]
|
||||
SetKeyboardHandler(JsValue),
|
||||
#[error("Failed to define extenstion")]
|
||||
DefineEx(JsValue),
|
||||
}
|
||||
|
||||
pub struct Editor {
|
||||
js: bindgen::Editor,
|
||||
}
|
||||
|
||||
unsafe impl Send for Editor {}
|
||||
|
||||
unsafe impl Sync for Editor {}
|
||||
|
||||
impl Editor {
|
||||
/// ace.edit
|
||||
pub fn open(element: &str, options: Option<&EditorOptions>) -> Result<Self> {
|
||||
let editor = bindgen::edit(element, options.map(|options| options.to_js()))
|
||||
.map_err(EditorError::Open)?;
|
||||
Ok(Editor { js: editor })
|
||||
}
|
||||
|
||||
pub fn set_theme(&self, theme: &str) -> Result<()> {
|
||||
self.js.set_theme(theme).map_err(EditorError::SetTheme)
|
||||
}
|
||||
|
||||
pub fn set_keyboard_handler(&self, handler: &str) -> Result<()> {
|
||||
self.js
|
||||
.set_keyboard_handler(handler)
|
||||
.map_err(EditorError::SetKeyboardHandler)
|
||||
}
|
||||
|
||||
pub async fn define_vim_w(callback: Box<dyn Fn() + 'static>) -> Result<()> {
|
||||
let notify = Arc::new(tokio::sync::Notify::new());
|
||||
let waiter = Arc::clone(¬ify);
|
||||
|
||||
let load_module = Closure::once(move |_: JsValue| {
|
||||
notify.notify_waiters();
|
||||
});
|
||||
|
||||
bindgen::load_module("ace/keyboard/vim", &load_module);
|
||||
waiter.notified().await;
|
||||
|
||||
let value = bindgen::require("ace/keyboard/vim").map_err(EditorError::DefineEx)?;
|
||||
|
||||
let define_ex = Reflect::get(&value, &JsValue::from("CodeMirror"))
|
||||
.and_then(|code_mirror| Reflect::get(&code_mirror, &JsValue::from("Vim")))
|
||||
.and_then(|vim| Reflect::get(&vim, &JsValue::from("defineEx")))
|
||||
.map_err(EditorError::DefineEx)?;
|
||||
let callback = Closure::wrap(callback);
|
||||
|
||||
js_sys::Function::from(define_ex)
|
||||
.call3(
|
||||
&JsValue::null(),
|
||||
&JsValue::from("write"),
|
||||
&JsValue::from("w"),
|
||||
callback.as_ref().unchecked_ref(),
|
||||
)
|
||||
.map_err(EditorError::DefineEx)?;
|
||||
callback.forget();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_value(&self) -> String {
|
||||
self.js.get_value()
|
||||
}
|
||||
}
|
||||
12
crates/floating-ui/Cargo.toml
Normal file
12
crates/floating-ui/Cargo.toml
Normal file
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "floating-ui"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
serde = "1.0.219"
|
||||
serde-wasm-bindgen = "0.6.5"
|
||||
wasm-bindgen = "0.2.100"
|
||||
wasm-bindgen-futures = "0.4.50"
|
||||
web-sys = "0.3.77"
|
||||
|
||||
83
crates/floating-ui/src/lib.rs
Normal file
83
crates/floating-ui/src/lib.rs
Normal file
@@ -0,0 +1,83 @@
|
||||
use wasm_bindgen::{
|
||||
JsValue,
|
||||
prelude::{Closure, wasm_bindgen},
|
||||
};
|
||||
use web_sys::js_sys::{self, Array, Object, Reflect};
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(js_namespace = ["globalThis", "FloatingUIDOM"], js_name = computePosition)]
|
||||
pub async fn compute_position(
|
||||
reference: JsValue,
|
||||
floating: JsValue,
|
||||
options: JsValue,
|
||||
) -> JsValue;
|
||||
#[wasm_bindgen(js_namespace = ["globalThis", "FloatingUIDOM"])]
|
||||
pub fn offset(value: i32) -> JsValue;
|
||||
#[wasm_bindgen(js_namespace = ["globalThis", "FloatingUIDOM"])]
|
||||
pub fn flip() -> JsValue;
|
||||
#[wasm_bindgen(js_namespace = ["globalThis", "FloatingUIDOM"])]
|
||||
pub fn shift() -> JsValue;
|
||||
#[wasm_bindgen(js_namespace = ["globalThis", "FloatingUIDOM"])]
|
||||
pub fn arrow(options: JsValue) -> JsValue;
|
||||
#[wasm_bindgen(js_namespace = ["globalThis", "FloatingUIDOM"], js_name = autoUpdate)]
|
||||
pub fn auto_update(
|
||||
reference: JsValue,
|
||||
floating: JsValue,
|
||||
callback: &Closure<dyn FnMut()>,
|
||||
options: JsValue,
|
||||
) -> js_sys::Function;
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
pub struct ComputeOptions {
|
||||
placement: String,
|
||||
#[serde(with = "serde_wasm_bindgen::preserve")]
|
||||
middleware: Array,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ComputePosition {
|
||||
pub x: f64,
|
||||
pub y: f64,
|
||||
pub placement: String,
|
||||
pub strategy: String,
|
||||
pub middleware_data: MiddlewareData,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize, Debug)]
|
||||
pub struct MiddlewareData {
|
||||
pub arrow: ArrowPosition,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize, Debug)]
|
||||
pub struct ArrowPosition {
|
||||
pub x: f64,
|
||||
}
|
||||
|
||||
pub fn compute_options(offset_value: i32, element: &JsValue) -> JsValue {
|
||||
let middleware = Array::new();
|
||||
middleware.push(&offset(offset_value));
|
||||
middleware.push(&flip());
|
||||
middleware.push(&shift());
|
||||
let options = {
|
||||
let options = Object::new();
|
||||
Reflect::set(&options, &JsValue::from("element"), element).unwrap();
|
||||
options
|
||||
};
|
||||
middleware.push(&arrow(JsValue::from(options)));
|
||||
|
||||
let options = {
|
||||
let options = Object::new();
|
||||
Reflect::set(
|
||||
&options,
|
||||
&JsValue::from("middleware"),
|
||||
&JsValue::from(middleware),
|
||||
)
|
||||
.unwrap();
|
||||
options
|
||||
};
|
||||
|
||||
JsValue::from(options)
|
||||
}
|
||||
13
crates/istyles/Cargo.toml
Normal file
13
crates/istyles/Cargo.toml
Normal file
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "istyles"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
syn = { version = "2.0", features = ["full"] }
|
||||
quote = "1.0"
|
||||
serde_json = "1.0"
|
||||
proc-macro2 = "1.0.95"
|
||||
55
crates/istyles/src/lib.rs
Normal file
55
crates/istyles/src/lib.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use serde_json::Value;
|
||||
use syn::{LitStr, parse_macro_input};
|
||||
|
||||
#[proc_macro]
|
||||
pub fn istyles(input: TokenStream) -> TokenStream {
|
||||
let (css_module_map, mod_name) = parse_macro_input!(input with parse_input);
|
||||
|
||||
let value: Value = match serde_json::from_str(&css_module_map) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
return syn::Error::new_spanned(css_module_map, format!("Invalid JSON: {}", e))
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
};
|
||||
|
||||
let consts = generate_consts(&value);
|
||||
let expanded = quote! {
|
||||
pub mod #mod_name {
|
||||
#consts
|
||||
}
|
||||
};
|
||||
|
||||
TokenStream::from(expanded)
|
||||
}
|
||||
|
||||
fn parse_input(input: syn::parse::ParseStream) -> syn::Result<(String, syn::Ident)> {
|
||||
let mod_name: syn::Ident = input.parse()?;
|
||||
input.parse::<syn::Token![,]>()?;
|
||||
let path: LitStr = input.parse()?;
|
||||
let path = path.value();
|
||||
let json = std::fs::read_to_string(&path)
|
||||
.map_err(|e| syn::Error::new_spanned(&path, format!("Failed to read JSON: {:?}", e)))?;
|
||||
Ok((json, mod_name))
|
||||
}
|
||||
|
||||
fn generate_consts(value: &Value) -> proc_macro2::TokenStream {
|
||||
match value {
|
||||
Value::Object(map) => {
|
||||
let const_decls = map.iter().map(|(k, v)| {
|
||||
let key = k.replace('-', "_");
|
||||
let key_ident = syn::Ident::new(&key, proc_macro2::Span::call_site());
|
||||
let value_str = v.as_str().unwrap_or_default();
|
||||
|
||||
quote! {
|
||||
pub const #key_ident: &str = #value_str;
|
||||
}
|
||||
});
|
||||
quote! { #(#const_decls)* }
|
||||
}
|
||||
_ => quote! {},
|
||||
}
|
||||
}
|
||||
10
crates/split-grid/Cargo.toml
Normal file
10
crates/split-grid/Cargo.toml
Normal file
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "split-grid"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
serde = "1.0.219"
|
||||
serde-wasm-bindgen = "0.6.5"
|
||||
wasm-bindgen = "0.2.100"
|
||||
web-sys = "0.3.77"
|
||||
38
crates/split-grid/src/lib.rs
Normal file
38
crates/split-grid/src/lib.rs
Normal file
@@ -0,0 +1,38 @@
|
||||
use wasm_bindgen::{JsValue, prelude::wasm_bindgen};
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
pub type Grid;
|
||||
|
||||
#[wasm_bindgen(js_namespace = ["globalThis"], js_name = Split)]
|
||||
pub fn split(options: &JsValue) -> Grid;
|
||||
|
||||
#[wasm_bindgen(method)]
|
||||
pub fn destroy(this: &Grid);
|
||||
}
|
||||
|
||||
unsafe impl Send for Grid {}
|
||||
unsafe impl Sync for Grid {}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct Gutter {
|
||||
pub track: i32,
|
||||
#[serde(with = "serde_wasm_bindgen::preserve")]
|
||||
pub element: JsValue,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SplitOptions {
|
||||
pub min_size: i32,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub row_gutters: Option<Vec<Gutter>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub column_gutters: Option<Vec<Gutter>>,
|
||||
}
|
||||
|
||||
impl From<SplitOptions> for JsValue {
|
||||
fn from(value: SplitOptions) -> Self {
|
||||
serde_wasm_bindgen::to_value(&value).unwrap()
|
||||
}
|
||||
}
|
||||
45
index.html
Normal file
45
index.html
Normal file
@@ -0,0 +1,45 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script src="https://unpkg.com/split-grid/dist/split-grid.js"></script>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/@floating-ui/core@1.7.0"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@floating-ui/dom@1.7.0"></script>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.40.1/ace.min.js" integrity="sha512-yZCW/4uyGQEinu8VJLwUj/w4hXUGuP027EvOt+wLzI0T3v+2o/UhK6L7dwt2QXYOdcnYAS1yheQk6AzNVdlA5A==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.40.1/mode-sql.min.js" integrity="sha512-dABa+YCy/MOELLU5BKnJeR/pcORL8icJNR1TBZWrRmlFoWXZEFketX6Qx/DVBkewMZC8mUOGJMJ70fwxcpIDCw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.40.1/theme-gruvbox.min.js" integrity="sha512-csAdQ+MAmg252+rlVpEgxnAzlzPBBEDzi8QSoaMyV1lJqMZuzASQUDyTpT1W2gGqrRLioKPaX0ylDwkMXxzDsw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.40.1/theme-github.min.js" integrity="sha512-6PQOyKN8ulKmC/NQlWnhk2Upjza1Lj9pbYD/6+gNuNG+U28jW/x9iJQJuiQkfQAFggY1sToW9va3vY4xfpsJaA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.41.0/theme-github_dark.min.js" integrity="sha512-9KPSgJ+mukvk+UY25jG7bM1hOR43Sw9jmJxc5q5aa7RAMoQeyY12PNfwwUAPhe9AtOpUA1jKsDRgxeDCXe/uMg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300..800;1,300..800&family=Source+Code+Pro:ital,wght@0,200..900;1,200..900&display=swap" rel="stylesheet">
|
||||
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css" integrity="sha512-NhSC1YmyruXifcj/KFRWoC561YpHpc5Jtzgvbuzx5VozKpWvQ+4nXhPdFgmx8xqexRcpAglTj9sIBWINXa8x5w==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||
|
||||
<link data-trunk href="./assets/module.postcss/index.module.css" rel="css">
|
||||
<link data-trunk href="./assets/module.postcss/playground.module.css" rel="css">
|
||||
<link data-trunk href="./assets/module.postcss/header.module.css" rel="css">
|
||||
<link data-trunk href="./assets/module.postcss/button_set.module.css" rel="css">
|
||||
<link data-trunk href="./assets/module.postcss/icon.module.css" rel="css">
|
||||
<link data-trunk href="./assets/module.postcss/editor.module.css" rel="css">
|
||||
<link data-trunk href="./assets/module.postcss/output.module.css" rel="css">
|
||||
<link data-trunk href="./assets/module.postcss/pop_button.module.css" rel="css">
|
||||
<link data-trunk href="./assets/module.postcss/menu_group.module.css" rel="css">
|
||||
<link data-trunk href="./assets/module.postcss/menu_item.module.css" rel="css">
|
||||
<link data-trunk href="./assets/module.postcss/selectable_menu_item.module.css" rel="css">
|
||||
<link data-trunk href="./assets/module.postcss/loader.module.css" rel="css">
|
||||
<link data-trunk href="./assets/module.postcss/config_element.module.css" rel="css">
|
||||
|
||||
<link data-trunk href="./assets/module.postcss/output/execute.module.css" rel="css">
|
||||
<link data-trunk href="./assets/module.postcss/output/header.module.css" rel="css">
|
||||
<link data-trunk href="./assets/module.postcss/output/section.module.css" rel="css">
|
||||
<link data-trunk href="./assets/module.postcss/output/share.module.css" rel="css">
|
||||
|
||||
<link data-trunk rel="rust" href="Cargo.toml" data-bin="app" data-type="main" />
|
||||
<link data-trunk rel="rust" href="Cargo.toml" data-bin="worker" data-type="worker" data-loader-shim data-bindgen-target="web" />
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
||||
3930
package-lock.json
generated
Normal file
3930
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
13
package.json
Normal file
13
package.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"postcss-mixins": "^11.0.3",
|
||||
"postcss-modules": "^6.0.1",
|
||||
"postcss-plugin": "^1.0.0",
|
||||
"postcss-preset-env": "^10.1.6",
|
||||
"postcss-simple-vars": "^7.0.1",
|
||||
},
|
||||
"devDependencies": {
|
||||
"postcss": "^8.5.3",
|
||||
"postcss-cli": "^11.0.1"
|
||||
}
|
||||
}
|
||||
15
postcss.config.js
Normal file
15
postcss.config.js
Normal file
@@ -0,0 +1,15 @@
|
||||
module.exports = () => ({
|
||||
plugins: [
|
||||
require('postcss-simple-vars')(),
|
||||
require('postcss-preset-env')(),
|
||||
require('postcss-mixins')(),
|
||||
require("postcss-modules")({
|
||||
getJSON: function(_, json, outputFileName) {
|
||||
var path = require("path");
|
||||
var fs = require("fs");
|
||||
var jsonFileName = path.resolve(outputFileName + ".map");
|
||||
fs.writeFileSync(jsonFileName, JSON.stringify(json));
|
||||
},
|
||||
}),
|
||||
]
|
||||
});
|
||||
68
src/app/button_set.rs
Normal file
68
src/app/button_set.rs
Normal file
@@ -0,0 +1,68 @@
|
||||
use istyles::istyles;
|
||||
use leptos::{prelude::*, tachys::html};
|
||||
use web_sys::MouseEvent;
|
||||
|
||||
istyles!(styles, "assets/module.postcss/button_set.module.css.map");
|
||||
|
||||
#[component]
|
||||
pub fn ButtonSet(
|
||||
#[prop(default = String::new())] class_name: String,
|
||||
children: Children,
|
||||
) -> impl IntoView {
|
||||
let class = format!("{} {}", styles::set, class_name);
|
||||
|
||||
view! { <div class=class>{children()}</div> }
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Button<C>(
|
||||
#[prop(default = false)] is_primary: bool,
|
||||
#[prop(default = false)] is_small: bool,
|
||||
#[prop(optional)] icon_left: Option<AnyView>,
|
||||
#[prop(optional)] icon_right: Option<AnyView>,
|
||||
#[prop(optional)] node_ref: NodeRef<html::element::Button>,
|
||||
on_click: C,
|
||||
children: Children,
|
||||
) -> impl IntoView
|
||||
where
|
||||
C: FnMut(MouseEvent) + Send + 'static,
|
||||
{
|
||||
let class = format!(
|
||||
"{} {}",
|
||||
if is_primary {
|
||||
styles::primary
|
||||
} else {
|
||||
styles::secondary
|
||||
},
|
||||
if is_small { styles::small } else { "" }
|
||||
);
|
||||
|
||||
let icon_left = move || {
|
||||
if let Some(icon) = icon_left {
|
||||
view! { <span class=styles::iconLeft>{icon}</span> }.into_any()
|
||||
} else {
|
||||
().into_any()
|
||||
}
|
||||
};
|
||||
|
||||
let icon_right = move || {
|
||||
if let Some(icon) = icon_right {
|
||||
view! { <span class=styles::iconRight>{icon}</span> }.into_any()
|
||||
} else {
|
||||
().into_any()
|
||||
}
|
||||
};
|
||||
|
||||
view! {
|
||||
<button type="button" class=class on:click=on_click node_ref=node_ref>
|
||||
{icon_left()}
|
||||
{children()}
|
||||
{icon_right()}
|
||||
</button>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Rule() -> impl IntoView {
|
||||
view! { <span class=styles::rule /> }
|
||||
}
|
||||
50
src/app/config_element.rs
Normal file
50
src/app/config_element.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
use istyles::istyles;
|
||||
use leptos::prelude::*;
|
||||
use web_sys::Event;
|
||||
|
||||
use crate::app::menu_item::MenuItem;
|
||||
|
||||
istyles!(
|
||||
styles,
|
||||
"assets/module.postcss/config_element.module.css.map"
|
||||
);
|
||||
|
||||
#[component]
|
||||
pub fn Select<E>(
|
||||
on_change: E,
|
||||
name: String,
|
||||
#[prop(default = true)] is_default: bool,
|
||||
children: Children,
|
||||
) -> impl IntoView
|
||||
where
|
||||
E: FnMut(Event) + Send + 'static,
|
||||
{
|
||||
view! {
|
||||
<ConfigElement name=name is_default=is_default>
|
||||
<select class=styles::select on:change=on_change>
|
||||
{children()}
|
||||
</select>
|
||||
</ConfigElement>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn ConfigElement(
|
||||
name: String,
|
||||
#[prop(default = true)] is_default: bool,
|
||||
children: Children,
|
||||
) -> impl IntoView {
|
||||
let style = if is_default {
|
||||
styles::name
|
||||
} else {
|
||||
styles::notDefault
|
||||
};
|
||||
view! {
|
||||
<MenuItem>
|
||||
<div class=styles::container>
|
||||
<span class=style>{name}</span>
|
||||
<div class=styles::value>{children()}</div>
|
||||
</div>
|
||||
</MenuItem>
|
||||
}
|
||||
}
|
||||
118
src/app/config_menu.rs
Normal file
118
src/app/config_menu.rs
Normal file
@@ -0,0 +1,118 @@
|
||||
use leptos::prelude::*;
|
||||
use reactive_stores::Store;
|
||||
use wasm_bindgen::JsValue;
|
||||
use web_sys::{Event, HtmlSelectElement};
|
||||
|
||||
use crate::{
|
||||
SQLightError,
|
||||
app::{
|
||||
GlobalState, GlobalStateStoreFields, Orientation, Theme,
|
||||
config_element::Select as SelectConfig, menu_group::MenuGroup,
|
||||
},
|
||||
};
|
||||
|
||||
const ACE_KEYBOARDS: [&str; 5] = ["ace", "emacs", "sublime", "vim", "vscode"];
|
||||
const ACE_THEMES: [&str; 3] = ["github", "github_dark", "gruvbox"];
|
||||
|
||||
fn selecet_view(s: &str, selected: &str) -> AnyView {
|
||||
if s == selected {
|
||||
view! {
|
||||
<option selected value=s>
|
||||
{s}
|
||||
</option>
|
||||
}
|
||||
.into_any()
|
||||
} else {
|
||||
view! { <option value=s>{s}</option> }.into_any()
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn ConfigMenu() -> impl IntoView {
|
||||
let state = expect_context::<Store<GlobalState>>();
|
||||
|
||||
let ace_keyboard_change = move |event: Event| {
|
||||
if let Some(target) = event.target() {
|
||||
let select = HtmlSelectElement::from(JsValue::from(target));
|
||||
state.editor_config().write().keyboard = select.value();
|
||||
if let Some(Err(err)) = state.editor().read().as_ref().map(|editor| {
|
||||
editor.set_keyboard_handler(&format!("ace/keyboard/{}", select.value()))
|
||||
}) {
|
||||
state
|
||||
.last_error()
|
||||
.set(Some(SQLightError::new_ace_editor(err)));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let ace_theme_change = move |event: Event| {
|
||||
if let Some(target) = event.target() {
|
||||
let select = HtmlSelectElement::from(JsValue::from(target));
|
||||
state.editor_config().write().theme = select.value();
|
||||
if let Some(Err(err)) = state
|
||||
.editor()
|
||||
.read()
|
||||
.as_ref()
|
||||
.map(|editor| editor.set_theme(&format!("ace/theme/{}", select.value())))
|
||||
{
|
||||
state
|
||||
.last_error()
|
||||
.set(Some(SQLightError::new_ace_editor(err)));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let theme_change = move |event: Event| {
|
||||
if let Some(target) = event.target() {
|
||||
let select = HtmlSelectElement::from(JsValue::from(target));
|
||||
*state.theme().write() = Theme::from_value(&select.value());
|
||||
}
|
||||
};
|
||||
|
||||
let orientation_change = move |event: Event| {
|
||||
if let Some(target) = event.target() {
|
||||
let select = HtmlSelectElement::from(JsValue::from(target));
|
||||
*state.orientation().write() = Orientation::from_value(&select.value());
|
||||
}
|
||||
};
|
||||
|
||||
view! {
|
||||
<MenuGroup title="Editor".into()>
|
||||
<SelectConfig name="Keybinding".into() on_change=ace_keyboard_change>
|
||||
{move || {
|
||||
ACE_KEYBOARDS
|
||||
.into_iter()
|
||||
.map(|s| selecet_view(s, &state.editor_config().read().keyboard))
|
||||
.collect_view()
|
||||
}}
|
||||
</SelectConfig>
|
||||
<SelectConfig name="Theme".into() on_change=ace_theme_change>
|
||||
{move || {
|
||||
ACE_THEMES
|
||||
.into_iter()
|
||||
.map(|s| selecet_view(s, &state.editor_config().read().theme))
|
||||
.collect_view()
|
||||
}}
|
||||
</SelectConfig>
|
||||
</MenuGroup>
|
||||
|
||||
<MenuGroup title="UI".into()>
|
||||
<SelectConfig name="Theme".into() on_change=theme_change>
|
||||
{move || {
|
||||
["System", "Light", "Dark"]
|
||||
.into_iter()
|
||||
.map(|s| selecet_view(s, &state.theme().read().to_value()))
|
||||
.collect_view()
|
||||
}}
|
||||
</SelectConfig>
|
||||
<SelectConfig name="Orientation".into() on_change=orientation_change>
|
||||
{move || {
|
||||
["Automatic", "Horizontal", "Vertical"]
|
||||
.into_iter()
|
||||
.map(|s| selecet_view(s, &state.orientation().read().to_value()))
|
||||
.collect_view()
|
||||
}}
|
||||
</SelectConfig>
|
||||
</MenuGroup>
|
||||
}
|
||||
}
|
||||
36
src/app/context_menu.rs
Normal file
36
src/app/context_menu.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
use leptos::prelude::*;
|
||||
use reactive_stores::Store;
|
||||
|
||||
use crate::app::{
|
||||
GlobalState, GlobalStateStoreFields, menu_group::MenuGroup, select_one::SelectOne,
|
||||
};
|
||||
|
||||
#[component]
|
||||
pub fn ContextMenu() -> impl IntoView {
|
||||
let state = expect_context::<Store<GlobalState>>();
|
||||
|
||||
view! {
|
||||
<MenuGroup title="Choose whether to keep the context".into()>
|
||||
<SelectOne
|
||||
name="Discard Context".into()
|
||||
current_value=move || { *state.keep_ctx().read() }
|
||||
this_value=false
|
||||
change_value=move || {
|
||||
*state.keep_ctx().write() = false;
|
||||
}
|
||||
>
|
||||
"Each execution is in a new DB."
|
||||
</SelectOne>
|
||||
<SelectOne
|
||||
name="Keep Context".into()
|
||||
current_value=move || { *state.keep_ctx().read() }
|
||||
this_value=true
|
||||
change_value=move || {
|
||||
*state.keep_ctx().write() = true;
|
||||
}
|
||||
>
|
||||
"Keep the results of each execution."
|
||||
</SelectOne>
|
||||
</MenuGroup>
|
||||
}
|
||||
}
|
||||
60
src/app/editor.rs
Normal file
60
src/app/editor.rs
Normal file
@@ -0,0 +1,60 @@
|
||||
use aceditor::EditorOptionsBuilder;
|
||||
use istyles::istyles;
|
||||
use leptos::prelude::*;
|
||||
use reactive_stores::Store;
|
||||
use wasm_bindgen_futures::spawn_local;
|
||||
use web_sys::UrlSearchParams;
|
||||
|
||||
use crate::{
|
||||
SQLightError,
|
||||
app::{GlobalState, GlobalStateStoreFields, header::execute},
|
||||
};
|
||||
|
||||
istyles!(styles, "assets/module.postcss/editor.module.css.map");
|
||||
|
||||
#[component]
|
||||
pub fn Editor() -> impl IntoView {
|
||||
let state = expect_context::<Store<GlobalState>>();
|
||||
|
||||
let editor_ref = NodeRef::new();
|
||||
|
||||
editor_ref.on_load(move |_| {
|
||||
let shared_code = || {
|
||||
let search = window().location().search().ok()?;
|
||||
let params = UrlSearchParams::new_with_str(&search).ok()?;
|
||||
params.get("code")
|
||||
};
|
||||
let opt = EditorOptionsBuilder::default()
|
||||
.mode("ace/mode/sql")
|
||||
.theme(&format!(
|
||||
"ace/theme/{}",
|
||||
state.editor_config().read_untracked().theme
|
||||
))
|
||||
.keyboard(&format!(
|
||||
"ace/keyboard/{}",
|
||||
state.editor_config().read_untracked().keyboard
|
||||
))
|
||||
.value(&shared_code().unwrap_or_else(|| state.code().get_untracked()))
|
||||
.build();
|
||||
|
||||
match aceditor::Editor::open("ace_editor", Some(&opt)) {
|
||||
Ok(editor) => state.editor().set(Some(editor)),
|
||||
Err(err) => state
|
||||
.last_error()
|
||||
.set(Some(SQLightError::new_ace_editor(err))),
|
||||
}
|
||||
|
||||
spawn_local(async move {
|
||||
if let Err(err) = aceditor::Editor::define_vim_w(execute(state)).await {
|
||||
state
|
||||
.last_error()
|
||||
.set(Some(SQLightError::new_ace_editor(err)));
|
||||
}
|
||||
});
|
||||
});
|
||||
view! {
|
||||
<div class=styles::container>
|
||||
<div node_ref=editor_ref id="ace_editor" class=styles::ace></div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
187
src/app/header.rs
Normal file
187
src/app/header.rs
Normal file
@@ -0,0 +1,187 @@
|
||||
use istyles::istyles;
|
||||
use leptos::{prelude::*, tachys::html};
|
||||
use reactive_stores::Store;
|
||||
use web_sys::{Url, UrlSearchParams};
|
||||
|
||||
use crate::{
|
||||
PrepareOptions, WorkerRequest,
|
||||
app::{
|
||||
button_set::{Button, ButtonSet, Rule},
|
||||
config_menu::ConfigMenu,
|
||||
context_menu::ContextMenu,
|
||||
icon::{build_icon, config_icon, expandable_icon},
|
||||
output::change_focus,
|
||||
pop_button::PopButton,
|
||||
state::{Focus, GlobalState, GlobalStateStoreFields},
|
||||
vfs_menu::VfsMenu,
|
||||
},
|
||||
};
|
||||
|
||||
istyles!(styles, "assets/module.postcss/header.module.css.map");
|
||||
|
||||
#[component]
|
||||
pub fn Header() -> impl IntoView {
|
||||
let menu_container = NodeRef::new();
|
||||
|
||||
view! {
|
||||
<>
|
||||
<div id="header" class=styles::container>
|
||||
<div class=styles::left>
|
||||
<ButtonSet>
|
||||
<ExecuteButton />
|
||||
</ButtonSet>
|
||||
|
||||
<ButtonSet>
|
||||
<VfsMenuButton menu_container=menu_container />
|
||||
<Rule />
|
||||
<ContextMenuButton menu_container=menu_container />
|
||||
</ButtonSet>
|
||||
</div>
|
||||
<div class=styles::right>
|
||||
<ButtonSet>
|
||||
<ShareButton />
|
||||
</ButtonSet>
|
||||
<ButtonSet>
|
||||
<ConfigMenuButton menu_container=menu_container />
|
||||
</ButtonSet>
|
||||
</div>
|
||||
</div>
|
||||
<div node_ref=menu_container></div>
|
||||
</>
|
||||
}
|
||||
}
|
||||
|
||||
pub fn execute(state: Store<GlobalState>) -> Box<dyn Fn() + Send + 'static> {
|
||||
Box::new(move || {
|
||||
let Some(code) = state
|
||||
.editor()
|
||||
.read_untracked()
|
||||
.as_ref()
|
||||
.map(|editor| editor.get_value())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
state.code().set(code.clone());
|
||||
change_focus(state, Some(Focus::Execute));
|
||||
std::mem::take(&mut *state.output().write());
|
||||
if let Some(worker) = &*state.worker().read_untracked() {
|
||||
worker.send_task(WorkerRequest::Prepare(PrepareOptions {
|
||||
id: String::new(),
|
||||
sql: code,
|
||||
clear_on_prepare: !*state.keep_ctx().read_untracked(),
|
||||
}));
|
||||
worker.send_task(WorkerRequest::Continue(String::new()));
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn ExecuteButton() -> impl IntoView {
|
||||
let state = expect_context::<Store<GlobalState>>();
|
||||
|
||||
let on_click = execute(state);
|
||||
|
||||
view! {
|
||||
<Button is_primary=true icon_right=build_icon() on_click=move |_| on_click()>
|
||||
"Run"
|
||||
</Button>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn VfsMenuButton(menu_container: NodeRef<html::element::Div>) -> impl IntoView {
|
||||
let state = expect_context::<Store<GlobalState>>();
|
||||
|
||||
let button = move |toggle, node_ref| {
|
||||
view! {
|
||||
<Button icon_right=expandable_icon() on_click=toggle node_ref=node_ref>
|
||||
{move || state.vfs().read().value()}
|
||||
</Button>
|
||||
}
|
||||
.into_any()
|
||||
};
|
||||
|
||||
view! {
|
||||
<PopButton
|
||||
button=button
|
||||
menu=Box::new(|_close| { view! { <VfsMenu /> }.into_any() })
|
||||
menu_container=menu_container
|
||||
></PopButton>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn ContextMenuButton(menu_container: NodeRef<html::element::Div>) -> impl IntoView {
|
||||
let state = expect_context::<Store<GlobalState>>();
|
||||
|
||||
let button = move |toggle, node_ref| {
|
||||
view! {
|
||||
<Button icon_right=expandable_icon() on_click=toggle node_ref=node_ref>
|
||||
{move || if *state.keep_ctx().read() { "Keep Context" } else { "Discard Context" }}
|
||||
</Button>
|
||||
}
|
||||
.into_any()
|
||||
};
|
||||
|
||||
view! {
|
||||
<PopButton
|
||||
button=button
|
||||
menu=Box::new(|_close| { view! { <ContextMenu /> }.into_any() })
|
||||
menu_container=menu_container
|
||||
></PopButton>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn ConfigMenuButton(menu_container: NodeRef<html::element::Div>) -> impl IntoView {
|
||||
let button = |toggle, node_ref| {
|
||||
view! {
|
||||
<Button
|
||||
icon_left=config_icon()
|
||||
icon_right=expandable_icon()
|
||||
on_click=toggle
|
||||
node_ref=node_ref
|
||||
>
|
||||
"Config"
|
||||
</Button>
|
||||
}
|
||||
.into_any()
|
||||
};
|
||||
|
||||
view! {
|
||||
<PopButton
|
||||
button=button
|
||||
menu=Box::new(|_close| { view! { <ConfigMenu /> }.into_any() })
|
||||
menu_container=menu_container
|
||||
></PopButton>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn ShareButton() -> impl IntoView {
|
||||
let state = expect_context::<Store<GlobalState>>();
|
||||
|
||||
let click = move |_| {
|
||||
let Some(code) = state
|
||||
.editor()
|
||||
.read()
|
||||
.as_ref()
|
||||
.map(|editor| editor.get_value())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Ok(href) = window().location().href().and_then(|href| {
|
||||
let url = Url::new(&href)?;
|
||||
let params = UrlSearchParams::new()?;
|
||||
params.set("code", &code);
|
||||
url.set_search(¶ms.to_string().as_string().unwrap());
|
||||
Ok(url.href())
|
||||
}) {
|
||||
*state.share_href().write() = Some(href);
|
||||
change_focus(state, Some(Focus::Share));
|
||||
}
|
||||
};
|
||||
|
||||
view! { <Button on_click=click>"Share"</Button> }
|
||||
}
|
||||
81
src/app/icon.rs
Normal file
81
src/app/icon.rs
Normal file
@@ -0,0 +1,81 @@
|
||||
use istyles::istyles;
|
||||
use leptos::prelude::*;
|
||||
|
||||
istyles!(styles, "assets/module.postcss/icon.module.css.map");
|
||||
|
||||
pub fn build_icon() -> AnyView {
|
||||
view! {
|
||||
<svg
|
||||
class=styles::icon
|
||||
height="14"
|
||||
viewBox="8 4 10 16"
|
||||
width="12"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M8 5v14l11-7z" />
|
||||
</svg>
|
||||
}
|
||||
.into_any()
|
||||
}
|
||||
|
||||
pub fn config_icon() -> AnyView {
|
||||
view! {
|
||||
<svg
|
||||
class=styles::icon
|
||||
height="15"
|
||||
viewBox="0 0 24 24"
|
||||
width="15"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M19.43 12.98c.04-.32.07-.64.07-.98s-.03-.66-.07-.98l2.11-1.65c.19-.15.24-.42.12-.64l-2-3.46c-.12-.22-.39-.3-.61-.22l-2.49 1c-.52-.4-1.08-.73-1.69-.98l-.38-2.65C14.46 2.18 14.25 2 14 2h-4c-.25 0-.46.18-.49.42l-.38 2.65c-.61.25-1.17.59-1.69.98l-2.49-1c-.23-.09-.49 0-.61.22l-2 3.46c-.13.22-.07.49.12.64l2.11 1.65c-.04.32-.07.65-.07.98s.03.66.07.98l-2.11 1.65c-.19.15-.24.42-.12.64l2 3.46c.12.22.39.3.61.22l2.49-1c.52.4 1.08.73 1.69.98l.38 2.65c.03.24.24.42.49.42h4c.25 0 .46-.18.49-.42l.38-2.65c.61-.25 1.17-.59 1.69-.98l2.49 1c.23.09.49 0 .61-.22l2-3.46c.12-.22.07-.49-.12-.64l-2.11-1.65zM12 15.5c-1.93 0-3.5-1.57-3.5-3.5s1.57-3.5 3.5-3.5 3.5 1.57 3.5 3.5-1.57 3.5-3.5 3.5z" />
|
||||
</svg>
|
||||
}.into_any()
|
||||
}
|
||||
|
||||
pub fn expandable_icon() -> AnyView {
|
||||
view! {
|
||||
<svg
|
||||
class=styles::icon
|
||||
height="10"
|
||||
viewBox="6 8 12 8"
|
||||
width="10"
|
||||
opacity="0.5"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z" />
|
||||
</svg>
|
||||
}
|
||||
.into_any()
|
||||
}
|
||||
|
||||
pub fn checkmark_icon() -> AnyView {
|
||||
view! {
|
||||
<svg
|
||||
class=styles::icon
|
||||
height="18"
|
||||
viewBox="2 2 22 22"
|
||||
width="18"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z" />
|
||||
</svg>
|
||||
}
|
||||
.into_any()
|
||||
}
|
||||
|
||||
pub fn clipboard_icon() -> AnyView {
|
||||
view! {
|
||||
<svg
|
||||
class=styles::icon
|
||||
height="18"
|
||||
width="18"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect x="7" y="15" width="7" height="2" />
|
||||
<rect x="7" y="11" width="10" height="2" />
|
||||
<rect x="7" y="7" width="10" height="2" />
|
||||
<path d="M19,3L19,3h-4.18C14.4,1.84,13.3,1,12,1c-1.3,0-2.4,0.84-2.82,2H5h0C4.86,3,4.73,3.01,4.6,3.04 C4.21,3.12,3.86,3.32,3.59,3.59c-0.18,0.18-0.33,0.4-0.43,0.64C3.06,4.46,3,4.72,3,5v14c0,0.27,0.06,0.54,0.16,0.78 c0.1,0.24,0.25,0.45,0.43,0.64c0.27,0.27,0.62,0.47,1.01,0.55C4.73,20.99,4.86,21,5,21h0h14h0c1.1,0,2-0.9,2-2V5 C21,3.9,20.1,3,19,3z M12,2.75c0.41,0,0.75,0.34,0.75,0.75c0,0.41-0.34,0.75-0.75,0.75c-0.41,0-0.75-0.34-0.75-0.75 C11.25,3.09,11.59,2.75,12,2.75z M19,19H5V5h14V19z" />
|
||||
</svg>
|
||||
}.into_any()
|
||||
}
|
||||
15
src/app/loader.rs
Normal file
15
src/app/loader.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
use istyles::istyles;
|
||||
use leptos::prelude::*;
|
||||
|
||||
istyles!(styles, "assets/module.postcss/loader.module.css.map");
|
||||
|
||||
#[component]
|
||||
pub fn Loader() -> impl IntoView {
|
||||
view! {
|
||||
<div>
|
||||
<span class=styles::dot>"⬤"</span>
|
||||
<span class=styles::dot>"⬤"</span>
|
||||
<span class=styles::dot>"⬤"</span>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
14
src/app/menu_group.rs
Normal file
14
src/app/menu_group.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
use istyles::istyles;
|
||||
use leptos::prelude::*;
|
||||
|
||||
istyles!(styles, "assets/module.postcss/menu_group.module.css.map");
|
||||
|
||||
#[component]
|
||||
pub fn MenuGroup(title: String, children: Children) -> impl IntoView {
|
||||
view! {
|
||||
<div class=styles::container>
|
||||
<h1 class=styles::title>{title}</h1>
|
||||
<div class=styles::content>{children()}</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
9
src/app/menu_item.rs
Normal file
9
src/app/menu_item.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
use istyles::istyles;
|
||||
use leptos::prelude::*;
|
||||
|
||||
istyles!(styles, "assets/module.postcss/menu_item.module.css.map");
|
||||
|
||||
#[component]
|
||||
pub fn MenuItem(children: Children) -> impl IntoView {
|
||||
view! { <div class=styles::container>{children()}</div> }
|
||||
}
|
||||
20
src/app/mod.rs
Normal file
20
src/app/mod.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
mod button_set;
|
||||
mod config_element;
|
||||
mod config_menu;
|
||||
mod context_menu;
|
||||
mod editor;
|
||||
mod header;
|
||||
mod icon;
|
||||
mod loader;
|
||||
mod menu_group;
|
||||
mod menu_item;
|
||||
mod output;
|
||||
mod playground;
|
||||
mod pop_button;
|
||||
mod select_one;
|
||||
mod selectable_menu_item;
|
||||
mod state;
|
||||
mod vfs_menu;
|
||||
|
||||
pub use playground::playground;
|
||||
pub use state::*;
|
||||
141
src/app/output.rs
Normal file
141
src/app/output.rs
Normal file
@@ -0,0 +1,141 @@
|
||||
mod execute;
|
||||
mod header;
|
||||
mod loader;
|
||||
mod section;
|
||||
mod share;
|
||||
mod simple_pane;
|
||||
mod status;
|
||||
|
||||
use execute::Execute;
|
||||
use istyles::istyles;
|
||||
use leptos::prelude::*;
|
||||
use reactive_stores::Store;
|
||||
use share::Share;
|
||||
use status::Status;
|
||||
|
||||
use crate::app::state::{Focus, GlobalState, GlobalStateStoreFields};
|
||||
|
||||
istyles!(styles, "assets/module.postcss/output.module.css.map");
|
||||
|
||||
fn close() -> AnyView {
|
||||
let state = expect_context::<Store<GlobalState>>();
|
||||
|
||||
if state.read().is_focus() {
|
||||
view! {
|
||||
<button class=styles::tabClose on:click=move |_| change_focus(state, None)>
|
||||
"Close"
|
||||
</button>
|
||||
}
|
||||
.into_any()
|
||||
} else {
|
||||
().into_any()
|
||||
}
|
||||
}
|
||||
|
||||
fn body() -> AnyView {
|
||||
let state = expect_context::<Store<GlobalState>>();
|
||||
|
||||
if state.read().is_focus() {
|
||||
view! {
|
||||
<>
|
||||
<div class=styles::body>
|
||||
<Show
|
||||
when=move || matches!(*state.focus().read(), Some(Focus::Execute))
|
||||
fallback=|| ()
|
||||
>
|
||||
<Execute />
|
||||
</Show>
|
||||
|
||||
<Show
|
||||
when=move || matches!(*state.focus().read(), Some(Focus::Share))
|
||||
fallback=|| ()
|
||||
>
|
||||
<Share />
|
||||
</Show>
|
||||
|
||||
<Show
|
||||
when=move || matches!(*state.focus().read(), Some(Focus::Status))
|
||||
fallback=|| ()
|
||||
>
|
||||
<Status />
|
||||
</Show>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
.into_any()
|
||||
} else {
|
||||
().into_any()
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Tab(kind: Focus, label: String) -> impl IntoView {
|
||||
let state = expect_context::<Store<GlobalState>>();
|
||||
|
||||
let class = move || {
|
||||
if matches!(*state.focus().read(), Some(focus) if focus == kind) {
|
||||
styles::tabSelected
|
||||
} else {
|
||||
styles::tab
|
||||
}
|
||||
};
|
||||
|
||||
view! {
|
||||
<Show when=move || state.opened_focus().read().contains(&kind) fallback=|| ()>
|
||||
<button class=class>{label.clone()}</button>
|
||||
</Show>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Output() -> impl IntoView {
|
||||
let state = expect_context::<Store<GlobalState>>();
|
||||
|
||||
view! {
|
||||
<Show when=move || *state.show_something().read() fallback=|| ()>
|
||||
<div class=styles::container>
|
||||
<div class=styles::tabs>
|
||||
<Tab
|
||||
kind=Focus::Execute
|
||||
label="Execution".into()
|
||||
on:click=move |_| change_focus(state, Some(Focus::Execute))
|
||||
/>
|
||||
|
||||
<Tab
|
||||
kind=Focus::Share
|
||||
label="Share".into()
|
||||
on:click=move |_| change_focus(state, Some(Focus::Share))
|
||||
/>
|
||||
|
||||
<Tab
|
||||
kind=Focus::Status
|
||||
label="Status".into()
|
||||
on:click=move |_| change_focus(state, Some(Focus::Status))
|
||||
/>
|
||||
{close}
|
||||
</div>
|
||||
{body}
|
||||
</div>
|
||||
</Show>
|
||||
}
|
||||
}
|
||||
|
||||
pub fn change_focus(state: Store<GlobalState>, focus: Option<Focus>) {
|
||||
if let Some(focus) = focus {
|
||||
state.opened_focus().write().insert(focus);
|
||||
state
|
||||
.is_focused()
|
||||
.maybe_update(|before| !std::mem::replace(before, true));
|
||||
} else {
|
||||
state
|
||||
.is_focused()
|
||||
.maybe_update(|before| std::mem::replace(before, false));
|
||||
}
|
||||
|
||||
state
|
||||
.focus()
|
||||
.maybe_update(|before| std::mem::replace(before, focus) != focus);
|
||||
state
|
||||
.show_something()
|
||||
.maybe_update(|before| !std::mem::replace(before, true));
|
||||
}
|
||||
110
src/app/output/execute.rs
Normal file
110
src/app/output/execute.rs
Normal file
@@ -0,0 +1,110 @@
|
||||
use istyles::istyles;
|
||||
use leptos::prelude::*;
|
||||
use reactive_stores::{Store, StoreFieldIterator};
|
||||
|
||||
use crate::app::{
|
||||
output::{header::Header, loader::Loader, section::Section, simple_pane::SimplePane},
|
||||
state::{GlobalState, GlobalStateStoreFields},
|
||||
};
|
||||
use crate::{SQLiteStatementResult, SQLiteStatementTable};
|
||||
|
||||
istyles!(
|
||||
styles,
|
||||
"assets/module.postcss/output/execute.module.css.map"
|
||||
);
|
||||
|
||||
fn get_output(table: &SQLiteStatementTable) -> Option<AnyView> {
|
||||
let Some(values) = &table.values else {
|
||||
return None;
|
||||
};
|
||||
Some(
|
||||
view! {
|
||||
<table class=styles::table>
|
||||
<tr>
|
||||
{values
|
||||
.columns
|
||||
.iter()
|
||||
.map(|s| {
|
||||
view! { <th class=styles::tdAndTh>{s.to_string()}</th> }
|
||||
})
|
||||
.collect_view()}
|
||||
</tr>
|
||||
{values
|
||||
.rows
|
||||
.iter()
|
||||
.map(|row| {
|
||||
view! {
|
||||
<tr>
|
||||
|
||||
{row
|
||||
.iter()
|
||||
.map(|s| {
|
||||
view! { <td class=styles::tdAndTh>{s.to_string()}</td> }
|
||||
})
|
||||
.collect_view()}
|
||||
</tr>
|
||||
}
|
||||
})
|
||||
.collect_view()}
|
||||
</table>
|
||||
}
|
||||
.into_any(),
|
||||
)
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Output() -> AnyView {
|
||||
let state = expect_context::<Store<GlobalState>>();
|
||||
|
||||
view! {
|
||||
<>
|
||||
<Show
|
||||
when=move || {
|
||||
state
|
||||
.output()
|
||||
.read()
|
||||
.last()
|
||||
.is_none_or(|r| !matches!(r, SQLiteStatementResult::Finish))
|
||||
}
|
||||
fallback=|| ()
|
||||
>
|
||||
<Loader />
|
||||
</Show>
|
||||
|
||||
<For
|
||||
each=move || state.output().iter_unkeyed().enumerate()
|
||||
key=|(idx, _)| *idx
|
||||
children=move |(idx, item)| {
|
||||
match &*item.read() {
|
||||
SQLiteStatementResult::Finish => {
|
||||
view! { <Header label="Finished".into() /> }.into_any()
|
||||
}
|
||||
SQLiteStatementResult::Step(table) => {
|
||||
let label = format!("Statement #{}", idx + 1);
|
||||
if let Some(output) = get_output(table) {
|
||||
view! {
|
||||
<Section label=label>
|
||||
<p>{output}</p>
|
||||
</Section>
|
||||
}
|
||||
.into_any()
|
||||
} else {
|
||||
().into_any()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
.into_any()
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Execute() -> impl IntoView {
|
||||
view! {
|
||||
<SimplePane>
|
||||
<Output />
|
||||
</SimplePane>
|
||||
}
|
||||
}
|
||||
9
src/app/output/header.rs
Normal file
9
src/app/output/header.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
use istyles::istyles;
|
||||
use leptos::prelude::*;
|
||||
|
||||
istyles!(styles, "assets/module.postcss/output/header.module.css.map");
|
||||
|
||||
#[component]
|
||||
pub fn Header(label: String) -> impl IntoView {
|
||||
view! { <span class=styles::container>{label}</span> }
|
||||
}
|
||||
13
src/app/output/loader.rs
Normal file
13
src/app/output/loader.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
use leptos::prelude::*;
|
||||
|
||||
use crate::app::{loader::Loader as GenericLoader, output::header::Header};
|
||||
|
||||
#[component]
|
||||
pub fn Loader() -> impl IntoView {
|
||||
view! {
|
||||
<div>
|
||||
<Header label="Progress".into() />
|
||||
<GenericLoader />
|
||||
</div>
|
||||
}
|
||||
}
|
||||
21
src/app/output/section.rs
Normal file
21
src/app/output/section.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
use istyles::istyles;
|
||||
use leptos::prelude::*;
|
||||
|
||||
use crate::app::output::header::Header;
|
||||
|
||||
istyles!(
|
||||
styles,
|
||||
"assets/module.postcss/output/section.module.css.map"
|
||||
);
|
||||
|
||||
#[component]
|
||||
pub fn Section(label: String, children: Children) -> impl IntoView {
|
||||
view! {
|
||||
<div>
|
||||
<Header label=label />
|
||||
<pre>
|
||||
<code class=styles::code>{children()}</code>
|
||||
</pre>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
55
src/app/output/share.rs
Normal file
55
src/app/output/share.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
use std::{sync::Arc, time::Duration};
|
||||
|
||||
use istyles::istyles;
|
||||
use leptos::prelude::*;
|
||||
use reactive_stores::Store;
|
||||
use wasm_bindgen_futures::{JsFuture, spawn_local};
|
||||
|
||||
use crate::app::{GlobalState, GlobalStateStoreFields, icon::clipboard_icon};
|
||||
|
||||
istyles!(styles, "assets/module.postcss/output/share.module.css.map");
|
||||
|
||||
#[component]
|
||||
fn Copied<H>(href: H, children: Children) -> impl IntoView
|
||||
where
|
||||
H: Fn() -> String + Send + Sync + 'static,
|
||||
{
|
||||
let (copied, set_copied) = signal(false);
|
||||
let href = Arc::new(href);
|
||||
let href1 = Arc::clone(&href);
|
||||
|
||||
let copy = move |_| {
|
||||
let href = Arc::clone(&href1);
|
||||
spawn_local(async move {
|
||||
set_copied.set(true);
|
||||
if let Err(err) =
|
||||
JsFuture::from(window().navigator().clipboard().write_text(&href())).await
|
||||
{
|
||||
log::error!("Failed to write href to clipboard: {err:?}");
|
||||
}
|
||||
set_timeout(move || set_copied.set(false), Duration::from_millis(1000));
|
||||
});
|
||||
};
|
||||
|
||||
view! {
|
||||
<p class=move || { if *copied.read() { styles::active } else { styles::container } }>
|
||||
<a href=move || href()>{children()}</a>
|
||||
<button class=styles::button on:click=copy>
|
||||
{clipboard_icon()}
|
||||
</button>
|
||||
<span class=styles::text>"Copied!"</span>
|
||||
</p>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Links() -> impl IntoView {
|
||||
let state = expect_context::<Store<GlobalState>>();
|
||||
let code_url = move || state.share_href().get_untracked().unwrap_or_default();
|
||||
view! { <Copied href=code_url>Embedded code in link</Copied> }
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Share() -> impl IntoView {
|
||||
view! { <Links /> }
|
||||
}
|
||||
6
src/app/output/simple_pane.rs
Normal file
6
src/app/output/simple_pane.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
use leptos::prelude::*;
|
||||
|
||||
#[component]
|
||||
pub fn SimplePane(children: Children) -> impl IntoView {
|
||||
view! { <div>{children()}</div> }
|
||||
}
|
||||
80
src/app/output/status.rs
Normal file
80
src/app/output/status.rs
Normal file
@@ -0,0 +1,80 @@
|
||||
use std::ops::Deref;
|
||||
|
||||
use leptos::prelude::*;
|
||||
use reactive_stores::Store;
|
||||
|
||||
use crate::{
|
||||
SQLightError, SQLitendError, WorkerError,
|
||||
app::{
|
||||
GlobalState, GlobalStateStoreFields,
|
||||
output::{section::Section, simple_pane::SimplePane},
|
||||
},
|
||||
};
|
||||
|
||||
const OPFS_SAH_POOL_OPENED_DETAILS: &str = "Due to OPFS SyncAccessHandle restrictions, \
|
||||
the db can only have one web tab access.
|
||||
|
||||
Please close other tabs and refresh, or switch to Memory VFS.";
|
||||
|
||||
#[component]
|
||||
pub fn Status() -> impl IntoView {
|
||||
let state = expect_context::<Store<GlobalState>>();
|
||||
|
||||
let show = move || match &*state.last_error().read() {
|
||||
Some(error) => {
|
||||
let summary = format!("{}", error.deref());
|
||||
let details = match error.deref() {
|
||||
SQLightError::Worker(worker) => match worker {
|
||||
WorkerError::SQLite(sqlitend_error) => match sqlitend_error {
|
||||
SQLitendError::ToCStr
|
||||
| SQLitendError::GetColumnName(_)
|
||||
| SQLitendError::Utf8Text => {
|
||||
"This shouldn't happen, please create an issue on github."
|
||||
}
|
||||
SQLitendError::OpenDb(_) => {
|
||||
"If database disk image is malformed, please enable the discard context option and use it once."
|
||||
}
|
||||
SQLitendError::Prepare(_) => "Please check if the syntax is correct.",
|
||||
SQLitendError::Step(_) => {
|
||||
"If database disk image is malformed, please enable the discard context option and use it once."
|
||||
}
|
||||
SQLitendError::UnsupportColumnType(_) => {
|
||||
"An unsupported type was encountered, please create an issue on github."
|
||||
}
|
||||
},
|
||||
WorkerError::NotFound | WorkerError::OpfsSAHError => {
|
||||
"This shouldn't happen, please create an issue on github."
|
||||
}
|
||||
WorkerError::InvaildState => {
|
||||
"SQLite is in an abnormal state when executing SQLite."
|
||||
}
|
||||
WorkerError::OpfsSAHPoolOpened => OPFS_SAH_POOL_OPENED_DETAILS,
|
||||
},
|
||||
SQLightError::AceEditor(ace_editor) => match ace_editor {
|
||||
aceditor::EditorError::Serde(_)
|
||||
| aceditor::EditorError::SetTheme(_)
|
||||
| aceditor::EditorError::SetKeyboardHandler(_)
|
||||
| aceditor::EditorError::Open(_)
|
||||
| aceditor::EditorError::DefineEx(_) => {
|
||||
"This shouldn't happen, please create an issue on github."
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
view! {
|
||||
<details open>
|
||||
<summary>{summary}</summary>
|
||||
<p>{details}</p>
|
||||
</details>
|
||||
}
|
||||
.into_any()
|
||||
}
|
||||
None => view! { "No Error" }.into_any(),
|
||||
};
|
||||
|
||||
view! {
|
||||
<SimplePane>
|
||||
<Section label="Last Error".into()>{show}</Section>
|
||||
</SimplePane>
|
||||
}
|
||||
}
|
||||
250
src/app/playground.rs
Normal file
250
src/app/playground.rs
Normal file
@@ -0,0 +1,250 @@
|
||||
use std::ops::Deref;
|
||||
|
||||
use istyles::istyles;
|
||||
use leptos::prelude::*;
|
||||
use leptos::tachys::html;
|
||||
use reactive_stores::Store;
|
||||
use split_grid::{Gutter, SplitOptions};
|
||||
use tokio::sync::mpsc::UnboundedReceiver;
|
||||
use wasm_bindgen::{JsCast, prelude::Closure};
|
||||
use wasm_bindgen_futures::spawn_local;
|
||||
use web_sys::wasm_bindgen::JsValue;
|
||||
|
||||
use crate::app::{
|
||||
Focus,
|
||||
editor::Editor,
|
||||
header::Header,
|
||||
output::{Output, change_focus},
|
||||
state::{GlobalState, GlobalStateStoreFields, Orientation, Theme, Vfs},
|
||||
};
|
||||
use crate::{WorkerHandle, WorkerResponse, handle_state};
|
||||
|
||||
istyles!(styles, "assets/module.postcss/playground.module.css.map");
|
||||
|
||||
pub fn playground(
|
||||
worker: (WorkerHandle, UnboundedReceiver<WorkerResponse>),
|
||||
) -> Box<dyn FnOnce() -> AnyView + 'static> {
|
||||
Box::new(move || {
|
||||
let (worker_handle, rx) = worker;
|
||||
let state = GlobalState::load().unwrap_or_default();
|
||||
provide_context(Store::new(state));
|
||||
|
||||
let state = expect_context::<Store<GlobalState>>();
|
||||
state.worker().set(Some(worker_handle));
|
||||
|
||||
handle_last_error(state);
|
||||
handle_system_theme(state);
|
||||
handle_automic_orientation(state);
|
||||
handle_connect_db(state);
|
||||
hanlde_save_state(state);
|
||||
|
||||
spawn_local(handle_state(state, rx));
|
||||
|
||||
view! {
|
||||
<div id="playground" class=styles::container>
|
||||
<Header />
|
||||
<ResizableArea />
|
||||
</div>
|
||||
}
|
||||
.into_any()
|
||||
})
|
||||
}
|
||||
|
||||
fn hanlde_save_state(state: Store<GlobalState>) {
|
||||
Effect::new(move || {
|
||||
state.vfs().track();
|
||||
state.editor_config().track();
|
||||
state.orientation().track();
|
||||
state.theme().track();
|
||||
state.keep_ctx().track();
|
||||
state.code().track();
|
||||
|
||||
state.read_untracked().save();
|
||||
});
|
||||
}
|
||||
|
||||
fn handle_connect_db(state: Store<GlobalState>) {
|
||||
Effect::new(move || {
|
||||
if let Some(worker) = &*state.worker().read() {
|
||||
worker.send_task(crate::WorkerRequest::Open(crate::OpenOptions {
|
||||
filename: "test.db".into(),
|
||||
persist: *state.vfs().read() == Vfs::OPFS,
|
||||
}));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn handle_system_theme(state: Store<GlobalState>) {
|
||||
Effect::new(move || {
|
||||
let theme = match *state.theme().read() {
|
||||
Theme::System => {
|
||||
if let Ok(Some(query)) = window().match_media("(prefers-color-scheme: dark)") {
|
||||
if query.matches() { "dark" } else { "light" }
|
||||
} else {
|
||||
"light"
|
||||
}
|
||||
}
|
||||
Theme::SystemLight | Theme::Light => "light",
|
||||
Theme::SystemDark | Theme::Dark => "dark",
|
||||
};
|
||||
if let Some(element) = document().document_element() {
|
||||
element.set_attribute("data-theme", theme).unwrap()
|
||||
}
|
||||
});
|
||||
|
||||
if let Ok(Some(query)) = window().match_media("(prefers-color-scheme: dark)") {
|
||||
let f = move |query: web_sys::MediaQueryList| {
|
||||
if state.theme().get_untracked().is_system() {
|
||||
*state.theme().write() = if query.matches() {
|
||||
Theme::SystemDark
|
||||
} else {
|
||||
Theme::SystemLight
|
||||
};
|
||||
}
|
||||
};
|
||||
f(query.clone());
|
||||
let callback = Closure::<dyn Fn(web_sys::MediaQueryList)>::new(f);
|
||||
query
|
||||
.add_event_listener_with_callback("change", callback.as_ref().unchecked_ref())
|
||||
.unwrap();
|
||||
callback.forget();
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_last_error(state: Store<GlobalState>) {
|
||||
Effect::new(move || {
|
||||
if state.last_error().read().is_some() {
|
||||
change_focus(state, Some(Focus::Status));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn handle_automic_orientation(state: Store<GlobalState>) {
|
||||
if let Ok(Some(query)) = window().match_media("(max-width: 1600px)") {
|
||||
let f = move |query: web_sys::MediaQueryList| {
|
||||
if state.orientation().get_untracked().is_auto() {
|
||||
*state.orientation().write() = if query.matches() {
|
||||
Orientation::AutoHorizontal
|
||||
} else {
|
||||
Orientation::AutoVertical
|
||||
};
|
||||
}
|
||||
};
|
||||
f(query.clone());
|
||||
let callback = Closure::<dyn Fn(web_sys::MediaQueryList)>::new(f);
|
||||
query
|
||||
.add_event_listener_with_callback("change", callback.as_ref().unchecked_ref())
|
||||
.unwrap();
|
||||
callback.forget();
|
||||
}
|
||||
}
|
||||
|
||||
fn gird_style() -> String {
|
||||
let state = expect_context::<Store<GlobalState>>();
|
||||
|
||||
let (focused_grid_style, unfocused_grid_style) = match *state.orientation().read() {
|
||||
Orientation::Horizontal | Orientation::AutoHorizontal => (
|
||||
styles::resizeableAreaRowOutputFocused.to_string(),
|
||||
styles::resizeableAreaRowOutputUnfocused.to_string(),
|
||||
),
|
||||
Orientation::Automatic | Orientation::Vertical | Orientation::AutoVertical => (
|
||||
styles::resizeableAreaColumnOutputFocused.to_string(),
|
||||
styles::resizeableAreaColumnOutputUnfocused.to_string(),
|
||||
),
|
||||
};
|
||||
|
||||
if state.read().is_focus() {
|
||||
focused_grid_style
|
||||
} else {
|
||||
unfocused_grid_style
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_outer_style() -> String {
|
||||
let state = expect_context::<Store<GlobalState>>();
|
||||
|
||||
match *state.orientation().read() {
|
||||
Orientation::Horizontal | Orientation::AutoHorizontal => {
|
||||
styles::splitRowsGutter.to_string()
|
||||
}
|
||||
Orientation::Automatic | Orientation::Vertical | Orientation::AutoVertical => {
|
||||
styles::splitColumnsGutter.to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_inner_style() -> String {
|
||||
let state = expect_context::<Store<GlobalState>>();
|
||||
|
||||
match *state.orientation().read() {
|
||||
Orientation::Horizontal | Orientation::AutoHorizontal => {
|
||||
styles::splitRowsGutterHandle.to_string()
|
||||
}
|
||||
Orientation::Automatic | Orientation::Vertical | Orientation::AutoVertical => String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn ResizableArea() -> impl IntoView {
|
||||
let state = expect_context::<Store<GlobalState>>();
|
||||
|
||||
let node_ref = NodeRef::<html::element::Div>::new();
|
||||
let drag_handle = NodeRef::<html::element::Div>::new();
|
||||
|
||||
Effect::new(move || {
|
||||
state.orientation().track();
|
||||
state.is_focused().track();
|
||||
|
||||
if let Some(div) = &*node_ref.read() {
|
||||
let style = div.deref().style();
|
||||
let _ = style.remove_property("grid-template-columns");
|
||||
let _ = style.remove_property("grid-template-rows");
|
||||
}
|
||||
});
|
||||
|
||||
Effect::new(move || {
|
||||
state.show_something().track();
|
||||
|
||||
let element = if let Some(element) = &*drag_handle.read() {
|
||||
JsValue::from(element)
|
||||
} else {
|
||||
JsValue::null()
|
||||
};
|
||||
|
||||
let options = match *state.orientation().read() {
|
||||
Orientation::Horizontal | Orientation::AutoHorizontal => SplitOptions {
|
||||
min_size: 100,
|
||||
row_gutters: Some(vec![Gutter { track: 1, element }]),
|
||||
column_gutters: None,
|
||||
},
|
||||
Orientation::Automatic | Orientation::Vertical | Orientation::AutoVertical => {
|
||||
SplitOptions {
|
||||
min_size: 100,
|
||||
row_gutters: None,
|
||||
column_gutters: Some(vec![Gutter { track: 1, element }]),
|
||||
}
|
||||
}
|
||||
};
|
||||
let grid = split_grid::split(&options.into());
|
||||
on_cleanup(move || grid.destroy());
|
||||
});
|
||||
|
||||
view! {
|
||||
<div node_ref=node_ref class=gird_style>
|
||||
<div class=styles::editor>
|
||||
<Editor />
|
||||
</div>
|
||||
<Show when=move || state.read().is_focus() fallback=|| ()>
|
||||
<div node_ref=drag_handle class=handle_outer_style>
|
||||
<span class=handle_inner_style>"⣿"</span>
|
||||
</div>
|
||||
</Show>
|
||||
<Show when=move || *state.show_something().read() fallback=|| ()>
|
||||
<div class=styles::output>
|
||||
<Output />
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
</div>
|
||||
}
|
||||
}
|
||||
223
src/app/pop_button.rs
Normal file
223
src/app/pop_button.rs
Normal file
@@ -0,0 +1,223 @@
|
||||
use std::{ops::Deref, sync::Arc};
|
||||
|
||||
use floating_ui::{
|
||||
ArrowPosition, ComputePosition, MiddlewareData, auto_update, compute_options, compute_position,
|
||||
};
|
||||
use istyles::istyles;
|
||||
use js_sys::Object;
|
||||
use leptos::{portal::Portal, prelude::*, tachys::html};
|
||||
use wasm_bindgen::{JsCast, JsValue, prelude::Closure};
|
||||
use wasm_bindgen_futures::spawn_local;
|
||||
use web_sys::{KeyboardEvent, MouseEvent};
|
||||
|
||||
use crate::FragileComfirmed;
|
||||
|
||||
istyles!(styles, "assets/module.postcss/pop_button.module.css.map");
|
||||
|
||||
#[component]
|
||||
pub fn PopButton<B, M>(
|
||||
button: B,
|
||||
menu: M,
|
||||
#[prop(optional)] menu_container: NodeRef<html::element::Div>,
|
||||
) -> impl IntoView
|
||||
where
|
||||
B: FnOnce(Box<dyn FnMut(MouseEvent) + Send>, NodeRef<html::element::Button>) -> AnyView,
|
||||
M: Fn(Box<dyn Fn()>) -> AnyView + Send + Sync + 'static,
|
||||
{
|
||||
let (is_open, set_open) = signal(false);
|
||||
let toggle = move || set_open.set(!is_open.get());
|
||||
let close = move || set_open.set(false);
|
||||
|
||||
let arrow_ref = NodeRef::<html::element::Div>::new();
|
||||
let reference_ref = NodeRef::<html::element::Button>::new();
|
||||
let floating_ref = NodeRef::<html::element::Div>::new();
|
||||
let menu_ref = NodeRef::<html::element::Div>::new();
|
||||
|
||||
Effect::new(move || {
|
||||
let key_listener = move |event: KeyboardEvent| {
|
||||
if !is_open.get_untracked() {
|
||||
return;
|
||||
}
|
||||
|
||||
if event.key() == "Escape" {
|
||||
set_open(false);
|
||||
}
|
||||
};
|
||||
|
||||
let callback = FragileComfirmed::new(Closure::<dyn Fn(KeyboardEvent)>::new(key_listener));
|
||||
|
||||
window()
|
||||
.add_event_listener_with_callback("keydown", callback.as_ref().unchecked_ref())
|
||||
.unwrap();
|
||||
|
||||
on_cleanup(move || {
|
||||
window()
|
||||
.remove_event_listener_with_callback("keydown", callback.as_ref().unchecked_ref())
|
||||
.unwrap();
|
||||
drop(callback)
|
||||
});
|
||||
});
|
||||
|
||||
Effect::new(move || {
|
||||
let listener = move |event: MouseEvent| {
|
||||
if !is_open.get_untracked() {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(target) = event.target() {
|
||||
let node = target.dyn_into::<web_sys::Node>().ok();
|
||||
if !reference_ref.with_untracked(|reference| {
|
||||
reference
|
||||
.as_ref()
|
||||
.is_some_and(|reference| reference.deref().contains(node.as_ref()))
|
||||
}) && !floating_ref.with_untracked(|floating| {
|
||||
floating
|
||||
.as_ref()
|
||||
.is_some_and(|floating| floating.deref().contains(node.as_ref()))
|
||||
}) {
|
||||
set_open(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let callback = FragileComfirmed::new(Closure::<dyn Fn(MouseEvent)>::new(listener));
|
||||
|
||||
window()
|
||||
.add_event_listener_with_callback("click", callback.as_ref().unchecked_ref())
|
||||
.unwrap();
|
||||
|
||||
on_cleanup(move || {
|
||||
window()
|
||||
.remove_event_listener_with_callback("click", callback.as_ref().unchecked_ref())
|
||||
.unwrap();
|
||||
drop(callback)
|
||||
});
|
||||
});
|
||||
|
||||
Effect::new(move || {
|
||||
let callback = Closure::new(move || {
|
||||
let options = compute_options(10, &arrow_ref.get_untracked().into());
|
||||
|
||||
spawn_local(async move {
|
||||
let value = compute_position(
|
||||
reference_ref.get_untracked().into(),
|
||||
floating_ref.get_untracked().into(),
|
||||
options,
|
||||
)
|
||||
.await;
|
||||
|
||||
let ComputePosition {
|
||||
x,
|
||||
y,
|
||||
placement,
|
||||
strategy,
|
||||
middleware_data,
|
||||
} = serde_wasm_bindgen::from_value(value).unwrap();
|
||||
|
||||
if let Some(element) = floating_ref.get_untracked() {
|
||||
let style = element.deref().style();
|
||||
#[derive(serde::Serialize)]
|
||||
struct Style {
|
||||
position: String,
|
||||
left: String,
|
||||
top: String,
|
||||
}
|
||||
|
||||
let pos = serde_wasm_bindgen::to_value(&Style {
|
||||
position: strategy,
|
||||
left: format!("{x}px"),
|
||||
top: format!("{y}px"),
|
||||
})
|
||||
.unwrap();
|
||||
Object::assign(&style, &pos.into());
|
||||
}
|
||||
|
||||
if let Some(element) = arrow_ref.get_untracked() {
|
||||
let MiddlewareData {
|
||||
arrow: ArrowPosition { x },
|
||||
} = middleware_data;
|
||||
let style = element.deref().style();
|
||||
#[derive(serde::Serialize)]
|
||||
struct Style {
|
||||
left: String,
|
||||
}
|
||||
|
||||
let pos = serde_wasm_bindgen::to_value(&Style {
|
||||
left: format!("{x}px"),
|
||||
})
|
||||
.unwrap();
|
||||
Object::assign(&style, &pos.into());
|
||||
}
|
||||
|
||||
if let Some(menu_ref) = menu_ref.get_untracked() {
|
||||
let class = if placement == "top" {
|
||||
styles::contentTop
|
||||
} else if placement == "bottom" {
|
||||
styles::contentBottom
|
||||
} else {
|
||||
""
|
||||
};
|
||||
menu_ref.set_class_name(class);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if let (Some(reference), Some(floating)) = (&*reference_ref.read(), &*floating_ref.read()) {
|
||||
let func = auto_update(
|
||||
reference.into(),
|
||||
floating.into(),
|
||||
&callback,
|
||||
JsValue::default(),
|
||||
);
|
||||
let func = FragileComfirmed::new(func);
|
||||
let callback = FragileComfirmed::new(callback);
|
||||
|
||||
on_cleanup(move || {
|
||||
func.call0(&JsValue::null()).unwrap();
|
||||
drop(callback);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
let menu = Arc::new(menu);
|
||||
let float = move || {
|
||||
let menu_clone = Arc::clone(&menu);
|
||||
view! {
|
||||
<Show when=is_open fallback=|| ()>
|
||||
<div
|
||||
class=styles::container
|
||||
node_ref=floating_ref
|
||||
style="position: absolute; width: max-content;"
|
||||
>
|
||||
<div
|
||||
class=styles::arrow
|
||||
node_ref=arrow_ref
|
||||
style="position: absolute; pointer-events: none; bottom: 100%; transform: rotate(180deg);"
|
||||
>
|
||||
<svg width="20" height="20" viewBox="0 0 20 20">
|
||||
<path stroke="none" d="M0,0 H20 L10,10 Q10,10 10,10 Z"></path>
|
||||
<clipPath id=":rh:">
|
||||
<rect x="0" y="0" width="20" height="20"></rect>
|
||||
</clipPath>
|
||||
</svg>
|
||||
</div>
|
||||
<div node_ref=menu_ref>
|
||||
<div>{menu_clone(Box::new(close))}</div>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
}
|
||||
};
|
||||
|
||||
let float = Arc::new(float);
|
||||
let total = move || {
|
||||
let float_clone = Arc::clone(&float);
|
||||
if let Some(container) = menu_container.get() {
|
||||
view! { <Portal mount=container.clone()>{float_clone()}</Portal> }.into_any()
|
||||
} else {
|
||||
float_clone().into_any()
|
||||
}
|
||||
};
|
||||
|
||||
view! { <>{button(Box::new(move |_| toggle()), reference_ref)} {total}</> }
|
||||
}
|
||||
27
src/app/select_one.rs
Normal file
27
src/app/select_one.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
use leptos::prelude::*;
|
||||
|
||||
use crate::app::selectable_menu_item::SelectableMenuItem;
|
||||
|
||||
#[component]
|
||||
pub fn SelectOne<T, C, CH>(
|
||||
name: String,
|
||||
current_value: C,
|
||||
this_value: T,
|
||||
mut change_value: CH,
|
||||
children: Children,
|
||||
) -> impl IntoView
|
||||
where
|
||||
C: Fn() -> T + Send + 'static,
|
||||
CH: FnMut() + Send + 'static,
|
||||
T: PartialEq + Eq + Send + 'static,
|
||||
{
|
||||
view! {
|
||||
<SelectableMenuItem
|
||||
name=name
|
||||
selected=move || { current_value() == this_value }
|
||||
on_click=move |_| change_value()
|
||||
>
|
||||
{children()}
|
||||
</SelectableMenuItem>
|
||||
}
|
||||
}
|
||||
37
src/app/selectable_menu_item.rs
Normal file
37
src/app/selectable_menu_item.rs
Normal file
@@ -0,0 +1,37 @@
|
||||
use istyles::istyles;
|
||||
use leptos::prelude::*;
|
||||
use web_sys::MouseEvent;
|
||||
|
||||
use crate::app::{icon::checkmark_icon, menu_item::MenuItem};
|
||||
|
||||
istyles!(
|
||||
styles,
|
||||
"assets/module.postcss/selectable_menu_item.module.css.map"
|
||||
);
|
||||
|
||||
#[component]
|
||||
pub fn SelectableMenuItem<S, C>(
|
||||
name: String,
|
||||
selected: S,
|
||||
on_click: C,
|
||||
children: Children,
|
||||
) -> impl IntoView
|
||||
where
|
||||
S: Fn() -> bool + Send + 'static,
|
||||
C: FnMut(MouseEvent) + Send + 'static,
|
||||
{
|
||||
view! {
|
||||
<MenuItem>
|
||||
<button
|
||||
class=move || { if selected() { styles::selected } else { styles::container } }
|
||||
on:click=on_click
|
||||
>
|
||||
<div class=styles::header>
|
||||
<span class=styles::checkmark>{checkmark_icon()}</span>
|
||||
<span class=styles::name>{name}</span>
|
||||
</div>
|
||||
<div class=styles::description>{children()}</div>
|
||||
</button>
|
||||
</MenuItem>
|
||||
}
|
||||
}
|
||||
192
src/app/state.rs
Normal file
192
src/app/state.rs
Normal file
@@ -0,0 +1,192 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use aceditor::Editor;
|
||||
use leptos::tachys::dom::window;
|
||||
use reactive_stores::Store;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{FragileComfirmed, SQLightError, SQLiteStatementResult, WorkerHandle};
|
||||
|
||||
#[derive(Store, Serialize, Deserialize)]
|
||||
pub struct GlobalState {
|
||||
vfs: Vfs,
|
||||
editor_config: EditorConfig,
|
||||
orientation: Orientation,
|
||||
theme: Theme,
|
||||
keep_ctx: bool,
|
||||
code: String,
|
||||
// runtime state below
|
||||
#[serde(skip)]
|
||||
worker: Option<WorkerHandle>,
|
||||
#[serde(skip)]
|
||||
pub editor: Option<Editor>,
|
||||
#[serde(skip)]
|
||||
focus: Option<Focus>,
|
||||
#[serde(skip)]
|
||||
is_focused: bool,
|
||||
#[serde(skip)]
|
||||
opened_focus: HashSet<Focus>,
|
||||
#[serde(skip)]
|
||||
share_href: Option<String>,
|
||||
#[serde(skip)]
|
||||
show_something: bool,
|
||||
#[serde(skip)]
|
||||
output: Vec<SQLiteStatementResult>,
|
||||
#[serde(skip)]
|
||||
last_error: Option<FragileComfirmed<SQLightError>>,
|
||||
}
|
||||
|
||||
impl Default for GlobalState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
editor_config: EditorConfig::default(),
|
||||
code: String::new(),
|
||||
focus: None,
|
||||
show_something: false,
|
||||
orientation: Orientation::Automatic,
|
||||
theme: Theme::System,
|
||||
output: Vec::new(),
|
||||
vfs: Vfs::Memory,
|
||||
keep_ctx: false,
|
||||
share_href: None,
|
||||
is_focused: false,
|
||||
opened_focus: HashSet::new(),
|
||||
worker: None,
|
||||
editor: None,
|
||||
last_error: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GlobalState {
|
||||
pub fn load() -> Option<Self> {
|
||||
let storage = window().local_storage().ok()??;
|
||||
let value = storage.get("config").ok()??;
|
||||
serde_json::from_str(&value).ok()
|
||||
}
|
||||
|
||||
pub fn save(&self) {
|
||||
if let Some(Err(e)) = window()
|
||||
.local_storage()
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|s| s.set_item("config", &serde_json::to_string(self).unwrap()))
|
||||
{
|
||||
log::error!("Faild to save config to localstorage: {e:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct EditorConfig {
|
||||
pub keyboard: String,
|
||||
pub theme: String,
|
||||
}
|
||||
|
||||
impl Default for EditorConfig {
|
||||
fn default() -> Self {
|
||||
EditorConfig {
|
||||
keyboard: "ace".into(),
|
||||
theme: "github".into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GlobalState {
|
||||
pub fn is_focus(&self) -> bool {
|
||||
self.focus.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum Vfs {
|
||||
Memory,
|
||||
OPFS,
|
||||
}
|
||||
|
||||
impl Vfs {
|
||||
pub fn value(&self) -> String {
|
||||
match self {
|
||||
Vfs::Memory => "Memory".into(),
|
||||
Vfs::OPFS => "OPFS".into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub enum Focus {
|
||||
Execute,
|
||||
Share,
|
||||
Status,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Serialize, Deserialize)]
|
||||
pub enum Theme {
|
||||
System,
|
||||
SystemLight,
|
||||
SystemDark,
|
||||
Light,
|
||||
Dark,
|
||||
}
|
||||
|
||||
impl Theme {
|
||||
pub fn is_system(&self) -> bool {
|
||||
matches!(self, Theme::System | Theme::SystemLight | Theme::SystemDark)
|
||||
}
|
||||
|
||||
pub fn from_value(s: &str) -> Self {
|
||||
match s {
|
||||
"System" => Self::System,
|
||||
"Light" => Self::Light,
|
||||
"Dark" => Self::Dark,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_value(&self) -> String {
|
||||
match self {
|
||||
Theme::System | Theme::SystemLight | Theme::SystemDark => "System",
|
||||
Theme::Light => "Light",
|
||||
Theme::Dark => "Dark",
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Serialize, Deserialize)]
|
||||
pub enum Orientation {
|
||||
Automatic,
|
||||
AutoHorizontal,
|
||||
AutoVertical,
|
||||
Horizontal,
|
||||
Vertical,
|
||||
}
|
||||
|
||||
impl Orientation {
|
||||
pub fn is_auto(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Orientation::Automatic | Orientation::AutoVertical | Orientation::AutoHorizontal
|
||||
)
|
||||
}
|
||||
|
||||
pub fn from_value(s: &str) -> Self {
|
||||
match s {
|
||||
"Automatic" => Self::Automatic,
|
||||
"Horizontal" => Self::Horizontal,
|
||||
"Vertical" => Self::Vertical,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_value(&self) -> String {
|
||||
match self {
|
||||
Orientation::Automatic | Orientation::AutoVertical | Orientation::AutoHorizontal => {
|
||||
"Automatic"
|
||||
}
|
||||
Orientation::Horizontal => "Horizontal",
|
||||
Orientation::Vertical => "Vertical",
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
36
src/app/vfs_menu.rs
Normal file
36
src/app/vfs_menu.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
use leptos::prelude::*;
|
||||
use reactive_stores::Store;
|
||||
|
||||
use crate::app::{
|
||||
GlobalState, GlobalStateStoreFields, Vfs, menu_group::MenuGroup, select_one::SelectOne,
|
||||
};
|
||||
|
||||
#[component]
|
||||
pub fn VfsMenu() -> impl IntoView {
|
||||
let state = expect_context::<Store<GlobalState>>();
|
||||
|
||||
view! {
|
||||
<MenuGroup title="Choose SQLite VFS".into()>
|
||||
<SelectOne
|
||||
name="Memory".into()
|
||||
current_value=move || { *state.vfs().read() }
|
||||
this_value=Vfs::Memory
|
||||
change_value=move || {
|
||||
*state.vfs().write() = Vfs::Memory;
|
||||
}
|
||||
>
|
||||
"Data will be lost after refreshing."
|
||||
</SelectOne>
|
||||
<SelectOne
|
||||
name="OPFS".into()
|
||||
current_value=move || { *state.vfs().read() }
|
||||
this_value=Vfs::OPFS
|
||||
change_value=move || {
|
||||
*state.vfs().write() = Vfs::OPFS;
|
||||
}
|
||||
>
|
||||
"Persistent Storage."
|
||||
</SelectOne>
|
||||
</MenuGroup>
|
||||
}
|
||||
}
|
||||
11
src/bin/app.rs
Normal file
11
src/bin/app.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
use leptos::prelude::*;
|
||||
use sqlight::{app::playground, setup_worker};
|
||||
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
|
||||
#[wasm_bindgen(main)]
|
||||
async fn main() {
|
||||
console_error_panic_hook::set_once();
|
||||
console_log::init_with_level(log::Level::Debug).unwrap();
|
||||
mount_to_body(playground(setup_worker().await));
|
||||
}
|
||||
42
src/bin/worker.rs
Normal file
42
src/bin/worker.rs
Normal file
@@ -0,0 +1,42 @@
|
||||
use sqlight::{WorkerRequest, WorkerResponse, worker};
|
||||
use tokio::sync::mpsc::UnboundedReceiver;
|
||||
use wasm_bindgen::{JsCast, JsValue, prelude::Closure};
|
||||
use wasm_bindgen_futures::spawn_local;
|
||||
use web_sys::{DedicatedWorkerGlobalScope, MessageEvent};
|
||||
|
||||
fn main() {
|
||||
console_error_panic_hook::set_once();
|
||||
console_log::init_with_level(log::Level::Warn).unwrap();
|
||||
|
||||
let (tx, rx) = tokio::sync::mpsc::unbounded_channel::<JsValue>();
|
||||
|
||||
let scope: DedicatedWorkerGlobalScope = JsValue::from(js_sys::global()).into();
|
||||
spawn_local(execute_task(scope.clone(), rx));
|
||||
|
||||
let on_message = Closure::<dyn Fn(MessageEvent)>::new(move |ev: MessageEvent| {
|
||||
tx.send(ev.data()).unwrap();
|
||||
});
|
||||
|
||||
scope.set_onmessage(Some(on_message.as_ref().unchecked_ref()));
|
||||
scope
|
||||
.post_message(&serde_wasm_bindgen::to_value(&WorkerResponse::Ready).unwrap())
|
||||
.expect("Faild to send ready to window");
|
||||
on_message.forget();
|
||||
}
|
||||
|
||||
async fn execute_task(scope: DedicatedWorkerGlobalScope, mut rx: UnboundedReceiver<JsValue>) {
|
||||
while let Some(request) = rx.recv().await {
|
||||
let request = serde_wasm_bindgen::from_value::<WorkerRequest>(request).unwrap();
|
||||
let resp = match request {
|
||||
WorkerRequest::Open(options) => WorkerResponse::Open(worker::open(options).await),
|
||||
WorkerRequest::Prepare(options) => WorkerResponse::Prepare(worker::prepare(options)),
|
||||
WorkerRequest::Continue(id) => WorkerResponse::Continue(worker::r#continue(&id)),
|
||||
WorkerRequest::StepOver(id) => WorkerResponse::StepOver(worker::step_over(&id)),
|
||||
WorkerRequest::StepIn(id) => WorkerResponse::StepIn(worker::step_in(&id)),
|
||||
WorkerRequest::StepOut(id) => WorkerResponse::StepOut(worker::step_out(&id)),
|
||||
};
|
||||
if let Err(err) = scope.post_message(&serde_wasm_bindgen::to_value(&resp).unwrap()) {
|
||||
log::error!("Failed to send task to window: {resp:?}, {err:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
245
src/lib.rs
Normal file
245
src/lib.rs
Normal file
@@ -0,0 +1,245 @@
|
||||
pub mod app;
|
||||
pub mod worker;
|
||||
|
||||
use aceditor::EditorError;
|
||||
use app::{GlobalState, GlobalStateStoreFields};
|
||||
use fragile::Fragile;
|
||||
use leptos::prelude::*;
|
||||
use reactive_stores::Store;
|
||||
use serde_json::Value as JsonValue;
|
||||
use std::{
|
||||
ops::{Deref, DerefMut},
|
||||
sync::Arc,
|
||||
};
|
||||
use tokio::sync::mpsc::UnboundedReceiver;
|
||||
use wasm_bindgen::{JsCast, prelude::Closure};
|
||||
use web_sys::{MessageEvent, Worker, WorkerOptions, WorkerType};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
type Result<T> = std::result::Result<T, WorkerError>;
|
||||
|
||||
/// A [`FragileComfirmed<T>`] wraps a non sendable `T` to be safely send to other threads.
|
||||
///
|
||||
/// Once the value has been wrapped it can be sent to other threads but access
|
||||
/// to the value on those threads will fail.
|
||||
pub struct FragileComfirmed<T> {
|
||||
fragile: Fragile<T>,
|
||||
}
|
||||
|
||||
unsafe impl<T> Send for FragileComfirmed<T> {}
|
||||
unsafe impl<T> Sync for FragileComfirmed<T> {}
|
||||
|
||||
impl<T> FragileComfirmed<T> {
|
||||
pub fn new(t: T) -> Self {
|
||||
FragileComfirmed {
|
||||
fragile: Fragile::new(t),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Deref for FragileComfirmed<T> {
|
||||
type Target = T;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.fragile.get()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DerefMut for FragileComfirmed<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.fragile.get_mut()
|
||||
}
|
||||
}
|
||||
|
||||
pub const PERSIST_VFS: &str = "sqlight-sahpool";
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum SQLightError {
|
||||
#[error(transparent)]
|
||||
Worker(#[from] WorkerError),
|
||||
#[error(transparent)]
|
||||
AceEditor(#[from] EditorError),
|
||||
}
|
||||
|
||||
impl SQLightError {
|
||||
pub fn new_worker(err: WorkerError) -> FragileComfirmed<Self> {
|
||||
FragileComfirmed::new(Self::Worker(err))
|
||||
}
|
||||
|
||||
pub fn new_ace_editor(err: EditorError) -> FragileComfirmed<Self> {
|
||||
FragileComfirmed::new(Self::AceEditor(err))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug, Serialize, Deserialize)]
|
||||
pub enum WorkerError {
|
||||
#[error(transparent)]
|
||||
SQLite(#[from] SQLitendError),
|
||||
#[error("Not found database by id")]
|
||||
NotFound,
|
||||
#[error("Execute sqlite with invaild state")]
|
||||
InvaildState,
|
||||
#[error("OPFS already opened")]
|
||||
OpfsSAHPoolOpened,
|
||||
#[error("OPFS unexpected error")]
|
||||
OpfsSAHError,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum WorkerRequest {
|
||||
Open(OpenOptions),
|
||||
Prepare(PrepareOptions),
|
||||
Continue(String),
|
||||
StepOver(String),
|
||||
StepIn(String),
|
||||
StepOut(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum WorkerResponse {
|
||||
Ready,
|
||||
Open(Result<String>),
|
||||
Prepare(Result<()>),
|
||||
Continue(Result<Vec<SQLiteStatementResult>>),
|
||||
StepOver(Result<SQLiteStatementResult>),
|
||||
StepIn(Result<()>),
|
||||
StepOut(Result<SQLiteStatementResult>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct OpenOptions {
|
||||
pub filename: String,
|
||||
pub persist: bool,
|
||||
}
|
||||
|
||||
impl OpenOptions {
|
||||
pub fn uri(&self) -> String {
|
||||
format!(
|
||||
"file:{}?vfs={}",
|
||||
self.filename,
|
||||
if self.persist { PERSIST_VFS } else { "memvfs" }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct PrepareOptions {
|
||||
pub id: String,
|
||||
pub sql: String,
|
||||
pub clear_on_prepare: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct InnerError {
|
||||
pub code: i32,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum SQLiteStatementResult {
|
||||
Finish,
|
||||
Step(SQLiteStatementTable),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct SQLiteStatementTable {
|
||||
pub sql: String,
|
||||
pub position: [usize; 2],
|
||||
pub values: Option<SQLiteStatementValues>,
|
||||
pub done: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct SQLiteStatementValues {
|
||||
pub columns: Vec<String>,
|
||||
pub rows: Vec<Vec<JsonValue>>,
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug, Serialize, Deserialize)]
|
||||
pub enum SQLitendError {
|
||||
#[error("An error occurred while converting a string to a CString")]
|
||||
ToCStr,
|
||||
#[error("An error occurred while opening the DB: {0:?}")]
|
||||
OpenDb(InnerError),
|
||||
#[error("An error occurred while preparing stmt: {0:?}")]
|
||||
Prepare(InnerError),
|
||||
#[error("An error occurred while stepping to the next line")]
|
||||
Step(InnerError),
|
||||
#[error("An error occurred while getting column name: {0}")]
|
||||
GetColumnName(String),
|
||||
#[error("The text is not a utf8 string")]
|
||||
Utf8Text,
|
||||
#[error("The column type is not support: {0}")]
|
||||
UnsupportColumnType(i32),
|
||||
}
|
||||
|
||||
pub struct WorkerHandle(Worker);
|
||||
|
||||
impl WorkerHandle {
|
||||
pub fn send_task(&self, req: WorkerRequest) {
|
||||
if let Err(err) = self
|
||||
.0
|
||||
.post_message(&serde_wasm_bindgen::to_value(&req).unwrap())
|
||||
{
|
||||
log::error!("Failed to send task to worker: {req:?}, {err:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for WorkerHandle {}
|
||||
unsafe impl Sync for WorkerHandle {}
|
||||
|
||||
pub async fn setup_worker() -> (WorkerHandle, UnboundedReceiver<WorkerResponse>) {
|
||||
let uri = "./worker_loader.js";
|
||||
|
||||
let opts = WorkerOptions::new();
|
||||
opts.set_type(WorkerType::Module);
|
||||
|
||||
let worker = match Worker::new_with_options(uri, &opts) {
|
||||
Ok(worker) => worker,
|
||||
Err(err) => panic!("Failed to new setup worker: {err:?}"),
|
||||
};
|
||||
|
||||
let notify = Arc::new(tokio::sync::Notify::new());
|
||||
let wait = Arc::clone(¬ify);
|
||||
|
||||
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
|
||||
|
||||
let on_message = Closure::<dyn Fn(MessageEvent)>::new(move |ev: MessageEvent| {
|
||||
match serde_wasm_bindgen::from_value(ev.data()) {
|
||||
Ok(WorkerResponse::Ready) => notify.notify_one(),
|
||||
Ok(resp) => tx.send(resp).unwrap(),
|
||||
Err(err) => log::error!("Failed to parse message {:?}", err),
|
||||
}
|
||||
});
|
||||
|
||||
worker.set_onmessage(Some(on_message.as_ref().unchecked_ref()));
|
||||
on_message.forget();
|
||||
wait.notified().await;
|
||||
|
||||
(WorkerHandle(worker), rx)
|
||||
}
|
||||
|
||||
pub async fn handle_state(state: Store<GlobalState>, mut rx: UnboundedReceiver<WorkerResponse>) {
|
||||
while let Some(resp) = rx.recv().await {
|
||||
match resp {
|
||||
WorkerResponse::Ready => unreachable!(),
|
||||
WorkerResponse::Open(result) => match result {
|
||||
Ok(_) => (),
|
||||
Err(err) => state.last_error().set(Some(SQLightError::new_worker(err))),
|
||||
},
|
||||
WorkerResponse::Prepare(result) => {
|
||||
if let Err(err) = result {
|
||||
state.last_error().set(Some(SQLightError::new_worker(err)));
|
||||
}
|
||||
}
|
||||
WorkerResponse::Continue(result) => match result {
|
||||
Ok(results) => state.output().set(results),
|
||||
Err(err) => state.last_error().set(Some(SQLightError::new_worker(err))),
|
||||
},
|
||||
WorkerResponse::StepOver(_)
|
||||
| WorkerResponse::StepIn(_)
|
||||
| WorkerResponse::StepOut(_) => unimplemented!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
191
src/worker/mod.rs
Normal file
191
src/worker/mod.rs
Normal file
@@ -0,0 +1,191 @@
|
||||
mod sqlitend;
|
||||
|
||||
use crate::{OpenOptions, PERSIST_VFS, PrepareOptions, SQLiteStatementResult, WorkerError};
|
||||
use once_cell::sync::Lazy;
|
||||
use parking_lot::Mutex;
|
||||
use sqlite_wasm_rs::{
|
||||
export::{OpfsSAHPoolCfgBuilder, OpfsSAHPoolUtil},
|
||||
mem_vfs::MemVfsUtil,
|
||||
};
|
||||
use sqlitend::{SQLiteDb, SQLitePreparedStatement, SQLiteStatements};
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tokio::sync::OnceCell;
|
||||
|
||||
type Result<T> = std::result::Result<T, WorkerError>;
|
||||
|
||||
static DB_POOL: Lazy<Mutex<HashMap<String, SQLiteWorker>>> =
|
||||
Lazy::new(|| Mutex::new(HashMap::new()));
|
||||
|
||||
static FS_UTIL: Lazy<FSUtil> = Lazy::new(|| FSUtil {
|
||||
mem: MemVfsUtil::new(),
|
||||
opfs: OnceCell::new(),
|
||||
});
|
||||
|
||||
struct FSUtil {
|
||||
mem: MemVfsUtil,
|
||||
opfs: OnceCell<OpfsSAHPoolUtil>,
|
||||
}
|
||||
|
||||
struct SQLiteWorker {
|
||||
id: String,
|
||||
db: Option<Arc<SQLiteDb>>,
|
||||
open_options: OpenOptions,
|
||||
state: SQLiteState,
|
||||
}
|
||||
|
||||
enum SQLiteState {
|
||||
Idie,
|
||||
Prepared(PreparedState),
|
||||
}
|
||||
|
||||
struct PreparedState {
|
||||
stmts: SQLiteStatements,
|
||||
prepared: Option<SQLitePreparedStatement>,
|
||||
}
|
||||
|
||||
fn with_worker<F, T>(id: &str, mut f: F) -> Result<T>
|
||||
where
|
||||
F: FnMut(&mut SQLiteWorker) -> Result<T>,
|
||||
{
|
||||
f(DB_POOL.lock().get_mut(id).ok_or(WorkerError::NotFound)?)
|
||||
}
|
||||
|
||||
pub async fn open(options: OpenOptions) -> Result<String> {
|
||||
if let Some(worker) = DB_POOL.lock().get(&options.filename) {
|
||||
return Ok(worker.id.clone());
|
||||
}
|
||||
if options.persist {
|
||||
let util = FS_UTIL
|
||||
.opfs
|
||||
.get_or_try_init(|| async {
|
||||
sqlite_wasm_rs::sahpool_vfs::install(
|
||||
Some(
|
||||
&OpfsSAHPoolCfgBuilder::new()
|
||||
.directory(PERSIST_VFS)
|
||||
.vfs_name(PERSIST_VFS)
|
||||
.build(),
|
||||
),
|
||||
false,
|
||||
)
|
||||
.await
|
||||
.map_err(|_| WorkerError::OpfsSAHPoolOpened)
|
||||
})
|
||||
.await?;
|
||||
if util.get_capacity() - util.get_file_count() * 3 < 3 {
|
||||
util.add_capacity(3)
|
||||
.await
|
||||
.map_err(|_| WorkerError::OpfsSAHError)?;
|
||||
}
|
||||
}
|
||||
// FIXME: multi db support
|
||||
let id = String::new();
|
||||
let db = SQLiteDb::open(&options.uri())?;
|
||||
let worker = SQLiteWorker {
|
||||
id: id.clone(),
|
||||
db: Some(db),
|
||||
open_options: options,
|
||||
state: SQLiteState::Idie,
|
||||
};
|
||||
DB_POOL.lock().insert(id.clone(), worker);
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
pub fn prepare(options: PrepareOptions) -> Result<()> {
|
||||
with_worker(&options.id, |worker| {
|
||||
if options.clear_on_prepare {
|
||||
worker.db.take();
|
||||
|
||||
let filename = &worker.open_options.filename;
|
||||
let FSUtil { mem, opfs } = &*FS_UTIL;
|
||||
if worker.open_options.persist {
|
||||
if let Some(opfs) = opfs.get() {
|
||||
opfs.unlink(filename)
|
||||
.map_err(|_| WorkerError::OpfsSAHError)?;
|
||||
}
|
||||
} else {
|
||||
mem.delete_db(filename);
|
||||
}
|
||||
|
||||
worker.db = Some(SQLiteDb::open(&worker.open_options.uri())?);
|
||||
}
|
||||
|
||||
let stmts = worker.db.as_ref().unwrap().prepare(&options.sql)?;
|
||||
worker.state = SQLiteState::Prepared(PreparedState {
|
||||
stmts,
|
||||
prepared: None,
|
||||
});
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn r#continue(id: &str) -> Result<Vec<SQLiteStatementResult>> {
|
||||
with_worker(id, |worker| {
|
||||
let state = std::mem::replace(&mut worker.state, SQLiteState::Idie);
|
||||
let mut result = match state {
|
||||
SQLiteState::Idie => return Err(WorkerError::InvaildState),
|
||||
SQLiteState::Prepared(prepared_state) => {
|
||||
let mut result = vec![];
|
||||
if let Some(stmt) = prepared_state.prepared {
|
||||
result.push(stmt.pack(stmt.get_all()?));
|
||||
}
|
||||
result.extend(prepared_state.stmts.stmts_result()?);
|
||||
result
|
||||
}
|
||||
};
|
||||
result.push(SQLiteStatementResult::Finish);
|
||||
Ok(result)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn step_over(id: &str) -> Result<SQLiteStatementResult> {
|
||||
with_worker(id, |worker| match &mut worker.state {
|
||||
SQLiteState::Idie => Err(WorkerError::InvaildState),
|
||||
SQLiteState::Prepared(prepared_state) => {
|
||||
if let Some(prepared) = &mut prepared_state.prepared {
|
||||
if let Some(value) = prepared.get_one()? {
|
||||
Ok(prepared.pack(Some(value)))
|
||||
} else {
|
||||
let done = prepared.pack(None);
|
||||
prepared_state.prepared = None;
|
||||
Ok(done)
|
||||
}
|
||||
} else if let Some(prepared) = prepared_state.stmts.prepare_next()? {
|
||||
Ok(prepared.pack(prepared.get_all()?))
|
||||
} else {
|
||||
Ok(SQLiteStatementResult::Finish)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn step_in(id: &str) -> Result<()> {
|
||||
with_worker(id, |worker| {
|
||||
match &mut worker.state {
|
||||
SQLiteState::Idie => return Err(WorkerError::InvaildState),
|
||||
SQLiteState::Prepared(prepared_state) => {
|
||||
if prepared_state.prepared.is_some() {
|
||||
return Err(WorkerError::InvaildState);
|
||||
}
|
||||
let prepared = prepared_state
|
||||
.stmts
|
||||
.prepare_next()?
|
||||
.ok_or(WorkerError::InvaildState)?;
|
||||
prepared_state.prepared = Some(prepared);
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn step_out(id: &str) -> Result<SQLiteStatementResult> {
|
||||
with_worker(id, |worker| match &mut worker.state {
|
||||
SQLiteState::Idie => Err(WorkerError::InvaildState),
|
||||
SQLiteState::Prepared(prepared_state) => {
|
||||
if let Some(prepared) = prepared_state.prepared.take() {
|
||||
Ok(prepared.pack(prepared.get_all()?))
|
||||
} else {
|
||||
Err(WorkerError::InvaildState)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
266
src/worker/sqlitend.rs
Normal file
266
src/worker/sqlitend.rs
Normal file
@@ -0,0 +1,266 @@
|
||||
use serde_json::Value as JsonValue;
|
||||
use sqlite_wasm_rs::*;
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{self, AtomicBool};
|
||||
|
||||
use crate::{
|
||||
InnerError, SQLiteStatementResult, SQLiteStatementTable, SQLiteStatementValues, SQLitendError,
|
||||
};
|
||||
|
||||
type Result<T> = std::result::Result<T, SQLitendError>;
|
||||
|
||||
fn cstr(s: &str) -> Result<CString> {
|
||||
CString::new(s).map_err(|_| SQLitendError::ToCStr)
|
||||
}
|
||||
|
||||
fn sqlite_err(code: i32, db: *mut sqlite3) -> InnerError {
|
||||
let message = unsafe {
|
||||
let ptr = sqlite3_errmsg(db);
|
||||
CStr::from_ptr(ptr).to_string_lossy().to_string()
|
||||
};
|
||||
InnerError { code, message }
|
||||
}
|
||||
|
||||
pub struct SQLiteDb {
|
||||
sqlite3: *mut sqlite3,
|
||||
}
|
||||
|
||||
unsafe impl Send for SQLiteDb {}
|
||||
unsafe impl Sync for SQLiteDb {}
|
||||
|
||||
impl SQLiteDb {
|
||||
pub fn open(filename: &str) -> Result<Arc<Self>> {
|
||||
let mut sqlite3 = std::ptr::null_mut();
|
||||
let ret = unsafe {
|
||||
sqlite3_open_v2(
|
||||
cstr(filename)?.as_ptr().cast(),
|
||||
&mut sqlite3 as *mut _,
|
||||
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
|
||||
std::ptr::null(),
|
||||
)
|
||||
};
|
||||
|
||||
if ret != SQLITE_OK {
|
||||
return Err(SQLitendError::OpenDb(sqlite_err(ret, sqlite3)));
|
||||
}
|
||||
|
||||
Ok(Arc::new(Self { sqlite3 }))
|
||||
}
|
||||
|
||||
pub fn prepare(self: &Arc<Self>, sql: &str) -> Result<SQLiteStatements> {
|
||||
let sql = cstr(sql)?;
|
||||
let tail = sql.as_ptr();
|
||||
|
||||
Ok(SQLiteStatements {
|
||||
sql,
|
||||
db: Arc::clone(self),
|
||||
tail,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for SQLiteDb {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
sqlite3_close(self.sqlite3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SQLiteStatements {
|
||||
sql: CString,
|
||||
db: Arc<SQLiteDb>,
|
||||
tail: *const i8,
|
||||
}
|
||||
|
||||
unsafe impl Send for SQLiteStatements {}
|
||||
unsafe impl Sync for SQLiteStatements {}
|
||||
|
||||
impl SQLiteStatements {
|
||||
pub fn prepare_next(&mut self) -> Result<Option<SQLitePreparedStatement>> {
|
||||
if self.tail.is_null() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let sqlite3 = self.db.sqlite3;
|
||||
let mut stmt: *mut sqlite3_stmt = std::ptr::null_mut();
|
||||
let mut tail = std::ptr::null();
|
||||
|
||||
let ret = unsafe {
|
||||
sqlite3_prepare_v3(sqlite3, self.tail, -1, 0, &mut stmt as _, &mut tail as _)
|
||||
};
|
||||
|
||||
if ret != SQLITE_OK {
|
||||
return Err(SQLitendError::Prepare(sqlite_err(ret, sqlite3)));
|
||||
}
|
||||
|
||||
let sql = unsafe { sqlite3_sql(stmt) };
|
||||
if sql.is_null() {
|
||||
return Ok(None);
|
||||
}
|
||||
let sql = unsafe { CStr::from_ptr(sql).to_string_lossy().to_string() };
|
||||
|
||||
let start_offset = self.tail as usize - self.sql.as_ptr() as usize;
|
||||
let end_offset = start_offset + sql.len();
|
||||
let position = [start_offset, end_offset];
|
||||
|
||||
self.tail = tail;
|
||||
|
||||
Ok(Some(SQLitePreparedStatement {
|
||||
sql,
|
||||
done: AtomicBool::new(false),
|
||||
position,
|
||||
sqlite3,
|
||||
stmt,
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn stmts_result(self) -> Result<Vec<SQLiteStatementResult>> {
|
||||
let mut result = vec![];
|
||||
for stmt in self {
|
||||
let stmt = stmt?;
|
||||
result.push(stmt.pack(stmt.get_all()?));
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for SQLiteStatements {
|
||||
type Item = Result<SQLitePreparedStatement>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.prepare_next().transpose()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SQLitePreparedStatement {
|
||||
sql: String,
|
||||
position: [usize; 2],
|
||||
done: AtomicBool,
|
||||
sqlite3: *mut sqlite3,
|
||||
stmt: *mut sqlite3_stmt,
|
||||
}
|
||||
|
||||
unsafe impl Send for SQLitePreparedStatement {}
|
||||
unsafe impl Sync for SQLitePreparedStatement {}
|
||||
|
||||
impl SQLitePreparedStatement {
|
||||
/// Stepping to the next line
|
||||
fn step(&self) -> Result<bool> {
|
||||
let ret = unsafe { sqlite3_step(self.stmt) };
|
||||
match ret {
|
||||
SQLITE_DONE => {
|
||||
self.done.store(true, atomic::Ordering::SeqCst);
|
||||
Ok(false)
|
||||
}
|
||||
SQLITE_ROW => Ok(true),
|
||||
code => Err(SQLitendError::Step(sqlite_err(code, self.sqlite3))),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pack(&self, values: Option<SQLiteStatementValues>) -> SQLiteStatementResult {
|
||||
let values = SQLiteStatementTable {
|
||||
sql: self.sql.clone(),
|
||||
position: self.position,
|
||||
done: self.done.load(atomic::Ordering::SeqCst),
|
||||
values,
|
||||
};
|
||||
SQLiteStatementResult::Step(values)
|
||||
}
|
||||
|
||||
pub fn get_all(&self) -> Result<Option<SQLiteStatementValues>> {
|
||||
let mut values = match self.get_one()? {
|
||||
Some(value) => value,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
while let Some(value) = self.get_one()? {
|
||||
for row in value.rows {
|
||||
values.rows.push(row);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Some(values))
|
||||
}
|
||||
|
||||
/// Get data for all columns of the current row
|
||||
pub fn get_one(&self) -> Result<Option<SQLiteStatementValues>> {
|
||||
if !self.step()? {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let column_count = unsafe { sqlite3_column_count(self.stmt) };
|
||||
|
||||
let mut column = Vec::with_capacity(column_count as usize);
|
||||
let mut row = Vec::with_capacity(column_count as usize);
|
||||
|
||||
for col_ndx in 0..column_count {
|
||||
// column_name as key
|
||||
let (column_name, column_type) = unsafe {
|
||||
let ptr = sqlite3_column_name(self.stmt, col_ndx);
|
||||
if ptr.is_null() {
|
||||
return Err(SQLitendError::GetColumnName(
|
||||
"the column name is a null pointer, this shouldn't happen".into(),
|
||||
));
|
||||
}
|
||||
let Ok(column_name) = CStr::from_ptr(ptr).to_str() else {
|
||||
return Err(SQLitendError::GetColumnName(
|
||||
"the column name is not a string, this shouldn't happen".into(),
|
||||
));
|
||||
};
|
||||
(column_name, sqlite3_column_type(self.stmt, col_ndx))
|
||||
};
|
||||
|
||||
// https://www.sqlite.org/c3ref/column_blob.html
|
||||
let value = unsafe {
|
||||
match column_type {
|
||||
SQLITE_NULL => JsonValue::Null,
|
||||
SQLITE_INTEGER => {
|
||||
let number = sqlite3_column_int64(self.stmt, col_ndx);
|
||||
JsonValue::from(number)
|
||||
}
|
||||
SQLITE_FLOAT => JsonValue::from(sqlite3_column_double(self.stmt, col_ndx)),
|
||||
SQLITE_TEXT => {
|
||||
let slice = {
|
||||
let text = sqlite3_column_text(self.stmt, col_ndx);
|
||||
// get text size, there may be problems if use as cstr
|
||||
let len = sqlite3_column_bytes(self.stmt, col_ndx);
|
||||
std::slice::from_raw_parts(text, len as usize)
|
||||
};
|
||||
// must be UTF-8 TEXT result
|
||||
let Ok(text) = std::str::from_utf8(slice) else {
|
||||
return Err(SQLitendError::Utf8Text);
|
||||
};
|
||||
JsonValue::from(text)
|
||||
}
|
||||
SQLITE_BLOB => {
|
||||
let slice = {
|
||||
let blob = sqlite3_column_blob(self.stmt, col_ndx);
|
||||
let len = sqlite3_column_bytes(self.stmt, col_ndx);
|
||||
std::slice::from_raw_parts(blob.cast::<u8>(), len as usize)
|
||||
};
|
||||
JsonValue::from(slice)
|
||||
}
|
||||
_ => return Err(SQLitendError::UnsupportColumnType(column_type)),
|
||||
}
|
||||
};
|
||||
|
||||
column.push(column_name.into());
|
||||
row.push(value);
|
||||
}
|
||||
|
||||
Ok(Some(SQLiteStatementValues {
|
||||
columns: column,
|
||||
rows: vec![row],
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for SQLitePreparedStatement {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
sqlite3_finalize(self.stmt);
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user