loukai-app 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (290) hide show
  1. package/README.md +558 -0
  2. package/bin/loukai.js +32 -0
  3. package/package.json +243 -0
  4. package/src/main/appState.js +250 -0
  5. package/src/main/audioEngine.js +478 -0
  6. package/src/main/creator/conversionService.js +503 -0
  7. package/src/main/creator/downloadManager.js +1128 -0
  8. package/src/main/creator/ffmpegService.js +487 -0
  9. package/src/main/creator/installLogger.js +51 -0
  10. package/src/main/creator/keyDetection.js +212 -0
  11. package/src/main/creator/llmService.js +370 -0
  12. package/src/main/creator/lrclibService.js +340 -0
  13. package/src/main/creator/python/crepe_runner.py +189 -0
  14. package/src/main/creator/python/demucs_runner.py +158 -0
  15. package/src/main/creator/python/whisper_runner.py +172 -0
  16. package/src/main/creator/pythonRunner.js +268 -0
  17. package/src/main/creator/stemBuilder.js +491 -0
  18. package/src/main/creator/systemChecker.js +474 -0
  19. package/src/main/handlers/appHandlers.js +45 -0
  20. package/src/main/handlers/audioHandlers.js +33 -0
  21. package/src/main/handlers/autotuneHandlers.js +28 -0
  22. package/src/main/handlers/canvasHandlers.js +84 -0
  23. package/src/main/handlers/creatorHandlers.js +159 -0
  24. package/src/main/handlers/editorHandlers.js +98 -0
  25. package/src/main/handlers/effectsHandlers.js +100 -0
  26. package/src/main/handlers/fileHandlers.js +45 -0
  27. package/src/main/handlers/index.js +78 -0
  28. package/src/main/handlers/libraryHandlers.js +96 -0
  29. package/src/main/handlers/mixerHandlers.js +64 -0
  30. package/src/main/handlers/playerHandlers.js +39 -0
  31. package/src/main/handlers/preferencesHandlers.js +46 -0
  32. package/src/main/handlers/queueHandlers.js +81 -0
  33. package/src/main/handlers/rendererHandlers.js +63 -0
  34. package/src/main/handlers/settingsHandlers.js +42 -0
  35. package/src/main/handlers/webServerHandlers.js +105 -0
  36. package/src/main/main.js +2351 -0
  37. package/src/main/preload.js +252 -0
  38. package/src/main/settingsManager.js +139 -0
  39. package/src/main/statePersistence.js +193 -0
  40. package/src/main/utils/pathValidator.js +112 -0
  41. package/src/main/webServer.js +2535 -0
  42. package/src/native/autotune.js +417 -0
  43. package/src/renderer/adapters/ElectronBridge.js +677 -0
  44. package/src/renderer/canvas.html +80 -0
  45. package/src/renderer/components/App.jsx +303 -0
  46. package/src/renderer/components/AppRoot.jsx +37 -0
  47. package/src/renderer/components/AudioDeviceSettings.jsx +145 -0
  48. package/src/renderer/components/EffectsPanelWrapper.jsx +267 -0
  49. package/src/renderer/components/MixerTab.jsx +233 -0
  50. package/src/renderer/components/MixerTabWrapper.jsx +31 -0
  51. package/src/renderer/components/PortalSelect.jsx +239 -0
  52. package/src/renderer/components/QueueTab.jsx +116 -0
  53. package/src/renderer/components/RequestsListWrapper.jsx +78 -0
  54. package/src/renderer/components/ServerTab.jsx +472 -0
  55. package/src/renderer/components/SongInfoBarWrapper.jsx +77 -0
  56. package/src/renderer/components/StatusBar.jsx +92 -0
  57. package/src/renderer/components/TabNavigation.jsx +77 -0
  58. package/src/renderer/components/TransportControlsWrapper.jsx +69 -0
  59. package/src/renderer/components/creator/CreateTab.jsx +1236 -0
  60. package/src/renderer/dist/assets/kaiPlayer-CoMx__a_.js +2 -0
  61. package/src/renderer/dist/assets/kaiPlayer-CoMx__a_.js.map +1 -0
  62. package/src/renderer/dist/assets/microphoneEngine-BaCUhhQc.js +2 -0
  63. package/src/renderer/dist/assets/microphoneEngine-BaCUhhQc.js.map +1 -0
  64. package/src/renderer/dist/assets/player-DVrqp7N5.js +3 -0
  65. package/src/renderer/dist/assets/player-DVrqp7N5.js.map +1 -0
  66. package/src/renderer/dist/assets/songLoaders-BaTgGib4.js +2 -0
  67. package/src/renderer/dist/assets/songLoaders-BaTgGib4.js.map +1 -0
  68. package/src/renderer/dist/assets/webrtcManager-BhCHWceK.js +2 -0
  69. package/src/renderer/dist/assets/webrtcManager-BhCHWceK.js.map +1 -0
  70. package/src/renderer/dist/js/autoTuneWorklet.js +224 -0
  71. package/src/renderer/dist/js/micPitchDetectorWorklet.js +137 -0
  72. package/src/renderer/dist/js/musicAnalysisWorklet.js +216 -0
  73. package/src/renderer/dist/js/phaseVocoderWorklet.js +341 -0
  74. package/src/renderer/dist/js/soundtouch-worklet.js +1395 -0
  75. package/src/renderer/dist/renderer.css +1 -0
  76. package/src/renderer/dist/renderer.js +62 -0
  77. package/src/renderer/dist/renderer.js.map +1 -0
  78. package/src/renderer/dist/renderer.woff2 +0 -0
  79. package/src/renderer/hooks/useKeyboardShortcuts.js +154 -0
  80. package/src/renderer/index.html +24 -0
  81. package/src/renderer/index.html.backup +372 -0
  82. package/src/renderer/js/PlayerInterface.js +267 -0
  83. package/src/renderer/js/autoTuneWorklet.js +224 -0
  84. package/src/renderer/js/butterchurnVerify.js +46 -0
  85. package/src/renderer/js/canvas-app.js +114 -0
  86. package/src/renderer/js/cdgPlayer.js +685 -0
  87. package/src/renderer/js/kaiPlayer.js +1200 -0
  88. package/src/renderer/js/karaokeRenderer.js +3392 -0
  89. package/src/renderer/js/micPitchDetectorWorklet.js +137 -0
  90. package/src/renderer/js/microphoneEngine.js +656 -0
  91. package/src/renderer/js/musicAnalysisWorklet.js +216 -0
  92. package/src/renderer/js/phaseVocoderWorklet.js +341 -0
  93. package/src/renderer/js/player.js +232 -0
  94. package/src/renderer/js/referencePitchTracker.js +130 -0
  95. package/src/renderer/js/songLoaders.js +334 -0
  96. package/src/renderer/js/soundtouch-worklet.js +1395 -0
  97. package/src/renderer/js/webrtcManager.js +511 -0
  98. package/src/renderer/lib/butterchurn.min.js +6739 -0
  99. package/src/renderer/lib/butterchurnPresets.min.js +1 -0
  100. package/src/renderer/lib/cdgraphics-wrapper.js +16 -0
  101. package/src/renderer/lib/cdgraphics.js +299 -0
  102. package/src/renderer/public/js/autoTuneWorklet.js +224 -0
  103. package/src/renderer/public/js/micPitchDetectorWorklet.js +137 -0
  104. package/src/renderer/public/js/musicAnalysisWorklet.js +216 -0
  105. package/src/renderer/public/js/phaseVocoderWorklet.js +341 -0
  106. package/src/renderer/public/js/soundtouch-worklet.js +1395 -0
  107. package/src/renderer/react-entry.jsx +44 -0
  108. package/src/renderer/styles/tailwind.css +106 -0
  109. package/src/renderer/utils/qrCodeGenerator.js +98 -0
  110. package/src/renderer/vite.config.js +31 -0
  111. package/src/shared/adapters/BridgeInterface.js +195 -0
  112. package/src/shared/components/EffectsPanel.jsx +177 -0
  113. package/src/shared/components/LibraryPanel.jsx +701 -0
  114. package/src/shared/components/LineDetailCanvas.jsx +167 -0
  115. package/src/shared/components/LyricLine.jsx +505 -0
  116. package/src/shared/components/LyricRejection.jsx +84 -0
  117. package/src/shared/components/LyricSuggestion.jsx +80 -0
  118. package/src/shared/components/LyricsEditorCanvas.jsx +271 -0
  119. package/src/shared/components/MixerPanel.jsx +94 -0
  120. package/src/shared/components/PlayerControls.jsx +206 -0
  121. package/src/shared/components/PortalSelect.jsx +239 -0
  122. package/src/shared/components/QueueList.jsx +365 -0
  123. package/src/shared/components/QuickSearch.jsx +126 -0
  124. package/src/shared/components/RequestsList.jsx +121 -0
  125. package/src/shared/components/SongEditor.jsx +1362 -0
  126. package/src/shared/components/SongInfoBar.jsx +81 -0
  127. package/src/shared/components/ThemeToggle.jsx +106 -0
  128. package/src/shared/components/Toast.jsx +30 -0
  129. package/src/shared/components/VisualizationSettings.jsx +243 -0
  130. package/src/shared/constants.js +95 -0
  131. package/src/shared/context/BridgeContext.jsx +32 -0
  132. package/src/shared/contexts/AudioContext.jsx +37 -0
  133. package/src/shared/contexts/PlayerContext.jsx +66 -0
  134. package/src/shared/contexts/SettingsContext.jsx +50 -0
  135. package/src/shared/defaults.js +158 -0
  136. package/src/shared/formatUtils.js +59 -0
  137. package/src/shared/formatUtils.test.js +207 -0
  138. package/src/shared/hooks/useAppState.js +97 -0
  139. package/src/shared/hooks/useAudioEngine.js +264 -0
  140. package/src/shared/hooks/usePlayer.js +89 -0
  141. package/src/shared/hooks/useSettingsPersistence.js +74 -0
  142. package/src/shared/hooks/useWebRTC.js +118 -0
  143. package/src/shared/ipcContracts.js +299 -0
  144. package/src/shared/package.json +3 -0
  145. package/src/shared/services/creatorService.js +373 -0
  146. package/src/shared/services/creatorService.test.js +413 -0
  147. package/src/shared/services/editorService.js +213 -0
  148. package/src/shared/services/editorService.test.js +219 -0
  149. package/src/shared/services/effectsService.js +271 -0
  150. package/src/shared/services/effectsService.test.js +418 -0
  151. package/src/shared/services/libraryService.js +438 -0
  152. package/src/shared/services/libraryService.test.js +474 -0
  153. package/src/shared/services/mixerService.js +172 -0
  154. package/src/shared/services/mixerService.test.js +399 -0
  155. package/src/shared/services/playerService.js +221 -0
  156. package/src/shared/services/playerService.test.js +357 -0
  157. package/src/shared/services/preferencesService.js +219 -0
  158. package/src/shared/services/queueService.js +226 -0
  159. package/src/shared/services/queueService.test.js +430 -0
  160. package/src/shared/services/requestsService.js +155 -0
  161. package/src/shared/services/requestsService.test.js +362 -0
  162. package/src/shared/services/serverSettingsService.js +151 -0
  163. package/src/shared/services/settingsService.js +257 -0
  164. package/src/shared/services/settingsService.test.js +295 -0
  165. package/src/shared/state/StateManager.js +263 -0
  166. package/src/shared/utils/audio.js +42 -0
  167. package/src/shared/utils/format.js +32 -0
  168. package/src/shared/utils/lyricsUtils.js +162 -0
  169. package/src/test/setup.js +40 -0
  170. package/src/utils/cdgLoader.js +180 -0
  171. package/src/utils/m4aLoader.js +333 -0
  172. package/src/web/App.jsx +578 -0
  173. package/src/web/adapters/WebBridge.js +428 -0
  174. package/src/web/components/PlayerSettingsPanel.jsx +231 -0
  175. package/src/web/components/SongSearch.jsx +180 -0
  176. package/src/web/dist/assets/index-0H-RnRrV.js +51 -0
  177. package/src/web/dist/assets/index-0H-RnRrV.js.map +1 -0
  178. package/src/web/dist/assets/index-DYW2zB0u.css +1 -0
  179. package/src/web/dist/index.html +15 -0
  180. package/src/web/index.html +14 -0
  181. package/src/web/main.jsx +10 -0
  182. package/src/web/package-lock.json +1765 -0
  183. package/src/web/pages/SongRequestPage.jsx +619 -0
  184. package/src/web/styles/tailwind.css +68 -0
  185. package/src/web/vite.config.js +27 -0
  186. package/static/fonts/material-icons.woff2 +0 -0
  187. package/static/images/butterchurn-screenshots/Aderrasi - Potion of Spirits.png +0 -0
  188. package/static/images/butterchurn-screenshots/Aderrasi - Songflower _Moss Posy_.png +0 -0
  189. package/static/images/butterchurn-screenshots/Aderrasi - Storm of the Eye _Thunder_ - mash0000 - quasi pseudo meta concentrics.png +0 -0
  190. package/static/images/butterchurn-screenshots/Aderrasi _ Geiss - Airhandler _Kali Mix_ - Canvas Mix.png +0 -0
  191. package/static/images/butterchurn-screenshots/An AdamFX n Martin Infusion 2 flexi - Why The Sky Looks Diffrent Today - AdamFx n Martin Infusion - Tack Tile Disfunction B.png +0 -0
  192. package/static/images/butterchurn-screenshots/Cope - The Neverending Explosion of Red Liquid Fire.png +0 -0
  193. proton lights __Krash_s beat code_ _Phat_remix02b.png +0 -0
  194. package/static/images/butterchurn-screenshots/Eo_S_ _ Phat - cubetrace - v2.png +0 -0
  195. package/static/images/butterchurn-screenshots/Eo_S_ _ Zylot - skylight _Stained Glass Majesty mix_.png +0 -0
  196. package/static/images/butterchurn-screenshots/Flexi - alien fish pond.png +0 -0
  197. package/static/images/butterchurn-screenshots/Flexi - area 51.png +0 -0
  198. package/static/images/butterchurn-screenshots/Flexi - infused with the spiral.png +0 -0
  199. package/static/images/butterchurn-screenshots/Flexi - mindblob _shiny mix_.png +0 -0
  200. package/static/images/butterchurn-screenshots/Flexi - mindblob mix.png +0 -0
  201. package/static/images/butterchurn-screenshots/Flexi - predator-prey-spirals.png +0 -0
  202. package/static/images/butterchurn-screenshots/Flexi - smashing fractals _acid etching mix_.png +0 -0
  203. package/static/images/butterchurn-screenshots/Flexi - truly soft piece of software - this is generic texturing _Jelly_ .png +0 -0
  204. package/static/images/butterchurn-screenshots/Flexi _ Martin - astral projection.png +0 -0
  205. package/static/images/butterchurn-screenshots/Flexi _ Martin - cascading decay swing.png +0 -0
  206. package/static/images/butterchurn-screenshots/Flexi _ amandio c - piercing 05 - Kopie _2_ - Kopie.png +0 -0
  207. package/static/images/butterchurn-screenshots/Flexi _ stahlregen - jelly showoff parade.png +0 -0
  208. package/static/images/butterchurn-screenshots/Flexi_ fishbrain_ Geiss _ Martin - tokamak witchery.png +0 -0
  209. package/static/images/butterchurn-screenshots/Flexi_ martin _ geiss - dedicated to the sherwin maxawow.png +0 -0
  210. package/static/images/butterchurn-screenshots/Fumbling_Foo _ Flexi_ Martin_ Orb_ Unchained - Star Nova v7b.png +0 -0
  211. package/static/images/butterchurn-screenshots/Geiss - Cauldron - painterly 2 _saturation remix_.png +0 -0
  212. package/static/images/butterchurn-screenshots/Geiss - Reaction Diffusion 2.png +0 -0
  213. package/static/images/butterchurn-screenshots/Geiss - Spiral Artifact.png +0 -0
  214. package/static/images/butterchurn-screenshots/Geiss - Thumb Drum.png +0 -0
  215. package/static/images/butterchurn-screenshots/Geiss _ Flexi _ Martin - disconnected.png +0 -0
  216. package/static/images/butterchurn-screenshots/Geiss_ Flexi _ Stahlregen - Thumbdrum Tokamak _crossfiring aftermath jelly mashup_.png +0 -0
  217. package/static/images/butterchurn-screenshots/Goody - The Wild Vort.png +0 -0
  218. package/static/images/butterchurn-screenshots/Idiot - Star Of Annon.png +0 -0
  219. package/static/images/butterchurn-screenshots/Krash _ Illusion - Spiral Movement.png +0 -0
  220. package/static/images/butterchurn-screenshots/Martin - QBikal - Surface Turbulence IIb.png +0 -0
  221. package/static/images/butterchurn-screenshots/Martin - acid wiring.png +0 -0
  222. package/static/images/butterchurn-screenshots/Martin - charisma.png +0 -0
  223. package/static/images/butterchurn-screenshots/Martin - liquid arrows.png +0 -0
  224. package/static/images/butterchurn-screenshots/Milk Artist At our Best - FED - SlowFast Ft AdamFX n Martin - HD CosmoFX.png +0 -0
  225. package/static/images/butterchurn-screenshots/ORB - Waaa.png +0 -0
  226. package/static/images/butterchurn-screenshots/Phat_fiShbRaiN_Eo_S_Mandala_Chasers_remix.png +0 -0
  227. package/static/images/butterchurn-screenshots/Rovastar - Oozing Resistance.png +0 -0
  228. package/static/images/butterchurn-screenshots/Rovastar _ Loadus _ Geiss - FractalDrop _Triple Mix_.png +0 -0
  229. package/static/images/butterchurn-screenshots/TonyMilkdrop - Leonardo Da Vinci_s Balloon _Flexi - merry-go-round _ techstyle_.png +0 -0
  230. package/static/images/butterchurn-screenshots/TonyMilkdrop - Magellan_s Nebula _Flexi - you enter first _ multiverse_.png +0 -0
  231. package/static/images/butterchurn-screenshots/Unchained - Rewop.png +0 -0
  232. package/static/images/butterchurn-screenshots/Unchained - Unified Drag 2.png +0 -0
  233. package/static/images/butterchurn-screenshots/Unchained _ Rovastar - Wormhole Pillars _Hall of Shadows mix_.png +0 -0
  234. package/static/images/butterchurn-screenshots/Zylot - Paint Spill _Music Reactive Paint Mix_.png +0 -0
  235. package/static/images/butterchurn-screenshots/Zylot - Star Ornament.png +0 -0
  236. package/static/images/butterchurn-screenshots/Zylot - True Visionary _Final Mix_.png +0 -0
  237. package/static/images/butterchurn-screenshots/_Aderrasi - Wanderer in Curved Space - mash0000 - faclempt kibitzing meshuggana schmaltz _Geiss color mix_.png +0 -0
  238. package/static/images/butterchurn-screenshots/_Geiss - Artifact 01.png +0 -0
  239. package/static/images/butterchurn-screenshots/_Geiss - Desert Rose 2.png +0 -0
  240. package/static/images/butterchurn-screenshots/_Geiss - untitled.png +0 -0
  241. package/static/images/butterchurn-screenshots/_Mig_049.png +0 -0
  242. package/static/images/butterchurn-screenshots/_Mig_085.png +0 -0
  243. package/static/images/butterchurn-screenshots/_Rovastar _ Geiss - Hurricane Nightmare _Posterize Mix_.png +0 -0
  244. package/static/images/butterchurn-screenshots/___ Royal - Mashup _197_.png +0 -0
  245. package/static/images/butterchurn-screenshots/___ Royal - Mashup _220_.png +0 -0
  246. package/static/images/butterchurn-screenshots/___ Royal - Mashup _431_.png +0 -0
  247. package/static/images/butterchurn-screenshots/cope _ martin - mother-of-pearl.png +0 -0
  248. package/static/images/butterchurn-screenshots/fiShbRaiN _ Flexi - witchcraft 2_0.png +0 -0
  249. package/static/images/butterchurn-screenshots/flexi - bouncing balls _double mindblob neon mix_.png +0 -0
  250. package/static/images/butterchurn-screenshots/flexi - mom_ why the sky looks different today.png +0 -0
  251. package/static/images/butterchurn-screenshots/flexi - patternton_ district of media_ capitol of the united abstractions of fractopia.png +0 -0
  252. package/static/images/butterchurn-screenshots/flexi - swing out on the spiral.png +0 -0
  253. package/static/images/butterchurn-screenshots/flexi - what is the matrix.png +0 -0
  254. package/static/images/butterchurn-screenshots/flexi _ amandio c - organic _random mashup_.png +0 -0
  255. package/static/images/butterchurn-screenshots/flexi _ amandio c - organic12-3d-2_milk.png +0 -0
  256. package/static/images/butterchurn-screenshots/flexi _ fishbrain - neon mindblob grafitti.png +0 -0
  257. package/static/images/butterchurn-screenshots/flexi _ geiss - pogo cubes vs_ tokamak vs_ game of life _stahls jelly 4_5 finish_.png +0 -0
  258. package/static/images/butterchurn-screenshots/high-altitude basket unraveling - singh grooves nitrogen argon nz_.png +0 -0
  259. package/static/images/butterchurn-screenshots/martin - The Bridge of Khazad-Dum.png +0 -0
  260. package/static/images/butterchurn-screenshots/martin - angel flight.png +0 -0
  261. package/static/images/butterchurn-screenshots/martin - another kind of groove.png +0 -0
  262. package/static/images/butterchurn-screenshots/martin - bombyx mori.png +0 -0
  263. package/static/images/butterchurn-screenshots/martin - castle in the air.png +0 -0
  264. package/static/images/butterchurn-screenshots/martin - chain breaker.png +0 -0
  265. package/static/images/butterchurn-screenshots/martin - disco mix 4.png +0 -0
  266. package/static/images/butterchurn-screenshots/martin - extreme heat.png +0 -0
  267. package/static/images/butterchurn-screenshots/martin - frosty caves 2.png +0 -0
  268. package/static/images/butterchurn-screenshots/martin - fruit machine.png +0 -0
  269. package/static/images/butterchurn-screenshots/martin - ghost city.png +0 -0
  270. package/static/images/butterchurn-screenshots/martin - glass corridor.png +0 -0
  271. package/static/images/butterchurn-screenshots/martin - infinity _2010 update_.png +0 -0
  272. package/static/images/butterchurn-screenshots/martin - mandelbox explorer - high speed demo version.png +0 -0
  273. package/static/images/butterchurn-screenshots/martin - mucus cervix.png +0 -0
  274. package/static/images/butterchurn-screenshots/martin - reflections on black tiles.png +0 -0
  275. package/static/images/butterchurn-screenshots/martin - stormy sea _2010 update_.png +0 -0
  276. package/static/images/butterchurn-screenshots/martin - witchcraft reloaded.png +0 -0
  277. package/static/images/butterchurn-screenshots/martin _ flexi - diamond cutter _prismaticvortex_com_ - camille - i wish i wish i wish i was constrained.png +0 -0
  278. package/static/images/butterchurn-screenshots/martin _shadow harlequins shape code_ - fata morgana.png +0 -0
  279. package/static/images/butterchurn-screenshots/martin_ flexi_ fishbrain _ sto - enterstate _random mashup_.png +0 -0
  280. package/static/images/butterchurn-screenshots/sawtooth grin roam.png +0 -0
  281. package/static/images/butterchurn-screenshots/shifter - dark tides bdrv mix 2.png +0 -0
  282. package/static/images/butterchurn-screenshots/suksma - Rovastar - Sunflower Passion _Enlightment Mix__Phat_edit _ flexi und martin shaders - circumflex in character classes in regular expression.png +0 -0
  283. package/static/images/butterchurn-screenshots/suksma - heretical crosscut playpen.png +0 -0
  284. package/static/images/butterchurn-screenshots/suksma - uninitialized variabowl _hydroponic chronic_.png +0 -0
  285. package/static/images/butterchurn-screenshots/suksma - vector exp 1 - couldn_t not.png +0 -0
  286. package/static/images/butterchurn-screenshots/yin - 191 - Temporal singularities.png +0 -0
  287. package/static/images/logo-512.png +0 -0
  288. package/static/images/logo.png +0 -0
  289. package/static/loukai-logo.png +0 -0
  290. package/static/screenshot-generator.html +610 -0
