feat: add Vue Router + stow-able AppSidebar; stub Fetch/Stats/Settings views
This commit is contained in:
parent
f38c73db97
commit
7bd37ef982
10 changed files with 603 additions and 50 deletions
304
web/package-lock.json
generated
304
web/package-lock.json
generated
|
|
@ -14,7 +14,8 @@
|
||||||
"@vueuse/core": "^14.2.1",
|
"@vueuse/core": "^14.2.1",
|
||||||
"@vueuse/integrations": "^14.2.1",
|
"@vueuse/integrations": "^14.2.1",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "^3.0.4",
|
||||||
"vue": "^3.5.25"
|
"vue": "^3.5.25",
|
||||||
|
"vue-router": "^5.0.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^24.10.1",
|
"@types/node": "^24.10.1",
|
||||||
|
|
@ -90,6 +91,22 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@babel/generator": {
|
||||||
|
"version": "7.29.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
|
||||||
|
"integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/parser": "^7.29.0",
|
||||||
|
"@babel/types": "^7.29.0",
|
||||||
|
"@jridgewell/gen-mapping": "^0.3.12",
|
||||||
|
"@jridgewell/trace-mapping": "^0.3.28",
|
||||||
|
"jsesc": "^3.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@babel/helper-string-parser": {
|
"node_modules/@babel/helper-string-parser": {
|
||||||
"version": "7.27.1",
|
"version": "7.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
|
||||||
|
|
@ -843,7 +860,6 @@
|
||||||
"version": "0.3.13",
|
"version": "0.3.13",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||||
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/sourcemap-codec": "^1.5.0",
|
"@jridgewell/sourcemap-codec": "^1.5.0",
|
||||||
|
|
@ -854,7 +870,6 @@
|
||||||
"version": "2.3.5",
|
"version": "2.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
|
||||||
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
|
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/gen-mapping": "^0.3.5",
|
"@jridgewell/gen-mapping": "^0.3.5",
|
||||||
|
|
@ -865,7 +880,6 @@
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||||
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.0.0"
|
"node": ">=6.0.0"
|
||||||
|
|
@ -881,7 +895,6 @@
|
||||||
"version": "0.3.31",
|
"version": "0.3.31",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
|
||||||
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/resolve-uri": "^3.1.0",
|
"@jridgewell/resolve-uri": "^3.1.0",
|
||||||
|
|
@ -2208,6 +2221,33 @@
|
||||||
"vscode-uri": "^3.0.8"
|
"vscode-uri": "^3.0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@vue-macros/common": {
|
||||||
|
"version": "3.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vue-macros/common/-/common-3.1.2.tgz",
|
||||||
|
"integrity": "sha512-h9t4ArDdniO9ekYHAD95t9AZcAbb19lEGK+26iAjUODOIJKmObDNBSe4+6ELQAA3vtYiFPPBtHh7+cQCKi3Dng==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@vue/compiler-sfc": "^3.5.22",
|
||||||
|
"ast-kit": "^2.1.2",
|
||||||
|
"local-pkg": "^1.1.2",
|
||||||
|
"magic-string-ast": "^1.0.2",
|
||||||
|
"unplugin-utils": "^0.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.19.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/vue-macros"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"vue": "^2.7.0 || ^3.2.25"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"vue": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@vue/compiler-core": {
|
"node_modules/@vue/compiler-core": {
|
||||||
"version": "3.5.29",
|
"version": "3.5.29",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.29.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.29.tgz",
|
||||||
|
|
@ -2505,7 +2545,6 @@
|
||||||
"version": "8.16.0",
|
"version": "8.16.0",
|
||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
|
||||||
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
|
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
|
|
@ -2567,6 +2606,38 @@
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ast-kit": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ast-kit/-/ast-kit-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/parser": "^7.28.5",
|
||||||
|
"pathe": "^2.0.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.19.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sxzz"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ast-walker-scope": {
|
||||||
|
"version": "0.8.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/ast-walker-scope/-/ast-walker-scope-0.8.3.tgz",
|
||||||
|
"integrity": "sha512-cbdCP0PGOBq0ASG+sjnKIoYkWMKhhz+F/h9pRexUdX2Hd38+WOlBkRKlqkGOSm0YQpcFMQBJeK4WspUAkwsEdg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/parser": "^7.28.4",
|
||||||
|
"ast-kit": "^2.1.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.19.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sxzz"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/balanced-match": {
|
"node_modules/balanced-match": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||||
|
|
@ -2627,7 +2698,6 @@
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz",
|
||||||
"integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==",
|
"integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"readdirp": "^5.0.0"
|
"readdirp": "^5.0.0"
|
||||||
|
|
@ -2680,7 +2750,6 @@
|
||||||
"version": "0.1.8",
|
"version": "0.1.8",
|
||||||
"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz",
|
"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz",
|
||||||
"integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==",
|
"integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/config-chain": {
|
"node_modules/config-chain": {
|
||||||
|
|
@ -2940,11 +3009,16 @@
|
||||||
"node": ">=12.0.0"
|
"node": ">=12.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/exsolve": {
|
||||||
|
"version": "1.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz",
|
||||||
|
"integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/fdir": {
|
"node_modules/fdir": {
|
||||||
"version": "6.5.0",
|
"version": "6.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
||||||
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
|
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12.0.0"
|
"node": ">=12.0.0"
|
||||||
|
|
@ -3217,6 +3291,80 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/jsesc": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"jsesc": "bin/jsesc"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/json5": {
|
||||||
|
"version": "2.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
||||||
|
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"json5": "lib/cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/local-pkg": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"mlly": "^1.7.4",
|
||||||
|
"pkg-types": "^2.3.0",
|
||||||
|
"quansync": "^0.2.11"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/antfu"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/local-pkg/node_modules/confbox": {
|
||||||
|
"version": "0.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz",
|
||||||
|
"integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/local-pkg/node_modules/pkg-types": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"confbox": "^0.2.2",
|
||||||
|
"exsolve": "^1.0.7",
|
||||||
|
"pathe": "^2.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/local-pkg/node_modules/quansync": {
|
||||||
|
"version": "0.2.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz",
|
||||||
|
"integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/antfu"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/sxzz"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/lru-cache": {
|
"node_modules/lru-cache": {
|
||||||
"version": "11.2.6",
|
"version": "11.2.6",
|
||||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz",
|
||||||
|
|
@ -3262,6 +3410,21 @@
|
||||||
"@jridgewell/sourcemap-codec": "^1.5.5"
|
"@jridgewell/sourcemap-codec": "^1.5.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/magic-string-ast": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/magic-string-ast/-/magic-string-ast-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-CvkkH1i81zl7mmb94DsRiFeG9V2fR2JeuK8yDgS8oiZSFa++wWLEgZ5ufEOyLHbvSbD1gTRKv9NdX69Rnvr9JA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"magic-string": "^0.30.19"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.19.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sxzz"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/mdn-data": {
|
"node_modules/mdn-data": {
|
||||||
"version": "2.12.2",
|
"version": "2.12.2",
|
||||||
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz",
|
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz",
|
||||||
|
|
@ -3305,7 +3468,6 @@
|
||||||
"version": "1.8.0",
|
"version": "1.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz",
|
||||||
"integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==",
|
"integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"acorn": "^8.15.0",
|
"acorn": "^8.15.0",
|
||||||
|
|
@ -3335,7 +3497,6 @@
|
||||||
"version": "0.4.1",
|
"version": "0.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz",
|
||||||
"integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==",
|
"integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/nanoid": {
|
"node_modules/nanoid": {
|
||||||
|
|
@ -3538,7 +3699,6 @@
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
|
||||||
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
|
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/perfect-debounce": {
|
"node_modules/perfect-debounce": {
|
||||||
|
|
@ -3557,7 +3717,6 @@
|
||||||
"version": "4.0.3",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
|
|
@ -3591,7 +3750,6 @@
|
||||||
"version": "1.3.1",
|
"version": "1.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz",
|
||||||
"integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==",
|
"integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"confbox": "^0.1.8",
|
"confbox": "^0.1.8",
|
||||||
|
|
@ -3665,7 +3823,6 @@
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz",
|
||||||
"integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==",
|
"integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 20.19.0"
|
"node": ">= 20.19.0"
|
||||||
|
|
@ -3759,6 +3916,12 @@
|
||||||
"node": ">=v12.22.7"
|
"node": ">=v12.22.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/scule": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/semver": {
|
"node_modules/semver": {
|
||||||
"version": "7.7.4",
|
"version": "7.7.4",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
|
||||||
|
|
@ -4006,7 +4169,6 @@
|
||||||
"version": "0.2.15",
|
"version": "0.2.15",
|
||||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
|
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
|
||||||
"integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
|
"integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fdir": "^6.5.0",
|
"fdir": "^6.5.0",
|
||||||
|
|
@ -4118,7 +4280,6 @@
|
||||||
"version": "1.6.3",
|
"version": "1.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz",
|
||||||
"integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==",
|
"integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/unconfig": {
|
"node_modules/unconfig": {
|
||||||
|
|
@ -4237,7 +4398,6 @@
|
||||||
"version": "0.3.1",
|
"version": "0.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/unplugin-utils/-/unplugin-utils-0.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/unplugin-utils/-/unplugin-utils-0.3.1.tgz",
|
||||||
"integrity": "sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==",
|
"integrity": "sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"pathe": "^2.0.3",
|
"pathe": "^2.0.3",
|
||||||
|
|
@ -4438,6 +4598,98 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/vue-router": {
|
||||||
|
"version": "5.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-5.0.3.tgz",
|
||||||
|
"integrity": "sha512-nG1c7aAFac7NYj8Hluo68WyWfc41xkEjaR0ViLHCa3oDvTQ/nIuLJlXJX1NUPw/DXzx/8+OKMng045HHQKQKWw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/generator": "^7.28.6",
|
||||||
|
"@vue-macros/common": "^3.1.1",
|
||||||
|
"@vue/devtools-api": "^8.0.6",
|
||||||
|
"ast-walker-scope": "^0.8.3",
|
||||||
|
"chokidar": "^5.0.0",
|
||||||
|
"json5": "^2.2.3",
|
||||||
|
"local-pkg": "^1.1.2",
|
||||||
|
"magic-string": "^0.30.21",
|
||||||
|
"mlly": "^1.8.0",
|
||||||
|
"muggle-string": "^0.4.1",
|
||||||
|
"pathe": "^2.0.3",
|
||||||
|
"picomatch": "^4.0.3",
|
||||||
|
"scule": "^1.3.0",
|
||||||
|
"tinyglobby": "^0.2.15",
|
||||||
|
"unplugin": "^3.0.0",
|
||||||
|
"unplugin-utils": "^0.3.1",
|
||||||
|
"yaml": "^2.8.2"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/posva"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@pinia/colada": ">=0.21.2",
|
||||||
|
"@vue/compiler-sfc": "^3.5.17",
|
||||||
|
"pinia": "^3.0.4",
|
||||||
|
"vue": "^3.5.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@pinia/colada": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@vue/compiler-sfc": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"pinia": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vue-router/node_modules/@vue/devtools-api": {
|
||||||
|
"version": "8.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-8.0.7.tgz",
|
||||||
|
"integrity": "sha512-tc1TXAxclsn55JblLkFVcIRG7MeSJC4fWsPjfM7qu/IcmPUYnQ5Q8vzWwBpyDY24ZjmZTUCCwjRSNbx58IhlAA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@vue/devtools-kit": "^8.0.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vue-router/node_modules/@vue/devtools-kit": {
|
||||||
|
"version": "8.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-8.0.7.tgz",
|
||||||
|
"integrity": "sha512-H6esJGHGl5q0E9iV3m2EoBQHJ+V83WMW83A0/+Fn95eZ2iIvdsq4+UCS6yT/Fdd4cGZSchx/MdWDreM3WqMsDw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@vue/devtools-shared": "^8.0.7",
|
||||||
|
"birpc": "^2.6.1",
|
||||||
|
"hookable": "^5.5.3",
|
||||||
|
"perfect-debounce": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vue-router/node_modules/@vue/devtools-shared": {
|
||||||
|
"version": "8.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-8.0.7.tgz",
|
||||||
|
"integrity": "sha512-CgAb9oJH5NUmbQRdYDj/1zMiaICYSLtm+B1kxcP72LBrifGAjUmt8bx52dDH1gWRPlQgxGPqpAMKavzVirAEhA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/vue-router/node_modules/perfect-debounce": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/vue-router/node_modules/unplugin": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/unplugin/-/unplugin-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@jridgewell/remapping": "^2.3.5",
|
||||||
|
"picomatch": "^4.0.3",
|
||||||
|
"webpack-virtual-modules": "^0.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vue-tsc": {
|
"node_modules/vue-tsc": {
|
||||||
"version": "3.2.5",
|
"version": "3.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-3.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-3.2.5.tgz",
|
||||||
|
|
@ -4482,7 +4734,6 @@
|
||||||
"version": "0.6.2",
|
"version": "0.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz",
|
||||||
"integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==",
|
"integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/whatwg-mimetype": {
|
"node_modules/whatwg-mimetype": {
|
||||||
|
|
@ -4657,6 +4908,21 @@
|
||||||
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
|
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/yaml": {
|
||||||
|
"version": "2.8.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz",
|
||||||
|
"integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==",
|
||||||
|
"license": "ISC",
|
||||||
|
"bin": {
|
||||||
|
"yaml": "bin.mjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14.6"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/eemeli"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,8 @@
|
||||||
"@vueuse/core": "^14.2.1",
|
"@vueuse/core": "^14.2.1",
|
||||||
"@vueuse/integrations": "^14.2.1",
|
"@vueuse/integrations": "^14.2.1",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "^3.0.4",
|
||||||
"vue": "^3.5.25"
|
"vue": "^3.5.25",
|
||||||
|
"vue-router": "^5.0.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^24.10.1",
|
"@types/node": "^24.10.1",
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,18 @@
|
||||||
<template>
|
<template>
|
||||||
<div id="app" :class="{ 'rich-motion': motion.rich.value }">
|
<div id="app" :class="{ 'rich-motion': motion.rich.value }">
|
||||||
<LabelView />
|
<AppSidebar />
|
||||||
|
<main class="app-main">
|
||||||
|
<RouterView />
|
||||||
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted } from 'vue'
|
import { onMounted } from 'vue'
|
||||||
|
import { RouterView } from 'vue-router'
|
||||||
import { useMotion } from './composables/useMotion'
|
import { useMotion } from './composables/useMotion'
|
||||||
import { useHackerMode } from './composables/useEasterEgg'
|
import { useHackerMode } from './composables/useEasterEgg'
|
||||||
import LabelView from './views/LabelView.vue'
|
import AppSidebar from './components/AppSidebar.vue'
|
||||||
|
|
||||||
const motion = useMotion()
|
const motion = useMotion()
|
||||||
const { restore } = useHackerMode()
|
const { restore } = useHackerMode()
|
||||||
|
|
@ -34,6 +38,15 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
#app {
|
#app {
|
||||||
|
display: flex;
|
||||||
min-height: 100dvh;
|
min-height: 100dvh;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-main {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0; /* prevent flex blowout */
|
||||||
|
margin-left: var(--sidebar-width, 200px);
|
||||||
|
transition: margin-left 250ms ease;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
274
web/src/components/AppSidebar.vue
Normal file
274
web/src/components/AppSidebar.vue
Normal file
|
|
@ -0,0 +1,274 @@
|
||||||
|
<template>
|
||||||
|
<!-- Mobile backdrop scrim -->
|
||||||
|
<div
|
||||||
|
v-if="isMobile && !stowed"
|
||||||
|
class="sidebar-scrim"
|
||||||
|
aria-hidden="true"
|
||||||
|
@click="stow()"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<nav
|
||||||
|
class="sidebar"
|
||||||
|
:class="{ stowed, mobile: isMobile }"
|
||||||
|
:style="{ '--sidebar-w': stowed ? '56px' : '200px' }"
|
||||||
|
aria-label="App navigation"
|
||||||
|
>
|
||||||
|
<!-- Logo + stow toggle -->
|
||||||
|
<div class="sidebar-header">
|
||||||
|
<span v-if="!stowed" class="sidebar-logo">
|
||||||
|
<span class="logo-icon">🐦</span>
|
||||||
|
<span class="logo-name">Avocet</span>
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
class="stow-btn"
|
||||||
|
:aria-label="stowed ? 'Expand navigation' : 'Collapse navigation'"
|
||||||
|
@click="toggle()"
|
||||||
|
>
|
||||||
|
{{ stowed ? '›' : '‹' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Nav items -->
|
||||||
|
<ul class="nav-list" role="list">
|
||||||
|
<li v-for="item in navItems" :key="item.path">
|
||||||
|
<RouterLink
|
||||||
|
:to="item.path"
|
||||||
|
class="nav-item"
|
||||||
|
:title="stowed ? item.label : ''"
|
||||||
|
@click="isMobile && stow()"
|
||||||
|
>
|
||||||
|
<span class="nav-icon" aria-hidden="true">{{ item.icon }}</span>
|
||||||
|
<span v-if="!stowed" class="nav-label">{{ item.label }}</span>
|
||||||
|
</RouterLink>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- Mobile hamburger button rendered outside the sidebar so it's visible when stowed -->
|
||||||
|
<button
|
||||||
|
v-if="isMobile && stowed"
|
||||||
|
class="mobile-hamburger"
|
||||||
|
aria-label="Open navigation"
|
||||||
|
@click="toggle()"
|
||||||
|
>
|
||||||
|
☰
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||||
|
import { RouterLink } from 'vue-router'
|
||||||
|
|
||||||
|
const LS_KEY = 'cf-avocet-nav-stowed'
|
||||||
|
|
||||||
|
const navItems = [
|
||||||
|
{ path: '/', icon: '🃏', label: 'Label' },
|
||||||
|
{ path: '/fetch', icon: '📥', label: 'Fetch' },
|
||||||
|
{ path: '/stats', icon: '📊', label: 'Stats' },
|
||||||
|
{ path: '/settings', icon: '⚙️', label: 'Settings' },
|
||||||
|
]
|
||||||
|
|
||||||
|
const stowed = ref(localStorage.getItem(LS_KEY) === 'true')
|
||||||
|
const winWidth = ref(window.innerWidth)
|
||||||
|
const isMobile = computed(() => winWidth.value < 640)
|
||||||
|
|
||||||
|
function toggle() {
|
||||||
|
stowed.value = !stowed.value
|
||||||
|
localStorage.setItem(LS_KEY, String(stowed.value))
|
||||||
|
// Update CSS variable on :root so .app-main margin-left syncs
|
||||||
|
document.documentElement.style.setProperty('--sidebar-width', stowed.value ? '56px' : '200px')
|
||||||
|
}
|
||||||
|
|
||||||
|
function stow() {
|
||||||
|
stowed.value = true
|
||||||
|
localStorage.setItem(LS_KEY, 'true')
|
||||||
|
document.documentElement.style.setProperty('--sidebar-width', '56px')
|
||||||
|
}
|
||||||
|
|
||||||
|
function onResize() { winWidth.value = window.innerWidth }
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
window.addEventListener('resize', onResize)
|
||||||
|
// Apply persisted sidebar width to :root on mount
|
||||||
|
document.documentElement.style.setProperty('--sidebar-width', stowed.value ? '56px' : '200px')
|
||||||
|
// On mobile, default to stowed
|
||||||
|
if (isMobile.value && !localStorage.getItem(LS_KEY)) {
|
||||||
|
stowed.value = true
|
||||||
|
document.documentElement.style.setProperty('--sidebar-width', '56px')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => window.removeEventListener('resize', onResize))
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.sidebar {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: var(--sidebar-w, 200px);
|
||||||
|
background: var(--color-surface-raised, #e4ebf5);
|
||||||
|
border-right: 1px solid var(--color-border, #d0d7e8);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
z-index: 200;
|
||||||
|
transition: width 250ms ease;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar.stowed {
|
||||||
|
width: 56px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile: slide in/out from left */
|
||||||
|
.sidebar.mobile {
|
||||||
|
box-shadow: 2px 0 16px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar.mobile.stowed {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
width: 200px; /* keep width so slide-in looks right */
|
||||||
|
transition: transform 250ms ease, width 250ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar.mobile:not(.stowed) {
|
||||||
|
transform: translateX(0);
|
||||||
|
transition: transform 250ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-scrim {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.3);
|
||||||
|
z-index: 199;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0.75rem 0.5rem 0.75rem 0.75rem;
|
||||||
|
border-bottom: 1px solid var(--color-border, #d0d7e8);
|
||||||
|
min-height: 52px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-logo {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.4rem;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-icon {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-name {
|
||||||
|
font-family: var(--font-display, var(--font-body, sans-serif));
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--app-primary, #2A6080);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stow-btn {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--color-text-secondary, #6b7a99);
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: background 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stow-btn:hover {
|
||||||
|
background: var(--color-border, #d0d7e8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-list {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.6rem;
|
||||||
|
padding: 0.65rem 0.75rem;
|
||||||
|
color: var(--color-text, #1a2338);
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
transition: background 0.15s, color 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item:hover {
|
||||||
|
background: color-mix(in srgb, var(--app-primary, #2A6080) 10%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item.router-link-active {
|
||||||
|
background: color-mix(in srgb, var(--app-primary, #2A6080) 15%, transparent);
|
||||||
|
color: var(--app-primary, #2A6080);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item.router-link-active::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 3px;
|
||||||
|
background: var(--app-primary, #2A6080);
|
||||||
|
border-radius: 0 2px 2px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-icon {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 24px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-label {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile hamburger — visible when sidebar is stowed on mobile */
|
||||||
|
.mobile-hamburger {
|
||||||
|
position: fixed;
|
||||||
|
top: 0.75rem;
|
||||||
|
left: 0.75rem;
|
||||||
|
z-index: 201;
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border: 1px solid var(--color-border, #d0d7e8);
|
||||||
|
background: var(--color-surface-raised, #e4ebf5);
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.sidebar,
|
||||||
|
.sidebar.mobile,
|
||||||
|
.sidebar.mobile.stowed {
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import { createPinia } from 'pinia'
|
import { createPinia } from 'pinia'
|
||||||
|
import { router } from './router'
|
||||||
// Self-hosted fonts — no Google Fonts CDN (privacy requirement)
|
// Self-hosted fonts — no Google Fonts CDN (privacy requirement)
|
||||||
import '@fontsource/fraunces/400.css'
|
import '@fontsource/fraunces/400.css'
|
||||||
import '@fontsource/fraunces/700.css'
|
import '@fontsource/fraunces/700.css'
|
||||||
|
|
@ -11,6 +12,9 @@ import './assets/theme.css'
|
||||||
import './assets/avocet.css'
|
import './assets/avocet.css'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
|
|
||||||
|
if ('scrollRestoration' in history) history.scrollRestoration = 'manual'
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
app.use(createPinia())
|
app.use(createPinia())
|
||||||
|
app.use(router)
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
|
|
|
||||||
17
web/src/router/index.ts
Normal file
17
web/src/router/index.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { createRouter, createWebHashHistory } from 'vue-router'
|
||||||
|
import LabelView from '../views/LabelView.vue'
|
||||||
|
|
||||||
|
// Views are lazy-loaded to keep initial bundle small
|
||||||
|
const FetchView = () => import('../views/FetchView.vue')
|
||||||
|
const StatsView = () => import('../views/StatsView.vue')
|
||||||
|
const SettingsView = () => import('../views/SettingsView.vue')
|
||||||
|
|
||||||
|
export const router = createRouter({
|
||||||
|
history: createWebHashHistory(),
|
||||||
|
routes: [
|
||||||
|
{ path: '/', component: LabelView, meta: { title: 'Label' } },
|
||||||
|
{ path: '/fetch', component: FetchView, meta: { title: 'Fetch' } },
|
||||||
|
{ path: '/stats', component: StatsView, meta: { title: 'Stats' } },
|
||||||
|
{ path: '/settings', component: SettingsView, meta: { title: 'Settings' } },
|
||||||
|
],
|
||||||
|
})
|
||||||
2
web/src/views/FetchView.vue
Normal file
2
web/src/views/FetchView.vue
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
<template><div class="stub-view"><h2>📥 Fetch</h2><p>Coming soon</p></div></template>
|
||||||
|
<style scoped>.stub-view { padding: 2rem; }</style>
|
||||||
|
|
@ -1,11 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="label-view">
|
<div class="label-view">
|
||||||
<!-- App bar -->
|
|
||||||
<div class="app-bar">
|
|
||||||
<span class="app-title">Avocet</span>
|
|
||||||
<span class="app-subtitle">Email Labeler</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Header bar -->
|
<!-- Header bar -->
|
||||||
<header class="lv-header">
|
<header class="lv-header">
|
||||||
<span class="queue-count">
|
<span class="queue-count">
|
||||||
|
|
@ -298,28 +292,6 @@ onUnmounted(() => {
|
||||||
min-height: 100dvh;
|
min-height: 100dvh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-bar {
|
|
||||||
display: flex;
|
|
||||||
align-items: baseline;
|
|
||||||
gap: 0.5rem;
|
|
||||||
padding-bottom: 0.25rem;
|
|
||||||
border-bottom: 2px solid var(--color-border, #d0d7e8);
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-title {
|
|
||||||
font-family: var(--font-display, var(--font-body, sans-serif));
|
|
||||||
font-size: 1.25rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: var(--app-primary, #2A6080);
|
|
||||||
letter-spacing: -0.02em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-subtitle {
|
|
||||||
font-size: 0.75rem;
|
|
||||||
color: var(--color-text-secondary, #6b7a99);
|
|
||||||
font-family: var(--font-mono, monospace);
|
|
||||||
}
|
|
||||||
|
|
||||||
.queue-status {
|
.queue-status {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
|
|
|
||||||
2
web/src/views/SettingsView.vue
Normal file
2
web/src/views/SettingsView.vue
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
<template><div class="stub-view"><h2>⚙️ Settings</h2><p>Coming soon</p></div></template>
|
||||||
|
<style scoped>.stub-view { padding: 2rem; }</style>
|
||||||
2
web/src/views/StatsView.vue
Normal file
2
web/src/views/StatsView.vue
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
<template><div class="stub-view"><h2>📊 Stats</h2><p>Coming soon</p></div></template>
|
||||||
|
<style scoped>.stub-view { padding: 2rem; }</style>
|
||||||
Loading…
Reference in a new issue