Repository: antfu-collective/icones Branch: main Commit: 0bc591826236 Files: 98 Total size: 172.1 KB Directory structure: gitextract_bmvhmq2c/ ├── .github/ │ ├── renovate.json │ └── workflows/ │ └── ci.yml ├── .gitignore ├── .npmrc ├── .vscode/ │ └── settings.json ├── LICENSE ├── README.md ├── electron/ │ ├── build/ │ │ └── icon.icns │ ├── electron-builder.json5 │ ├── eslint.config.js │ ├── package.json │ └── src/ │ ├── main/ │ │ └── index.ts │ └── renderer/ │ └── index.js ├── eslint.config.js ├── index.html ├── netlify.toml ├── package.json ├── pnpm-workspace.yaml ├── public/ │ └── search.xml ├── scripts/ │ └── prepare.ts ├── src/ │ ├── App.vue │ ├── auto-imports.d.ts │ ├── components/ │ │ ├── ActionsMenu.vue │ │ ├── Bag.vue │ │ ├── CollectionEntries.vue │ │ ├── CollectionEntry.vue │ │ ├── ColorPicker.vue │ │ ├── CustomSelect.vue │ │ ├── DarkSwitcher.vue │ │ ├── Drawer.vue │ │ ├── FAB.vue │ │ ├── Footer.vue │ │ ├── HelpPage.vue │ │ ├── Icon.vue │ │ ├── IconButton.vue │ │ ├── IconDetail.vue │ │ ├── IconSet.vue │ │ ├── Icons.vue │ │ ├── InstallIconSet.vue │ │ ├── Modal.vue │ │ ├── ModalDialog.vue │ │ ├── Navbar.vue │ │ ├── Notification.vue │ │ ├── Progress.vue │ │ ├── SearchBar.vue │ │ ├── SettingsCollectionsList.vue │ │ ├── SnippetPreview.vue │ │ ├── WithNavbar.vue │ │ └── electron/ │ │ ├── NavElectron.vue │ │ ├── NavPlaceholder.vue │ │ └── SearchElectron.vue │ ├── components.d.ts │ ├── data/ │ │ ├── index.ts │ │ ├── search-alias.ts │ │ └── variant-category.ts │ ├── env.ts │ ├── hooks/ │ │ ├── color.ts │ │ ├── index.ts │ │ └── search.ts │ ├── html.d.ts │ ├── main.css │ ├── main.ts │ ├── pages/ │ │ ├── [...all].vue │ │ ├── collection/ │ │ │ └── [id].vue │ │ ├── index.vue │ │ └── settings.vue │ ├── shims.d.ts │ ├── store/ │ │ ├── collection.ts │ │ ├── dark.ts │ │ ├── dialog.ts │ │ ├── index.ts │ │ ├── indexedDB.ts │ │ ├── localstorage.ts │ │ ├── packing.ts │ │ └── progress.ts │ ├── sw.ts │ └── utils/ │ ├── case.ts │ ├── dataUrlToBlob.ts │ ├── electron.ts │ ├── icons.ts │ ├── pack-worker-client.ts │ ├── pack.ts │ ├── query.ts │ ├── sample.ts │ ├── shiki.ts │ ├── svg/ │ │ ├── base64.ts │ │ ├── bufferToString.ts │ │ ├── helpers.ts │ │ ├── htmlToJsx.ts │ │ ├── index.ts │ │ ├── loader.ts │ │ └── prettier.ts │ ├── svgToPng.ts │ └── worker/ │ ├── index.ts │ └── types.ts ├── tsconfig.json ├── unocss.config.ts └── vite.config.ts ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/renovate.json ================================================ { "extends": [ "config:recommended" ], "rangeStrategy": "bump", "packageRules": [ { "description": "Group all non-major updates weekly except @iconify/json", "extends": ["schedule:weekly"], "matchPackagePatterns": ["*"], "excludePackageNames": ["@iconify/json"], "matchUpdateTypes": ["minor", "patch"], "groupName": "all non-major dependencies", "groupSlug": "all-minor-patch" }, { "description": "Create non-major @iconify/json updates daily", "matchUpdateTypes": ["minor", "patch"], "matchPackagePatterns": ["^@iconify/json"], "extends": ["schedule:daily"], "automerge": true }, { "description": "Suppress major updates using Dependency Dashboard", "matchUpdateTypes": ["major"], "dependencyDashboardApproval": true } ] } ================================================ FILE: .github/workflows/ci.yml ================================================ name: CI on: push: branches: - main pull_request: branches: - main jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install pnpm uses: pnpm/action-setup@v4 - name: Set node uses: actions/setup-node@v4 with: node-version: lts/* cache: pnpm - name: Install run: pnpm install - name: Lint run: pnpm run lint typecheck: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install pnpm uses: pnpm/action-setup@v4 - name: Set node uses: actions/setup-node@v4 with: node-version: lts/* cache: pnpm - name: Install run: pnpm install - name: Typecheck run: pnpm run typecheck build: runs-on: ${{ matrix.os }} strategy: matrix: node-version: [lts/*] os: [ubuntu-latest] fail-fast: false steps: - uses: actions/checkout@v4 - name: Install pnpm uses: pnpm/action-setup@v4 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ cache: pnpm - run: pnpm install - name: Build run: pnpm run build ================================================ FILE: .gitignore ================================================ node_modules yarn-error.log dist .idea src/assets/collections.json .DS_Store public/collections public/lib release collections-info.json collections-meta.json dist-electron dev-dist ================================================ FILE: .npmrc ================================================ shamefully-hoist=true ignore-workspace-root-check=true shell-emulator=true ================================================ FILE: .vscode/settings.json ================================================ { "cSpell.words": [ "icones" ], // Enable the ESlint flat config support "eslint.experimental.useFlatConfig": true, // Disable the default formatter, use eslint instead "prettier.enable": false, "editor.formatOnSave": false, // Auto fix "editor.codeActionsOnSave": { "source.fixAll": "explicit", "source.organizeImports": "never" }, // Silent the stylistic rules in you IDE, but still auto fix them "eslint.rules.customizations": [ { "rule": "style/*", "severity": "off" }, { "rule": "*-indent", "severity": "off" }, { "rule": "*-spacing", "severity": "off" }, { "rule": "*-spaces", "severity": "off" }, { "rule": "*-order", "severity": "off" }, { "rule": "*-dangle", "severity": "off" }, { "rule": "*-newline", "severity": "off" }, { "rule": "*quotes", "severity": "off" }, { "rule": "*semi", "severity": "off" } ], // Enable eslint for all supported languages "eslint.validate": [ "javascript", "javascriptreact", "typescript", "typescriptreact", "vue", "html", "markdown", "json", "jsonc", "yaml" ] } ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2020 Anthony Fu Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================

Icônes

Icon Explorer with Instant searching, powered by Iconify

Go to App

Electron is coming...

![](./screenshots/1.png) ![](./screenshots/2.png) ![](./screenshots/3.png) ![](./screenshots/4.png) ![](./screenshots/5.png)

Dark Mode is now Live!

![](./screenshots/6.png)

### Features - **Instant Fuzzy Searching** _- all are done locally, no web queries!_ - The **Bag** _- select your icons and pack them into a ready-to-use icon font!_ - _[svg-packer](https://github.com/antfu/svg-packer) was born from this XD_ - Copy the usage scripts - SVGs direct download - Mobile friendly - Collection bookmarks - Categories filters - Dark mode - Built with [Vite](https://github.com/vitejs/vite) and Vue 3 - If you like how it's built - try [🏕 Vitesse](https://github.com/antfu/vitesse), an opinionated starter template made from Icônes ### Community - [VS Code Extension](https://github.com/afzalsayed96/vscode-icones) by [@afzalsayed96](https://github.com/afzalsayed96) ### TODOs - Electron client (Coming!) - Full-offline mode - pack all the icons ## License MIT - Anthony Fu 2020 ================================================ FILE: electron/electron-builder.json5 ================================================ /** * @see https://www.electron.build/configuration/configuration */ { "productName": "Icônes", "appId": "me.antfu.icones", "directories": { "output": "release" }, "icon": "build/icon.png", "mac": { "target": "dmg" }, "extraResources": [ { "from": "build/icon.png", "to": "icon.png" } ], "publish": { "provider": "github", "owner": "antfu", "repo": "icones", "private": false }, "files": ["dist-electron", "dist"], "win": { "target": [ { "target": "nsis", "arch": ["x64"] } ] }, "nsis": { "oneClick": false, "perMachine": false, "allowToChangeInstallationDirectory": true, "deleteAppDataOnUninstall": false } } ================================================ FILE: electron/eslint.config.js ================================================ // @ts-check import antfu from '@antfu/eslint-config' export default antfu( { ignores: [ // eslint ignore globs here ], }, { rules: { // overrides }, }, ) ================================================ FILE: electron/package.json ================================================ { "name": "icones-electron", "version": "0.0.0", "appname": "Icônes", "description": "Explorer for Iconify with Instant searching.", "author": "Anthony Fu", "license": "MIT", "homepage": "https://github.com/antfu/icones#readme", "repository": { "type": "git", "url": "git+https://github.com/antfu/icones.git" }, "bugs": { "url": "https://github.com/antfu/icones/issues" }, "main": "dist-electron/main/index.js", "copyright": "Copyright © 2020 Anthony Fu", "scripts": { "dev": "vite ../ --port 3333 --mode electron", "copy": "cp -r ../dist ./", "build": "vite build ../ --mode electron && pnpm copy && electron-builder" }, "devDependencies": { "electron": "27.0.4", "electron-builder": "24.6.4", "electron-devtools-installer": "3.2.0", "vite-plugin-electron": "0.15.4", "vite-plugin-electron-renderer": "0.14.5", "vite-plugin-esmodule": "1.5.0" } } ================================================ FILE: electron/src/main/index.ts ================================================ import path from 'node:path' import { app, BrowserWindow, shell } from 'electron' import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer' let mainWindow: BrowserWindow | null = null app.disableHardwareAcceleration() const PROJECT_ROOT = path.resolve(__dirname, '../..') async function createMainWindow() { const win = new BrowserWindow({ title: app.name, show: false, width: 660, height: 500, minWidth: 200, minHeight: 200, titleBarStyle: 'hiddenInset', webPreferences: { nodeIntegration: true, contextIsolation: false, }, }) if (app.isPackaged) { win.loadFile(path.join(PROJECT_ROOT, 'dist/index.html')) win.removeMenu() } else { win.loadURL('http://localhost:3333/') win.webContents.openDevTools() await installExtension(VUEJS_DEVTOOLS) } win.on('ready-to-show', () => { win.show() }) win.on('closed', () => { mainWindow = null }) const handleRedirect = (e: Event, url: string) => { if (url !== win.webContents.getURL()) { e.preventDefault() shell.openExternal(url) } } // @ts-expect-error - no types win.webContents.on('will-navigate', handleRedirect) win.webContents.setWindowOpenHandler((details) => { shell.openExternal(details.url) return { action: 'deny' } }) return win } if (!app.requestSingleInstanceLock()) app.quit() app.on('window-all-closed', () => { app.quit() }) app.on('activate', async () => { if (!mainWindow) mainWindow = await createMainWindow() }) ; (async () => { await app.whenReady() mainWindow = await createMainWindow() mainWindow.focus() })() .catch(console.error) ================================================ FILE: electron/src/renderer/index.js ================================================ ================================================ FILE: eslint.config.js ================================================ // @ts-check import antfu from '@antfu/eslint-config' export default antfu( { ignores: [ '**/src/assets/collections.json', '**/public/collections', '**/public/lib', '**/release', '**/collections-info.json', '**/collections-meta.json', '**/dist-electron', ], formatters: true, }, ) ================================================ FILE: index.html ================================================ Icônes
================================================ FILE: netlify.toml ================================================ [build] publish = "dist" command = "pnpm run build" [build.environment] NODE_VERSION = "20" [[redirects]] from = "/*" to = "/index.html" status = 200 ================================================ FILE: package.json ================================================ { "name": "icones", "type": "module", "version": "0.0.0", "private": true, "packageManager": "pnpm@10.24.0", "author": "Anthony Fu", "license": "MIT", "homepage": "https://github.com/antfu/icones#readme", "repository": { "type": "git", "url": "git+https://github.com/antfu/icones.git" }, "bugs": { "url": "https://github.com/antfu/icones/issues" }, "scripts": { "postinstall": "esno scripts/prepare.ts", "lint": "eslint .", "dev": "vite --port 3333 --open", "dev-pwa": "SW_DEV=true vite --port 3333", "typecheck": "vue-tsc --noEmit", "dev:electron": "npm -C ./electron run dev", "build": "NODE_ENV=production vite build", "build:electron": "NODE_ENV=production npm -C ./electron run build" }, "dependencies": { "@antfu/utils": "^9.3.0", "@vueuse/core": "^14.1.0", "dexie": "^4.2.1", "file-saver": "^2.0.5", "floating-vue": "^5.2.2", "fzf": "^0.5.2", "hotkeys-js": "^3.13.15", "iconify-icon": "^3.0.2", "prettier": "^3.7.3", "ultrahtml": "^1.6.0", "vue": "^3.5.25", "vue-chemistry": "^0.2.2", "vue-router": "^4.6.3" }, "devDependencies": { "@antfu/eslint-config": "^6.2.0", "@iconify/json": "^2.2.413", "@types/file-saver": "^2.0.7", "@types/fs-extra": "^11.0.4", "@vitejs/plugin-vue": "^6.0.2", "client-zip": "^2.5.0", "dayjs": "^1.11.19", "eslint": "^9.39.1", "eslint-plugin-format": "^1.0.2", "esno": "^4.8.0", "fast-glob": "^3.3.3", "fs-extra": "^11.3.2", "lru-cache": "^11.2.4", "pnpm": "^10.24.0", "shiki": "^3.17.1", "svg-packer": "^1.0.0", "typescript": "^5.9.3", "unocss": "^66.5.9", "unplugin-auto-import": "^20.3.0", "unplugin-vue-components": "^30.0.0", "vite": "^7.2.6", "vite-plugin-pages": "^0.33.1", "vite-plugin-pwa": "^1.2.0", "vue-tsc": "^3.1.5" } } ================================================ FILE: pnpm-workspace.yaml ================================================ packages: - electron neverBuiltDependencies: - ttf2woff2 ================================================ FILE: public/search.xml ================================================ Icônes ⚡️ Iconify Icon Explorer UTF-8 https://icones.js.org/android-chrome-192x192.png https://icones.js.org/android-chrome-192x192.png https://icones.js.org/favicon.svg ================================================ FILE: scripts/prepare.ts ================================================ import path from 'node:path' import { fileURLToPath } from 'node:url' import fs from 'fs-extra' const __dirname = path.dirname(fileURLToPath(import.meta.url)) const out = path.resolve(__dirname, '../public') function ObjectPick(source: Record, keys: string[]) { const obj: Record = {} for (const key of keys) obj[key] = source[key] return obj } function humanFileSize(size: number) { const i = Math.floor(Math.log(size) / Math.log(1024)) const v = (size / 1024 ** i) return `${v.toFixed(2)} ${['B', 'kB', 'MB', 'GB', 'TB'][i]}` } async function prepareJSON() { const dir = path.resolve(__dirname, '../node_modules/@iconify/json') const collectionsDir = path.resolve(__dirname, '../public/collections') const raw = await fs.readJSON(path.join(dir, 'collections.json')) await fs.ensureDir(collectionsDir) const collections = Object .entries(raw) .map(([id, v]) => ({ ...(v as any), id, category: (v as any).hidden ? 'Deprecated / Unavailable' : (v as any).category, })) const collectionsMeta = [] for (const info of collections) { const setData = await fs.readJSON(path.join(dir, 'json', `${info.id}.json`)) const icons = Object.keys(setData.icons) const categories = setData.categories const meta = { ...info, icons, categories } const rawFilePath = path.join(collectionsDir, `${info.id}.json`) const metaFilePath = path.join(collectionsDir, `${info.id}-meta.json`) await fs.writeJSON(rawFilePath, setData) await fs.writeJSON(metaFilePath, meta) collectionsMeta.push(meta) info.sampleIcons = icons.slice(0, 9) if (info.id === 'logos') { info.sampleIcons = [ 'vue', 'vitejs', 'vitest', 'rollupjs', 'github-icon', 'eslint', 'esbuild', 'typescript-icon', 'netlify-icon', ] } // non-square icons if (['flag', 'flagpack', 'cif', 'fa', 'fontisto', 'et', 'ps'].includes(info.id)) info.sampleIcons = info.sampleIcons.slice(0, 6) info.prepacked = { prefix: setData.prefix, width: setData.width, height: setData.height, icons: ObjectPick(setData.icons, info.sampleIcons), } info.size = humanFileSize(fs.statSync(rawFilePath).size) } await fs.writeJSON(path.join(out, 'collections-meta.json'), collectionsMeta) const infoOut = path.resolve(__dirname, '../src/data') await fs.writeJSON(path.join(infoOut, 'collections-info.json'), collections) } prepareJSON() ================================================ FILE: src/App.vue ================================================ ================================================ FILE: src/auto-imports.d.ts ================================================ /* eslint-disable */ /* prettier-ignore */ // @ts-nocheck // noinspection JSUnusedGlobalSymbols // Generated by unplugin-auto-import // biome-ignore lint: disable export {} declare global { const EffectScope: typeof import('vue').EffectScope const asyncComputed: typeof import('@vueuse/core').asyncComputed const autoResetRef: typeof import('@vueuse/core').autoResetRef const computed: typeof import('vue').computed const computedAsync: typeof import('@vueuse/core').computedAsync const computedEager: typeof import('@vueuse/core').computedEager const computedInject: typeof import('@vueuse/core').computedInject const computedWithControl: typeof import('@vueuse/core').computedWithControl const controlledComputed: typeof import('@vueuse/core').controlledComputed const controlledRef: typeof import('@vueuse/core').controlledRef const createApp: typeof import('vue').createApp const createEventHook: typeof import('@vueuse/core').createEventHook const createGlobalState: typeof import('@vueuse/core').createGlobalState const createInjectionState: typeof import('@vueuse/core').createInjectionState const createReactiveFn: typeof import('@vueuse/core').createReactiveFn const createRef: typeof import('@vueuse/core').createRef const createReusableTemplate: typeof import('@vueuse/core').createReusableTemplate const createSharedComposable: typeof import('@vueuse/core').createSharedComposable const createTemplatePromise: typeof import('@vueuse/core').createTemplatePromise const createUnrefFn: typeof import('@vueuse/core').createUnrefFn const customRef: typeof import('vue').customRef const debouncedRef: typeof import('@vueuse/core').debouncedRef const debouncedWatch: typeof import('@vueuse/core').debouncedWatch const defineAsyncComponent: typeof import('vue').defineAsyncComponent const defineComponent: typeof import('vue').defineComponent const eagerComputed: typeof import('@vueuse/core').eagerComputed const effectScope: typeof import('vue').effectScope const extendRef: typeof import('@vueuse/core').extendRef const getCurrentInstance: typeof import('vue').getCurrentInstance const getCurrentScope: typeof import('vue').getCurrentScope const getCurrentWatcher: typeof import('vue').getCurrentWatcher const h: typeof import('vue').h const ignorableWatch: typeof import('@vueuse/core').ignorableWatch const inject: typeof import('vue').inject const injectLocal: typeof import('@vueuse/core').injectLocal const isDefined: typeof import('@vueuse/core').isDefined const isProxy: typeof import('vue').isProxy const isReactive: typeof import('vue').isReactive const isReadonly: typeof import('vue').isReadonly const isRef: typeof import('vue').isRef const isShallow: typeof import('vue').isShallow const makeDestructurable: typeof import('@vueuse/core').makeDestructurable const markRaw: typeof import('vue').markRaw const nextTick: typeof import('vue').nextTick const onActivated: typeof import('vue').onActivated const onBeforeMount: typeof import('vue').onBeforeMount const onBeforeRouteLeave: typeof import('vue-router').onBeforeRouteLeave const onBeforeRouteUpdate: typeof import('vue-router').onBeforeRouteUpdate const onBeforeUnmount: typeof import('vue').onBeforeUnmount const onBeforeUpdate: typeof import('vue').onBeforeUpdate const onClickOutside: typeof import('@vueuse/core').onClickOutside const onDeactivated: typeof import('vue').onDeactivated const onElementRemoval: typeof import('@vueuse/core').onElementRemoval const onErrorCaptured: typeof import('vue').onErrorCaptured const onKeyStroke: typeof import('@vueuse/core').onKeyStroke const onLongPress: typeof import('@vueuse/core').onLongPress const onMounted: typeof import('vue').onMounted const onRenderTracked: typeof import('vue').onRenderTracked const onRenderTriggered: typeof import('vue').onRenderTriggered const onScopeDispose: typeof import('vue').onScopeDispose const onServerPrefetch: typeof import('vue').onServerPrefetch const onStartTyping: typeof import('@vueuse/core').onStartTyping const onUnmounted: typeof import('vue').onUnmounted const onUpdated: typeof import('vue').onUpdated const onWatcherCleanup: typeof import('vue').onWatcherCleanup const pausableWatch: typeof import('@vueuse/core').pausableWatch const provide: typeof import('vue').provide const provideLocal: typeof import('@vueuse/core').provideLocal const reactify: typeof import('@vueuse/core').reactify const reactifyObject: typeof import('@vueuse/core').reactifyObject const reactive: typeof import('vue').reactive const reactiveComputed: typeof import('@vueuse/core').reactiveComputed const reactiveOmit: typeof import('@vueuse/core').reactiveOmit const reactivePick: typeof import('@vueuse/core').reactivePick const readonly: typeof import('vue').readonly const ref: typeof import('vue').ref const refAutoReset: typeof import('@vueuse/core').refAutoReset const refDebounced: typeof import('@vueuse/core').refDebounced const refDefault: typeof import('@vueuse/core').refDefault const refManualReset: typeof import('@vueuse/core').refManualReset const refThrottled: typeof import('@vueuse/core').refThrottled const refWithControl: typeof import('@vueuse/core').refWithControl const resolveComponent: typeof import('vue').resolveComponent const resolveRef: typeof import('@vueuse/core').resolveRef const resolveUnref: typeof import('@vueuse/core').resolveUnref const shallowReactive: typeof import('vue').shallowReactive const shallowReadonly: typeof import('vue').shallowReadonly const shallowRef: typeof import('vue').shallowRef const syncRef: typeof import('@vueuse/core').syncRef const syncRefs: typeof import('@vueuse/core').syncRefs const templateRef: typeof import('@vueuse/core').templateRef const throttledRef: typeof import('@vueuse/core').throttledRef const throttledWatch: typeof import('@vueuse/core').throttledWatch const toRaw: typeof import('vue').toRaw const toReactive: typeof import('@vueuse/core').toReactive const toRef: typeof import('vue').toRef const toRefs: typeof import('vue').toRefs const toValue: typeof import('vue').toValue const triggerRef: typeof import('vue').triggerRef const tryOnBeforeMount: typeof import('@vueuse/core').tryOnBeforeMount const tryOnBeforeUnmount: typeof import('@vueuse/core').tryOnBeforeUnmount const tryOnMounted: typeof import('@vueuse/core').tryOnMounted const tryOnScopeDispose: typeof import('@vueuse/core').tryOnScopeDispose const tryOnUnmounted: typeof import('@vueuse/core').tryOnUnmounted const unref: typeof import('vue').unref const unrefElement: typeof import('@vueuse/core').unrefElement const until: typeof import('@vueuse/core').until const useActiveElement: typeof import('@vueuse/core').useActiveElement const useAnimate: typeof import('@vueuse/core').useAnimate const useArrayDifference: typeof import('@vueuse/core').useArrayDifference const useArrayEvery: typeof import('@vueuse/core').useArrayEvery const useArrayFilter: typeof import('@vueuse/core').useArrayFilter const useArrayFind: typeof import('@vueuse/core').useArrayFind const useArrayFindIndex: typeof import('@vueuse/core').useArrayFindIndex const useArrayFindLast: typeof import('@vueuse/core').useArrayFindLast const useArrayIncludes: typeof import('@vueuse/core').useArrayIncludes const useArrayJoin: typeof import('@vueuse/core').useArrayJoin const useArrayMap: typeof import('@vueuse/core').useArrayMap const useArrayReduce: typeof import('@vueuse/core').useArrayReduce const useArraySome: typeof import('@vueuse/core').useArraySome const useArrayUnique: typeof import('@vueuse/core').useArrayUnique const useAsyncQueue: typeof import('@vueuse/core').useAsyncQueue const useAsyncState: typeof import('@vueuse/core').useAsyncState const useAttrs: typeof import('vue').useAttrs const useBase64: typeof import('@vueuse/core').useBase64 const useBattery: typeof import('@vueuse/core').useBattery const useBluetooth: typeof import('@vueuse/core').useBluetooth const useBreakpoints: typeof import('@vueuse/core').useBreakpoints const useBroadcastChannel: typeof import('@vueuse/core').useBroadcastChannel const useBrowserLocation: typeof import('@vueuse/core').useBrowserLocation const useCached: typeof import('@vueuse/core').useCached const useClipboard: typeof import('@vueuse/core').useClipboard const useClipboardItems: typeof import('@vueuse/core').useClipboardItems const useCloned: typeof import('@vueuse/core').useCloned const useColorMode: typeof import('@vueuse/core').useColorMode const useConfirmDialog: typeof import('@vueuse/core').useConfirmDialog const useCountdown: typeof import('@vueuse/core').useCountdown const useCounter: typeof import('@vueuse/core').useCounter const useCssModule: typeof import('vue').useCssModule const useCssVar: typeof import('@vueuse/core').useCssVar const useCssVars: typeof import('vue').useCssVars const useCurrentElement: typeof import('@vueuse/core').useCurrentElement const useCycleList: typeof import('@vueuse/core').useCycleList const useDark: typeof import('@vueuse/core').useDark const useDateFormat: typeof import('@vueuse/core').useDateFormat const useDebounce: typeof import('@vueuse/core').useDebounce const useDebounceFn: typeof import('@vueuse/core').useDebounceFn const useDebouncedRefHistory: typeof import('@vueuse/core').useDebouncedRefHistory const useDeviceMotion: typeof import('@vueuse/core').useDeviceMotion const useDeviceOrientation: typeof import('@vueuse/core').useDeviceOrientation const useDevicePixelRatio: typeof import('@vueuse/core').useDevicePixelRatio const useDevicesList: typeof import('@vueuse/core').useDevicesList const useDisplayMedia: typeof import('@vueuse/core').useDisplayMedia const useDocumentVisibility: typeof import('@vueuse/core').useDocumentVisibility const useDraggable: typeof import('@vueuse/core').useDraggable const useDropZone: typeof import('@vueuse/core').useDropZone const useElementBounding: typeof import('@vueuse/core').useElementBounding const useElementByPoint: typeof import('@vueuse/core').useElementByPoint const useElementHover: typeof import('@vueuse/core').useElementHover const useElementSize: typeof import('@vueuse/core').useElementSize const useElementVisibility: typeof import('@vueuse/core').useElementVisibility const useEventBus: typeof import('@vueuse/core').useEventBus const useEventListener: typeof import('@vueuse/core').useEventListener const useEventSource: typeof import('@vueuse/core').useEventSource const useEyeDropper: typeof import('@vueuse/core').useEyeDropper const useFavicon: typeof import('@vueuse/core').useFavicon const useFetch: typeof import('@vueuse/core').useFetch const useFileDialog: typeof import('@vueuse/core').useFileDialog const useFileSystemAccess: typeof import('@vueuse/core').useFileSystemAccess const useFocus: typeof import('@vueuse/core').useFocus const useFocusWithin: typeof import('@vueuse/core').useFocusWithin const useFps: typeof import('@vueuse/core').useFps const useFullscreen: typeof import('@vueuse/core').useFullscreen const useGamepad: typeof import('@vueuse/core').useGamepad const useGeolocation: typeof import('@vueuse/core').useGeolocation const useId: typeof import('vue').useId const useIdle: typeof import('@vueuse/core').useIdle const useImage: typeof import('@vueuse/core').useImage const useInfiniteScroll: typeof import('@vueuse/core').useInfiniteScroll const useIntersectionObserver: typeof import('@vueuse/core').useIntersectionObserver const useInterval: typeof import('@vueuse/core').useInterval const useIntervalFn: typeof import('@vueuse/core').useIntervalFn const useKeyModifier: typeof import('@vueuse/core').useKeyModifier const useLastChanged: typeof import('@vueuse/core').useLastChanged const useLink: typeof import('vue-router').useLink const useLocalStorage: typeof import('@vueuse/core').useLocalStorage const useMagicKeys: typeof import('@vueuse/core').useMagicKeys const useManualRefHistory: typeof import('@vueuse/core').useManualRefHistory const useMediaControls: typeof import('@vueuse/core').useMediaControls const useMediaQuery: typeof import('@vueuse/core').useMediaQuery const useMemoize: typeof import('@vueuse/core').useMemoize const useMemory: typeof import('@vueuse/core').useMemory const useModel: typeof import('vue').useModel const useMounted: typeof import('@vueuse/core').useMounted const useMouse: typeof import('@vueuse/core').useMouse const useMouseInElement: typeof import('@vueuse/core').useMouseInElement const useMousePressed: typeof import('@vueuse/core').useMousePressed const useMutationObserver: typeof import('@vueuse/core').useMutationObserver const useNavigatorLanguage: typeof import('@vueuse/core').useNavigatorLanguage const useNetwork: typeof import('@vueuse/core').useNetwork const useNow: typeof import('@vueuse/core').useNow const useObjectUrl: typeof import('@vueuse/core').useObjectUrl const useOffsetPagination: typeof import('@vueuse/core').useOffsetPagination const useOnline: typeof import('@vueuse/core').useOnline const usePageLeave: typeof import('@vueuse/core').usePageLeave const useParallax: typeof import('@vueuse/core').useParallax const useParentElement: typeof import('@vueuse/core').useParentElement const usePerformanceObserver: typeof import('@vueuse/core').usePerformanceObserver const usePermission: typeof import('@vueuse/core').usePermission const usePointer: typeof import('@vueuse/core').usePointer const usePointerLock: typeof import('@vueuse/core').usePointerLock const usePointerSwipe: typeof import('@vueuse/core').usePointerSwipe const usePreferredColorScheme: typeof import('@vueuse/core').usePreferredColorScheme const usePreferredContrast: typeof import('@vueuse/core').usePreferredContrast const usePreferredDark: typeof import('@vueuse/core').usePreferredDark const usePreferredLanguages: typeof import('@vueuse/core').usePreferredLanguages const usePreferredReducedMotion: typeof import('@vueuse/core').usePreferredReducedMotion const usePreferredReducedTransparency: typeof import('@vueuse/core').usePreferredReducedTransparency const usePrevious: typeof import('@vueuse/core').usePrevious const useRafFn: typeof import('@vueuse/core').useRafFn const useRefHistory: typeof import('@vueuse/core').useRefHistory const useResizeObserver: typeof import('@vueuse/core').useResizeObserver const useRoute: typeof import('vue-router').useRoute const useRouter: typeof import('vue-router').useRouter const useSSRWidth: typeof import('@vueuse/core').useSSRWidth const useScreenOrientation: typeof import('@vueuse/core').useScreenOrientation const useScreenSafeArea: typeof import('@vueuse/core').useScreenSafeArea const useScriptTag: typeof import('@vueuse/core').useScriptTag const useScroll: typeof import('@vueuse/core').useScroll const useScrollLock: typeof import('@vueuse/core').useScrollLock const useSessionStorage: typeof import('@vueuse/core').useSessionStorage const useShare: typeof import('@vueuse/core').useShare const useSlots: typeof import('vue').useSlots const useSorted: typeof import('@vueuse/core').useSorted const useSpeechRecognition: typeof import('@vueuse/core').useSpeechRecognition const useSpeechSynthesis: typeof import('@vueuse/core').useSpeechSynthesis const useStepper: typeof import('@vueuse/core').useStepper const useStorage: typeof import('@vueuse/core').useStorage const useStorageAsync: typeof import('@vueuse/core').useStorageAsync const useStyleTag: typeof import('@vueuse/core').useStyleTag const useSupported: typeof import('@vueuse/core').useSupported const useSwipe: typeof import('@vueuse/core').useSwipe const useTemplateRef: typeof import('vue').useTemplateRef const useTemplateRefsList: typeof import('@vueuse/core').useTemplateRefsList const useTextDirection: typeof import('@vueuse/core').useTextDirection const useTextSelection: typeof import('@vueuse/core').useTextSelection const useTextareaAutosize: typeof import('@vueuse/core').useTextareaAutosize const useThrottle: typeof import('@vueuse/core').useThrottle const useThrottleFn: typeof import('@vueuse/core').useThrottleFn const useThrottledRefHistory: typeof import('@vueuse/core').useThrottledRefHistory const useTimeAgo: typeof import('@vueuse/core').useTimeAgo const useTimeAgoIntl: typeof import('@vueuse/core').useTimeAgoIntl const useTimeout: typeof import('@vueuse/core').useTimeout const useTimeoutFn: typeof import('@vueuse/core').useTimeoutFn const useTimeoutPoll: typeof import('@vueuse/core').useTimeoutPoll const useTimestamp: typeof import('@vueuse/core').useTimestamp const useTitle: typeof import('@vueuse/core').useTitle const useToNumber: typeof import('@vueuse/core').useToNumber const useToString: typeof import('@vueuse/core').useToString const useToggle: typeof import('@vueuse/core').useToggle const useTransition: typeof import('@vueuse/core').useTransition const useUrlSearchParams: typeof import('@vueuse/core').useUrlSearchParams const useUserMedia: typeof import('@vueuse/core').useUserMedia const useVModel: typeof import('@vueuse/core').useVModel const useVModels: typeof import('@vueuse/core').useVModels const useVibrate: typeof import('@vueuse/core').useVibrate const useVirtualList: typeof import('@vueuse/core').useVirtualList const useWakeLock: typeof import('@vueuse/core').useWakeLock const useWebNotification: typeof import('@vueuse/core').useWebNotification const useWebSocket: typeof import('@vueuse/core').useWebSocket const useWebWorker: typeof import('@vueuse/core').useWebWorker const useWebWorkerFn: typeof import('@vueuse/core').useWebWorkerFn const useWindowFocus: typeof import('@vueuse/core').useWindowFocus const useWindowScroll: typeof import('@vueuse/core').useWindowScroll const useWindowSize: typeof import('@vueuse/core').useWindowSize const watch: typeof import('vue').watch const watchArray: typeof import('@vueuse/core').watchArray const watchAtMost: typeof import('@vueuse/core').watchAtMost const watchDebounced: typeof import('@vueuse/core').watchDebounced const watchDeep: typeof import('@vueuse/core').watchDeep const watchEffect: typeof import('vue').watchEffect const watchIgnorable: typeof import('@vueuse/core').watchIgnorable const watchImmediate: typeof import('@vueuse/core').watchImmediate const watchOnce: typeof import('@vueuse/core').watchOnce const watchPausable: typeof import('@vueuse/core').watchPausable const watchPostEffect: typeof import('vue').watchPostEffect const watchSyncEffect: typeof import('vue').watchSyncEffect const watchThrottled: typeof import('@vueuse/core').watchThrottled const watchTriggerable: typeof import('@vueuse/core').watchTriggerable const watchWithFilter: typeof import('@vueuse/core').watchWithFilter const whenever: typeof import('@vueuse/core').whenever } // for type re-export declare global { // @ts-ignore export type { Component, Slot, Slots, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, ShallowRef, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue' import('vue') } ================================================ FILE: src/components/ActionsMenu.vue ================================================ ================================================ FILE: src/components/Bag.vue ================================================ ================================================ FILE: src/components/CollectionEntries.vue ================================================ ================================================ FILE: src/components/CollectionEntry.vue ================================================ ================================================ FILE: src/components/ColorPicker.vue ================================================ ================================================ FILE: src/components/CustomSelect.vue ================================================ ================================================ FILE: src/components/DarkSwitcher.vue ================================================ ================================================ FILE: src/components/Drawer.vue ================================================ ================================================ FILE: src/components/FAB.vue ================================================ ================================================ FILE: src/components/Footer.vue ================================================ ================================================ FILE: src/components/HelpPage.vue ================================================ ================================================ FILE: src/components/Icon.vue ================================================ ================================================ FILE: src/components/IconButton.vue ================================================ ================================================ FILE: src/components/IconDetail.vue ================================================ ================================================ FILE: src/components/IconSet.vue ================================================ ================================================ FILE: src/components/Icons.vue ================================================ ================================================ FILE: src/components/InstallIconSet.vue ================================================ ================================================ FILE: src/components/Modal.vue ================================================ ================================================ FILE: src/components/ModalDialog.vue ================================================ ================================================ FILE: src/components/Navbar.vue ================================================ ================================================ FILE: src/components/Notification.vue ================================================ ================================================ FILE: src/components/Progress.vue ================================================ ================================================ FILE: src/components/SearchBar.vue ================================================ ================================================ FILE: src/components/SettingsCollectionsList.vue ================================================ ================================================ FILE: src/components/SnippetPreview.vue ================================================ ================================================ FILE: src/components/WithNavbar.vue ================================================ ================================================ FILE: src/components/electron/NavElectron.vue ================================================ ================================================ FILE: src/components/electron/NavPlaceholder.vue ================================================ ================================================ FILE: src/components/electron/SearchElectron.vue ================================================ ================================================ FILE: src/components.d.ts ================================================ /* eslint-disable */ // @ts-nocheck // biome-ignore lint: disable // oxlint-disable // ------ // Generated by unplugin-vue-components // Read more: https://github.com/vuejs/core/pull/3399 export {} /* prettier-ignore */ declare module 'vue' { export interface GlobalComponents { ActionsMenu: typeof import('./components/ActionsMenu.vue')['default'] Bag: typeof import('./components/Bag.vue')['default'] CollectionEntries: typeof import('./components/CollectionEntries.vue')['default'] CollectionEntry: typeof import('./components/CollectionEntry.vue')['default'] ColorPicker: typeof import('./components/ColorPicker.vue')['default'] CustomSelect: typeof import('./components/CustomSelect.vue')['default'] DarkSwitcher: typeof import('./components/DarkSwitcher.vue')['default'] Drawer: typeof import('./components/Drawer.vue')['default'] FAB: typeof import('./components/FAB.vue')['default'] Footer: typeof import('./components/Footer.vue')['default'] HelpPage: typeof import('./components/HelpPage.vue')['default'] Icon: typeof import('./components/Icon.vue')['default'] IconButton: typeof import('./components/IconButton.vue')['default'] IconDetail: typeof import('./components/IconDetail.vue')['default'] Icons: typeof import('./components/Icons.vue')['default'] IconSet: typeof import('./components/IconSet.vue')['default'] InstallIconSet: typeof import('./components/InstallIconSet.vue')['default'] Modal: typeof import('./components/Modal.vue')['default'] ModalDialog: typeof import('./components/ModalDialog.vue')['default'] Navbar: typeof import('./components/Navbar.vue')['default'] NavElectron: typeof import('./components/electron/NavElectron.vue')['default'] NavPlaceholder: typeof import('./components/electron/NavPlaceholder.vue')['default'] Notification: typeof import('./components/Notification.vue')['default'] Progress: typeof import('./components/Progress.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] SearchBar: typeof import('./components/SearchBar.vue')['default'] SearchElectron: typeof import('./components/electron/SearchElectron.vue')['default'] SettingsCollectionsList: typeof import('./components/SettingsCollectionsList.vue')['default'] SnippetPreview: typeof import('./components/SnippetPreview.vue')['default'] WithNavbar: typeof import('./components/WithNavbar.vue')['default'] } } ================================================ FILE: src/data/index.ts ================================================ import type { IconifyJSON } from 'iconify-icon' import { notNullish } from '@antfu/utils' import { AsyncFzf } from 'fzf' import { addCollection } from 'iconify-icon' import { isLocalMode, staticPath } from '../env' import { loadCollection, saveCollection } from '../store/indexedDB' import { favoritedCollectionIds, isExcludedCollection, isFavoritedCollection, isRecentCollection, recentCollectionIds, } from '../store/localstorage' import { inProgress, progressMessage } from '../store/progress' import infoJSON from './collections-info.json' import { variantCategories } from './variant-category' export const specialTabs = ['all', 'recent'] export type PresentType = 'favorite' | 'recent' | 'normal' export interface CollectionInfo { id: string name: string author?: { name: string, url: string } license?: { title: string, url: string } url?: string sampleIcons?: string[] category?: string palette?: string total?: number prepacked?: IconifyJSON /** * The icon set was deprecated and is no longer available */ hidden?: boolean } export interface CollectionMeta extends CollectionInfo { icons: string[] categories?: Record variants?: Record } const loadedMeta = ref([]) const installed = ref([]) export const collections = infoJSON.map(c => Object.freeze(c as any as CollectionInfo)) export const enabledCollections = computed(() => collections.filter(c => !isExcludedCollection(c))) export const categories = Array.from(new Set(collections.map(i => i.category).filter(notNullish))) export const isSearchOpen = ref(false) export const categorySearch = ref('') const fzf = new AsyncFzf(collections, { casing: 'case-insensitive', fuzzy: 'v1', selector: v => `${v.name} ${v.id} ${v.category} ${v.author}`, }) export const filteredCollections = ref(enabledCollections.value) watch([categorySearch, enabledCollections], ([q]) => { if (!q) { filteredCollections.value = enabledCollections.value } else { fzf.find(q).then((result) => { filteredCollections.value = result.map(i => i.item) }).catch(() => { // The search is canceled }) } }) export const sortedCollectionsInfo = computed(() => filteredCollections.value .sort((a, b) => favoritedCollectionIds.value.indexOf(b.id) - favoritedCollectionIds.value.indexOf(a.id)), ) export const favoritedCollections = computed(() => filteredCollections.value.filter(i => isFavoritedCollection(i.id)) .sort((a, b) => favoritedCollectionIds.value.indexOf(b.id) - favoritedCollectionIds.value.indexOf(a.id)), ) export const recentCollections = computed(() => filteredCollections.value.filter(i => isRecentCollection(i.id)) .sort((a, b) => recentCollectionIds.value.indexOf(b.id) - recentCollectionIds.value.indexOf(a.id)), ) export function isInstalled(id: string) { return installed.value.includes(id) } export function isMetaLoaded(id: string) { return !!loadedMeta.value.find(i => i.id === id) } // install the preview icons on the homepage export function preInstall() { for (const collection of collections) { if (collection.prepacked) addCollection(collection.prepacked as any) } } export async function tryInstallFromLocal(id: string) { if (specialTabs.includes(id)) return false if (isLocalMode) return true if (installed.value.includes(id)) return true const result = await loadCollection(id) if (!result || !result.data) return false const data = result.data addCollection(data) installed.value.push(id) return true } // load full iconset export async function downloadAndInstall(id: string) { if (specialTabs.includes(id)) return false if (installed.value.includes(id)) return true const data = Object.freeze(await fetch(`${staticPath}/collections/${id}.json`).then(r => r.json())) addCollection(data) installed.value.push(id) if (!isLocalMode) saveCollection(id, data) // async return true } export async function cacheCollection(id: string) { progressMessage.value = 'Downloading...' inProgress.value = true await nextTick() await downloadAndInstall(id) inProgress.value = false } export async function getCollectionMeta(id: string): Promise { let meta = loadedMeta.value.find(i => i.id === id) if (meta) return meta meta = await fetch(`${staticPath}/collections/${id}-meta.json`).then(r => r.json()) if (!meta) return null meta.variants ||= getVariantCategories(meta) meta = Object.freeze(meta) loadedMeta.value.push(meta) return meta } function getVariantCategories(collection: CollectionMeta) { const variantsRule = variantCategories[collection.id] if (!variantsRule) return const variants: Record = {} for (const icon of collection.icons) { const name = variantsRule.find(i => typeof i[1] === 'string' ? icon.endsWith(i[1]) : i[1].test(icon))?.[0] || 'Regular' if (!variants[name]) variants[name] = [] variants[name].push(icon) } return variants } export async function getFullMeta() { if (loadedMeta.value.length === collections.length) return loadedMeta.value loadedMeta.value = Object.freeze( await fetch(`${staticPath}/collections-meta.json`).then(r => r.json()), ) return loadedMeta.value } preInstall() ================================================ FILE: src/data/search-alias.ts ================================================ export const searchAlias: string[][] = [ ['account', 'person', 'profile', 'user'], ['add', 'create', 'new', 'plus'], ['alert', 'bell', 'notification', 'notify', 'reminder'], ['approve', 'like', 'recommend', 'thumbs-up'], ['left', 'previous'], ['next', 'right'], ['attach', 'connect', 'link'], ['bag', 'basket', 'cart'], ['bookmark', 'tag', 'label'], ['building', 'home', 'house'], ['calendar', 'date', 'event'], ['cancel', 'close'], ['delete', 'remove', 'trash'], ['chat', 'conversation', 'message'], ['clock', 'time', 'timer', 'alarm'], ['cog', 'gear', 'preferences', 'settings'], ['directory', 'folder'], ['disapprove', 'dislike', 'thumbs-down'], ['document', 'file', 'paper'], ['earth', 'globe', 'world', 'planet', 'global'], ['email', 'envelope', 'mail'], ['eye', 'view', 'visible'], ['favorite', 'heart', 'love'], ['feed', 'rss', 'subscribe', 'subscription'], ['list', 'menu'], ['lock', 'secure', 'security'], ['unlock', 'lock-open'], ['log-in', 'login', 'sign-in'], ['log-out', 'logout', 'sign-out'], ['magnifier', 'search', 'find', 'magnify'], ['photo', 'picture', 'image'], ['refresh', 'reload', 'update', 'sync'], ['speaker', 'audio', 'volume', 'sound'], ['speed', 'fast'], ['accessibility', 'ally', 'a11y'], ['edit', 'pen', 'pencil', 'write'], ['moon', 'night', 'dark'], ['sun', 'day'], ['bulb', 'idea'], ['pin', 'location', 'map', 'marker'], ['bot', 'robot', 'android'], ['db', 'database'], ['external', 'launch'], ['airplane', 'flight'], ['chart', 'graph'], ['monitor', 'screen'], ['video', 'film'], ['support', 'help', 'question'], ['mute', 'silence', 'sound-off', 'volume-off'], ['code', 'development', 'program', 'terminal', 'braces'], ['phone', 'call'], ['car', 'vehicle', 'transport', 'taxi'], ] ================================================ FILE: src/data/variant-category.ts ================================================ // @keep-sorted export const variantCategories: Record = { 'academicons': [ ['Square', '-square'], ], 'akar-icons': [ ['Fill', '-fill'], ], 'ant-design': [ ['Outlined', '-outlined'], ['Filled', '-filled'], ['Twotone', '-twotone'], ], 'basil': [ ['Outline', '-outline'], ['Solid', '-solid'], ], 'bi': [ ['Fill', '-fill'], ], 'clarity': [ ['Line', '-line'], ['Solid', '-solid'], ['Outline Badged', '-outline-badged'], ['Solid Badged', '-solid-badged'], ['Outline Alerted', '-outline-alerted'], ['Solid Alerted', '-solid-alerted'], ], 'eos-icons': [ ['Outlined', '-outlined'], ], 'fluent': [ ['Filled 20', '-20-filled'], ['Regular 20', '-20-regular'], ['Filled 24', '-24-filled'], ['Regular 24', '-24-regular'], ['Filled 16', '-16-filled'], ['Regular 16', '-16-regular'], ['Filled 48', '-48-filled'], ['Regular 48', '-48-regular'], ['Filled 28', '-28-filled'], ['Regular 28', '-28-regular'], ['Filled 32', '-32-filled'], ['Regular 32', '-32-regular'], ['Filled 12', '-12-filled'], ['Regular 12', '-12-regular'], ['Filled 10', '-10-filled'], ['Regular 10', '-10-regular'], ], 'heroicons': [ ['20 Solid', '-20-solid'], ['Solid', '-solid'], ], 'ic': [ ['Outline', /^outline-/], ['Round', /^round-/], ['Sharp', /^sharp-/], ['Twotone', /^twotone-/], ['Baseline', /^baseline-/], ], 'iconamoon': [ ['Bold', '-bold'], ['Duotone', '-duotone'], ['Fill', '-fill'], ['Light', '-light'], ['Thin', '-thin'], ], 'iconoir': [ ['Solid', '-solid'], ], 'ion': [ ['Outline', '-outline'], ['Sharp', '-sharp'], ['Regular', ''], ], 'line-md': [ ['Twotone', '-twotone'], ['Twotone Transition', '-twotone-transition'], ['Loop', '-loop'], ['Filled', '-filled'], ], 'majesticons': [ ['Line', '-line'], ], 'maki': [ ['11px', '-11'], ], 'material-symbols-light': [ ['Outline Rounded', '-outline-rounded'], ['Outline Sharp', '-outline-sharp'], ['Outline', '-outline'], ['Rounded', '-rounded'], ['Sharp', '-sharp'], ], 'material-symbols': [ ['Outline Rounded', '-outline-rounded'], ['Outline', '-outline'], ['Rounded', '-rounded'], ['Sharp', '-sharp'], ], 'mdi': [ ['Outline', '-outline'], ], 'mingcute': [ ['Fill', '-fill'], ['Line', '-line'], ], 'mynaui': [ ['Solid', '-solid'], ], 'octicon': [ ['16px', '-16'], ], 'ph': [ ['Bold', '-bold'], ['Duotone', '-duotone'], ['Fill', '-fill'], ['Light', '-light'], ['Thin', '-thin'], ], 'ri': [ ['Fill', '-fill'], ['Line', '-line'], ], 'si': [ ['Fill', '-fill'], ['Line', '-line'], ['Duotone', '-duotone'], ], 'solar': [ ['Bold', '-bold'], ['Duotone', '-duotone'], ['Broken', '-broken'], ['Twotone', '-twotone'], ['Outline', '-outline'], ['Linear', '-linear'], ], 'tabler': [ ['Filled', '-filled'], ['Lines', '-lines'], ], 'teenyicons': [ ['Outline', '-outline'], ['Solid', '-solid'], ], 'twemoji': [ ['Medium Dark Skin Tone', '-medium-dark-skin-tone'], ['Medium Light Skin Tone', '-medium-light-skin-tone'], ['Dark Skin Tone', '-dark-skin-tone'], ['Light Skin Tone', '-light-skin-tone'], ['Medium Skin Tone', '-medium-skin-tone'], ], 'typcn': [ ['Outline', '-outline'], ['Thick', '-thick'], ], 'zondicons': [ ['Outline', '-outline'], ['Solid', '-solid'], ], } ================================================ FILE: src/env.ts ================================================ export const isElectron = import.meta.env.MODE === 'electron' export const isVSCode = location.protocol === 'vscode-webview:' export const isLocalMode = isElectron || isVSCode export const basePath = isVSCode ? window.baseURI : '/' export const staticPath = isVSCode ? window.staticURI : (isElectron && import.meta.env.PROD) ? '../../app.asar/dist' : '' ================================================ FILE: src/hooks/color.ts ================================================ import { themeColor } from '../store' export function useThemeColor() { const style = computed(() => ({ '--theme-color': themeColor.value, })) return { style, } } ================================================ FILE: src/hooks/index.ts ================================================ export * from './color' export * from './search' ================================================ FILE: src/hooks/search.ts ================================================ import type { Ref } from 'vue' import type { CollectionMeta } from '../data' import { asyncExtendedMatch, AsyncFzf } from 'fzf' import { computed, markRaw, ref, watch } from 'vue' import { specialTabs } from '../data' import { searchAlias } from '../data/search-alias' import { cleanupQuery } from '../utils/query' export function useSearch(collection: Ref) { const route = useRoute() const router = useRouter() const category = computed({ get() { return route.query.category as string || '' }, set(value: string) { router.replace({ query: cleanupQuery({ ...route.query, category: value }) }) }, }) const variant = computed({ get() { return route.query.variant as string || '' }, set(value: string) { router.replace({ query: cleanupQuery({ ...route.query, variant: value }) }) }, }) const search = computed({ get() { return route.query.s as string || '' }, set(value: string) { router.replace({ query: cleanupQuery({ ...route.query, s: value }) }) }, }) const isAll = computed(() => collection.value && specialTabs.includes(collection.value.id)) const searchParts = computed(() => search.value.trim().toLowerCase().split(' ').filter(Boolean)) const aliasedSearchCandidates = computed(() => { const options = new Set([ searchParts.value.join(' '), ]) searchParts.value.forEach((i, idx, arr) => { const alias = searchAlias.find(a => a.includes(i)) if (alias?.length) { alias.forEach((a) => { options.add([...arr.slice(0, idx), a, arr.slice(idx + 1)].filter(Boolean).join(' ').trim()) }) } }) return [...options] }) // Matching any character used in extended match // https://github.com/junegunn/fzf#search-syntax const useExtendedMatch = computed(() => /[ '^$!]/.test(search.value)) const iconSource = computed(() => { if (!collection.value) return [] return (category.value && variant.value) ? arrayIntersection( collection.value.categories?.[category.value] || [], collection.value.variants?.[variant.value] || [], ) : category.value ? (collection.value.categories?.[category.value] || []) : variant.value ? (collection.value.variants?.[variant.value] || []) : collection.value.icons }) const fzf = computed(() => { return markRaw(new AsyncFzf(iconSource.value, { casing: 'case-insensitive', match: asyncExtendedMatch, })) }) const fzfFast = computed(() => { return markRaw(new AsyncFzf(iconSource.value, { casing: 'case-insensitive', // v1 is faster // https://fzf.netlify.app/docs/latest#async-finder-considering-other-options-first fuzzy: 'v1', })) }) const icons = ref([]) function runSearch() { const finder = (useExtendedMatch.value || aliasedSearchCandidates.value.length > 1) ? fzf : fzfFast const searchString = aliasedSearchCandidates.value.join(' | ') finder.value.find(searchString) .then((result) => { icons.value = result.map(i => i.item) }) .catch(() => { // The search is canceled }) } const debouncedSearch = useDebounceFn(runSearch, 200) watch([category, variant, () => collection.value?.id], () => { runSearch() }) watchEffect(() => { if (!search.value) { icons.value = iconSource.value return } if (isAll.value && !useExtendedMatch.value) { icons.value = iconSource.value .filter(i => aliasedSearchCandidates.value.some(s => i.includes(s))) return } debouncedSearch() }) return { collection, search, category, variant, icons, } } // @unocss-include export function getSearchHighlightHTML( text: string, search: string, baseClass = 'color-fade', activeClass = 'text-primary', ) { const start = text.indexOf(search || '') if (!search || start < 0) return `${text}` const end = start + search.length return `${text.slice(0, start)}${text.slice(start, end)}${text.slice(end)}` } export function arrayIntersection(a: T[], b: T[]) { return a.filter(i => b.includes(i)) } ================================================ FILE: src/html.d.ts ================================================ // for UnoCSS attributify mode compact in Volar // refer: https://github.com/johnsoncodehk/volar/issues/1077#issuecomment-1145361472 declare module '@vue/runtime-dom' { interface HTMLAttributes { [key: string]: any } } declare module '@vue/runtime-core' { interface AllowedComponentProps { [key: string]: any } } export {} ================================================ FILE: src/main.css ================================================ body { padding: 0; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } html.dark { background: #181818; color-scheme: dark; } html.dark .shiki, html.dark .shiki span { color: var(--shiki-dark); } html:not(.dark) .shiki, html:not(.dark) .shiki span { color: var(--shiki-light); } .btn { --uno: border border-base rounded shadow-sm outline-none px-4 py-1 text-gray-600 text-sm transition-all bg-base hover-(bg-gray-50 shadow) dark-(border-dark-200 text-gray-300) dark-hover-(border-primary bg-dark-100 text-primary) focus-(shadow outline-none); } .btn.small { --uno: px-2 py-1 text-sm; } .dragging { -webkit-app-region: drag; } .non-dragging { -webkit-app-region: no-drag; } .overflow-overlay { overflow: auto; overflow: overlay; } .overflow-y-overlay { overflow-y: auto; overflow-y: overlay; } .overflow-x-overlay { overflow-x: auto; overflow-x: overlay; } /* Scrollbar */ * { -webkit-overflow-scrolling: touch; -ms-overflow-style: -ms-autohiding-scrollbar; scrollbar-width: 6px; scrollbar-color: transparent; } ::-webkit-scrollbar { height: 6px; width: 6px; } ::-webkit-scrollbar-track { background: transparent; } ::-webkit-scrollbar-thumb { background: rgba(128, 128, 128, 0.2); border-radius: 3px; } ::-webkit-scrollbar-thumb:active { background: rgba(128, 128, 128, 0.5); border-radius: 3px; } /* Tootip */ .v-popper--theme-tooltip .v-popper__inner, .v-popper--theme-menu .v-popper__inner { --uno: important-bg-base; } .v-popper--theme-tooltip .v-popper__arrow-outer, .v-popper--theme-menu .v-popper__arrow-outer { --uno: important-border-none; } .v-popper--theme-tooltip .v-popper__inner, .v-popper--theme-menu .v-popper__inner { --uno: border border-base shadow-lg; } .v-popper--theme-menu .v-popper__arrow-outer { visibility: hidden; } .v-popper--theme-menu .v-popper__arrow-inner { visibility: hidden; } /* fallback black svg in dark mode */ .icons-item svg, .dark .icons-item [fill='#000'], .dark .icons-item [fill='#000000'], .dark .icons-item [fill='black'] { fill: currentColor; } .dark .icons-item [stroke='#000'], .dark .icons-item [stroke='#000000'], .dark .icons-item [stroke='black'] { stroke: currentColor; } /* Color Mode transition */ ::view-transition-old(root), ::view-transition-new(root) { animation: none; mix-blend-mode: normal; } ::view-transition-old(root) { z-index: 1; } ::view-transition-new(root) { z-index: 2147483646; } .dark::view-transition-old(root) { z-index: 2147483646; } .dark::view-transition-new(root) { z-index: 1; } ================================================ FILE: src/main.ts ================================================ import { createApp } from 'vue' import { createRouter, createWebHashHistory, createWebHistory } from 'vue-router' import routes from '~pages' import App from './App.vue' import { basePath, isElectron } from './env' import '@unocss/reset/tailwind.css' import 'floating-vue/dist/style.css' import './utils/electron' import './main.css' import 'uno.css' const app = createApp(App) const router = createRouter({ history: isElectron ? createWebHashHistory(basePath) : createWebHistory(basePath), routes, }) if (!isElectron && PWA) { router.isReady().then(async () => { const { registerSW } = await import('virtual:pwa-register') registerSW({ immediate: true }) }) } app.use(router) app.mount('#app') ================================================ FILE: src/pages/[...all].vue ================================================ ================================================ FILE: src/pages/collection/[id].vue ================================================ ================================================ FILE: src/pages/index.vue ================================================ ================================================ FILE: src/pages/settings.vue ================================================ ================================================ FILE: src/shims.d.ts ================================================ interface Window { // for vscode baseURI?: string staticURI?: string } declare const vscode: any declare const __BUILD_TIME__: string declare const PWA: boolean declare module '*.vue' { import type { defineComponent } from './vue' const Component: ReturnType export default Component } ================================================ FILE: src/store/collection.ts ================================================ import type { CollectionMeta } from '../data' import { collections, downloadAndInstall, getCollectionMeta, getFullMeta, isInstalled, isMetaLoaded, tryInstallFromLocal, } from '../data' import { isLocalMode } from '../env' import { useSearch } from '../hooks' import { isExcludedCollection, recentIconIds } from './localstorage' const currentCollectionId = ref('') const loaded = ref(false) const installed = ref(false) const collection = shallowRef(null) export const getSearchResults = createSharedComposable(() => { return useSearch(collection) }) export function useCurrentCollection() { return collection } export function isCurrentCollectionLoading() { return computed(() => !loaded.value) } const recentIconsCollection = computed((): CollectionMeta => ({ id: 'recent', name: 'Recent', icons: recentIconIds.value, categories: Object.fromEntries( Array.from(new Set( recentIconIds.value.map(i => i.split(':')[0]), )) .map(id => [collections.find(i => i.id === id)?.name || id, recentIconIds.value.filter(i => i.startsWith(`${id}:`))]), ), })) export async function setCurrentCollection(id: string) { currentCollectionId.value = id if (!id) { loaded.value = false installed.value = false collection.value = null return collection.value } loaded.value = isMetaLoaded(id) installed.value = isInstalled(id) if (!installed.value) { if (isLocalMode) installed.value = await downloadAndInstall(id) else installed.value = await tryInstallFromLocal(id) } if (id === 'all') { const meta = await getFullMeta() collection.value = { id: 'all', name: 'All', icons: meta.flatMap((c) => { if (isExcludedCollection(c)) return [] return c.icons.map(i => `${c.id}:${i}`) }), } loaded.value = true } else if (id === 'recent') { collection.value = recentIconsCollection.value loaded.value = true } else { collection.value = await getCollectionMeta(id) loaded.value = true } return collection.value } ================================================ FILE: src/store/dark.ts ================================================ export const isDark = useDark({ storageKey: 'icones-schema', }) ================================================ FILE: src/store/dialog.ts ================================================ export const showHelp = ref(false) export const showCaseSelect = ref(false) ================================================ FILE: src/store/index.ts ================================================ export * from './collection' export * from './dark' export * from './dialog' export * from './localstorage' export * from './packing' export * from './progress' ================================================ FILE: src/store/indexedDB.ts ================================================ import type { Table } from 'dexie' import Dexie from 'dexie' const db = new Dexie('icones') db.version(1).stores({ collections: 'id, data', }) const collections: Table = (db as any).collections export async function loadCollection(id: string) { return await collections.where({ id }).first() } export async function saveCollection(id: string, data: any) { return await collections.put({ id, data }, 'id') } export default db ================================================ FILE: src/store/localstorage.ts ================================================ import type { CollectionInfo } from '../data' import type { IdCase } from '../utils/case' import { idCases } from '../utils/case' const RECENT_COLLECTION_CAPACITY = 10 const RECENT_ICONS_CAPACITY = 100 export type ActiveMode = 'normal' | 'select' | 'copy' export const themeColor = useStorage('icones-theme-color', '#329672') export const iconSize = useStorage('icones-icon-size', '2xl') export const previewColor = useStorage('icones-preview-color', '#888888') export const copyPreviewColor = useStorage('icones-copy-preview-color', false) export const listType = useStorage('icones-list-type', 'grid') export const favoritedCollectionIds = useStorage('icones-fav-collections', []) export const recentCollectionIds = useStorage('icones-recent-collections', []) export const recentIconIds = useStorage('icones-recent-icons', []) export const bags = useStorage('icones-bags', []) export const activeMode = useStorage('active-mode', 'normal') export const preferredCase = useStorage('icones-preferfed-case', 'iconify') export const drawerCollapsed = useStorage('icones-drawer-collapsed', false) export const selectedPackageManager = useStorage('icones-package-manager', 'pnpm') export const excludedCollectionIds = useStorage('icones-excluded-collections', []) export const excludedCategories = useStorage('icones-excluded-categories', [ 'Archive / Unmaintained', 'Deprecated / Unavailable', ]) export function getTransformedId(icon: string) { return idCases[preferredCase.value]?.(icon) || icon } export function isFavoritedCollection(id: string) { return favoritedCollectionIds.value.includes(id) } export function isExcludedCollection(collection: CollectionInfo) { return excludedCollectionIds.value.includes(collection.id) || excludedCategories.value.includes(collection.category || '') } export function isExcludedCategory(category: string | undefined) { return category && excludedCategories.value.includes(category) } export function isRecentCollection(id: string) { return recentCollectionIds.value.includes(id) } export function pushRecentCollection(id: string) { recentCollectionIds.value = [id, ...recentCollectionIds.value.filter(i => i !== id)].slice(0, RECENT_COLLECTION_CAPACITY) } export function removeRecentCollection(id: string) { recentCollectionIds.value = recentCollectionIds.value.filter(i => i !== id) } export function isRecentIcon(id: string) { return recentIconIds.value.includes(id) } export function pushRecentIcon(id: string) { recentIconIds.value = [id, ...recentIconIds.value.filter(i => i !== id)].slice(0, RECENT_ICONS_CAPACITY) } export function removeRecentIcon(id: string) { recentIconIds.value = recentIconIds.value.filter(i => i !== id) } export function toggleFavoriteCollection(id: string) { const index = favoritedCollectionIds.value.indexOf(id) if (index >= 0) favoritedCollectionIds.value.splice(index, 1) else favoritedCollectionIds.value.push(id) } export function toggleExcludedCollection(id: string) { const index = excludedCollectionIds.value.indexOf(id) if (index >= 0) excludedCollectionIds.value.splice(index, 1) else excludedCollectionIds.value.push(id) } export function toggleExcludedCategory(category: string) { const index = excludedCategories.value.indexOf(category) if (index >= 0) excludedCategories.value.splice(index, 1) else excludedCategories.value.push(category) } export function addToBag(id: string) { if (!bags.value.includes(id)) bags.value.push(id) } export function removeFromBag(id: string) { const index = bags.value.indexOf(id) if (index >= 0) bags.value.splice(index, 1) } export function inBag(id: string) { return bags.value.includes(id) } export function toggleBag(id: string) { const index = bags.value.indexOf(id) if (index >= 0) bags.value.splice(index, 1) else bags.value.push(id) } export function clearBag() { bags.value = [] } ================================================ FILE: src/store/packing.ts ================================================ export const isPacking = ref(false) export const packingProgress = ref(0) ================================================ FILE: src/store/progress.ts ================================================ export const inProgress = ref(false) export const progress = ref(0) export const progressMessage = ref('') ================================================ FILE: src/sw.ts ================================================ import { getIcons } from '@iconify/utils' import { cacheNames, clientsClaim } from 'workbox-core' import { cleanupOutdatedCaches, createHandlerBoundToURL, precacheAndRoute } from 'workbox-precaching' import { NavigationRoute, registerRoute } from 'workbox-routing' declare let self: ServiceWorkerGlobalScope // self.__WB_MANIFEST is default injection point const swManifest = self.__WB_MANIFEST precacheAndRoute(swManifest) // clean old assets cleanupOutdatedCaches() // to allow work offline registerRoute(new NavigationRoute( createHandlerBoundToURL('index.html'), )) self.skipWaiting() clientsClaim() function buildCollectionResponseHeaders(cachedResponse: Response) { const age = cachedResponse.headers.get('age') const date = cachedResponse.headers.get('date') const etag = cachedResponse.headers.get('etag') const contentType = cachedResponse.headers.get('content-type') const cacheControl = cachedResponse.headers.get('cache-control') const headers: Record = { 'access-control-allow-headers': 'Origin, X-Requested-With, Content-Type, Accept, Accept-Encoding', 'access-control-allow-methods': 'GET, OPTIONS', 'access-control-allow-origin': '*', 'access-control-max-age': '86400', 'cache-control': 'public, max-age=604800, min-refresh=604800, immutable', 'content-type': 'application/json; charset=utf-8', 'cross-origin-resource-policy': 'cross-origin', } if (age) headers.age = age if (date) headers.date = date if (etag) headers.etag = etag if (contentType) headers['content-type'] = contentType if (cacheControl) headers['cache-control'] = cacheControl return headers } const swManifestMap = new Map( swManifest.map((entry) => { if (typeof entry === 'string') { const e = entry[0] === '/' ? entry : `/${entry}` return [e, e] } else { const e = entry.url[0] === '/' ? entry.url : `/${entry.url}` return [e, entry.revision ? `${e}?__WB_REVISION__=${entry.revision}` : e] } }), ) async function getCollection(request: Request, name: string, icons: string[]) { try { const cache = await caches.open(cacheNames.precache) const collectionUrl = `/collections/${name}.json` const url = swManifestMap.get(collectionUrl) ?? collectionUrl let cachedResponse = await cache.match(url) if (!cachedResponse) { cachedResponse = await fetch(url) await cache.put(url, cachedResponse.clone()) } const collection = await cachedResponse.json() return new Response(JSON.stringify(getIcons(collection, icons)), { status: cachedResponse.status, statusText: cachedResponse.statusText, headers: buildCollectionResponseHeaders(cachedResponse), }) } catch { return await fetch(request) } } const fetchRegex = /^https:\/\/(api\.iconify\.design|api\.simplesvg\.com|api\.unisvg\.com)\/(.*)\.json\?icons=(.*)$/ self.addEventListener('fetch', (e) => { const url = e.request.url const match = url.match(fetchRegex) if (match) { e.respondWith(getCollection( e.request, match[2], match[3].replaceAll('%2C', ',').split(','), )) } }) ================================================ FILE: src/utils/case.ts ================================================ export const idCases = { bare(id: string) { return id.replace(/^.*:/, '') }, barePascal(id: string) { return id.replace(/^.*:/, '').replace(/(?:^|[-_:]+)(\w)/g, (_, c) => c.toUpperCase()) }, iconify(id: string) { return id }, dash(id: string) { return id.replace(/:/g, '-') }, slash(id: string) { return id.replace(/:/g, '/') }, doubleHyphen(id: string) { return id.replace(/:/g, '--') }, camel(id: string) { return id.replace(/[-_:]+(\w)/g, (_, c) => c.toUpperCase()) }, pascal(id: string) { return id.replace(/(?:^|[-_:]+)(\w)/g, (_, c) => c.toUpperCase()) }, component(id: string) { return `<${id.replace(/(?:^|[-_:]+)(\w)/g, (_, c) => c.toUpperCase())}/>` }, componentKebab(id: string) { return `<${id.replace(/:/g, '-')}/>` }, unocssColon(id: string) { return `i-${id}` }, unocss(id: string) { return `i-${id.replace(/:/g, '-')}` }, iconifyTailwind(id: string) { return `icon-[${id.replace(/:/g, '--')}]` }, } export type IdCase = keyof typeof idCases ================================================ FILE: src/utils/dataUrlToBlob.ts ================================================ export function dataUrlToBlob(dataurl: string) { const parts = dataurl.split(',') const type = parts[0].split(':')[1].split(';')[0] const base64 = atob(parts[1]) const arr = new Uint8Array(base64.length) for (let i = 0; i < base64.length; i++) arr[i] = base64.charCodeAt(i) return new Blob([arr], { type }) } ================================================ FILE: src/utils/electron.ts ================================================ import hotkeys from 'hotkeys-js' import { isSearchOpen } from '../data' import { isElectron } from '../env' if (isElectron) { hotkeys('ctrl+f, command+f', (e) => { e.preventDefault() isSearchOpen.value = !isSearchOpen.value }) } ================================================ FILE: src/utils/icons.ts ================================================ import type { BuiltInParserName as PrettierParser } from 'prettier' import type { CollectionInfo } from '../data' import { isVSCode } from '../env' import { getTransformedId } from '../store' import { getSvgSymbol } from './pack' import { API_ENTRY, bufferToString, ClearSvg, getSvg, SvgToAstro, SvgToDataURL, SvgToJSX, SvgToQwik, SvgToReactNative, SvgToSolid, SvgToSvelte, SvgToTSX, SvgToVue, toComponentName, } from './svg' import { svgToPngDataUrl } from './svgToPng' export interface Snippet { name: string tag?: string lang: string // for shiki prettierParser: PrettierParser // for prettier } export { toComponentName } export async function Download(blob: Blob, name: string) { if (isVSCode) { blob.arrayBuffer().then( buffer => vscode.postMessage({ command: 'download', name, text: bufferToString(buffer), }), ) } else { const url = URL.createObjectURL(blob) const a = document.createElement('a') a.href = url a.download = name a.click() a.remove() } } export const SnippetMap: Record> = { Snippets: { 'svg': { name: 'SVG', lang: 'html', prettierParser: 'html' }, 'svg-symbol': { name: 'SVG Symbol', lang: 'html', prettierParser: 'html' }, 'png': { name: 'PNG', lang: 'html', prettierParser: 'html' }, 'html': { name: 'Iconify', lang: 'html', prettierParser: 'html' }, 'pure-jsx': { name: 'JSX', lang: 'jsx', prettierParser: 'typescript' }, }, Components: { 'vue': { name: 'Vue', lang: 'vue', prettierParser: 'vue' }, 'vue-ts': { name: 'Vue', tag: 'TS', lang: 'vue', prettierParser: 'vue' }, 'jsx': { name: 'React', lang: 'jsx', prettierParser: 'typescript' }, 'tsx': { name: 'React', tag: 'TS', lang: 'tsx', prettierParser: 'typescript' }, 'svelte': { name: 'Svelte', lang: 'svelte', prettierParser: 'typescript' }, 'qwik': { name: 'Qwik', lang: 'tsx', prettierParser: 'typescript' }, 'solid': { name: 'Solid', lang: 'tsx', prettierParser: 'typescript' }, 'astro': { name: 'Astro', lang: 'astro', prettierParser: 'typescript' }, 'react-native': { name: 'React Native', lang: 'tsx', prettierParser: 'typescript' }, 'unplugin': { name: 'Unplugin Icons', lang: 'tsx', prettierParser: 'typescript' }, 'unocss': { name: 'UnoCSS', lang: 'html', prettierParser: 'html' }, 'unocss-attributify': { name: 'UnoCSS', tag: 'attributify', lang: 'html', prettierParser: 'html' }, }, Links: { url: { name: 'URL', lang: 'html', prettierParser: 'html' }, data_url: { name: 'Data URL', lang: 'html', prettierParser: 'html' }, }, } export async function getIconSnippet( collections: CollectionInfo[], icon: string, type: string, snippet = true, color = 'currentColor', ): Promise { if (!icon) return let url = `${API_ENTRY}/${icon}.svg` if (color !== 'currentColor') url = `${url}?color=${encodeURIComponent(color)}` switch (type) { case 'id': return getTransformedId(icon) case 'url': return url case 'html': return `` case 'css': return `background: url('${url}') no-repeat center center / contain;` case 'svg': return await getSvg(collections, icon, '32', color) case 'png': return await svgToPngDataUrl(await getSvg(collections, icon, '32', color)) case 'svg-symbol': return await getSvgSymbol(collections, icon, '32', color) case 'data_url': return SvgToDataURL(await getSvg(collections, icon, undefined, color)) case 'pure-jsx': return ClearSvg(await getSvg(collections, icon, undefined, color)) case 'jsx': return SvgToJSX(await getSvg(collections, icon, undefined, color), toComponentName(icon), snippet) case 'tsx': return SvgToTSX(await getSvg(collections, icon, undefined, color), toComponentName(icon), snippet) case 'qwik': return SvgToQwik(await getSvg(collections, icon, undefined, color), toComponentName(icon), snippet) case 'vue': return SvgToVue(await getSvg(collections, icon, undefined, color), toComponentName(icon)) case 'vue-ts': return SvgToVue(await getSvg(collections, icon, undefined, color), toComponentName(icon), true) case 'solid': return SvgToSolid(await getSvg(collections, icon, undefined, color), toComponentName(icon), snippet) case 'svelte': return SvgToSvelte(await getSvg(collections, icon, undefined, color)) case 'astro': return SvgToAstro(await getSvg(collections, icon, undefined, color)) case 'react-native': return SvgToReactNative(await getSvg(collections, icon, undefined, color), toComponentName(icon), snippet) case 'unplugin': return `import ${toComponentName(icon)} from '~icons/${icon.split(':')[0]}/${icon.split(':')[1]}'` case 'unocss': return `
` case 'unocss-attributify': return `
` } } export function getIconDownloadLink(icon: string) { return `${API_ENTRY}/${icon}.svg?download=true&inline=false&height=auto` } ================================================ FILE: src/utils/pack-worker-client.ts ================================================ import PackerWorker from './worker?worker' export const packerWorker = new PackerWorker({ name: 'IconesPackWorker', }) ================================================ FILE: src/utils/pack.ts ================================================ import type { CollectionInfo } from '../data' import type { PackType } from './svg' import { Download } from './icons' import { getSvg, LoadIconSvgs } from './svg' export async function getSvgSymbol( collections: CollectionInfo[], icon: string, size = '1em', color = 'currentColor', ) { const svgMarkup = await getSvg(collections, icon, size, color) const symbolElem = document.createElementNS('http://www.w3.org/2000/svg', 'symbol') const node = document.createElement('div') // Create any old element node.innerHTML = svgMarkup // Grab the inner HTML and move into a symbol element symbolElem.innerHTML = node.querySelector('svg')!.innerHTML symbolElem.setAttribute('viewBox', node.querySelector('svg')!.getAttribute('viewBox')!) symbolElem.id = icon.replace(/:/, '-') // Simple slugify for quick symbol lookup return symbolElem?.outerHTML } export async function PackSVGSprite( collections: CollectionInfo[], icons: string[], options: any = {}, ) { if (!icons.length) return const data = await LoadIconSvgs(collections, icons) let symbols = '' for (const { name } of data) symbols += `${await getSvgSymbol(collections, name, options.size, options.color)}\n` const svg = ` ${symbols} ` const blob = new Blob([svg], { type: 'image/svg+xml' }) Download(blob, 'sprite.svg') } export async function PackIconFont( collections: CollectionInfo[], icons: string[], options: any = {}, ) { if (!icons.length) return const { packerWorker } = await import('./pack-worker-client') return new Promise((resolve, reject) => { packerWorker.addEventListener('message', (event) => { if (event.data.error) { reject(event.data.error) return } const { blob, name } = event.data as { blob: ArrayBuffer, name: string } Download( new Blob([blob]), name, ) resolve() }, { once: true }) const arrayBuffer = createArrayBufferFromCollections(collections) packerWorker.postMessage({ operation: 'pack-font-zip', collections: arrayBuffer, payload: { icons: toRaw(icons), ...toRaw(options), }, }, [arrayBuffer]) }) } export async function PackSvgZip( collections: CollectionInfo[], icons: string[], name: string, ) { if (!icons.length) return const { packerWorker } = await import('./pack-worker-client') return new Promise((resolve, reject) => { packerWorker.addEventListener('message', (event) => { if (event.data.error) { reject(event.data.error) return } const { blob } = event.data as { blob: ArrayBuffer } Download( new Blob([blob]), `${name}.zip`, ) resolve() }, { once: true }) const arrayBuffer = createArrayBufferFromCollections(collections) packerWorker.postMessage({ operation: 'pack-svg-zip', collections: arrayBuffer, payload: { icons: toRaw(icons), }, }, [arrayBuffer]) }) } export async function PackJsonZip( collections: CollectionInfo[], icons: string[], name: string, ) { if (!icons.length) return const { packerWorker } = await import('./pack-worker-client') return new Promise((resolve, reject) => { packerWorker.addEventListener('message', (event) => { if (event.data.error) { reject(event.data.error) return } const { blob } = event.data as { blob: ArrayBuffer } Download( new Blob([blob]), `${name}.zip`, ) resolve() }, { once: true }) const arrayBuffer = createArrayBufferFromCollections(collections) packerWorker.postMessage({ operation: 'pack-json-zip', collections: arrayBuffer, payload: { icons: toRaw(icons), name, }, }, [arrayBuffer]) }) } export async function PackZip( collections: CollectionInfo[], icons: string[], name: string, type: PackType = 'svg', ) { if (!icons.length) return const { packerWorker } = await import('./pack-worker-client') return new Promise((resolve, reject) => { packerWorker.addEventListener('message', (event) => { if (event.data.error) { reject(event.data.error) return } const { blob } = event.data as { blob: ArrayBuffer } Download( new Blob([blob]), `${name}-${type}.zip`, ) resolve() }, { once: true }) const arrayBuffer = createArrayBufferFromCollections(collections) packerWorker.postMessage({ operation: 'pack-zip', collections: arrayBuffer, payload: { icons: toRaw(icons), name, type, }, }, [arrayBuffer]) }) } function createArrayBufferFromCollections( collections: CollectionInfo[], ) { return new TextEncoder().encode(JSON.stringify(collections)).buffer } ================================================ FILE: src/utils/query.ts ================================================ export function cleanupQuery(query: Record) { for (const key of Object.keys(query)) { if (!query[key]) delete query[key] } return query } ================================================ FILE: src/utils/sample.ts ================================================ export function sample(arr: T[], num: number) { return Array.from({ length: num }, () => arr[Math.floor(arr.length * Math.random())]) } ================================================ FILE: src/utils/shiki.ts ================================================ import type { HighlighterCore } from 'shiki/core' import { createHighlighterCore } from 'shiki/core' import { createJavaScriptRegexEngine } from 'shiki/engine/javascript' export const shiki = computedAsync(async (onCancel) => { const shiki = await createHighlighterCore({ engine: createJavaScriptRegexEngine(), themes: [ () => import('shiki/themes/vitesse-dark.mjs'), () => import('shiki/themes/vitesse-light.mjs'), ], langs: [ () => import('shiki/langs/html.mjs'), () => import('shiki/langs/jsx.mjs'), () => import('shiki/langs/tsx.mjs'), () => import('shiki/langs/vue.mjs'), () => import('shiki/langs/astro.mjs'), () => import('shiki/langs/svelte.mjs'), ], }) onCancel(() => shiki?.dispose()) return shiki }) export function highlight(code: string, lang: string) { if (!shiki.value) return code return shiki.value.codeToHtml(code, { lang, defaultColor: false, themes: { dark: 'vitesse-dark', light: 'vitesse-light', }, }) } ================================================ FILE: src/utils/svg/base64.ts ================================================ /* eslint-disable eslint-comments/no-unlimited-disable */ /* eslint-disable */ // @ts-expect-error ignore const Base64={_keyStr:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",encode:function(e){var t="";var n,r,i,s,o,u,a;var f=0;e=Base64._utf8_encode(e);while(f>2;o=(n&3)<<4|r>>4;u=(r&15)<<2|i>>6;a=i&63;if(isNaN(r)){u=a=64}else if(isNaN(i)){a=64}t=t+this._keyStr.charAt(s)+this._keyStr.charAt(o)+this._keyStr.charAt(u)+this._keyStr.charAt(a)}return t},decode:function(e){var t="";var n,r,i;var s,o,u,a;var f=0;e=e.replace(/[^A-Za-z0-9\+\/\=]/g,"");while(f>4;r=(o&15)<<4|u>>2;i=(u&3)<<6|a;t=t+String.fromCharCode(n);if(u!=64){t=t+String.fromCharCode(r)}if(a!=64){t=t+String.fromCharCode(i)}}t=Base64._utf8_decode(t);return t},_utf8_encode:function(e){e=e.replace(/\r\n/g,"\n");var t="";for(var n=0;n127&&r<2048){t+=String.fromCharCode(r>>6|192);t+=String.fromCharCode(r&63|128)}else{t+=String.fromCharCode(r>>12|224);t+=String.fromCharCode(r>>6&63|128);t+=String.fromCharCode(r&63|128)}}return t},_utf8_decode:function(e){var t="";var n=0;var r=c1=c2=0;while(n191&&r<224){c2=e.charCodeAt(n+1);t+=String.fromCharCode((r&31)<<6|c2&63);n+=2}else{c2=e.charCodeAt(n+1);c3=e.charCodeAt(n+2);t+=String.fromCharCode((r&15)<<12|(c2&63)<<6|c3&63);n+=3}}return t}} export default Base64 ================================================ FILE: src/utils/svg/bufferToString.ts ================================================ export function bufferToString(buffer: ArrayBuffer) { return String.fromCharCode.apply(null, new Uint16Array(buffer) as any) } ================================================ FILE: src/utils/svg/helpers.ts ================================================ import type { Node } from 'ultrahtml' import type { CollectionInfo } from '../../data' import { encodeSvgForCss } from '@iconify/utils' import { parse, transformSync } from 'ultrahtml' import Base64 from './base64' import { HtmlToJSX } from './htmlToJsx' import { getSvg } from './loader' import { prettierCode } from './prettier' export type PackType = 'svg' | 'tsx' | 'jsx' | 'vue' | 'solid' | 'qwik' | 'svelte' | 'astro' | 'react-native' | 'json' export function normalizeZipFleName(svgName: string): string { return svgName.replace(':', '-') } export function toComponentName(icon: string) { return icon.split(/[:\-_]/).filter(Boolean).map(s => s[0].toUpperCase() + s.slice(1).toLowerCase()).join('') } export function ClearSvg(svgCode: string, reactJSX?: boolean) { const result = transformSync(parse(svgCode).children[0] as Node, [ (node: Node): Node => { if (node.name !== 'svg') return node const attributes = node.attributes || {} // keep only 'viewBox', 'width', 'height', 'focusable', 'xmlns', 'xlink' attributes const allowedAttributes = ['viewBox', 'width', 'height', 'focusable', 'xmlns', 'xlink'] for (const key of Object.keys(attributes)) { if (!allowedAttributes.includes(key)) { delete attributes[key] } } node.attributes = attributes return node }, ]) return HtmlToJSX(result, reactJSX) } export function SvgToDataURL(svg: string) { const base64 = `data:image/svg+xml;base64,${Base64.encode(svg)}` const plain = `data:image/svg+xml,${encodeSvgForCss(svg)}` // Return the shorter of the two data URLs return base64.length < plain.length ? base64 : plain } export function SvgToJSX(svg: string, name: string, snippet: boolean) { const code = ` export function ${name}(props) { return ( ${ClearSvg(svg, true).replace(//, '')} ) }` if (snippet) return prettierCode(code, 'babel-ts') else return prettierCode(`import React from 'react'\n${code}\nexport default ${name}`, 'babel-ts') } export function SvgToTSX(svg: string, name: string, snippet: boolean, reactJSX = true) { let code = ` export function ${name}(props: SVGProps) { return ( ${ClearSvg(svg, reactJSX).replace(//, '')} ) }` code = snippet ? code : `import React, { SVGProps } from 'react'\n${code}\nexport default ${name}` return prettierCode(code, 'babel-ts') } export function SvgToQwik(svg: string, name: string, snippet: boolean) { let code = ` export function ${name}(props: QwikIntrinsicElements['svg'], key: string) { return ( ${ClearSvg(svg, false).replace(//, '')} ) }` code = snippet ? code : `import type { QwikIntrinsicElements } from '@builder.io/qwik'\n${code}\nexport default ${name}` return prettierCode(code, 'babel-ts') } export function SvgToVue(svg: string, name: string, isTs?: boolean) { const content = ` ` const code = isTs ? content.replace('