aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorNick Coutsos <[email protected]>2020-08-01 19:38:08 -0400
committerNick Coutsos <[email protected]>2020-08-01 19:38:08 -0400
commitf50c65e031cb52d0f37bb0280abdc6567412e56d (patch)
treebe5cd7010f363d7e7bec1edaa96326b4c9c060f2
downloadkeymap-editor-f50c65e031cb52d0f37bb0280abdc6567412e56d.tar.gz
keymap-editor-f50c65e031cb52d0f37bb0280abdc6567412e56d.zip
Initial commit
-rw-r--r--.gitignore80
-rw-r--r--data/keycodes.json264
-rw-r--r--data/keymap.json231
-rw-r--r--data/layers.json56
-rw-r--r--data/layout.json56
-rw-r--r--index.html25
-rw-r--r--keycodes.js41
-rw-r--r--keymap.js97
-rw-r--r--layers.js81
-rw-r--r--layout.js43
-rw-r--r--main.js75
-rw-r--r--search.js109
-rw-r--r--style.css139
13 files changed, 1297 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..6214c43
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,80 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# TypeScript v1 declaration files
+typings/
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variables file
+.env
+.env.test
+
+# parcel-bundler cache (https://parceljs.org/)
+.cache
+
+# next.js build output
+.next
+
+# nuxt.js build output
+.nuxt
+
+# vuepress build output
+.vuepress/dist
+
+# Serverless directories
+.serverless/
+
+# FuseBox cache
+.fusebox/
+
+# DynamoDB Local files
+.dynamodb/
diff --git a/data/keycodes.json b/data/keycodes.json
new file mode 100644
index 0000000..c401591
--- /dev/null
+++ b/data/keycodes.json
@@ -0,0 +1,264 @@
+[
+ { "code": "`KC_NO`", "aliases": ["`XXXXXXX`"], "description": "Ignore this key (NOOP)" },
+ { "code": "`KC_TRANSPARENT`", "aliases": ["`KC_TRNS`", "`_______`"], "description": "Use the next lowest non-transparent key", "symbol": "∅" },
+ { "code": "`KC_A`", "aliases": [ ], "description": "`a` and `A`" },
+ { "code": "`KC_B`", "aliases": [ ], "description": "`b` and `B`" },
+ { "code": "`KC_C`", "aliases": [ ], "description": "`c` and `C`" },
+ { "code": "`KC_D`", "aliases": [ ], "description": "`d` and `D`" },
+ { "code": "`KC_E`", "aliases": [ ], "description": "`e` and `E`" },
+ { "code": "`KC_F`", "aliases": [ ], "description": "`f` and `F`" },
+ { "code": "`KC_G`", "aliases": [ ], "description": "`g` and `G`" },
+ { "code": "`KC_H`", "aliases": [ ], "description": "`h` and `H`" },
+ { "code": "`KC_I`", "aliases": [ ], "description": "`i` and `I`" },
+ { "code": "`KC_J`", "aliases": [ ], "description": "`j` and `J`" },
+ { "code": "`KC_K`", "aliases": [ ], "description": "`k` and `K`" },
+ { "code": "`KC_L`", "aliases": [ ], "description": "`l` and `L`" },
+ { "code": "`KC_M`", "aliases": [ ], "description": "`m` and `M`" },
+ { "code": "`KC_N`", "aliases": [ ], "description": "`n` and `N`" },
+ { "code": "`KC_O`", "aliases": [ ], "description": "`o` and `O`" },
+ { "code": "`KC_P`", "aliases": [ ], "description": "`p` and `P`" },
+ { "code": "`KC_Q`", "aliases": [ ], "description": "`q` and `Q`" },
+ { "code": "`KC_R`", "aliases": [ ], "description": "`r` and `R`" },
+ { "code": "`KC_S`", "aliases": [ ], "description": "`s` and `S`" },
+ { "code": "`KC_T`", "aliases": [ ], "description": "`t` and `T`" },
+ { "code": "`KC_U`", "aliases": [ ], "description": "`u` and `U`" },
+ { "code": "`KC_V`", "aliases": [ ], "description": "`v` and `V`" },
+ { "code": "`KC_W`", "aliases": [ ], "description": "`w` and `W`" },
+ { "code": "`KC_X`", "aliases": [ ], "description": "`x` and `X`" },
+ { "code": "`KC_Y`", "aliases": [ ], "description": "`y` and `Y`" },
+ { "code": "`KC_Z`", "aliases": [ ], "description": "`z` and `Z`" },
+ { "code": "`KC_1`", "aliases": [ ], "description": "`1` and `!`" },
+ { "code": "`KC_2`", "aliases": [ ], "description": "`2` and `@`" },
+ { "code": "`KC_3`", "aliases": [ ], "description": "`3` and `#`" },
+ { "code": "`KC_4`", "aliases": [ ], "description": "`4` and `$`" },
+ { "code": "`KC_5`", "aliases": [ ], "description": "`5` and `%`" },
+ { "code": "`KC_6`", "aliases": [ ], "description": "`6` and `^`" },
+ { "code": "`KC_7`", "aliases": [ ], "description": "`7` and `&`" },
+ { "code": "`KC_8`", "aliases": [ ], "description": "`8` and `*`" },
+ { "code": "`KC_9`", "aliases": [ ], "description": "`9` and `(`" },
+ { "code": "`KC_0`", "aliases": [ ], "description": "`0` and `)`" },
+ { "code": "`KC_ENTER`", "aliases": ["`KC_ENT`"], "description": "Return (Enter)" },
+ { "code": "`KC_ESCAPE`", "aliases": ["`KC_ESC`"], "description": "Escape" },
+ { "code": "`KC_BSPACE`", "aliases": ["`KC_BSPC`"], "description": "Delete (Backspace)" },
+ { "code": "`KC_TAB`", "aliases": [ ], "description": "Tab" },
+ { "code": "`KC_SPACE`", "aliases": ["`KC_SPC`"], "description": "Spacebar" },
+ { "code": "`KC_MINUS`", "aliases": ["`KC_MINS`"], "description": "`-` and `_`", "symbol": "-" },
+ { "code": "`KC_EQUAL`", "aliases": ["`KC_EQL`"], "description": "`=` and `+`", "symbol": "+" },
+ { "code": "`KC_LBRACKET`", "aliases": ["`KC_LBRC`"], "description": "`[` and `{`", "symbol": "[" },
+ { "code": "`KC_RBRACKET`", "aliases": ["`KC_RBRC`"], "description": "`]` and `}`", "symbol": "]" },
+ { "code": "`KC_BSLASH`", "aliases": ["`KC_BSLS`"], "description": "`\\` and `|`", "symbol": "\\" },
+ { "code": "`KC_NONUS_HASH`", "aliases": ["`KC_NUHS`"], "description": "Non-US `#` and `~`", "symbol": "`" },
+ { "code": "`KC_SCOLON`", "aliases": ["`KC_SCLN`"], "description": "`;` and `:`", "symbol": ";" },
+ { "code": "`KC_QUOTE`", "aliases": ["`KC_QUOT`"], "description": "`'` and `\"`", "symbol": "'" },
+ { "code": "`KC_GRAVE`", "aliases": ["`KC_GRV`", "`KC_ZKHK`"], "description": "<code>&#96;</code> and `~`, JIS Zenkaku/Hankaku", "symbol": "~" },
+ { "code": "`KC_COMMA`", "aliases": ["`KC_COMM`"], "description": "`,` and `<`", "symbol": "," },
+ { "code": "`KC_DOT`", "aliases": [ ], "description": "`.` and `>`", "symbol": "." },
+ { "code": "`KC_SLASH`", "aliases": ["`KC_SLSH`"], "description": "`/` and `?`", "symbol": "/" },
+ { "code": "`KC_CAPSLOCK`", "aliases": ["`KC_CLCK`", "`KC_CAPS`"], "description": "Caps Lock" },
+ { "code": "`KC_F1`", "aliases": [ ], "description": "F1" },
+ { "code": "`KC_F2`", "aliases": [ ], "description": "F2" },
+ { "code": "`KC_F3`", "aliases": [ ], "description": "F3" },
+ { "code": "`KC_F4`", "aliases": [ ], "description": "F4" },
+ { "code": "`KC_F5`", "aliases": [ ], "description": "F5" },
+ { "code": "`KC_F6`", "aliases": [ ], "description": "F6" },
+ { "code": "`KC_F7`", "aliases": [ ], "description": "F7" },
+ { "code": "`KC_F8`", "aliases": [ ], "description": "F8" },
+ { "code": "`KC_F9`", "aliases": [ ], "description": "F9" },
+ { "code": "`KC_F10`", "aliases": [ ], "description": "F10" },
+ { "code": "`KC_F11`", "aliases": [ ], "description": "F11" },
+ { "code": "`KC_F12`", "aliases": [ ], "description": "F12" },
+ { "code": "`KC_PSCREEN`", "aliases": ["`KC_PSCR`"], "description": "Print Screen" },
+ { "code": "`KC_SCROLLLOCK`", "aliases": ["`KC_SLCK`", "`KC_BRMD`"], "description": "Scroll Lock, Brightness Down (macOS)" },
+ { "code": "`KC_PAUSE`", "aliases": ["`KC_PAUS`", "`KC_BRK`", "`KC_BRMU`"], "description": "Pause, Brightness Up (macOS)" },
+ { "code": "`KC_INSERT`", "aliases": ["`KC_INS`"], "description": "Insert" },
+ { "code": "`KC_HOME`", "aliases": [ ], "description": "Home" },
+ { "code": "`KC_PGUP`", "aliases": [ ], "description": "Page Up" },
+ { "code": "`KC_DELETE`", "aliases": ["`KC_DEL`"], "description": "Forward Delete" },
+ { "code": "`KC_END`", "aliases": [ ], "description": "End" },
+ { "code": "`KC_PGDOWN`", "aliases": ["`KC_PGDN`"], "description": "Page Down", "symbol": "PgDn" },
+ { "code": "`KC_RIGHT`", "aliases": ["`KC_RGHT`"], "description": "Right Arrow", "symbol": "►" },
+ { "code": "`KC_LEFT`", "aliases": [ ], "description": "Left Arrow", "symbol": "◀︎" },
+ { "code": "`KC_DOWN`", "aliases": [ ], "description": "Down Arrow", "symbol": "▼" },
+ { "code": "`KC_UP`", "aliases": [ ], "description": "Up Arrow", "symbol": "▲" },
+ { "code": "`KC_NUMLOCK`", "aliases": ["`KC_NLCK`"], "description": "Keypad Num Lock and Clear" },
+ { "code": "`KC_KP_SLASH`", "aliases": ["`KC_PSLS`"], "description": "Keypad `/`", "symbol": "/" },
+ { "code": "`KC_KP_ASTERISK`", "aliases": ["`KC_PAST`"], "description": "Keypad `*`" },
+ { "code": "`KC_KP_MINUS`", "aliases": ["`KC_PMNS`"], "description": "Keypad `-`" },
+ { "code": "`KC_KP_PLUS`", "aliases": ["`KC_PPLS`"], "description": "Keypad `+`" },
+ { "code": "`KC_KP_ENTER`", "aliases": ["`KC_PENT`"], "description": "Keypad Enter" },
+ { "code": "`KC_KP_1`", "aliases": ["`KC_P1`"], "description": "Keypad `1` and End" },
+ { "code": "`KC_KP_2`", "aliases": ["`KC_P2`"], "description": "Keypad `2` and Down Arrow" },
+ { "code": "`KC_KP_3`", "aliases": ["`KC_P3`"], "description": "Keypad `3` and Page Down" },
+ { "code": "`KC_KP_4`", "aliases": ["`KC_P4`"], "description": "Keypad `4` and Left Arrow" },
+ { "code": "`KC_KP_5`", "aliases": ["`KC_P5`"], "description": "Keypad `5`" },
+ { "code": "`KC_KP_6`", "aliases": ["`KC_P6`"], "description": "Keypad `6` and Right Arrow" },
+ { "code": "`KC_KP_7`", "aliases": ["`KC_P7`"], "description": "Keypad `7` and Home" },
+ { "code": "`KC_KP_8`", "aliases": ["`KC_P8`"], "description": "Keypad `8` and Up Arrow" },
+ { "code": "`KC_KP_9`", "aliases": ["`KC_P9`"], "description": "Keypad `9` and Page Up" },
+ { "code": "`KC_KP_0`", "aliases": ["`KC_P0`"], "description": "Keypad `0` and Insert" },
+ { "code": "`KC_KP_DOT`", "aliases": ["`KC_PDOT`"], "description": "Keypad `.` and Delete" },
+ { "code": "`KC_NONUS_BSLASH`", "aliases": ["`KC_NUBS`"], "description": "Non-US `\\` and `|`" },
+ { "code": "`KC_APPLICATION`", "aliases": ["`KC_APP`"], "description": "Application (Windows Context Menu Key)" },
+ { "code": "`KC_POWER`", "aliases": [ ], "description": "System Power" },
+ { "code": "`KC_KP_EQUAL`", "aliases": ["`KC_PEQL`"], "description": "Keypad `=`", "symbol": "=" },
+ { "code": "`KC_F13`", "aliases": [ ], "description": "F13" },
+ { "code": "`KC_F14`", "aliases": [ ], "description": "F14" },
+ { "code": "`KC_F15`", "aliases": [ ], "description": "F15" },
+ { "code": "`KC_F16`", "aliases": [ ], "description": "F16" },
+ { "code": "`KC_F17`", "aliases": [ ], "description": "F17" },
+ { "code": "`KC_F18`", "aliases": [ ], "description": "F18" },
+ { "code": "`KC_F19`", "aliases": [ ], "description": "F19" },
+ { "code": "`KC_F20`", "aliases": [ ], "description": "F20" },
+ { "code": "`KC_F21`", "aliases": [ ], "description": "F21" },
+ { "code": "`KC_F22`", "aliases": [ ], "description": "F22" },
+ { "code": "`KC_F23`", "aliases": [ ], "description": "F23" },
+ { "code": "`KC_F24`", "aliases": [ ], "description": "F24" },
+ { "code": "`KC_EXECUTE`", "aliases": ["`KC_EXEC`"], "description": "Execute" },
+ { "code": "`KC_HELP`", "aliases": [ ], "description": "Help" },
+ { "code": "`KC_MENU`", "aliases": [ ], "description": "Menu" },
+ { "code": "`KC_SELECT`", "aliases": ["`KC_SLCT`"], "description": "Select" },
+ { "code": "`KC_STOP`", "aliases": [ ], "description": "Stop" },
+ { "code": "`KC_AGAIN`", "aliases": ["`KC_AGIN`"], "description": "Again" },
+ { "code": "`KC_UNDO`", "aliases": [ ], "description": "Undo" },
+ { "code": "`KC_CUT`", "aliases": [ ], "description": "Cut" },
+ { "code": "`KC_COPY`", "aliases": [ ], "description": "Copy" },
+ { "code": "`KC_PASTE`", "aliases": ["`KC_PSTE`"], "description": "Paste" },
+ { "code": "`KC_FIND`", "aliases": [ ], "description": "Find" },
+ { "code": "`KC__MUTE`", "aliases": [ ], "description": "Mute" },
+ { "code": "`KC__VOLUP`", "aliases": [ ], "description": "Volume Up" },
+ { "code": "`KC__VOLDOWN`", "aliases": [ ], "description": "Volume Down" },
+ { "code": "`KC_LOCKING_CAPS`", "aliases": ["`KC_LCAP`"], "description": "Locking Caps Lock" },
+ { "code": "`KC_LOCKING_NUM`", "aliases": ["`KC_LNUM`"], "description": "Locking Num Lock" },
+ { "code": "`KC_LOCKING_SCROLL`", "aliases": ["`KC_LSCR`"], "description": "Locking Scroll Lock" },
+ { "code": "`KC_KP_COMMA`", "aliases": ["`KC_PCMM`"], "description": "Keypad `,`" },
+ { "code": "`KC_KP_EQUAL_AS400`", "aliases": [ ], "description": "Keypad `=` on AS/400 keyboards" },
+ { "code": "`KC_INT1`", "aliases": ["`KC_RO`"], "description": "JIS `\\` and `_`" },
+ { "code": "`KC_INT2`", "aliases": ["`KC_KANA`"], "description": "JIS Katakana/Hiragana" },
+ { "code": "`KC_INT3`", "aliases": ["`KC_JYEN`"], "description": "JIS `¥` and `|`" },
+ { "code": "`KC_INT4`", "aliases": ["`KC_HENK`"], "description": "JIS Henkan" },
+ { "code": "`KC_INT5`", "aliases": ["`KC_MHEN`"], "description": "JIS Muhenkan" },
+ { "code": "`KC_INT6`", "aliases": [ ], "description": "JIS Numpad `,`" },
+ { "code": "`KC_INT7`", "aliases": [ ], "description": "International 7" },
+ { "code": "`KC_INT8`", "aliases": [ ], "description": "International 8" },
+ { "code": "`KC_INT9`", "aliases": [ ], "description": "International 9" },
+ { "code": "`KC_LANG1`", "aliases": ["`KC_HAEN`"], "description": "Hangul/English" },
+ { "code": "`KC_LANG2`", "aliases": ["`KC_HANJ`"], "description": "Hanja" },
+ { "code": "`KC_LANG3`", "aliases": [ ], "description": "JIS Katakana" },
+ { "code": "`KC_LANG4`", "aliases": [ ], "description": "JIS Hiragana" },
+ { "code": "`KC_LANG5`", "aliases": [ ], "description": "JIS Zenkaku/Hankaku" },
+ { "code": "`KC_LANG6`", "aliases": [ ], "description": "Language 6" },
+ { "code": "`KC_LANG7`", "aliases": [ ], "description": "Language 7" },
+ { "code": "`KC_LANG8`", "aliases": [ ], "description": "Language 8" },
+ { "code": "`KC_LANG9`", "aliases": [ ], "description": "Language 9" },
+ { "code": "`KC_ALT_ERASE`", "aliases": ["`KC_ERAS`"], "description": "Alternate Erase" },
+ { "code": "`KC_SYSREQ`", "aliases": [ ], "description": "SysReq/Attention" },
+ { "code": "`KC_CANCEL`", "aliases": [ ], "description": "Cancel" },
+ { "code": "`KC_CLEAR`", "aliases": ["`KC_CLR`"], "description": "Clear" },
+ { "code": "`KC_PRIOR`", "aliases": [ ], "description": "Prior" },
+ { "code": "`KC_RETURN`", "aliases": [ ], "description": "Return" },
+ { "code": "`KC_SEPARATOR`", "aliases": [ ], "description": "Separator" },
+ { "code": "`KC_OUT`", "aliases": [ ], "description": "Out" },
+ { "code": "`KC_OPER`", "aliases": [ ], "description": "Oper" },
+ { "code": "`KC_CLEAR_AGAIN`", "aliases": [ ], "description": "Clear/Again" },
+ { "code": "`KC_CRSEL`", "aliases": [ ], "description": "CrSel/Props" },
+ { "code": "`KC_EXSEL`", "aliases": [ ], "description": "ExSel" },
+ { "code": "`KC_LCTRL`", "aliases": ["`KC_LCTL`"], "description": "Left Control", "isModifier": true },
+ { "code": "`KC_LSHIFT`", "aliases": ["`KC_LSFT`"], "description": "Left Shift", "isModifier": true },
+ { "code": "`KC_LALT`", "aliases": ["`KC_LOPT`"], "description": "Left Alt (Option)", "symbol": "⌥", "isModifier": true },
+ { "code": "`KC_LGUI`", "aliases": ["`KC_LCMD`", "`KC_LWIN`"], "description": "Left GUI (Windows/Command/Meta key)", "symbol": "⌘", "isModifier": true },
+ { "code": "`KC_RCTRL`", "aliases": ["`KC_RCTL`"], "description": "Right Control", "isModifier": true },
+ { "code": "`KC_RSHIFT`", "aliases": ["`KC_RSFT`"], "description": "Right Shift", "isModifier": true },
+ { "code": "`KC_RALT`", "aliases": ["`KC_ROPT`", "`KC_ALGR`"], "description": "Right Alt (Option/AltGr)", "symbol": "⌥", "isModifier": true },
+ { "code": "`KC_RGUI`", "aliases": ["`KC_RCMD`", "`KC_RWIN`"], "description": "Right GUI (Windows/Command/Meta key)", "symbol": "⌘", "isModifier": true },
+ { "code": "`KC_SYSTEM_POWER`", "aliases": ["`KC_PWR`"], "description": "System Power Down" },
+ { "code": "`KC_SYSTEM_SLEEP`", "aliases": ["`KC_SLEP`"], "description": "System Sleep" },
+ { "code": "`KC_SYSTEM_WAKE`", "aliases": ["`KC_WAKE`"], "description": "System Wake" },
+ { "code": "`KC_AUDIO_MUTE`", "aliases": ["`KC_MUTE`"], "description": "Mute" },
+ { "code": "`KC_AUDIO_VOL_UP`", "aliases": ["`KC_VOLU`"], "description": "Volume Up" },
+ { "code": "`KC_AUDIO_VOL_DOWN`", "aliases": ["`KC_VOLD`"], "description": "Volume Down" },
+ { "code": "`KC_MEDIA_NEXT_TRACK`", "aliases": ["`KC_MNXT`"], "description": "Next Track" },
+ { "code": "`KC_MEDIA_PREV_TRACK`", "aliases": ["`KC_MPRV`"], "description": "Previous Track" },
+ { "code": "`KC_MEDIA_STOP`", "aliases": ["`KC_MSTP`"], "description": "Stop Track" },
+ { "code": "`KC_MEDIA_PLAY_PAUSE`", "aliases": ["`KC_MPLY`"], "description": "Play/Pause Track" },
+ { "code": "`KC_MEDIA_SELECT`", "aliases": ["`KC_MSEL`"], "description": "Launch Media Player" },
+ { "code": "`KC_MEDIA_EJECT`", "aliases": ["`KC_EJCT`"], "description": "Eject" },
+ { "code": "`KC_MAIL`", "aliases": [ ], "description": "Launch Mail" },
+ { "code": "`KC_CALCULATOR`", "aliases": ["`KC_CALC`"], "description": "Launch Calculator" },
+ { "code": "`KC_MY_COMPUTER`", "aliases": ["`KC_MYCM`"], "description": "Launch My Computer" },
+ { "code": "`KC_WWW_SEARCH`", "aliases": ["`KC_WSCH`"], "description": "Browser Search" },
+ { "code": "`KC_WWW_HOME`", "aliases": ["`KC_WHOM`"], "description": "Browser Home" },
+ { "code": "`KC_WWW_BACK`", "aliases": ["`KC_WBAK`"], "description": "Browser Back" },
+ { "code": "`KC_WWW_FORWARD`", "aliases": ["`KC_WFWD`"], "description": "Browser Forward" },
+ { "code": "`KC_WWW_STOP`", "aliases": ["`KC_WSTP`"], "description": "Browser Stop" },
+ { "code": "`KC_WWW_REFRESH`", "aliases": ["`KC_WREF`"], "description": "Browser Refresh" },
+ { "code": "`KC_WWW_FAVORITES`", "aliases": ["`KC_WFAV`"], "description": "Browser Favorites" },
+ { "code": "`KC_MEDIA_FAST_FORWARD`", "aliases": ["`KC_MFFD`"] , "description": "Next Track" },
+ { "code": "`KC_MEDIA_REWIND`", "aliases": ["`KC_MRWD`"], "description": "Previous Track" },
+ { "code": "`KC_BRIGHTNESS_UP`", "aliases": ["`KC_BRIU`"], "description": "Brightness Up" },
+ { "code": "`KC_BRIGHTNESS_DOWN`", "aliases": ["`KC_BRID`"], "description": "Brightness Down" },
+
+ { "code": "`RESET`", "aliases": [], "description": "Put the keyboard into bootloader mode for flashing" },
+ { "code": "`DEBUG`", "aliases": [], "description": "Toggle debug mode" },
+ { "code": "`EEPROM_RESET`", "aliases": ["`EEP_RST`"], "description": "Reinitializes the keyboard's EEPROM (persistent memory)" },
+
+ { "code": "`DF(layer)`", "description": "Set the base (default) layer" },
+ { "code": "`MO(layer)`", "description": "Momentarily turn on `layer` when pressed (requires `KC_TRNS` on destination layer" },
+ { "code": "`OSL(layer)`", "description": "Momentarily activates `layer` until a key is pressed. See [One Shot Keys](one_shot_keys.md) for details." },
+ { "code": "`LM(layer, mod)`", "description": "Momentarily turn on `layer` (like MO) with `mod` active as well. Where `mod` is a mods_bit. Mods can be viewed [here](mod_tap.md). Example Implementation: `LM(LAYER_1, MOD_LALT)`" },
+ { "code": "`LT(layer, kc)`", "description": "Turn on `layer` when held, `kc` when tapped" },
+ { "code": "`TG(layer)`", "description": "Toggle `layer` on or off" },
+ { "code": "`TO(layer)`", "description": "Turns on `layer` and turns off all other layers, except the default layer" },
+ { "code": "`TT(layer)`", "description": "Normally acts like MO unless it's tapped multiple times, which toggles `layer` on" },
+
+ { "code": "`LCTL(kc)`", "aliases": [], "description": "Hold Left Control and press `kc`" },
+ { "code": "`LSFT(kc)`", "aliases": [], "description": "Hold Left Shift and press `kc`", "symbol": "⇧" },
+ { "code": "`LALT(kc)`", "aliases": ["`LOPT(kc)`"], "description": "Hold Left Alt and press `kc`" },
+ { "code": "`LGUI(kc)`", "aliases": ["`LCMD(kc)`", "`LWIN(kc)`"], "description": "Hold Left GUI and press `kc`", "symbol": "⌘" },
+ { "code": "`RCTL(kc)`", "description": "Hold Right Control and press `kc`" },
+ { "code": "`RSFT(kc)`", "description": "Hold Right Shift and press `kc`", "symbol": "⇧" },
+ { "code": "`RALT(kc)`", "aliases": ["`ROPT(kc)`", "`ALGR(kc)`"], "description": "Hold Right Alt and press `kc`" },
+ { "code": "`RGUI(kc)`", "aliases": ["`RCMD(kc)`", "`LWIN(kc)`"], "description": "Hold Right GUI and press `kc`", "symbol": "⌘" },
+ { "code": "`SGUI(kc)`", "aliases": ["`SCMD(kc)`", "`SWIN(kc)`"], "description": "Hold Left Shift and GUI and press `kc`" },
+ { "code": "`LCA(kc)`", "description": "Hold Left Control and Alt and press `kc`" },
+ { "code": "`LCAG(kc)`", "description": "Hold Left Control, Alt and GUI and press `kc`" },
+ { "code": "`MEH(kc)`", "description": "Hold Left Control, Shift and Alt and press `kc`" },
+ { "code": "`HYPR(kc)`", "description": "Hold Left Control, Shift, Alt and GUI and press `kc`" },
+ { "code": "`KC_MEH`", "description": "Left Control, Shift and Alt" },
+ { "code": "`KC_HYPR`", "description": "Left Control, Shift, Alt and GUI" },
+
+ { "code": "`MT(mod, kc)`", "description": "`mod` when held, `kc` when tapped" },
+ { "code": "`LCTL_T(kc)`", "aliases": ["`CTL_T(kc)`"], "description": "Left Control when held, `kc` when tapped" },
+ { "code": "`LSFT_T(kc)`", "aliases": ["`SFT_T(kc)`"], "description": "Left Shift when held, `kc` when tapped" },
+ { "code": "`LALT_T(kc)`", "aliases": ["`LOPT_T(kc)`", "`ALT_T(kc)`", "`OPT_T(kc)`"], "description": "Left Alt when held, `kc` when tapped" },
+ { "code": "`LGUI_T(kc)`", "aliases": ["`LCMD_T(kc)`", "`LWIN_T(kc)`", "`GUI_T(kc)`", "`CMD_T(kc)`", "`WIN_T(kc)`"], "description": "Left GUI when held, `kc` when tapped" },
+ { "code": "`RCTL_T(kc)`", "description": "Right Control when held, `kc` when tapped" },
+ { "code": "`RSFT_T(kc)`", "description": "Right Shift when held, `kc` when tapped" },
+ { "code": "`RALT_T(kc)`", "aliases": ["`ROPT_T(kc)`", "`ALGR_T(kc)`"], "description": "Right Alt when held, `kc` when tapped" },
+ { "code": "`RGUI_T(kc)`", "aliases": ["`RCMD_T(kc)`", "`RWIN_T(kc)`"], "description": "Right GUI when held, `kc` when tapped" },
+ { "code": "`SGUI_T(kc)`", "aliases": ["`SCMD_T(kc)`", "`SWIN_T(kc)`"], "description": "Left Shift and GUI when held, `kc` when tapped" },
+ { "code": "`LCA_T(kc)`", "description": "Left Control and Alt when held, `kc` when tapped" },
+ { "code": "`LCAG_T(kc)`", "description": "Left Control, Alt and GUI when held, `kc` when tapped" },
+ { "code": "`RCAG_T(kc)`", "description": "Right Control, Alt and GUI when held, `kc` when tapped" },
+ { "code": "`C_S_T(kc)`", "description": "Left Control and Shift when held, `kc` when tapped" },
+ { "code": "`MEH_T(kc)`", "description": "Left Control, Shift and Alt when held, `kc` when tapped" },
+ { "code": "`HYPR_T(kc)`", "aliases": ["`ALL_T(kc)`"], "description": "Left Control, Shift, Alt and GUI when held, `kc` when tapped - more info [here](http://brettterpstra.com/2012/12/08/a-useful-caps-lock-key/)" },
+
+ { "code": "`RGB_TOG`", "description": "Toggle RGB lighting on or off" },
+ { "code": "`RGB_MODE_FORWARD`", "aliases": ["`RGB_MOD`"], "description": "Cycle through modes, reverse direction when Shift is held" },
+ { "code": "`RGB_MODE_REVERSE`", "aliases": ["`RGB_RMOD`"], "description": "Cycle through modes in reverse, forward direction when Shift is held" },
+ { "code": "`RGB_HUI`", "description": "Increase hue, decrease hue when Shift is held" },
+ { "code": "`RGB_HUD`", "description": "Decrease hue, increase hue when Shift is held" },
+ { "code": "`RGB_SAI`", "description": "Increase saturation, decrease saturation when Shift is held" },
+ { "code": "`RGB_SAD`", "description": "Decrease saturation, increase saturation when Shift is held" },
+ { "code": "`RGB_VAI`", "description": "Increase value (brightness), decrease value when Shift is held" },
+ { "code": "`RGB_VAD`", "description": "Decrease value (brightness), increase value when Shift is held" },
+ { "code": "`RGB_SPI`", "description": "Increase effect speed (does not support eeprom yet), decrease speed when Shift is held" },
+ { "code": "`RGB_SPD`", "description": "Decrease effect speed (does not support eeprom yet), increase speed when Shift is held" },
+ { "code": "`KC_LCPO`", "description": "Left Control when held, `(` when tapped" },
+ { "code": "`KC_RCPC`", "description": "Right Control when held, `)` when tapped" },
+ { "code": "`KC_LSPO`", "description": "Left Shift when held, `(` when tapped", "symbol": "⇧/(" },
+ { "code": "`KC_RSPC`", "description": "Right Shift when held, `)` when tapped", "symbol": "⇧/)" },
+ { "code": "`KC_LAPO`", "description": "Left Alt when held, `(` when tapped" },
+ { "code": "`KC_RAPC`", "description": "Right Alt when held, `)` when tapped" },
+ { "code": "`KC_SFTENT`", "description": "Right Shift when held, Enter when tapped" }
+]
diff --git a/data/keymap.json b/data/keymap.json
new file mode 100644
index 0000000..c8678a6
--- /dev/null
+++ b/data/keymap.json
@@ -0,0 +1,231 @@
+{
+ "keyboard": "handwired/dactyl_reduced",
+ "keymap": "default",
+ "layout": "LAYOUT",
+ "layers": [
+ [
+ "KC_TAB",
+ "KC_Q",
+ "KC_W",
+ "KC_E",
+ "KC_R",
+ "KC_T",
+ "KC_Y",
+ "KC_U",
+ "KC_I",
+ "KC_O",
+ "KC_P",
+ "KC_BSLS",
+ "KC_CAPS",
+ "KC_A",
+ "KC_S",
+ "KC_D",
+ "KC_F",
+ "KC_G",
+ "KC_H",
+ "KC_J",
+ "KC_K",
+ "KC_L",
+ "LT(2,KC_SCLN)",
+ "KC_QUOT",
+ "KC_LSPO",
+ "KC_Z",
+ "KC_X",
+ "KC_C",
+ "KC_V",
+ "KC_B",
+ "KC_N",
+ "KC_M",
+ "KC_COMM",
+ "KC_DOT",
+ "KC_SLSH",
+ "KC_RSPC",
+ "KC_LCTL",
+ "KC_LEFT",
+ "KC_RGHT",
+ "KC_DOWN",
+ "KC_UP",
+ "KC_LBRC",
+ "KC_LCTL",
+ "KC_BSPC",
+ "KC_RALT",
+ "KC_ESC",
+ "KC_LALT",
+ "KC_LGUI",
+ "MO(1)",
+ "KC_PGUP",
+ "KC_ENT",
+ "KC_SPC",
+ "MO(2)",
+ "KC_PGDN"
+ ],
+ [
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_GRAVE",
+ "KC_MINS",
+ "KC_EQL",
+ "KC_TRNS",
+ "KC_LBRC",
+ "KC_7",
+ "KC_8",
+ "KC_9",
+ "KC_RBRC",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_KP_PLUS",
+ "KC_4",
+ "KC_5",
+ "KC_6",
+ "KC_KP_ASTERISK",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_KP_MINUS",
+ "KC_1",
+ "KC_2",
+ "KC_3",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_0",
+ "KC_DOT",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_LCMD",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_LSHIFT",
+ "KC_TRNS"
+ ],
+ [
+ "RESET",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "LCMD(KC_LBRC)",
+ "LCMD(LSFT(KC_LBRC))",
+ "LCMD(LSFT(KC_RBRC))",
+ "LCMD(KC_RBRC)",
+ "KC_TRNS",
+ "KC_TRNS",
+ "RGB_MODE_FORWARD",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_F8",
+ "KC_F9",
+ "KC_F7",
+ "LCMD(LALT(KC_GRAVE))",
+ "KC_TRNS",
+ "KC_TRNS",
+ "RGB_HUI",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC__VOLDOWN",
+ "KC__VOLUP",
+ "KC__MUTE",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS"
+ ],
+ [
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_F7",
+ "KC_F8",
+ "KC_F9",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_F4",
+ "KC_F5",
+ "KC_F6",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_F1",
+ "KC_F2",
+ "KC_F3",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS",
+ "KC_TRNS"
+ ]
+ ]
+} \ No newline at end of file
diff --git a/data/layers.json b/data/layers.json
new file mode 100644
index 0000000..3edf07b
--- /dev/null
+++ b/data/layers.json
@@ -0,0 +1,56 @@
+[
+ { "code": "KC_TAB" },
+ { "code": "KC_Q" },
+ { "code": "KC_W" },
+ { "code": "KC_E" },
+ { "code": "KC_R" },
+ { "code": "KC_T" },
+ { "code": "KC_Y" },
+ { "code": "KC_U" },
+ { "code": "KC_I" },
+ { "code": "KC_O" },
+ { "code": "KC_P" },
+ { "code": "KC_BSLS" },
+ { "code": "KC_CAPS" },
+ { "code": "KC_A" },
+ { "code": "KC_S" },
+ { "code": "KC_D" },
+ { "code": "KC_F" },
+ { "code": "KC_G" },
+ { "code": "KC_H" },
+ { "code": "KC_J" },
+ { "code": "KC_K" },
+ { "code": "KC_L" },
+ { "code": "LT(2, KC_SCLN)" },
+ { "code": "KC_QUOT" },
+ { "code": "KC_LSPO" },
+ { "code": "KC_Z" },
+ { "code": "KC_X" },
+ { "code": "KC_C" },
+ { "code": "KC_V" },
+ { "code": "KC_B" },
+ { "code": "KC_N" },
+ { "code": "KC_M" },
+ { "code": "KC_COMM" },
+ { "code": "KC_DOT" },
+ { "code": "KC_SLSH" },
+ { "code": "KC_RSPC" },
+ { "code": "KC_LCTL" },
+ { "code": "KC_LEFT" },
+ { "code": "KC_RGHT" },
+ { "code": "KC_DOWN" },
+ { "code": "KC_UP" },
+ { "code": "KC_LBRC" },
+ { "code": "KC_LCTL" },
+ { "code": "KC_BSPC" },
+ { "code": "KC_RALT" },
+ { "code": "KC_ESC" },
+ { "code": "KC_LALT" },
+ { "code": "KC_LGUI" },
+ { "code": "MO(1)" },
+ { "code": "KC_PGUP" },
+ { "code": "KC_ENT" },
+ { "code": "KC_SPC" },
+ { "code": "MO(2)" },
+ { "code": "KC_PGDN" }
+]
diff --git a/data/layout.json b/data/layout.json
new file mode 100644
index 0000000..8e44978
--- /dev/null
+++ b/data/layout.json
@@ -0,0 +1,56 @@
+[
+ { "label": "L00", "x": 0, "y": 0.5 },
+ { "label": "L01", "x": 1, "y": 0.5 },
+ { "label": "L02", "x": 2, "y": 0.2 },
+ { "label": "L03", "x": 3, "y": 0 },
+ { "label": "L04", "x": 4, "y": 0.25 },
+ { "label": "L05", "x": 5, "y": 0.25 },
+ { "label": "R06", "x": 13, "y": 0.25 },
+ { "label": "R07", "x": 14, "y": 0.25 },
+ { "label": "R08", "x": 15, "y": 0 },
+ { "label": "R09", "x": 16, "y": 0.2 },
+ { "label": "R0A", "x": 17, "y": 0.5 },
+ { "label": "R0B", "x": 18, "y": 0.5 },
+ { "label": "L10", "x": 0, "y": 1.5 },
+ { "label": "L11", "x": 1, "y": 1.5 },
+ { "label": "L12", "x": 2, "y": 1.2 },
+ { "label": "L13", "x": 3, "y": 1 },
+ { "label": "L14", "x": 4, "y": 1.25 },
+ { "label": "L15", "x": 5, "y": 1.25 },
+ { "label": "R16", "x": 13, "y": 1.25 },
+ { "label": "R17", "x": 14, "y": 1.25 },
+ { "label": "R18", "x": 15, "y": 1 },
+ { "label": "R19", "x": 16, "y": 1.2 },
+ { "label": "R1A", "x": 17, "y": 1.5 },
+ { "label": "R1B", "x": 18, "y": 1.5 },
+ { "label": "L20", "x": 0, "y": 2.5 },
+ { "label": "L21", "x": 1, "y": 2.5 },
+ { "label": "L22", "x": 2, "y": 2.2 },
+ { "label": "L23", "x": 3, "y": 2 },
+ { "label": "L24", "x": 4, "y": 2.25 },
+ { "label": "L25", "x": 5, "y": 2.25 },
+ { "label": "R26", "x": 13, "y": 2.25 },
+ { "label": "R27", "x": 14, "y": 2.25 },
+ { "label": "R28", "x": 15, "y": 2 },
+ { "label": "R29", "x": 16, "y": 2.2 },
+ { "label": "R2A", "x": 17, "y": 2.5 },
+ { "label": "R2B", "x": 18, "y": 2.5 },
+ { "label": "L32", "x": 2, "y": 3.2 },
+ { "label": "L33", "x": 3, "y": 3 },
+ { "label": "L34", "x": 4, "y": 3.25 },
+ { "label": "R37", "x": 14, "y": 3.25 },
+ { "label": "R38", "x": 15, "y": 3 },
+ { "label": "R39", "x": 16, "y": 3.2 },
+ { "label": "L42", "x": 6.5, "y": 2, "r": 20, "rx": 4.5, "ry": 3 },
+ { "label": "L43", "x": 7.5, "y": 2, "r": 20, "rx": 4.5, "ry": 3 },
+ { "label": "R48", "x": 10.5, "y": 2, "r": -20, "rx": 14.5, "ry": 3 },
+ { "label": "R49", "x": 11.5, "y": 2, "r": -20, "rx": 14.5, "ry": 3 },
+ { "label": "L40", "x": 5.5, "y": 3, "r": 20, "rx": 4.5, "ry": 3, "h": 2 },
+ { "label": "L41", "x": 6.5, "y": 3, "r": 20, "rx": 4.5, "ry": 3, "h": 2 },
+ { "label": "L44", "x": 7.5, "y": 3, "r": 20, "rx": 4.5, "ry": 3 },
+ { "label": "R47", "x": 10.5, "y": 3, "r": -20, "rx": 14.5, "ry": 3 },
+ { "label": "R4A", "x": 11.5, "y": 3, "r": -20, "rx": 14.5, "ry": 3, "h": 2 },
+ { "label": "R4B", "x": 12.5, "y": 3, "r": -20, "rx": 14.5, "ry": 3, "h": 2 },
+ { "label": "L45", "x": 7.5, "y": 4, "r": 20, "rx": 4.5, "ry": 3 },
+ { "label": "R46", "x": 10.5, "y": 4, "r": -20, "rx": 14.5, "ry": 3 }
+]
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..193a470
--- /dev/null
+++ b/index.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<html>
+<head>
+ <meta charset="utf-8" />
+ <link rel="stylesheet" href="style.css">
+</head>
+<body>
+ <div id="layer-selector">
+ <p>Layers:</p>
+ <ul></ul>
+ <button>Add layer</button>
+ </div>
+ <div id="layers"></div>
+ <div id="search" style="display: none">
+ <p></p>
+ <input type="text" />
+ <ul></ul>
+ </div>
+
+ <button id="export">Export</button>
+
+ <script src="https://rawgit.com/farzher/fuzzysort/master/fuzzysort.js"></script>
+ <script type="module" src="main.js"></script>
+</body>
+</html>
diff --git a/keycodes.js b/keycodes.js
new file mode 100644
index 0000000..f141c36
--- /dev/null
+++ b/keycodes.js
@@ -0,0 +1,41 @@
+
+export function loadKeycodes () {
+ return fetch('data/keycodes.json')
+ .then(response => response.json())
+ .then(normalizeKeycodes)
+}
+
+export async function loadIndexedKeycodes() {
+ return loadKeycodes()
+ .then(keycodes => (
+ keycodes.reduce((map, keycode) => Object.assign(map, { [keycode.code]: keycode }), {})
+ ))
+}
+
+function normalizeKeycodes (keycodes) {
+ return keycodes.map(keycode => {
+ keycode.params = (keycode.code.match(/\((.+?)\)/) || ['', ''])[1]
+ .split(',')
+ .map(t => t.trim())
+ .filter(t => !!t)
+
+ keycode.aliases = [keycode.code, ...(keycode.aliases || [])]
+ .map(code => code.replace(/`/g, ''))
+ .map(code => code.replace(/\(.+\)/, ''))
+
+ keycode.symbol = keycode.symbol || (
+ [...keycode.aliases]
+ .sort((a, b) => a.length - b.length)[0]
+ .replace(/^KC_/, '')
+ )
+
+ return keycode
+ })
+ .reduce((keycodes, keycode) => {
+ for (let alias of keycode.aliases) {
+ keycodes.push(Object.assign({}, keycode, { code: alias }))
+ }
+
+ return keycodes
+ }, [])
+}
diff --git a/keymap.js b/keymap.js
new file mode 100644
index 0000000..1a99d40
--- /dev/null
+++ b/keymap.js
@@ -0,0 +1,97 @@
+import { loadIndexedKeycodes } from './keycodes.js'
+
+let keycodesIndex_ = loadIndexedKeycodes()
+
+export function loadKeymap () {
+ return fetch('data/keymap.json')
+ .then(response => response.json())
+}
+
+function findParentKey (element) {
+ const isRoot = node => node == document.body
+ const isKey = node => node.classList.contains('key')
+ let node = element
+
+ while (!isRoot(node) && !isKey(node)) { node = node.parentNode }
+
+ if (isRoot(node)) {
+ console.error('Could not find parent key for', element)
+ }
+
+ return node
+}
+
+export function recalculateDepth (element) {
+ function getDepth (node) {
+ const childDepths = Array.from(node.childNodes).map(node => getDepth(node))
+ return node.nodeName !== '#text'
+ ? 1 + Math.max(0, ...childDepths)
+ : 0
+ }
+
+ const key = findParentKey(element)
+ key.dataset.depth = getDepth(key) - 1
+}
+
+export async function setKeycode(element, code) {
+ const keycodesIndex = await keycodesIndex_
+ const paramsPattern = /\((.+)\)/
+ const keycode = keycodesIndex[code.replace(paramsPattern, '')]
+ const params = (code.match(paramsPattern) || ['', ''])[1]
+ .split(',')
+ .map(s => s.trim())
+ .filter(s => !!s)
+
+ if (element.classList.contains('param') && element.dataset.param === 'layer') {
+ element.dataset.code = code
+ element.textContent = code
+ return
+ }
+
+ if (!keycode) {
+ console.warn('wtf', code, code.replace(paramsPattern, ''))
+ return
+ }
+
+ if (!element.classList.contains('code')) {
+ for (let child of [...element.childNodes]) {
+ element.removeChild(child)
+ }
+
+ const codeElement = document.createElement('span')
+ codeElement.classList.add('code')
+ element.appendChild(codeElement)
+ element = codeElement
+ }
+
+ element.textContent = keycode.symbol
+ element.dataset.code = keycode.code
+ element.dataset.keycode = code
+
+ if (keycode.params.length > 0) {
+ const paramsElement = document.createElement('span')
+ element.appendChild(paramsElement)
+ paramsElement.classList.add('params')
+
+ for (let i = 0; i < keycode.params.length; i++) {
+ const value = params[i]
+
+ const paramElement = document.createElement('span')
+ paramElement.classList.add('param', 'code')
+
+ if (value && value.match(/.+\(.+\)/)) {
+ setKeycode(paramElement, value)
+ } else {
+ const param = keycode.params[i]
+ const label = param === 'layer' ? value : ((keycodesIndex[value] || {}).symbol || value)
+ paramElement.textContent = label
+ paramElement.dataset.param = param
+ paramElement.dataset.code = value
+ }
+
+ paramsElement.appendChild(paramElement)
+ }
+ }
+
+ recalculateDepth(element)
+}
diff --git a/layers.js b/layers.js
new file mode 100644
index 0000000..ef43670
--- /dev/null
+++ b/layers.js
@@ -0,0 +1,81 @@
+import { setKeycode } from './keymap.js'
+import { renderLayout } from './layout.js'
+
+const layers = document.querySelector('#layers')
+const layerSelector = document.querySelector('#layer-selector ul')
+
+export function selectLayer (index) {
+ const activeLayer = layers.querySelector('.layer.active')
+ const activeLayerTab = layerSelector.querySelector('.active')
+ const layer = layers.querySelector(`[data-layer="${index}"]`)
+ const layerTab = layerSelector.querySelector(`[data-layer="${index}"]`)
+
+ console.log({
+ activeLayer,
+ activeLayerTab,
+ layer,
+ layerTab
+ })
+
+ activeLayer && activeLayer.classList.remove('active')
+ activeLayerTab && activeLayerTab.classList.remove('active')
+ layer && layer.classList.add('active')
+ layerTab && layerTab.classList.add('active')
+}
+
+export function addLayer (layout, layer) {
+ const layerElement = renderLayout(layout)
+ const li = document.createElement('li')
+ const layerIndex = layers.children.length
+
+ layerElement.classList.add('layer')
+ layerElement.dataset.layer = li.dataset.layer = li.textContent = layerIndex
+
+ layers.appendChild(layerElement)
+ layerSelector.appendChild(li)
+ selectLayer(layerIndex)
+
+ layerSelector.addEventListener('click', event => {
+ if (event.target.dataset.layer !== undefined) {
+ selectLayer(event.target.dataset.layer)
+ }
+ })
+
+ for (let i = 0; i < layout.length; i++) {
+ // const key = layout[i]
+ const code = layer[i] || 'KC_TRNS'
+ // const keyElement = document.createElement('div')
+ const keyElement = layerElement.children[i]
+
+ // const x = key.x * 65
+ // const y = key.y * 65
+ // const rx = (key.x - (key.rx || key.x)) * -65
+ // const ry = (key.y - (key.ry || key.y)) * -65
+
+ setKeycode(keyElement, code)
+ // keyElement.classList.add('key')
+ // keyElement.classList.add(`key-${key.u}u`)
+ // keyElement.classList.add(`key-${key.h}h`)
+
+ // keyElement.style.top = `${y}px`
+ // keyElement.style.left = `${x}px`
+ // keyElement.style.transformOrigin = `${rx}px ${ry}px`
+ // keyElement.style.transform = `rotate(${key.r || 0}deg)`
+ // keyElement.dataset.u = key.u
+ // keyElement.dataset.h = key.h
+ // layerElement.appendChild(keyElement)
+ // recalculateDepth(keyElement)
+
+ keyElement.addEventListener('mouseover', event => {
+ const old = document.querySelector('.highlight')
+ old && old.classList.remove('highlight')
+ event.target.classList.add('highlight')
+ }, true)
+
+ keyElement.addEventListener('mouseleave', event => {
+ event.target.classList.remove('highlight')
+ }, true)
+ }
+
+ return layerElement
+}
diff --git a/layout.js b/layout.js
new file mode 100644
index 0000000..8d18265
--- /dev/null
+++ b/layout.js
@@ -0,0 +1,43 @@
+export function loadLayout () {
+ return fetch('data/layout.json')
+ .then(response => response.json())
+ .then(layout => layout.map(key => ({
+ ...key,
+ u: key.u || 1,
+ h: key.h || 1
+ })))
+}
+
+export function renderLayout (layout) {
+ const layerElement = document.createElement('div')
+
+ for (let i = 0; i < layout.length; i++) {
+ const key = layout[i]
+
+ const x = key.x * 65
+ const y = key.y * 65
+ const rx = (key.x - (key.rx || key.x)) * -65
+ const ry = (key.y - (key.ry || key.y)) * -65
+
+ const keyElement = document.createElement('div')
+ keyElement.classList.add('key')
+ keyElement.classList.add(`key-${key.u}u`)
+ keyElement.classList.add(`key-${key.h}h`)
+
+ keyElement.style.top = `${y}px`
+ keyElement.style.left = `${x}px`
+ keyElement.style.transformOrigin = `${rx}px ${ry}px`
+ keyElement.style.transform = `rotate(${key.r || 0}deg)`
+
+ keyElement.dataset.label = key.label
+ keyElement.dataset.u = key.u
+ keyElement.dataset.h = key.h
+
+ const codeElement = document.createElement('span')
+ codeElement.classList.add('code')
+ keyElement.appendChild(codeElement)
+ layerElement.appendChild(keyElement)
+ }
+
+ return layerElement
+}
diff --git a/main.js b/main.js
new file mode 100644
index 0000000..4be6855
--- /dev/null
+++ b/main.js
@@ -0,0 +1,75 @@
+import * as search from './search.js'
+import { loadKeymap, setKeycode } from './keymap.js'
+import { addLayer, selectLayer } from './layers.js'
+import { loadLayout } from './layout.js'
+
+
+async function main() {
+ // const keycodes = await loadKeycodes()
+ // const keycodesIndex = keycodes.reduce((map, keycode) => Object.assign(map, { [keycode.code]: keycode }), {})
+ // const layout = await loadLayout()
+
+ const layout = await loadLayout()
+ const keymap = await loadKeymap()
+ let active
+
+ search.onSelect(code => {
+ if (active) {
+ setKeycode(active, code)
+ // recalculateDepth(active)
+ }
+ })
+
+ // addLayer(layout, keymap)
+ // document.getElementById('layers').appendChild(renderLayout(layout))
+ for (let layer of keymap.layers) {
+ addLayer(layout, layer)
+ }
+ selectLayer(0)
+
+ document.body.addEventListener('click', event => {
+ if (event.target.classList.contains('key') || event.target.classList.contains('code')) {
+ active = event.target
+ search.activate(event.target)
+ }
+ })
+
+ document.querySelector('#layer-selector button').addEventListener('click', () => {
+ addLayer(layout, [])
+ })
+
+ function extractCode (code) {
+ const paramsElement = code.querySelector('.params')
+ if (!paramsElement) {
+ return code.dataset.code
+ }
+
+ const params = [...paramsElement.children].map(extractCode)
+ return `${code.dataset.code}(${params.join(',')})`
+ }
+
+ document.querySelector('#export').addEventListener('click', () => {
+ const layers = []
+ for (let layer of [...document.querySelectorAll('#layers .layer')]) {
+ const layerExport = []
+ for (let key of [...layer.childNodes]) {
+ layerExport.push(extractCode(key.querySelector('.code')))
+ }
+
+ layers.push(layerExport)
+ }
+
+ const newKeymap = Object.assign({}, keymap, { layers })
+ // const blob = new Blob([JSON.stringify(newKeymap, null, 2)], {
+ // type: 'application/octet-stream',
+ // name: 'default.json'
+ // })
+ const file = new File([JSON.stringify(newKeymap, null, 2)], 'default.json', {
+ type: 'application/octet-stream'
+ })
+
+ location.href = URL.createObjectURL(file)
+ })
+}
+
+main()
diff --git a/search.js b/search.js
new file mode 100644
index 0000000..192282d
--- /dev/null
+++ b/search.js
@@ -0,0 +1,109 @@
+import { loadKeycodes } from './keycodes.js'
+
+/* global fuzzysort */
+const root = document.querySelector('#search')
+export const prompt = root.querySelector('p')
+export const input = root.querySelector('input')
+export const results = root.querySelector('ul')
+let param = null
+let onSelect_ = null
+
+export function onSelect (callback) {
+ onSelect_ = callback
+}
+
+const clear = () => {
+ for (let node of [...results.childNodes]) {
+ results.removeChild(node)
+ }
+}
+
+const getOptions = (param, keycodes) => {
+ switch (param) {
+ case 'layer':
+ return [{code: '1' }, {code: '2' }, {code: '3' }]
+ case 'mod':
+ return keycodes.filter(keycode => keycode.isModifier)
+ case 'kc':
+ default:
+ return keycodes
+ }
+}
+
+async function query () {
+ const keycodes = await loadKeycodes()
+ const options = getOptions(param, keycodes)
+ const searchResults = fuzzysort.go(input.value, options, {
+ key: 'code',
+ limit: 30
+ })
+
+ clear()
+ for (let result of searchResults) {
+ const item = document.createElement('li')
+ item.innerHTML = fuzzysort.highlight(result)
+ item.addEventListener('click', () => {
+ onSelect_ && onSelect_(result.obj.code)
+ hide()
+ })
+
+ results.appendChild(item)
+ }
+}
+
+export const hide = () => {
+ root.style.display = 'none'
+}
+
+export const activate = function activate (target) {
+ const rect = target.getBoundingClientRect()
+ if (target.classList.contains('key')) {
+ target = target.querySelector('.code')
+ }
+
+ param = target.dataset.param || 'kc'
+ if (target.dataset.param === 'layer') {
+ prompt.textContent = 'Select layer...'
+ } else if (target.dataset.param === 'mod') {
+ prompt.textContent = 'Select modifier...'
+ } else {
+ prompt.textContent = 'Select key code...'
+ }
+
+ root.style.display = 'block'
+ root.style.top = `${window.scrollY + (rect.top + rect.bottom) / 2}px`
+ root.style.left = `${window.scrollX + (rect.left + rect.right) / 2}px`
+ input.value = target.dataset.code
+
+ clear()
+ setTimeout(() => {
+ input.focus()
+ input.select()
+ })
+}
+
+function debounce(fn, limit) {
+ let delay = null
+ let delayed = null
+
+ return function debounced(...args) {
+ delayed = () => fn(...args)
+ if (!delay) {
+ delay = setTimeout(() => {
+ delay = null
+ delayed()
+ }, limit)
+ }
+ }
+}
+
+input.addEventListener('keypress', debounce(() => query(input.value), 250))
+document.body.addEventListener('click', (event) => {
+ const inactive = root.style.display === 'none'
+ const child = root.contains(event.target)
+ if (inactive || child) {
+ return
+ }
+
+ hide()
+}, true)
diff --git a/style.css b/style.css
new file mode 100644
index 0000000..2bceaea
--- /dev/null
+++ b/style.css
@@ -0,0 +1,139 @@
+:root {
+ --dark-red: #910e0e;
+ --dark-blue: #6d99c6;
+}
+
+html { font-family: avenir, sans-serif; }
+html, body {
+ width: 100vw;
+ height: 100vh;
+ overflow: auto;
+ padding: 0;
+ margin: 0;
+}
+
+#layer-selector * { display: inline-block; }
+#layer-selector ul {
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+}
+#layer-selector li {
+ cursor: pointer;
+ background-color: lightgray;
+ color: darkgray;
+ border-radius: 5px;
+ padding: 5px;
+ margin: 2px;
+}
+#layer-selector li.active {
+ background-color: mediumseagreen;
+ color: white;
+}
+
+#layers { padding-left: 20px;}
+#layers .layer {
+ position: relative;
+ transition: transform 250ms linear;
+}
+#layers .layer:not(.active) {
+ transform: translate(0, -100vh);
+}
+
+[data-u="1"] { width: 60px; }
+[data-u="2"] { width: 125px; }
+[data-h="1"] { height: 60px; }
+[data-h="2"] { height: 125px; }
+
+.key {
+ position: absolute;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ color: #999;
+ font-size: 110%;
+ border: 1px solid lightgray;
+ border-radius: 5px;
+}
+.key:hover {
+ background-color: var(--dark-red);
+ /*transition: 250ms;*/
+ z-index: 1;
+}
+.key:hover .code {
+ background-color: var(--dark-red);
+ color: white;
+}
+.key > .code {
+ padding: 5px;
+}
+
+.key[data-depth="3"] { font-size: 90%; }
+.key[data-depth="5"] { font-size: 75%; }
+
+.code {
+ cursor: pointer;
+ display: inline-block;
+ box-sizing: content-box;
+ min-width: 0.5em;
+ text-align: center;
+ border-radius: 4px;
+}
+.code.highlight {
+ background-color: white !important;
+ color: var(--dark-red) !important;
+}
+
+.params::before { content: '('; opacity: 0.4; font-weight: bold; margin: 2px; }
+.params::after { content: ')'; opacity: 0.4; font-weight: bold; margin: 2px; }
+.param:not(:last-child)::after { content: ','; }
+.param[data-code="undefined"]::before { content: '∅'; font-size: 80%; }
+
+#search {
+ position: absolute;
+ transform: translate(-60px, -30px);
+ z-index: 2;
+}
+#search p {
+ margin: 0;
+ font-size: 90%;
+ font-weight: bold;
+}
+#search input {
+ display: block;
+ padding: 0;
+ margin: 0;
+ width: 120px;
+ height: 60px;
+ font-size: 120%;
+ border: 2px solid steelblue;
+ border-radius: 4px;
+
+ text-align: center;
+ line-height: 60px;
+}
+#search ul {
+ font-family: monospace;
+ list-style-position: inside;
+ list-style-type: none;
+ padding: 4px;
+ background: rgba(0, 0, 0, 0.8);
+ border-radius: 4px;
+}
+#search li {
+ cursor: pointer;
+ color: white;
+ padding: 5px;
+}
+#search li:hover {
+ background: white;
+ color: black;
+}
+#search b { color: red; }
+
+#export {
+ position: absolute;
+ bottom: 20px;
+ right: 20px;
+}