Browse Source

Update layout

Add indicators, hide controls on mouseout, and put pedal board in
bottom.
master
TheoryOfNekomata 3 weeks ago
parent
commit
05d5d45099
8 changed files with 457 additions and 135 deletions
  1. BIN
      docs/screenshots/screenshot-00.png
  2. +2
    -0
      package.json
  3. +296
    -0
      pnpm-lock.yaml
  4. +6
    -0
      postcss.config.js
  5. +11
    -4
      src/main/index.ts
  6. +130
    -62
      src/renderer/src/App.tsx
  7. +4
    -69
      src/renderer/src/assets/main.css
  8. +8
    -0
      tailwind.config.js

BIN
docs/screenshots/screenshot-00.png View File

Before After
Width: 1275  |  Height: 198  |  Size: 26 KiB Width: 1044  |  Height: 202  |  Size: 17 KiB

+ 2
- 0
package.json View File

@@ -34,6 +34,7 @@
"@types/react": "^18.2.48",
"@types/react-dom": "^18.2.18",
"@vitejs/plugin-react": "^4.2.1",
"autoprefixer": "^10.4.19",
"electron": "^28.2.0",
"electron-builder": "^24.9.1",
"electron-vite": "^2.0.0",
@@ -42,6 +43,7 @@
"prettier": "^3.2.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"tailwindcss": "^3.4.3",
"typescript": "^5.3.3",
"vite": "^5.0.12"
}


+ 296
- 0
pnpm-lock.yaml View File

@@ -40,6 +40,9 @@ devDependencies:
'@vitejs/plugin-react':
specifier: ^4.2.1
version: 4.2.1(vite@5.2.8)
autoprefixer:
specifier: ^10.4.19
version: 10.4.19(postcss@8.4.38)
electron:
specifier: ^28.2.0
version: 28.2.10
@@ -64,6 +67,9 @@ devDependencies:
react-dom:
specifier: ^18.2.0
version: 18.2.0(react@18.2.0)
tailwindcss:
specifier: ^3.4.3
version: 3.4.3
typescript:
specifier: ^5.3.3
version: 5.4.4
@@ -82,6 +88,11 @@ packages:
engines: {node: '>=0.10.0'}
dev: true

/@alloc/quick-lru@5.2.0:
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
engines: {node: '>=10'}
dev: true

/@ampproject/remapping@2.3.0:
resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
engines: {node: '>=6.0.0'}
@@ -1507,6 +1518,18 @@ packages:
engines: {node: '>=12'}
dev: true

/any-promise@1.3.0:
resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
dev: true

/anymatch@3.1.3:
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
engines: {node: '>= 8'}
dependencies:
normalize-path: 3.0.0
picomatch: 2.3.1
dev: true

/app-builder-bin@4.0.0:
resolution: {integrity: sha512-xwdG0FJPQMe0M0UA4Tz0zEB8rBJTRA5a476ZawAqiBkMv16GRK5xpXThOjMaEOFnZ6zabejjG4J3da0SXG63KA==}
dev: true
@@ -1596,6 +1619,10 @@ packages:
zip-stream: 4.1.1
dev: true

/arg@5.0.2:
resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
dev: true

/argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}

@@ -1721,6 +1748,22 @@ packages:
engines: {node: '>= 4.0.0'}
dev: true

/autoprefixer@10.4.19(postcss@8.4.38):
resolution: {integrity: sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==}
engines: {node: ^10 || ^12 || >=14}
hasBin: true
peerDependencies:
postcss: ^8.1.0
dependencies:
browserslist: 4.23.0
caniuse-lite: 1.0.30001607
fraction.js: 4.3.7
normalize-range: 0.1.2
picocolors: 1.0.0
postcss: 8.4.38
postcss-value-parser: 4.2.0
dev: true

/available-typed-arrays@1.0.7:
resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
engines: {node: '>= 0.4'}
@@ -1737,6 +1780,11 @@ packages:
requiresBuild: true
dev: true

/binary-extensions@2.3.0:
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
engines: {node: '>=8'}
dev: true

