@freqhole/playlistz 0.0.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 (180) hide show
  1. package/.changeset/config.json +11 -0
  2. package/.changeset/nice-wolves-thank.md +5 -0
  3. package/.freqhole-versions.json +4 -0
  4. package/.github/copilot-instructions.md +201 -0
  5. package/.github/workflows/changesets.yml +50 -0
  6. package/.github/workflows/npm-publish.yml +124 -0
  7. package/.github/workflows/pr-checks.yml +103 -0
  8. package/README.md +30 -0
  9. package/build-component.js +141 -0
  10. package/build-zip-bundle-lib.js +44 -0
  11. package/config/playwright.config.ts +47 -0
  12. package/config/vite.config.ts +44 -0
  13. package/config/vitest.config.ts +39 -0
  14. package/dist/assets/automerge_wasm_bg-Cik4BF9l.wasm +0 -0
  15. package/dist/assets/index-CbOXzGiA.js +216 -0
  16. package/dist/assets/index-CbOXzGiA.js.map +1 -0
  17. package/dist/assets/index-TvJ6RFpy.css +1 -0
  18. package/dist/assets/midden-DceCrT_L.js +2 -0
  19. package/dist/assets/midden-DceCrT_L.js.map +1 -0
  20. package/dist/assets/midden_bg-BLhfGIU-.wasm +0 -0
  21. package/dist/index.html +55 -0
  22. package/dist/sw.js +134 -0
  23. package/docs/AUTOMERGE_P2P_PLAN.md +233 -0
  24. package/docs/COLLABORATIVE_SHARING_PLAN.md +188 -0
  25. package/docs/E2E_TESTID_PLAN.md +234 -0
  26. package/docs/IROH_P2P_PLAN.md +302 -0
  27. package/docs/ROADMAP.md +695 -0
  28. package/docs/TODO.md +167 -0
  29. package/docs/bundle-embedding-plan.md +134 -0
  30. package/docs/standalone-refactor.md +184 -0
  31. package/e2e/all-playlists.spec.ts +220 -0
  32. package/e2e/audio-player.spec.ts +226 -0
  33. package/e2e/collaborative-features.spec.ts +229 -0
  34. package/e2e/contexts.ts +238 -0
  35. package/e2e/edit-panel.spec.ts +87 -0
  36. package/e2e/fixtures/bare-glitch-1s.m4a +0 -0
  37. package/e2e/fixtures/bare-glitch-1s.mp3 +0 -0
  38. package/e2e/fixtures/bare-glitch-1s.ogg +0 -0
  39. package/e2e/fixtures/chord-stack-3s.wav +0 -0
  40. package/e2e/fixtures/cover-anim.gif +0 -0
  41. package/e2e/fixtures/cover-blue.png +0 -0
  42. package/e2e/fixtures/cover-checkers.png +0 -0
  43. package/e2e/fixtures/cover-gradient.jpg +0 -0
  44. package/e2e/fixtures/cover-mono.gif +0 -0
  45. package/e2e/fixtures/cover-noise.png +0 -0
  46. package/e2e/fixtures/cover-plasma.webp +0 -0
  47. package/e2e/fixtures/cover-portrait.jpg +0 -0
  48. package/e2e/fixtures/cover-red.png +0 -0
  49. package/e2e/fixtures/cover-thumb.jpg +0 -0
  50. package/e2e/fixtures/cover-wide.webp +0 -0
  51. package/e2e/fixtures/generate.mjs +257 -0
  52. package/e2e/fixtures/long-drone-90s.mp3 +0 -0
  53. package/e2e/fixtures/noisy-binaural-8s.mp3 +0 -0
  54. package/e2e/fixtures/tagged-a3-4s.m4a +0 -0
  55. package/e2e/fixtures/tagged-a3-4s.mp3 +0 -0
  56. package/e2e/fixtures/tagged-a3-4s.ogg +0 -0
  57. package/e2e/fixtures/tagged-c5-3s.m4a +0 -0
  58. package/e2e/fixtures/tagged-c5-3s.mp3 +0 -0
  59. package/e2e/fixtures/tagged-c5-3s.ogg +0 -0
  60. package/e2e/fixtures/tagged-f4-6s.m4a +0 -0
  61. package/e2e/fixtures/tagged-f4-6s.mp3 +0 -0
  62. package/e2e/fixtures/tagged-f4-6s.ogg +0 -0
  63. package/e2e/fixtures/tone-220hz-10s.wav +0 -0
  64. package/e2e/fixtures/tone-440hz-2s.wav +0 -0
  65. package/e2e/fixtures/tone-880hz-5s.wav +0 -0
  66. package/e2e/fixtures/tone-stereo-3s.wav +0 -0
  67. package/e2e/fixtures/user-provided/README.md +1 -0
  68. package/e2e/helpers/app.ts +143 -0
  69. package/e2e/helpers/hooks.ts +133 -0
  70. package/e2e/helpers/index.ts +12 -0
  71. package/e2e/helpers/media.ts +125 -0
  72. package/e2e/helpers.ts +10 -0
  73. package/e2e/p2p-collaboration.spec.ts +356 -0
  74. package/e2e/p2p-multi-peer.spec.ts +723 -0
  75. package/e2e/p2p-states.spec.ts +302 -0
  76. package/e2e/playback.spec.ts +56 -0
  77. package/e2e/playlist-crud.spec.ts +126 -0
  78. package/e2e/share-link-autoplay.spec.ts +129 -0
  79. package/e2e/sharing-access.spec.ts +205 -0
  80. package/e2e/sharing.spec.ts +195 -0
  81. package/e2e/song-cache-state.spec.ts +202 -0
  82. package/e2e/zip-bundle.spec.ts +855 -0
  83. package/eslint.config.js +114 -0
  84. package/index.html +54 -0
  85. package/package.json +119 -0
  86. package/public/sw.js +134 -0
  87. package/scripts/use-local.mjs +37 -0
  88. package/scripts/use-published.mjs +37 -0
  89. package/src/App.tsx +9 -0
  90. package/src/cli/check.ts +164 -0
  91. package/src/cli/generate.ts +184 -0
  92. package/src/cli/http.ts +88 -0
  93. package/src/cli/index.ts +65 -0
  94. package/src/cli/init.ts +18 -0
  95. package/src/components/AllPlaylistsPanel.tsx +713 -0
  96. package/src/components/AudioPlayer.tsx +122 -0
  97. package/src/components/MarqueeText.tsx +101 -0
  98. package/src/components/PlaylistCoverModal.tsx +519 -0
  99. package/src/components/PlaylistEditPanel.tsx +803 -0
  100. package/src/components/PlaylistSharePanel.tsx +1020 -0
  101. package/src/components/ShareLinkKnockPanel.tsx +144 -0
  102. package/src/components/SharePanel.tsx +584 -0
  103. package/src/components/SongEditModal.tsx +453 -0
  104. package/src/components/SongEditPanel.tsx +578 -0
  105. package/src/components/SongRow.tsx +689 -0
  106. package/src/components/index.tsx +494 -0
  107. package/src/components/playlist/index.tsx +1203 -0
  108. package/src/context/PlaylistzContext.tsx +74 -0
  109. package/src/dev-hooks.ts +35 -0
  110. package/src/hooks/createDocIndexQuery.ts +53 -0
  111. package/src/hooks/createDocStore.test.ts +303 -0
  112. package/src/hooks/createDocStore.ts +90 -0
  113. package/src/hooks/useDragAndDrop.test.ts +474 -0
  114. package/src/hooks/useDragAndDrop.ts +400 -0
  115. package/src/hooks/useImageModal.test.ts +174 -0
  116. package/src/hooks/useImageModal.ts +201 -0
  117. package/src/hooks/usePlaylistManager.test.ts +453 -0
  118. package/src/hooks/usePlaylistManager.ts +685 -0
  119. package/src/hooks/usePlaylistsQuery.test.tsx +120 -0
  120. package/src/hooks/usePlaylistsQuery.ts +44 -0
  121. package/src/hooks/useSongState.test.ts +236 -0
  122. package/src/hooks/useSongState.ts +114 -0
  123. package/src/hooks/useUIState.ts +71 -0
  124. package/src/index.tsx +18 -0
  125. package/src/services/audioService.dev.ts +22 -0
  126. package/src/services/audioService.test.ts +1226 -0
  127. package/src/services/audioService.ts +1395 -0
  128. package/src/services/automergeRepo.test.ts +269 -0
  129. package/src/services/automergeRepo.ts +226 -0
  130. package/src/services/blobTransferService.dev.ts +119 -0
  131. package/src/services/blobTransferService.test.ts +441 -0
  132. package/src/services/blobTransferService.ts +702 -0
  133. package/src/services/docIndexService.test.ts +179 -0
  134. package/src/services/docIndexService.ts +118 -0
  135. package/src/services/fileProcessingService.test.ts +554 -0
  136. package/src/services/fileProcessingService.ts +239 -0
  137. package/src/services/imageService.test.ts +701 -0
  138. package/src/services/imageService.ts +365 -0
  139. package/src/services/indexedDBService.integration.test.ts +104 -0
  140. package/src/services/indexedDBService.test.ts +202 -0
  141. package/src/services/indexedDBService.ts +436 -0
  142. package/src/services/offlineService.test.ts +661 -0
  143. package/src/services/offlineService.ts +382 -0
  144. package/src/services/p2pService.test.ts +305 -0
  145. package/src/services/p2pService.ts +344 -0
  146. package/src/services/playlistDocService.test.ts +448 -0
  147. package/src/services/playlistDocService.ts +707 -0
  148. package/src/services/playlistDownloadService.test.ts +674 -0
  149. package/src/services/playlistDownloadService.ts +389 -0
  150. package/src/services/sharingService.test.ts +812 -0
  151. package/src/services/sharingService.ts +1073 -0
  152. package/src/services/sharingState.ts +161 -0
  153. package/src/services/songReactivity.test.ts +620 -0
  154. package/src/services/songReactivity.ts +145 -0
  155. package/src/services/standaloneService.test.ts +1025 -0
  156. package/src/services/standaloneService.ts +588 -0
  157. package/src/services/streamingAudioService.test.ts +275 -0
  158. package/src/services/streamingAudioService.ts +166 -0
  159. package/src/styles.css +428 -0
  160. package/src/test-setup.ts +547 -0
  161. package/src/types/global.d.ts +40 -0
  162. package/src/types/playlist.ts +99 -0
  163. package/src/utils/hashUtils.ts +41 -0
  164. package/src/utils/log.ts +97 -0
  165. package/src/utils/m3u.test.ts +172 -0
  166. package/src/utils/m3u.ts +136 -0
  167. package/src/utils/mockData.ts +166 -0
  168. package/src/utils/standaloneTemplates.test.ts +175 -0
  169. package/src/utils/standaloneTemplates.ts +83 -0
  170. package/src/utils/swTemplate.ts +84 -0
  171. package/src/utils/timeUtils.ts +166 -0
  172. package/src/utils/typeGuards.ts +171 -0
  173. package/src/web-component.tsx +98 -0
  174. package/src/zip-bundle/index.ts +7 -0
  175. package/src/zip-bundle/m3u.ts +45 -0
  176. package/src/zip-bundle/types.ts +50 -0
  177. package/src/zip-bundle/utils.ts +33 -0
  178. package/src/zip-bundle/zipBuilder.ts +309 -0
  179. package/tailwind.config.js +55 -0
  180. package/tsconfig.json +43 -0