@@ -0,0 +1,472 @@
1
+ /**
2
+ * ServerTab - Server settings and management component
3
+ */
4
+
5
+ import React, { useState, useEffect, useCallback } from 'react';
6
+ import { generateQRCode } from '../utils/qrCodeGenerator.js';
7
+
8
+ export function ServerTab({ bridge }) {
9
+ const [serverUrl, setServerUrl] = useState(null);
10
+ const [serverPort, setServerPort] = useState(null);
11
+ const [settings, setSettings] = useState({
12
+ serverName: 'Loukai Karaoke',
13
+ port: 3069,
14
+ allowSongRequests: true,
15
+ requireKJApproval: true,
16
+ streamVocalsToClients: false,
17
+ showQrCode: true,
18
+ displayQueue: true,
19
+ });
20
+ const [adminPassword, setAdminPassword] = useState('');
21
+ const [hasPassword, setHasPassword] = useState(false);
22
+ const [pendingRequests, setPendingRequests] = useState(0);
23
+ const [totalRequests, setTotalRequests] = useState(0);
24
+ const [message, setMessage] = useState(null);
25
+ const [qrCodeDataUrl, setQrCodeDataUrl] = useState(null);
26
+
27
+ // Wrap in useCallback to stabilize reference
28
+ const updateRequestsStats = useCallback(async () => {
29
+ try {
30
+ if (!bridge?.getRequests) return;
31
+ const requests = await bridge.getRequests();
32
+ const pending = requests.filter((r) => r.status === 'pending').length;
33
+ setPendingRequests(pending);
34
+ setTotalRequests(requests.length);
35
+ } catch {
36
+ // Silently fail
37
+ }
38
+ }, [bridge]);
39
+
40
+ // Load initial server status and settings
41
+ useEffect(() => {
42
+ if (!bridge) return;
43
+
44
+ const loadData = async () => {
45
+ try {
46
+ // Get server URL
47
+ const url = await bridge.getServerUrl();
48
+ setServerUrl(url);
49
+ if (url) {
50
+ const port = new URL(url).port;
51
+ setServerPort(port);
52
+ }
53
+
54
+ // Get settings
55
+ const serverSettings = await bridge.getServerSettings();
56
+ if (serverSettings) {
57
+ setSettings({
58
+ serverName: serverSettings.serverName || 'Loukai Karaoke',
59
+ port: serverSettings.port || 3069,
60
+ allowSongRequests: serverSettings.allowSongRequests !== false,
61
+ requireKJApproval: serverSettings.requireKJApproval !== false,
62
+ streamVocalsToClients: serverSettings.streamVocalsToClients === true,
63
+ showQrCode: serverSettings.showQrCode !== false,
64
+ displayQueue: serverSettings.displayQueue !== false,
65
+ });
66
+ }
67
+
68
+ // Check password status
69
+ if (bridge.getAdminPasswordStatus) {
70
+ const passwordSet = await bridge.getAdminPasswordStatus();
71
+ setHasPassword(passwordSet);
72
+ }
73
+
74
+ // Get request stats
75
+ updateRequestsStats();
76
+ } catch (error) {
77
+ console.error('Failed to load server data:', error);
78
+ }
79
+ };
80
+
81
+ loadData();
82
+
83
+ // Poll for server URL and request stats
84
+ const pollInterval = setInterval(() => {
85
+ if (bridge.getServerUrl) {
86
+ bridge
87
+ .getServerUrl()
88
+ .then((url) => {
89
+ setServerUrl(url);
90
+ if (url) {
91
+ const port = new URL(url).port;
92
+ setServerPort(port);
93
+ // Generate QR code when URL is available
94
+ generateQRCode(url, { width: 300 }).then(setQrCodeDataUrl).catch(console.error);
95
+ }
96
+ })
97
+ .catch(console.error);
98
+ }
99
+ updateRequestsStats();
100
+ }, 5000);
101
+
102
+ return () => clearInterval(pollInterval);
103
+ }, [bridge, updateRequestsStats]);
104
+
105
+ const handleSaveSettings = async () => {
106
+ try {
107
+ await bridge.updateServerSettings(settings);
108
+ showMessage('Settings saved successfully', 'success');
109
+ } catch (error) {
110
+ console.error('Failed to save settings:', error);
111
+ showMessage('Failed to save settings', 'error');
112
+ }
113
+ };
114
+
115
+ const handleSetPassword = async () => {
116
+ const password = adminPassword.trim();
117
+
118
+ if (!password) {
119
+ showMessage('Please enter a password', 'error');
120
+ return;
121
+ }
122
+
123
+ if (password.length < 6) {
124
+ showMessage('Password must be at least 6 characters', 'error');
125
+ return;
126
+ }
127
+
128
+ try {
129
+ if (bridge.setAdminPassword) {
130
+ await bridge.setAdminPassword(password);
131
+ setAdminPassword('');
132
+ setHasPassword(true);
133
+ showMessage('Admin password set successfully', 'success');
134
+ }
135
+ } catch (error) {
136
+ console.error('Failed to set password:', error);
137
+ showMessage('Failed to set admin password', 'error');
138
+ }
139
+ };
140
+
141
+ const handleOpenServer = () => {
142
+ if (serverUrl && bridge?.openExternal) {
143
+ bridge.openExternal(serverUrl);
144
+ }
145
+ };
146
+
147
+ const handleOpenAdmin = () => {
148
+ try {
149
+ if (serverPort && bridge?.openExternal) {
150
+ bridge.openExternal(`http://localhost:${serverPort}/admin`);
151
+ }
152
+ } catch (error) {
153
+ console.error('Failed to open admin panel:', error);
154
+ }
155
+ };
156
+
157
+ const handleClearRequests = async () => {
158
+ if (!confirm('Are you sure you want to clear all song requests? This cannot be undone.')) {
159
+ return;
160
+ }
161
+
162
+ try {
163
+ if (bridge.clearAllRequests) {
164
+ await bridge.clearAllRequests();
165
+ showMessage('All requests cleared', 'success');
166
+ updateRequestsStats();
167
+ }
168
+ } catch (error) {
169
+ console.error('Failed to clear requests:', error);
170
+ showMessage('Failed to clear requests', 'error');
171
+ }
172
+ };
173
+
174
+ const showMessage = (text, type = 'info') => {
175
+ setMessage({ text, type });
176
+ setTimeout(() => setMessage(null), 3000);
177
+ };
178
+
179
+ const handleSettingChange = (key, value) => {
180
+ setSettings((prev) => ({ ...prev, [key]: value }));
181
+ };
182
+
183
+ const isServerRunning = Boolean(serverUrl);
184
+
185
+ return (
186
+ <div className="p-5 h-full overflow-y-auto bg-white dark:bg-gray-900">
187
+ {message && (
188
+ <div
189
+ className={`fixed top-5 right-5 px-5 py-3 rounded text-white font-medium z-[10000] shadow-lg animate-slide-in ${
190
+ message.type === 'success'
191
+ ? 'bg-green-600'
192
+ : message.type === 'error'
193
+ ? 'bg-red-600'
194
+ : 'bg-blue-500'
195
+ }`}
196
+ >
197
+ {message.text}
198
+ </div>
199
+ )}
200
+
201
+ <div className="flex items-center justify-between mb-8 pb-4 border-b border-gray-200 dark:border-gray-700">
202
+ <h2 className="text-2xl font-semibold text-gray-900 dark:text-gray-100">
203
+ Web Server Settings
204
+ </h2>
205
+ <div className="flex items-center gap-2">
206
+ <span
207
+ className={`w-3 h-3 rounded-full ${isServerRunning ? 'bg-green-500' : 'bg-red-500'}`}
208
+ />
209
+ <span className="text-gray-700 dark:text-gray-300">
210
+ {isServerRunning ? `Running on port ${serverPort}` : 'Not running'}
211
+ </span>
212
+ </div>
213
+ </div>
214
+
215
+ <div className="space-y-6">
216
+ {/* Server Control */}
217
+ <div className="bg-gray-50 dark:bg-gray-800 rounded-lg p-6">
218
+ <h3 className="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">
219
+ Server Control
220
+ </h3>
221
+ <div className="space-y-4">
222
+ <div className="flex items-center justify-between gap-4">
223
+ <label className="text-sm font-medium text-gray-700 dark:text-gray-300">
224
+ Server Status:
225
+ </label>
226
+ <div className="flex items-center gap-3 flex-1">
227
+ <span className="text-gray-900 dark:text-gray-100 flex-1">
228
+ {serverUrl || 'Not running'}
229
+ </span>
230
+ <button
231
+ className="flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed text-white rounded transition-colors"
232
+ onClick={handleOpenServer}
233
+ disabled={!isServerRunning}
234
+ >
235
+ <span className="material-icons text-lg">open_in_new</span>
236
+ Open in Browser
237
+ </button>
238
+ </div>
239
+ </div>
240
+ </div>
241
+ </div>
242
+
243
+ {/* Server Settings */}
244
+ <div className="bg-gray-50 dark:bg-gray-800 rounded-lg p-6">
245
+ <h3 className="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">
246
+ General Settings
247
+ </h3>
248
+ <div className="space-y-4">
249
+ <div className="space-y-2">
250
+ <label
251
+ htmlFor="serverName"
252
+ className="block text-sm font-medium text-gray-700 dark:text-gray-300"
253
+ >
254
+ Server Name:
255
+ </label>
256
+ <input
257
+ type="text"
258
+ id="serverName"
259
+ className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
260
+ placeholder="Loukai Karaoke"
261
+ value={settings.serverName}
262
+ onChange={(e) => handleSettingChange('serverName', e.target.value)}
263
+ />
264
+ </div>
265
+
266
+ <div className="space-y-2">
267
+ <label
268
+ htmlFor="serverPort"
269
+ className="block text-sm font-medium text-gray-700 dark:text-gray-300"
270
+ >
271
+ Port:
272
+ </label>
273
+ <input
274
+ type="number"
275
+ id="serverPort"
276
+ className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
277
+ placeholder="3069"
278
+ min="1024"
279
+ max="65535"
280
+ value={settings.port}
281
+ onChange={(e) => handleSettingChange('port', parseInt(e.target.value) || 3069)}
282
+ />
283
+ <p className="text-xs text-gray-500 dark:text-gray-400">
284
+ Server must be restarted for port changes to take effect
285
+ </p>
286
+ </div>
287
+
288
+ <div className="flex items-center">
289
+ <label className="flex items-center cursor-pointer select-none">
290
+ <input
291
+ type="checkbox"
292
+ id="allowSongRequests"
293
+ className="w-4 h-4 mr-2 cursor-pointer"
294
+ checked={settings.allowSongRequests}
295
+ onChange={(e) => handleSettingChange('allowSongRequests', e.target.checked)}
296
+ />
297
+ <span className="text-gray-900 dark:text-gray-100">Allow Song Requests</span>
298
+ </label>
299
+ </div>
300
+
301
+ <div className="flex items-center">
302
+ <label className="flex items-center cursor-pointer select-none">
303
+ <input
304
+ type="checkbox"
305
+ id="requireKJApproval"
306
+ className="w-4 h-4 mr-2 cursor-pointer"
307
+ checked={settings.requireKJApproval}
308
+ onChange={(e) => handleSettingChange('requireKJApproval', e.target.checked)}
309
+ />
310
+ <span className="text-gray-900 dark:text-gray-100">Require KJ Approval</span>
311
+ </label>
312
+ </div>
313
+
314
+ <div className="flex items-center">
315
+ <label className="flex items-center cursor-pointer select-none">
316
+ <input
317
+ type="checkbox"
318
+ id="streamVocalsToClients"
319
+ className="w-4 h-4 mr-2 cursor-pointer"
320
+ checked={settings.streamVocalsToClients}
321
+ onChange={(e) => handleSettingChange('streamVocalsToClients', e.target.checked)}
322
+ />
323
+ <span className="text-gray-900 dark:text-gray-100">
324
+ Stream Vocals to Clients (LAN only)
325
+ </span>
326
+ </label>
327
+ </div>
328
+
329
+ <div className="flex items-center">
330
+ <label className="flex items-center cursor-pointer select-none">
331
+ <input
332
+ type="checkbox"
333
+ id="showQrCode"
334
+ className="w-4 h-4 mr-2 cursor-pointer"
335
+ checked={settings.showQrCode}
336
+ onChange={(e) => handleSettingChange('showQrCode', e.target.checked)}
337
+ />
338
+ <span className="text-gray-900 dark:text-gray-100">Show QR code</span>
339
+ </label>
340
+ </div>
341
+
342
+ <div className="flex items-center">
343
+ <label className="flex items-center cursor-pointer select-none">
344
+ <input
345
+ type="checkbox"
346
+ id="displayQueue"
347
+ className="w-4 h-4 mr-2 cursor-pointer"
348
+ checked={settings.displayQueue}
349
+ onChange={(e) => handleSettingChange('displayQueue', e.target.checked)}
350
+ />
351
+ <span className="text-gray-900 dark:text-gray-100">Display queue</span>
352
+ </label>
353
+ </div>
354
+
355
+ <button
356
+ className="flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded transition-colors"
357
+ onClick={handleSaveSettings}
358
+ >
359
+ <span className="material-icons text-lg">save</span>
360
+ Save Settings
361
+ </button>
362
+ </div>
363
+ </div>
364
+
365
+ {/* Admin Password */}
366
+ <div className="bg-gray-50 dark:bg-gray-800 rounded-lg p-6">
367
+ <h3 className="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">
368
+ Admin Security
369
+ </h3>
370
+ <div className="space-y-4">
371
+ <div className="space-y-2">
372
+ <label
373
+ htmlFor="adminPassword"
374
+ className="block text-sm font-medium text-gray-700 dark:text-gray-300"
375
+ >
376
+ Admin Password:
377
+ </label>
378
+ <div className="flex gap-2">
379
+ <input
380
+ type="password"
381
+ id="adminPassword"
382
+ className="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
383
+ placeholder="Enter new admin password"
384
+ value={adminPassword}
385
+ onChange={(e) => setAdminPassword(e.target.value)}
386
+ />
387
+ <button
388
+ className="flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded transition-colors"
389
+ onClick={handleSetPassword}
390
+ >
391
+ <span className="material-icons text-lg">security</span>
392
+ Set Password
393
+ </button>
394
+ </div>
395
+ <p className="text-xs text-gray-500 dark:text-gray-400">
396
+ KJs will need this password to access the admin panel
397
+ </p>
398
+ </div>
399
+
400
+ <div
401
+ className={`px-4 py-2 rounded ${hasPassword ? 'bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200' : 'bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300'}`}
402
+ >
403
+ {hasPassword ? 'Admin password is set' : 'No admin password set'}
404
+ </div>
405
+ </div>
406
+ </div>
407
+
408
+ {/* Song Requests */}
409
+ <div className="bg-gray-50 dark:bg-gray-800 rounded-lg p-6">
410
+ <h3 className="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">
411
+ Song Requests
412
+ </h3>
413
+ <div className="space-y-4">
414
+ <div className="grid grid-cols-2 gap-4">
415
+ <div className="bg-white dark:bg-gray-700 rounded-lg p-4 text-center">
416
+ <div className="text-3xl font-bold text-blue-600 dark:text-blue-400">
417
+ {pendingRequests}
418
+ </div>
419
+ <div className="text-sm text-gray-600 dark:text-gray-400">Pending</div>
420
+ </div>
421
+ <div className="bg-white dark:bg-gray-700 rounded-lg p-4 text-center">
422
+ <div className="text-3xl font-bold text-gray-700 dark:text-gray-300">
423
+ {totalRequests}
424
+ </div>
425
+ <div className="text-sm text-gray-600 dark:text-gray-400">Total</div>
426
+ </div>
427
+ </div>
428
+ <div className="flex gap-2">
429
+ <button
430
+ className="flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded transition-colors flex-1"
431
+ onClick={handleOpenAdmin}
432
+ >
433
+ <span className="material-icons text-lg">admin_panel_settings</span>
434
+ Open Admin Panel
435
+ </button>
436
+ <button
437
+ className="flex items-center gap-2 px-4 py-2 bg-red-600 hover:bg-red-700 text-white rounded transition-colors flex-1"
438
+ onClick={handleClearRequests}
439
+ >
440
+ <span className="material-icons text-lg">delete_sweep</span>
441
+ Clear All Requests
442
+ </button>
443
+ </div>
444
+ </div>
445
+ </div>
446
+
447
+ {/* QR Code */}
448
+ {qrCodeDataUrl && (
449
+ <div className="bg-gray-50 dark:bg-gray-800 rounded-lg p-6">
450
+ <h3 className="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">
451
+ Quick Access QR Code
452
+ </h3>
453
+ <div className="flex flex-col items-center gap-4">
454
+ <img
455
+ src={qrCodeDataUrl}
456
+ alt="Server URL QR Code"
457
+ className="border-4 border-white rounded-lg shadow-lg"
458
+ style={{ width: '300px', height: '300px' }}
459
+ />
460
+ <p className="text-sm text-gray-600 dark:text-gray-400 text-center">
461
+ Scan this QR code to access the song request page
462
+ </p>
463
+ <p className="text-xs text-gray-500 dark:text-gray-500 text-center font-mono">
464
+ {serverUrl}
465
+ </p>
466
+ </div>
467
+ </div>
468
+ )}
469
+ </div>
470
+ </div>
471
+ );
472
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * SongInfoBarWrapper - Renderer-specific wrapper for SongInfoBar
3
+ * Manages state and bridge integration
4
+ */
5
+
6
+ import React, { useState, useEffect } from 'react';
7
+ import { SongInfoBar } from '../../shared/components/SongInfoBar.jsx';
8
+ import { UI_DEFAULTS } from '../../shared/defaults.js';
9
+
10
+ export function SongInfoBarWrapper({ bridge }) {
11
+ const [currentSong, setCurrentSong] = useState(null);
12
+ const [sidebarCollapsed, setSidebarCollapsed] = useState(UI_DEFAULTS.sidebarCollapsed);
13
+
14
+ useEffect(() => {
15
+ if (!bridge) return;
16
+
17
+ // Subscribe to current song changes
18
+ const unsubscribe = bridge.onCurrentSongChanged?.((song) => {
19
+ setCurrentSong(song);
20
+ });
21
+
22
+ // Fetch initial state
23
+ bridge
24
+ .getQueue?.()
25
+ .then((data) => {
26
+ if (data.currentSong) {
27
+ setCurrentSong(data.currentSong);
28
+ }
29
+ })
30
+ .catch(console.error);
31
+
32
+ // Load sidebar state
33
+ bridge.api?.settings
34
+ .get('sidebarCollapsed', UI_DEFAULTS.sidebarCollapsed)
35
+ .then((collapsed) => {
36
+ setSidebarCollapsed(collapsed);
37
+ // Apply initial state to sidebar
38
+ const sidebar = document.getElementById('app-sidebar');
39
+ if (sidebar && collapsed) {
40
+ sidebar.classList.add('-ml-80', 'w-0', 'p-0', 'border-0');
41
+ }
42
+ })
43
+ .catch(console.error);
44
+
45
+ return () => unsubscribe && unsubscribe();
46
+ }, [bridge]);
47
+
48
+ const handleMenuClick = async () => {
49
+ const sidebar = document.getElementById('app-sidebar');
50
+ if (!sidebar) return;
51
+
52
+ const newCollapsedState = !sidebarCollapsed;
53
+ setSidebarCollapsed(newCollapsedState);
54
+
55
+ // Toggle Tailwind classes
56
+ if (newCollapsedState) {
57
+ sidebar.classList.add('-ml-80', 'w-0', 'p-0', 'border-0');
58
+ } else {
59
+ sidebar.classList.remove('-ml-80', 'w-0', 'p-0', 'border-0');
60
+ }
61
+
62
+ // Save state
63
+ try {
64
+ await bridge.api?.settings.set('sidebarCollapsed', newCollapsedState);
65
+ } catch (error) {
66
+ console.error('Failed to save sidebar state:', error);
67
+ }
68
+ };
69
+
70
+ return (
71
+ <SongInfoBar
72
+ currentSong={currentSong}
73
+ onMenuClick={handleMenuClick}
74
+ sidebarCollapsed={sidebarCollapsed}
75
+ />
76
+ );
77
+ }
@@ -0,0 +1,92 @@
1
+ /**
2
+ * StatusBar - Renderer status bar component
3
+ *
4
+ * Displays status messages, server URL, latency, and xruns
5
+ */
6
+
7
+ import React, { useState, useEffect } from 'react';
8
+
9
+ export function StatusBar({ bridge }) {
10
+ const [statusText, _setStatusText] = useState('Ready');
11
+ const [webUrl, setWebUrl] = useState(null);
12
+ const [latency, setLatency] = useState(null);
13
+ const [xruns, setXruns] = useState(0);
14
+
15
+ useEffect(() => {
16
+ if (!bridge) return;
17
+
18
+ const unsubscribers = [];
19
+
20
+ // Listen for audio latency updates
21
+ if (bridge.onLatencyUpdate) {
22
+ const unsubLatency = bridge.onLatencyUpdate((latencyMs) => {
23
+ setLatency(latencyMs);
24
+ });
25
+ if (unsubLatency) unsubscribers.push(unsubLatency);
26
+ }
27
+
28
+ // Listen for xrun updates
29
+ if (bridge.onXRunUpdate) {
30
+ const unsubXrun = bridge.onXRunUpdate((count) => {
31
+ setXruns(count);
32
+ });
33
+ if (unsubXrun) unsubscribers.push(unsubXrun);
34
+ }
35
+
36
+ // Get initial server URL
37
+ if (bridge.getServerUrl) {
38
+ bridge
39
+ .getServerUrl()
40
+ .then((url) => {
41
+ setWebUrl(url || null);
42
+ })
43
+ .catch(console.error);
44
+ }
45
+
46
+ // Poll for server URL updates (every 5 seconds)
47
+ const pollInterval = setInterval(() => {
48
+ if (bridge.getServerUrl) {
49
+ bridge
50
+ .getServerUrl()
51
+ .then((url) => {
52
+ setWebUrl(url || null);
53
+ })
54
+ .catch(console.error);
55
+ }
56
+ }, 5000);
57
+
58
+ return () => {
59
+ unsubscribers.forEach((unsub) => unsub && unsub());
60
+ clearInterval(pollInterval);
61
+ };
62
+ }, [bridge]);
63
+
64
+ const handleUrlClick = () => {
65
+ if (webUrl && bridge?.openExternal) {
66
+ bridge.openExternal(webUrl);
67
+ }
68
+ };
69
+
70
+ return (
71
+ <div className="flex justify-between items-center h-6 bg-white dark:bg-gray-800 border-t border-gray-200 dark:border-gray-700 px-4 text-xs text-gray-600 dark:text-gray-400">
72
+ <div className="flex-1">
73
+ <span>{statusText}</span>
74
+ </div>
75
+ <div className="flex-1 text-center">
76
+ {webUrl && (
77
+ <span
78
+ className="text-blue-500 dark:text-blue-400 cursor-pointer hover:underline"
79
+ onClick={handleUrlClick}
80
+ title="Click to open in browser"
81
+ >
82
+ 🌐 {webUrl}
83
+ </span>
84
+ )}
85
+ </div>
86
+ <div className="flex-1 flex gap-4 justify-end">
87
+ <span>Latency: {latency !== null ? `${latency.toFixed(1)} ms` : '-- ms'}</span>
88
+ <span>XRuns: {xruns}</span>
89
+ </div>
90
+ </div>
91
+ );
92
+ }