/bl@4.1.0:
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
dependencies:
@@ -1890,6 +1938,11 @@ packages:
engines: {node: '>=6'}
dev: true

/camelcase-css@2.0.1:
resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==}
engines: {node: '>= 6'}
dev: true

/caniuse-lite@1.0.30001607:
resolution: {integrity: sha512-WcvhVRjXLKFB/kmOFVwELtMxyhq3iM/MvmXcyCe2PNf166c39mptscOc/45TTS96n2gpNV2z7+NakArTWZCQ3w==}
dev: true
@@ -1911,6 +1964,21 @@ packages:
supports-color: 7.2.0
dev: true

/chokidar@3.6.0:
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
engines: {node: '>= 8.10.0'}
dependencies:
anymatch: 3.1.3
braces: 3.0.2
glob-parent: 5.1.2
is-binary-path: 2.1.0
is-glob: 4.0.3
normalize-path: 3.0.0
readdirp: 3.6.0
optionalDependencies:
fsevents: 2.3.3
dev: true

/chownr@2.0.0:
resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==}
engines: {node: '>=10'}
@@ -1977,6 +2045,11 @@ packages:
delayed-stream: 1.0.0
dev: true

/commander@4.1.1:
resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
engines: {node: '>= 6'}
dev: true

/commander@5.1.0:
resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==}
engines: {node: '>= 6'}
@@ -2053,6 +2126,12 @@ packages:
which: 2.0.2
dev: true

/cssesc@3.0.0:
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
engines: {node: '>=4'}
hasBin: true
dev: true

/csstype@3.1.3:
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
dev: true
@@ -2135,6 +2214,10 @@ packages:
requiresBuild: true
optional: true

/didyoumean@1.2.2:
resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
dev: true

/dir-compare@3.3.0:
resolution: {integrity: sha512-J7/et3WlGUCxjdnD3HAAzQ6nsnc0WL6DD7WcwJb7c39iH1+AWfg+9OqzJNaI6PkBwBvm1mhZNL9iY/nRiZXlPg==}
dependencies:
@@ -2149,6 +2232,10 @@ packages:
path-type: 4.0.0
dev: true

/dlv@1.1.3:
resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
dev: true

/dmg-builder@24.13.3(electron-builder-squirrel-windows@24.13.3):
resolution: {integrity: sha512-rcJUkMfnJpfCboZoOOPf4L29TRtEieHNOeAbYPWPxlaBw/Z1RKrRA86dOI9rwaI4tQSc/RD82zTNHprfUHXsoQ==}
dependencies:
@@ -2805,6 +2892,10 @@ packages:
mime-types: 2.1.35
dev: true

/fraction.js@4.3.7:
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
dev: true

/fs-constants@1.0.0:
resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==}
dev: true
@@ -3174,6 +3265,13 @@ packages:
has-bigints: 1.0.2
dev: true

/is-binary-path@2.1.0:
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
engines: {node: '>=8'}
dependencies:
binary-extensions: 2.3.0
dev: true

/is-boolean-object@1.1.2:
resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==}
engines: {node: '>= 0.4'}
@@ -3383,6 +3481,11 @@ packages:
minimatch: 3.1.2
dev: true

/jiti@1.21.0:
resolution: {integrity: sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==}
hasBin: true
dev: true

/js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}

@@ -3465,6 +3568,20 @@ packages:
type-check: 0.4.0
dev: true

/lilconfig@2.1.0:
resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==}
engines: {node: '>=10'}
dev: true

/lilconfig@3.1.1:
resolution: {integrity: sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==}
engines: {node: '>=14'}
dev: true

/lines-and-columns@1.2.4:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
dev: true

/locate-path@6.0.0:
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
engines: {node: '>=10'}
@@ -3674,6 +3791,14 @@ packages:
/ms@2.1.2:
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}

/mz@2.7.0:
resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
dependencies:
any-promise: 1.3.0
object-assign: 4.1.1
thenify-all: 1.6.0
dev: true

/nanoid@3.3.7:
resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
@@ -3699,6 +3824,11 @@ packages:
engines: {node: '>=0.10.0'}
dev: true

