Skip to content

Commit debeeba

Browse files
committed
feat: enhance tv-board-zoom functionality with new mutation handling and improve bull-off theme layout
1 parent c7223d6 commit debeeba

7 files changed

Lines changed: 419 additions & 12 deletions

File tree

dist/autodarts-xconfig.user.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30624,6 +30624,11 @@ div.css-nfhdnc {
3062430624
backdrop-filter: blur(1px);
3062530625
}
3062630626

30627+
#ad-ext-player-display .ad-ext-player > .chakra-stack{
30628+
grid-template-rows: max-content max-content !important;
30629+
align-content: center !important;
30630+
}
30631+
3062730632
#ad-ext-player-display .ad-ext-player.ad-ext-player-active{
3062830633
border: 2px solid rgba(102, 187, 106, var(--bull-active-border-alpha)) !important;
3062930634
box-shadow:
@@ -30652,6 +30657,11 @@ div.css-nfhdnc {
3065230657
color: #9ca8b9 !important;
3065330658
}
3065430659

30660+
#ad-ext-player-display .ad-ext-player.ad-ext-player-inactive p.chakra-text.ad-ext-player-score,
30661+
#ad-ext-player-display .ad-ext-player.ad-ext-player-inactive .ad-ext_winner-score-wrapper > p{
30662+
font-size: 7.2em !important;
30663+
}
30664+
3065530665
span.css-3fr5p8{
3065630666
background: linear-gradient(90deg, var(--bull-green), var(--bull-red));
3065730667
color: #101215;

src/features/themes/bull-off/style.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,11 @@ div.css-nfhdnc {
129129
backdrop-filter: blur(1px);
130130
}
131131
132+
#ad-ext-player-display .ad-ext-player > .chakra-stack{
133+
grid-template-rows: max-content max-content !important;
134+
align-content: center !important;
135+
}
136+
132137
#ad-ext-player-display .ad-ext-player.ad-ext-player-active{
133138
border: 2px solid rgba(102, 187, 106, var(--bull-active-border-alpha)) !important;
134139
box-shadow:
@@ -157,6 +162,11 @@ div.css-nfhdnc {
157162
color: #9ca8b9 !important;
158163
}
159164
165+
#ad-ext-player-display .ad-ext-player.ad-ext-player-inactive p.chakra-text.ad-ext-player-score,
166+
#ad-ext-player-display .ad-ext-player.ad-ext-player-inactive .ad-ext_winner-score-wrapper > p{
167+
font-size: 7.2em !important;
168+
}
169+
160170
span.css-3fr5p8{
161171
background: linear-gradient(90deg, var(--bull-green), var(--bull-red));
162172
color: #101215;

src/features/tv-board-zoom/index.js

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,20 @@ const THROW_HISTORY_CLICK_SELECTORS = Object.freeze([
3131
"#ad-ext-turn .ad-ext-turn-throw",
3232
".ad-ext-turn-throw",
3333
]);
34+
const ZOOM_STRUCTURE_TARGET_SELECTORS = Object.freeze([
35+
"svg",
36+
".showAnimations",
37+
".ad-ext-theme-board-canvas",
38+
".ad-ext-theme-board-viewport",
39+
".ad-ext-theme-board-panel",
40+
".ad-ext-theme-content-board",
41+
]);
42+
const ZOOM_SEMANTIC_CONTAINER_SELECTORS = Object.freeze([
43+
".suggestion",
44+
".ad-ext-player-score",
45+
"#ad-ext-turn",
46+
".ad-ext-turn-throw",
47+
]);
3448

3549
function isThrowHistoryClickTarget(targetNode) {
3650
if (!targetNode || typeof targetNode.closest !== "function") {
@@ -48,6 +62,143 @@ function resolveZoomLevel(zoomLevel) {
4862
return 2.75;
4963
}
5064

65+
function toElementNode(node) {
66+
let current = node || null;
67+
while (current && current.nodeType !== 1) {
68+
current = current.parentNode || null;
69+
}
70+
return current || null;
71+
}
72+
73+
function elementMatchesAnySelector(node, selectors = []) {
74+
const elementNode = toElementNode(node);
75+
if (!elementNode || typeof elementNode.matches !== "function") {
76+
return false;
77+
}
78+
79+
return selectors.some((selector) => {
80+
try {
81+
return elementNode.matches(selector);
82+
} catch (_) {
83+
return false;
84+
}
85+
});
86+
}
87+
88+
function nodeOrAncestorMatchesAnySelector(node, selectors = []) {
89+
const elementNode = toElementNode(node);
90+
if (!elementNode || typeof elementNode.closest !== "function") {
91+
return false;
92+
}
93+
94+
return selectors.some((selector) => {
95+
try {
96+
return Boolean(elementNode.closest(selector));
97+
} catch (_) {
98+
return false;
99+
}
100+
});
101+
}
102+
103+
function getTouchedMutationNodes(mutation) {
104+
const nodes = [];
105+
const pushNode = (node) => {
106+
if (node) {
107+
nodes.push(node);
108+
}
109+
};
110+
111+
pushNode(mutation?.target || null);
112+
Array.from(mutation?.addedNodes || []).forEach(pushNode);
113+
Array.from(mutation?.removedNodes || []).forEach(pushNode);
114+
return nodes;
115+
}
116+
117+
function isDirectWatchedNodeMutation(mutation, watchedNodes = []) {
118+
if (!Array.isArray(watchedNodes) || !watchedNodes.length) {
119+
return false;
120+
}
121+
122+
return getTouchedMutationNodes(mutation).some((node) => watchedNodes.includes(node));
123+
}
124+
125+
function isDescendantWatchedNodeMutation(mutation, watchedNodes = []) {
126+
if (!Array.isArray(watchedNodes) || !watchedNodes.length) {
127+
return false;
128+
}
129+
130+
return getTouchedMutationNodes(mutation).some((touchedNode) => {
131+
return watchedNodes.some((watchedNode) => {
132+
if (!watchedNode || touchedNode === watchedNode) {
133+
return touchedNode === watchedNode;
134+
}
135+
136+
if (typeof touchedNode?.contains === "function" && touchedNode.contains(watchedNode)) {
137+
return true;
138+
}
139+
if (typeof watchedNode?.contains === "function" && watchedNode.contains(touchedNode)) {
140+
return true;
141+
}
142+
return false;
143+
});
144+
});
145+
}
146+
147+
export function shouldScheduleTvBoardZoomMutation(mutations = [], context = {}) {
148+
if (!Array.isArray(mutations) || !mutations.length) {
149+
return false;
150+
}
151+
152+
const watchedNodes = [
153+
context.boardSurface?.svg || null,
154+
context.boardSurface?.group || null,
155+
context.boardSurface?.zoomTarget || null,
156+
context.boardSurface?.zoomHost || null,
157+
context.zoomState?.zoomedElement || null,
158+
context.zoomState?.zoomHost || null,
159+
].filter(Boolean);
160+
161+
return mutations.some((mutation) => {
162+
if (!mutation || typeof mutation !== "object") {
163+
return false;
164+
}
165+
166+
const mutationType = String(
167+
mutation.type ||
168+
(mutation.attributeName
169+
? "attributes"
170+
: mutation.addedNodes || mutation.removedNodes
171+
? "childList"
172+
: "")
173+
);
174+
175+
if (mutationType === "attributes") {
176+
return (
177+
isDirectWatchedNodeMutation(mutation, watchedNodes) ||
178+
elementMatchesAnySelector(mutation.target, ZOOM_STRUCTURE_TARGET_SELECTORS)
179+
);
180+
}
181+
182+
if (mutationType === "characterData") {
183+
return nodeOrAncestorMatchesAnySelector(mutation.target, ZOOM_SEMANTIC_CONTAINER_SELECTORS);
184+
}
185+
186+
if (mutationType === "childList") {
187+
return (
188+
isDescendantWatchedNodeMutation(mutation, watchedNodes) ||
189+
getTouchedMutationNodes(mutation).some((node) =>
190+
nodeOrAncestorMatchesAnySelector(node, [
191+
...ZOOM_STRUCTURE_TARGET_SELECTORS,
192+
...ZOOM_SEMANTIC_CONTAINER_SELECTORS,
193+
])
194+
)
195+
);
196+
}
197+
198+
return false;
199+
});
200+
}
201+
51202
function getNodeClassName(node) {
52203
if (!node || typeof node.getAttribute !== "function") {
53204
return "";
@@ -163,6 +314,7 @@ export function initializeTvBoardZoom(context = {}) {
163314
lastTurnId: "",
164315
lastThrowCount: -1,
165316
lastAppliedSignature: "",
317+
lastAppliedIntentSignature: "",
166318
releaseTimeoutId: 0,
167319
targetStyleSnapshot: null,
168320
hostStyleSnapshot: null,
@@ -337,6 +489,9 @@ export function initializeTvBoardZoom(context = {}) {
337489
if (!hasExternalDomMutation(mutations, isManagedNode)) {
338490
return;
339491
}
492+
if (!shouldScheduleTvBoardZoomMutation(mutations, { boardSurface: boardCache.surface, zoomState })) {
493+
return;
494+
}
340495
invalidateBoardCache();
341496
scheduler.schedule();
342497
},

src/features/tv-board-zoom/logic.js

Lines changed: 50 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ const CHECKOUT_DOUBLE_ZOOM_RANGE = Object.freeze({
3434
min: 2.35,
3535
max: 3.15,
3636
});
37+
const TRANSFORM_SIGNATURE_STEP_PX = 0.5;
3738

3839
function parseScoreText(text) {
3940
const match = String(text || "").match(/-?\d+/);
@@ -165,6 +166,19 @@ export function getBestVisibleScoreFromDom(documentRef, windowRef) {
165166
return candidates[0].value;
166167
}
167168

169+
function quantizeForSignature(value, step = TRANSFORM_SIGNATURE_STEP_PX) {
170+
if (!Number.isFinite(value)) {
171+
return 0;
172+
}
173+
174+
const numericStep = Number(step);
175+
if (!Number.isFinite(numericStep) || numericStep <= 0) {
176+
return value;
177+
}
178+
179+
return Math.round(value / numericStep) * numericStep;
180+
}
181+
168182
function parseSegmentWithFallback(segmentName, x01Rules) {
169183
if (x01Rules && typeof x01Rules.parseSegment === "function") {
170184
const parsed = x01Rules.parseSegment(segmentName);
@@ -931,18 +945,22 @@ export function buildZoomTransform(options = {}) {
931945
}
932946

933947
const transform = `translate(${tx.toFixed(2)}px, ${ty.toFixed(2)}px) scale(${zoomLevel.toFixed(4)})`;
934-
const signature = [
935-
baseTransform || "none",
936-
tx.toFixed(2),
937-
ty.toFixed(2),
938-
zoomLevel.toFixed(4),
948+
const intentSignature = [
939949
String(segmentPoint.parsedSegment?.normalized || intent.segment || ""),
940950
String(intent.reason || ""),
951+
zoomLevel.toFixed(4),
952+
].join("|");
953+
const signature = [
954+
baseTransform || "none",
955+
quantizeForSignature(tx).toFixed(2),
956+
quantizeForSignature(ty).toFixed(2),
957+
intentSignature,
941958
].join("|");
942959

943960
return {
944961
transform,
945962
baseTransform,
963+
intentSignature,
946964
signature,
947965
anchor,
948966
tx,
@@ -1271,6 +1289,7 @@ export function applyZoom(
12711289
restoreTargetStyle(state, state.zoomedElement);
12721290
state.zoomedElement = null;
12731291
state.lastAppliedSignature = "";
1292+
state.lastAppliedIntentSignature = "";
12741293
}
12751294
if (state.zoomHost && state.zoomHost !== hostNode) {
12761295
restoreHostStyle(state, state.zoomHost);
@@ -1299,30 +1318,47 @@ export function applyZoom(
12991318
}
13001319
if (hostNode?.classList) {
13011320
cacheHostStyle(state, hostNode);
1302-
hostNode.classList.add(ZOOM_HOST_CLASS);
1303-
setStyleWithPriority(hostNode.style, "overflow", "hidden", "important");
1304-
setStyleWithPriority(hostNode.style, "overflow-x", "hidden", "important");
1305-
setStyleWithPriority(hostNode.style, "overflow-y", "hidden", "important");
1321+
if (!hostNode.classList.contains(ZOOM_HOST_CLASS)) {
1322+
hostNode.classList.add(ZOOM_HOST_CLASS);
1323+
}
1324+
if (getStyleValue(hostNode.style, "overflow") !== "hidden") {
1325+
setStyleWithPriority(hostNode.style, "overflow", "hidden", "important");
1326+
}
1327+
if (getStyleValue(hostNode.style, "overflow-x") !== "hidden") {
1328+
setStyleWithPriority(hostNode.style, "overflow-x", "hidden", "important");
1329+
}
1330+
if (getStyleValue(hostNode.style, "overflow-y") !== "hidden") {
1331+
setStyleWithPriority(hostNode.style, "overflow-y", "hidden", "important");
1332+
}
13061333
}
13071334
applyGifOverlayContainment(state, targetNode, hostNode || targetNode);
13081335

13091336
const composedTransform = zoomData.baseTransform
13101337
? `${zoomData.baseTransform} ${zoomData.transform}`
13111338
: zoomData.transform;
1339+
const isSameVisualIntent =
1340+
state.zoomedElement === targetNode &&
1341+
state.zoomHost === (hostNode || null) &&
1342+
state.lastAppliedIntentSignature === zoomData.intentSignature;
13121343
if (state.zoomedElement === targetNode && state.lastAppliedSignature === zoomData.signature) {
13131344
state.zoomHost = hostNode || null;
13141345
return zoomData;
13151346
}
13161347

1317-
targetNode.classList.add(ZOOM_CLASS);
1348+
if (!targetNode.classList.contains(ZOOM_CLASS)) {
1349+
targetNode.classList.add(ZOOM_CLASS);
1350+
}
13181351
targetNode.style.transformOrigin = "0 0";
13191352
targetNode.style.willChange = "transform";
1320-
targetNode.style.transition = `transform ${speedConfig.zoomInMs}ms ${speedConfig.easingIn}`;
1353+
targetNode.style.transition = isSameVisualIntent
1354+
? "none"
1355+
: `transform ${speedConfig.zoomInMs}ms ${speedConfig.easingIn}`;
13211356
targetNode.style.transform = composedTransform;
13221357

13231358
state.zoomedElement = targetNode;
13241359
state.zoomHost = hostNode || null;
13251360
state.lastAppliedSignature = zoomData.signature;
1361+
state.lastAppliedIntentSignature = zoomData.intentSignature;
13261362
return zoomData;
13271363
}
13281364

@@ -1343,6 +1379,7 @@ export function resetZoom(speedConfig, state, immediate = false) {
13431379
state.zoomHost = null;
13441380
state.hostStyleSnapshot = null;
13451381
state.lastAppliedSignature = "";
1382+
state.lastAppliedIntentSignature = "";
13461383
return;
13471384
}
13481385

@@ -1358,6 +1395,7 @@ export function resetZoom(speedConfig, state, immediate = false) {
13581395
state.targetStyleSnapshot = null;
13591396
state.hostStyleSnapshot = null;
13601397
state.lastAppliedSignature = "";
1398+
state.lastAppliedIntentSignature = "";
13611399
return;
13621400
}
13631401

@@ -1384,5 +1422,6 @@ export function resetZoom(speedConfig, state, immediate = false) {
13841422
}
13851423

13861424
state.lastAppliedSignature = "";
1425+
state.lastAppliedIntentSignature = "";
13871426
}, releaseDelay);
13881427
}

tests/runtime/theme-template-parity.test.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,10 @@ test("bull-off keeps oldrepo board-first grid and no preview spacer", () => {
113113
css,
114114
/#ad-ext-player-display\s+\.ad-ext-player\.ad-ext-player-active\s*>\s*\.chakra-stack,\s*#ad-ext-player-display\s+\.ad-ext-player\.ad-ext-player-winner\s*>\s*\.chakra-stack\s*\{[^}]*grid-template-rows:\s*max-content max-content\s*!important;[^}]*align-content:\s*center\s*!important;/s
115115
);
116+
assert.match(
117+
css,
118+
/#ad-ext-player-display\s+\.ad-ext-player\s*>\s*\.chakra-stack\s*\{[^}]*grid-template-rows:\s*max-content max-content\s*!important;[^}]*align-content:\s*center\s*!important;/s
119+
);
116120
assert.match(
117121
css,
118122
/#ad-ext-player-display\s+\.ad-ext-player\s*>\s*\.chakra-stack\s*\{[^}]*display:\s*grid\s*!important;[^}]*grid-template-columns:\s*minmax\(0,\s*1fr\)\s*max-content\s*!important;[^}]*gap:\s*0px\s*!important;[^}]*min-width:\s*0\s*!important;/s
@@ -121,6 +125,10 @@ test("bull-off keeps oldrepo board-first grid and no preview spacer", () => {
121125
css,
122126
/#ad-ext-player-display\s+\.ad-ext-player\s+\.ad-ext-player-score\s*\{[^}]*justify-self:\s*end\s*!important;[^}]*min-width:\s*max-content\s*!important;[^}]*white-space:\s*nowrap\s*!important;/s
123127
);
128+
assert.match(
129+
css,
130+
/#ad-ext-player-display\s+\.ad-ext-player\.ad-ext-player-inactive\s+p\.chakra-text\.ad-ext-player-score,\s*#ad-ext-player-display\s+\.ad-ext-player\.ad-ext-player-inactive\s+\.ad-ext_winner-score-wrapper\s*>\s*p\s*\{[^}]*font-size:\s*7\.2em\s*!important;/s
131+
);
124132
assert.match(css, /grid-template-columns:\s*0\.94fr 1\.06fr\s*!important;/);
125133
assert.match(
126134
css,

0 commit comments

Comments
 (0)