[
  {
    "path": ".editorconfig",
    "content": "root = true\r\n\r\n[*]\r\ncharset = utf-8\r\nindent_style = space\r\nindent_size = 2\r\nend_of_line = lf\r\ninsert_final_newline = true\r\ntrim_trailing_whitespace = true"
  },
  {
    "path": ".eslintignore",
    "content": "node_modules\ndist\nout\n.gitignore\n"
  },
  {
    "path": ".eslintrc.cjs",
    "content": "/* eslint-env node */\nrequire('@rushstack/eslint-patch/modern-module-resolution')\n\nmodule.exports = {\n  root: true,\n  env: {\n    browser: true,\n    commonjs: true,\n    es6: true,\n    node: true,\n    'vue/setup-compiler-macros': true\n  },\n  extends: [\n    'plugin:vue/vue3-recommended',\n    'eslint:recommended',\n    '@vue/eslint-config-typescript/recommended',\n    '@vue/eslint-config-prettier'\n  ],\n  rules: {\n    '@typescript-eslint/ban-ts-comment': ['error', { 'ts-ignore': 'allow-with-description' }],\n    '@typescript-eslint/explicit-module-boundary-types': 'off',\n    '@typescript-eslint/no-empty-function': ['error', { allow: ['arrowFunctions'] }],\n    // '@typescript-eslint/no-explicit-any': 'error',\n    '@typescript-eslint/no-non-null-assertion': 'off',\n    '@typescript-eslint/no-var-requires': 'off',\n    'vue/require-default-prop': 'off',\n    'vue/multi-word-component-names': 'off',\n    '@typescript-eslint/no-explicit-any': 'off'\n  },\n  overrides: [\n    {\n      files: ['*.js'],\n      rules: {\n        '@typescript-eslint/explicit-function-return-type': 'off'\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": ".github/workflows/electron_build.yml",
    "content": "name: Build Electron App\nenv:\n  GH_TOKEN: ${{ secrets.GH_TOKEN }}\non:\n  push:\n    tags:\n      - 'v*'\n\njobs:\n  build-linux:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Create Draft Release\n        id: create_release\n        uses: actions/create-release@v1\n        env:\n          GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}\n        with:\n          tag_name: ${{ github.ref }}\n          release_name: Release ${{ github.ref }}\n          draft: true\n          prerelease: false\n\n      - name: Check out repository\n        uses: actions/checkout@v2\n\n      - name: Set up Node.js\n        uses: actions/setup-node@v2\n        with:\n          node-version: '18'\n\n      - name: Install dependencies\n        run: npm install\n\n      - name: Build Electron App for Windows\n        run: npm run build && npx electron-builder --linux --config\n\n      - name: Upload Linux Artifact\n        uses: actions/upload-artifact@v4\n        with:\n          name: linux-app\n          path: dist/*.AppImage\n          compression-level: 0\n\n  build-windows:\n    runs-on: windows-latest\n    steps:\n      - name: Check out repository\n        uses: actions/checkout@v2\n\n      - name: Set up Node.js\n        uses: actions/setup-node@v2\n        with:\n          node-version: '18'\n\n      - name: Install dependencies\n        run: npm install\n\n      - name: Build Electron App for Windows\n        run: npm run build && npx electron-builder --win --config\n\n      - name: Upload Windows Artifact\n        uses: actions/upload-artifact@v4\n        with:\n          name: windows-app\n          path: dist/*.exe\n          compression-level: 0\n\n  build-mac:\n    runs-on: macos-latest\n    steps:\n      - name: Check out repository\n        uses: actions/checkout@v2\n\n      - name: Set up Node.js\n        uses: actions/setup-node@v2\n        with:\n          node-version: '18'\n\n      - name: Import Apple Developer Certificate\n        env:\n          MAC_CERTIFICATE: ${{ secrets.MAC_CERTIFICATE }}\n          CERTIFICATE_PASSWORD: ${{ secrets.CERTIFICATE_PASSWORD }}\n        run: |\n          echo \"$MAC_CERTIFICATE\" | base64 --decode > certificate.p12\n          security create-keychain -p \"\" build.keychain\n          security default-keychain -s build.keychain\n          security unlock-keychain -p \"\" build.keychain\n          security import certificate.p12 -k build.keychain -P \"$CERTIFICATE_PASSWORD\" -T /usr/bin/codesign\n          security set-key-partition-list -S apple-tool:,apple: -s -k \"\" build.keychain\n          security find-identity -v\n\n      - name: Install dependencies\n        run: npm install\n\n      - name: Resign modules\n        run: npm run resign\n\n      - name: Build Electron App for Mac\n        run: npm run build && npx electron-builder --mac --config\n        env:\n          CSC_NAME: \"Jan Vincent Lunge (BLH4PG2L7J)\"\n          CSC_KEYCHAIN: build.keychain\n\n      - name: Upload Mac Artifact\n        uses: actions/upload-artifact@v4\n        with:\n          name: mac-app\n          path: dist/*.dmg\n          compression-level: 0\n\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules\ndist\nout\n*.log*\n.idea\n.DS_Store\n.pog.code-workspace\n"
  },
  {
    "path": ".npmrc",
    "content": "ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/\n"
  },
  {
    "path": ".prettierignore",
    "content": "out\ndist\npnpm-lock.yaml\nLICENSE.md\ntsconfig.json\ntsconfig.*.json\n"
  },
  {
    "path": ".vscode/extensions.json",
    "content": "{\n  \"recommendations\": [\"dbaeumer.vscode-eslint\"]\n}\n"
  },
  {
    "path": ".vscode/launch.json",
    "content": "{\n  \"version\": \"0.2.0\",\n  \"configurations\": [\n    {\n      \"name\": \"Debug Main Process\",\n      \"type\": \"node\",\n      \"request\": \"launch\",\n      \"cwd\": \"${workspaceRoot}\",\n      \"runtimeExecutable\": \"${workspaceRoot}/node_modules/.bin/electron-vite\",\n      \"windows\": {\n        \"runtimeExecutable\": \"${workspaceRoot}/node_modules/.bin/electron-vite.cmd\"\n      },\n      \"runtimeArgs\": [\"--sourcemap\"]\n    }\n  ]\n}\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n  \"[typescript]\": {\n    \"editor.defaultFormatter\": \"esbenp.prettier-vscode\"\n  },\n  \"[javascript]\": {\n    \"editor.defaultFormatter\": \"esbenp.prettier-vscode\"\n  },\n  \"[json]\": {\n    \"editor.defaultFormatter\": \"esbenp.prettier-vscode\"\n  }\n}\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License (MIT)\n\nCopyright 2023 Jan Lunge\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "![logo](demo/pog-header.png?raw=true)\n<h1 align=\"center\">POG</h1>\n<h4 align=\"center\">\nKMK GUI, Layout Editor, Keymap Editor, Flashing Utility\n</h4>\n<p align=\"center\">\n    <a href=\"https://github.com/JanLunge/pog/stargazers\"><img src=\"https://img.shields.io/github/stars/JanLunge/pog\" alt=\"Stars Badge\"/></a>\n    <a href=\"https://github.com/JanLunge/pog/network/members\"><img src=\"https://img.shields.io/github/forks/JanLunge/pog\" alt=\"Forks Badge\"/></a>\n    <img src=\"https://badgen.net/badge/version/v1.4.4\" alt=\"\">\n</p>\n\n![preview](demo/pog-screenshot.png?raw=true)\n\n# Documentation\nthe documentation is available [here](https://github.com/JanLunge/pog-docs). Feel free to contribute\n\n# Installation\ndownload the pre-built binaries for Windows, Mac and Linux are available in the [releases](https://github.com/JanLunge/pog/releases)\n\n# Development Setup\n## dependencies\n* node 16\n* yarn\n\ninstall everything with\n`yarn`\nthen just run it with dev to start\n`yarn dev`\n\nto release a new version use `npm version minor` or `major` or `patch` then just push and github actions will do the rest\n\n# Tasks\n## bugs\n- [ ] maximum call stack error when closing the app\n## urgent\n- [x] check if a keyboard is connected (usb drive) in the keyboard selector preview\n- [x] show serial output in the gui\n- [ ] automatically get the correct serial device (by serial number)\n- [ ] guides etc. for setup + split workflow | help menu + videos\n- [ ] save wiring info in qr code or so\n- [ ] share pog.json files\n- [ ] check if the controller you use even has the pin you specified (controller lookup and serial to get pins )\n- [ ] generate layout based on matrix + clear layout button / delete multiple\n- [ ] features case-insensitive and via gui or pog json\n\n## features wishlist\n- [ ] bluetooth workflow\n- [ ] language switcher for german and other layouts changing the labels on the keymap\n- [ ] modtap/tapdance/macros/sequences\n- [ ] encoder support direct pin click\n- [ ] way to handle differences between pog.json to kmk code\n- [ ] wiring preview\n\n\n### Build\n\n```bash\n# For windows\n$ npm run build:win\n\n# For macOS\n$ npm run build:mac\n\n# For Linux\n$ npm run build:linux\n```\n"
  },
  {
    "path": "build/entitlements.mac.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n  <dict>\n    <key>com.apple.security.cs.allow-jit</key>\n    <true/>\n    <key>com.apple.security.cs.allow-unsigned-executable-memory</key>\n    <true/>\n    <key>com.apple.security.cs.allow-dyld-environment-variables</key>\n    <true/>\n  </dict>\n</plist>\n"
  },
  {
    "path": "build/notarize.js",
    "content": "const { notarize } = require('@electron/notarize')\nconst path = require('path')\nconst fs = require('fs')\nconst { execSync } = require('child_process')\n\nmodule.exports = async (context) => {\n  if (process.platform !== 'darwin') return\n\n  console.log('aftersign hook triggered, start to notarize app.')\n\n  if (!process.env.CI) {\n    console.log(`skipping notarizing, not in CI.`)\n    return\n  }\n\n  if (!('APPLE_ID' in process.env && 'APPLE_ID_PASS' in process.env)) {\n    console.warn('skipping notarizing, APPLE_ID and APPLE_ID_PASS env variables must be set.')\n    return\n  }\n\n  const appId = 'com.electron.app'\n\n  const { appOutDir } = context\n\n  const appName = context.packager.appInfo.productFilename\n\n  try {\n    await notarize({\n      appBundleId: appId,\n      appPath: `${appOutDir}/${appName}.app`,\n      appleId: process.env.APPLE_ID,\n      appleIdPassword: process.env.APPLEIDPASS\n    })\n  } catch (error) {\n    console.error(error)\n  }\n\n  console.log(`done notarizing ${appId}.`)\n}\n"
  },
  {
    "path": "build/resign.js",
    "content": "const fs = require('fs')\nconst path = require('path')\nconst execSync = require('child_process').execSync\n\nfunction findNativeModules(dir, fileList = []) {\n  const files = fs.readdirSync(dir)\n\n  files.forEach((file) => {\n    const filePath = path.join(dir, file)\n    const fileStat = fs.lstatSync(filePath)\n\n    if (fileStat.isDirectory()) {\n      findNativeModules(filePath, fileList)\n    } else if (filePath.endsWith('.node')) {\n      fileList.push(filePath)\n    }\n  })\n\n  return fileList\n}\n\nconst resign = () => {\n  if (process.platform !== 'darwin') return\n\n  const nativeModules = findNativeModules('./node_modules')\n  console.log(nativeModules)\n\n  nativeModules.forEach((module) => {\n    // const fullPath = path.join(appOutDir, \"pog.app\", module)\n    execSync(`codesign --deep --force --verbose --sign \"BLH4PG2L7J\" \"${module}\"`)\n  })\n  console.log('signed all node modules')\n}\n\nresign()\n"
  },
  {
    "path": "dev-app-update.yml",
    "content": "provider: generic\nurl: https://pog.heaper.de/auto-updates\nupdaterCacheDirName: vue-vite-electron-updater\n"
  },
  {
    "path": "electron-builder.yml",
    "content": "appId: de.heaper.pog\nproductName: pog\ndirectories:\n  buildResources: build\nfiles:\n  - '!**/.vscode/*'\n  - '!src/*'\n  - '!electron.vite.config.{js,ts,mjs,cjs}'\n  - '!{.eslintignore,.eslintrc.cjs,.prettierignore,yulyuly.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}'\n  - '!{tsconfig.json,tsconfig.node.json,tsconfig.web.json}'\nasarUnpack:\n  - resources/*\nafterSign: build/notarize.js\nwin:\n  executableName: pog\n  target:\n    - target: portable\n      arch:\n        - x64\n    - target: nsis\n      arch:\n        - x64\nnsis:\n  artifactName: ${name}-${version}-${arch}-setup.${ext}\n  shortcutName: ${productName}\n  uninstallDisplayName: ${productName}\n  createDesktopShortcut: always\nmac:\n  entitlementsInherit: build/entitlements.mac.plist\n  extendInfo:\n    - NSCameraUsageDescription: Application requests access to the device's camera.\n    - NSMicrophoneUsageDescription: Application requests access to the device's microphone.\n    - NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder.\n    - NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder.\n#  publish:\n#    - generic\n  target:\n    - target: dmg\n      arch:\n        - x64\n        - arm64\n    - target: zip\n      arch:\n        - x64\n        - arm64\ndmg:\n  artifactName: ${name}-${version}-${arch}.${ext}\nlinux:\n  target:\n    - target: AppImage\n      arch:\n        - x64\n#    - snap\n#    - deb\n  maintainer: Jan Lunge\n  category: Utility\nappImage:\n  artifactName: ${name}-${version}-${arch}.${ext}\nnpmRebuild: false\npublish:\n  provider: github\n  repo: pog\n  owner: janlunge\n  releaseType: draft\n#  url: https://pog.heaper.de/auto-updates\n"
  },
  {
    "path": "electron.vite.config.ts",
    "content": "import { resolve } from 'path'\nimport { defineConfig, externalizeDepsPlugin } from 'electron-vite'\nimport vue from '@vitejs/plugin-vue'\n\nexport default defineConfig({\n  main: {\n    plugins: [externalizeDepsPlugin()]\n  },\n  preload: {\n    plugins: [externalizeDepsPlugin()]\n  },\n  renderer: {\n    resolve: {\n      alias: {\n        '@renderer': resolve('src/renderer/src')\n      }\n    },\n    plugins: [vue()]\n  }\n})\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"pog\",\n  \"version\": \"2.2.0\",\n  \"license\": \"MIT\",\n  \"description\": \"A KMK firmware configurator\",\n  \"main\": \"./out/main/index.js\",\n  \"author\": \"Jan Lunge\",\n  \"homepage\": \"https://github.com/wlard/pog\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/janlunge/pog.git\"\n  },\n  \"scripts\": {\n    \"format\": \"prettier --write .\",\n    \"lint\": \"eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts,.vue --fix\",\n    \"typecheck:node\": \"tsc --noEmit -p tsconfig.node.json --composite false\",\n    \"typecheck:web\": \"vue-tsc --noEmit -p tsconfig.web.json --composite false\",\n    \"typecheck\": \"npm run typecheck:node && npm run typecheck:web\",\n    \"start\": \"electron-vite preview\",\n    \"dev\": \"electron-vite dev\",\n    \"build\": \"npm run typecheck && electron-vite build\",\n    \"resign\": \"node build/resign.js\",\n    \"postinstall\": \"electron-builder install-app-deps\",\n    \"build:win\": \"npm run build && electron-builder --win --config\",\n    \"build:mac\": \"npm run build && electron-builder --mac --config\",\n    \"build:linux\": \"npm run build && electron-builder --linux --config\",\n    \"build:all\": \"npm run build && electron-builder --win --mac --linux --config\",\n    \"devtools\": \"./node_modules/.bin/vue-devtools\",\n    \"postversion\": \"git push --tags\"\n  },\n  \"dependencies\": {\n    \"@electron-toolkit/preload\": \"^1.0.3\",\n    \"@electron-toolkit/utils\": \"^1.0.2\",\n    \"@floating-ui/vue\": \"^1.1.9\",\n    \"@mdi/font\": \"^7.1.96\",\n    \"@viselect/vue\": \"^3.2.5\",\n    \"@vueuse/core\": \"^9.12.0\",\n    \"@wagmi/core\": \"^0.9.7\",\n    \"@web3modal/ethereum\": \"^2.1.2\",\n    \"@web3modal/html\": \"^2.1.2\",\n    \"@wlard/vue-class-store\": \"^3.0.0\",\n    \"@wlard/vue3-popper\": \"^1.3.1\",\n    \"chroma-js\": \"^2.4.2\",\n    \"daisyui\": \"^3.9.2\",\n    \"dayjs\": \"^1.11.7\",\n    \"decompress\": \"^4.2.1\",\n    \"drivelist\": \"^12.0.2\",\n    \"electron-updater\": \"^5.3.0\",\n    \"ethers\": \"^5\",\n    \"lighten-darken-color\": \"^1.0.0\",\n    \"mini-svg-data-uri\": \"^1.4.4\",\n    \"request\": \"^2.88.2\",\n    \"sass\": \"^1.58.0\",\n    \"serialport\": \"^10.5.0\",\n    \"tailwindcss\": \"^3.3.3\",\n    \"ulid\": \"^2.3.0\",\n    \"vue-multiselect\": \"^3.0.0-beta.1\",\n    \"vue-router\": \"^4.1.6\",\n    \"vuedraggable\": \"^4.1.0\"\n  },\n  \"devDependencies\": {\n    \"@electron-toolkit/tsconfig\": \"^1.0.1\",\n    \"@electron/notarize\": \"^1.2.3\",\n    \"@rushstack/eslint-patch\": \"^1.2.0\",\n    \"@types/node\": \"16.18.11\",\n    \"@vitejs/plugin-vue\": \"^4.0.0\",\n    \"@vue/devtools\": \"^6.5.0\",\n    \"@vue/eslint-config-prettier\": \"^7.0.0\",\n    \"@vue/eslint-config-typescript\": \"^11.0.2\",\n    \"autoprefixer\": \"^10.4.13\",\n    \"electron\": \"^21.3.3\",\n    \"electron-builder\": \"^23.6.0\",\n    \"electron-vite\": \"^1.0.17\",\n    \"eslint\": \"^8.31.0\",\n    \"eslint-plugin-vue\": \"^9.8.0\",\n    \"less\": \"^4.1.3\",\n    \"postcss\": \"^8.4.21\",\n    \"prettier\": \"^2.8.2\",\n    \"prettier-plugin-tailwindcss\": \"^0.2.3\",\n    \"typescript\": \"5.5.4\",\n    \"vite\": \"^4.0.4\",\n    \"vue\": \"^3.2.45\",\n    \"vue-tsc\": \"^1.0.22\"\n  }\n}\n"
  },
  {
    "path": "postcss.config.js",
    "content": "module.exports = {\n  plugins: {\n    tailwindcss: {},\n    autoprefixer: {}\n  }\n}\n"
  },
  {
    "path": "prettier.config.js",
    "content": "// prettier.config.js\nmodule.exports = {\n  plugins: [require('prettier-plugin-tailwindcss')],\n  singleQuote: true,\n  semi: false,\n  printWidth: 100,\n  trailingComma: 'none',\n}\n"
  },
  {
    "path": "src/main/index.ts",
    "content": "import { app, shell, BrowserWindow, ipcMain, Menu } from 'electron'\nimport { join } from 'path'\nimport { electronApp, optimizer, is } from '@electron-toolkit/utils'\nimport icon from '../../resources/icon.png?asset'\nimport drivelist from 'drivelist'\nimport { flashFirmware } from './kmkUpdater'\n// import './saveConfig'\nimport { checkForUSBKeyboards, handleSelectDrive, selectKeyboard } from './selectKeyboard'\nimport { updateFirmware } from './kmkUpdater'\nimport './keyboardDetector' // Import keyboard detector\nimport serialPort from 'serialport'\nimport { ReadlineParser } from '@serialport/parser-readline'\nimport fs from 'fs'\nimport { currentKeyboard } from './store'\nimport { saveConfiguration } from './saveConfig'\nlet mainWindow: BrowserWindow | null = null\nexport { mainWindow }\n\nconst isMac = process.platform === 'darwin'\n\nlet triedToQuit = false\nconst template: unknown = [\n  // { role: 'appMenu' }\n  ...(isMac\n    ? [\n        {\n          label: app.name,\n          submenu: [\n            { role: 'about' },\n            // {\n            //   label: 'Check for Updates...',\n            //   click: () => {\n            //     autoUpdater.checkForUpdates()\n            //   }\n            // },\n            { type: 'separator' },\n            { role: 'services' },\n            { type: 'separator' },\n            { role: 'hide' },\n            { role: 'hideOthers' },\n            { role: 'unhide' },\n            { type: 'separator' },\n            { role: 'quit' }\n          ]\n        }\n      ]\n    : []),\n  // { role: 'fileMenu' }\n  {\n    label: 'File',\n    submenu: [isMac ? { role: 'close' } : { role: 'quit' }]\n  },\n  // { role: 'editMenu' }\n  {\n    label: 'Edit',\n    submenu: [\n      { role: 'undo' },\n      { role: 'redo' },\n      { type: 'separator' },\n      { role: 'cut' },\n      { role: 'copy' },\n      { role: 'paste' },\n      ...(isMac\n        ? [\n            { role: 'pasteAndMatchStyle' },\n            { role: 'delete' },\n            { role: 'selectAll' },\n            { type: 'separator' },\n            {\n              label: 'Speech',\n              submenu: [{ role: 'startSpeaking' }, { role: 'stopSpeaking' }]\n            }\n          ]\n        : [{ role: 'delete' }, { type: 'separator' }, { role: 'selectAll' }])\n    ]\n  },\n  // { role: 'viewMenu' }\n  {\n    label: 'View',\n    submenu: [\n      { role: 'reload' },\n      { role: 'forceReload' },\n      { role: 'toggleDevTools' },\n      { type: 'separator' },\n      { role: 'resetZoom' },\n      { role: 'zoomIn' },\n      { role: 'zoomOut' },\n      { type: 'separator' },\n      { role: 'togglefullscreen' }\n    ]\n  },\n  // { role: 'windowMenu' }\n  {\n    label: 'Window',\n    submenu: [\n      { role: 'minimize' },\n      { role: 'zoom' },\n      ...(isMac\n        ? [{ type: 'separator' }, { role: 'front' }, { type: 'separator' }, { role: 'window' }]\n        : [{ role: 'close' }])\n    ]\n  },\n  {\n    role: 'help',\n    submenu: [\n      {\n        label: 'Learn More',\n        click: async () => {\n          const { shell } = require('electron')\n          await shell.openExternal('https://electronjs.org')\n        }\n      }\n    ]\n  }\n]\n\nconst menu = Menu.buildFromTemplate(template as Electron.MenuItem[])\nMenu.setApplicationMenu(menu)\n\nconst createWindow = async () => {\n  // Create the browser window.\n  mainWindow = new BrowserWindow({\n    width: 900,\n    height: 670,\n    show: false,\n    autoHideMenuBar: true,\n    backgroundColor: '#000000',\n    ...(process.platform === 'linux' ? { icon } : {}),\n    webPreferences: {\n      preload: join(__dirname, '../preload/index.js'),\n      sandbox: false,\n      nodeIntegration: true,\n      contextIsolation: true,\n      experimentalFeatures: true\n    }\n  })\n\n  mainWindow.on('ready-to-show', () => {\n    mainWindow?.show()\n  })\n\n  mainWindow.webContents.setWindowOpenHandler((details) => {\n    shell.openExternal(details.url)\n    return { action: 'deny' }\n  })\n\n  // HMR for renderer base on electron-vite cli.\n  // Load the remote URL for development or the local html file for production.\n  if (is.dev && process.env['ELECTRON_RENDERER_URL']) {\n    mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'])\n  } else {\n    mainWindow.loadFile(join(__dirname, '../renderer/index.html'))\n  }\n}\n\n// This method will be called when Electron has finished\n// initialization and is ready to create browser windows.\n// Some APIs can only be used after this event occurs.\napp.whenReady().then(() => {\n  // Set app user model id for windows\n  electronApp.setAppUserModelId('de.janlunge.pog')\n\n  // Default open or close DevTools by F12 in development\n  // and ignore CommandOrControl + R in production.\n  // see https://github.com/alex8088/electron-toolkit/tree/master/packages/utils\n  app.on('browser-window-created', (_, window) => {\n    optimizer.watchWindowShortcuts(window)\n  })\n\n  createWindow()\n\n  app.on('activate', function () {\n    // On macOS it's common to re-create a window in the app when the\n    // dock icon is clicked and there are no other windows open.\n    if (BrowserWindow.getAllWindows().length === 0) createWindow()\n  })\n})\n\n// Quit when all windows are closed, except on macOS. There, it's common\n// for applications and their menu bar to stay active until the user quits\n// explicitly with Cmd + Q.\napp.on('window-all-closed', () => {\n  if (process.platform !== 'darwin') {\n    app.quit()\n  }\n})\napp.on('before-quit', (event) => {\n  // Prevent the default behavior of this event\n  if (debugPort !== undefined && !triedToQuit) {\n    event.preventDefault()\n    triedToQuit = true\n    console.log('Preparing to quit...')\n    debugPort.close(() => {\n      console.log('Port closed')\n      debugPort = undefined\n\n      // Instead of app.quit(), directly exit the process\n      process.exit(0)\n    })\n  } else if (!triedToQuit) {\n    // Now allow the app to continue quitting\n    process.exit(0)\n  }\n})\n\n// In this file you can include the rest of your app\"s specific main process\n// code. You can also put them in separate files and require them here.\n\n// select keyboard\n// update KMK\n// save keymap\n\nipcMain.handle('selectDrive', () => handleSelectDrive())\nipcMain.handle('deselectKeyboard', () => deselectKeyboard())\nipcMain.handle('rescanKeyboards', () => scanForKeyboards())\nipcMain.handle('updateFirmware', () => updateFirmware())\nipcMain.on('saveConfiguration', (_event, data) => saveConfiguration(data))\nipcMain.handle('checkForUSBKeyboards', (_event, data) => checkForUSBKeyboards(data))\nipcMain.handle('selectKeyboard', (_event, data) => selectKeyboard(data))\nipcMain.handle('serialPorts', (_event, data) => checkSerialDevices())\nipcMain.on('serialSend', (_event, data) => sendSerial(data))\nipcMain.handle('serialConnect', (_event, data) => serialConnect(data))\nipcMain.handle('openExternal', (_event, data) => openExternal(data))\n\n// autoUpdater.on('update-available', () => {\n//   if (mainWindow) mainWindow.webContents.send('update_available')\n// })\n// autoUpdater.on('update-downloaded', () => {\n//   if (mainWindow) mainWindow.webContents.send('update_downloaded')\n// })\n\nconst baudRate = 9600\nconst startTime = new Date()\nlet currentChunk = 0\nlet sendMode = ''\nexport let pogconfigbuffer = ''\nexport let keymapbuffer = ''\nlet total_chunks = 0\nconst chunksize = 1200\n\nconst getBoardInfo = (port) => {\n  return new Promise((res, rej) => {\n    // connect to port and get the response just once\n    const sport = new serialPort.SerialPort({ path: port.path, baudRate, autoOpen: true }, (e) => {\n      // if the connection fails reject the promise\n      if (e) return rej(e)\n    })\n\n    const sparser = sport.pipe(new ReadlineParser({ delimiter: '\\n' }))\n    sparser.once('data', (data) => {\n      sport.close()\n      return res({ ...port, ...JSON.parse(data) })\n    })\n    // request the info\n    sport.write('info_simple\\n')\n  })\n}\n\n// timer helper function\nconst timeout = (prom, time) => {\n  let timer\n  return Promise.race([prom, new Promise((_r, rej) => (timer = setTimeout(rej, time)))]).finally(\n    () => clearTimeout(timer)\n  )\n}\n\nexport const serialBoards: { value: any[] } = { value: [] }\n// TODO: resolve callbacks properly\n// https://stackoverflow.com/questions/69608234/get-promise-resolve-from-separate-callback\nconst scanForKeyboards = async () => {\n  console.log('checking for connected keyboards via serial')\n  if (connectedKeyboardPort && connectedKeyboardPort.isOpen) connectedKeyboardPort.close()\n  const ports = await serialPort.SerialPort.list()\n  console.log('found the following raw ports:', ports)\n  const circuitPythonPorts = ports.filter((port) => {\n    // TODO: make sure the port is used for a pog keyboard\n    // we dont want to send serial data to a REPL that is not a keyboard with pog firmware\n    const manufacturer = port.manufacturer ? port.manufacturer.toLowerCase() : ''\n    // if the manufactuer is pog or has pog as suffix or prefix with the - we assume its a pog keyboard\n    return (\n      manufacturer.endsWith('-pog') || manufacturer.startsWith('pog-') || manufacturer === 'pog'\n    )\n  })\n  const boards = (await Promise.allSettled(\n    circuitPythonPorts.map(async (a) => await timeout(getBoardInfo(a), 2000))\n  )) as {\n    status: 'fulfilled' | 'rejected'\n    value: { name: string; id: string; path: string }\n  }[]\n  const filteredBoards: { name: string; id: string; path: string }[] = boards\n    .filter((a) => a.value !== undefined)\n    .map((a) => a.value)\n\n  console.log('found the following boards:', filteredBoards)\n  filteredBoards.map((a) => console.log(`${a.name} - ${a.id} | ${a.path}`))\n  mainWindow?.webContents.send('keyboardScan', {\n    keyboards: filteredBoards\n  })\n  serialBoards.value = filteredBoards\n  return filteredBoards\n}\n\nlet currentPackage = ''\nlet addedChunks = 0\n\nfunction crossSum(s: string) {\n  // Compute the cross sum\n  let total = 0\n  for (let i = 0; i < s.length; i++) {\n    const c = s.charAt(i)\n    total += c.charCodeAt(0)\n  }\n\n  return total\n}\n\nconst sendConfigChunk = (port) => {\n  port.write(\n    JSON.stringify({\n      current_chunk: currentChunk,\n      total_chunks,\n      data: pogconfigbuffer.substring(\n        chunksize * currentChunk,\n        chunksize * currentChunk + chunksize\n      )\n    }) + '\\n'\n  )\n  console.log('done sending next config chunk waiting for microcontroller')\n}\nconst sendKeymapChunk = (port) => {\n  port.write(\n    JSON.stringify({\n      current_chunk: currentChunk,\n      total_chunks,\n      data: keymapbuffer.substring(chunksize * currentChunk, chunksize * currentChunk + chunksize)\n    }) + '\\n'\n  )\n  console.log('done sending next keymap chunk waiting for microcontroller')\n}\nexport let connectedKeyboardPort: any = null\n\nexport const connectSerialKeyboard = async (keyboard) => {\n  connectedKeyboardPort = new serialPort.SerialPort(\n    { path: keyboard.path, baudRate, autoOpen: true },\n    (e) => {}\n  )\n  const parser = connectedKeyboardPort.pipe(new ReadlineParser({ delimiter: '\\n' }))\n  // parser.once('data', (data) => {\n  //   sport.close()\n  //   res({ ...port, ...JSON.parse(data) })\n  // })\n  // port.write('info_simple\\n');\n  parser.on('data', (data) => {\n    try {\n      const chunk = JSON.parse(data.toString())\n      if (chunk.type === 'pogconfig') {\n        console.log('got chunk', chunk.current_chunk, 'of', chunk.total_chunks)\n        const checksum = crossSum(chunk.data)\n        console.log(\n          'checking cross sum',\n          checksum,\n          chunk.cross_sum,\n          checksum === chunk.cross_sum ? 'valid' : 'invalid'\n        )\n        // if(Math.random() > 0.8){\n        //     console.error('fake invalid')\n        //     port.write('0\\n')\n        //     return\n        // }\n        currentPackage += chunk.data\n        addedChunks++\n\n        if (chunk.current_chunk === Math.ceil(chunk.total_chunks)) {\n          const validated = Math.ceil(chunk.total_chunks) === addedChunks\n          console.log('done', addedChunks, chunk.current_chunk, validated)\n          addedChunks = 0\n          connectedKeyboardPort.write('y\\n')\n          const pogconfig = JSON.parse(currentPackage)\n          // info was successfully queried push to frontend\n          mainWindow?.webContents.send('serialKeyboardPogConfig', {\n            pogconfig\n          })\n          currentPackage = ''\n          return\n        }\n        connectedKeyboardPort.write('1\\n')\n        return\n      } else {\n        console.log('keyboard info', chunk)\n      }\n    } catch (e) {\n      // console.log('not a proper json command, moving to simple commands', e, data, data.toString())\n    }\n\n    // pinging for next chunk\n    if (data === '1') {\n      if (sendMode === 'saveConfig') {\n        total_chunks = Math.ceil(pogconfigbuffer.length / chunksize)\n        console.log(\n          'got signal that last chunk came in fine, sending more if we have more',\n          currentChunk,\n          total_chunks,\n          data\n        )\n\n        if (currentChunk > total_chunks) {\n          console.log('done sending')\n          const dif = new Date().getTime() - startTime.getTime()\n          console.log(`took ${dif / 1000}s`)\n        } else {\n          sendConfigChunk(connectedKeyboardPort)\n          currentChunk += 1\n        }\n      } else if (sendMode === 'saveKeymap') {\n        total_chunks = Math.ceil(keymapbuffer.length / chunksize)\n        console.log(\n          'last chunk came in fine, sending more if we have more',\n          currentChunk,\n          total_chunks,\n          data\n        )\n        if (currentChunk <= total_chunks) {\n          sendKeymapChunk(connectedKeyboardPort)\n          currentChunk += 1\n        }\n      }\n    } else if (data === 'y') {\n      console.log('something else', data)\n      // general reset\n      sendMode = ''\n      currentChunk = 0\n    }\n    return\n  })\n}\nexport const writePogConfViaSerial = (pogconfig) => {\n  pogconfigbuffer = pogconfig\n  currentChunk = 0\n  sendMode = 'saveConfig'\n  if (!connectedKeyboardPort) {\n    console.log('port not set')\n  } else if (!connectedKeyboardPort.isConnected) {\n    connectedKeyboardPort.open(() => {\n      console.log('port open again')\n      connectedKeyboardPort.write('save\\n')\n    })\n  } else {\n    connectedKeyboardPort.write('save\\n')\n  }\n}\nexport const writeKeymapViaSerial = (pogconfig) => {\n  keymapbuffer = pogconfig\n  currentChunk = 0\n  sendMode = 'saveKeymap'\n  if (!connectedKeyboardPort) {\n    console.log('port not set')\n  } else if (!connectedKeyboardPort.isConnected) {\n    connectedKeyboardPort.open(() => {\n      console.log('port open again')\n      connectedKeyboardPort.write('saveKeymap\\n')\n    })\n  } else {\n    connectedKeyboardPort.write('saveKeymap\\n')\n  }\n}\nconst deselectKeyboard = () => {\n  if (connectedKeyboardPort && connectedKeyboardPort.isConnected) {\n    connectedKeyboardPort.close()\n  }\n}\n\nlet debugPort: any = undefined\nconst CONNECTION_TIMEOUT = 5000 // 5 seconds timeout\n\nconst notifyConnectionStatus = (connected: boolean, error?: string) => {\n  mainWindow?.webContents.send('serialConnectionStatus', { connected, error })\n}\n\nconst closeDebugPort = () => {\n  return new Promise<void>((resolve) => {\n    if (debugPort?.isOpen) {\n      debugPort.close(() => {\n        debugPort = undefined\n        resolve()\n      })\n    } else {\n      debugPort = undefined\n      resolve()\n    }\n  })\n}\n\nconst serialConnect = async (port) => {\n  console.log('connecting to serial port', port)\n\n  try {\n    // First ensure any existing connection is properly closed\n    await closeDebugPort()\n\n    // Create a promise that will reject after timeout\n    const timeoutPromise = new Promise((_, reject) => {\n      setTimeout(() => reject(new Error('Connection timeout')), CONNECTION_TIMEOUT)\n    })\n\n    // Create the connection promise\n    const connectionPromise = new Promise((resolve, reject) => {\n      try {\n        debugPort = new serialPort.SerialPort({ path: port, baudRate, autoOpen: true }, (err) => {\n          if (err) {\n            reject(err)\n            return\n          }\n\n          const parser = debugPort.pipe(new ReadlineParser({ delimiter: '\\n' }))\n\n          parser.on('data', (data) => {\n            console.log('got data from serial', data)\n            mainWindow?.webContents.send('serialData', { message: data + '\\n' })\n          })\n\n          debugPort.on('error', (error) => {\n            console.error('Serial port error:', error)\n            notifyConnectionStatus(false, error.message)\n            closeDebugPort()\n          })\n\n          debugPort.on('close', () => {\n            console.log('Serial port closed')\n            notifyConnectionStatus(false)\n          })\n\n          resolve(true)\n        })\n      } catch (err) {\n        reject(err)\n      }\n    })\n\n    // Wait for either connection or timeout\n    await Promise.race([connectionPromise, timeoutPromise])\n\n    console.log('Successfully connected to serial port')\n    notifyConnectionStatus(true)\n  } catch (error: any) {\n    console.error('Failed to connect:', error)\n    await closeDebugPort()\n    notifyConnectionStatus(false, error instanceof Error ? error.message : 'Unknown error')\n    throw error\n  }\n}\n\nconst sendSerial = (message) => {\n  if (!debugPort?.isOpen) {\n    console.error('Cannot send: port not open')\n    return\n  }\n\n  console.log('sending serial', message)\n  mainWindow?.webContents.send('serialData', { message: `> sent: ${message}\\n` })\n\n  try {\n    let buffer\n    if (message === 'ctrlc') {\n      buffer = Buffer.from('\\x03\\x03', 'utf8')\n    } else if (message === 'ctrld') {\n      buffer = Buffer.from('\\x04', 'utf8')\n    } else {\n      buffer = Buffer.from(message + '\\r\\n', 'utf8')\n    }\n\n    debugPort.write(buffer, (err) => {\n      if (err) {\n        console.error('error sending serial', err)\n        notifyConnectionStatus(false, 'Failed to send data')\n      }\n    })\n  } catch (error) {\n    console.error('Error sending serial data:', error)\n    notifyConnectionStatus(false, 'Failed to send data')\n  }\n}\n\n// Add new IPC handler for disconnect\nipcMain.handle('serialDisconnect', async () => {\n  await closeDebugPort()\n})\n\nconst checkSerialDevices = async () => {\n  try {\n    console.log('checking serial devices')\n    const ports = await serialPort.SerialPort.list()\n\n    if (ports.length === 0) {\n      console.log('No serial ports found')\n      return []\n    }\n\n    const returnPorts = ports.map((port) => {\n      return {\n        port: port.path,\n        manufacturer: port.manufacturer,\n        serialNumber: port.serialNumber\n        // Add more attributes here if needed\n      }\n    })\n    console.log('found serial ports', returnPorts)\n    return returnPorts\n  } catch (error) {\n    console.error('Error fetching the list of serial ports:', error)\n    return []\n  }\n}\n\nconst openExternal = (url) => {\n  shell.openExternal(url)\n}\n\n// Drive and Firmware handlers\nipcMain.handle('list-drives', async () => {\n  try {\n    const drives = await drivelist.list()\n    const filteredDrives = drives\n      .map((drive) => ({\n        path: drive.mountpoints[0]?.path || '',\n        name: drive.mountpoints[0]?.label || drive.description || 'Unknown Drive',\n        isReadOnly: drive.isReadOnly,\n        isRemovable: drive.isRemovable,\n        isSystem: drive.isSystem,\n        isUSB: drive.isUSB,\n        isCard: drive.isCard\n      }))\n      .filter((drive) => drive.path !== '' && drive.isUSB)\n    console.log('drives', filteredDrives)\n    return filteredDrives\n  } catch (error) {\n    console.error('Failed to list drives:', error)\n    throw error\n  }\n})\n\nipcMain.handle(\n  'flash-detection-firmware',\n  async (_event, { drivePath, serialNumber }: { drivePath: string; serialNumber: string }) => {\n    try {\n      // Set the current keyboard path\n      currentKeyboard.path = drivePath\n      currentKeyboard.name = 'New Keyboard'\n      currentKeyboard.id = Date.now().toString()\n      currentKeyboard.serialNumber = serialNumber\n      console.log('flashing detection firmware currentKeyboard', currentKeyboard)\n\n      // Create necessary directories if they don't exist\n      if (!fs.existsSync(drivePath)) {\n        throw new Error(`Drive path ${drivePath} does not exist`)\n      }\n\n      // Flash the detection firmware\n      await flashFirmware(drivePath)\n\n      // setup both ports to listen for detection\n      return { success: true }\n    } catch (error) {\n      console.error('Failed to flash firmware:', error)\n      throw error\n    }\n  }\n)\n\n// // Keyboard History handlers\n// ipcMain.handle('list-keyboards', () => {\n//   try {\n//     return listKeyboards()\n//   } catch (error) {\n//     console.error('Failed to list keyboards:', error)\n//     throw error\n//   }\n// })\n\n// Serial Port handlers\nipcMain.handle('serial-ports', async () => {\n  try {\n    console.log('checking serial devices')\n    const ports = await serialPort.SerialPort.list()\n\n    if (ports.length === 0) {\n      console.log('No serial ports found')\n      return []\n    }\n\n    const returnPorts = ports.map((port) => ({\n      port: port.path,\n      manufacturer: port.manufacturer,\n      serialNumber: port.serialNumber\n    }))\n    console.log('found serial ports', returnPorts)\n    return returnPorts\n  } catch (error) {\n    console.error('Error fetching the list of serial ports:', error)\n    return []\n  }\n})\n\nipcMain.handle('serial-connect', async (_event, port: string) => {\n  return serialConnect(port)\n})\n\nipcMain.handle('serial-disconnect', async () => {\n  return closeDebugPort()\n})\n"
  },
  {
    "path": "src/main/keyboardDetector.ts",
    "content": "import { SerialPort } from 'serialport'\nimport { ReadlineParser } from '@serialport/parser-readline'\nimport { BrowserWindow, ipcMain } from 'electron'\nimport { flashFirmware } from './kmkUpdater'\nimport path from 'path'\nimport fs from 'fs'\nimport { currentKeyboard } from './store'\n\ninterface DetectionData {\n  rows: string[]\n  cols: string[]\n  diodeDirection: 'COL2ROW' | 'ROW2COL'\n  pressedKeys: { row: number; col: number }[]\n}\n\nexport class KeyboardDetector {\n  private port: SerialPort | null = null\n  private parser: ReadlineParser | null = null\n  private detectionData: DetectionData = {\n    rows: [],\n    cols: [],\n    diodeDirection: 'COL2ROW',\n    pressedKeys: []\n  }\n\n  async startDetection(window: BrowserWindow) {\n    try {\n      // Flash detection firmware\n    //   const detectionFirmwarePath = path.join(__dirname, '../firmware/detection')\n    //   await flashFirmware(detectionFirmwarePath)\n\n      // Wait for the board to restart\n    //   await new Promise(resolve => setTimeout(resolve, 2000))\n\n      // Find the data port (second port)\n      const serialNumber = currentKeyboard.serialNumber\n      console.log('Using serial number for detection:', serialNumber)\n\n      // Find both serial ports for this serial number\n      const ports = await SerialPort.list()\n      const matchingPorts = ports\n        .filter(port => port.serialNumber === serialNumber)\n        .sort((a, b) => a.path.localeCompare(b.path))\n\n      if (matchingPorts.length < 2) {\n        throw new Error('Could not find both serial ports for keyboard')\n      }\n\n      // Save ports to current keyboard, with lower numbered port as port A\n      currentKeyboard.serialPortA = matchingPorts[0].path\n      currentKeyboard.serialPortB = matchingPorts[1].path\n\n      // Use port B (higher numbered port) for detection\n      const dataPort = currentKeyboard.serialPortB\n\n      if (!dataPort) {\n        throw new Error('Data port not found. Make sure both serial ports are properly connected.')\n      }\n\n      // Open serial connection\n      this.port = new SerialPort({\n        path: dataPort,\n        baudRate: 115200\n      })\n\n      this.parser = this.port.pipe(new ReadlineParser())\n\n      // Handle incoming data\n      this.parser.on('data', (data: string) => {\n        this.handleDetectionData(data, window)\n      })\n\n      // Start detection mode\n      this.port.write('start_detection\\n')\n    } catch (error) {\n      console.error('Detection failed:', error)\n      throw error\n    }\n  }\n\n  private handleDetectionData(data: string, window: BrowserWindow) {\n    try {\n      const message = JSON.parse(data)\n      console.log('Received message:', message)\n      switch (message.type) {\n        case 'new_key_press':\n          // Send update to renderer\n          console.log('Sending new_key_press to renderer', message)\n          window.webContents.send('detection-update', message)\n          break\n        case 'existing_key_press':\n          // Send update to renderer\n          console.log('Sending existing_key_press to renderer', message)\n          window.webContents.send('detection-update', message)\n          break\n        case 'used_pins':\n          this.detectionData.diodeDirection = message.direction\n          console.log('Sending used_pins to renderer', message)\n          window.webContents.send('detection-update', message)\n          break\n      }\n    } catch (error) {\n      console.error('Failed to handle detection data:', error)\n    }\n  }\n\n  stopDetection() {\n    if (this.port) {\n      this.port.write('stop_detection\\n')\n      this.port.close()\n      this.port = null\n      this.parser = null\n    }\n  }\n\n  getDetectionData(): DetectionData {\n    return this.detectionData\n  }\n}\n\n// Create detector instance\nconst detector = new KeyboardDetector()\n\n// IPC handlers\nipcMain.handle('start-detection', async (event) => {\n  const window = BrowserWindow.fromWebContents(event.sender)\n  if (window) {\n    await detector.startDetection(window)\n    return { success: true }\n  }\n  throw new Error('Window not found')\n})\n\nipcMain.handle('stop-detection', () => {\n  detector.stopDetection()\n  return { success: true }\n})\n\nipcMain.handle('get-detection-data', () => {\n  return detector.getDetectionData()\n}) "
  },
  {
    "path": "src/main/kmkUpdater.ts",
    "content": "import { appDir, currentKeyboard } from './store'\nimport * as fs from 'fs-extra'\nimport request from 'request'\nimport decompress from 'decompress'\nimport { mainWindow } from './index'\nimport { detectionFirmware } from './pythontemplates/detection'\nimport { writeFile } from 'fs/promises'\nimport { join } from 'path'\nimport { app } from 'electron'\nimport { bootpy } from './pythontemplates/boot'\n\n// downloads kmk to app storage\nexport const updateFirmware = async () => {\n  const versionSha = '5a6669d1da219444e027fb20f57d4f5b3ecdedfe'\n  console.log('updating kmk firmware', appDir, versionSha)\n  const file_url = `https://github.com/KMKfw/kmk_firmware/archive/${versionSha}.zip`\n  const targetPath = appDir + 'kmk.zip'\n  if (!fs.existsSync(appDir)) {\n    fs.mkdirSync(appDir)\n  }\n  // Save variable to know progress\n  let received_bytes = 0\n  let total_bytes = 0\n  mainWindow?.webContents.send('update-kmk-progress', {\n    state: 'downloading',\n    progress: received_bytes / total_bytes\n  })\n\n  const out = fs.createWriteStream(targetPath)\n  // download the newest version on\n  await new Promise<void>((resolve, reject): void => {\n    request\n      .get(file_url)\n      .on('response', (data) => {\n        // Change the total bytes value to get progress later.\n        total_bytes = parseInt(data.headers['content-length']) || 1028312\n        console.log('updated total', total_bytes, data.headers, data.statusCode)\n      })\n      .on('data', (chunk) => {\n        // Update the received bytes\n        received_bytes += chunk.length\n        console.log(total_bytes, received_bytes)\n        mainWindow?.webContents.send('onUpdateFirmwareInstallProgress', {\n          state: 'downloading',\n          progress: received_bytes / total_bytes\n        })\n      })\n      .pipe(out)\n      .on('finish', async () => {\n        console.log('kmk downloaded')\n        mainWindow?.webContents.send('onUpdateFirmwareInstallProgress', {\n          state: 'unpacking',\n          progress: 0\n        })\n        resolve()\n      }).onerror = (err) => {\n      console.error(err)\n      reject()\n    }\n  })\n  // decompress the downloaded zip file\n  await decompress(`${appDir}kmk.zip`, `${appDir}/kmk`)\n    .then((files) => {\n      console.log('kmk decompressed', files.length)\n      mainWindow?.webContents.send('onUpdateFirmwareInstallProgress', {\n        state: 'copying',\n        progress: 0\n      })\n    })\n    .catch((error) => {\n      console.log(error)\n    })\n  // file copy needs to await the decompression\n  try {\n    console.log('moving kmk into keyboard')\n    // write a file to the keyboard with the version sha\n    if (fs.existsSync(`${currentKeyboard.path}/kmk`)) {\n      console.log('removing old kmk folder')\n      const countFilesRecursive = async (dir: string): Promise<number> => {\n        const files = await fs.readdir(dir, { withFileTypes: true })\n        let count = files.length\n\n        for (const file of files) {\n          if (file.isDirectory()) {\n            count += await countFilesRecursive(`${dir}/${file.name}`)\n          }\n        }\n        return count\n      }\n\n      const deleteWithProgress = async (dir: string) => {\n        let processedFiles = 0\n        const totalFiles = await countFilesRecursive(dir)\n\n        const deleteRecursive = async (currentDir: string) => {\n          const currentFiles = await fs.readdir(currentDir, { withFileTypes: true })\n          for (const file of currentFiles) {\n            const filePath = `${currentDir}/${file.name}`\n            if (file.isDirectory()) {\n              await deleteRecursive(filePath)\n            }\n            await fs.promises.rm(filePath, { force: true, recursive: true })\n            processedFiles++\n            mainWindow?.webContents.send('onUpdateFirmwareInstallProgress', {\n              state: 'cleaning', \n              progress: (processedFiles / totalFiles) * 100\n            })\n          }\n        }\n\n        await deleteRecursive(dir)\n      }\n      \n      await deleteWithProgress(`${currentKeyboard.path}/kmk`)\n    }\n    if (!fs.existsSync(`${currentKeyboard.path}/kmk`)) {\n      fs.mkdirSync(`${currentKeyboard.path}/kmk`)\n    }\n    console.log('writing version to keyboard', versionSha)\n    fs.writeFileSync(`${currentKeyboard.path}/kmk/version`, versionSha)\n    console.log('copying kmk to keyboard', `${currentKeyboard.path}/kmk`)\n    const countFiles = async (src: string): Promise<number> => {\n      const files = await fs.readdir(src, { withFileTypes: true })\n      let count = files.length\n\n      for (const file of files) {\n        if (file.isDirectory()) {\n          count += (await countFiles(`${src}/${file.name}`)) - 1 // subtract 1 to not count the directory itself twice\n        }\n      }\n      return count\n    }\n\n    let processedFiles = 0\n    const copyWithProgress = async (src: string, dest: string, totalFiles: number) => {\n      const files = await fs.readdir(src, { withFileTypes: true })\n\n      for (const file of files) {\n        const srcPath = `${src}/${file.name}`\n        const destPath = `${dest}/${file.name}`\n\n        if (file.isDirectory()) {\n          await fs.ensureDir(destPath)\n          await copyWithProgress(srcPath, destPath, totalFiles)\n        } else {\n          console.log(\n            'copying file',\n            destPath,\n            processedFiles,\n            totalFiles,\n            processedFiles / totalFiles\n          )\n          await fs.copy(srcPath, destPath)\n          processedFiles++\n          mainWindow?.webContents.send('onUpdateFirmwareInstallProgress', {\n            state: 'copying',\n            progress: (processedFiles / totalFiles) * 100\n          })\n        }\n      }\n    }\n\n    try {\n      const sourcePath = `${appDir}/kmk/kmk_firmware-${versionSha}/kmk`\n      const totalFiles = await countFiles(sourcePath)\n      await copyWithProgress(sourcePath, `${currentKeyboard.path}/kmk`, totalFiles)\n      mainWindow?.webContents.send('onUpdateFirmwareInstallProgress', {\n        state: 'done',\n        progress: 1,\n        message: 'Firmware updated successfully, to version ' + versionSha\n      })\n    } catch (err) {\n      console.error('Error during copy:', err)\n      mainWindow?.webContents.send('onUpdateFirmwareInstallProgress', {\n        state: 'error',\n        progress: 0\n      })\n    }\n  } catch (err) {\n    console.error(err)\n  }\n}\n\nexport async function flashFirmware(firmwarePath: string): Promise<void> {\n  try {\n    console.log('flashing firmware initial', firmwarePath)\n    // Create detection firmware file\n    const detectionPath = join(firmwarePath, 'code.py')\n    await writeFile(detectionPath, detectionFirmware)\n\n    const bootPath = join(firmwarePath, 'boot.py')\n    await writeFile(bootPath, bootpy)\n    \n    // Wait for the board to restart\n    await new Promise(resolve => setTimeout(resolve, 2000))\n    mainWindow?.webContents.send('onUpdateFirmwareInstallProgress', {\n      state: 'done',\n      progress: 1,\n      message: 'Initiated detection firmware flashed'\n    })\n  } catch (error) {\n    console.error('Failed to flash firmware:', error)\n    throw error\n  }\n}\n"
  },
  {
    "path": "src/main/pythontemplates/boot.ts",
    "content": "export const bootpy = `# boot.py - v1.0.5\nimport usb_cdc\nimport supervisor\nimport storage\nimport microcontroller\n\n# optional\n# supervisor.set_next_stack_limit(4096 + 4096)\nusb_cdc.enable(console=True, data=True)\n# used to identify pog compatible keyboards while scanning com ports\nsupervisor.set_usb_identification(\"Pog\", \"Pog Keyboard\")\n\n# index configs\n# 0 - show usb drive | 0 false, 1 true\nif microcontroller.nvm[0] == 0:\n    storage.disable_usb_drive()\n    storage.remount(\"/\", False)\n`\n"
  },
  {
    "path": "src/main/pythontemplates/code.ts",
    "content": "export const codepy = `# Main Keyboard Configuration - v1.0.0\nimport board\nimport pog\n# check if we just want to run the coord_mappping Assistant\nif pog.coordMappingAssistant:\n    from coordmaphelper import CoordMapKeyboard\n    if __name__ == '__main__':\n        CoordMapKeyboard().go()\n    print(\"Exiting Coord Mapping Assistant Because of an error\")\nelse:\n    from kb import POGKeyboard\n    # set the required features for you keyboard and keymap\n    # add custom ones in the kb.py\n\n    keyboard = POGKeyboard(features=pog.kbFeatures)\n\n    # manage settings for our modules and extensions here\n    keyboard.tapdance.tap_time = 200\n\n    # Keymap\n    import keymap\n    keyboard.keymap = keymap.keymap\n\n    # Encoder Keymap if available\n    if pog.hasEncoders:\n        keyboard.encoder_handler.map = keymap.encoderKeymap\n\n    # Execute the keyboard loop\n    if __name__ == '__main__':\n        keyboard.go()\n`\n"
  },
  {
    "path": "src/main/pythontemplates/coordmaphelper.ts",
    "content": "export const coordmaphelperpy = `# coordmaphelper.py v1.0.1\nimport board\nimport pog\nfrom kb import POGKeyboard\nfrom kmk.keys import KC\nfrom kmk.modules.macros import Press, Release, Tap, Macros\n\nclass CoordMapKeyboard(POGKeyboard):\n    def __init__(self):\n        super().__init__(features=['basic', 'macros'])\n        print(\"Running coord_mapping assistant\")\n        print(\"Press each key to get its coord_mapping value\")\n\n        if not hasattr(pog, 'keyCount') or pog.keyCount == 0:\n            raise ValueError(\"pog.keyCount is not set or is zero\")\n\n        N = pog.keyCount * 2\n        coord_mapping = list(range(N))\n        layer = []\n        print(f\"coord_mapping = {coord_mapping}\")\n        print(f\"Total keys: {N}\")\n\n        for i in range(N):\n            c, r = divmod(i, 100)\n            d, u = divmod(r, 10)\n            print(f\"Adding key {i} ({c}{d}{u})\")\n            try:\n                layer.append(\n                    KC.MACRO(\n                        Tap(getattr(KC, f\"N{c}\")),\n                        Tap(getattr(KC, f\"N{d}\")),\n                        Tap(getattr(KC, f\"N{u}\")),\n                        Tap(KC.SPC),\n                    )\n                )\n            except AttributeError as e:\n                print(f\"Error creating macro for key {i}: {e}\")\n\n        if not layer:\n            raise ValueError(\"No keys were added to the layer\")\n\n        print(f\"Layer created with {len(layer)} keys\")\n        self.keymap = [layer]\n        self.coord_mapping = coord_mapping\n        print(f\"Keymap initialized with {len(self.keymap[0])} keys\")\n`\n"
  },
  {
    "path": "src/main/pythontemplates/customkeys.ts",
    "content": "export const customkeyspy = `# These are yous custom keycodes do any needed imports at the top - v1.0.0\n# then you can reference them in your keymap with for example customkeys.MyKey\n\nfrom kmk.keys import KC\nfrom kmk.modules.macros import Tap, Release, Press\nimport microcontroller\n\n# Here you can define your custom keys\n# MyKey = KC.X\n\n# Builtin macros for use in pog\ndef next_boot_dfu(keyboard):\n    print('setting next boot to dfu') #serial feedback\n    microcontroller.on_next_reset(microcontroller.RunMode.UF2)\n\nDFUMODE = KC.MACRO(next_boot_dfu)\n\ndef next_boot_safe(keyboard):\n    print('setting next boot to safe') #serial feedback\n    microcontroller.on_next_reset(microcontroller.RunMode.SAFE_MODE)\nSAFEMODE = KC.MACRO(next_boot_safe)\n\ndef toggle_drive(keyboard):\n    print('toggling usb drive') #serial feedback\n    if microcontroller.nvm[0] == 0:\n        microcontroller.nvm[0] = 1\n    else:\n        microcontroller.nvm[0] = 0\n\nToggleDrive = KC.MACRO(toggle_drive)`\n"
  },
  {
    "path": "src/main/pythontemplates/detection.ts",
    "content": "export const detectionFirmware = `import board\nimport digitalio\nimport time\nimport supervisor\nimport usb_cdc\nimport json\n\n# Initialize empty lists for pins and their IOs\npin_names = []\nios = []\n\nprint(\"Scanning for available GPIO pins...\")\n\n# Iterate over all attributes of the board module\nfor pin_name in dir(board):\n    if pin_name.startswith('GP'):  # We are only interested in GPIO pins\n        try:\n            pin = getattr(board, pin_name)\n            # Try to initialize the pin as a digital input\n            io = digitalio.DigitalInOut(pin)\n            io.switch_to_input(pull=digitalio.Pull.UP)\n            pin_names.append(pin_name)  # If successful, add it to the list\n            ios.append(io)  # Keep the IO object\n        except (AttributeError, ValueError):\n            print(f\"Skipping {pin_name} (not available)\")\n            continue\n\nif not pin_names:\n    print(\"No usable GPIO pins found!\")\n    while True:\n        pass\n\npin_names.sort()  # Sort them for consistent ordering\nprint(f\"Found usable pins: {', '.join(pin_names)}\")\n\n# Create pins list with references\npins = list(zip([getattr(board, name) for name in pin_names], pin_names))\n\n# Track connections for each pin\nrow_connections = {}  # pins that act as rows\ncol_connections = {}  # pins that act as columns\n# Initialize empty sets for each pin\nfor pin_name in pin_names:\n    row_connections[pin_name] = set()\n    col_connections[pin_name] = set()\n\ndef read_pin_reliable(pin):\n    # Read the pin 3 times with a small delay between reads\n    readings = []\n    for _ in range(3):\n        readings.append(not pin.value)  # not pin.value because True means pressed\n        # time.sleep(0.001)\n    # Return True only if all readings indicate pressed\n    return all(readings)\n\ndef print_connections(pin_name):\n    print(f\"Connections for {pin_name}:\")\n    if row_connections[pin_name]:  # Only print if there are connections\n        print(f\"As Row -> Columns: {sorted(row_connections[pin_name])}\")\n    if col_connections[pin_name]:  # Only print if there are connections\n        print(f\"As Column <- Rows: {sorted(col_connections[pin_name])}\")\n\nprint(\"Starting diode direction test\")\nprint(\"Press any key to exit\")\nprint(\"Connect switches between pins to test...\")\n\ndata_serial = usb_cdc.data\nif not data_serial:\n    supervisor.reload()\ntry:\n    data_serial.write(json.dumps({\n                                'type': 'start_detection',\n                                'pins': pin_names,\n                            }).encode() + b'\\\\n')\n    while True:  # Exit on any key press\n        for row_idx in range(len(pins)):\n            # Set current pin as row (output low)\n            ios[row_idx].switch_to_output(value=False)\n            \n            # Test all other pins as columns\n            for col_idx in range(len(pins)):\n                if col_idx != row_idx:\n                    ios[col_idx].switch_to_input(pull=digitalio.Pull.UP)\n                    time.sleep(0.001)  # Small delay for pin to settle\n                    \n                    if read_pin_reliable(ios[col_idx]):  # Key is pressed (confirmed by 3 readings)\n                        row_pin = pins[row_idx][1]\n                        col_pin = pins[col_idx][1]\n                        print(f\"New key press detected! Direction: ({row_pin}->{col_pin})\")\n                        print_connections(row_pin)\n                        print_connections(col_pin)\n                        # Check if this is a new connection\n                        if col_pin not in row_connections[row_pin]:\n                            data_serial.write(json.dumps({\n                                'type': 'new_key_press',\n                                'row': row_pin,\n                                'col': col_pin,\n                            }).encode() + b'\\\\n')\n                            row_connections[row_pin].add(col_pin)\n                            col_connections[col_pin].add(row_pin)\n                        else:\n                            data_serial.write(json.dumps({\n                                'type': 'existing_key_press',\n                                'row': row_pin,\n                                'col': col_pin,\n                            }).encode() + b'\\\\n')\n                        data_serial.write(json.dumps({\n                            'type': 'used_pins',\n                            'rows': sorted(row_connections[row_pin]),\n                            'cols': sorted(col_connections[col_pin]),\n                        }).encode() + b'\\\\n')\n                           \n                    ios[col_idx].switch_to_input(pull=digitalio.Pull.UP)  # Reset column pin\n            \n            # Reset row pin\n            ios[row_idx].switch_to_input(pull=digitalio.Pull.UP)\n            # time.sleep(0.001)\n        \n        # time.sleep(0.01)  # Small delay between full matrix scans\n\nfinally:\n    # Clean up\n    print(\"Cleaning up...\")\n    for io in ios:\n        io.deinit()\n    \n    print(\"Final connection summary:\")\n    for pin_name in pin_names:\n        if row_connections[pin_name] or col_connections[pin_name]:\n            print_connections(pin_name)\n`\n"
  },
  {
    "path": "src/main/pythontemplates/kb.ts",
    "content": "export const kbpy = `# kb.py KB base config - v1.0.0\nimport board\nimport pog\nimport microcontroller\n\nfrom kmk.kmk_keyboard import KMKKeyboard\nfrom kmk.scanners import DiodeOrientation\nfrom kmk.scanners.keypad import KeysScanner\n\nclass POGKeyboard(KMKKeyboard):\n    def __init__(self, features=['basic']):\n        super().__init__()\n        if \"basic\" in features:\n            from kmk.modules.layers import Layers;\n            combo_layers = {\n            # combolayers can be added here\n            # (1, 2): 3,\n            }\n            self.modules.append(Layers(combo_layers))\n            from kmk.extensions.media_keys import MediaKeys; self.extensions.append(MediaKeys())\n        if \"international\" in features:\n            from kmk.extensions.international import International\n            self.extensions.append(International())\n        if \"serial\" in features:\n            from pog_serial import pogSerial; self.modules.append(pogSerial())\n\n        if \"oneshot\" in features:\n            from kmk.modules.sticky_keys import StickyKeys\n            sticky_keys = StickyKeys()\n            # optional: set a custom release timeout in ms (default: 1000ms)\n            # sticky_keys = StickyKeys(release_after=5000)\n            self.modules.append(sticky_keys)\n\n\n        if \"tapdance\" in features:\n            from kmk.modules.tapdance import TapDance\n            self.tapdance = TapDance()\n            self.modules.append(self.tapdance)\n\n        if \"holdtap\" in features:\n            from kmk.modules.holdtap import HoldTap; self.modules.append(HoldTap())\n\n        if \"mousekeys\" in features:\n            from kmk.modules.mouse_keys import MouseKeys; self.modules.append(MouseKeys())\n\n        if \"combos\" in features:\n            from kmk.modules.combos import Combos, Chord, Sequence\n            self.combos = Combos()\n            self.modules.append(self.combos)\n\n        # if \"macros\" in features:\n        from kmk.modules.macros import Macros\n        self.macros = Macros()\n        self.modules.append(self.macros)\n\n        # TODO: not tested yet\n        if \"capsword\" in features:\n            from kmk.modules.capsword import CapsWord\n            self.capsword = CapsWord()\n            self.modules.append(self.capsword)\n\n        if pog.config['split']:\n            from kmk.modules.split import Split, SplitSide, SplitType\n\n            # Split Side Detection\n            if pog.splitSide == \"label\":\n                from storage import getmount\n                side = SplitSide.RIGHT if str(getmount('/').label)[-1] == 'R' else SplitSide.LEFT\n            if pog.splitSide == \"vbus\":\n                import digitalio\n\n                vbus = digitalio.DigitalInOut(pog.vbusPin)\n                vbus.direction = digitalio.Direction.INPUT\n                side = SplitSide.RIGHT if vbus.value == False else SplitSide.LEFT\n            if pog.splitSide == \"left\" or pog.splitSide == \"right\":\n                side = SplitSide.RIGHT if pog.splitSide == \"right\" else SplitSide.LEFT\n\n            # Split Type Configuration\n            if pog.keyboardType == \"splitBLE\":\n                print(\"split with 2 pins\")\n                self.split = Split(\n                    split_type=SplitType.BLE,\n                    split_side=side,\n                    split_flip=pog.splitFlip)\n            elif pog.keyboardType == \"splitSerial\":\n                print(\"split with 2 pins (UART)\")\n                self.split = Split(\n                    split_side=side,\n                    split_target_left=pog.splitTargetLeft,\n                    split_type=SplitType.UART,\n                    data_pin=pog.splitPinA,\n                    data_pin2=pog.splitPinB,\n                    use_pio=pog.splitUsePio,\n                    split_flip=pog.splitFlip,\n                    uart_flip=pog.splitUartFlip)\n            else:\n                # Nested under pog.split == True => splitOnewire\n                print('split with 1 pin')\n                self.split = Split(\n                    split_side=side,\n                    split_target_left=pog.splitTargetLeft,\n                    data_pin=pog.splitPinA,\n                    use_pio=pog.splitUsePio,\n                    split_flip=pog.splitFlip)\n\n            self.modules.append(self.split)\n\n        # Add your own modules and extensions here\n        # or sort them into the correct spot to have the correct import order\n\n\n        # Encoders\n        if pog.hasEncoders:\n            from kmk.modules.encoder import EncoderHandler\n            self.encoder_handler = EncoderHandler()\n            self.encoder_handler.pins = pog.encoders\n            self.modules.append(self.encoder_handler)\n\n        if \"rgb\" in features:\n            from kmk.extensions.RGB import RGB\n            rgb = RGB(\n                pixel_pin=eval(pog.rgbPin),\n                num_pixels=pog.rgbNumLeds,\n                rgb_order=(1, 0, 2),\n                val_limit=40, # Maximum brightness level. Only change if you know what you are doing!\n                hue_default=pog.rgbOptions[\"hueDefault\"],\n                sat_default=pog.rgbOptions[\"satDefault\"],\n                val_default=pog.rgbOptions[\"valDefault\"],\n                animation_speed=pog.rgbOptions[\"animationSpeed\"],\n                animation_mode=pog.rgbOptions[\"animationMode\"],\n                breathe_center=pog.rgbOptions[\"breatheCenter\"],\n                knight_effect_length=pog.rgbOptions[\"knightEffectLength\"],\n            )\n            self.extensions.append(rgb)\n\n        # direct pin wiring\n        # Must be set during init to override defaulting to matrix wiring\n        if pog.directWiring:\n            self.matrix = KeysScanner(\n                pins=pog.pins_tuple,\n                value_when_pressed=False,\n                pull=True,\n                interval=0.02,\n                max_events=64\n            )\n\n        # matrix wiring\n        if pog.matrixWiring:\n            self.col_pins = pog.col_pins_tuple\n            self.row_pins = pog.row_pins_tuple\n            self.diode_orientation = DiodeOrientation.ROW2COL if pog.config[\"diodeDirection\"] == \"ROW2COL\" else DiodeOrientation.COL2ROW\n\n        # coord_mapping\n        if len(pog.config[\"coordMap\"]) != 0:\n            self.coord_mapping = [int(val) for val in pog.coordMapping.split(\",\")[:-1]]\n\n\n`\n"
  },
  {
    "path": "src/main/pythontemplates/keymap.ts",
    "content": "export const keymappy = `#keymap.py KB base config - v1.0.0\nfrom kmk.keys import KC\nfrom kmk.modules.macros import Macros, Press, Release, Tap, Delay\nfrom kmk.modules.combos import Chord, Sequence\nimport pog\nimport customkeys\n\nkeymap = []\nfor l, layer in enumerate(pog.config['keymap']):\n    layerKeymap = []\n    for k, key in enumerate(layer):\n        layerKeymap.append(eval(key))\n    keymap.append(tuple(layerKeymap))\n\nencoderKeymap = []\nfor l, layer in enumerate(pog.config['encoderKeymap']):\n    layerEncoders = []\n    for e, encoder in enumerate(layer):\n        layerEncoders.append(tuple(map(eval, encoder)))\n    encoderKeymap.append(tuple(layerEncoders))\n`\n"
  },
  {
    "path": "src/main/pythontemplates/pog.ts",
    "content": "export const pogpy = `# pog.py Import the pog config - v0.9.5\nimport json\nimport board\nfrom kmk.keys import KC\nimport microcontroller\n\nconfig = {}\nconfigbuffer = bytearray()\nconfigbufferlen = 0\ntry:\n    with open(\"/pog.json\", \"r\") as fp:\n        x = fp.read()\n        # parse x:\n        config = json.loads(x)\n        configbuffer = json.dumps(config)\n        configbufferlen = len(configbuffer)\nexcept OSError as e:\n    microcontroller.nvm[0] = 1\n    raise Exception(\"Could not read pog.json file. mounting drive\")\n\nprint(\"starting keyboard %s (%s)\" % (config[\"name\"], config[\"id\"]))\n\ndef pinValid(pin):\n    if pin == \"\":\n        return False\n    if config[\"pinPrefix\"] == \"quickpin\":\n        pin = f'{eval(pin)}'\n    if pin in [f'board.{alias}' for alias in dir(board)]:\n        return True\n    else:\n        print(f'INVALID PIN FOUND {pin}')\n        return False\n\n# Pin setup\ndef renderPin(pin):\n    pinLabel = ''\n    if config[\"pinPrefix\"] == \"gp\":\n        pinLabel = \"board.GP\" + pin\n    elif config[\"pinPrefix\"] == \"board\":\n        pinLabel = \"board.\" + pin\n    elif config[\"pinPrefix\"] == \"quickpin\":\n        pinLabel = \"pins[\" + pin + \"]\"\n    else:\n        pinLabel = pin\n    if pinValid(pinLabel):\n        return pinLabel\n\n\n\ncolPinsArray = []\nfor i, item in enumerate(config[\"colPins\"]):\n    colPinsArray.append(renderPin(item))\n# Remove the 'None's from the list of pins\ncolPinsArray = [pin for pin in colPinsArray if pin is not None]\ncolPins = \",\".join(colPinsArray)\nif len(colPinsArray) == 1:\n    colPins = colPins + \",\"\n# Create actual tuple of pin objects for direct use\ncol_pins_tuple = tuple(eval(pin) for pin in colPinsArray)\n\nrowPinsArray = []\nfor i, item in enumerate(config[\"rowPins\"]):\n    rowPinsArray.append(renderPin(item))\n# Remove the 'None's from the list of pins\nrowPinsArray = [pin for pin in rowPinsArray if pin is not None]\nrowPins = \",\".join(rowPinsArray)\nif len(rowPinsArray) == 1:\n    rowPins = rowPins + \",\"\n# Create actual tuple of pin objects for direct use\nrow_pins_tuple = tuple(eval(pin) for pin in rowPinsArray)\n\npinsArray = []\nfor i, item in enumerate(config[\"directPins\"]):\n    pinsArray.append(renderPin(item))\n# Remove the 'None's from the list of pins\npinsArray = [pin for pin in pinsArray if pin is not None]\npins = \",\".join(pinsArray)\nif len(pinsArray) == 1:\n    pins = pins + \",\"\n# Create actual tuple of pin objects for direct use\npins_tuple = tuple(eval(pin) for pin in pinsArray)\n\nkbFeatures = config.get('kbFeatures')\n\nrgbPin = config[\"rgbPin\"] if pinValid(config[\"rgbPin\"]) else None\nrgbNumLeds = config[\"rgbNumLeds\"]\nrgbOptions = config[\"rgbOptions\"]\nif not config[\"rgbOptions\"] and \"rgb\" in kbFeatures:\n    print(\"rgbOptions not set when rgb is needed\")\n\n\nmatrixWiring = False\ndirectWiring = False\n\nif config['wiringMethod'] == 'matrix':\n    matrixWiring = True\n    keyCount = len(rowPinsArray) * len(colPinsArray)\n    print(f\"Matrix wiring: rows={rowPins} cols={colPins}\")\nelse:\n    directWiring = True\n    keyCount = len(pinsArray)\n    print(f\"Direct wiring: pins={pins}\")\n\n\n# encoders\nhasEncoders = len(config['encoders']) != 0\n\nencoderArray = []\nfor i, item in enumerate(config[\"encoders\"]):\n    encoderArray.append([eval(renderPin(item['pad_a'])), eval(renderPin(item['pad_b'])), None])\nencoderTupleArray = []\nfor i, item in enumerate(encoderArray):\n    encoderTupleArray.append(tuple(item))\nencoders = tuple(encoderTupleArray)\n\n\n# coord map\ncoordMappingAssistant = config['coordMapSetup']\ndef convert_coord_mapping():\n    if not config.get(\"coordMap\"):\n        return \"\"\n    str = \"\"\n    for row in config[\"coordMap\"]:\n        str += \"    \" + \",\".join(val for val in row)+\",\"\n    return str.replace(\"spc,\", \"    \")\n\n\ncoordMapping = convert_coord_mapping()\n\nkeyboardType = None\nif config.get('keyboardType'):\n    keyboardType = config['keyboardType']\n\nsplitSide = None\nif config.get('splitSide'):\n    splitSide = config['splitSide']\n\nsplitTargetLeft = None\nif config.get('splitTargetLeft'):\n    splitTargetLeft = config['splitTargetLeft']\n\nsplitPinA = None\nsplitPinB = None\nif config.get('splitPinA'):\n    splitPinA = eval(renderPin(config['splitPinA']))\nif config.get('splitPinB'):\n    splitPinB = eval(renderPin(config['splitPinB']))\nsplitUsePio = config.get('splitUsePio')\nsplitFlip = config.get('splitFlip')\nsplitUartFlip = config.get('splitUartFlip')\n\nvbusPin = None\nif config.get('vbusPin') and config.get('splitSide') == 'vbus' and pinValid(\"board.\" + config['vbusPin']):\n    vbusPin = eval(\"board.\" + config['vbusPin'])\n\n# led pin without prefix for now\nif config.get('ledPin'):\n  ledPin = eval(config.get('ledPin'))\n  ledLength = int(config.get('ledLength'))\n\n`\n"
  },
  {
    "path": "src/main/pythontemplates/pog_serial.ts",
    "content": "export const pog_serialpy = `# pog_serial module - v0.9.5\nfrom usb_cdc import data\nfrom kmk.modules import Module\nfrom kmk.utils import Debug\nimport pog\nimport json\nimport gc\nimport time\nimport microcontroller\nimport os\nimport supervisor\nimport math\nimport board\n\ndebug = Debug(__name__)\n\naction = \"\"\nchunkindex = 0\n\ndef sendConfig():\n    def cross_sum(s):\n        \"\"\"\n        Returns the cross sum of a string, where each character is mapped to its Unicode code point.\n        \"\"\"\n        # Compute the cross sum\n        total = 0\n        for c in s:\n            total += ord(c)\n\n        return total\n    global action\n    global chunkindex\n    print('writing chunk', chunkindex)\n\n    chunksize = 800\n    chunk_count = pog.configbufferlen / chunksize\n    if chunkindex > chunk_count:\n        return\n    chunk = (json.dumps({\n        'type': 'pogconfig',\n        'current_chunk': chunkindex + 1, # start at 1 for the first chunk\n        'total_chunks': math.ceil(chunk_count), # only show full chunks\n        'data': pog.configbuffer[chunksize*chunkindex:chunksize*chunkindex+chunksize],\n        'totalsize': pog.configbufferlen,\n        'cross_sum': cross_sum(pog.configbuffer[chunksize*chunkindex:chunksize*chunkindex+chunksize])\n    })+\"\\\\n\").encode()\n    print(chunk)\n    wrote = data.write(chunk)\n    print('wrote', wrote)\n\ndef readConfigChunk(line):\n    global action\n    global chunkindex\n    lasttime = time.monotonic_ns()\n    jsondata = json.loads(line)\n    print('json loadin chunk', jsondata['current_chunk'] ,jsondata['total_chunks'],time.monotonic_ns() - lasttime)\n    lasttime = time.monotonic_ns()\n\n    #tmpConfigFile = open('received_file.json', 'a')\n    #tmpConfigFile.write(jsondata['data'])\n    # print(jsondata['current_chunk'])\n    tmpConfigFile = open('received_file.json', 'a')\n    print('open file',time.monotonic_ns() - lasttime)\n    lasttime = time.monotonic_ns()\n    # print('saving to file', line)\n    tmpConfigFile.write(jsondata['data'])\n    tmpConfigFile.close()\n\n    print('writing', jsondata['current_chunk'], \"of\", jsondata['total_chunks'])\n    if jsondata['total_chunks'] == jsondata['current_chunk']:\n        print('done with reading the pog.config')\n        data.write('y\\\\n'.encode())\n        action = \"\"\n        chunkindex = 0\n        try:\n            jsonfile = open('received_file.json', 'r')\n            json.loads(jsonfile.read())\n            jsonfile.close()\n            print('file close')\n            # set as new pog.json\n            os.rename('/pog.json','/pog.json.bk')\n            os.rename('/received_file.json','/pog.json')\n        except ValueError as err:\n            print('sent file is not valid json', err)\n    else:\n        data.write('1\\\\n'.encode())\n\ndef readKeymapChunk(line):\n    global action\n    jsondata = json.loads(line)\n    print('json loadin chunk', jsondata['current_chunk'] ,jsondata['total_chunks'])\n    tmpConfigFile = open('received_file.py', 'a')\n    print('open file')\n    tmpConfigFile.write(jsondata['data'])\n    tmpConfigFile.close()\n    print('writing', jsondata['current_chunk'], \"of\", jsondata['total_chunks'])\n    if jsondata['total_chunks'] == jsondata['current_chunk']:\n        print('done with reading the pog.config')\n        data.write('y\\\\n'.encode())\n        action = \"\"\n        os.rename('/keymap.py','/keymap.py.bk')\n        os.rename('/received_file.py','/keymap.py')\n    else:\n        data.write('1\\\\n'.encode())\n\nclass pogSerial(Module):\n    buffer = bytearray()\n\n    def during_bootup(self, keyboard):\n        try:\n            data.timeout = 0\n        except AttributeError:\n            pass\n\n    def before_matrix_scan(self, keyboard):\n        pass\n\n    def after_matrix_scan(self, keyboard):\n        pass\n\n    def process_key(self, keyboard, key, is_pressed, int_coord):\n        return key\n\n    def before_hid_send(self, keyboard):\n        # Serial.data isn't initialized.\n        if not data:\n            return\n        # Nothing to parse.\n        if data.in_waiting == 0:\n            return\n        self.buffer.extend(data.read(64))\n        idx = self.buffer.find(b'\\\\n')\n        # No full command yet.\n        if idx == -1:\n            return\n\n        print('got serial request')\n\n        try:\n            line = (self.buffer[:idx]).decode('utf-8')\n            self.buffer = self.buffer[idx + 1 :]\n            global action\n            global chunkindex\n            if action == 'readConfig':\n                print('data transmit mode: reading config file in chunks')\n                readConfigChunk(line)\n            elif action == 'readKeymap':\n                print('data transmit mode: reading keymap file in chunks')\n                readKeymapChunk(line)\n            else:\n                split = line.split()\n                if split[0] == 'info':\n                    # print keyboard info\n                    action = 'info'\n                    chunkindex = 0\n                    print('query keyboard info from serial')\n                    sendConfig()\n                if split[0] == 'info_simple':\n                    # print basic keyboard info\n                    print('getting basic keyboard info')\n                    data.write((json.dumps({\"driveMounted\": microcontroller.nvm[0]!=0 ,\"name\": pog.config['name'], \"manufacturer\": pog.config['manufacturer'], \"id\": pog.config['id'], \"board\": dir(board) })+\"\\\\n\").encode())\n                if split[0] == 'save':\n                    # read chunks\n                    file_to_delete = open(\"received_file.json\",'w')\n                    file_to_delete.close()\n                    print('start reading chunks')\n                    action = \"readConfig\"\n                    data.write('1\\\\n'.encode())\n                if split[0] == 'saveKeymap':\n                    # read chunks\n                    file_to_delete = open(\"received_file.py\",'w')\n                    file_to_delete.close()\n                    print('start reading chunks')\n                    action = \"readKeymap\"\n                    data.write('1\\\\n'.encode())\n                if split[0] == 'reset':\n                    microcontroller.reset()\n                if split[0] == 'drive':\n                    if microcontroller.nvm[0] == 0:\n                        microcontroller.nvm[0] = 1\n                    else:\n                        microcontroller.nvm[0] = 0\n                    print('toggling Drive to', microcontroller.nvm[0])\n                if split[0] == '1' or split[0] == '0':\n                    # contine chunk\n                    if split[0] == '1':\n                        chunkindex += 1\n                    if action == 'info':\n                        sendConfig()\n                if split[0] == 'y':\n                    print('resetting action')\n                    action = \"\"\n                    chunkindex = 0\n\n\n        except Exception as err:\n            debug(f'error: {err}')\n\n    def after_hid_send(self, keyboard):\n        pass\n\n    def on_powersave_enable(self, keyboard):\n        pass\n\n    def on_powersave_disable(self, keyboard):\n        pass\n\n`\n"
  },
  {
    "path": "src/main/saveConfig.ts",
    "content": "import * as fs from 'fs-extra'\nimport { currentKeyboard } from './store'\nimport { pogpy } from './pythontemplates/pog'\nimport { coordmaphelperpy } from './pythontemplates/coordmaphelper'\nimport { customkeyspy } from './pythontemplates/customkeys'\nimport { kbpy } from './pythontemplates/kb'\nimport { codepy } from './pythontemplates/code'\nimport { bootpy } from './pythontemplates/boot'\nimport { pog_serialpy } from './pythontemplates/pog_serial'\nimport { keymappy } from './pythontemplates/keymap'\nimport { writePogConfViaSerial, mainWindow } from './index'\n\nexport const saveConfiguration = async (data: string) => {\n  const { pogConfig, serial, writeFirmware } = JSON.parse(data)\n  if (serial) {\n    // write by serial to current keyboard\n    console.log('writing firmware via usb serial')\n    writePogConfViaSerial(JSON.stringify(pogConfig, null, 0))\n    return\n  }\n\n  // write via mounted USB drive\n  console.log('writing firmware via usb files', 'overwriting Firmware:', writeFirmware)\n\n  type WriteTask = { name: string; path: string; contents: string }\n  const tasks: WriteTask[] = []\n\n  // Always write pog.json\n  tasks.push({\n    name: 'pog.json',\n    path: currentKeyboard.path + '/pog.json',\n    contents: JSON.stringify(pogConfig, null, 4)\n  })\n\n  const files = [\n    { name: 'pog.py', contents: pogpy },\n    { name: 'code.py', contents: codepy },\n    { name: 'coordmaphelper.py', contents: coordmaphelperpy },\n    { name: 'customkeys.py', contents: customkeyspy },\n    { name: 'boot.py', contents: bootpy },\n    { name: 'pog_serial.py', contents: pog_serialpy },\n    { name: 'keymap.py', contents: keymappy },\n    { name: 'kb.py', contents: kbpy }\n  ]\n\n  for (const file of files) {\n    const targetPath = currentKeyboard.path + '/' + file.name\n    if (!fs.existsSync(targetPath) || writeFirmware) {\n      tasks.push({ name: file.name, path: targetPath, contents: file.contents })\n    }\n  }\n\n  const total = tasks.length\n  let completed = 0\n\n  for (const task of tasks) {\n    try {\n      await fs.promises.writeFile(task.path, task.contents)\n      completed += 1\n      mainWindow?.webContents.send('save-configuration-progress', {\n        state: 'writing',\n        filename: task.name,\n        completed,\n        total\n      })\n    } catch (e) {\n      console.error(`error writing ${task.name}`, e)\n      mainWindow?.webContents.send('save-configuration-progress', {\n        state: 'error',\n        filename: task.name,\n        completed,\n        total\n      })\n    }\n  }\n\n  mainWindow?.webContents.send('save-configuration-progress', {\n    state: 'done',\n    completed,\n    total\n  })\n}\n"
  },
  {
    "path": "src/main/selectKeyboard.ts",
    "content": "import * as fs from 'fs-extra'\nimport { currentKeyboard } from './store'\nimport { dialog } from 'electron'\nimport { connectedKeyboardPort, connectSerialKeyboard, serialBoards } from './index'\nimport * as serialPort from 'serialport'\n\n// invoked from frontend to select a drive or folder load the conig from\nexport const handleSelectDrive = async () => {\n  const { canceled, filePaths } = await dialog.showOpenDialog({\n    properties: ['openDirectory']\n  })\n  if (canceled) return\n  return await loadKeyboard(filePaths[0])\n}\nconst loadKeyboard = async (path) => {\n  if (!fs.existsSync(`${path}`)) {\n    return { error: 'pathNotFound' }\n  }\n  const folderContents = await fs.promises.readdir(`${path}`)\n  // check for kmk, code.py and boot.py\n  currentKeyboard.path = path\n  let codeContents: string | undefined = undefined\n  if (folderContents.includes('code.py')) {\n    codeContents = await fs.promises.readFile(`${currentKeyboard.path}/code.py`, {\n      encoding: 'utf8',\n      flag: 'r'\n    })\n  }\n  let configContents = undefined\n  if (folderContents.includes('pog.json')) {\n    configContents = JSON.parse(\n      await fs.promises.readFile(`${currentKeyboard.path}/pog.json`, {\n        encoding: 'utf8',\n        flag: 'r'\n      })\n    )\n  }\n  console.log('found something', folderContents)\n  return {\n    path,\n    folderContents,\n    codeContents,\n    configContents\n  }\n}\nexport const selectKeyboard = async ({ path, id }: { path: string; id: string }) => {\n  console.log('Selecting keyboard:', path, id)\n  if (id) {\n    // connect serial if available\n    const port = serialBoards.value.find((a) => a.id === id)\n    if (!port) return { error: 'not a serial keyboard' }\n    console.log('Found serial board:', serialBoards, id)\n\n    // Find both ports for this keyboard\n    const allPorts = await serialPort.SerialPort.list()\n    const keyboardPorts = allPorts\n      .filter(p => p.serialNumber === port.serialNumber)\n      .sort((a, b) => a.path.localeCompare(b.path))\n\n    if (keyboardPorts.length >= 2) {\n      currentKeyboard.serialPortA = keyboardPorts[0].path\n      currentKeyboard.serialPortB = keyboardPorts[1].path\n      currentKeyboard.serialNumber = port.serialNumber\n      console.log('Set keyboard ports:', currentKeyboard.serialPortA, currentKeyboard.serialPortB)\n    }\n\n    await connectSerialKeyboard(port)\n    connectedKeyboardPort.write('info\\n')\n  }\n  if (path) {\n    console.log('checking keyboard files for', path)\n    return await loadKeyboard(path)\n  } else if (id) {\n    console.log('connecting serial keyboard')\n    return { success: true }\n  }\n  return { error: 'not all args provided' }\n}\n\nexport const checkForUSBKeyboards = async (keyboardPaths: string[]) => {\n  console.log('checking for usb keyboards', keyboardPaths)\n  // check for each path in the filesystem if it exists\n  const connectedKeyboards: { path: string; connected: boolean }[] = []\n  for (const path of keyboardPaths) {\n    if (fs.existsSync(path)) {\n      connectedKeyboards.push({\n        path,\n        connected: true\n      })\n    }\n  }\n  return connectedKeyboards\n}\n"
  },
  {
    "path": "src/main/store.ts",
    "content": "// Store for global variables\nimport { app } from 'electron'\n\nexport const appDir = app.getPath('appData') + '/pog/'\n\ninterface Keyboard {\n  path: string\n  name: string\n  id: string\n  usingSerial?: boolean\n  serialPortA?: string\n  serialPortB?: string\n  serialNumber?: string\n}\n\nexport const currentKeyboard: Keyboard = {\n  path: '',\n  name: '',\n  id: '',\n  usingSerial: false,\n  serialPortA: '',\n  serialPortB: '',\n  serialNumber: ''\n}\n"
  },
  {
    "path": "src/preload/index.d.ts",
    "content": "import { ElectronAPI } from '@electron-toolkit/preload'\n\n\nexport interface IElectronAPI {\n  // Keyboard History API\n  listKeyboards: () => Promise<\n    Array<{\n      id: string\n      name: string\n      path: string\n      usingSerial?: boolean\n    }>\n  >\n  keyboardScan: (callback: (event: Event, value: { keyboards: any[] }) => void) => void\n  serialKeyboardPogConfig: (callback: (event: Event, value: { pogconfig: any }) => void) => void\n\n  // Drive and Firmware API\n  listDrives: () => Promise<\n    Array<{\n      path: string\n      name: string\n      isReadOnly: boolean\n      isRemovable: boolean\n      isSystem: boolean\n      isUSB: boolean\n      isCard: boolean\n    }>\n  >\n\n  flashDetectionFirmware: (params: {\n    drivePath: string\n    serialNumber?: string\n  }) => Promise<{ success: boolean }>\n\n  // Serial Port API\n  serialPorts: () => Promise<\n    Array<{\n      port: string\n      manufacturer?: string\n      serialNumber?: string\n    }>\n  >\n  serialConnect: (port: string) => Promise<void>\n  serialDisconnect: () => Promise<void>\n  serialData: (callback: (event: any, data: { message: string }) => void) => void\n  offSerialData: (callback: (event: any, data: { message: string }) => void) => void\n  serialConnectionStatus: (\n    callback: (event: any, data: { connected: boolean; error?: string }) => void\n  ) => void\n  serialSend: (message: string) => void\n\n  // Keyboard Detection API\n  startDetection: () => Promise<{ success: boolean }>\n  stopDetection: () => Promise<{ success: boolean }>\n  getDetectionData: () => Promise<{\n    rows: string[]\n    cols: string[]\n    diodeDirection: 'COL2ROW' | 'ROW2COL'\n    pressedKeys: { row: number; col: number }[]\n  }>\n  onDetectionUpdate: (callback: (data: any, event: any) => void) => void\n  removeDetectionListeners: () => void\n  onUpdateFirmwareInstallProgress: (callback: (data: any, event: any) => void) => void\n  onSaveConfigurationProgress: (\n    callback: (event: any, data: { state: 'writing' | 'done' | 'error'; filename?: string; completed: number; total: number }) => void\n  ) => void\n  offSaveConfigurationProgress: (\n    callback: (event: any, data: { state: 'writing' | 'done' | 'error'; filename?: string; completed: number; total: number }) => void\n  ) => void\n\n  // Legacy API (to be migrated)\n  selectKeyboard: (data: any) => Promise<any>\n  deselectKeyboard: () => Promise<void>\n  openExternal: (url: string) => Promise<void>\n  selectDrive: () => Promise<any>\n  updateFirmware: () => Promise<void>\n  saveConfiguration: (data: any) => Promise<void>\n  rescanKeyboards: () => Promise<void>\n  checkForUSBKeyboards: (keyboardPaths: string[]) => Promise<any>\n}\n\nexport declare global {\n  interface Window {\n    electron: ElectronAPI\n    api: IElectronAPI\n  }\n}\n"
  },
  {
    "path": "src/preload/index.ts",
    "content": "import { contextBridge, ipcRenderer } from 'electron'\nimport { electronAPI } from '@electron-toolkit/preload'\n\n// Custom APIs for renderer\nconst api = {\n  selectDrive: () => ipcRenderer.invoke('selectDrive'),\n  updateFirmware: () => ipcRenderer.invoke('updateFirmware'),\n  saveConfiguration: (data) => ipcRenderer.send('saveConfiguration', data),\n  selectKeyboard: (data) => ipcRenderer.invoke('selectKeyboard', data),\n  onUpdateFirmwareInstallProgress: (callback) =>\n    ipcRenderer.on('onUpdateFirmwareInstallProgress', callback),\n  onSaveConfigurationProgress: (callback) =>\n    ipcRenderer.on('save-configuration-progress', callback),\n  offSaveConfigurationProgress: (callback) =>\n    ipcRenderer.removeListener('save-configuration-progress', callback),\n  keyboardScan: (callback) => {\n    ipcRenderer.on('keyboardScan', callback)\n  },\n  serialKeyboardPogConfig: (callback) => {\n    ipcRenderer.on('serialKeyboardPogConfig', callback)\n  },\n  rescanKeyboards: () => ipcRenderer.invoke('rescanKeyboards'),\n  checkForUSBKeyboards: (data) => ipcRenderer.invoke('checkForUSBKeyboards', data),\n  deselectKeyboard: () => ipcRenderer.invoke('deselectKeyboard'),\n  serialData: (callback) => ipcRenderer.on('serialData', callback),\n  offSerialData: (callback) => ipcRenderer.removeListener('serialData', callback),\n  serialConnectionStatus: (callback) => ipcRenderer.on('serialConnectionStatus', callback),\n  serialPorts: () => ipcRenderer.invoke('serial-ports'),\n  serialSend: (data) => ipcRenderer.send('serialSend', data),\n  serialConnect: (port: string) => ipcRenderer.invoke('serial-connect', port),\n  serialDisconnect: () => ipcRenderer.invoke('serial-disconnect'),\n  openExternal: (data) => ipcRenderer.invoke('openExternal', data),\n  // Keyboard Detection API\n  startDetection: () => ipcRenderer.invoke('start-detection'),\n  stopDetection: () => ipcRenderer.invoke('stop-detection'),\n  getDetectionData: () => ipcRenderer.invoke('get-detection-data'),\n  onDetectionUpdate: (callback) => \n    ipcRenderer.on('detection-update', callback),\n  // Keyboard History API\n  listKeyboards: () => ipcRenderer.invoke('list-keyboards'),\n  // Drive and Firmware API\n  listDrives: () => ipcRenderer.invoke('list-drives'),\n  flashDetectionFirmware: (drivePath: string) => ipcRenderer.invoke('flash-detection-firmware', drivePath)\n}\n\n// Use `contextBridge` APIs to expose Electron APIs to\n// renderer only if context isolation is enabled, otherwise\n// just add to the DOM global.\nif (process.contextIsolated) {\n  try {\n    contextBridge.exposeInMainWorld('electron', electronAPI)\n    contextBridge.exposeInMainWorld('api', api)\n  } catch (error) {\n    console.error(error)\n  }\n} else {\n  // @ts-ignore (define in dts)\n  window.electron = electronAPI\n  // @ts-ignore (define in dts)\n  window.api = api\n}\n"
  },
  {
    "path": "src/renderer/index.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"UTF-8\" />\n    <title>POG</title>\n    <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->\n<!--    <meta-->\n<!--      http-equiv=\"Content-Security-Policy\"-->\n<!--      content=\"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'\"-->\n<!--    />-->\n    <script src=\"http://localhost:8098\"></script>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "src/renderer/src/App.vue",
    "content": "<script setup lang=\"ts\">\nimport { computed, onMounted, onUnmounted } from 'vue'\nimport { addToHistory, keyboardStore, notifications, serialKeyboards } from './store'\nimport { addSerialLine } from './store/serial'\nimport { useRouter } from 'vue-router'\nimport LoadingOverlay from './components/LoadingOverlay.vue'\nconst router = useRouter()\nconst store = computed(() => {\n  return keyboardStore\n})\nconsole.log('store added to debug menu', store)\n\nwindow.api.keyboardScan((_event: Event, value: { keyboards }) => {\n  console.log('found keyboards via serial', value)\n  serialKeyboards.value = value.keyboards.map((a) => {\n    const b = a\n    b.port = b.path\n    delete b.path\n    return b\n  })\n})\n\nwindow.api.serialKeyboardPogConfig((_event: Event, value: { pogconfig }) => {\n  console.log('loaded pog config', value)\n  keyboardStore.import({\n    path: '',\n    serial: true,\n    folderContents: ['pog.json', 'kmk'],\n    configContents: value.pogconfig\n  })\n  if (keyboardStore.pogConfigured) {\n    addToHistory(keyboardStore)\n  }\n  router.push('/configurator/keymap')\n})\n\nlet serialHandler: ((event: any, data: { message: string }) => void) | null = null\nonMounted(() => {\n  serialHandler = (_event, data) => addSerialLine(data.message)\n  window.api.serialData(serialHandler)\n})\nonUnmounted(() => {\n  if (serialHandler) window.api.offSerialData(serialHandler)\n  serialHandler = null\n})\n</script>\n\n<template>\n  <div class=\"notifications\">\n    <div v-for=\"(notification, nindex) in notifications\" class=\"alert alert-error shadow-lg\">\n      <div>\n        <svg\n          xmlns=\"http://www.w3.org/2000/svg\"\n          class=\"h-6 w-6 flex-shrink-0 stroke-current\"\n          fill=\"none\"\n          viewBox=\"0 0 24 24\"\n        >\n          <path\n            stroke-linecap=\"round\"\n            stroke-linejoin=\"round\"\n            stroke-width=\"2\"\n            d=\"M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z\"\n          />\n        </svg>\n        <span>{{ notification.label }}</span>\n        <button class=\"btn btn-ghost btn-sm\" @click=\"notifications.splice(nindex, 1)\">\n          <i class=\"mdi mdi-close\"></i>\n        </button>\n      </div>\n    </div>\n  </div>\n  <router-view></router-view>\n  <LoadingOverlay />\n</template>\n<style lang=\"scss\">\nhtml,\nbody,\n#app {\n  height: 100%;\n  font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen-Sans, Ubuntu, Cantarell,\n    Helvetica Neue, sans-serif;\n}\n.tooltip {\n  @apply rounded bg-base-300 p-4 shadow;\n  max-width: 300px;\n}\n.notifications {\n  position: absolute;\n  top: 0;\n  display: flex;\n  @apply z-20 flex-col gap-4 p-4;\n}\n</style>\n"
  },
  {
    "path": "src/renderer/src/assets/css/styles.less",
    "content": "body {\n  display: flex;\n  flex-direction: column;\n  font-family: Roboto, -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Segoe UI', 'Oxygen',\n    'Ubuntu', 'Cantarell', 'Open Sans', sans-serif;\n  color: #86a5b1;\n  background-color: #2f3241;\n}\n\n* {\n  padding: 0;\n  margin: 0;\n}\n\nul {\n  list-style: none;\n}\n\ncode {\n  font-weight: 600;\n  padding: 3px 5px;\n  border-radius: 2px;\n  background-color: #26282e;\n  font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace;\n  font-size: 85%;\n}\n\na {\n  color: #9feaf9;\n  font-weight: 600;\n  cursor: pointer;\n  text-decoration: none;\n  outline: none;\n}\n\na:hover {\n  border-bottom: 1px solid;\n}\n\n#app {\n  flex: 1;\n  display: flex;\n  flex-direction: column;\n  max-width: 840px;\n  margin: 0 auto;\n  padding: 15px 30px 0 30px;\n}\n\n.versions {\n  margin: 0 auto;\n  float: none;\n  clear: both;\n  overflow: hidden;\n  font-family: 'Menlo', 'Lucida Console', monospace;\n  color: #c2f5ff;\n  line-height: 1;\n  transition: all 0.3s;\n\n  li {\n    display: block;\n    float: left;\n    border-right: 1px solid rgba(194, 245, 255, 0.4);\n    padding: 0 20px;\n    font-size: 13px;\n    opacity: 0.8;\n\n    &:last-child {\n      border: none;\n    }\n  }\n}\n\n.hero-logo {\n  margin-top: -0.4rem;\n  transition: all 0.3s;\n}\n\n@media (max-width: 840px) {\n  .versions {\n    display: none;\n  }\n\n  .hero-logo {\n    margin-top: -1.5rem;\n  }\n}\n\n.hero-text {\n  font-weight: 400;\n  color: #c2f5ff;\n  text-align: center;\n  margin-top: -0.5rem;\n  margin-bottom: 10px;\n}\n\n@media (max-width: 660px) {\n  .hero-logo {\n    display: none;\n  }\n\n  .hero-text {\n    margin-top: 20px;\n  }\n}\n\n.hero-tagline {\n  text-align: center;\n  margin-bottom: 14px;\n}\n\n.links {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 24px;\n  font-size: 18px;\n  font-weight: 500;\n\n  a {\n    font-weight: 500;\n  }\n\n  .link-item {\n    padding: 0 4px;\n  }\n}\n\n.features {\n  display: flex;\n  flex-wrap: wrap;\n  margin: -6px;\n\n  .feature-item {\n    width: 33.33%;\n    box-sizing: border-box;\n    padding: 6px;\n  }\n\n  article {\n    background-color: rgba(194, 245, 255, 0.1);\n    border-radius: 8px;\n    box-sizing: border-box;\n    padding: 12px;\n    height: 100%;\n  }\n\n  span {\n    color: #d4e8ef;\n    word-break: break-all;\n  }\n\n  .title {\n    font-size: 17px;\n    font-weight: 500;\n    color: #c2f5ff;\n    line-height: 22px;\n    overflow: hidden;\n    text-overflow: ellipsis;\n    white-space: nowrap;\n  }\n\n  .detail {\n    font-size: 14px;\n    font-weight: 500;\n    line-height: 22px;\n    margin-top: 6px;\n  }\n}\n\n@media (max-width: 660px) {\n  .features .feature-item {\n    width: 50%;\n  }\n}\n\n@media (max-width: 480px) {\n  .links {\n    flex-direction: column;\n    line-height: 32px;\n\n    .link-dot {\n      display: none;\n    }\n  }\n\n  .features .feature-item {\n    width: 100%;\n  }\n}\n"
  },
  {
    "path": "src/renderer/src/assets/microcontrollers/microcontrollers.json",
    "content": "[\n    {\n        \"id\": \"0xcb-helios\",\n        \"name\": \"0xCB Helios\",\n        \"information\": \"The <a class=\\\"link-primary link\\\" target=\\\"_blank\\\" href=\\\"https://keeb.supply/products/0xcb-helios\\\">0xCB Helios</a> is an Elite-C compatible MicroController that is based on the RP2040.\",\n        \"image\": true,\n        \"imageUrl\": \"https://github.com/0xCB-dev/0xCB-Helios\",\n        \"license\": \"CC BY-SA 4.0\",\n        \"licenseUrl\": \"https://creativecommons.org/licenses/by-sa/4.0/\"\n    },\n    {\n        \"id\": \"pi-pico\",\n        \"name\": \"Raspberry Pi Pico\",\n        \"information\": \"The <a class=\\\"link-primary link\\\" target=\\\"_blank\\\" href=\\\"https://www.raspberrypi.com/products/raspberry-pi-pico/\\\">Raspberry Pi Pico</a> is a low-cost, high-performance microcontroller board based on the RP2040 chip, designed for embedded projects and IoT applications.\",\n        \"image\": true,\n        \"imageUrl\": \"https://www.raspberrypi.com/documentation/microcontrollers/pico-series.html\",\n        \"license\": \"CC BY-SA 4.0\",\n        \"licenseUrl\": \"https://creativecommons.org/licenses/by-sa/4.0/\"\n    }\n]"
  },
  {
    "path": "src/renderer/src/components/AutomaticSetup.vue",
    "content": "<template>\n  <div class=\"min-h-screen bg-base-100 p-6\">\n    <div class=\"mx-auto max-w-5xl space-y-8\">\n      <div class=\"flex items-center justify-between\">\n        <h2 class=\"text-center text-4xl font-bold text-base-content\">Mapping your Pinout</h2>\n        <button class=\"btn btn-ghost\" @click=\"toggleDebug\">\n          <i class=\"mdi mdi-bug text-2xl\" :class=\"{ 'text-primary': showDebug }\"></i>\n        </button>\n      </div>\n\n      <div class=\"grid gap-6 md:grid-cols-2\">\n        <!-- Detection Info Panel -->\n        <div class=\"card bg-base-200 shadow-lg\">\n          <div class=\"card-body\">\n            <h3 class=\"card-title text-xl font-bold text-base-content\">Detected Configuration</h3>\n            <div class=\"space-y-4 text-base-content/80\">\n              <div class=\"flex justify-between\">\n                <span class=\"font-medium\">Row Pins:</span>\n                <span class=\"font-mono text-primary\">{{ detectionData.rows.join(', ') }}</span>\n              </div>\n              <div class=\"flex justify-between\">\n                <span class=\"font-medium\">Column Pins:</span>\n                <span class=\"font-mono text-primary\">{{ detectionData.cols.join(', ') }}</span>\n              </div>\n              <div class=\"flex justify-between\">\n                <span class=\"font-medium\">Total Keys:</span>\n                <span class=\"font-mono text-primary\">{{ detectionData.pressedKeys.length }}</span>\n              </div>\n              <div class=\"flex justify-between\">\n                <span class=\"font-medium\">Matrix Size:</span>\n                <span class=\"font-mono text-primary\"\n                  >{{ detectionData.rows.length }}x{{ detectionData.cols.length }}</span\n                >\n              </div>\n              <div class=\"flex justify-between\">\n                <span class=\"font-medium\">Total Pins:</span>\n                <span class=\"font-mono text-primary\">{{\n                  detectionData.cols.length + detectionData.rows.length\n                }}</span>\n              </div>\n            </div>\n          </div>\n        </div>\n\n        <!-- Instructions Panel -->\n        <div class=\"card bg-base-200 shadow-lg\">\n          <div class=\"card-body\">\n            <h3 class=\"card-title text-xl font-bold text-base-content\">Instructions</h3>\n            <div class=\"space-y-4\">\n              <p class=\"text-base-content/80\">To map your keyboard matrix, please:</p>\n              <ol class=\"list-inside list-decimal space-y-3 text-base-content/80\">\n                <li class=\"flex items-center space-x-2\">\n                  <span class=\"font-medium\">1.</span>\n                  <span>Press each key on your keyboard exactly once</span>\n                </li>\n                <li class=\"flex items-center space-x-2\">\n                  <span class=\"font-medium\">2.</span>\n                  <span>Make sure to hit all keys, including modifiers</span>\n                </li>\n                <li class=\"flex items-center space-x-2\">\n                  <span class=\"font-medium\">3.</span>\n                  <span>Watch the key preview below light up as you press</span>\n                </li>\n                <li class=\"flex items-center space-x-2\">\n                  <span class=\"font-medium\">4.</span>\n                  <span>Click \"Done\" when you've pressed all keys</span>\n                </li>\n              </ol>\n            </div>\n          </div>\n        </div>\n      </div>\n\n      <!-- Debug Panel -->\n      <div v-if=\"showDebug\" class=\"card bg-base-200 shadow-lg\">\n        <div class=\"card-body\">\n          <Debug />\n        </div>\n      </div>\n\n      <!-- Key Preview -->\n      <div class=\"card bg-base-200 shadow-lg\">\n        <div class=\"card-body\">\n          <h3 class=\"card-title text-xl font-bold text-base-content\">Key Press Preview</h3>\n          <div class=\"grid w-full grid-cols-[repeat(auto-fill,minmax(48px,1fr))] gap-2 p-4\">\n            <div\n              v-for=\"(key, index) in detectionData.pressedKeys\"\n              :key=\"index\"\n              class=\"flex h-12 items-center justify-center rounded-lg font-medium transition-all duration-200\"\n              :class=\"{\n                'bg-base-300 text-base-content': !(\n                  detectionData.lastKeyPress &&\n                  detectionData.lastKeyPress.row === key.row &&\n                  detectionData.lastKeyPress.col === key.col\n                ),\n                'scale-105 bg-primary text-primary-content shadow-md':\n                  detectionData.lastKeyPress &&\n                  detectionData.lastKeyPress.row === key.row &&\n                  detectionData.lastKeyPress.col === key.col\n              }\"\n            >\n              {{ index + 1 }}\n            </div>\n          </div>\n        </div>\n      </div>\n\n      <!-- Action Button -->\n      <div class=\"flex justify-center\">\n        <button class=\"btn btn-primary\" @click=\"proceed\">Done pressing keys</button>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script setup lang=\"ts\">\nimport router from '@renderer/router'\nimport { keyboardStore } from '@renderer/store'\nimport { ref, onMounted, onUnmounted } from 'vue'\nimport Debug from './debug.vue'\n\nconst showDebug = ref(false)\nconst detectionData = ref<{\n  rows: string[]\n  cols: string[]\n  pressedKeys: { row: number; col: number }[]\n  lastKeyPress: { row: number; col: number } | null\n}>({\n  rows: [],\n  cols: [],\n  pressedKeys: [],\n  lastKeyPress: null\n})\n\nonMounted(async () => {\n  await window.api.startDetection()\n})\n\nfunction handleDetectionUpdate(data: any, event: any) {\n  //   detectionData.value = data\n  console.log('handleDetectionUpdate', data, event)\n  switch (event.type) {\n    case 'new_key_press':\n      detectionData.value.pressedKeys.push({ row: event.row, col: event.col })\n      detectionData.value.lastKeyPress = { row: event.row, col: event.col }\n      break\n    case 'existing_key_press':\n      detectionData.value.lastKeyPress = { row: event.row, col: event.col }\n      if (\n        !detectionData.value.pressedKeys.some(\n          (key) => key.row === event.row && key.col === event.col\n        )\n      ) {\n        detectionData.value.pressedKeys.push({ row: event.row, col: event.col })\n      }\n      break\n    case 'used_pins':\n      detectionData.value.rows = event.rows\n      detectionData.value.cols = event.cols\n      break\n  }\n}\n\nfunction proceed() {\n  // Emit completion event with configuration\n  // window.api.stopDetection()\n  // detectionData.value\n  keyboardStore.rowPins = detectionData.value.rows\n  keyboardStore.colPins = detectionData.value.cols\n  keyboardStore.rows = detectionData.value.rows.length\n  keyboardStore.cols = detectionData.value.cols.length\n  keyboardStore.diodeDirection = 'ROW2COL'\n  keyboardStore.coordMapSetup = true\n  keyboardStore.pinPrefix = 'board'\n\n  router.push('/automatic-setup/firmware')\n}\n\ndefineEmits(['setup-complete'])\n\nonMounted(() => {\n  console.log('onMounted')\n  window.api.onDetectionUpdate((data: any, event: any) => handleDetectionUpdate(data, event))\n})\n\nonUnmounted(() => {\n  //   window.api.removeDetectionListeners()\n  //   window.api.stopDetection()\n})\n\nfunction toggleDebug() {\n  showDebug.value = !showDebug.value\n}\n</script>\n"
  },
  {
    "path": "src/renderer/src/components/BaseModal.vue",
    "content": "<template>\n  <Transition\n    enter-active-class=\"transition duration-300 ease-out\"\n    enter-from-class=\"opacity-0\"\n    enter-to-class=\"opacity-100\"\n    leave-active-class=\"transition duration-200 ease-in\"\n    leave-from-class=\"opacity-100\"\n    leave-to-class=\"opacity-0\"\n  >\n    <div\n      v-if=\"props.open\"\n      class=\"fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm\"\n      @click.self=\"$emit('close')\"\n    >\n      <div\n        class=\"relative w-full max-w-md rounded-2xl border border-gray-200/20 bg-base-100/80 p-8 shadow-2xl backdrop-blur-md\"\n      >\n        <h3 v-if=\"props.title\" class=\"mb-2 text-xl font-semibold\">{{ props.title }}</h3>\n        <div class=\"py-2\">\n          <slot />\n        </div>\n        <div class=\"mt-6 flex items-center justify-end gap-2\">\n          <button class=\"btn justify-self-start\" @click=\"$emit('close')\">\n            {{ props.cancelText }}\n          </button>\n          <button\n            v-if=\"props.secondaryText\"\n            class=\"btn btn-primary justify-self-center\"\n            @click=\"$emit('secondary')\"\n          >\n            {{ props.secondaryText }}\n          </button>\n          <div v-else></div>\n          <button\n            v-if=\"props.showConfirm && props.confirmText\"\n            class=\"btn btn-primary justify-self-end\"\n            @click=\"$emit('confirm')\"\n          >\n            {{ props.confirmText }}\n          </button>\n        </div>\n      </div>\n    </div>\n  </Transition>\n</template>\n\n<script setup lang=\"ts\">\ninterface Props {\n  open: boolean\n  title?: string\n  confirmText?: string\n  cancelText?: string\n  secondaryText?: string\n  showConfirm?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n  title: '',\n  confirmText: '',\n  cancelText: 'Cancel',\n  secondaryText: '',\n  showConfirm: true\n})\n\ndefineEmits<{\n  close: []\n  confirm: []\n  secondary: []\n}>()\n</script>\n"
  },
  {
    "path": "src/renderer/src/components/CircuitPythonSetup.vue",
    "content": "<template>\n  <div class=\"flex flex-col items-center justify-center p-8\">\n    <h2 class=\"mb-6 text-2xl font-bold\">CircuitPython Setup Required</h2>\n    <div class=\"max-w-2xl rounded-lg bg-base-100 p-6\">\n      <p class=\"mb-4\">\n        Before configuring your keyboard with Pog, CircuitPython needs to be installed on your board.\n      </p>\n      <ol class=\"mb-4 list-inside list-decimal space-y-2\">\n        <li>Download CircuitPython\n             for your board from circuitpython.org</li>\n        <li>Connect your keyboard while holding the RESET button</li>\n        <li>Copy the CircuitPython UF2 file to the mounted drive (the drive will be named RPI-RP2)</li>\n        <li>Wait for the board to restart (if will show up as a drive named CIRCUITPY)</li>\n      </ol>\n      <p class=\"mb-4\">\n        Once CircuitPython is installed, your board will appear as a CIRCUITPY drive.\n      </p>\n\n      <!-- Drive Selection -->\n      <div v-if=\"showDriveSelect\" class=\"mt-6 space-y-4\">\n        <p class=\"font-medium\">Select your CIRCUITPY drive:</p>\n        <div v-if=\"drives.length > 0\" class=\"space-y-2\">\n          <div\n            v-for=\"drive in drives\"\n            :key=\"drive.path\"\n            class=\"cursor-pointer rounded-lg border p-3 transition-colors\"\n            :class=\"{\n              'border-primary bg-primary/10': selectedDrive === drive.path\n            }\"\n            @click=\"selectedDrive = drive.path\"\n          >\n            <div class=\"flex items-center\">\n              <i class=\"mdi mdi-usb mr-2 text-xl\"></i>\n              <div>\n                <p class=\"font-medium\">{{ drive.name }}</p>\n                <p class=\"text-sm text-gray-600\">{{ drive.path }}</p>\n              </div>\n            </div>\n          </div>\n        </div>\n        <div v-else class=\"text-center text-gray-600\">\n          <p>No CIRCUITPY drives found.</p>\n          <button\n            class=\"mt-2 rounded border px-4 py-2 text-sm hover:bg-gray-50\"\n            @click=\"scanDrives\"\n          >\n            Rescan\n          </button>\n        </div>\n      </div>\n    </div>\n\n    <!-- Action Buttons -->\n    <div class=\"mt-6 flex gap-4\">\n        <button v-if=\"!showDriveSelect\" class=\"btn\" @click=\"router.back()\">Back</button>\n      <button v-if=\"!showDriveSelect\" class=\"btn btn-primary\" @click=\"showDriveSelect = true\">\n        Continue to Setup\n      </button>\n      <template v-else>\n        <button class=\"btn\" @click=\"showDriveSelect = false\">Back</button>\n        <button class=\"btn btn-primary\" :disabled=\"!selectedDrive\" @click=\"handleContinue\">\n          Continue\n        </button>\n      </template>\n    </div>\n  </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, onMounted } from 'vue'\nimport router from '../router'\nimport { keyboardStore } from '../store'\n\nconst showDriveSelect = ref(false)\nconst selectedDrive = ref('')\nconst drives = ref<{ path: string; name: string }[]>([])\n\nasync function scanDrives() {\n  try {\n    const result = await window.api.listDrives()\n    // drives.value = result.filter(drive => \n    //   drive.name.toLowerCase().includes('circuitpy') ||\n    //   drive.path.toLowerCase().includes('circuitpy')\n    // )\n    drives.value = result\n  } catch (error) {\n    console.error('Failed to scan drives:', error)\n  }\n}\n\nfunction handleContinue() {\n  if (selectedDrive.value) {\n    // emit('continue', selectedDrive.value)\n    keyboardStore.path = selectedDrive.value\n    router.push('/automatic-setup/method')\n  }\n}\n\n// const emit = defineEmits(['continue'])\n\nonMounted(() => {\n  scanDrives()\n})\n</script> "
  },
  {
    "path": "src/renderer/src/components/Community.vue",
    "content": "<template>\n  <div>\n    <w3m-core-button label=\"Login\"></w3m-core-button>\n    <div v-if=\"accountAddress\">\n      {{ renderedAccountAddress }}\n      <div class=\"btn\" @click=\"sign\">sign</div>\n    </div>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { EthereumClient, modalConnectors, walletConnectProvider } from '@web3modal/ethereum'\nimport { Web3Modal } from '@web3modal/html'\nimport { configureChains, createClient, signMessage } from '@wagmi/core'\nimport { arbitrum, mainnet, polygon } from '@wagmi/core/chains'\nimport { watchAccount } from '@wagmi/core'\nimport { computed, ref } from 'vue'\nconst chains = [arbitrum, mainnet, polygon]\n\n// Wagmi Core Client\nconst { provider } = configureChains(chains, [\n  walletConnectProvider({ projectId: '7f2678536a5b6b5643d94a6428e341a1' })\n])\nconst wagmiClient = createClient({\n  autoConnect: true,\n  connectors: modalConnectors({\n    projectId: '7f2678536a5b6b5643d94a6428e341a1',\n    version: '1', // or \"2\"\n    appName: 'web3Modal',\n    chains\n  }),\n  provider\n})\n\n// Web3Modal and Ethereum Client\nconst ethereumClient = new EthereumClient(wagmiClient, chains)\nconst web3modal = new Web3Modal(\n  {\n    projectId: '7f2678536a5b6b5643d94a6428e341a1',\n    themeColor: 'orange',\n    themeBackground: 'themeColor',\n    enableAccountView: false\n  },\n  ethereumClient\n)\nconsole.log(web3modal)\n// const unsubscribe = web3modal.subscribeModal((newState) =>\n// console.log(newState)\n// );\nconst accountAddress = ref('')\nwatchAccount((account) => {\n  if (account.address) {\n    console.log('account changed', account.address)\n    accountAddress.value = account.address\n  } else {\n    accountAddress.value = ''\n    console.log('account disconnected')\n  }\n})\nconst sign = async () => {\n  const signature = await signMessage({\n    message: 'pog wants authorisation'\n  })\n  console.log(signature)\n}\nconst renderedAccountAddress = computed(() => {\n  return accountAddress.value.slice(0, 4) + '...' + accountAddress.value.slice(-4)\n})\n</script>\n\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "src/renderer/src/components/CoordMap.vue",
    "content": "<template>\n  <dialog id=\"flash_modal\" class=\"modal\">\n    <div class=\"modal-box\">\n      <h3 class=\"text-lg font-bold\">Attention</h3>\n      <p class=\"py-4\">\n        Flashing the pog utilities on to the keyboard will delete the code.py and similar files from\n        the keyboard.\n      </p>\n      <p class=\"py-4\">Be sure to backup your code if you still need any of it.</p>\n      <div class=\"flex justify-between\">\n        <div class=\"btn\">Abort</div>\n        <div class=\"btn btn-warning\" @click=\"flashCoordMapping({ overwrite: true })\">Flash POG</div>\n      </div>\n    </div>\n    <form method=\"dialog\" class=\"modal-backdrop\">\n      <button>close</button>\n    </form>\n  </dialog>\n  <div>\n    <p class=\"py-4\">1. Install the debug code on the keyboard</p>\n    <button class=\"btn btn-primary btn-sm\" @click=\"promptFlashing\">\n      Flash CoordMap Finder to keyboard\n    </button>\n    <div>\n      <p class=\"py-4\">2. Click the text area and follow the guide below</p>\n\n      <p class=\"mb-4\">\n        3. Now press each key starting in the top left corner in the first row and moving to the\n        right when you reached the end press the last key once again to start with the next row\n      </p>\n\n      <p class=\"py-4\">\n        If nothing is happening first replug the board in case it hasnt started and wait 5 seconds.\n        If this did not help check the diode direction or pins.\n      </p>\n      <p class=\"py-4\">\n        the coordmap should be printed as a list of 3 digit numbers seperated by spaces.<br />\n        eg 001 005 008 002 ... <br />\n        it will print this via a hotkey on the number row so make sure to switch to something like\n        qwerty if you are using azerty or another layout that maps other keys to the number row. for\n        split keyboards try the coordmap with the type set to normal as depending on the split side\n        detection the secondary half might not output to usb.\n      </p>\n      <textarea\n        id=\"keycapture\"\n        v-model=\"coordmap\"\n        class=\"textarea textarea-bordered w-full font-mono\"\n      ></textarea>\n    </div>\n    <div class=\"flex gap-2 py-4\">\n      <button class=\"btn btn-primary\" @click=\"addRow\">new Row</button>\n      <button class=\"btn btn-primary\" @click=\"addSpc\">add Space</button>\n      <button class=\"btn btn-primary\" @click=\"rmLast\">remove last</button>\n      <button class=\"btn btn-primary\" @click=\"clear\">clear</button>\n    </div>\n\n    <div>\n      <KeyboardLayout\n        :key-layout=\"keyboardlayout\"\n        :keymap=\"[]\"\n        :layouts=\"[]\"\n        mode=\"layout\"\n      ></KeyboardLayout>\n    </div>\n    <div>\n      <pre class=\"my-2 rounded bg-base-300 p-4\">{{ coordmapstring }}</pre>\n    </div>\n    <div class=\"flex gap-2\">\n      <button class=\"btn btn-primary mt-2\" @click=\"done\">\n        {{ initialSetup ? 'next' : 'save Coord Maping & create keyboard layout' }}\n      </button>\n      <button v-if=\"!initialSetup\" class=\"btn btn-primary mt-2\" @click=\"onlySave\">\n        only save Coord Maping\n      </button>\n    </div>\n    <p class=\"my-4\">\n      note if your key indexes changed you need to rebuild your layout or adjust the indexes on the\n      on the layout editor, only saving the coord map is only advisable if you wanted to modify\n      spacings but not the order of the keys\n    </p>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { computed, ref } from 'vue'\nimport KeyboardLayout from './KeyboardLayout.vue'\nimport { Key, keyboardStore, KeyInfo } from '../store'\nconst coordmap = ref('')\nconst props = defineProps<{ initialSetup: boolean }>()\nconst emits = defineEmits(['next'])\n\nconsole.log('loading coordmap', keyboardStore.coordMap)\ncoordmap.value = keyboardStore.coordMap\n  .map((row) => {\n    return row.join(' ')\n  })\n  .join(' row ')\n\nconst addRow = () => {\n  coordmap.value = coordmap.value + '\\n'\n  ;(document.querySelector('#keycapture') as HTMLInputElement).focus()\n}\nconst addSpc = () => {\n  coordmap.value += 'spc '\n  ;(document.querySelector('#keycapture') as HTMLInputElement).focus()\n}\nconst done = () => {\n  keyboardStore.setKeys(keyboardlayout.value as KeyInfo[])\n  keyboardStore.coordMap = keys.value\n  emits('next')\n}\nconst onlySave = () => {\n  keyboardStore.coordMap = keys.value\n}\nconst promptFlashing = () => {\n  if (props.initialSetup) {\n    // after valid ok then flash file with overwrite on\n    ;(document.getElementById('flash_modal') as HTMLDialogElement).showModal()\n  } else {\n    flashCoordMapping({ overwrite: false })\n  }\n}\nconst flashCoordMapping = async ({ overwrite }: { overwrite: boolean }) => {\n  console.log('flashCoordMapping with overwrite:', overwrite)\n  keyboardStore.coordMapSetup = true\n  ;(document.getElementById('flash_modal') as HTMLDialogElement).close()\n  overwrite = Boolean(overwrite)\n  await window.api.saveConfiguration(\n    JSON.stringify({\n      pogConfig: keyboardStore.serialize(),\n      writeFirmware: overwrite,\n      writeCoordMapHelper: true\n    })\n  )\n}\nconst keys = computed(() => {\n  // array of rows\n  const tmpKeys = coordmap.value.replaceAll(/\\n|\\r\\n|\\r/gi, ' row ')\n  const rows: any[] = []\n  let rowIndex = 0\n  let lastkey = ''\n  tmpKeys.split(' ').forEach((key) => {\n    if (key === '' || key.length !== 3) return\n    // next row\n    if (key === lastkey && !['row', 'spc'].includes(key)) {\n      if (!coordmap.value.endsWith(' ')) return\n      coordmap.value = coordmap.value.slice(0, -4)\n      addRow()\n      return\n    }\n    if (key === 'row') {\n      rowIndex++\n      return\n    }\n    if (!rows[rowIndex]) rows[rowIndex] = []\n    rows[rowIndex].push(key)\n    lastkey = key\n  })\n  return rows\n})\nconst coordmapstring = computed(() => {\n  let str = 'coord_mapping = [\\n'\n  keys.value.forEach((row) => {\n    str += row.join(',') + ',\\n'\n  })\n  str += ']'\n  return str.replaceAll(/spc,/gi, '    ')\n})\nconst keyboardlayout = computed(() => {\n  const realKeys: Key[] = []\n  let globalkeyindex = 0\n  keys.value.forEach((row, rowindex) => {\n    row.forEach((key, kindex) => {\n      if (key === 'spc') return\n      const keyToAdd = new Key({\n        x: kindex,\n        y: rowindex,\n        idx: globalkeyindex\n      })\n      realKeys.push(keyToAdd)\n      globalkeyindex++\n    })\n  })\n  console.log(realKeys)\n  return realKeys\n})\n\nconst rmLast = () => {\n  coordmap.value = coordmap.value.split(' ').slice(0, -1).join(' ')\n}\nconst clear = () => {\n  coordmap.value = ''\n}\n</script>\n\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "src/renderer/src/components/EncoderLayer.vue",
    "content": "<template>\n  <div\n    v-if=\"keyboardStore.encoderKeymap[lindex] && keyboardStore.encoderKeymap[lindex][eindex]\"\n    class=\"mb-4 flex items-center gap-4\"\n  >\n    <p class=\"w-24\">Layer {{ lindex }}</p>\n    <input\n      v-model=\"keyboardStore.encoderKeymap[lindex][eindex][0]\"\n      type=\"text\"\n      class=\"input input-bordered input-sm\"\n      @blur=\"handleBlur(0)\"\n    />\n    <input\n      v-model=\"keyboardStore.encoderKeymap[lindex][eindex][1]\"\n      type=\"text\"\n      class=\"input input-bordered input-sm\"\n      @blur=\"handleBlur(1)\"\n    />\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { keyboardStore } from '../store'\nconst props = defineProps(['lindex', 'layer', 'eindex'])\nif (!keyboardStore.encoderKeymap[props.lindex]) {\n  // create the layer\n  keyboardStore.encoderKeymap[props.lindex] = []\n}\nif (!keyboardStore.encoderKeymap[props.lindex][props.eindex]) {\n  keyboardStore.encoderKeymap[props.lindex][props.eindex] = ['KC.TRNS', 'KC.TRNS']\n}\n\nconst handleBlur = (index: number) => {\n  const value = keyboardStore.encoderKeymap[props.lindex][props.eindex][index]\n  if (!value || value === '▽') {\n    keyboardStore.encoderKeymap[props.lindex][props.eindex][index] = 'KC.TRNS'\n  }\n}\n</script>\n\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "src/renderer/src/components/EncoderSetup.vue",
    "content": "<template>\n  <div>\n    <div\n      v-for=\"(encoder, eindex) in keyboardStore.encoders\"\n      class=\"my-2 grid gap-4 bg-base-300 p-4\"\n    >\n      <div class=\"flex justify-between gap-4\">\n        <p class=\"text-lg font-bold\">Encoder {{ eindex }}</p>\n        <button class=\"btn btn-error btn-xs\" @click=\"removeEncoder(eindex)\">\n          <i class=\"mdi mdi-delete\"></i> remove encoder\n        </button>\n      </div>\n      <p>Prefix: {{ keyboardStore.pinPrefix }} - {{ pinPrefixHint }}</p>\n      <div class=\"mb-2 flex items-center gap-4\">\n        <label>Pad A</label>\n        <input\n          v-model=\"encoder.pad_a\"\n          type=\"text\"\n          class=\"input input-bordered input-sm\"\n          placeholder=\"14\"\n        />\n      </div>\n      <div class=\"flex items-center gap-4\">\n        <label>Pad B</label>\n        <input\n          v-model=\"encoder.pad_b\"\n          type=\"text\"\n          class=\"input input-bordered input-sm\"\n          placeholder=\"14\"\n        />\n      </div>\n      <div>\n        Keymap\n        <EncoderLayer\n          v-for=\"(_layer, lindex) in keyboardStore.keymap\"\n          :lindex=\"lindex\"\n          :eindex=\"eindex\"\n        ></EncoderLayer>\n      </div>\n    </div>\n    <div class=\"btn btn-primary btn-sm mt-2\" @click=\"addEncoder\">\n      <i class=\"mdi mdi-plus\"></i>add Encoder\n    </div>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { keyboardStore, pinPrefixHint } from '../store'\nimport EncoderLayer from './EncoderLayer.vue'\n\nconst cleanEncoders = () => {\n  if (keyboardStore.encoderKeymap.length !== keyboardStore.keymap.length) {\n    // add or remove encoder layers to match the keymap layer count\n    while (keyboardStore.encoderKeymap.length <= keyboardStore.keymap.length) {\n      // add an empty layer\n      keyboardStore.encoderKeymap.push([\n        // selectedConfig.value.encoders.map((a) => [\"KC.TRNS\", \"KC.TRNS\"]),\n      ])\n    }\n    while (keyboardStore.encoderKeymap.length > keyboardStore.keymap.length) {\n      // remove a layer\n      keyboardStore.encoderKeymap.pop()\n    }\n  }\n}\ncleanEncoders()\nconst addEncoder = () => {\n  const encoder = { pad_a: '', pad_b: '' }\n  // TODO: initialize encoder keymap according to layers and encoders\n  // check the amount of layers\n  // add one encoder to each layer (push)\n  // cleanEncoders();\n\n  keyboardStore.encoderKeymap.forEach((layer) => {\n    layer.push(['KC.TRNS', 'KC.TRNS'])\n  })\n  keyboardStore.encoders.push(encoder)\n}\n\nconst removeEncoder = (index: number) => {\n  // remove the encoder\n\n  // cleanEncoders();\n  keyboardStore.encoders = keyboardStore.encoders.filter((_e, eindex) => {\n    return eindex !== index\n  })\n  // remove that index from each keymap layer\n  keyboardStore.encoderKeymap.forEach((layer, lindex) => {\n    keyboardStore.encoderKeymap[lindex] = layer.filter((_l, eindex) => eindex !== index)\n  })\n}\n</script>\n\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "src/renderer/src/components/HsvColorPicker.vue",
    "content": "<template>\n  <div class=\"mb-2 flex items-center gap-4\">\n    <div class=\"flex flex-col gap-2\">\n      <h2 class=\"font-bold\">Color & Brightness</h2>\n      <div class=\"grid grid-cols-2 gap-4\">\n        <div class=\"grid grid-cols-2 gap-2\">\n          <label>Hue:</label>\n          <input\n            v-model=\"hsvColor.hue\"\n            class=\"input input-bordered input-sm\"\n            type=\"number\"\n            max=\"255\"\n            min=\"0\"\n            @input=\"onInput\"\n          />\n          <label>Saturation:</label>\n          <input\n            v-model=\"hsvColor.sat\"\n            class=\"input input-bordered input-sm\"\n            type=\"number\"\n            max=\"255\"\n            min=\"0\"\n            @input=\"onInput\"\n          />\n          <label>Brightness:</label>\n          <input\n            v-model=\"hsvColor.val\"\n            class=\"input input-bordered input-sm\"\n            type=\"number\"\n            max=\"255\"\n            min=\"0\"\n            @input=\"onInput\"\n          />\n        </div>\n        <input\n          v-model=\"rgbColor\"\n          type=\"color\"\n          class=\"input input-sm row-span-1 h-full w-full\"\n          placeholder=\"14\"\n          @change=\"onColorPicker\"\n        />\n      </div>\n    </div>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { onMounted, Ref, ref } from 'vue'\nimport { keyboardStore } from '../store'\nimport { hexToHSL, hslToHex } from '../helpers/colors'\n\nconst emit = defineEmits(['change'])\n\nconst rgbColor = ref('')\nconst hsvColor: Ref<{ hue: number; sat: number; val: number }> = ref({ hue: 0, sat: 0, val: 0 })\n\nonMounted(() => {\n  if (!keyboardStore.rgbOptions) return\n\n  hsvColor.value.hue = keyboardStore.rgbOptions.hueDefault\n  hsvColor.value.sat = keyboardStore.rgbOptions.satDefault\n  hsvColor.value.val = keyboardStore.rgbOptions.valDefault\n\n  rgbColor.value = hslToHex(hsvColor.value.hue, hsvColor.value.sat, hsvColor.value.val)\n})\n\nconst onInput = () => {\n  rgbColor.value = hslToHex(hsvColor.value.hue, hsvColor.value.sat, hsvColor.value.val)\n  emit('change', hsvColor.value)\n}\n\nconst onColorPicker = () => {\n  hsvColor.value = hexToHSL(rgbColor.value)\n  emit('change', hsvColor.value)\n}\n</script>\n"
  },
  {
    "path": "src/renderer/src/components/KeyCap.vue",
    "content": "<template>\n  <div\n    v-if=\"visible\"\n    ref=\"keyElem\"\n    class=\"keycap\"\n    style=\"user-select: none\"\n    :data-index=\"keyIndex\"\n    :style=\"{\n      left: keyData.x * (baseKeyWidth + keyGap) + 'px',\n      top: keyData.y * (baseKeyWidth + keyGap) + 'px',\n      width: keyWidth + 'px',\n      height: keyHeight + 'px',\n      transform: `rotate(${keyData.r}deg)`,\n      transformOrigin: rotationOrigin\n    }\"\n    :class=\"{\n      selected: mainSelected,\n      'is-trns': isTRNS,\n      encoder: typeof keyData.encoderIndex === 'number'\n    }\"\n  >\n    <div\n      v-if=\"keyData.w2 || keyData.h2\"\n      class=\"keyborder-blocker\"\n      :style=\"{\n        left: '1px',\n        top: '4px',\n        width: keyWidth - 2 + 'px',\n        height: keyHeight - 8 + 'px'\n      }\"\n    ></div>\n    <div\n      class=\"keyborder\"\n      :style=\"{\n        width: keyWidth + 'px',\n        height: keyHeight + 'px',\n        backgroundColor: keyColorDark\n      }\"\n    ></div>\n    <div\n      v-if=\"keyData.w2 || keyData.h2\"\n      class=\"keyborder\"\n      :class=\"{ selected: mainSelected }\"\n      :style=\"{\n        left: keyData.x2 * baseKeyWidth - 1 + 'px',\n        width: keyWidth2 + 'px',\n        height: keyHeight2 + 'px'\n      }\"\n    ></div>\n    <div\n      v-if=\"keyData.w2 || keyData.h2\"\n      class=\"keytop\"\n      :style=\"{\n        height: keyTopHeight2 + 'px',\n        left: keyData.x2 * (baseKeyWidth + keyGap) + 1 + 'px',\n        backgroundColor: keyColor\n      }\"\n    ></div>\n    <!--    <div-->\n    <!--      class=\"keytop\"-->\n    <!--      @click=\"bgClick\"-->\n    <!--    ></div>-->\n    <div\n      v-else\n      class=\"keytop\"\n      :style=\"{\n        top: !mainLabel || isSimple ? '4px' : '14px',\n        height: keyTopHeight + 'px',\n        background: keyColor\n      }\"\n    ></div>\n    <div v-if=\"!isSimple && mode !== 'layout'\" class=\"keylabel-action\">\n      <div\n        v-if=\"typeof keyData.encoderIndex === 'number' && mode !== 'layout'\"\n        class=\"encoder-labels\"\n      >\n        <div v-html=\"encoderActionA\"></div>\n        <div v-html=\"encoderActionB\"></div>\n      </div>\n      <span v-else>\n        {{ mainLabel.action }}\n      </span>\n    </div>\n    <!--    <div class=\"keylabel-action\"></div>-->\n    <div\n      class=\"keylabels\"\n      :class=\"{ 'has-args': !isSimple }\"\n      :style=\"{ height: keyTopHeight + 'px', top: !mainLabel || isSimple ? '4px' : '14px' }\"\n    >\n      <!--      <div class=\"keylabel\" :class=\"['keylabel-'+index]\" v-for=\"(label,index) in keyData.labels\">-->\n      <!--        <div class=\"keylabel-inner\">-->\n      <!--          {{label}}-->\n      <!--        </div>-->\n      <!--      </div>-->\n      <div v-if=\"mainLabel\" class=\"keylabel keylabel-center\">\n        <span\n          v-if=\"isSimple || typeof keyData.encoderIndex === 'number'\"\n          class=\"keylabel-main\"\n          v-html=\"mainLabel.action\"\n        ></span>\n        <div v-else class=\"flex h-full flex-col justify-between p-1\">\n          <span\n            class=\"keylabel-main\"\n            v-html=\"\n              mainLabel.layerNamePosition === 'main'\n                ? mainLabel.main + ' ' + layerName\n                : mainLabel.main\n            \"\n          ></span>\n          <span\n            class=\"keylabel-lower\"\n            v-html=\"\n              mainLabel.layerNamePosition === 'lower'\n                ? mainLabel.lower + ' ' + layerName\n                : mainLabel.lower\n            \"\n          ></span>\n        </div>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { computed, nextTick, onMounted, ref, VNodeRef, watch } from 'vue'\nimport { selectedLayer, selectedKeys, userSettings } from '../store'\nimport { renderLabel } from '../helpers'\nimport chroma from 'chroma-js'\nconst props = defineProps([\n  'keyData',\n  'keyIndex',\n  'mode',\n  'keymap',\n  'matrixWidth',\n  'layouts',\n  'wiringMethod'\n])\ndefineEmits(['selected'])\n\nconst keyGap = 4\n// hide normal labels and show the keymap thing\nconst action = computed(() => {\n  if (props.mode === 'layout') return //String(props.keyData.matrix)\n  let keyIndex = 0\n\n  keyIndex = props.keyData.coordMapIndex\n  if (!props.keymap[selectedLayer.value]) return 'No layer'\n  const keyCode = props.keymap[selectedLayer.value][keyIndex]\n  // resolve readable character\n  if (!keyCode || keyCode === 'KC.TRNS') return '▽'\n  return keyCode\n})\n\nconst isTRNS = computed(() => {\n  return action.value === '▽'\n})\n\nconst visible = computed(() => {\n  // hide decal keys\n  if (props.keyData.d) {\n    return false\n  }\n  // show correct variant\n  const variant: number[] = props.keyData.variant\n  if (variant) {\n    if (variant.length !== 2) return false\n    if (props.layouts[variant[0]]) return props.layouts[variant[0]].selected === variant[1]\n  }\n  // show keys that don't have variant\n  return true\n})\n\nconst baseKeyWidth = ref(54)\nconst keyWidthU = computed(() => {\n  // if(props.keyData.w2) return props.keyData.w2\n  return props.keyData.w || 1\n})\nconst keyHeightU = computed(() => {\n  return props.keyData.h || 1\n})\nconst keyWidth2U = computed(() => {\n  return props.keyData.w2 || 1\n})\nconst keyHeight2U = computed(() => {\n  return props.keyData.h2 || 1\n})\nconst keyWidth = computed(() => {\n  return keyWidthU.value * baseKeyWidth.value + (keyWidthU.value - 1) * keyGap\n})\nconst keyHeight = computed(() => {\n  return keyHeightU.value * baseKeyWidth.value + (keyHeightU.value - 1) * keyGap\n})\nconst keyWidth2 = computed(() => {\n  return keyWidth2U.value * baseKeyWidth.value + (keyWidth2U.value - 1) * keyGap\n})\nconst keyHeight2 = computed(() => {\n  return keyHeight2U.value * baseKeyWidth.value + (keyHeight2U.value - 1) * keyGap\n})\n// const hasArguments = computed(() => {\n//   if (!action.value) return false\n//   return action.value.includes(')')\n// })\n// const keyTopWidth = computed(() => {\n//   return keyWidth.value - keyGap * 2 - 4 //+ ((keyWidthU.value-1)*keyGap))\n// })\nconst keyTopHeight = computed(() => {\n  let padding = 3\n  if (mainLabel.value && !mainLabel.value.simple) padding += 10\n  return keyHeight.value - padding * keyHeightU.value - keyGap + (keyHeightU.value - 1) * keyGap\n})\n// const keyTopWidth2 = computed(() => {\n//   const padding = 0\n//    return keyWidth2.value - padding * keyWidth2U.value - keyGap - 2 + (keyWidth2U.value - 1) * keyGap\n// })\nconst keyTopHeight2 = computed(() => {\n  const padding = 3\n  return keyHeight2.value - padding * keyHeight2U.value - keyGap + (keyHeight2U.value - 1) * keyGap\n})\nconst mainLabel = computed(() => {\n  // in Layout Mode show the matrix pos\n  if (props.mode === 'layout') {\n    return {\n      simple: true,\n      action: props.keyData.getMatrixLabel(),\n      layer: null,\n      lower: '',\n      main: '',\n      layerNamePosition: ''\n    }\n  }\n  // otherwise show the action from the keymap\n  // if (!action.value) return {simple: true,action: '',}\n\n  // render readable label\n  return renderLabel(action.value)\n})\n\n// const argLabel = computed(() => {\n//   if (hasArguments.value && action.value) {\n//     const argAction = action.value.split('(')[1].replace(')', '')\n//     if (argAction.startsWith('KC.')) {\n//       return argAction.split('.')[1]\n//     }\n//     return argAction\n//   }\n//   return\n// })\n\nconst mainSelected = ref(false)\nconst argsSelected = ref(false)\n// const bgClick = (e:MouseEvent) => {\n//   mainSelected.value = true;\n//   argsSelected.value = false;\n//   emit(\"selected\", {\n//     key: props.keyData.matrix,\n//     args: argsSelected.value,\n//     keyIndex: props.keyIndex,\n//     added: e.shiftKey\n//   });\n// };\n// const argClick = () => {\n//   argsSelected.value = true;\n//   mainSelected.value = false;\n//   emit(\"selected\", {\n//     key: props.keyData.matrix,\n//     args: argsSelected.value,\n//     keyIndex: props.keyIndex,\n//   });\n// };\n\n// watch(\n//   () => selectedKey.value.key,\n//   (newValue) => {\n//     if (selectedKey.value.key !== props.keyData.matrix) {\n//       mainSelected.value = false;\n//       argsSelected.value = false;\n//     }\n//   }\n// );\nwatch(\n  () => [...selectedKeys.value],\n  (_newValue) => {\n    if (selectedKeys.value.has(props.keyIndex)) {\n      mainSelected.value = true\n      argsSelected.value = false\n    } else {\n      mainSelected.value = false\n      argsSelected.value = false\n    }\n  }\n)\nconst rotationOrigin = computed(() => {\n  if (typeof props.keyData.rx !== 'number' || typeof props.keyData.ry !== 'number') return '0 0'\n  const x = props.keyData.rx * 58 - props.keyData.x * (baseKeyWidth.value + keyGap)\n  const y = props.keyData.ry * 58 - props.keyData.y * (baseKeyWidth.value + keyGap)\n  return `${x}px ${y}px` // return \"xpx ypx\"\n})\n\nconst keyColor = computed(() => {\n  if (userSettings.value.reduceKeymapColors) return undefined\n  if (mainLabel.value && mainLabel.value.layer && props.keyData.keyboard) {\n    if (props.keyData.keyboard.layers[mainLabel.value.layer]) {\n      return props.keyData.keyboard.layers[mainLabel.value.layer].color\n    }\n  } else if (mainLabel.value && mainLabel.value.action === 'MT') {\n    return '#592424'\n  }\n  return undefined\n})\nconst keyColorDark = computed(() => {\n  if (keyColor.value) {\n    return chroma(keyColor.value).darken(2).hex()\n  }\n  return undefined\n})\nconst layerName = computed(() => {\n  if (!props.keyData.keyboard) return ''\n  if (!mainLabel.value.layer) return ''\n  const layer = props.keyData.keyboard.layers[mainLabel.value.layer]\n  return layer ? layer.name : ''\n})\n\nconst encoderActionA = computed(() => {\n  // get encoder index then lookup current keycode\n  if (!props.keyData.getEncoderLabel) return\n  return renderLabel(props.keyData.getEncoderLabel().a).action\n})\n\nconst encoderActionB = computed(() => {\n  // get encoder index then lookup current keycode\n  if (!props.keyData.getEncoderLabel) return\n  return renderLabel(props.keyData.getEncoderLabel().b).action\n})\n\nconst isSimple = computed(() => {\n  if (typeof props.keyData.encoderIndex === 'number') return false\n  return mainLabel.value.simple\n})\n\nconst keyElem = ref<VNodeRef | null>(null)\nconst fixLabelWidth = () => {\n  // key is eventually hidden with a layout variant\n  if (!keyElem.value) return\n  const label = keyElem.value.querySelector('.keylabel-main')\n  const labels = keyElem.value.querySelector('.keylabels')\n  if (label) {\n    console.log('fixing label width')\n    label.style.transform = `scale(1)`\n    const labelWidth = label.getBoundingClientRect().width\n    const wrapperWidth = labels.getBoundingClientRect().width\n    const scaling = Math.min(wrapperWidth / labelWidth, 1)\n    label.style.transform = `scale(${scaling})`\n  }\n}\nwatch(mainLabel, async () => {\n  console.log('watching cap layer')\n  await nextTick()\n  fixLabelWidth()\n})\nonMounted(() => {\n  fixLabelWidth()\n})\n</script>\n\n<style lang=\"scss\" scoped>\n.keyborder {\n  // outer key outline and background\n  background: #171717;\n  background-image: url('../assets/keycaptophighlight.png');\n  background-repeat: repeat-x;\n  position: absolute;\n  width: 54px;\n  height: 54px;\n  cursor: pointer;\n  z-index: 0;\n  border-radius: 10px;\n  .encoder & {\n    border-bottom-left-radius: 50%;\n    border-bottom-right-radius: 50%;\n  }\n  .selected & {\n    border-color: white;\n    z-index: 4;\n    box-shadow: 0 0 0 1px rgba(255, 255, 255, 1);\n  }\n}\n.encoder-labels {\n  position: absolute;\n  top: 2px;\n  z-index: 10;\n  @apply flex w-full justify-between;\n  & > div {\n    //background: #646464;\n    line-height: 15px;\n    @apply rounded px-1.5;\n  }\n}\n.keyborder-blocker {\n  background: #3e3e3e;\n  position: absolute;\n  width: 52px;\n  height: 52px;\n  cursor: pointer;\n  border-radius: 12px;\n  z-index: 5;\n}\n.keytop {\n  position: absolute;\n  height: 42px;\n  width: calc(100% - 2px);\n  left: 1px;\n  top: 4px;\n  right: 1px;\n  background: #3e3e3e;\n  cursor: pointer;\n  border-radius: 12px;\n  z-index: 6;\n  .encoder & {\n    border-radius: 50%;\n  }\n  .selected.encoder & {\n    border-bottom: 1px solid white;\n  }\n}\n.keylabels {\n  position: absolute;\n  pointer-events: none;\n  width: calc(100% - 12px);\n  left: 6px;\n  top: 4px;\n  right: 6px;\n  line-height: 1rem;\n  //z-index: 3;\n  z-index: 7;\n  .selected & {\n  }\n}\n.keylabel {\n  position: absolute;\n  width: 100%;\n  height: 100%; //calc(48px - 5px);\n  @apply gap-1;\n\n  &-0 {\n    left: 8px;\n    top: 2px;\n    @apply flex items-start justify-start text-center;\n  }\n  &-3 {\n    right: 8px;\n    bottom: 2px;\n    @apply flex items-end justify-end text-center;\n  }\n  &-center {\n    @apply flex items-center justify-center text-center;\n    flex-wrap: wrap;\n  }\n  .arg-top {\n    @apply text-center;\n    position: absolute;\n    top: 0px;\n    left: 6px;\n    right: 6px;\n    font-size: 10px;\n  }\n  .arg-bottom {\n    @apply flex items-center justify-center rounded text-center;\n    position: absolute;\n    //border: 1px solid #666;\n    left: 6px;\n    right: 6px;\n    bottom: 2px;\n    height: 28px;\n    pointer-events: all;\n    cursor: pointer;\n    &.selected {\n      border-color: white;\n    }\n  }\n}\n.keycap {\n  position: absolute;\n  @apply transition-all;\n  .dragging & {\n    transition: all 0.08s ease-out;\n  }\n  &.is-trns {\n    opacity: 0.3;\n  }\n}\n//.keycap {\n//  width: 50px;\n//  height: 50px;\n//  position: absolute;\n//  background: #333;\n//  @apply rounded;\n//  &::after{\n//    @apply absolute;\n//    background: #red;\n//    width: 100px;\n//    height: 100px;\n//    content: '';\n//  }\n//}\n</style>\n<style lang=\"scss\">\n.keylabel {\n  font-weight: bold;\n  font-size: 18px;\n  //text-shadow: 1px 2px 6px rgba(0, 0, 0, 0.6);\n  i.mdi {\n    font-size: 18px;\n  }\n}\n.keylabel-main {\n  white-space: nowrap;\n  .has-args & {\n    i.mdi {\n      font-size: 14px;\n    }\n  }\n}\n.keylabel-lower {\n  font-size: 10px;\n  font-weight: bold;\n  font-style: italic;\n  width: 100%;\n  i.mdi {\n    font-size: 12px;\n  }\n}\n.keylabel-action {\n  font-size: 10px;\n  font-weight: bold;\n  //font-style: italic;\n  width: 100%;\n  position: absolute;\n  z-index: 10;\n  text-align: center;\n}\n</style>\n"
  },
  {
    "path": "src/renderer/src/components/KeyLayoutInfo.vue",
    "content": "<template>\n  \n    <div class=\"flex justify-between items-center h-10\">\n      <div v-if=\"selectedKeys.size === 0\">\n        <p class=\"font-bold\">No keys selected</p>\n      </div>\n      <div v-else>\n        <p class=\"font-bold\">\n          <template v-if=\"selectedKeys.size === 1\">\n            Key #{{ [...selectedKeys][0] }}\n          </template>\n          <template v-else>\n            {{ selectedKeys.size }} keys selected\n          </template>\n        </p>\n      </div>\n      <div v-if=\"selectedKeys.size === 1\" class=\"flex gap-2\">\n        <button \n          class=\"btn btn-sm\" \n          :disabled=\"[...selectedKeys][0] === 0\"\n          @click=\"selectPreviousKey\"\n        >\n          Previous Key\n        </button>\n        <button \n          class=\"btn btn-sm\" \n          :disabled=\"[...selectedKeys][0] === layout.length - 1\"\n          @click=\"selectNextKey\"\n        >\n          Next Key\n        </button>\n      </div>\n  </div>\n  <hr class=\"border-base-300\">\n  <p class=\"mt-2 text-sm\">Basics</p>\n  <div class=\"flex flex-col gap-2\">\n    <div class=\"grid grid-cols-4 gap-2 text-right\">\n      <div class=\"keydata-input-group\">\n        <span>x</span>\n        <input\n          v-model=\"tmpKey.x\"\n          type=\"text\"\n          placeholder=\"x\"\n          class=\"keyinfo-input\"\n          @change=\"updateKey\"\n        />\n      </div>\n      <div class=\"keydata-input-group\">\n        <span>y</span>\n        <input\n          v-model=\"tmpKey.y\"\n          type=\"text\"\n          placeholder=\"y\"\n          class=\"keyinfo-input\"\n          @change=\"updateKey\"\n        />\n      </div>\n      <div class=\"keydata-input-group\">\n        <span>x2</span>\n        <input\n          v-model=\"tmpKey.x2\"\n          type=\"text\"\n          placeholder=\"x2\"\n          class=\"keyinfo-input\"\n          @change=\"updateKey\"\n        />\n      </div>\n      <div class=\"keydata-input-group\">\n        <span>y2</span>\n        <input\n          v-model=\"tmpKey.y2\"\n          type=\"text\"\n          placeholder=\"y2\"\n          class=\"keyinfo-input\"\n          @change=\"updateKey\"\n        />\n      </div>\n    </div>\n    <div class=\"grid grid-cols-4 gap-2 text-right\">\n      <div class=\"keydata-input-group\">\n        <span>w</span>\n        <input\n          v-model=\"tmpKey.w\"\n          placeholder=\"w\"\n          type=\"text\"\n          class=\"keyinfo-input\"\n          @change=\"updateKey\"\n        />\n      </div>\n      <div class=\"keydata-input-group\">\n        <span>h</span>\n        <input\n          v-model=\"tmpKey.h\"\n          placeholder=\"h\"\n          type=\"text\"\n          class=\"keyinfo-input\"\n          @change=\"updateKey\"\n        />\n      </div>\n      <div class=\"keydata-input-group\">\n        <span>w2</span>\n        <input\n          v-model=\"tmpKey.w2\"\n          placeholder=\"w2\"\n          type=\"text\"\n          class=\"keyinfo-input\"\n          @change=\"updateKey\"\n        />\n      </div>\n      <div class=\"keydata-input-group\">\n        <span>h2</span>\n        <input\n          v-model=\"tmpKey.h2\"\n          placeholder=\"h2\"\n          type=\"text\"\n          class=\"keyinfo-input\"\n          @change=\"updateKey\"\n        />\n      </div>\n    </div>\n  </div>\n  <template v-if=\"keyboardStore.wiringMethod === 'matrix' && false\">\n    <p class=\"mt-2 text-sm\" :class=\"{ 'text-error': matrixValid }\">Matrix</p>\n    <div class=\"flex gap-2\">\n      <div class=\"keydata-input-group\">\n        <span>row</span>\n        <input\n          v-model=\"tmpKey.matrix[0]\"\n          type=\"text\"\n          class=\"keyinfo-input w-1/2\"\n          placeholder=\"row\"\n          @change=\"updateKey\"\n        />\n      </div>\n      <div class=\"keydata-input-group\">\n        <span>col</span>\n        <input\n          v-model=\"tmpKey.matrix[1]\"\n          type=\"text\"\n          class=\"keyinfo-input w-1/2\"\n          placeholder=\"col\"\n          @change=\"updateKey\"\n        />\n      </div>\n    </div>\n  </template>\n  <div>\n    <p>Key Index <span class=\"text-xs\">(from CoordMap)</span></p>\n    <input v-model=\"tmpKey.coordMapIndex\" type=\"text\" class=\"keyinfo-input\" @change=\"updateKey\" />\n  </div>\n  <div>\n    <p>Encoder Index</p>\n    <input v-model=\"tmpKey.encoderIndex\" type=\"text\" class=\"keyinfo-input\" @change=\"updateKey\" />\n  </div>\n  <div v-if=\"keyboardStore.layouts.length !== 0\" class=\"flex gap-1\">\n    <label>\n      <span>Variant</span>\n      <input v-model=\"tmpKey.variant[0]\" type=\"text\" class=\"keyinfo-input\" @change=\"updateKey\" />\n    </label>\n    <label>\n      <span>Variant option</span>\n      <input v-model=\"tmpKey.variant[1]\" type=\"text\" class=\"keyinfo-input\" @change=\"updateKey\" />\n    </label>\n  </div>\n  <span>Rotation</span>\n  <div class=\"flex gap-1\">\n    <input\n      v-model=\"tmpKey.r\"\n      type=\"number\"\n      step=\"1\"\n      class=\"keyinfo-input w-1/3\"\n      @change=\"updateKey\"\n    />deg\n    <input\n      v-model=\"tmpKey.rx\"\n      type=\"number\"\n      step=\"1\"\n      class=\"keyinfo-input w-1/3\"\n      placeholder=\"rotation x\"\n      @change=\"updateKey\"\n    />\n    <input\n      v-model=\"tmpKey.ry\"\n      type=\"number\"\n      step=\"1\"\n      class=\"keyinfo-input w-1/3\"\n      placeholder=\"rotation y\"\n      @change=\"updateKey\"\n    />\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { Key, selectedKeys } from '../store'\nimport { computed, ref, watch } from 'vue'\nimport { isNumber } from '@vueuse/core'\nimport { keyboardStore } from '../store'\nconst props = defineProps<{\n  layout: Key[]\n}>()\n\nconst tmpKey = ref<{\n  x: number | ''\n  y: number | ''\n  x2: number | ''\n  y2: number | ''\n  w: number | ''\n  h: number | ''\n  w2: number | ''\n  h2: number | ''\n\n  matrix: (number | '')[]\n  variant: (number | '')[]\n  coordMapIndex?: number | ''\n  encoderIndex?: number | ''\n  r: number | ''\n  rx: number | ''\n  ry: number | ''\n}>({\n  x: 0,\n  y: 0,\n  x2: 0,\n  y2: 0,\n  w: 1,\n  h: 1,\n  w2: 0,\n  h2: 0,\n\n  matrix: ['', ''],\n  variant: ['', ''],\n  coordMapIndex: '',\n  encoderIndex: '',\n  r: 0,\n  rx: 0,\n  ry: 0\n})\n\nconst isAttrSame = (keys, attr) => {\n  return keys.reduce((acc, val) => acc.add(val[attr]), new Set()).size === 1\n}\nconst getSameKeyAttrs = (keys) => {\n  console.log(keys)\n  const sameAttrs = new Map()\n  ;['y', 'y2', 'x', 'x2', 'w', 'w2', 'h', 'h2', 'r', 'ry', 'rx', 'encoderIndex'].forEach((attr) => {\n    if (isAttrSame(keys, attr) && keys[0][attr] !== undefined) {\n      console.log('attr is same', attr)\n      sameAttrs.set(attr, keys[0][attr])\n    }\n  })\n  const returnObj = Object.fromEntries(sameAttrs)\n  console.log(returnObj)\n  return returnObj\n}\nconst updateSelectedKey = () => {\n  console.log('updating selected keys')\n  console.log(props.layout)\n  console.log(selectedKeys.value)\n  if ([...selectedKeys.value].length === 1 && props.layout?.length > 0) {\n    const keyToLoad = props.layout[[...selectedKeys.value][0]]\n\n    // only load overlapping data from all selected keys\n\n    tmpKey.value = {\n      x: keyToLoad.x,\n      y: keyToLoad.y,\n      x2: keyToLoad.x2 ?? '',\n      y2: keyToLoad.y2 ?? '',\n      w: keyToLoad.w,\n      h: keyToLoad.h,\n      w2: keyToLoad.w2 ?? '',\n      h2: keyToLoad.h2 ?? '',\n      r: keyToLoad.r,\n      rx: keyToLoad.rx,\n      ry: keyToLoad.ry,\n      matrix: keyToLoad.matrix ?? ['', ''],\n      variant: keyToLoad.variant ?? ['', ''],\n      coordMapIndex: keyToLoad.coordMapIndex ?? '',\n      encoderIndex: keyToLoad.encoderIndex ?? ''\n    }\n  } else {\n    // set every property that has different values to \"\"\n    tmpKey.value = {\n      matrix: ['', ''],\n      variant: ['', ''],\n      coordMapIndex: '',\n      encoderIndex: '',\n      x2: '',\n      x: '',\n      y2: '',\n      y: '',\n      w: '',\n      h: '',\n      w2: '',\n      h2: '',\n      r: '',\n      rx: '',\n      ry: ''\n    }\n    const attrs = getSameKeyAttrs(props.layout.filter((_a, i) => selectedKeys.value.has(i)))\n    tmpKey.value = { ...tmpKey.value, ...attrs }\n  }\n}\nupdateSelectedKey()\n\nwatch(\n  () => new Set(selectedKeys.value),\n  (newVal) => {\n    console.log('selected keys changed', newVal)\n    updateSelectedKey()\n  },\n  { deep: true }\n)\n\nconst emit = defineEmits(['update:layout'])\n\nconst updateKey = () => {\n  // Create a new copy of the layout to modify\n  const newLayout = [...props.layout]\n  \n  selectedKeys.value.forEach((keyIndex) => {\n    // Create a new object with only the modified properties\n    const updates: Partial<(typeof props.layout)[0]> = {}\n\n    // Only add properties to updates if they have a non-empty value\n    if (tmpKey.value.x !== '') updates.x = Number(tmpKey.value.x)\n    if (tmpKey.value.x2 !== '') updates.x2 = Number(tmpKey.value.x2)\n    if (tmpKey.value.y2 !== '') updates.y2 = Number(tmpKey.value.y2)\n    if (tmpKey.value.y !== '') updates.y = Number(tmpKey.value.y)\n    if (tmpKey.value.w !== '') updates.w = Number(tmpKey.value.w)\n    if (tmpKey.value.h !== '') updates.h = Number(tmpKey.value.h)\n    if (tmpKey.value.w2 !== '') updates.w2 = Number(tmpKey.value.w2)\n    if (tmpKey.value.h2 !== '') updates.h2 = Number(tmpKey.value.h2)\n    if (tmpKey.value.r !== '') updates.r = Number(tmpKey.value.r)\n    if (tmpKey.value.rx !== '') updates.rx = Number(tmpKey.value.rx)\n    if (tmpKey.value.ry !== '') updates.ry = Number(tmpKey.value.ry)\n\n    // Handle coordMapIndex only if it was explicitly changed\n    if (tmpKey.value.coordMapIndex !== '') {\n      updates.coordMapIndex = Number(tmpKey.value.coordMapIndex)\n    }\n\n    // Handle encoderIndex only if it was explicitly changed\n    if (tmpKey.value.encoderIndex !== '') {\n      updates.encoderIndex = Number(tmpKey.value.encoderIndex)\n    }\n\n    // Handle matrix updates\n    if (tmpKey.value.matrix) {\n      if (tmpKey.value.matrix[0] !== '') {\n        if (!Array.isArray(newLayout[keyIndex].matrix)) {\n          updates.matrix = [Number(tmpKey.value.matrix[0]), NaN]\n        } else {\n          updates.matrix = [...newLayout[keyIndex].matrix]\n          updates.matrix[0] = Number(tmpKey.value.matrix[0])\n        }\n      }\n      if (tmpKey.value.matrix[1] !== '') {\n        updates.matrix = Array.isArray(updates.matrix)\n          ? updates.matrix\n          : Array.isArray(newLayout[keyIndex].matrix)\n          ? [...newLayout[keyIndex].matrix]\n          : [NaN, NaN]\n        updates.matrix[1] = Number(tmpKey.value.matrix[1])\n      }\n    }\n\n    // Handle variant updates\n    if (tmpKey.value.variant) {\n      if (tmpKey.value.variant[0] !== '') {\n        updates.variant = Array.isArray(newLayout[keyIndex].variant)\n          ? [...newLayout[keyIndex].variant]\n          : [NaN, NaN]\n        updates.variant[0] = Number(tmpKey.value.variant[0])\n      }\n      if (tmpKey.value.variant[1] !== '') {\n        updates.variant = Array.isArray(updates.variant)\n          ? updates.variant\n          : Array.isArray(newLayout[keyIndex].variant)\n          ? [...newLayout[keyIndex].variant]\n          : [NaN, NaN]\n        updates.variant[1] = Number(tmpKey.value.variant[1])\n      }\n    }\n\n    // Update the key in our new layout copy\n    newLayout[keyIndex] = Object.assign(newLayout[keyIndex], updates)\n    savePartialLayout(newLayout)\n  })\n\n  // Emit the entire updated layout once\n  emit('update:layout', newLayout)\n}\n\nconst matrixValid = computed(() => {\n  return !isNumber(tmpKey.value.matrix[0]) || !isNumber(tmpKey.value.matrix[1])\n})\n\nconst savePartialLayout = (newLayout: Key[]) => {\n  console.log('saving partial layout', newLayout)\n  newLayout.forEach((key, i) => {\n    // Only update specific properties that can be changed in the layout editor\n    const keyProps = ['x', 'y', 'x2', 'y2', 'w', 'h', 'w2', 'h2', 'r', 'rx', 'ry', 'matrix', 'variant', 'coordMapIndex', 'encoderIndex']\n    keyProps.forEach(prop => {\n      if (key[prop] !== undefined && key[prop] !== keyboardStore.keys[i][prop]) {\n        keyboardStore.keys[i][prop] = key[prop]\n      }\n    })\n  })\n}\n\nconst selectNextKey = () => {\n  const currentKey = [...selectedKeys.value][0]\n  if (currentKey < props.layout.length - 1) {\n    selectedKeys.value = new Set([currentKey + 1])\n  }\n}\n\nconst selectPreviousKey = () => {\n  const currentKey = [...selectedKeys.value][0]\n  if (currentKey > 0) {\n    selectedKeys.value = new Set([currentKey - 1])\n  }\n}\n</script>\n\n<style lang=\"scss\" scoped>\n.keyinfo-input {\n  @apply input input-bordered input-sm w-full flex-shrink;\n  padding-left: 3px;\n  padding-right: 3px;\n}\n.keydata-input-group {\n  @apply flex items-center gap-1;\n}\n</style>\n"
  },
  {
    "path": "src/renderer/src/components/KeyPicker.vue",
    "content": "<template>\n  <div class=\"tabs tabs-boxed my-4\">\n    <a class=\"tab\" :class=\"{ 'tab-active': layout === 'qwerty' }\" @click=\"layout = 'qwerty'\"\n      >QWERTY</a\n    >\n    <a class=\"tab\" :class=\"{ 'tab-active': layout === 'colemak' }\" @click=\"layout = 'colemak'\"\n      >Colemak</a\n    >\n    <a class=\"tab\" :class=\"{ 'tab-active': layout === 'colemak-dh' }\" @click=\"layout = 'colemak-dh'\"\n      >Colemak DH</a\n    >\n    <a class=\"tab\" :class=\"{ 'tab-active': layout === 'dvorak' }\" @click=\"layout = 'dvorak'\"\n      >Dvorak</a\n    >\n  </div>\n  <div id=\"keyboard-picker\" :style=\"{ transform: `scale(${scale})` }\">\n    <Qwerty v-if=\"layout === 'qwerty'\" @key=\"setKey\" />\n    <Colemak v-if=\"layout === 'colemak'\" @key=\"setKey\" />\n    <ColemakDH v-if=\"layout === 'colemak-dh'\" @key=\"setKey\" />\n    <Dvorak v-if=\"layout === 'dvorak'\" @key=\"setKey\" />\n  </div>\n  <div v-show=\"showSecondary\" class=\"secondary mb-4\">\n    <div class=\"tabs tabs-boxed mt-4\">\n      <a class=\"tab\" :class=\"{ 'tab-active': category === 'basic' }\" @click=\"category = 'basic'\"\n        >Basic</a\n      >\n      <a class=\"tab\" :class=\"{ 'tab-active': category === 'layers' }\" @click=\"category = 'layers'\"\n        >Layers</a\n      >\n      <a class=\"tab\" :class=\"{ 'tab-active': category === 'kmk' }\" @click=\"category = 'kmk'\">KMK</a>\n      <a class=\"tab\" :class=\"{ 'tab-active': category === 'app' }\" @click=\"category = 'app'\"\n        >App/Media/Mouse</a\n      >\n      <a class=\"tab\" :class=\"{ 'tab-active': category === 'rgb' }\" @click=\"category = 'rgb'\">RGB</a>\n      <a class=\"tab\" :class=\"{ 'tab-active': category === 'advanced' }\" @click=\"category = 'advanced'\"\n        >Advanced & Help</a\n      >\n    </div>\n    <div class=\"key-chooser\">\n      <div v-if=\"category === 'basic'\" class=\"bonus\">\n        <div class=\"key\" @click=\"setKey('KC.NO')\">Empty</div>\n        <div class=\"key\" @click=\"setKey('KC.TRNS')\">▽</div>\n\n        <div class=\"key\" @click=\"setKey('KC.KP_EQUAL')\">=</div>\n        <div class=\"key\" @click=\"setKey('KC.KP_COMMA')\">,</div>\n\n        <div class=\"key\" @click=\"setKey('KC.TILDE')\">~</div>\n        <div class=\"key\" @click=\"setKey('KC.EXLM')\">!</div>\n        <div class=\"key\" @click=\"setKey('KC.AT')\">@</div>\n        <div class=\"key\" @click=\"setKey('KC.HASH')\">#</div>\n        <div class=\"key\" @click=\"setKey('KC.DOLLAR')\">$</div>\n        <div class=\"key\" @click=\"setKey('KC.PERCENT')\">%</div>\n        <div class=\"key\" @click=\"setKey('KC.CIRCUMFLEX')\">^</div>\n        <div class=\"key\" @click=\"setKey('KC.AMPERSAND')\">&</div>\n        <div class=\"key\" @click=\"setKey('KC.ASTERISK')\">*</div>\n        <div class=\"key\" @click=\"setKey('KC.LEFT_PAREN')\">(</div>\n        <div class=\"key\" @click=\"setKey('KC.RIGHT_PAREN')\">)</div>\n        <div class=\"key\" @click=\"setKey('KC.UNDERSCORE')\">_</div>\n        <div class=\"key\" @click=\"setKey('KC.PLUS')\">+</div>\n        <div class=\"key\" @click=\"setKey('KC.LCBR')\">{</div>\n        <div class=\"key\" @click=\"setKey('KC.RCBR')\">}</div>\n        <div class=\"key\" @click=\"setKey('KC.LABK')\">&lt;</div>\n        <div class=\"key\" @click=\"setKey('KC.RABK')\">&gt;</div>\n        <div class=\"key\" @click=\"setKey('KC.COLN')\">:</div>\n        <div class=\"key\" @click=\"setKey('KC.PIPE')\">|</div>\n        <div class=\"key\" @click=\"setKey('KC.QUES')\">?</div>\n        <div class=\"key\" @click=\"setKey('KC.DQT')\">\"</div>\n      </div>\n    </div>\n    <div v-if=\"category === 'layers'\" class=\"key-chooser flex\">\n      <div class=\"bonus\">\n        <div class=\"group\">\n          <div\n            v-for=\"(_layer, index) in keyboardStore.keymap\"\n            class=\"key\"\n            @click=\"setKey(`KC.MO(${index})`)\"\n          >\n            MO({{ index }})\n          </div>\n        </div>\n        <!--      <div class=\"key\" @click=\"setKey('KC.LM()')\">LM(l, mod)</div>-->\n        <!--      <div class=\"key\" @click=\"setKey('KC.LT()')\">LT(l, kc)</div>-->\n        <div class=\"group\">\n          <div\n            v-for=\"(_layer, index) in keyboardStore.keymap\"\n            class=\"key\"\n            @click=\"setKey(`KC.TG(${index})`)\"\n          >\n            TG({{ index }})\n          </div>\n        </div>\n        <div class=\"group\">\n          <div\n            v-for=\"(_layer, index) in keyboardStore.keymap\"\n            class=\"key\"\n            @click=\"setKey(`KC.TO(${index})`)\"\n          >\n            TO({{ index }})\n          </div>\n        </div>\n        <div class=\"group\">\n          <div\n            v-for=\"(_layer, index) in keyboardStore.keymap\"\n            class=\"key\"\n            @click=\"setKey(`KC.TT(${index})`)\"\n          >\n            TT({{ index }})\n          </div>\n        </div>\n        <div class=\"group\">\n          <div\n            v-for=\"(_layer, index) in keyboardStore.keymap\"\n            class=\"key\"\n            @click=\"setKey(`KC.LM(${index},KC.LGUI)`)\"\n          >\n            LM({{ index }}, mod)\n          </div>\n        </div>\n      </div>\n      <div class=\"bonus\"></div>\n    </div>\n\n    <div v-if=\"category === 'kmk'\" class=\"key-chooser flex\">\n      <div class=\"bonus\">\n        <div class=\"key\" @click=\"setKey('KC.RESET')\">Reset</div>\n        <div class=\"key\" @click=\"setKey('KC.RELOAD')\">Reload</div>\n        <div class=\"key\" @click=\"setKey('KC.DEBUG')\">Debug</div>\n        <div class=\"key\" @click=\"setKey('KC.BKDL')\">BKDL</div>\n      </div>\n    </div>\n    <div v-if=\"category === 'app'\" class=\"key-chooser flex\">\n      <div class=\"bonus\">\n        <div class=\"key\" @click=\"setKey('KC.MPLY')\">Play Pause</div>\n        <div class=\"key\" @click=\"setKey('KC.MUTE')\">Mute</div>\n        <div class=\"key\" @click=\"setKey('KC.VOLD')\">Vol Down</div>\n        <div class=\"key\" @click=\"setKey('KC.VOLU')\">Vol Up</div>\n        <div class=\"key\" @click=\"setKey('KC.MFFD')\">next track (OSX)</div>\n        <div class=\"key\" @click=\"setKey('KC.MRWD')\">prev track (OSX)</div>\n\n        <div class=\"key\" @click=\"setKey('KC.MNXT')\">next track (win)</div>\n        <div class=\"key\" @click=\"setKey('KC.MPRV')\">prev track (win)</div>\n        <div class=\"key\" @click=\"setKey('KC.MSTP')\">stop track (win)</div>\n\n        <div class=\"key\" @click=\"setKey('KC.BRIU')\">bright up</div>\n        <div class=\"key\" @click=\"setKey('KC.BRID')\">bright down</div>\n\n        <div class=\"key\" @click=\"setKey('KC.EJCT')\">eject (OSX)</div>\n      </div>\n    </div>\n    <div v-if=\"category === 'rgb'\" class=\"key-chooser flex\">\n      <div class=\"bonus\">\n        <div class=\"key\" @click=\"setKey('KC.RGB_TOG')\">RGB Toggle</div>\n        <div class=\"key\" @click=\"setKey('KC.RGB_HUI')\">RGB Hue increase</div>\n        <div class=\"key\" @click=\"setKey('KC.RGB_HUD')\">RGB Hue decrease</div>\n      </div>\n    </div>\n    <div v-if=\"category === 'advanced'\">\n      <p>you can build way more advanced things with custom keys here are some resources, for some you might need to edit the kb.py file or enable a keyboard feature. when testing these check the output of the REPL for errors\n      </p>\n      <ul>\n        <li><a class=\"text-primary\" href=\"https://github.com/KMKfw/kmk_firmware/blob/main/docs/en/macros.md\">Macros</a> KC.MACRO(\"send a string\")</li>\n        <li><a class=\"text-primary\" href=\"https://github.com/KMKfw/kmk_firmware/blob/main/docs/en/keycodes.md\">Keycodes</a> List of all keys</li>\n        <li><a class=\"text-primary\" href=\"https://github.com/KMKfw/kmk_firmware/blob/main/docs/en/layers.md\">Layers</a> How layers work</li>\n        <li><a class=\"text-primary\" href=\"https://github.com/KMKfw/kmk_firmware/blob/main/docs/en/combos.md\">Combos</a> Multiple keys pressed at simultaneously output a different key</li>\n        <li><a class=\"text-primary\" href=\"https://github.com/KMKfw/kmk_firmware/blob/main/docs/en/holdtap.md\">Holdtap</a> Holding a key down for longer than a certain amount of time outputs a different key</li>\n        <li><a class=\"text-primary\" href=\"https://github.com/KMKfw/kmk_firmware/blob/main/docs/en/combo_layers.md\">Combo Layers</a> pressing 2 layer keys at once opens a different layer</li>\n      </ul>\n    </div>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { computed, onMounted, ref } from 'vue'\nimport { keyboardStore } from '../store'\nimport Qwerty from './picker-layouts/Qwerty.vue'\nimport Colemak from './picker-layouts/Colemak.vue'\nimport ColemakDH from './picker-layouts/ColemakDH.vue'\nimport Dvorak from './picker-layouts/Dvorak.vue'\n\nconst layout = ref('qwerty')\nconst category = ref('basic')\nconst emit = defineEmits(['setKey'])\n\nconst props = defineProps<{\n  showSecondary?: boolean\n}>()\n\nconst showSecondary = computed(() => props.showSecondary !== false)\n\n// set the currently selected key to keycode\nconst setKey = (key: string | number) => {\n  emit('setKey', String(key))\n}\nconst scale = ref(1)\nconst scaleKeyboard = () => {\n  const wrapper = document.querySelector('#keyboard-picker')\n  const keyboard = document.querySelector('#keyboard-picker .key-chooser')\n  if (!wrapper || !keyboard) return\n  const ww = wrapper.getBoundingClientRect().width\n  const wk = keyboard.getBoundingClientRect().width\n  scale.value = Math.min(ww / wk, 1)\n}\nonMounted(() => {\n  window.addEventListener('resize', () => scaleKeyboard())\n  scaleKeyboard()\n})\n</script>\n\n<style lang=\"scss\">\n:root {\n  --key-size: 35px;\n}\n#keyboard-picker {\n  @apply flex flex-wrap justify-center;\n  transform-origin: center top;\n}\n.key-chooser {\n  gap: 4px;\n  @apply flex flex-col flex-wrap;\n  .row {\n    @apply flex;\n    gap: 4px;\n  }\n  .bonus {\n    margin-top: 20px;\n    gap: 4px;\n    @apply flex;\n    flex-wrap: wrap;\n    .key {\n      width: calc(var(--key-size) * 1.4);\n      height: calc(var(--key-size) * 1.4);\n    }\n  }\n  .group {\n    @apply mr-2 flex gap-1;\n  }\n  .blocker-half {\n    width: calc(var(--key-size) / 2);\n    height: var(--key-size);\n    @apply shrink-0;\n  }\n  .blocker-full {\n    height: var(--key-size);\n    width: var(--key-size);\n    @apply shrink-0;\n  }\n  .key {\n    width: var(--key-size);\n    height: var(--key-size);\n    background: #444444;\n    @apply flex shrink-0 flex-col items-center justify-center rounded text-center transition-all;\n    font-size: 14px;\n    line-height: 16px;\n    border: 1px solid #555;\n    cursor: pointer;\n    &.sm {\n      font-size: 10px;\n    }\n    i.mdi {\n      font-size: 18px;\n    }\n    &:hover {\n      background: #555;\n    }\n    &-2u {\n      width: calc(var(--key-size) * 2 + 8px);\n    }\n    &-1-25u {\n      width: calc(var(--key-size) * 1.25 + 2px);\n    }\n    &-1-5u {\n      width: calc(var(--key-size) * 1.5 + 4px);\n    }\n    &-1-75u {\n      width: calc(var(--key-size) * 1.75 + 4px);\n    }\n    &-2-25u {\n      width: calc(var(--key-size) * 2.25 + 8px);\n    }\n    &-2-5u {\n      width: calc(var(--key-size) * 2.5 + 8px);\n    }\n    &-6u {\n      width: calc(var(--key-size) * 6.25 + 18px);\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "src/renderer/src/components/KeyboardLayout.vue",
    "content": "<template>\n  <SelectionArea\n    ref=\"keyboardContainer\"\n    class=\"keyboard-container container\"\n    :class=\"{ 'fixed-height': fixedHeight }\"\n    :options=\"{ selectables: ['.keycap'] }\"\n    :on-move=\"onMove\"\n    :on-start=\"onStart\"\n  >\n    <div\n      id=\"keyboardlayout-wrapper\"\n      class=\"relative flex justify-center\"\n      :style=\"{\n        height: keyboardHeight + 'px',\n        width: keyboardScale * (keyboardWidth * 58) + 'px'\n      }\"\n    >\n      <div\n        id=\"keyboardlayout\"\n        class=\"relative w-full\"\n        :style=\"{\n          width: keyboardWidth * 58 + 'px',\n          // height: keyboardHeight * 58 + 'px',\n          transform: `scale( ${keyboardScale})`,\n          'transform-origin': 'left top'\n        }\"\n        :class=\"{ dragging: moving }\"\n      >\n        <div\n          v-if=\"mode === 'layout' && selectedKeys.size !== 0\"\n          class=\"rotation-origin-helper\"\n          :style=\"{ left: rotationOriginX, top: rotationOriginY }\"\n        ></div>\n        <div class=\"wire-preview\">\n          <!--          for each key show 2 wires o the next keys-->\n        </div>\n\n        <key-cap\n          v-for=\"(key, keyIndex) in keyLayout\"\n          :key=\"key.id\"\n          :key-data=\"key\"\n          :key-index=\"keyIndex\"\n          :mode=\"mode\"\n          :keymap=\"keymap\"\n          :matrix-width=\"matrixWidth\"\n          :layouts=\"layouts\"\n        >\n        </key-cap>\n      </div>\n    </div>\n  </SelectionArea>\n</template>\n\n<script lang=\"ts\" setup>\nimport KeyCap from './KeyCap.vue'\nimport { computed, nextTick, onMounted, onUnmounted, ref, VNodeRef, watch } from 'vue'\nimport { keyboardStore, selectedKeys } from '../store'\nimport { SelectionArea } from '@viselect/vue'\nimport type { SelectionEvent } from '@viselect/vue'\nimport { isNumber } from '@vueuse/core'\nimport { useDebounceFn } from '@vueuse/core'\n\nconst props = defineProps(['keyLayout', 'keymap', 'mode', 'matrixWidth', 'layouts', 'fixedHeight'])\n// mode can be layout or keymap\nconst keyboardContainer = ref<VNodeRef | null>(null)\n// find right edge\nconst keyboardWidth = computed(() => {\n  let maxW = 0\n  props.keyLayout.forEach((k) => {\n    const width = k.w || 1\n    const rightEdge = k.x + width\n    if (rightEdge > maxW) {\n      maxW = rightEdge\n    }\n  })\n  return maxW\n})\n\n// find bottom edge\nconst keyboardKeyHeight = computed(() => {\n  let maxH = 0\n  props.keyLayout.forEach((k) => {\n    const height = k.h || 1\n    const bottomEdge = k.y + height\n    if (bottomEdge > maxH) {\n      maxH = bottomEdge\n    }\n  })\n  return maxH\n})\n\nconst keyboardScale = ref(1)\nconst keyboardHeight = ref(200)\nconst updateHeight = () => {\n  // check all keys\n  const wrapper = keyboardContainer.value.$el\n  const keys = wrapper.querySelectorAll('.keycap')\n  let lowestKey = 0\n  keys.forEach((key) => {\n    const box = key.getBoundingClientRect()\n    const height = box.height + box.top - key.parentNode.getBoundingClientRect().top\n    if (height > lowestKey) {\n      lowestKey = height\n    }\n  })\n  // if(props.fixedHeight &&  keyboardHeight.value < lowestKey ){\n  //   console.log('lowest key ignored', lowestKey,keyboardHeight.value)\n  //   return\n  // }\n  keyboardHeight.value = lowestKey\n}\nconst updateScale = () => {\n  // updateHeight()\n  if (!keyboardContainer.value) return\n  const wrapper = keyboardContainer.value.$el\n  let heightScale = 1\n  let widthScale = 1\n  if (wrapper) {\n    if (keyboardWidth.value === 0) return\n    if (keyboardWidth.value / keyboardKeyHeight.value > 2.71) {\n      const wrapperWidth = wrapper.getBoundingClientRect().width\n      widthScale = Math.min(wrapperWidth / (keyboardWidth.value * 58), 1)\n    } else {\n      const wrapperHeight = wrapper.getBoundingClientRect().height\n      heightScale = Math.min(wrapperHeight / (keyboardKeyHeight.value * 58), 1)\n    }\n    // if (props.fixedHeight) {\n    //   const wrapperHeight = wrapper.parentNode.getBoundingClientRect().height\n    //   heightScale = Math.min(wrapperHeight / keyboardHeight.value, 1)\n    //   if (heightScale < 1){\n    //     console.log('needed scale', heightScale, wrapperHeight, keyboardHeight.value)\n    //   }\n    // }\n    keyboardScale.value = Math.min(heightScale, widthScale)\n  }\n\n  updateHeight()\n}\nonMounted(async () => {\n  // adjust keyboard size to fit\n  // TODO: figure out why i need to apply it 3 times to work\n  updateScale()\n  await nextTick()\n  updateScale()\n  setTimeout(async () => {\n    updateHeight()\n  }, 100)\n  window.addEventListener('resize', updateScale)\n})\n\nonUnmounted(() => {\n  window.removeEventListener('resize', updateScale)\n})\nwatch(props.keyLayout, () => {\n  console.log('keylayout changed')\n  updateScale()\n  updateHeight()\n})\n\nconst rotationOriginX = computed(() => {\n  if (!selectedKeys.value.size) return 0\n  const firstSelectedKeyIndex = [...selectedKeys.value][0]\n  if (!props.keyLayout[firstSelectedKeyIndex]) return '0'\n  const x = props.keyLayout[firstSelectedKeyIndex].rx * 58\n  return `${x}px` // return \"xpx ypx\"\n})\nconst rotationOriginY = computed(() => {\n  if (!selectedKeys.value.size) return 0\n  const firstSelectedKeyIndex = [...selectedKeys.value][0]\n  if (!props.keyLayout[firstSelectedKeyIndex]) return '0'\n  const y = props.keyLayout[firstSelectedKeyIndex].ry * 58\n  return `${y}px` // return \"xpx ypx\"\n})\n\n// const deselectKey = (e: MouseEvent) => {\n//   console.log(e);\n//   if (\n//     e.target &&\n//     (e.target as unknown as { id: string }).id === \"keyboardlayout-wrapper\"\n//   ) {\n//     selectedKeys.value.clear()\n//     selectedKey.value = { keyIndex: NaN, key: [], args: false };\n//   }\n// };\nconst extractIndexes = (els: Element[]): number[] => {\n  return els\n    .map((v) => v.getAttribute('data-index'))\n    .filter((a) => !isNumber(a))\n    .map(Number)\n}\nconst moving = ref(false)\nconst moveStart = ref({ x: 0, y: 0 })\nconst writtenDelta = ref({ x: 0, y: 0 })\nconst onStart = ({ event, selection }: SelectionEvent) => {\n  if (props.mode === 'static') {\n    selection.cancel()\n    return\n  }\n  if (event?.shiftKey && props.mode === 'layout') {\n    if (event instanceof MouseEvent) {\n      // save start point\n      moving.value = true\n      moveStart.value.x = event.clientX\n      moveStart.value.y = event.clientY\n      writtenDelta.value.x = 0\n      writtenDelta.value.y = 0\n      selection.getSelectionArea().classList.add('hidden')\n    }\n    return\n  }\n  selection.getSelectionArea().classList.remove('hidden')\n  if (!event?.ctrlKey && !event?.metaKey) {\n    selection.clearSelection()\n    selectedKeys.value.clear()\n  }\n}\nconst roundNearQtr = (number: number) => {\n  return Math.round(number * 4) / 4\n}\nconst onMove = ({\n  store: {\n    changed: { added, removed }\n  },\n  event\n}: SelectionEvent) => {\n  if (props.mode === 'static') {\n    return\n  }\n  if (event?.shiftKey && props.mode === 'layout') {\n    if (event instanceof MouseEvent) {\n      // console.log(event, selection);\n      moving.value = true\n      // move keys by start distance\n      const delta = { x: 0, y: 0 }\n      delta.x = (event.clientX - moveStart.value.x) * (1 / keyboardScale.value)\n      delta.y = (event.clientY - moveStart.value.y) * (1 / keyboardScale.value)\n      // snap in every 0.25 of a key width 58\n      const deltaTmp = {\n        x: roundNearQtr(delta.x / 58),\n        y: roundNearQtr(delta.y / 58)\n      }\n      // subtract already written distance\n      const writableDelta = {\n        x: deltaTmp.x - writtenDelta.value.x,\n        y: deltaTmp.y - writtenDelta.value.y\n      }\n      writtenDelta.value.x = deltaTmp.x\n      writtenDelta.value.y = deltaTmp.y\n      // write to each key\n      selectedKeys.value.forEach((keyIndex) => {\n        keyboardStore.keys[keyIndex].delta({ property: 'x', value: writableDelta.x })\n        keyboardStore.keys[keyIndex].delta({ property: 'y', value: writableDelta.y })\n      })\n    }\n\n    resetMoving()\n    return\n  }\n  extractIndexes(added).forEach((id) => selectedKeys.value.add(id))\n  extractIndexes(removed).forEach((id) => selectedKeys.value.delete(id))\n}\nconst resetMoving = useDebounceFn(() => {\n  moving.value = false\n}, 1000)\n\ndefineExpose({ keyboardContainer })\n</script>\n\n<style lang=\"scss\" scoped>\n.rotation-origin-helper {\n  width: 5px;\n  height: 5px;\n  background: red;\n  position: absolute;\n  z-index: 10;\n  border-radius: 5px;\n  transform: translate(-50%, -50%);\n}\n\n.container {\n  user-select: none;\n  height: 100%;\n  width: 100%;\n  @apply flex items-center justify-center p-4;\n}\n\n.keyboard-layout {\n}\n</style>\n<style lang=\"scss\">\n.selection-area {\n  background: rgba(152, 90, 19, 0.2);\n  border: 2px solid rgb(242, 140, 24);\n  border-radius: 0.1em;\n  z-index: 100;\n\n  &.hidden {\n    opacity: 0;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/renderer/src/components/KeyboardName.vue",
    "content": "<template>\n  <div class=\"mt-8\">\n    <div class=\"mb-4\">\n      <p class=\"mb-2 text-sm\">Name</p>\n      <input v-model=\"keyboardStore.name\" type=\"text\" class=\"input input-bordered w-full\" />\n    </div>\n    <div class=\"mb-4\">\n      <p class=\"mb-2 text-sm\">Manufacturer (optional)</p>\n      <input v-model=\"keyboardStore.manufacturer\" type=\"text\" class=\"input input-bordered w-full\" />\n    </div>\n    <div class=\"mb-4\">\n      <p class=\"mb-2 text-sm\">Description (optional)</p>\n      <textarea\n        v-model=\"keyboardStore.description\"\n        type=\"text\"\n        class=\"textarea textarea-bordered w-full\"\n      />\n    </div>\n    <div class=\"mb-4\">\n      <p class=\"mb-2 text-sm\">Tags (optional)</p>\n      <VueMultiselect\n        v-model=\"keyboardStore.tags\"\n        :options=\"keyboardTags\"\n        :multiple=\"true\"\n        :taggable=\"true\"\n        class=\"w-full\"\n        @tag=\"addTag\"\n      >\n      </VueMultiselect>\n    </div>\n    \n    <div class=\"mb-4\">\n      <p class=\"mb-2 text-sm\">Keyboard Features</p>\n      <VueMultiselect\n        v-model=\"keyboardStore.kbFeatures\"\n        :options=\"availableFeatures\"\n        :multiple=\"true\"\n        class=\"w-full\"\n      >\n        <template #option=\"{ option }\">\n          <span>{{ formatFeatureName(option) }}</span>\n        </template>\n      </VueMultiselect>\n    </div>\n\n    <div class=\"mt-8 flex justify-center\">\n      <button v-if=\"initialSetup\" class=\"btn btn-primary\" @click=\"$emit('next')\">next</button>\n    </div>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { keyboardStore } from '../store'\nimport VueMultiselect from 'vue-multiselect'\ndefineProps(['initialSetup'])\nconst keyboardTags = ['65%']\n\nconst addTag = (tag) => {\n  console.log(tag)\n  keyboardStore.tags.push(tag)\n}\n\nconst availableFeatures = [\n  'basic',\n  'serial',\n  'oneshot',\n  'tapdance',\n  'holdtap',\n  'mousekeys',\n  'combos',\n  'macros',\n  'capsword',\n  'international'\n]\n\nconst formatFeatureName = (feature: string) => {\n  return feature.split('_')\n    .map(word => word.charAt(0).toUpperCase() + word.slice(1))\n    .join(' ')\n}\n</script>\n\n<style lang=\"scss\">\n@import 'vue-multiselect/dist/vue-multiselect.css';\n.multiselect__tags {\n  width: 100%;\n  background: transparent;\n  border: none;\n}\n.multiselect__placeholder {\n  background: transparent;\n}\n.multiselect__option--selected.multiselect__option {\n  background: #674848;\n}\n.multiselect__option {\n  background: #252525;\n  color: #fff;\n  &:hover {\n    background: #353535;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/renderer/src/components/KeymapEditor.vue",
    "content": "<template>\n  <div class=\"relative\">\n    <!--    <h2 class=\"mb-2 inline-block absolute top-6\" style=\"transform: rotate(-90deg);  left: -15px\">Layers</h2>-->\n    <div class=\"mb-2 flex gap-2\">\n      <button class=\"btn btn-sm\" @click=\"addLayer\"><i class=\"mdi mdi-plus\"></i>add Layer</button>\n      <button class=\"btn btn-sm\" :disabled=\"keyboardStore.keymap.length === 1\" @click=\"removeLayer\">\n        <i class=\"mdi mdi-trash-can\"></i>remove Layer\n      </button>\n      <button class=\"btn btn-sm\" @click=\"duplicateLayer\">\n        <i class=\"mdi mdi-content-duplicate\"></i>Duplicate Layer\n      </button>\n      <button class=\"btn btn-sm\" @click=\"toggleSettings\">\n        <i class=\"mdi mdi-cog\"></i>\n      </button>\n    </div>\n    <div v-if=\"settingsOpen\" class=\"mb-4 flex gap-2\">\n      <label class=\"flex items-center gap-2\">\n        <input v-model=\"userSettings.reduceKeymapColors\" type=\"checkbox\" class=\"checkbox\" />\n        <span>Reduce keymap colors</span>\n      </label>\n      <label class=\"flex items-center gap-2\">\n        <input v-model=\"userSettings.autoSelectNextKey\" type=\"checkbox\" class=\"checkbox\" />\n        <span>Auto-select next key</span>\n      </label>\n    </div>\n    <div class=\"mt-4 flex items-center\">\n      <div class=\"flex gap-2\">\n        <KeymapLayer\n          v-for=\"(layer, index) in keyboardStore.keymap\"\n          :key=\"index\"\n          :layer=\"layer\"\n          :index=\"index\"\n        />\n      </div>\n    </div>\n  </div>\n\n  <div class=\"my-12\">\n    <keyboard-layout\n      :key-layout=\"keyboardStore.keys\"\n      :keymap=\"keyboardStore.keymap\"\n      :matrix-width=\"keyboardStore.cols\"\n      :layouts=\"keyboardStore.layouts\"\n    />\n  </div>\n  <div class=\"my-4\">\n    <p class=\"mb-2 text-sm font-bold\">\n      Keycode Options for Selected Key(s)\n      <span class=\"text-sm text-warning\">{{ coordMapWarning }}</span>\n    </p>\n    <div class=\"flex gap-2\">\n      <div class=\"flex-grow\">\n        <input\n          v-model=\"currentKeyCode\"\n          :disabled=\"selectedKeys.size === 0\"\n          type=\"text\"\n          class=\"input input-bordered input-sm w-full\"\n        />\n      </div>\n      <button v-if=\"selectedKeys.size > 0\" class=\"btn btn-primary btn-sm\" @click=\"openMacroModal\">\n        Custom Macro\n      </button>\n      <!-- Templates Dropdown with Floating UI -->\n      <div v-if=\"selectedKeys.size > 0\" class=\"relative\">\n        <button ref=\"templatesButtonRef\" class=\"btn btn-sm\" @click=\"toggleTemplatesDropdown\">\n          <i class=\"mdi mdi-file-document-outline mr-1\"></i>Templates\n          <i class=\"mdi mdi-chevron-down ml-1\"></i>\n        </button>\n        <Teleport to=\"body\">\n          <div\n            v-if=\"templatesDropdownOpen\"\n            class=\"fixed inset-0 z-40\"\n            @click=\"closeTemplatesDropdown\"\n          ></div>\n          <ul\n            v-if=\"templatesDropdownOpen\"\n            ref=\"templatesDropdownRef\"\n            :style=\"floatingStyles\"\n            class=\"menu rounded-box z-50 w-52 bg-base-300 p-2 shadow-lg\"\n          >\n            <li class=\"menu-title\"><span>Insert Template</span></li>\n            <li>\n              <a @click=\"insertTemplate('macro'), closeTemplatesDropdown()\"\n                ><i class=\"mdi mdi-keyboard\"></i> Macro</a\n              >\n            </li>\n            <li>\n              <a @click=\"insertTemplate('string'), closeTemplatesDropdown()\"\n                ><i class=\"mdi mdi-format-text\"></i> String</a\n              >\n            </li>\n            <li>\n              <a @click=\"insertTemplate('tapdance'), closeTemplatesDropdown()\"\n                ><i class=\"mdi mdi-gesture-double-tap\"></i> Tap Dance</a\n              >\n            </li>\n            <li>\n              <a @click=\"insertTemplate('custom'), closeTemplatesDropdown()\"\n                ><i class=\"mdi mdi-cog\"></i> Custom Key</a\n              >\n            </li>\n            <li class=\"my-1 h-px bg-base-content/20\"></li>\n            <li class=\"menu-title\"><span>Documentation</span></li>\n            <li>\n              <a\n                href=\"https://github.com/KMKfw/kmk_firmware/blob/main/docs/en/macros.md\"\n                target=\"_blank\"\n              >\n                <i class=\"mdi mdi-open-in-new\"></i> Macros Guide\n              </a>\n            </li>\n            <li>\n              <a\n                href=\"https://github.com/KMKfw/kmk_firmware/blob/main/docs/en/tapdance.md\"\n                target=\"_blank\"\n              >\n                <i class=\"mdi mdi-open-in-new\"></i> Tap Dance Guide\n              </a>\n            </li>\n            <li>\n              <a\n                href=\"https://github.com/KMKfw/kmk_firmware/blob/main/docs/en/keycodes.md\"\n                target=\"_blank\"\n              >\n                <i class=\"mdi mdi-open-in-new\"></i> Keycodes Reference\n              </a>\n            </li>\n            <li>\n              <a\n                href=\"https://github.com/KMKfw/kmk_firmware/blob/main/docs/en/layers.md\"\n                target=\"_blank\"\n              >\n                <i class=\"mdi mdi-open-in-new\"></i> Layers Guide\n              </a>\n            </li>\n          </ul>\n        </Teleport>\n      </div>\n    </div>\n    <div v-if=\"keycodeModeForSelection === 'custom'\" class=\"p-2 text-sm italic\">\n      <p>\n        To add your own custom keycodes, edit the file `customkeys.py` to add your own and then use\n        them with `customkeys.MyKey` in your keymap\n      </p>\n    </div>\n  </div>\n  <KeyPicker :show-secondary=\"true\" @set-key=\"setKey\"></KeyPicker>\n\n  <!-- Macro Modal -->\n  <MacroModal\n    :is-open=\"macroModal.isOpen\"\n    :initial-macro-code=\"macroModal.initialCode\"\n    @close=\"closeMacroModal\"\n    @apply=\"applyMacroCode\"\n  />\n</template>\n\n<script lang=\"ts\" setup>\nimport { keyboardStore, selectedKeys, selectedLayer, userSettings } from '../store'\nimport KeyboardLayout from './KeyboardLayout.vue'\nimport KeyPicker from './KeyPicker.vue'\nimport MacroModal from './MacroModal.vue'\nimport { cleanupKeymap, selectNextKey } from '../helpers'\nimport { computed, ref, watch } from 'vue'\nimport KeymapLayer from './KeymapLayer.vue'\nimport { useFloating, offset, flip, shift, autoUpdate } from '@floating-ui/vue'\n\nselectedKeys.value.clear()\n\ntype KeycodeMode = 'simple' | 'combo' | 'macro' | 'custom' | 'tapdance' | 'string'\n\nconst keycodeModeForSelection = ref<KeycodeMode>('simple')\n\nconst detectKeycodeType = (keycode: string): KeycodeMode => {\n  if (!keycode || keycode === 'No key selected' || keycode === '▽') return 'simple'\n  if (keycode.includes('KC.MACRO(\"') || keycode.includes(\"KC.MACRO('\")) return 'string'\n  if (keycode.includes('KC.MACRO(')) return 'macro'\n  if (keycode.includes('KC.TD(')) return 'tapdance'\n  if (keycode.includes('customkeys.')) return 'custom'\n  if (keycode.includes('KC.COMBO(')) return 'combo'\n  return 'simple'\n}\nconst setKey = (keyCode: string) => {\n  selectedKeys.value.forEach((index) => {\n    keyboardStore.keys[index].setOnKeymap(keyCode)\n  })\n  // if one key is selected select the next\n  // TODO: only select visible keys\n  if (selectedKeys.value.size === 1 && userSettings.value.autoSelectNextKey) {\n    selectNextKey()\n  }\n}\ncleanupKeymap()\nconst addLayer = () => {\n  if (!keyboardStore.keymap[0]) {\n    keyboardStore.keymap.push(Array(keyboardStore.cols * keyboardStore.rows).fill('KC.TRNS'))\n  }\n  const tmpKeymap = [...keyboardStore.keymap[0]]\n  tmpKeymap.fill('KC.TRNS')\n  keyboardStore.keymap.push(tmpKeymap)\n  // if needed also add an encoder layer\n  const encoderCount = keyboardStore.encoders.length\n  if (encoderCount !== 0) {\n    keyboardStore.encoderKeymap.push(Array(encoderCount).fill(['KC.TRNS', 'KC.TRNS']))\n  }\n}\nconst removeLayer = () => {\n  if (keyboardStore.keymap.length <= 1) return\n\n  // if needed also remove the encoder layer\n  const encoderCount = keyboardStore.encoders.length\n  if (encoderCount !== 0) {\n    keyboardStore.encoderKeymap.splice(selectedLayer.value, 1)\n  }\n  keyboardStore.layers.splice(selectedLayer.value, 1)\n  keyboardStore.keymap.splice(selectedLayer.value, 1)\n  if (selectedLayer.value === keyboardStore.keymap.length - 1 && selectedLayer.value !== 0) {\n    selectedLayer.value = keyboardStore.keymap.length - 2\n  }\n}\nconst duplicateLayer = () => {\n  keyboardStore.keymap.push([...keyboardStore.keymap[selectedLayer.value]])\n}\nconst currentKeyCode = computed({\n  get() {\n    const keys = keyboardStore.keys.filter((_k, index) => selectedKeys.value.has(index))\n    if (keys.length === 0) return 'No key selected'\n    const actions: string[] = []\n    keys.forEach((key) => {\n      actions.push(keyboardStore.getActionForKey({ key, layer: selectedLayer.value }))\n    })\n\n    return actions[0]\n  },\n  set(newVal) {\n    if (newVal === '▽') return\n    let setNewVal = newVal\n    if (!newVal || selectedKeys.value.size === 0) {\n      setNewVal = 'KC.TRNS'\n    }\n    const keys = keyboardStore.keys.filter((_k, index) => selectedKeys.value.has(index))\n    keys.forEach((key) => {\n      key.setOnKeymap(setNewVal)\n    })\n  }\n})\nconst insertTemplate = (templateType: KeycodeMode) => {\n  const keys = keyboardStore.keys.filter((_k, index) => selectedKeys.value.has(index))\n  keys.forEach((key) => {\n    if (templateType === 'macro') {\n      key.setOnKeymap('KC.MACRO(Press(KC.LCTL),Tap(KC.A),Release(KC.LCTL))')\n    } else if (templateType === 'string') {\n      key.setOnKeymap('KC.MACRO(\"Sample string\")')\n    } else if (templateType === 'tapdance') {\n      key.setOnKeymap('KC.TD(KC.A,KC.B)')\n    } else if (templateType === 'custom') {\n      key.setOnKeymap('customkeys.MyKey')\n    }\n  })\n}\nconst settingsOpen = ref(false)\nconst toggleSettings = () => {\n  settingsOpen.value = !settingsOpen.value\n}\n\n// Floating UI for Templates dropdown\nconst templatesButtonRef = ref<HTMLElement | null>(null)\nconst templatesDropdownRef = ref<HTMLElement | null>(null)\nconst templatesDropdownOpen = ref(false)\n\nconst { floatingStyles } = useFloating(templatesButtonRef, templatesDropdownRef, {\n  placement: 'bottom-end',\n  middleware: [offset(4), flip(), shift({ padding: 8 })],\n  whileElementsMounted: autoUpdate\n})\n\nconst toggleTemplatesDropdown = () => {\n  templatesDropdownOpen.value = !templatesDropdownOpen.value\n}\n\nconst closeTemplatesDropdown = () => {\n  templatesDropdownOpen.value = false\n}\n\nconst macroModal = ref({\n  isOpen: false,\n  initialCode: ''\n})\n\nconst openMacroModal = () => {\n  macroModal.value = { isOpen: true, initialCode: currentKeyCode.value }\n}\n\nconst closeMacroModal = () => {\n  macroModal.value.isOpen = false\n}\n\nconst applyMacroCode = (macroCode: string) => {\n  currentKeyCode.value = macroCode\n  closeMacroModal()\n}\n\nconst coordMapWarning = computed(() => {\n  // show if any of the selected keys does not have and idx\n  const keys = keyboardStore.keys.filter((_k, index) => selectedKeys.value.has(index))\n  if (keys.length === 0) return ''\n  console.log(keys, keys[0].coordMapIndex)\n  if (keys.some((key) => typeof key.coordMapIndex !== 'number')) {\n    return '⚠️ no coordmap index set in the layout for this key'\n  }\n  return ''\n})\n\nwatch(\n  () => currentKeyCode.value,\n  (newKeyCode) => {\n    if (newKeyCode && newKeyCode !== 'No key selected') {\n      const detectedType = detectKeycodeType(newKeyCode)\n      keycodeModeForSelection.value = detectedType\n    }\n  },\n  { immediate: true }\n)\n</script>\n"
  },
  {
    "path": "src/renderer/src/components/KeymapLayer.vue",
    "content": "<template>\n  <div\n    class=\"tab font-bold\"\n    :class=\"{ 'tab-active': index === selectedLayer }\"\n    :style=\"{\n      background: keyboardStore.layers[index].color || '#434343',\n      color: 'white'\n    }\"\n    @click=\"selectedLayer = index\"\n  >\n    {{ index }} {{ keyboardStore.layers[index].name }}\n    <Popper>\n      <span class=\"edit-btn ml-4 px-1\"><i class=\"mdi mdi-cog\"></i></span>\n      <template #content>\n        <div class=\"popover text-left\">\n          <span>Name</span>\n          <input v-model=\"keyboardStore.layers[index].name\" class=\"input input-bordered input-sm\" />\n          <span>Color</span>\n          <label class=\"relative\">\n            <div\n              class=\"h-8 w-full cursor-pointer rounded border border-white border-opacity-40\"\n              :style=\"{ background: keyboardStore.layers[index].color }\"\n            ></div>\n            <input\n              v-model=\"keyboardStore.layers[index].color\"\n              type=\"color\"\n              style=\"visibility: hidden; position: absolute\"\n            />\n          </label>\n          <div class=\"mt-2 flex gap-2\">\n            <div\n              class=\"colorswatch\"\n              style=\"background: #333\"\n              @click=\"keyboardStore.layers[index].color = undefined\"\n            ></div>\n            <div\n              class=\"colorswatch\"\n              style=\"background: #0ca508\"\n              @click=\"keyboardStore.layers[index].color = '#0ca508'\"\n            ></div>\n            <div\n              class=\"colorswatch\"\n              style=\"background: #259eb9\"\n              @click=\"keyboardStore.layers[index].color = '#259eb9'\"\n            ></div>\n            <div\n              class=\"colorswatch\"\n              style=\"background: #f28c18\"\n              @click=\"keyboardStore.layers[index].color = '#f28c18'\"\n            ></div>\n          </div>\n        </div>\n      </template>\n    </Popper>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport Popper from '@wlard/vue3-popper'\nimport { keyboardStore, selectedLayer } from '../store'\nconst props = defineProps(['layer', 'index'])\nif (!keyboardStore.layers[props.index])\n  keyboardStore.layers[props.index] = { name: '', color: undefined }\n</script>\n<style lang=\"scss\" scoped>\n.tab {\n  @apply rounded pr-1;\n  background: #434343;\n  border: 2px solid transparent;\n  opacity: 0.6;\n  .edit-btn {\n    opacity: 0.5;\n    @apply rounded transition-all;\n  }\n  &:hover .edit-btn {\n    opacity: 1;\n    background: rgba(0, 0, 0, 0.2);\n  }\n}\n\n.tab-active {\n  @apply bg-primary font-bold text-black;\n  border: white 2px solid;\n  opacity: 1;\n}\n.popover {\n  @apply rounded border border-white border-opacity-40 bg-base-100 p-2 shadow-2xl;\n}\n.colorswatch {\n  height: 28px;\n  width: 28px;\n  @apply cursor-pointer rounded;\n}\n</style>\n"
  },
  {
    "path": "src/renderer/src/components/KmkInstaller.vue",
    "content": "<template>\n  <BaseModal\n    :open=\"isUpdateOpen\"\n    title=\"Attention\"\n    confirm-text=\"Update POG files\"\n    cancel-text=\"Abort\"\n    @close=\"isUpdateOpen = false\"\n    @confirm=\"updatePOG\"\n  >\n    <p>\n      Updating the POG files will overwrite all files on your keyboard generated by POG (e.g. kb.py,\n      code.py, customkeys.py, etc.)\n    </p>\n    <p class=\"pt-2\">Be sure to backup your code if you still need any of it.</p>\n  </BaseModal>\n\n  <BaseModal\n    :open=\"isRestoreOpen\"\n    title=\"Restore Configuration\"\n    cancel-text=\"Cancel\"\n    secondary-text=\"Keep ID\"\n    confirm-text=\"Generate New ID\"\n    @close=\"isRestoreOpen = false\"\n    @secondary=\"restoreConfig(false)\"\n    @confirm=\"restoreConfig(true)\"\n  >\n    <p>\n      Do you want to generate new ID for the restored configuration? This is only recommended if you\n      are restoring a configuration from another keyboard.\n    </p>\n  </BaseModal>\n\n  <div class=\"mt-4 p-4 text-left\">\n    <p>\n      <a href=\"https://kmkfw.io/\" target=\"_blank\" class=\"link\">KMK</a> is a capable firmware for\n      keyboards using the Rp2040.\n    </p>\n    <p>\n      Before you proceed make sure you installed\n      <a class=\"link\" href=\"https://circuitpython.org/downloads\" target=\"_blank\">CircuitPython</a>\n      on your controller\n    </p>\n    <p>\n      Info: This does not work when the controller is only connected via the serial port (and not as\n      mounted usb drive)\n    </p>\n    <br />\n    <p v-if=\"!keyboardStore.firmwareInstalled\">\n      By clicking the button below, you can install KMK automatically to the following drive:\n      <span class=\"font-mono\">{{ keyboardStore.path }}</span>\n    </p>\n    <div class=\"holder\">\n      <div class=\"flex justify-center\">\n        <div v-if=\"keyboardStore.firmwareInstalled\" class=\"stats mt-8 shadow-xl\">\n          <div class=\"stat text-left\">\n            <div class=\"stat-figure text-primary\">\n              <i class=\"mdi mdi-check text-3xl\"></i>\n            </div>\n            <div class=\"stat-title\">Firmware Installed</div>\n            <div class=\"stat-value text-primary\">KMK</div>\n            <!--        <div class=\"stat-desc\">modified on</div>-->\n          </div>\n        </div>\n      </div>\n      <div\n        v-if=\"['', 'done'].includes(kmkInstallState)\"\n        class=\"mt-8 flex flex-col items-center justify-center\"\n      >\n        <div\n          class=\"mt-8 grid justify-center gap-4\"\n          :class=\"{\n            'grid-cols-1': !keyboardStore.firmwareInstalled,\n            'grid-cols-2': keyboardStore.firmwareInstalled\n          }\"\n        >\n          <button class=\"btn btn-primary\" @click=\"updateKMK\">\n            {{ keyboardStore.firmwareInstalled ? 'update' : 'install' }} KMK\n          </button>\n          <button v-if=\"!initialSetup\" class=\"btn btn-primary\" @click=\"isUpdateOpen = true\">\n            update Firmware\n          </button>\n          <button v-if=\"!initialSetup\" class=\"btn btn-primary\" @click=\"backupConfiguration\">\n            Backup Config\n          </button>\n          <button\n            v-if=\"!initialSetup || keyboardStore.firmwareInstalled\"\n            class=\"btn btn-primary\"\n            @click=\"restoreConfiguration\"\n          >\n            Restore Config\n          </button>\n        </div>\n        <input\n          ref=\"fileInput\"\n          type=\"file\"\n          accept=\".json\"\n          style=\"display: none\"\n          @change=\"handleFileUpload\"\n        />\n      </div>\n      <div v-if=\"initialSetup\" class=\"mt-8 flex justify-center\">\n        <button\n          v-if=\"keyboardStore.firmwareInstalled\"\n          class=\"btn btn-primary mt-4 block\"\n          @click=\"$emit('next')\"\n        >\n          Next\n        </button>\n      </div>\n\n      <div\n        v-if=\"['downloading', 'copying', 'unpacking'].includes(kmkInstallState)\"\n        class=\"mt-4 flex flex-col items-center justify-center\"\n      >\n        <p class=\"m-4 mt-8\">{{ kmkInstallState || '' }}</p>\n        <progress class=\"progress progress-primary w-56\" :value=\"progress\" max=\"100\"></progress>\n        <span v-if=\"progress !== 0\"> {{ isNaN(progress) ? 'Done' : progress }}% </span>\n      </div>\n    </div>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { ref } from 'vue'\nimport BaseModal from './BaseModal.vue'\nimport { keyboardStore, addToHistory } from '../store'\nimport dayjs from 'dayjs'\nimport { ulid } from 'ulid'\nimport { saveConfigurationWithLoading } from '../helpers/saveConfigurationWrapper'\n\nconst progress = ref(0)\nconst kmkInstallState = ref('')\nconst isUpdateOpen = ref(false)\nconst isRestoreOpen = ref(false)\nconst fileInput = ref<HTMLInputElement>()\nconst pendingConfigData = ref<any>(null)\n\nconst props = defineProps<{ initialSetup: boolean }>()\nconst emit = defineEmits(['next', 'done'])\nconst startTime = ref(dayjs())\nconst endTime = ref(dayjs())\n\nconst updateKMK = async () => {\n  await window.api.updateFirmware()\n  kmkInstallState.value = 'downloading'\n  startTime.value = dayjs()\n}\n\nconst updatePOG = async () => {\n  await saveConfigurationWithLoading(\n    JSON.stringify({ pogConfig: keyboardStore.serialize(), writeFirmware: true })\n  )\n  isUpdateOpen.value = false\n}\n\nconst backupConfiguration = async () => {\n  try {\n    const configData = keyboardStore.serialize()\n    const jsonString = JSON.stringify(configData, null, 2)\n    const blob = new Blob([jsonString], { type: 'application/json' })\n    const url = URL.createObjectURL(blob)\n    const link = document.createElement('a')\n    link.href = url\n    link.download = 'pog_backup.json'\n    document.body.appendChild(link)\n    link.click()\n    document.body.removeChild(link)\n    URL.revokeObjectURL(url)\n  } catch (error) {\n    console.error('Error downloading configuration:', error)\n  }\n}\n\nconst restoreConfiguration = () => {\n  fileInput.value?.click()\n}\n\nconst handleFileUpload = async (event: Event) => {\n  const target = event.target as HTMLInputElement\n  const file = target.files?.[0]\n  if (!file) return\n  try {\n    const configData = JSON.parse(await file.text())\n    pendingConfigData.value = configData\n    isRestoreOpen.value = true\n  } catch (error) {\n    console.error('Error reading or parsing configuration file:', error)\n  }\n  target.value = ''\n}\n\nconst restoreConfig = async (generateNewIds: boolean) => {\n  if (!pendingConfigData.value) return\n\n  const configData = generateNewIds ? { ...pendingConfigData.value } : pendingConfigData.value\n  if (generateNewIds) {\n    configData.id = ulid()\n  }\n\n  try {\n    isRestoreOpen.value = false\n\n    await saveConfigurationWithLoading(\n      JSON.stringify({ pogConfig: configData, serial: false, writeFirmware: true })\n    )\n    if (keyboardStore.path) {\n      const keyboardData = await window.api.selectKeyboard({ path: keyboardStore.path })\n      if (keyboardData && !keyboardData.error) {\n        keyboardStore.import(keyboardData)\n      }\n    }\n    if (keyboardStore.keymap.length === 0) keyboardStore.keymap = [[]]\n    keyboardStore.coordMapSetup = false\n\n    if (props.initialSetup) {\n      addToHistory(keyboardStore)\n    }\n    emit('done')\n  } catch (e) {\n    console.error('restore failed', e)\n  }\n}\n\nwindow.api.onUpdateFirmwareInstallProgress(\n  (_event: Event, value: { state: string; progress: number }) => {\n    console.log('kmk progress', value)\n    // don't go back from done\n    if (kmkInstallState.value !== 'done') {\n      kmkInstallState.value = value.state\n      console.log('progress', value.progress)\n      progress.value = Math.round(value.progress)\n      if (value.state === 'done') {\n        keyboardStore.firmwareInstalled = true\n        endTime.value = dayjs()\n        console.log(startTime.value, endTime.value)\n      }\n    }\n  }\n)\n</script>\n"
  },
  {
    "path": "src/renderer/src/components/LayoutEditor.vue",
    "content": "<template>\n  <div class=\"flex gap-2\">\n    <div class=\"btn btn-sm mb-4 p-2\" @click=\"showConverter\">\n      <i class=\"mdi mdi-import\"></i>Import from KLE\n    </div>\n    <div class=\"btn btn-sm mb-4 p-2\" @click=\"showQmkConverter\">\n      <i class=\"mdi mdi-import\"></i>Import from Qmk info json\n    </div>\n    <div class=\"btn btn-sm mb-4 p-2\" @click=\"showRawPogOutput\">\n      <i class=\"mdi mdi-export\"></i>export from pog\n    </div>\n  </div>\n  <div v-if=\"converterVisible\">\n    <div class=\"flex gap-2\">\n      <div class=\"text-left\">\n        <p>\n          you can import json files from the\n          <a class=\"link\" href=\"http://keyboard-layout-editor.com\" target=\"_blank\"\n            >keyboard layout editor</a\n          >\n        </p>\n        <p>set the top left label to the matrix position eg '0,1' for row:0 col:1</p>\n        <p>\n          set the bottom right label for a layout variant<br />\n          eg. '0,1' this would be the first layout option using its other variant\n        </p>\n      </div>\n      <div class=\"flex items-center justify-center\">\n        <img src=\"@renderer/assets/kle.png\" class=\"rounded\" style=\"width: 300px\" />\n      </div>\n    </div>\n    <div class=\"mt-4 flex gap-2\">\n      <textarea\n        v-model=\"kleInput\"\n        class=\"textarea textarea-bordered w-full\"\n        style=\"line-height: 1rem\"\n        rows=\"8\"\n      ></textarea>\n    </div>\n    <div class=\"mt-2 flex flex-col gap-2\">\n      <span class=\"text-warning\">this will overwrite your existing layout</span>\n      <button class=\"btn btn-primary btn-sm my-4\" @click=\"convert\">convert to pog.json</button>\n    </div>\n    <hr />\n  </div>\n  <div v-if=\"qmkConverterVisible\">\n    <div>\n      <textarea v-model=\"qmkJson\" class=\"textarea textarea-bordered w-full\"></textarea>\n      <button class=\"btn\" @click=\"convertqmk\">convert</button>\n    </div>\n  </div>\n  <div v-if=\"showRawPogLayout\">\n    <textarea v-model=\"pogOutput\" class=\"textarea textarea-bordered w-full\"></textarea>\n  </div>\n  <div>\n    <div class=\"flex justify-between\">\n      <div class=\"flex gap-1\">\n        <button class=\"btn btn-primary btn-sm\" @click=\"addKey\">\n          <i class=\"mdi mdi-plus\"></i>add key\n        </button>\n        <button\n          class=\"btn btn-primary btn-sm\"\n          :disabled=\"selectedKeys.size === 0\"\n          @click=\"removeKey\"\n        >\n          <i class=\"mdi mdi-trash-can\"></i>remove key\n        </button>\n      </div>\n    </div>\n    <div class=\"my-5\">\n      <keyboard-layout\n        :key-layout=\"keyboardStore.keys\"\n        :keymap=\"keyboardStore.keymap\"\n        :matrix-width=\"keyboardStore.cols\"\n        :layouts=\"keyboardStore.layouts\"\n        mode=\"layout\"\n      />\n    </div>\n    <div class=\"flex gap-2\">\n      <div class=\"w-1/2\">\n        <variant-switcher></variant-switcher>\n      </div>\n      <div class=\"w-1/2\">\n        <key-layout-info :layout=\"keyboardStore.keys\"></key-layout-info>\n      </div>\n    </div>\n    <div v-if=\"initialSetup\" class=\"btn btn-primary\" @click=\"setupDone\">Finish Setup</div>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { cleanupKeymap, KleToPog, selectNextKey, selectPrevKey } from '../helpers'\nimport { onMounted, ref } from 'vue'\nimport { selectedKeys, keyboardStore, Key, addToHistory } from '../store'\nimport KeyboardLayout from './KeyboardLayout.vue'\nimport { isNumber, onKeyStroke } from '@vueuse/core'\nimport KeyLayoutInfo from './KeyLayoutInfo.vue'\nimport VariantSwitcher from './VariantSwitcher.vue'\n// import { useRouter } from 'vue-router'\n\n// const router = useRouter()\nconst kleInput = ref('')\nconst showRawPogLayout = ref(false)\nconst pogOutput = ref('')\nselectedKeys.value.clear()\nconst emit = defineEmits(['next'])\nconst props = defineProps(['initialSetup'])\nconst converterVisible = ref(false)\nconst showConverter = () => {\n  converterVisible.value = !converterVisible.value\n}\nconst qmkConverterVisible = ref(false)\nconst qmkJson = ref(`{\"layout\": [\n{ \"label\": \"K10\", \"matrix\": [1, 0], \"w\": 1, \"x\": 0, \"y\": 0 },\n{ \"label\": \"K11\", \"matrix\": [1, 1], \"w\": 1, \"x\": 1, \"y\": 0 },\n{ \"label\": \"K02\", \"matrix\": [0, 2], \"w\": 1, \"x\": 2, \"y\": 0 }\n]\n}`)\nconst showQmkConverter = () => {\n  qmkConverterVisible.value = !qmkConverterVisible.value\n}\nconst convertqmk = () => {\n  console.log('converting qmk to pog')\n  try {\n    const keys = JSON.parse(qmkJson.value).layout\n    console.log(keys)\n    keyboardStore.setKeys(keys)\n  } catch (e) {\n    console.log(e)\n  }\n}\nconst showRawPogOutput = () => {\n  showRawPogLayout.value = !showRawPogLayout.value\n  // export\n  pogOutput.value = JSON.stringify(keyboardStore.getKeys(), null, 4)\n}\nconst convert = () => {\n  const layout = KleToPog(kleInput.value)\n  // const pogOutput = JSON.stringify(layout, null, 4)\n  // extract variants\n  console.log('checking for variants', layout)\n  if (props.initialSetup) {\n    // selectedConfig.value.layouts = layout,\n    layout.forEach((key) => {\n      console.log(key)\n      if (key.variant && isNumber(key.variant[0])) {\n        // check if variant exists\n        console.log('checking key for variant', key.variant)\n        if (!keyboardStore.layouts[key.variant[0]]) {\n          // if not create it\n          keyboardStore.layouts[key.variant[0]] = {\n            name: `Variant ${key.variant[0]}`,\n            selected: 0,\n            variants: []\n          }\n        }\n      }\n    })\n    // create default keymap\n    console.log('setting layout', layout)\n    keyboardStore.setKeys(layout)\n    cleanupKeymap()\n    // saveKeymap()\n  }\n  if (layout.length > 0) {\n    keyboardStore.setKeys(layout)\n    cleanupKeymap()\n    converterVisible.value = false\n  }\n}\nconst setupDone = () => {\n  // if (!selectedConfig.value) return\n  // if (!selectedConfig.value.layouts) selectedConfig.value.layouts = { keymap: [], labels: [] }\n  // selectedConfig.value.layouts.keymap = tmpLayout.value;\n  if (keyboardStore.keymap.length === 0) {\n    // initialize it with one layer\n    keyboardStore.keymap = [[]]\n  }\n  // save\n  keyboardStore.coordMapSetup = false\n  window.api.saveConfiguration(\n    JSON.stringify({ pogConfig: keyboardStore.serialize() }) // here was a writeFirmware: false forgot what that did\n  )\n  // save config to localstorage\n  addToHistory(keyboardStore)\n\n  emit('next')\n}\n\n// const saveKeymap = async () => {\n//   // const data = {\n//   //   rowPins: selectedKeyboard.value.configContents.pins.rows,\n//   //   colPins: selectedKeyboard.value.configContents.pins.cols,\n//   //   keymap: keymap.value,\n//   //   diodeDirection: selectedKeyboard.value.configContents.matrix.diodeDirection,\n//   //   config: selectedKeyboard.value\n//   // }\n//   const data = keyboardStore.serialize()\n//   console.log(data)\n//   // save to pog.json\n//   // const saveResponse = await window.api.saveKeymap(JSON.stringify(data))\n// }\n\nconst addKey = () => {\n  // add key to the last position (+ keywidth ) + 1\n  // basically just to not have them overlap\n  if (keyboardStore.keys.length === 0) {\n    keyboardStore.addKey({\n      x: 0,\n      y: 0,\n      matrix: []\n    })\n  } else {\n    let lastKey = new Key({\n      x: 0,\n      y: 0,\n      // matrix: [],\n      w: 1\n    })\n    keyboardStore.keys.forEach((key) => {\n      if (lastKey.y < key.y) lastKey = key\n      if (lastKey.y === key.y && lastKey.x < key.x) lastKey = key\n    })\n    keyboardStore.addKey({\n      x: lastKey.x + (lastKey.w || 1),\n      y: lastKey.y,\n      matrix: []\n    })\n  }\n}\n\nconst removeKey = () => {\n  const keys = [...selectedKeys.value].map((key) => keyboardStore.keys[key].id)\n  keyboardStore.removeKeys({ ids: keys })\n  selectedKeys.value.clear()\n}\n\nonMounted(() => {\n  // move keys with arrows\n\n  onKeyStroke('ArrowDown', (e: KeyboardEvent) => {\n    e.preventDefault()\n    if (e.shiftKey) {\n      // set key width\n      keyboardStore.deltaForKeys({\n        keyIndexes: [...selectedKeys.value],\n        value: 0.25,\n        property: 'h'\n      })\n      return\n    }\n    keyboardStore.deltaForKeys({ keyIndexes: [...selectedKeys.value], value: 0.25, property: 'y' })\n  })\n  onKeyStroke('ArrowUp', (e: KeyboardEvent) => {\n    e.preventDefault()\n    if (e.shiftKey) {\n      // set key width\n      keyboardStore.deltaForKeys({\n        keyIndexes: [...selectedKeys.value],\n        value: -0.25,\n        property: 'h'\n      })\n      return\n    }\n    keyboardStore.deltaForKeys({ keyIndexes: [...selectedKeys.value], value: -0.25, property: 'y' })\n  })\n  onKeyStroke('ArrowLeft', (e: KeyboardEvent) => {\n    e.preventDefault()\n    if (e.altKey && selectedKeys.value.size === 1) {\n      // alt select next key\n      selectPrevKey()\n      return\n    }\n    if (e.shiftKey) {\n      // set key width\n      keyboardStore.deltaForKeys({\n        keyIndexes: [...selectedKeys.value],\n        value: -0.25,\n        property: 'w'\n      })\n      return\n    }\n    keyboardStore.deltaForKeys({ keyIndexes: [...selectedKeys.value], value: -0.25, property: 'x' })\n  })\n  onKeyStroke('ArrowRight', (e: KeyboardEvent) => {\n    e.preventDefault()\n    if (e.altKey && [...selectedKeys.value].length === 1) {\n      // alt select next key\n      selectNextKey()\n      return\n    }\n    if (e.shiftKey) {\n      // set key width\n      keyboardStore.deltaForKeys({\n        keyIndexes: [...selectedKeys.value],\n        value: 0.25,\n        property: 'w'\n      })\n      return\n    }\n    keyboardStore.deltaForKeys({ keyIndexes: [...selectedKeys.value], value: 0.25, property: 'x' })\n  })\n})\n//\n// const hasSelectedKeys = computed(() => {\n//   return selectedKeys.value.size\n// })\n</script>\n\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "src/renderer/src/components/LoadingOverlay.vue",
    "content": "<template>\n  <Transition\n    enter-active-class=\"transition duration-300 ease-out\"\n    enter-from-class=\"opacity-0 -translate-y-2\"\n    enter-to-class=\"opacity-100 translate-y-0\"\n    leave-active-class=\"transition duration-200 ease-in\"\n    leave-from-class=\"opacity-100 translate-y-0\"\n    leave-to-class=\"opacity-0 -translate-y-2\"\n  >\n    <div\n      v-if=\"getLoadingState()\"\n      class=\"fixed left-2 top-2 z-50 w-[350px] rounded-2xl border border-white/20 bg-base-100/40 p-4 text-xs shadow-2xl backdrop-blur-md\"\n    >\n      <div class=\"mb-2 flex items-center gap-3\">\n        <div class=\"relative h-6 w-6\">\n          <Transition\n            mode=\"out-in\"\n            enter-active-class=\"transition duration-200 ease-out\"\n            enter-from-class=\"opacity-0 scale-90\"\n            enter-to-class=\"opacity-100 scale-100\"\n            leave-active-class=\"transition duration-150 ease-in\"\n            leave-from-class=\"opacity-100 scale-100\"\n            leave-to-class=\"opacity-0 scale-90\"\n          >\n            <div v-if=\"statusPhase !== 'reloaded'\" key=\"spinner\">\n              <div class=\"h-6 w-6 rounded-full border-2 border-white/20\"></div>\n              <div\n                class=\"absolute left-0 top-0 h-6 w-6 animate-spin rounded-full border-2 border-orange-500 border-t-transparent\"\n              ></div>\n            </div>\n            <div v-else key=\"check\" class=\"flex h-6 w-6 items-center justify-center text-green-400\">\n              <i class=\"mdi mdi-check-circle-outline text-xl\"></i>\n            </div>\n          </Transition>\n        </div>\n        <div class=\"flex items-baseline gap-2 text-white\">\n          <span class=\"text-base font-semibold\">{{ headline }}</span>\n        </div>\n        <div class=\"ml-auto\">\n          <button class=\"btn btn-ghost btn-xs text-white/80\" @click=\"toggleShowLogs\">\n            {{ showLogs ? 'Hide logs' : 'Show logs' }}\n          </button>\n        </div>\n      </div>\n      <div class=\"space-y-1 text-white\">\n        <div v-if=\"statusLine\" class=\"opacity-90\">{{ statusLine }}</div>\n        <template v-if=\"showLogs\">\n          <div v-if=\"lastThreeSerialLines.length\" class=\"my-1 h-px w-full bg-white/20\"></div>\n          <div v-for=\"(line, idx) in lastThreeSerialLines\" :key=\"idx\" class=\"opacity-70\">\n            {{ line }}\n          </div>\n        </template>\n      </div>\n    </div>\n  </Transition>\n</template>\n\n<script setup lang=\"ts\">\nimport { onMounted, onUnmounted, watch, ref, computed } from 'vue'\nimport { serialLogs } from '@renderer/store/serial'\nimport { getLoadingState, hideLoading } from '../helpers/saveConfigurationWrapper'\n\n// No props needed - uses global loading state\n\nconst emit = defineEmits<{\n  (e: 'done'): void\n}>()\n\nconst progress = ref<{\n  state: 'writing' | 'done' | 'error' | ''\n  filename?: string\n  completed: number\n  total: number\n}>({ state: '', completed: 0, total: 0 })\nconst lastThreeSerialLines = computed(() => serialLogs.value.slice(0, 3))\ntype Phase = 'saving' | 'saved' | 'reloading' | 'reloaded' | 'error' | ''\nconst statusPhase = ref<Phase>('')\nconst statusLine = computed(() => {\n  if (statusPhase.value === 'saved') return 'Saving successful'\n  if (statusPhase.value === 'reloading') return 'Reloading keyboard…'\n  if (statusPhase.value === 'reloaded') return 'Reload complete. Keyboard started.'\n  if (statusPhase.value === 'error') return 'Error while saving'\n  if (progress.value.filename) return `Writing: ${progress.value.filename}`\n  return ''\n})\nconst headline = computed(() => (statusPhase.value === 'reloaded' ? 'Done' : 'Saving…'))\n\nlet phaseEnterAt: number | null = null\nlet minHideTimeout: number | null = null\nlet pendingPhaseTimeout: number | null = null\nlet pendingAfterTimeout: number | null = null\nlet fallbackTimeout: number | null = null\n\nconst MIN_VISIBLE_MS = 500\nconst READY_MARKERS = [\n  'initialising pogkeyboard',\n  'initialising pogkeyboard',\n  'use 6kro',\n  'mem_info used:',\n  'enable mouse'\n]\n\nlet saveProgressHandler:\n  | ((\n      event: any,\n      data: {\n        state: 'writing' | 'done' | 'error'\n        filename?: string\n        completed: number\n        total: number\n      }\n    ) => void)\n  | null = null\nlet unwatchSerial: (() => void) | null = null\nconst hasTriggeredReady = ref(false)\n\n// Show logs preference\nconst SHOW_LOGS_KEY = 'pog:overlay:showLogs'\nconst showLogs = ref(false)\nconst toggleShowLogs = () => {\n  showLogs.value = !showLogs.value\n  try {\n    localStorage.setItem(SHOW_LOGS_KEY, showLogs.value ? '1' : '0')\n  } catch {}\n}\n\nconst enterPhase = (phase: Phase) => {\n  statusPhase.value = phase\n  phaseEnterAt = Date.now()\n}\n\nconst transitionToWithMin = (phase: Phase, afterEntered?: () => void) => {\n  const now = Date.now()\n  const remaining = Math.max(0, MIN_VISIBLE_MS - (now - (phaseEnterAt ?? now)))\n  if (pendingPhaseTimeout) {\n    clearTimeout(pendingPhaseTimeout)\n    pendingPhaseTimeout = null\n  }\n  if (pendingAfterTimeout) {\n    clearTimeout(pendingAfterTimeout)\n    pendingAfterTimeout = null\n  }\n  pendingPhaseTimeout = setTimeout(() => {\n    enterPhase(phase)\n    if (afterEntered) {\n      pendingAfterTimeout = setTimeout(() => {\n        afterEntered()\n      }, MIN_VISIBLE_MS) as unknown as number\n    }\n  }, remaining) as unknown as number\n}\n\nconst attachListeners = () => {\n  if (!saveProgressHandler) {\n    saveProgressHandler = (_event, data) => {\n      progress.value = {\n        state: data.state,\n        filename: data.filename,\n        completed: data.completed,\n        total: data.total\n      }\n      if (data.state === 'error') {\n        transitionToWithMin('error', () => scheduleDone())\n      } else if (data.state === 'done') {\n        transitionToWithMin('saved', () => transitionToWithMin('reloading'))\n      }\n    }\n    if (window.api.onSaveConfigurationProgress) {\n      window.api.onSaveConfigurationProgress(saveProgressHandler)\n    }\n  }\n  if (!unwatchSerial) {\n    unwatchSerial = watch(\n      () => serialLogs.value[0],\n      (line) => {\n        if (!line) return\n        const lcline = String(line).toLowerCase()\n        if (READY_MARKERS.some((m) => lcline.includes(m))) {\n          console.log('ready marker', line)\n          transitionToWithMin('reloaded', () => scheduleDone())\n        }\n      }\n    )\n  }\n}\n\nconst detachListeners = () => {\n  // Remove listeners explicitly to avoid duplicates across multiple saves\n  if (saveProgressHandler && window.api.offSaveConfigurationProgress) {\n    window.api.offSaveConfigurationProgress(saveProgressHandler)\n  }\n  if (unwatchSerial) {\n    unwatchSerial()\n    unwatchSerial = null\n  }\n  saveProgressHandler = null\n}\n\nconst scheduleDone = () => {\n  const now = Date.now()\n  const minUntil = (phaseEnterAt ?? now) + MIN_VISIBLE_MS\n  const remaining = Math.max(0, minUntil - now)\n  if (minHideTimeout) clearTimeout(minHideTimeout)\n  minHideTimeout = setTimeout(() => {\n    emit('done')\n    hideLoading()\n    // reset local state for next run\n    progress.value = { state: '', completed: 0, total: 0 }\n    statusPhase.value = ''\n    phaseEnterAt = null\n    hasTriggeredReady.value = false\n    if (fallbackTimeout) {\n      clearTimeout(fallbackTimeout)\n      fallbackTimeout = null\n    }\n    if (pendingPhaseTimeout) {\n      clearTimeout(pendingPhaseTimeout)\n      pendingPhaseTimeout = null\n    }\n    if (pendingAfterTimeout) {\n      clearTimeout(pendingAfterTimeout)\n      pendingAfterTimeout = null\n    }\n  }, remaining) as unknown as number\n}\n\nwatch(\n  () => getLoadingState(),\n  (visible) => {\n    if (visible) {\n      enterPhase('saving')\n      attachListeners()\n      if (fallbackTimeout) clearTimeout(fallbackTimeout)\n      // Safety: autohide after 15s\n      fallbackTimeout = setTimeout(() => {\n        scheduleDone()\n      }, 15000) as unknown as number\n    } else {\n      detachListeners()\n      if (minHideTimeout) {\n        clearTimeout(minHideTimeout)\n        minHideTimeout = null\n      }\n      if (pendingPhaseTimeout) {\n        clearTimeout(pendingPhaseTimeout)\n        pendingPhaseTimeout = null\n      }\n      if (pendingAfterTimeout) {\n        clearTimeout(pendingAfterTimeout)\n        pendingAfterTimeout = null\n      }\n      if (fallbackTimeout) {\n        clearTimeout(fallbackTimeout)\n        fallbackTimeout = null\n      }\n      progress.value = { state: '', completed: 0, total: 0 }\n      statusPhase.value = ''\n      phaseEnterAt = null\n    }\n  },\n  { immediate: true }\n)\n\nonMounted(() => {\n  if (getLoadingState()) {\n    statusPhase.value = 'saving'\n    attachListeners()\n  }\n  try {\n    showLogs.value = localStorage.getItem(SHOW_LOGS_KEY) === '1'\n  } catch {}\n})\n\nonUnmounted(() => {\n  detachListeners()\n  if (minHideTimeout) clearTimeout(minHideTimeout)\n  if (fallbackTimeout) clearTimeout(fallbackTimeout)\n  if (pendingPhaseTimeout) clearTimeout(pendingPhaseTimeout)\n  if (pendingAfterTimeout) clearTimeout(pendingAfterTimeout)\n})\n</script>\n\n<style scoped>\n.animate-spin {\n  animation: spin 1s linear infinite;\n}\n\n@keyframes spin {\n  from {\n    transform: rotate(0deg);\n  }\n  to {\n    transform: rotate(360deg);\n  }\n}\n</style>\n"
  },
  {
    "path": "src/renderer/src/components/MacroModal.vue",
    "content": "<template>\n  <Transition\n    enter-active-class=\"transition duration-300 ease-out\"\n    enter-from-class=\"opacity-0\"\n    enter-to-class=\"opacity-100\"\n    leave-active-class=\"transition duration-200 ease-in\"\n    leave-from-class=\"opacity-100\"\n    leave-to-class=\"opacity-0\"\n  >\n    <div\n      v-if=\"isOpen\"\n      class=\"fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4 backdrop-blur-sm\"\n      @click.self=\"closeModal\"\n    >\n      <div\n        class=\"relative max-h-[90vh] w-11/12 max-w-6xl overflow-y-auto rounded-2xl border border-gray-200/20 bg-base-100/80 p-8 shadow-2xl backdrop-blur-md\"\n      >\n        <h3 class=\"mb-6 text-2xl font-semibold\">Custom macro builder</h3>\n\n        <!-- Parse Error -->\n        <div v-if=\"parseError\" class=\"mb-4 rounded-lg bg-error/20 p-4 text-error\">\n          <div class=\"font-semibold\">Parse Error:</div>\n          <div class=\"text-sm\">{{ parseError }}</div>\n        </div>\n\n        <div class=\"mb-6\">\n          <label class=\"label\">\n            <span class=\"label-text font-semibold\">Number of keys:</span>\n          </label>\n          <div class=\"flex gap-2\">\n            <button\n              v-for=\"num in [2, 3, 4]\"\n              :key=\"num\"\n              :class=\"['btn btn-sm', selectedKeyCount === num ? 'btn-primary' : 'btn-outline']\"\n              @click=\"selectKeyCount(num)\"\n            >\n              {{ num }} keys\n            </button>\n          </div>\n        </div>\n\n        <div v-if=\"selectedKeyCount > 0\" class=\"my-8\">\n          <div class=\"flex items-center justify-center gap-6\">\n            <template v-for=\"(_, index) in selectedKeyCount\" :key=\"index\">\n              <div class=\"flex flex-col items-center gap-3\">\n                <div\n                  class=\"flex h-20 w-28 cursor-pointer items-center justify-center rounded-lg border-2 border-dashed transition-all duration-200\"\n                  :class=\"getKeyBoxClass(index)\"\n                  @click=\"selectKeyForIndex(index)\"\n                >\n                  <div v-if=\"macroKeys[index]?.keycode\" class=\"text-center text-sm font-bold\">\n                    {{ macroKeys[index].keycode }}\n                  </div>\n                  <div v-else class=\"text-sm text-gray-400\">Key {{ index + 1 }}</div>\n                </div>\n              </div>\n              <div v-if=\"index < selectedKeyCount - 1\" class=\"mx-2 text-3xl font-bold text-primary\">\n                +\n              </div>\n            </template>\n          </div>\n        </div>\n\n        <div v-if=\"selectedKeyCount > 0\" class=\"mt-6\">\n          <KeyPicker :show-secondary=\"false\" @set-key=\"setKeycodeForCurrentKey\" />\n        </div>\n\n        <div class=\"mt-8 flex items-center justify-end gap-3\">\n          <button class=\"btn\" @click=\"closeModal\">Cancel</button>\n          <button class=\"btn btn-primary\" :disabled=\"!isValidMacro\" @click=\"applyMacro\">\n            Apply\n          </button>\n        </div>\n      </div>\n    </div>\n  </Transition>\n</template>\n\n<script lang=\"ts\" setup>\nimport { ref, computed, watch } from 'vue'\nimport KeyPicker from './KeyPicker.vue'\n\ninterface MacroKey {\n  keyId: string\n  keycode: string\n}\n\nconst props = defineProps<{\n  isOpen: boolean\n  initialMacroCode?: string\n}>()\n\nconst emit = defineEmits(['close', 'apply'])\n\nconst selectedKeyCount = ref(0)\nconst macroKeys = ref<MacroKey[]>([])\nconst currentSelectedKeyIndex = ref<number | null>(null)\nconst parseError = ref('')\n\nconst isValidMacro = computed(() => {\n  return selectedKeyCount.value > 0 && macroKeys.value.every((key) => key.keycode.trim())\n})\n\nconst getKeyBoxClass = (index: number) => {\n  const isSelected = currentSelectedKeyIndex.value === index\n  const hasKeycode = macroKeys.value[index]?.keycode\n\n  if (isSelected) return 'border-primary bg-primary/20 shadow-lg'\n  if (hasKeycode) return 'border-primary bg-primary/10'\n  return 'border-gray-400 hover:border-primary hover:bg-primary/5'\n}\n\nconst selectKeyCount = (count: number) => {\n  selectedKeyCount.value = count\n  macroKeys.value = Array(count)\n    .fill(null)\n    .map(() => ({ keyId: '', keycode: '' }))\n  currentSelectedKeyIndex.value = 0\n}\n\nconst selectKeyForIndex = (index: number) => {\n  currentSelectedKeyIndex.value = index\n}\n\nconst setKeycodeForCurrentKey = (keycode: string) => {\n  const currentIndex = currentSelectedKeyIndex.value\n  if (currentIndex !== null) {\n    macroKeys.value[currentIndex].keycode = keycode\n    // Auto move to next key (sequential, not just empty ones)\n    const nextIndex = currentIndex + 1\n    if (nextIndex < selectedKeyCount.value) {\n      // Move to next key\n      currentSelectedKeyIndex.value = nextIndex\n    } else {\n      // All keys filled, deselect\n      currentSelectedKeyIndex.value = null\n    }\n  }\n}\n\nconst closeModal = () => {\n  resetState()\n  emit('close')\n}\n\nconst generateMacroCode = () => {\n  const validKeys = macroKeys.value.filter((key) => key.keycode.trim())\n  const pressKeys = validKeys.map((key) => `Press(${key.keycode})`)\n  const releaseKeys = validKeys\n    .slice()\n    .reverse()\n    .map((key) => `Release(${key.keycode})`)\n  return `KC.MACRO(${[...pressKeys, ...releaseKeys].join(',')})`\n}\n\nconst applyMacro = () => {\n  if (isValidMacro.value) {\n    emit('apply', generateMacroCode())\n    closeModal()\n  }\n}\n\nconst parseExistingMacro = (macroCode: string) => {\n  parseError.value = ''\n  resetState()\n\n  if (!macroCode || macroCode === 'No key selected' || macroCode === '▽') return\n  if (!macroCode.includes('KC.MACRO(')) {\n    parseError.value = 'Not a valid macro code'\n    return\n  }\n\n  try {\n    const pressMatches = macroCode.match(/Press\\(([^)]+)\\)/g) || []\n    const tapMatches = macroCode.match(/Tap\\(([^)]+)\\)/g) || []\n    const allKeycodes = new Set<string>()\n\n    ;[...pressMatches, ...tapMatches].forEach((match) => {\n      const keycode = match\n        .replace(/(Press|Tap)\\(/, '')\n        .replace(')', '')\n        .trim()\n      allKeycodes.add(keycode)\n    })\n\n    if (allKeycodes.size > 0) {\n      selectedKeyCount.value = allKeycodes.size\n      macroKeys.value = Array.from(allKeycodes).map((keycode, index) => ({\n        keyId: `key_${index}`,\n        keycode\n      }))\n      currentSelectedKeyIndex.value = 0\n    } else {\n      parseError.value = 'Could not parse macro actions. Expected Press() or Tap() functions.'\n    }\n  } catch (error) {\n    parseError.value = `Error parsing macro: ${error}`\n  }\n}\n\nconst resetState = () => {\n  selectedKeyCount.value = 0\n  macroKeys.value = []\n  currentSelectedKeyIndex.value = null\n  parseError.value = ''\n}\n\nwatch(\n  () => props.isOpen,\n  (isOpen) => {\n    if (!isOpen) {\n      resetState()\n    } else if (props.initialMacroCode) {\n      parseExistingMacro(props.initialMacroCode)\n    }\n  }\n)\n\nwatch(\n  () => props.initialMacroCode,\n  (newCode) => {\n    if (newCode && props.isOpen) {\n      parseExistingMacro(newCode)\n    }\n  }\n)\n</script>\n"
  },
  {
    "path": "src/renderer/src/components/MatrixSetup.vue",
    "content": "<template>\n  <div>\n    <p class=\"max-w-xl py-4\">\n      Define the size of your keyboard matrix here, set it as big as you need. For easier wiring set\n      it to the max number of cols/rows on your keyboard\n    </p>\n    <div class=\"mb-4 flex flex-col gap-2\">\n      <div>\n        <p class=\"mb-2 text-sm\">Keyboard Type</p>\n        <select v-model=\"keyboardStore.keyboardType\" class=\"select select-bordered mb-2 w-full\">\n          <option value=\"normal\">Normal</option>\n          <option value=\"splitBle\">Split (Bluetooth)</option>\n          <option value=\"splitSerial\">Split (Serial)</option>\n          <option value=\"splitOnewire\">Split (1 Pin)</option>\n        </select>\n      </div>\n      <label\n        v-if=\"['splitSerial', 'splitOnewire'].includes(keyboardStore.keyboardType)\"\n        class=\"flex gap-2\"\n      >\n        <input v-model=\"keyboardStore.splitUsePio\" type=\"checkbox\" class=\"checkbox\" />\n        <span>usePio</span>\n      </label>\n      <label\n        v-if=\"['splitSerial'].includes(keyboardStore.keyboardType)\"\n        class=\"flex gap-2\"\n      >\n        <input v-model=\"keyboardStore.splitUartFlip\" type=\"checkbox\" class=\"checkbox\" />\n        <span>uartFlip</span>\n      </label>\n\n      <label\n        v-if=\"['splitSerial', 'splitOnewire', 'splitBle'].includes(keyboardStore.keyboardType)\"\n        class=\"flex gap-2\"\n      >\n        <input v-model=\"keyboardStore.splitFlip\" type=\"checkbox\" class=\"checkbox\" />\n        <span>splitFlip</span>\n      </label>\n    </div>\n    <div class=\"mb-4\">\n      <p class=\"mb-2 text-sm\">Wiring Method</p>\n      <select v-model=\"keyboardStore.wiringMethod\" class=\"select select-bordered w-full\">\n        <option value=\"matrix\">Matrix</option>\n        <option value=\"direct\">Direct Pins</option>\n      </select>\n    </div>\n\n    <div v-if=\"keyboardStore.wiringMethod === 'matrix'\" class=\"mb-8 grid grid-cols-2 gap-2\">\n      <InputLabel\n        v-model=\"keyboardStore.cols\"\n        placeholder=\"1\"\n        input-type=\"number\"\n        label=\"Matrix Width\"\n        :min=\"0\"\n        @input=\"checkMatrix\"\n      ></InputLabel>\n      <InputLabel\n        v-model=\"keyboardStore.rows\"\n        placeholder=\"1\"\n        input-type=\"number\"\n        label=\"Matrix Height\"\n        :min=\"0\"\n        @input=\"checkMatrix\"\n      ></InputLabel>\n    </div>\n    <div v-if=\"keyboardStore.wiringMethod === 'direct'\" class=\"mb-8\">\n      <InputLabel\n        v-model=\"keyboardStore.pins\"\n        placeholder=\"1\"\n        input-type=\"number\"\n        label=\"Pin Count\"\n        :min=\"1\"\n        @input=\"checkMatrix\"\n      ></InputLabel>\n    </div>\n    <div v-if=\"initialSetup\" class=\"mb-8 flex justify-center\">\n      <button\n        class=\"btn btn-primary\"\n        :class=\"{ 'btn-disabled': matrixEmpty }\"\n        @click=\"$emit('next')\"\n      >\n        Next\n      </button>\n    </div>\n    <div v-if=\"keyboardStore.wiringMethod === 'matrix'\" class=\"grid-visualizer\">\n      <div v-for=\"_row in keyboardStore.rows\" class=\"row\">\n        <div v-for=\"_col in keyboardStore.cols\" class=\"col\"></div>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport InputLabel from './ui/InputLabel.vue'\nimport { keyboardStore } from '../store'\nimport { computed } from 'vue'\n\ndefineProps(['initialSetup'])\nconst matrixEmpty = computed(() => {\n  return !(\n    keyboardStore.cols !== 0 &&\n    keyboardStore.rows &&\n    keyboardStore.diodeDirection !== undefined\n  )\n})\nconst checkMatrix = () => {\n  if (keyboardStore.cols < 0) keyboardStore.cols = 0\n  if (keyboardStore.rows < 0) keyboardStore.rows = 0\n  if (keyboardStore.pins < 0) keyboardStore.pins = 0\n}\n</script>\n\n<style lang=\"scss\" scoped>\n.grid-visualizer {\n  display: flex;\n  flex-direction: column;\n  gap: 4px;\n  align-items: center;\n  width: 100%;\n}\n\n.row {\n  display: flex;\n  gap: 4px;\n}\n\n.col {\n  width: 40px;\n  height: 40px;\n  border: 1px solid white;\n  opacity: 0.7;\n  @apply rounded;\n}\n</style>\n"
  },
  {
    "path": "src/renderer/src/components/PinSetup.vue",
    "content": "<template>\n  <div class=\"flex items-center justify-center\">\n    <p class=\"max-w-md py-4\">\n      Define the mapping for columns and rows to the microcontroller pins. For split keyboards\n      define the method for detecting the split side.\n    </p>\n  </div>\n  <div v-if=\"initialSetup\" class=\"my-8 flex w-full justify-center\">\n    <button\n      class=\"btn btn-primary\"\n      :class=\"{ 'btn-disabled': !pinsCompleted }\"\n      @click=\"$emit('next')\"\n    >\n      Next\n    </button>\n  </div>\n  <div class=\"mt-5 flex justify-center gap-8\">\n    <!-- LEFT COLUMN -->\n    <div class=\"flex-grow-0\">\n      <div\n        v-if=\"keyboardStore.wiringMethod === 'matrix'\"\n        class=\"mb-4 grid grid-cols-1 gap-2 rounded bg-base-100 p-2 py-8\"\n        style=\"width: 350px\"\n      >\n        <p class=\"flex items-center justify-center pb-4 text-xl font-bold\">\n          Row Pins\n          <span class=\"badge badge-primary ml-2 font-bold\">{{ keyboardStore.rowPins.length }}</span>\n        </p>\n        <div\n          v-for=\"(_pin, index) in keyboardStore.rowPins\"\n          class=\"grid grid-cols-6 items-center gap-2\"\n        >\n          <p class=\"mr-2 text-right\">{{ index }}</p>\n          <input\n            v-model=\"keyboardStore.rowPins[index]\"\n            class=\"input input-bordered input-sm col-span-4\"\n            type=\"text\"\n            placeholder=\"17\"\n          />\n        </div>\n      </div>\n      <div\n        v-if=\"keyboardStore.wiringMethod === 'matrix'\"\n        class=\"mb-4 grid grid-cols-1 gap-2 rounded bg-base-100 p-2 py-8\"\n        style=\"width: 350px\"\n      >\n        <p class=\"flex items-center justify-center pb-4 text-xl font-bold\">\n          Column Pins\n          <span class=\"badge badge-primary ml-2 font-bold\">{{ keyboardStore.colPins.length }}</span>\n        </p>\n        <div\n          v-for=\"(_pin, index) in keyboardStore.colPins\"\n          class=\"grid grid-cols-6 items-center gap-2\"\n        >\n          <span class=\"mr-2 text-right\">{{ index }}</span>\n          <input\n            v-model=\"keyboardStore.colPins[index]\"\n            class=\"input input-bordered input-sm col-span-4\"\n            type=\"text\"\n            placeholder=\"17\"\n          />\n        </div>\n      </div>\n      <div\n        v-if=\"keyboardStore.wiringMethod === 'direct'\"\n        class=\"mb-4 grid grid-cols-1 gap-2 rounded bg-base-100 p-2 py-8\"\n        style=\"width: 350px\"\n      >\n        <p class=\"flex items-center justify-center pb-4 text-xl font-bold\">\n          Direct Pins\n          <span class=\"badge badge-primary ml-2 font-bold\">{{\n            keyboardStore.directPins.length\n          }}</span>\n        </p>\n        <div\n          v-for=\"(_pin, index) in keyboardStore.directPins\"\n          class=\"grid grid-cols-6 items-center gap-2\"\n        >\n          <p class=\"mr-2 text-right\">{{ index }}</p>\n          <input\n            v-model=\"keyboardStore.directPins[index]\"\n            class=\"input input-bordered input-sm col-span-4\"\n            type=\"text\"\n            placeholder=\"17\"\n          />\n        </div>\n      </div>\n      <div\n        v-if=\"showVbusOption\"\n        class=\"mb-4 grid w-full grid-cols-1 gap-2 rounded bg-base-100 p-2 py-8\"\n        style=\"width: 350px\"\n      >\n        <p class=\"flex items-center justify-center pb-4 text-xl font-bold\">\n          Split Pins <span class=\"badge badge-primary ml-2 font-bold\">{{ numberOfSplitPins }}</span>\n        </p>\n        <div v-if=\"keyboardStore.splitSide === 'vbus'\">\n          <label class=\"mb-2 flex items-center gap-2\">\n            <span>VBUS Pin</span>\n            <input\n              v-model=\"keyboardStore.vbusPin\"\n              type=\"text\"\n              class=\"input input-bordered input-sm col-span-4\"\n            />\n          </label>\n          <p class=\"px-2 py-2 text-sm italic\">{{ vbusPinHint }}</p>\n        </div>\n        <div>\n          <label\n            v-if=\"keyboardStore.keyboardType === 'splitOnewire'\"\n            class=\"mb-2 flex items-center gap-2\"\n          >\n            <span class=\"mr-2 text-right\">SplitPin</span>\n            <input\n              v-model=\"keyboardStore.splitPinA\"\n              type=\"text\"\n              class=\"input input-bordered input-sm col-span-4\"\n            />\n          </label>\n          <label\n            v-if=\"keyboardStore.keyboardType === 'splitSerial'\"\n            class=\"mb-2 flex items-center gap-2\"\n          >\n            <span class=\"mr-2 text-right\">SplitPin A</span>\n            <input\n              v-model=\"keyboardStore.splitPinA\"\n              type=\"text\"\n              class=\"input input-bordered input-sm col-span-4\"\n            />\n          </label>\n          <label\n            v-if=\"keyboardStore.keyboardType === 'splitSerial'\"\n            class=\"mb-2 flex items-center gap-2\"\n          >\n            <span class=\"mr-2 text-right\">SplitPin B</span>\n            <input\n              v-model=\"keyboardStore.splitPinB\"\n              type=\"text\"\n              class=\"input input-bordered input-sm col-span-4\"\n            />\n          </label>\n          <p class=\"px-2 py-2 text-sm italic\">{{ splitPinHint }}</p>\n        </div>\n      </div>\n    </div>\n    <!-- RIGHT COLUMN -->\n    <div class=\"flex w-1/3 flex-col items-center\" style=\"width: 400px\">\n      <div class=\"mb-4 w-full\">\n        <p class=\"mb-2 text-sm\">Pin Prefix</p>\n        <select v-model=\"keyboardStore.pinPrefix\" class=\"select select-bordered mb-2 w-full\">\n          <option value=\"gp\">GP</option>\n          <option value=\"none\">none</option>\n          <option value=\"board\">board</option>\n          <option value=\"quickpin\">quickpin</option>\n        </select>\n        <p class=\"px-2 py-2 text-sm italic\">{{ pinPrefixHint }}</p>\n      </div>\n\n      <div v-if=\"keyboardStore.wiringMethod === 'matrix'\" class=\"mb-4 w-full\">\n        <p class=\"mb-2 text-sm\">Diode Direction</p>\n        <select v-model=\"keyboardStore.diodeDirection\" class=\"select select-bordered mb-2 w-full\">\n          <option value=\"COL2ROW\">COL2ROW</option>\n          <option value=\"ROW2COL\">ROW2COL</option>\n        </select>\n      </div>\n\n      <div v-if=\"keyboardStore.isSplit()\" class=\"mb-4 w-full\">\n        <p class=\"mb-2 text-sm\">Split Side Detection</p>\n        <select v-model=\"keyboardStore.splitSide\" class=\"select select-bordered mb-2 w-full\">\n          <option value=\"left\">Config (Left)</option>\n          <option value=\"right\">Config (Right)</option>\n          <option v-if=\"showVbusOption\" value=\"vbus\">VBUS</option>\n          <option value=\"label\">Drive Label (L/R)</option>\n        </select>\n        <p class=\"px-2 py-2 text-sm italic\">{{ splitSideHint }}</p>\n      </div>\n\n      <div class=\"mt-4 w-full\">\n        <p class=\"mb-2 text-sm\">Microcontroller</p>\n        <select v-model=\"keyboardStore.controller\" class=\"select select-bordered w-full\">\n          <option\n            v-for=\"microcontroller of microcontrollers\"\n            :key=\"microcontroller.id\"\n            :value=\"microcontroller.id\"\n          >\n            {{ microcontroller.name }}\n          </option>\n          <option value=\"\">other</option>\n        </select>\n      </div>\n    </div>\n    <div class=\"flex w-1/3 flex-col items-center\">\n      <div v-if=\"selectedMicrocontroller.id\">\n        <p class=\"py-4\" v-html=\"selectedMicrocontroller.information\"></p>\n        <img\n          v-if=\"selectedMicrocontroller.image\"\n          :src=\"`/src/assets/microcontrollers/${selectedMicrocontroller.id}.png`\"\n          :alt=\"`Pinout Image of ${selectedMicrocontroller.name}`\"\n          class=\"board-image\"\n        />\n        <small class=\"license-link base-300\">\n          Image License:\n          <a\n            :href=\"selectedMicrocontroller.licenseUrl\"\n            target=\"_blank\"\n            class=\"link-primary pr-1\"\n            v-text=\"selectedMicrocontroller.license\"\n          ></a\n          >|<a :href=\"selectedMicrocontroller.imageUrl\" target=\"_blank\" class=\"link-primary pl-1\"\n            >source</a\n          >\n        </small>\n      </div>\n\n      <div v-if=\"!keyboardStore.controller\">\n        <p class=\"py-4\">\n          Feel free to submit other microcontroller pinouts. Ensure you have the permission to use\n          the pinout image if it has not been created by you. In the meantime here are links to\n          other pinouts\n        </p>\n        <p>Currently this tool works with any RP2040 controller.</p>\n        <p class=\"py-4\">Just look for a pinout and use any pin that is starting with GP.</p>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { computed } from 'vue' // import { useRouter } from 'vue-router'\nimport { keyboardStore, pinPrefixHint, splitPinHint, splitSideHint, vbusPinHint } from '../store' // const router = useRouter()\nimport microcontrollers from '@renderer/assets/microcontrollers/microcontrollers.json'\n\ndefineProps<{ initialSetup: boolean }>()\ndefineEmits(['next'])\nconst selectedMicrocontroller = computed(\n  () =>\n    microcontrollers.find((m) => m.id == keyboardStore.controller) || {\n      id: false,\n      license: '',\n      licenseUrl: '',\n      information: '',\n      imageUrl: '',\n      name: '',\n      image: ''\n    }\n)\n\n// validate pin count\nif (keyboardStore.wiringMethod === 'matrix') {\n  if (keyboardStore.rows !== keyboardStore.rowPins.length) {\n    keyboardStore.rowPins = Array.from(\n      { length: keyboardStore.rows },\n      (_x, i) => keyboardStore.rowPins[i]\n    )\n    keyboardStore.rowPins = keyboardStore.rowPins.slice(0, keyboardStore.rows)\n  }\n\n  if (keyboardStore.cols !== keyboardStore.colPins.length) {\n    keyboardStore.colPins = Array.from(\n      { length: keyboardStore.cols },\n      (_x, i) => keyboardStore.colPins[i]\n    )\n    keyboardStore.colPins = keyboardStore.colPins.slice(0, keyboardStore.cols)\n  }\n} else if (keyboardStore.wiringMethod === 'direct') {\n  keyboardStore.directPins = Array.from(\n    { length: keyboardStore.pins },\n    (_x, i) => keyboardStore.directPins[i]\n  )\n  keyboardStore.directPins = keyboardStore.directPins.slice(0, keyboardStore.pins)\n}\n\nconst pinsCompleted = computed(() => {\n  if (keyboardStore.colPins.includes('')) return false\n  if (keyboardStore.rowPins.includes('')) return false\n  return true\n})\n\nconst showVbusOption = computed(() => {\n  return (\n    keyboardStore.keyboardType === 'splitSerial' || keyboardStore.keyboardType === 'splitOnewire'\n  )\n})\n\nconst numberOfSplitPins = computed(() => {\n  let pins = 0\n  if (keyboardStore.splitSide === 'vbus') pins++\n  if (keyboardStore.keyboardType === 'splitSerial') return pins + 2\n  if (keyboardStore.keyboardType === 'splitOnewire') return pins + 1\n  return pins\n})\n</script>\n<style lang=\"scss\" scoped>\n.license-link {\n  font-size: 0.55em;\n}\n\n.board-image {\n  //width: 180px;\n  //max-width: 180px;\n  max-height: 60vh;\n  height: auto;\n  font-size: 0.7em;\n  background-size: contain;\n}\n\n.controller-labels {\n  @apply absolute grid;\n  width: 130px;\n  padding-top: 15px;\n  font-size: 14px;\n  line-height: 21.4px;\n  font-family: 'Lucida Console', monospace;\n  z-index: 2;\n\n  &-right {\n    @apply right-0 text-left;\n    //width: 188px;\n  }\n\n  &-left {\n    @apply text-right;\n  }\n\n  &-bottom {\n    transform: rotateZ(-90deg);\n    @apply text-right;\n    top: 295px;\n    left: 128px;\n  }\n\n  & > div {\n    @apply rounded px-3;\n    &:hover {\n      background: #666666;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "src/renderer/src/components/RawKeymapEditor.vue",
    "content": "<template>\n  <p class=\"font-bold\">Keymap</p>\n  <div>\n    <div v-for=\"(layer, layerindex) in keyboardStore.keymap\">\n      layer {{ layerindex }}\n      <div\n        class=\"mb-4 grid gap-2 rounded border border-white border-opacity-40 p-2\"\n        :style=\"{\n          gridTemplateColumns: `repeat(${keyboardStore.cols}, minmax(0, 1fr))`\n        }\"\n      >\n        <input\n          v-for=\"(_key, index) in layer\"\n          v-model=\"keyboardStore.keymap[layerindex][index]\"\n          class=\"input input-bordered input-sm\"\n          placeholder=\"KC.A\"\n        />\n      </div>\n    </div>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { keyboardStore } from '../store'\n</script>\n\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "src/renderer/src/components/RgbSetup.vue",
    "content": "<template>\n  <div>\n    <label class=\"flex gap-4\">\n      <input v-model=\"rgbEnabled\" class=\"checkbox\" type=\"checkbox\" @change=\"toggleRgbEnabled\" />\n      <p>Enable RGB</p>\n    </label>\n    <p class=\"my-4 rounded bg-base-300 p-4\">\n      Info: To enable RGB you also will need a neopixel.mpy file in the lib folder on your\n      CircuitPython drive eg. adafruit-circuitpython-neopixel-py-6.3.11.zip as neopixel.py . You can\n      download that here:\n      <a\n        href=\"https://github.com/adafruit/Adafruit_CircuitPython_NeoPixel/releases\"\n        class=\"underline\"\n        target=\"_blank\"\n        >Adafruit GitHub</a\n      >\n    </p>\n\n    <div class=\"mb-4 flex\">\n      <div class=\"grid grid-cols-2 gap-2\">\n        <label>RGB Pin</label>\n        <input\n          v-model=\"rgbPin\"\n          type=\"text\"\n          class=\"input input-bordered input-sm\"\n          placeholder=\"board.GP3\"\n          @change=\"savePin\"\n        />\n        <label>RGB Number of LEDS</label>\n        <input\n          v-model=\"rgbNumLeds\"\n          type=\"text\"\n          class=\"input input-bordered input-sm\"\n          placeholder=\"14\"\n          @change=\"saveNumLeds\"\n        />\n        <label>Animation Mode</label>\n        <select\n          v-model=\"rgbAnimationMode\"\n          class=\"select select-bordered input-sm mb-4\"\n          @change=\"saveMode\"\n        >\n          <option value=\"0\">Off</option>\n          <option value=\"1\">Static</option>\n          <option value=\"2\">Static Standby</option>\n          <option value=\"3\">Breathing</option>\n          <option value=\"4\">Rainbow</option>\n          <option value=\"5\">Breathing Rainbow</option>\n          <option value=\"6\">Knight</option>\n          <option value=\"7\">Swirl</option>\n          <option value=\"8\">Custom</option>\n        </select>\n        <div\n          v-if=\"rgbAnimationMode != 0 && rgbAnimationMode != 1 && rgbAnimationMode != 2\"\n          class=\"col-span-2 flex justify-between\"\n        >\n          <label>Animation Speed</label>\n          <input\n            v-model=\"rgbAnimationSpeed\"\n            type=\"text\"\n            class=\"input input-bordered input-sm\"\n            placeholder=\"1\"\n            @change=\"saveAnimationSpeed\"\n          />\n        </div>\n        <div\n          v-if=\"rgbAnimationMode == 3 || rgbAnimationMode == 5 || rgbAnimationMode == 8\"\n          class=\"col-span-2 flex justify-between\"\n        >\n          <label>Breathe Center</label>\n          <input\n            v-model=\"rgbBreatheCenter\"\n            type=\"text\"\n            class=\"input input-bordered input-sm\"\n            placeholder=\"1\"\n            min=\"1\"\n            max=\"2.7\"\n            step=\"0.1\"\n            @change=\"saveBreatheCenter\"\n          />\n        </div>\n        <div\n          v-if=\"rgbAnimationMode == 6 || rgbAnimationMode == 8\"\n          class=\"col-span-2 flex justify-between\"\n        >\n          <label>Knight Effect Length</label>\n          <input\n            v-model=\"rgbKnightEffectLength\"\n            type=\"text\"\n            class=\"input input-bordered input-sm\"\n            placeholder=\"1\"\n            @change=\"saveKnightEffectLength\"\n          />\n        </div>\n      </div>\n    </div>\n    <HsvColorPicker @change=\"saveColor\" />\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { keyboardStore } from '../store'\nimport { onMounted, ref } from 'vue'\nimport HsvColorPicker from './HsvColorPicker.vue'\n\nconst rgbPin = ref('')\nconst rgbNumLeds = ref('')\nconst rgbAnimationMode = ref(0)\nconst rgbAnimationSpeed = ref(0)\nconst rgbBreatheCenter = ref(0)\nconst rgbKnightEffectLength = ref(0)\nconst rgbEnabled = ref(false)\n\nonMounted(() => {\n  rgbPin.value = keyboardStore.rgbPin\n  rgbNumLeds.value = String(keyboardStore.rgbNumLeds)\n  rgbAnimationMode.value = keyboardStore.rgbOptions.animationMode\n  rgbAnimationSpeed.value = keyboardStore.rgbOptions.animationSpeed\n  rgbBreatheCenter.value = keyboardStore.rgbOptions.breatheCenter\n  rgbKnightEffectLength.value = keyboardStore.rgbOptions.knightEffectLength\n\n  rgbEnabled.value = keyboardStore.kbFeatures.some((feature) => feature.toLowerCase() === 'rgb')\n})\n\nconst savePin = () => {\n  keyboardStore.rgbPin = rgbPin.value\n}\n\nconst saveNumLeds = () => {\n  keyboardStore.rgbNumLeds = Number(rgbNumLeds.value)\n}\n\nconst saveMode = () => {\n  keyboardStore.rgbOptions.animationMode = Number(rgbAnimationMode.value)\n}\n\nconst saveAnimationSpeed = () => {\n  keyboardStore.rgbOptions.animationSpeed = Number(rgbAnimationSpeed.value)\n}\n\nconst saveBreatheCenter = () => {\n  keyboardStore.rgbOptions.breatheCenter = Number(rgbBreatheCenter.value)\n}\n\nconst saveKnightEffectLength = () => {\n  keyboardStore.rgbOptions.knightEffectLength = Number(rgbKnightEffectLength.value)\n}\n\nconst saveColor = (hsvColor) => {\n  keyboardStore.rgbOptions.hueDefault = hsvColor.hue\n  keyboardStore.rgbOptions.satDefault = hsvColor.sat\n  keyboardStore.rgbOptions.valDefault = hsvColor.val\n}\n\nconst toggleRgbEnabled = () => {\n  if (keyboardStore.kbFeatures.some((feature) => feature.toLowerCase() === 'rgb')) {\n    keyboardStore.kbFeatures = keyboardStore.kbFeatures.filter(\n      (feature) => feature.toLowerCase() !== 'rgb'\n    )\n  } else {\n    keyboardStore.kbFeatures.push('rgb')\n  }\n}\n</script>\n\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "src/renderer/src/components/SetupMethodSelector.vue",
    "content": "<template>\n  <div class=\"flex flex-col items-center justify-center p-8\">\n    <h2 class=\"mb-6 text-2xl font-bold\">Choose Setup Method</h2>\n    <div class=\"mb-8 text-center\">\n      <p class=\"mb-2\">\n        {{\n          kmkInstallState !== 'done'\n            ? 'Installing firmware... (should take up to 10 seconds)'\n            : 'Firmware installed'\n        }}\n      </p>\n      <progress v-if=\"kmkInstallState !== 'done'\" class=\"progress progress-primary w-56\"></progress>\n      <p v-else>\n        initial Firmware install complete, please unplug your keyboard and plug it back in to\n        continue\n      </p>\n    </div>\n    <div v-if=\"kmkInstallState === 'done'\">\n      <!-- Serial Port Selection -->\n      <h3 class=\"mb-3 text-xl font-semibold\">Serial Port</h3>\n      <p class=\"mb-4 text-center\">\n        Select the serial port for your keyboard. We've automatically detected the most likely port.\n        <button class=\"btn btn-primary btn-sm mt-2\" @click=\"scanPorts\">Rescan</button>\n      </p>\n\n      <div class=\"space-y-4\">\n        <div v-if=\"ports.length > 0\" class=\"space-y-2\">\n          <div\n            v-for=\"port in ports\"\n            :key=\"port.port\"\n            class=\"cursor-pointer rounded-lg border p-3 transition-colors\"\n            :class=\"{\n              'border-primary bg-primary/10': selectedPort === port.port\n            }\"\n            @click=\"selectPort(port)\"\n          >\n            <div class=\"flex items-center justify-between\">\n              <div>\n                <p class=\"font-medium\">{{ port.port }}</p>\n                <p class=\"text-sm text-gray-600\">\n                  {{ port.manufacturer || 'Unknown manufacturer' }}\n                  {{ port.serialNumber ? `(${port.serialNumber})` : '' }}\n                </p>\n              </div>\n              <div v-if=\"isRecommendedPort(port)\" class=\"text-sm text-green-600\">Recommended</div>\n            </div>\n          </div>\n        </div>\n        <div v-else class=\"text-center text-gray-600\">\n          <p>No serial ports found.</p>\n          <button class=\"mt-2 rounded border px-4 py-2 text-sm hover:bg-gray-50\" @click=\"scanPorts\">\n            Rescan\n          </button>\n        </div>\n      </div>\n    </div>\n  </div>\n\n  <!-- Setup Method Selection -->\n  <div v-if=\"selectedPort\" class=\"grid max-w-4xl grid-cols-2 gap-8 p-4\">\n    <div\n      class=\"cursor-pointer rounded-lg bg-base-200 p-6 transition-colors\"\n      :class=\"{\n        'pointer-events-none cursor-not-allowed opacity-20': !selectedPort,\n        'hover:bg-base-200': selectedPort\n      }\"\n      @click=\"selectMethod('manual')\"\n    >\n      <h3 class=\"mb-3 text-xl font-semibold text-base-content\">Manual Setup</h3>\n      <p class=\"\">\n        Configure your keyboard manually by specifying the pins, rows, columns, and diode direction.\n        Recommended for experienced users or if you have your keyboard's documentation.\n      </p>\n    </div>\n    <div\n      class=\"cursor-pointer rounded-lg bg-base-200 p-6 transition-colors\"\n      :class=\"{\n        'pointer-events-none cursor-not-allowed opacity-20': !selectedPort,\n        'hover:bg-base-200': selectedPort\n      }\"\n      @click=\"selectMethod('automatic')\"\n    >\n      <h3 class=\"mb-3 text-xl font-semibold\">Automatic Detection (Experimental)</h3>\n      <p class=\"\">\n        Let Pog automatically detect your keyboard's configuration by pressing each key. We'll flash\n        a special firmware and guide you through the process.\n      </p>\n    </div>\n  </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { keyboardStore } from '@renderer/store'\nimport { ref, onMounted } from 'vue'\nimport router from '../router'\n// import { directiveHooks } from '@vueuse/core'\n\ninterface SerialPort {\n  port: string\n  manufacturer?: string\n  serialNumber?: string\n}\n\nconst ports = ref<SerialPort[]>([])\nconst selectedPort = ref('')\nasync function scanPorts() {\n  try {\n    const result = await window.api.serialPorts()\n    console.log('raw serialPorts', result)\n    // Group ports by serial number\n    const portsBySerial = result.reduce((acc, port) => {\n      if (port.serialNumber) {\n        if (!acc[port.serialNumber]) {\n          acc[port.serialNumber] = []\n        }\n        acc[port.serialNumber].push(port)\n      }\n      return acc\n    }, {} as Record<string, SerialPort[]>)\n    console.log('portsBySerial', portsBySerial)\n    // Sort ports within each group and take only the first port (lower number)\n    ports.value = Object.entries(portsBySerial).map(([serialNumber, ports]) => {\n      ports.sort((a, b) => a.port.localeCompare(b.port))\n      // Store both ports in the keyboard store\n      if (ports.length >= 2) {\n        const keyboard = {\n          serialNumber,\n          serialPortA: ports[0].port,\n          serialPortB: ports[1].port,\n          manufacturer: ports[0].manufacturer,\n          port: ports[0].port, // Keep the first port as the main port for backwards compatibility\n          hasDataSerial: ports.length >= 2\n        }\n        return keyboard\n      }\n      return ports[0]\n      // return false\n    })\n    //   .filter((port) => port !== false)\n    console.log('ports', ports.value)\n\n    // Auto-select recommended port if available\n    const recommended = ports.value.find((port) => isRecommendedPort(port))\n    if (recommended?.serialNumber) {\n      keyboardStore.serialNumber = recommended.serialNumber\n    }\n  } catch (error) {\n    console.error('Failed to scan ports:', error)\n  }\n}\n\nfunction isRecommendedPort(port: SerialPort): boolean {\n  const manufacturer = port.manufacturer?.toLowerCase() || ''\n  return (\n    manufacturer.includes('circuitpython') ||\n    manufacturer.endsWith('-pog') ||\n    manufacturer.startsWith('pog-') ||\n    manufacturer === 'pog'\n  )\n}\n\nfunction selectPort(port: SerialPort) {\n  console.log('selectPort', port)\n  keyboardStore.serialNumber = port.serialNumber || 'not found'\n  selectedPort.value = port.port\n}\n\nfunction selectMethod(method: 'manual' | 'automatic') {\n  if (keyboardStore.serialNumber) {\n    handleMethodSelect({ method })\n  }\n}\n\nconst progress = ref(0)\nconst kmkInstallState = ref('')\n\nonMounted(() => {\n  scanPorts()\n  // flash the firmware\n  window.api.flashDetectionFirmware({\n    drivePath: keyboardStore.path || ''\n    // serialNumber: keyboardStore.serialNumber\n  })\n})\n\nwindow.api.onUpdateFirmwareInstallProgress(\n  (_event: Event, value: { state: string; progress: number }) => {\n    console.log('kmk progress', value)\n    kmkInstallState.value = value.state\n    progress.value = Math.round(value.progress)\n  }\n)\n\nasync function handleMethodSelect({ method }: { method: 'manual' | 'automatic' }) {\n  try {\n    // Connect to the serial port first\n    // await window.api.serialConnect(port)\n    // keyboardStore.usingSerial = true\n    // save the serial number\n    if (method === 'manual') {\n      router.push('/setup-wizard')\n    } else {\n      console.log('flashing detection firmware', keyboardStore.path, keyboardStore.serialNumber)\n      // For automatic setup, we need to flash the detection firmware first\n      window.api.flashDetectionFirmware({\n        drivePath: keyboardStore.path || '',\n        serialNumber: keyboardStore.serialNumber\n      })\n      router.push('/automatic-setup/mapping')\n    }\n  } catch (error) {\n    console.error('Failed to setup keyboard:', error)\n    // TODO: Show error to user\n  }\n}\n</script>\n"
  },
  {
    "path": "src/renderer/src/components/VariantOption.vue",
    "content": "<template>\n  <div class=\"flex items-center pr-4\">\n    <div class=\"mr-2\">\n      <button class=\"btn btn-primary btn-xs\" @click=\"removeOption\">\n        <i class=\"mdi mdi-close\"></i>\n      </button>\n    </div>\n    <div\n      v-if=\"layout.variants && layout.variants.length > 0\"\n      class=\"flex h-12 w-full items-center gap-2\"\n    >\n      <input v-model=\"variantName\" type=\"text\" class=\"input input-bordered input-sm\" />\n      <select\n        v-model=\"selectedOption\"\n        class=\"select select-bordered select-sm mr-4\"\n        @change=\"selectMultiVariant\"\n      >\n        <option\n          v-for=\"(option, oindex) in layout.variants.filter((_a, i) => i !== 0)\"\n          :value=\"oindex\"\n        >\n          {{ option }}\n        </option>\n      </select>\n    </div>\n    <div v-else class=\"flex h-12 w-full items-center gap-2\">\n      <input v-model=\"variantName\" class=\"input input-bordered input-sm w-full\" />\n      <input v-model=\"selectedBool\" type=\"checkbox\" class=\"checkbox\" @input=\"selectBool\" />\n    </div>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { computed, ref } from 'vue'\nimport { keyboardStore } from '../store'\n\nconst props = defineProps(['layout', 'index'])\nconst selectedOption = ref(0)\nconst selectedBool = ref(false)\nselectedBool.value = keyboardStore.layouts[props.index].selected === 1\nselectedOption.value = keyboardStore.layouts[props.index].selected\nconst selectMultiVariant = () => {\n  selectVariant({ layoutIndex: props.index, variant: selectedOption.value })\n}\nconst selectBool = () => {\n  selectVariant({\n    layoutIndex: props.index,\n    variant: !selectedBool.value ? 1 : 0\n  })\n}\nconst selectVariant = ({ layoutIndex, variant }: { layoutIndex: number; variant: number }) => {\n  keyboardStore.layouts[layoutIndex].selected = variant\n}\n\nconst variantName = computed({\n  get() {\n    return props.layout.name\n  },\n  set(newVal) {\n    keyboardStore.layouts[props.index].name = newVal\n  }\n})\nconst removeOption = () => {\n  keyboardStore.layouts = keyboardStore.layouts.filter((_a, index) => index !== props.index)\n}\n</script>\n\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "src/renderer/src/components/VariantSwitcher.vue",
    "content": "<template>\n  <div>\n    <button class=\"btn btn-primary btn-sm mb-4\" @click=\"addLayoutOption\">\n      <i class=\"mdi mdi-plus\"></i> add Layout Option\n    </button>\n    <VariantOption\n      v-for=\"(layout, index) in keyboardStore.layouts\"\n      :layout=\"layout\"\n      :index=\"index\"\n    />\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { keyboardStore } from '../store'\nimport VariantOption from './VariantOption.vue'\nconst addLayoutOption = () => {\n  keyboardStore.layouts.push({\n    name: 'variant',\n    selected: 0,\n    variants: []\n  })\n}\n</script>\n\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "src/renderer/src/components/debug.vue",
    "content": "<template>\n  <div class=\"flex h-full flex-col\">\n    <div class=\"mb-2 text-sm\">\n      <div class=\"mb-2 flex items-center justify-between\">\n        <h3 class=\"font-bold\">Debug Console</h3>\n        <div class=\"form-control\">\n          <label class=\"label cursor-pointer\">\n            <input v-model=\"autoscroll\" type=\"checkbox\" class=\"checkbox checkbox-xs\" />\n            <span class=\"label-text pl-1 text-xs\">autoscroll</span>\n          </label>\n        </div>\n      </div>\n      <div class=\"mb-2 text-xs opacity-70\">\n        Connect to your keyboard's serial console for debugging. Press the reload key on your\n        keyboard first.\n      </div>\n    </div>\n\n    <div class=\"mb-2 flex gap-2\">\n      <div class=\"flex flex-grow gap-2\">\n        <select\n          v-model=\"selectedPort\"\n          class=\"select select-sm flex-grow\"\n          :disabled=\"isConnected || isConnecting\"\n        >\n          <option value=\"\">Select a port</option>\n          <option v-for=\"port in sortedPorts\" :key=\"port.port\" :value=\"port.port\">\n            {{ port.manufacturer }} - {{ port.port }}\n          </option>\n        </select>\n        <button\n          class=\"btn btn-square btn-sm\"\n          :disabled=\"isConnected || isConnecting\"\n          @click=\"refreshPorts\"\n        >\n          <i class=\"mdi mdi-refresh\"></i>\n        </button>\n      </div>\n\n      <button\n        v-if=\"!isConnected\"\n        class=\"btn btn-sm\"\n        :disabled=\"!selectedPort || isConnecting\"\n        @click=\"connect\"\n      >\n        {{ isConnecting ? 'Connecting...' : 'Connect' }}\n      </button>\n      <button v-else class=\"btn btn-error btn-sm\" @click=\"disconnect\">Disconnect</button>\n    </div>\n\n    <div v-if=\"statusMessage\" class=\"mb-2\">\n      <div class=\"badge badge-sm\" :class=\"isConnected ? 'badge-success' : 'badge-error'\">\n        {{ statusMessage }}\n      </div>\n    </div>\n\n    <textarea\n      id=\"repl-output\"\n      v-model=\"output\"\n      class=\"textarea textarea-bordered mb-2 flex-grow p-2 font-mono text-xs\"\n      readonly\n    ></textarea>\n\n    <div class=\"mb-2 flex gap-2\">\n      <input\n        v-model=\"inputData\"\n        class=\"input input-bordered input-sm flex-grow text-xs\"\n        :disabled=\"!isConnected\"\n        placeholder=\"Enter command...\"\n        @keyup.enter=\"sendData\"\n      />\n      <button class=\"btn btn-sm\" :disabled=\"!isConnected\" @click=\"sendData\">Send</button>\n    </div>\n\n    <div class=\"flex gap-2\">\n      <button class=\"btn btn-sm flex-grow\" :disabled=\"!isConnected\" @click=\"enterRepl\">\n        Enter REPL\n      </button>\n      <button class=\"btn btn-sm flex-grow\" :disabled=\"!isConnected\" @click=\"exitRepl\">\n        Exit REPL\n      </button>\n    </div>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'\nimport { keyboardStore } from '@renderer/store'\nimport { serialLogs } from '@renderer/store/serial'\n\nconst output = ref('')\nconst inputData = ref('')\nconst ports = ref<any[]>([])\nconst selectedPort = ref('')\nconst statusMessage = ref('')\nconst autoscroll = ref(true)\nconst isConnected = ref(false)\nconst isConnecting = ref(false)\n\nconst sortedPorts = computed(() => {\n  return [...ports.value]\n    .filter((a) => a.serialNumber !== undefined)\n    .sort((a, b) => {\n      // First try to match by serial number if available\n      if (keyboardStore.serialNumber) {\n        const aMatchesSerial = a.serialNumber === keyboardStore.serialNumber\n        const bMatchesSerial = b.serialNumber === keyboardStore.serialNumber\n        if (aMatchesSerial && !bMatchesSerial) return -1\n        if (!aMatchesSerial && bMatchesSerial) return 1\n      }\n      \n      // Then sort by port number\n      const aNum = parseInt(a.port.replace(/[^\\d]/g, ''))\n      const bNum = parseInt(b.port.replace(/[^\\d]/g, ''))\n      if (aNum < bNum) return -1\n      if (aNum > bNum) return 1\n      return 0\n    })\n})\n\n// Auto-connect to the first available port when ports are refreshed\nwatch(sortedPorts, (newPorts) => {\n  if (!isConnected.value && !isConnecting.value && newPorts.length > 0) {\n    selectedPort.value = newPorts[0].port\n    connect()\n  }\n}, { immediate: true })\n\nconst scrollTextarea = () => {\n  nextTick(() => {\n    const replOutput = document.getElementById('repl-output')\n    if (replOutput && autoscroll.value) {\n      replOutput.scrollTop = replOutput.scrollHeight\n    }\n  })\n}\nconst unwatch = ref<() => void>()\nonMounted(async () => {\n  await refreshPorts()\n  // serial handler now centralized in App.vue via store/serial\n  // Listen for updates by watching the store instead of attaching listeners here\n  // Keep textarea in sync\n  unwatch.value = watch(\n    () => serialLogs.value,\n    (logs) => {\n      output.value = logs.slice().reverse().join('\\n') + '\\n'\n      scrollTextarea()\n    },\n    { immediate: true, deep: true }\n  )\n  window.api.serialConnectionStatus((_event: Event, status: any) => {\n    console.log('Connection status:', status)\n    isConnected.value = status.connected\n    isConnecting.value = false\n    statusMessage.value = status.connected ? 'Connected' : status.error || 'Disconnected'\n  })\n})\n\nonUnmounted(() => {\n  unwatch.value?.()\n})\n\nconst refreshPorts = async () => {\n  try {\n    ports.value = await window.api.serialPorts()\n    if (ports.value.length === 0) {\n      statusMessage.value = 'No ports available'\n    }\n  } catch (error) {\n    console.error('Failed to refresh ports:', error)\n    statusMessage.value = 'Failed to refresh ports'\n  }\n}\n\nconst sendData = () => {\n  if (!isConnected.value) {\n    statusMessage.value = 'Not connected'\n    return\n  }\n  if (!inputData.value.trim()) {\n    return\n  }\n  window.api.serialSend(inputData.value)\n  inputData.value = ''\n}\n\nconst connect = async () => {\n  if (!selectedPort.value) {\n    statusMessage.value = 'Please select a port'\n    return\n  }\n\n  try {\n    isConnecting.value = true\n    statusMessage.value = 'Connecting...'\n    await window.api.serialConnect(selectedPort.value)\n  } catch (error: any) {\n    console.error('Failed to connect to the port:', error)\n    statusMessage.value = error.message || 'Failed to connect'\n    isConnecting.value = false\n    isConnected.value = false\n  }\n}\n\nconst disconnect = async () => {\n  try {\n    statusMessage.value = 'Disconnecting...'\n    await window.api.serialDisconnect()\n    isConnected.value = false\n    selectedPort.value = ''\n    statusMessage.value = 'Disconnected'\n  } catch (error: any) {\n    console.error('Failed to disconnect:', error)\n    statusMessage.value = error.message || 'Failed to disconnect'\n  }\n}\n\nconst enterRepl = () => {\n  if (!isConnected.value) {\n    statusMessage.value = 'Not connected'\n    return\n  }\n  statusMessage.value = 'Entering REPL...'\n  window.api.serialSend('ctrlc')\n  setTimeout(() => {\n    window.api.serialSend('ctrlc')\n  }, 1000)\n}\n\nconst exitRepl = () => {\n  if (!isConnected.value) {\n    statusMessage.value = 'Not connected'\n    return\n  }\n  statusMessage.value = 'Exiting REPL...'\n  window.api.serialSend('ctrld')\n}\n</script>\n\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "src/renderer/src/components/installPogFirmware.vue",
    "content": "<template>\n  <div class=\"min-h-screen bg-base-100 p-6\">\n    <div class=\"mx-auto max-w-2xl space-y-8\">\n      <div class=\"card bg-base-200 shadow-lg\">\n        <div class=\"card-body\">\n          <h2 class=\"card-title text-2xl font-bold text-base-content text-center\">Installing Firmware</h2>\n          \n          <!-- Status Display -->\n          <div class=\"text-center space-y-4 py-4\">\n            <div class=\"text-lg font-medium text-base-content/80 capitalize\">\n              {{ kmkInstallState || 'Preparing...' }}\n            </div>\n            \n            <!-- Progress Bar -->\n            <div class=\"w-full\">\n              <progress \n                class=\"progress progress-primary w-full\" \n                :value=\"progress\" \n                :max=\"100\"\n              ></progress>\n              <div class=\"text-sm text-base-content/60 mt-2\">\n                {{ progress }}% Complete\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script setup lang=\"ts\">\nimport router from '@renderer/router'\nimport { keyboardStore } from '@renderer/store'\nimport { onMounted, ref } from 'vue'\n\nconst progress = ref(0)\nconst kmkInstallState = ref('')\n\nconst updateKMK = async () => {\n  await window.api.updateFirmware()\n  kmkInstallState.value = 'downloading'\n}\n\nconst updatePOG = async () => {\n  window.api.saveConfiguration(\n    JSON.stringify({ pogConfig: keyboardStore.serialize(), writeFirmware: true })\n  )\n}\nonMounted(() => {\n  updateKMK()\n})\n\nwindow.api.onUpdateFirmwareInstallProgress(\n  (_event: Event, value: { state: string; progress: number }) => {\n    console.log('kmk progress', value)\n    // don't go back from done\n    if (kmkInstallState.value !== 'done') {\n      kmkInstallState.value = value.state\n      console.log('progress', value.progress)\n      progress.value = Math.round(value.progress)\n      if (value.state === 'done') {\n        keyboardStore.firmwareInstalled = true\n        updatePOG()\n        router.push('/configurator/coordmap')\n      }\n    }\n  }\n)\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "src/renderer/src/components/picker-layouts/Colemak.vue",
    "content": "<template>\n  <div class=\"key-chooser flex\">\n    <div class=\"row\">\n      <div class=\"key\" @click=\"emit('key', 'KC.ESC')\">ESC</div>\n      <div class=\"blocker-full\"></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.F1')\">F1</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.F2')\">F2</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.F3')\">F3</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.F4')\">F4</div>\n      <div class=\"blocker-half\"></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.F5')\">F5</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.F6')\">F6</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.F7')\">F7</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.F8')\">F8</div>\n      <div class=\"blocker-half\"></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.F9')\">F9</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.F10')\">F10</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.F11')\">F11</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.F12')\">F12</div>\n      <div class=\"blocker-half\"></div>\n      <div class=\"key sm\" @click=\"emit('key', 'KC.PSCREEN')\">Print Screen</div>\n      <div class=\"key sm\" @click=\"emit('key', 'KC.SCROLLLOCK')\">Scroll Lock</div>\n      <div class=\"key sm\" @click=\"emit('key', 'KC.PAUSE')\">Pause</div>\n      <div class=\"blocker-half\"></div>\n      <div class=\"blocker-full\"></div>\n      <div class=\"blocker-full\"></div>\n      <div class=\"blocker-full\"></div>\n      <div class=\"blocker-full\"></div>\n    </div>\n    <div class=\"row\">\n      <div class=\"key\" @click=\"emit('key', 'KC.GRV')\"><span>~</span><span>`</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.N1')\"><span>!</span><span>1</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.N2')\"><span>@</span><span>2</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.N3')\"><span>#</span><span>3</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.N4')\"><span>$</span><span>4</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.N5')\"><span>%</span><span>5</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.N6')\"><span>^</span><span>6</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.N7')\"><span>&</span><span>7</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.N8')\"><span>*</span><span>8</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.N9')\"><span>(</span><span>9</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.N0')\"><span>)</span><span>0</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.MINS')\"><span>_</span><span>-</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.EQL')\"><span>+</span><span>=</span></div>\n      <div class=\"key key-2u\" @click=\"emit('key', 'KC.BSPC')\">Bksp</div>\n      <div class=\"blocker-half\"></div>\n      <div class=\"key sm\" @click=\"emit('key', 'KC.INSERT')\">Insert</div>\n      <div class=\"key sm\" @click=\"emit('key', 'KC.HOME')\">Home</div>\n      <div class=\"key sm\" @click=\"emit('key', 'KC.PGUP')\">Page Up</div>\n      <div class=\"blocker-half\"></div>\n      <div class=\"key sm\" @click=\"emit('key', 'KC.NUMLOCK')\">Num Lock</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_SLASH')\">/</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_ASTERISK')\">*</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_MINUS')\">-</div>\n    </div>\n    <div class=\"row\">\n      <div class=\"key key-1-5u\" @click=\"emit('key', 'KC.TAB')\">Tab</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.Q')\">Q</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.W')\">W</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.F')\">F</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.P')\">P</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.G')\">G</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.J')\">J</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.L')\">L</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.U')\">U</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.Y')\">Y</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.SCLN')\"><span>:</span><span>;</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.LBRC')\"><span>{</span><span>[</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.RBRC')\"><span>}</span><span>]</span></div>\n      <div class=\"key key-1-5u\" @click=\"emit('key', 'KC.BSLS')\"><span>|</span><span>\\</span></div>\n      <div class=\"blocker-half\"></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.DEL')\">Del</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.END')\">End</div>\n      <div class=\"key sm\" @click=\"emit('key', 'KC.PGDOWN')\">Page Down</div>\n      <div class=\"blocker-half\"></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_7')\">7</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_8')\">8</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_9')\">9</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_PLUS')\">+</div>\n    </div>\n    <div class=\"row\">\n      <div class=\"key key-1-75u\" @click=\"emit('key', 'KC.CAPS')\">Caps Lock</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.A')\">A</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.R')\">R</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.S')\">S</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.T')\">T</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.D')\">D</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.H')\">H</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.N')\">N</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.E')\">E</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.I')\">I</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.O')\">O</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.QUOT')\"><span>\"</span><span>'</span></div>\n      <div class=\"key key-2-25u\" @click=\"emit('key', 'KC.ENT')\">Enter</div>\n\n      <div class=\"blocker-half\"></div>\n      <div class=\"blocker-full\"></div>\n      <div class=\"blocker-full\"></div>\n      <div class=\"blocker-full\"></div>\n      <div class=\"blocker-half\"></div>\n\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_4')\">4</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_5')\">5</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_6')\">6</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_PLUS')\">+</div>\n    </div>\n    <div class=\"row\">\n      <div class=\"key key-2-5u\" @click=\"emit('key', 'KC.LSFT')\">LShift</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.Z')\">Z</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.X')\">X</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.C')\">C</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.V')\">V</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.B')\">B</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.K')\">K</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.M')\">M</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.COMM')\"><span>&lt;</span><span>,</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.DOT')\"><span>&gt;</span><span>,</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.SLSH')\"><span>?</span><span>/</span></div>\n      <div class=\"key key-2-5u\" @click=\"emit('key', 'KC.RSFT')\">RShift</div>\n      <div class=\"blocker-half\"></div>\n      <div class=\"blocker-full\"></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.UP')\"><i class=\"mdi mdi-arrow-up\"></i></div>\n      <div class=\"blocker-full\"></div>\n      <div class=\"blocker-half\"></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_1')\">1</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_2')\">2</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_3')\">3</div>\n      <div class=\"key sm\" @click=\"emit('key', 'KC.KP_ENTER')\">Num Enter</div>\n    </div>\n    <div class=\"row\">\n      <div class=\"key key-1-25u\" @click=\"emit('key', 'KC.LCTL')\">LCtrl</div>\n      <div class=\"key key-1-25u\" @click=\"emit('key', 'KC.LGUI')\">LGui</div>\n      <div class=\"key key-1-25u\" @click=\"emit('key', 'KC.LALT')\">LAlt</div>\n\n      <div class=\"key key-6u\" @click=\"emit('key', 'KC.SPC')\">Space</div>\n\n      <div class=\"key key-1-25u\" @click=\"emit('key', 'KC.RALT')\">RAlt</div>\n      <div class=\"key key-1-25u\" @click=\"emit('key', 'KC.RGUI')\">RGui</div>\n      <div class=\"key key-1-25u\" @click=\"emit('key', 'KC.APP')\">Menu</div>\n      <div class=\"key key-1-25u\" @click=\"emit('key', 'KC.RCTL')\">RCTL</div>\n      <div class=\"blocker-half\"></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.LEFT')\"><i class=\"mdi mdi-arrow-left\"></i></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.DOWN')\"><i class=\"mdi mdi-arrow-down\"></i></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.RIGHT')\"><i class=\"mdi mdi-arrow-right\"></i></div>\n      <div class=\"blocker-half\"></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_0')\">0</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_0')\">0</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_DOT')\">.</div>\n      <div class=\"key sm\" @click=\"emit('key', 'KC.KP_ENTER')\">Num Enter</div>\n    </div>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nconst emit = defineEmits(['key']);\n</script>\n"
  },
  {
    "path": "src/renderer/src/components/picker-layouts/ColemakDH.vue",
    "content": "<template>\n  <div class=\"key-chooser flex\">\n    <div class=\"row\">\n      <div class=\"key\" @click=\"emit('key', 'KC.ESC')\">ESC</div>\n      <div class=\"blocker-full\"></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.F1')\">F1</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.F2')\">F2</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.F3')\">F3</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.F4')\">F4</div>\n      <div class=\"blocker-half\"></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.F5')\">F5</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.F6')\">F6</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.F7')\">F7</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.F8')\">F8</div>\n      <div class=\"blocker-half\"></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.F9')\">F9</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.F10')\">F10</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.F11')\">F11</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.F12')\">F12</div>\n      <div class=\"blocker-half\"></div>\n      <div class=\"key sm\" @click=\"emit('key', 'KC.PSCREEN')\">Print Screen</div>\n      <div class=\"key sm\" @click=\"emit('key', 'KC.SCROLLLOCK')\">Scroll Lock</div>\n      <div class=\"key sm\" @click=\"emit('key', 'KC.PAUSE')\">Pause</div>\n      <div class=\"blocker-half\"></div>\n      <div class=\"blocker-full\"></div>\n      <div class=\"blocker-full\"></div>\n      <div class=\"blocker-full\"></div>\n      <div class=\"blocker-full\"></div>\n    </div>\n    <div class=\"row\">\n      <div class=\"key\" @click=\"emit('key', 'KC.GRV')\"><span>~</span><span>`</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.N1')\"><span>!</span><span>1</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.N2')\"><span>@</span><span>2</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.N3')\"><span>#</span><span>3</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.N4')\"><span>$</span><span>4</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.N5')\"><span>%</span><span>5</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.N6')\"><span>^</span><span>6</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.N7')\"><span>&</span><span>7</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.N8')\"><span>*</span><span>8</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.N9')\"><span>(</span><span>9</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.N0')\"><span>)</span><span>0</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.MINS')\"><span>_</span><span>-</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.EQL')\"><span>+</span><span>=</span></div>\n      <div class=\"key key-2u\" @click=\"emit('key', 'KC.BSPC')\">Bksp</div>\n      <div class=\"blocker-half\"></div>\n      <div class=\"key sm\" @click=\"emit('key', 'KC.INSERT')\">Insert</div>\n      <div class=\"key sm\" @click=\"emit('key', 'KC.HOME')\">Home</div>\n      <div class=\"key sm\" @click=\"emit('key', 'KC.PGUP')\">Page Up</div>\n      <div class=\"blocker-half\"></div>\n      <div class=\"key sm\" @click=\"emit('key', 'KC.NUMLOCK')\">Num Lock</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_SLASH')\">/</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_ASTERISK')\">*</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_MINUS')\">-</div>\n    </div>\n    <div class=\"row\">\n      <div class=\"key key-1-5u\" @click=\"emit('key', 'KC.TAB')\">Tab</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.Q')\">Q</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.W')\">W</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.F')\">F</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.P')\">P</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.B')\">B</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.J')\">J</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.L')\">L</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.U')\">U</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.Y')\">Y</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.SCLN')\"><span>:</span><span>;</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.LBRC')\"><span>{</span><span>[</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.RBRC')\"><span>}</span><span>]</span></div>\n      <div class=\"key key-1-5u\" @click=\"emit('key', 'KC.BSLS')\"><span>|</span><span>\\</span></div>\n      <div class=\"blocker-half\"></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.DEL')\">Del</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.END')\">End</div>\n      <div class=\"key sm\" @click=\"emit('key', 'KC.PGDOWN')\">Page Down</div>\n      <div class=\"blocker-half\"></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_7')\">7</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_8')\">8</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_9')\">9</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_PLUS')\">+</div>\n    </div>\n    <div class=\"row\">\n      <div class=\"key key-1-75u\" @click=\"emit('key', 'KC.CAPS')\">Caps Lock</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.A')\">A</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.R')\">R</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.S')\">S</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.T')\">T</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.G')\">G</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.M')\">M</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.N')\">N</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.E')\">E</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.I')\">I</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.O')\">O</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.QUOT')\"><span>\"</span><span>'</span></div>\n      <div class=\"key key-2-25u\" @click=\"emit('key', 'KC.ENT')\">Enter</div>\n\n      <div class=\"blocker-half\"></div>\n      <div class=\"blocker-full\"></div>\n      <div class=\"blocker-full\"></div>\n      <div class=\"blocker-full\"></div>\n      <div class=\"blocker-half\"></div>\n\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_4')\">4</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_5')\">5</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_6')\">6</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_PLUS')\">+</div>\n    </div>\n    <div class=\"row\">\n      <div class=\"key key-2-5u\" @click=\"emit('key', 'KC.LSFT')\">LShift</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.Z')\">Z</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.X')\">X</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.C')\">C</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.D')\">D</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.V')\">V</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.K')\">K</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.H')\">H</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.COMM')\"><span>&lt;</span><span>,</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.DOT')\"><span>&gt;</span><span>,</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.SLSH')\"><span>?</span><span>/</span></div>\n      <div class=\"key key-2-5u\" @click=\"emit('key', 'KC.RSFT')\">RShift</div>\n      <div class=\"blocker-half\"></div>\n      <div class=\"blocker-full\"></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.UP')\"><i class=\"mdi mdi-arrow-up\"></i></div>\n      <div class=\"blocker-full\"></div>\n      <div class=\"blocker-half\"></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_1')\">1</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_2')\">2</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_3')\">3</div>\n      <div class=\"key sm\" @click=\"emit('key', 'KC.KP_ENTER')\">Num Enter</div>\n    </div>\n    <div class=\"row\">\n      <div class=\"key key-1-25u\" @click=\"emit('key', 'KC.LCTL')\">LCtrl</div>\n      <div class=\"key key-1-25u\" @click=\"emit('key', 'KC.LGUI')\">LGui</div>\n      <div class=\"key key-1-25u\" @click=\"emit('key', 'KC.LALT')\">LAlt</div>\n\n      <div class=\"key key-6u\" @click=\"emit('key', 'KC.SPC')\">Space</div>\n\n      <div class=\"key key-1-25u\" @click=\"emit('key', 'KC.RALT')\">RAlt</div>\n      <div class=\"key key-1-25u\" @click=\"emit('key', 'KC.RGUI')\">RGui</div>\n      <div class=\"key key-1-25u\" @click=\"emit('key', 'KC.APP')\">Menu</div>\n      <div class=\"key key-1-25u\" @click=\"emit('key', 'KC.RCTL')\">RCTL</div>\n      <div class=\"blocker-half\"></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.LEFT')\"><i class=\"mdi mdi-arrow-left\"></i></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.DOWN')\"><i class=\"mdi mdi-arrow-down\"></i></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.RIGHT')\"><i class=\"mdi mdi-arrow-right\"></i></div>\n      <div class=\"blocker-half\"></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_0')\">0</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_0')\">0</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_DOT')\">.</div>\n      <div class=\"key sm\" @click=\"emit('key', 'KC.KP_ENTER')\">Num Enter</div>\n    </div>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nconst emit = defineEmits(['key']);\n</script>\n"
  },
  {
    "path": "src/renderer/src/components/picker-layouts/Dvorak.vue",
    "content": "<template>\n  <div class=\"key-chooser flex\">\n    <div class=\"row\">\n      <div class=\"key\" @click=\"emit('key', 'KC.ESC')\">ESC</div>\n      <div class=\"blocker-full\"></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.F1')\">F1</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.F2')\">F2</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.F3')\">F3</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.F4')\">F4</div>\n      <div class=\"blocker-half\"></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.F5')\">F5</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.F6')\">F6</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.F7')\">F7</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.F8')\">F8</div>\n      <div class=\"blocker-half\"></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.F9')\">F9</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.F10')\">F10</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.F11')\">F11</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.F12')\">F12</div>\n      <div class=\"blocker-half\"></div>\n      <div class=\"key sm\" @click=\"emit('key', 'KC.PSCREEN')\">Print Screen</div>\n      <div class=\"key sm\" @click=\"emit('key', 'KC.SCROLLLOCK')\">Scroll Lock</div>\n      <div class=\"key sm\" @click=\"emit('key', 'KC.PAUSE')\">Pause</div>\n      <div class=\"blocker-half\"></div>\n      <div class=\"blocker-full\"></div>\n      <div class=\"blocker-full\"></div>\n      <div class=\"blocker-full\"></div>\n      <div class=\"blocker-full\"></div>\n    </div>\n    <div class=\"row\">\n      <div class=\"key\" @click=\"emit('key', 'KC.GRV')\"><span>~</span><span>`</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.N1')\"><span>!</span><span>1</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.N2')\"><span>@</span><span>2</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.N3')\"><span>#</span><span>3</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.N4')\"><span>$</span><span>4</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.N5')\"><span>%</span><span>5</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.N6')\"><span>^</span><span>6</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.N7')\"><span>&</span><span>7</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.N8')\"><span>*</span><span>8</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.N9')\"><span>(</span><span>9</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.N0')\"><span>)</span><span>0</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.LBRC')\"><span>{</span><span>[</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.RBRC')\"><span>}</span><span>]</span></div>\n      <div class=\"key key-2u\" @click=\"emit('key', 'KC.BSPC')\">Bksp</div>\n      <div class=\"blocker-half\"></div>\n      <div class=\"key sm\" @click=\"emit('key', 'KC.INSERT')\">Insert</div>\n      <div class=\"key sm\" @click=\"emit('key', 'KC.HOME')\">Home</div>\n      <div class=\"key sm\" @click=\"emit('key', 'KC.PGUP')\">Page Up</div>\n      <div class=\"blocker-half\"></div>\n      <div class=\"key sm\" @click=\"emit('key', 'KC.NUMLOCK')\">Num Lock</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_SLASH')\">/</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_ASTERISK')\">*</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_MINUS')\">-</div>\n    </div>\n    <div class=\"row\">\n      <div class=\"key key-1-5u\" @click=\"emit('key', 'KC.TAB')\">Tab</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.QUOT')\"><span>\"</span><span>'</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.COMM')\"><span>&lt;</span><span>,</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.DOT')\"><span>&gt;</span><span>.</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.P')\">P</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.Y')\">Y</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.F')\">F</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.G')\">G</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.C')\">C</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.R')\">R</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.L')\">L</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.SLSH')\"><span>?</span><span>/</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.EQL')\"><span>+</span><span>=</span></div>\n      <div class=\"key key-1-5u\" @click=\"emit('key', 'KC.BSLS')\"><span>|</span><span>\\</span></div>\n      <div class=\"blocker-half\"></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.DEL')\">Del</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.END')\">End</div>\n      <div class=\"key sm\" @click=\"emit('key', 'KC.PGDOWN')\">Page Down</div>\n      <div class=\"blocker-half\"></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_7')\">7</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_8')\">8</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_9')\">9</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_PLUS')\">+</div>\n    </div>\n    <div class=\"row\">\n      <div class=\"key key-1-75u\" @click=\"emit('key', 'KC.CAPS')\">Caps Lock</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.A')\">A</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.O')\">O</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.E')\">E</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.U')\">U</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.I')\">I</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.D')\">D</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.H')\">H</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.T')\">T</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.N')\">N</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.S')\">S</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.MINS')\"><span>_</span><span>-</span></div>\n      <div class=\"key key-2-25u\" @click=\"emit('key', 'KC.ENT')\">Enter</div>\n\n      <div class=\"blocker-half\"></div>\n      <div class=\"blocker-full\"></div>\n      <div class=\"blocker-full\"></div>\n      <div class=\"blocker-full\"></div>\n      <div class=\"blocker-half\"></div>\n\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_4')\">4</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_5')\">5</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_6')\">6</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_PLUS')\">+</div>\n    </div>\n    <div class=\"row\">\n      <div class=\"key key-2-5u\" @click=\"emit('key', 'KC.LSFT')\">LShift</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.SCLN')\"><span>:</span><span>;</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.Q')\">Q</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.J')\">J</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.K')\">K</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.X')\">X</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.B')\">B</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.M')\">M</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.W')\">W</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.V')\">V</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.Z')\">Z</div>\n      <div class=\"key key-2-5u\" @click=\"emit('key', 'KC.RSFT')\">RShift</div>\n      <div class=\"blocker-half\"></div>\n      <div class=\"blocker-full\"></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.UP')\"><i class=\"mdi mdi-arrow-up\"></i></div>\n      <div class=\"blocker-full\"></div>\n      <div class=\"blocker-half\"></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_1')\">1</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_2')\">2</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_3')\">3</div>\n      <div class=\"key sm\" @click=\"emit('key', 'KC.KP_ENTER')\">Num Enter</div>\n    </div>\n    <div class=\"row\">\n      <div class=\"key key-1-25u\" @click=\"emit('key', 'KC.LCTL')\">LCtrl</div>\n      <div class=\"key key-1-25u\" @click=\"emit('key', 'KC.LGUI')\">LGui</div>\n      <div class=\"key key-1-25u\" @click=\"emit('key', 'KC.LALT')\">LAlt</div>\n\n      <div class=\"key key-6u\" @click=\"emit('key', 'KC.SPC')\">Space</div>\n\n      <div class=\"key key-1-25u\" @click=\"emit('key', 'KC.RALT')\">RAlt</div>\n      <div class=\"key key-1-25u\" @click=\"emit('key', 'KC.RGUI')\">RGui</div>\n      <div class=\"key key-1-25u\" @click=\"emit('key', 'KC.APP')\">Menu</div>\n      <div class=\"key key-1-25u\" @click=\"emit('key', 'KC.RCTL')\">RCTL</div>\n      <div class=\"blocker-half\"></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.LEFT')\"><i class=\"mdi mdi-arrow-left\"></i></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.DOWN')\"><i class=\"mdi mdi-arrow-down\"></i></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.RIGHT')\"><i class=\"mdi mdi-arrow-right\"></i></div>\n      <div class=\"blocker-half\"></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_0')\">0</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_0')\">0</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_DOT')\">.</div>\n      <div class=\"key sm\" @click=\"emit('key', 'KC.KP_ENTER')\">Num Enter</div>\n    </div>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nconst emit = defineEmits(['key']);\n</script>\n"
  },
  {
    "path": "src/renderer/src/components/picker-layouts/Qwerty.vue",
    "content": "<template>\n  <div class=\"key-chooser flex\">\n    <div class=\"row\">\n      <div class=\"key\" @click=\"emit('key', 'KC.ESC')\">ESC</div>\n      <div class=\"blocker-full\"></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.F1')\">F1</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.F2')\">F2</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.F3')\">F3</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.F4')\">F4</div>\n      <div class=\"blocker-half\"></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.F5')\">F5</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.F6')\">F6</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.F7')\">F7</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.F8')\">F8</div>\n      <div class=\"blocker-half\"></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.F9')\">F9</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.F10')\">F10</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.F11')\">F11</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.F12')\">F12</div>\n      <div class=\"blocker-half\"></div>\n      <div class=\"key sm\" @click=\"emit('key', 'KC.PSCREEN')\">Print Screen</div>\n      <div class=\"key sm\" @click=\"emit('key', 'KC.SCROLLLOCK')\">Scroll Lock</div>\n      <div class=\"key sm\" @click=\"emit('key', 'KC.PAUSE')\">Pause</div>\n      <div class=\"blocker-half\"></div>\n      <div class=\"blocker-full\"></div>\n      <div class=\"blocker-full\"></div>\n      <div class=\"blocker-full\"></div>\n      <div class=\"blocker-full\"></div>\n    </div>\n    <div class=\"row\">\n      <div class=\"key\" @click=\"emit('key', 'KC.GRV')\"><span>~</span><span>`</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.N1')\"><span>!</span><span>1</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.N2')\"><span>@</span><span>2</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.N3')\"><span>#</span><span>3</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.N4')\"><span>$</span><span>4</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.N5')\"><span>%</span><span>5</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.N6')\"><span>^</span><span>6</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.N7')\"><span>&</span><span>7</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.N8')\"><span>*</span><span>8</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.N9')\"><span>(</span><span>9</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.N0')\"><span>)</span><span>0</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.MINS')\"><span>_</span><span>-</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.EQL')\"><span>+</span><span>=</span></div>\n      <div class=\"key key-2u\" @click=\"emit('key', 'KC.BSPC')\">Bksp</div>\n      <div class=\"blocker-half\"></div>\n      <div class=\"key sm\" @click=\"emit('key', 'KC.INSERT')\">Insert</div>\n      <div class=\"key sm\" @click=\"emit('key', 'KC.HOME')\">Home</div>\n      <div class=\"key sm\" @click=\"emit('key', 'KC.PGUP')\">Page Up</div>\n      <div class=\"blocker-half\"></div>\n      <div class=\"key sm\" @click=\"emit('key', 'KC.NUMLOCK')\">Num Lock</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_SLASH')\">/</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_ASTERISK')\">*</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_MINUS')\">-</div>\n    </div>\n    <div class=\"row\">\n      <div class=\"key key-1-5u\" @click=\"emit('key', 'KC.TAB')\">Tab</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.Q')\">Q</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.W')\">W</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.E')\">E</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.R')\">R</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.T')\">T</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.Y')\">Y</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.U')\">U</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.I')\">I</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.O')\">O</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.P')\">P</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.LBRC')\"><span>{</span><span>[</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.RBRC')\"><span>}</span><span>]</span></div>\n      <div class=\"key key-1-5u\" @click=\"emit('key', 'KC.BSLS')\"><span>|</span><span>\\</span></div>\n      <div class=\"blocker-half\"></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.DEL')\">Del</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.END')\">End</div>\n      <div class=\"key sm\" @click=\"emit('key', 'KC.PGDOWN')\">Page Down</div>\n      <div class=\"blocker-half\"></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_7')\">7</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_8')\">8</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_9')\">9</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_PLUS')\">+</div>\n    </div>\n    <div class=\"row\">\n      <div class=\"key key-1-75u\" @click=\"emit('key', 'KC.CAPS')\">Caps Lock</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.A')\">A</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.S')\">S</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.D')\">D</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.F')\">F</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.G')\">G</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.H')\">H</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.J')\">J</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.K')\">K</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.L')\">L</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.SCLN')\"><span>:</span><span>;</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.QUOT')\"><span>\"</span><span>'</span></div>\n      <div class=\"key key-2-25u\" @click=\"emit('key', 'KC.ENT')\">Enter</div>\n\n      <div class=\"blocker-half\"></div>\n      <div class=\"blocker-full\"></div>\n      <div class=\"blocker-full\"></div>\n      <div class=\"blocker-full\"></div>\n      <div class=\"blocker-half\"></div>\n\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_4')\">4</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_5')\">5</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_6')\">6</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_PLUS')\">+</div>\n    </div>\n    <div class=\"row\">\n      <div class=\"key key-2-5u\" @click=\"emit('key', 'KC.LSFT')\">LShift</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.Z')\">Z</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.X')\">X</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.C')\">C</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.V')\">V</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.B')\">B</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.N')\">N</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.M')\">M</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.COMM')\"><span>&lt;</span><span>,</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.DOT')\"><span>&gt;</span><span>,</span></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.SLSH')\"><span>?</span><span>/</span></div>\n      <div class=\"key key-2-5u\" @click=\"emit('key', 'KC.RSFT')\">RShift</div>\n      <div class=\"blocker-half\"></div>\n      <div class=\"blocker-full\"></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.UP')\"><i class=\"mdi mdi-arrow-up\"></i></div>\n      <div class=\"blocker-full\"></div>\n      <div class=\"blocker-half\"></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_1')\">1</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_2')\">2</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_3')\">3</div>\n      <div class=\"key sm\" @click=\"emit('key', 'KC.KP_ENTER')\">Num Enter</div>\n    </div>\n    <div class=\"row\">\n      <div class=\"key key-1-25u\" @click=\"emit('key', 'KC.LCTL')\">LCtrl</div>\n      <div class=\"key key-1-25u\" @click=\"emit('key', 'KC.LGUI')\">LGui</div>\n      <div class=\"key key-1-25u\" @click=\"emit('key', 'KC.LALT')\">LAlt</div>\n\n      <div class=\"key key-6u\" @click=\"emit('key', 'KC.SPC')\">Space</div>\n\n      <div class=\"key key-1-25u\" @click=\"emit('key', 'KC.RALT')\">RAlt</div>\n      <div class=\"key key-1-25u\" @click=\"emit('key', 'KC.RGUI')\">RGui</div>\n      <div class=\"key key-1-25u\" @click=\"emit('key', 'KC.APP')\">Menu</div>\n      <div class=\"key key-1-25u\" @click=\"emit('key', 'KC.RCTL')\">RCTL</div>\n      <div class=\"blocker-half\"></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.LEFT')\"><i class=\"mdi mdi-arrow-left\"></i></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.DOWN')\"><i class=\"mdi mdi-arrow-down\"></i></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.RIGHT')\"><i class=\"mdi mdi-arrow-right\"></i></div>\n      <div class=\"blocker-half\"></div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_0')\">0</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_0')\">0</div>\n      <div class=\"key\" @click=\"emit('key', 'KC.KP_DOT')\">.</div>\n      <div class=\"key sm\" @click=\"emit('key', 'KC.KP_ENTER')\">Num Enter</div>\n    </div>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nconst emit = defineEmits(['key']);\n</script>\n"
  },
  {
    "path": "src/renderer/src/components/setup/Wizard.vue",
    "content": "<template>\n  <div>\n    <p>\n      Before you start please check that your controller is using circuit python, if not please\n      download the matching firmware and drag it on the usb drive that appears when you enter its\n      bootloader mode\n    </p>\n    <p>\n      when you see the circuit python usb drive it is a good time to rename the drive to reflect\n      your keyboard name\n    </p>\n    <p>\n      With this done please select the drive so POG can start to flash the firmware, you will be\n      able to configure everything after the flashing is done\n    </p>\n    <button class=\"btn btn-primary\" @click=\"flashFirmwareToDrive\">Select Drive</button>\n  </div>\n  <div>\n    <p>now unplug the keyboard and plug it back in to boot up the pog code</p>\n    loading\n  </div>\n  <div>\n    <input type=\"text\" placeholder=\"name\" />\n    <MatrixSetup :initial-setup=\"true\" />\n    define here how the pins are used\n  </div>\n  <div v-if=\"false\">\n    <p>info prepare controller with circuit python</p>\n    <p>plugin controller and select the usb drive</p>\n    <p>flash kmk on drive + add pog files and an id in the pog.json</p>\n    <p>unplug and replug keyboard to boot pog</p>\n    <p>\n      listen for serial ports with pog manufacturer then grab its info and compare with created id\n    </p>\n    <p>open both connections to repl and data for debugging</p>\n\n    <p>name + matrix size + pins[rows,cols,directpins,rgb,encoders] (loadable from qr or link)</p>\n    <p>coordmap press each key</p>\n    <p>use generated layout or modify (or was loaded from link/qr)</p>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport MatrixSetup from '../MatrixSetup.vue'\n\nconst flashFirmwareToDrive = () => {\n  console.log('select drive and flash kmk firmware + pog files + id ')\n}\n</script>\n\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "src/renderer/src/components/ui/InputLabel.vue",
    "content": "<template>\n  <div class=\"form-control\">\n    <label class=\"label\">\n      <span class=\"label-text\">{{ label }}</span>\n    </label>\n    <input\n      v-model=\"localValue\"\n      :type=\"inputType || 'text'\"\n      :placeholder=\"placeholder\"\n      class=\"input input-bordered\"\n      @input=\"changed\"\n    />\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { onMounted, ref, watch } from 'vue'\nimport { isNumber } from '@vueuse/core'\n\nconst props = defineProps(['label', 'placeholder', 'modelValue', 'inputType', 'min'])\nconst emits = defineEmits(['update:modelValue', 'input'])\nconst localValue = ref('')\nonMounted(() => {\n  localValue.value = props.modelValue\n})\nwatch(() => props.modelValue, () => {\n  localValue.value = props.modelValue\n})\nconst changed = () => {\n  if (isNumber(props.min)) {\n    console.log('min of', props.min)\n    if (Number(localValue.value) < props.min) localValue.value = String(props.min)\n  }\n  emits('update:modelValue', localValue.value)\n  emits('input')\n}\n</script>\n"
  },
  {
    "path": "src/renderer/src/composables/useLoadingOverlay.ts",
    "content": "import { ref, onMounted, onUnmounted } from 'vue'\nimport { onLoadingChange, hideLoading } from '../helpers/saveConfigurationWrapper'\n\nexport const useLoadingOverlay = () => {\n  const isLoading = ref(false)\n  let fallbackTimeout: number | null = null\n\n  const hideLoadingOverlay = () => {\n    if (fallbackTimeout) {\n      clearTimeout(fallbackTimeout)\n      fallbackTimeout = null\n    }\n    hideLoading()\n  }\n\n  const setupFallbackTimeout = (timeoutMs = 15000) => {\n    if (fallbackTimeout) {\n      clearTimeout(fallbackTimeout)\n    }\n    fallbackTimeout = setTimeout(() => {\n      if (isLoading.value) {\n        hideLoadingOverlay()\n      }\n    }, timeoutMs) as unknown as number\n  }\n\n  // Watch for loading state changes\n  const unwatchLoading = onLoadingChange((loading) => {\n    isLoading.value = loading\n  })\n\n  onMounted(() => {\n    // Set up fallback timeout when component mounts and loading is active\n    if (isLoading.value) {\n      setupFallbackTimeout()\n    }\n  })\n\n  onUnmounted(() => {\n    unwatchLoading()\n    if (fallbackTimeout) {\n      clearTimeout(fallbackTimeout)\n      fallbackTimeout = null\n    }\n  })\n\n  return {\n    isLoading,\n    hideLoadingOverlay,\n    setupFallbackTimeout\n  }\n}\n"
  },
  {
    "path": "src/renderer/src/env.d.ts",
    "content": "/// <reference types=\"vite/client\" />\n\ndeclare module '*.vue' {\n  import type { DefineComponent } from 'vue'\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types\n  const component: DefineComponent<{}, {}, any>\n  export default component\n}\n"
  },
  {
    "path": "src/renderer/src/helpers/colors.ts",
    "content": "export const hexToHSL = (hex): {hue: number, sat: number, val: number} => {\n  const result: RegExpExecArray | null = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex)\n  if (!result) return { hue: 0, sat: 0, val: 0 }\n  let r = parseInt(result[1], 16)\n  let g = parseInt(result[2], 16)\n  let b = parseInt(result[3], 16)\n\n  r /= 255\n  g /= 255\n  b /= 255\n\n  const max = Math.max(r, g, b)\n  const min = Math.min(r, g, b)\n  let h = 0\n  let s = 0\n  const l = (max + min) / 2\n\n  if (max == min) {\n    h = s = 0 // achromatic\n  } else {\n    const d = max - min\n    s = l > 0.5 ? d / (2 - max - min) : d / (max + min)\n    switch (max) {\n      case r:\n        h = (g - b) / d + (g < b ? 6 : 0)\n        break\n      case g:\n        h = (b - r) / d + 2\n        break\n      case b:\n        h = (r - g) / d + 4\n        break\n    }\n    h /= 6\n  }\n\n  return {\n    hue: Math.round(h * 255),\n    sat: Math.round(s * 255),\n    val: Math.round(l * 255)\n  }\n}\n\nexport function hslToHex(h, s, l): string {\n  s /= 255\n  l /= 255\n\n  const c = (1 - Math.abs(2 * l - 1)) * s\n  const x = c * (1 - Math.abs(((h / 60) % 2) - 1))\n  const m = l - c / 2\n  let r = 0\n  let g = 0\n  let b = 0\n\n  if (0 <= h && h < 60) {\n    r = c\n    g = x\n    b = 0\n  } else if (60 <= h && h < 120) {\n    r = x\n    g = c\n    b = 0\n  } else if (120 <= h && h < 180) {\n    r = 0\n    g = c\n    b = x\n  } else if (180 <= h && h < 240) {\n    r = 0\n    g = x\n    b = c\n  } else if (240 <= h && h < 300) {\n    r = x\n    g = 0\n    b = c\n  } else if (300 <= h && h < 360) {\n    r = c\n    g = 0\n    b = x\n  }\n  // Having obtained RGB, convert channels to hex\n  let r_str = Math.round((r + m) * 255).toString(16)\n  let g_str = Math.round((g + m) * 255).toString(16)\n  let b_str = Math.round((b + m) * 255).toString(16)\n\n  // Prepend 0s, if necessary\n  if (r_str.length == 1) r_str = '0' + r\n  if (g_str.length == 1) g_str = '0' + g\n  if (b_str.length == 1) b_str = '0' + b\n\n  return '#' + r_str + g_str + b_str\n}\n"
  },
  {
    "path": "src/renderer/src/helpers/index.ts",
    "content": "import JSON5 from 'json5'\nimport { keyboardStore, KeyInfo, selectedKeys } from '../store'\nexport const matrixPositionToIndex = ({\n  pos,\n  matrixWidth\n}: {\n  pos: [number, number]\n  matrixWidth: number\n}) => {\n  if (!pos) return 0\n  return Number(pos[0]) * matrixWidth + Number(pos[1])\n}\n\nconst formatMatrixFromLabel = (label: string): [number, number] | false => {\n  const matrix = label.split(',').map((a) => Number(a))\n  if (matrix.length !== 2) return false\n  return [matrix[0], matrix[1]]\n}\n\nexport const cleanupKeymap = () => {\n  const filledKeymap = keyboardStore.keymap.map((layer) => {\n    // replace empty keys with KC.TRNS\n    const tmpLayer = layer.map((key: string | undefined) => {\n      if (!key) return 'KC.TRNS'\n      return key\n    })\n\n    const matrixKeyCount = keyboardStore.physicalKeyCount()\n    if (matrixKeyCount > tmpLayer.length) {\n      while (matrixKeyCount > tmpLayer.length) {\n        tmpLayer.push('KC.TRNS')\n      }\n    } else if (matrixKeyCount < tmpLayer.length) {\n      while (matrixKeyCount < tmpLayer.length) {\n        tmpLayer.pop()\n      }\n    }\n    if (tmpLayer) return tmpLayer\n    return []\n  })\n  if (filledKeymap) keyboardStore.keymap = filledKeymap\n  console.log('fixed & set new keymap to ', filledKeymap)\n}\n\nconst pickKeyAttributes = ({\n  w,\n  w2,\n  h,\n  h2,\n  x,\n  x2,\n  y2\n}: {\n  w: number\n  w2: number\n  h: number\n  h2: number\n  x: number\n  x2: number\n  y?: number\n  y2: number\n}) => ({\n  w,\n  w2,\n  h,\n  h2,\n  x,\n  x2,\n  // y,\n  y2\n})\n\n// convert a kle keymap to pog\nexport const KleToPog = (kleString: string) => {\n  let keymap = []\n  try {\n    keymap = JSON5.parse(kleString)\n  } catch (e) {\n    console.log(e)\n    try {\n      keymap = JSON5.parse('[' + kleString + ']')\n    } catch (e) {\n      console.log(e)\n    }\n  }\n\n  // parse Layout file\n  const configContents: {\n    layouts: { keymap: string[][]; labels: string[] | string[][] }\n  } = {\n    layouts: { keymap, labels: [] }\n  }\n  // const keyboardInfo = ref<{\n  //   keys: KeyData[]\n  // }>({ keys: [] })\n\n  // in place update kle to pog layout => iterate over rows\n  let currentX = 0\n  let currentY = 0\n  let keydata: any = undefined // data to carry over to the next key until it is overwritten\n  let firstKeyInRow = true\n  const keys: KeyInfo[] = []\n  configContents.layouts.keymap.forEach((row) => {\n    if (Array.isArray(row)) {\n      // normal row\n      row.forEach((keyOrData) => {\n        // tmp key info\n        let key: KeyInfo = { x: NaN, y: NaN }\n        if (typeof keyOrData === 'string') {\n          // this is a key\n          const labels = keyOrData.split('\\n')\n          if (labels.length === 1) {\n            // just the main label\n            // labels = [\"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", keyOrData];\n            // key.matrixPos = keyOrData;\n            const matrix = formatMatrixFromLabel(keyOrData)\n            if (matrix !== false) key.matrix = matrix\n          } else if (labels.length === 4) {\n            // shortened labels top left and bottom right\n            // labels = [keyOrData];\n            // key.matrixPos = labels[0];\n            const matrix = formatMatrixFromLabel(labels[0])\n            if (matrix !== false) key.matrix = matrix\n            key.variant = labels[3].split(',').map((a) => Number(a)) as [number, number]\n          } else {\n            // all labels just keep split\n            // key.matrixPos = keyOrData[0];\n            const matrix = formatMatrixFromLabel(keyOrData[0])\n            if (matrix !== false) key.matrix = matrix\n            // key.variant = keyOrData[3]\n          }\n          // key.labels = labels;\n          // Position data\n          if (keydata) {\n            key = { ...key, ...pickKeyAttributes(keydata) }\n            if (keydata.y) currentY += keydata.y\n            if (keydata.x) currentX = keydata.x + currentX\n            if (firstKeyInRow) {\n              key.x = currentX\n              firstKeyInRow = false\n            } else {\n              key.x = currentX\n            }\n          }\n          if (!key.y) key.y = currentY\n          if (!key.x) key.x = currentX\n          keydata = undefined\n          if (!key.w || key.w === 1) {\n            currentX++\n          } else {\n            currentX = currentX + key.w\n          }\n          keys.push(key)\n        } else {\n          // this is just data for the next key\n          keydata = keyOrData\n        }\n        // add 1 to left distance // next key\n      })\n      // add 1 to top distance // next row\n      currentX = 0\n      firstKeyInRow = true\n      currentY++\n    }\n  })\n  console.log('created layout', keys)\n  return keys\n}\n\nexport const selectNextKey = () => {\n  selectedKeys.value = new Set([\n    Math.min([...selectedKeys.value][0] + 1, keyboardStore.keyCount() - 1)\n  ])\n}\nexport const selectPrevKey = () => {\n  selectedKeys.value = new Set([Math.max([...selectedKeys.value][0] - 1, 0)])\n}\n\nexport const renderLabel = (keycode: string) => {\n  const keyLabels: {\n    [key: string]: { label?: string; alt?: string; icon?: string; iconWindows?: string }\n  } = {\n    // Define labels for your keycodes here\n    F1: { label: 'F1' },\n    F2: { label: 'F2' },\n    F3: { label: 'F3' },\n    F4: { label: 'F4' },\n    F5: { label: 'F5' },\n    F6: { label: 'F6' },\n    F7: { label: 'F7' },\n    F8: { label: 'F8' },\n    F9: { label: 'F9' },\n    F10: { label: 'F10' },\n    F11: { label: 'F11' },\n    F12: { label: 'F12' },\n    A: { label: 'A' },\n    B: { label: 'B' },\n    C: { label: 'C' },\n    D: { label: 'D' },\n    E: { label: 'E' },\n    F: { label: 'F' },\n    G: { label: 'G' },\n    H: { label: 'H' },\n    I: { label: 'I' },\n    J: { label: 'J' },\n    K: { label: 'K' },\n    L: { label: 'L' },\n    M: { label: 'M' },\n    N: { label: 'N' },\n    O: { label: 'O' },\n    P: { label: 'P' },\n    Q: { label: 'Q' },\n    R: { label: 'R' },\n    S: { label: 'S' },\n    T: { label: 'T' },\n    U: { label: 'U' },\n    V: { label: 'V' },\n    W: { label: 'W' },\n    X: { label: 'X' },\n    Y: { label: 'Y' },\n    Z: { label: 'Z' },\n    N1: { label: '1' },\n    N2: { label: '2' },\n    N3: { label: '3' },\n    N4: { label: '4' },\n    N5: { label: '5' },\n    N6: { label: '6' },\n    N7: { label: '7' },\n    N8: { label: '8' },\n    N9: { label: '9' },\n    N0: { label: '0' },\n    ESC: { icon: 'mdi-keyboard-esc' },\n    ENT: { icon: 'mdi-keyboard-return' },\n    SPC: { icon: 'mdi-keyboard-space' },\n    DOT: { label: '.' },\n    COMM: { label: ',' },\n    SLSH: { label: '/' },\n    KP_SLASH: { label: '/' },\n    SCLN: { label: ';' },\n    QUOT: { label: \"'\" },\n    LSFT: { icon: 'mdi-apple-keyboard-shift' },\n    RSFT: { icon: 'mdi-apple-keyboard-shift' },\n    LBRC: { label: '[' },\n    RBRC: { label: ']' },\n    LABK: { label: '<' },\n    RABK: { label: '>' },\n    LCBR: { label: '{' },\n    RCBR: { label: '}' },\n    LEFT_PAREN: { label: '(' },\n    RIGHT_PAREN: { label: ')' },\n    DQT: { label: '\"' },\n    COLN: { label: ':' },\n    EXLM: { label: '!' },\n    PERCENT: { label: '%' },\n    AMPERSAND: { label: '&' },\n    TILDE: { label: '~' },\n    PIPE: { label: '|' },\n    DOLLAR: { label: '$' },\n    HASH: { label: '#' },\n    QUES: { label: '?' },\n    BSLS: { label: '\\\\' },\n    MINS: { label: '-' },\n    EQL: { label: '=' },\n    CAPS: { icon: 'mdi-apple-keyboard-caps' },\n    TAB: { icon: 'mdi-keyboard-tab' },\n    BSPC: { icon: 'mdi-backspace' },\n    DEL: { icon: 'mdi-backspace-reverse' },\n    LCTL: { icon: 'mdi-apple-keyboard-control' },\n    RCTL: { icon: 'mdi-apple-keyboard-control' },\n    LALT: { icon: 'mdi-apple-keyboard-option' },\n    RALT: { icon: 'mdi-apple-keyboard-option' },\n    LGUI: { icon: 'mdi-apple-keyboard-command' },\n    RGUI: { icon: 'mdi-apple-keyboard-command' },\n    HOME: { icon: 'mdi-arrow-top-left' },\n    END: { icon: 'mdi-arrow-bottom-right' },\n    PGDOWN: { icon: 'mdi-arrow-down' },\n    PGUP: { icon: 'mdi-arrow-up' },\n    UP: { icon: 'mdi-arrow-up-thin' },\n    LEFT: { icon: 'mdi-arrow-left-thin' },\n    DOWN: { icon: 'mdi-arrow-down-thin' },\n    RIGHT: { icon: 'mdi-arrow-right-thin' },\n    GRV: { label: '`' },\n    PLUS: { label: '+' },\n    AT: { label: '@' },\n    UNDERSCORE: { label: '_' },\n    CIRCUMFLEX: { label: '^' },\n    ASTERISK: { label: '*' },\n\n    // Layer\n    MO: { label: 'MO' },\n    MT: { label: 'MT' },\n    LT: { label: 'LT' },\n    TT: { label: 'TT' },\n    TG: { label: 'TG' },\n    TO: { label: 'TO' },\n    TD: { label: 'TD' },\n\n    HT: { label: 'HT' },\n    OS: { label: 'OS' },\n\n    // Media\n    MPLY: { label: 'Play/Pause', icon: 'mdi-play-pause' },\n    VOLU: { label: 'Vol up', icon: 'mdi-volume-plus' },\n    VOLD: { label: 'Vol down', icon: 'mdi-volume-minus' },\n    MEDIA_PLAY_PAUSE: { label: 'Play/Pause', icon: 'mdi-play-pause' },\n    MRWD: { label: 'Prev Track', icon: 'mdi-skip-previous' },\n    MFFD: { label: 'Next Track', icon: 'mdi-skip-next' },\n    send_string: { label: 'String' },\n    RESET: { label: 'Reset' },\n    RELOAD: { label: 'Reload' },\n    DEBUG: { label: 'Debug' },\n    RGB_TOG: { label: 'Toggle<br/>RGB' },\n    RGB_HUI: { label: 'RGB<br/>Hue +' },\n    RGB_HUD: { label: 'RGB<br/>Hue -' },\n    RGB_SAI: { label: 'RGB<br/>Sat +' },\n    RGB_SAD: { label: 'RGB<br/>Sat -' },\n    RGB_ANI: { label: 'RGB<br/>Animation +' },\n    RGB_AND: { label: 'RGB<br/>Animation -' },\n    RGB_MODE_SWIRL: { label: 'RGB<br/>Swirl' },\n    RGB_MODE_PLAIN: { label: 'RGB<br/>Plain' },\n    RGB_MODE_KNIGHT: { label: 'RGB<br/>Knight' },\n    RGB_MODE_RAINBOW: { label: 'RGB<br/>Rainbow' }\n  }\n\n  const keylabel: {\n    simple: boolean\n    action: string\n    main: string\n    lower: string\n    params: any[]\n    layer: number | null\n    layerNamePosition: string\n  } = {\n    simple: true,\n    action: '',\n    params: [],\n    layer: null,\n    main: '',\n    lower: '',\n    layerNamePosition: ''\n  }\n\n  // Check if the keycode is a sequence\n  if (keycode.startsWith('KC.MACRO(') && keycode.endsWith(')')) {\n    const reg = /^\\s*KC\\.MACRO\\(\\s*\"([^\"]*)\"\\s*\\)\\s*$/\n    if (reg.test(keycode)) {\n      keylabel.action = '<p class=\"keylabel-small\">String</p>'\n    } else {\n      keylabel.action = '<p class=\"keylabel-small\">Macro</p>'\n    }\n  } else if (keycode.startsWith('customkeys.')) {\n    keylabel.action = 'custom'\n    keylabel.simple = false\n    const customcode = keycode.substring(11)\n    keylabel.main = `${customcode}`\n  } else {\n    // Check for modifier keys\n    // if (keycode.includes('KC.LSHIFT') || keycode.includes('KC.RSHIFT') ||\n    //   keycode.includes('KC.LCTL') || keycode.includes('KC.RCTL') ||\n    //   keycode.includes('KC.LALT') || keycode.includes('KC.RALT')) {\n    //   label += '^ ';\n    // }\n\n    // Check for key presses\n    const keyMatch = keycode.match(/KC\\.(\\w+)/)\n    if (keyMatch) {\n      const key = keyMatch[1]\n      const foundKey = keyLabels[key]\n      if (!foundKey) {\n        keylabel.action = keycode\n      } else if (foundKey.icon) {\n        keylabel.action = `<i class=\"mdi ${foundKey.icon}\"></i>`\n      } else if (foundKey.label) {\n        keylabel.action += foundKey.label\n      }\n      // if it has arguments render them as keycode as well\n      if (keycode.includes('(')) {\n        const match = keycode.match(/^[^(]+\\((.*)\\)$/)\n        // dont render options for some keys eg. MT\n        if (match && match[1]) {\n          const params = match[1].split(',').map((a) => renderLabel(a))\n          let maxParams = 10\n          if (['LT', 'MT'].includes(key)) {\n            // keycodes that have a label at the top\n            maxParams = 2\n          }\n          switch (key) {\n            case 'LT':\n              keylabel.main = String(params[1].action)\n              keylabel.lower = params[0].action\n              keylabel.layer = Number(params[0].action)\n              keylabel.simple = false\n              keylabel.layerNamePosition = 'lower'\n              break\n            case 'OS':\n              keylabel.main = String(params[0].action)\n              keylabel.simple = false\n              break\n            case 'TD':\n              keylabel.main = String(params.map((a) => a.action).join(' '))\n              keylabel.simple = false\n              break\n            case 'MT':\n              keylabel.main = String(params[0].action)\n              keylabel.lower = String(params[1].action)\n              keylabel.simple = false\n              break\n            case 'HT':\n              keylabel.main = String(params[0].action)\n              keylabel.lower = String(params[1].action)\n              keylabel.simple = false\n              break\n            case 'TT':\n              keylabel.main = String(params[0].action)\n              keylabel.layer = Number(params[0].action)\n              keylabel.simple = false\n              break\n            case 'TO':\n              keylabel.main = String(params[0].action)\n              keylabel.layer = Number(params[0].action)\n              keylabel.simple = false\n              break\n            case 'TG':\n              keylabel.main = String(params[0].action)\n              keylabel.layer = Number(params[0].action)\n              keylabel.simple = false\n              break\n            case 'MO':\n              keylabel.main = String(params[0].action)\n              keylabel.layer = Number(params[0].action)\n              keylabel.simple = false\n              keylabel.layerNamePosition = 'main'\n              break\n          }\n          keylabel.params = params.slice(0, maxParams)\n        }\n      }\n    } else {\n      // custom keycodes\n      keylabel.action = keycode\n    }\n  }\n\n  return keylabel\n}\nconst controllers = {\n  '0xcb_helios': ['GP29']\n}\n\nexport const microcontrollerPinValid = ({\n  controller,\n  pin\n}: {\n  controller: string\n  pin: string\n}) => {\n  // check if the controller has this pin\n  return controllers[controller].includes(pin)\n}\n"
  },
  {
    "path": "src/renderer/src/helpers/saveConfigurationWrapper.ts",
    "content": "import { ref } from 'vue'\n\n// Global loading state\nconst isLoading = ref(false)\nconst loadingCallbacks = new Set<(loading: boolean) => void>()\n\n// Subscribe to loading state changes\nexport const onLoadingChange = (callback: (loading: boolean) => void) => {\n  const unwatch = () => {\n    loadingCallbacks.delete(callback)\n  }\n  loadingCallbacks.add(callback)\n  return unwatch\n}\n\n// Get current loading state\nexport const getLoadingState = () => isLoading.value\n\n// Set loading state and notify all callbacks\nconst setLoading = (loading: boolean) => {\n  isLoading.value = loading\n  loadingCallbacks.forEach((callback) => callback(loading))\n}\n\n// Wrapper for saveConfiguration that automatically handles loading overlay\nexport const saveConfigurationWithLoading = async (data: string) => {\n  if (isLoading.value) {\n    console.warn('Save operation already in progress, ignoring duplicate call')\n    return\n  }\n\n  try {\n    setLoading(true)\n    await window.api.saveConfiguration(data)\n  } catch (error) {\n    console.error('Error in saveConfigurationWithLoading:', error)\n    throw error\n  } finally {\n    // Don't set loading to false here - let the LoadingOverlay handle it\n    // based on the progress events from the main process\n  }\n}\n\n// Function to manually hide loading (for fallback timeouts)\nexport const hideLoading = () => {\n  setLoading(false)\n}\n"
  },
  {
    "path": "src/renderer/src/helpers/types.d.ts",
    "content": ""
  },
  {
    "path": "src/renderer/src/main.ts",
    "content": "import { createApp } from 'vue'\nimport App from './App.vue'\nimport router from './router'\n\nimport '@mdi/font/css/materialdesignicons.css'\nimport './style/index.css'\n// import '@vueform/multiselect/themes/default.css'\nimport './style/multiselect.css'\n\nconst app = createApp(App)\napp.use(router)\napp.mount('#app')\n"
  },
  {
    "path": "src/renderer/src/router/index.ts",
    "content": "import { createRouter, createWebHashHistory } from 'vue-router'\nimport LaunchScreen from '../screens/LaunchScreen.vue'\nimport AddKeyboard from '../screens/AddKeyboard.vue'\nimport KeyboardConfigurator from '../screens/KeyboardConfigurator.vue'\nimport KmkInstaller from '../components/KmkInstaller.vue'\nimport SetupWizard from '../screens/SetupWizard.vue'\nimport LayoutEditor from '../components/LayoutEditor.vue'\nimport KeymapEditor from '../components/KeymapEditor.vue'\nimport EncoderSetup from '../components/EncoderSetup.vue'\nimport MatrixSetup from '../components/MatrixSetup.vue'\nimport PinSetup from '../components/PinSetup.vue'\nimport RawKeymapEditor from '../components/RawKeymapEditor.vue'\nimport KeyboardName from '../components/KeyboardName.vue'\nimport CoordMap from '../components/CoordMap.vue'\nimport RgbSetup from '../components/RgbSetup.vue'\nimport Community from '../components/Community.vue'\n// import Debug from '../components/debug.vue'\n\nimport KeyboardSelector from '../screens/KeyboardSelector.vue'\nimport CircuitPythonSetup from '../components/CircuitPythonSetup.vue'\nimport SetupMethodSelector from '../components/SetupMethodSelector.vue'\nimport AutomaticSetup from '../components/AutomaticSetup.vue'\n// import KeyboardSetup from '../screens/KeyboardSetup.vue'\nimport InstallPogFirmware from '../components/installPogFirmware.vue'\n\nconst routes = [\n  {\n    path: '/',\n    name: 'Launch',\n    component: LaunchScreen\n  },\n  {\n    path: '/add-keyboard',\n    name: 'Add Keyboard',\n    component: AddKeyboard\n  },\n  {\n    // manual setup\n    path: '/setup-wizard',\n    name: 'Setup Wizard',\n    component: SetupWizard\n  },\n  {\n    path: '/keyboard-selector',\n    name: 'Keyboard Selector',\n    component: KeyboardSelector\n  },\n  {\n    path: '/automatic-setup',\n    name: 'Automatic Setup',\n    children: [\n      {\n        path: 'circuit-python',\n        name: 'CircuitPython Setup',\n        component: CircuitPythonSetup\n      },\n      {\n        path: 'method',\n        name: 'Setup Method',\n        component: SetupMethodSelector\n      },\n      {\n        path: 'mapping',\n        name: 'Automatic Setup',\n        component: AutomaticSetup\n      },\n      {\n        path: 'firmware',\n        name: 'Pog Firmware',\n        component: InstallPogFirmware\n      }\n    ]\n  },\n  {\n    path: '/configurator',\n    name: 'Configurator',\n    component: KeyboardConfigurator,\n    children: [\n      {\n        path: 'keymap',\n        name: 'Keymap',\n        component: KeymapEditor\n      },\n      {\n        path: 'layout-editor',\n        name: 'Layout Editor',\n        component: LayoutEditor\n      },\n      {\n        path: 'encoder',\n        name: 'Encoder',\n        component: EncoderSetup\n      },\n      {\n        path: 'info',\n        name: 'Info',\n        component: KeyboardName\n      },\n      {\n        path: 'matrix',\n        name: 'Matrix',\n        component: MatrixSetup\n      },\n      {\n        path: 'pins',\n        name: 'Pins',\n        component: PinSetup\n      },\n      {\n        path: 'coordmap',\n        name: 'CoordMap',\n        component: CoordMap\n      },\n      {\n        path: 'raw-keymap',\n        name: 'Raw Keymap',\n        component: RawKeymapEditor\n      },\n      {\n        path: 'firmware',\n        name: 'Firmware',\n        component: KmkInstaller\n      },\n      {\n        path: 'coordmap',\n        component: CoordMap,\n        name: 'CoordMap'\n      },\n      {\n        path: 'community',\n        component: Community,\n        name: 'Community'\n      },\n      {\n        path: 'rgb',\n        name: 'RGB',\n        component: RgbSetup\n      }\n    ]\n  }\n]\n\nexport const router = createRouter({\n  history: createWebHashHistory(),\n  routes\n})\nexport default router\n"
  },
  {
    "path": "src/renderer/src/screens/AddKeyboard.vue",
    "content": "<template>\n  <div class=\"btn\" @click=\"$router.push('/')\"><i class=\"mdi mdi-close\"></i></div>\n  <p>Create a Custom Keyboard firmware from scratch or choose a Keyboard from the community</p>\n  <div>\n    <button class=\"btn btn-primary\" @click=\"selectDrive\">from scratch</button>\n    <button class=\"btn\">Select a Keyboard</button>\n  </div>\n  <div>\n    {{ keyboardStore.path }}\n    {{ keyboardStore.keys.length }}\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { keyboardStore } from '../store'\nimport { useRouter } from 'vue-router'\nconst router = useRouter()\nconst selectDrive = async () => {\n  const keyboard = await window.api.selectDrive()\n  console.log(keyboard)\n  keyboardStore.import(keyboard)\n  console.log(keyboardStore)\n  if (keyboardStore.pogConfigured) {\n    router.push('/configurator/keymap')\n  } else {\n    router.push('/setup-wizard')\n  }\n}\n</script>\n\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "src/renderer/src/screens/KeyboardConfigurator.vue",
    "content": "<template>\n  <div class=\"flex h-screen\">\n    <ul\n      class=\"menu flex-shrink-0 bg-base-100\"\n      :class=\"{\n        'menu-open': menuOpen\n      }\"\n    >\n      <li class=\"flex items-center p-4 text-xl font-bold\" @click=\"iconClick\">\n        <img src=\"@renderer/assets/icon.png\" alt=\"\" class=\"h-24 rounded\" />\n      </li>\n      <li class=\"flex items-center p-4 pt-0\">\n        <span\n          class=\"w-full cursor-pointer rounded bg-base-300 text-center text-xs\"\n          @click=\"reselectKeyboard\"\n        >\n          <i class=\"mdi mdi-keyboard\"></i>\n          {{ keyboardStore.name }}</span\n        >\n      </li>\n      <li>\n        <router-link to=\"/configurator/keymap\"\n          ><i class=\"mdi mdi-alphabetical-variant\"></i>Keymap</router-link\n        >\n      </li>\n      <li>\n        <router-link to=\"/configurator/layout-editor\"\n          ><i class=\"mdi mdi-keyboard-variant\"></i>Keyboard Layout</router-link\n        >\n      </li>\n      <li>\n        <router-link to=\"/configurator/encoder\"\n          ><i class=\"mdi mdi-axis-z-rotate-clockwise\"></i>Encoder</router-link\n        >\n      </li>\n      <li>\n        <router-link to=\"/configurator/rgb\"><i class=\"mdi mdi-led-on\"></i>RGB</router-link>\n      </li>\n      <hr class=\"border-white border-opacity-20\" />\n      <li>\n        <router-link to=\"/configurator/info\"\n          ><i class=\"mdi mdi-information-outline\"></i>Info</router-link\n        >\n      </li>\n      <li>\n        <router-link to=\"/configurator/matrix\"><i class=\"mdi mdi-grid\"></i>Matrix</router-link>\n      </li>\n      <li>\n        <router-link to=\"/configurator/pins\"\n          ><i class=\"mdi mdi-electric-switch\"></i>Pins</router-link\n        >\n      </li>\n      <li>\n        <router-link to=\"/configurator/coordmap\"\n          ><i class=\"mdi mdi-sort-numeric-ascending\"></i>CoordMap</router-link\n        >\n      </li>\n      <li>\n        <router-link to=\"/configurator/raw-keymap\"\n          ><i class=\"mdi mdi-code-brackets\"></i>Raw Keymap</router-link\n        >\n      </li>\n      <li>\n        <router-link to=\"/configurator/firmware\"><i class=\"mdi mdi-flash\"></i>Firmware</router-link>\n      </li>\n    </ul>\n    <div class=\"flex h-full w-full flex-col overflow-y-auto\">\n      <div class=\"z-10 flex items-center justify-between bg-base-100 py-4 shadow-xl\">\n        <h1\n          id=\"navTitle\"\n          class=\"flex-grow overflow-auto text-center text-4xl font-bold\"\n          contenteditable=\"true\"\n          spellcheck=\"false\"\n          style=\"line-height: 48px; max-height: 100px\"\n        >\n          {{ currentRouteName }}\n        </h1>\n        <div class=\"btn btn-ghost mr-4\" @click=\"info\">\n          <i class=\"mdi mdi-help-circle-outline text-2xl\"></i>\n        </div>\n        <div class=\"btn btn-primary mr-4\" @click=\"saveKeymap\">\n          <i class=\"mdi mdi-content-save text-2xl\"></i>\n        </div>\n        <div class=\"btn mr-4\" :class=\"{ 'btn-primary': showDebug }\" @click=\"toggleDebug\">\n          <i class=\"mdi mdi-bug text-2xl\"></i>\n        </div>\n      </div>\n      <div class=\"flex flex-grow overflow-hidden bg-base-200\">\n        <div class=\"flex-grow overflow-y-auto px-4 pt-4\">\n          <router-view></router-view>\n        </div>\n        <div\n          v-show=\"showDebug\"\n          class=\"w-[600px] flex-shrink-0 overflow-y-auto border-l border-base-300 bg-base-100 p-4\"\n        >\n          <Debug />\n        </div>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { addToHistory, keyboardStore } from '../store'\nimport { useRoute, useRouter } from 'vue-router'\nimport { computed, onMounted, ref } from 'vue'\nimport Debug from '../components/debug.vue'\nimport { saveConfigurationWithLoading } from '../helpers/saveConfigurationWrapper'\n\nconst router = useRouter()\nconst route = useRoute()\nconst showDebug = ref(false)\n\n// nav guard\nconsole.log('path is', keyboardStore.path)\nif (!keyboardStore.path && !keyboardStore.usingSerial) {\n  router.push('/')\n}\n\nconst toggleDebug = () => {\n  showDebug.value = !showDebug.value\n}\n\nconst reselectKeyboard = () => {\n  window.api.deselectKeyboard()\n  router.push('/')\n}\n\nconst saveKeymap = async () => {\n  try {\n    keyboardStore.coordMapSetup = false\n    const keyboardData = keyboardStore.serialize()\n    addToHistory(keyboardStore)\n    console.log(keyboardStore.coordMapSetup)\n    await saveConfigurationWithLoading(\n      JSON.stringify({ pogConfig: keyboardData, serial: keyboardStore.usingSerial })\n    )\n  } catch (error) {\n    console.error('Error saving keymap:', error)\n  }\n}\n\nconst currentRouteName = computed(() => route.matched[1]?.name)\n\nconst menuOpen = ref(true)\n\nconst iconClick = () => {\n  // menuOpen.value = !menuOpen.value\n  reselectKeyboard()\n}\n\nonMounted(() => {\n  const title = document.getElementById('navTitle')\n  title?.addEventListener('blur', () => {\n    if (typeof currentRouteName.value === 'string') {\n      title.innerText = currentRouteName.value\n    }\n  })\n  title?.addEventListener('focus', () => {\n    title.innerText = ''\n  })\n})\n\nconst info = () => {\n  // TODO: open a browser window with the help docs for this route\n  window.api.openExternal('https://pog.heaper.de/docs/intro')\n}\n</script>\n\n<style lang=\"scss\" scoped>\n.router-link-active {\n  @apply font-bold text-primary;\n}\n.menu {\n  width: 80px;\n  overflow: hidden;\n  overflow-y: auto;\n  flex-wrap: nowrap;\n  * {\n    white-space: nowrap;\n  }\n  li a {\n    opacity: 0.5;\n    &.router-link-active {\n      opacity: 1;\n    }\n    &:focus {\n      background: transparent;\n      @apply text-primary;\n    }\n  }\n}\n.menu-open {\n  width: 200px;\n  position: relative;\n  height: 100vh;\n}\n/* Fallback spinner animation in case Tailwind's animate-spin is unavailable */\n.animate-spin {\n  animation: spin 1s linear infinite;\n}\n@keyframes spin {\n  from {\n    transform: rotate(0deg);\n  }\n  to {\n    transform: rotate(360deg);\n  }\n}\n</style>\n"
  },
  {
    "path": "src/renderer/src/screens/KeyboardSelector.vue",
    "content": "<template>\n  <div class=\"min-h-screen bg-base-200 flex items-center justify-center\">\n    <div class=\"max-w-2xl w-full p-8\">\n      <div class=\"text-center mb-12\">\n        <button class=\"btn btn-primary\" @click=\"router.back()\">Back</button>\n      </div>\n\n      <!-- Recent Keyboards -->\n      <div v-if=\"recentKeyboards.length > 0\" class=\"mb-12\">\n        <h2 class=\"text-2xl font-semibold mb-4\">Recent Keyboards</h2>\n        <div class=\"grid gap-4\">\n          <div\n            v-for=\"keyboard in recentKeyboards\"\n            :key=\"keyboard.id\"\n            class=\"bg-base-100 p-4 rounded-lg flex items-center justify-between cursor-pointer hover:bg-base-300\"\n            @click=\"selectKeyboard(keyboard)\"\n          >\n            <div>\n              <h3 class=\"font-medium\">{{ keyboard.name }}</h3>\n              <p class=\"text-sm text-gray-500\">{{ keyboard.path }}</p>\n            </div>\n            <i class=\"mdi mdi-chevron-right text-2xl\"></i>\n          </div>\n        </div>\n      </div>\n\n      <!-- Setup New Keyboard -->\n      <div class=\"bg-base-100 rounded-lg p-8\">\n        <h2 class=\"text-2xl font-semibold mb-4\">Set Up New Keyboard</h2>\n        <div class=\"space-y-4\">\n          <button\n            class=\"btn btn-primary w-full\"\n            @click=\"setupNewKeyboard\"\n          >\n            <i class=\"mdi mdi-plus-circle mr-2\"></i>\n            New Keyboard\n          </button>\n          <button\n            class=\"btn w-full\"\n            @click=\"addExistingKeyboard\"\n          >\n            <i class=\"mdi mdi-file-import mr-2\"></i>\n            Add existing POG keyboard\n          </button>\n        </div>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, onMounted } from 'vue'\nimport { useRouter } from 'vue-router'\nimport { addToHistory, keyboardStore } from '../store'\n\ninterface Keyboard {\n  id: string\n  name: string\n  path: string\n  usingSerial?: boolean\n}\n\nconst router = useRouter()\nconst recentKeyboards = ref<Keyboard[]>([])\n\nasync function loadRecentKeyboards() {\n  try {\n    // const keyboards = await window.api.listKeyboards()\n    // recentKeyboards.value = keyboards\n  } catch (error) {\n    console.error('Failed to load recent keyboards:', error)\n  }\n}\n\nasync function setupNewKeyboard() {\n  console.log('setupNewKeyboard')\n  router.push('/automatic-setup/circuit-python')\n}\n\nasync function addExistingKeyboard() {\n  const keyboard = await window.api.selectDrive()\n  console.log(keyboard)\n  keyboardStore.import(keyboard)\n  console.log(keyboardStore)\n  if (keyboardStore.pogConfigured) {\n    router.push('/configurator/keymap')\n    // also save to history\n    addToHistory(keyboardStore)\n  } else {\n    router.push('/setup-wizard')\n  }\n}\n\nfunction selectKeyboard(keyboard: Keyboard) {\n  // Load keyboard configuration\n  Object.assign(keyboardStore, keyboard)\n  // Navigate to configurator\n  router.push('/configurator/keymap')\n}\n\nonMounted(() => {\n  console.log('KeyboardSelector onMounted')\n  loadRecentKeyboards()\n})\n</script> "
  },
  {
    "path": "src/renderer/src/screens/KeyboardSetup.vue",
    "content": "<template>\n  <div class=\"min-h-screen bg-base-200\">\n    <div class=\"container mx-auto py-8\">\n      <router-view\n        @setup-complete=\"handleSetupComplete\"\n      ></router-view>\n    </div>\n  </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { keyboardStore } from '../store'\n\nfunction handleSetupComplete(config: any) {\n  // Update the keyboard store with the detected configuration\n  keyboardStore.rows = config.rows\n  keyboardStore.cols = config.cols\n  keyboardStore.diodeDirection = config.diodeDirection\n  keyboardStore.coordMapSetup = true\n  keyboardStore.pinPrefix = 'board'\n  \n  // Navigate to the keymap editor\n//   router.push('/configurator/keymap')\n}\n</script> "
  },
  {
    "path": "src/renderer/src/screens/LaunchScreen.vue",
    "content": "<template>\n  <div class=\"flex flex-col p-4\">\n    <div class=\"flex items-center justify-between px-12\">\n      <div class=\"flex items-center gap-2\">\n        <div class=\"flex items-center gap-2\">\n          <img src=\"../assets/icon.png\" alt=\"\" class=\"w-16\" />\n        </div>\n        <div>\n          <p class=\"text-2xl font-bold\">Easy Keyboard Configurator</p>\n          <p class=\"text-sm\">Effortlessly customize your keyboard with Pog</p>\n        </div>\n      </div>\n      <div class=\"my-4 flex flex-col items-center justify-center gap-2\">\n        <button class=\"btn btn-primary\" @click=\"goToAddKeyboard\">\n          <i class=\"mdi mdi-plus mr-1 text-lg\"></i><span class=\"text-xs\">add keyboard</span>\n        </button>\n      </div>\n    </div>\n    <div v-if=\"sortedKeyboards.length !== 0\" class=\"divider\"></div>\n    <div class=\"absolute right-2 top-40 flex justify-end\">\n      <button class=\"btn btn-sm\" @click=\"refreshConnectedBoards\">\n        <i class=\"mdi mdi-refresh\"></i>\n      </button>\n    </div>\n    <TransitionGroup name=\"list\" tag=\"ul\" class=\"keyboard-list\">\n      <div\n        v-for=\"keyboard in sortedKeyboards\"\n        :key=\"keyboard.id\"\n        class=\"keyboard-preview\"\n        :class=\"{\n          'opacity-50': keyboard.path && !keyboard.driveConnected\n        }\"\n        @click=\"selectKeyboard(keyboard)\"\n      >\n        <div class=\"image\">\n          <div class=\"h-full w-full overflow-hidden p-2\">\n            <keyboard-layout\n              v-if=\"keyboard.keys\"\n              :key-layout=\"keyboard.keys\"\n              :keymap=\"keyboard.keymap\"\n              :matrix-width=\"keyboard.cols\"\n              :layouts=\"keyboard.layouts\"\n              mode=\"static\"\n              :fixed-height=\"true\"\n            ></keyboard-layout>\n          </div>\n        </div>\n        <div class=\"relative flex flex-grow flex-col pr-14\">\n          <p\n            v-if=\"\n              serialKeyboards.find(({ id, driveMounted }) => id === keyboard.id && !driveMounted)\n            \"\n          >\n            <span class=\"rounded bg-info p-1 text-xs\"> Serial <span>BETA</span> </span>\n          </p>\n          <p v-else-if=\"keyboard.path\">\n            <span\n              v-if=\"serialKeyboards.find((a) => a.id === keyboard.id)\"\n              class=\"mr-2 rounded bg-gray-600 p-1 text-xs\"\n            >\n              Serial available\n            </span>\n            <span v-if=\"keyboard.driveConnected\" class=\"rounded bg-accent p-1 text-xs\"\n              >USB Drive Mounted</span\n            >\n            <span v-else class=\"rounded bg-error p-1 text-xs\">USB Drive Disconnected</span>\n          </p>\n          <p v-else><span class=\"rounded bg-error p-1 text-xs\">Read Only Serial</span></p>\n          <button\n            class=\"btn btn-error btn-xs absolute right-5 top-0 opacity-50 hover:opacity-100\"\n            @click.stop=\"removeFromHistory(keyboard)\"\n          >\n            <i class=\"mdi mdi-close\"></i>\n          </button>\n          <p class=\"font-bold\">{{ keyboard.name }}</p>\n          <p class=\"text-sm italic\">{{ keyboard.manufacturer }}</p>\n          <p class=\"mt-2 text-xs italic\">{{ keyboard.path }}</p>\n          <p class=\"mt-2\" style=\"font-size: 12px\">{{ keyboard.description }}</p>\n          <div class=\"mt-2\">\n            <div v-for=\"tag in keyboard.tags\" :key=\"tag\" class=\"badge badge-outline\">\n              {{ tag }}\n            </div>\n          </div>\n        </div>\n      </div>\n    </TransitionGroup>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { useRouter } from 'vue-router'\nimport {\n  keyboardStore,\n  keyboardHistory,\n  notifications,\n  KeyboardStore,\n  selectedLayer,\n  serialKeyboards\n} from '../store'\nimport KeyboardLayout from '../components/KeyboardLayout.vue'\nimport { computed, onMounted, ref, watch } from 'vue'\nconst router = useRouter()\nselectedLayer.value = 0\n\n\n\nconst sortedKeyboards = computed(() => {\n  return [...keyboards.value].sort((a, b) => {\n    if (a.driveConnected && !b.driveConnected) return -1\n    if (!a.driveConnected && b.driveConnected) return 1\n    return 0\n  })\n})\n\nconst selectKeyboard = async (keyboard) => {\n  const isSerial = serialKeyboards.value.find(\n    ({ id, driveMounted }) => id === keyboard.id && !driveMounted\n  )\n  if (isSerial || !keyboard.path) {\n    console.log('connectiong keyboard via serial')\n    window.api.selectKeyboard({ id: keyboard.id })\n  } else {\n    const keyboardData = await window.api.selectKeyboard({ path: keyboard.path })\n    console.log('connecting to keyboard via files')\n    if (keyboardData.error) {\n      if (keyboardData.error === 'pathNotFound') {\n        console.log('keyboard is not connected')\n        notifications.value.push({ label: 'Keyboard not connected' })\n      }\n      return\n    }\n    keyboardStore.import(keyboardData)\n    router.push('/configurator/keymap')\n  }\n}\n\nconst removeFromHistory = (keyboard) => {\n  keyboardHistory.value = keyboardHistory.value.filter((board) => board.id !== keyboard.id)\n  keyboards.value = keyboards.value.filter((board) => board.id !== keyboard.id)\n}\n\nconst keyboards = ref<any[]>([])\nkeyboardHistory.value.forEach((keyb) => {\n  const keyboard = new KeyboardStore()\n  keyboard.import({\n    configContents: keyb,\n    path: keyb.path,\n    folderContents: ['pog.json'],\n    codeContents: ''\n  })\n  keyboards.value.push(keyboard)\n})\n\nconst addSerialKeyboards = () => {\n  serialKeyboards.value.forEach((board) => {\n    if (!keyboards.value.find((a) => a.id === board.id)) {\n      keyboards.value.unshift({\n        id: board.id,\n        port: board.port,\n        name: board.name,\n        description: board.description,\n        manufacturer: board.manufacturer\n      })\n    } else {\n      // update the port\n      console.log('adding port to keyboard', board.id, board.port, board.serialNumber)\n      // keyboards.value.find((a) => a.id === board.id).port = board.port\n      // keyboards.value.find((a) => a.id === board.id).serialNumber = board.serialNumber\n    }\n  })\n}\n\nconst checkForUSBKeyboards = () => {\n  const keyboardPaths = keyboards.value.map((keyboard) => keyboard.path)\n  console.log('requesting usb keyboards', keyboardPaths)\n  window.api.checkForUSBKeyboards(keyboardPaths).then((keyboardConnections) => {\n    keyboardConnections.forEach((keyboard) => {\n      console.log(keyboard)\n      if (keyboard.connected) {\n        // set connected state in the keyboard list\n        keyboards.value.find((a) => a.path === keyboard.path).driveConnected = true\n      }\n    })\n  })\n  console.log('updated kbs', keyboards.value)\n}\nwatch(serialKeyboards, () => {\n  addSerialKeyboards()\n})\n\nconst refreshConnectedBoards = () => {\n  checkForUSBKeyboards()\n  addSerialKeyboards()\n}\n\nconst goToAddKeyboard = () => {\n  router.push('/keyboard-selector')\n}\n\nonMounted(() => {\n  window.api.rescanKeyboards()\n  // load the serial keyboards into the shown list\n  addSerialKeyboards()\n  checkForUSBKeyboards()\n})\n</script>\n\n<style lang=\"scss\" scoped>\n.keyboard-list {\n  @apply mx-auto mt-2 gap-2 pb-2;\n  max-width: 700px;\n}\n.keyboard-preview {\n  @apply flex cursor-pointer gap-4 rounded border border-white border-opacity-0 p-4 transition-all;\n  &:hover {\n    @apply border-opacity-40 bg-base-200;\n  }\n  .image {\n    @apply flex flex-shrink-0 items-center justify-center rounded;\n    width: 350px;\n    height: 130px;\n    border: 1px solid #333;\n  }\n}\n.list-enter-active,\n.list-leave-active {\n  transition: all 0.5s ease;\n}\n.list-enter-from,\n.list-leave-to {\n  opacity: 0;\n  transform: translateY(-30px);\n}\n</style>\n"
  },
  {
    "path": "src/renderer/src/screens/SetupWizard.vue",
    "content": "<template>\n  <div class=\"flex h-full w-full flex-col items-center\">\n    <div class=\"flex-grow-0\">\n      <h1 class=\"my-4 mt-8 text-center text-5xl font-bold\">Initial Keyboard Setup</h1>\n      <div class=\"mb-8 flex items-center justify-center gap-4 text-center\">\n        <div class=\"badge badge-primary badge-outline p-4\">Keyboard: {{ keyboardStore.path }}</div>\n        <div class=\"btn btn-circle btn-primary\" @click=\"$router.push('/')\">\n          <i class=\"mdi mdi-sync text-xl\"></i>\n        </div>\n      </div>\n\n      <ul class=\"steps mb-2 w-full w-full\">\n        <li class=\"step\" :class=\"{ 'step-primary': currentStep >= 0 }\" @click=\"currentStep = 0\">\n          Firmware\n        </li>\n        <li\n          class=\"step\"\n          :class=\"{ 'step-primary': currentStep >= 1 }\"\n          @click=\"currentStep > 1 ? (currentStep = 1) : undefined\"\n        >\n          Name\n        </li>\n        <li\n          class=\"step\"\n          :class=\"{ 'step-primary': currentStep >= 2 }\"\n          @click=\"currentStep > 2 ? (currentStep = 2) : undefined\"\n        >\n          Matrix\n        </li>\n        <li\n          class=\"step\"\n          :class=\"{ 'step-primary': currentStep >= 3 }\"\n          @click=\"currentStep > 3 ? (currentStep = 3) : undefined\"\n        >\n          Pins\n        </li>\n        <li\n          class=\"step\"\n          :class=\"{ 'step-primary': currentStep >= 4 }\"\n          @click=\"currentStep > 4 ? (currentStep = 4) : undefined\"\n        >\n          Coordmap\n        </li>\n        <li\n          class=\"step\"\n          :class=\"{ 'step-primary': currentStep >= 5 }\"\n          @click=\"currentStep > 5 ? (currentStep = 5) : undefined\"\n        >\n          Layout\n        </li>\n      </ul>\n    </div>\n    <div id=\"step-scroller\" class=\"flex-grow-1 h-full overflow-y-auto px-8\">\n      <KmkInstaller\n        v-if=\"currentStep === 0\"\n        :initial-setup=\"true\"\n        @next=\"currentStep++\"\n        @done=\"toConfigurator\"\n      />\n      <keyboard-name v-if=\"currentStep === 1\" :initial-setup=\"true\" @next=\"currentStep++\" />\n      <MatrixSetup v-if=\"currentStep === 2\" :initial-setup=\"true\" @next=\"currentStep++\" />\n      <PinSetup v-if=\"currentStep === 3\" :initial-setup=\"true\" @next=\"currentStep++\" />\n      <CoordMap v-if=\"currentStep === 4\" :initial-setup=\"true\" @next=\"currentStep++\" />\n      <LayoutEditor v-if=\"currentStep === 5\" :initial-setup=\"true\" @next=\"toConfigurator\" />\n    </div>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { keyboardStore } from '../store'\nimport { ref, watch } from 'vue'\n\nimport KmkInstaller from '../components/KmkInstaller.vue'\nimport MatrixSetup from '../components/MatrixSetup.vue'\nimport PinSetup from '../components/PinSetup.vue'\nimport LayoutEditor from '../components/LayoutEditor.vue'\nimport KeyboardName from '../components/KeyboardName.vue'\nimport { useRouter } from 'vue-router'\nimport CoordMap from '../components/CoordMap.vue'\n\nconst router = useRouter()\n\n// const steps = ref([\"kmk\", \"matrix\", \"pins\", \"layout\"]);\nconst currentStep = ref(0)\nconst toConfigurator = () => {\n  router.push('/configurator/keymap')\n  window.api.serialSend('ctrld')\n}\n\nwatch(currentStep, () => {\n  const scroller = document.querySelector('#step-scroller')\n  if (scroller) {\n    scroller.scrollTop = 0\n  }\n})\n</script>\n\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "src/renderer/src/store/index.ts",
    "content": "import { computed, ref } from 'vue'\nimport VueStore from '@wlard/vue-class-store'\nimport { ulid } from 'ulid'\nimport { useRouter } from 'vue-router'\nimport { matrixPositionToIndex } from '../helpers'\nimport { useStorage } from '@vueuse/core'\nimport dayjs from 'dayjs'\n\nconst router = useRouter()\n// @ts-ignore will be used later\ntype KeyActions = {\n  type: 'chord' | 'tap' | 'short_hold' | 'hold' | 'macro'\n  keycodes: string[]\n  actions: KeyActions[]\n}[]\n\nexport const keyboardHistory = useStorage<any[]>('keyboardHistory', [])\nexport const addToHistory = (keyboard) => {\n  console.log('saving keyboard to history', keyboard)\n  // to get rid of reactivity and proxys while deepcloning // does not work with structured clone\n  const keyboardData = {\n    ...JSON.parse(JSON.stringify(keyboard.serialize())),\n    ...(keyboard.path && { path: keyboard.path })\n  }\n  if (!keyboardHistory.value.find((board) => board.id === keyboard.id)) {\n    keyboardHistory.value.unshift(keyboardData)\n  } else {\n    const index = keyboardHistory.value.findIndex((board) => board.id === keyboard.id)\n    // push this version to the backups as well\n    if (!keyboardHistory.value[index].backups) keyboardHistory.value[index].backups = []\n    const backups = [keyboardData, ...keyboardHistory.value[index].backups].slice(0, 100)\n    keyboardHistory.value[index] = {\n      ...keyboardData,\n      backups\n    }\n  }\n}\n\n// list of key indexes that are selected\nexport const selectedKeys = ref<Set<number>>(new Set())\n\n// currently selected keymap layer\nexport const selectedLayer = ref(0)\n\ntype EncoderLayer = EncoderActions[]\ntype EncoderActions = [string, string]\n\nexport type BaseKeyInfo = {\n  x: number\n  y: number\n  x2?: number\n  y2?: number\n  w?: number\n  h?: number\n  h2?: number\n  w2?: number\n  r?: number\n  rx?: number\n  ry?: number\n}\nexport type KeyInfo = BaseKeyInfo & {\n  matrix?: [number, number]\n  variant?: [number, number]\n  directPinIndex?: number\n  coordMapIndex?: number\n  idx?: number\n  encoderIndex?: number\n  keyboard?: any\n}\n\nexport class Key {\n  id = ulid()\n  x = 0\n  x2?: number = undefined\n  y = 0\n  y2?: number = undefined\n  w = 1\n  w2?: number = undefined\n  h = 1\n  h2?: number = undefined\n  r = 0\n  rx = 0\n  ry = 0\n  matrix?: [number, number] = undefined\n  coordMapIndex?: number = undefined\n  encoderIndex?: number = undefined\n  variant?: [number, number] = undefined\n  keyboard?: any\n\n  constructor({\n    x,\n    y,\n    matrix,\n    variant,\n    h,\n    w,\n    x2,\n    y2,\n    h2,\n    w2,\n    r,\n    rx,\n    ry,\n    directPinIndex, // Todo: will be removed for idx in the future\n    coordMapIndex, // Todo: will also be removed for idx\n    idx,\n    encoderIndex,\n    keyboard\n  }: KeyInfo) {\n    this.x = x\n    this.y = y\n    if (matrix && matrix.length === 2) {\n      this.matrix = matrix\n    }\n    if (variant && variant.length === 2) {\n      this.variant = variant\n    }\n    // coordmap things\n    if (!idx && typeof directPinIndex === 'number') idx = directPinIndex\n    if (typeof idx === 'number') this.coordMapIndex = idx\n    if (!idx && typeof coordMapIndex === 'number') this.coordMapIndex = coordMapIndex\n    if (h) this.h = h\n    if (w) this.w = w\n    if (h2) this.h2 = h2\n    if (w2) this.w2 = w2\n    if (x2) this.x2 = x2\n    if (y2) this.y2 = y2\n    if (r) this.r = r\n    if (rx) this.rx = rx\n    if (ry) this.ry = ry\n    if (typeof encoderIndex === 'number') this.encoderIndex = encoderIndex\n    this.keyboard = keyboard\n  }\n\n  serialize() {\n    const tmpKey: KeyInfo = {\n      x: this.x,\n      y: this.y\n    }\n    if (this.w !== 1) {\n      tmpKey.w = this.w\n    }\n    if (this.h !== 1) {\n      tmpKey.h = this.h\n    }\n    if (this.w2) {\n      tmpKey.w2 = this.w2\n    }\n    if (this.h2) {\n      tmpKey.h2 = this.h2\n    }\n    if (this.x2) {\n      tmpKey.x2 = this.x2\n    }\n    if (this.y2) {\n      tmpKey.y2 = this.y2\n    }\n    if (this.r) {\n      tmpKey.r = this.r\n    }\n    if (this.ry) {\n      tmpKey.ry = this.ry\n    }\n    if (this.rx) {\n      tmpKey.rx = this.rx\n    }\n    if (Array.isArray(this.matrix) && this.matrix.length === 2) {\n      tmpKey.matrix = this.matrix.map((n) => Number(n)) as [number, number]\n    }\n    if (Array.isArray(this.variant) && this.variant.length === 2) {\n      tmpKey.variant = this.variant.map((n) => Number(n)) as [number, number]\n    }\n    if (typeof this.coordMapIndex === 'number') {\n      tmpKey.idx = this.coordMapIndex\n    }\n    if (typeof this.encoderIndex === 'number') {\n      tmpKey.encoderIndex = this.encoderIndex\n    }\n    return tmpKey\n  }\n\n  set({}) {}\n\n  // happity hoppety property\n  delta({ property, value }: { value: number; property: keyof BaseKeyInfo }) {\n    console.log('writing delta', property, value, this[property])\n    if (this[property]) {\n      // validate eg not less than 0 on w and h\n      if (['w', 'h'].includes(property)) {\n        if (this[property]! + value < 0.25) {\n          return\n        }\n      }\n      // @ts-ignore only using correct keys from type\n      this[property] = this[property] + value\n    } else {\n      this[property] = value\n    }\n  }\n\n  getKeymapIndex() {\n    // if (!this.matrix && typeof this.coordMapIndex !== 'number') return undefined\n    // if (this.keyboard.wiringMethod === 'matrix') {\n    //   return matrixPositionToIndex({\n    //     pos: this.matrix || [0,0],\n    //     matrixWidth: keyboardStore.cols\n    //   })\n    // } else {\n    return this.coordMapIndex\n    // }\n  }\n\n  setOnKeymap(keyCode) {\n    const keyIndex = this.getKeymapIndex()\n    console.log('index', keyIndex, keyCode)\n    if (typeof keyIndex !== 'number') return\n\n    console.log('setting ', this.id, 'to', keyCode, 'at', keyIndex)\n    if (!keyCode.includes('(')) {\n      // TODO: could set this as arg in a key\n      // if (\n      //   currentKeyAction &&\n      //   currentKeyAction.includes(\"(\") &&\n      //   selectedKey.value.args\n      // ) {\n      //   // Validate for what args this function takes\n      //   // only set this as arg\n      //   // TODO: handle multiple args\n      //   let action = currentKeyAction.split(\"(\")[0].replace(\")\", \"\");\n      //   keymap.value[selectedLayer.value][keyIndex] =\n      //     action + \"(\" + keyCode + \")\";\n      //   return;\n      // }\n    }\n    keyboardStore.keymap[selectedLayer.value][keyIndex] = keyCode\n  }\n\n  getMatrixLabel() {\n    if (typeof this.coordMapIndex === 'number') return this.coordMapIndex\n    if (this.matrix) {\n      if (\n        typeof this.matrix[0] === 'number' &&\n        !isNaN(this.matrix[0]) &&\n        typeof this.matrix[1] === 'number' &&\n        !isNaN(this.matrix[1])\n      )\n        return `${this.matrix[0]} - ${this.matrix[1]}`\n      if (typeof this.matrix[0] === 'number' && !isNaN(this.matrix[0]))\n        return `${this.matrix[0]} - X`\n      if (typeof this.matrix[1] === 'number' && !isNaN(this.matrix[1]))\n        return `X - ${this.matrix[1]}`\n    }\n    return ''\n  }\n\n  getEncoderLabel() {\n    if (typeof this.encoderIndex !== 'number') return { a: '', b: '' }\n    return {\n      a: this.keyboard.encoderKeymap[0][this.encoderIndex][0],\n      b: this.keyboard.encoderKeymap[0][this.encoderIndex][1]\n    }\n  }\n}\n\nexport type RgbOptions = {\n  animationMode: number\n  hueDefault: number\n  satDefault: number\n  valDefault: number\n  animationSpeed: number\n  breatheCenter: number\n  knightEffectLength: number\n}\n\nexport class Keyboard {\n  id = ulid()\n  path?: string = undefined\n  name = ''\n  manufacturer = ''\n  tags: string[] = []\n  description = ''\n\n  // serial interface\n  port?: string = undefined // only set when serial is available\n  usingSerial = false\n  serialNumber = ''\n  serialPortA?: string = undefined // Port with lower number\n  serialPortB?: string = undefined // Port with higher number\n\n  driveConnected = false\n  driveContents: string[] = []\n\n  //manage the code.py yourself\n  flashingMode: 'automatic' | 'manual' = 'automatic'\n\n  pogConfigured = false\n  firmwareInstalled = false\n\n  // layout\n  keys: Key[] = []\n  // Layout options\n  layouts: { name: string; variants: string[]; selected: number }[] = []\n  coordMap: string[][] = []\n  // wiring\n\n  controller = ''\n  diodeDirection: 'ROW2COL' | 'COL2ROW' = 'COL2ROW'\n  wiringMethod: 'matrix' | 'direct' = 'matrix'\n  rows = 1\n  cols = 1\n  pins = 1\n  rowPins: string[] = []\n  colPins: string[] = []\n  directPins: string[] = []\n  coordMapSetup = false\n\n  rgbPin = ''\n  rgbNumLeds = 0\n  rgbOptions: RgbOptions = {\n    animationMode: 0,\n    hueDefault: 0,\n    satDefault: 255,\n    valDefault: 255,\n    animationSpeed: 1,\n    breatheCenter: 1,\n    knightEffectLength: 3\n  }\n\n  pinPrefix: 'board' | 'gp' | 'none' | 'quickpin' = 'gp'\n  // features\n\n  encoders: { pad_a: string; pad_b: string }[] = []\n\n  // split features\n  keyboardType: 'normal' | 'splitBLE' | 'splitSerial' | 'splitOnewire' = 'normal'\n  splitSide: 'left' | 'right' | 'vbus' | 'label' = 'left'\n  // split = false\n  splitPinA = ''\n  splitPinB = ''\n  vbusPin = 'VBUS_SENSE'\n  splitUsePio = true\n  splitFlip = false\n  splitUartFlip = false\n\n  // keymaps\n\n  // layer > encoder index > encoder action index > keycode\n  encoderKeymap: EncoderLayer[] = []\n  keymap: (string | undefined)[][] = [[]]\n  layers: { name: string; color: string | undefined }[] = []\n\n  kbFeatures = [\n    'basic',\n    'serial',\n    'oneshot',\n    'tapdance',\n    'holdtap',\n    'mousekeys',\n    'combos',\n    'macros'\n  ]\n\n  constructor() {}\n\n  // Keys\n  setKeys(keys: KeyInfo[]) {\n    this.keys = []\n    if (!keys || keys.length === 0) return\n    keys.forEach((key) => {\n      const tmpKey = new Key({ ...key, keyboard: this })\n      this.keys.push(tmpKey)\n    })\n  }\n\n  getKeys() {\n    return this.keys.map((key) => key.serialize())\n  }\n\n  addKey(key) {\n    this.keys.push(new Key({ ...key, keyboard: this }))\n  }\n\n  removeKeys({ ids }: { ids: string[] }) {\n    this.keys = this.keys.filter((a) => !ids.includes(a.id))\n  }\n\n  deltaForKeys({\n    keyIndexes,\n    property,\n    value\n  }: {\n    keyIndexes: number[]\n    property: keyof BaseKeyInfo\n    value: number\n  }) {\n    keyIndexes.forEach((keyIndex) => {\n      this.keys[keyIndex].delta({ property, value })\n    })\n  }\n\n  hasFile(filename) {\n    return this.driveContents.includes(filename)\n  }\n\n  isSplit() {\n    return this.keyboardType !== 'normal'\n  }\n\n  // count keys on the matrix\n  physicalKeyCount() {\n    let keycount = 0\n    if (this.wiringMethod === 'matrix') {\n      keycount = this.rows * this.cols\n    } else {\n      keycount = this.pins\n    }\n    if (this.isSplit()) {\n      keycount = keycount * 2\n    }\n    return keycount\n  }\n\n  // count keys in the layout (including variant keys so duplicate physical keys)\n  keyCount() {\n    return this.keys.length\n  }\n\n  getMatrixWidth() {\n    let width = 0\n    if (this.wiringMethod === 'matrix') {\n      width = this.cols\n    } else {\n      width = this.pins\n    }\n    if (this.isSplit()) {\n      width = width * 2\n    }\n    return width\n  }\n\n  // get keymap index for matrix pos of a key\n  getKeymapIndexForKey({ key }) {\n    const keyIndex = matrixPositionToIndex({\n      pos: key.matrix,\n      matrixWidth: this.getMatrixWidth()\n    })\n    return keyIndex\n  }\n\n  getActionForKey({ key, layer }) {\n    if (!this.keymap[layer]) return 'No layer'\n    const keyCode = this.keymap[layer][key.getKeymapIndex()]\n    // resolve readable character\n    if (!keyCode || keyCode === 'KC.TRNS') return '▽'\n    return keyCode\n  }\n\n  import({\n    path,\n    configContents,\n    folderContents,\n    serial\n  }: {\n    path: string\n    codeContents?: string\n    configContents: any\n    folderContents: string[]\n    serial?: boolean\n  }) {\n    this.clear()\n    this.id = ulid()\n    this.path = path\n    this.driveContents = folderContents\n    this.usingSerial = serial === true\n    this.pogConfigured = this.hasFile('pog.json')\n    this.firmwareInstalled = this.hasFile('kmk')\n    if (this.pogConfigured) {\n      console.log('pog.json exists, importing keyboard features')\n      this.setKeys(configContents.keys)\n\n      if (configContents.id) this.id = configContents.id\n      if (configContents.name) this.name = configContents.name\n      if (configContents.description) this.description = configContents.description\n      if (configContents.tags) this.tags = configContents.tags\n      if (configContents.manufacturer) this.manufacturer = configContents.manufacturer\n      if (configContents.keyboardType) this.keyboardType = configContents.keyboardType\n      this.wiringMethod = configContents.wiringMethod || 'matrix'\n      this.flashingMode = configContents.flashingMode || 'automatic'\n      this.pinPrefix = configContents.pinPrefix || 'gp'\n      this.coordMapSetup = configContents.coordMapSetup || false\n\n      if (configContents.coordMap) this.coordMap = configContents.coordMap\n      if (configContents.rows) this.rows = configContents.rows\n      if (configContents.cols) this.cols = configContents.cols\n      if (configContents.pins) this.pins = configContents.pins\n      if (configContents.rowPins) this.rowPins = configContents.rowPins\n      if (configContents.colPins) this.colPins = configContents.colPins\n      if (configContents.diodeDirection) this.diodeDirection = configContents.diodeDirection\n      if (configContents.directPins) this.directPins = configContents.directPins\n      if (configContents.controller) this.controller = configContents.controller\n      if (configContents.keymap) this.keymap = configContents.keymap\n      if (configContents.layouts) this.layouts = configContents.layouts\n      if (configContents.layers) this.layers = configContents.layers\n\n      if (configContents.splitPinA) this.splitPinA = configContents.splitPinA\n      if (configContents.splitPinB) this.splitPinB = configContents.splitPinB\n      if (configContents.splitSide) this.splitSide = configContents.splitSide\n      if (configContents.vbusPin) this.vbusPin = configContents.vbusPin\n      if (configContents.splitUsePio) this.splitUsePio = configContents.splitUsePio\n      if (configContents.splitFlip) this.splitFlip = configContents.splitFlip\n      if (configContents.splitUartFlip) this.splitUartFlip = configContents.splitUartFlip\n\n      // encoders\n      if (configContents.encoders) this.encoders = configContents.encoders\n      if (configContents.encoderKeymap) this.encoderKeymap = configContents.encoderKeymap\n\n      //RGB\n      if (configContents.rgbPin) this.rgbPin = configContents.rgbPin\n      if (configContents.rgbNumLeds) this.rgbNumLeds = Number(configContents.rgbNumLeds)\n      if (configContents.rgbOptions) this.rgbOptions = configContents.rgbOptions\n\n      if (configContents.kbFeatures) this.kbFeatures = configContents.kbFeatures\n    }\n  }\n\n  clear() {\n    this.pogConfigured = false\n    this.path = ''\n    this.coordMap = []\n    this.layers = []\n    this.name = ''\n    this.description = ''\n    this.tags = []\n    this.manufacturer = ''\n    this.keyboardType = 'normal'\n    this.wiringMethod = 'matrix'\n    this.flashingMode = 'automatic'\n    this.pinPrefix = 'gp'\n    this.coordMapSetup = false\n    this.rows = 1\n    this.cols = 1\n    this.pins = 1\n    this.rowPins = []\n    this.colPins = []\n    this.directPins = []\n    this.diodeDirection = 'COL2ROW'\n    this.controller = ''\n    this.keymap = []\n    this.layouts = []\n    this.encoders = []\n    this.encoderKeymap = []\n    this.rgbPin = ''\n    this.rgbNumLeds = 0\n    this.rgbOptions = {\n      animationMode: 0,\n      hueDefault: 0,\n      satDefault: 255,\n      valDefault: 255,\n      animationSpeed: 1,\n      breatheCenter: 1,\n      knightEffectLength: 3\n    }\n    // split pins\n    this.splitPinA = ''\n    this.splitPinB = ''\n    this.splitSide = 'left'\n    this.vbusPin = 'VBUS_SENSE'\n    this.splitUsePio = true\n    this.splitFlip = false\n    this.splitUartFlip = false\n    // Reset serial ports\n    this.serialPortA = undefined\n    this.serialPortB = undefined\n    this.serialNumber = ''\n  }\n\n  serialize() {\n    return {\n      id: this.id,\n      name: this.name,\n      manufacturer: this.manufacturer,\n      description: this.description,\n      tags: this.tags,\n      controller: this.controller,\n      keyboardType: this.keyboardType,\n\n      wiringMethod: this.wiringMethod,\n      diodeDirection: this.wiringMethod == 'matrix' ? this.diodeDirection : '',\n      rows: this.wiringMethod == 'matrix' ? this.rows : 0,\n      cols: this.wiringMethod == 'matrix' ? this.cols : 0,\n      pins: this.wiringMethod == 'direct' ? this.pins : 0,\n\n      rowPins: this.wiringMethod == 'matrix' ? this.rowPins : [],\n      colPins: this.wiringMethod == 'matrix' ? this.colPins : [],\n      directPins: this.wiringMethod == 'direct' ? this.directPins : [],\n\n      encoders: this.encoders,\n\n      layouts: this.layouts,\n      keys: this.getKeys(),\n\n      keymap: this.keymap,\n      encoderKeymap: this.encoderKeymap,\n      layers: this.layers,\n\n      split: this.keyboardType !== 'normal',\n      splitPinA: this.splitPinA,\n      splitPinB: this.splitPinB,\n      splitSide: this.splitSide,\n      vbusPin: this.vbusPin,\n      splitUsePio: this.splitUsePio,\n      splitFlip: this.splitFlip,\n      splitUartFlip: this.splitUartFlip,\n\n      coordMap: this.coordMap,\n      pinPrefix: this.pinPrefix,\n      coordMapSetup: this.coordMapSetup,\n\n      rgbPin: this.rgbPin,\n      rgbNumLeds: this.rgbNumLeds,\n      rgbOptions: this.rgbOptions,\n\n      kbFeatures: this.kbFeatures,\n\n      flashingMode: this.flashingMode,\n      lastEdited: dayjs().format('YYYY-MM-DD HH:mm')\n    }\n  }\n}\n\n@VueStore\nexport class KeyboardStore extends Keyboard {}\n\nexport const keyboardStore = new KeyboardStore()\n\n// [0,2,3] index of array is the selected layout and the value its option\nexport const selectedVariants = ref<number[]>([])\nexport const layoutVariants = ref<(string | string[])[]>([])\n\nexport const isNewKeyboardSetup = computed(() => {\n  if (router) return router.currentRoute.value.path.startsWith('/setup-wizard')\n  return false\n})\n\nexport const notifications = ref<{ label: string }[]>([])\n\nexport const pinPrefixHint = computed(() => {\n  switch (keyboardStore.pinPrefix) {\n    case 'gp':\n      return 'Generates `board.GP1` like pins from numbers'\n      break\n    case 'board':\n      return 'Generates `board.yourpin` like pins from text'\n      break\n    case 'none':\n      return 'Generates `yourpin` like pins from text'\n      break\n    case 'quickpin':\n      return 'Generates `pins[1]` like pins from numbers'\n    default:\n      return ''\n  }\n})\n\nexport const splitSideHint = computed(() => {\n  switch (keyboardStore.splitSide) {\n    case 'label':\n      return 'Detects split side by label on the microcontroller'\n      break\n    case 'vbus':\n      return 'Detects split side using VBUS pin'\n      break\n    case 'left':\n      return 'Detects split side using value in config file (left side)'\n      break\n    case 'right':\n      return 'Detects split side using value in config file (right side)'\n      break\n    default:\n      return ''\n  }\n})\n\nexport const splitPinHint = computed(() => {\n  switch (keyboardStore.keyboardType) {\n    case 'splitSerial':\n      return 'Defines the serial pins used to connect the two halves.'\n      break\n    case 'splitOnewire':\n      return 'Defines the data pin used to connect the two halves'\n      break\n    default:\n      return ''\n  }\n})\n\nexport const vbusPinHint = computed(() => {\n  return `Generates 'board.${keyboardStore.vbusPin}'`\n})\n\nexport const userSettings = useStorage('user-settings', {\n  reduceKeymapColors: false,\n  autoSelectNextKey: false\n})\n\nexport const serialKeyboards = ref<any[]>([])\n"
  },
  {
    "path": "src/renderer/src/store/serial.ts",
    "content": "import { ref } from 'vue'\n\nexport const serialLogs = ref<string[]>([])\n\nconst MAX_LOGS = 500\n\nexport function addSerialLine(raw: string) {\n  const line = String(raw || '').trim()\n  if (!line) return\n  // ignore consecutive duplicates\n  if (serialLogs.value[0] === line) return\n  serialLogs.value.unshift(line)\n  if (serialLogs.value.length > MAX_LOGS) serialLogs.value.length = MAX_LOGS\n}\n\n\n"
  },
  {
    "path": "src/renderer/src/style/index.css",
    "content": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n"
  },
  {
    "path": "src/renderer/src/style/multiselect.css",
    "content": ".multiselect {\n  @apply relative mx-auto w-full flex items-center justify-end box-border text-white cursor-pointer border border-white border-opacity-40 rounded-xl text-base leading-snug outline-none;\n}\n.multiselect.is-disabled {\n  @apply cursor-default bg-gray-100;\n}\n.multiselect.is-open {\n  @apply rounded-b-none;\n}\n.multiselect.is-open-top {\n  @apply rounded-t-none;\n}\n/*.multiselect.is-active {*/\n/*  @apply ring ring-green-500 ring-opacity-30;*/\n/*}*/\n\n.multiselect-wrapper {\n  @apply relative mx-auto w-full flex items-center justify-end box-border cursor-pointer outline-none;\n}\n\n.multiselect-single-label {\n  @apply flex items-center h-full max-w-full absolute left-0 top-0 pointer-events-none bg-transparent leading-snug pl-3.5 pr-16 box-border rtl:left-auto rtl:right-0 rtl:pl-0 rtl:pr-3.5;\n}\n\n.multiselect-single-label-text {\n  @apply overflow-ellipsis overflow-hidden block whitespace-nowrap max-w-full;\n}\n\n.multiselect-multiple-label {\n  @apply flex items-center h-full absolute left-0 top-0 pointer-events-none bg-transparent leading-snug pl-3.5 rtl:left-auto rtl:right-0 rtl:pl-0 rtl:pr-3.5;\n}\n\n.multiselect-search {\n  @apply w-full absolute inset-0 outline-none focus:ring-0 appearance-none box-border border-0 text-base font-sans bg-white rounded pl-3.5 rtl:pl-0 rtl:pr-3.5;\n}\n\n.multiselect-tags {\n  @apply flex-grow flex-shrink flex flex-wrap items-center mt-1 pl-2 rtl:pl-0 rtl:pr-2;\n}\n\n.multiselect-tag {\n  @apply bg-green-500 text-white text-sm font-semibold py-0.5 pl-2 rounded mr-1 mb-1 flex items-center whitespace-nowrap rtl:pl-0 rtl:pr-2 rtl:mr-0 rtl:ml-1;\n}\n.multiselect-tag.is-disabled {\n  @apply pr-2 opacity-50 rtl:pl-2;\n}\n\n.multiselect-tag-remove {\n  @apply flex items-center justify-center p-1 mx-0.5 rounded-sm hover:bg-black hover:bg-opacity-10;\n}\n\n.multiselect-tag-remove-icon {\n  @apply bg-multiselect-remove bg-center bg-no-repeat opacity-30 inline-block w-3 h-3;\n}\n\n.multiselect-tag-remove:hover .multiselect-tag-remove-icon {\n  @apply opacity-60;\n}\n\n.multiselect-tags-search-wrapper {\n  @apply inline-block relative mx-1 mb-1 flex-grow flex-shrink h-full;\n}\n\n.multiselect-tags-search {\n  @apply absolute inset-0 border-0 outline-none focus:ring-0 appearance-none p-0 text-base font-sans box-border w-full bg-transparent;\n}\n\n.multiselect-tags-search-copy {\n  @apply invisible whitespace-pre-wrap inline-block h-px;\n}\n\n.multiselect-placeholder {\n  @apply flex items-center h-full absolute left-0 top-0 pointer-events-none bg-transparent leading-snug pl-3.5 text-gray-400 rtl:left-auto rtl:right-0 rtl:pl-0 rtl:pr-3.5;\n}\n\n.multiselect-caret {\n  @apply bg-multiselect-caret bg-center bg-no-repeat w-2.5 h-4 py-px box-content mr-3.5 relative z-10 opacity-40 flex-shrink-0 flex-grow-0 transition-transform transform pointer-events-none rtl:mr-0 rtl:ml-3.5;\n}\n.multiselect-caret.is-open {\n  @apply rotate-180 pointer-events-auto;\n}\n\n.multiselect-clear {\n  @apply pr-3.5 relative z-10 opacity-40 transition duration-300 flex-shrink-0 flex-grow-0 flex hover:opacity-80 rtl:pr-0 rtl:pl-3.5 ;\n}\n\n.multiselect-clear-icon {\n  @apply bg-multiselect-remove bg-center bg-no-repeat w-2.5 h-4 py-px box-content inline-block;\n  color: white;\n}\n\n.multiselect-spinner {\n  @apply bg-multiselect-spinner bg-center bg-no-repeat w-4 h-4 z-10 mr-3.5 animate-spin flex-shrink-0 flex-grow-0 rtl:mr-0 rtl:ml-3.5;\n}\n\n.multiselect-inifite {\n  @apply flex items-center justify-center w-full;\n}\n\n.multiselect-inifite-spinner {\n  @apply bg-multiselect-spinner bg-center bg-no-repeat w-4 h-4 z-10 animate-spin flex-shrink-0 flex-grow-0 m-3.5;\n}\n\n.multiselect-dropdown {\n  @apply max-h-60 absolute -left-px -right-px bottom-0 transform translate-y-full border border-gray-300 -mt-px overflow-y-scroll z-50 bg-white flex flex-col rounded-b;\n}\n.multiselect-dropdown.is-top {\n  @apply -translate-y-full top-px bottom-auto rounded-b-none rounded-t;\n}\n.multiselect-dropdown.is-hidden {\n  @apply hidden;\n}\n\n.multiselect-options {\n  @apply flex flex-col p-0 m-0 list-none;\n}\n\n.multiselect-group {\n  @apply p-0 m-0;\n}\n\n.multiselect-group-label {\n  @apply flex text-sm box-border items-center justify-start text-left py-1 px-3 font-semibold bg-gray-200 cursor-default leading-normal;\n}\n.multiselect-group-label.is-pointable {\n  @apply cursor-pointer;\n}\n.multiselect-group-label.is-pointed {\n  @apply bg-gray-300 text-gray-700;\n}\n.multiselect-group-label.is-selected {\n  @apply bg-green-600 text-white;\n}\n.multiselect-group-label.is-disabled {\n  @apply bg-gray-100 text-gray-300 cursor-not-allowed;\n}\n.multiselect-group-label.is-selected.is-pointed {\n  @apply bg-green-600 text-white opacity-90;\n}\n.multiselect-group-label.is-selected.is-disabled {\n  @apply text-green-100 bg-green-600 bg-opacity-50 cursor-not-allowed;\n}\n\n.multiselect-group-options {\n  @apply p-0 m-0;\n}\n\n.multiselect-option {\n  @apply flex items-center justify-start box-border text-left cursor-pointer text-base leading-snug py-2 px-3;\n}\n.multiselect-option.is-pointed {\n  @apply text-gray-800 bg-gray-100;\n}\n.multiselect-option.is-selected {\n  @apply text-white bg-green-500;\n}\n.multiselect-option.is-disabled {\n  @apply text-gray-300 cursor-not-allowed;\n}\n.multiselect-option.is-selected.is-pointed {\n  @apply text-white bg-green-500 opacity-90;\n}\n.multiselect-option.is-selected.is-disabled {\n  @apply text-green-100 bg-green-500 bg-opacity-50 cursor-not-allowed;\n}\n\n.multiselect-no-options {\n  @apply py-2 px-3 text-gray-600 bg-white text-left rtl:text-right;\n}\n\n.multiselect-no-results {\n  @apply py-2 px-3 text-gray-600 bg-white text-left rtl:text-right;\n}\n\n.multiselect-fake-input {\n  @apply bg-transparent absolute left-0 right-0 -bottom-px w-full h-px border-0 p-0 appearance-none outline-none text-transparent;\n}\n\n.multiselect-assistive-text {\n  @apply absolute -m-px w-px h-px overflow-hidden;\n  clip: rect(0 0 0 0);\n}\n\n.multiselect-spacer {\n  @apply h-9 py-px box-content;\n}\n"
  },
  {
    "path": "tailwind.config.js",
    "content": "/** @type {import('tailwindcss').Config} */\nconst svgToDataUri = require('mini-svg-data-uri')\nmodule.exports = {\n  relative: true,\n  content: ['./index.html', './src/**/*.vue'],\n  theme: {\n    extend: {\n      backgroundImage: (theme) => ({\n        'multiselect-caret': `url(\"${svgToDataUri(\n          `<svg viewBox=\"0 0 320 512\" fill=\"white\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M31.3 192h257.3c17.8 0 26.7 21.5 14.1 34.1L174.1 354.8c-7.8 7.8-20.5 7.8-28.3 0L17.2 226.1C4.6 213.5 13.5 192 31.3 192z\"></path></svg>`\n        )}\")`,\n        'multiselect-spinner': `url(\"${svgToDataUri(\n          `<svg viewBox=\"0 0 512 512\" fill=\"${theme(\n            'colors.green.500'\n          )}\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M456.433 371.72l-27.79-16.045c-7.192-4.152-10.052-13.136-6.487-20.636 25.82-54.328 23.566-118.602-6.768-171.03-30.265-52.529-84.802-86.621-144.76-91.424C262.35 71.922 256 64.953 256 56.649V24.56c0-9.31 7.916-16.609 17.204-15.96 81.795 5.717 156.412 51.902 197.611 123.408 41.301 71.385 43.99 159.096 8.042 232.792-4.082 8.369-14.361 11.575-22.424 6.92z\"></path></svg>`\n        )}\")`,\n        'multiselect-remove': `url(\"${svgToDataUri(\n          `<svg viewBox=\"0 0 320 512\" fill=\"white\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M207.6 256l107.72-107.72c6.23-6.23 6.23-16.34 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 0-22.58L207.6 256z\"></path></svg>`\n        )}\")`\n      })\n    }\n  },\n  plugins: [require('daisyui')],\n  daisyui: {\n    // themes: false, // true: all themes | false: only light + dark | array: specific themes like this [\"light\", \"dark\", \"cupcake\"]\n    darkTheme: 'worange', // name of one of the included themes for dark mode\n    base: true, // applies background color and foreground color for root element by default\n    styled: true, // include daisyUI colors and design decisions for all components\n    utils: true, // adds responsive and modifier utility classes\n    rtl: false, // rotate style direction from left-to-right to right-to-left. You also need to add dir=\"rtl\" to your html tag and install `tailwindcss-flip` plugin for Tailwind CSS.\n    prefix: '', // prefix for daisyUI classnames (components, modifiers and responsive class names. Not colors)\n    logs: true, // Shows info about daisyU\n    themes: [\n      {\n        worange: {\n          'color-scheme': 'dark',\n          'primary-content': '#131616',\n          secondary: '#6d3a9c',\n          accent: '#51a800',\n          'accent-content': '#000000',\n          neutral: '#2F1B05',\n          info: '#2563eb',\n          success: '#16a34a',\n          warning: '#d97706',\n          error: '#dc2626',\n          primary: '#ef8f4c',\n          'base-100': '#171717',\n          'base-200': '#252525',\n          'base-300': '#383838'\n        }\n      }\n    ]\n    // darkTheme: 'halloween',\n    // themes:[\n    //   {\n    //     halloween:{\n    //       ...require(\"daisyui/src/theming/themes\")[\"[data-theme=halloween]\"],\n    //       primary: '#ef8f4c',\n    //       'base-100': '#171717',\n    //       'base-200': '#252525',\n    //       'base-300': '#383838',\n    //     }\n    //   }\n    // ]\n  }\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"files\": [],\n  \"references\": [{ \"path\": \"./tsconfig.node.json\" }, { \"path\": \"./tsconfig.web.json\" }],\n  \"compilerOptions\": {\n    \"paths\": {\n      \"@/*\": [\n        \"./*\"\n      ]\n    }\n  }\n}\n"
  },
  {
    "path": "tsconfig.node.json",
    "content": "{\n  \"extends\": \"@electron-toolkit/tsconfig/tsconfig.node.json\",\n  \"include\": [\"electron.vite.config.*\", \"src/main/*\", \"src/main/pythontemplates/*\", \"src/preload/*\"],\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"types\": [\"electron-vite/node\"],\n    \"noUnusedLocals\": false,\n    \"noUnusedParameters\": false,\n  }\n}\n"
  },
  {
    "path": "tsconfig.web.json",
    "content": "{\n  \"extends\": \"@electron-toolkit/tsconfig/tsconfig.web.json\",\n  \"include\": [\n    \"src/renderer/src/env.d.ts\",\n    \"src/renderer/src/**/*\",\n    \"src/renderer/src/**/*.vue\",\n    \"src/preload/*.d.ts\"\n  ],\n  \"compilerOptions\": {\n    \"experimentalDecorators\": true,\n    \"composite\": true,\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"@renderer/*\": [\n        \"src/renderer/src/*\"\n      ]\n    }\n  }\n}\n"
  }
]