/normalize-range@0.1.2:
resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==}
engines: {node: '>=0.10.0'}
dev: true

/normalize-url@6.1.0:
resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==}
engines: {node: '>=10'}
@@ -3708,6 +3838,11 @@ packages:
engines: {node: '>=0.10.0'}
dev: true

/object-hash@3.0.0:
resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
engines: {node: '>= 6'}
dev: true

/object-inspect@1.13.1:
resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==}
dev: true
@@ -3854,6 +3989,16 @@ packages:
engines: {node: '>=8.6'}
dev: true

/pify@2.3.0:
resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
engines: {node: '>=0.10.0'}
dev: true

/pirates@4.0.6:
resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==}
engines: {node: '>= 6'}
dev: true

/plist@3.1.0:
resolution: {integrity: sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==}
engines: {node: '>=10.4.0'}
@@ -3868,6 +4013,67 @@ packages:
engines: {node: '>= 0.4'}
dev: true

/postcss-import@15.1.0(postcss@8.4.38):
resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==}
engines: {node: '>=14.0.0'}
peerDependencies:
postcss: ^8.0.0
dependencies:
postcss: 8.4.38
postcss-value-parser: 4.2.0
read-cache: 1.0.0
resolve: 1.22.8
dev: true

/postcss-js@4.0.1(postcss@8.4.38):
resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==}
engines: {node: ^12 || ^14 || >= 16}
peerDependencies:
postcss: ^8.4.21
dependencies:
camelcase-css: 2.0.1
postcss: 8.4.38
dev: true

/postcss-load-config@4.0.2(postcss@8.4.38):
resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==}
engines: {node: '>= 14'}
peerDependencies:
postcss: '>=8.0.9'
ts-node: '>=9.0.0'
peerDependenciesMeta:
postcss:
optional: true
ts-node:
optional: true
dependencies:
lilconfig: 3.1.1
postcss: 8.4.38
yaml: 2.4.1
dev: true

/postcss-nested@6.0.1(postcss@8.4.38):
resolution: {integrity: sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==}
engines: {node: '>=12.0'}
peerDependencies:
postcss: ^8.2.14
dependencies:
postcss: 8.4.38
postcss-selector-parser: 6.0.16
dev: true

/postcss-selector-parser@6.0.16:
resolution: {integrity: sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==}
engines: {node: '>=4'}
dependencies:
cssesc: 3.0.0
util-deprecate: 1.0.2
dev: true

/postcss-value-parser@4.2.0:
resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
dev: true

/postcss@8.4.38:
resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==}
engines: {node: ^10 || ^12 || >=14}
@@ -3963,6 +4169,12 @@ packages:
dependencies:
loose-envify: 1.4.0

/read-cache@1.0.0:
resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
dependencies:
pify: 2.3.0
dev: true

/read-config-file@6.3.2:
resolution: {integrity: sha512-M80lpCjnE6Wt6zb98DoW8WHR09nzMSpu8XHtPkiTHrJ5Az9CybfeQhTJ8D7saeBHpGhLPIVyA8lcL6ZmdKwY6Q==}
engines: {node: '>=12.0.0'}
@@ -4002,6 +4214,13 @@ packages:
minimatch: 5.1.6
dev: true

/readdirp@3.6.0:
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
engines: {node: '>=8.10.0'}
dependencies:
picomatch: 2.3.1
dev: true

/reflect.getprototypeof@1.0.6:
resolution: {integrity: sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==}
engines: {node: '>= 0.4'}
@@ -4038,6 +4257,15 @@ packages:
engines: {node: '>=4'}
dev: true

/resolve@1.22.8:
resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==}
hasBin: true
dependencies:
is-core-module: 2.13.1
path-parse: 1.0.7
supports-preserve-symlinks-flag: 1.0.0
dev: true

/resolve@2.0.0-next.5:
resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==}
hasBin: true
@@ -4383,6 +4611,20 @@ packages:
engines: {node: '>=8'}
dev: true

