@tomorrowos/sdk 0.2.5 → 0.3.1

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 (339) hide show
  1. package/dist/index.d.ts +3 -1
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +1 -0
  4. package/dist/playlist-catalog.d.ts +35 -0
  5. package/dist/playlist-catalog.d.ts.map +1 -0
  6. package/dist/playlist-catalog.js +133 -0
  7. package/dist/store/memory-store.d.ts +9 -1
  8. package/dist/store/memory-store.d.ts.map +1 -1
  9. package/dist/store/memory-store.js +35 -0
  10. package/dist/store/types.d.ts +41 -0
  11. package/dist/store/types.d.ts.map +1 -1
  12. package/dist/tomorrowos.d.ts +14 -0
  13. package/dist/tomorrowos.d.ts.map +1 -1
  14. package/dist/tomorrowos.js +137 -1
  15. package/package.json +1 -1
  16. package/templates/cms-starter/node_modules/.bin/esbuild +16 -0
  17. package/templates/cms-starter/node_modules/.bin/esbuild.cmd +17 -0
  18. package/templates/cms-starter/node_modules/.bin/esbuild.ps1 +28 -0
  19. package/templates/cms-starter/node_modules/.bin/tomorrowos +16 -0
  20. package/templates/cms-starter/node_modules/.bin/tomorrowos.cmd +17 -0
  21. package/templates/cms-starter/node_modules/.bin/tomorrowos.ps1 +28 -0
  22. package/templates/cms-starter/node_modules/.bin/tsc +16 -0
  23. package/templates/cms-starter/node_modules/.bin/tsc.cmd +17 -0
  24. package/templates/cms-starter/node_modules/.bin/tsc.ps1 +28 -0
  25. package/templates/cms-starter/node_modules/.bin/tsserver +16 -0
  26. package/templates/cms-starter/node_modules/.bin/tsserver.cmd +17 -0
  27. package/templates/cms-starter/node_modules/.bin/tsserver.ps1 +28 -0
  28. package/templates/cms-starter/node_modules/.bin/tsx +16 -0
  29. package/templates/cms-starter/node_modules/.bin/tsx.cmd +17 -0
  30. package/templates/cms-starter/node_modules/.bin/tsx.ps1 +28 -0
  31. package/templates/cms-starter/node_modules/.package-lock.json +140 -0
  32. package/templates/cms-starter/node_modules/@esbuild/win32-x64/README.md +3 -0
  33. package/templates/cms-starter/node_modules/@esbuild/win32-x64/esbuild.exe +0 -0
  34. package/templates/cms-starter/node_modules/@esbuild/win32-x64/package.json +20 -0
  35. package/templates/cms-starter/node_modules/@types/node/LICENSE +21 -0
  36. package/templates/cms-starter/node_modules/@types/node/README.md +15 -0
  37. package/templates/cms-starter/node_modules/@types/node/assert/strict.d.ts +8 -0
  38. package/templates/cms-starter/node_modules/@types/node/assert.d.ts +1062 -0
  39. package/templates/cms-starter/node_modules/@types/node/async_hooks.d.ts +605 -0
  40. package/templates/cms-starter/node_modules/@types/node/buffer.buffer.d.ts +471 -0
  41. package/templates/cms-starter/node_modules/@types/node/buffer.d.ts +1936 -0
  42. package/templates/cms-starter/node_modules/@types/node/child_process.d.ts +1475 -0
  43. package/templates/cms-starter/node_modules/@types/node/cluster.d.ts +577 -0
  44. package/templates/cms-starter/node_modules/@types/node/compatibility/disposable.d.ts +16 -0
  45. package/templates/cms-starter/node_modules/@types/node/compatibility/index.d.ts +9 -0
  46. package/templates/cms-starter/node_modules/@types/node/compatibility/indexable.d.ts +20 -0
  47. package/templates/cms-starter/node_modules/@types/node/compatibility/iterators.d.ts +21 -0
  48. package/templates/cms-starter/node_modules/@types/node/console.d.ts +452 -0
  49. package/templates/cms-starter/node_modules/@types/node/constants.d.ts +21 -0
  50. package/templates/cms-starter/node_modules/@types/node/crypto.d.ts +4590 -0
  51. package/templates/cms-starter/node_modules/@types/node/dgram.d.ts +597 -0
  52. package/templates/cms-starter/node_modules/@types/node/diagnostics_channel.d.ts +578 -0
  53. package/templates/cms-starter/node_modules/@types/node/dns/promises.d.ts +479 -0
  54. package/templates/cms-starter/node_modules/@types/node/dns.d.ts +871 -0
  55. package/templates/cms-starter/node_modules/@types/node/domain.d.ts +170 -0
  56. package/templates/cms-starter/node_modules/@types/node/events.d.ts +977 -0
  57. package/templates/cms-starter/node_modules/@types/node/fs/promises.d.ts +1270 -0
  58. package/templates/cms-starter/node_modules/@types/node/fs.d.ts +4375 -0
  59. package/templates/cms-starter/node_modules/@types/node/globals.d.ts +172 -0
  60. package/templates/cms-starter/node_modules/@types/node/globals.typedarray.d.ts +38 -0
  61. package/templates/cms-starter/node_modules/@types/node/http.d.ts +2049 -0
  62. package/templates/cms-starter/node_modules/@types/node/http2.d.ts +2708 -0
  63. package/templates/cms-starter/node_modules/@types/node/https.d.ts +578 -0
  64. package/templates/cms-starter/node_modules/@types/node/index.d.ts +93 -0
  65. package/templates/cms-starter/node_modules/@types/node/inspector.generated.d.ts +3966 -0
  66. package/templates/cms-starter/node_modules/@types/node/module.d.ts +539 -0
  67. package/templates/cms-starter/node_modules/@types/node/net.d.ts +1031 -0
  68. package/templates/cms-starter/node_modules/@types/node/os.d.ts +506 -0
  69. package/templates/cms-starter/node_modules/@types/node/package.json +140 -0
  70. package/templates/cms-starter/node_modules/@types/node/path.d.ts +200 -0
  71. package/templates/cms-starter/node_modules/@types/node/perf_hooks.d.ts +961 -0
  72. package/templates/cms-starter/node_modules/@types/node/process.d.ts +1961 -0
  73. package/templates/cms-starter/node_modules/@types/node/punycode.d.ts +117 -0
  74. package/templates/cms-starter/node_modules/@types/node/querystring.d.ts +152 -0
  75. package/templates/cms-starter/node_modules/@types/node/readline/promises.d.ts +162 -0
  76. package/templates/cms-starter/node_modules/@types/node/readline.d.ts +589 -0
  77. package/templates/cms-starter/node_modules/@types/node/repl.d.ts +430 -0
  78. package/templates/cms-starter/node_modules/@types/node/sea.d.ts +153 -0
  79. package/templates/cms-starter/node_modules/@types/node/stream/consumers.d.ts +38 -0
  80. package/templates/cms-starter/node_modules/@types/node/stream/promises.d.ts +90 -0
  81. package/templates/cms-starter/node_modules/@types/node/stream/web.d.ts +533 -0
  82. package/templates/cms-starter/node_modules/@types/node/stream.d.ts +1698 -0
  83. package/templates/cms-starter/node_modules/@types/node/string_decoder.d.ts +67 -0
  84. package/templates/cms-starter/node_modules/@types/node/test.d.ts +1787 -0
  85. package/templates/cms-starter/node_modules/@types/node/timers/promises.d.ts +108 -0
  86. package/templates/cms-starter/node_modules/@types/node/timers.d.ts +286 -0
  87. package/templates/cms-starter/node_modules/@types/node/tls.d.ts +1259 -0
  88. package/templates/cms-starter/node_modules/@types/node/trace_events.d.ts +197 -0
  89. package/templates/cms-starter/node_modules/@types/node/ts5.6/buffer.buffer.d.ts +468 -0
  90. package/templates/cms-starter/node_modules/@types/node/ts5.6/globals.typedarray.d.ts +34 -0
  91. package/templates/cms-starter/node_modules/@types/node/ts5.6/index.d.ts +93 -0
  92. package/templates/cms-starter/node_modules/@types/node/tty.d.ts +208 -0
  93. package/templates/cms-starter/node_modules/@types/node/url.d.ts +964 -0
  94. package/templates/cms-starter/node_modules/@types/node/util.d.ts +2331 -0
  95. package/templates/cms-starter/node_modules/@types/node/v8.d.ts +809 -0
  96. package/templates/cms-starter/node_modules/@types/node/vm.d.ts +1001 -0
  97. package/templates/cms-starter/node_modules/@types/node/wasi.d.ts +181 -0
  98. package/templates/cms-starter/node_modules/@types/node/web-globals/abortcontroller.d.ts +34 -0
  99. package/templates/cms-starter/node_modules/@types/node/web-globals/domexception.d.ts +68 -0
  100. package/templates/cms-starter/node_modules/@types/node/web-globals/events.d.ts +97 -0
  101. package/templates/cms-starter/node_modules/@types/node/web-globals/fetch.d.ts +55 -0
  102. package/templates/cms-starter/node_modules/@types/node/worker_threads.d.ts +715 -0
  103. package/templates/cms-starter/node_modules/@types/node/zlib.d.ts +598 -0
  104. package/templates/cms-starter/node_modules/esbuild/LICENSE.md +21 -0
  105. package/templates/cms-starter/node_modules/esbuild/README.md +3 -0
  106. package/templates/cms-starter/node_modules/esbuild/bin/esbuild +223 -0
  107. package/templates/cms-starter/node_modules/esbuild/install.js +300 -0
  108. package/templates/cms-starter/node_modules/esbuild/lib/main.d.ts +716 -0
  109. package/templates/cms-starter/node_modules/esbuild/lib/main.js +2532 -0
  110. package/templates/cms-starter/node_modules/esbuild/package.json +74 -0
  111. package/templates/cms-starter/node_modules/tsx/LICENSE +21 -0
  112. package/templates/cms-starter/node_modules/tsx/README.md +32 -0
  113. package/templates/cms-starter/node_modules/tsx/dist/cjs/api/index.cjs +1 -0
  114. package/templates/cms-starter/node_modules/tsx/dist/cjs/api/index.d.cts +35 -0
  115. package/templates/cms-starter/node_modules/tsx/dist/cjs/api/index.d.mts +35 -0
  116. package/templates/cms-starter/node_modules/tsx/dist/cjs/api/index.mjs +1 -0
  117. package/templates/cms-starter/node_modules/tsx/dist/cjs/index.cjs +1 -0
  118. package/templates/cms-starter/node_modules/tsx/dist/cjs/index.mjs +1 -0
  119. package/templates/cms-starter/node_modules/tsx/dist/cli.cjs +54 -0
  120. package/templates/cms-starter/node_modules/tsx/dist/cli.mjs +55 -0
  121. package/templates/cms-starter/node_modules/tsx/dist/client-D3mGB526.cjs +1 -0
  122. package/templates/cms-starter/node_modules/tsx/dist/client-D_mPDF5S.mjs +1 -0
  123. package/templates/cms-starter/node_modules/tsx/dist/esm/api/index.cjs +1 -0
  124. package/templates/cms-starter/node_modules/tsx/dist/esm/api/index.d.cts +35 -0
  125. package/templates/cms-starter/node_modules/tsx/dist/esm/api/index.d.mts +35 -0
  126. package/templates/cms-starter/node_modules/tsx/dist/esm/api/index.mjs +1 -0
  127. package/templates/cms-starter/node_modules/tsx/dist/esm/index.cjs +1 -0
  128. package/templates/cms-starter/node_modules/tsx/dist/esm/index.mjs +1 -0
  129. package/templates/cms-starter/node_modules/tsx/dist/get-pipe-path-D4YM6rQt.cjs +1 -0
  130. package/templates/cms-starter/node_modules/tsx/dist/get-pipe-path-_tAJyU_v.mjs +1 -0
  131. package/templates/cms-starter/node_modules/tsx/dist/index-BWFBUo6r.cjs +1 -0
  132. package/templates/cms-starter/node_modules/tsx/dist/index-D9F1FXzN.cjs +14 -0
  133. package/templates/cms-starter/node_modules/tsx/dist/index-XurvG3JN.mjs +14 -0
  134. package/templates/cms-starter/node_modules/tsx/dist/index-gbaejti9.mjs +1 -0
  135. package/templates/cms-starter/node_modules/tsx/dist/lexer-DQCqS3nf.mjs +3 -0
  136. package/templates/cms-starter/node_modules/tsx/dist/lexer-DgIbo0BU.cjs +3 -0
  137. package/templates/cms-starter/node_modules/tsx/dist/loader.cjs +1 -0
  138. package/templates/cms-starter/node_modules/tsx/dist/loader.mjs +1 -0
  139. package/templates/cms-starter/node_modules/tsx/dist/node-features-B9BBLzwu.mjs +1 -0
  140. package/templates/cms-starter/node_modules/tsx/dist/node-features-CQLdkVE6.cjs +1 -0
  141. package/templates/cms-starter/node_modules/tsx/dist/package-CGdS2_oX.cjs +1 -0
  142. package/templates/cms-starter/node_modules/tsx/dist/package-DyJMwVU5.mjs +1 -0
  143. package/templates/cms-starter/node_modules/tsx/dist/patch-repl.cjs +1 -0
  144. package/templates/cms-starter/node_modules/tsx/dist/patch-repl.mjs +1 -0
  145. package/templates/cms-starter/node_modules/tsx/dist/preflight.cjs +1 -0
  146. package/templates/cms-starter/node_modules/tsx/dist/preflight.mjs +1 -0
  147. package/templates/cms-starter/node_modules/tsx/dist/register-BOkp8V6j.cjs +10 -0
  148. package/templates/cms-starter/node_modules/tsx/dist/register-BnTWPeIB.mjs +10 -0
  149. package/templates/cms-starter/node_modules/tsx/dist/register-CHVGxKtC.cjs +2 -0
  150. package/templates/cms-starter/node_modules/tsx/dist/register-D_B8UL5H.mjs +2 -0
  151. package/templates/cms-starter/node_modules/tsx/dist/repl.cjs +3 -0
  152. package/templates/cms-starter/node_modules/tsx/dist/repl.mjs +3 -0
  153. package/templates/cms-starter/node_modules/tsx/dist/require-CjvaJWEr.cjs +1 -0
  154. package/templates/cms-starter/node_modules/tsx/dist/require-DzmC1hVr.mjs +1 -0
  155. package/templates/cms-starter/node_modules/tsx/dist/suppress-warnings.cjs +1 -0
  156. package/templates/cms-starter/node_modules/tsx/dist/suppress-warnings.mjs +1 -0
  157. package/templates/cms-starter/node_modules/tsx/dist/temporary-directory-B83uKxJF.cjs +1 -0
  158. package/templates/cms-starter/node_modules/tsx/dist/temporary-directory-BDDVQOvU.mjs +1 -0
  159. package/templates/cms-starter/node_modules/tsx/dist/types-Cxp8y2TL.d.ts +5 -0
  160. package/templates/cms-starter/node_modules/tsx/package.json +67 -0
  161. package/templates/cms-starter/node_modules/typescript/LICENSE.txt +55 -0
  162. package/templates/cms-starter/node_modules/typescript/README.md +50 -0
  163. package/templates/cms-starter/node_modules/typescript/SECURITY.md +41 -0
  164. package/templates/cms-starter/node_modules/typescript/ThirdPartyNoticeText.txt +193 -0
  165. package/templates/cms-starter/node_modules/typescript/bin/tsc +2 -0
  166. package/templates/cms-starter/node_modules/typescript/bin/tsserver +2 -0
  167. package/templates/cms-starter/node_modules/typescript/lib/_tsc.js +133818 -0
  168. package/templates/cms-starter/node_modules/typescript/lib/_tsserver.js +659 -0
  169. package/templates/cms-starter/node_modules/typescript/lib/_typingsInstaller.js +222 -0
  170. package/templates/cms-starter/node_modules/typescript/lib/cs/diagnosticMessages.generated.json +2122 -0
  171. package/templates/cms-starter/node_modules/typescript/lib/de/diagnosticMessages.generated.json +2122 -0
  172. package/templates/cms-starter/node_modules/typescript/lib/es/diagnosticMessages.generated.json +2122 -0
  173. package/templates/cms-starter/node_modules/typescript/lib/fr/diagnosticMessages.generated.json +2122 -0
  174. package/templates/cms-starter/node_modules/typescript/lib/it/diagnosticMessages.generated.json +2122 -0
  175. package/templates/cms-starter/node_modules/typescript/lib/ja/diagnosticMessages.generated.json +2122 -0
  176. package/templates/cms-starter/node_modules/typescript/lib/ko/diagnosticMessages.generated.json +2122 -0
  177. package/templates/cms-starter/node_modules/typescript/lib/lib.d.ts +22 -0
  178. package/templates/cms-starter/node_modules/typescript/lib/lib.decorators.d.ts +384 -0
  179. package/templates/cms-starter/node_modules/typescript/lib/lib.decorators.legacy.d.ts +22 -0
  180. package/templates/cms-starter/node_modules/typescript/lib/lib.dom.asynciterable.d.ts +41 -0
  181. package/templates/cms-starter/node_modules/typescript/lib/lib.dom.d.ts +39429 -0
  182. package/templates/cms-starter/node_modules/typescript/lib/lib.dom.iterable.d.ts +571 -0
  183. package/templates/cms-starter/node_modules/typescript/lib/lib.es2015.collection.d.ts +147 -0
  184. package/templates/cms-starter/node_modules/typescript/lib/lib.es2015.core.d.ts +597 -0
  185. package/templates/cms-starter/node_modules/typescript/lib/lib.es2015.d.ts +28 -0
  186. package/templates/cms-starter/node_modules/typescript/lib/lib.es2015.generator.d.ts +77 -0
  187. package/templates/cms-starter/node_modules/typescript/lib/lib.es2015.iterable.d.ts +605 -0
  188. package/templates/cms-starter/node_modules/typescript/lib/lib.es2015.promise.d.ts +81 -0
  189. package/templates/cms-starter/node_modules/typescript/lib/lib.es2015.proxy.d.ts +128 -0
  190. package/templates/cms-starter/node_modules/typescript/lib/lib.es2015.reflect.d.ts +144 -0
  191. package/templates/cms-starter/node_modules/typescript/lib/lib.es2015.symbol.d.ts +46 -0
  192. package/templates/cms-starter/node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts +326 -0
  193. package/templates/cms-starter/node_modules/typescript/lib/lib.es2016.array.include.d.ts +116 -0
  194. package/templates/cms-starter/node_modules/typescript/lib/lib.es2016.d.ts +21 -0
  195. package/templates/cms-starter/node_modules/typescript/lib/lib.es2016.full.d.ts +23 -0
  196. package/templates/cms-starter/node_modules/typescript/lib/lib.es2016.intl.d.ts +31 -0
  197. package/templates/cms-starter/node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts +21 -0
  198. package/templates/cms-starter/node_modules/typescript/lib/lib.es2017.d.ts +26 -0
  199. package/templates/cms-starter/node_modules/typescript/lib/lib.es2017.date.d.ts +31 -0
  200. package/templates/cms-starter/node_modules/typescript/lib/lib.es2017.full.d.ts +23 -0
  201. package/templates/cms-starter/node_modules/typescript/lib/lib.es2017.intl.d.ts +44 -0
  202. package/templates/cms-starter/node_modules/typescript/lib/lib.es2017.object.d.ts +49 -0
  203. package/templates/cms-starter/node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts +135 -0
  204. package/templates/cms-starter/node_modules/typescript/lib/lib.es2017.string.d.ts +45 -0
  205. package/templates/cms-starter/node_modules/typescript/lib/lib.es2017.typedarrays.d.ts +53 -0
  206. package/templates/cms-starter/node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts +77 -0
  207. package/templates/cms-starter/node_modules/typescript/lib/lib.es2018.asynciterable.d.ts +53 -0
  208. package/templates/cms-starter/node_modules/typescript/lib/lib.es2018.d.ts +24 -0
  209. package/templates/cms-starter/node_modules/typescript/lib/lib.es2018.full.d.ts +24 -0
  210. package/templates/cms-starter/node_modules/typescript/lib/lib.es2018.intl.d.ts +83 -0
  211. package/templates/cms-starter/node_modules/typescript/lib/lib.es2018.promise.d.ts +30 -0
  212. package/templates/cms-starter/node_modules/typescript/lib/lib.es2018.regexp.d.ts +37 -0
  213. package/templates/cms-starter/node_modules/typescript/lib/lib.es2019.array.d.ts +79 -0
  214. package/templates/cms-starter/node_modules/typescript/lib/lib.es2019.d.ts +24 -0
  215. package/templates/cms-starter/node_modules/typescript/lib/lib.es2019.full.d.ts +24 -0
  216. package/templates/cms-starter/node_modules/typescript/lib/lib.es2019.intl.d.ts +23 -0
  217. package/templates/cms-starter/node_modules/typescript/lib/lib.es2019.object.d.ts +33 -0
  218. package/templates/cms-starter/node_modules/typescript/lib/lib.es2019.string.d.ts +37 -0
  219. package/templates/cms-starter/node_modules/typescript/lib/lib.es2019.symbol.d.ts +24 -0
  220. package/templates/cms-starter/node_modules/typescript/lib/lib.es2020.bigint.d.ts +765 -0
  221. package/templates/cms-starter/node_modules/typescript/lib/lib.es2020.d.ts +27 -0
  222. package/templates/cms-starter/node_modules/typescript/lib/lib.es2020.date.d.ts +42 -0
  223. package/templates/cms-starter/node_modules/typescript/lib/lib.es2020.full.d.ts +24 -0
  224. package/templates/cms-starter/node_modules/typescript/lib/lib.es2020.intl.d.ts +474 -0
  225. package/templates/cms-starter/node_modules/typescript/lib/lib.es2020.number.d.ts +28 -0
  226. package/templates/cms-starter/node_modules/typescript/lib/lib.es2020.promise.d.ts +47 -0
  227. package/templates/cms-starter/node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts +99 -0
  228. package/templates/cms-starter/node_modules/typescript/lib/lib.es2020.string.d.ts +44 -0
  229. package/templates/cms-starter/node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts +41 -0
  230. package/templates/cms-starter/node_modules/typescript/lib/lib.es2021.d.ts +23 -0
  231. package/templates/cms-starter/node_modules/typescript/lib/lib.es2021.full.d.ts +24 -0
  232. package/templates/cms-starter/node_modules/typescript/lib/lib.es2021.intl.d.ts +166 -0
  233. package/templates/cms-starter/node_modules/typescript/lib/lib.es2021.promise.d.ts +48 -0
  234. package/templates/cms-starter/node_modules/typescript/lib/lib.es2021.string.d.ts +33 -0
  235. package/templates/cms-starter/node_modules/typescript/lib/lib.es2021.weakref.d.ts +78 -0
  236. package/templates/cms-starter/node_modules/typescript/lib/lib.es2022.array.d.ts +121 -0
  237. package/templates/cms-starter/node_modules/typescript/lib/lib.es2022.d.ts +25 -0
  238. package/templates/cms-starter/node_modules/typescript/lib/lib.es2022.error.d.ts +75 -0
  239. package/templates/cms-starter/node_modules/typescript/lib/lib.es2022.full.d.ts +24 -0
  240. package/templates/cms-starter/node_modules/typescript/lib/lib.es2022.intl.d.ts +145 -0
  241. package/templates/cms-starter/node_modules/typescript/lib/lib.es2022.object.d.ts +26 -0
  242. package/templates/cms-starter/node_modules/typescript/lib/lib.es2022.regexp.d.ts +39 -0
  243. package/templates/cms-starter/node_modules/typescript/lib/lib.es2022.string.d.ts +25 -0
  244. package/templates/cms-starter/node_modules/typescript/lib/lib.es2023.array.d.ts +924 -0
  245. package/templates/cms-starter/node_modules/typescript/lib/lib.es2023.collection.d.ts +21 -0
  246. package/templates/cms-starter/node_modules/typescript/lib/lib.es2023.d.ts +22 -0
  247. package/templates/cms-starter/node_modules/typescript/lib/lib.es2023.full.d.ts +24 -0
  248. package/templates/cms-starter/node_modules/typescript/lib/lib.es2023.intl.d.ts +56 -0
  249. package/templates/cms-starter/node_modules/typescript/lib/lib.es2024.arraybuffer.d.ts +65 -0
  250. package/templates/cms-starter/node_modules/typescript/lib/lib.es2024.collection.d.ts +29 -0
  251. package/templates/cms-starter/node_modules/typescript/lib/lib.es2024.d.ts +26 -0
  252. package/templates/cms-starter/node_modules/typescript/lib/lib.es2024.full.d.ts +24 -0
  253. package/templates/cms-starter/node_modules/typescript/lib/lib.es2024.object.d.ts +29 -0
  254. package/templates/cms-starter/node_modules/typescript/lib/lib.es2024.promise.d.ts +35 -0
  255. package/templates/cms-starter/node_modules/typescript/lib/lib.es2024.regexp.d.ts +25 -0
  256. package/templates/cms-starter/node_modules/typescript/lib/lib.es2024.sharedmemory.d.ts +68 -0
  257. package/templates/cms-starter/node_modules/typescript/lib/lib.es2024.string.d.ts +29 -0
  258. package/templates/cms-starter/node_modules/typescript/lib/lib.es5.d.ts +4601 -0
  259. package/templates/cms-starter/node_modules/typescript/lib/lib.es6.d.ts +23 -0
  260. package/templates/cms-starter/node_modules/typescript/lib/lib.esnext.array.d.ts +35 -0
  261. package/templates/cms-starter/node_modules/typescript/lib/lib.esnext.collection.d.ts +96 -0
  262. package/templates/cms-starter/node_modules/typescript/lib/lib.esnext.d.ts +29 -0
  263. package/templates/cms-starter/node_modules/typescript/lib/lib.esnext.decorators.d.ts +28 -0
  264. package/templates/cms-starter/node_modules/typescript/lib/lib.esnext.disposable.d.ts +193 -0
  265. package/templates/cms-starter/node_modules/typescript/lib/lib.esnext.error.d.ts +24 -0
  266. package/templates/cms-starter/node_modules/typescript/lib/lib.esnext.float16.d.ts +445 -0
  267. package/templates/cms-starter/node_modules/typescript/lib/lib.esnext.full.d.ts +24 -0
  268. package/templates/cms-starter/node_modules/typescript/lib/lib.esnext.intl.d.ts +21 -0
  269. package/templates/cms-starter/node_modules/typescript/lib/lib.esnext.iterator.d.ts +148 -0
  270. package/templates/cms-starter/node_modules/typescript/lib/lib.esnext.promise.d.ts +34 -0
  271. package/templates/cms-starter/node_modules/typescript/lib/lib.esnext.sharedmemory.d.ts +25 -0
  272. package/templates/cms-starter/node_modules/typescript/lib/lib.scripthost.d.ts +322 -0
  273. package/templates/cms-starter/node_modules/typescript/lib/lib.webworker.asynciterable.d.ts +41 -0
  274. package/templates/cms-starter/node_modules/typescript/lib/lib.webworker.d.ts +13150 -0
  275. package/templates/cms-starter/node_modules/typescript/lib/lib.webworker.importscripts.d.ts +23 -0
  276. package/templates/cms-starter/node_modules/typescript/lib/lib.webworker.iterable.d.ts +340 -0
  277. package/templates/cms-starter/node_modules/typescript/lib/pl/diagnosticMessages.generated.json +2122 -0
  278. package/templates/cms-starter/node_modules/typescript/lib/pt-br/diagnosticMessages.generated.json +2122 -0
  279. package/templates/cms-starter/node_modules/typescript/lib/ru/diagnosticMessages.generated.json +2122 -0
  280. package/templates/cms-starter/node_modules/typescript/lib/tr/diagnosticMessages.generated.json +2122 -0
  281. package/templates/cms-starter/node_modules/typescript/lib/tsc.js +8 -0
  282. package/templates/cms-starter/node_modules/typescript/lib/tsserver.js +8 -0
  283. package/templates/cms-starter/node_modules/typescript/lib/tsserverlibrary.d.ts +17 -0
  284. package/templates/cms-starter/node_modules/typescript/lib/tsserverlibrary.js +21 -0
  285. package/templates/cms-starter/node_modules/typescript/lib/typesMap.json +497 -0
  286. package/templates/cms-starter/node_modules/typescript/lib/typescript.d.ts +11437 -0
  287. package/templates/cms-starter/node_modules/typescript/lib/typescript.js +200276 -0
  288. package/templates/cms-starter/node_modules/typescript/lib/typingsInstaller.js +8 -0
  289. package/templates/cms-starter/node_modules/typescript/lib/watchGuard.js +53 -0
  290. package/templates/cms-starter/node_modules/typescript/lib/zh-cn/diagnosticMessages.generated.json +2122 -0
  291. package/templates/cms-starter/node_modules/typescript/lib/zh-tw/diagnosticMessages.generated.json +2122 -0
  292. package/templates/cms-starter/node_modules/typescript/package.json +120 -0
  293. package/templates/cms-starter/node_modules/undici-types/LICENSE +21 -0
  294. package/templates/cms-starter/node_modules/undici-types/README.md +6 -0
  295. package/templates/cms-starter/node_modules/undici-types/agent.d.ts +31 -0
  296. package/templates/cms-starter/node_modules/undici-types/api.d.ts +43 -0
  297. package/templates/cms-starter/node_modules/undici-types/balanced-pool.d.ts +29 -0
  298. package/templates/cms-starter/node_modules/undici-types/cache.d.ts +36 -0
  299. package/templates/cms-starter/node_modules/undici-types/client.d.ts +108 -0
  300. package/templates/cms-starter/node_modules/undici-types/connector.d.ts +34 -0
  301. package/templates/cms-starter/node_modules/undici-types/content-type.d.ts +21 -0
  302. package/templates/cms-starter/node_modules/undici-types/cookies.d.ts +28 -0
  303. package/templates/cms-starter/node_modules/undici-types/diagnostics-channel.d.ts +66 -0
  304. package/templates/cms-starter/node_modules/undici-types/dispatcher.d.ts +256 -0
  305. package/templates/cms-starter/node_modules/undici-types/env-http-proxy-agent.d.ts +21 -0
  306. package/templates/cms-starter/node_modules/undici-types/errors.d.ts +149 -0
  307. package/templates/cms-starter/node_modules/undici-types/eventsource.d.ts +61 -0
  308. package/templates/cms-starter/node_modules/undici-types/fetch.d.ts +209 -0
  309. package/templates/cms-starter/node_modules/undici-types/file.d.ts +39 -0
  310. package/templates/cms-starter/node_modules/undici-types/filereader.d.ts +54 -0
  311. package/templates/cms-starter/node_modules/undici-types/formdata.d.ts +108 -0
  312. package/templates/cms-starter/node_modules/undici-types/global-dispatcher.d.ts +9 -0
  313. package/templates/cms-starter/node_modules/undici-types/global-origin.d.ts +7 -0
  314. package/templates/cms-starter/node_modules/undici-types/handlers.d.ts +15 -0
  315. package/templates/cms-starter/node_modules/undici-types/header.d.ts +4 -0
  316. package/templates/cms-starter/node_modules/undici-types/index.d.ts +71 -0
  317. package/templates/cms-starter/node_modules/undici-types/interceptors.d.ts +17 -0
  318. package/templates/cms-starter/node_modules/undici-types/mock-agent.d.ts +50 -0
  319. package/templates/cms-starter/node_modules/undici-types/mock-client.d.ts +25 -0
  320. package/templates/cms-starter/node_modules/undici-types/mock-errors.d.ts +12 -0
  321. package/templates/cms-starter/node_modules/undici-types/mock-interceptor.d.ts +93 -0
  322. package/templates/cms-starter/node_modules/undici-types/mock-pool.d.ts +25 -0
  323. package/templates/cms-starter/node_modules/undici-types/package.json +55 -0
  324. package/templates/cms-starter/node_modules/undici-types/patch.d.ts +33 -0
  325. package/templates/cms-starter/node_modules/undici-types/pool-stats.d.ts +19 -0
  326. package/templates/cms-starter/node_modules/undici-types/pool.d.ts +39 -0
  327. package/templates/cms-starter/node_modules/undici-types/proxy-agent.d.ts +28 -0
  328. package/templates/cms-starter/node_modules/undici-types/readable.d.ts +65 -0
  329. package/templates/cms-starter/node_modules/undici-types/retry-agent.d.ts +8 -0
  330. package/templates/cms-starter/node_modules/undici-types/retry-handler.d.ts +116 -0
  331. package/templates/cms-starter/node_modules/undici-types/util.d.ts +18 -0
  332. package/templates/cms-starter/node_modules/undici-types/webidl.d.ts +228 -0
  333. package/templates/cms-starter/node_modules/undici-types/websocket.d.ts +150 -0
  334. package/templates/cms-starter/package-lock.json +592 -0
  335. package/templates/cms-starter/package.json +2 -2
  336. package/templates/cms-starter/public/index.html +42 -14
  337. package/templates/cms-starter/public/methods.js +505 -371
  338. package/templates/cms-starter/public/panel.css +130 -1
  339. package/templates/cms-starter/server.ts +7 -0
