@spicetify/kit 0.3.0 → 0.4.0
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/README.md +82 -0
- package/dist/args.js +15 -0
- package/dist/cli.js +4 -1
- package/dist/create.js +9 -5
- package/dist/dev.js +86 -22
- package/dist/install.js +4 -6
- package/dist/launch.js +24 -24
- package/dist/push.js +187 -44
- package/dist/remove.js +37 -0
- package/dist/stdlib-boundary.js +2 -2
- package/dist/version.js +1 -1
- package/package.json +6 -4
- package/vendor/classmaps/1030000/classmap-19f856aefd5.json +109 -0
- package/vendor/shims/platform.d.ts +1640 -3235
- package/vendor/stdlib/metadata.json +1 -1
- package/vendor/stdlib/query.ts +31 -0
- package/vendor/stdlib/src/client.ts +90 -1
- package/vendor/stdlib/src/events.mix.ts +9 -0
- package/vendor/stdlib/src/events.ts +1 -1
- package/vendor/stdlib/src/registers/menu-order.ts +17 -0
- package/vendor/stdlib/src/registers/menu.ts +8 -4
- package/vendor/stdlib/src/registers/mount.ts +35 -9
- package/vendor/stdlib/src/registers/navlink.tsx +3 -50
- package/vendor/stdlib/src/registers/playbarButton.tsx +0 -19
- package/vendor/stdlib/src/registers/playbarWidget.tsx +0 -19
- package/vendor/stdlib/src/registers/root.ts +1 -1
- package/vendor/stdlib/src/registers/route.ts +1 -23
- package/vendor/stdlib/src/registers/topbarLeftButton.tsx +0 -24
- package/vendor/stdlib/src/registers/topbarRightButton.tsx +0 -24
- package/vendor/stdlib/src/storage.ts +1 -1
- package/vendor/stdlib/src/webpack/ReactComponents.xpui.ts +3 -9
- package/vendor/stdlib/src/webpack/URI.xpui.ts +1 -1
- package/vendor/stdlib/src/webpack/capture-readiness.ts +5 -3
- package/vendor/stdlib/src/webpack/index.ts +10 -4
- package/vendor/stdlib/src/webpack/playlist-menu.ts +54 -0
- package/vendor/stdlib/src/wpunpk.mix.ts +1 -1
- package/vendor/classmaps/1020096/classmap-19f856aefd5.json +0 -109
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (C) 2026 spicetify
|
|
3
|
+
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { QueryClient } from "@tanstack/query-core";
|
|
7
|
+
import type { ModuleRuntimeContext } from "./mod.ts";
|
|
8
|
+
|
|
9
|
+
export { QueryClient, QueryObserver, isCancelledError } from "@tanstack/query-core";
|
|
10
|
+
export type { QueryKey, QueryFunctionContext } from "@tanstack/query-core";
|
|
11
|
+
|
|
12
|
+
/** A cache owned by one module load; unloading cancels queries and discards data. */
|
|
13
|
+
export function createModuleQueryClient(ctx: Pick<ModuleRuntimeContext, "defer">): QueryClient {
|
|
14
|
+
const client = new QueryClient({
|
|
15
|
+
defaultOptions: {
|
|
16
|
+
queries: {
|
|
17
|
+
staleTime: 5 * 60 * 1000,
|
|
18
|
+
gcTime: 10 * 60 * 1000,
|
|
19
|
+
retry: false,
|
|
20
|
+
refetchOnWindowFocus: false,
|
|
21
|
+
refetchOnReconnect: false,
|
|
22
|
+
},
|
|
23
|
+
mutations: { retry: false },
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
ctx.defer(async () => {
|
|
27
|
+
await client.cancelQueries();
|
|
28
|
+
client.clear();
|
|
29
|
+
});
|
|
30
|
+
return client;
|
|
31
|
+
}
|
|
@@ -25,22 +25,111 @@ export interface PopupModalCompatibility {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export interface DaemonCapabilities {
|
|
28
|
+
acquireWindowControls?(onDisconnect: (error: Error) => void): Promise<{ release(): Promise<void> }>;
|
|
28
29
|
available(): Promise<boolean>;
|
|
29
30
|
apply(): Promise<unknown>;
|
|
30
31
|
blockUpdates(): Promise<unknown>;
|
|
31
32
|
unblockUpdates(): Promise<unknown>;
|
|
33
|
+
updateAndApplySupported?(): Promise<boolean | null>;
|
|
34
|
+
updateAndApply?: UpdateAndApplyCapability;
|
|
35
|
+
managedSpotify?: ManagedSpotifyCapability;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type ManagedSpotifyJob =
|
|
39
|
+
| { kind: "idle" }
|
|
40
|
+
| { kind: "running"; jobId: string; phase: "checking" | "downloading" | "preparing" | "activating" }
|
|
41
|
+
| { kind: "complete"; jobId: string }
|
|
42
|
+
| { kind: "failed"; jobId: string; message: string };
|
|
43
|
+
|
|
44
|
+
export type ManagedSpotifyInstallation =
|
|
45
|
+
| { kind: "external" }
|
|
46
|
+
| { kind: "unavailable"; message: string }
|
|
47
|
+
| { kind: "managed"; version: string; channel: "stable" | "testing"; nativeBlocked: boolean | null };
|
|
48
|
+
|
|
49
|
+
export type ManagedSpotifyAvailability =
|
|
50
|
+
| { kind: "current"; version: string }
|
|
51
|
+
| { kind: "ready"; version: string }
|
|
52
|
+
| { kind: "unavailable"; version: string; message: string };
|
|
53
|
+
|
|
54
|
+
export interface ManagedSpotifySnapshot {
|
|
55
|
+
installation: ManagedSpotifyInstallation;
|
|
56
|
+
job: ManagedSpotifyJob;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface ManagedSpotifyCapability {
|
|
60
|
+
status(): Promise<ManagedSpotifySnapshot | null>;
|
|
61
|
+
check(): Promise<ManagedSpotifyAvailability>;
|
|
62
|
+
update(): Promise<UpdateAdmission>;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export type UpdateFailureCode =
|
|
66
|
+
| "unsupported-platform"
|
|
67
|
+
| "unsupported-target"
|
|
68
|
+
| "update-unavailable"
|
|
69
|
+
| "renderer-timeout"
|
|
70
|
+
| "spotify-update-failed"
|
|
71
|
+
| "apply-failed";
|
|
72
|
+
|
|
73
|
+
export type UpdateAndApplyStatus =
|
|
74
|
+
| { kind: "idle" }
|
|
75
|
+
| { kind: "accepted"; jobId: string; fromVersion: string }
|
|
76
|
+
| { kind: "waiting-for-update"; jobId: string; fromVersion: string }
|
|
77
|
+
| { kind: "downloading"; jobId: string; targetVersion: string }
|
|
78
|
+
| { kind: "installing-spotify"; jobId: string; targetVersion: string }
|
|
79
|
+
| { kind: "applying-spicetify"; jobId: string; targetVersion: string }
|
|
80
|
+
| { kind: "securing"; jobId: string; targetVersion?: string; message?: string; manualRecovery?: boolean }
|
|
81
|
+
| { kind: "complete"; jobId: string; fromVersion: string; toVersion: string }
|
|
82
|
+
| { kind: "failed-safe"; jobId: string; code: UpdateFailureCode; message: string };
|
|
83
|
+
|
|
84
|
+
export interface UpdateAdmission {
|
|
85
|
+
jobId: string;
|
|
86
|
+
disposition: "accepted" | "joined";
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface UpdateAndApplyCapability {
|
|
90
|
+
(): Promise<UpdateAdmission>;
|
|
91
|
+
observe(listener: (status: UpdateAndApplyStatus) => void): () => void;
|
|
32
92
|
}
|
|
33
93
|
|
|
34
94
|
export interface SnackbarCapabilities {
|
|
35
95
|
enqueueSnackbar(message: string, options?: { variant?: string }): unknown;
|
|
36
96
|
}
|
|
37
97
|
|
|
98
|
+
type CosmosBody = Record<string, unknown>;
|
|
99
|
+
type CosmosHeaders = Spicetify.CosmosAsync.Headers;
|
|
100
|
+
type CosmosMethod = Spicetify.CosmosAsync.Method;
|
|
101
|
+
type CosmosResponse = Omit<Spicetify.CosmosAsync.Response, "body"> & { body: unknown };
|
|
102
|
+
|
|
103
|
+
export interface CosmosCapabilities {
|
|
104
|
+
head(url: string, headers?: CosmosHeaders): Promise<CosmosHeaders>;
|
|
105
|
+
get(url: string, body?: CosmosBody, headers?: CosmosHeaders): Promise<unknown>;
|
|
106
|
+
post(url: string, body?: CosmosBody, headers?: CosmosHeaders): Promise<unknown>;
|
|
107
|
+
put(url: string, body?: CosmosBody, headers?: CosmosHeaders): Promise<unknown>;
|
|
108
|
+
del(url: string, body?: CosmosBody, headers?: CosmosHeaders): Promise<unknown>;
|
|
109
|
+
patch(url: string, body?: CosmosBody, headers?: CosmosHeaders): Promise<unknown>;
|
|
110
|
+
sub(
|
|
111
|
+
url: string,
|
|
112
|
+
callback: (body: unknown) => void,
|
|
113
|
+
onError?: (error: unknown) => void,
|
|
114
|
+
body?: CosmosBody,
|
|
115
|
+
headers?: CosmosHeaders,
|
|
116
|
+
): Promise<unknown>;
|
|
117
|
+
postSub(
|
|
118
|
+
url: string,
|
|
119
|
+
body: CosmosBody | null,
|
|
120
|
+
callback: (body: unknown) => void,
|
|
121
|
+
onError?: (error: unknown) => void,
|
|
122
|
+
): Promise<unknown>;
|
|
123
|
+
request(method: CosmosMethod, url: string, body?: CosmosBody, headers?: CosmosHeaders): Promise<CosmosResponse>;
|
|
124
|
+
resolve(method: CosmosMethod, url: string, body?: CosmosBody, headers?: CosmosHeaders): Promise<CosmosResponse>;
|
|
125
|
+
}
|
|
126
|
+
|
|
38
127
|
export interface ClientCapabilities {
|
|
39
128
|
readonly player: typeof Spicetify.Player;
|
|
40
129
|
readonly platform: typeof Spicetify.Platform;
|
|
41
130
|
readonly storage: typeof Spicetify.LocalStorage;
|
|
42
131
|
readonly uri: typeof Spicetify.URI;
|
|
43
|
-
readonly cosmos:
|
|
132
|
+
readonly cosmos: CosmosCapabilities;
|
|
44
133
|
readonly corsProxy: typeof Spicetify.CORSProxy;
|
|
45
134
|
readonly graphQL: typeof Spicetify.GraphQL;
|
|
46
135
|
readonly locale: typeof Spicetify.Locale;
|
|
@@ -21,6 +21,15 @@ function createGlobalThisShadow() {
|
|
|
21
21
|
|
|
22
22
|
type CosmosRequest = { uri: string; method: string; body: string };
|
|
23
23
|
|
|
24
|
+
declare global {
|
|
25
|
+
var sendCosmosRequest: (request: {
|
|
26
|
+
request: string;
|
|
27
|
+
persistent?: boolean;
|
|
28
|
+
onSuccess?: (response: string) => void;
|
|
29
|
+
onFailure?: (error: unknown) => void;
|
|
30
|
+
}) => unknown;
|
|
31
|
+
}
|
|
32
|
+
|
|
24
33
|
const CosmosRequestSubject = new Subject<CosmosRequest>();
|
|
25
34
|
export const UpdateTitlebarSubject = new BehaviorSubject<number>(-1);
|
|
26
35
|
|
|
@@ -41,7 +41,7 @@ const playerListener = ({ data: state }: any) => {
|
|
|
41
41
|
cachedState = state;
|
|
42
42
|
};
|
|
43
43
|
|
|
44
|
-
const historyListener = (location) => EventBus.History.updated.next(location);
|
|
44
|
+
const historyListener = (location: unknown) => EventBus.History.updated.next(location);
|
|
45
45
|
|
|
46
46
|
const updateTitlebarListener = (height: number) => EventBus.ControlMessage.titlebar_updated.next(height);
|
|
47
47
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (C) 2026 Afonso Jorge Ramos
|
|
3
|
+
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const placeMenuItemsHost = (menu: Element, host: HTMLElement): void => {
|
|
7
|
+
const settingsRow = Array.from(menu.children).find(
|
|
8
|
+
(child) => child.matches('a[href="/preferences"]') || child.querySelector('a[href="/preferences"]'),
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
if (settingsRow) {
|
|
12
|
+
if (settingsRow.nextElementSibling !== host) settingsRow.after(host);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (host.parentElement !== menu) menu.append(host);
|
|
17
|
+
};
|
|
@@ -9,6 +9,7 @@ import { matchLast } from "../util.ts";
|
|
|
9
9
|
|
|
10
10
|
import { warn } from "../logger.ts";
|
|
11
11
|
import { transformer } from "../../mixin.ts";
|
|
12
|
+
import { placeMenuItemsHost } from "./menu-order.ts";
|
|
12
13
|
import { Registry } from "./registry.ts";
|
|
13
14
|
|
|
14
15
|
type __MenuContext = React.Context<MenuContext>;
|
|
@@ -125,14 +126,17 @@ if (
|
|
|
125
126
|
);
|
|
126
127
|
|
|
127
128
|
const ItemBoundary = createItemBoundary(R, "menu");
|
|
128
|
-
const live: Array<{ menu: Element; root: { unmount: () => void } }> = [];
|
|
129
|
+
const live: Array<{ menu: Element; host: HTMLElement; root: { unmount: () => void } }> = [];
|
|
129
130
|
const processed = new WeakSet<Element>();
|
|
130
131
|
|
|
131
|
-
const sweep = () => {
|
|
132
|
+
const sweep = (records: MutationRecord[] = []) => {
|
|
133
|
+
const changedMenus = new Set(records.map((record) => record.target));
|
|
132
134
|
for (let i = live.length - 1; i >= 0; i--) {
|
|
133
135
|
if (!live[i].menu.isConnected) {
|
|
134
136
|
live[i].root.unmount();
|
|
135
137
|
live.splice(i, 1);
|
|
138
|
+
} else if (changedMenus.has(live[i].menu)) {
|
|
139
|
+
placeMenuItemsHost(live[i].menu, live[i].host);
|
|
136
140
|
}
|
|
137
141
|
}
|
|
138
142
|
if (items.size === 0) return;
|
|
@@ -142,7 +146,7 @@ if (
|
|
|
142
146
|
const host = document.createElement("li");
|
|
143
147
|
host.setAttribute("role", "presentation");
|
|
144
148
|
host.className = "spicetify-menu-items";
|
|
145
|
-
menu
|
|
149
|
+
placeMenuItemsHost(menu, host);
|
|
146
150
|
const root = createRoot(host);
|
|
147
151
|
globalThis.__MenuContext ??= R.createContext(null);
|
|
148
152
|
root.render(
|
|
@@ -152,7 +156,7 @@ if (
|
|
|
152
156
|
...items.all().map((item, i) => R.createElement(ItemBoundary, { key: i }, item)),
|
|
153
157
|
),
|
|
154
158
|
);
|
|
155
|
-
live.push({ menu, root });
|
|
159
|
+
live.push({ menu, host, root });
|
|
156
160
|
}
|
|
157
161
|
};
|
|
158
162
|
new MutationObserver(sweep).observe(document.body, { childList: true, subtree: true });
|
|
@@ -143,10 +143,10 @@ const ensureAnchorStyle = () => {
|
|
|
143
143
|
// constantly, and per-anchor subtree observers multiply that cost. Each
|
|
144
144
|
// tick is one isConnected check per pending or placed anchor.
|
|
145
145
|
type WatchedAnchor = { host: HTMLElement; place: () => boolean; started: boolean; start: () => void };
|
|
146
|
-
const watched
|
|
146
|
+
const watched = new Set<WatchedAnchor>();
|
|
147
147
|
let bodyObserver: MutationObserver | undefined;
|
|
148
148
|
const watchAnchor = (anchor: WatchedAnchor) => {
|
|
149
|
-
watched.
|
|
149
|
+
watched.add(anchor);
|
|
150
150
|
bodyObserver ??= (() => {
|
|
151
151
|
const observer = new MutationObserver(() => {
|
|
152
152
|
for (const a of watched) {
|
|
@@ -160,6 +160,13 @@ const watchAnchor = (anchor: WatchedAnchor) => {
|
|
|
160
160
|
observer.observe(document.body, { childList: true, subtree: true });
|
|
161
161
|
return observer;
|
|
162
162
|
})();
|
|
163
|
+
return () => {
|
|
164
|
+
watched.delete(anchor);
|
|
165
|
+
if (watched.size === 0) {
|
|
166
|
+
bodyObserver?.disconnect();
|
|
167
|
+
bodyObserver = undefined;
|
|
168
|
+
}
|
|
169
|
+
};
|
|
163
170
|
};
|
|
164
171
|
|
|
165
172
|
export function mountRegistryAnchor(spec: AnchorSpec): void {
|
|
@@ -192,6 +199,8 @@ export function mountRegistryAnchor(spec: AnchorSpec): void {
|
|
|
192
199
|
const start = () => {
|
|
193
200
|
const root = createRoot(host);
|
|
194
201
|
const ItemBoundary = createItemBoundary(R, spec.className);
|
|
202
|
+
const itemKeys = new Map<unknown, number>();
|
|
203
|
+
let nextItemKey = 0;
|
|
195
204
|
const Wrapper = () => {
|
|
196
205
|
const [, refresh] = R.useReducer((n: number) => n + 1, 0);
|
|
197
206
|
R.useEffect(() => {
|
|
@@ -201,12 +210,20 @@ export function mountRegistryAnchor(spec: AnchorSpec): void {
|
|
|
201
210
|
if (spec.renderItems) {
|
|
202
211
|
return R.createElement(ItemBoundary, null, spec.renderItems(spec.registry.all()));
|
|
203
212
|
}
|
|
213
|
+
for (const item of itemKeys.keys()) {
|
|
214
|
+
if (!spec.registry.has(item)) itemKeys.delete(item);
|
|
215
|
+
}
|
|
204
216
|
return R.createElement(
|
|
205
217
|
R.Fragment,
|
|
206
218
|
null,
|
|
207
|
-
...byOrder(spec.registry.all()).map((item: unknown
|
|
208
|
-
|
|
209
|
-
|
|
219
|
+
...byOrder(spec.registry.all()).map((item: unknown) => {
|
|
220
|
+
let key = itemKeys.get(item);
|
|
221
|
+
if (key === undefined) {
|
|
222
|
+
key = nextItemKey++;
|
|
223
|
+
itemKeys.set(item, key);
|
|
224
|
+
}
|
|
225
|
+
return R.createElement(ItemBoundary, { key }, item);
|
|
226
|
+
}),
|
|
210
227
|
);
|
|
211
228
|
};
|
|
212
229
|
root.render(R.createElement(Wrapper));
|
|
@@ -240,6 +257,8 @@ export interface AdjacentSpec {
|
|
|
240
257
|
|
|
241
258
|
export function mountAdjacent(spec: AdjacentSpec): { remove: () => void } {
|
|
242
259
|
let removed = false;
|
|
260
|
+
let unwatch: (() => void) | undefined;
|
|
261
|
+
let retry: ReturnType<typeof setTimeout> | undefined;
|
|
243
262
|
let root: { render?: (node: unknown) => void; unmount?: () => void } | undefined;
|
|
244
263
|
const host = document.createElement("span");
|
|
245
264
|
host.className = spec.className;
|
|
@@ -247,6 +266,7 @@ export function mountAdjacent(spec: AdjacentSpec): { remove: () => void } {
|
|
|
247
266
|
host.dataset.spicetifyAnchor = "";
|
|
248
267
|
|
|
249
268
|
const place = (): boolean => {
|
|
269
|
+
if (removed) return false;
|
|
250
270
|
const target = spec.findTarget();
|
|
251
271
|
if (!target?.parentElement) return false;
|
|
252
272
|
const before = spec.side === "before" ? target : target.nextSibling;
|
|
@@ -255,9 +275,15 @@ export function mountAdjacent(spec: AdjacentSpec): { remove: () => void } {
|
|
|
255
275
|
};
|
|
256
276
|
|
|
257
277
|
const remove = () => {
|
|
278
|
+
if (removed) return;
|
|
258
279
|
removed = true;
|
|
259
|
-
|
|
260
|
-
|
|
280
|
+
clearTimeout(retry);
|
|
281
|
+
unwatch?.();
|
|
282
|
+
try {
|
|
283
|
+
root?.unmount?.();
|
|
284
|
+
} finally {
|
|
285
|
+
host.remove();
|
|
286
|
+
}
|
|
261
287
|
};
|
|
262
288
|
|
|
263
289
|
// Transform experiments render through injected __renderX() calls, and there
|
|
@@ -291,14 +317,14 @@ export function mountAdjacent(spec: AdjacentSpec): { remove: () => void } {
|
|
|
291
317
|
if (place()) {
|
|
292
318
|
start();
|
|
293
319
|
// Reuse the shared observer for re-placement only (already started).
|
|
294
|
-
watchAnchor({ host, place, started: true, start: () => {} });
|
|
320
|
+
unwatch = watchAnchor({ host, place, started: true, start: () => {} });
|
|
295
321
|
return;
|
|
296
322
|
}
|
|
297
323
|
if (Date.now() > deadline) {
|
|
298
324
|
spec.onGiveUp();
|
|
299
325
|
return;
|
|
300
326
|
}
|
|
301
|
-
setTimeout(attempt, 150);
|
|
327
|
+
retry = setTimeout(attempt, 150);
|
|
302
328
|
};
|
|
303
329
|
attempt();
|
|
304
330
|
});
|
|
@@ -5,11 +5,10 @@
|
|
|
5
5
|
|
|
6
6
|
import { React } from "../expose/React.ts";
|
|
7
7
|
import { createIconComponent } from "../createIconComponent.tsx";
|
|
8
|
-
import { transformer } from "../../mixin.ts";
|
|
9
8
|
import { Platform } from "../expose/Platform.ts";
|
|
10
9
|
import { warn } from "../logger.ts";
|
|
11
10
|
import { classnames } from "../webpack/ClassNames.ts";
|
|
12
|
-
import {
|
|
11
|
+
import { Tooltip } from "../webpack/ReactComponents.ts";
|
|
13
12
|
import { UI } from "../webpack/ComponentLibrary.ts";
|
|
14
13
|
import { mountRegistryAnchor } from "./mount.ts";
|
|
15
14
|
import { Registry } from "./registry.ts";
|
|
@@ -29,52 +28,6 @@ export default registry;
|
|
|
29
28
|
|
|
30
29
|
let refresh: React.DispatchWithoutAction | undefined;
|
|
31
30
|
|
|
32
|
-
declare global {
|
|
33
|
-
var __renderNavLinks: () => React.ReactNode;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
globalThis.__renderNavLinks = () =>
|
|
37
|
-
React.createElement(() => {
|
|
38
|
-
[, refresh] = React.useReducer((n) => n + 1, 0);
|
|
39
|
-
|
|
40
|
-
if (!ScrollableContainer) {
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return (
|
|
45
|
-
<ScrollableContainer className="custom-navlinks-scrollable_container" onlyHorizontalWheel>
|
|
46
|
-
{registry.all()}
|
|
47
|
-
</ScrollableContainer>
|
|
48
|
-
);
|
|
49
|
-
});
|
|
50
|
-
transformer(
|
|
51
|
-
(emit) => (str) => {
|
|
52
|
-
emit();
|
|
53
|
-
|
|
54
|
-
str = str.replace(
|
|
55
|
-
/{(?=[^{}]*(?:{[^{}]*(?:{[^{}]*(?:{[^{}]*}[^{}]*)*}[^{}]*)*}[^{}]*)*(?<=[,{])"data-testid":"global-nav-bar")([^{}]*(?:{[^{}]*(?:{[^{}]*(?:{[^{}]*}[^{}]*)*}[^{}]*)*}[^{}]*)*(?<=[,{]))children:([^{}]*(?:{[^{}]*(?:{[^{}]*(?:{[^{}]*}[^{}]*)*}[^{}]*)*}[^{}]*)*)(,[^{}]*(?:{[^{}]*(?:{[^{}]*(?:{[^{}]*}[^{}]*)*}[^{}]*)*}[^{}]*)*|)}/,
|
|
56
|
-
"{$1children:(function(children){const p=children[0].props;p.children=[p.children,__renderNavLinks()].flat();return children})($2)$3}",
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
return str;
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
glob: /^\/xpui\.js/,
|
|
63
|
-
},
|
|
64
|
-
);
|
|
65
|
-
transformer(
|
|
66
|
-
(emit) => (str) => {
|
|
67
|
-
emit();
|
|
68
|
-
|
|
69
|
-
str = str.replace('["","/","/home/",', '["","/","/home/","/bespoke/*",');
|
|
70
|
-
|
|
71
|
-
return str;
|
|
72
|
-
},
|
|
73
|
-
{
|
|
74
|
-
glob: /^\/dwp\-top\-bar\.js/,
|
|
75
|
-
},
|
|
76
|
-
);
|
|
77
|
-
|
|
78
31
|
mountRegistryAnchor({
|
|
79
32
|
className: "spicetify-navlinks-anchor",
|
|
80
33
|
registry,
|
|
@@ -88,8 +41,8 @@ mountRegistryAnchor({
|
|
|
88
41
|
// A real flex box, not the default display:contents. The history-buttons
|
|
89
42
|
// wrapper spaces its children with a wide gap meant to separate button
|
|
90
43
|
// groups; as one box the anchor takes that gap once (separating our links
|
|
91
|
-
// from the back/forward group), while the nav links inside
|
|
92
|
-
//
|
|
44
|
+
// from the back/forward group), while the nav links inside are spaced by
|
|
45
|
+
// the anchor's own gap (stdlib index.scss).
|
|
93
46
|
hostDisplay: "flex",
|
|
94
47
|
});
|
|
95
48
|
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
|
|
6
6
|
import { React } from "../expose/React.ts";
|
|
7
7
|
import { createIconComponent } from "../createIconComponent.tsx";
|
|
8
|
-
import { transformer } from "../../mixin.ts";
|
|
9
8
|
import { Tooltip } from "../webpack/ReactComponents.ts";
|
|
10
9
|
import { UI } from "../webpack/ComponentLibrary.ts";
|
|
11
10
|
import { classnames } from "../webpack/ClassNames.ts";
|
|
@@ -27,24 +26,6 @@ export default registry;
|
|
|
27
26
|
|
|
28
27
|
let refresh: React.DispatchWithoutAction | undefined;
|
|
29
28
|
|
|
30
|
-
declare global {
|
|
31
|
-
var __renderPlaybarBarControls: any;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
globalThis.__renderPlaybarBarControls = () => registry.all().reverse();
|
|
35
|
-
transformer(
|
|
36
|
-
(emit) => (str) => {
|
|
37
|
-
emit();
|
|
38
|
-
|
|
39
|
-
str = str.replace(/(children:\[)([^\[]*djJumpButtonFactory)/, "$1...__renderPlaybarBarControls(),$2");
|
|
40
|
-
|
|
41
|
-
return str;
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
glob: /^\/xpui\.js/,
|
|
45
|
-
},
|
|
46
|
-
);
|
|
47
|
-
|
|
48
29
|
mountRegistryAnchor({
|
|
49
30
|
className: "spicetify-playbar-buttons",
|
|
50
31
|
registry,
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
|
|
6
6
|
import { React } from "../expose/React.ts";
|
|
7
7
|
import { createIconComponent } from "../createIconComponent.tsx";
|
|
8
|
-
import { transformer } from "../../mixin.ts";
|
|
9
8
|
import { Tooltip } from "../webpack/ReactComponents.ts";
|
|
10
9
|
import { UI } from "../webpack/ComponentLibrary.ts";
|
|
11
10
|
import { mountRegistryAnchor } from "./mount.ts";
|
|
@@ -26,24 +25,6 @@ export default registry;
|
|
|
26
25
|
|
|
27
26
|
let refresh: React.DispatchWithoutAction | undefined;
|
|
28
27
|
|
|
29
|
-
declare global {
|
|
30
|
-
var __renderNowPlayingWidgets: any;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
globalThis.__renderNowPlayingWidgets = () => registry.all();
|
|
34
|
-
transformer(
|
|
35
|
-
(emit) => (str) => {
|
|
36
|
-
emit();
|
|
37
|
-
|
|
38
|
-
str = str.replace(/(hideButtonFactory[^\]]*)/, "$1,...__renderNowPlayingWidgets()");
|
|
39
|
-
|
|
40
|
-
return str;
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
glob: /^\/xpui\.js/,
|
|
44
|
-
},
|
|
45
|
-
);
|
|
46
|
-
|
|
47
28
|
mountRegistryAnchor({
|
|
48
29
|
className: "spicetify-playbar-widgets",
|
|
49
30
|
registry,
|
|
@@ -56,7 +56,7 @@ globalThis.__renderRootChildren = () =>
|
|
|
56
56
|
return childrenRegistry.all();
|
|
57
57
|
});
|
|
58
58
|
globalThis.__renderRootProviders = (providers: React.ReactElement[]) => {
|
|
59
|
-
const MultiProvider = ({ children }) => {
|
|
59
|
+
const MultiProvider = ({ children }: React.PropsWithChildren) => {
|
|
60
60
|
const [, refresh] = React.useReducer((n) => n + 1, 0);
|
|
61
61
|
if (!providersRegistry.refresh.value) {
|
|
62
62
|
providersRegistry.refresh.resolve(refresh);
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { transformer } from "../../mixin.ts";
|
|
7
6
|
import { Platform } from "../expose/Platform.ts";
|
|
8
7
|
import { React } from "../expose/React.ts";
|
|
9
8
|
import { mountRegistryAnchor } from "./mount.ts";
|
|
@@ -25,28 +24,7 @@ export default registry;
|
|
|
25
24
|
let refresh: (() => void) | undefined;
|
|
26
25
|
let historyHooked = false;
|
|
27
26
|
|
|
28
|
-
|
|
29
|
-
var __renderRoutes: any;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
globalThis.__renderRoutes = () => registry.all();
|
|
33
|
-
transformer(
|
|
34
|
-
(emit) => (str) => {
|
|
35
|
-
emit();
|
|
36
|
-
|
|
37
|
-
str = str.replace(
|
|
38
|
-
/(\(0,[a-zA-Z_$][\w$]*\.jsx\)\([a-zA-Z_$][\w$]*\.[a-zA-Z_$][\w$]*,\{[^{]*path:"\/search\/\*")/,
|
|
39
|
-
"...__renderRoutes(),$1",
|
|
40
|
-
);
|
|
41
|
-
|
|
42
|
-
return str;
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
glob: /^\/xpui\.js/,
|
|
46
|
-
},
|
|
47
|
-
);
|
|
48
|
-
|
|
49
|
-
// Transform-free path: the client router never learns module routes, so a
|
|
27
|
+
// The client router never learns module routes, so a
|
|
50
28
|
// history-driven overlay covers the main view whenever the current path
|
|
51
29
|
// matches a registered <Route>. The client renders its own not-found page
|
|
52
30
|
// underneath; back/forward keep working because only real history is used.
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
|
|
6
6
|
import { React } from "../expose/React.ts";
|
|
7
7
|
import { createIconComponent } from "../createIconComponent.tsx";
|
|
8
|
-
import { transformer } from "../../mixin.ts";
|
|
9
8
|
import { Tooltip } from "../webpack/ReactComponents.ts";
|
|
10
9
|
import { UI } from "../webpack/ComponentLibrary.ts";
|
|
11
10
|
import { mountRegistryAnchor } from "./mount.ts";
|
|
@@ -26,29 +25,6 @@ export default registry;
|
|
|
26
25
|
|
|
27
26
|
let refresh: React.DispatchWithoutAction | undefined;
|
|
28
27
|
|
|
29
|
-
declare global {
|
|
30
|
-
var __renderTopbarLeftButtons: () => React.ReactNode;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
globalThis.__renderTopbarLeftButtons = () =>
|
|
34
|
-
React.createElement(() => {
|
|
35
|
-
[, refresh] = React.useReducer((n) => n + 1, 0);
|
|
36
|
-
|
|
37
|
-
return <>{registry.all()}</>;
|
|
38
|
-
});
|
|
39
|
-
transformer(
|
|
40
|
-
(emit) => (str) => {
|
|
41
|
-
emit();
|
|
42
|
-
|
|
43
|
-
str = str.replace(/("top-bar-forward-button"[^\]]*)/g, "$1,__renderTopbarLeftButtons()");
|
|
44
|
-
|
|
45
|
-
return str;
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
glob: /^\/xpui\.js/,
|
|
49
|
-
},
|
|
50
|
-
);
|
|
51
|
-
|
|
52
28
|
mountRegistryAnchor({
|
|
53
29
|
className: "spicetify-topbar-left-buttons",
|
|
54
30
|
registry,
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
|
|
6
6
|
import { React } from "../expose/React.ts";
|
|
7
7
|
import { createIconComponent } from "../createIconComponent.tsx";
|
|
8
|
-
import { transformer } from "../../mixin.ts";
|
|
9
8
|
import { Tooltip } from "../webpack/ReactComponents.ts";
|
|
10
9
|
import { UI } from "../webpack/ComponentLibrary.ts";
|
|
11
10
|
import { mountRegistryAnchor } from "./mount.ts";
|
|
@@ -26,29 +25,6 @@ export default registry;
|
|
|
26
25
|
|
|
27
26
|
let refresh: React.DispatchWithoutAction | undefined;
|
|
28
27
|
|
|
29
|
-
declare global {
|
|
30
|
-
var __renderTopbarRightButtons: () => React.ReactNode;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
globalThis.__renderTopbarRightButtons = () =>
|
|
34
|
-
React.createElement(() => {
|
|
35
|
-
[, refresh] = React.useReducer((n) => n + 1, 0);
|
|
36
|
-
|
|
37
|
-
return <>{registry.all().reverse()}</>;
|
|
38
|
-
});
|
|
39
|
-
transformer(
|
|
40
|
-
(emit) => (str) => {
|
|
41
|
-
emit();
|
|
42
|
-
|
|
43
|
-
str = str.replace(/("login-button"[^\}]*\}[^\}]*\}[^\}]*\}\))/, "$1,__renderTopbarRightButtons()");
|
|
44
|
-
|
|
45
|
-
return str;
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
glob: /^\/xpui\.js/,
|
|
49
|
-
},
|
|
50
|
-
);
|
|
51
|
-
|
|
52
28
|
mountRegistryAnchor({
|
|
53
29
|
className: "spicetify-topbar-right-buttons",
|
|
54
30
|
registry,
|
|
@@ -55,7 +55,7 @@ export function createSyncedStorage(playlistUri: string) {
|
|
|
55
55
|
async function getUris(key: string) {
|
|
56
56
|
assertSmallerSize(encodeURIComponent(key), CHUNK_SIZE, 1, "Can't fit key in a single chunk");
|
|
57
57
|
|
|
58
|
-
const { items } = await PlaylistAPI.getContents(playlistUri, {
|
|
58
|
+
const { items }: { items: Array<{ uri: string }> } = await PlaylistAPI.getContents(playlistUri, {
|
|
59
59
|
filter: key,
|
|
60
60
|
limit: 1e9,
|
|
61
61
|
});
|
|
@@ -11,6 +11,7 @@ import { Platform } from "../expose/Platform.ts";
|
|
|
11
11
|
import { exportedFunctions, exportedMemos, modules, src } from "./index.ts";
|
|
12
12
|
import { webpackRequire } from "../wpunpk.mix.ts";
|
|
13
13
|
import { React } from "../expose/React.ts";
|
|
14
|
+
import { findPlaylistMenu } from "./playlist-menu.ts";
|
|
14
15
|
|
|
15
16
|
type SnackbarProviderT = any;
|
|
16
17
|
|
|
@@ -43,16 +44,9 @@ const requireExports = (id: ReturnType<typeof findModuleId>): Record<string, any
|
|
|
43
44
|
id === undefined ? {} : (webpackRequire(id) as Record<string, any>);
|
|
44
45
|
|
|
45
46
|
const ContextMenuModuleID = findModuleId("toggleContextMenu", (s) => s.includes("toggleContextMenu"));
|
|
46
|
-
// Dead since 1.2.9x: no module carries value:"playlist" + canView +
|
|
47
|
-
// permissions anymore; Menus.Playlist stays undefined until re-needled.
|
|
48
|
-
const playlistMenuModuleID = findModuleId(
|
|
49
|
-
'value:"playlist" + canView + permissions',
|
|
50
|
-
(s) => s.includes('value:"playlist"') && s.includes("canView") && s.includes("permissions"),
|
|
51
|
-
);
|
|
52
47
|
|
|
53
|
-
Menus.Playlist =
|
|
54
|
-
|
|
55
|
-
);
|
|
48
|
+
Menus.Playlist = findPlaylistMenu(modules, requireExports);
|
|
49
|
+
if (Menus.Playlist === undefined) warn("[stdlib] webpack needle miss: playlist menu component");
|
|
56
50
|
|
|
57
51
|
export const Cards: any = Object.assign(
|
|
58
52
|
{
|
|
@@ -105,7 +105,7 @@ export const from: (uri: ParsableAsURI) => URIClass<any> = findAndExcludeBy("all
|
|
|
105
105
|
export const fromString: (str: string) => URIClass<any> = findAndExcludeBy("Argument `uri` must be a string.") as any;
|
|
106
106
|
|
|
107
107
|
is.PlaylistV1OrV2 = (
|
|
108
|
-
is.Playlist && is.PlaylistV2
|
|
108
|
+
typeof is.Playlist === "function" && typeof is.PlaylistV2 === "function"
|
|
109
109
|
? findAndExcludeBy(`${(is.Playlist as Function).name}(e)||${(is.PlaylistV2 as Function).name}(e)`)
|
|
110
110
|
: undefined
|
|
111
111
|
) as any;
|