A music player that connects to your cloud/distributed storage.
1import { Temporal } from "~/common/temporal.js";
2import { xxh32r } from "xxh32/dist/raw.js";
3
4/**
5 * @import {Track} from "~/definitions/types.d.ts"
6 */
7
8/**
9 * @template T
10 * @param {Array<T>} array
11 * @returns Array<T>
12 */
13export function arrayShuffle(array) {
14 if (array.length === 0) {
15 return [];
16 }
17
18 array = [...array];
19
20 for (let index = array.length - 1; index > 0; index--) {
21 const randArr = crypto.getRandomValues(new Uint32Array(1));
22 const randVal = randArr[0] / 2 ** 32;
23 const newIndex = Math.floor(randVal * (index + 1));
24 [array[index], array[newIndex]] = [array[newIndex], array[index]];
25 }
26
27 return array;
28}
29
30/**
31 * @param {string | undefined | null} value
32 */
33export function boolAttr(value) {
34 return value === "";
35}
36
37/**
38 * @param {string} a
39 * @param {string} b
40 */
41export function compareTimestamps(a, b) {
42 return Temporal.Instant.compare(
43 Temporal.Instant.from(a),
44 Temporal.Instant.from(b),
45 );
46}
47
48/**
49 * @param {any} object
50 */
51export function hash(object) {
52 return xxh32r(jsonEncode(object)).toString();
53}
54
55/**
56 * @param {Track[]} tracks
57 * @param {Record<string, Track[]>} initial
58 * @returns {Record<string, Track[]>}
59 */
60export function groupTracksPerScheme(
61 tracks,
62 initial = {},
63) {
64 /** @type {Record<string, Track[]>} */
65 const acc = initial;
66
67 tracks.forEach((track) => {
68 const scheme = track.uri.substring(0, track.uri.indexOf(":"));
69 acc[scheme] ??= [];
70 acc[scheme].push(track);
71 });
72
73 return acc;
74}
75
76/**
77 * @param {string[]} uris
78 * @returns {Record<string, string[]>}
79 */
80export function groupUrisPerScheme(uris) {
81 /** @type {Record<string, string[]>} */
82 const acc = {};
83
84 uris.forEach((uri) => {
85 const scheme = uri.substring(0, uri.indexOf(":"));
86 acc[scheme] ??= [];
87 acc[scheme].push(uri);
88 });
89
90 return acc;
91}
92
93/**
94 * @param {unknown} test
95 */
96export function isPrimitive(test) {
97 return test !== Object(test);
98}
99
100/**
101 * @template T
102 * @param {any} a
103 * @returns {T}
104 */
105export function jsonDecode(a) {
106 return JSON.parse(new TextDecoder().decode(a));
107}
108
109/**
110 * @template T
111 * @param {T} a
112 * @returns Uint8Array
113 */
114export function jsonEncode(a) {
115 return new TextEncoder().encode(JSON.stringify(a));
116}
117
118/**
119 * @template {Record<string, any>} T
120 * @param {T} rec
121 */
122export function removeUndefinedValuesFromRecord(rec) {
123 const recClone = { ...rec };
124
125 Object.entries(recClone).forEach(([key, value]) => {
126 if (value === undefined) {
127 delete recClone[key];
128 }
129 });
130
131 return recClone;
132}
133
134/**
135 * @template {Record<string, any>} T
136 * @param {T} rec
137 */
138export function recursivelyCloneRecords(rec) {
139 const recClone = { ...rec };
140
141 Object.entries(recClone).forEach(([key, value]) => {
142 if (typeof value === "object") {
143 /** @ts-ignore */
144 recClone[key] = recursivelyCloneRecords(value);
145 }
146 });
147
148 return recClone;
149}
150
151/**
152 * @param {string} str
153 * @returns {string}
154 */
155export function safeDecodeURIComponent(str) {
156 return str.replace(
157 /%u([0-9A-Fa-f]{4})|%([0-9A-Fa-f]{2})/g,
158 (_, unicode, byte) =>
159 unicode
160 ? String.fromCharCode(parseInt(unicode, 16))
161 : String.fromCharCode(parseInt(byte, 16)),
162 );
163}