@@ -1,13 +1,23 @@
1
- const PANEL_PLAYLIST_KEY = "tomorrowos.panel.playlistDraft";
2
- const PANEL_SCHEDULE_KEY = "tomorrowos.panel.scheduleDraft";
3
1
  const PANEL_MEDIA_BASE_KEY = "tomorrowos.panel.mediaBaseUrl";
4
2
 
5
- /** @type {{ id: string, url: string, name: string, type: string, durationMs: number }[]} */
6
- let playlistItems = [];
3
+ /** @type {Array<Record<string, unknown>>} */
4
+ let playlistsCatalog = [];
7
5
 
8
6
  /** @type {Array<Record<string, unknown>>} */
9
7
  let devicesCache = [];
10
8
 
9
+ /** @type {string|null} */
10
+ let selectedPlaylistId = null;
11
+
12
+ /** True while creating a new playlist locally (assets allowed before Save). */
13
+ let playlistDraftActive = false;
14
+
15
+ /** @type {{ id: string, url: string, name: string, type: string, durationMs: number }[]} */
16
+ let editorItems = [];
17
+
18
+ /** @type {string|null} */
19
+ let publishModalDeviceId = null;
20
+
11
21
  let devicePollTimer = null;
12
22
 
13
23
  function escapeHtml(value) {
@@ -37,17 +47,9 @@ function formatDateTimeSeconds(iso) {
37
47
  if (!iso) return "—";
38
48
  const d = new Date(iso);
39
49
  if (Number.isNaN(d.getTime())) return "—";
40
- return d.toLocaleString(undefined, {
41
- year: "numeric",
42
- month: "short",
43
- day: "numeric",
44
- hour: "2-digit",
45
- minute: "2-digit",
46
- second: "2-digit"
47
- });
50
+ return d.toLocaleString();
48
51
  }
49
52
 
50
- /** TV uptime: now minus last boot time (device.lastBootAt). Not active when app is offline. */
51
53
  function formatDeviceOnlineLabel(device) {
52
54
  if (!device.connected) return "Not active";
53
55
  const bootIso = device.lastBootAt;
@@ -57,142 +59,6 @@ function formatDeviceOnlineLabel(device) {
57
59
  return formatDurationMs(Date.now() - bootMs);
58
60
  }
59
61
 
60
- async function fetchDevices() {
61
- try {
62
- const res = await fetch("/devices");
63
- const data = await res.json();
64
- if (Array.isArray(data.devices)) {
65
- devicesCache = data.devices;
66
- renderDeviceCards();
67
- }
68
- } catch (err) {
69
- showResult({ status: "failed", error: err.message });
70
- }
71
- }
72
-
73
- function renderDeviceCards() {
74
- const grid = document.getElementById("devicesGrid");
75
- if (!grid) return;
76
-
77
- grid.innerHTML = "";
78
-
79
- if (devicesCache.length === 0) {
80
- const empty = document.createElement("p");
81
- empty.className = "devices-empty";
82
- empty.textContent = "No paired devices yet. Enter a code above.";
83
- grid.appendChild(empty);
84
- return;
85
- }
86
-
87
- for (const device of devicesCache) {
88
- const card = document.createElement("article");
89
- card.className = "device-card";
90
- card.dataset.deviceId = device.deviceId;
91
-
92
- const header = document.createElement("div");
93
- header.className = "device-card-header";
94
-
95
- const led = document.createElement("span");
96
- led.className = `status-led ${device.connected ? "status-led--online" : "status-led--offline"}`;
97
- led.title = device.connected ? "Connected" : "Disconnected";
98
-
99
- const title = document.createElement("h3");
100
- title.className = "device-card-title";
101
- title.textContent = device.deviceName || "Screen";
102
-
103
- header.appendChild(led);
104
- header.appendChild(title);
105
-
106
- const meta = document.createElement("dl");
107
- meta.className = "device-meta";
108
-
109
- const rows = [
110
- ["Device ID", device.deviceId],
111
- ["System", device.system || device.platform || "—"],
112
- [
113
- "Device online",
114
- formatDeviceOnlineLabel(device)
115
- ],
116
- ["Last boot", formatDateTimeSeconds(device.lastBootAt)],
117
- ["Latest content push", formatDateTimeSeconds(device.lastPolicyPushAt)]
118
- ];
119
-
120
- for (const [label, value] of rows) {
121
- const row = document.createElement("div");
122
- row.className = "device-meta-row";
123
- const dt = document.createElement("dt");
124
- dt.textContent = label;
125
- const dd = document.createElement("dd");
126
- if (label === "Device online") {
127
- dd.className = "device-online-time";
128
- dd.title = "Current time minus this TV last boot time";
129
- }
130
- dd.textContent = value;
131
- row.appendChild(dt);
132
- row.appendChild(dd);
133
- meta.appendChild(row);
134
- }
135
-
136
- const actions = document.createElement("div");
137
- actions.className = "device-card-actions";
138
-
139
- const publishBtn = document.createElement("button");
140
- publishBtn.type = "button";
141
- publishBtn.className = "primary";
142
- publishBtn.textContent = "Publish";
143
- publishBtn.addEventListener("click", () => publishToDevice(device.deviceId));
144
-
145
- const infoBtn = document.createElement("button");
146
- infoBtn.type = "button";
147
- infoBtn.textContent = "Info";
148
- infoBtn.addEventListener("click", () => deviceAction(device.deviceId, "get-info"));
149
-
150
- const capsBtn = document.createElement("button");
151
- capsBtn.type = "button";
152
- capsBtn.textContent = "Capabilities";
153
- capsBtn.addEventListener("click", () =>
154
- deviceAction(device.deviceId, "get-capabilities")
155
- );
156
-
157
- const rebootBtn = document.createElement("button");
158
- rebootBtn.type = "button";
159
- rebootBtn.textContent = "Reboot";
160
- rebootBtn.addEventListener("click", () => deviceAction(device.deviceId, "reboot"));
161
-
162
- const clearBtn = document.createElement("button");
163
- clearBtn.type = "button";
164
- clearBtn.textContent = "Clear";
165
- clearBtn.addEventListener("click", () => deviceAction(device.deviceId, "content/clear"));
166
-
167
- const unpairBtn = document.createElement("button");
168
- unpairBtn.type = "button";
169
- unpairBtn.className = "danger";
170
- unpairBtn.textContent = "Unpair";
171
- unpairBtn.addEventListener("click", () => unpairDevice(device.deviceId));
172
-
173
- actions.appendChild(publishBtn);
174
- actions.appendChild(infoBtn);
175
- actions.appendChild(capsBtn);
176
- actions.appendChild(rebootBtn);
177
- actions.appendChild(clearBtn);
178
- actions.appendChild(unpairBtn);
179
-
180
- card.appendChild(header);
181
- card.appendChild(meta);
182
- card.appendChild(actions);
183
- grid.appendChild(card);
184
- }
185
- }
186
-
187
- function startDevicePolling() {
188
- if (devicePollTimer) clearInterval(devicePollTimer);
189
-
190
- void fetchDevices();
191
- devicePollTimer = setInterval(() => {
192
- void fetchDevices();
193
- }, 8000);
194
- }
195
-
196
62
  function showResult(data) {
197
63
  document.getElementById("result").textContent = JSON.stringify(data, null, 2);
198
64
  }
@@ -205,12 +71,9 @@ function isLocalPanelHost(hostname) {
205
71
  function normalizeMediaBaseUrl(raw) {
206
72
  let s = String(raw || "").trim();
207
73
  if (!s) return "";
208
- if (!/^https?:\/\//i.test(s)) {
209
- s = `http://${s}`;
210
- }
74
+ if (!/^https?:\/\//i.test(s)) s = `http://${s}`;
211
75
  try {
212
- const u = new URL(s);
213
- return u.origin;
76
+ return new URL(s).origin;
214
77
  } catch {
215
78
  return "";
216
79
  }
@@ -223,11 +86,7 @@ function getMediaBaseOrigin() {
223
86
  ""
224
87
  );
225
88
  if (fromInput) return fromInput;
226
-
227
- if (!isLocalPanelHost(window.location.hostname)) {
228
- return window.location.origin;
229
- }
230
-
89
+ if (!isLocalPanelHost(window.location.hostname)) return window.location.origin;
231
90
  return "";
232
91
  }
233
92
 
@@ -248,12 +107,9 @@ function absoluteMediaUrl(path) {
248
107
  const p = String(path || "").trim();
249
108
  if (!p) return "";
250
109
  if (/^https?:\/\//i.test(p)) return p;
251
-
252
110
  const base = getMediaBaseOrigin();
253
111
  if (!base) {
254
- throw new Error(
255
- "Local CMS only: set CMS URL for screens (your PC LAN IP, e.g. http://192.168.1.105:3000 — not localhost)."
256
- );
112
+ throw new Error("Set CMS URL for screens (LAN IP, not localhost).");
257
113
  }
258
114
  return `${base}${p.startsWith("/") ? p : `/${p}`}`;
259
115
  }
@@ -276,94 +132,181 @@ function defaultDurationMs(type) {
276
132
  }
277
133
 
278
134
  function normalizeDurationMs(item) {
279
- const maxMs = 3600 * 1000;
280
135
  const minMs = 1000;
136
+ const maxMs = 3600 * 1000;
281
137
  let ms = Number(item?.durationMs);
282
- if (!Number.isFinite(ms) || ms < minMs) {
283
- return defaultDurationMs(item?.type);
284
- }
285
- if (ms === 1000000) {
286
- return defaultDurationMs(item?.type);
287
- }
138
+ if (!Number.isFinite(ms) || ms < minMs) return defaultDurationMs(item?.type);
139
+ if (ms === 1000000) return defaultDurationMs(item?.type);
288
140
  return Math.min(maxMs, ms);
289
141
  }
290
142
 
291
- function savePlaylistDraft() {
292
- localStorage.setItem(PANEL_PLAYLIST_KEY, JSON.stringify(playlistItems));
143
+ function buildScheduleFromForm() {
144
+ const schedule = {};
145
+ const startDate = document.getElementById("scheduleStartDate")?.value?.trim();
146
+ const endDate = document.getElementById("scheduleEndDate")?.value?.trim();
147
+ const startTime = document.getElementById("scheduleStartTime")?.value?.trim();
148
+ const endTime = document.getElementById("scheduleEndTime")?.value?.trim();
149
+ const days = [...document.querySelectorAll(".day-checkbox:checked")].map((el) =>
150
+ Number(el.value)
151
+ );
152
+ if (startDate) schedule.startDate = startDate;
153
+ if (endDate) schedule.endDate = endDate;
154
+ if (startTime) schedule.start = startTime;
155
+ if (endTime) schedule.end = endTime;
156
+ if (days.length > 0) schedule.daysOfWeek = days;
157
+ return Object.keys(schedule).length > 0 ? schedule : undefined;
293
158
  }
294
159
 
295
- function saveScheduleDraft() {
296
- const draft = {
297
- startDate: document.getElementById("scheduleStartDate")?.value || "",
298
- endDate: document.getElementById("scheduleEndDate")?.value || "",
299
- startTime: document.getElementById("scheduleStartTime")?.value || "",
300
- endTime: document.getElementById("scheduleEndTime")?.value || "",
301
- days: [...document.querySelectorAll(".day-checkbox:checked")].map((el) => Number(el.value))
302
- };
303
- localStorage.setItem(PANEL_SCHEDULE_KEY, JSON.stringify(draft));
160
+ function loadScheduleIntoForm(schedule) {
161
+ const s = schedule || {};
162
+ document.getElementById("scheduleStartDate").value = s.startDate || "";
163
+ document.getElementById("scheduleEndDate").value = s.endDate || "";
164
+ document.getElementById("scheduleStartTime").value = s.start || "";
165
+ document.getElementById("scheduleEndTime").value = s.end || "";
166
+ document.querySelectorAll(".day-checkbox").forEach((el) => {
167
+ el.checked =
168
+ Array.isArray(s.daysOfWeek) && s.daysOfWeek.includes(Number(el.value));
169
+ });
170
+ }
171
+
172
+ function getSelectedPlaylist() {
173
+ return playlistsCatalog.find((p) => p.id === selectedPlaylistId) || null;
304
174
  }
305
175
 
306
- function loadScheduleDraft() {
176
+ async function fetchPlaylists() {
307
177
  try {
308
- const raw = localStorage.getItem(PANEL_SCHEDULE_KEY);
309
- if (!raw) return;
310
- const draft = JSON.parse(raw);
311
- if (draft.startDate) document.getElementById("scheduleStartDate").value = draft.startDate;
312
- if (draft.endDate) document.getElementById("scheduleEndDate").value = draft.endDate;
313
- if (draft.startTime) document.getElementById("scheduleStartTime").value = draft.startTime;
314
- if (draft.endTime) document.getElementById("scheduleEndTime").value = draft.endTime;
315
- document.querySelectorAll(".day-checkbox").forEach((el) => {
316
- el.checked = Array.isArray(draft.days) && draft.days.includes(Number(el.value));
178
+ const res = await fetch("/playlists");
179
+ let data = {};
180
+ try {
181
+ data = await res.json();
182
+ } catch {
183
+ data = {};
184
+ }
185
+ if (!res.ok) {
186
+ console.warn("[CMS] GET /playlists failed", res.status, data);
187
+ if (res.status === 404) {
188
+ showResult({
189
+ status: "failed",
190
+ error:
191
+ "CMS server is missing /playlists. Restart CMS with @tomorrowos/sdk 0.3.1 or newer."
192
+ });
193
+ }
194
+ return;
195
+ }
196
+ if (Array.isArray(data.playlists)) {
197
+ playlistsCatalog = data.playlists;
198
+ renderPlaylistCatalog();
199
+ if (selectedPlaylistId && !getSelectedPlaylist()) {
200
+ selectedPlaylistId = playlistsCatalog[0]?.id || null;
201
+ loadEditorFromSelection();
202
+ }
203
+ }
204
+ } catch (err) {
205
+ console.error("[CMS] fetchPlaylists:", err);
206
+ showResult({ status: "failed", error: err.message });
207
+ }
208
+ }
209
+
210
+ function renderPlaylistCatalog() {
211
+ const list = document.getElementById("playlistCatalog");
212
+ if (!list) return;
213
+ list.innerHTML = "";
214
+
215
+ if (playlistsCatalog.length === 0) {
216
+ const li = document.createElement("li");
217
+ li.className = "playlist-catalog-item";
218
+ li.textContent = "No playlists yet. Tap +.";
219
+ list.appendChild(li);
220
+ return;
221
+ }
222
+
223
+ for (const pl of playlistsCatalog) {
224
+ const li = document.createElement("li");
225
+ li.className = "playlist-catalog-item";
226
+ if (pl.id === selectedPlaylistId) li.classList.add("playlist-catalog-item--active");
227
+ li.innerHTML = `<strong>${escapeHtml(pl.name)}</strong><small>v${pl.version} · ${(pl.items || []).length} items</small>`;
228
+ li.addEventListener("click", () => {
229
+ playlistDraftActive = false;
230
+ selectedPlaylistId = pl.id;
231
+ loadEditorFromSelection();
232
+ renderPlaylistCatalog();
317
233
  });
318
- } catch {
319
- /* ignore */
234
+ list.appendChild(li);
320
235
  }
321
236
  }
322
237
 
323
- function loadPlaylistDraft() {
324
- try {
325
- const raw = localStorage.getItem(PANEL_PLAYLIST_KEY);
326
- if (!raw) return;
327
- const parsed = JSON.parse(raw);
328
- if (Array.isArray(parsed)) {
329
- playlistItems = parsed.map((item) => ({
330
- ...item,
331
- durationMs: normalizeDurationMs(item)
332
- }));
333
- savePlaylistDraft();
334
- renderPlaylist();
238
+ function isPlaylistEditorOpen() {
239
+ return playlistDraftActive || !!selectedPlaylistId;
240
+ }
241
+
242
+ function loadEditorFromSelection() {
243
+ const pl = getSelectedPlaylist();
244
+ const nameInput = document.getElementById("playlistName");
245
+ const editorTitle = document.getElementById("editorTitle");
246
+
247
+ if (!pl) {
248
+ if (playlistDraftActive) {
249
+ if (editorTitle) editorTitle.textContent = "New playlist";
250
+ renderEditorAssets();
251
+ return;
335
252
  }
336
- } catch {
337
- playlistItems = [];
253
+ playlistDraftActive = false;
254
+ if (editorTitle) editorTitle.textContent = "Playlist editor";
255
+ if (nameInput) nameInput.value = "";
256
+ editorItems = [];
257
+ loadScheduleIntoForm(null);
258
+ renderEditorAssets();
259
+ return;
338
260
  }
261
+
262
+ playlistDraftActive = false;
263
+ if (editorTitle) editorTitle.textContent = `Edit: ${pl.name}`;
264
+ if (nameInput) nameInput.value = pl.name || "";
265
+ loadScheduleIntoForm(pl.schedule);
266
+ editorItems = (pl.items || []).map((item) => ({
267
+ id: crypto.randomUUID(),
268
+ url: item.url,
269
+ name: item.url?.split("/").pop() || "asset",
270
+ type: item.type || "image",
271
+ durationMs: normalizeDurationMs(item)
272
+ }));
273
+ renderEditorAssets();
339
274
  }
340
275
 
341
- function renderPlaylist() {
276
+ function renderEditorAssets() {
342
277
  const list = document.getElementById("playlistList");
343
278
  const empty = document.getElementById("playlistEmpty");
344
279
  list.querySelectorAll(".playlist-item").forEach((el) => el.remove());
345
280
 
346
- if (playlistItems.length === 0) {
281
+ if (!isPlaylistEditorOpen()) {
347
282
  empty.classList.remove("hidden");
283
+ empty.textContent = "Select or create a playlist (Playlists +).";
284
+ return;
285
+ }
286
+
287
+ if (editorItems.length === 0) {
288
+ empty.classList.remove("hidden");
289
+ empty.textContent = "No assets yet. Tap + to upload.";
348
290
  return;
349
291
  }
350
292
 
351
293
  empty.classList.add("hidden");
352
294
 
353
- for (const item of playlistItems) {
295
+ for (const item of editorItems) {
354
296
  const li = document.createElement("li");
355
297
  li.className = "playlist-item";
356
- li.dataset.id = item.id;
357
298
 
358
299
  if (item.type === "image" || item.type === "video") {
359
300
  const thumb = document.createElement(item.type === "video" ? "video" : "img");
360
301
  thumb.className = "playlist-item-thumb";
361
- thumb.src = absoluteMediaUrl(item.url);
302
+ try {
303
+ thumb.src = absoluteMediaUrl(item.url);
304
+ } catch {
305
+ thumb.removeAttribute("src");
306
+ }
362
307
  if (item.type === "video") {
363
308
  thumb.muted = true;
364
309
  thumb.playsInline = true;
365
- } else {
366
- thumb.alt = item.name;
367
310
  }
368
311
  li.appendChild(thumb);
369
312
  }
@@ -378,20 +321,14 @@ function renderPlaylist() {
378
321
 
379
322
  const actions = document.createElement("div");
380
323
  actions.className = "playlist-item-actions";
381
-
382
- const durLabel = document.createElement("label");
383
- durLabel.style.fontSize = "0.75rem";
384
- durLabel.textContent = "sec ";
385
324
  const durInput = document.createElement("input");
386
325
  durInput.type = "number";
387
326
  durInput.min = "1";
388
327
  durInput.max = "3600";
389
328
  durInput.value = String(Math.round(item.durationMs / 1000));
390
329
  durInput.addEventListener("change", () => {
391
- const seconds = Math.min(3600, Math.max(1, Number(durInput.value) || 10));
392
- item.durationMs = seconds * 1000;
393
- meta.textContent = `${item.type} · ${seconds}s`;
394
- savePlaylistDraft();
330
+ item.durationMs = Math.min(3600, Math.max(1, Number(durInput.value) || 10)) * 1000;
331
+ meta.textContent = `${item.type} · ${Math.round(item.durationMs / 1000)}s`;
395
332
  });
396
333
 
397
334
  const removeBtn = document.createElement("button");
@@ -399,15 +336,12 @@ function renderPlaylist() {
399
336
  removeBtn.className = "danger";
400
337
  removeBtn.textContent = "Remove";
401
338
  removeBtn.addEventListener("click", () => {
402
- playlistItems = playlistItems.filter((x) => x.id !== item.id);
403
- savePlaylistDraft();
404
- renderPlaylist();
339
+ editorItems = editorItems.filter((x) => x.id !== item.id);
340
+ renderEditorAssets();
405
341
  });
406
342
 
407
- actions.appendChild(durLabel);
408
343
  actions.appendChild(durInput);
409
344
  actions.appendChild(removeBtn);
410
-
411
345
  li.appendChild(name);
412
346
  li.appendChild(meta);
413
347
  li.appendChild(actions);
@@ -415,11 +349,321 @@ function renderPlaylist() {
415
349
  }
416
350
  }
417
351
 
352
+ async function saveCurrentPlaylist() {
353
+ const name = String(document.getElementById("playlistName")?.value || "").trim();
354
+ if (!name) {
355
+ alert("Enter a playlist name.");
356
+ return;
357
+ }
358
+ if (editorItems.length === 0) {
359
+ alert("Add at least one asset before saving.");
360
+ return;
361
+ }
362
+
363
+ let items;
364
+ try {
365
+ items = editorItems.map((item) => ({
366
+ url: absoluteMediaUrl(item.url),
367
+ type: item.type,
368
+ durationMs: item.durationMs
369
+ }));
370
+ } catch (err) {
371
+ alert(err.message);
372
+ return;
373
+ }
374
+
375
+ const body = {
376
+ id: selectedPlaylistId || undefined,
377
+ name,
378
+ schedule: buildScheduleFromForm(),
379
+ items
380
+ };
381
+
382
+ const res = await fetch("/playlists", {
383
+ method: "POST",
384
+ headers: { "Content-Type": "application/json" },
385
+ body: JSON.stringify(body)
386
+ });
387
+ const data = await res.json();
388
+ showResult(data);
389
+
390
+ if (!res.ok) {
391
+ alert(data.error || "Save failed");
392
+ return;
393
+ }
394
+
395
+ playlistDraftActive = false;
396
+ selectedPlaylistId = data.playlist?.id || selectedPlaylistId;
397
+ await fetchPlaylists();
398
+ loadEditorFromSelection();
399
+ }
400
+
401
+ async function deleteCurrentPlaylist() {
402
+ if (!selectedPlaylistId) {
403
+ alert("Select a playlist to delete.");
404
+ return;
405
+ }
406
+ const pl = getSelectedPlaylist();
407
+ if (
408
+ !confirm(
409
+ `Delete playlist "${pl?.name}"? Devices already playing it keep their cached copy until Clear or reboot without sync. New devices cannot receive it.`
410
+ )
411
+ ) {
412
+ return;
413
+ }
414
+
415
+ const res = await fetch(`/playlists/${encodeURIComponent(selectedPlaylistId)}`, {
416
+ method: "DELETE"
417
+ });
418
+ const data = await res.json();
419
+ showResult(data);
420
+ if (!res.ok) {
421
+ alert(data.error || "Delete failed");
422
+ return;
423
+ }
424
+
425
+ selectedPlaylistId = null;
426
+ playlistDraftActive = false;
427
+ editorItems = [];
428
+ await fetchPlaylists();
429
+ loadEditorFromSelection();
430
+ }
431
+
432
+ function newPlaylistDraft() {
433
+ selectedPlaylistId = null;
434
+ playlistDraftActive = true;
435
+ const nameInput = document.getElementById("playlistName");
436
+ if (nameInput) {
437
+ nameInput.value = "";
438
+ nameInput.focus();
439
+ }
440
+ loadScheduleIntoForm(null);
441
+ editorItems = [];
442
+ renderPlaylistCatalog();
443
+ renderEditorAssets();
444
+ const editorTitle = document.getElementById("editorTitle");
445
+ if (editorTitle) editorTitle.textContent = "New playlist";
446
+ document.getElementById("playlistEditorSection")?.scrollIntoView({ behavior: "smooth", block: "start" });
447
+ showResult({
448
+ status: "draft",
449
+ message: "New playlist — enter a name, add assets with + (right), then Save playlist."
450
+ });
451
+ }
452
+
453
+ async function fetchDevices() {
454
+ try {
455
+ const res = await fetch("/devices");
456
+ const data = await res.json();
457
+ if (Array.isArray(data.devices)) {
458
+ devicesCache = data.devices;
459
+ renderDeviceCards();
460
+ }
461
+ } catch (err) {
462
+ showResult({ status: "failed", error: err.message });
463
+ }
464
+ }
465
+
466
+ function renderDeviceCards() {
467
+ const grid = document.getElementById("devicesGrid");
468
+ if (!grid) return;
469
+ grid.innerHTML = "";
470
+
471
+ if (devicesCache.length === 0) {
472
+ const empty = document.createElement("p");
473
+ empty.className = "devices-empty";
474
+ empty.textContent = "No paired devices yet.";
475
+ grid.appendChild(empty);
476
+ return;
477
+ }
478
+
479
+ for (const device of devicesCache) {
480
+ const card = document.createElement("article");
481
+ card.className = "device-card";
482
+
483
+ const header = document.createElement("div");
484
+ header.className = "device-card-header";
485
+ const led = document.createElement("span");
486
+ led.className = `status-led ${device.connected ? "status-led--online" : "status-led--offline"}`;
487
+ const title = document.createElement("h3");
488
+ title.className = "device-card-title";
489
+ title.textContent = device.deviceName || "Screen";
490
+ header.appendChild(led);
491
+ header.appendChild(title);
492
+
493
+ const published = document.createElement("ul");
494
+ published.className = "device-published-list";
495
+ const pubs = Array.isArray(device.publishedPlaylists) ? device.publishedPlaylists : [];
496
+ if (pubs.length === 0) {
497
+ const li = document.createElement("li");
498
+ li.textContent = "No playlists published";
499
+ published.appendChild(li);
500
+ } else {
501
+ for (const p of pubs) {
502
+ const li = document.createElement("li");
503
+ const label = document.createElement("span");
504
+ label.textContent = `${p.name} (v${p.version})`;
505
+ const rm = document.createElement("button");
506
+ rm.type = "button";
507
+ rm.textContent = "Remove";
508
+ rm.addEventListener("click", () => removePlaylistFromDevice(device.deviceId, p.playlistId));
509
+ li.appendChild(label);
510
+ li.appendChild(rm);
511
+ published.appendChild(li);
512
+ }
513
+ }
514
+
515
+ const meta = document.createElement("dl");
516
+ meta.className = "device-meta";
517
+ const rows = [
518
+ ["Device ID", device.deviceId],
519
+ ["System", device.system || device.platform || "—"],
520
+ ["Device online", formatDeviceOnlineLabel(device)],
521
+ ["Last boot", formatDateTimeSeconds(device.lastBootAt)],
522
+ ["Latest push", formatDateTimeSeconds(device.lastPolicyPushAt)]
523
+ ];
524
+ for (const [label, value] of rows) {
525
+ const row = document.createElement("div");
526
+ row.className = "device-meta-row";
527
+ const dt = document.createElement("dt");
528
+ dt.textContent = label;
529
+ const dd = document.createElement("dd");
530
+ dd.textContent = value;
531
+ row.appendChild(dt);
532
+ row.appendChild(dd);
533
+ meta.appendChild(row);
534
+ }
535
+
536
+ const actions = document.createElement("div");
537
+ actions.className = "device-card-actions";
538
+
539
+ const publishBtn = document.createElement("button");
540
+ publishBtn.type = "button";
541
+ publishBtn.className = "primary";
542
+ publishBtn.textContent = "Publish";
543
+ publishBtn.addEventListener("click", () => openPublishModal(device.deviceId));
544
+
545
+ const infoBtn = document.createElement("button");
546
+ infoBtn.type = "button";
547
+ infoBtn.textContent = "Info";
548
+ infoBtn.addEventListener("click", () => deviceAction(device.deviceId, "get-info"));
549
+
550
+ const rebootBtn = document.createElement("button");
551
+ rebootBtn.type = "button";
552
+ rebootBtn.textContent = "Reboot";
553
+ rebootBtn.addEventListener("click", () => deviceAction(device.deviceId, "reboot"));
554
+
555
+ const clearBtn = document.createElement("button");
556
+ clearBtn.type = "button";
557
+ clearBtn.textContent = "Clear";
558
+ clearBtn.addEventListener("click", () => deviceAction(device.deviceId, "content/clear"));
559
+
560
+ const unpairBtn = document.createElement("button");
561
+ unpairBtn.type = "button";
562
+ unpairBtn.className = "danger";
563
+ unpairBtn.textContent = "Unpair";
564
+ unpairBtn.addEventListener("click", () => unpairDevice(device.deviceId));
565
+
566
+ actions.appendChild(publishBtn);
567
+ actions.appendChild(infoBtn);
568
+ actions.appendChild(rebootBtn);
569
+ actions.appendChild(clearBtn);
570
+ actions.appendChild(unpairBtn);
571
+
572
+ card.appendChild(header);
573
+ card.appendChild(published);
574
+ card.appendChild(meta);
575
+ card.appendChild(actions);
576
+ grid.appendChild(card);
577
+ }
578
+ }
579
+
580
+ function openPublishModal(deviceId) {
581
+ publishModalDeviceId = deviceId;
582
+ const modal = document.getElementById("publishModal");
583
+ const checklist = document.getElementById("publishChecklist");
584
+ const hint = document.getElementById("publishModalHint");
585
+ if (!modal || !checklist) return;
586
+
587
+ if (playlistsCatalog.length === 0) {
588
+ alert("Create and save at least one playlist first.");
589
+ return;
590
+ }
591
+
592
+ hint.textContent = `Device ${deviceId} — select playlists to publish (snapshot at publish time).`;
593
+ checklist.innerHTML = "";
594
+
595
+ for (const pl of playlistsCatalog) {
596
+ const label = document.createElement("label");
597
+ const cb = document.createElement("input");
598
+ cb.type = "checkbox";
599
+ cb.value = pl.id;
600
+ cb.dataset.name = pl.name;
601
+ const pubs = devicesCache.find((d) => d.deviceId === deviceId)?.publishedPlaylists || [];
602
+ if (pubs.some((p) => p.playlistId === pl.id)) cb.checked = true;
603
+ label.appendChild(cb);
604
+ label.appendChild(document.createTextNode(` ${pl.name} (v${pl.version})`));
605
+ checklist.appendChild(label);
606
+ }
607
+
608
+ modal.classList.remove("hidden");
609
+ }
610
+
611
+ function closePublishModal() {
612
+ publishModalDeviceId = null;
613
+ document.getElementById("publishModal")?.classList.add("hidden");
614
+ }
615
+
616
+ async function confirmPublishModal() {
617
+ if (!publishModalDeviceId) return;
618
+ const ids = [
619
+ ...document.querySelectorAll("#publishChecklist input[type=checkbox]:checked")
620
+ ].map((el) => el.value);
621
+
622
+ if (ids.length === 0) {
623
+ alert("Select at least one playlist.");
624
+ return;
625
+ }
626
+
627
+ const res = await fetch(
628
+ `/device/${encodeURIComponent(publishModalDeviceId)}/assignments`,
629
+ {
630
+ method: "POST",
631
+ headers: { "Content-Type": "application/json" },
632
+ body: JSON.stringify({ playlistIds: ids })
633
+ }
634
+ );
635
+ const data = await res.json();
636
+ showResult({ deviceId: publishModalDeviceId, publish: data });
637
+ if (!res.ok) {
638
+ alert(data.error || "Publish failed");
639
+ return;
640
+ }
641
+ closePublishModal();
642
+ await fetchDevices();
643
+ }
644
+
645
+ async function removePlaylistFromDevice(deviceId, playlistId) {
646
+ if (!confirm("Remove this playlist from the device? Currently playing content may continue until Clear or failed reboot sync.")) {
647
+ return;
648
+ }
649
+ const res = await fetch(
650
+ `/device/${encodeURIComponent(deviceId)}/assignments/${encodeURIComponent(playlistId)}`,
651
+ { method: "DELETE" }
652
+ );
653
+ const data = await res.json();
654
+ showResult({ deviceId, remove: data });
655
+ if (!res.ok) {
656
+ alert(data.error || "Remove failed");
657
+ return;
658
+ }
659
+ await fetchDevices();
660
+ }
661
+
418
662
  async function uploadFile(file) {
419
663
  const q = new URLSearchParams({ filename: file.name });
420
664
  const res = await fetch(`/media/upload?${q.toString()}`, {
421
665
  method: "POST",
422
- headers: { "Content-Type": file.type || "application/octet-stream" },
666
+ headers: { "Content-Type": "application/octet-stream" },
423
667
  body: file
424
668
  });
425
669
  const data = await res.json();
@@ -430,219 +674,109 @@ async function uploadFile(file) {
430
674
  }
431
675
 
432
676
  async function addAssetFromFile(file) {
433
- showResult({ status: "uploading", filename: file.name });
677
+ if (!isPlaylistEditorOpen()) {
678
+ newPlaylistDraft();
679
+ }
434
680
  const data = await uploadFile(file);
435
681
  const type = inferMediaType(file.name, file.type);
436
- playlistItems.push({
682
+ editorItems.push({
437
683
  id: crypto.randomUUID(),
438
684
  url: data.url,
439
685
  name: file.name,
440
686
  type,
441
687
  durationMs: defaultDurationMs(type)
442
688
  });
443
- savePlaylistDraft();
444
- renderPlaylist();
689
+ renderEditorAssets();
445
690
  showResult({ status: "uploaded", ...data });
446
691
  }
447
692
 
448
- function buildSchedule() {
449
- const schedule = {};
450
- const startDate = document.getElementById("scheduleStartDate")?.value?.trim();
451
- const endDate = document.getElementById("scheduleEndDate")?.value?.trim();
452
- const startTime = document.getElementById("scheduleStartTime")?.value?.trim();
453
- const endTime = document.getElementById("scheduleEndTime")?.value?.trim();
454
- const days = [...document.querySelectorAll(".day-checkbox:checked")].map((el) =>
455
- Number(el.value)
456
- );
457
-
458
- if (startDate) schedule.startDate = startDate;
459
- if (endDate) schedule.endDate = endDate;
460
- if (startTime) schedule.start = startTime;
461
- if (endTime) schedule.end = endTime;
462
- if (days.length > 0) schedule.daysOfWeek = days;
463
-
464
- return Object.keys(schedule).length > 0 ? schedule : undefined;
465
- }
466
-
467
- function buildPolicyPayload() {
468
- const schedule = buildSchedule();
469
- const playlist = {
470
- id: "main",
471
- name: "Playlist",
472
- items: playlistItems.map((item) => ({
473
- url: absoluteMediaUrl(item.url),
474
- type: item.type,
475
- durationMs: item.durationMs
476
- }))
477
- };
478
- if (schedule) playlist.schedule = schedule;
479
-
480
- return {
481
- policy: {
482
- playlists: [playlist],
483
- fallback: { type: "brand" }
484
- }
485
- };
486
- }
487
-
488
693
  async function verify() {
489
694
  const code = String(document.getElementById("code").value || "")
490
695
  .trim()
491
696
  .toUpperCase()
492
697
  .replace(/[^0-9A-Z]/g, "");
493
-
494
698
  if (code.length !== 6) {
495
699
  showResult({ status: "failed", error: "Enter the 6-character code from the screen." });
496
700
  return;
497
701
  }
498
-
499
702
  const res = await fetch("/pairing/verify", {
500
703
  method: "POST",
501
704
  headers: { "Content-Type": "application/json" },
502
705
  body: JSON.stringify({ code })
503
706
  });
504
-
505
707
  const data = await res.json();
506
708
  showResult(data);
507
-
508
709
  if (data.deviceId) {
509
- const codeInput = document.getElementById("code");
510
- if (codeInput) codeInput.value = "";
710
+ document.getElementById("code").value = "";
511
711
  await fetchDevices();
512
712
  }
513
713
  }
514
714
 
515
715
  async function unpairDevice(deviceId) {
516
- if (
517
- !confirm(
518
- "Unpair this device? Its card will be removed; the screen keeps the same permanent pairing code."
519
- )
520
- ) {
521
- return;
522
- }
523
-
716
+ if (!confirm("Unpair this device?")) return;
524
717
  const res = await fetch("/pairing/unpair", {
525
718
  method: "POST",
526
719
  headers: { "Content-Type": "application/json" },
527
720
  body: JSON.stringify({ deviceId })
528
721
  });
529
-
530
722
  const data = await res.json();
531
723
  showResult(data);
532
-
533
- if (res.ok) {
534
- await fetchDevices();
535
- } else {
536
- alert(data.error || "Unpair failed");
537
- }
538
- }
539
-
540
- async function publishToDevice(deviceId) {
541
- if (!deviceId) {
542
- alert("Unknown device.");
543
- return;
544
- }
545
-
546
- if (playlistItems.length === 0) {
547
- alert("Add at least one asset to the playlist.");
548
- return;
549
- }
550
-
551
- const mediaBase = getMediaBaseOrigin();
552
- if (!mediaBase) {
553
- alert(
554
- "Local CMS only: set CMS URL for screens first (e.g. http://192.168.1.105:3000 — your PC LAN IP, not localhost)."
555
- );
556
- return;
557
- }
558
- if (isLocalPanelHost(new URL(mediaBase).hostname)) {
559
- const ok = confirm(
560
- "Media URLs use localhost. TVs cannot download from localhost unless the player runs on this PC. Continue anyway?"
561
- );
562
- if (!ok) return;
563
- }
564
-
565
- saveScheduleDraft();
566
- let payload;
567
- try {
568
- payload = buildPolicyPayload();
569
- } catch (err) {
570
- alert(err.message);
571
- return;
572
- }
573
-
574
- const res = await fetch(`/device/${deviceId}/content/set-policy`, {
575
- method: "POST",
576
- headers: { "Content-Type": "application/json" },
577
- body: JSON.stringify(payload)
578
- });
579
-
580
- const data = await res.json();
581
- showResult({ deviceId, publish: payload, response: data });
582
-
583
- if (res.ok) {
584
- await fetchDevices();
585
- }
724
+ if (res.ok) await fetchDevices();
586
725
  }
587
726
 
588
727
  async function deviceAction(deviceId, action) {
589
728
  if (!deviceId) return;
590
-
591
729
  if (action === "reboot" && !confirm("Reboot this device?")) return;
592
730
  if (action === "content/clear" && !confirm("Clear content on this device?")) return;
593
-
594
731
  const res = await fetch(`/device/${encodeURIComponent(deviceId)}/${action}`, {
595
732
  method: "POST"
596
733
  });
597
734
  const data = await res.json();
598
735
  showResult({ deviceId, action, ...data });
736
+ if (action === "reboot" && res.ok) await fetchDevices();
737
+ }
599
738
 
600
- if (action === "reboot" && res.ok) {
601
- await fetchDevices();
602
- }
739
+ function startDevicePolling() {
740
+ if (devicePollTimer) clearInterval(devicePollTimer);
741
+ void fetchDevices();
742
+ devicePollTimer = setInterval(() => void fetchDevices(), 8000);
603
743
  }
604
744
 
605
745
  document.addEventListener("DOMContentLoaded", () => {
606
746
  const savedMediaBase = localStorage.getItem(PANEL_MEDIA_BASE_KEY);
607
747
  const cmsBaseInput = document.getElementById("cmsDeviceBaseUrl");
608
- if (savedMediaBase && cmsBaseInput) {
609
- cmsBaseInput.value = savedMediaBase;
610
- } else if (cmsBaseInput && !isLocalPanelHost(window.location.hostname)) {
748
+ if (savedMediaBase && cmsBaseInput) cmsBaseInput.value = savedMediaBase;
749
+ else if (cmsBaseInput && !isLocalPanelHost(window.location.hostname)) {
611
750
  cmsBaseInput.value = window.location.origin;
612
751
  }
613
752
 
614
- loadPlaylistDraft();
615
- loadScheduleDraft();
753
+ void fetchPlaylists();
616
754
  startDevicePolling();
617
755
 
618
- document.getElementById("addAssetBtn").addEventListener("click", () => {
619
- document.getElementById("fileInput").click();
756
+ document.getElementById("newPlaylistBtn")?.addEventListener("click", newPlaylistDraft);
757
+ document.getElementById("savePlaylistBtn")?.addEventListener("click", () => void saveCurrentPlaylist());
758
+ document.getElementById("deletePlaylistBtn")?.addEventListener("click", () => void deleteCurrentPlaylist());
759
+ document.getElementById("publishConfirmBtn")?.addEventListener("click", () => void confirmPublishModal());
760
+
761
+ document.querySelectorAll("[data-close-modal]").forEach((el) => {
762
+ el.addEventListener("click", closePublishModal);
763
+ });
764
+
765
+ document.getElementById("addAssetBtn")?.addEventListener("click", () => {
766
+ if (!isPlaylistEditorOpen()) {
767
+ newPlaylistDraft();
768
+ }
769
+ document.getElementById("fileInput")?.click();
620
770
  });
621
771
 
622
- document.getElementById("fileInput").addEventListener("change", async (ev) => {
772
+ document.getElementById("fileInput")?.addEventListener("change", async (ev) => {
623
773
  const files = ev.target.files;
624
774
  if (!files?.length) return;
625
775
  try {
626
- for (const file of files) {
627
- await addAssetFromFile(file);
628
- }
776
+ for (const file of files) await addAssetFromFile(file);
629
777
  } catch (err) {
630
778
  alert(err.message);
631
- showResult({ status: "failed", error: err.message });
632
779
  }
633
780
  ev.target.value = "";
634
781
  });
635
-
636
- [
637
- "scheduleStartDate",
638
- "scheduleEndDate",
639
- "scheduleStartTime",
640
- "scheduleEndTime"
641
- ].forEach((id) => {
642
- document.getElementById(id)?.addEventListener("change", saveScheduleDraft);
643
- });
644
- document.querySelectorAll(".day-checkbox").forEach((el) => {
645
- el.addEventListener("change", saveScheduleDraft);
646
- });
647
-
648
782
  });