[
  {
    "path": ".github/renovate.json",
    "content": "{\n  \"extends\": [\n    \"config:recommended\"\n  ],\n  \"rangeStrategy\": \"bump\",\n  \"packageRules\": [\n    {\n      \"description\": \"Group all non-major updates weekly except @iconify/json\",\n      \"extends\": [\"schedule:weekly\"],\n      \"matchPackagePatterns\": [\"*\"],\n      \"excludePackageNames\": [\"@iconify/json\"],\n      \"matchUpdateTypes\": [\"minor\", \"patch\"],\n      \"groupName\": \"all non-major dependencies\",\n      \"groupSlug\": \"all-minor-patch\"\n    },\n    {\n      \"description\": \"Create non-major @iconify/json updates daily\",\n      \"matchUpdateTypes\": [\"minor\", \"patch\"],\n      \"matchPackagePatterns\": [\"^@iconify/json\"],\n      \"extends\": [\"schedule:daily\"],\n      \"automerge\": true\n    },\n    {\n      \"description\": \"Suppress major updates using Dependency Dashboard\",\n      \"matchUpdateTypes\": [\"major\"],\n      \"dependencyDashboardApproval\": true\n    }\n  ]\n}\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  push:\n    branches:\n      - main\n\n  pull_request:\n    branches:\n      - main\n\njobs:\n  lint:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Install pnpm\n        uses: pnpm/action-setup@v4\n\n      - name: Set node\n        uses: actions/setup-node@v4\n        with:\n          node-version: lts/*\n          cache: pnpm\n\n      - name: Install\n        run: pnpm install\n\n      - name: Lint\n        run: pnpm run lint\n\n  typecheck:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Install pnpm\n        uses: pnpm/action-setup@v4\n\n      - name: Set node\n        uses: actions/setup-node@v4\n        with:\n          node-version: lts/*\n          cache: pnpm\n\n      - name: Install\n        run: pnpm install\n\n      - name: Typecheck\n        run: pnpm run typecheck\n\n  build:\n    runs-on: ${{ matrix.os }}\n\n    strategy:\n      matrix:\n        node-version: [lts/*]\n        os: [ubuntu-latest]\n      fail-fast: false\n\n    steps:\n      - uses: actions/checkout@v4\n      - name: Install pnpm\n        uses: pnpm/action-setup@v4\n      - name: Use Node.js ${{ matrix.node-version }}\n        uses: actions/setup-node@v4\n        with:\n          node-version: ${{ matrix.node-version }}\n          registry-url: https://registry.npmjs.org/\n          cache: pnpm\n      - run: pnpm install\n      - name: Build\n        run: pnpm run build\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules\nyarn-error.log\ndist\n.idea\n\nsrc/assets/collections.json\n.DS_Store\n\npublic/collections\npublic/lib\nrelease\ncollections-info.json\ncollections-meta.json\n\ndist-electron\ndev-dist\n"
  },
  {
    "path": ".npmrc",
    "content": "shamefully-hoist=true\nignore-workspace-root-check=true\nshell-emulator=true\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n  \"cSpell.words\": [\n    \"icones\"\n  ],\n\n  // Enable the ESlint flat config support\n  \"eslint.experimental.useFlatConfig\": true,\n\n  // Disable the default formatter, use eslint instead\n  \"prettier.enable\": false,\n  \"editor.formatOnSave\": false,\n\n  // Auto fix\n  \"editor.codeActionsOnSave\": {\n    \"source.fixAll\": \"explicit\",\n    \"source.organizeImports\": \"never\"\n  },\n\n  // Silent the stylistic rules in you IDE, but still auto fix them\n  \"eslint.rules.customizations\": [\n    { \"rule\": \"style/*\", \"severity\": \"off\" },\n    { \"rule\": \"*-indent\", \"severity\": \"off\" },\n    { \"rule\": \"*-spacing\", \"severity\": \"off\" },\n    { \"rule\": \"*-spaces\", \"severity\": \"off\" },\n    { \"rule\": \"*-order\", \"severity\": \"off\" },\n    { \"rule\": \"*-dangle\", \"severity\": \"off\" },\n    { \"rule\": \"*-newline\", \"severity\": \"off\" },\n    { \"rule\": \"*quotes\", \"severity\": \"off\" },\n    { \"rule\": \"*semi\", \"severity\": \"off\" }\n  ],\n\n  // Enable eslint for all supported languages\n  \"eslint.validate\": [\n    \"javascript\",\n    \"javascriptreact\",\n    \"typescript\",\n    \"typescriptreact\",\n    \"vue\",\n    \"html\",\n    \"markdown\",\n    \"json\",\n    \"jsonc\",\n    \"yaml\"\n  ]\n}\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020 Anthony Fu\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "<h1 align=\"center\">\nIcônes\n</h1>\n\n<p align=\"center\">Icon Explorer with <b>Instant</b> searching, powered by <a href=\"https://github.com/iconify/iconify\" target=\"_blank\">Iconify</a> </p>\n\n<p align=\"center\">\n<a href=\"https://icones.js.org\">Go to App</a>\n</p>\n\n<p align=\"center\">\n<sub><em>Electron is coming...</em></sub>\n</p>\n\n![](./screenshots/1.png)\n![](./screenshots/2.png)\n![](./screenshots/3.png)\n![](./screenshots/4.png)\n![](./screenshots/5.png)\n\n<p align=\"center\">\n<sub><em>Dark Mode is now Live!</em></sub>\n</p>\n\n![](./screenshots/6.png)\n\n<p align=\"center\">\n  <a href=\"https://cdn.jsdelivr.net/gh/antfu/static/sponsors.svg\">\n    <img src='https://cdn.jsdelivr.net/gh/antfu/static/sponsors.svg'/>\n  </a>\n</p>\n\n### Features\n\n- **Instant Fuzzy Searching** _- all are done locally, no web queries!_\n- The **Bag** _- select your icons and pack them into a ready-to-use icon font!_\n  - _[svg-packer](https://github.com/antfu/svg-packer) was born from this XD_\n- Copy the usage scripts\n- SVGs direct download\n- Mobile friendly\n- Collection bookmarks\n- Categories filters\n- Dark mode\n- Built with [Vite](https://github.com/vitejs/vite) and Vue 3\n  - If you like how it's built - try [🏕 Vitesse](https://github.com/antfu/vitesse), an opinionated starter template made from Icônes\n\n### Community\n\n- [VS Code Extension](https://github.com/afzalsayed96/vscode-icones) by [@afzalsayed96](https://github.com/afzalsayed96)\n\n### TODOs\n\n- Electron client (Coming!)\n- Full-offline mode - pack all the icons\n\n## License\n\nMIT - Anthony Fu 2020\n"
  },
  {
    "path": "electron/electron-builder.json5",
    "content": "/**\n * @see https://www.electron.build/configuration/configuration\n */\n{\n  \"productName\": \"Icônes\",\n  \"appId\": \"me.antfu.icones\",\n  \"directories\": {\n    \"output\": \"release\"\n  },\n  \"icon\": \"build/icon.png\",\n  \"mac\": {\n    \"target\": \"dmg\"\n  },\n  \"extraResources\": [\n    {\n      \"from\": \"build/icon.png\",\n      \"to\": \"icon.png\"\n    }\n  ],\n  \"publish\": {\n    \"provider\": \"github\",\n    \"owner\": \"antfu\",\n    \"repo\": \"icones\",\n    \"private\": false\n  },\n  \"files\": [\"dist-electron\", \"dist\"],\n  \"win\": {\n    \"target\": [\n      {\n        \"target\": \"nsis\",\n        \"arch\": [\"x64\"]\n      }\n    ]\n  },\n  \"nsis\": {\n    \"oneClick\": false,\n    \"perMachine\": false,\n    \"allowToChangeInstallationDirectory\": true,\n    \"deleteAppDataOnUninstall\": false\n  }\n}\n"
  },
  {
    "path": "electron/eslint.config.js",
    "content": "// @ts-check\nimport antfu from '@antfu/eslint-config'\n\nexport default antfu(\n  {\n    ignores: [\n      // eslint ignore globs here\n    ],\n  },\n  {\n    rules: {\n      // overrides\n    },\n  },\n)\n"
  },
  {
    "path": "electron/package.json",
    "content": "{\n  \"name\": \"icones-electron\",\n  \"version\": \"0.0.0\",\n  \"appname\": \"Icônes\",\n  \"description\": \"Explorer for Iconify with Instant searching.\",\n  \"author\": \"Anthony Fu<https://github.com/antfu>\",\n  \"license\": \"MIT\",\n  \"homepage\": \"https://github.com/antfu/icones#readme\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/antfu/icones.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/antfu/icones/issues\"\n  },\n  \"main\": \"dist-electron/main/index.js\",\n  \"copyright\": \"Copyright © 2020 Anthony Fu\",\n  \"scripts\": {\n    \"dev\": \"vite ../ --port 3333 --mode electron\",\n    \"copy\": \"cp -r ../dist ./\",\n    \"build\": \"vite build ../ --mode electron && pnpm copy && electron-builder\"\n  },\n  \"devDependencies\": {\n    \"electron\": \"27.0.4\",\n    \"electron-builder\": \"24.6.4\",\n    \"electron-devtools-installer\": \"3.2.0\",\n    \"vite-plugin-electron\": \"0.15.4\",\n    \"vite-plugin-electron-renderer\": \"0.14.5\",\n    \"vite-plugin-esmodule\": \"1.5.0\"\n  }\n}\n"
  },
  {
    "path": "electron/src/main/index.ts",
    "content": "import path from 'node:path'\nimport { app, BrowserWindow, shell } from 'electron'\nimport installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'\n\nlet mainWindow: BrowserWindow | null = null\n\napp.disableHardwareAcceleration()\n\nconst PROJECT_ROOT = path.resolve(__dirname, '../..')\n\nasync function createMainWindow() {\n  const win = new BrowserWindow({\n    title: app.name,\n    show: false,\n    width: 660,\n    height: 500,\n    minWidth: 200,\n    minHeight: 200,\n    titleBarStyle: 'hiddenInset',\n    webPreferences: {\n      nodeIntegration: true,\n      contextIsolation: false,\n    },\n  })\n\n  if (app.isPackaged) {\n    win.loadFile(path.join(PROJECT_ROOT, 'dist/index.html'))\n    win.removeMenu()\n  }\n  else {\n    win.loadURL('http://localhost:3333/')\n    win.webContents.openDevTools()\n    await installExtension(VUEJS_DEVTOOLS)\n  }\n\n  win.on('ready-to-show', () => {\n    win.show()\n  })\n\n  win.on('closed', () => {\n    mainWindow = null\n  })\n\n  const handleRedirect = (e: Event, url: string) => {\n    if (url !== win.webContents.getURL()) {\n      e.preventDefault()\n      shell.openExternal(url)\n    }\n  }\n\n  // @ts-expect-error - no types\n  win.webContents.on('will-navigate', handleRedirect)\n\n  win.webContents.setWindowOpenHandler((details) => {\n    shell.openExternal(details.url)\n    return { action: 'deny' }\n  })\n\n  return win\n}\n\nif (!app.requestSingleInstanceLock())\n  app.quit()\n\napp.on('window-all-closed', () => {\n  app.quit()\n})\n\napp.on('activate', async () => {\n  if (!mainWindow)\n    mainWindow = await createMainWindow()\n})\n\n; (async () => {\n  await app.whenReady()\n\n  mainWindow = await createMainWindow()\n  mainWindow.focus()\n})()\n  .catch(console.error)\n"
  },
  {
    "path": "electron/src/renderer/index.js",
    "content": ""
  },
  {
    "path": "eslint.config.js",
    "content": "// @ts-check\nimport antfu from '@antfu/eslint-config'\n\nexport default antfu(\n  {\n    ignores: [\n      '**/src/assets/collections.json',\n      '**/public/collections',\n      '**/public/lib',\n      '**/release',\n      '**/collections-info.json',\n      '**/collections-meta.json',\n      '**/dist-electron',\n    ],\n    formatters: true,\n  },\n)\n"
  },
  {
    "path": "index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>Icônes</title>\n    <link rel=\"icon\" href=\"/favicon.svg\" />\n    <link rel=\"icon\" href=\"/favicon-dark.svg\" media=\"(prefers-color-scheme: light)\" />\n    <link rel=\"search\" type=\"application/opensearchdescription+xml\" href=\"/search.xml\" title=\"Icônes\" />\n  </head>\n  <body class=\"dragging bg-base color-base\">\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "netlify.toml",
    "content": "[build]\npublish = \"dist\"\ncommand = \"pnpm run build\"\n\n[build.environment]\nNODE_VERSION = \"20\"\n\n[[redirects]]\nfrom = \"/*\"\nto = \"/index.html\"\nstatus = 200\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"icones\",\n  \"type\": \"module\",\n  \"version\": \"0.0.0\",\n  \"private\": true,\n  \"packageManager\": \"pnpm@10.24.0\",\n  \"author\": \"Anthony Fu<https://github.com/antfu>\",\n  \"license\": \"MIT\",\n  \"homepage\": \"https://github.com/antfu/icones#readme\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/antfu/icones.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/antfu/icones/issues\"\n  },\n  \"scripts\": {\n    \"postinstall\": \"esno scripts/prepare.ts\",\n    \"lint\": \"eslint .\",\n    \"dev\": \"vite --port 3333 --open\",\n    \"dev-pwa\": \"SW_DEV=true vite --port 3333\",\n    \"typecheck\": \"vue-tsc --noEmit\",\n    \"dev:electron\": \"npm -C ./electron run dev\",\n    \"build\": \"NODE_ENV=production vite build\",\n    \"build:electron\": \"NODE_ENV=production npm -C ./electron run build\"\n  },\n  \"dependencies\": {\n    \"@antfu/utils\": \"^9.3.0\",\n    \"@vueuse/core\": \"^14.1.0\",\n    \"dexie\": \"^4.2.1\",\n    \"file-saver\": \"^2.0.5\",\n    \"floating-vue\": \"^5.2.2\",\n    \"fzf\": \"^0.5.2\",\n    \"hotkeys-js\": \"^3.13.15\",\n    \"iconify-icon\": \"^3.0.2\",\n    \"prettier\": \"^3.7.3\",\n    \"ultrahtml\": \"^1.6.0\",\n    \"vue\": \"^3.5.25\",\n    \"vue-chemistry\": \"^0.2.2\",\n    \"vue-router\": \"^4.6.3\"\n  },\n  \"devDependencies\": {\n    \"@antfu/eslint-config\": \"^6.2.0\",\n    \"@iconify/json\": \"^2.2.413\",\n    \"@types/file-saver\": \"^2.0.7\",\n    \"@types/fs-extra\": \"^11.0.4\",\n    \"@vitejs/plugin-vue\": \"^6.0.2\",\n    \"client-zip\": \"^2.5.0\",\n    \"dayjs\": \"^1.11.19\",\n    \"eslint\": \"^9.39.1\",\n    \"eslint-plugin-format\": \"^1.0.2\",\n    \"esno\": \"^4.8.0\",\n    \"fast-glob\": \"^3.3.3\",\n    \"fs-extra\": \"^11.3.2\",\n    \"lru-cache\": \"^11.2.4\",\n    \"pnpm\": \"^10.24.0\",\n    \"shiki\": \"^3.17.1\",\n    \"svg-packer\": \"^1.0.0\",\n    \"typescript\": \"^5.9.3\",\n    \"unocss\": \"^66.5.9\",\n    \"unplugin-auto-import\": \"^20.3.0\",\n    \"unplugin-vue-components\": \"^30.0.0\",\n    \"vite\": \"^7.2.6\",\n    \"vite-plugin-pages\": \"^0.33.1\",\n    \"vite-plugin-pwa\": \"^1.2.0\",\n    \"vue-tsc\": \"^3.1.5\"\n  }\n}\n"
  },
  {
    "path": "pnpm-workspace.yaml",
    "content": "packages:\n  - electron\nneverBuiltDependencies:\n  - ttf2woff2\n"
  },
  {
    "path": "public/search.xml",
    "content": "<OpenSearchDescription xmlns=\"http://a9.com/-/spec/opensearch/1.1/\" xmlns:moz=\"http://www.mozilla.org/2006/browser/search/\">\n  <ShortName>Icônes</ShortName>\n  <Description>⚡️ Iconify Icon Explorer</Description>\n  <InputEncoding>UTF-8</InputEncoding>\n  <Image width=\"192\" height=\"192\">https://icones.js.org/android-chrome-192x192.png</Image>\n  <Image width=\"512\" height=\"512\">https://icones.js.org/android-chrome-192x192.png</Image>\n  <Image width=\"16\" height=\"16\">https://icones.js.org/favicon.svg</Image>\n  <Url type=\"text/html\" template=\"https://icones.js.org/collection/all?s={searchTerms}\"/>\n</OpenSearchDescription>\n"
  },
  {
    "path": "scripts/prepare.ts",
    "content": "import path from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport fs from 'fs-extra'\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url))\nconst out = path.resolve(__dirname, '../public')\n\nfunction ObjectPick(source: Record<string, any>, keys: string[]) {\n  const obj: Record<string, any> = {}\n  for (const key of keys)\n    obj[key] = source[key]\n  return obj\n}\n\nfunction humanFileSize(size: number) {\n  const i = Math.floor(Math.log(size) / Math.log(1024))\n  const v = (size / 1024 ** i)\n  return `${v.toFixed(2)} ${['B', 'kB', 'MB', 'GB', 'TB'][i]}`\n}\n\nasync function prepareJSON() {\n  const dir = path.resolve(__dirname, '../node_modules/@iconify/json')\n  const collectionsDir = path.resolve(__dirname, '../public/collections')\n\n  const raw = await fs.readJSON(path.join(dir, 'collections.json'))\n  await fs.ensureDir(collectionsDir)\n\n  const collections = Object\n    .entries(raw)\n    .map(([id, v]) => ({\n      ...(v as any),\n      id,\n      category: (v as any).hidden ? 'Deprecated / Unavailable' : (v as any).category,\n    }))\n\n  const collectionsMeta = []\n\n  for (const info of collections) {\n    const setData = await fs.readJSON(path.join(dir, 'json', `${info.id}.json`))\n\n    const icons = Object.keys(setData.icons)\n    const categories = setData.categories\n    const meta = { ...info, icons, categories }\n    const rawFilePath = path.join(collectionsDir, `${info.id}.json`)\n    const metaFilePath = path.join(collectionsDir, `${info.id}-meta.json`)\n\n    await fs.writeJSON(rawFilePath, setData)\n    await fs.writeJSON(metaFilePath, meta)\n    collectionsMeta.push(meta)\n\n    info.sampleIcons = icons.slice(0, 9)\n    if (info.id === 'logos') {\n      info.sampleIcons = [\n        'vue',\n        'vitejs',\n        'vitest',\n        'rollupjs',\n        'github-icon',\n        'eslint',\n        'esbuild',\n        'typescript-icon',\n        'netlify-icon',\n      ]\n    }\n    // non-square icons\n    if (['flag', 'flagpack', 'cif', 'fa', 'fontisto', 'et', 'ps'].includes(info.id))\n      info.sampleIcons = info.sampleIcons.slice(0, 6)\n\n    info.prepacked = {\n      prefix: setData.prefix,\n      width: setData.width,\n      height: setData.height,\n      icons: ObjectPick(setData.icons, info.sampleIcons),\n    }\n    info.size = humanFileSize(fs.statSync(rawFilePath).size)\n  }\n\n  await fs.writeJSON(path.join(out, 'collections-meta.json'), collectionsMeta)\n  const infoOut = path.resolve(__dirname, '../src/data')\n  await fs.writeJSON(path.join(infoOut, 'collections-info.json'), collections)\n}\n\nprepareJSON()\n"
  },
  {
    "path": "src/App.vue",
    "content": "<script setup lang='ts'>\nimport { useThemeColor } from './hooks'\n\nconst { style } = useThemeColor()\n</script>\n\n<template>\n  <div class=\"flex flex-col h-screen overflow-hidden bg-base\" :style=\"style\">\n    <div class=\"h-full flex-auto overflow-overlay\">\n      <RouterView />\n    </div>\n    <Progress />\n  </div>\n</template>\n"
  },
  {
    "path": "src/auto-imports.d.ts",
    "content": "/* eslint-disable */\n/* prettier-ignore */\n// @ts-nocheck\n// noinspection JSUnusedGlobalSymbols\n// Generated by unplugin-auto-import\n// biome-ignore lint: disable\nexport {}\ndeclare global {\n  const EffectScope: typeof import('vue').EffectScope\n  const asyncComputed: typeof import('@vueuse/core').asyncComputed\n  const autoResetRef: typeof import('@vueuse/core').autoResetRef\n  const computed: typeof import('vue').computed\n  const computedAsync: typeof import('@vueuse/core').computedAsync\n  const computedEager: typeof import('@vueuse/core').computedEager\n  const computedInject: typeof import('@vueuse/core').computedInject\n  const computedWithControl: typeof import('@vueuse/core').computedWithControl\n  const controlledComputed: typeof import('@vueuse/core').controlledComputed\n  const controlledRef: typeof import('@vueuse/core').controlledRef\n  const createApp: typeof import('vue').createApp\n  const createEventHook: typeof import('@vueuse/core').createEventHook\n  const createGlobalState: typeof import('@vueuse/core').createGlobalState\n  const createInjectionState: typeof import('@vueuse/core').createInjectionState\n  const createReactiveFn: typeof import('@vueuse/core').createReactiveFn\n  const createRef: typeof import('@vueuse/core').createRef\n  const createReusableTemplate: typeof import('@vueuse/core').createReusableTemplate\n  const createSharedComposable: typeof import('@vueuse/core').createSharedComposable\n  const createTemplatePromise: typeof import('@vueuse/core').createTemplatePromise\n  const createUnrefFn: typeof import('@vueuse/core').createUnrefFn\n  const customRef: typeof import('vue').customRef\n  const debouncedRef: typeof import('@vueuse/core').debouncedRef\n  const debouncedWatch: typeof import('@vueuse/core').debouncedWatch\n  const defineAsyncComponent: typeof import('vue').defineAsyncComponent\n  const defineComponent: typeof import('vue').defineComponent\n  const eagerComputed: typeof import('@vueuse/core').eagerComputed\n  const effectScope: typeof import('vue').effectScope\n  const extendRef: typeof import('@vueuse/core').extendRef\n  const getCurrentInstance: typeof import('vue').getCurrentInstance\n  const getCurrentScope: typeof import('vue').getCurrentScope\n  const getCurrentWatcher: typeof import('vue').getCurrentWatcher\n  const h: typeof import('vue').h\n  const ignorableWatch: typeof import('@vueuse/core').ignorableWatch\n  const inject: typeof import('vue').inject\n  const injectLocal: typeof import('@vueuse/core').injectLocal\n  const isDefined: typeof import('@vueuse/core').isDefined\n  const isProxy: typeof import('vue').isProxy\n  const isReactive: typeof import('vue').isReactive\n  const isReadonly: typeof import('vue').isReadonly\n  const isRef: typeof import('vue').isRef\n  const isShallow: typeof import('vue').isShallow\n  const makeDestructurable: typeof import('@vueuse/core').makeDestructurable\n  const markRaw: typeof import('vue').markRaw\n  const nextTick: typeof import('vue').nextTick\n  const onActivated: typeof import('vue').onActivated\n  const onBeforeMount: typeof import('vue').onBeforeMount\n  const onBeforeRouteLeave: typeof import('vue-router').onBeforeRouteLeave\n  const onBeforeRouteUpdate: typeof import('vue-router').onBeforeRouteUpdate\n  const onBeforeUnmount: typeof import('vue').onBeforeUnmount\n  const onBeforeUpdate: typeof import('vue').onBeforeUpdate\n  const onClickOutside: typeof import('@vueuse/core').onClickOutside\n  const onDeactivated: typeof import('vue').onDeactivated\n  const onElementRemoval: typeof import('@vueuse/core').onElementRemoval\n  const onErrorCaptured: typeof import('vue').onErrorCaptured\n  const onKeyStroke: typeof import('@vueuse/core').onKeyStroke\n  const onLongPress: typeof import('@vueuse/core').onLongPress\n  const onMounted: typeof import('vue').onMounted\n  const onRenderTracked: typeof import('vue').onRenderTracked\n  const onRenderTriggered: typeof import('vue').onRenderTriggered\n  const onScopeDispose: typeof import('vue').onScopeDispose\n  const onServerPrefetch: typeof import('vue').onServerPrefetch\n  const onStartTyping: typeof import('@vueuse/core').onStartTyping\n  const onUnmounted: typeof import('vue').onUnmounted\n  const onUpdated: typeof import('vue').onUpdated\n  const onWatcherCleanup: typeof import('vue').onWatcherCleanup\n  const pausableWatch: typeof import('@vueuse/core').pausableWatch\n  const provide: typeof import('vue').provide\n  const provideLocal: typeof import('@vueuse/core').provideLocal\n  const reactify: typeof import('@vueuse/core').reactify\n  const reactifyObject: typeof import('@vueuse/core').reactifyObject\n  const reactive: typeof import('vue').reactive\n  const reactiveComputed: typeof import('@vueuse/core').reactiveComputed\n  const reactiveOmit: typeof import('@vueuse/core').reactiveOmit\n  const reactivePick: typeof import('@vueuse/core').reactivePick\n  const readonly: typeof import('vue').readonly\n  const ref: typeof import('vue').ref\n  const refAutoReset: typeof import('@vueuse/core').refAutoReset\n  const refDebounced: typeof import('@vueuse/core').refDebounced\n  const refDefault: typeof import('@vueuse/core').refDefault\n  const refManualReset: typeof import('@vueuse/core').refManualReset\n  const refThrottled: typeof import('@vueuse/core').refThrottled\n  const refWithControl: typeof import('@vueuse/core').refWithControl\n  const resolveComponent: typeof import('vue').resolveComponent\n  const resolveRef: typeof import('@vueuse/core').resolveRef\n  const resolveUnref: typeof import('@vueuse/core').resolveUnref\n  const shallowReactive: typeof import('vue').shallowReactive\n  const shallowReadonly: typeof import('vue').shallowReadonly\n  const shallowRef: typeof import('vue').shallowRef\n  const syncRef: typeof import('@vueuse/core').syncRef\n  const syncRefs: typeof import('@vueuse/core').syncRefs\n  const templateRef: typeof import('@vueuse/core').templateRef\n  const throttledRef: typeof import('@vueuse/core').throttledRef\n  const throttledWatch: typeof import('@vueuse/core').throttledWatch\n  const toRaw: typeof import('vue').toRaw\n  const toReactive: typeof import('@vueuse/core').toReactive\n  const toRef: typeof import('vue').toRef\n  const toRefs: typeof import('vue').toRefs\n  const toValue: typeof import('vue').toValue\n  const triggerRef: typeof import('vue').triggerRef\n  const tryOnBeforeMount: typeof import('@vueuse/core').tryOnBeforeMount\n  const tryOnBeforeUnmount: typeof import('@vueuse/core').tryOnBeforeUnmount\n  const tryOnMounted: typeof import('@vueuse/core').tryOnMounted\n  const tryOnScopeDispose: typeof import('@vueuse/core').tryOnScopeDispose\n  const tryOnUnmounted: typeof import('@vueuse/core').tryOnUnmounted\n  const unref: typeof import('vue').unref\n  const unrefElement: typeof import('@vueuse/core').unrefElement\n  const until: typeof import('@vueuse/core').until\n  const useActiveElement: typeof import('@vueuse/core').useActiveElement\n  const useAnimate: typeof import('@vueuse/core').useAnimate\n  const useArrayDifference: typeof import('@vueuse/core').useArrayDifference\n  const useArrayEvery: typeof import('@vueuse/core').useArrayEvery\n  const useArrayFilter: typeof import('@vueuse/core').useArrayFilter\n  const useArrayFind: typeof import('@vueuse/core').useArrayFind\n  const useArrayFindIndex: typeof import('@vueuse/core').useArrayFindIndex\n  const useArrayFindLast: typeof import('@vueuse/core').useArrayFindLast\n  const useArrayIncludes: typeof import('@vueuse/core').useArrayIncludes\n  const useArrayJoin: typeof import('@vueuse/core').useArrayJoin\n  const useArrayMap: typeof import('@vueuse/core').useArrayMap\n  const useArrayReduce: typeof import('@vueuse/core').useArrayReduce\n  const useArraySome: typeof import('@vueuse/core').useArraySome\n  const useArrayUnique: typeof import('@vueuse/core').useArrayUnique\n  const useAsyncQueue: typeof import('@vueuse/core').useAsyncQueue\n  const useAsyncState: typeof import('@vueuse/core').useAsyncState\n  const useAttrs: typeof import('vue').useAttrs\n  const useBase64: typeof import('@vueuse/core').useBase64\n  const useBattery: typeof import('@vueuse/core').useBattery\n  const useBluetooth: typeof import('@vueuse/core').useBluetooth\n  const useBreakpoints: typeof import('@vueuse/core').useBreakpoints\n  const useBroadcastChannel: typeof import('@vueuse/core').useBroadcastChannel\n  const useBrowserLocation: typeof import('@vueuse/core').useBrowserLocation\n  const useCached: typeof import('@vueuse/core').useCached\n  const useClipboard: typeof import('@vueuse/core').useClipboard\n  const useClipboardItems: typeof import('@vueuse/core').useClipboardItems\n  const useCloned: typeof import('@vueuse/core').useCloned\n  const useColorMode: typeof import('@vueuse/core').useColorMode\n  const useConfirmDialog: typeof import('@vueuse/core').useConfirmDialog\n  const useCountdown: typeof import('@vueuse/core').useCountdown\n  const useCounter: typeof import('@vueuse/core').useCounter\n  const useCssModule: typeof import('vue').useCssModule\n  const useCssVar: typeof import('@vueuse/core').useCssVar\n  const useCssVars: typeof import('vue').useCssVars\n  const useCurrentElement: typeof import('@vueuse/core').useCurrentElement\n  const useCycleList: typeof import('@vueuse/core').useCycleList\n  const useDark: typeof import('@vueuse/core').useDark\n  const useDateFormat: typeof import('@vueuse/core').useDateFormat\n  const useDebounce: typeof import('@vueuse/core').useDebounce\n  const useDebounceFn: typeof import('@vueuse/core').useDebounceFn\n  const useDebouncedRefHistory: typeof import('@vueuse/core').useDebouncedRefHistory\n  const useDeviceMotion: typeof import('@vueuse/core').useDeviceMotion\n  const useDeviceOrientation: typeof import('@vueuse/core').useDeviceOrientation\n  const useDevicePixelRatio: typeof import('@vueuse/core').useDevicePixelRatio\n  const useDevicesList: typeof import('@vueuse/core').useDevicesList\n  const useDisplayMedia: typeof import('@vueuse/core').useDisplayMedia\n  const useDocumentVisibility: typeof import('@vueuse/core').useDocumentVisibility\n  const useDraggable: typeof import('@vueuse/core').useDraggable\n  const useDropZone: typeof import('@vueuse/core').useDropZone\n  const useElementBounding: typeof import('@vueuse/core').useElementBounding\n  const useElementByPoint: typeof import('@vueuse/core').useElementByPoint\n  const useElementHover: typeof import('@vueuse/core').useElementHover\n  const useElementSize: typeof import('@vueuse/core').useElementSize\n  const useElementVisibility: typeof import('@vueuse/core').useElementVisibility\n  const useEventBus: typeof import('@vueuse/core').useEventBus\n  const useEventListener: typeof import('@vueuse/core').useEventListener\n  const useEventSource: typeof import('@vueuse/core').useEventSource\n  const useEyeDropper: typeof import('@vueuse/core').useEyeDropper\n  const useFavicon: typeof import('@vueuse/core').useFavicon\n  const useFetch: typeof import('@vueuse/core').useFetch\n  const useFileDialog: typeof import('@vueuse/core').useFileDialog\n  const useFileSystemAccess: typeof import('@vueuse/core').useFileSystemAccess\n  const useFocus: typeof import('@vueuse/core').useFocus\n  const useFocusWithin: typeof import('@vueuse/core').useFocusWithin\n  const useFps: typeof import('@vueuse/core').useFps\n  const useFullscreen: typeof import('@vueuse/core').useFullscreen\n  const useGamepad: typeof import('@vueuse/core').useGamepad\n  const useGeolocation: typeof import('@vueuse/core').useGeolocation\n  const useId: typeof import('vue').useId\n  const useIdle: typeof import('@vueuse/core').useIdle\n  const useImage: typeof import('@vueuse/core').useImage\n  const useInfiniteScroll: typeof import('@vueuse/core').useInfiniteScroll\n  const useIntersectionObserver: typeof import('@vueuse/core').useIntersectionObserver\n  const useInterval: typeof import('@vueuse/core').useInterval\n  const useIntervalFn: typeof import('@vueuse/core').useIntervalFn\n  const useKeyModifier: typeof import('@vueuse/core').useKeyModifier\n  const useLastChanged: typeof import('@vueuse/core').useLastChanged\n  const useLink: typeof import('vue-router').useLink\n  const useLocalStorage: typeof import('@vueuse/core').useLocalStorage\n  const useMagicKeys: typeof import('@vueuse/core').useMagicKeys\n  const useManualRefHistory: typeof import('@vueuse/core').useManualRefHistory\n  const useMediaControls: typeof import('@vueuse/core').useMediaControls\n  const useMediaQuery: typeof import('@vueuse/core').useMediaQuery\n  const useMemoize: typeof import('@vueuse/core').useMemoize\n  const useMemory: typeof import('@vueuse/core').useMemory\n  const useModel: typeof import('vue').useModel\n  const useMounted: typeof import('@vueuse/core').useMounted\n  const useMouse: typeof import('@vueuse/core').useMouse\n  const useMouseInElement: typeof import('@vueuse/core').useMouseInElement\n  const useMousePressed: typeof import('@vueuse/core').useMousePressed\n  const useMutationObserver: typeof import('@vueuse/core').useMutationObserver\n  const useNavigatorLanguage: typeof import('@vueuse/core').useNavigatorLanguage\n  const useNetwork: typeof import('@vueuse/core').useNetwork\n  const useNow: typeof import('@vueuse/core').useNow\n  const useObjectUrl: typeof import('@vueuse/core').useObjectUrl\n  const useOffsetPagination: typeof import('@vueuse/core').useOffsetPagination\n  const useOnline: typeof import('@vueuse/core').useOnline\n  const usePageLeave: typeof import('@vueuse/core').usePageLeave\n  const useParallax: typeof import('@vueuse/core').useParallax\n  const useParentElement: typeof import('@vueuse/core').useParentElement\n  const usePerformanceObserver: typeof import('@vueuse/core').usePerformanceObserver\n  const usePermission: typeof import('@vueuse/core').usePermission\n  const usePointer: typeof import('@vueuse/core').usePointer\n  const usePointerLock: typeof import('@vueuse/core').usePointerLock\n  const usePointerSwipe: typeof import('@vueuse/core').usePointerSwipe\n  const usePreferredColorScheme: typeof import('@vueuse/core').usePreferredColorScheme\n  const usePreferredContrast: typeof import('@vueuse/core').usePreferredContrast\n  const usePreferredDark: typeof import('@vueuse/core').usePreferredDark\n  const usePreferredLanguages: typeof import('@vueuse/core').usePreferredLanguages\n  const usePreferredReducedMotion: typeof import('@vueuse/core').usePreferredReducedMotion\n  const usePreferredReducedTransparency: typeof import('@vueuse/core').usePreferredReducedTransparency\n  const usePrevious: typeof import('@vueuse/core').usePrevious\n  const useRafFn: typeof import('@vueuse/core').useRafFn\n  const useRefHistory: typeof import('@vueuse/core').useRefHistory\n  const useResizeObserver: typeof import('@vueuse/core').useResizeObserver\n  const useRoute: typeof import('vue-router').useRoute\n  const useRouter: typeof import('vue-router').useRouter\n  const useSSRWidth: typeof import('@vueuse/core').useSSRWidth\n  const useScreenOrientation: typeof import('@vueuse/core').useScreenOrientation\n  const useScreenSafeArea: typeof import('@vueuse/core').useScreenSafeArea\n  const useScriptTag: typeof import('@vueuse/core').useScriptTag\n  const useScroll: typeof import('@vueuse/core').useScroll\n  const useScrollLock: typeof import('@vueuse/core').useScrollLock\n  const useSessionStorage: typeof import('@vueuse/core').useSessionStorage\n  const useShare: typeof import('@vueuse/core').useShare\n  const useSlots: typeof import('vue').useSlots\n  const useSorted: typeof import('@vueuse/core').useSorted\n  const useSpeechRecognition: typeof import('@vueuse/core').useSpeechRecognition\n  const useSpeechSynthesis: typeof import('@vueuse/core').useSpeechSynthesis\n  const useStepper: typeof import('@vueuse/core').useStepper\n  const useStorage: typeof import('@vueuse/core').useStorage\n  const useStorageAsync: typeof import('@vueuse/core').useStorageAsync\n  const useStyleTag: typeof import('@vueuse/core').useStyleTag\n  const useSupported: typeof import('@vueuse/core').useSupported\n  const useSwipe: typeof import('@vueuse/core').useSwipe\n  const useTemplateRef: typeof import('vue').useTemplateRef\n  const useTemplateRefsList: typeof import('@vueuse/core').useTemplateRefsList\n  const useTextDirection: typeof import('@vueuse/core').useTextDirection\n  const useTextSelection: typeof import('@vueuse/core').useTextSelection\n  const useTextareaAutosize: typeof import('@vueuse/core').useTextareaAutosize\n  const useThrottle: typeof import('@vueuse/core').useThrottle\n  const useThrottleFn: typeof import('@vueuse/core').useThrottleFn\n  const useThrottledRefHistory: typeof import('@vueuse/core').useThrottledRefHistory\n  const useTimeAgo: typeof import('@vueuse/core').useTimeAgo\n  const useTimeAgoIntl: typeof import('@vueuse/core').useTimeAgoIntl\n  const useTimeout: typeof import('@vueuse/core').useTimeout\n  const useTimeoutFn: typeof import('@vueuse/core').useTimeoutFn\n  const useTimeoutPoll: typeof import('@vueuse/core').useTimeoutPoll\n  const useTimestamp: typeof import('@vueuse/core').useTimestamp\n  const useTitle: typeof import('@vueuse/core').useTitle\n  const useToNumber: typeof import('@vueuse/core').useToNumber\n  const useToString: typeof import('@vueuse/core').useToString\n  const useToggle: typeof import('@vueuse/core').useToggle\n  const useTransition: typeof import('@vueuse/core').useTransition\n  const useUrlSearchParams: typeof import('@vueuse/core').useUrlSearchParams\n  const useUserMedia: typeof import('@vueuse/core').useUserMedia\n  const useVModel: typeof import('@vueuse/core').useVModel\n  const useVModels: typeof import('@vueuse/core').useVModels\n  const useVibrate: typeof import('@vueuse/core').useVibrate\n  const useVirtualList: typeof import('@vueuse/core').useVirtualList\n  const useWakeLock: typeof import('@vueuse/core').useWakeLock\n  const useWebNotification: typeof import('@vueuse/core').useWebNotification\n  const useWebSocket: typeof import('@vueuse/core').useWebSocket\n  const useWebWorker: typeof import('@vueuse/core').useWebWorker\n  const useWebWorkerFn: typeof import('@vueuse/core').useWebWorkerFn\n  const useWindowFocus: typeof import('@vueuse/core').useWindowFocus\n  const useWindowScroll: typeof import('@vueuse/core').useWindowScroll\n  const useWindowSize: typeof import('@vueuse/core').useWindowSize\n  const watch: typeof import('vue').watch\n  const watchArray: typeof import('@vueuse/core').watchArray\n  const watchAtMost: typeof import('@vueuse/core').watchAtMost\n  const watchDebounced: typeof import('@vueuse/core').watchDebounced\n  const watchDeep: typeof import('@vueuse/core').watchDeep\n  const watchEffect: typeof import('vue').watchEffect\n  const watchIgnorable: typeof import('@vueuse/core').watchIgnorable\n  const watchImmediate: typeof import('@vueuse/core').watchImmediate\n  const watchOnce: typeof import('@vueuse/core').watchOnce\n  const watchPausable: typeof import('@vueuse/core').watchPausable\n  const watchPostEffect: typeof import('vue').watchPostEffect\n  const watchSyncEffect: typeof import('vue').watchSyncEffect\n  const watchThrottled: typeof import('@vueuse/core').watchThrottled\n  const watchTriggerable: typeof import('@vueuse/core').watchTriggerable\n  const watchWithFilter: typeof import('@vueuse/core').watchWithFilter\n  const whenever: typeof import('@vueuse/core').whenever\n}\n// for type re-export\ndeclare global {\n  // @ts-ignore\n  export type { Component, Slot, Slots, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, ShallowRef, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'\n  import('vue')\n}\n"
  },
  {
    "path": "src/components/ActionsMenu.vue",
    "content": "<script setup lang='ts'>\nimport type { PropType } from 'vue'\nimport type { CollectionMeta } from '../data'\nimport { cacheCollection, downloadAndInstall, isInstalled } from '../data'\nimport { isElectron } from '../env'\nimport { activeMode, iconSize, inProgress, isFavoritedCollection, listType, progressMessage, toggleFavoriteCollection } from '../store'\nimport { PackIconFont, PackJsonZip, PackSvgZip } from '../utils/pack'\n\nconst props = defineProps({\n  collection: {\n    type: Object as PropType<CollectionMeta>,\n    required: true,\n  },\n})\n\nconst menu = ref(\n  listType.value === 'list'\n    ? 'list'\n    : iconSize.value === 'text-4xl'\n      ? 'large'\n      : 'small',\n)\n\nasync function packIconFont() {\n  if (!props.collection)\n    return\n\n  progressMessage.value = 'Downloading...'\n  inProgress.value = true\n  await nextTick()\n  await downloadAndInstall(props.collection.id)\n  progressMessage.value = 'Packing up...'\n  await nextTick()\n  await PackIconFont(\n    [props.collection],\n    props.collection.icons.map(i => `${props.collection!.id}:${i}`),\n    { fontName: props.collection.name, fileName: props.collection.id },\n  )\n  inProgress.value = false\n}\n\nasync function packSvgs() {\n  if (!props.collection)\n    return\n\n  progressMessage.value = 'Downloading...'\n  inProgress.value = true\n  await nextTick()\n  await downloadAndInstall(props.collection.id)\n  progressMessage.value = 'Packing up...'\n  await nextTick()\n  await PackSvgZip(\n    [props.collection],\n    props.collection.icons.map(i => `${props.collection!.id}:${i}`),\n    props.collection.id,\n  )\n  inProgress.value = false\n}\n\nasync function packJson() {\n  if (!props.collection)\n    return\n\n  progressMessage.value = 'Downloading...'\n  inProgress.value = true\n  await nextTick()\n  await downloadAndInstall(props.collection.id)\n  progressMessage.value = 'Packing up...'\n  await nextTick()\n  await PackJsonZip(\n    [props.collection],\n    props.collection.icons.map(i => `${props.collection!.id}:${i}`),\n    props.collection.id,\n  )\n  inProgress.value = false\n}\n\nasync function cache() {\n  if (!props.collection)\n    return\n\n  await cacheCollection(props.collection.id)\n}\n\nwatch(\n  menu,\n  async (current, prev) => {\n    switch (current) {\n      case 'small':\n        iconSize.value = 'text-2xl'\n        listType.value = 'grid'\n        return\n      case 'large':\n        iconSize.value = 'text-4xl'\n        listType.value = 'grid'\n        return\n      case 'list':\n        iconSize.value = 'text-3xl'\n        listType.value = 'list'\n        return\n      case 'select':\n        activeMode.value = 'select'\n        break\n      case 'copy':\n        activeMode.value = 'copy'\n        break\n      case 'download_iconfont':\n        packIconFont()\n        break\n      case 'download_svgs':\n        packSvgs()\n        break\n      case 'download_json':\n        packJson()\n        break\n      case 'cache':\n        cache()\n        break\n    }\n\n    await nextTick()\n    menu.value = prev\n  },\n  { flush: 'pre' },\n)\n\nconst installed = computed(() => {\n  return props.collection && isInstalled(props.collection.id)\n})\n\nconst favorited = computed(() => isFavoritedCollection(props.collection.id))\n\n// const options = computed(() => [\n//   {\n//     label: 'Size',\n//     children: [\n//       { label: 'Small', value: 'small' },\n//       { label: 'Large', value: 'large' },\n//       { label: 'List', value: 'list' },\n//     ],\n//   },\n//   {\n//     label: 'Modes',\n//     children: [\n//       { label: 'Multiple select', value: 'select' },\n//       { label: 'Name copying mode', value: 'copy' },\n//     ],\n//   },\n//   /*\n//    TODO: due to this function requires to download and pack\n//                   the full set, we should make some UI to aware users\n//                   in browser version.\n//   */\n//   props.collection.id !== 'all'\n//     ? {\n//         label: 'Downloads',\n//         children: [\n//           (!isElectron && !installed) ? { label: 'Cache in Browser', value: 'cache' } : null,\n//           { label: 'Iconfont', value: 'download_iconfont', disabled: inProgress.value },\n//           { label: 'SVGs Zip', value: 'download_svgs', disabled: inProgress.value },\n//           { label: 'JSON', value: 'download_json', disabled: inProgress.value },\n//         ].filter(Boolean),\n//       }\n//     : null,\n// ].filter(Boolean))\n</script>\n\n<template>\n  <div flex=\"~ gap3\" text-xl items-center>\n    <DarkSwitcher />\n\n    <RouterLink\n      icon-button\n      i-carbon-settings\n      title=\"Settings\"\n      to=\"/settings\"\n    />\n\n    <button\n      v-if=\"collection.id !== 'all'\"\n      icon-button\n      :class=\"favorited ? 'i-carbon:star-filled' : 'i-carbon:star'\"\n      title=\"Toggle Favorite\"\n      @click=\"toggleFavoriteCollection(collection.id)\"\n    />\n\n    <!-- Download State -->\n    <div\n      v-if=\"installed && !isElectron\"\n      icon-button class=\"!op50\"\n      i-carbon-cloud-auditing\n      title=\"Cached in browser\"\n    />\n\n    <!-- Menu -->\n    <div icon-button cursor-pointer relative i-carbon-menu title=\"Menu\">\n      <select\n        v-model=\"menu\"\n        absolute w-full dark:bg-dark-100 text-base top-0 right-0 opacity-0 z-10\n      >\n        <optgroup label=\"Size\">\n          <option value=\"small\">\n            Small\n          </option>\n          <option value=\"large\">\n            Large\n          </option>\n          <option value=\"list\">\n            List\n          </option>\n        </optgroup>\n        <optgroup label=\"Modes\">\n          <option value=\"select\">\n            Multiple select\n          </option>\n          <option value=\"copy\">\n            Name copying mode\n          </option>\n        </optgroup>\n\n        <!--\n            TODO: due to this function requires to download and pack\n                  the full set, we should make some UI to aware users\n                  in browser version.\n          -->\n        <optgroup v-if=\"collection.id !== 'all'\" label=\"Downloads\">\n          <option v-if=\"!isElectron && !installed\" value=\"cache\">\n            Cache in Browser\n          </option>\n          <option value=\"download_iconfont\" :disabled=\"inProgress\">\n            Iconfont\n          </option>\n          <option value=\"download_svgs\" :disabled=\"inProgress\">\n            SVGs Zip\n          </option>\n          <option value=\"download_json\" :disabled=\"inProgress\">\n            JSON\n          </option>\n        </optgroup>\n      </select>\n    </div>\n    <!-- TODO: improve design of custom select -->\n    <!-- <CustomSelect v-model=\"menu\" :options=\"options\">\n      <div icon-button cursor-pointer relative i-carbon-menu title=\"Menu\" />\n    </CustomSelect> -->\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/Bag.vue",
    "content": "<script setup lang='ts'>\nimport type { PackType } from '../utils/svg'\nimport { collections } from '../data'\nimport { bags, clearBag } from '../store'\nimport { PackIconFont, PackSVGSprite, PackZip } from '../utils/pack'\n\nconst emit = defineEmits<{\n  (event: 'close'): void\n  (event: 'select', value: string): void\n}>()\n\nconst showPackOption = ref(false)\n\nfunction clear() {\n  // eslint-disable-next-line no-alert\n  if (confirm('Are you sure to remove all icons from the bag?')) {\n    clearBag()\n    emit('close')\n  }\n}\n\nasync function packIconFont() {\n  // TODO: customzie\n  await PackIconFont(\n    collections,\n    bags.value,\n  )\n}\n\nasync function packSVGSprite() {\n  await PackSVGSprite(\n    collections,\n    bags.value,\n  )\n}\n\nasync function PackSvgs(type: PackType = 'svg') {\n  await PackZip(\n    collections,\n    bags.value,\n    'icones-bags',\n    type,\n  )\n}\n</script>\n\n<template>\n  <div class=\"h-full flex flex-col w-screen md:w-96 xl:w-128\">\n    <div\n      class=\"\n        py-3 px-6 flex flex-none border-b border-base\n      \"\n    >\n      <div>\n        <NavPlaceholder class=\"md:hidden\" />\n        <div class=\"text-lg\">\n          Bag\n        </div>\n        <div class=\"opacity-50 text-xs\">\n          {{ bags.length }} icons picked\n        </div>\n      </div>\n      <div class=\"flex-auto\" />\n      <IconButton v-if=\"bags.length\" class=\"text-xl mr-4 flex-none\" icon=\"carbon:delete\" @click=\"clear\" />\n      <IconButton class=\"text-2xl flex-none\" icon=\"carbon:close\" @click=\"$emit('close')\" />\n    </div>\n\n    <template v-if=\"bags.length\">\n      <div class=\"flex-auto overflow-y-overflow py-3 px-1\">\n        <Icons :icons=\"bags\" @select=\"(e: any) => $emit('select', e)\" />\n      </div>\n\n      <div\n        v-show=\"showPackOption\"\n        class=\"relative flex-none border-t border-base py-3 px-6 text-2xl opacity-75\"\n      >\n        <IconButton class=\"absolute top-0 right-0 p-3 text-2xl flex-none leading-none\" icon=\"carbon:close\" @click=\"showPackOption = false\" />\n        <button class=\"btn small mr-1 mb-1 opacity-75\" @click=\"PackSvgs('svg')\">\n          SVG\n        </button>\n        <button class=\"btn small mr-1 mb-1 opacity-75\" @click=\"PackSvgs('vue')\">\n          Vue\n        </button>\n        <button class=\"btn small mr-1 mb-1 opacity-75\" @click=\"PackSvgs('jsx')\">\n          React\n        </button>\n        <button class=\"btn small mr-1 mb-1 opacity-75\" @click=\"PackSvgs('tsx')\">\n          React<sup class=\"opacity-50 -mr-1\">TS</sup>\n        </button>\n        <button class=\"btn small mr-1 mb-1 opacity-75\" @click=\"PackSvgs('svelte')\">\n          Svelte\n        </button>\n        <button class=\"btn small mr-1 mb-1 opacity-75\" @click=\"PackSvgs('qwik')\">\n          Qwik\n        </button>\n        <button class=\"btn small mr-1 mb-1 opacity-75\" @click=\"PackSvgs('solid')\">\n          Solid\n        </button>\n        <button class=\"btn small mr-1 mb-1 opacity-75\" @click=\"PackSvgs('astro')\">\n          Astro\n        </button>\n        <button class=\"btn small mr-1 mb-1 opacity-75\" @click=\"PackSvgs('react-native')\">\n          React Native\n        </button>\n        <button class=\"btn small mr-1 mb-1 opacity-75\" @click=\"PackSvgs('json')\">\n          JSON\n        </button>\n      </div>\n\n      <div\n        class=\"\n          flex-none border-t border-base py-3 px-6 text-2xl opacity-75\n        \"\n      >\n        <IconButton class=\"p-1 cursor-pointer hover:text-primary\" icon=\"carbon:download\" text=\"Download Zip\" :active=\"true\" @click=\"showPackOption = true\" />\n        <IconButton class=\"p-1 cursor-pointer hover:text-primary\" icon=\"carbon:function\" text=\"Generate Icon Fonts\" :active=\"true\" @click=\"packIconFont\" />\n        <IconButton class=\"p-1 cursor-pointer hover:text-primary\" icon=\"carbon:apps\" text=\"Download SVG Sprite\" :active=\"true\" @click=\"packSVGSprite\" />\n      </div>\n    </template>\n\n    <template v-else>\n      <div class=\"text-center px-4 py-8 text-gray-500 italic font-light text-sm\">\n        No icons yet ;)\n      </div>\n    </template>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/CollectionEntries.vue",
    "content": "<script setup lang=\"ts\">\nimport type { CollectionInfo, PresentType } from '../data'\n\ndefineProps<{\n  collections: CollectionInfo[]\n  type?: PresentType\n}>()\n</script>\n\n<template>\n  <div class=\"collections-list grid gap2\" p2>\n    <CollectionEntry\n      v-for=\"collection of collections\"\n      :key=\"collection.id\"\n      :type=\"type\"\n      :collection=\"collection\"\n    />\n  </div>\n</template>\n\n<style>\n.collections-list {\n  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));\n}\n</style>\n"
  },
  {
    "path": "src/components/CollectionEntry.vue",
    "content": "<script setup lang=\"ts\">\nimport type { CollectionInfo, PresentType } from '../data'\nimport { isFavoritedCollection, removeRecentCollection, toggleFavoriteCollection } from '../store'\n\ndefineProps<{\n  collection: CollectionInfo\n  type?: PresentType\n}>()\n</script>\n\n<template>\n  <RouterLink\n    :key=\"collection.id\"\n    p3 relative\n    border=\"~ base\"\n    :class=\"{ 'border-dashed': collection.hidden }\"\n    class=\"grid grid-cols-[1fr_90px] gap2 items-center color-base transition-all translate-z-0 group\"\n    hover=\"text-primary !border-primary shadow\"\n    :to=\"`/collection/${collection.id}`\"\n  >\n    <div ml2>\n      <div class=\"flex-auto text-lg leading-1em my1\" :class=\"{ 'line-through group-hover:no-underline': collection.hidden }\">\n        {{ collection.name }}\n        <span inline-flex align-top flex=\"items-center gap-0.5\" m=\"l--0.5\">\n          <div v-if=\"isFavoritedCollection(collection.id)\" op80 text-xs i-carbon-star-filled />\n          <div v-if=\"collection.hidden\" op80 text-xs text-orange i-carbon:information-disabled />\n        </span>\n      </div>\n      <div flex=\"~ col auto\" opacity-50 text-xs>\n        <span>{{ collection.author?.name }}</span>\n        <span op50>{{ collection.license?.title }}</span>\n        <span m1 />\n        <span>{{ collection.total }} icons</span>\n      </div>\n    </div>\n    <Icons\n      :icons=\"collection.sampleIcons\"\n      :namespace=\"`${collection.id}:`\"\n      color-class=\"\"\n      size=\"xl\"\n      spacing=\"m-1\"\n      class=\"ma justify-center opacity-75 flex-wrap pointer-events-none\"\n    />\n    <div\n      absolute top--1px right--1px\n      flex=\"~ items-center\"\n      op0 group-hover=\"op100 transition-all\"\n      un-children=\"op-64 hover:op-100\"\n    >\n      <button\n        border=\"~ primary\" p2 bg-base\n        :title=\"isFavoritedCollection(collection.id) ? 'Remove from favorites' : 'Add to favorites'\"\n        :class=\"{ 'border-dashed': collection.hidden }\"\n        @click.prevent=\"toggleFavoriteCollection(collection.id)\"\n      >\n        <div v-if=\"isFavoritedCollection(collection.id)\" i-carbon-star-filled />\n        <div v-else i-carbon-star />\n      </button>\n      <button\n        v-if=\"type === 'recent'\"\n        border=\"~ primary\" p2 bg-base ml--1px\n        :title=\"type === 'recent' ? 'Remove from recent' : type === 'favorite' || isFavoritedCollection(collection.id) ? 'Remove from favorites' : 'Add to favorites'\"\n        :class=\"{ 'border-dashed': collection.hidden }\"\n        @click.prevent=\"removeRecentCollection(collection.id)\"\n      >\n        <div i-carbon-delete />\n      </button>\n    </div>\n  </RouterLink>\n</template>\n"
  },
  {
    "path": "src/components/ColorPicker.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps({\n  value: {\n    type: String,\n    required: true,\n  },\n})\n\nconst emit = defineEmits(['update:value'])\n</script>\n\n<template>\n  <div class=\"relative\">\n    <div>\n      <slot />\n    </div>\n    <input\n      class=\"absolute top-0 bottom-0 left-0 right-0 opacity-0 w-full h-full cursor-pointer\"\n      :value=\"value\"\n      type=\"color\"\n      @input=\"e => emit('update:value', (e.target as any).value)\"\n    >\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/CustomSelect.vue",
    "content": "<script setup lang='ts'>\ndefineProps<{\n  options: {\n    label: string\n    children: {\n      label: string\n      value: string\n      disabled?: boolean\n    }[]\n  }[]\n  modelValue: string\n}>()\n\nconst emit = defineEmits(['update:modelValue'])\nconst visible = ref(false)\nconst target = ref(null)\n\nonClickOutside(target, () => visible.value = false)\n</script>\n\n<template>\n  <div ref=\"target\" relative>\n    <div @click=\"visible = true\">\n      <slot />\n    </div>\n    <div\n      v-if=\"visible\"\n      class=\"absolute rounded-md w-60 border-gray-300 border-1 dark:bg-dark-100 text-base top-20px right-0 z-10 shadow-md text-gray-500 px-4 py-2 bg-base\"\n    >\n      <template v-for=\"(optgroup) in options\" :key=\"optgroup.label\">\n        <div text-gray-600 font-semibold py-1>\n          {{ optgroup.label }}\n        </div>\n\n        <div\n          v-for=\"(option) in optgroup.children\"\n          :key=\"option.value\"\n          class=\"cursor-pointer mx-2 text-sm leading-6 py-1 pl-2 dark:hover-bg-gray-800 hover-bg-gray-100 hover-rounded\"\n          :class=\"{\n            'color-primary': option.value === modelValue,\n            'cursor-not-allowed opacity-50': option?.disabled,\n          }\"\n          @click=\"{ emit('update:modelValue', option.value); visible = false; }\"\n        >\n          {{ option.label }}\n        </div>\n      </template>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/DarkSwitcher.vue",
    "content": "<script setup lang=\"ts\">\nimport { isDark } from '../store'\n\nconst isAppearanceTransition = typeof document !== 'undefined'\n  // @ts-expect-error: Transition API\n  && document.startViewTransition\n  && !window.matchMedia('(prefers-reduced-motion: reduce)').matches\n\nfunction toggleDark(event?: MouseEvent) {\n  if (!isAppearanceTransition || !event) {\n    isDark.value = !isDark.value\n    return\n  }\n\n  const x = event.clientX\n  const y = event.clientY\n  const endRadius = Math.hypot(\n    Math.max(x, innerWidth - x),\n    Math.max(y, innerHeight - y),\n  )\n  const transition = document.startViewTransition(async () => {\n    isDark.value = !isDark.value\n    await nextTick()\n  })\n\n  transition.ready.then(() => {\n    const clipPath = [\n      `circle(0px at ${x}px ${y}px)`,\n      `circle(${endRadius}px at ${x}px ${y}px)`,\n    ]\n    document.documentElement.animate(\n      {\n        clipPath: isDark.value\n          ? [...clipPath].reverse()\n          : clipPath,\n      },\n      {\n        duration: 400,\n        easing: 'ease-in',\n        fill: 'forwards',\n        pseudoElement: isDark.value\n          ? '::view-transition-old(root)'\n          : '::view-transition-new(root)',\n      },\n    )\n  })\n}\n</script>\n\n<template>\n  <button\n    icon-button\n    dark:i-carbon-moon i-carbon:sun\n    @click=\"toggleDark\"\n  />\n</template>\n"
  },
  {
    "path": "src/components/Drawer.vue",
    "content": "<script setup lang='ts'>\nimport { categorySearch, filteredCollections, sortedCollectionsInfo, specialTabs } from '../data'\nimport { isElectron } from '../env'\nimport { isFavoritedCollection, recentIconIds, toggleFavoriteCollection } from '../store'\n\nconst route = useRoute()\nconst current = computed(() => route.path.split('/').slice(-1)[0])\n\nconst collections = computed(() => {\n  const _collections = categorySearch.value\n    ? filteredCollections.value\n    : [\n        { id: 'all', name: 'All' },\n        { id: 'recent', name: 'Recent' },\n        ...sortedCollectionsInfo.value,\n      ]\n\n  return _collections.map(collection => ({\n    ...collection,\n    to: {\n      name: 'collection-id',\n      params: { id: collection.id },\n      query: {\n        s: route.query.s,\n      },\n    },\n  }))\n})\n</script>\n\n<template>\n  <div border=\"r base\" relative>\n    <NavPlaceholder class=\"mb-4\" />\n    <div\n      v-if=\"!isElectron\"\n      sticky top-0 bg-base z-1\n    >\n      <div flex=\"~ justify-between\" border=\"b base\">\n        <button\n          icon-button text-xl px-4 py-3\n          @click=\"$router.replace('/')\"\n        >\n          <div i-carbon:arrow-left />\n        </button>\n      </div>\n\n      <!-- Searching -->\n      <SearchBar\n        v-model:search=\"categorySearch\"\n        placeholder=\"Search category...\"\n        input-class=\"text-xs\"\n        :border=\"false\"\n        class=\"border-b border-base\"\n      />\n    </div>\n\n    <!-- Collections -->\n    <RouterLink\n      v-for=\"collection in collections\"\n      :key=\"collection.id\"\n      class=\"px-3 py-1 flex border-b border-base\"\n      :to=\"collection.to\"\n    >\n      <div\n        class=\"flex-auto py-1\"\n        :class=\"collection.id === current ? 'text-primary' : ''\"\n      >\n        <div class=\"text-base leading-tight\">\n          {{ collection.name }}\n          <span v-if=\"collection.hidden\" m=\"l--0.5\" op80 text-xs text-orange inline-block align-top i-carbon:information-disabled />\n        </div>\n        <div class=\"text-xs block opacity-50 mt-1\">\n          {{\n            collection.id === 'recent'\n              ? `${recentIconIds.length} icons`\n              : collection.id !== 'all'\n                ? `${collection.total} icons`\n                : `${collections.length} iconsets`\n          }}\n        </div>\n      </div>\n      <button\n        v-if=\"!specialTabs.includes(collection.id)\"\n        icon-button\n        :class=\"isFavoritedCollection(collection.id) ? 'op50 hover:op100' : 'op0 hover:op50' \"\n        class=\"flex-none text-lg p0.5 -mr-1 hover:text-primary flex\"\n        @click=\"toggleFavoriteCollection(collection.id)\"\n      >\n        <div :class=\"isFavoritedCollection(collection.id) ? 'i-carbon-star-filled' : 'i-carbon-star'\" ma />\n      </button>\n    </RouterLink>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/FAB.vue",
    "content": "<script setup lang='ts'>\ndefineProps({\n  icon: {\n    type: String,\n    required: true,\n  },\n  number: {\n    type: Number,\n    default: 0,\n  },\n})\n</script>\n\n<template>\n  <div\n    class=\"\n      p-4 m-6 fixed bottom-0 right-0 shadow-lg bg-white rounded-full text-2xl cursor-pointer border border-transparent hover:bg-gray-50\n      dark:bg-dark-100 dark:border dark:border-dark-300 dark:hover:bg-dark-200\n    \"\n  >\n    <Icon class=\"block text-gray-700 dark:text-gray-400\" :icon=\"icon\" />\n    <div\n      v-if=\"number\"\n      class=\"absolute top-0 right-0 -mt-1 -mr-1 bg-primary text-white text-xs leading-none rounded-full shadow text-center\"\n      style=\"padding: 5px; min-width: 22px\"\n    >\n      {{ number }}\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/Footer.vue",
    "content": "<script setup lang=\"ts\">\nconst buildTime = __BUILD_TIME__\n\nconst timeAgo = useTimeAgo(new Date(buildTime))\n</script>\n\n<template>\n  <footer class=\"text-center text-sm pt-8 pb-6\">\n    <p class=\"color-fade\">\n      built by\n      <a\n        class=\"opacity-75 hover:opacity-100\"\n        href=\"https://github.com/antfu\"\n        target=\"_blank\"\n      >@antfu</a>,\n      powered by\n      <a\n        class=\"opacity-75 hover:opacity-100\"\n        href=\"https://iconify.design\"\n        target=\"_blank\"\n      >Iconify</a>\n    </p>\n    <div color-fade mt-1 op50 italic>\n      Last update: {{ buildTime }} ({{ timeAgo }})\n    </div>\n  </footer>\n</template>\n"
  },
  {
    "path": "src/components/HelpPage.vue",
    "content": "<template>\n  <div class=\"p-6 w-120 help-page\">\n    <p class=\"mb-2 opacity-75\">\n      How to use the icon?\n    </p>\n\n    <h2 class=\"bold text-lg mb-1\">\n      Copy per Icon\n    </h2>\n    <p>\n      You can copy the icon as SVG to paste in almost any editor (Figma, Sketch, Illustrator, etc.), or copy as component to use in your web apps.\n    </p>\n\n    <h2 class=\"bold text-lg mt-5 mb-1\">\n      Iconify Runtime\n    </h2>\n    <p>\n      Iconify provides a runtime solution that fetches icons on the go.\n      Refer its <a href=\"https://iconify.design/\" target=\"_blank\">documentation</a> for more details.\n    </p>\n\n    <h2 class=\"bold text-lg mt-5 mb-1\">\n      Atomic CSS\n    </h2>\n    <p>\n      Created by the author of <b>Icônes</b>. With the power of <a href=\"https://github.com/antfu/unocss\">UnoCSS</a>, you can use the icons with <b>Pure CSS</b> using <a href=\"https://github.com/unocss/unocss/tree/main/packages-presets/preset-icons\" target=\"_blank\"><code>@unocss/preset-icons</code></a>.\n    </p>\n    <p class=\"mt-2\">\n      Check out <a href=\"https://antfu.me/posts/icons-in-pure-css\" target=\"_blank\">this blog post</a> for more.\n    </p>\n\n    <h2 class=\"bold text-lg mt-5 mb-1\">\n      Components\n    </h2>\n    <p>\n      Created by the author of <b>Icônes</b>, <a href=\"https://github.com/antfu/unplugin-icons\" target=\"_blank\"><code>unplugin-icons</code></a> is a on-demand\n      solution to generate icons as components on the fly.\n    </p>\n    <p class=\"mt-2\">\n      Check out <a href=\"https://antfu.me/posts/journey-with-icons-continues\" target=\"_blank\">this blog post</a> for the story behind.\n    </p>\n  </div>\n</template>\n\n<style lang=\"postcss\">\n.help-page p {\n  @apply text-black/60 dark:text-white/60;\n}\n.help-page a {\n  @apply text-primary opacity-75 hover:opacity-100;\n}\n</style>\n"
  },
  {
    "path": "src/components/Icon.vue",
    "content": "<script lang=\"ts\">\nimport { loadIcon } from 'iconify-icon'\nimport { LRUCache } from 'lru-cache'\n\nconst cache = new LRUCache<string, HTMLElement>({\n  max: 1_000,\n})\n\nconst mounted = new WeakSet<HTMLElement>()\n\nfunction getIcon(name: string) {\n  const el = cache.get(name)\n  if (el) {\n    if (!mounted.has(el)) {\n      mounted.add(el)\n      return el\n    }\n  }\n  const icon = document.createElement('iconify-icon')\n  icon.setAttribute('icon', name)\n  cache.set(name, icon)\n  mounted.add(icon)\n  return icon\n}\n\nfunction unmountIcon(name: string, icon: HTMLElement) {\n  mounted.delete(icon)\n  cache.set(name, icon)\n}\n</script>\n\n<script setup lang=\"ts\">\nconst props = defineProps({\n  icon: {\n    type: String,\n    required: true,\n  },\n  class: {\n    type: String,\n    default: '',\n  },\n  outerClass: {\n    type: String,\n    default: '',\n  },\n})\n\nconst el = ref<HTMLDivElement>()\nlet node: HTMLElement | undefined\nconst widthStyle = ref<string | undefined>()\n\nwatchEffect(() => {\n  if (node)\n    node.className = props.class\n})\n\nonMounted(() => {\n  const icon = props.icon\n  node = getIcon(props.icon)\n  el.value?.appendChild(node)\n  loadIcon(icon).then((data) => {\n    widthStyle.value = `width: ${(data.width ?? 16) / (data.height ?? 16)}em;`\n  }).catch(console.error)\n})\n\nonBeforeUnmount(() => {\n  if (node)\n    unmountIcon(props.icon, node)\n})\n</script>\n\n<template>\n  <div ref=\"el\" class=\"icon-container\" :class=\"[props.class, props.outerClass]\" :style=\"widthStyle\" />\n</template>\n\n<style>\niconify-icon {\n  min-width: 1em;\n  min-height: 1em;\n  display: block;\n}\n\n.icon-container {\n  display: inline-block;\n  vertical-align: middle;\n  line-height: 1em !important;\n  box-sizing: content-box;\n}\n</style>\n"
  },
  {
    "path": "src/components/IconButton.vue",
    "content": "<script setup lang='ts'>\ndefineProps({\n  icon: {\n    type: String,\n    required: true,\n  },\n  active: {\n    type: Boolean,\n    default: false,\n  },\n  none: {\n    type: Boolean,\n    default: false,\n  },\n  text: {\n    type: String,\n    default: '',\n  },\n  to: {\n    type: String,\n    default: '',\n  },\n})\n</script>\n\n<template>\n  <component\n    :is=\"to ? 'RouterLink' : 'button'\"\n    :to=\"to\"\n    class=\"icon-button m-auto\"\n    :class=\"none ? '' : active ? 'opacity-100 hover:opacity-100' : 'opacity-25 hover:opacity-50'\"\n  >\n    <Icon\n      :key=\"icon\"\n      :icon=\"icon\"\n      class=\"inline-block align-middle\"\n    />\n    <div\n      v-if=\"text\"\n      class=\"text-xs ml-2 inline-block align-middle\"\n    >\n      {{ text }}\n    </div>\n  </component>\n</template>\n"
  },
  {
    "path": "src/components/IconDetail.vue",
    "content": "<script setup lang='ts'>\nimport { collections } from '../data'\nimport { activeMode, copyPreviewColor, getTransformedId, inBag, preferredCase, previewColor, pushRecentIcon, showCaseSelect, showHelp, toggleBag } from '../store'\nimport { idCases } from '../utils/case'\nimport { dataUrlToBlob } from '../utils/dataUrlToBlob'\nimport { Download, getIconSnippet, SnippetMap, toComponentName } from '../utils/icons'\nimport InstallIconSet from './InstallIconSet.vue'\n\nconst props = defineProps({\n  icon: {\n    type: String,\n    required: true,\n  },\n  showCollection: {\n    type: Boolean,\n    required: true,\n  },\n})\n\nconst emit = defineEmits(['close', 'copy', 'next', 'prev'])\n\nconst caseSelector = ref<HTMLDivElement>()\nconst transformedId = computed(() => getTransformedId(props.icon))\nconst color = computed(() => copyPreviewColor.value ? previewColor.value : 'currentColor')\n\nonClickOutside(caseSelector, () => {\n  showCaseSelect.value = false\n})\n\nonKeyStroke('ArrowLeft', (e) => {\n  if (!props.icon)\n    return\n  emit('prev')\n  e.preventDefault()\n})\n\nonKeyStroke('ArrowRight', (e) => {\n  if (!props.icon)\n    return\n  emit('next')\n  e.preventDefault()\n})\n\nasync function copyText(text?: string) {\n  if (text) {\n    try {\n      await navigator.clipboard.writeText(text)\n      return true\n    }\n    catch {\n    }\n  }\n  return false\n}\n\nasync function copyPng(dataUrl: string): Promise<boolean> {\n  try {\n    const blob = dataUrlToBlob(dataUrl)\n    const item = new ClipboardItem({ 'image/png': blob })\n    await navigator.clipboard.write([item])\n    return true\n  }\n  catch (e) {\n    console.error('Failed to copy png error', e)\n    return false\n  }\n}\n\nasync function copy(type: string) {\n  pushRecentIcon(props.icon)\n\n  const svg = await getIconSnippet(collections, props.icon, type, true, color.value)\n  if (!svg)\n    return\n\n  emit(\n    'copy',\n    type === 'png'\n      ? await copyPng(svg)\n      : await copyText(svg),\n  )\n}\n\nasync function download(type: string) {\n  pushRecentIcon(props.icon)\n  const text = await getIconSnippet(collections, props.icon, type, false, color.value)\n  if (!text)\n    return\n  const ext = (type === 'solid' || type === 'qwik' || type === 'react-native') ? 'tsx' : type\n  const name = `${toComponentName(props.icon)}.${ext}`\n  const blob = type === 'png'\n    ? dataUrlToBlob(text)\n    : new Blob([text], { type: 'text/plain;charset=utf-8' })\n  Download(blob, name)\n}\n\nfunction toggleSelectingMode() {\n  switch (activeMode.value) {\n    case 'select':\n      activeMode.value = 'normal'\n      break\n    default:\n      activeMode.value = 'select'\n      emit('close')\n      break\n  }\n}\n\nconst collection = computed(() => {\n  const id = props.icon.split(':')[0]\n  return collections.find(i => i.id === id)\n})\n</script>\n\n<template>\n  <div class=\"p-2 flex flex-col flex-wrap md:flex-row md:text-left relative\">\n    <IconButton\n      class=\"absolute top-0 right-0 p-3 text-2xl flex-none leading-none\" icon=\"carbon:close\"\n      @click=\"$emit('close')\"\n    />\n    <div :style=\"{ color: previewColor }\">\n      <ColorPicker v-model:value=\"previewColor\" class=\"inline-block\">\n        <Icon :key=\"icon\" outer-class=\"p-4 text-8xl\" :icon=\"icon\" />\n      </ColorPicker>\n    </div>\n    <div class=\"px-6 py-2 mb-2 md:px-2 md:py-4\">\n      <button class=\"op35 hover:text-primary hover:op100 text-sm !outline-none\" @click=\"showHelp = !showHelp\">\n        How to use the icon?\n      </button>\n      <div class=\"flex op75 relative font-mono\">\n        {{ transformedId }}\n        <IconButton icon=\"carbon:copy\" class=\"ml-2\" @click=\"copy('id')\" />\n        <IconButton icon=\"carbon:chevron-up\" class=\"ml-2\" @click=\"showCaseSelect = !showCaseSelect\" />\n        <div class=\"flex-auto\" />\n        <div\n          v-if=\"showCaseSelect\" ref=\"caseSelector\"\n          class=\"absolute left-0 bottom-1.8em text-sm rounded shadow p-2 bg-base dark:border dark:border-dark-200\"\n        >\n          <div\n            v-for=\"[k, v] of Object.entries(idCases)\" :key=\"k\" class=\"flex items-center p-1 cursor-pointer\"\n            :class=\"k === preferredCase ? 'text-primary' : ''\" @click=\"preferredCase = k as any\"\n          >\n            <Icon\n              icon=\"carbon:checkmark\" class=\"text-primary text-lg\" outer-class=\"mr-1\"\n              :class=\"k === preferredCase ? '' : 'opacity-0'\"\n            />\n            <span class=\"flex-auto mr-2\">{{ v(icon) }}</span>\n          </div>\n        </div>\n      </div>\n      <div v-if=\"collection?.license\">\n        <a class=\"text-xs opacity-50 hover:opacity-100\" :href=\"collection.license.url\" target=\"_blank\">{{\n          collection.license.title }}</a>\n      </div>\n\n      <p v-if=\"showCollection && collection\" class=\"flex mb-1 op50 text-sm\">\n        Collection:\n        <RouterLink\n          class=\"ml-1 text-gray-600 hover:text-gray-700 dark:text-gray-300 dark:hover:text-gray-200\"\n          :to=\"`/collection/${collection.id}`\"\n        >\n          {{ collection.name }}\n        </RouterLink>\n      </p>\n\n      <div>\n        <button\n          class=\"\n            inline-block leading-1em border border-base my-2 mr-2 font-sans pl-2 pr-3 py-1 rounded-full text-sm cursor-pointer\n            hover:bg-gray-50 dark:hover:bg-dark-200\n          \" :class=\"inBag(icon) ? 'text-primary' : 'op50'\" @click=\"toggleBag(icon)\"\n        >\n          <template v-if=\"inBag(icon)\">\n            <Icon class=\"inline-block text-lg align-middle\" icon=\"carbon:shopping-bag\" />\n            <span class=\"inline-block align-middle ml1\">in bag</span>\n          </template>\n          <template v-else>\n            <Icon class=\"inline-block text-lg align-middle\" icon=\"carbon:add\" />\n            <span class=\"inline-block align-middle ml1\">add to bag</span>\n          </template>\n        </button>\n\n        <button\n          v-if=\"inBag(icon)\" class=\"\n            inline-block leading-1em border border-base my-2 mr-2 font-sans pl-2 pr-3 py-1 rounded-full text-sm cursor-pointer\n            hover:bg-gray-50 dark:hover:bg-dark-200\n          \" :class=\"activeMode === 'select' ? 'text-primary' : 'op50'\" @click=\"toggleSelectingMode\"\n        >\n          <Icon class=\"inline-block text-lg align-middle\" icon=\"carbon:list-checked\" />\n          <span class=\"inline-block align-middle ml1\">multiple select</span>\n        </button>\n\n        <button\n          class=\"\n            inline-block leading-1em border border-base my-2 mr-2 font-sans pl-2 pr-3 py-1 rounded-full text-sm cursor-pointer\n            hover:bg-gray-50 dark:hover:bg-dark-200\n          \" :class=\"copyPreviewColor ? 'text-primary' : 'op50'\" @click=\"copyPreviewColor = !copyPreviewColor\"\n        >\n          <Icon v-if=\"!copyPreviewColor\" class=\"inline-block text-lg align-middle\" icon=\"carbon:checkbox\" />\n          <Icon v-else class=\"inline-block text-lg align-middle\" icon=\"carbon:checkbox-checked\" />\n          <span class=\"inline-block align-middle ml1\">copy with color</span>\n        </button>\n      </div>\n\n      <div class=\"flex flex-wrap mt-2\">\n        <template v-for=\"(group, groupName) in SnippetMap\" :key=\"groupName\">\n          <div class=\"mr-4\">\n            <div class=\"my-1 op50 text-sm\">\n              {{ groupName }}\n            </div>\n            <div class=\"flex gap-1\">\n              <template v-for=\"(snippet, type) in group\" :key=\"`${icon}-${groupName}-${type}`\">\n                <SnippetPreview\n                  :collection=\"collection\"\n                  :icon=\"icon\"\n                  :snippet=\"snippet\"\n                  :type=\"type\"\n                  :color=\"color\"\n                >\n                  <button class=\"btn small opacity-75\" @click=\"copy(type)\">\n                    {{ snippet.name }}<sup v-if=\"snippet.tag\" class=\"opacity-50 -mr-1\">{{ snippet.tag }}</sup>\n                  </button>\n                </SnippetPreview>\n              </template>\n            </div>\n          </div>\n        </template>\n        <div class=\"mr-4\">\n          <div class=\"my-1 op50 text-sm\">\n            Download\n          </div>\n          <button class=\"btn small mr-1 mb-1 opacity-75\" @click=\"download('svg')\">\n            SVG\n          </button>\n          <button class=\"btn small mr-1 mb-1 opacity-75\" @click=\"download('png')\">\n            PNG\n          </button>\n          <button class=\"btn small mr-1 mb-1 opacity-75\" @click=\"download('vue')\">\n            Vue\n          </button>\n          <button class=\"btn small mr-1 mb-1 opacity-75\" @click=\"download('jsx')\">\n            React\n          </button>\n          <button class=\"btn small mr-1 mb-1 opacity-75\" @click=\"download('tsx')\">\n            React<sup class=\"opacity-50 -mr-1\">TS</sup>\n          </button>\n          <button class=\"btn small mr-1 mb-1 opacity-75\" @click=\"download('svelte')\">\n            Svelte\n          </button>\n          <button class=\"btn small mr-1 mb-1 opacity-75\" @click=\"download('qwik')\">\n            Qwik\n          </button>\n          <button class=\"btn small mr-1 mb-1 opacity-75\" @click=\"download('solid')\">\n            Solid\n          </button>\n          <button class=\"btn small mr-1 mb-1 opacity-75\" @click=\"download('astro')\">\n            Astro\n          </button>\n          <button class=\"btn small mr-1 mb-1 opacity-75\" @click=\"download('react-native')\">\n            React Native\n          </button>\n        </div>\n        <div class=\"mr-4\">\n          <div class=\"my-1 op50 text-sm\">\n            View on\n          </div>\n          <a\n            v-if=\"collection\" class=\"btn small mr-1 mb-1 opacity-75 inline-flex\"\n            :href=\"`https://icon-sets.iconify.design/${collection.id}/?icon-filter=${icon.split(':')[1]}`\" target=\"_blank\"\n          >\n            Iconify\n          </a>\n          <a\n            v-if=\"collection\" class=\"btn small mr-1 mb-1 opacity-75 inline-flex\"\n            :href=\"`https://uno.antfu.me/?s=i-${icon.replace(':', '-')}`\" target=\"_blank\"\n          >\n            UnoCSS\n          </a>\n        </div>\n      </div>\n\n      <InstallIconSet v-if=\"collection\" :collection=\"collection\" />\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/IconSet.vue",
    "content": "<!-- eslint-disable no-console -->\n<script setup lang='ts'>\nimport { cacheCollection, specialTabs } from '../data'\nimport { isLocalMode } from '../env'\nimport { activeMode, bags, drawerCollapsed, getSearchResults, iconSize, isCurrentCollectionLoading, listType, showHelp, toggleBag } from '../store'\nimport { getIconSnippet } from '../utils/icons'\nimport { cleanupQuery } from '../utils/query'\n\nconst route = useRoute()\nconst router = useRouter()\n\nconst showBag = ref(false)\nconst copied = ref(false)\nconst current = computed({\n  get() {\n    return (route.query.icon as string) || ''\n  },\n  set(value) {\n    router.replace({ query: cleanupQuery({ ...route.query, icon: value }) })\n  },\n})\nconst max = ref(isLocalMode ? 500 : 200)\nconst searchbar = ref<{ input: HTMLElement }>()\n\nconst { search, icons, category, collection, variant } = getSearchResults()\nconst loading = isCurrentCollectionLoading()\n\nconst maxMap = new Map<string, number>()\nconst id = computed(() => collection.value?.id)\nconst url = computed(() => collection.value?.url || collection.value?.author?.url)\nconst npm = computed(() => (id.value != null && !specialTabs.includes(id.value)) ? `https://npmx.dev/package/@iconify-json/${id.value}` : '')\nconst namespace = computed(() => (id.value != null && !specialTabs.includes(id.value)) ? `${id.value}:` : '')\n\nfunction onCopy(status: boolean) {\n  copied.value = status\n  setTimeout(() => {\n    copied.value = false\n  }, 2000)\n}\n\nfunction toggleCategory(cat: string) {\n  if (category.value === cat)\n    category.value = ''\n  else\n    category.value = cat\n}\n\nfunction toggleVariant(v: string) {\n  if (variant.value === v)\n    variant.value = ''\n  else\n    variant.value = v\n}\n\nasync function copyText(text?: string) {\n  if (text) {\n    try {\n      await navigator.clipboard.writeText(text)\n      return true\n    }\n    catch {\n    }\n  }\n  return false\n}\n\nasync function onSelect(icon: string) {\n  switch (activeMode.value) {\n    case 'select':\n      toggleBag(icon)\n      break\n    case 'copy':\n      onCopy(await copyText(await getIconSnippet(\n        [collection.value!],\n        icon,\n        'id',\n        true,\n      ) || icon))\n      break\n    default:\n      current.value = icon\n      break\n  }\n}\n\nfunction loadMore() {\n  max.value += 100\n  maxMap.set(namespace.value, max.value)\n}\n\nasync function loadAll() {\n  if (!namespace.value)\n    return\n\n  await cacheCollection(collection.value!.id)\n  max.value = icons.value.length\n  maxMap.set(namespace.value, max.value)\n}\n\nfunction next(delta = 1) {\n  const name = current.value.startsWith(namespace.value)\n    ? current.value.slice(namespace.value.length)\n    : current.value\n  const index = icons.value.indexOf(name)\n  if (index === -1)\n    return\n  const newOne = icons.value[index + delta]\n  if (newOne)\n    current.value = namespace.value + newOne\n}\n\nwatch(\n  () => namespace.value,\n  () => max.value = maxMap.get(namespace.value) || 200,\n)\n\nfunction focusSearch() {\n  searchbar.value?.input.focus()\n}\n\nonMounted(focusSearch)\nwatch(router.currentRoute, focusSearch, { immediate: true })\n\nrouter.afterEach(() => {\n  focusSearch()\n})\n\nonKeyStroke('/', (e) => {\n  e.preventDefault()\n  focusSearch()\n})\n\nonKeyStroke('Escape', () => {\n  if (current.value !== '') {\n    current.value = ''\n    focusSearch()\n  }\n})\n\nconst categoriesContainer = ref<HTMLElement | null>(null)\nconst { x } = useScroll(categoriesContainer)\nuseEventListener(categoriesContainer, 'wheel', (e: WheelEvent) => {\n  e.preventDefault()\n  if (e.deltaX)\n    x.value += e.deltaX\n  else\n    x.value += e.deltaY\n}, {\n  passive: false,\n})\n</script>\n\n<template>\n  <WithNavbar>\n    <div class=\"flex flex-auto h-full overflow-hidden\">\n      <Drawer\n        h-full overflow-y-overlay flex-none hidden md:block\n        :w=\"drawerCollapsed ? '0px' : '250px'\"\n        transition-all duration-300\n      />\n\n      <button\n        fixed top=\"50%\" flex=\"~ items-end justify-center\" w-5 h-8\n        icon-button transition-all duration-300\n        border=\"t r b base rounded-r-full\" z-10 max-md:hidden\n        title=\"Toggle Sidebar\"\n        :style=\"{ left: drawerCollapsed ? '0px' : '250px' }\"\n        @click=\"drawerCollapsed = !drawerCollapsed\"\n      >\n        <div\n          i-carbon-chevron-left\n          icon-button ml--1\n          transition duration-300 ease-in-out\n          :class=\"drawerCollapsed ? 'transform rotate-180' : ''\"\n        />\n      </button>\n\n      <!-- Loading -->\n      <div\n        v-if=\"collection && loading\"\n        class=\"h-full w-full flex-auto relative bg-base bg-opacity-75 content-center transition-opacity duration-100\"\n        :class=\"loading ? '' : 'opacity-0 pointer-events-none'\"\n      >\n        <div class=\"absolute text-gray-800 dark:text-dark-500\" style=\"top:50%;left:50%;transform:translate(-50%,-50%)\">\n          Loading...\n        </div>\n      </div>\n\n      <div v-else-if=\"collection\" h-full w-full relative max-h-full grid=\"~ rows-[max-content_1fr]\" of-hidden>\n        <div pt-5 flex=\"~ col gap-2\">\n          <div class=\"flex px-8\">\n            <!-- Left -->\n            <div class=\"flex-auto px-2\">\n              <NavPlaceholder class=\"md:hidden\" />\n\n              <div class=\"text-gray-900 text-xl flex items-center select-none dark:text-gray-200\">\n                <div class=\"whitespace-no-wrap of-hidden\">\n                  {{ collection.name }}\n                </div>\n                <!-- Information icons -->\n                <div ml-1 flex=\"~ items-center gap-1\">\n                  <div v-if=\"collection.hidden\" i-carbon:information-disabled text=\"orange sm\" title=\"The icon set was deprecated and is no longer available\" />\n                  <a\n                    v-if=\"url\"\n                    class=\"flex items-center text-base opacity-25 hover:opacity-100\"\n                    :href=\"url\"\n                    target=\"_blank\"\n                  >\n                    <Icon icon=\"la:external-link-square-alt-solid\" />\n                  </a>\n                  <a\n                    v-if=\"npm\"\n                    class=\"flex items-center text-base opacity-25 hover:opacity-100\"\n                    :href=\"npm\"\n                    target=\"_blank\"\n                  >\n                    <Icon icon=\"la:npm\" />\n                  </a>\n                </div>\n                <div class=\"flex-auto\" />\n              </div>\n              <div class=\"text-xs block opacity-50\">\n                {{ collection.author?.name }}\n              </div>\n              <div v-if=\"collection.license\">\n                <a\n                  class=\"text-xs opacity-50 hover:opacity-100\"\n                  :href=\"collection.license.url\"\n                  target=\"_blank\"\n                >{{ collection.license.title }}</a>\n              </div>\n            </div>\n\n            <!-- Right -->\n            <div class=\"flex flex-col\">\n              <ActionsMenu :collection=\"collection\" />\n              <div class=\"flex-auto\" />\n            </div>\n          </div>\n\n          <!-- Categories -->\n          <div v-if=\"collection.categories\" ref=\"categoriesContainer\" class=\"mx-8 flex flex-wrap gap-2 select-none\">\n            <div\n              v-for=\"c of Object.keys(collection.categories).sort()\"\n              :key=\"c\"\n              class=\"\n                whitespace-nowrap text-sm inline-block px-2 border border-base rounded-full hover:bg-gray-50 cursor-pointer\n                dark:border-dark-200 dark:hover:bg-dark-200\n              \"\n              :class=\"c === category ? 'text-primary border-primary dark:border-primary' : 'opacity-75'\"\n              @click=\"toggleCategory(c)\"\n            >\n              {{ c }}\n            </div>\n          </div>\n\n          <!-- Searching -->\n          <SearchBar\n            ref=\"searchbar\"\n            v-model:search=\"search\"\n            class=\"mx-8 hidden md:flex\"\n          />\n\n          <!-- Variants --->\n          <div v-if=\"collection.variants\" class=\"mx-8 mb-2 flex flex-wrap gap-2 select-none items-center\">\n            <div text-sm op50>\n              Variants\n            </div>\n            <div\n              v-for=\"c of Object.keys(collection.variants).sort()\"\n              :key=\"c\"\n              class=\"\n                whitespace-nowrap text-sm inline-block px-2 border border-base rounded-full hover:bg-gray-50 cursor-pointer\n                dark:border-dark-200 dark:hover:bg-dark-200\n              \"\n              :class=\"c === variant ? 'text-primary border-primary dark:border-primary' : 'opacity-75'\"\n              @click=\"toggleVariant(c)\"\n            >\n              {{ c }}\n            </div>\n          </div>\n        </div>\n        <div of-y-scroll of-x-hidden>\n          <!-- Icons -->\n          <div class=\"px-5 pt-2 pb-4 text-center\">\n            <Icons\n              :key=\"namespace\"\n              :icons=\"icons.slice(0, max)\"\n              :selected=\"bags\"\n              :class=\"iconSize\"\n              :display=\"listType\"\n              :search=\"search\"\n              :namespace=\"namespace\"\n              @select=\"onSelect\"\n            />\n            <button v-if=\"icons.length > max\" class=\"btn mx-1 my-3\" @click=\"loadMore\">\n              Load More\n            </button>\n            <button v-if=\"icons.length > max && namespace\" class=\"btn mx-1 my-3\" @click=\"loadAll\">\n              Load All ({{ icons.length - max }})\n            </button>\n            <p class=\"color-fade text-sm pt-4\">\n              {{ icons.length }} icons\n            </p>\n          </div>\n\n          <Footer />\n        </div>\n      </div>\n\n      <template v-if=\"collection\">\n        <!-- Bag Fab -->\n        <FAB\n          v-if=\"bags.length\"\n          icon=\"carbon:shopping-bag\"\n          :number=\"bags.length\"\n          @click=\"showBag = true\"\n        />\n\n        <!-- Bag -->\n        <Modal :value=\"showBag\" direction=\"right\" @close=\"showBag = false\">\n          <Bag\n            @close=\"showBag = false\"\n            @select=\"onSelect\"\n          />\n        </Modal>\n\n        <!-- Details -->\n        <Modal :value=\"!!current\" @close=\"current = ''\">\n          <IconDetail\n            :icon=\"current\" :show-collection=\"specialTabs.includes(collection.id)\"\n            @close=\"current = ''\"\n            @copy=\"onCopy\"\n            @next=\"next(1)\"\n            @prev=\"next(-1)\"\n          />\n        </Modal>\n\n        <!-- Help -->\n        <ModalDialog :value=\"showHelp\" @close=\"showHelp = false\">\n          <HelpPage />\n        </ModalDialog>\n\n        <!-- Mode -->\n        <div\n          class=\"fixed top-0 right-0 pl-4 pr-2 py-1 rounded-l-full bg-primary text-white shadow mt-16 cursor-pointer transition-transform duration-300 ease-in-out\"\n          :style=\"activeMode !== 'normal' ? {} : { transform: 'translateX(120%)' }\"\n          @click=\"activeMode = 'normal'\"\n        >\n          {{ activeMode === 'select' ? 'Multiple select' : 'Name copying mode' }}\n          <Icon icon=\"carbon:close\" class=\"inline-block text-xl align-text-bottom\" />\n        </div>\n\n        <SearchElectron />\n\n        <Notification :value=\"copied\">\n          <Icon icon=\"mdi:check\" class=\"inline-block mr-2 font-xl align-middle\" />\n          <span class=\"align-middle\">Copied</span>\n        </Notification>\n      </template>\n    </div>\n  </WithNavbar>\n</template>\n"
  },
  {
    "path": "src/components/Icons.vue",
    "content": "<script setup lang=\"ts\">\nimport type { PropType } from 'vue'\nimport { Tooltip } from 'floating-vue'\nimport { getSearchHighlightHTML, useThemeColor } from '../hooks'\n\ndefineProps({\n  icons: {\n    type: Array as PropType<any[]>,\n    default: () => [],\n  },\n  selected: {\n    type: Array,\n    default: () => [],\n  },\n  size: {\n    type: String,\n    default: '2xl',\n  },\n  spacing: {\n    type: String,\n    default: 'm-2',\n  },\n  search: {\n    type: String,\n    default: '',\n  },\n  display: {\n    type: String,\n    default: 'grid',\n  },\n  namespace: {\n    type: String,\n    default: '',\n  },\n  colorClass: {\n    type: String,\n    default: 'text-dark-600 dark:text-dark-900',\n  },\n})\ndefineEmits<{\n  (event: 'select', id: string): void\n}>()\n\nconst { style } = useThemeColor()\n</script>\n\n<template>\n  <div class=\"non-dragging flex flex-wrap select-none justify-center\" :class=\"`text-${size} ${colorClass}`\">\n    <div\n      v-for=\"icon of icons \" :key=\"icon\" class=\"non-dragging icons-item relative\"\n      :class=\"[spacing, selected.includes(namespace + icon) ? 'active' : '']\"\n      @click=\"$emit('select', namespace + icon)\"\n    >\n      <div v-if=\"display === 'list'\" class=\"icon-border flex gap-1\">\n        <Icon\n          :key=\"icon\" class=\"non-dragging leading-none\" :cache=\"true\"\n          :icon=\"namespace + icon\"\n        />\n        <span\n          class=\"text-sm px-1 m-auto\"\n          v-html=\"getSearchHighlightHTML(icon, search)\"\n        />\n      </div>\n      <Tooltip v-else placement=\"bottom\">\n        <Icon\n          :key=\"icon\" class=\"non-dragging leading-none icon-border h-1em\" :cache=\"true\"\n          :icon=\"namespace + icon\"\n        />\n        <template #popper>\n          <div :style=\"style\" class=\"leading-none border-none z-100 text-primary opacity-75 text-sm\">\n            {{ icon }}\n          </div>\n        </template>\n      </Tooltip>\n    </div>\n  </div>\n</template>\n\n<style>\n.icons-item:hover,\n.icons-item.active {\n  color: var(--theme-color);\n}\n\n.icon-border {\n  position: relative;\n}\n\n.icon-border.active::after {\n  content: '';\n  position: absolute;\n  top: -3px;\n  left: -3px;\n  right: -3px;\n  bottom: -3px;\n  border-radius: 4px;\n  background: var(--theme-color);\n  opacity: 0.1;\n}\n\n.icon-border:hover::before {\n  content: '';\n  position: absolute;\n  top: -4px;\n  left: -4px;\n  right: -4px;\n  bottom: -4px;\n  border-radius: 4px;\n  border: 1px solid var(--theme-color);\n  opacity: 0.4;\n}\n</style>\n"
  },
  {
    "path": "src/components/InstallIconSet.vue",
    "content": "<script setup lang=\"ts\">\nimport type { PropType } from 'vue'\nimport type { CollectionMeta } from '../data'\nimport { ref } from 'vue'\nimport { selectedPackageManager } from '../store'\n\nconst props = defineProps({\n  collection: {\n    type: Object as PropType<CollectionMeta>,\n    required: true,\n  },\n})\n\nconst managers = ['pnpm', 'npm', 'yarn', 'bun'] as const\n\nconst icons = {\n  npm: 'i-logos:npm-icon',\n  pnpm: 'i-logos:pnpm',\n  yarn: 'i-logos:yarn',\n  bun: 'i-logos:bun',\n}\n\nfunction selectManager(packageName: string) {\n  selectedPackageManager.value = packageName\n}\n\nconst status = ref(false)\n\nasync function copyText() {\n  const text = `${selectedPackageManager.value} ${selectedPackageManager.value !== 'npm' ? 'add' : 'i'} -D @iconify-json/${props.collection.id}`\n  status.value = true\n  setTimeout(() => {\n    status.value = false\n  }, 2000)\n\n  if (text) {\n    try {\n      await navigator.clipboard.writeText(text)\n      return true\n    }\n    catch {}\n  }\n  return false\n}\n</script>\n\n<template>\n  <div lt-md:hidden>\n    <a\n      href=\"https://iconify.design/docs/icons/json.html\" target=\"_blank\"\n      class=\"block w-fit my-1 text-sm mt6 op50 hover:op100 hover:text-primary\"\n    >\n      Install Iconify Iconset\n    </a>\n    <div class=\"border-1 border-base rounded w-fit min-w-100 mt1\">\n      <div flex=\"~ gap-4 items-center\" p3 border=\"b base\">\n        <label\n          v-for=\"manager in managers\" :key=\"manager\"\n          flex=\"~ items-center gap-2\"\n          :class=\"[manager === selectedPackageManager ? 'op100' : 'op25']\"\n          @change=\"selectManager(manager)\"\n        >\n          <input type=\"radio\" name=\"manager\" :value=\"manager\" hidden>\n          <div :class=\"icons[manager]\" />\n          <div mt--1>{{ manager }}</div>\n        </label>\n      </div>\n\n      <div flex=\"~ gap-2 items-center\" p3>\n        <code flex-auto>\n          <span style=\"color:#80A665;\">{{ selectedPackageManager }}</span>\n          <span style=\"color:#DBD7CAEE;\" />\n          <span style=\"color:#B8A965;\">{{ selectedPackageManager !== 'npm' ? ' add ' : ' i ' }} -D </span>\n          <span style=\"color:#DBD7CAEE;\" /><span style=\"color:#DBD7CAEE;\" />\n          <span style=\"color:#C98A7D;\">@iconify-json/{{ props.collection.id }}</span>\n        </code>\n        <IconButton icon=\"carbon:copy\" @click=\"copyText\" />\n      </div>\n      <Notification :value=\"status\">\n        <Icon icon=\"mdi:check\" class=\"inline-block mr-2 font-xl align-middle\" />\n        <span class=\"align-middle\">Copied</span>\n      </Notification>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/Modal.vue",
    "content": "<script setup lang='ts'>\nconst props = withDefaults(defineProps<{\n  value?: boolean\n  direction?: string\n}>(), {\n  value: false,\n  direction: 'bottom',\n})\n\nconst emit = defineEmits(['close'])\n\nconst { width, height } = useWindowSize()\nconst isSmall = computed(() => width.value < 600 || height.value < 450)\n\nconst positionClass = computed(() => {\n  if (isSmall.value)\n    return 'bottom-0 left-0 right-0 top-0 of-auto'\n  switch (props.direction) {\n    case 'bottom':\n      return 'bottom-0 left-0 right-0 border-t'\n    case 'top':\n      return 'top-0 left-0 right-0 border-b'\n    case 'left':\n      return 'bottom-0 left-0 top-0 border-r'\n    case 'right':\n      return 'bottom-0 top-0 right-0 border-l'\n    default:\n      return ''\n  }\n})\n\nconst transform = computed(() => {\n  switch (props.direction) {\n    case 'bottom':\n      return 'translateY(100%)'\n    case 'top':\n      return 'translateY(-100%)'\n    case 'left':\n      return 'translateX(-100%)'\n    case 'right':\n      return 'translateX(100%)'\n    default:\n      return ''\n  }\n})\n</script>\n\n<template>\n  <div\n    fixed top-0 bottom-0 left-0 right-0 z-40\n    :class=\"value ? '' : 'pointer-events-none'\"\n  >\n    <div\n      bg-base bottom-0 left-0 right-0 top-0 absolute transition-opacity duration-500 ease-out\n      :class=\"value ? 'opacity-85' : 'opacity-0'\"\n      @click=\"emit('close')\"\n    />\n    <div\n      bg-base border border-base absolute transition-all duration-200 ease-out\n      :class=\"positionClass\"\n      :style=\"value ? {} : { transform }\"\n    >\n      <slot />\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/ModalDialog.vue",
    "content": "<script setup lang='ts'>\nwithDefaults(defineProps<{\n  value?: boolean\n  direction?: string\n}>(), {\n  value: false,\n  direction: 'bottom',\n})\n\nconst emit = defineEmits(['close'])\n</script>\n\n<template>\n  <div\n    class=\"fixed top-0 bottom-0 left-0 right-0 z-60\"\n    :class=\"value ? '' : 'pointer-events-none'\"\n  >\n    <div\n      class=\"\n        bg-base bottom-0 left-0 right-0 top-0 absolute transition-opacity duration-500 ease-out\n      \"\n      :class=\"value ? 'opacity-85' : 'opacity-0'\"\n      @click=\"emit('close')\"\n    />\n    <div\n      class=\"\n        bg-base absolute transition-all duration-200 ease-out shadow rounded-md transform\n        border border-base left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2\n      \"\n      :class=\"value ? 'opacity-100' : 'opacity-0'\"\n    >\n      <slot />\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/Navbar.vue",
    "content": "<script lang=\"ts\">\nimport { isElectron } from '../env'\nimport { getSearchResults, isDark } from '../store'\n\nexport default defineComponent({\n  setup() {\n    const route = useRoute()\n\n    return {\n      ...getSearchResults(),\n      isElectron,\n      isDark,\n      showNav: computed(() => !route.path.startsWith('/collection')),\n      isHomepage: computed(() => route.path === '/'),\n    }\n  },\n})\n</script>\n\n<template>\n  <NavElectron\n    v-if=\"isElectron && !isHomepage\"\n  />\n  <nav\n    class=\"dragging\"\n    flex=\"~ gap4 none\"\n    p4 relative bg-base z-10 border=\"b base\" text-xl\n    :class=\"showNav ? '' : 'md:hidden'\"\n  >\n    <!-- In Collections -->\n    <template v-if=\"!isHomepage && !isElectron\">\n      <RouterLink\n        class=\"non-dragging\"\n        icon-button flex-none\n        i-carbon:arrow-left\n        to=\"/\"\n      />\n    </template>\n\n    <!-- Homepage Only -->\n    <template v-if=\"showNav\">\n      <!-- <RouterLink\n        class=\"non-dragging\"\n        i-carbon:search icon-button flex-none\n        to=\"/collection/all\"\n      /> -->\n      <div flex-auto />\n      <h1\n        absolute top-0 left-0 right-0 bottom-0 flex items-center justify-center\n        text-xl font-light tracking-2px pointer-events-none\n      >\n        Icônes\n      </h1>\n      <a\n        class=\"non-dragging\"\n        i-carbon-logo-github icon-button flex-none\n        href=\"https://github.com/antfu/icones\"\n        target=\"_blank\"\n        title=\"GitHub\"\n      />\n      <RouterLink\n        class=\"non-dragging\"\n        i-carbon-settings icon-button flex-none\n        to=\"/settings\"\n        title=\"Settings\"\n      />\n      <DarkSwitcher flex-none />\n    </template>\n\n    <!-- Searching -->\n    <SearchBar\n      v-if=\"collection\"\n      v-model:search=\"search\"\n      class=\"flex w-full\"\n      :style=\"false\"\n      :icon=\"false\"\n    />\n  </nav>\n</template>\n"
  },
  {
    "path": "src/components/Notification.vue",
    "content": "<script setup lang='ts'>\nwithDefaults(\n  defineProps<{ value?: boolean }>(),\n  { value: false },\n)\n</script>\n\n<template>\n  <div\n    class=\"fixed top-0 left-0 right-0 z-50 text-center\"\n    :class=\"value ? '' : 'pointer-events-none overflow-hidden'\"\n  >\n    <div\n      class=\"\n        px-3 py-1 rounded inline-block m-3 transition-all duration-300 text-primary\n        bg-base border border-base\n        flex flex-inline items-center\n      \"\n      :style=\"value ? {} : { transform: 'translateY(-150%)' }\"\n      :class=\"value ? 'shadow' : 'shadow-none'\"\n    >\n      <slot />\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/Progress.vue",
    "content": "<script setup lang='ts'>\nimport { inProgress, progressMessage } from '../store'\n</script>\n\n<template>\n  <div\n    border=\"~ base\"\n    class=\"fixed bottom-0 left-0 bg-base color-base rounded-tr shadow px-3 py-1 text-sm flex\"\n    :style=\"inProgress ? {} : { transform: 'translateY(120%)' }\"\n  >\n    <Icon icon=\"carbon:circle-dash\" class=\"rotating m-auto mr-1 text-lg\" />\n    <span class=\"m-auto px-1 blinking\">{{ progressMessage }}</span>\n  </div>\n</template>\n\n<style>\n@keyframes rotating {\n  from {\n    transform: rotate(0deg);\n  }\n  to {\n    transform: rotate(360deg);\n  }\n}\n@keyframes blinking {\n  from {\n    opacity: 1;\n  }\n  50% {\n    opacity: 0.3;\n  }\n  to {\n    opacity: 1;\n  }\n}\n\n.rotating {\n  animation: rotating 2s linear infinite;\n}\n\n.blinking {\n  animation: blinking 2s linear infinite;\n}\n</style>\n"
  },
  {
    "path": "src/components/SearchBar.vue",
    "content": "<script setup lang='ts'>\ndefineProps({\n  search: {\n    type: String,\n    default: undefined,\n  },\n  placeholder: {\n    type: String,\n    default: 'Search...',\n  },\n  style: {\n    type: Boolean,\n    default: true,\n  },\n  border: {\n    type: Boolean,\n    default: true,\n  },\n  icon: {\n    type: [String, Boolean],\n    default: 'carbon:search',\n  },\n  inputClass: {\n    type: String,\n    default: '',\n  },\n})\n\nconst emit = defineEmits(['update:search', 'onKeydown'])\n\nconst input = ref<HTMLInputElement>()\ndefineExpose({ input })\n\nfunction onKeydown(event: any) {\n  emit('onKeydown', event)\n}\n\nconst update = useDebounceFn((event: any) => {\n  emit('update:search', event.target.value)\n}, 250)\n\nfunction clear() {\n  emit('update:search', '')\n}\n</script>\n\n<template>\n  <div :class=\"style ? ['md:flex md:shadow md:rounded outline-none md:py-1 py-3 px-4', { 'border-b border-x border-b md:border-t border-base': border }] : ''\">\n    <Icon v-if=\"icon\" :icon=\"icon\" class=\"m-auto flex-none opacity-60\" />\n    <form action=\"/collection/all\" class=\"flex-auto\" role=\"search\" method=\"get\" @submit.prevent>\n      <input\n        ref=\"input\"\n        :value=\"search\"\n        aria-label=\"Search\"\n        :class=\"inputClass\"\n        class=\"text-base outline-none w-full py-1 px-4 m-0 bg-transparent\"\n        name=\"s\"\n        :placeholder=\"placeholder\"\n        autofocus\n        autocomplete=\"off\"\n        @input=\"update\"\n        @keydown=\"onKeydown\"\n      >\n    </form>\n\n    <button class=\"flex items-center opacity-60 hover:opacity-80\">\n      <Icon v-if=\"search\" icon=\"carbon:close\" class=\"m-auto text-lg -mr-1\" @click=\"clear\" />\n    </button>\n    <slot name=\"actions\" />\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/SettingsCollectionsList.vue",
    "content": "<script setup lang=\"ts\">\nimport type { CollectionInfo } from '../data'\nimport { isInstalled } from '../data'\nimport { isElectron } from '../env'\nimport { isExcludedCategory, isExcludedCollection, isFavoritedCollection, toggleExcludedCollection, toggleFavoriteCollection } from '../store'\n\ndefineProps<{\n  collections: readonly CollectionInfo[]\n}>()\n</script>\n\n<template>\n  <div>\n    <div\n      v-for=\"c, idx of collections\" :key=\"c.id\" flex=\"~ gap-2\" py1 px2 items-center\n      border=\"~ base\" mt--1px break-inside-avoid\n      :class=\"idx === 0 ? 'border-t' : ''\"\n    >\n      <RouterLink\n        :to=\"`/collection/${c.id}`\"\n        flex-auto\n        :class=\"isExcludedCollection(c) ? 'op25 line-through' : ''\"\n      >\n        {{ c.name }}\n      </RouterLink>\n      <div />\n      <div\n        v-if=\"isInstalled(c.id) && !isElectron\"\n        icon-button class=\"!op50\"\n        i-carbon-cloud-auditing\n        title=\"Cached in browser\"\n      />\n      <button\n        v-if=\"!isExcludedCollection(c)\"\n        icon-button\n        :class=\"isFavoritedCollection(c.id) ? 'i-carbon:star-filled text-yellow' : 'i-carbon:star'\"\n        title=\"Toggle Favorite\"\n        @click=\"toggleFavoriteCollection(c.id)\"\n      />\n      <button\n        v-if=\"!isExcludedCategory(c.category)\"\n        icon-button\n        :class=\"isExcludedCollection(c) ? 'i-carbon:view-off text-rose' : 'i-carbon:view'\"\n        title=\"Toggle Visible\"\n        @click=\"toggleExcludedCollection(c.id)\"\n      />\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/SnippetPreview.vue",
    "content": "<script lang='ts' setup>\nimport type { CollectionInfo } from '../data'\nimport type { Snippet } from '../utils/icons'\nimport { Menu } from 'floating-vue'\nimport { collections } from '../data'\nimport { getIconSnippet } from '../utils/icons'\nimport { highlight } from '../utils/shiki'\nimport { prettierCode } from '../utils/svg'\n\nconst props = defineProps<{\n  collection?: CollectionInfo\n  icon: string\n  snippet: Snippet\n  type: string\n  color: string\n}>()\n\nconst code = ref<string>('')\n\nasync function onShow() {\n  if (!code.value) {\n    code.value = await getIconSnippet(\n      props.collection ? [props.collection] : collections,\n      props.icon,\n      props.type,\n      false,\n      props.color,\n    ) || ''\n  }\n}\n\nconst highlightCode = computedAsync(async () => {\n  const c = code.value\n  const formatted = (await prettierCode(c, props.snippet.prettierParser)).trim()\n  return highlight(formatted, props.snippet.lang)\n})\n</script>\n\n<template>\n  <Menu :delay=\"0\" placement=\"top\" distance=\"10\" @show=\"onShow\">\n    <slot />\n    <template #popper>\n      <div color-base px3 py2 border=\"b base\" bg-gray:5>\n        Snippet Preview\n        <span op50>Click the button to copy</span>\n      </div>\n      <div w-full max-h-50 of-auto>\n        <div h-fit max-w-70 sm:max-w-100 md:max-w-120 lg:max-w-150 p2 text-sm v-html=\"highlightCode\" />\n      </div>\n    </template>\n  </Menu>\n</template>\n"
  },
  {
    "path": "src/components/WithNavbar.vue",
    "content": "<template>\n  <div class=\"flex h-screen flex-col overflow-hidden\">\n    <Navbar />\n    <div class=\"flex-auto flex flex-col of-hidden\">\n      <slot />\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/electron/NavElectron.vue",
    "content": "<template>\n  <div\n    class=\"electron-nav dragging cursor-pointer flex-none flex justify-start items-center fixed top-0 left-0 z-50 border-base bg-base border-r border-b rounded-br opacity-0 hover:opacity-100 transition-all duration-300\"\n  >\n    <div class=\"mac-controls flex-none\" />\n    <IconButton\n      v-if=\"$route.path !== '/'\" to=\"/\"\n      class=\"text-lg px-2 flex-none ma\"\n      icon=\"carbon:chevron-left\"\n    />\n  </div>\n</template>\n\n<style>\n.electron-nav {\n  height: 38px;\n}\n\n.mac-controls {\n  width: 70px;\n}\n\n.electron-nav .icon-button svg {\n  margin-top: -2px;\n}\n</style>\n"
  },
  {
    "path": "src/components/electron/NavPlaceholder.vue",
    "content": "<script setup lang=\"ts\">\nimport { isElectron } from '../../env'\n</script>\n\n<template>\n  <div v-if=\"isElectron\" class=\"flex-none\" style=\"height: 27px;\">\n    <slot />\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/electron/SearchElectron.vue",
    "content": "<script setup lang=\"ts\">\nimport { isSearchOpen } from '../../data'\nimport { isElectron } from '../../env'\nimport { getSearchResults } from '../../store'\n\nconst { search, collection } = getSearchResults()\nconst input = ref<HTMLInputElement | null>(null)\n\nwatch(isSearchOpen, (v) => {\n  if (input.value) {\n    if (v) {\n      input.value.focus()\n    }\n    else {\n      input.value.blur()\n      search.value = ''\n    }\n  }\n})\n</script>\n\n<template>\n  <Notification v-if=\"isElectron\" class=\"text-right md:hidden\" :value=\"isSearchOpen\">\n    <div v-if=\"collection\" class=\"flex text-base\">\n      <Icon icon=\"carbon:search\" class=\"m-auto flex-none opacity-60\" />\n      <input\n        ref=\"input\"\n        v-model=\"search\"\n        class=\"text-base outline-none py-1 px-4 flex-auto m-0 bg-transparent\"\n        placeholder=\"Search...\"\n        @keydown.esc=\"isSearchOpen = false\"\n      >\n      <Icon icon=\"carbon:close\" class=\"m-auto text-lg -mr-1 opacity-60\" @click=\"isSearchOpen = false\" />\n    </div>\n  </Notification>\n</template>\n"
  },
  {
    "path": "src/components.d.ts",
    "content": "/* eslint-disable */\n// @ts-nocheck\n// biome-ignore lint: disable\n// oxlint-disable\n// ------\n// Generated by unplugin-vue-components\n// Read more: https://github.com/vuejs/core/pull/3399\n\nexport {}\n\n/* prettier-ignore */\ndeclare module 'vue' {\n  export interface GlobalComponents {\n    ActionsMenu: typeof import('./components/ActionsMenu.vue')['default']\n    Bag: typeof import('./components/Bag.vue')['default']\n    CollectionEntries: typeof import('./components/CollectionEntries.vue')['default']\n    CollectionEntry: typeof import('./components/CollectionEntry.vue')['default']\n    ColorPicker: typeof import('./components/ColorPicker.vue')['default']\n    CustomSelect: typeof import('./components/CustomSelect.vue')['default']\n    DarkSwitcher: typeof import('./components/DarkSwitcher.vue')['default']\n    Drawer: typeof import('./components/Drawer.vue')['default']\n    FAB: typeof import('./components/FAB.vue')['default']\n    Footer: typeof import('./components/Footer.vue')['default']\n    HelpPage: typeof import('./components/HelpPage.vue')['default']\n    Icon: typeof import('./components/Icon.vue')['default']\n    IconButton: typeof import('./components/IconButton.vue')['default']\n    IconDetail: typeof import('./components/IconDetail.vue')['default']\n    Icons: typeof import('./components/Icons.vue')['default']\n    IconSet: typeof import('./components/IconSet.vue')['default']\n    InstallIconSet: typeof import('./components/InstallIconSet.vue')['default']\n    Modal: typeof import('./components/Modal.vue')['default']\n    ModalDialog: typeof import('./components/ModalDialog.vue')['default']\n    Navbar: typeof import('./components/Navbar.vue')['default']\n    NavElectron: typeof import('./components/electron/NavElectron.vue')['default']\n    NavPlaceholder: typeof import('./components/electron/NavPlaceholder.vue')['default']\n    Notification: typeof import('./components/Notification.vue')['default']\n    Progress: typeof import('./components/Progress.vue')['default']\n    RouterLink: typeof import('vue-router')['RouterLink']\n    RouterView: typeof import('vue-router')['RouterView']\n    SearchBar: typeof import('./components/SearchBar.vue')['default']\n    SearchElectron: typeof import('./components/electron/SearchElectron.vue')['default']\n    SettingsCollectionsList: typeof import('./components/SettingsCollectionsList.vue')['default']\n    SnippetPreview: typeof import('./components/SnippetPreview.vue')['default']\n    WithNavbar: typeof import('./components/WithNavbar.vue')['default']\n  }\n}\n"
  },
  {
    "path": "src/data/index.ts",
    "content": "import type { IconifyJSON } from 'iconify-icon'\nimport { notNullish } from '@antfu/utils'\nimport { AsyncFzf } from 'fzf'\nimport { addCollection } from 'iconify-icon'\nimport { isLocalMode, staticPath } from '../env'\nimport { loadCollection, saveCollection } from '../store/indexedDB'\nimport {\n  favoritedCollectionIds,\n  isExcludedCollection,\n  isFavoritedCollection,\n  isRecentCollection,\n  recentCollectionIds,\n} from '../store/localstorage'\nimport { inProgress, progressMessage } from '../store/progress'\nimport infoJSON from './collections-info.json'\nimport { variantCategories } from './variant-category'\n\nexport const specialTabs = ['all', 'recent']\n\nexport type PresentType = 'favorite' | 'recent' | 'normal'\n\nexport interface CollectionInfo {\n  id: string\n  name: string\n  author?: { name: string, url: string }\n  license?: { title: string, url: string }\n  url?: string\n  sampleIcons?: string[]\n  category?: string\n  palette?: string\n  total?: number\n  prepacked?: IconifyJSON\n  /**\n   * The icon set was deprecated and is no longer available\n   */\n  hidden?: boolean\n}\n\nexport interface CollectionMeta extends CollectionInfo {\n  icons: string[]\n  categories?: Record<string, string[]>\n  variants?: Record<string, string[]>\n}\n\nconst loadedMeta = ref<CollectionMeta[]>([])\nconst installed = ref<string[]>([])\n\nexport const collections = infoJSON.map(c => Object.freeze(c as any as CollectionInfo))\nexport const enabledCollections = computed(() => collections.filter(c => !isExcludedCollection(c)))\n\nexport const categories = Array.from(new Set(collections.map(i => i.category).filter(notNullish)))\n\nexport const isSearchOpen = ref(false)\nexport const categorySearch = ref('')\n\nconst fzf = new AsyncFzf(collections, {\n  casing: 'case-insensitive',\n  fuzzy: 'v1',\n  selector: v => `${v.name} ${v.id} ${v.category} ${v.author}`,\n})\n\nexport const filteredCollections = ref<CollectionInfo[]>(enabledCollections.value)\n\nwatch([categorySearch, enabledCollections], ([q]) => {\n  if (!q) {\n    filteredCollections.value = enabledCollections.value\n  }\n  else {\n    fzf.find(q).then((result) => {\n      filteredCollections.value = result.map(i => i.item)\n    }).catch(() => {\n      // The search is canceled\n    })\n  }\n})\n\nexport const sortedCollectionsInfo = computed(() =>\n  filteredCollections.value\n    .sort((a, b) => favoritedCollectionIds.value.indexOf(b.id) - favoritedCollectionIds.value.indexOf(a.id)),\n)\n\nexport const favoritedCollections = computed(() =>\n  filteredCollections.value.filter(i => isFavoritedCollection(i.id))\n    .sort((a, b) => favoritedCollectionIds.value.indexOf(b.id) - favoritedCollectionIds.value.indexOf(a.id)),\n)\n\nexport const recentCollections = computed(() =>\n  filteredCollections.value.filter(i => isRecentCollection(i.id))\n    .sort((a, b) => recentCollectionIds.value.indexOf(b.id) - recentCollectionIds.value.indexOf(a.id)),\n)\n\nexport function isInstalled(id: string) {\n  return installed.value.includes(id)\n}\nexport function isMetaLoaded(id: string) {\n  return !!loadedMeta.value.find(i => i.id === id)\n}\n\n// install the preview icons on the homepage\nexport function preInstall() {\n  for (const collection of collections) {\n    if (collection.prepacked)\n      addCollection(collection.prepacked as any)\n  }\n}\n\nexport async function tryInstallFromLocal(id: string) {\n  if (specialTabs.includes(id))\n    return false\n\n  if (isLocalMode)\n    return true\n\n  if (installed.value.includes(id))\n    return true\n\n  const result = await loadCollection(id)\n  if (!result || !result.data)\n    return false\n\n  const data = result.data\n  addCollection(data)\n  installed.value.push(id)\n\n  return true\n}\n\n// load full iconset\nexport async function downloadAndInstall(id: string) {\n  if (specialTabs.includes(id))\n    return false\n\n  if (installed.value.includes(id))\n    return true\n\n  const data = Object.freeze(await fetch(`${staticPath}/collections/${id}.json`).then(r => r.json()))\n\n  addCollection(data)\n  installed.value.push(id)\n\n  if (!isLocalMode)\n    saveCollection(id, data) // async\n\n  return true\n}\n\nexport async function cacheCollection(id: string) {\n  progressMessage.value = 'Downloading...'\n  inProgress.value = true\n  await nextTick()\n  await downloadAndInstall(id)\n  inProgress.value = false\n}\n\nexport async function getCollectionMeta(id: string): Promise<CollectionMeta | null> {\n  let meta = loadedMeta.value.find(i => i.id === id)\n  if (meta)\n    return meta\n\n  meta = await fetch(`${staticPath}/collections/${id}-meta.json`).then(r => r.json())\n\n  if (!meta)\n    return null\n\n  meta.variants ||= getVariantCategories(meta)\n\n  meta = Object.freeze(meta)\n\n  loadedMeta.value.push(meta)\n\n  return meta\n}\n\nfunction getVariantCategories(collection: CollectionMeta) {\n  const variantsRule = variantCategories[collection.id]\n  if (!variantsRule)\n    return\n\n  const variants: Record<string, string[]> = {}\n\n  for (const icon of collection.icons) {\n    const name = variantsRule.find(i => typeof i[1] === 'string' ? icon.endsWith(i[1]) : i[1].test(icon))?.[0] || 'Regular'\n    if (!variants[name])\n      variants[name] = []\n    variants[name].push(icon)\n  }\n\n  return variants\n}\n\nexport async function getFullMeta() {\n  if (loadedMeta.value.length === collections.length)\n    return loadedMeta.value\n\n  loadedMeta.value = Object.freeze(\n    await fetch(`${staticPath}/collections-meta.json`).then(r => r.json()),\n  )\n\n  return loadedMeta.value\n}\n\npreInstall()\n"
  },
  {
    "path": "src/data/search-alias.ts",
    "content": "export const searchAlias: string[][] = [\n  ['account', 'person', 'profile', 'user'],\n  ['add', 'create', 'new', 'plus'],\n  ['alert', 'bell', 'notification', 'notify', 'reminder'],\n  ['approve', 'like', 'recommend', 'thumbs-up'],\n  ['left', 'previous'],\n  ['next', 'right'],\n  ['attach', 'connect', 'link'],\n  ['bag', 'basket', 'cart'],\n  ['bookmark', 'tag', 'label'],\n  ['building', 'home', 'house'],\n  ['calendar', 'date', 'event'],\n  ['cancel', 'close'],\n  ['delete', 'remove', 'trash'],\n  ['chat', 'conversation', 'message'],\n  ['clock', 'time', 'timer', 'alarm'],\n  ['cog', 'gear', 'preferences', 'settings'],\n  ['directory', 'folder'],\n  ['disapprove', 'dislike', 'thumbs-down'],\n  ['document', 'file', 'paper'],\n  ['earth', 'globe', 'world', 'planet', 'global'],\n  ['email', 'envelope', 'mail'],\n  ['eye', 'view', 'visible'],\n  ['favorite', 'heart', 'love'],\n  ['feed', 'rss', 'subscribe', 'subscription'],\n  ['list', 'menu'],\n  ['lock', 'secure', 'security'],\n  ['unlock', 'lock-open'],\n  ['log-in', 'login', 'sign-in'],\n  ['log-out', 'logout', 'sign-out'],\n  ['magnifier', 'search', 'find', 'magnify'],\n  ['photo', 'picture', 'image'],\n  ['refresh', 'reload', 'update', 'sync'],\n  ['speaker', 'audio', 'volume', 'sound'],\n  ['speed', 'fast'],\n  ['accessibility', 'ally', 'a11y'],\n  ['edit', 'pen', 'pencil', 'write'],\n  ['moon', 'night', 'dark'],\n  ['sun', 'day'],\n  ['bulb', 'idea'],\n  ['pin', 'location', 'map', 'marker'],\n  ['bot', 'robot', 'android'],\n  ['db', 'database'],\n  ['external', 'launch'],\n  ['airplane', 'flight'],\n  ['chart', 'graph'],\n  ['monitor', 'screen'],\n  ['video', 'film'],\n  ['support', 'help', 'question'],\n  ['mute', 'silence', 'sound-off', 'volume-off'],\n  ['code', 'development', 'program', 'terminal', 'braces'],\n  ['phone', 'call'],\n  ['car', 'vehicle', 'transport', 'taxi'],\n]\n"
  },
  {
    "path": "src/data/variant-category.ts",
    "content": "// @keep-sorted\nexport const variantCategories: Record<string, [string, string | RegExp][]> = {\n  'academicons': [\n    ['Square', '-square'],\n  ],\n  'akar-icons': [\n    ['Fill', '-fill'],\n  ],\n  'ant-design': [\n    ['Outlined', '-outlined'],\n    ['Filled', '-filled'],\n    ['Twotone', '-twotone'],\n  ],\n  'basil': [\n    ['Outline', '-outline'],\n    ['Solid', '-solid'],\n  ],\n  'bi': [\n    ['Fill', '-fill'],\n  ],\n  'clarity': [\n    ['Line', '-line'],\n    ['Solid', '-solid'],\n    ['Outline Badged', '-outline-badged'],\n    ['Solid Badged', '-solid-badged'],\n    ['Outline Alerted', '-outline-alerted'],\n    ['Solid Alerted', '-solid-alerted'],\n  ],\n  'eos-icons': [\n    ['Outlined', '-outlined'],\n  ],\n  'fluent': [\n    ['Filled 20', '-20-filled'],\n    ['Regular 20', '-20-regular'],\n    ['Filled 24', '-24-filled'],\n    ['Regular 24', '-24-regular'],\n    ['Filled 16', '-16-filled'],\n    ['Regular 16', '-16-regular'],\n    ['Filled 48', '-48-filled'],\n    ['Regular 48', '-48-regular'],\n    ['Filled 28', '-28-filled'],\n    ['Regular 28', '-28-regular'],\n    ['Filled 32', '-32-filled'],\n    ['Regular 32', '-32-regular'],\n    ['Filled 12', '-12-filled'],\n    ['Regular 12', '-12-regular'],\n    ['Filled 10', '-10-filled'],\n    ['Regular 10', '-10-regular'],\n  ],\n  'heroicons': [\n    ['20 Solid', '-20-solid'],\n    ['Solid', '-solid'],\n  ],\n  'ic': [\n    ['Outline', /^outline-/],\n    ['Round', /^round-/],\n    ['Sharp', /^sharp-/],\n    ['Twotone', /^twotone-/],\n    ['Baseline', /^baseline-/],\n  ],\n  'iconamoon': [\n    ['Bold', '-bold'],\n    ['Duotone', '-duotone'],\n    ['Fill', '-fill'],\n    ['Light', '-light'],\n    ['Thin', '-thin'],\n  ],\n  'iconoir': [\n    ['Solid', '-solid'],\n  ],\n  'ion': [\n    ['Outline', '-outline'],\n    ['Sharp', '-sharp'],\n    ['Regular', ''],\n  ],\n  'line-md': [\n    ['Twotone', '-twotone'],\n    ['Twotone Transition', '-twotone-transition'],\n    ['Loop', '-loop'],\n    ['Filled', '-filled'],\n  ],\n  'majesticons': [\n    ['Line', '-line'],\n  ],\n  'maki': [\n    ['11px', '-11'],\n  ],\n  'material-symbols-light': [\n    ['Outline Rounded', '-outline-rounded'],\n    ['Outline Sharp', '-outline-sharp'],\n    ['Outline', '-outline'],\n    ['Rounded', '-rounded'],\n    ['Sharp', '-sharp'],\n  ],\n  'material-symbols': [\n    ['Outline Rounded', '-outline-rounded'],\n    ['Outline', '-outline'],\n    ['Rounded', '-rounded'],\n    ['Sharp', '-sharp'],\n  ],\n  'mdi': [\n    ['Outline', '-outline'],\n  ],\n  'mingcute': [\n    ['Fill', '-fill'],\n    ['Line', '-line'],\n  ],\n  'mynaui': [\n    ['Solid', '-solid'],\n  ],\n  'octicon': [\n    ['16px', '-16'],\n  ],\n  'ph': [\n    ['Bold', '-bold'],\n    ['Duotone', '-duotone'],\n    ['Fill', '-fill'],\n    ['Light', '-light'],\n    ['Thin', '-thin'],\n  ],\n  'ri': [\n    ['Fill', '-fill'],\n    ['Line', '-line'],\n  ],\n  'si': [\n    ['Fill', '-fill'],\n    ['Line', '-line'],\n    ['Duotone', '-duotone'],\n  ],\n  'solar': [\n    ['Bold', '-bold'],\n    ['Duotone', '-duotone'],\n    ['Broken', '-broken'],\n    ['Twotone', '-twotone'],\n    ['Outline', '-outline'],\n    ['Linear', '-linear'],\n  ],\n  'tabler': [\n    ['Filled', '-filled'],\n    ['Lines', '-lines'],\n  ],\n  'teenyicons': [\n    ['Outline', '-outline'],\n    ['Solid', '-solid'],\n  ],\n  'twemoji': [\n    ['Medium Dark Skin Tone', '-medium-dark-skin-tone'],\n    ['Medium Light Skin Tone', '-medium-light-skin-tone'],\n    ['Dark Skin Tone', '-dark-skin-tone'],\n    ['Light Skin Tone', '-light-skin-tone'],\n    ['Medium Skin Tone', '-medium-skin-tone'],\n  ],\n  'typcn': [\n    ['Outline', '-outline'],\n    ['Thick', '-thick'],\n  ],\n  'zondicons': [\n    ['Outline', '-outline'],\n    ['Solid', '-solid'],\n  ],\n}\n"
  },
  {
    "path": "src/env.ts",
    "content": "export const isElectron = import.meta.env.MODE === 'electron'\nexport const isVSCode = location.protocol === 'vscode-webview:'\nexport const isLocalMode = isElectron || isVSCode\n\nexport const basePath = isVSCode ? window.baseURI : '/'\nexport const staticPath = isVSCode\n  ? window.staticURI\n  : (isElectron && import.meta.env.PROD) ? '../../app.asar/dist' : ''\n"
  },
  {
    "path": "src/hooks/color.ts",
    "content": "import { themeColor } from '../store'\n\nexport function useThemeColor() {\n  const style = computed<any>(() => ({\n    '--theme-color': themeColor.value,\n  }))\n\n  return {\n    style,\n  }\n}\n"
  },
  {
    "path": "src/hooks/index.ts",
    "content": "export * from './color'\nexport * from './search'\n"
  },
  {
    "path": "src/hooks/search.ts",
    "content": "import type { Ref } from 'vue'\nimport type { CollectionMeta } from '../data'\nimport { asyncExtendedMatch, AsyncFzf } from 'fzf'\nimport { computed, markRaw, ref, watch } from 'vue'\nimport { specialTabs } from '../data'\nimport { searchAlias } from '../data/search-alias'\nimport { cleanupQuery } from '../utils/query'\n\nexport function useSearch(collection: Ref<CollectionMeta | null>) {\n  const route = useRoute()\n  const router = useRouter()\n\n  const category = computed({\n    get() {\n      return route.query.category as string || ''\n    },\n    set(value: string) {\n      router.replace({ query: cleanupQuery({ ...route.query, category: value }) })\n    },\n  })\n  const variant = computed({\n    get() {\n      return route.query.variant as string || ''\n    },\n    set(value: string) {\n      router.replace({ query: cleanupQuery({ ...route.query, variant: value }) })\n    },\n  })\n  const search = computed({\n    get() {\n      return route.query.s as string || ''\n    },\n    set(value: string) {\n      router.replace({ query: cleanupQuery({ ...route.query, s: value }) })\n    },\n  })\n\n  const isAll = computed(() => collection.value && specialTabs.includes(collection.value.id))\n  const searchParts = computed(() => search.value.trim().toLowerCase().split(' ').filter(Boolean))\n\n  const aliasedSearchCandidates = computed(() => {\n    const options = new Set([\n      searchParts.value.join(' '),\n    ])\n\n    searchParts.value.forEach((i, idx, arr) => {\n      const alias = searchAlias.find(a => a.includes(i))\n      if (alias?.length) {\n        alias.forEach((a) => {\n          options.add([...arr.slice(0, idx), a, arr.slice(idx + 1)].filter(Boolean).join(' ').trim())\n        })\n      }\n    })\n\n    return [...options]\n  })\n\n  // Matching any character used in extended match\n  // https://github.com/junegunn/fzf#search-syntax\n  const useExtendedMatch = computed(() => /[ '^$!]/.test(search.value))\n\n  const iconSource = computed(() => {\n    if (!collection.value)\n      return []\n\n    return (category.value && variant.value)\n      ? arrayIntersection(\n          collection.value.categories?.[category.value] || [],\n          collection.value.variants?.[variant.value] || [],\n        )\n      : category.value\n        ? (collection.value.categories?.[category.value] || [])\n        : variant.value\n          ? (collection.value.variants?.[variant.value] || [])\n          : collection.value.icons\n  })\n\n  const fzf = computed(() => {\n    return markRaw(new AsyncFzf(iconSource.value, {\n      casing: 'case-insensitive',\n      match: asyncExtendedMatch,\n    }))\n  })\n\n  const fzfFast = computed(() => {\n    return markRaw(new AsyncFzf(iconSource.value, {\n      casing: 'case-insensitive',\n      // v1 is faster\n      // https://fzf.netlify.app/docs/latest#async-finder-considering-other-options-first\n      fuzzy: 'v1',\n    }))\n  })\n\n  const icons = ref<string[]>([])\n\n  function runSearch() {\n    const finder = (useExtendedMatch.value || aliasedSearchCandidates.value.length > 1)\n      ? fzf\n      : fzfFast\n\n    const searchString = aliasedSearchCandidates.value.join(' | ')\n\n    finder.value.find(searchString)\n      .then((result) => {\n        icons.value = result.map(i => i.item)\n      })\n      .catch(() => {\n        // The search is canceled\n      })\n  }\n\n  const debouncedSearch = useDebounceFn(runSearch, 200)\n\n  watch([category, variant, () => collection.value?.id], () => {\n    runSearch()\n  })\n\n  watchEffect(() => {\n    if (!search.value) {\n      icons.value = iconSource.value\n      return\n    }\n\n    if (isAll.value && !useExtendedMatch.value) {\n      icons.value = iconSource.value\n        .filter(i => aliasedSearchCandidates.value.some(s => i.includes(s)))\n      return\n    }\n\n    debouncedSearch()\n  })\n\n  return {\n    collection,\n    search,\n    category,\n    variant,\n    icons,\n  }\n}\n\n// @unocss-include\nexport function getSearchHighlightHTML(\n  text: string,\n  search: string,\n  baseClass = 'color-fade',\n  activeClass = 'text-primary',\n) {\n  const start = text.indexOf(search || '')\n\n  if (!search || start < 0)\n    return `<span class=\"${baseClass}\">${text}</span>`\n\n  const end = start + search.length\n  return `<span class=\"${baseClass}\">${text.slice(0, start)}<b class=\"${activeClass}\">${text.slice(start, end)}</b>${text.slice(end)}</span>`\n}\n\nexport function arrayIntersection<T>(a: T[], b: T[]) {\n  return a.filter(i => b.includes(i))\n}\n"
  },
  {
    "path": "src/html.d.ts",
    "content": "// for UnoCSS attributify mode compact in Volar\n// refer: https://github.com/johnsoncodehk/volar/issues/1077#issuecomment-1145361472\ndeclare module '@vue/runtime-dom' {\n  interface HTMLAttributes {\n    [key: string]: any\n  }\n}\ndeclare module '@vue/runtime-core' {\n  interface AllowedComponentProps {\n    [key: string]: any\n  }\n}\nexport {}\n"
  },
  {
    "path": "src/main.css",
    "content": "body {\n  padding: 0;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n}\n\nhtml.dark {\n  background: #181818;\n  color-scheme: dark;\n}\nhtml.dark .shiki,\nhtml.dark .shiki span {\n  color: var(--shiki-dark);\n}\nhtml:not(.dark) .shiki,\nhtml:not(.dark) .shiki span {\n  color: var(--shiki-light);\n}\n\n.btn {\n  --uno: border border-base rounded shadow-sm outline-none px-4 py-1 text-gray-600 text-sm transition-all bg-base\n    hover-(bg-gray-50 shadow) dark-(border-dark-200 text-gray-300) dark-hover-(border-primary bg-dark-100 text-primary)\n    focus-(shadow outline-none);\n}\n\n.btn.small {\n  --uno: px-2 py-1 text-sm;\n}\n\n.dragging {\n  -webkit-app-region: drag;\n}\n\n.non-dragging {\n  -webkit-app-region: no-drag;\n}\n\n.overflow-overlay {\n  overflow: auto;\n  overflow: overlay;\n}\n\n.overflow-y-overlay {\n  overflow-y: auto;\n  overflow-y: overlay;\n}\n\n.overflow-x-overlay {\n  overflow-x: auto;\n  overflow-x: overlay;\n}\n\n/* Scrollbar */\n* {\n  -webkit-overflow-scrolling: touch;\n  -ms-overflow-style: -ms-autohiding-scrollbar;\n  scrollbar-width: 6px;\n  scrollbar-color: transparent;\n}\n\n::-webkit-scrollbar {\n  height: 6px;\n  width: 6px;\n}\n\n::-webkit-scrollbar-track {\n  background: transparent;\n}\n\n::-webkit-scrollbar-thumb {\n  background: rgba(128, 128, 128, 0.2);\n  border-radius: 3px;\n}\n\n::-webkit-scrollbar-thumb:active {\n  background: rgba(128, 128, 128, 0.5);\n  border-radius: 3px;\n}\n\n/* Tootip */\n.v-popper--theme-tooltip .v-popper__inner,\n.v-popper--theme-menu .v-popper__inner {\n  --uno: important-bg-base;\n}\n\n.v-popper--theme-tooltip .v-popper__arrow-outer,\n.v-popper--theme-menu .v-popper__arrow-outer {\n  --uno: important-border-none;\n}\n\n.v-popper--theme-tooltip .v-popper__inner,\n.v-popper--theme-menu .v-popper__inner {\n  --uno: border border-base shadow-lg;\n}\n\n.v-popper--theme-menu .v-popper__arrow-outer {\n  visibility: hidden;\n}\n\n.v-popper--theme-menu .v-popper__arrow-inner {\n  visibility: hidden;\n}\n\n/* fallback black svg in dark mode */\n.icons-item svg,\n.dark .icons-item [fill='#000'],\n.dark .icons-item [fill='#000000'],\n.dark .icons-item [fill='black'] {\n  fill: currentColor;\n}\n\n.dark .icons-item [stroke='#000'],\n.dark .icons-item [stroke='#000000'],\n.dark .icons-item [stroke='black'] {\n  stroke: currentColor;\n}\n\n/* Color Mode transition */\n::view-transition-old(root),\n::view-transition-new(root) {\n  animation: none;\n  mix-blend-mode: normal;\n}\n::view-transition-old(root) {\n  z-index: 1;\n}\n::view-transition-new(root) {\n  z-index: 2147483646;\n}\n.dark::view-transition-old(root) {\n  z-index: 2147483646;\n}\n.dark::view-transition-new(root) {\n  z-index: 1;\n}\n"
  },
  {
    "path": "src/main.ts",
    "content": "import { createApp } from 'vue'\nimport { createRouter, createWebHashHistory, createWebHistory } from 'vue-router'\nimport routes from '~pages'\nimport App from './App.vue'\nimport { basePath, isElectron } from './env'\n\nimport '@unocss/reset/tailwind.css'\nimport 'floating-vue/dist/style.css'\nimport './utils/electron'\nimport './main.css'\nimport 'uno.css'\n\nconst app = createApp(App)\n\nconst router = createRouter({\n  history: isElectron ? createWebHashHistory(basePath) : createWebHistory(basePath),\n  routes,\n})\n\nif (!isElectron && PWA) {\n  router.isReady().then(async () => {\n    const { registerSW } = await import('virtual:pwa-register')\n    registerSW({ immediate: true })\n  })\n}\n\napp.use(router)\napp.mount('#app')\n"
  },
  {
    "path": "src/pages/[...all].vue",
    "content": "<template>\n  <div>\n    Not found\n  </div>\n</template>\n"
  },
  {
    "path": "src/pages/collection/[id].vue",
    "content": "<script setup lang='ts'>\nimport { pushRecentCollection, setCurrentCollection, useCurrentCollection } from '../../store'\n\nconst props = defineProps<{\n  id: string\n}>()\n\nwatch(\n  () => props.id,\n  () => setCurrentCollection(props.id),\n  { immediate: true },\n)\n\nonUnmounted(() => setCurrentCollection(''))\n\nconst collection = useCurrentCollection()\n\nonMounted(() => {\n  pushRecentCollection(props.id)\n})\n</script>\n\n<template>\n  <WithNavbar v-if=\"!collection\">\n    <div class=\"py-8 px-4 text-gray-700 text-center dark:text-dark-700\">\n      Loading...\n    </div>\n  </WithNavbar>\n  <IconSet v-else />\n</template>\n"
  },
  {
    "path": "src/pages/index.vue",
    "content": "<script setup lang='ts'>\nimport type { PresentType } from '../data'\nimport { categories, categorySearch, favoritedCollections, filteredCollections, recentCollections } from '../data'\n\nconst searchbar = ref<{ input: HTMLElement }>()\n\nconst categorized = ref(getIconList(categorySearch.value))\nconst availableCategories = computed(() => categorized.value.filter(c => c.collections.length > 0))\n\nlet categorizeDebounceTimer: NodeJS.Timeout | null = null\n\nwatch([categorySearch, favoritedCollections, recentCollections], ([newVal]) => {\n  if (categorizeDebounceTimer)\n    clearTimeout(categorizeDebounceTimer)\n  categorizeDebounceTimer = setTimeout(() => {\n    categorizeDebounceTimer = null\n    categorized.value = getIconList(newVal)\n  }, 500)\n})\n\nfunction getIconList(searchString: string) {\n  if (searchString) {\n    return [\n      {\n        name: 'Result',\n        type: 'result' as PresentType,\n        collections: filteredCollections.value,\n      },\n    ]\n  }\n  else {\n    return [\n      {\n        name: 'Favorites',\n        type: 'favorite' as PresentType,\n        collections: favoritedCollections.value,\n      },\n      {\n        name: 'Recent',\n        type: 'recent' as PresentType,\n        collections: recentCollections.value,\n      },\n      ...categories.map(category => ({\n        name: category,\n        type: 'normal' as PresentType,\n        collections: filteredCollections.value.filter(collection => collection.category === category),\n      })),\n    ]\n  }\n}\n\nconst router = useRouter()\nonKeyStroke('/', (e) => {\n  e.preventDefault()\n  router.replace('/collection/all')\n})\nonMounted(() => searchbar.value?.input.focus())\n\nconst platform = (navigator as any).userAgentData?.platform || navigator.platform || ''\nconst isMacOS = platform.toUpperCase().includes('MAC')\n\nfunction onKeydown(e: KeyboardEvent) {\n  if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {\n    router.replace(`/collection/all?s=${categorySearch.value}`)\n    categorySearch.value = ''\n  }\n}\n</script>\n\n<template>\n  <WithNavbar>\n    <div of-hidden grid=\"~ rows-[max-content_1fr]\">\n      <!-- Searching -->\n      <div md:mx-6 md:mt-6>\n        <SearchBar\n          ref=\"searchbar\"\n          v-model:search=\"categorySearch\"\n          placeholder=\"Search category...\"\n          flex\n          @on-keydown=\"onKeydown\"\n        />\n        <RouterLink\n          :class=\"categorySearch ? '' : 'op0 pointer-events-none'\"\n          px4 py2 w-full mt--1px text-sm z--1 h-10\n          flex=\"~ gap-2\" items-center\n          border=\"~ base rounded-b\"\n          hover=\"text-primary !border-primary shadow\"\n          :to=\"`/collection/all?s=${categorySearch}`\"\n        >\n          <div i-carbon-direction-right-01 scale-y--100 op50 />\n          Search for all icons...\n          <div>\n            <kbd text-sm border=\"~ base rounded\" px1>{{ isMacOS ? '⌘' : 'Ctrl' }}</kbd> + <kbd text-sm border=\"~ base rounded\" px1>Enter</kbd>\n          </div>\n        </RouterLink>\n      </div>\n\n      <div of-y-auto relative space-y-6>\n        <!-- Category listing -->\n        <template v-for=\"c of availableCategories\" :key=\"c.name\">\n          <div px4>\n            <div px-2 text-op-50 text-lg sticky top-0 bg-base z-1>\n              {{ c.name }}\n            </div>\n            <CollectionEntries\n              of-hidden\n              :collections=\"c.collections\"\n              :type=\"c.type\"\n            />\n          </div>\n        </template>\n\n        <div\n          v-if=\"availableCategories.length === 0\"\n          class=\"flex flex-col flex-grow w-full py-6 justify-center items-center\"\n        >\n          <Icon icon=\"ph:x-circle-bold\" class=\"text-4xl mb-2 opacity-20\" />\n          <span class=\"text-lg opacity-60\">There is no result corresponding to your search query.</span>\n        </div>\n\n        <Footer />\n      </div>\n    </div>\n  </WithNavbar>\n</template>\n"
  },
  {
    "path": "src/pages/settings.vue",
    "content": "<script setup lang=\"ts\">\nimport type { PresentType } from '../data'\nimport { categories, collections } from '../data'\nimport { isExcludedCategory, toggleExcludedCategory } from '../store'\n\nconst categorizedCollections = computed(() => categories.map(category => ({\n  name: category,\n  type: 'normal' as PresentType,\n  collections: collections.filter(collection => collection.category === category),\n})))\n</script>\n\n<template>\n  <WithNavbar>\n    <div py-4 of-hidden grid=\"~ rows-[max-content_1fr]\">\n      <!-- <h1 text-xl>\n        Features\n      </h1>\n\n      <input id=\"toggle-dark-mode\" v-model=\"darkMode\" type=\"checkbox\"> -->\n\n      <div px4>\n        <h1 text-xl>\n          Collections\n        </h1>\n        <p op50 mb5>\n          Manage collections to be listed in the home page and search results.\n        </p>\n      </div>\n\n      <div of-y-auto w-full px4 pb4 class=\"masonry\">\n        <div v-for=\"c of categorizedCollections\" :key=\"c.name\" mb-10>\n          <div flex py1 px2 break-inside-avoid>\n            <h1 font-bold op75 flex-auto>\n              {{ c.name }}\n            </h1>\n            <button\n              icon-button\n              :class=\"isExcludedCategory(c.name) ? 'i-carbon:view-off text-red' : 'i-carbon:view'\"\n              title=\"Toggle Visible\"\n              @click=\"toggleExcludedCategory(c.name)\"\n            />\n          </div>\n\n          <SettingsCollectionsList :collections=\"c.collections\" />\n        </div>\n      </div>\n    </div>\n  </WithNavbar>\n</template>\n\n<style>\n.masonry {\n  list-style: none;\n  column-gap: 1em;\n  column-count: 1;\n}\n@screen sm {\n  .masonry {\n    column-count: 2;\n  }\n}\n@screen md {\n  .masonry {\n    column-count: 3;\n  }\n}\n@screen lg {\n  .masonry {\n    column-count: 4;\n  }\n}\n@screen xl {\n  .masonry {\n    column-count: 5;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/shims.d.ts",
    "content": "interface Window {\n  // for vscode\n  baseURI?: string\n  staticURI?: string\n}\n\ndeclare const vscode: any\ndeclare const __BUILD_TIME__: string\ndeclare const PWA: boolean\n\ndeclare module '*.vue' {\n  import type { defineComponent } from './vue'\n\n  const Component: ReturnType<typeof defineComponent>\n  export default Component\n}\n"
  },
  {
    "path": "src/store/collection.ts",
    "content": "import type { CollectionMeta } from '../data'\nimport {\n  collections,\n  downloadAndInstall,\n  getCollectionMeta,\n  getFullMeta,\n  isInstalled,\n  isMetaLoaded,\n  tryInstallFromLocal,\n} from '../data'\nimport { isLocalMode } from '../env'\nimport { useSearch } from '../hooks'\nimport { isExcludedCollection, recentIconIds } from './localstorage'\n\nconst currentCollectionId = ref('')\nconst loaded = ref(false)\nconst installed = ref(false)\nconst collection = shallowRef<CollectionMeta | null>(null)\n\nexport const getSearchResults = createSharedComposable(() => {\n  return useSearch(collection)\n})\n\nexport function useCurrentCollection() {\n  return collection\n}\n\nexport function isCurrentCollectionLoading() {\n  return computed(() => !loaded.value)\n}\n\nconst recentIconsCollection = computed((): CollectionMeta => ({\n  id: 'recent',\n  name: 'Recent',\n  icons: recentIconIds.value,\n  categories: Object.fromEntries(\n    Array.from(new Set(\n      recentIconIds.value.map(i => i.split(':')[0]),\n    ))\n      .map(id => [collections.find(i => i.id === id)?.name || id, recentIconIds.value.filter(i => i.startsWith(`${id}:`))]),\n  ),\n}))\n\nexport async function setCurrentCollection(id: string) {\n  currentCollectionId.value = id\n\n  if (!id) {\n    loaded.value = false\n    installed.value = false\n    collection.value = null\n    return collection.value\n  }\n\n  loaded.value = isMetaLoaded(id)\n  installed.value = isInstalled(id)\n\n  if (!installed.value) {\n    if (isLocalMode)\n      installed.value = await downloadAndInstall(id)\n    else\n      installed.value = await tryInstallFromLocal(id)\n  }\n\n  if (id === 'all') {\n    const meta = await getFullMeta()\n    collection.value = {\n      id: 'all',\n      name: 'All',\n      icons: meta.flatMap((c) => {\n        if (isExcludedCollection(c))\n          return []\n        return c.icons.map(i => `${c.id}:${i}`)\n      }),\n    }\n    loaded.value = true\n  }\n  else if (id === 'recent') {\n    collection.value = recentIconsCollection.value\n    loaded.value = true\n  }\n  else {\n    collection.value = await getCollectionMeta(id)\n    loaded.value = true\n  }\n\n  return collection.value\n}\n"
  },
  {
    "path": "src/store/dark.ts",
    "content": "export const isDark = useDark({\n  storageKey: 'icones-schema',\n})\n"
  },
  {
    "path": "src/store/dialog.ts",
    "content": "export const showHelp = ref(false)\nexport const showCaseSelect = ref(false)\n"
  },
  {
    "path": "src/store/index.ts",
    "content": "export * from './collection'\nexport * from './dark'\nexport * from './dialog'\nexport * from './localstorage'\nexport * from './packing'\nexport * from './progress'\n"
  },
  {
    "path": "src/store/indexedDB.ts",
    "content": "import type { Table } from 'dexie'\nimport Dexie from 'dexie'\n\nconst db = new Dexie('icones')\n\ndb.version(1).stores({\n  collections: 'id, data',\n})\n\nconst collections: Table = (db as any).collections\n\nexport async function loadCollection(id: string) {\n  return await collections.where({ id }).first()\n}\n\nexport async function saveCollection(id: string, data: any) {\n  return await collections.put({ id, data }, 'id')\n}\n\nexport default db\n"
  },
  {
    "path": "src/store/localstorage.ts",
    "content": "import type { CollectionInfo } from '../data'\nimport type { IdCase } from '../utils/case'\nimport { idCases } from '../utils/case'\n\nconst RECENT_COLLECTION_CAPACITY = 10\nconst RECENT_ICONS_CAPACITY = 100\n\nexport type ActiveMode = 'normal' | 'select' | 'copy'\n\nexport const themeColor = useStorage('icones-theme-color', '#329672')\nexport const iconSize = useStorage('icones-icon-size', '2xl')\nexport const previewColor = useStorage('icones-preview-color', '#888888')\nexport const copyPreviewColor = useStorage('icones-copy-preview-color', false)\nexport const listType = useStorage('icones-list-type', 'grid')\nexport const favoritedCollectionIds = useStorage<string[]>('icones-fav-collections', [])\nexport const recentCollectionIds = useStorage<string[]>('icones-recent-collections', [])\nexport const recentIconIds = useStorage<string[]>('icones-recent-icons', [])\nexport const bags = useStorage<string[]>('icones-bags', [])\nexport const activeMode = useStorage<ActiveMode>('active-mode', 'normal')\nexport const preferredCase = useStorage<IdCase>('icones-preferfed-case', 'iconify')\nexport const drawerCollapsed = useStorage<boolean>('icones-drawer-collapsed', false)\nexport const selectedPackageManager = useStorage<string>('icones-package-manager', 'pnpm')\n\nexport const excludedCollectionIds = useStorage<string[]>('icones-excluded-collections', [])\nexport const excludedCategories = useStorage<string[]>('icones-excluded-categories', [\n  'Archive / Unmaintained',\n  'Deprecated / Unavailable',\n])\n\nexport function getTransformedId(icon: string) {\n  return idCases[preferredCase.value]?.(icon) || icon\n}\n\nexport function isFavoritedCollection(id: string) {\n  return favoritedCollectionIds.value.includes(id)\n}\n\nexport function isExcludedCollection(collection: CollectionInfo) {\n  return excludedCollectionIds.value.includes(collection.id) || excludedCategories.value.includes(collection.category || '')\n}\n\nexport function isExcludedCategory(category: string | undefined) {\n  return category && excludedCategories.value.includes(category)\n}\n\nexport function isRecentCollection(id: string) {\n  return recentCollectionIds.value.includes(id)\n}\n\nexport function pushRecentCollection(id: string) {\n  recentCollectionIds.value = [id, ...recentCollectionIds.value.filter(i => i !== id)].slice(0, RECENT_COLLECTION_CAPACITY)\n}\n\nexport function removeRecentCollection(id: string) {\n  recentCollectionIds.value = recentCollectionIds.value.filter(i => i !== id)\n}\n\nexport function isRecentIcon(id: string) {\n  return recentIconIds.value.includes(id)\n}\n\nexport function pushRecentIcon(id: string) {\n  recentIconIds.value = [id, ...recentIconIds.value.filter(i => i !== id)].slice(0, RECENT_ICONS_CAPACITY)\n}\n\nexport function removeRecentIcon(id: string) {\n  recentIconIds.value = recentIconIds.value.filter(i => i !== id)\n}\n\nexport function toggleFavoriteCollection(id: string) {\n  const index = favoritedCollectionIds.value.indexOf(id)\n  if (index >= 0)\n    favoritedCollectionIds.value.splice(index, 1)\n  else\n    favoritedCollectionIds.value.push(id)\n}\n\nexport function toggleExcludedCollection(id: string) {\n  const index = excludedCollectionIds.value.indexOf(id)\n  if (index >= 0)\n    excludedCollectionIds.value.splice(index, 1)\n  else\n    excludedCollectionIds.value.push(id)\n}\n\nexport function toggleExcludedCategory(category: string) {\n  const index = excludedCategories.value.indexOf(category)\n  if (index >= 0)\n    excludedCategories.value.splice(index, 1)\n  else\n    excludedCategories.value.push(category)\n}\n\nexport function addToBag(id: string) {\n  if (!bags.value.includes(id))\n    bags.value.push(id)\n}\n\nexport function removeFromBag(id: string) {\n  const index = bags.value.indexOf(id)\n  if (index >= 0)\n    bags.value.splice(index, 1)\n}\n\nexport function inBag(id: string) {\n  return bags.value.includes(id)\n}\n\nexport function toggleBag(id: string) {\n  const index = bags.value.indexOf(id)\n  if (index >= 0)\n    bags.value.splice(index, 1)\n  else\n    bags.value.push(id)\n}\n\nexport function clearBag() {\n  bags.value = []\n}\n"
  },
  {
    "path": "src/store/packing.ts",
    "content": "export const isPacking = ref(false)\nexport const packingProgress = ref(0)\n"
  },
  {
    "path": "src/store/progress.ts",
    "content": "export const inProgress = ref(false)\nexport const progress = ref(0)\nexport const progressMessage = ref('')\n"
  },
  {
    "path": "src/sw.ts",
    "content": "import { getIcons } from '@iconify/utils'\nimport { cacheNames, clientsClaim } from 'workbox-core'\nimport { cleanupOutdatedCaches, createHandlerBoundToURL, precacheAndRoute } from 'workbox-precaching'\nimport { NavigationRoute, registerRoute } from 'workbox-routing'\n\ndeclare let self: ServiceWorkerGlobalScope\n\n// self.__WB_MANIFEST is default injection point\nconst swManifest = self.__WB_MANIFEST\nprecacheAndRoute(swManifest)\n\n// clean old assets\ncleanupOutdatedCaches()\n\n// to allow work offline\nregisterRoute(new NavigationRoute(\n  createHandlerBoundToURL('index.html'),\n))\n\nself.skipWaiting()\nclientsClaim()\n\nfunction buildCollectionResponseHeaders(cachedResponse: Response) {\n  const age = cachedResponse.headers.get('age')\n  const date = cachedResponse.headers.get('date')\n  const etag = cachedResponse.headers.get('etag')\n  const contentType = cachedResponse.headers.get('content-type')\n  const cacheControl = cachedResponse.headers.get('cache-control')\n\n  const headers: Record<string, string> = {\n    'access-control-allow-headers': 'Origin, X-Requested-With, Content-Type, Accept, Accept-Encoding',\n    'access-control-allow-methods': 'GET, OPTIONS',\n    'access-control-allow-origin': '*',\n    'access-control-max-age': '86400',\n    'cache-control': 'public, max-age=604800, min-refresh=604800, immutable',\n    'content-type': 'application/json; charset=utf-8',\n    'cross-origin-resource-policy': 'cross-origin',\n  }\n\n  if (age)\n    headers.age = age\n\n  if (date)\n    headers.date = date\n\n  if (etag)\n    headers.etag = etag\n\n  if (contentType)\n    headers['content-type'] = contentType\n\n  if (cacheControl)\n    headers['cache-control'] = cacheControl\n\n  return headers\n}\n\nconst swManifestMap = new Map<string, string>(\n  swManifest.map((entry) => {\n    if (typeof entry === 'string') {\n      const e = entry[0] === '/' ? entry : `/${entry}`\n      return [e, e]\n    }\n    else {\n      const e = entry.url[0] === '/' ? entry.url : `/${entry.url}`\n      return [e, entry.revision ? `${e}?__WB_REVISION__=${entry.revision}` : e]\n    }\n  }),\n)\n\nasync function getCollection(request: Request, name: string, icons: string[]) {\n  try {\n    const cache = await caches.open(cacheNames.precache)\n    const collectionUrl = `/collections/${name}.json`\n    const url = swManifestMap.get(collectionUrl) ?? collectionUrl\n    let cachedResponse = await cache.match(url)\n    if (!cachedResponse) {\n      cachedResponse = await fetch(url)\n      await cache.put(url, cachedResponse.clone())\n    }\n\n    const collection = await cachedResponse.json()\n\n    return new Response(JSON.stringify(getIcons(collection, icons)), {\n      status: cachedResponse.status,\n      statusText: cachedResponse.statusText,\n      headers: buildCollectionResponseHeaders(cachedResponse),\n    })\n  }\n  catch {\n    return await fetch(request)\n  }\n}\n\nconst fetchRegex = /^https:\\/\\/(api\\.iconify\\.design|api\\.simplesvg\\.com|api\\.unisvg\\.com)\\/(.*)\\.json\\?icons=(.*)$/\n\nself.addEventListener('fetch', (e) => {\n  const url = e.request.url\n  const match = url.match(fetchRegex)\n  if (match) {\n    e.respondWith(getCollection(\n      e.request,\n      match[2],\n      match[3].replaceAll('%2C', ',').split(','),\n    ))\n  }\n})\n"
  },
  {
    "path": "src/utils/case.ts",
    "content": "export const idCases = {\n  bare(id: string) {\n    return id.replace(/^.*:/, '')\n  },\n  barePascal(id: string) {\n    return id.replace(/^.*:/, '').replace(/(?:^|[-_:]+)(\\w)/g, (_, c) => c.toUpperCase())\n  },\n  iconify(id: string) {\n    return id\n  },\n  dash(id: string) {\n    return id.replace(/:/g, '-')\n  },\n  slash(id: string) {\n    return id.replace(/:/g, '/')\n  },\n  doubleHyphen(id: string) {\n    return id.replace(/:/g, '--')\n  },\n  camel(id: string) {\n    return id.replace(/[-_:]+(\\w)/g, (_, c) => c.toUpperCase())\n  },\n  pascal(id: string) {\n    return id.replace(/(?:^|[-_:]+)(\\w)/g, (_, c) => c.toUpperCase())\n  },\n  component(id: string) {\n    return `<${id.replace(/(?:^|[-_:]+)(\\w)/g, (_, c) => c.toUpperCase())}/>`\n  },\n  componentKebab(id: string) {\n    return `<${id.replace(/:/g, '-')}/>`\n  },\n  unocssColon(id: string) {\n    return `i-${id}`\n  },\n  unocss(id: string) {\n    return `i-${id.replace(/:/g, '-')}`\n  },\n  iconifyTailwind(id: string) {\n    return `icon-[${id.replace(/:/g, '--')}]`\n  },\n}\n\nexport type IdCase = keyof typeof idCases\n"
  },
  {
    "path": "src/utils/dataUrlToBlob.ts",
    "content": "export function dataUrlToBlob(dataurl: string) {\n  const parts = dataurl.split(',')\n  const type = parts[0].split(':')[1].split(';')[0]\n  const base64 = atob(parts[1])\n  const arr = new Uint8Array(base64.length)\n  for (let i = 0; i < base64.length; i++)\n    arr[i] = base64.charCodeAt(i)\n  return new Blob([arr], { type })\n}\n"
  },
  {
    "path": "src/utils/electron.ts",
    "content": "import hotkeys from 'hotkeys-js'\nimport { isSearchOpen } from '../data'\nimport { isElectron } from '../env'\n\nif (isElectron) {\n  hotkeys('ctrl+f, command+f', (e) => {\n    e.preventDefault()\n    isSearchOpen.value = !isSearchOpen.value\n  })\n}\n"
  },
  {
    "path": "src/utils/icons.ts",
    "content": "import type { BuiltInParserName as PrettierParser } from 'prettier'\nimport type { CollectionInfo } from '../data'\nimport { isVSCode } from '../env'\nimport { getTransformedId } from '../store'\nimport { getSvgSymbol } from './pack'\nimport {\n  API_ENTRY,\n  bufferToString,\n  ClearSvg,\n  getSvg,\n  SvgToAstro,\n  SvgToDataURL,\n  SvgToJSX,\n  SvgToQwik,\n  SvgToReactNative,\n  SvgToSolid,\n  SvgToSvelte,\n  SvgToTSX,\n  SvgToVue,\n  toComponentName,\n} from './svg'\nimport { svgToPngDataUrl } from './svgToPng'\n\nexport interface Snippet {\n  name: string\n  tag?: string\n  lang: string // for shiki\n  prettierParser: PrettierParser // for prettier\n}\nexport { toComponentName }\n\nexport async function Download(blob: Blob, name: string) {\n  if (isVSCode) {\n    blob.arrayBuffer().then(\n      buffer => vscode.postMessage({\n        command: 'download',\n        name,\n        text: bufferToString(buffer),\n      }),\n    )\n  }\n  else {\n    const url = URL.createObjectURL(blob)\n    const a = document.createElement('a')\n    a.href = url\n    a.download = name\n    a.click()\n    a.remove()\n  }\n}\n\nexport const SnippetMap: Record<string, Record<string, Snippet>> = {\n  Snippets: {\n    'svg': { name: 'SVG', lang: 'html', prettierParser: 'html' },\n    'svg-symbol': { name: 'SVG Symbol', lang: 'html', prettierParser: 'html' },\n    'png': { name: 'PNG', lang: 'html', prettierParser: 'html' },\n    'html': { name: 'Iconify', lang: 'html', prettierParser: 'html' },\n    'pure-jsx': { name: 'JSX', lang: 'jsx', prettierParser: 'typescript' },\n  },\n  Components: {\n    'vue': { name: 'Vue', lang: 'vue', prettierParser: 'vue' },\n    'vue-ts': { name: 'Vue', tag: 'TS', lang: 'vue', prettierParser: 'vue' },\n    'jsx': { name: 'React', lang: 'jsx', prettierParser: 'typescript' },\n    'tsx': { name: 'React', tag: 'TS', lang: 'tsx', prettierParser: 'typescript' },\n    'svelte': { name: 'Svelte', lang: 'svelte', prettierParser: 'typescript' },\n    'qwik': { name: 'Qwik', lang: 'tsx', prettierParser: 'typescript' },\n    'solid': { name: 'Solid', lang: 'tsx', prettierParser: 'typescript' },\n    'astro': { name: 'Astro', lang: 'astro', prettierParser: 'typescript' },\n    'react-native': { name: 'React Native', lang: 'tsx', prettierParser: 'typescript' },\n    'unplugin': { name: 'Unplugin Icons', lang: 'tsx', prettierParser: 'typescript' },\n    'unocss': { name: 'UnoCSS', lang: 'html', prettierParser: 'html' },\n    'unocss-attributify': { name: 'UnoCSS', tag: 'attributify', lang: 'html', prettierParser: 'html' },\n  },\n  Links: {\n    url: { name: 'URL', lang: 'html', prettierParser: 'html' },\n    data_url: { name: 'Data URL', lang: 'html', prettierParser: 'html' },\n  },\n}\n\nexport async function getIconSnippet(\n  collections: CollectionInfo[],\n  icon: string,\n  type: string,\n  snippet = true,\n  color = 'currentColor',\n): Promise<string | undefined> {\n  if (!icon)\n    return\n\n  let url = `${API_ENTRY}/${icon}.svg`\n  if (color !== 'currentColor')\n    url = `${url}?color=${encodeURIComponent(color)}`\n\n  switch (type) {\n    case 'id':\n      return getTransformedId(icon)\n    case 'url':\n      return url\n    case 'html':\n      return `<span class=\"iconify\" data-icon=\"${icon}\" data-inline=\"false\"${color === 'currentColor' ? '' : ` style=\"color: ${color}\"`}></span>`\n    case 'css':\n      return `background: url('${url}') no-repeat center center / contain;`\n    case 'svg':\n      return await getSvg(collections, icon, '32', color)\n    case 'png':\n      return await svgToPngDataUrl(await getSvg(collections, icon, '32', color))\n    case 'svg-symbol':\n      return await getSvgSymbol(collections, icon, '32', color)\n    case 'data_url':\n      return SvgToDataURL(await getSvg(collections, icon, undefined, color))\n    case 'pure-jsx':\n      return ClearSvg(await getSvg(collections, icon, undefined, color))\n    case 'jsx':\n      return SvgToJSX(await getSvg(collections, icon, undefined, color), toComponentName(icon), snippet)\n    case 'tsx':\n      return SvgToTSX(await getSvg(collections, icon, undefined, color), toComponentName(icon), snippet)\n    case 'qwik':\n      return SvgToQwik(await getSvg(collections, icon, undefined, color), toComponentName(icon), snippet)\n    case 'vue':\n      return SvgToVue(await getSvg(collections, icon, undefined, color), toComponentName(icon))\n    case 'vue-ts':\n      return SvgToVue(await getSvg(collections, icon, undefined, color), toComponentName(icon), true)\n    case 'solid':\n      return SvgToSolid(await getSvg(collections, icon, undefined, color), toComponentName(icon), snippet)\n    case 'svelte':\n      return SvgToSvelte(await getSvg(collections, icon, undefined, color))\n    case 'astro':\n      return SvgToAstro(await getSvg(collections, icon, undefined, color))\n    case 'react-native':\n      return SvgToReactNative(await getSvg(collections, icon, undefined, color), toComponentName(icon), snippet)\n    case 'unplugin':\n      return `import ${toComponentName(icon)} from '~icons/${icon.split(':')[0]}/${icon.split(':')[1]}'`\n    case 'unocss':\n      return `<div class=\"i-${icon}\" />`\n    case 'unocss-attributify':\n      return `<div i-${icon} />`\n  }\n}\n\nexport function getIconDownloadLink(icon: string) {\n  return `${API_ENTRY}/${icon}.svg?download=true&inline=false&height=auto`\n}\n"
  },
  {
    "path": "src/utils/pack-worker-client.ts",
    "content": "import PackerWorker from './worker?worker'\n\nexport const packerWorker = new PackerWorker({\n  name: 'IconesPackWorker',\n})\n"
  },
  {
    "path": "src/utils/pack.ts",
    "content": "import type { CollectionInfo } from '../data'\nimport type { PackType } from './svg'\nimport { Download } from './icons'\nimport { getSvg, LoadIconSvgs } from './svg'\n\nexport async function getSvgSymbol(\n  collections: CollectionInfo[],\n  icon: string,\n  size = '1em',\n  color = 'currentColor',\n) {\n  const svgMarkup = await getSvg(collections, icon, size, color)\n\n  const symbolElem = document.createElementNS('http://www.w3.org/2000/svg', 'symbol')\n  const node = document.createElement('div') // Create any old element\n  node.innerHTML = svgMarkup\n\n  // Grab the inner HTML and move into a symbol element\n  symbolElem.innerHTML = node.querySelector('svg')!.innerHTML\n  symbolElem.setAttribute('viewBox', node.querySelector('svg')!.getAttribute('viewBox')!)\n  symbolElem.id = icon.replace(/:/, '-') // Simple slugify for quick symbol lookup\n\n  return symbolElem?.outerHTML\n}\n\nexport async function PackSVGSprite(\n  collections: CollectionInfo[],\n  icons: string[],\n  options: any = {},\n) {\n  if (!icons.length)\n    return\n  const data = await LoadIconSvgs(collections, icons)\n\n  let symbols = ''\n  for (const { name } of data)\n    symbols += `${await getSvgSymbol(collections, name, options.size, options.color)}\\n`\n\n  const svg = `<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n<defs>\n${symbols}\n</defs>\n</svg>`\n\n  const blob = new Blob([svg], { type: 'image/svg+xml' })\n  Download(blob, 'sprite.svg')\n}\n\nexport async function PackIconFont(\n  collections: CollectionInfo[],\n  icons: string[],\n  options: any = {},\n) {\n  if (!icons.length)\n    return\n\n  const { packerWorker } = await import('./pack-worker-client')\n\n  return new Promise<void>((resolve, reject) => {\n    packerWorker.addEventListener('message', (event) => {\n      if (event.data.error) {\n        reject(event.data.error)\n        return\n      }\n      const { blob, name } = event.data as { blob: ArrayBuffer, name: string }\n      Download(\n        new Blob([blob]),\n        name,\n      )\n      resolve()\n    }, { once: true })\n    const arrayBuffer = createArrayBufferFromCollections(collections)\n    packerWorker.postMessage({\n      operation: 'pack-font-zip',\n      collections: arrayBuffer,\n      payload: {\n        icons: toRaw(icons),\n        ...toRaw(options),\n      },\n    }, [arrayBuffer])\n  })\n}\n\nexport async function PackSvgZip(\n  collections: CollectionInfo[],\n  icons: string[],\n  name: string,\n) {\n  if (!icons.length)\n    return\n\n  const { packerWorker } = await import('./pack-worker-client')\n\n  return new Promise<void>((resolve, reject) => {\n    packerWorker.addEventListener('message', (event) => {\n      if (event.data.error) {\n        reject(event.data.error)\n        return\n      }\n      const { blob } = event.data as { blob: ArrayBuffer }\n      Download(\n        new Blob([blob]),\n        `${name}.zip`,\n      )\n      resolve()\n    }, { once: true })\n    const arrayBuffer = createArrayBufferFromCollections(collections)\n    packerWorker.postMessage({\n      operation: 'pack-svg-zip',\n      collections: arrayBuffer,\n      payload: {\n        icons: toRaw(icons),\n      },\n    }, [arrayBuffer])\n  })\n}\n\nexport async function PackJsonZip(\n  collections: CollectionInfo[],\n  icons: string[],\n  name: string,\n) {\n  if (!icons.length)\n    return\n\n  const { packerWorker } = await import('./pack-worker-client')\n\n  return new Promise<void>((resolve, reject) => {\n    packerWorker.addEventListener('message', (event) => {\n      if (event.data.error) {\n        reject(event.data.error)\n        return\n      }\n      const { blob } = event.data as { blob: ArrayBuffer }\n      Download(\n        new Blob([blob]),\n        `${name}.zip`,\n      )\n      resolve()\n    }, { once: true })\n    const arrayBuffer = createArrayBufferFromCollections(collections)\n    packerWorker.postMessage({\n      operation: 'pack-json-zip',\n      collections: arrayBuffer,\n      payload: {\n        icons: toRaw(icons),\n        name,\n      },\n    }, [arrayBuffer])\n  })\n}\n\nexport async function PackZip(\n  collections: CollectionInfo[],\n  icons: string[],\n  name: string,\n  type: PackType = 'svg',\n) {\n  if (!icons.length)\n    return\n\n  const { packerWorker } = await import('./pack-worker-client')\n\n  return new Promise<void>((resolve, reject) => {\n    packerWorker.addEventListener('message', (event) => {\n      if (event.data.error) {\n        reject(event.data.error)\n        return\n      }\n      const { blob } = event.data as { blob: ArrayBuffer }\n      Download(\n        new Blob([blob]),\n        `${name}-${type}.zip`,\n      )\n      resolve()\n    }, { once: true })\n    const arrayBuffer = createArrayBufferFromCollections(collections)\n    packerWorker.postMessage({\n      operation: 'pack-zip',\n      collections: arrayBuffer,\n      payload: {\n        icons: toRaw(icons),\n        name,\n        type,\n      },\n    }, [arrayBuffer])\n  })\n}\n\nfunction createArrayBufferFromCollections(\n  collections: CollectionInfo[],\n) {\n  return new TextEncoder().encode(JSON.stringify(collections)).buffer\n}\n"
  },
  {
    "path": "src/utils/query.ts",
    "content": "export function cleanupQuery(query: Record<string, string | undefined | null>) {\n  for (const key of Object.keys(query)) {\n    if (!query[key])\n      delete query[key]\n  }\n  return query\n}\n"
  },
  {
    "path": "src/utils/sample.ts",
    "content": "export function sample<T>(arr: T[], num: number) {\n  return Array.from({ length: num }, () => arr[Math.floor(arr.length * Math.random())])\n}\n"
  },
  {
    "path": "src/utils/shiki.ts",
    "content": "import type { HighlighterCore } from 'shiki/core'\nimport { createHighlighterCore } from 'shiki/core'\nimport { createJavaScriptRegexEngine } from 'shiki/engine/javascript'\n\nexport const shiki = computedAsync<HighlighterCore>(async (onCancel) => {\n  const shiki = await createHighlighterCore({\n    engine: createJavaScriptRegexEngine(),\n    themes: [\n      () => import('shiki/themes/vitesse-dark.mjs'),\n      () => import('shiki/themes/vitesse-light.mjs'),\n    ],\n    langs: [\n      () => import('shiki/langs/html.mjs'),\n      () => import('shiki/langs/jsx.mjs'),\n      () => import('shiki/langs/tsx.mjs'),\n      () => import('shiki/langs/vue.mjs'),\n      () => import('shiki/langs/astro.mjs'),\n      () => import('shiki/langs/svelte.mjs'),\n    ],\n  })\n\n  onCancel(() => shiki?.dispose())\n  return shiki\n})\n\nexport function highlight(code: string, lang: string) {\n  if (!shiki.value)\n    return code\n  return shiki.value.codeToHtml(code, {\n    lang,\n    defaultColor: false,\n    themes: {\n      dark: 'vitesse-dark',\n      light: 'vitesse-light',\n    },\n  })\n}\n"
  },
  {
    "path": "src/utils/svg/base64.ts",
    "content": "/* eslint-disable eslint-comments/no-unlimited-disable */\n/* eslint-disable */\n// @ts-expect-error ignore\nconst 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<e.length){n=e.charCodeAt(f++);r=e.charCodeAt(f++);i=e.charCodeAt(f++);s=n>>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<e.length){s=this._keyStr.indexOf(e.charAt(f++));o=this._keyStr.indexOf(e.charAt(f++));u=this._keyStr.indexOf(e.charAt(f++));a=this._keyStr.indexOf(e.charAt(f++));n=s<<2|o>>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;n<e.length;n++){var r=e.charCodeAt(n);if(r<128){t+=String.fromCharCode(r)}else if(r>127&&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(n<e.length){r=e.charCodeAt(n);if(r<128){t+=String.fromCharCode(r);n++}else if(r>191&&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}}\n\nexport default Base64\n"
  },
  {
    "path": "src/utils/svg/bufferToString.ts",
    "content": "export function bufferToString(buffer: ArrayBuffer) {\n  return String.fromCharCode.apply(null, new Uint16Array(buffer) as any)\n}\n"
  },
  {
    "path": "src/utils/svg/helpers.ts",
    "content": "import type { Node } from 'ultrahtml'\nimport type { CollectionInfo } from '../../data'\nimport { encodeSvgForCss } from '@iconify/utils'\nimport { parse, transformSync } from 'ultrahtml'\nimport Base64 from './base64'\nimport { HtmlToJSX } from './htmlToJsx'\nimport { getSvg } from './loader'\nimport { prettierCode } from './prettier'\n\nexport type PackType = 'svg' | 'tsx' | 'jsx' | 'vue' | 'solid' | 'qwik' | 'svelte' | 'astro' | 'react-native' | 'json'\n\nexport function normalizeZipFleName(svgName: string): string {\n  return svgName.replace(':', '-')\n}\n\nexport function toComponentName(icon: string) {\n  return icon.split(/[:\\-_]/).filter(Boolean).map(s => s[0].toUpperCase() + s.slice(1).toLowerCase()).join('')\n}\n\nexport function ClearSvg(svgCode: string, reactJSX?: boolean) {\n  const result = transformSync(parse(svgCode).children[0] as Node, [\n    (node: Node): Node => {\n      if (node.name !== 'svg')\n        return node\n\n      const attributes = node.attributes || {}\n      // keep only 'viewBox', 'width', 'height', 'focusable', 'xmlns', 'xlink' attributes\n      const allowedAttributes = ['viewBox', 'width', 'height', 'focusable', 'xmlns', 'xlink']\n      for (const key of Object.keys(attributes)) {\n        if (!allowedAttributes.includes(key)) {\n          delete attributes[key]\n        }\n      }\n\n      node.attributes = attributes\n\n      return node\n    },\n  ])\n\n  return HtmlToJSX(result, reactJSX)\n}\n\nexport function SvgToDataURL(svg: string) {\n  const base64 = `data:image/svg+xml;base64,${Base64.encode(svg)}`\n  const plain = `data:image/svg+xml,${encodeSvgForCss(svg)}`\n  // Return the shorter of the two data URLs\n  return base64.length < plain.length ? base64 : plain\n}\n\nexport function SvgToJSX(svg: string, name: string, snippet: boolean) {\n  const code = `\nexport function ${name}(props) {\n  return (\n    ${ClearSvg(svg, true).replace(/<svg (.*?)>/, '<svg $1 {...props}>')}\n  )\n}`\n  if (snippet)\n    return prettierCode(code, 'babel-ts')\n  else\n    return prettierCode(`import React from 'react'\\n${code}\\nexport default ${name}`, 'babel-ts')\n}\n\nexport function SvgToTSX(svg: string, name: string, snippet: boolean, reactJSX = true) {\n  let code = `\nexport function ${name}(props: SVGProps<SVGSVGElement>) {\n  return (\n    ${ClearSvg(svg, reactJSX).replace(/<svg (.*?)>/, '<svg $1 {...props}>')}\n  )\n}`\n\n  code = snippet ? code : `import React, { SVGProps } from 'react'\\n${code}\\nexport default ${name}`\n  return prettierCode(code, 'babel-ts')\n}\n\nexport function SvgToQwik(svg: string, name: string, snippet: boolean) {\n  let code = `\nexport function ${name}(props: QwikIntrinsicElements['svg'], key: string) {\n  return (\n    ${ClearSvg(svg, false).replace(/<svg (.*?)>/, '<svg $1 {...props} key={key}>')}\n  )\n}`\n\n  code = snippet ? code : `import type { QwikIntrinsicElements } from '@builder.io/qwik'\\n${code}\\nexport default ${name}`\n  return prettierCode(code, 'babel-ts')\n}\n\nexport function SvgToVue(svg: string, name: string, isTs?: boolean) {\n  const content = `\n<template>\n  ${ClearSvg(svg)}\n</template>\n\n<script>\nexport default {\n  name: '${name}'\n}\n</script>`\n  const code = isTs ? content.replace('<script>', '<script lang=\"ts\">') : content\n  return prettierCode(code, 'vue')\n}\n\nexport function SvgToSolid(svg: string, name: string, snippet: boolean) {\n  let code = `\nexport function ${name}(props: JSX.IntrinsicElements['svg']) {\n  return (\n    ${svg.replace(/<svg (.*?)>/, '<svg $1 {...props}>')}\n  )\n}`\n\n  code = snippet ? code : `import type { JSX } from 'solid-js'\\n${code}\\nexport default ${name}`\n  return prettierCode(code, 'babel-ts')\n}\n\nexport function SvgToSvelte(svg: string) {\n  return `${svg.replace(/<svg (.*?)>/, '<svg $1 {...$$$props}>')}`\n}\n\nexport function SvgToAstro(svg: string) {\n  return `\n---\nconst props = Astro.props \n---\n\n${svg.replace(/<svg (.*?)>/, '<svg $1 {...props}>')}\n`\n}\n\nexport function SvgToReactNative(svg: string, name: string, snippet: boolean) {\n  function replaceTags(svg: string, replacements: {\n    from: string\n    to: string\n  }[]): string {\n    let result = svg\n    replacements.forEach(({ from, to }) => {\n      result = result.replace(new RegExp(`<${from}(.*?)>`, 'g'), `<${to}$1>`)\n        .replace(new RegExp(`</${from}>`, 'g'), `</${to}>`)\n    })\n    return result\n  }\n\n  function generateImports(usedComponents: string[]): string {\n    // Separate Svg from the other components\n    const svgIndex = usedComponents.indexOf('Svg')\n    if (svgIndex !== -1)\n      usedComponents.splice(svgIndex, 1)\n\n    // Join all other component names with a comma and wrap them in curly braces\n    const componentsString = usedComponents.length > 0 ? `{ ${usedComponents.join(', ')} }` : ''\n\n    // Return the consolidated import statement, ensuring Svg is imported as a default import\n    return `import Svg, ${componentsString} from 'react-native-svg';`\n  }\n\n  const replacements: {\n    from: string\n    to: string\n  }[] = [\n    { from: 'svg', to: 'Svg' },\n    { from: 'path', to: 'Path' },\n    { from: 'g', to: 'G' },\n    { from: 'circle', to: 'Circle' },\n    { from: 'rect', to: 'Rect' },\n    { from: 'line', to: 'Line' },\n    { from: 'polyline', to: 'Polyline' },\n    { from: 'polygon', to: 'Polygon' },\n    { from: 'ellipse', to: 'Ellipse' },\n    { from: 'text', to: 'Text' },\n    { from: 'tspan', to: 'Tspan' },\n    { from: 'textPath', to: 'TextPath' },\n    { from: 'defs', to: 'Defs' },\n    { from: 'use', to: 'Use' },\n    { from: 'symbol', to: 'Symbol' },\n    { from: 'linearGradient', to: 'LinearGradient' },\n    { from: 'radialGradient', to: 'RadialGradient' },\n    { from: 'stop', to: 'Stop' },\n  ]\n\n  const reactNativeSvgCode = replaceTags(ClearSvg(svg, true), replacements)\n    .replace(/className=/g, '')\n    .replace(/href=/g, 'xlinkHref=')\n    .replace(/clip-path=/g, 'clipPath=')\n    .replace(/fill-opacity=/g, 'fillOpacity=')\n    .replace(/stroke-width=/g, 'strokeWidth=')\n    .replace(/stroke-linecap=/g, 'strokeLinecap=')\n    .replace(/stroke-linejoin=/g, 'strokeLinejoin=')\n    .replace(/stroke-miterlimit=/g, 'strokeMiterlimit=')\n\n  const svgComponents = replacements.map(({ to }) => to)\n  const imports = generateImports(svgComponents.filter(component => reactNativeSvgCode.includes(component)))\n\n  let code = `\n${imports}\n\nexport function ${name}(props) {\n return (\n    ${reactNativeSvgCode}\n )\n}`\n\n  if (!snippet)\n    code = `import React from 'react';\\n${code}\\nexport default ${name}`\n\n  return prettierCode(code, 'babel-ts')\n}\n\nexport async function LoadIconSvgs(\n  collections: CollectionInfo[],\n  icons: string[],\n) {\n  return await Promise.all(\n    icons\n      .filter(Boolean)\n      .sort()\n      .map(async (name) => {\n        return {\n          name,\n          svg: await getSvg(collections, name),\n        }\n      }),\n  )\n}\n"
  },
  {
    "path": "src/utils/svg/htmlToJsx.ts",
    "content": "function transformToReactJSX(jsx: string) {\n  const reactJSX = jsx\n    .replace(/(class|(stroke-\\w+)|(\\w+:\\w+))=/g, (i) => {\n      if (i === 'class=')\n        return 'className='\n      return i.split(/[:\\-]/)\n        .map((i, idx) => idx === 0\n          ? i.toLowerCase()\n          : i[0].toUpperCase() + i.slice(1).toLowerCase())\n        .join('')\n    })\n    // transform HTML-style comment to JSX-style comment\n    .replaceAll('<!--', '{/*')\n    .replaceAll('-->', '*/}')\n  return reactJSX\n}\n\nexport function HtmlToJSX(html: string, reactJSX = false) {\n  const jsx = html.replace(/([\\w-]+)=/g, (i) => {\n    const words = i.split('-')\n    if (words.length === 1 || words[0] === 'stroke')\n      return i\n    return words\n      .map((i, idx) => idx === 0\n        ? i.toLowerCase()\n        : i[0].toUpperCase() + i.slice(1).toLowerCase())\n      .join('')\n  })\n  return reactJSX ? transformToReactJSX(jsx) : jsx\n}\n"
  },
  {
    "path": "src/utils/svg/index.ts",
    "content": "export { default } from './base64'\nexport { bufferToString } from './bufferToString'\nexport type { PackType } from './helpers'\nexport {\n  ClearSvg,\n  LoadIconSvgs,\n  normalizeZipFleName,\n  SvgToAstro,\n  SvgToDataURL,\n  SvgToJSX,\n  SvgToQwik,\n  SvgToReactNative,\n  SvgToSolid,\n  SvgToSvelte,\n  SvgToTSX,\n  SvgToVue,\n  toComponentName,\n} from './helpers'\nexport { HtmlToJSX } from './htmlToJsx'\nexport { API_ENTRY, getLicenseComment, getSvg, getSvgLocal } from './loader'\nexport { prettierCode } from './prettier'\n"
  },
  {
    "path": "src/utils/svg/loader.ts",
    "content": "import type { CollectionInfo } from '../../data'\nimport { buildIcon, loadIcon } from 'iconify-icon'\n\nexport const API_ENTRY = 'https://api.iconify.design'\n\nexport async function getLicenseComment(collections: CollectionInfo[], icon: string) {\n  const [id] = icon.split(':')\n  const collection = collections.find(i => i.id === id)\n  if (!collection) {\n    return ''\n  }\n  return `<!-- Icon from ${collection?.name} by ${collection?.author?.name} - ${collection?.license?.url} -->`\n}\n\nexport async function getSvgLocal(\n  collections: CollectionInfo[],\n  icon: string,\n  size = '1em',\n  color = 'currentColor',\n) {\n  const data = await loadIcon(icon)\n  if (!data)\n    return\n  const built = buildIcon(data, { height: size })\n  if (!built)\n    return\n  const license = await getLicenseComment(collections, icon)\n  const xlink = built.body.includes('xlink:') ? ' xmlns:xlink=\"http://www.w3.org/1999/xlink\"' : ''\n  return `<svg xmlns=\"http://www.w3.org/2000/svg\"${xlink} ${Object.entries(built.attributes).map(([k, v]) => `${k}=\"${v}\"`).join(' ')}>${license}${built.body}</svg>`.replaceAll('currentColor', color)\n}\n\nexport async function getSvg(\n  collections: CollectionInfo[],\n  icon: string,\n  size = '1em',\n  color = 'currentColor',\n) {\n  const local = await getSvgLocal(collections, icon, size, color)\n  if (local)\n    return local\n\n  const mode = import.meta.env.DEV && !PWA ? 'no-cors' : undefined\n  return await fetch(`${API_ENTRY}/${icon}.svg?inline=false&height=${size}&color=${encodeURIComponent(color)}`, {\n    mode,\n  }).then(r => r.text()) || ''\n}\n"
  },
  {
    "path": "src/utils/svg/prettier.ts",
    "content": "import type { BuiltInParserName } from 'prettier'\nimport { isElectron } from '../../env'\n\nexport async function prettierCode(code: string, parser: BuiltInParserName) {\n  if (!isElectron)\n    return code\n  try {\n    const format = await import('prettier').then(r => r.format)\n    return format(code, {\n      parser,\n      semi: false,\n      singleQuote: true,\n    })\n  }\n  catch {\n    return code\n  }\n}\n"
  },
  {
    "path": "src/utils/svgToPng.ts",
    "content": "export async function svgToPngDataUrl(svg: string) {\n  const scaleFactor = 16\n\n  const canvas = document.createElement('canvas')\n  const imgPreview = document.createElement('img')\n  imgPreview.setAttribute('style', 'position: absolute; top: -9999px')\n  document.body.appendChild(imgPreview)\n  const canvasCtx = canvas.getContext('2d')!\n\n  const svgBlob: Blob = new Blob([svg], { type: 'image/svg+xml;charset=utf-8' })\n  const svgDataUrl = URL.createObjectURL(svgBlob)\n\n  return new Promise<string>((resolve) => {\n    imgPreview.onload = async () => {\n      const img = new Image()\n      const dimensions: { width: number, height: number } = await getDimensions(imgPreview.src)\n\n      Object.assign(canvas, {\n        width: dimensions.width * scaleFactor,\n        height: dimensions.height * scaleFactor,\n      })\n\n      img.crossOrigin = 'anonymous'\n      img.src = imgPreview.src\n      img.onload = () => {\n        canvasCtx.drawImage(img, 0, 0, canvas.width, canvas.height)\n        const imgData = canvas.toDataURL('image/png')\n        resolve(imgData)\n      }\n\n      function getDimensions(\n        src: string,\n      ): Promise<{ width: number, height: number }> {\n        return new Promise((resolve) => {\n          const _img = new Image()\n          _img.src = src\n          _img.onload = () => {\n            resolve({\n              width: _img.naturalWidth,\n              height: _img.naturalHeight,\n            })\n          }\n        })\n      }\n    }\n    imgPreview.src = svgDataUrl\n  })\n    .finally(() => {\n      document.body.removeChild(imgPreview)\n    })\n}\n"
  },
  {
    "path": "src/utils/worker/index.ts",
    "content": "/// <reference lib=\"webworker\" />\n\nimport type { CollectionInfo } from '../../data'\nimport type { PackType } from '../svg'\nimport type { PackOperation, WorkerPackMessage } from './types'\nimport { downloadZip } from 'client-zip'\nimport { getSvg, LoadIconSvgs, normalizeZipFleName, SvgToAstro, SvgToJSX, SvgToQwik, SvgToReactNative, SvgToSolid, SvgToSvelte, SvgToTSX, SvgToVue, toComponentName } from '../svg'\n\nglobalThis.onmessage = async (event: MessageEvent<WorkerPackMessage<PackOperation>>) => {\n  const message = event.data\n  let blob: Blob | undefined\n  let name: string | undefined\n  try {\n    const collections: CollectionInfo[] = JSON.parse(\n      new TextDecoder().decode(message.collections),\n    )\n    if (isPackZipMessage(message)) {\n      blob = await downloadZip(\n        PreparePackZip(\n          collections,\n          message.payload.icons,\n          message.payload.name,\n          message.payload.type,\n        ),\n      ).blob()\n    }\n    else if (isPackJsonZipMessage(message)) {\n      blob = await downloadZip(\n        PrepareIconSvgs(\n          collections,\n          message.payload.icons,\n          'json',\n          message.payload.name,\n        ),\n      ).blob()\n    }\n    else if (isPackSvgZipMessage(message)) {\n      blob = await downloadZip(\n        PrepareIconSvgs(\n          collections,\n          message.payload.icons,\n          'svg',\n        ),\n      ).blob()\n    }\n    else if (isPackFontZipMessage(message)) {\n      const result = await PackIconFont(\n        collections,\n        message.payload.icons,\n        message.payload.options,\n      )\n      if (result) {\n        ([blob, name] = result)\n      }\n    }\n  }\n  catch (e: any) {\n    console.error('PackWorker: error while generating the zip', e)\n    globalThis.postMessage({ error: e && 'message' in e ? e.message : String(e) })\n    return\n  }\n\n  if (blob) {\n    try {\n      const arrayBuffer = await blob.arrayBuffer()\n      globalThis.postMessage({\n        blob: arrayBuffer,\n        name,\n      }, [arrayBuffer])\n    }\n    catch (e: any) {\n      console.error('PackWorker: error while transferring generated zip', e)\n      globalThis.postMessage({ error: e && 'message' in e ? e.message : String(e) })\n    }\n  }\n  else {\n    globalThis.postMessage({ error: 'No blob generated' })\n  }\n}\n\nexport function isPackZipMessage(\n  message: WorkerPackMessage<PackOperation>,\n): message is WorkerPackMessage<'pack-zip'> {\n  return message.operation === 'pack-zip'\n}\n\nexport function isPackJsonZipMessage(\n  message: WorkerPackMessage<PackOperation>,\n): message is WorkerPackMessage<'pack-json-zip'> {\n  return message.operation === 'pack-json-zip'\n}\n\nexport function isPackSvgZipMessage(\n  message: WorkerPackMessage<PackOperation>,\n): message is WorkerPackMessage<'pack-svg-zip'> {\n  return message.operation === 'pack-svg-zip'\n}\n\nexport function isPackFontZipMessage(\n  message: WorkerPackMessage<PackOperation>,\n): message is WorkerPackMessage<'pack-font-zip'> {\n  return message.operation === 'pack-font-zip'\n}\n\nasync function* PrepareIconSvgs(\n  collections: CollectionInfo[],\n  icons: string[],\n  format: 'svg' | 'json',\n  name?: string,\n) {\n  if (format === 'json') {\n    const svgs = await LoadIconSvgs(collections, icons)\n    yield {\n      name: `${name}.json`,\n      input: new Blob([JSON.stringify(svgs, null, 2)], { type: 'application/json; charset=utf-8' }),\n    }\n    return\n  }\n\n  for (const icon of icons) {\n    if (!icon)\n      continue\n\n    const svg = await getSvg(collections, icon)\n\n    yield {\n      name: `${normalizeZipFleName(icon)}.svg`,\n      input: new Blob([svg], { type: 'image/svg+xml' }),\n    }\n  }\n}\n\nasync function* PreparePackZip(\n  collections: CollectionInfo[],\n  icons: string[],\n  name: string,\n  type: PackType,\n) {\n  if (type === 'json' || type === 'svg') {\n    yield* PrepareIconSvgs(collections, icons, type, name)\n    return\n  }\n\n  const ext = (type === 'solid' || type === 'qwik' || type === 'react-native') ? 'tsx' : type\n\n  for (const name of icons) {\n    if (!name)\n      continue\n\n    const svg = await getSvg(collections, name)\n\n    const componentName = toComponentName(normalizeZipFleName(name))\n    let content: string\n\n    switch (type) {\n      case 'vue':\n        content = await SvgToVue(svg, componentName)\n        break\n      case 'jsx':\n        content = await SvgToJSX(svg, componentName, false)\n        break\n      case 'svelte':\n        content = SvgToSvelte(svg)\n        break\n      case 'astro':\n        content = SvgToAstro(svg)\n        break\n      case 'qwik':\n        content = await SvgToQwik(svg, componentName, false)\n        break\n      case 'react-native':\n        content = await SvgToReactNative(svg, componentName, false)\n        break\n      case 'solid':\n        content = await SvgToSolid(svg, componentName, false)\n        break\n      case 'tsx':\n        content = await SvgToTSX(svg, componentName, false)\n        break\n      default:\n        continue\n    }\n\n    yield {\n      name: `${componentName}.${ext}`,\n      input: new Blob([content], { type: 'text/plain' }),\n    }\n  }\n}\n\nasync function PackIconFont(\n  collections: CollectionInfo[],\n  icons: string[],\n  options: any = {},\n) {\n  if (!icons.length)\n    return\n\n  const [data, { SvgPacker }] = await Promise.all([\n    LoadIconSvgs(collections, icons),\n    import('svg-packer'),\n  ])\n  const result = await SvgPacker({\n    fontName: 'Iconify Explorer Font',\n    fileName: 'iconfont',\n    cssPrefix: 'i',\n    ...options,\n    icons: data,\n  })\n\n  return [result.zip.blob, result.zip.name] as const\n}\n"
  },
  {
    "path": "src/utils/worker/types.ts",
    "content": "import type { PackType } from '../svg'\n\nexport type PackOperation = 'pack-zip' | 'pack-json-zip' | 'pack-svg-zip' | 'pack-font-zip'\n\nexport interface PackZipPayload {\n  icons: string[]\n  name: string\n  type: PackType\n}\nexport interface PackJsonZipPayload {\n  icons: string[]\n  name: string\n}\nexport interface PackSvgZipPayload {\n  icons: string[]\n}\n\nexport interface PackFontZipPayload {\n  icons: string[]\n  options: any\n}\n\nexport interface WorkerPackMessage<O extends PackOperation> {\n  payload: O extends 'pack-zip' ? PackZipPayload\n    : O extends 'pack-json-zip' ? PackJsonZipPayload\n      : O extends 'pack-svg-zip' ? PackSvgZipPayload\n        : O extends 'pack-font-zip' ? PackFontZipPayload\n          : never\n  operation: O\n  collections: ArrayBuffer\n}\n\nexport interface WorkerPackResponse {\n  blob: ArrayBuffer\n  name?: string\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"esnext\",\n    \"jsx\": \"preserve\",\n    \"lib\": [\"DOM\", \"ESNext\", \"WebWorker\"],\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"Bundler\",\n    \"resolveJsonModule\": true,\n    \"types\": [\n      \"vite/client\",\n      \"vite-plugin-pages/client\",\n      \"vite-plugin-pwa/client\"\n    ],\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"esModuleInterop\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"skipLibCheck\": true\n  },\n  \"exclude\": [\n    \"dist\",\n    \"node_modules\"\n  ]\n}\n"
  },
  {
    "path": "unocss.config.ts",
    "content": "import { defineConfig, presetAttributify, presetIcons, presetWind3, transformerDirectives, transformerVariantGroup } from 'unocss'\n\nexport default defineConfig({\n  shortcuts: {\n    'border-base': 'border-hex-888/25',\n    'border-dark-only': 'border-transparent dark:border-dark-100',\n    'bg-base': 'bg-white dark:bg-[#181818]',\n    'color-base': 'text-gray-900 dark:text-gray-300',\n    'color-fade': 'text-gray-900:50 dark:text-gray-300:50',\n    'icon-button': 'op50 hover:op100 my-auto shrink-0',\n  },\n  presets: [\n    presetWind3(),\n    presetIcons(),\n    presetAttributify(),\n  ],\n  transformers: [\n    transformerVariantGroup(),\n    transformerDirectives(),\n  ],\n  theme: {\n    colors: {\n      primary: 'var(--theme-color)',\n      dark: {\n        100: '#222',\n        200: '#333',\n        300: '#444',\n        400: '#555',\n        500: '#666',\n        600: '#777',\n        700: '#888',\n        800: '#999',\n        900: '#aaa',\n      },\n    },\n  },\n})\n"
  },
  {
    "path": "vite.config.ts",
    "content": "import { rmSync } from 'node:fs'\nimport { join, resolve } from 'node:path'\nimport process from 'node:process'\nimport Vue from '@vitejs/plugin-vue'\nimport dayjs from 'dayjs'\nimport fg from 'fast-glob'\nimport { SvgPackerVitePlugin } from 'svg-packer/vite'\nimport UnoCSS from 'unocss/vite'\nimport AutoImport from 'unplugin-auto-import/vite'\nimport Components from 'unplugin-vue-components/vite'\nimport { defineConfig } from 'vite'\nimport electron from 'vite-plugin-electron'\nimport renderer from 'vite-plugin-electron-renderer'\n// @ts-expect-error type resolution\nimport esmodule from 'vite-plugin-esmodule'\nimport Pages from 'vite-plugin-pages'\nimport { VitePWA } from 'vite-plugin-pwa'\n\nexport default defineConfig(({ mode }) => {\n  const isElectron = mode === 'electron'\n  const isBuild = process.argv.slice(2).includes('build')\n\n  if (isElectron)\n    rmSync('dist-electron', { recursive: true, force: true })\n\n  return {\n    plugins: [\n      isElectron && electron([\n        {\n          entry: 'src/main/index.ts',\n          vite: {\n            build: {\n              minify: isBuild,\n              outDir: 'dist-electron/main',\n            },\n          },\n        },\n      ]),\n      isElectron && renderer(),\n      isElectron && esmodule(['prettier']),\n      Vue({\n        customElement: [\n          'iconify-icon',\n        ],\n        template: {\n          compilerOptions: {\n            isCustomElement: tag => tag === 'iconify-icon',\n          },\n        },\n      }),\n      Pages({\n        importMode: 'sync',\n      }),\n      Components({\n        dts: 'src/components.d.ts',\n      }),\n      AutoImport({\n        imports: [\n          'vue',\n          'vue-router',\n          '@vueuse/core',\n        ],\n        dts: 'src/auto-imports.d.ts',\n      }),\n      SvgPackerVitePlugin(),\n      !isElectron && VitePWA({\n        strategies: 'injectManifest',\n        srcDir: 'src',\n        filename: 'sw.ts',\n        registerType: 'autoUpdate',\n        manifest: {\n          name: 'Icônes',\n          short_name: 'Icônes',\n          icons: [\n            {\n              src: '/android-chrome-192x192.png',\n              sizes: '192x192',\n              type: 'image/png',\n            },\n            {\n              src: '/android-chrome-512x512.png',\n              sizes: '512x512',\n              type: 'image/png',\n            },\n          ],\n        },\n        injectManifest: {\n          // collections-meta.json ~7.5MB\n          maximumFileSizeToCacheInBytes: 10 * 1024 * 1024,\n        },\n        integration: {\n          configureOptions(viteConfig, options) {\n            if (viteConfig.command === 'build')\n              options.includeAssets = fg.sync('**/*.*', { cwd: join(process.cwd(), 'public'), onlyFiles: true })\n          },\n        },\n        devOptions: {\n          enabled: process.env.SW_DEV === 'true',\n          /* when using generateSW the PWA plugin will switch to classic */\n          type: 'module',\n          navigateFallback: 'index.html',\n        },\n      }),\n      UnoCSS(),\n    ],\n    define: {\n      __BUILD_TIME__: JSON.stringify(dayjs().format('YYYY/MM/DD HH:mm')),\n      PWA: !isElectron && (process.env.NODE_ENV === 'production' || process.env.SW_DEV === 'true'),\n    },\n    resolve: {\n      alias: {\n        'iconify-icon': resolve(__dirname, 'node_modules/iconify-icon/dist/iconify-icon.mjs'),\n      },\n    },\n    worker: {\n      format: 'es',\n      rollupOptions: {\n        treeshake: true,\n      },\n      plugins: () => [\n        SvgPackerVitePlugin(),\n      ],\n    },\n  }\n})\n"
  }
]