From addc6bfb1117c3398c8811941dd6a1f7eb937075 Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Tue, 29 Oct 2024 16:20:48 +0100 Subject: [PATCH] fix issues with tabs --- public/manifest.json | 1 - src/app.css | 3 +- src/components/colorschemepicker.module.css | 22 ++++- src/components/colorschemepicker.tsx | 23 +++-- src/components/tabs.tsx | 102 +++++++++++--------- src/features/command/index.tsx | 45 +++------ src/features/file/index.tsx | 52 ++++++---- src/features/menu/index.tsx | 31 +++--- src/routes/(editor)/about.tsx | 10 +- src/routes/(editor)/edit.module.css | 9 ++ src/routes/(editor)/edit.tsx | 29 +++--- src/routes/(editor)/index.module.css | 9 ++ src/routes/(editor)/index.tsx | 4 +- src/routes/(editor)/instructions.tsx | 7 ++ 14 files changed, 210 insertions(+), 137 deletions(-) create mode 100644 src/routes/(editor)/instructions.tsx diff --git a/public/manifest.json b/public/manifest.json index 491b2ca..39b90c9 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -10,7 +10,6 @@ } ], "start_url": ".", - "display": "standalone", "display_override": [ "window-controls-overlay" ], diff --git a/src/app.css b/src/app.css index 4ece343..df7ff87 100644 --- a/src/app.css +++ b/src/app.css @@ -49,6 +49,7 @@ body { grid: 100% / 100%; background-color: var(--surface-1); + color: var(--text-2); margin: 0; @@ -68,7 +69,7 @@ a { } h1 { - color: var(--primary); + color: var(--text-1); text-transform: uppercase; font-size: 4rem; font-weight: 100; diff --git a/src/components/colorschemepicker.module.css b/src/components/colorschemepicker.module.css index f9af56f..0a64665 100644 --- a/src/components/colorschemepicker.module.css +++ b/src/components/colorschemepicker.module.css @@ -1,4 +1,22 @@ .picker { - border: none; - background-color: var(--surface-3); + display: flex; + flex-flow: row; + align-items: center; + border: 1px solid transparent; + border-radius: var(--radii-m); + padding: var(--padding-s); + + & select { + border: none; + background-color: var(--surface-3); + border-radius: var(--radii-m); + + &:focus { + outline: none; + } + } + + &:has(:focus-visible) { + border-color: var(--info); + } } \ No newline at end of file diff --git a/src/components/colorschemepicker.tsx b/src/components/colorschemepicker.tsx index 66ff9ee..6075a53 100644 --- a/src/components/colorschemepicker.tsx +++ b/src/components/colorschemepicker.tsx @@ -1,5 +1,6 @@ import { Accessor, Component, createEffect, createSignal, For, Setter } from "solid-js"; import css from './colorschemepicker.module.css'; +import { CgDarkMode } from "solid-icons/cg"; export enum ColorScheme { Auto = 'light dark', @@ -31,13 +32,17 @@ export const ColorSchemePicker: Component = (props) => { setter(currentValue); }); - return ; + return ; }; \ No newline at end of file diff --git a/src/components/tabs.tsx b/src/components/tabs.tsx index f27601a..1b0c75e 100644 --- a/src/components/tabs.tsx +++ b/src/components/tabs.tsx @@ -1,19 +1,14 @@ -import { Accessor, children, createContext, createEffect, createMemo, createSignal, For, onCleanup, ParentComponent, Setter, Show, useContext } from "solid-js"; +import { Accessor, children, createContext, createEffect, createMemo, createSignal, For, JSX, onCleanup, ParentComponent, Setter, Show, useContext } from "solid-js"; import { IoCloseCircleOutline } from "solid-icons/io"; import css from "./tabs.module.css"; -import { Command, CommandType, commandArguments, noop, useCommands } from "~/features/command"; - -commandArguments; +import { Command, CommandType, noop, useCommands } from "~/features/command"; interface TabsContextType { - register(id: string, label: string, options?: Partial): Accessor; + activate(id: string | undefined): void; + isActive(id: string): Accessor; readonly onClose: Accessor | undefined> } -interface TabOptions { - closable: boolean; -} - const TabsContext = createContext(); const useTabs = () => { @@ -29,29 +24,39 @@ const useTabs = () => { export const Tabs: ParentComponent<{ active?: Setter, onClose?: CommandType<[string]> }> = (props) => { const commandsContext = useCommands(); const [active, setActive] = createSignal(undefined); - const [tabs, setTabs] = createSignal }>>(new Map()); createEffect(() => { props.active?.(active()); }); - createEffect(() => { - setActive(tabs().keys().toArray().at(-1)); - }); - const ctx = { - register(id: string, label: string, options: Partial) { - setTabs(tabs => { - tabs.set(id, { label, options }); - - return new Map(tabs); - }); + activate(id: string) { + setActive(id); + }, + isActive(id: string) { return createMemo(() => active() === id); }, + onClose: createMemo(() => props.onClose), }; + return + <_Tabs active={active()} onClose={props.onClose}>{props.children} + ; +} + +const _Tabs: ParentComponent<{ active: string | undefined, onClose?: CommandType<[string]> }> = (props) => { + const commandsContext = useCommands(); + const tabsContext = useTabs(); + + const resolved = children(() => props.children); + const tabs = createMemo(() => resolved.toArray().filter(c => c instanceof HTMLElement).map(({ id, dataset }, i) => ({ id, label: dataset.tabLabel, options: { closable: dataset.tabClosable } }))); + + createEffect(() => { + tabsContext.activate(tabs().at(-1)?.id); + }); + const onClose = (e: Event) => { if (!commandsContext || !props.onClose) { return; @@ -60,33 +65,44 @@ export const Tabs: ParentComponent<{ active?: Setter, onClos return commandsContext.execute(props.onClose, e); }; - return -
-
- { - ([id, { label, options: { closable = false } }]) => - - - - - - - - } -
+ return
+
+ { + ({ id, label, options: { closable = false } }) => + + + + + + + + } +
- {props.children} -
- ; -} + {resolved()} +
; +}; export const Tab: ParentComponent<{ id: string, label: string, closable?: boolean }> = (props) => { const context = useTabs(); - - const isActive = context.register(props.id, props.label, { - closable: props.closable ?? false - }); const resolved = children(() => props.children); + const isActive = context.isActive(props.id); + const [ref, setRef] = createSignal(); - return {resolved()}; + // const isActive = context.register(props.id, props.label, { + // closable: props.closable ?? false, + // ref: ref, + // }); + + return
+ + {resolved()} + +
; } \ No newline at end of file diff --git a/src/features/command/index.tsx b/src/features/command/index.tsx index 7b5446f..fae37c0 100644 --- a/src/features/command/index.tsx +++ b/src/features/command/index.tsx @@ -31,24 +31,28 @@ const Root: ParentComponent<{ commands: CommandType[] }> = (props) => { }, execute(command: CommandType, event: Event): boolean | undefined { - const contexts = contextualArguments.get(command); + const args = ((): T => { - if (contexts === undefined) { - return; - } + const contexts = contextualArguments.get(command); - const element = event.composedPath().find(el => contexts.has(el)); + if (contexts === undefined) { + return [] as any; + } - if (element === undefined) { - return; - } + const element = event.composedPath().find(el => contexts.has(el)); + + if (element === undefined) { + return [] as any; + } + + const args = contexts.get(element)! as Accessor; + return args(); + })(); event.preventDefault(); event.stopPropagation(); - const args = contexts.get(element)! as Accessor; - - command(...args()); + command(...args); return false; }, @@ -159,17 +163,6 @@ export const createCommand = (label: string, command: }); }; -export const commandArguments = (element: Element, commandAndArgs: Accessor<[CommandType, T]>) => { - const ctx = useContext(CommandContext); - const args = createMemo(() => commandAndArgs()[1]); - - if (!ctx) { - return; - } - - ctx.addContextualArguments(commandAndArgs()[0], element, args); -} - export const noop = Object.defineProperties(createCommand('noop', () => { }), { withLabel: { value(label: string) { @@ -180,12 +173,4 @@ export const noop = Object.defineProperties(createCommand('noop', () => { }), { }, }) as CommandType & { withLabel(label: string): CommandType }; -declare module "solid-js" { - namespace JSX { - interface Directives { - commandArguments(): [CommandType, T]; - } - } -} - export { Context } from './contextMenu'; \ No newline at end of file diff --git a/src/features/file/index.tsx b/src/features/file/index.tsx index 7110a85..fb72c7f 100644 --- a/src/features/file/index.tsx +++ b/src/features/file/index.tsx @@ -4,6 +4,8 @@ import { createStore } from "solid-js/store"; import { isServer } from "solid-js/web"; import * as json from './parser/json'; +const ROOT = '__root__'; + interface FileEntity { key: string; handle: FileSystemDirectoryHandle; @@ -25,7 +27,9 @@ interface InternalFilesContextType { interface FilesContextType { readonly files: Accessor, + readonly root: Accessor, + open(directory: FileSystemDirectoryHandle): void; get(key: string): Accessor set(key: string, handle: FileSystemDirectoryHandle): Promise; remove(key: string): Promise; @@ -42,10 +46,16 @@ const clientContext = (): InternalFilesContextType => { return { onChange(hook: (key: string, handle: FileSystemDirectoryHandle) => any) { - const callHook = (key: string, handle: FileSystemDirectoryHandle) => setTimeout(() => hook(key, handle), 1); + const callHook = (key: string, handle: FileSystemDirectoryHandle) => { + if (!key || key === ROOT) { + return; + } + + setTimeout(() => hook(key, handle), 1); + }; db.files.hook('creating', (_: string, { key, handle }: FileEntity) => { callHook(key, handle); }); - db.files.hook('deleting', (_: string, { key, handle }: FileEntity) => callHook(key, handle)); + db.files.hook('deleting', (_: string, { key, handle }: FileEntity = { key: undefined!, handle: undefined! }) => callHook(key, handle)); db.files.hook('updating', (_1: Object, _2: string, { key, handle }: FileEntity) => callHook(key, handle)); }, @@ -59,13 +69,13 @@ const clientContext = (): InternalFilesContextType => { return (await db.files.delete(key)); }, async keys() { - return (await db.files.toArray()).map(f => f.key); + return (await db.files.where('key').notEqual(ROOT).toArray()).map(f => f.key); }, async entries() { - return await db.files.toArray(); + return await db.files.where('key').notEqual(ROOT).toArray(); }, async list() { - const files = await db.files.toArray(); + const files = await db.files.where('key').notEqual(ROOT).toArray(); return files.map(f => f.handle) }, @@ -99,27 +109,35 @@ const serverContext = (): InternalFilesContextType => ({ export const FilesProvider: ParentComponent = (props) => { const internal = isServer ? serverContext() : clientContext(); - const [state, setState] = createStore<{ files: FileEntity[] }>({ files: [] }); + const [state, setState] = createStore<{ openedFiles: FileEntity[], root: FileSystemDirectoryHandle | undefined }>({ openedFiles: [], root: undefined }); - const updateFilesInState = async () => { - const entities = await internal.entries(); - - setState('files', entities); - }; - - internal.onChange((key: string, handle: FileSystemDirectoryHandle) => { - updateFilesInState(); + internal.onChange(async () => { + setState('openedFiles', await internal.entries()); }); onMount(() => { - updateFilesInState(); + (async () => { + const [root, files] = await Promise.all([ + internal.get(ROOT), + internal.entries(), + ]); + + setState('root', root); + setState('openedFiles', files); + })(); }); const context: FilesContextType = { - files: createMemo(() => state.files), + files: createMemo(() => state.openedFiles), + root: createMemo(() => state.root), + + open(directory: FileSystemDirectoryHandle) { + setState('root', directory); + internal.set(ROOT, directory); + }, get(key: string): Accessor { - return createMemo(() => state.files.find(entity => entity.key === key)?.handle); + return createMemo(() => state.openedFiles.find(entity => entity.key === key)?.handle); }, async set(key: string, handle: FileSystemDirectoryHandle) { diff --git a/src/features/menu/index.tsx b/src/features/menu/index.tsx index 2713998..a5cb21b 100644 --- a/src/features/menu/index.tsx +++ b/src/features/menu/index.tsx @@ -1,7 +1,7 @@ import { Accessor, Component, For, JSX, Match, ParentComponent, Setter, Show, Switch, children, createContext, createEffect, createMemo, createSignal, createUniqueId, mergeProps, onCleanup, onMount, useContext } from "solid-js"; import { Portal } from "solid-js/web"; import { createStore } from "solid-js/store"; -import { CommandType, Command } from "../command"; +import { CommandType, Command, useCommands } from "../command"; import css from "./index.module.css"; export interface MenuContextType { @@ -35,7 +35,7 @@ const MenuContext = createContext(); export const MenuProvider: ParentComponent<{ commands?: CommandType[] }> = (props) => { const [ref, setRef] = createSignal(); - const [store, setStore] = createStore<{ items: Record }>({ items: {} }); + const [store, setStore] = createStore<{ items: Map }>({ items: new Map }); const ctx = { ref, @@ -43,19 +43,19 @@ export const MenuProvider: ParentComponent<{ commands?: CommandType[] }> = (prop addItems(items: (Item | ItemWithChildren)[]) { return setStore('items', values => { for (const item of items) { - values[item.id] = item; + values.set(item.id, item); } - return values; + return new Map(values.entries()); }) }, items() { - return Object.values(store.items); + return store.items.values(); }, commands() { - return Object.values(store.items) - .map(item => item.kind === 'node' ? item.children.filter(c => c.kind === 'leaf').map(c => c.command) : item.command) - .flat() + return store.items.values() + .flatMap(item => item.kind === 'node' ? item.children.filter(c => c.kind === 'leaf').map(c => c.command) : [item.command]) + .toArray() .concat(props.commands ?? []); }, }; @@ -100,11 +100,12 @@ const Separator: Component = (props) => { } const Root: ParentComponent<{}> = (props) => { - const menu = useMenu(); + const menuContext = useMenu(); + const commandContext = useCommands(); const [current, setCurrent] = createSignal(); const items = children(() => props.children).toArray() as unknown as (Item | ItemWithChildren)[]; - menu.addItems(items) + menuContext.addItems(items) const close = () => { const el = current(); @@ -118,10 +119,10 @@ const Root: ParentComponent<{}> = (props) => { const onExecute = (command?: CommandType) => { return command - ? async () => { - await command?.(); - + ? (e: Event) => { close(); + + return commandContext?.execute(command, e); } : () => { } }; @@ -132,7 +133,7 @@ const Root: ParentComponent<{}> = (props) => { }; - return + return { item => { @@ -349,4 +350,4 @@ declare module "solid-js" { anchor?: string | undefined; } } -} \ No newline at end of file +} diff --git a/src/routes/(editor)/about.tsx b/src/routes/(editor)/about.tsx index 8371d91..afae184 100644 --- a/src/routes/(editor)/about.tsx +++ b/src/routes/(editor)/about.tsx @@ -1,10 +1,8 @@ import { Title } from "@solidjs/meta"; export default function Home() { - return ( -
- About -

About

-
- ); + return <> + About +

About

+ ; } diff --git a/src/routes/(editor)/edit.module.css b/src/routes/(editor)/edit.module.css index 990ff55..2bcab2b 100644 --- a/src/routes/(editor)/edit.module.css +++ b/src/routes/(editor)/edit.module.css @@ -23,4 +23,13 @@ content: ' •'; } } +} + +.blank { + display: grid; + grid: 100% / 100%; + inline-size: 100%; + block-size: 100%; + + place-items: center; } \ No newline at end of file diff --git a/src/routes/(editor)/edit.tsx b/src/routes/(editor)/edit.tsx index b671979..4de5c0a 100644 --- a/src/routes/(editor)/edit.tsx +++ b/src/routes/(editor)/edit.tsx @@ -4,7 +4,7 @@ import { Sidebar } from "~/components/sidebar"; import { emptyFolder, FolderEntry, walk as fileTreeWalk, Tree } from "~/components/filetree"; import { Menu } from "~/features/menu"; import { Grid, load, useFiles } from "~/features/file"; -import { Command, Context, createCommand, Modifier, noop, useCommands } from "~/features/command"; +import { Command, CommandType, Context, createCommand, Modifier, noop, useCommands } from "~/features/command"; import { GridApi } from "~/features/file/grid"; import { Tab, Tabs } from "~/components/tabs"; import css from "./edit.module.css"; @@ -35,21 +35,20 @@ async function* walk(directory: FileSystemDirectoryHandle, path: string[] = []): } }; -const open = createCommand('open folder', async () => { - const directory = await window.showDirectoryPicker({ mode: 'readwrite' }); - - useFiles().set('__root__', directory); -}, { key: 'o', modifier: Modifier.Control }); interface Entries extends Map> { } export default function Edit(props: ParentProps) { const filesContext = useFiles(); - const root = filesContext.get('__root__'); + const open = createCommand('open folder', async () => { + const directory = await window.showDirectoryPicker({ mode: 'readwrite' }); + + filesContext.open(directory); + }, { key: 'o', modifier: Modifier.Control }); return - open()}>open a folder}>{ + }>{ root => } ; @@ -58,11 +57,11 @@ export default function Edit(props: ParentProps) { const Editor: Component<{ root: FileSystemDirectoryHandle }> = (props) => { const filesContext = useFiles(); - const tabs = createMemo(() => filesContext.files().map(({ handle }) => { + const tabs = createMemo(() => filesContext.files().map(({ key, handle }) => { const [api, setApi] = createSignal(); const [entries, setEntries] = createSignal(new Map()); - return ({ handle, api, setApi, entries, setEntries }); + return ({ key, handle, api, setApi, entries, setEntries }); })); const [active, setActive] = createSignal(); const [contents, setContents] = createSignal>>(new Map()); @@ -247,8 +246,8 @@ const Editor: Component<{ root: FileSystemDirectoryHandle }> = (props) => { { - ({ handle, setApi, setEntries }) => @@ -314,4 +313,10 @@ const Content: Component<{ directory: FileSystemDirectoryHandle, api?: Setter; +}; + +const Blank: Component<{ open: CommandType }> = (props) => { + return
+ +
}; \ No newline at end of file diff --git a/src/routes/(editor)/index.module.css b/src/routes/(editor)/index.module.css index 63f3244..0a4b674 100644 --- a/src/routes/(editor)/index.module.css +++ b/src/routes/(editor)/index.module.css @@ -1,4 +1,13 @@ .main { display: grid; place-content: center; + gap: var(--padding-m); + + ul { + display: flex; + flex-flow: column; + gap: var(--padding-s); + padding-inline-start: var(--padding-l); + margin: 0; + } } \ No newline at end of file diff --git a/src/routes/(editor)/index.tsx b/src/routes/(editor)/index.tsx index a7995ca..4a8679a 100644 --- a/src/routes/(editor)/index.tsx +++ b/src/routes/(editor)/index.tsx @@ -46,7 +46,9 @@ export default function Index() { ); diff --git a/src/routes/(editor)/instructions.tsx b/src/routes/(editor)/instructions.tsx new file mode 100644 index 0000000..d542828 --- /dev/null +++ b/src/routes/(editor)/instructions.tsx @@ -0,0 +1,7 @@ + + +export default function Instructions() { + + + return <>; +}; \ No newline at end of file