diff --git a/frontend/.gitignore b/frontend/.gitignore index 8b055b7a6..1191bbebf 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -24,4 +24,8 @@ yarn-debug.log* yarn-error.log* playwright-report -test-results \ No newline at end of file +test-results + +# auto-generated files +/src/assets/material-symbols-icons.json +/src/assets/material-symbols-icons.d.ts \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 1438432a5..817f7b17e 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -12,6 +12,7 @@ "@atlaskit/pragmatic-drag-and-drop": "^1.7.4", "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", + "@iconify/react": "^6.0.0", "@mantine/core": "^8.0.1", "@mantine/dropzone": "^8.0.1", "@mantine/hooks": "^8.0.1", @@ -29,7 +30,6 @@ "i18next-browser-languagedetector": "^8.1.0", "i18next-http-backend": "^3.0.2", "jszip": "^3.10.1", - "material-symbols": "^0.33.0", "pdf-lib": "^1.17.1", "pdfjs-dist": "^3.11.174", "react": "^19.1.0", @@ -40,6 +40,8 @@ "web-vitals": "^2.1.4" }, "devDependencies": { + "@iconify-json/material-symbols": "^1.2.33", + "@iconify/utils": "^3.0.1", "@playwright/test": "^1.40.0", "@types/node": "^24.2.1", "@types/react": "^19.1.4", @@ -89,6 +91,28 @@ "node": ">=6.0.0" } }, + "node_modules/@antfu/install-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz", + "integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==", + "dev": true, + "dependencies": { + "package-manager-detector": "^1.3.0", + "tinyexec": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@antfu/utils": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-9.2.0.tgz", + "integrity": "sha512-Oq1d9BGZakE/FyoEtcNeSwM7MpDO2vUBi11RWBZXf75zPsbUVWmUs03EqkRFrcgbXyKTas0BdZWC1wcuSoqSAw==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/@asamuzakjp/css-color": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", @@ -1192,6 +1216,104 @@ "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==", "license": "MIT" }, + "node_modules/@iconify-json/material-symbols": { + "version": "1.2.33", + "resolved": "https://registry.npmjs.org/@iconify-json/material-symbols/-/material-symbols-1.2.33.tgz", + "integrity": "sha512-Bs0X1+/vpJydW63olrGh60zkR8/Y70sI14AIWaP7Z6YQXukzWANH4q3I0sIPklbIn1oL6uwLvh0zQyd6Vh79LQ==", + "dev": true, + "dependencies": { + "@iconify/types": "*" + } + }, + "node_modules/@iconify/react": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@iconify/react/-/react-6.0.0.tgz", + "integrity": "sha512-eqNscABVZS8eCpZLU/L5F5UokMS9mnCf56iS1nM9YYHdH8ZxqZL9zyjSwW60IOQFsXZkilbBiv+1paMXBhSQnw==", + "license": "MIT", + "dependencies": { + "@iconify/types": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/cyberalien" + }, + "peerDependencies": { + "react": ">=16" + } + }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "license": "MIT" + }, + "node_modules/@iconify/utils": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.0.1.tgz", + "integrity": "sha512-A78CUEnFGX8I/WlILxJCuIJXloL0j/OJ9PSchPAfCargEIKmUBWvvEMmKWB5oONwiUqlNt+5eRufdkLxeHIWYw==", + "dev": true, + "dependencies": { + "@antfu/install-pkg": "^1.1.0", + "@antfu/utils": "^9.2.0", + "@iconify/types": "^2.0.0", + "debug": "^4.4.1", + "globals": "^15.15.0", + "kolorist": "^1.8.0", + "local-pkg": "^1.1.1", + "mlly": "^1.7.4" + } + }, + "node_modules/@iconify/utils/node_modules/confbox": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", + "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", + "dev": true + }, + "node_modules/@iconify/utils/node_modules/globals": { + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@iconify/utils/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==", + "dev": true, + "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/@iconify/utils/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true + }, + "node_modules/@iconify/utils/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==", + "dev": true, + "dependencies": { + "confbox": "^0.2.2", + "exsolve": "^1.0.7", + "pathe": "^2.0.3" + } + }, "node_modules/@isaacs/fs-minipass": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", @@ -4446,6 +4568,12 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/exsolve": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz", + "integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==", + "dev": true + }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", @@ -5553,6 +5681,12 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/kolorist": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", + "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", + "dev": true + }, "node_modules/license-checker": { "version": "25.0.1", "resolved": "https://registry.npmjs.org/license-checker/-/license-checker-25.0.1.tgz", @@ -6097,12 +6231,6 @@ "semver": "bin/semver.js" } }, - "node_modules/material-symbols": { - "version": "0.33.0", - "resolved": "https://registry.npmjs.org/material-symbols/-/material-symbols-0.33.0.tgz", - "integrity": "sha512-t9/Gz+14fClRgN7oVOt5CBuwsjFLxSNP9BRDyMrI5el3IZNvoD94IDGJha0YYivyAow24rCS0WOkAv4Dp+YjNg==", - "license": "Apache-2.0" - }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -6653,6 +6781,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/package-manager-detector": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.3.0.tgz", + "integrity": "sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==", + "dev": true + }, "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", @@ -7403,6 +7537,22 @@ "node": ">=6" } }, + "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==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ] + }, "node_modules/querystringify": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", @@ -8615,6 +8765,12 @@ "dev": true, "license": "MIT" }, + "node_modules/tinyexec": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz", + "integrity": "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==", + "dev": true + }, "node_modules/tinyglobby": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz", diff --git a/frontend/package.json b/frontend/package.json index cde323bcc..eaa5f20d4 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -8,6 +8,7 @@ "@atlaskit/pragmatic-drag-and-drop": "^1.7.4", "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", + "@iconify/react": "^6.0.0", "@mantine/core": "^8.0.1", "@mantine/dropzone": "^8.0.1", "@mantine/hooks": "^8.0.1", @@ -25,7 +26,6 @@ "i18next-browser-languagedetector": "^8.1.0", "i18next-http-backend": "^3.0.2", "jszip": "^3.10.1", - "material-symbols": "^0.33.0", "pdf-lib": "^1.17.1", "pdfjs-dist": "^3.11.174", "react": "^19.1.0", @@ -36,10 +36,14 @@ "web-vitals": "^2.1.4" }, "scripts": { + "predev": "npm run generate-icons", "dev": "npx tsc --noEmit && vite", + "prebuild": "npm run generate-icons", "build": "npx tsc --noEmit && vite build", "preview": "vite preview", "generate-licenses": "node scripts/generate-licenses.js", + "generate-icons": "node scripts/generate-icons.js", + "generate-icons:verbose": "node scripts/generate-icons.js --verbose", "test": "vitest", "test:watch": "vitest --watch", "test:coverage": "vitest --coverage", @@ -66,6 +70,8 @@ ] }, "devDependencies": { + "@iconify-json/material-symbols": "^1.2.33", + "@iconify/utils": "^3.0.1", "@playwright/test": "^1.40.0", "@types/node": "^24.2.1", "@types/react": "^19.1.4", diff --git a/frontend/scripts/generate-icons.js b/frontend/scripts/generate-icons.js new file mode 100644 index 000000000..681b06728 --- /dev/null +++ b/frontend/scripts/generate-icons.js @@ -0,0 +1,175 @@ +#!/usr/bin/env node + +const { icons } = require('@iconify-json/material-symbols'); +const { getIcons } = require('@iconify/utils'); +const fs = require('fs'); +const path = require('path'); + +// Check for verbose flag +const isVerbose = process.argv.includes('--verbose') || process.argv.includes('-v'); + +// Logging functions +const info = (message) => console.log(message); +const debug = (message) => { + if (isVerbose) { + console.log(message); + } +}; + +// Function to scan codebase for LocalIcon usage +function scanForUsedIcons() { + const usedIcons = new Set(); + const srcDir = path.join(__dirname, '..', 'src'); + + info('🔍 Scanning codebase for LocalIcon usage...'); + + if (!fs.existsSync(srcDir)) { + console.error('❌ Source directory not found:', srcDir); + process.exit(1); + } + + // Recursively scan all .tsx and .ts files + function scanDirectory(dir) { + const files = fs.readdirSync(dir); + + files.forEach(file => { + const filePath = path.join(dir, file); + const stat = fs.statSync(filePath); + + if (stat.isDirectory()) { + scanDirectory(filePath); + } else if (file.endsWith('.tsx') || file.endsWith('.ts')) { + const content = fs.readFileSync(filePath, 'utf8'); + + // Match LocalIcon usage: + const localIconMatches = content.match(/]*icon="([^"]+)"/g); + if (localIconMatches) { + localIconMatches.forEach(match => { + const iconMatch = match.match(/icon="([^"]+)"/); + if (iconMatch) { + usedIcons.add(iconMatch[1]); + debug(` Found: ${iconMatch[1]} in ${path.relative(srcDir, filePath)}`); + } + }); + } + + // Match old material-symbols-rounded spans: icon-name + const spanMatches = content.match(/]*className="[^"]*material-symbols-rounded[^"]*"[^>]*>([^<]+)<\/span>/g); + if (spanMatches) { + spanMatches.forEach(match => { + const iconMatch = match.match(/>([^<]+)<\/span>/); + if (iconMatch && iconMatch[1].trim()) { + const iconName = iconMatch[1].trim(); + usedIcons.add(iconName); + debug(` Found (legacy): ${iconName} in ${path.relative(srcDir, filePath)}`); + } + }); + } + + // Match Icon component usage: + const iconMatches = content.match(/]*icon="material-symbols:([^"]+)"/g); + if (iconMatches) { + iconMatches.forEach(match => { + const iconMatch = match.match(/icon="material-symbols:([^"]+)"/); + if (iconMatch) { + usedIcons.add(iconMatch[1]); + debug(` Found (Icon): ${iconMatch[1]} in ${path.relative(srcDir, filePath)}`); + } + }); + } + } + }); + } + + scanDirectory(srcDir); + + const iconArray = Array.from(usedIcons).sort(); + info(`📋 Found ${iconArray.length} unique icons across codebase`); + + return iconArray; +} + +// Auto-detect used icons +const usedIcons = scanForUsedIcons(); + +// Check if we need to regenerate (compare with existing) +const outputPath = path.join(__dirname, '..', 'src', 'assets', 'material-symbols-icons.json'); +let needsRegeneration = true; + +if (fs.existsSync(outputPath)) { + try { + const existingSet = JSON.parse(fs.readFileSync(outputPath, 'utf8')); + const existingIcons = Object.keys(existingSet.icons || {}).sort(); + const currentIcons = [...usedIcons].sort(); + + if (JSON.stringify(existingIcons) === JSON.stringify(currentIcons)) { + needsRegeneration = false; + info(`✅ Icon set already up-to-date (${usedIcons.length} icons, ${Math.round(fs.statSync(outputPath).size / 1024)}KB)`); + } + } catch (error) { + // If we can't parse existing file, regenerate + needsRegeneration = true; + } +} + +if (!needsRegeneration) { + info('🎉 No regeneration needed!'); + process.exit(0); +} + +info(`🔍 Extracting ${usedIcons.length} icons from Material Symbols...`); + +// Extract only our used icons from the full set +const extractedIcons = getIcons(icons, usedIcons); + +if (!extractedIcons) { + console.error('❌ Failed to extract icons'); + process.exit(1); +} + +// Check for missing icons +const extractedIconNames = Object.keys(extractedIcons.icons || {}); +const missingIcons = usedIcons.filter(icon => !extractedIconNames.includes(icon)); + +if (missingIcons.length > 0) { + info(`âš ī¸ Missing icons (${missingIcons.length}): ${missingIcons.join(', ')}`); + info('💡 These icons don\'t exist in Material Symbols. Please use available alternatives.'); +} + +// Create output directory +const outputDir = path.join(__dirname, '..', 'src', 'assets'); +if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, { recursive: true }); +} + +// Write the extracted icon set to a file (outputPath already defined above) +fs.writeFileSync(outputPath, JSON.stringify(extractedIcons, null, 2)); + +info(`✅ Successfully extracted ${Object.keys(extractedIcons.icons || {}).length} icons`); +info(`đŸ“Ļ Bundle size: ${Math.round(JSON.stringify(extractedIcons).length / 1024)}KB`); +info(`💾 Saved to: ${outputPath}`); + +// Generate TypeScript types +const typesContent = `// Auto-generated icon types +// This file is automatically generated by scripts/generate-icons.js +// Do not edit manually - changes will be overwritten + +export type MaterialSymbolIcon = ${usedIcons.map(icon => `'${icon}'`).join(' | ')}; + +export interface IconSet { + prefix: string; + icons: Record; + width?: number; + height?: number; +} + +// Re-export the icon set as the default export with proper typing +declare const iconSet: IconSet; +export default iconSet; +`; + +const typesPath = path.join(outputDir, 'material-symbols-icons.d.ts'); +fs.writeFileSync(typesPath, typesContent); + +info(`📝 Generated types: ${typesPath}`); +info(`🎉 Icon extraction complete!`); \ No newline at end of file diff --git a/frontend/src/assets/3rdPartyLicenses.json b/frontend/src/assets/3rdPartyLicenses.json index 0235380af..2f19f5db6 100644 --- a/frontend/src/assets/3rdPartyLicenses.json +++ b/frontend/src/assets/3rdPartyLicenses.json @@ -21,6 +21,13 @@ "moduleLicense": "Apache-2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, + { + "moduleName": "@atlaskit/pragmatic-drag-and-drop", + "moduleUrl": "https://github.com/atlassian/pragmatic-drag-and-drop", + "moduleVersion": "1.7.4", + "moduleLicense": "Apache-2.0", + "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" + }, { "moduleName": "@babel/code-frame", "moduleUrl": "https://github.com/babel/babel", @@ -59,7 +66,7 @@ { "moduleName": "@babel/parser", "moduleUrl": "https://github.com/babel/babel", - "moduleVersion": "7.27.3", + "moduleVersion": "7.28.3", "moduleLicense": "MIT", "moduleLicenseUrl": "https://opensource.org/licenses/MIT" }, @@ -87,7 +94,7 @@ { "moduleName": "@babel/types", "moduleUrl": "https://github.com/babel/babel", - "moduleVersion": "7.27.3", + "moduleVersion": "7.28.2", "moduleLicense": "MIT", "moduleLicenseUrl": "https://opensource.org/licenses/MIT" }, @@ -217,6 +224,20 @@ "moduleLicense": "MIT", "moduleLicenseUrl": "https://opensource.org/licenses/MIT" }, + { + "moduleName": "@iconify/react", + "moduleUrl": "https://github.com/iconify/iconify", + "moduleVersion": "6.0.0", + "moduleLicense": "MIT", + "moduleLicenseUrl": "https://opensource.org/licenses/MIT" + }, + { + "moduleName": "@iconify/types", + "moduleUrl": "https://github.com/iconify/iconify", + "moduleVersion": "2.0.0", + "moduleLicense": "MIT", + "moduleLicenseUrl": "https://opensource.org/licenses/MIT" + }, { "moduleName": "@isaacs/fs-minipass", "moduleUrl": "https://github.com/npm/fs-minipass", @@ -399,6 +420,20 @@ "moduleLicense": "MIT", "moduleLicenseUrl": "https://opensource.org/licenses/MIT" }, + { + "moduleName": "@tanstack/react-virtual", + "moduleUrl": "https://github.com/TanStack/virtual", + "moduleVersion": "3.13.12", + "moduleLicense": "MIT", + "moduleLicenseUrl": "https://opensource.org/licenses/MIT" + }, + { + "moduleName": "@tanstack/virtual-core", + "moduleUrl": "https://github.com/TanStack/virtual", + "moduleVersion": "3.13.12", + "moduleLicense": "MIT", + "moduleLicenseUrl": "https://opensource.org/licenses/MIT" + }, { "moduleName": "@testing-library/dom", "moduleUrl": "https://github.com/testing-library/dom-testing-library", @@ -567,6 +602,13 @@ "moduleLicense": "MIT", "moduleLicenseUrl": "https://opensource.org/licenses/MIT" }, + { + "moduleName": "bind-event-listener", + "moduleUrl": "https://github.com/alexreardon/bind-event-listener", + "moduleVersion": "3.0.0", + "moduleLicense": "MIT", + "moduleLicenseUrl": "https://opensource.org/licenses/MIT" + }, { "moduleName": "brace-expansion", "moduleUrl": "https://github.com/juliangruber/brace-expansion", @@ -1246,13 +1288,6 @@ "moduleLicense": "MIT", "moduleLicenseUrl": "https://opensource.org/licenses/MIT" }, - { - "moduleName": "material-symbols", - "moduleUrl": "https://github.com/marella/material-symbols", - "moduleVersion": "0.33.0", - "moduleLicense": "Apache-2.0", - "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" - }, { "moduleName": "math-intrinsics", "moduleUrl": "https://github.com/es-shims/math-intrinsics", @@ -1494,7 +1529,7 @@ { "moduleName": "postcss", "moduleUrl": "https://github.com/postcss/postcss", - "moduleVersion": "8.5.3", + "moduleVersion": "8.5.6", "moduleLicense": "MIT", "moduleLicenseUrl": "https://opensource.org/licenses/MIT" }, @@ -1526,6 +1561,13 @@ "moduleLicense": "MIT", "moduleLicenseUrl": "https://opensource.org/licenses/MIT" }, + { + "moduleName": "raf-schd", + "moduleUrl": "https://github.com/alexreardon/raf-schd", + "moduleVersion": "4.0.3", + "moduleLicense": "MIT", + "moduleLicenseUrl": "https://opensource.org/licenses/MIT" + }, { "moduleName": "react-dom", "moduleUrl": "https://github.com/facebook/react", diff --git a/frontend/src/components/shared/LandingPage.tsx b/frontend/src/components/shared/LandingPage.tsx index 937e9cfbd..14322076e 100644 --- a/frontend/src/components/shared/LandingPage.tsx +++ b/frontend/src/components/shared/LandingPage.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { Container, Text, Button, Checkbox, Group, useMantineColorScheme } from '@mantine/core'; import { Dropzone } from '@mantine/dropzone'; -import AddIcon from '@mui/icons-material/Add'; +import LocalIcon from './LocalIcon'; import { useTranslation } from 'react-i18next'; import { useFileHandler } from '../../hooks/useFileHandler'; import { useFilesModalContext } from '../../contexts/FilesModalContext'; @@ -138,7 +138,7 @@ const LandingPage = () => { onClick={handleOpenFilesModal} onMouseEnter={() => setIsUploadHover(false)} > - + {!isUploadHover && ( {t('landing.addFiles', 'Add Files')} @@ -165,7 +165,7 @@ const LandingPage = () => { onClick={handleNativeUploadClick} onMouseEnter={() => setIsUploadHover(true)} > - upload + {isUploadHover && ( {t('landing.uploadFromComputer', 'Upload from computer')} diff --git a/frontend/src/components/shared/LanguageSelector.tsx b/frontend/src/components/shared/LanguageSelector.tsx index 1d9e5b2dc..d3a346a8e 100644 --- a/frontend/src/components/shared/LanguageSelector.tsx +++ b/frontend/src/components/shared/LanguageSelector.tsx @@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react'; import { Menu, Button, ScrollArea, ActionIcon } from '@mantine/core'; import { useTranslation } from 'react-i18next'; import { supportedLanguages } from '../../i18n'; -import LanguageIcon from '@mui/icons-material/Language'; +import LocalIcon from './LocalIcon'; import styles from './LanguageSelector.module.css'; interface LanguageSelectorProps { @@ -105,13 +105,13 @@ const LanguageSelector = ({ position = 'bottom-start', offset = 8, compact = fal } }} > - language + ) : ( )} diff --git a/frontend/src/components/shared/Tooltip.tsx b/frontend/src/components/shared/Tooltip.tsx index 4c216d318..7940112ca 100644 --- a/frontend/src/components/shared/Tooltip.tsx +++ b/frontend/src/components/shared/Tooltip.tsx @@ -1,5 +1,6 @@ import React, { useState, useRef, useEffect } from 'react'; import { createPortal } from 'react-dom'; +import LocalIcon from './LocalIcon'; import { isClickOutside, addEventListenerWithCleanup } from '../../utils/genericUtils'; import { useTooltipPosition } from '../../hooks/useTooltipPosition'; import { TooltipTip } from '../../types/tips'; @@ -171,9 +172,7 @@ export const Tooltip: React.FC = ({ }} title="Close tooltip" > - - close - + )} {arrow && getArrowClass() && ( diff --git a/frontend/src/components/tools/ToolPicker.tsx b/frontend/src/components/tools/ToolPicker.tsx index d81bf5ef0..9a46c8a3e 100644 --- a/frontend/src/components/tools/ToolPicker.tsx +++ b/frontend/src/components/tools/ToolPicker.tsx @@ -25,19 +25,39 @@ const ToolPicker = ({ selectedToolKey, onSelect, filteredTools, isSearching = fa const quickAccessRef = useRef(null); const allToolsRef = useRef(null); - // On resize adjust headers height to offset height + // Keep header heights in sync with any dynamic size changes useLayoutEffect(() => { const update = () => { if (quickHeaderRef.current) { - setQuickHeaderHeight(quickHeaderRef.current.offsetHeight); + setQuickHeaderHeight(quickHeaderRef.current.offsetHeight || 0); } if (allHeaderRef.current) { - setAllHeaderHeight(allHeaderRef.current.offsetHeight); + setAllHeaderHeight(allHeaderRef.current.offsetHeight || 0); } }; + update(); + + // Update on window resize window.addEventListener("resize", update); - return () => window.removeEventListener("resize", update); + + // Update on element resize (e.g., font load, badge count change, zoom) + const observers: ResizeObserver[] = []; + if (typeof ResizeObserver !== "undefined") { + const observe = (el: HTMLDivElement | null, cb: () => void) => { + if (!el) return; + const ro = new ResizeObserver(() => cb()); + ro.observe(el); + observers.push(ro); + }; + observe(quickHeaderRef.current, update); + observe(allHeaderRef.current, update); + } + + return () => { + window.removeEventListener("resize", update); + observers.forEach(o => o.disconnect()); + }; }, []); const { sections: visibleSections } = useToolSections(filteredTools); @@ -152,7 +172,7 @@ const ToolPicker = ({ selectedToolKey, onSelect, filteredTools, isSearching = fa ref={allHeaderRef} style={{ position: "sticky", - top: quickSection ? quickHeaderHeight - 1: 0, + top: quickSection ? quickHeaderHeight -1 : 0, zIndex: 2, borderTop: `0.0625rem solid var(--tool-header-border)`, borderBottom: `0.0625rem solid var(--tool-header-border)`, diff --git a/frontend/src/components/tools/shared/ToolStep.tsx b/frontend/src/components/tools/shared/ToolStep.tsx index c2ff1d5f6..9d86d2f03 100644 --- a/frontend/src/components/tools/shared/ToolStep.tsx +++ b/frontend/src/components/tools/shared/ToolStep.tsx @@ -1,7 +1,6 @@ import React, { createContext, useContext, useMemo, useRef } from 'react'; import { Text, Stack, Box, Flex, Divider } from '@mantine/core'; -import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; -import ChevronRightIcon from '@mui/icons-material/ChevronRight'; +import LocalIcon from '../../shared/LocalIcon'; import { Tooltip } from '../../shared/Tooltip'; import { TooltipTip } from '../../../types/tips'; import { createFilesToolStep, FilesToolStepProps } from './FilesToolStep'; @@ -54,9 +53,7 @@ const renderTooltipTitle = ( {title} - - gpp_maybe - + ); @@ -125,14 +122,12 @@ const ToolStep = ({ {isCollapsed ? ( - ) : ( - diff --git a/frontend/src/components/tools/shared/ToolWorkflowTitle.tsx b/frontend/src/components/tools/shared/ToolWorkflowTitle.tsx index 31c305d4a..6ed949442 100644 --- a/frontend/src/components/tools/shared/ToolWorkflowTitle.tsx +++ b/frontend/src/components/tools/shared/ToolWorkflowTitle.tsx @@ -1,5 +1,6 @@ import React from 'react'; import { Flex, Text, Divider } from '@mantine/core'; +import LocalIcon from '../../shared/LocalIcon'; import { Tooltip } from '../../shared/Tooltip'; export interface ToolWorkflowTitleProps { @@ -29,9 +30,7 @@ export function ToolWorkflowTitle({ title, tooltip }: ToolWorkflowTitleProps) { {title} - - gpp_maybe - + diff --git a/frontend/src/components/tools/toolPicker/ToolSearch.tsx b/frontend/src/components/tools/toolPicker/ToolSearch.tsx index c17784a52..774126aa2 100644 --- a/frontend/src/components/tools/toolPicker/ToolSearch.tsx +++ b/frontend/src/components/tools/toolPicker/ToolSearch.tsx @@ -1,6 +1,7 @@ import React, { useState, useRef, useEffect, useMemo } from "react"; import { Stack, Button, Text } from "@mantine/core"; import { useTranslation } from "react-i18next"; +import LocalIcon from '../../shared/LocalIcon'; import { ToolRegistryEntry } from "../../../data/toolsTaxonomy"; import { TextInput } from "../../shared/TextInput"; import './ToolPicker.css'; @@ -74,7 +75,7 @@ const ToolSearch = ({ value={value} onChange={handleSearchChange} placeholder={placeholder || t("toolPicker.searchPlaceholder", "Search tools...")} - icon={hideIcon ? undefined : search} + icon={hideIcon ? undefined : } autoComplete="off" /> diff --git a/frontend/src/data/useTranslatedToolRegistry.tsx b/frontend/src/data/useTranslatedToolRegistry.tsx index 3c3b5e89b..db8b2cf23 100644 --- a/frontend/src/data/useTranslatedToolRegistry.tsx +++ b/frontend/src/data/useTranslatedToolRegistry.tsx @@ -1,4 +1,5 @@ import React, { useMemo } from 'react'; +import LocalIcon from '../components/shared/LocalIcon'; import { useTranslation } from 'react-i18next'; import SplitPdfPanel from "../tools/Split"; import CompressPdfPanel from "../tools/Compress"; @@ -50,7 +51,7 @@ export function useFlatToolRegistry(): ToolRegistry { // Signing "certSign": { - icon: workspace_premium, + icon: , name: t("home.certSign.title", "Sign with Certificate"), component: null, view: "sign", @@ -59,7 +60,7 @@ export function useFlatToolRegistry(): ToolRegistry { subcategoryId: SubcategoryId.SIGNING }, "sign": { - icon: signature, + icon: , name: t("home.sign.title", "Sign"), component: null, view: "sign", @@ -72,7 +73,7 @@ export function useFlatToolRegistry(): ToolRegistry { // Document Security "addPassword": { - icon: password, + icon: , name: t("home.addPassword.title", "Add Password"), component: AddPassword, view: "security", @@ -85,7 +86,7 @@ export function useFlatToolRegistry(): ToolRegistry { settingsComponent: AddPasswordSettings }, "watermark": { - icon: branding_watermark, + icon: , name: t("home.watermark.title", "Add Watermark"), component: AddWatermark, view: "format", @@ -98,7 +99,7 @@ export function useFlatToolRegistry(): ToolRegistry { settingsComponent: AddWatermarkSingleStepSettings }, "add-stamp": { - icon: approval, + icon: , name: t("home.AddStampRequest.title", "Add Stamp to PDF"), component: null, view: "format", @@ -107,7 +108,7 @@ export function useFlatToolRegistry(): ToolRegistry { subcategoryId: SubcategoryId.DOCUMENT_SECURITY }, "sanitize": { - icon: cleaning_services, + icon: , name: t("home.sanitize.title", "Sanitize"), component: Sanitize, view: "security", @@ -120,7 +121,7 @@ export function useFlatToolRegistry(): ToolRegistry { settingsComponent: SanitizeSettings }, "flatten": { - icon: layers_clear, + icon: , name: t("home.flatten.title", "Flatten"), component: null, view: "format", @@ -129,7 +130,7 @@ export function useFlatToolRegistry(): ToolRegistry { subcategoryId: SubcategoryId.DOCUMENT_SECURITY }, "unlock-pdf-forms": { - icon: preview_off, + icon: , name: t("home.unlockPDFForms.title", "Unlock PDF Forms"), component: UnlockPdfForms, view: "security", @@ -142,7 +143,7 @@ export function useFlatToolRegistry(): ToolRegistry { settingsComponent: UnlockPdfFormsSettings }, "manage-certificates": { - icon: license, + icon: , name: t("home.manageCertificates.title", "Manage Certificates"), component: null, view: "security", @@ -151,7 +152,7 @@ export function useFlatToolRegistry(): ToolRegistry { subcategoryId: SubcategoryId.DOCUMENT_SECURITY }, "change-permissions": { - icon: lock, + icon: , name: t("home.changePermissions.title", "Change Permissions"), component: ChangePermissions, view: "security", @@ -166,7 +167,7 @@ export function useFlatToolRegistry(): ToolRegistry { // Verification "get-all-info-on-pdf": { - icon: fact_check, + icon: , name: t("home.getPdfInfo.title", "Get ALL Info on PDF"), component: null, view: "extract", @@ -175,7 +176,7 @@ export function useFlatToolRegistry(): ToolRegistry { subcategoryId: SubcategoryId.VERIFICATION }, "validate-pdf-signature": { - icon: verified, + icon: , name: t("home.validateSignature.title", "Validate PDF Signature"), component: null, view: "security", @@ -188,7 +189,7 @@ export function useFlatToolRegistry(): ToolRegistry { // Document Review "read": { - icon: article, + icon: , name: t("home.read.title", "Read"), component: null, view: "view", @@ -197,7 +198,7 @@ export function useFlatToolRegistry(): ToolRegistry { subcategoryId: SubcategoryId.DOCUMENT_REVIEW }, "change-metadata": { - icon: assignment, + icon: , name: t("home.changeMetadata.title", "Change Metadata"), component: null, view: "format", @@ -208,7 +209,7 @@ export function useFlatToolRegistry(): ToolRegistry { // Page Formatting "cropPdf": { - icon: crop, + icon: , name: t("home.crop.title", "Crop PDF"), component: null, view: "format", @@ -217,7 +218,7 @@ export function useFlatToolRegistry(): ToolRegistry { subcategoryId: SubcategoryId.PAGE_FORMATTING }, "rotate": { - icon: rotate_right, + icon: , name: t("home.rotate.title", "Rotate"), component: null, view: "format", @@ -226,7 +227,7 @@ export function useFlatToolRegistry(): ToolRegistry { subcategoryId: SubcategoryId.PAGE_FORMATTING }, "splitPdf": { - icon: content_cut, + icon: , name: t("home.split.title", "Split"), component: SplitPdfPanel, view: "split", @@ -237,7 +238,7 @@ export function useFlatToolRegistry(): ToolRegistry { settingsComponent: SplitSettings }, "reorganize-pages": { - icon: move_down, + icon: , name: t("home.reorganizePages.title", "Reorganize Pages"), component: null, view: "pageEditor", @@ -246,7 +247,7 @@ export function useFlatToolRegistry(): ToolRegistry { subcategoryId: SubcategoryId.PAGE_FORMATTING }, "adjust-page-size-scale": { - icon: crop_free, + icon: , name: t("home.scalePages.title", "Adjust page size/scale"), component: null, view: "format", @@ -255,7 +256,7 @@ export function useFlatToolRegistry(): ToolRegistry { subcategoryId: SubcategoryId.PAGE_FORMATTING }, "addPageNumbers": { - icon: 123, + icon: , name: t("home.addPageNumbers.title", "Add Page Numbers"), component: null, view: "format", @@ -264,7 +265,7 @@ export function useFlatToolRegistry(): ToolRegistry { subcategoryId: SubcategoryId.PAGE_FORMATTING }, "multi-page-layout": { - icon: dashboard, + icon: , name: t("home.pageLayout.title", "Multi-Page Layout"), component: null, view: "format", @@ -273,7 +274,7 @@ export function useFlatToolRegistry(): ToolRegistry { subcategoryId: SubcategoryId.PAGE_FORMATTING }, "single-large-page": { - icon: looks_one, + icon: , name: t("home.pdfToSinglePage.title", "PDF to Single Large Page"), component: SingleLargePage, view: "format", @@ -285,7 +286,7 @@ export function useFlatToolRegistry(): ToolRegistry { operationConfig: singleLargePageOperationConfig }, "add-attachments": { - icon: attachment, + icon: , name: t("home.attachments.title", "Add Attachments"), component: null, view: "format", @@ -298,7 +299,7 @@ export function useFlatToolRegistry(): ToolRegistry { // Extraction "extractPages": { - icon: upload, + icon: , name: t("home.extractPages.title", "Extract Pages"), component: null, view: "extract", @@ -307,7 +308,7 @@ export function useFlatToolRegistry(): ToolRegistry { subcategoryId: SubcategoryId.EXTRACTION }, "extract-images": { - icon: filter, + icon: , name: t("home.extractImages.title", "Extract Images"), component: null, view: "extract", @@ -320,7 +321,7 @@ export function useFlatToolRegistry(): ToolRegistry { // Removal "removePages": { - icon: delete, + icon: , name: t("home.removePages.title", "Remove Pages"), component: null, view: "remove", @@ -329,7 +330,7 @@ export function useFlatToolRegistry(): ToolRegistry { subcategoryId: SubcategoryId.REMOVAL }, "remove-blank-pages": { - icon: scan_delete, + icon: , name: t("home.removeBlanks.title", "Remove Blank Pages"), component: null, view: "remove", @@ -338,7 +339,7 @@ export function useFlatToolRegistry(): ToolRegistry { subcategoryId: SubcategoryId.REMOVAL }, "remove-annotations": { - icon: thread_unread, + icon: , name: t("home.removeAnnotations.title", "Remove Annotations"), component: null, view: "remove", @@ -347,7 +348,7 @@ export function useFlatToolRegistry(): ToolRegistry { subcategoryId: SubcategoryId.REMOVAL }, "remove-image": { - icon: remove_selection, + icon: , name: t("home.removeImagePdf.title", "Remove Image"), component: null, view: "format", @@ -356,7 +357,7 @@ export function useFlatToolRegistry(): ToolRegistry { subcategoryId: SubcategoryId.REMOVAL }, "remove-password": { - icon: lock_open_right, + icon: , name: t("home.removePassword.title", "Remove Password"), component: RemovePassword, view: "security", @@ -369,7 +370,7 @@ export function useFlatToolRegistry(): ToolRegistry { settingsComponent: RemovePasswordSettings }, "remove-certificate-sign": { - icon: remove_moderator, + icon: , name: t("home.removeCertSign.title", "Remove Certificate Sign"), component: RemoveCertificateSign, view: "security", @@ -385,7 +386,7 @@ export function useFlatToolRegistry(): ToolRegistry { // Automation "automate": { - icon: automation, + icon: , name: t("home.automate.title", "Automate"), component: React.lazy(() => import('../tools/Automate')), view: "format", @@ -396,7 +397,7 @@ export function useFlatToolRegistry(): ToolRegistry { endpoints: ["handleData"] }, "auto-rename-pdf-file": { - icon: match_word, + icon: , name: t("home.auto-rename.title", "Auto Rename PDF File"), component: null, view: "format", @@ -405,7 +406,7 @@ export function useFlatToolRegistry(): ToolRegistry { subcategoryId: SubcategoryId.AUTOMATION }, "auto-split-pages": { - icon: split_scene_right, + icon: , name: t("home.autoSplitPDF.title", "Auto Split Pages"), component: null, view: "format", @@ -414,7 +415,7 @@ export function useFlatToolRegistry(): ToolRegistry { subcategoryId: SubcategoryId.AUTOMATION }, "auto-split-by-size-count": { - icon: content_cut, + icon: , name: t("home.autoSizeSplitPDF.title", "Auto Split by Size/Count"), component: null, view: "format", @@ -427,7 +428,7 @@ export function useFlatToolRegistry(): ToolRegistry { // Advanced Formatting "adjustContrast": { - icon: palette, + icon: , name: t("home.adjustContrast.title", "Adjust Colors/Contrast"), component: null, view: "format", @@ -436,7 +437,7 @@ export function useFlatToolRegistry(): ToolRegistry { subcategoryId: SubcategoryId.ADVANCED_FORMATTING }, "repair": { - icon: build, + icon: , name: t("home.repair.title", "Repair"), component: Repair, view: "format", @@ -449,7 +450,7 @@ export function useFlatToolRegistry(): ToolRegistry { settingsComponent: RepairSettings }, "detect-split-scanned-photos": { - icon: scanner, + icon: , name: t("home.ScannerImageSplit.title", "Detect & Split Scanned Photos"), component: null, view: "format", @@ -458,7 +459,7 @@ export function useFlatToolRegistry(): ToolRegistry { subcategoryId: SubcategoryId.ADVANCED_FORMATTING }, "overlay-pdfs": { - icon: layers, + icon: , name: t("home.overlay-pdfs.title", "Overlay PDFs"), component: null, view: "format", @@ -467,7 +468,7 @@ export function useFlatToolRegistry(): ToolRegistry { subcategoryId: SubcategoryId.ADVANCED_FORMATTING }, "replace-and-invert-color": { - icon: format_color_fill, + icon: , name: t("home.replaceColorPdf.title", "Replace & Invert Color"), component: null, view: "format", @@ -476,7 +477,7 @@ export function useFlatToolRegistry(): ToolRegistry { subcategoryId: SubcategoryId.ADVANCED_FORMATTING }, "add-image": { - icon: image, + icon: , name: t("home.addImage.title", "Add Image"), component: null, view: "format", @@ -485,7 +486,7 @@ export function useFlatToolRegistry(): ToolRegistry { subcategoryId: SubcategoryId.ADVANCED_FORMATTING }, "edit-table-of-contents": { - icon: bookmark_add, + icon: , name: t("home.editTableOfContents.title", "Edit Table of Contents"), component: null, view: "format", @@ -494,7 +495,7 @@ export function useFlatToolRegistry(): ToolRegistry { subcategoryId: SubcategoryId.ADVANCED_FORMATTING }, "scanner-effect": { - icon: scanner, + icon: , name: t("home.fakeScan.title", "Scanner Effect"), component: null, view: "format", @@ -507,7 +508,7 @@ export function useFlatToolRegistry(): ToolRegistry { // Developer Tools "show-javascript": { - icon: javascript, + icon: , name: t("home.showJS.title", "Show JavaScript"), component: null, view: "extract", @@ -516,7 +517,7 @@ export function useFlatToolRegistry(): ToolRegistry { subcategoryId: SubcategoryId.DEVELOPER_TOOLS }, "dev-api": { - icon: open_in_new, + icon: , name: t("home.devApi.title", "API"), component: null, view: "external", @@ -526,7 +527,7 @@ export function useFlatToolRegistry(): ToolRegistry { link: "https://stirlingpdf.io/swagger-ui/5.21.0/index.html" }, "dev-folder-scanning": { - icon: open_in_new, + icon: , name: t("home.devFolderScanning.title", "Automated Folder Scanning"), component: null, view: "external", @@ -536,7 +537,7 @@ export function useFlatToolRegistry(): ToolRegistry { link: "https://docs.stirlingpdf.com/Advanced%20Configuration/Folder%20Scanning/" }, "dev-sso-guide": { - icon: open_in_new, + icon: , name: t("home.devSsoGuide.title", "SSO Guide"), component: null, view: "external", @@ -546,7 +547,7 @@ export function useFlatToolRegistry(): ToolRegistry { link: "https://docs.stirlingpdf.com/Advanced%20Configuration/Single%20Sign-On%20Configuration", }, "dev-airgapped": { - icon: open_in_new, + icon: , name: t("home.devAirgapped.title", "Air-gapped Setup"), component: null, view: "external", @@ -559,7 +560,7 @@ export function useFlatToolRegistry(): ToolRegistry { // Recommended Tools "compare": { - icon: compare, + icon: , name: t("home.compare.title", "Compare"), component: null, view: "format", @@ -568,7 +569,7 @@ export function useFlatToolRegistry(): ToolRegistry { subcategoryId: SubcategoryId.GENERAL }, "compress": { - icon: zoom_in_map, + icon: , name: t("home.compress.title", "Compress"), component: CompressPdfPanel, view: "compress", @@ -580,7 +581,7 @@ export function useFlatToolRegistry(): ToolRegistry { settingsComponent: CompressSettings }, "convert": { - icon: sync_alt, + icon: , name: t("home.convert.title", "Convert"), component: ConvertPanel, view: "convert", @@ -626,7 +627,7 @@ export function useFlatToolRegistry(): ToolRegistry { settingsComponent: ConvertSettings }, "mergePdfs": { - icon: library_add, + icon: , name: t("home.merge.title", "Merge"), component: null, view: "merge", @@ -636,7 +637,7 @@ export function useFlatToolRegistry(): ToolRegistry { maxFiles: -1 }, "multi-tool": { - icon: dashboard_customize, + icon: , name: t("home.multiTool.title", "Multi-Tool"), component: null, view: "pageEditor", @@ -646,7 +647,7 @@ export function useFlatToolRegistry(): ToolRegistry { maxFiles: -1 }, "ocr": { - icon: quick_reference_all, + icon: , name: t("home.ocr.title", "OCR"), component: OCRPanel, view: "convert", @@ -658,7 +659,7 @@ export function useFlatToolRegistry(): ToolRegistry { settingsComponent: OCRSettings }, "redact": { - icon: visibility_off, + icon: , name: t("home.redact.title", "Redact"), component: null, view: "redact", diff --git a/frontend/src/global.d.ts b/frontend/src/global.d.ts index eb4b5d6c2..5511059a8 100644 --- a/frontend/src/global.d.ts +++ b/frontend/src/global.d.ts @@ -4,4 +4,15 @@ declare module "../components/PageEditor"; declare module "../components/Viewer"; declare module "*.js"; declare module '*.module.css'; -declare module 'pdfjs-dist'; \ No newline at end of file +declare module 'pdfjs-dist'; + +// Auto-generated icon set JSON import +declare module '../assets/material-symbols-icons.json' { + const value: { + prefix: string; + icons: Record; + width?: number; + height?: number; + }; + export default value; +} \ No newline at end of file diff --git a/frontend/src/hooks/tools/automate/useSuggestedAutomations.ts b/frontend/src/hooks/tools/automate/useSuggestedAutomations.ts index 9ddce1e0b..006c9f179 100644 --- a/frontend/src/hooks/tools/automate/useSuggestedAutomations.ts +++ b/frontend/src/hooks/tools/automate/useSuggestedAutomations.ts @@ -1,11 +1,15 @@ import { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; -import StarIcon from '@mui/icons-material/Star'; -import CompressIcon from '@mui/icons-material/Compress'; -import SecurityIcon from '@mui/icons-material/Security'; -import TextFieldsIcon from '@mui/icons-material/TextFields'; +import React from 'react'; +import LocalIcon from '../../../components/shared/LocalIcon'; import { SuggestedAutomation } from '../../../types/automation'; +// Create icon components +const CompressIcon = () => React.createElement(LocalIcon, { icon: 'compress', width: '1.5rem', height: '1.5rem' }); +const TextFieldsIcon = () => React.createElement(LocalIcon, { icon: 'text-fields', width: '1.5rem', height: '1.5rem' }); +const SecurityIcon = () => React.createElement(LocalIcon, { icon: 'security', width: '1.5rem', height: '1.5rem' }); +const StarIcon = () => React.createElement(LocalIcon, { icon: 'star', width: '1.5rem', height: '1.5rem' }); + export function useSuggestedAutomations(): SuggestedAutomation[] { const { t } = useTranslation(); diff --git a/frontend/src/index.css b/frontend/src/index.css index f7e5e0865..ec2585e8c 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -1,9 +1,3 @@ -@import 'material-symbols/rounded.css'; - -.material-symbols-rounded { - font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24; -} - body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 215a9378b..6886183a1 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -42,7 +42,7 @@ // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ // "noUncheckedSideEffectImports": true, /* Check side effect imports. */ - // "resolveJsonModule": true, /* Enable importing .json files. */ + "resolveJsonModule": true, /* Enable importing .json files. */ // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */