diff --git a/bun.lockb b/bun.lockb index a01ed5a..9f3d804 100644 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 93a1ba6..4138368 100644 --- a/package.json +++ b/package.json @@ -1,20 +1,25 @@ { "name": "example-basic", - "type": "module", + "dependencies": { + "@solidjs/meta": "^0.29.4", + "@solidjs/router": "^0.14.7", + "@solidjs/start": "^1.0.8", + "dexie": "^4.0.8", + "solid-icons": "^1.1.0", + "solid-js": "^1.9.1", + "vinxi": "^0.4.1" + }, + "engines": { + "node": ">=18" + }, "scripts": { "dev": "vinxi dev", "build": "vinxi build", "start": "vinxi start", "version": "vinxi version" }, - "dependencies": { - "@solidjs/meta": "^0.29.4", - "@solidjs/router": "^0.14.1", - "@solidjs/start": "^1.0.6", - "solid-js": "^1.8.18", - "vinxi": "^0.4.1" - }, - "engines": { - "node": ">=18" + "type": "module", + "devDependencies": { + "@types/wicg-file-system-access": "^2023.10.5" } } diff --git a/src/app.css b/src/app.css index 8596998..2789e4a 100644 --- a/src/app.css +++ b/src/app.css @@ -1,17 +1,125 @@ +html { + inline-size: 100%; + block-size: 100%; + overflow: clip; +} + body { + inline-size: 100%; + block-size: 100%; + overflow: clip; + + margin: 0; + font-family: Gordita, Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; + + * { + box-sizing: border-box; + } + + & .menu-root { + display: grid; + grid-auto-flow: column; + justify-content: start; + position: relative; + + gap: .5em; + padding-inline-start: 1em; + block-size: 2em; + + background-color: #eee; + + & > .logo { + inline-size: 3em; + block-size: 3em; + padding: .75em; + margin-block-end: -1em; + background-color: inherit; + border-radius: .25em; + } + + & > div { + display: contents; + } + + .menu-item { + padding: .5em 1em; + + border: none; + background-color: #eee; + cursor: pointer; + + text-align: start; + + &:hover { + background-color: #fff; + } + } + + .menu-child { + position: fixed; + inset-inline-start: anchor(self-start); + inset-block-start: anchor(end); + + grid-auto-flow: row; + place-content: start; + + gap: .5em; + padding: .5em 0; + inline-size: max-content; + + background-color: #fff; + border: 1px solid #eee; + border-block-start-width: 0; + margin: unset; + + &:popover-open { + display: grid; + } + + & > .menu-item { + background-color: #fff; + + &:hover { + background-color: #f8f8f8; + } + } + } + + :popover-open + .menu-item { + background-color: #fff; + } + } + + & > main { + block-size: 100%; + + padding: 1em; + padding-block-start: 1.5em; + + overflow: clip auto; + + & details { + & > summary::marker { + content: url('data:image/svg+xml;charset=UTF-8,'); + } + + &[open] > summary::marker { + content: url('data:image/svg+xml;charset=UTF-8,'); + } + + & span { + cursor: pointer; + } + } + + } } a { margin-right: 1rem; } -main { - text-align: center; - padding: 1em; - margin: 0 auto; -} - h1 { color: #335d92; text-transform: uppercase; diff --git a/src/features/file/index.tsx b/src/features/file/index.tsx index 50a54ea..37b95f6 100644 --- a/src/features/file/index.tsx +++ b/src/features/file/index.tsx @@ -1,12 +1,67 @@ -import { createContext, useContext } from "solid-js"; +import Dexie, { EntityTable } from "dexie"; +import { Component, createContext, useContext } from "solid-js"; +import { isServer } from "solid-js/web"; -const FilesContext = createContext(); +type Handle = FileSystemFileHandle|FileSystemDirectoryHandle; -export const FilesProvider = (props) => { - return {props.children}; +interface File { + name: string; + handle: Handle; } -export const useFiles = () => useContext(FilesContext); +type Store = Dexie & { + files: EntityTable; +}; + +interface FilesContextType { + set(name: string, handle: Handle): Promise; + get(name: string): Promise; + list(): Promise; +} + +const FilesContext = createContext(); + +const clientContext = (): FilesContextType => { + const db = new Dexie('Files') as Store; + + db.version(1).stores({ + files: 'name, handle' + }); + + return { + async set(name: string, handle: Handle) { + await db.files.put({ name, handle }); + }, + async get(name: string) { + return (await db.files.get(name))?.handle; + }, + async list() { + const files = await db.files.toArray(); + + return files.map(f => f.handle) + }, + } +}; + +const serverContext = (): FilesContextType => ({ + set(){ + return Promise.resolve(); + }, + get(name: string){ + return Promise.resolve(undefined); + }, + list(){ + return Promise.resolve([]); + }, +}); + +export const FilesProvider = (props) => { + const ctx = isServer ? serverContext() : clientContext(); + + return {props.children}; +} + +export const useFiles = () => useContext(FilesContext)!; export const open = () => { diff --git a/src/features/menu/index.tsx b/src/features/menu/index.tsx index 12dfccf..a5b7a12 100644 --- a/src/features/menu/index.tsx +++ b/src/features/menu/index.tsx @@ -1,6 +1,5 @@ -import { Accessor, Component, For, JSX, ParentComponent, Setter, Show, children, createContext, createMemo, createSignal, createUniqueId, mergeProps, onCleanup, onMount, splitProps, useContext } from "solid-js"; +import { Accessor, Component, For, JSX, ParentComponent, Setter, Show, children, createContext, createEffect, createMemo, createSignal, createUniqueId, mergeProps, onCleanup, onMount, splitProps, useContext } from "solid-js"; import { Portal, isServer } from "solid-js/web"; -import './style.css'; import { createStore } from "solid-js/store"; export interface MenuContextType { @@ -57,31 +56,13 @@ export const MenuProvider: ParentComponent = (props) => { const addItems = (items: (Item|ItemWithChildren)[]) => setStore('items', values => { for (const item of items) { - // const existing = values.get(item.id); - - // if(item.children && existing?.children instanceof Map) { - // for (const child of item.children) { - // existing.children.set(child.id, child); - // } - // } - // else if (item.children && existing === undefined){ - // values.set(item.id, { ...item, children: new Map(item.children.map(c => [ c.id, c ])) }); - // } - // else { - // values.set(item.id, item as Item); - // } values[item.id] = item; } return values; }); - const items = createMemo<(Item|ItemWithChildren)[]>(() => - Array.from( - Object.values(store.items), - // item => item.children instanceof Map ? { ...item, children: Array.from(item.children.values()) } : item - ) - ); - const commands = createMemo(() => items().map(item => item.children instanceof Array ? item.children.map(c => c.command) : item.command).flat()); + const items = () => Object.values(store.items); + const commands = () => Object.values(store.items).map(item => item.children?.map(c => c.command) ?? item.command).flat(); return {props.children}; } @@ -90,7 +71,7 @@ const useMenu = () => { const context = useContext(MenuContext); if(context === undefined) { - throw new Error(' is called outside of a '); + throw new Error(`MenuContext is called outside of a `); } return context; @@ -117,31 +98,81 @@ const Item: Component = (props) => { const Root: ParentComponent<{}> = (props) => { const menu = useMenu(); - - menu.addItems((isServer + const [ current, setCurrent ] = createSignal(); + const items = (isServer ? props.children - : props.children?.map(c => c())) ?? []) + : props.children?.map(c => c())) ?? []; + + menu.addItems(items) + + const close = () => { + const el = current(); + + if(el) { + el.hidePopover(); + + setCurrent(undefined); + } + }; + + const onExecute = (command: Command) => { + return async () => { + await command?.(); + + close(); + } + }; const Button: Component<{ label: string, command: Command }|{ [key: string]: any }> = (props) => { const [ local, rest ] = splitProps(props, ['label', 'command']); - return ; + return ; }; return - - {(item) => <> -