Merge branch 'main' into feature/swap-to-tricep-for-iac

This commit is contained in:
Chris Kruining 2024-12-03 07:14:53 +01:00
commit 47872137e0
No known key found for this signature in database
GPG key ID: EB894A3560CCCAD2
10 changed files with 128 additions and 44 deletions

View file

@ -76,8 +76,8 @@ export default defineConfig({
}, },
server: { server: {
preset: 'bun', preset: 'bun',
prerender: { // prerender: {
crawlLinks: true, // crawlLinks: true,
}, // },
}, },
}); });

BIN
bun.lockb

Binary file not shown.

View file

@ -2,9 +2,9 @@
"name": "calque", "name": "calque",
"dependencies": { "dependencies": {
"@solidjs/meta": "^0.29.4", "@solidjs/meta": "^0.29.4",
"@solidjs/router": "^0.15.0", "@solidjs/router": "^0.15.1",
"@solidjs/start": "^1.0.9", "@solidjs/start": "^1.0.10",
"dexie": "^4.0.9", "dexie": "^4.0.10",
"iterator-helpers-polyfill": "^3.0.1", "iterator-helpers-polyfill": "^3.0.1",
"solid-icons": "^1.1.0", "solid-icons": "^1.1.0",
"solid-js": "^1.9.3", "solid-js": "^1.9.3",
@ -22,16 +22,16 @@
}, },
"type": "module", "type": "module",
"devDependencies": { "devDependencies": {
"@happy-dom/global-registrator": "^15.11.0", "@happy-dom/global-registrator": "^15.11.7",
"@sinonjs/fake-timers": "^13.0.5", "@sinonjs/fake-timers": "^13.0.5",
"@solidjs/testing-library": "^0.8.10", "@solidjs/testing-library": "^0.8.10",
"@testing-library/jest-dom": "^6.6.3", "@testing-library/jest-dom": "^6.6.3",
"@testing-library/user-event": "^14.5.2", "@testing-library/user-event": "^14.5.2",
"@types/sinonjs__fake-timers": "^8.1.5", "@types/sinonjs__fake-timers": "^8.1.5",
"@types/wicg-file-system-access": "^2023.10.5", "@types/wicg-file-system-access": "^2023.10.5",
"bun-types": "^1.1.34", "bun-types": "^1.1.38",
"jsdom": "^25.0.1", "jsdom": "^25.0.1",
"vite-plugin-pwa": "^0.21.0", "vite-plugin-pwa": "^0.21.1",
"vite-plugin-solid-svg": "^0.8.1", "vite-plugin-solid-svg": "^0.8.1",
"workbox-window": "^7.3.0" "workbox-window": "^7.3.0"
} }

View file

