set up lustre and sass
This commit is contained in:
parent
9c1c11a277
commit
afafb49cd6
27 changed files with 814 additions and 61 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -2,6 +2,8 @@ target
|
|||
.env
|
||||
.DS_Store
|
||||
node_modules
|
||||
css_dist
|
||||
glm_dist
|
||||
js_dist
|
||||
pgdata
|
||||
.vite
|
||||
|
|
|
|||
132
components/src/add-selection-button.ts
Normal file
132
components/src/add-selection-button.ts
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
import { createRef, ref } from "lit/directives/ref.js";
|
||||
|
||||
import "./add-selection-modal-contents.tsx";
|
||||
|
||||
@customElement("add-selection-button")
|
||||
export class AddSelectionButton extends LitElement {
|
||||
@property({ attribute: true })
|
||||
columns = "";
|
||||
|
||||
@state()
|
||||
private _active = false;
|
||||
|
||||
private _labelInputRef = createRef<HTMLInputElement>();
|
||||
|
||||
private _nameInputRef = createRef<HTMLInputElement>();
|
||||
|
||||
private _typePopoverRef = createRef<HTMLDivElement>();
|
||||
|
||||
static override styles = css`
|
||||
:host {
|
||||
height: 100%;
|
||||
--shadow: 0 0.5rem 0.5rem #3333;
|
||||
}
|
||||
|
||||
button.main {
|
||||
appearance: none;
|
||||
border: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-weight: inherit;
|
||||
font-size: inherit;
|
||||
font-family: inherit;
|
||||
cursor: pointer;
|
||||
background: none;
|
||||
}
|
||||
|
||||
div.th {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
border: dashed 1px #ccc;
|
||||
border-left: none;
|
||||
border-top: none;
|
||||
font-family: "Funnel Sans";
|
||||
background: #0001;
|
||||
box-sizing: border-box;
|
||||
padding: 0 0.5rem;
|
||||
}
|
||||
|
||||
input#name-input {
|
||||
appearance: none;
|
||||
background: none;
|
||||
border: none;
|
||||
outline: none;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
font-weight: inherit;
|
||||
}
|
||||
|
||||
button#type-config-button {
|
||||
anchor-name: --type-config-button;
|
||||
}
|
||||
|
||||
.config-popover:popover-open {
|
||||
box-sizing: border-box;
|
||||
inset: unset;
|
||||
margin: 0;
|
||||
margin-top: 1rem;
|
||||
position: fixed;
|
||||
display: block;
|
||||
width: 20rem;
|
||||
border: solid 1px #ccc;
|
||||
border-radius: 0.25rem;
|
||||
filter: drop-shadow(var(--shadow));
|
||||
}
|
||||
|
||||
#type-popover {
|
||||
position-anchor: --type-config-button;
|
||||
position-area: bottom;
|
||||
}
|
||||
`;
|
||||
|
||||
protected override updated() {
|
||||
if (this._active && this._labelInputRef.value) {
|
||||
this._labelInputRef.value.focus();
|
||||
}
|
||||
}
|
||||
|
||||
private _activate() {
|
||||
this._active = true;
|
||||
}
|
||||
|
||||
private _handleLabelChange(ev: InputEvent) {
|
||||
}
|
||||
|
||||
private _showTypePopover() {
|
||||
this._typePopoverRef.value?.showPopover();
|
||||
}
|
||||
|
||||
protected override render() {
|
||||
if (this._active) {
|
||||
return html`
|
||||
<div class="th">
|
||||
<input
|
||||
type="text"
|
||||
${ref(
|
||||
this._labelInputRef,
|
||||
)}
|
||||
id="label-input"
|
||||
name="name-input"
|
||||
@change="${this._handleLabelChange}"
|
||||
>
|
||||
<button type="button" id="type-config-button" @click="${this
|
||||
._showTypePopover}">
|
||||
abc
|
||||
</button>
|
||||
<button type="submit">Create</button>
|
||||
</div>
|
||||
<div ${ref(
|
||||
this._typePopoverRef,
|
||||
)} id="type-popover" class="config-popover" popover="auto">
|
||||
<input type="text" ${ref(this._nameInputRef)} name="name">
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<button type="button" class="main" @click="${this._activate}">+</button>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { createRef, type Ref, ref } from "lit/directives/ref.js";
|
||||
|
||||
import "./add-selection-modal-contents.tsx";
|
||||
|
||||
@customElement("add-selection-button")
|
||||
export class AddSelectionButton extends LitElement {
|
||||
@property({ attribute: true })
|
||||
columns = "";
|
||||
|
||||
private _dialogRef: Ref<HTMLDialogElement> = createRef();
|
||||
|
||||
static override styles = css`
|
||||
button.th {
|
||||
appearance: none;
|
||||
border: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-weight: inherit;
|
||||
font-size: inherit;
|
||||
font-family: inherit;
|
||||
cursor: pointer;
|
||||
background: none;
|
||||
}
|
||||
|
||||
dialog {
|
||||
border: solid 1px #ccc;
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0 0.5rem 0.5rem #3333;
|
||||
font-family:
|
||||
"Averia Serif Libre",
|
||||
"Open Sans",
|
||||
"Helvetica Neue",
|
||||
Arial,
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
dialog::backdrop {
|
||||
background: #0001;
|
||||
}
|
||||
`;
|
||||
|
||||
showModal() {
|
||||
this._dialogRef.value?.showModal();
|
||||
}
|
||||
|
||||
protected override render() {
|
||||
return html`
|
||||
<button type="button" class="th" @click="${this.showModal}">+</button>
|
||||
<dialog ${ref(this._dialogRef)} closedby="any">
|
||||
<add-selection-modal-content
|
||||
columns="${this.columns}"
|
||||
></add-selection-modal-content>
|
||||
</dialog>
|
||||
`;
|
||||
}
|
||||
}
|
||||
1
components/src/entrypoints/viewer-components.ts
Normal file
1
components/src/entrypoints/viewer-components.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { AddSelectionButton } from "../add-selection-button.ts";
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { AddSelectionButton } from "../add-selection-button.tsx";
|
||||
4
glm/.gitignore
vendored
Normal file
4
glm/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
*.beam
|
||||
*.ez
|
||||
/build
|
||||
erl_crash.dump
|
||||
24
glm/README.md
Normal file
24
glm/README.md
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# glm
|
||||
|
||||
[](https://hex.pm/packages/glm)
|
||||
[](https://hexdocs.pm/glm/)
|
||||
|
||||
```sh
|
||||
gleam add glm@1
|
||||
```
|
||||
```gleam
|
||||
import glm
|
||||
|
||||
pub fn main() -> Nil {
|
||||
// TODO: An example of the project in use
|
||||
}
|
||||
```
|
||||
|
||||
Further documentation can be found at <https://hexdocs.pm/glm>.
|
||||
|
||||
## Development
|
||||
|
||||
```sh
|
||||
gleam run # Run the project
|
||||
gleam test # Run the tests
|
||||
```
|
||||
24
glm/gleam.toml
Normal file
24
glm/gleam.toml
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
name = "glm"
|
||||
version = "1.0.0"
|
||||
target = "javascript"
|
||||
|
||||
# Fill out these fields if you intend to generate HTML documentation or publish
|
||||
# your project to the Hex package manager.
|
||||
#
|
||||
# description = ""
|
||||
# licences = ["Apache-2.0"]
|
||||
# repository = { type = "github", user = "", repo = "" }
|
||||
# links = [{ title = "Website", href = "" }]
|
||||
#
|
||||
# For a full reference of all the available options, you can have a look at
|
||||
# https://gleam.run/writing-gleam/gleam-toml/.
|
||||
|
||||
[dependencies]
|
||||
gleam_stdlib = ">= 0.44.0 and < 2.0.0"
|
||||
lustre = ">= 5.2.1 and < 6.0.0"
|
||||
gleam_json = ">= 3.0.2 and < 4.0.0"
|
||||
gleam_regexp = ">= 1.1.1 and < 2.0.0"
|
||||
|
||||
[dev-dependencies]
|
||||
gleeunit = ">= 1.0.0 and < 2.0.0"
|
||||
lustre_dev_tools = ">= 1.9.0 and < 2.0.0"
|
||||
51
glm/manifest.toml
Normal file
51
glm/manifest.toml
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
# This file was generated by Gleam
|
||||
# You typically do not need to edit this file
|
||||
|
||||
packages = [
|
||||
{ name = "argv", version = "1.0.2", build_tools = ["gleam"], requirements = [], otp_app = "argv", source = "hex", outer_checksum = "BA1FF0929525DEBA1CE67256E5ADF77A7CDDFE729E3E3F57A5BDCAA031DED09D" },
|
||||
{ name = "directories", version = "1.2.0", build_tools = ["gleam"], requirements = ["envoy", "gleam_stdlib", "platform", "simplifile"], otp_app = "directories", source = "hex", outer_checksum = "D13090CFCDF6759B87217E8DDD73A75903A700148A82C1D33799F333E249BF9E" },
|
||||
{ name = "envoy", version = "1.0.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "envoy", source = "hex", outer_checksum = "95FD059345AA982E89A0B6E2A3BF1CF43E17A7048DCD85B5B65D3B9E4E39D359" },
|
||||
{ name = "exception", version = "2.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "exception", source = "hex", outer_checksum = "329D269D5C2A314F7364BD2711372B6F2C58FA6F39981572E5CA68624D291F8C" },
|
||||
{ name = "filepath", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "B06A9AF0BF10E51401D64B98E4B627F1D2E48C154967DA7AF4D0914780A6D40A" },
|
||||
{ name = "fs", version = "11.4.1", build_tools = ["rebar3"], requirements = [], otp_app = "fs", source = "hex", outer_checksum = "DD00A61D89EAC01D16D3FC51D5B0EB5F0722EF8E3C1A3A547CD086957F3260A9" },
|
||||
{ name = "gleam_community_ansi", version = "1.4.3", build_tools = ["gleam"], requirements = ["gleam_community_colour", "gleam_regexp", "gleam_stdlib"], otp_app = "gleam_community_ansi", source = "hex", outer_checksum = "8A62AE9CC6EA65BEA630D95016D6C07E4F9973565FA3D0DE68DC4200D8E0DD27" },
|
||||
{ name = "gleam_community_colour", version = "2.0.2", build_tools = ["gleam"], requirements = ["gleam_json", "gleam_stdlib"], otp_app = "gleam_community_colour", source = "hex", outer_checksum = "E34DD2C896AC3792151EDA939DA435FF3B69922F33415ED3C4406C932FBE9634" },
|
||||
{ name = "gleam_crypto", version = "1.5.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_crypto", source = "hex", outer_checksum = "50774BAFFF1144E7872814C566C5D653D83A3EBF23ACC3156B757A1B6819086E" },
|
||||
{ name = "gleam_deque", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_deque", source = "hex", outer_checksum = "64D77068931338CF0D0CB5D37522C3E3CCA7CB7D6C5BACB41648B519CC0133C7" },
|
||||
{ name = "gleam_erlang", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "F91CE62A2D011FA13341F3723DB7DB118541AAA5FE7311BD2716D018F01EF9E3" },
|
||||
{ name = "gleam_http", version = "4.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "DB25DFC8530B64B77105405B80686541A0D96F7E2D83D807D6B2155FB9A8B1B8" },
|
||||
{ name = "gleam_httpc", version = "4.1.1", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_http", "gleam_stdlib"], otp_app = "gleam_httpc", source = "hex", outer_checksum = "C670EBD46FC1472AD5F1F74F1D3938D1D0AC1C7531895ED1D4DDCB6F07279F43" },
|
||||
{ name = "gleam_json", version = "3.0.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "874FA3C3BB6E22DD2BB111966BD40B3759E9094E05257899A7C08F5DE77EC049" },
|
||||
{ name = "gleam_otp", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "7020E652D18F9ABAC9C877270B14160519FA0856EE80126231C505D719AD68DA" },
|
||||
{ name = "gleam_package_interface", version = "3.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_package_interface", source = "hex", outer_checksum = "8F2D19DE9876D9401BB0626260958A6B1580BB233489C32831FE74CE0ACAE8B4" },
|
||||
{ name = "gleam_regexp", version = "1.1.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_regexp", source = "hex", outer_checksum = "9C215C6CA84A5B35BB934A9B61A9A306EC743153BE2B0425A0D032E477B062A9" },
|
||||
{ name = "gleam_stdlib", version = "0.62.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "DC8872BC0B8550F6E22F0F698CFE7F1E4BDA7312FDEB40D6C3F44C5B706C8310" },
|
||||
{ name = "gleam_yielder", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_yielder", source = "hex", outer_checksum = "8E4E4ECFA7982859F430C57F549200C7749823C106759F4A19A78AEA6687717A" },
|
||||
{ name = "gleeunit", version = "1.6.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "63022D81C12C17B7F1A60E029964E830A4CBD846BBC6740004FC1F1031AE0326" },
|
||||
{ name = "glint", version = "1.2.1", build_tools = ["gleam"], requirements = ["gleam_community_ansi", "gleam_community_colour", "gleam_stdlib", "snag"], otp_app = "glint", source = "hex", outer_checksum = "2214C7CEFDE457CEE62140C3D4899B964E05236DA74E4243DFADF4AF29C382BB" },
|
||||
{ name = "glisten", version = "8.0.1", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib", "logging", "telemetry"], otp_app = "glisten", source = "hex", outer_checksum = "534BB27C71FB9E506345A767C0D76B17A9E9199934340C975DC003C710E3692D" },
|
||||
{ name = "gramps", version = "3.0.3", build_tools = ["gleam"], requirements = ["gleam_crypto", "gleam_erlang", "gleam_http", "gleam_stdlib"], otp_app = "gramps", source = "hex", outer_checksum = "75F0F20C867A6217CBB632A7E563568D6A6366B850815041E8E0B4F179681E53" },
|
||||
{ name = "houdini", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "houdini", source = "hex", outer_checksum = "5BA517E5179F132F0471CB314F27FE210A10407387DA1EA4F6FD084F74469FC2" },
|
||||
{ name = "hpack_erl", version = "0.3.0", build_tools = ["rebar3"], requirements = [], otp_app = "hpack", source = "hex", outer_checksum = "D6137D7079169D8C485C6962DFE261AF5B9EF60FBC557344511C1E65E3D95FB0" },
|
||||
{ name = "logging", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "logging", source = "hex", outer_checksum = "1098FBF10B54B44C2C7FDF0B01C1253CAFACDACABEFB4B0D027803246753E06D" },
|
||||
{ name = "lustre", version = "5.2.1", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_json", "gleam_otp", "gleam_stdlib", "houdini"], otp_app = "lustre", source = "hex", outer_checksum = "DCD121F8E6B7E179B27D9A8AEB6C828D8380E26DF2E16D078511EDAD1CA9F2A7" },
|
||||
{ name = "lustre_dev_tools", version = "1.9.0", build_tools = ["gleam"], requirements = ["argv", "filepath", "fs", "gleam_community_ansi", "gleam_crypto", "gleam_deque", "gleam_erlang", "gleam_http", "gleam_httpc", "gleam_json", "gleam_otp", "gleam_package_interface", "gleam_regexp", "gleam_stdlib", "glint", "glisten", "mist", "repeatedly", "simplifile", "term_size", "tom", "wisp"], otp_app = "lustre_dev_tools", source = "hex", outer_checksum = "2132E6B2B7E89ED87C138FFE1F2CD70D859258D67222F26B5793CDACE9B07D75" },
|
||||
{ name = "marceau", version = "1.3.0", build_tools = ["gleam"], requirements = [], otp_app = "marceau", source = "hex", outer_checksum = "2D1C27504BEF45005F5DFB18591F8610FB4BFA91744878210BDC464412EC44E9" },
|
||||
{ name = "mist", version = "5.0.2", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_http", "gleam_otp", "gleam_stdlib", "gleam_yielder", "glisten", "gramps", "hpack_erl", "logging"], otp_app = "mist", source = "hex", outer_checksum = "0716CE491EA13E1AA1EFEC4B427593F8EB2B953B6EBDEBE41F15BE3D06A22918" },
|
||||
{ name = "platform", version = "1.0.0", build_tools = ["gleam"], requirements = [], otp_app = "platform", source = "hex", outer_checksum = "8339420A95AD89AAC0F82F4C3DB8DD401041742D6C3F46132A8739F6AEB75391" },
|
||||
{ name = "repeatedly", version = "2.1.2", build_tools = ["gleam"], requirements = [], otp_app = "repeatedly", source = "hex", outer_checksum = "93AE1938DDE0DC0F7034F32C1BF0D4E89ACEBA82198A1FE21F604E849DA5F589" },
|
||||
{ name = "simplifile", version = "2.3.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "0A868DAC6063D9E983477981839810DC2E553285AB4588B87E3E9C96A7FB4CB4" },
|
||||
{ name = "snag", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "snag", source = "hex", outer_checksum = "7E9F06390040EB5FAB392CE642771484136F2EC103A92AE11BA898C8167E6E17" },
|
||||
{ name = "telemetry", version = "1.3.0", build_tools = ["rebar3"], requirements = [], otp_app = "telemetry", source = "hex", outer_checksum = "7015FC8919DBE63764F4B4B87A95B7C0996BD539E0D499BE6EC9D7F3875B79E6" },
|
||||
{ name = "term_size", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "term_size", source = "hex", outer_checksum = "D00BD2BC8FB3EBB7E6AE076F3F1FF2AC9D5ED1805F004D0896C784D06C6645F1" },
|
||||
{ name = "tom", version = "1.1.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "tom", source = "hex", outer_checksum = "0910EE688A713994515ACAF1F486A4F05752E585B9E3209D8F35A85B234C2719" },
|
||||
{ name = "wisp", version = "1.8.0", build_tools = ["gleam"], requirements = ["directories", "exception", "gleam_crypto", "gleam_erlang", "gleam_http", "gleam_json", "gleam_stdlib", "houdini", "logging", "marceau", "mist", "simplifile"], otp_app = "wisp", source = "hex", outer_checksum = "0FE9049AFFB7C8D5FC0B154EEE2704806F4D51B97F44925D69349B3F4F192957" },
|
||||
]
|
||||
|
||||
[requirements]
|
||||
gleam_json = { version = ">= 3.0.2 and < 4.0.0" }
|
||||
gleam_regexp = { version = ">= 1.1.1 and < 2.0.0" }
|
||||
gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" }
|
||||
gleeunit = { version = ">= 1.0.0 and < 2.0.0" }
|
||||
lustre = { version = ">= 5.2.1 and < 6.0.0" }
|
||||
lustre_dev_tools = { version = ">= 1.9.0 and < 2.0.0" }
|
||||
10
glm/src/field_adder.ffi.mjs
Normal file
10
glm/src/field_adder.ffi.mjs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { Error, Ok } from "./gleam.mjs";
|
||||
|
||||
export function focusElement(selector, root) {
|
||||
const element = root.querySelector(selector);
|
||||
if (element) {
|
||||
element.focus();
|
||||
return new Ok(undefined);
|
||||
}
|
||||
return new Error(undefined);
|
||||
}
|
||||
239
glm/src/field_adder.gleam
Normal file
239
glm/src/field_adder.gleam
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
import gleam/dynamic.{type Dynamic}
|
||||
import gleam/dynamic/decode
|
||||
import gleam/json
|
||||
import gleam/regexp
|
||||
import gleam/result
|
||||
import gleam/string
|
||||
import lustre.{type App}
|
||||
import lustre/attribute as attr
|
||||
import lustre/component
|
||||
import lustre/effect.{type Effect}
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/html
|
||||
import lustre/event
|
||||
|
||||
pub const name: String = "field-adder"
|
||||
|
||||
pub fn component() -> App(Nil, Model, Msg) {
|
||||
lustre.component(init, update, view, [
|
||||
component.on_attribute_change("columns", fn(value) {
|
||||
Ok(
|
||||
json.parse(from: value, using: decode.list(of: decode.string))
|
||||
|> result.unwrap([])
|
||||
|> ParentChangedColumns,
|
||||
)
|
||||
}),
|
||||
component.on_attribute_change("root-path", fn(value) {
|
||||
ParentChangedRootPath(value) |> Ok
|
||||
}),
|
||||
])
|
||||
}
|
||||
|
||||
pub type Model {
|
||||
Model(
|
||||
columns: List(String),
|
||||
root_path: String,
|
||||
expanded: Bool,
|
||||
label_value: String,
|
||||
name_value: String,
|
||||
name_customized: Bool,
|
||||
field_type: String,
|
||||
submitting: Bool,
|
||||
)
|
||||
}
|
||||
|
||||
fn init(_) -> #(Model, Effect(Msg)) {
|
||||
#(
|
||||
Model(
|
||||
columns: [],
|
||||
root_path: "",
|
||||
expanded: False,
|
||||
label_value: "",
|
||||
name_value: "",
|
||||
name_customized: False,
|
||||
field_type: "text",
|
||||
submitting: False,
|
||||
),
|
||||
effect.none(),
|
||||
)
|
||||
}
|
||||
|
||||
pub type Msg {
|
||||
ParentChangedColumns(List(String))
|
||||
ParentChangedRootPath(String)
|
||||
UserClickedCancel
|
||||
UserExpandedComponent
|
||||
UserUpdatedName(String)
|
||||
UserUpdatedLabel(String)
|
||||
UserUpdatedFieldType(String)
|
||||
}
|
||||
|
||||
fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) {
|
||||
case msg {
|
||||
ParentChangedColumns(columns) -> #(Model(..model, columns:), effect.none())
|
||||
ParentChangedRootPath(root_path) -> #(
|
||||
Model(..model, root_path:),
|
||||
effect.none(),
|
||||
)
|
||||
UserClickedCancel -> #(
|
||||
Model(
|
||||
..model,
|
||||
expanded: False,
|
||||
label_value: "",
|
||||
name_value: "",
|
||||
name_customized: False,
|
||||
field_type: "text",
|
||||
),
|
||||
effect.none(),
|
||||
)
|
||||
UserExpandedComponent -> #(
|
||||
Model(..model, expanded: True),
|
||||
focus_element("#label-input"),
|
||||
)
|
||||
UserUpdatedName(name_value) -> #(
|
||||
Model(..model, name_value:, name_customized: True),
|
||||
effect.none(),
|
||||
)
|
||||
UserUpdatedLabel(label_value) -> #(
|
||||
Model(..model, label_value:, name_value: case model.name_customized {
|
||||
True -> model.name_value
|
||||
False -> label_value |> to_idiomatic_column_name
|
||||
}),
|
||||
effect.none(),
|
||||
)
|
||||
UserUpdatedFieldType(field_type) -> #(
|
||||
Model(..model, field_type:),
|
||||
effect.none(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn to_idiomatic_column_name(label: String) -> String {
|
||||
let assert Ok(re) = regexp.from_string("[^a-z0-9]")
|
||||
regexp.replace(each: re, in: label |> string.lowercase, with: "_")
|
||||
}
|
||||
|
||||
fn focus_element(selector: String) -> Effect(Msg) {
|
||||
use _, shadow_root <- effect.before_paint
|
||||
do_focus_element(selector:, in: shadow_root) |> result.unwrap(Nil)
|
||||
}
|
||||
|
||||
@external(javascript, "./field_adder.ffi.mjs", "focusElement")
|
||||
fn do_focus_element(
|
||||
selector _selector: String,
|
||||
in _root: Dynamic,
|
||||
) -> Result(Nil, Nil) {
|
||||
Error(Nil)
|
||||
}
|
||||
|
||||
fn view(model: Model) -> Element(Msg) {
|
||||
element.fragment([
|
||||
html.link([
|
||||
attr.rel("stylesheet"),
|
||||
attr.href(model.root_path <> "/css_dist/field_adder/index.css"),
|
||||
]),
|
||||
case model.expanded {
|
||||
False ->
|
||||
html.button(
|
||||
[
|
||||
attr.type_("button"),
|
||||
attr.class("expander__button"),
|
||||
event.on_click(UserExpandedComponent),
|
||||
],
|
||||
[html.text("+")],
|
||||
)
|
||||
True ->
|
||||
html.div([attr.class("header")], [
|
||||
html.form([attr.method("post"), attr.action("create-column")], [
|
||||
label_input(value: model.label_value, on_input: UserUpdatedLabel),
|
||||
html.button(
|
||||
[attr.type_("button"), attr.popovertarget("config-popover")],
|
||||
[html.text("...")],
|
||||
),
|
||||
config_popover(model),
|
||||
]),
|
||||
])
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
fn label_input(
|
||||
value value: String,
|
||||
on_input handle_input: fn(String) -> Msg,
|
||||
) -> Element(Msg) {
|
||||
html.input([
|
||||
attr.type_("text"),
|
||||
attr.id("label-input"),
|
||||
attr.name("label"),
|
||||
attr.class("header__input"),
|
||||
attr.placeholder("My New Column"),
|
||||
attr.value(value),
|
||||
event.on_input(handle_input),
|
||||
])
|
||||
}
|
||||
|
||||
fn config_popover(model: Model) -> Element(Msg) {
|
||||
html.div(
|
||||
[
|
||||
attr.id("config-popover"),
|
||||
attr.class("config-popover__container"),
|
||||
attr.popover("auto"),
|
||||
],
|
||||
[
|
||||
html.h2([attr.class("form-section__heading")], [
|
||||
html.text("Field Details"),
|
||||
]),
|
||||
html.label([attr.class("form-section__label"), attr.for("name-input")], [
|
||||
html.text("SQL-friendly Name"),
|
||||
]),
|
||||
html.input([
|
||||
attr.type_("text"),
|
||||
attr.name("name"),
|
||||
attr.class("form-section__input form-section__input--text"),
|
||||
attr.id("name-input"),
|
||||
attr.value(model.name_value),
|
||||
event.on_input(UserUpdatedName),
|
||||
]),
|
||||
html.label(
|
||||
[attr.for("field-type-select"), attr.class("form-section__label")],
|
||||
[html.text("Data Type")],
|
||||
),
|
||||
html.select(
|
||||
[
|
||||
attr.type_("text"),
|
||||
attr.name("field_type"),
|
||||
attr.class("form-section__input"),
|
||||
attr.id("field-type-select"),
|
||||
event.on_change(UserUpdatedFieldType),
|
||||
],
|
||||
[
|
||||
html.option(
|
||||
[attr.value("text"), attr.checked(model.field_type == "text")],
|
||||
"Text",
|
||||
),
|
||||
html.option(
|
||||
[attr.value("decimal"), attr.checked(model.field_type == "decimal")],
|
||||
"Decimal",
|
||||
),
|
||||
],
|
||||
),
|
||||
html.div([attr.class("form-buttons")], [
|
||||
html.button(
|
||||
[
|
||||
attr.type_("button"),
|
||||
attr.class("form-buttons__button form-buttons__button--cancel"),
|
||||
event.on_click(UserClickedCancel),
|
||||
],
|
||||
[html.text("Cancel")],
|
||||
),
|
||||
html.button(
|
||||
[
|
||||
attr.type_("submit"),
|
||||
attr.class("form-buttons__button form-buttons__button--submit"),
|
||||
],
|
||||
[html.text("Create")],
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
6
glm/src/glm.gleam.bak
Normal file
6
glm/src/glm.gleam.bak
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import field_adder
|
||||
|
||||
pub fn main() -> Nil {
|
||||
let assert Ok(_) = field_adder.register()
|
||||
Nil
|
||||
}
|
||||
|
|
@ -107,6 +107,46 @@ pub fn new_router(state: AppState) -> Router<()> {
|
|||
),
|
||||
),
|
||||
)
|
||||
.nest_service(
|
||||
"/glm_dist",
|
||||
ServiceBuilder::new()
|
||||
.layer(SetResponseHeaderLayer::if_not_present(
|
||||
CACHE_CONTROL,
|
||||
// FIXME: restore production value
|
||||
// HeaderValue::from_static("max-age=21600, stale-while-revalidate=86400"),
|
||||
HeaderValue::from_static("no-cache"),
|
||||
))
|
||||
.service(
|
||||
ServeDir::new("glm_dist").not_found_service(
|
||||
ServiceBuilder::new()
|
||||
.layer(SetResponseHeaderLayer::if_not_present(
|
||||
CACHE_CONTROL,
|
||||
HeaderValue::from_static("no-cache"),
|
||||
))
|
||||
.service(ServeFile::new("static/_404.html")),
|
||||
),
|
||||
),
|
||||
)
|
||||
.nest_service(
|
||||
"/css_dist",
|
||||
ServiceBuilder::new()
|
||||
.layer(SetResponseHeaderLayer::if_not_present(
|
||||
CACHE_CONTROL,
|
||||
// FIXME: restore production value
|
||||
// HeaderValue::from_static("max-age=21600, stale-while-revalidate=86400"),
|
||||
HeaderValue::from_static("no-cache"),
|
||||
))
|
||||
.service(
|
||||
ServeDir::new("css_dist").not_found_service(
|
||||
ServiceBuilder::new()
|
||||
.layer(SetResponseHeaderLayer::if_not_present(
|
||||
CACHE_CONTROL,
|
||||
HeaderValue::from_static("no-cache"),
|
||||
))
|
||||
.service(ServeFile::new("static/_404.html")),
|
||||
),
|
||||
),
|
||||
)
|
||||
.fallback_service(
|
||||
ServiceBuilder::new()
|
||||
.layer(SetResponseHeaderLayer::if_not_present(
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
{% include "meta_tags.html" %}
|
||||
<link rel="stylesheet" href="{{ settings.root_path }}/modern-normalize.min.css">
|
||||
<link rel="stylesheet" href="{{ settings.root_path }}/main.css">
|
||||
<link rel="stylesheet" href="{{ settings.root_path }}/css_dist/main.css">
|
||||
</head>
|
||||
<body>
|
||||
{% block main %}{% endblock main %}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
<script type="module" src="{{ settings.root_path }}/js_dist/lens-controls.mjs"></script>
|
||||
<script type="module" src="{{ settings.root_path }}/js_dist/viewer-components.mjs"></script>
|
||||
<script type="module" src="{{ settings.root_path }}/js_dist/viewer-controller.mjs"></script>
|
||||
<script type="module" src="{{ settings.root_path }}/glm_dist/field_adder.mjs"></script>
|
||||
<link rel="stylesheet" href="{{ settings.root_path }}/viewer.css">
|
||||
<table class="viewer">
|
||||
<thead>
|
||||
|
|
@ -14,8 +15,8 @@
|
|||
<div class="padded-cell">{{ field.label.clone().unwrap_or(field.name.clone()) }}</div>
|
||||
</th>
|
||||
{% endfor %}
|
||||
<th>
|
||||
<add-selection-button columns="{{ all_columns | json }}"></add-selection-button>
|
||||
<th class="column-adder">
|
||||
<field-adder root-path="{{ settings.root_path }}" columns="{{ all_columns | json }}"></field-adder>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
|
|
|||
13
mise.toml
13
mise.toml
|
|
@ -1,8 +1,12 @@
|
|||
[tools]
|
||||
deno = "latest"
|
||||
erlang = "latest"
|
||||
gleam = "latest"
|
||||
jujutsu = "latest"
|
||||
rebar = "latest"
|
||||
rust = { version = "1.88.0", components = "rust-analyzer,clippy" }
|
||||
watchexec = "latest"
|
||||
"github:sass/dart-sass" = "1.89.2"
|
||||
|
||||
[tasks.postgres]
|
||||
run = "docker run --rm -it -e POSTGRES_PASSWORD=guest -v './pgdata:/var/lib/postgresql/data' -p 127.0.0.1:5432:5432 postgres:17"
|
||||
|
|
@ -17,6 +21,15 @@ run = "deno task build"
|
|||
dir = "./components"
|
||||
sources = ["**/*.ts"]
|
||||
|
||||
[tasks.lustre]
|
||||
run = "gleam run -m lustre/dev build component field_adder --outdir=../glm_dist"
|
||||
dir = "./glm"
|
||||
sources = ["**/*.gleam"]
|
||||
|
||||
[tasks.sass]
|
||||
run = "sass sass/:css_dist/"
|
||||
sources = ["sass/**/*.scss"]
|
||||
|
||||
[env]
|
||||
RUST_LOG = "debug"
|
||||
RUST_BACKTRACE = "1"
|
||||
|
|
|
|||
57
sass/_forms.scss
Normal file
57
sass/_forms.scss
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
@use 'globals';
|
||||
|
||||
$section-gap: 1.5rem;
|
||||
$label-gap: 0.5rem;
|
||||
$button-gap: 0.25rem;
|
||||
|
||||
.form-section {
|
||||
&__heading {
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
&__label {
|
||||
display: block;
|
||||
font-weight: 600;
|
||||
margin-top: $section-gap;
|
||||
}
|
||||
|
||||
&__input {
|
||||
display: block;
|
||||
margin-top: $label-gap;
|
||||
font-family: globals.$font-family-data;
|
||||
|
||||
&--text {
|
||||
@include globals.rounded;
|
||||
border: globals.$default-border;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-buttons {
|
||||
display: flex;
|
||||
margin-top: $section-gap;
|
||||
justify-content: flex-end;
|
||||
|
||||
&__button {
|
||||
margin: 0 $button-gap;
|
||||
|
||||
&:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
&--cancel {
|
||||
@include globals.button-clear;
|
||||
}
|
||||
|
||||
&--submit {
|
||||
@include globals.button-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
54
sass/_globals.scss
Normal file
54
sass/_globals.scss
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
@use 'sass:color';
|
||||
|
||||
$button-primary-background: #07f;
|
||||
$button-primary-color: #fff;
|
||||
$default-border: solid 1px #ccc;
|
||||
$font-family-default: 'Averia Serif Libre', 'Open Sans', 'Helvetica Neue', Arial, sans-serif;
|
||||
$font-family-data: 'Funnel Sans', 'Open Sans', 'Helvetica Neue', Arial, sans-serif;
|
||||
$popover-border: $default-border;
|
||||
$popover-shadow: 0 0.5rem 0.5rem #3333;
|
||||
|
||||
@mixin reset-button {
|
||||
appearance: none;
|
||||
background: none;
|
||||
border: none;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
font-weight: inherit;
|
||||
}
|
||||
|
||||
@mixin button-base {
|
||||
@include reset-button;
|
||||
@include rounded;
|
||||
font-family: $font-family-default;
|
||||
padding: 0.5rem 1rem;
|
||||
transition: background 0.2s ease;
|
||||
}
|
||||
|
||||
@mixin button-primary {
|
||||
@include button-base;
|
||||
background: $button-primary-background;
|
||||
color: $button-primary-color;
|
||||
|
||||
&:hover {
|
||||
background: color.scale($button-primary-background, $lightness: -10%, $space: oklch);
|
||||
}
|
||||
}
|
||||
|
||||
@mixin button-clear {
|
||||
@include button-base;
|
||||
|
||||
&:hover {
|
||||
background: #0002;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin rounded-sm {
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
@mixin rounded {
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
16
sass/_viewer-shared.scss
Normal file
16
sass/_viewer-shared.scss
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
@use 'globals';
|
||||
|
||||
@mixin th {
|
||||
border: globals.$default-border;
|
||||
border-top: none;
|
||||
font-family: 'Funnel Sans';
|
||||
font-weight: bolder;
|
||||
background: #0001;
|
||||
height: 100%; /* css hack to make percentage based cell heights work */
|
||||
padding: 0.25rem 0.5rem;
|
||||
text-align: left;
|
||||
|
||||
&:first-child {
|
||||
border-left: none;
|
||||
}
|
||||
}
|
||||
48
sass/field_adder/_styles.scss
Normal file
48
sass/field_adder/_styles.scss
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
@use '../globals';
|
||||
@use '../viewer-shared';
|
||||
|
||||
field-adder {
|
||||
height: 100%;
|
||||
|
||||
&::part(expander) {
|
||||
@include globals.reset-button;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&::part(th-lookalike) {
|
||||
@include viewer-shared.th;
|
||||
border-right-style: dashed;
|
||||
border-bottom-style: dashed;
|
||||
border-left: none;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
&::part(label-input) {
|
||||
appearance: none;
|
||||
background: none;
|
||||
border: none;
|
||||
outline: none;
|
||||
font-weight: inherit;
|
||||
}
|
||||
|
||||
&::part(config-popover) {
|
||||
@include globals.rounded-sm;
|
||||
position: fixed;
|
||||
inset: unset;
|
||||
margin: 0;
|
||||
width: 20rem;
|
||||
padding: 1rem;
|
||||
font-weight: normal;
|
||||
border: globals.$popover-border;
|
||||
filter: drop-shadow(globals.$popover-shadow);
|
||||
|
||||
&:popover-open {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
&::part(form-section-header) {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
48
sass/field_adder/index.scss
Normal file
48
sass/field_adder/index.scss
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
@use '../globals';
|
||||
@use '../forms';
|
||||
@use '../viewer-shared';
|
||||
|
||||
:host {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.expander__button {
|
||||
@include globals.reset-button;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.header {
|
||||
@include viewer-shared.th;
|
||||
border-right-style: dashed;
|
||||
border-bottom-style: dashed;
|
||||
border-left: none;
|
||||
display: flex;
|
||||
|
||||
&__input {
|
||||
appearance: none;
|
||||
background: none;
|
||||
border: none;
|
||||
outline: none;
|
||||
font-weight: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.config-popover {
|
||||
&__container {
|
||||
@include globals.rounded;
|
||||
font-family: globals.$font-family-default;
|
||||
position: fixed;
|
||||
inset: unset;
|
||||
margin: 0;
|
||||
width: 20rem;
|
||||
padding: 1rem;
|
||||
font-weight: normal;
|
||||
border: globals.$popover-border;
|
||||
filter: drop-shadow(globals.$popover-shadow);
|
||||
|
||||
&:popover-open {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
1
sass/main.scss
Normal file
1
sass/main.scss
Normal file
|
|
@ -0,0 +1 @@
|
|||
|
||||
18
static/css/field_adder.css
Normal file
18
static/css/field_adder.css
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
field-adder {
|
||||
--popover-border: solid 1px #ccc;
|
||||
--popover-shadow: 0 0.5rem 0.5rem #3333;
|
||||
|
||||
height: 100%;
|
||||
|
||||
& button.expander {
|
||||
appearance: none;
|
||||
border: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-weight: inherit;
|
||||
font-size: inherit;
|
||||
font-family: inherit;
|
||||
cursor: pointer;
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
|
|
@ -11,6 +11,18 @@ button, input[type="submit"] {
|
|||
src: url("./averia_serif_libre/averia_serif_libre_regular.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Averia Serif Libre";
|
||||
src: url("./averia_serif_libre/averia_serif_libre_bold.ttf");
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Averia Serif Libre";
|
||||
src: url("./averia_serif_libre/averia_serif_libre_light.ttf");
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Funnel Sans";
|
||||
src: url("./funnel_sans/funnel_sans_variable.ttf");
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
table.viewer {
|
||||
border-collapse: collapse;
|
||||
height: 1px; /* css hack to make percentage based cell heights work */
|
||||
}
|
||||
|
||||
table.viewer > thead > tr > th {
|
||||
|
|
@ -7,10 +8,19 @@ table.viewer > thead > tr > th {
|
|||
border-top: none;
|
||||
font-family: "Funnel Sans";
|
||||
background: #0001;
|
||||
height: 100%; /* css hack to make percentage based cell heights work */
|
||||
padding: 0 0.5rem;
|
||||
text-align: left;
|
||||
|
||||
&:first-child {
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
&.column-adder {
|
||||
border: none;
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
table.viewer .padded-cell {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue