diff --git a/package.json b/package.json
index fee7b58..39d6abb 100644
--- a/package.json
+++ b/package.json
@@ -5,13 +5,15 @@
"private": true,
"scripts": {
"dev": "vite",
- "build": "vue-tsc && vite build",
+ "build": "node scripts/generate-sprite.js && vue-tsc && vite build",
+ "icons": "node scripts/generate-sprite.js",
"preview": "vite preview",
"tauri": "tauri",
"lint": "eslint . --fix",
"gen:api": "npx swagger-typescript-api@12.0.4 -p http://localhost:1337/api/docs-json -o ./src/api -n api.ts --axios --unwrap-response-data --extract-request-params --extract-request-body --single-http-client"
},
"dependencies": {
+ "@tanstack/vue-query": "^5.101.0",
"@floating-ui/vue": "^1.1.5",
"@tauri-apps/api": "^2",
"@tauri-apps/plugin-dialog": "^2",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 8ee07cb..59c8019 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -13,6 +13,9 @@ importers:
'@floating-ui/vue':
specifier: ^1.1.5
version: 1.1.11(vue@3.5.35(typescript@5.9.3))
+ '@tanstack/vue-query':
+ specifier: ^5.101.0
+ version: 5.101.0(vue@3.5.35(typescript@5.9.3))
'@tauri-apps/api':
specifier: ^2
version: 2.11.0
@@ -1086,6 +1089,22 @@ packages:
peerDependencies:
vite: ^5.2.0 || ^6 || ^7 || ^8
+ '@tanstack/match-sorter-utils@8.19.4':
+ resolution: {integrity: sha512-Wo1iKt2b9OT7d+YGhvEPD3DXvPv2etTusIMhMUoG7fbhmxcXCtIjJDEygy91Y2JFlwGyjqiBPRozme7UD8hoqg==}
+ engines: {node: '>=12'}
+
+ '@tanstack/query-core@5.101.0':
+ resolution: {integrity: sha512-cQetA74EB+seWySv1TTKr828TnP0u39m6LykwDXIo84SNortpDkp30TMEjkqtYCNP9c40uT/iwl6MLiufEt0Ow==}
+
+ '@tanstack/vue-query@5.101.0':
+ resolution: {integrity: sha512-sZeW0RvfEZ9QRiaXirE/HQZsFT5saMlPZVfeYvjPX6lqSBS9lkD7wfnCfzOBns6HD2f34Gx9cazkuU3Xj6hl/A==}
+ peerDependencies:
+ '@vue/composition-api': ^1.1.2
+ vue: ^2.6.0 || ^3.3.0
+ peerDependenciesMeta:
+ '@vue/composition-api':
+ optional: true
+
'@tauri-apps/api@2.11.0':
resolution: {integrity: sha512-7CinYODhky9lmO23xHnUFv0Xt43fbtWMyxZcLcRBlFkcgXKuEirBvHpmtJ89YMhyeGcq20Wuc47Fa4XjyniywA==}
@@ -2457,6 +2476,9 @@ packages:
resolution: {integrity: sha512-dLsljMd9sqwRkby8zhO1gSg3PnJIBFid8f4CQj/sXx+7cKx+E7u0PKhZ+U4wmhx7EfmtvnA318oVaIkAB1lRJw==}
hasBin: true
+ remove-accents@0.5.0:
+ resolution: {integrity: sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==}
+
reserved-identifiers@1.2.0:
resolution: {integrity: sha512-yE7KUfFvaBFzGPs5H3Ops1RevfUEsDc5Iz65rOwWg4lE8HJSYtle77uul3+573457oHvBKuHYDl/xqUkKpEEdw==}
engines: {node: '>=18'}
@@ -3373,6 +3395,20 @@ snapshots:
tailwindcss: 4.3.0
vite: 6.4.3(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.100.0)(yaml@2.9.0)
+ '@tanstack/match-sorter-utils@8.19.4':
+ dependencies:
+ remove-accents: 0.5.0
+
+ '@tanstack/query-core@5.101.0': {}
+
+ '@tanstack/vue-query@5.101.0(vue@3.5.35(typescript@5.9.3))':
+ dependencies:
+ '@tanstack/match-sorter-utils': 8.19.4
+ '@tanstack/query-core': 5.101.0
+ '@vue/devtools-api': 6.6.4
+ vue: 3.5.35(typescript@5.9.3)
+ vue-demi: 0.14.10(vue@3.5.35(typescript@5.9.3))
+
'@tauri-apps/api@2.11.0': {}
'@tauri-apps/cli-darwin-arm64@2.11.2':
@@ -4998,6 +5034,8 @@ snapshots:
dependencies:
jsesc: 3.1.0
+ remove-accents@0.5.0: {}
+
reserved-identifiers@1.2.0: {}
resolve-pkg-maps@1.0.0: {}
diff --git a/scripts/generate-sprite.js b/scripts/generate-sprite.js
new file mode 100644
index 0000000..5e79593
--- /dev/null
+++ b/scripts/generate-sprite.js
@@ -0,0 +1,49 @@
+import { readFileSync, writeFileSync, readdirSync } from 'fs'
+import { join, basename } from 'path'
+import { fileURLToPath } from 'url'
+
+const __dirname = fileURLToPath(new URL('.', import.meta.url))
+const iconsDir = join(__dirname, '../src/assets/icons')
+
+const svgFiles = readdirSync(iconsDir)
+ .filter(f => f.endsWith('.svg'))
+ .sort()
+
+if (!svgFiles.length) {
+ console.error('No SVG files found in', iconsDir)
+ process.exit(1)
+}
+
+// Extract viewBox and inner content from each SVG file
+const symbols = svgFiles.map((file) => {
+ const name = basename(file, '.svg')
+ const raw = readFileSync(join(iconsDir, file), 'utf-8')
+
+ const viewBox = (raw.match(/viewBox="([^"]+)"/) ?? [])[1] ?? '0 0 24 24'
+
+ // Everything between
+ const inner = (raw.match(/