@ -44,6 +44,42 @@
--padding-l: .75em; --padding-l: .75em;
--padding-xl: 1em; --padding-xl: 1em;
--padding-xxl: 1.5em; --padding-xxl: 1.5em;
--shadow-color: 220 3% 15%;
--shadow-strength: 1%;
--shadow-1: 0 1px 2px -1px oklch(var(--shadow-color) / calc(var(--shadow-strength) + 9%));
--shadow-2:
0 3px 5px -2px hsl(var(--shadow-color) / calc(var(--shadow-strength) + 3%)),
0 7px 14px -5px hsl(var(--shadow-color) / calc(var(--shadow-strength) + 5%));
--shadow-3:
0 -1px 3px 0 hsl(var(--shadow-color) / calc(var(--shadow-strength) + 2%)),
0 1px 2px -5px hsl(var(--shadow-color) / calc(var(--shadow-strength) + 2%)),
0 2px 5px -5px hsl(var(--shadow-color) / calc(var(--shadow-strength) + 4%)),
0 4px 12px -5px hsl(var(--shadow-color) / calc(var(--shadow-strength) + 5%)),
0 12px 15px -5px hsl(var(--shadow-color) / calc(var(--shadow-strength) + 7%));
--shadow-4:
0 -2px 5px 0 hsl(var(--shadow-color) / calc(var(--shadow-strength) + 2%)),
0 1px 1px -2px hsl(var(--shadow-color) / calc(var(--shadow-strength) + 3%)),
0 2px 2px -2px hsl(var(--shadow-color) / calc(var(--shadow-strength) + 3%)),
0 5px 5px -2px hsl(var(--shadow-color) / calc(var(--shadow-strength) + 4%)),
0 9px 9px -2px hsl(var(--shadow-color) / calc(var(--shadow-strength) + 5%)),
0 16px 16px -2px hsl(var(--shadow-color) / calc(var(--shadow-strength) + 6%));
--shadow-5:
0 -1px 2px 0 hsl(var(--shadow-color) / calc(var(--shadow-strength) + 2%)),
0 2px 1px -2px hsl(var(--shadow-color) / calc(var(--shadow-strength) + 3%)),
0 5px 5px -2px hsl(var(--shadow-color) / calc(var(--shadow-strength) + 3%)),
0 10px 10px -2px hsl(var(--shadow-color) / calc(var(--shadow-strength) + 4%)),
0 20px 20px -2px hsl(var(--shadow-color) / calc(var(--shadow-strength) + 5%)),
0 40px 40px -2px hsl(var(--shadow-color) / calc(var(--shadow-strength) + 7%));
--shadow-6:
0 -1px 2px 0 hsl(var(--shadow-color) / calc(var(--shadow-strength) + 2%)),
0 3px 2px -2px hsl(var(--shadow-color) / calc(var(--shadow-strength) + 3%)),
0 7px 5px -2px hsl(var(--shadow-color) / calc(var(--shadow-strength) + 3%)),
0 12px 10px -2px hsl(var(--shadow-color) / calc(var(--shadow-strength) + 4%)),
0 22px 18px -2px hsl(var(--shadow-color) / calc(var(--shadow-strength) + 5%)),
0 41px 33px -2px hsl(var(--shadow-color) / calc(var(--shadow-strength) + 6%)),
0 100px 80px -2px hsl(var(--shadow-color) / calc(var(--shadow-strength) + 7%));
} }
html { html {

View file

@ -2,13 +2,14 @@
display: flex; display: flex;
flex-flow: row; flex-flow: row;
align-items: center; align-items: center;
background-color: inherit;
border: 1px solid transparent; border: 1px solid transparent;
border-radius: var(--radii-m); border-radius: var(--radii-m);
padding: var(--padding-s); padding: var(--padding-s);
& select { & select {
border: none; border: none;
background-color: transparent; background-color: inherit;
border-radius: var(--radii-m); border-radius: var(--radii-m);
&:focus { &:focus {

View file

@ -1,9 +1,9 @@
import { Accessor, Component, createContext, createEffect, createMemo, createResource, For, ParentComponent, Setter, Show, Suspense, useContext } from "solid-js"; import { Component, createContext, createEffect, createResource, For, ParentComponent, Show, Suspense, useContext } from "solid-js";
import css from './colorschemepicker.module.css'; import css from './colorschemepicker.module.css';
import { CgDarkMode } from "solid-icons/cg"; import { CgDarkMode } from "solid-icons/cg";
import { action, createAsyncStore, query, useAction } from "@solidjs/router"; import { action, query, useAction } from "@solidjs/router";
import { useSession } from "vinxi/http"; import { useSession } from "vinxi/http";
import { createStore, reconcile, ReconcileOptions, SetStoreFunction } from "solid-js/store"; import { createStore } from "solid-js/store";
export enum ColorScheme { export enum ColorScheme {
Auto = 'light dark', Auto = 'light dark',
@ -38,7 +38,7 @@ const setState = action(async (state: State) => {
'use server'; 'use server';
const session = await getSession(); const session = await getSession();
await session.update(state); await session.update(prev => ({ ...prev, ...state }));
}, 'color-scheme'); }, 'color-scheme');
interface ThemeContextType { interface ThemeContextType {
@ -62,7 +62,7 @@ export const useTheme = () => {
}; };
export const ThemeProvider: ParentComponent = (props) => { export const ThemeProvider: ParentComponent = (props) => {
const [state, { mutate }] = createResource<State>(() => getState(), { deferStream: true, initialValue: { colorScheme: ColorScheme.Auto, hue: 0 } }); const [state, { mutate }] = createResource<State>(() => getState(), { deferStream: true });
const updateState = useAction(setState); const updateState = useAction(setState);
return <Suspense> return <Suspense>
@ -75,8 +75,8 @@ export const ThemeProvider: ParentComponent = (props) => {
return <ThemeContext.Provider value={{ return <ThemeContext.Provider value={{
get theme() { return store; }, get theme() { return store; },
setColorScheme(colorScheme: ColorScheme) { updateState(mutate(prev => ({ ...prev, colorScheme }))) }, setColorScheme(colorScheme: ColorScheme) { updateState(mutate(prev => ({ colorScheme, hue: prev?.hue ?? 0 }))) },
setHue(hue: number) { updateState(mutate(prev => ({ ...prev, hue }))) }, setHue(hue: number) { updateState(mutate(prev => ({ hue, colorScheme: prev?.colorScheme ?? ColorScheme.Auto }))) },
}}> }}>
{props.children} {props.children}
</ThemeContext.Provider>; </ThemeContext.Provider>;

View file

@ -20,6 +20,7 @@
} }
&.open > .content { &.open > .content {
min-inline-size: 10em;
inline-size: max-content; inline-size: max-content;
} }

View file

@ -26,9 +26,8 @@ export default createHandler(({ nonce }) => {
); );
}, event => { }, event => {
const nonce = crypto.randomUUID(); const nonce = crypto.randomUUID();
const isDev = process.env.NODE_ENV === 'development';
const base = `'self' 'nonce-${nonce}' ${isDev ? `'unsafe-eval'` : ''}`; const base = `'self' 'nonce-${nonce}' 'unsafe-eval'`;
const policies = { const policies = {
default: base, default: base,

View file

@ -1,28 +1,33 @@
import { Link, Meta, Style, Title } from "@solidjs/meta"; import { Link, Meta, Title } from "@solidjs/meta";
import { Component, createEffect, createMemo, createSignal, ErrorBoundary, ParentProps, Show, Suspense } from "solid-js"; import { Component, createMemo, createSignal, createUniqueId, ErrorBoundary, ParentProps, Show } from "solid-js";
import { FilesProvider } from "~/features/file"; import { FilesProvider } from "~/features/file";
import { CommandPalette, CommandPaletteApi, Menu, MenuProvider } from "~/features/menu"; import { CommandPalette, CommandPaletteApi, Menu, MenuProvider } from "~/features/menu";
import { A, RouteDefinition, useBeforeLeave } from "@solidjs/router"; import { A, RouteDefinition, useBeforeLeave } from "@solidjs/router";
import { createCommand, Modifier } from "~/features/command"; import { createCommand, Modifier } from "~/features/command";
import { ColorScheme, ColorSchemePicker, getState, useTheme } from "~/components/colorschemepicker"; import { ColorScheme, ColorSchemePicker, getState, useTheme } from "~/components/colorschemepicker";
import { getRequestEvent, isServer } from "solid-js/web"; import { getRequestEvent } from "solid-js/web";
import { HttpHeader } from "@solidjs/start"; import { HttpHeader } from "@solidjs/start";
import { FaSolidPalette } from "solid-icons/fa";
import css from "./editor.module.css"; import css from "./editor.module.css";
const event = getRequestEvent(); const event = getRequestEvent();
export const route: RouteDefinition = { export const route: RouteDefinition = {
preload: ({ params, location, intent }) => { preload: () => {
console.log();
return getState(); return getState();
}, },
}; };
export default function Editor(props: ParentProps) { export default function Editor(props: ParentProps) {
const theme = useTheme(); const theme = useTheme();
const themeMenuId = createUniqueId();
const [commandPalette, setCommandPalette] = createSignal<CommandPaletteApi>(); const [commandPalette, setCommandPalette] = createSignal<CommandPaletteApi>();
const lightness = createMemo(() => {
const scheme = theme.colorScheme === ColorScheme.Auto ? event?.request.headers.get('Sec-CH-Prefers-Color-Scheme') : theme.colorScheme;
return scheme === ColorScheme.Light ? .9 : .2;
});
const commands = [ const commands = [
createCommand('open command palette', () => { createCommand('open command palette', () => {
@ -47,26 +52,14 @@ export default function Editor(props: ParentProps) {
<Title>Calque</Title> <Title>Calque</Title>
<Show when={theme}>{ <Meta name="color-scheme" content={theme.colorScheme} />
theme => { <Meta name="theme-color" content={`oklch(${lightness()} .02 ${theme.hue})`} />
const lightness = createMemo(() => {
const scheme = theme().colorScheme === ColorScheme.Auto ? event?.request.headers.get('Sec-CH-Prefers-Color-Scheme') : theme().colorScheme;
return scheme === ColorScheme.Light ? .9 : .2;
});
return <>
<Meta name="color-scheme" content={theme().colorScheme} />
<Meta name="theme-color" content={`oklch(${lightness()} .02 ${theme().hue})`} />
<style>{` <style>{`
:root { :root {
--hue: ${theme().hue}deg !important; --hue: ${theme.hue}deg !important;
} }
`}</style> `}</style>
</>;
}
}</Show>
<Link rel="icon" href="/images/favicon.dark.svg" media="screen and (prefers-color-scheme: dark)" /> <Link rel="icon" href="/images/favicon.dark.svg" media="screen and (prefers-color-scheme: dark)" />
<Link rel="icon" href="/images/favicon.light.svg" media="screen and (prefers-color-scheme: light)" /> <Link rel="icon" href="/images/favicon.light.svg" media="screen and (prefers-color-scheme: light)" />
@ -85,8 +78,15 @@ export default function Editor(props: ParentProps) {
<Menu.Mount /> <Menu.Mount />
<section class={css.right}> <section class={css.right}>
<ColorSchemePicker /> <div class={css.themeMenu}>
<button class={css.themeMenuButton} id={`${themeMenuId}-button`} popoverTarget={`${themeMenuId}-dialog`} title="Open theme picker menu">
<FaSolidPalette />
</button>
<dialog class={css.themeMenuDialog} id={`${themeMenuId}-dialog`} popover anchor={`${themeMenuId}-button`}>
<ColorSchemePicker />
</dialog>
</div>
</section> </section>
</nav> </nav>

View file

@ -6,6 +6,12 @@
overflow: clip; overflow: clip;
background-color: var(--surface-300); background-color: var(--surface-300);
@media (display-mode: window-controls-overlay) {
& {
row-gap: var(--padding-l);
}
}
.menu { .menu {
view-transition-name: menu; view-transition-name: menu;
display: grid; display: grid;
@ -47,6 +53,47 @@
display: grid; display: grid;
grid-auto-flow: column; grid-auto-flow: column;
align-content: center; align-content: center;
& .themeMenu {
display: contents;
& > button {
display: flex;
justify-content: center;
padding: var(--padding-m);
border: none;
border-radius: var(--radii-m);
background-color: transparent;
&:hover {
background-color: var(--surface-500);
}
@media (display-mode: window-controls-overlay) {
& {
color: light-dark(#0008, #fff8);
}
}
}
& > dialog {
inset-inline-end: anchor(right);
inset-block-start: anchor(bottom);
padding: var(--padding-m);
border: none;
border-radius: var(--radii-m);
border-top-right-radius: 0;
background-color: var(--surface-600);
box-shadow: var(--shadow-3);
}
&:has(:popover-open) > button {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
background-color: var(--surface-600);
}
}
} }
} }