/sucrase@3.35.0:
resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==}
engines: {node: '>=16 || 14 >=14.17'}
hasBin: true
dependencies:
'@jridgewell/gen-mapping': 0.3.5
commander: 4.1.1
glob: 10.3.12
lines-and-columns: 1.2.4
mz: 2.7.0
pirates: 4.0.6
ts-interface-checker: 0.1.13
dev: true

/sumchecker@3.0.1:
resolution: {integrity: sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==}
engines: {node: '>= 8.0'}
@@ -4418,6 +4660,37 @@ packages:
tslib: 2.6.2
dev: true

/tailwindcss@3.4.3:
resolution: {integrity: sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==}
engines: {node: '>=14.0.0'}
hasBin: true
dependencies:
'@alloc/quick-lru': 5.2.0
arg: 5.0.2
chokidar: 3.6.0
didyoumean: 1.2.2
dlv: 1.1.3
fast-glob: 3.3.2
glob-parent: 6.0.2
is-glob: 4.0.3
jiti: 1.21.0
lilconfig: 2.1.0
micromatch: 4.0.5
normalize-path: 3.0.0
object-hash: 3.0.0
picocolors: 1.0.0
postcss: 8.4.38
postcss-import: 15.1.0(postcss@8.4.38)
postcss-js: 4.0.1(postcss@8.4.38)
postcss-load-config: 4.0.2(postcss@8.4.38)
postcss-nested: 6.0.1(postcss@8.4.38)
postcss-selector-parser: 6.0.16
resolve: 1.22.8
sucrase: 3.35.0
transitivePeerDependencies:
- ts-node
dev: true

/tar-stream@2.2.0:
resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==}
engines: {node: '>=6'}
@@ -4452,6 +4725,19 @@ packages:
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
dev: true

/thenify-all@1.6.0:
resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==}
engines: {node: '>=0.8'}
dependencies:
thenify: 3.3.1
dev: true

/thenify@3.3.1:
resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
dependencies:
any-promise: 1.3.0
dev: true

/tiny-typed-emitter@2.1.0:
resolution: {integrity: sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==}
dev: false
@@ -4494,6 +4780,10 @@ packages:
typescript: 5.4.4
dev: true

/ts-interface-checker@0.1.13:
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
dev: true

/tslib@2.6.2:
resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
dev: true
@@ -4754,6 +5044,12 @@ packages:
/yallist@4.0.0:
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}

/yaml@2.4.1:
resolution: {integrity: sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg==}
engines: {node: '>= 14'}
hasBin: true
dev: true

/yargs-parser@21.1.1:
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
engines: {node: '>=12'}


+ 6
- 0
postcss.config.js View File

@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

+ 11
- 4
src/main/index.ts View File

@@ -1,6 +1,6 @@
import { app, shell, BrowserWindow, ipcMain } from 'electron'
import { join } from 'path'
import { readFile, stat, writeFile } from "fs/promises";
import { readFile, stat, writeFile } from 'fs/promises'
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
import icon from '../../resources/icon.png?asset'

