@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.
- package/.changeset/config.json +11 -0
- package/.changeset/nice-wolves-thank.md +5 -0
- package/.freqhole-versions.json +4 -0
- package/.github/copilot-instructions.md +201 -0
- package/.github/workflows/changesets.yml +50 -0
- package/.github/workflows/npm-publish.yml +124 -0
- package/.github/workflows/pr-checks.yml +103 -0
- package/README.md +30 -0
- package/build-component.js +141 -0
- package/build-zip-bundle-lib.js +44 -0
- package/config/playwright.config.ts +47 -0
- package/config/vite.config.ts +44 -0
- package/config/vitest.config.ts +39 -0
- package/dist/assets/automerge_wasm_bg-Cik4BF9l.wasm +0 -0
- package/dist/assets/index-CbOXzGiA.js +216 -0
- package/dist/assets/index-CbOXzGiA.js.map +1 -0
- package/dist/assets/index-TvJ6RFpy.css +1 -0
- package/dist/assets/midden-DceCrT_L.js +2 -0
- package/dist/assets/midden-DceCrT_L.js.map +1 -0
- package/dist/assets/midden_bg-BLhfGIU-.wasm +0 -0
- package/dist/index.html +55 -0
- package/dist/sw.js +134 -0
- package/docs/AUTOMERGE_P2P_PLAN.md +233 -0
- package/docs/COLLABORATIVE_SHARING_PLAN.md +188 -0
- package/docs/E2E_TESTID_PLAN.md +234 -0
- package/docs/IROH_P2P_PLAN.md +302 -0
- package/docs/ROADMAP.md +695 -0
- package/docs/TODO.md +167 -0
- package/docs/bundle-embedding-plan.md +134 -0
- package/docs/standalone-refactor.md +184 -0
- package/e2e/all-playlists.spec.ts +220 -0
- package/e2e/audio-player.spec.ts +226 -0
- package/e2e/collaborative-features.spec.ts +229 -0
- package/e2e/contexts.ts +238 -0
- package/e2e/edit-panel.spec.ts +87 -0
- package/e2e/fixtures/bare-glitch-1s.m4a +0 -0
- package/e2e/fixtures/bare-glitch-1s.mp3 +0 -0
- package/e2e/fixtures/bare-glitch-1s.ogg +0 -0
- package/e2e/fixtures/chord-stack-3s.wav +0 -0
- package/e2e/fixtures/cover-anim.gif +0 -0
- package/e2e/fixtures/cover-blue.png +0 -0
- package/e2e/fixtures/cover-checkers.png +0 -0
- package/e2e/fixtures/cover-gradient.jpg +0 -0
- package/e2e/fixtures/cover-mono.gif +0 -0
- package/e2e/fixtures/cover-noise.png +0 -0
- package/e2e/fixtures/cover-plasma.webp +0 -0
- package/e2e/fixtures/cover-portrait.jpg +0 -0
- package/e2e/fixtures/cover-red.png +0 -0
- package/e2e/fixtures/cover-thumb.jpg +0 -0
- package/e2e/fixtures/cover-wide.webp +0 -0
- package/e2e/fixtures/generate.mjs +257 -0
- package/e2e/fixtures/long-drone-90s.mp3 +0 -0
- package/e2e/fixtures/noisy-binaural-8s.mp3 +0 -0
- package/e2e/fixtures/tagged-a3-4s.m4a +0 -0
- package/e2e/fixtures/tagged-a3-4s.mp3 +0 -0
- package/e2e/fixtures/tagged-a3-4s.ogg +0 -0
- package/e2e/fixtures/tagged-c5-3s.m4a +0 -0
- package/e2e/fixtures/tagged-c5-3s.mp3 +0 -0
- package/e2e/fixtures/tagged-c5-3s.ogg +0 -0
- package/e2e/fixtures/tagged-f4-6s.m4a +0 -0
- package/e2e/fixtures/tagged-f4-6s.mp3 +0 -0
- package/e2e/fixtures/tagged-f4-6s.ogg +0 -0
- package/e2e/fixtures/tone-220hz-10s.wav +0 -0
- package/e2e/fixtures/tone-440hz-2s.wav +0 -0
- package/e2e/fixtures/tone-880hz-5s.wav +0 -0
- package/e2e/fixtures/tone-stereo-3s.wav +0 -0
- package/e2e/fixtures/user-provided/README.md +1 -0
- package/e2e/helpers/app.ts +143 -0
- package/e2e/helpers/hooks.ts +133 -0
- package/e2e/helpers/index.ts +12 -0
- package/e2e/helpers/media.ts +125 -0
- package/e2e/helpers.ts +10 -0
- package/e2e/p2p-collaboration.spec.ts +356 -0
- package/e2e/p2p-multi-peer.spec.ts +723 -0
- package/e2e/p2p-states.spec.ts +302 -0
- package/e2e/playback.spec.ts +56 -0
- package/e2e/playlist-crud.spec.ts +126 -0
- package/e2e/share-link-autoplay.spec.ts +129 -0
- package/e2e/sharing-access.spec.ts +205 -0
- package/e2e/sharing.spec.ts +195 -0
- package/e2e/song-cache-state.spec.ts +202 -0
- package/e2e/zip-bundle.spec.ts +855 -0
- package/eslint.config.js +114 -0
- package/index.html +54 -0
- package/package.json +119 -0
- package/public/sw.js +134 -0
- package/scripts/use-local.mjs +37 -0
- package/scripts/use-published.mjs +37 -0
- package/src/App.tsx +9 -0
- package/src/cli/check.ts +164 -0
- package/src/cli/generate.ts +184 -0
- package/src/cli/http.ts +88 -0
- package/src/cli/index.ts +65 -0
- package/src/cli/init.ts +18 -0
- package/src/components/AllPlaylistsPanel.tsx +713 -0
- package/src/components/AudioPlayer.tsx +122 -0
- package/src/components/MarqueeText.tsx +101 -0
- package/src/components/PlaylistCoverModal.tsx +519 -0
- package/src/components/PlaylistEditPanel.tsx +803 -0
- package/src/components/PlaylistSharePanel.tsx +1020 -0
- package/src/components/ShareLinkKnockPanel.tsx +144 -0
- package/src/components/SharePanel.tsx +584 -0
- package/src/components/SongEditModal.tsx +453 -0
- package/src/components/SongEditPanel.tsx +578 -0
- package/src/components/SongRow.tsx +689 -0
- package/src/components/index.tsx +494 -0
- package/src/components/playlist/index.tsx +1203 -0
- package/src/context/PlaylistzContext.tsx +74 -0
- package/src/dev-hooks.ts +35 -0
- package/src/hooks/createDocIndexQuery.ts +53 -0
- package/src/hooks/createDocStore.test.ts +303 -0
- package/src/hooks/createDocStore.ts +90 -0
- package/src/hooks/useDragAndDrop.test.ts +474 -0
- package/src/hooks/useDragAndDrop.ts +400 -0
- package/src/hooks/useImageModal.test.ts +174 -0
- package/src/hooks/useImageModal.ts +201 -0
- package/src/hooks/usePlaylistManager.test.ts +453 -0
- package/src/hooks/usePlaylistManager.ts +685 -0
- package/src/hooks/usePlaylistsQuery.test.tsx +120 -0
- package/src/hooks/usePlaylistsQuery.ts +44 -0
- package/src/hooks/useSongState.test.ts +236 -0
- package/src/hooks/useSongState.ts +114 -0
- package/src/hooks/useUIState.ts +71 -0
- package/src/index.tsx +18 -0
- package/src/services/audioService.dev.ts +22 -0
- package/src/services/audioService.test.ts +1226 -0
- package/src/services/audioService.ts +1395 -0
- package/src/services/automergeRepo.test.ts +269 -0
- package/src/services/automergeRepo.ts +226 -0
- package/src/services/blobTransferService.dev.ts +119 -0
- package/src/services/blobTransferService.test.ts +441 -0
- package/src/services/blobTransferService.ts +702 -0
- package/src/services/docIndexService.test.ts +179 -0
- package/src/services/docIndexService.ts +118 -0
- package/src/services/fileProcessingService.test.ts +554 -0
- package/src/services/fileProcessingService.ts +239 -0
- package/src/services/imageService.test.ts +701 -0
- package/src/services/imageService.ts +365 -0
- package/src/services/indexedDBService.integration.test.ts +104 -0
- package/src/services/indexedDBService.test.ts +202 -0
- package/src/services/indexedDBService.ts +436 -0
- package/src/services/offlineService.test.ts +661 -0
- package/src/services/offlineService.ts +382 -0
- package/src/services/p2pService.test.ts +305 -0
- package/src/services/p2pService.ts +344 -0
- package/src/services/playlistDocService.test.ts +448 -0
- package/src/services/playlistDocService.ts +707 -0
- package/src/services/playlistDownloadService.test.ts +674 -0
- package/src/services/playlistDownloadService.ts +389 -0
- package/src/services/sharingService.test.ts +812 -0
- package/src/services/sharingService.ts +1073 -0
- package/src/services/sharingState.ts +161 -0
- package/src/services/songReactivity.test.ts +620 -0
- package/src/services/songReactivity.ts +145 -0
- package/src/services/standaloneService.test.ts +1025 -0
- package/src/services/standaloneService.ts +588 -0
- package/src/services/streamingAudioService.test.ts +275 -0
- package/src/services/streamingAudioService.ts +166 -0
- package/src/styles.css +428 -0
- package/src/test-setup.ts +547 -0
- package/src/types/global.d.ts +40 -0
- package/src/types/playlist.ts +99 -0
- package/src/utils/hashUtils.ts +41 -0
- package/src/utils/log.ts +97 -0
- package/src/utils/m3u.test.ts +172 -0
- package/src/utils/m3u.ts +136 -0
- package/src/utils/mockData.ts +166 -0
- package/src/utils/standaloneTemplates.test.ts +175 -0
- package/src/utils/standaloneTemplates.ts +83 -0
- package/src/utils/swTemplate.ts +84 -0
- package/src/utils/timeUtils.ts +166 -0
- package/src/utils/typeGuards.ts +171 -0
- package/src/web-component.tsx +98 -0
- package/src/zip-bundle/index.ts +7 -0
- package/src/zip-bundle/m3u.ts +45 -0
- package/src/zip-bundle/types.ts +50 -0
- package/src/zip-bundle/utils.ts +33 -0
- package/src/zip-bundle/zipBuilder.ts +309 -0
- package/tailwind.config.js +55 -0
- 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
|
+
}
|