src
pages
orchestrator
input-cache
output-management
scripts
···
41
41
"npm:sass@^1.87.0",
42
42
"npm:spellcaster@6",
43
43
"npm:throttle-debounce@^5.0.2",
44
44
+
"npm:uint8arrays@^5.1.0",
44
45
"npm:uri-js@^4.4.1",
45
46
"npm:vite-plugin-wasm@^3.4.1",
46
47
"npm:webamp@^1.5.0",
···
20
20
"query-string": "^9.1.2",
21
21
"spellcaster": "^6.0.0",
22
22
"throttle-debounce": "^5.0.2",
23
23
+
"uint8arrays": "^5.1.0",
23
24
"uri-js": "^4.4.1",
24
25
"webamp": "^1.5.0",
25
26
"xxh32": "^2.0.5"
···
4876
4877
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
4877
4878
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
4878
4879
},
4880
4880
+
"node_modules/multiformats": {
4881
4881
+
"version": "13.3.6",
4882
4882
+
"resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.3.6.tgz",
4883
4883
+
"integrity": "sha512-yakbt9cPYj8d3vi/8o/XWm61MrOILo7fsTL0qxNx6zS0Nso6K5JqqS2WV7vK/KSuDBvrW3KfCwAdAgarAgOmww==",
4884
4884
+
"license": "Apache-2.0 OR MIT"
4885
4885
+
},
4879
4886
"node_modules/music-metadata": {
4880
4887
"version": "11.2.3",
4881
4888
"resolved": "https://registry.npmjs.org/music-metadata/-/music-metadata-11.2.3.tgz",
···
6478
6485
},
6479
6486
"funding": {
6480
6487
"url": "https://github.com/sponsors/sindresorhus"
6488
6488
+
}
6489
6489
+
},
6490
6490
+
"node_modules/uint8arrays": {
6491
6491
+
"version": "5.1.0",
6492
6492
+
"resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.0.tgz",
6493
6493
+
"integrity": "sha512-vA6nFepEmlSKkMBnLBaUMVvAC4G3CTmO58C12y4sq6WPDOR7mOFYOi7GlrQ4djeSbP6JG9Pv9tJDM97PedRSww==",
6494
6494
+
"license": "Apache-2.0 OR MIT",
6495
6495
+
"dependencies": {
6496
6496
+
"multiformats": "^13.0.0"
6481
6497
}
6482
6498
},
6483
6499
"node_modules/ultrahtml": {
···
15
15
"query-string": "^9.1.2",
16
16
"spellcaster": "^6.0.0",
17
17
"throttle-debounce": "^5.0.2",
18
18
+
"uint8arrays": "^5.1.0",
18
19
"uri-js": "^4.4.1",
19
20
"webamp": "^1.5.0",
20
21
"xxh32": "^2.0.5"
···
49
49
50
50
await waitUntilAppletIsReady(configurator.input);
51
51
52
52
+
console.log("Ready.");
53
53
+
52
54
const cachedTracks = orchestrator.output.data.tracks.collection;
53
55
await configurator.input.sendAction("contextualize", cachedTracks);
54
56
57
57
+
console.log("Start list.");
58
58
+
55
59
const tracks = await configurator.input.sendAction<Track[]>("list", cachedTracks, {
56
60
timeoutDuration: 60000 * 60 * 24,
57
61
});
62
62
+
63
63
+
console.log("Got tracks", tracks);
58
64
59
65
// Process
60
66
const tracksWithMetadata = await tracks.reduce(
···
2
2
<script>
3
3
import { debounce } from "throttle-debounce";
4
4
import * as Automerge from "@automerge/automerge";
5
5
+
import * as Uint8 from "uint8arrays";
5
6
6
7
import type { Track } from "@applets/core/types.d.ts";
7
8
import type { State } from "./types.d.ts";
8
9
import { applet, register, waitUntilAppletIsReady } from "@scripts/applets/common";
9
10
11
11
+
type TracksDoc = { collection: Track[] };
12
12
+
13
13
+
const TRACKS_INITIAL_DOC = Automerge.load<TracksDoc>(
14
14
+
Uint8.fromString(
15
15
+
"hW9Kg5qsIsEAeAEQkb+c0IkXTSWyGqZ6jXtFxgETwM42fL3CMN78UZ4Qa3a9RfOrJu5qKzlM7IxwAUXelQYBAgMCEwIjBkACVgIHFQwhAiMCNAFCAlYCgAECfwB/AX8Bf+ub7MEGfwB/B38KY29sbGVjdGlvbn8AfwEBfwJ/AH8AAA",
16
16
+
"base64",
17
17
+
),
18
18
+
);
19
19
+
10
20
////////////////////////////////////////////
11
21
// SETUP
12
22
////////////////////////////////////////////
13
23
const context = register<State>();
14
24
25
25
+
// Data codec
26
26
+
const codec = {
27
27
+
decode(data: any) {
28
28
+
return {
29
29
+
hasSyncedTracks: data.hasSyncedTracks,
30
30
+
ready: context.data.ready,
31
31
+
tracks: Automerge.load<TracksDoc>(data.tracks),
32
32
+
};
33
33
+
},
34
34
+
35
35
+
encode(data: State) {
36
36
+
return {
37
37
+
hasSyncedTracks: true,
38
38
+
ready: context.data.ready,
39
39
+
tracks: Automerge.save(data.tracks),
40
40
+
};
41
41
+
},
42
42
+
};
43
43
+
44
44
+
context.codec = codec;
45
45
+
15
46
// Initial data
16
47
context.data = {
17
17
-
tracks: Automerge.from({ collection: [] }),
48
48
+
// Empty tracks collection, DO NOT CHANGE.
49
49
+
// (avoids the initial sync problem with Automerge)
50
50
+
tracks: TRACKS_INITIAL_DOC,
18
51
19
52
hasSyncedTracks: false,
20
53
···
29
62
// Load tracks if needed
30
63
if (context.isMainInstance())
31
64
loadTracks().then((doc) => {
65
65
+
console.log("LOADED DOC", doc);
66
66
+
32
67
if (doc) {
33
33
-
const mergedDoc = Automerge.merge(doc, context.data.tracks);
68
68
+
const mergedDoc = Automerge.merge(context.data.tracks, doc);
69
69
+
console.log("MERGED DOC", doc);
34
70
update({ tracks: mergedDoc });
35
71
}
36
72
···
42
78
context.data = { ...context.data, ...partial };
43
79
}
44
80
45
45
-
function updateTracks(tracks: Track[]): Automerge.Doc<{ collection: Track[] }> {
81
81
+
function updateTracks(tracks: Track[]): Automerge.Doc<TracksDoc> {
46
82
console.log(context.data.tracks);
83
83
+
console.log(context.isMainInstance());
47
84
48
85
const doc = Automerge.change(context.data.tracks, (d) => {
49
86
d.collection = cleanUndefinedValuesForTracks(tracks);
···
76
113
return undefined;
77
114
}
78
115
79
79
-
return Automerge.load<{ collection: Track[] }>(data as Uint8Array);
116
116
+
return Automerge.load<TracksDoc>(data as Uint8Array);
80
117
}
81
118
82
119
////////////////////////////////////////////
···
92
129
saveTracksToOutput(doc);
93
130
};
94
131
95
95
-
const saveTracksToOutput = debounce(
96
96
-
5000,
97
97
-
async function (doc: Automerge.Doc<{ collection: Track[] }>) {
98
98
-
const data = Automerge.save(doc);
132
132
+
const saveTracksToOutput = debounce(5000, async function (doc: Automerge.Doc<TracksDoc>) {
133
133
+
const data = Automerge.save(doc);
99
134
100
100
-
console.log("🔮 Saving tracks");
135
135
+
console.log("🔮 Saving tracks");
101
136
102
102
-
await configurator.output.sendAction("put", {
103
103
-
name: "tracks.json",
104
104
-
data,
105
105
-
});
137
137
+
await configurator.output.sendAction("put", {
138
138
+
name: "tracks.json",
139
139
+
data,
140
140
+
});
106
141
107
107
-
console.log("🔮 Tracks saved to output");
108
108
-
},
109
109
-
);
142
142
+
console.log("🔮 Tracks saved to output");
143
143
+
});
110
144
111
145
context.setActionHandler("tracks", tracksHandler);
112
146
···
93
93
if (event.data === "PING") {
94
94
channel.postMessage("PONG");
95
95
} else if (event.data?.type === "data") {
96
96
-
scope.data = event.data.data;
96
96
+
scope.data = context.codec.decode(event.data.data);
97
97
} else if (waitingForPong && event.data === "PONG") {
98
98
waitingForPong = false;
99
99
isMainInstance = false;
···
115
115
if (isMainInstance) {
116
116
channel.postMessage({
117
117
type: "data",
118
118
-
data: event.data,
118
118
+
data: context.codec.encode(event.data),
119
119
});
120
120
}
121
121
};
122
122
123
123
-
return {
123
123
+
const context = {
124
124
+
scope,
125
125
+
124
126
get id() {
125
127
return id;
126
128
},
···
133
135
scope.data = data;
134
136
},
135
137
138
138
+
codec: {
139
139
+
decode: (data: any) => data as DataType,
140
140
+
encode: (data: DataType) => data as any,
141
141
+
},
142
142
+
136
143
isMainInstance() {
137
144
return isMainInstance;
138
145
},
···
166
173
scope.setActionHandler(actionId, handler);
167
174
},
168
175
};
176
176
+
177
177
+
return context;
169
178
}
170
179
171
180
////////////////////////////////////////////