@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,144 @@
1
+ // modal overlay shown when a share link requires a knock before syncing.
2
+ // lets the user write a message to the owner and send the knock.
3
+ import { createSignal, Show } from "solid-js";
4
+ import { knockForDocAccess } from "../services/sharingService.js";
5
+ import { ensureSharingReady } from "../services/sharingService.js";
6
+
7
+ interface ShareLinkKnockPanelProps {
8
+ ownerNodeId: string;
9
+ docId: string;
10
+ title?: string;
11
+ ownerName?: string;
12
+ onAccepted: (docId: string) => void;
13
+ onDismiss: () => void;
14
+ }
15
+
16
+ export function ShareLinkKnockPanel(props: ShareLinkKnockPanelProps) {
17
+ const [message, setMessage] = createSignal("");
18
+ const [status, setStatus] = createSignal<
19
+ "idle" | "sending" | "pending" | "denied" | "error"
20
+ >("idle");
21
+ const [errorText, setErrorText] = createSignal("");
22
+
23
+ const handleSendKnock = async () => {
24
+ setStatus("sending");
25
+ setErrorText("");
26
+ try {
27
+ await ensureSharingReady();
28
+ const result = await knockForDocAccess(
29
+ props.ownerNodeId,
30
+ props.docId,
31
+ message(),
32
+ props.title
33
+ );
34
+ if (result.status === "accepted") {
35
+ props.onAccepted(props.docId);
36
+ } else if (result.status === "denied") {
37
+ setStatus("denied");
38
+ } else {
39
+ setStatus("pending");
40
+ }
41
+ } catch (err) {
42
+ setErrorText(err instanceof Error ? err.message : "knock failed");
43
+ setStatus("error");
44
+ }
45
+ };
46
+
47
+ const ownerLabel = () =>
48
+ props.ownerName || props.ownerNodeId.slice(0, 12) + "...";
49
+
50
+ return (
51
+ <div
52
+ data-testid="share-knock-panel"
53
+ class="fixed inset-0 flex items-center justify-center z-50 bg-black/80 backdrop-blur-sm"
54
+ >
55
+ <div class="bg-black border border-gray-700 p-6 max-w-sm w-full mx-4 space-y-4 font-mono">
56
+ <h2 class="text-white text-sm font-medium">knock to access playlist</h2>
57
+
58
+ <div class="space-y-1">
59
+ <p class="text-gray-400 text-xs">
60
+ <span class="text-gray-200">{ownerLabel()}</span> has shared{" "}
61
+ <Show when={props.title} fallback={<span>a playlist</span>}>
62
+ <span class="text-white">"{props.title}"</span>
63
+ </Show>{" "}
64
+ with access control enabled. send a knock to request access.
65
+ </p>
66
+ </div>
67
+
68
+ <Show
69
+ when={
70
+ status() === "idle" ||
71
+ status() === "sending" ||
72
+ status() === "error"
73
+ }
74
+ >
75
+ <div class="space-y-2">
76
+ <label class="block text-xs text-gray-400">
77
+ message to owner (optional)
78
+ </label>
79
+ <textarea
80
+ data-testid="input-knock-message"
81
+ value={message()}
82
+ onInput={(e) => setMessage(e.currentTarget.value)}
83
+ placeholder="hi, found your link and would love to listen..."
84
+ rows="3"
85
+ disabled={status() === "sending"}
86
+ class="w-full bg-black text-white text-xs border border-gray-700 px-2 py-1.5 focus:outline-none focus:border-magenta-500 resize-none disabled:opacity-50"
87
+ />
88
+ <Show when={errorText()}>
89
+ <p class="text-red-400 text-xs">{errorText()}</p>
90
+ </Show>
91
+ </div>
92
+
93
+ <div class="flex gap-2">
94
+ <button
95
+ data-testid="btn-send-knock"
96
+ onClick={() => void handleSendKnock()}
97
+ disabled={status() === "sending"}
98
+ class="flex-1 px-3 py-2 bg-magenta-500 hover:bg-magenta-600 disabled:bg-magenta-400 text-white text-sm transition-colors"
99
+ >
100
+ {status() === "sending" ? "sending..." : "send knock"}
101
+ </button>
102
+ <button
103
+ onClick={props.onDismiss}
104
+ disabled={status() === "sending"}
105
+ class="px-3 py-2 border border-gray-700 hover:border-gray-500 text-gray-400 hover:text-white text-sm transition-colors"
106
+ >
107
+ cancel
108
+ </button>
109
+ </div>
110
+ </Show>
111
+
112
+ <Show when={status() === "pending"}>
113
+ <div class="space-y-3">
114
+ <p class="text-gray-300 text-xs">
115
+ knock sent - waiting for{" "}
116
+ <span class="text-white">{ownerLabel()}</span> to accept. you can
117
+ dismiss this and try again later.
118
+ </p>
119
+ <button
120
+ onClick={props.onDismiss}
121
+ class="w-full px-3 py-2 border border-gray-700 hover:border-gray-500 text-gray-400 hover:text-white text-sm transition-colors"
122
+ >
123
+ dismiss
124
+ </button>
125
+ </div>
126
+ </Show>
127
+
128
+ <Show when={status() === "denied"}>
129
+ <div class="space-y-3">
130
+ <p class="text-red-400 text-xs">
131
+ access denied by the playlist owner.
132
+ </p>
133
+ <button
134
+ onClick={props.onDismiss}
135
+ class="w-full px-3 py-2 border border-gray-700 hover:border-gray-500 text-gray-400 hover:text-white text-sm transition-colors"
136
+ >
137
+ dismiss
138
+ </button>
139
+ </div>
140
+ </Show>
141
+ </div>
142
+ </div>
143
+ );
144
+ }