@@ -0,0 +1,161 @@
1
+ // reactive sharing state shared across the UI: p2p endpoint readiness,
2
+ // pending knock count, connected peer count, active transfer flag, and
3
+ // the persisted "endpoint enabled" toggle.
4
+ //
5
+ // module-level solid signals so every component sees the same state.
6
+ // call initSharingState() from any component that reads the signals.
7
+
8
+ import { createSignal } from "solid-js";
9
+ import {
10
+ getIdentity,
11
+ hasExistingIdentity,
12
+ onIdentityChange,
13
+ stopP2P,
14
+ } from "./p2pService.js";
15
+ import {
16
+ getInboundKnocks,
17
+ getOutboundKnocks,
18
+ onKnocksChanged,
19
+ ensureSharingReady,
20
+ } from "./sharingService.js";
21
+ import { loadSetting, saveSetting } from "./indexedDBService.js";
22
+ import { getIrohAdapter } from "./automergeRepo.js";
23
+ import {
24
+ onTransferCountChange,
25
+ getActiveTransferCount,
26
+ } from "./blobTransferService.js";
27
+
28
+ const ENDPOINT_ENABLED_KEY = "p2p:endpoint_enabled";
29
+
30
+ const [sharingReady, setSharingReady] = createSignal(false);
31
+ const [pendingKnockCount, setPendingKnockCount] = createSignal(0);
32
+ const [endpointEnabled, setEndpointEnabled] = createSignal(false);
33
+ const [connectedPeerCount, setConnectedPeerCount] = createSignal(0);
34
+ const [isTransferring, setIsTransferring] = createSignal(false);
35
+ // true once a p2p identity has been created (persisted); stays true even
36
+ // when the endpoint is toggled off. used to hide the "enable" button.
37
+ const [hasP2pIdentity, setHasP2pIdentity] = createSignal(
38
+ !!getIdentity()?.node_id
39
+ );
40
+ // number of outbound knocks we sent that are still pending (waiting for owner
41
+ // to accept). shown as a badge on the share button so the user can follow up.
42
+ const [outboundPendingCount, setOutboundPendingCount] = createSignal(0);
43
+
44
+ export {
45
+ sharingReady,
46
+ pendingKnockCount,
47
+ outboundPendingCount,
48
+ endpointEnabled,
49
+ connectedPeerCount,
50
+ isTransferring,
51
+ hasP2pIdentity,
52
+ };
53
+
54
+ let initialized = false;
55
+ let connPollTimer: ReturnType<typeof setInterval> | null = null;
56
+ let _unsubTransfer: (() => void) | null = null;
57
+
58
+ async function refreshKnockCount(): Promise<void> {
59
+ try {
60
+ const inbound = await getInboundKnocks();
61
+ setPendingKnockCount(inbound.filter((k) => k.status === "pending").length);
62
+ const outbound = await getOutboundKnocks();
63
+ setOutboundPendingCount(outbound.filter((k) => k.status === "pending").length);
64
+ } catch {
65
+ // idb unavailable (early boot)
66
+ }
67
+ }
68
+
69
+ function refreshConnSummary(): void {
70
+ try {
71
+ const summary = getIrohAdapter().getConnectionSummary();
72
+ setConnectedPeerCount(summary.connected);
73
+ } catch {
74
+ // adapter not ready yet
75
+ }
76
+ }
77
+
78
+ /**
79
+ * start tracking p2p readiness, knock inbox, connection count, and
80
+ * active transfers. idempotent - safe to call from every component.
81
+ */
82
+ export function initSharingState(): void {
83
+ if (initialized) return;
84
+ initialized = true;
85
+
86
+ onKnocksChanged(() => void refreshKnockCount());
87
+ void refreshKnockCount();
88
+
89
+ // poll connection count every 3s
90
+ connPollTimer = setInterval(refreshConnSummary, 3000);
91
+ refreshConnSummary();
92
+
93
+ // track active blob transfers
94
+ _unsubTransfer = onTransferCountChange(() => {
95
+ setIsTransferring(getActiveTransferCount() > 0);
96
+ });
97
+
98
+ // load persisted endpoint setting first, then decide whether to auto-start
99
+ void loadSetting<boolean>(ENDPOINT_ENABLED_KEY).then((persisted) => {
100
+ // identity may already be in-memory (populated by a previous module load)
101
+ const hasIdentity = !!getIdentity()?.node_id;
102
+ if (hasIdentity) setHasP2pIdentity(true);
103
+ else {
104
+ void hasExistingIdentity().then((exists) => {
105
+ if (exists) setHasP2pIdentity(true);
106
+ });
107
+ }
108
+
109
+ // only consider "enabled" if the user explicitly turned it on (persisted=true).
110
+ // null/undefined means never set - treat as disabled.
111
+ if (persisted === true) {
112
+ setEndpointEnabled(true);
113
+ void ensureSharingReady()
114
+ .then(() => setSharingReady(true))
115
+ .catch(() => {
116
+ // endpoint may fail silently on boot
117
+ });
118
+ }
119
+
120
+ // keep hasP2pIdentity up to date as identity resolves asynchronously
121
+ onIdentityChange((identity) => {
122
+ if (identity?.node_id) setHasP2pIdentity(true);
123
+ });
124
+ });
125
+ }
126
+
127
+ /**
128
+ * toggle the iroh endpoint on or off.
129
+ * persists the choice to indexeddb so the next page load respects it.
130
+ */
131
+ export async function toggleEndpoint(): Promise<void> {
132
+ const next = !endpointEnabled();
133
+ setEndpointEnabled(next);
134
+ await saveSetting(ENDPOINT_ENABLED_KEY, next);
135
+ if (next) {
136
+ await ensureSharingReady();
137
+ setSharingReady(true);
138
+ setHasP2pIdentity(true);
139
+ } else {
140
+ stopP2P();
141
+ setSharingReady(false);
142
+ setConnectedPeerCount(0);
143
+ }
144
+ }
145
+
146
+ /** reset module state. for use in tests only. */
147
+ export function _resetSharingStateForTests(): void {
148
+ initialized = false;
149
+ setSharingReady(false);
150
+ setPendingKnockCount(0);
151
+ setEndpointEnabled(false);
152
+ setConnectedPeerCount(0);
153
+ setIsTransferring(false);
154
+ setHasP2pIdentity(false);
155
+ if (connPollTimer) {
156
+ clearInterval(connPollTimer);
157
+ connPollTimer = null;
158
+ }
159
+ _unsubTransfer?.();
160
+ _unsubTransfer = null;
161
+ }