feat(ui): redesign UI with Tailwind CSS, CodeMirror 6, and new layout
- Replace all hand-written CSS with Tailwind utility classes - Upgrade CodeMirror 5 to CodeMirror 6 (scoped @codemirror/* packages) - Redesign layout with responsive sidebar navigation using JSX/Mithril - Flatten admin routes (e.g. /admin/presets -> /presets) - Add "remember me" login option with 2-week persistent cookie - Add delete confirmation dialogs across admin pages - Upgrade esbuild from 0.21 to 0.27 and update browser targets - Add Inter and Roboto Mono variable fonts with subset script
This commit is contained in:
1 parent
a7256620e0
commit
4a191afdec
64 files changed
+4147
-2421
No files matched your search
+34
-4
@@ -110,12 +110,28 @@ const inlineDepsPlugin = {
|
||||
const deps = [
|
||||
"parsimmon",
|
||||
"espresso-iisojs",
|
||||
"codemirror",
|
||||
"@codemirror",
|
||||
"mithril",
|
||||
"yaml",
|
||||
];
|
||||
const depFiles = new Set();
|
||||
build.onResolve({ filter: /^[^.]/ }, async (args) => {
|
||||
if (deps.some((d) => args.path.startsWith(d))) return undefined;
|
||||
if (args.pluginData === "inlineDeps") return undefined;
|
||||
if (
|
||||
depFiles.has(args.importer) ||
|
||||
deps.some((d) => args.path.startsWith(d))
|
||||
) {
|
||||
const res = await build.resolve(args.path, {
|
||||
importer: args.importer,
|
||||
namespace: args.namespace,
|
||||
resolveDir: args.resolveDir,
|
||||
kind: args.kind,
|
||||
with: args.with,
|
||||
pluginData: "inlineDeps",
|
||||
});
|
||||
depFiles.add(res.path);
|
||||
return res;
|
||||
}
|
||||
return { sideEffects: false, external: true };
|
||||
});
|
||||
},
|
||||
@@ -226,6 +242,16 @@ async function copyStatic(): Promise<void> {
|
||||
}
|
||||
|
||||
async function generateCss(): Promise<void> {
|
||||
const tailwindPlugin = {
|
||||
name: "tailwind",
|
||||
setup(build) {
|
||||
build.onLoad({ filter: /\/ui\/css\/app.css$/ }, async (args) => {
|
||||
const res = await execAsync(`npx @tailwindcss/cli -i ${args.path}`);
|
||||
return { loader: "css", contents: res.stdout };
|
||||
});
|
||||
},
|
||||
} as esbuild.Plugin;
|
||||
|
||||
const res = await esbuild.build({
|
||||
bundle: true,
|
||||
absWorkingDir: INPUT_DIR,
|
||||
@@ -235,7 +261,11 @@ async function generateCss(): Promise<void> {
|
||||
entryPoints: ["ui/css/app.css"],
|
||||
entryNames: "[dir]/[name]-[hash]",
|
||||
outfile: path.join(OUTPUT_DIR, "public/app.css"),
|
||||
target: ["chrome109", "safari15.6", "firefox115", "opera102", "edge118"],
|
||||
plugins: [tailwindPlugin],
|
||||
loader: {
|
||||
".woff2": "dataurl",
|
||||
},
|
||||
target: ["chrome111", "safari16.4", "firefox128"],
|
||||
metafile: true,
|
||||
});
|
||||
|
||||
@@ -293,7 +323,7 @@ async function generateFrontendJs(): Promise<void> {
|
||||
sourcesContent: false,
|
||||
platform: "browser",
|
||||
format: "esm",
|
||||
target: ["chrome109", "safari15.6", "firefox115", "opera102", "edge118"],
|
||||
target: ["chrome111", "safari16.4", "firefox128"],
|
||||
entryPoints: ["ui/app.ts"],
|
||||
entryNames: "[dir]/[name]-[hash]",
|
||||
outdir: path.join(OUTPUT_DIR, "public"),
|
||||
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
#!/bin/bash
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
declare -A FONTS=(
|
||||
[InterVariable]=https://rsms.me/inter/font-files/InterVariable.woff2
|
||||
[InterVariable-Italic]=https://rsms.me/inter/font-files/InterVariable-Italic.woff2
|
||||
[RobotoMono]=https://raw.githubusercontent.com/googlefonts/RobotoMono/main/fonts/variable/RobotoMono%5Bwght%5D.ttf
|
||||
)
|
||||
|
||||
for name in "${!FONTS[@]}"
|
||||
do
|
||||
url="${FONTS[$name]}"
|
||||
echo "Downloading $url"
|
||||
TMP=$(mktemp)
|
||||
curl "$url" -s --output "$TMP"
|
||||
pyftsubset "$TMP" --output-file="../ui/css/$name.woff2" --flavor=woff2 --unicodes="U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD" --layout-features+="tnum"
|
||||
done
|
||||
Reference in new issue
Block a user