From a41a4dc6e6ba8d290ad961c6e957af196b9e5bc4 Mon Sep 17 00:00:00 2001 From: set Date: Wed, 18 Sep 2024 16:32:25 +0200 Subject: [PATCH] Implemented a units settings page --- listbox.css | 75 ++++++ settings.css | 22 ++ src/components/listbox.tsx | 86 +++++++ src/components/setting.tsx | 65 +++++ src/formula.tsx | 20 +- src/main.ts | 29 +-- src/settingsTab.tsx | 208 ++++++++++++++-- src/spreadsheet.tsx | 469 ++++++------------------------------- src/table.tsx | 8 +- src/viewport.tsx | 396 +++++++++++++++++++++++++++++++ styles.css | 33 ++- 11 files changed, 963 insertions(+), 448 deletions(-) create mode 100644 listbox.css create mode 100644 settings.css create mode 100644 src/components/listbox.tsx create mode 100644 src/components/setting.tsx create mode 100644 src/viewport.tsx diff --git a/listbox.css b/listbox.css new file mode 100644 index 0000000..b828e4f --- /dev/null +++ b/listbox.css @@ -0,0 +1,75 @@ +.list-box { + -webkit-app-region: no-drag; + background: var(--background-modifier-form-field); + border: var(--input-border-width) solid var(--background-modifier-border); + color: var(--text-normal); + font-family: inherit; + + font-size: var(--font-ui-small); + border-radius: var(--input-radius); + outline: none; + + overflow: hidden; +} + +.list-item { + display: flex; + margin: 0 0; + padding: var(--size-2-1) var(--size-4-2); + font-size: var(--font-ui-smaller); + white-space: nowrap; + word-break: keep-all; + + align-items: center; + gap: var(--size-2-1); +} + +.list-item:focus, .list-item.active { + background: var(--text-selection); +} + +.list-box:focus .list-item:focus, .list-box:focus .list-item.active { + background: hsl(var(--accent-h), var(--accent-s), var(--accent-l)); + color: var(--text-on-accent); +} + +.list-box-viewport { + flex: 1; +} + +.icon-button:hover { + background: var(--text-selection); +} + +textarea.command-editor { + resize: vertical; + width: 100%; + font-family: var(--font-monospace-theme), monospace; +} + +.button-group { + display: flex; + flex-direction: row; + padding: 0 var(--size-4-2); + align-items: center; + width: 100%; + background: var(--color-base-20); +} + +.button-group .icon-button { + background: none; + border: none; + box-shadow: none; + line-height: 1em; + /*padding: var(--size-2-2);*/ +} + +.flex { + display: flex; + flex-direction: row; + gap: var(--size-2-2); +} + +.fill { + flex: 1; +} \ No newline at end of file diff --git a/settings.css b/settings.css new file mode 100644 index 0000000..2a03933 --- /dev/null +++ b/settings.css @@ -0,0 +1,22 @@ +.spreadsheet-settings { + display: grid; + grid-template-columns: auto 1fr; + + grid-template-areas: + 'datatypes editor'; + + gap: var(--size-2-2); +} + +.spreadsheet-settings > .list-box { + grid-area: datatypes; + resize: horizontal; +} + +.spreadsheet-settings > .editor { + grid-area: editor; +} + +input.monospace { + font-family: var(--font-monospace-theme); +} \ No newline at end of file diff --git a/src/components/listbox.tsx b/src/components/listbox.tsx new file mode 100644 index 0000000..af15d66 --- /dev/null +++ b/src/components/listbox.tsx @@ -0,0 +1,86 @@ +import React from "react"; +import * as lucide from "lucide-react"; + +export interface Controls { + onAdd?: (e: React.MouseEvent) => void, + onDelete?: (item: number) => void, + onSwap?: (item: number, item2: number) => boolean, + onSelect?: (index: number) => void, +} + +export default function ListBox(props: { + children: React.ReactNode[], + controls: Controls +}) { + const [state, setState] = React.useState({ + index: 0, + refs: props.children?.map(_ => React.createRef()) ?? [] + }); + + const setSelection = (move: { rel: number } | { abs: number }) => { + const index = 'rel' in move ? (props.children.length + state.index + move.rel) % props.children.length : move.abs % props.children.length; + + setState(prev => ({ + ...prev, + index + })); + + props.controls.onSelect?.(index); + }; + + return
({ + "uparrow": e => setSelection({rel: -1}), + "downarrow": e => setSelection({rel: 1}), + } as Record void>)[e.key.toLowerCase() as string]?.(e)}> + +
+
+ {props.controls.onAdd ?
{ + props.controls?.onAdd!(e); + }}> + +
: null} + + {props.controls.onDelete ?
{ + props.controls?.onDelete!(state.index); + }}> + +
: null} + {props.controls.onSwap ? <> +
{ + if (props.controls?.onSwap!(state.index, state.index - 1)) + setSelection({rel: -1}) + }}> + +
+
{ + if (props.controls?.onSwap!(state.index, state.index + 1)) + setSelection({rel: 1}) + }}> + +
+ : null} +
+
+ + {props.children.map((item, a) =>
setSelection({abs: a})}> + {item} +
)} + +
+} diff --git a/src/components/setting.tsx b/src/components/setting.tsx new file mode 100644 index 0000000..98baf05 --- /dev/null +++ b/src/components/setting.tsx @@ -0,0 +1,65 @@ +import React from "react"; +import * as obs from "obsidian"; + +type types = Button | Line | Dropdown | Toggle; + +type Button = { buttonText: string, onClick: () => void }; +type Line = { + onChange: (value: string) => void, + lineFormat: RegExp, + value: string, + placeholder?: string +}; +type Dropdown = { + onChange: (value: string) => void, + options: Record, + value: string, +}; +type Toggle = { + onChange: (checked: boolean) => void, + checked: boolean, +}; + +export default function Setting(props: { title: string, description?: string } & types) { + const ref = React.createRef(); + + React.useEffect(() => { + const setting = new obs.Setting(ref.current!) + .setName(props.title) + .setDesc(props.description ?? ''); + + if ("buttonText" in props) + setting + .addButton(btn => btn + .setButtonText(props.buttonText) + .onClick(props.onClick)) + + else if ("lineFormat" in props) + setting + .addText(cb => { + cb.inputEl.pattern = props.lineFormat.source; + return cb + .setValue(props.value) + .setPlaceholder(props.placeholder ?? '') + .onChange(value => { + if (cb.inputEl.validity.valid) + props.onChange(value); + }); + }) + + else if ("options" in props) + setting + .addDropdown(dropdown => dropdown + .addOptions(props.options) + .setValue(props.value) + .onChange(value => props.onChange(value))); + + else if ("checked" in props) + setting + .addToggle(toggle => toggle + .setValue(props.checked) + .onChange(checked => props.onChange(checked))); + }, []); + + return
; +} diff --git a/src/formula.tsx b/src/formula.tsx index e03801f..d3d97ec 100644 --- a/src/formula.tsx +++ b/src/formula.tsx @@ -3,7 +3,7 @@ import * as chrono from 'chrono-node'; import * as luxon from 'luxon'; import * as obs from 'obsidian'; -import {Value} from "./spreadsheet.js"; +import {Value, Selection} from "./viewport.js"; import {DEFAULT_ROW_HEIGHT} from "./table.js"; export type ResizeState = { @@ -15,7 +15,7 @@ export type ResizeState = { prevMouseY: number, }; -export default function FormulaBar(props: { activeCell: Value }) { +export default function FormulaBar(props: { selection: Selection.CellGroup[] }) { const [resize, setResize] = React.useState({ isResizing: false, height: DEFAULT_ROW_HEIGHT @@ -49,7 +49,6 @@ export default function FormulaBar(props: { activeCell: Value }) { return
- {props.activeCell.renderer().formula(props.activeCell)}
(props: Props): React.ReactNode { React.useSyncExternalStore(props.onChange, () => props.getRaw()); - // const ref = React.createRef(); - // setTimeout(() => ref.current?.focus()); + const ref = React.createRef(); + + React.useEffect(() => { + ref.current?.focus(); + ref.current?.select(); + }, [props]); return