Skip to content

Commit

Permalink
Implemented a units settings page
Browse files Browse the repository at this point in the history
  • Loading branch information
set authored and J-Cake committed Sep 18, 2024
1 parent 36636bf commit a41a4dc
Show file tree
Hide file tree
Showing 11 changed files with 963 additions and 448 deletions.
75 changes: 75 additions & 0 deletions listbox.css
Original file line number Diff line number Diff line change
@@ -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;
}
22 changes: 22 additions & 0 deletions settings.css
Original file line number Diff line number Diff line change
@@ -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);
}
86 changes: 86 additions & 0 deletions src/components/listbox.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLDivElement>()) ?? []
});

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 <div
className="list-box"
tabIndex={0}
onKeyUp={e => ({
"uparrow": e => setSelection({rel: -1}),
"downarrow": e => setSelection({rel: 1}),
} as Record<string, (e: React.KeyboardEvent) => void>)[e.key.toLowerCase() as string]?.(e)}>

<div className={"list-box-controls"}>
<div className={"button-group"}>
{props.controls.onAdd ? <div className="icon-button"
tabIndex={0}
onClick={e => {
props.controls?.onAdd!(e);
}}>
<lucide.Plus size={14}/>
</div> : null}

{props.controls.onDelete ? <div className="icon-button"
tabIndex={0}
onClick={() => {
props.controls?.onDelete!(state.index);
}}>
<lucide.Minus size={14}/>
</div> : null}
{props.controls.onSwap ? <>
<div className="icon-button"
tabIndex={0}
onClick={() => {
if (props.controls?.onSwap!(state.index, state.index - 1))
setSelection({rel: -1})
}}>
<lucide.ChevronUp size={14}/>
</div>
<div className="icon-button"
tabIndex={0}
onClick={() => {
if (props.controls?.onSwap!(state.index, state.index + 1))
setSelection({rel: 1})
}}>
<lucide.ChevronDown size={14}/>
</div>
</> : null}
</div>
</div>

{props.children.map((item, a) => <div
key={`list-box-item-${a}`}
className={`list-item${state.index == a ? " active" : ""}`}
ref={state.refs[a]}
onClick={_ => setSelection({abs: a})}>
{item}
</div>)}

</div>
}
65 changes: 65 additions & 0 deletions src/components/setting.tsx
Original file line number Diff line number Diff line change
@@ -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<string, string>,
value: string,
};
type Toggle = {
onChange: (checked: boolean) => void,
checked: boolean,
};

export default function Setting(props: { title: string, description?: string } & types) {
const ref = React.createRef<HTMLDivElement>();

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 <div ref={ref}></div>;
}
20 changes: 14 additions & 6 deletions src/formula.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -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<ResizeState>({
isResizing: false,
height: DEFAULT_ROW_HEIGHT
Expand Down Expand Up @@ -49,7 +49,6 @@ export default function FormulaBar(props: { activeCell: Value }) {
return <div className={"formula"}
style={{height: `${resize.height}px`}}>
<div className={"custom-input"}>
{props.activeCell.renderer().formula(props.activeCell)}
</div>
<span
className={"resize-handle horizontal"}
Expand Down Expand Up @@ -78,11 +77,15 @@ export namespace renderers {
},
formula<Props extends Value>(props: Props): React.ReactNode {
React.useSyncExternalStore(props.onChange, () => props.getRaw());
// const ref = React.createRef<HTMLTextAreaElement>();
// setTimeout(() => ref.current?.focus());
const ref = React.createRef<HTMLTextAreaElement>();

React.useEffect(() => {
ref.current?.focus();
ref.current?.select();
}, [props]);

return <textarea
// ref={ref}
ref={ref}
autoFocus={true}
value={props.getRaw()}
onChange={e => props.setRaw(e.target.value)}/>
Expand All @@ -102,6 +105,11 @@ export namespace renderers {
{"Invalid Date"}
</span>

React.useEffect(() => {
// ref.current?.focus();
// ref.current?.select();
}, [props]);

const date = luxon.DateTime.fromJSDate(parsed);

return <span className={"raw"}>
Expand Down
29 changes: 7 additions & 22 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,21 @@
import * as path from 'node:path';
import * as obs from 'obsidian';

import Spreadsheet, {SPREADSHEET_VIEW} from "./spreadsheet.js";
import SettingsTab from "./settingsTab.js";
import Spreadsheet, {SPREADSHEET_VIEW} from "./viewport.js";
import SettingsTab, { default_settings, Settings } from "./settingsTab.js";

export default class SpreadsheetPlugin extends obs.Plugin {
settings: SettingsTab | null = null;
settingsTab: SettingsTab | null = null;
settings: Settings = default_settings;

async onload() {
this.registerView(SPREADSHEET_VIEW, leaf => new Spreadsheet(leaf));
this.registerExtensions(["csv", "tab"], SPREADSHEET_VIEW);

this.addSettingTab(this.settings = new SettingsTab(this.app, this));
this.addSettingTab(this.settingsTab = new SettingsTab(this.app, this));

this.addCommand({
id: "open-new-spreadsheet",
name: "New Spreadsheet",
callback: async () => {
// this.app.vault.create(obs.normalizePath(i));
// this.app.vault.getRoot
// return await this.app.workspace.getLeaf(true).setViewState({ type: SPREADSHEET_VIEW, active: true });
}
});
this.settings = await this.loadData()
.then(res => Object.assign({}, default_settings, res));

this.registerEvent(this.app.workspace.on("file-menu", (menu, file) => menu
.addItem(item => item
Expand All @@ -43,13 +37,4 @@ export default class SpreadsheetPlugin extends obs.Plugin {
}
}).commands.executeCommandById(command);
}

async loadSettings() {
this.settings?.load(await this.loadData());
// this.settings = Object.assign({}, default_settings, await this.loadData());
}

async saveSettings() {
await this.saveData(this.settings?.get());
}
}
Loading

0 comments on commit a41a4dc

Please sign in to comment.