@@ -18,7 +18,6 @@ const defaultConfig: Config = {

const configPath = './config.json'
const naturalKeyWidth = 20
const pedalBoardWidth = 239
const height = 175

const getNaturalKeys = (range: string): number => {
@@ -47,7 +46,7 @@ const getNaturalKeys = (range: string): number => {
function createWindow(config: Config): void {
// Create the browser window.
const naturalKeys = getNaturalKeys(config.range)
const width = naturalKeys * naturalKeyWidth + pedalBoardWidth
const width = naturalKeys * naturalKeyWidth + Number(config.scaleFactor)
const mainWindow = new BrowserWindow({
width,
height,
@@ -127,9 +126,17 @@ app.whenReady().then(async () => {
await writeFile(configPath, JSON.stringify(effectiveConfig))
})

ipcMain.on('scalefactorchange', async (_event, value) => {
ipcMain.on('scalefactorchange', async (event, value) => {
effectiveConfig.scaleFactor = value
await writeFile(configPath, JSON.stringify(effectiveConfig))
await writeFile(configPath, JSON.stringify(effectiveConfig))
const webContents = event.sender
const win = BrowserWindow.fromWebContents(webContents)
if (!win) {
return
}
win.close()
createWindow(effectiveConfig)
})

ipcMain.on('rangechange', async (event, value) => {


+ 130
- 62
src/renderer/src/App.tsx View File

@@ -18,6 +18,7 @@ const RANGES = {
'E1-E7': '28|100',
'E1-G7': '28|103',
'A0-C8': '21|108',
'C0-B9': '12|119',
'Full MIDI': '0|127'
}

@@ -31,6 +32,25 @@ const App: React.FC = () => {
const [scaleFactor, setScaleFactor] = React.useState<number>(() =>
Number(new URLSearchParams(window.location.search).get('scaleFactor') || 1)
)
const [currentDeviceActive, setCurrentDeviceActive] = React.useState([
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false
])
const currentDeviceActiveTimeoutRef = React.useRef<number>()

const [devicesLoadedTimestamp, setDevicesLoadedTimestamp] = React.useState<number>()
const [midiAccess, setMidiAccess] = React.useState<MIDIAccess>()
@@ -116,11 +136,29 @@ const App: React.FC = () => {
return
}

const addActivity = (channel: number) => {
setCurrentDeviceActive((oldCurrentDeviceActive) =>
oldCurrentDeviceActive.map((state, i) => (i === channel ? true : state))
)
window.clearTimeout(currentDeviceActiveTimeoutRef.current)
currentDeviceActiveTimeoutRef.current = window.setTimeout(() => {
setCurrentDeviceActive((oldCurrentDeviceActive) =>
oldCurrentDeviceActive.map((state, i) => (i === channel ? false : state))
)
}, 100)
}

const midiEvent = e as MidiMessageEvent
const [messageType, param1, param2] = midiEvent.data
const channel = messageType & 0b00001111
addActivity(channel)

switch (messageType & 0b11110000) {
case 0b10110000: {
if (channel === 9) {
return
}

const controlNumber = param1
const value = param2

@@ -140,7 +178,6 @@ const App: React.FC = () => {
return
}
case 0b10010000: {
const channel = messageType & 0b00001111
const keyNumber = param1
const velocity = param2

@@ -161,7 +198,6 @@ const App: React.FC = () => {
return
}
case 0b10000000: {
const channel = messageType & 0b00001111
const keyNumber = param1

setKeyChannels((oldKeyChannels) => {
@@ -187,65 +223,14 @@ const App: React.FC = () => {
typeof midiAccess?.inputs !== 'undefined' ? Array.from(midiAccess.inputs) : undefined

return (
<div className="flex flex-col items-stretch justify-center h-full" key={devicesLoadedTimestamp}>
<div className="flex-shrink-0 flex items-center w-full">
<div className="flex-auto">
<select
className="w-full bg-black h-12 px-4"
name="queryDeviceKey"
defaultValue={queryDeviceKey}
onChange={handleChangeDevice}
>
{devices?.map(([name, device]) => (
<option key={name} value={name}>
{device.name}
</option>
))}
</select>
</div>
<div className="flex-auto">
<select
className="w-full bg-black h-12 px-4"
name="range"
defaultValue={range}
onChange={handleChangeRange}
>
{Object.entries(RANGES).map(([name, range]) => (
<option key={name} value={range}>
{name}
</option>
))}
</select>
</div>
<div className="flex items-center px-8 gap-8">
<div>
<label>
<input
name="scaleFactor"
type="radio"
value={1}
defaultChecked={scaleFactor === 1}
onChange={handleChangeScaleFactor}
/>{' '}
1x
</label>
</div>
<div>
<label>
<input
name="scaleFactor"
type="radio"
value={2}
defaultChecked={scaleFactor === 2}
onChange={handleChangeScaleFactor}
/>{' '}
2x
</label>
</div>
</div>
</div>
<div className="flex-auto flex items-center h-full w-full">
<div className="flex-auto h-full">
<div className="flex flex-col items-center h-full w-full group" key={devicesLoadedTimestamp}>
<div className="flex-auto relative w-full">
<div
className="absolute w-full h-full"
style={{
paddingRight: 'calc(var(--size-scale-factor) * 1px)'
}}
>
<Keyboard
key={range}
height="100%"
@@ -258,7 +243,48 @@ const App: React.FC = () => {
}}
/>
</div>
<div className="flex items-center gap-4 px-8">
</div>
<div className="flex gap-16 w-full">
<div className="w-0 flex-auto flex flex-col justify-center items-stretch px-4">
<label className="transition-opacity group-hover:opacity-100 opacity-0 block">
<span className="flex items-center h-3 text-xs -mb-3 relative pointer-events-none">
Input Device
</span>
<select
className="w-full bg-black h-8 pt-1"
name="queryDeviceKey"
defaultValue={queryDeviceKey}
onChange={handleChangeDevice}
>
{devices?.map(([name, device]) => (
<option key={name} value={name}>
{device.name}
</option>
))}
</select>
</label>
<div className="h-1.5 flex gap-1 items-center">
<div
className="rounded-full w-1.5 h-1.5 bg-yellow-400"
style={{
opacity:
devices?.find(([key]) => key === queryDeviceKey)?.[1]?.state === 'connected'
? 1
: 0.25
}}
/>
{currentDeviceActive.map((a, i) => (
<div
className="rounded-full w-1.5 h-1.5 bg-green-400"
key={i}
style={{
opacity: a ? 1 : 0.25
}}
/>
))}
</div>
</div>
<div className="flex-shrink-0 flex justify-center items-center gap-8 py-4">
<svg
style={{
height: 24,
@@ -314,6 +340,48 @@ const App: React.FC = () => {
</g>
</svg>
</div>
<div className="w-0 flex-auto flex items-center transition-opacity group-hover:opacity-100 opacity-0 px-4 gap-8">
<div className="flex-auto">
<select
className="w-full bg-black h-8"
name="range"
defaultValue={range}
onChange={handleChangeRange}
>
{Object.entries(RANGES).map(([name, range]) => (
<option key={name} value={range}>
{name}
</option>
))}
</select>
</div>
<div className="flex items-center gap-8">
<div>
<label>
<input
name="scaleFactor"
type="radio"
value={1}
defaultChecked={scaleFactor === 1}
onChange={handleChangeScaleFactor}
/>{' '}
1x
</label>
</div>
<div>
<label>
<input
name="scaleFactor"
type="radio"
value={2}
defaultChecked={scaleFactor === 2}
onChange={handleChangeScaleFactor}
/>{' '}
2x
</label>
</div>
</div>
</div>
</div>
</div>
)


+ 4
- 69
src/renderer/src/assets/main.css View File

@@ -1,3 +1,6 @@
@tailwind base;
@tailwind utilities;

:root {
height: 100%;
width: 100%;
@@ -9,77 +12,9 @@ body {
height: 100%;
width: 100%;
margin: 0;
font-family: sans-serif;
font-family: system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
}

#root {
display: contents;
}

.flex {
display: flex;
}

.flex-col {
flex-direction: column;
}

.items-stretch {
align-items: stretch;
}

.justify-center {
justify-content: center;
}

.h-full {
height: 100%;
}

.flex-shrink-0 {
flex-shrink: 0;
}

.items-center {
align-items: center;
}

.w-full {
width: 100%;
}

.gap-8 {
gap: 2rem;
}

.flex-auto {
flex: auto;
}

.bg-black {
background-color: black;
}

select {
color: inherit;
font: inherit;
border: 0;
}

.h-12 {
height: 3rem;
}

.px-4 {
padding-left: 1rem;
padding-right: 1rem;
}

.px-8 {
padding-left: 2rem;
padding-right: 2rem;
}

.gap-4 {
gap: 1rem;
}

+ 8
- 0
tailwind.config.js View File

@@ -0,0 +1,8 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./src/renderer/**/*.tsx'],
theme: {
extend: {}
},
plugins: []
}

Loading…
Cancel
Save