perfectionist-plugins 1.9.0 → 2.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 +143 -99
- package/better-client.js +1093 -0
- package/hide-unowned-skins.js +175 -58
- package/package.json +5 -3
- package/perfectionist-utils.js +69 -12
- package/unlock-profile-skins.js +153 -67
package/hide-unowned-skins.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
export const version = "2.
|
|
1
|
+
export const version = "2.5.1";
|
|
2
2
|
|
|
3
3
|
const PLUGIN_ID = "hide-unowned-skins";
|
|
4
4
|
const STYLE_ID = "perfectionist-hide-unowned-skins-style";
|
|
5
5
|
const HIDDEN_CLASS = "perf-unowned-skin-hidden";
|
|
6
6
|
const SKIN_ITEM_SELECTOR = ".skin-selection-item";
|
|
7
7
|
const DOT_SELECTOR = ".skin-selection-indicator-selector";
|
|
8
|
+
const SELECTED_CLASS = "skin-selection-item-selected";
|
|
8
9
|
const SESSION_URI = "/lol-champ-select/v1/session";
|
|
9
10
|
const SELECTION_URI = "/lol-champ-select/v1/session/my-selection";
|
|
10
11
|
const CAROUSEL_URI = "/lol-champ-select/v1/skin-carousel-skins";
|
|
@@ -17,7 +18,7 @@ const RETRY_MS = 5000;
|
|
|
17
18
|
const STATE_KEY = "perfectionist-plugins:state";
|
|
18
19
|
|
|
19
20
|
const CSS = `
|
|
20
|
-
.${HIDDEN_CLASS} { display: none !important; margin: 0 !important; padding: 0 !important; border: none !important; }
|
|
21
|
+
.${HIDDEN_CLASS} { display: none !important; margin: 0 !important; padding: 0 !important; border: none !important; pointer-events: none !important; }
|
|
21
22
|
[data-perf-dots] { justify-content: center !important; }
|
|
22
23
|
`;
|
|
23
24
|
|
|
@@ -161,13 +162,13 @@ function installFetchFilter() {
|
|
|
161
162
|
|
|
162
163
|
const wrapped = function (input, init) {
|
|
163
164
|
const url =
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
165
|
+
typeof input === "string"
|
|
166
|
+
? input
|
|
167
|
+
: typeof input?.url === "string"
|
|
168
|
+
? input.url
|
|
169
|
+
: typeof input?.href === "string"
|
|
170
|
+
? input.href
|
|
171
|
+
: "";
|
|
171
172
|
const promise = original.call(this, input, init);
|
|
172
173
|
if (!filterActive || url.split("?")[0].indexOf(CAROUSEL_URI) === -1) return promise;
|
|
173
174
|
return promise.then((response) => {
|
|
@@ -223,8 +224,7 @@ function installXhrFilter() {
|
|
|
223
224
|
const kept = filterSkinList(json);
|
|
224
225
|
recordClientOrder(kept ?? json);
|
|
225
226
|
if (kept) cache.filtered = JSON.stringify(kept);
|
|
226
|
-
} catch {
|
|
227
|
-
}
|
|
227
|
+
} catch {}
|
|
228
228
|
}
|
|
229
229
|
return cache.filtered;
|
|
230
230
|
};
|
|
@@ -270,6 +270,7 @@ let allSkinIds = null;
|
|
|
270
270
|
let currentChampionId = null;
|
|
271
271
|
let carouselOrder = [];
|
|
272
272
|
let selectedSkinId = null;
|
|
273
|
+
let lastServerSkinId = null;
|
|
273
274
|
let failure = null;
|
|
274
275
|
let requestToken = 0;
|
|
275
276
|
let refreshTimer = null;
|
|
@@ -279,11 +280,17 @@ let selectInFlight = false;
|
|
|
279
280
|
let sessionMisses = 0;
|
|
280
281
|
let championMisses = 0;
|
|
281
282
|
let renderObserver = null;
|
|
283
|
+
let sessionActive = false;
|
|
284
|
+
let repackTimer = null;
|
|
285
|
+
let removeStyle = null;
|
|
286
|
+
let lastSelectedDomIndex = -1;
|
|
287
|
+
let navFixAt = 0;
|
|
288
|
+
let lastSkipSignature = null;
|
|
289
|
+
let itemSkinIds = new WeakMap();
|
|
282
290
|
const repackOriginals = new Map();
|
|
283
291
|
const lastAppliedLeft = new Map();
|
|
284
292
|
const skinSuffixCache = new Map();
|
|
285
|
-
|
|
286
|
-
let removeStyle = null;
|
|
293
|
+
const widthCache = new Map();
|
|
287
294
|
|
|
288
295
|
function logError(scope, error) {
|
|
289
296
|
console.error(`[Perfectionist] Hide Unowned Skins: ${scope}`, error);
|
|
@@ -295,7 +302,8 @@ function skinSuffixOf(text) {
|
|
|
295
302
|
return /Skins\/Base\//i.test(text) ? 0 : null;
|
|
296
303
|
}
|
|
297
304
|
|
|
298
|
-
function
|
|
305
|
+
function skinIdFromImages(item) {
|
|
306
|
+
if (currentChampionId === null) return null;
|
|
299
307
|
for (const img of item.querySelectorAll("img[src]")) {
|
|
300
308
|
const src = img.getAttribute("src");
|
|
301
309
|
let suffix = skinSuffixCache.get(src);
|
|
@@ -309,6 +317,25 @@ function readSkinId(item) {
|
|
|
309
317
|
return suffix === null ? null : currentChampionId * 1000 + suffix;
|
|
310
318
|
}
|
|
311
319
|
|
|
320
|
+
function readSkinId(item) {
|
|
321
|
+
return itemSkinIds.get(item) ?? skinIdFromImages(item);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function effectiveOrder() {
|
|
325
|
+
return clientSkinOrder.length > 0 ? clientSkinOrder : carouselOrder;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function mapCarouselToItems(items) {
|
|
329
|
+
const order = effectiveOrder();
|
|
330
|
+
itemSkinIds = new WeakMap();
|
|
331
|
+
if (order.length === 0 || order.length !== items.length) return false;
|
|
332
|
+
items.forEach((item, index) => {
|
|
333
|
+
const id = order[index];
|
|
334
|
+
if (typeof id === "number") itemSkinIds.set(item, id);
|
|
335
|
+
});
|
|
336
|
+
return true;
|
|
337
|
+
}
|
|
338
|
+
|
|
312
339
|
function hideTargetFor(item) {
|
|
313
340
|
let target = item;
|
|
314
341
|
for (let depth = 0; depth < 2; depth++) {
|
|
@@ -327,6 +354,73 @@ function pruneRepack() {
|
|
|
327
354
|
}
|
|
328
355
|
}
|
|
329
356
|
|
|
357
|
+
function guardSelection(items) {
|
|
358
|
+
const selectedIndex = items.findIndex((item) => item.classList.contains(SELECTED_CLASS));
|
|
359
|
+
if (selectedIndex < 0) return false;
|
|
360
|
+
const previous = lastSelectedDomIndex;
|
|
361
|
+
lastSelectedDomIndex = selectedIndex;
|
|
362
|
+
const selected = items[selectedIndex];
|
|
363
|
+
const hidden = selected.classList.contains(HIDDEN_CLASS) || Boolean(selected.parentElement?.classList.contains(HIDDEN_CLASS));
|
|
364
|
+
if (!hidden) {
|
|
365
|
+
lastSkipSignature = null;
|
|
366
|
+
return false;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const visible = items.map((item, index) => (item.classList.contains(HIDDEN_CLASS) ? -1 : index)).filter((index) => index >= 0);
|
|
370
|
+
if (visible.length === 0) return false;
|
|
371
|
+
|
|
372
|
+
const forward = previous < 0 ? true : selectedIndex >= previous;
|
|
373
|
+
const target = forward
|
|
374
|
+
? (visible.find((index) => index > selectedIndex) ?? visible[0])
|
|
375
|
+
: ([...visible].reverse().find((index) => index < selectedIndex) ?? visible[visible.length - 1]);
|
|
376
|
+
const skinId = readSkinId(items[target]);
|
|
377
|
+
if (skinId === null) return false;
|
|
378
|
+
|
|
379
|
+
const signature = `${selectedIndex}:${skinId}`;
|
|
380
|
+
if (lastSkipSignature === signature) return true;
|
|
381
|
+
const now = Date.now();
|
|
382
|
+
if (now < navFixAt) return true;
|
|
383
|
+
navFixAt = now + 150;
|
|
384
|
+
lastSkipSignature = signature;
|
|
385
|
+
lastSelectedDomIndex = target;
|
|
386
|
+
selectSkin(skinId).catch((error) => logError("could not skip the unowned skin.", error));
|
|
387
|
+
return true;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function leftToPx(item, raw) {
|
|
391
|
+
if (!raw) return 0;
|
|
392
|
+
if (raw.endsWith("px")) return parseFloat(raw);
|
|
393
|
+
if (raw.endsWith("%")) return (parseFloat(raw) / 100) * (item.parentElement?.offsetWidth || 468);
|
|
394
|
+
return 0;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function originalLeftOf(item) {
|
|
398
|
+
const original = repackOriginals.get(item);
|
|
399
|
+
return leftToPx(item, original ? original.value : item.style.getPropertyValue("left"));
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function refreshOriginal(item) {
|
|
403
|
+
const current = item.style.getPropertyValue("left");
|
|
404
|
+
const priority = item.style.getPropertyPriority("left");
|
|
405
|
+
const last = lastAppliedLeft.get(item);
|
|
406
|
+
if (last !== undefined && current === last && priority === "important") return;
|
|
407
|
+
repackOriginals.set(item, { value: current, priority });
|
|
408
|
+
lastAppliedLeft.delete(item);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function widthOf(item, anchor) {
|
|
412
|
+
if (item.offsetWidth > 0) {
|
|
413
|
+
widthCache.set(item, item.offsetWidth);
|
|
414
|
+
return item.offsetWidth;
|
|
415
|
+
}
|
|
416
|
+
const measured = item.getBoundingClientRect().width;
|
|
417
|
+
if (measured > 0) {
|
|
418
|
+
widthCache.set(item, measured);
|
|
419
|
+
return measured;
|
|
420
|
+
}
|
|
421
|
+
return widthCache.get(item) ?? (item === anchor ? 117 : 73);
|
|
422
|
+
}
|
|
423
|
+
|
|
330
424
|
function repackVisibleItems(items) {
|
|
331
425
|
pruneRepack();
|
|
332
426
|
const visible = items.filter((item) => !item.classList.contains(HIDDEN_CLASS));
|
|
@@ -335,25 +429,15 @@ function repackVisibleItems(items) {
|
|
|
335
429
|
return;
|
|
336
430
|
}
|
|
337
431
|
|
|
338
|
-
const selectedIndex = items.findIndex((item) => item.classList.contains(
|
|
432
|
+
const selectedIndex = items.findIndex((item) => item.classList.contains(SELECTED_CLASS));
|
|
339
433
|
let anchor = selectedIndex >= 0 ? items[selectedIndex] : null;
|
|
340
434
|
if (!anchor && selectedSkinId !== null) {
|
|
341
435
|
anchor = visible.find((item) => readSkinId(item) === selectedSkinId) ?? null;
|
|
342
436
|
}
|
|
343
437
|
if (!anchor) anchor = visible[Math.floor(visible.length / 2)];
|
|
344
438
|
const anchorDomIndex = items.indexOf(anchor);
|
|
345
|
-
|
|
346
|
-
const
|
|
347
|
-
const toPx = (item) => {
|
|
348
|
-
const raw = item.style.left || "0";
|
|
349
|
-
if (raw.endsWith("px")) return parseFloat(raw);
|
|
350
|
-
const box = item.parentElement?.getBoundingClientRect().width || 468;
|
|
351
|
-
return (parseFloat(raw) / 100) * box;
|
|
352
|
-
};
|
|
353
|
-
|
|
354
|
-
const anchorLeft = toPx(anchor);
|
|
355
|
-
let rightX = anchorLeft + widthOf(anchor);
|
|
356
|
-
let leftX = anchorLeft;
|
|
439
|
+
refreshOriginal(anchor);
|
|
440
|
+
const anchorLeft = originalLeftOf(anchor);
|
|
357
441
|
|
|
358
442
|
const setLeft = (item, x) => {
|
|
359
443
|
const want = `${x}px`;
|
|
@@ -370,19 +454,23 @@ function repackVisibleItems(items) {
|
|
|
370
454
|
lastAppliedLeft.set(item, want);
|
|
371
455
|
};
|
|
372
456
|
|
|
373
|
-
|
|
374
|
-
|
|
457
|
+
let rightX = anchorLeft + widthOf(anchor, anchor);
|
|
458
|
+
for (let index = anchorDomIndex + 1; index < items.length; index++) {
|
|
459
|
+
const item = items[index];
|
|
460
|
+
if (item.classList.contains(HIDDEN_CLASS)) continue;
|
|
461
|
+
const width = widthOf(item, anchor);
|
|
462
|
+
setLeft(item, rightX);
|
|
463
|
+
rightX += width;
|
|
464
|
+
}
|
|
375
465
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
}
|
|
385
|
-
});
|
|
466
|
+
let leftX = anchorLeft;
|
|
467
|
+
for (let index = anchorDomIndex - 1; index >= 0; index--) {
|
|
468
|
+
const item = items[index];
|
|
469
|
+
if (item.classList.contains(HIDDEN_CLASS)) continue;
|
|
470
|
+
const width = widthOf(item, anchor);
|
|
471
|
+
leftX -= width;
|
|
472
|
+
setLeft(item, leftX);
|
|
473
|
+
}
|
|
386
474
|
}
|
|
387
475
|
|
|
388
476
|
function restoreRepack() {
|
|
@@ -400,6 +488,7 @@ function restoreRepack() {
|
|
|
400
488
|
|
|
401
489
|
function applyAll() {
|
|
402
490
|
const items = [...document.querySelectorAll(SKIN_ITEM_SELECTOR)];
|
|
491
|
+
mapCarouselToItems(items);
|
|
403
492
|
let hidden = 0;
|
|
404
493
|
items.forEach((item) => {
|
|
405
494
|
const skinId = currentChampionId === null ? null : readSkinId(item);
|
|
@@ -411,9 +500,10 @@ function applyAll() {
|
|
|
411
500
|
|
|
412
501
|
const dots = [...document.querySelectorAll(DOT_SELECTOR)];
|
|
413
502
|
let dotsHidden = 0;
|
|
414
|
-
const
|
|
503
|
+
const dotOrder = effectiveOrder();
|
|
504
|
+
const dotsMapped = dotOrder.length > 0 && dots.length === dotOrder.length;
|
|
415
505
|
dots.forEach((dot, index) => {
|
|
416
|
-
const skinId = dotsMapped ?
|
|
506
|
+
const skinId = dotsMapped ? dotOrder[index] : undefined;
|
|
417
507
|
const hide = ownedSkinIds !== null && skinId !== undefined && allSkinIds !== null && allSkinIds.has(skinId) && !ownedSkinIds.has(skinId);
|
|
418
508
|
if (hide) dotsHidden++;
|
|
419
509
|
dot.classList.toggle(HIDDEN_CLASS, hide);
|
|
@@ -424,8 +514,10 @@ function applyAll() {
|
|
|
424
514
|
else if (dotsHidden === 0 && dotList.hasAttribute("data-perf-dots")) dotList.removeAttribute("data-perf-dots");
|
|
425
515
|
}
|
|
426
516
|
|
|
427
|
-
if (ownedSkinIds !== null && hidden > 0)
|
|
428
|
-
|
|
517
|
+
if (ownedSkinIds !== null && hidden > 0) {
|
|
518
|
+
if (guardSelection(items)) runRepack();
|
|
519
|
+
else scheduleRepack();
|
|
520
|
+
} else if (hidden === 0) restoreRepack();
|
|
429
521
|
}
|
|
430
522
|
|
|
431
523
|
function scheduleRepack() {
|
|
@@ -433,6 +525,27 @@ function scheduleRepack() {
|
|
|
433
525
|
repackTimer = setTimeout(runRepack, REPACK_SETTLE_MS);
|
|
434
526
|
}
|
|
435
527
|
|
|
528
|
+
function startRenderObserver() {
|
|
529
|
+
if (!sessionActive || renderObserver || !api) return;
|
|
530
|
+
renderObserver = new MutationObserver(() => {
|
|
531
|
+
ensureSourceFilter();
|
|
532
|
+
const item = document.querySelector(SKIN_ITEM_SELECTOR);
|
|
533
|
+
if (!item && !document.querySelector(DOT_SELECTOR)) return;
|
|
534
|
+
applyAllGuarded();
|
|
535
|
+
if (item) scheduleRefresh();
|
|
536
|
+
});
|
|
537
|
+
try {
|
|
538
|
+
renderObserver.observe(document.documentElement ?? document, OBSERVER_OPTIONS);
|
|
539
|
+
} catch {
|
|
540
|
+
renderObserver = null;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
function stopRenderObserver() {
|
|
545
|
+
renderObserver?.disconnect();
|
|
546
|
+
renderObserver = null;
|
|
547
|
+
}
|
|
548
|
+
|
|
436
549
|
function runRepack() {
|
|
437
550
|
repackTimer = null;
|
|
438
551
|
renderObserver?.disconnect();
|
|
@@ -442,7 +555,7 @@ function runRepack() {
|
|
|
442
555
|
if (ownedSkinIds !== null && hidden) repackVisibleItems(items);
|
|
443
556
|
else restoreRepack();
|
|
444
557
|
} finally {
|
|
445
|
-
|
|
558
|
+
startRenderObserver();
|
|
446
559
|
}
|
|
447
560
|
}
|
|
448
561
|
|
|
@@ -451,7 +564,7 @@ function applyAllGuarded() {
|
|
|
451
564
|
try {
|
|
452
565
|
applyAll();
|
|
453
566
|
} finally {
|
|
454
|
-
|
|
567
|
+
startRenderObserver();
|
|
455
568
|
}
|
|
456
569
|
}
|
|
457
570
|
|
|
@@ -585,11 +698,13 @@ function updateForSession(session, selection) {
|
|
|
585
698
|
|
|
586
699
|
const knownUnownedSkin = allSkinIds !== null && allSkinIds.has(serverSkinId) && !ownedSkinIds.has(serverSkinId);
|
|
587
700
|
if (!selectInFlight && knownUnownedSkin) {
|
|
588
|
-
const
|
|
701
|
+
const direction = lastServerSkinId !== null && serverSkinId !== null && serverSkinId < lastServerSkinId ? -1 : 1;
|
|
702
|
+
const fallback = nextOwnedSkin(ownedSkinIds, serverSkinId ?? 0, direction) ?? nextOwnedSkin(ownedSkinIds, 0, 1);
|
|
589
703
|
if (fallback !== null) {
|
|
590
704
|
selectSkin(fallback).catch((error) => logError("could not correct the selected skin.", error));
|
|
591
705
|
}
|
|
592
706
|
}
|
|
707
|
+
if (!selectInFlight && belongsToChampion && ownedSkinIds.has(serverSkinId)) lastServerSkinId = serverSkinId;
|
|
593
708
|
|
|
594
709
|
applyAllGuarded();
|
|
595
710
|
}
|
|
@@ -606,11 +721,15 @@ function scheduleRefresh() {
|
|
|
606
721
|
function onSessionPush(raw) {
|
|
607
722
|
const msg = raw ?? {};
|
|
608
723
|
if (msg.eventType === "Delete") {
|
|
724
|
+
sessionActive = false;
|
|
725
|
+
stopRenderObserver();
|
|
609
726
|
resetState();
|
|
610
727
|
return;
|
|
611
728
|
}
|
|
612
729
|
const s = msg.data ?? msg;
|
|
613
730
|
if (!s?.myTeam) return;
|
|
731
|
+
sessionActive = true;
|
|
732
|
+
startRenderObserver();
|
|
614
733
|
updateForSession(s, null);
|
|
615
734
|
}
|
|
616
735
|
|
|
@@ -621,11 +740,17 @@ async function pollSession() {
|
|
|
621
740
|
try {
|
|
622
741
|
const session = await api.lcu.get(SESSION_URI);
|
|
623
742
|
sessionMisses = 0;
|
|
743
|
+
sessionActive = true;
|
|
744
|
+
startRenderObserver();
|
|
624
745
|
const selection = await api.lcu.get(SELECTION_URI).catch(() => null);
|
|
625
746
|
updateForSession(session, selection);
|
|
626
747
|
} catch {
|
|
627
748
|
sessionMisses++;
|
|
628
|
-
if (sessionMisses >= 3)
|
|
749
|
+
if (sessionMisses >= 3) {
|
|
750
|
+
sessionActive = false;
|
|
751
|
+
stopRenderObserver();
|
|
752
|
+
resetState();
|
|
753
|
+
}
|
|
629
754
|
} finally {
|
|
630
755
|
pollInFlight = false;
|
|
631
756
|
}
|
|
@@ -634,17 +759,9 @@ async function pollSession() {
|
|
|
634
759
|
function onEnable(utils) {
|
|
635
760
|
api = utils;
|
|
636
761
|
filterActive = true;
|
|
762
|
+
sessionActive = false;
|
|
637
763
|
removeStyle = api.addStyle(STYLE_ID, CSS);
|
|
638
764
|
|
|
639
|
-
renderObserver = new MutationObserver(() => {
|
|
640
|
-
ensureSourceFilter();
|
|
641
|
-
const item = document.querySelector(SKIN_ITEM_SELECTOR);
|
|
642
|
-
if (!item && !document.querySelector(DOT_SELECTOR)) return;
|
|
643
|
-
applyAllGuarded();
|
|
644
|
-
if (item) scheduleRefresh();
|
|
645
|
-
});
|
|
646
|
-
renderObserver.observe(document.documentElement ?? document, OBSERVER_OPTIONS);
|
|
647
|
-
|
|
648
765
|
window.addEventListener("resize", onWindowResize);
|
|
649
766
|
unsubscribe = api.subscribe(SESSION_URI, onSessionPush);
|
|
650
767
|
pollSession();
|
|
@@ -658,9 +775,9 @@ function onDisable() {
|
|
|
658
775
|
refreshTimer = null;
|
|
659
776
|
clearTimeout(repackTimer);
|
|
660
777
|
repackTimer = null;
|
|
661
|
-
|
|
778
|
+
sessionActive = false;
|
|
779
|
+
stopRenderObserver();
|
|
662
780
|
removeStyle?.();
|
|
663
|
-
renderObserver = null;
|
|
664
781
|
removeStyle = null;
|
|
665
782
|
filterActive = false;
|
|
666
783
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "perfectionist-plugins",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"description": "Small plugins that fix the annoying parts of League of Legends champ select. Built for Pengu Loader.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "perfectionist-utils.js",
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"./hide-unowned-skins.js": "./hide-unowned-skins.js",
|
|
12
12
|
"./unlock-profile-skins.js": "./unlock-profile-skins.js",
|
|
13
13
|
"./auto-claimer.js": "./auto-claimer.js",
|
|
14
|
-
"./auto-read.js": "./auto-read.js"
|
|
14
|
+
"./auto-read.js": "./auto-read.js",
|
|
15
|
+
"./better-client.js": "./better-client.js"
|
|
15
16
|
},
|
|
16
17
|
"files": [
|
|
17
18
|
"perfectionist-utils.js",
|
|
@@ -19,7 +20,8 @@
|
|
|
19
20
|
"hide-unowned-skins.js",
|
|
20
21
|
"unlock-profile-skins.js",
|
|
21
22
|
"auto-claimer.js",
|
|
22
|
-
"auto-read.js"
|
|
23
|
+
"auto-read.js",
|
|
24
|
+
"better-client.js"
|
|
23
25
|
],
|
|
24
26
|
"repository": {
|
|
25
27
|
"type": "git",
|
package/perfectionist-utils.js
CHANGED
|
@@ -6,7 +6,7 @@ const PANEL_ID = "perfectionist-plugins-panel";
|
|
|
6
6
|
const STYLE_ID = "perfectionist-plugins-style";
|
|
7
7
|
const TAB_TITLE = "Perfectionism";
|
|
8
8
|
|
|
9
|
-
export const version = "
|
|
9
|
+
export const version = "2.4.0";
|
|
10
10
|
|
|
11
11
|
const PKG = "perfectionist-plugins";
|
|
12
12
|
const CDN = "https://cdn.jsdelivr.net/npm";
|
|
@@ -22,6 +22,13 @@ const BUNDLED_MODULES = [
|
|
|
22
22
|
description: "Shared core for the Perfectionism plugins.",
|
|
23
23
|
core: true,
|
|
24
24
|
},
|
|
25
|
+
{
|
|
26
|
+
id: "better-client",
|
|
27
|
+
file: "better-client.js",
|
|
28
|
+
name: "BetterClient",
|
|
29
|
+
version: "1.7.0",
|
|
30
|
+
description: "Debloats the client: blocks telemetry, merges duplicate requests, caches immutable data, decodes images asynchronously and fixes several bugs.",
|
|
31
|
+
},
|
|
25
32
|
{
|
|
26
33
|
id: "ban-blocker",
|
|
27
34
|
file: "ban-blocker.js",
|
|
@@ -29,20 +36,20 @@ const BUNDLED_MODULES = [
|
|
|
29
36
|
version: "3.6.0",
|
|
30
37
|
description: "During bans, blocking a teammate instantly claims the champion they hover to ban.",
|
|
31
38
|
},
|
|
32
|
-
{
|
|
33
|
-
id: "hide-unowned-skins",
|
|
34
|
-
file: "hide-unowned-skins.js",
|
|
35
|
-
name: "Hide Unowned Skins",
|
|
36
|
-
version: "2.4.0",
|
|
37
|
-
description: "In champion select, only shows the skins you own for the picked champion.",
|
|
38
|
-
},
|
|
39
39
|
{
|
|
40
40
|
id: "unlock-profile-skins",
|
|
41
41
|
file: "unlock-profile-skins.js",
|
|
42
42
|
name: "Unlock Profile Skins",
|
|
43
|
-
version: "1.2.
|
|
43
|
+
version: "1.2.4",
|
|
44
44
|
description: "Unlocks every skin in the Set profile background window.",
|
|
45
45
|
},
|
|
46
|
+
{
|
|
47
|
+
id: "hide-unowned-skins",
|
|
48
|
+
file: "hide-unowned-skins.js",
|
|
49
|
+
name: "Hide Unowned Skins",
|
|
50
|
+
version: "2.5.1",
|
|
51
|
+
description: "In champion select, only shows the skins you own for the picked champion.",
|
|
52
|
+
},
|
|
46
53
|
{
|
|
47
54
|
id: "auto-claimer",
|
|
48
55
|
file: "auto-claimer.js",
|
|
@@ -120,6 +127,9 @@ const STYLE_CSS = `
|
|
|
120
127
|
#${PANEL_ID} .perf-chip:hover { color: #f0e6d2; border-color: #c89b3c; }
|
|
121
128
|
#${PANEL_ID} .perf-chip--on { color: #010a13; background: #c89b3c; border-color: #c89b3c; }
|
|
122
129
|
#${PANEL_ID} .perf-chip--on:hover { background: #f0e6d2; }
|
|
130
|
+
#${PANEL_ID} .perf-info { margin-top: 11px; padding-top: 8px; border-top: 1px solid rgba(120, 90, 40, 0.35); }
|
|
131
|
+
#${PANEL_ID} .perf-info-line { font-size: 10px; line-height: 16px; letter-spacing: 0.2px; color: #a09b8c;
|
|
132
|
+
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
123
133
|
|
|
124
134
|
#${PANEL_ID} .perf-tools { display: flex; align-items: center; gap: 10px; flex: 0 0 auto; margin-bottom: 14px; }
|
|
125
135
|
#${PANEL_ID} .perf-search { flex: 1 1 auto; min-width: 0; box-sizing: border-box;
|
|
@@ -334,6 +344,14 @@ function importModule(url) {
|
|
|
334
344
|
return coords?.importModule ? coords.importModule(url) : import(url);
|
|
335
345
|
}
|
|
336
346
|
|
|
347
|
+
// Lets a feature module (BetterClient → Pages Remake) pull its code only when enabled.
|
|
348
|
+
// A local override keeps the dev harness on freshly edited files instead of the CDN copy.
|
|
349
|
+
function loadPluginFile(file) {
|
|
350
|
+
const local = typeof window !== "undefined" ? window.PerfectionistLocalBase : null;
|
|
351
|
+
const url = local ? new URL(file, local).href : moduleUrl(file);
|
|
352
|
+
return importModule(url);
|
|
353
|
+
}
|
|
354
|
+
|
|
337
355
|
async function installModule(mod) {
|
|
338
356
|
state.installed[mod.id] = true;
|
|
339
357
|
delete state.enabled[mod.id];
|
|
@@ -629,6 +647,17 @@ function buildOptionRow(mod, option, disabled, onChange) {
|
|
|
629
647
|
return row;
|
|
630
648
|
}
|
|
631
649
|
|
|
650
|
+
function buildInfo(option) {
|
|
651
|
+
const block = el("div", "perf-info");
|
|
652
|
+
if (option.label) block.append(el("div", "perf-chips-label", option.label));
|
|
653
|
+
let lines = [];
|
|
654
|
+
try {
|
|
655
|
+
lines = option.lines?.() ?? [];
|
|
656
|
+
} catch {}
|
|
657
|
+
for (const line of lines) block.append(el("div", "perf-info-line", String(line)));
|
|
658
|
+
return block;
|
|
659
|
+
}
|
|
660
|
+
|
|
632
661
|
function buildChips(mod, option, locked) {
|
|
633
662
|
const block = el("div", "perf-chip-block");
|
|
634
663
|
block.title = option.description ?? "";
|
|
@@ -651,6 +680,28 @@ function buildChips(mod, option, locked) {
|
|
|
651
680
|
return block;
|
|
652
681
|
}
|
|
653
682
|
|
|
683
|
+
function buildAction(option, locked) {
|
|
684
|
+
const button = el("button", "perf-btn", option.label);
|
|
685
|
+
button.type = "button";
|
|
686
|
+
button.title = option.description ?? "";
|
|
687
|
+
button.disabled = Boolean(locked);
|
|
688
|
+
button.addEventListener("click", async () => {
|
|
689
|
+
const original = button.textContent;
|
|
690
|
+
button.disabled = true;
|
|
691
|
+
try {
|
|
692
|
+
await option.onClick?.();
|
|
693
|
+
button.textContent = "Copied";
|
|
694
|
+
} catch {
|
|
695
|
+
button.textContent = "Copy failed";
|
|
696
|
+
}
|
|
697
|
+
setTimeout(() => {
|
|
698
|
+
button.textContent = original;
|
|
699
|
+
button.disabled = Boolean(locked);
|
|
700
|
+
}, 1600);
|
|
701
|
+
});
|
|
702
|
+
return button;
|
|
703
|
+
}
|
|
704
|
+
|
|
654
705
|
function buildOptions(mod, locked) {
|
|
655
706
|
const options = registry.get(mod.id)?.options ?? [];
|
|
656
707
|
if (options.length === 0) return null;
|
|
@@ -660,6 +711,14 @@ function buildOptions(mod, locked) {
|
|
|
660
711
|
wrap.append(buildChips(mod, option, locked));
|
|
661
712
|
continue;
|
|
662
713
|
}
|
|
714
|
+
if (option.type === "info") {
|
|
715
|
+
wrap.append(buildInfo(option));
|
|
716
|
+
continue;
|
|
717
|
+
}
|
|
718
|
+
if (option.type === "action") {
|
|
719
|
+
wrap.append(buildAction(option, locked));
|
|
720
|
+
continue;
|
|
721
|
+
}
|
|
663
722
|
if (!option.children?.length) {
|
|
664
723
|
wrap.append(buildOptionRow(mod, option, Boolean(locked)));
|
|
665
724
|
continue;
|
|
@@ -741,9 +800,6 @@ function installedModules() {
|
|
|
741
800
|
return getAvailableModules().filter(isInstalled);
|
|
742
801
|
}
|
|
743
802
|
|
|
744
|
-
// The master switch is a UI lock only: it greys out every card's settings and
|
|
745
|
-
// toggle (same treatment as a disabled parent's child options) while each
|
|
746
|
-
// plugin keeps its own enabled/disabled state untouched.
|
|
747
803
|
function masterLocked() {
|
|
748
804
|
return installedModules().length > 0 && state.masterLocked === true;
|
|
749
805
|
}
|
|
@@ -1016,6 +1072,7 @@ const utils = {
|
|
|
1016
1072
|
getAvailableModules,
|
|
1017
1073
|
installModule,
|
|
1018
1074
|
uninstallModule,
|
|
1075
|
+
loadPluginFile,
|
|
1019
1076
|
checkForUpdates,
|
|
1020
1077
|
watch,
|
|
1021
1078
|
waitFor,
|