Compare commits
3 Commits
578736a492
...
76e3ba2488
| Author | SHA1 | Date | |
|---|---|---|---|
| 76e3ba2488 | |||
| b03cc80665 | |||
| 501c6746eb |
+110
-95
@@ -35,113 +35,128 @@ function findAnchorElement() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function start() {
|
function start() {
|
||||||
const store = new Store({
|
try {
|
||||||
storage: localStorage,
|
const store = new Store({
|
||||||
onWarn: (m) => console.warn(m),
|
storage: localStorage,
|
||||||
});
|
onWarn: (m) => console.warn(m),
|
||||||
const prefs = store.getPrefs();
|
});
|
||||||
|
const prefs = store.getPrefs();
|
||||||
|
|
||||||
const dialog = new Dialog({
|
// State that applyMode() and render() may touch on first call.
|
||||||
onTargetChange: (v) => {
|
// Declared up-front to avoid TDZ ReferenceError if applyMode()'s
|
||||||
const attr = currentAttribute()?.attr;
|
// anchor-miss branch fires before the natural declaration point.
|
||||||
if (!attr) return;
|
let lastSnapshot = null;
|
||||||
store.setTarget(attr, v);
|
let lastAttr = null;
|
||||||
render();
|
let lastDelta = 0;
|
||||||
},
|
let anchorError = null;
|
||||||
onModeChange: (m) => {
|
|
||||||
store.setMode(m);
|
|
||||||
prefs.mode = m;
|
|
||||||
applyMode();
|
|
||||||
},
|
|
||||||
onPosChange: (pos) => store.setPos(pos),
|
|
||||||
onClose: () => dialog.destroy(),
|
|
||||||
});
|
|
||||||
|
|
||||||
dialog.mount({ initialMode: prefs.mode, initialPos: prefs.pos });
|
// One-time migration: dialog now defaults to bottom-left, so reset any
|
||||||
applyMode();
|
// previously-saved position from the bottom-right era.
|
||||||
|
if (prefs.pos && (prefs.pos.x !== 0 || prefs.pos.y !== 0)) {
|
||||||
let lastSnapshot = null;
|
console.info('[tat] resetting dialog position to new bottom-left default');
|
||||||
let lastAttr = null;
|
prefs.pos = { x: 0, y: 0 };
|
||||||
let lastDelta = 0;
|
store.setPos(prefs.pos);
|
||||||
let anchorError = null;
|
|
||||||
|
|
||||||
function snapshot() {
|
|
||||||
const a = currentAttribute();
|
|
||||||
if (!a) {
|
|
||||||
return { error: "Couldn't read attribute — Torn may have updated the page." };
|
|
||||||
}
|
}
|
||||||
lastAttr = a.attr;
|
|
||||||
const summary = store.getSummary(a.attr);
|
|
||||||
return {
|
|
||||||
attr: a.attr,
|
|
||||||
gym: a.gym,
|
|
||||||
current: a.current,
|
|
||||||
target: store.getTarget(a.attr),
|
|
||||||
perTrain: lastDelta,
|
|
||||||
summary,
|
|
||||||
warn: store._saveDisabled ? 'saving disabled this session' : null,
|
|
||||||
anchorError: anchorError,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function render() {
|
const dialog = new Dialog({
|
||||||
lastSnapshot = snapshot();
|
onTargetChange: (v) => {
|
||||||
dialog.render(lastSnapshot);
|
const attr = currentAttribute()?.attr;
|
||||||
}
|
if (!attr) return;
|
||||||
|
store.setTarget(attr, v);
|
||||||
|
render();
|
||||||
|
},
|
||||||
|
onModeChange: (m) => {
|
||||||
|
store.setMode(m);
|
||||||
|
prefs.mode = m;
|
||||||
|
applyMode();
|
||||||
|
},
|
||||||
|
onPosChange: (pos) => store.setPos(pos),
|
||||||
|
onClose: () => dialog.destroy(),
|
||||||
|
});
|
||||||
|
|
||||||
function applyMode() {
|
dialog.mount({ initialMode: prefs.mode, initialPos: prefs.pos });
|
||||||
if (prefs.mode === 'anchored') {
|
applyMode();
|
||||||
const el = findAnchorElement();
|
|
||||||
if (el) {
|
function snapshot() {
|
||||||
const rect = el.getBoundingClientRect();
|
const a = currentAttribute();
|
||||||
dialog.setMode('anchored', { canAnchor: true });
|
if (!a) {
|
||||||
dialog._positionAnchored(rect);
|
return { error: "Couldn't read attribute — Torn may have updated the page." };
|
||||||
// observe
|
}
|
||||||
const ro = new ResizeObserver(() => {
|
lastAttr = a.attr;
|
||||||
if (prefs.mode === 'anchored') dialog._positionAnchored(el.getBoundingClientRect());
|
const summary = store.getSummary(a.attr);
|
||||||
});
|
return {
|
||||||
ro.observe(el);
|
attr: a.attr,
|
||||||
anchorError = null;
|
gym: a.gym,
|
||||||
|
current: a.current,
|
||||||
|
target: store.getTarget(a.attr),
|
||||||
|
perTrain: lastDelta,
|
||||||
|
summary,
|
||||||
|
warn: store._saveDisabled ? 'saving disabled this session' : null,
|
||||||
|
anchorError: anchorError,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
lastSnapshot = snapshot();
|
||||||
|
dialog.render(lastSnapshot);
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyMode() {
|
||||||
|
if (prefs.mode === 'anchored') {
|
||||||
|
const el = findAnchorElement();
|
||||||
|
if (el) {
|
||||||
|
const rect = el.getBoundingClientRect();
|
||||||
|
dialog.setMode('anchored', { canAnchor: true });
|
||||||
|
dialog._positionAnchored(rect);
|
||||||
|
// observe
|
||||||
|
const ro = new ResizeObserver(() => {
|
||||||
|
if (prefs.mode === 'anchored') dialog._positionAnchored(el.getBoundingClientRect());
|
||||||
|
});
|
||||||
|
ro.observe(el);
|
||||||
|
anchorError = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Anchor selector missed — don't snap to default, just keep current
|
||||||
|
// position and show a note.
|
||||||
|
anchorError = "Couldn't find the training form on this page.";
|
||||||
|
render();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Anchor selector missed — don't snap to default, just keep current
|
anchorError = null;
|
||||||
// position and show a note.
|
dialog.setMode('free');
|
||||||
anchorError = "Couldn't find the training form on this page.";
|
|
||||||
render();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
anchorError = null;
|
|
||||||
dialog.setMode('free');
|
|
||||||
}
|
|
||||||
|
|
||||||
// initial paint
|
// initial paint
|
||||||
render();
|
render();
|
||||||
|
|
||||||
// watch DOM for attribute changes
|
// watch DOM for attribute changes
|
||||||
let pending = false;
|
let pending = false;
|
||||||
const observer = new MutationObserver(() => {
|
const observer = new MutationObserver(() => {
|
||||||
if (pending) return;
|
if (pending) return;
|
||||||
pending = true;
|
pending = true;
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
pending = false;
|
pending = false;
|
||||||
const a = currentAttribute();
|
const a = currentAttribute();
|
||||||
if (a && (a.attr !== lastAttr || a.current !== lastSnapshot?.current)) render();
|
if (a && (a.attr !== lastAttr || a.current !== lastSnapshot?.current)) render();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
observer.observe(document.body, { childList: true, subtree: true, characterData: true });
|
||||||
observer.observe(document.body, { childList: true, subtree: true, characterData: true });
|
|
||||||
|
|
||||||
// intercept train requests
|
// intercept train requests
|
||||||
const prev = currentAttribute()?.current ?? 0;
|
const prev = currentAttribute()?.current ?? 0;
|
||||||
startRequestInterceptor({
|
startRequestInterceptor({
|
||||||
prevValue: prev,
|
prevValue: prev,
|
||||||
currentAttr: lastAttr,
|
currentAttr: lastAttr,
|
||||||
onTrain: ({ attr, delta, ts }) => {
|
onTrain: ({ attr, delta, ts }) => {
|
||||||
store.recordTrain(attr, delta, ts);
|
store.recordTrain(attr, delta, ts);
|
||||||
lastDelta = delta;
|
lastDelta = delta;
|
||||||
render();
|
render();
|
||||||
},
|
},
|
||||||
onParseFail: (url) => console.warn('[tat] could not parse train response from', url),
|
onParseFail: (url) => console.warn('[tat] could not parse train response from', url),
|
||||||
});
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error('[tat] failed to start:', e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (location.hash === '#tat-test') {
|
if (location.hash === '#tat-test') {
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ export class Dialog {
|
|||||||
|
|
||||||
if (initialMode === 'free') {
|
if (initialMode === 'free') {
|
||||||
root.style.bottom = '20px';
|
root.style.bottom = '20px';
|
||||||
root.style.right = '20px';
|
root.style.left = '20px';
|
||||||
if (initialPos.x || initialPos.y) {
|
if (initialPos.x || initialPos.y) {
|
||||||
root.style.transform = `translate(${initialPos.x}px, ${initialPos.y}px)`;
|
root.style.transform = `translate(${initialPos.x}px, ${initialPos.y}px)`;
|
||||||
}
|
}
|
||||||
@@ -124,7 +124,7 @@ export class Dialog {
|
|||||||
this.root.style.right = '';
|
this.root.style.right = '';
|
||||||
if (mode === 'free') {
|
if (mode === 'free') {
|
||||||
this.root.style.bottom = '20px';
|
this.root.style.bottom = '20px';
|
||||||
this.root.style.right = '20px';
|
this.root.style.left = '20px';
|
||||||
} else if (anchorInfo && anchorInfo.canAnchor) {
|
} else if (anchorInfo && anchorInfo.canAnchor) {
|
||||||
this._positionAnchored(anchorInfo.rect);
|
this._positionAnchored(anchorInfo.rect);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -315,7 +315,7 @@
|
|||||||
this.mode = opts.initialMode || 'free';
|
this.mode = opts.initialMode || 'free';
|
||||||
if (this.mode === 'free') {
|
if (this.mode === 'free') {
|
||||||
root.style.bottom = '20px';
|
root.style.bottom = '20px';
|
||||||
root.style.right = '20px';
|
root.style.left = '20px';
|
||||||
if (opts.initialPos && (opts.initialPos.x || opts.initialPos.y)) {
|
if (opts.initialPos && (opts.initialPos.x || opts.initialPos.y)) {
|
||||||
root.style.transform = 'translate(' + opts.initialPos.x + 'px, ' + opts.initialPos.y + 'px)';
|
root.style.transform = 'translate(' + opts.initialPos.x + 'px, ' + opts.initialPos.y + 'px)';
|
||||||
}
|
}
|
||||||
@@ -327,7 +327,7 @@
|
|||||||
this.mode = mode;
|
this.mode = mode;
|
||||||
if (!this.root) return;
|
if (!this.root) return;
|
||||||
this.root.style.transform = ''; this.root.style.top = ''; this.root.style.bottom = ''; this.root.style.left = ''; this.root.style.right = '';
|
this.root.style.transform = ''; this.root.style.top = ''; this.root.style.bottom = ''; this.root.style.left = ''; this.root.style.right = '';
|
||||||
if (mode === 'free') { this.root.style.bottom = '20px'; this.root.style.right = '20px'; }
|
if (mode === 'free') { this.root.style.bottom = '20px'; this.root.style.left = '20px'; }
|
||||||
else if (anchorInfo && anchorInfo.canAnchor) { this._positionAnchored(anchorInfo.rect); }
|
else if (anchorInfo && anchorInfo.canAnchor) { this._positionAnchored(anchorInfo.rect); }
|
||||||
else { this.root.style.top = '20px'; this.root.style.left = '50%'; this.root.style.transform = 'translateX(-50%)'; }
|
else { this.root.style.top = '20px'; this.root.style.left = '50%'; this.root.style.transform = 'translateX(-50%)'; }
|
||||||
}
|
}
|
||||||
@@ -446,87 +446,102 @@
|
|||||||
|
|
||||||
function start() {
|
function start() {
|
||||||
if (window.__tat_started) return; window.__tat_started = true;
|
if (window.__tat_started) return; window.__tat_started = true;
|
||||||
const store = new Store({ storage: localStorage, onWarn: function (m) { console.warn(m); } });
|
try {
|
||||||
const prefs = store.getPrefs();
|
const store = new Store({ storage: localStorage, onWarn: function (m) { console.warn(m); } });
|
||||||
|
const prefs = store.getPrefs();
|
||||||
|
|
||||||
const dialog = new Dialog({
|
// State that applyMode() and render() may touch on first call.
|
||||||
onTargetChange: function (v) {
|
// Declared up-front to avoid TDZ ReferenceError if applyMode()'s
|
||||||
const a = currentAttribute(); if (!a) return; store.setTarget(a.attr, v); render();
|
// anchor-miss branch fires before the natural declaration point.
|
||||||
},
|
let lastSnapshot = null;
|
||||||
onModeChange: function (m) { store.setMode(m); prefs.mode = m; applyMode(); },
|
let lastAttr = null;
|
||||||
onPosChange: function (pos) { store.setPos(pos); },
|
let lastDelta = 0;
|
||||||
onClose: function () { dialog.destroy(); },
|
let anchorError = null;
|
||||||
});
|
|
||||||
|
|
||||||
dialog.mount({ initialMode: prefs.mode, initialPos: prefs.pos });
|
// One-time migration: dialog now defaults to bottom-left, so reset any
|
||||||
applyMode();
|
// previously-saved position from the bottom-right era.
|
||||||
|
if (prefs.pos && (prefs.pos.x !== 0 || prefs.pos.y !== 0)) {
|
||||||
|
console.info('[tat] resetting dialog position to new bottom-left default');
|
||||||
|
prefs.pos = { x: 0, y: 0 };
|
||||||
|
store.setPos(prefs.pos);
|
||||||
|
}
|
||||||
|
|
||||||
let lastSnapshot = null;
|
const dialog = new Dialog({
|
||||||
let lastAttr = null;
|
onTargetChange: function (v) {
|
||||||
let lastDelta = 0;
|
const a = currentAttribute(); if (!a) return; store.setTarget(a.attr, v); render();
|
||||||
let anchorError = null;
|
},
|
||||||
|
onModeChange: function (m) { store.setMode(m); prefs.mode = m; applyMode(); },
|
||||||
|
onPosChange: function (pos) { store.setPos(pos); },
|
||||||
|
onClose: function () { dialog.destroy(); },
|
||||||
|
});
|
||||||
|
|
||||||
function snapshot() {
|
dialog.mount({ initialMode: prefs.mode, initialPos: prefs.pos });
|
||||||
const a = currentAttribute();
|
applyMode();
|
||||||
if (!a) return { error: "Couldn't read attribute — Torn may have updated the page." };
|
|
||||||
lastAttr = a.attr;
|
|
||||||
const summary = store.getSummary(a.attr);
|
|
||||||
return {
|
|
||||||
attr: a.attr, gym: a.gym, current: a.current,
|
|
||||||
target: store.getTarget(a.attr), perTrain: lastDelta, summary: summary,
|
|
||||||
warn: store._saveDisabled ? 'saving disabled this session' : null,
|
|
||||||
anchorError: anchorError,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function render() { lastSnapshot = snapshot(); dialog.render(lastSnapshot); }
|
function snapshot() {
|
||||||
|
const a = currentAttribute();
|
||||||
|
if (!a) return { error: "Couldn't read attribute — Torn may have updated the page." };
|
||||||
|
lastAttr = a.attr;
|
||||||
|
const summary = store.getSummary(a.attr);
|
||||||
|
return {
|
||||||
|
attr: a.attr, gym: a.gym, current: a.current,
|
||||||
|
target: store.getTarget(a.attr), perTrain: lastDelta, summary: summary,
|
||||||
|
warn: store._saveDisabled ? 'saving disabled this session' : null,
|
||||||
|
anchorError: anchorError,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function applyMode() {
|
function render() { lastSnapshot = snapshot(); dialog.render(lastSnapshot); }
|
||||||
if (prefs.mode === 'anchored') {
|
|
||||||
const el = findAnchorElement();
|
function applyMode() {
|
||||||
if (el) {
|
if (prefs.mode === 'anchored') {
|
||||||
const rect = el.getBoundingClientRect();
|
const el = findAnchorElement();
|
||||||
dialog.setMode('anchored', { canAnchor: true });
|
if (el) {
|
||||||
dialog._positionAnchored(rect);
|
const rect = el.getBoundingClientRect();
|
||||||
if (typeof ResizeObserver !== 'undefined') {
|
dialog.setMode('anchored', { canAnchor: true });
|
||||||
const ro = new ResizeObserver(function () {
|
dialog._positionAnchored(rect);
|
||||||
if (prefs.mode === 'anchored') dialog._positionAnchored(el.getBoundingClientRect());
|
if (typeof ResizeObserver !== 'undefined') {
|
||||||
});
|
const ro = new ResizeObserver(function () {
|
||||||
ro.observe(el);
|
if (prefs.mode === 'anchored') dialog._positionAnchored(el.getBoundingClientRect());
|
||||||
|
});
|
||||||
|
ro.observe(el);
|
||||||
|
}
|
||||||
|
anchorError = null;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
anchorError = null;
|
// Anchor selector missed — don't snap to default, just keep current
|
||||||
|
// position and show a note.
|
||||||
|
anchorError = "Couldn't find the training form on this page.";
|
||||||
|
render();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Anchor selector missed — don't snap to default, just keep current
|
anchorError = null;
|
||||||
// position and show a note.
|
dialog.setMode('free');
|
||||||
anchorError = "Couldn't find the training form on this page.";
|
|
||||||
render();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
anchorError = null;
|
|
||||||
dialog.setMode('free');
|
|
||||||
}
|
|
||||||
|
|
||||||
render();
|
render();
|
||||||
|
|
||||||
let pending = false;
|
let pending = false;
|
||||||
const observer = new MutationObserver(function () {
|
const observer = new MutationObserver(function () {
|
||||||
if (pending) return;
|
if (pending) return;
|
||||||
pending = true;
|
pending = true;
|
||||||
requestAnimationFrame(function () {
|
requestAnimationFrame(function () {
|
||||||
pending = false;
|
pending = false;
|
||||||
const a = currentAttribute();
|
const a = currentAttribute();
|
||||||
if (a && (a.attr !== lastAttr || a.current !== (lastSnapshot && lastSnapshot.current))) render();
|
if (a && (a.attr !== lastAttr || a.current !== (lastSnapshot && lastSnapshot.current))) render();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
observer.observe(document.body, { childList: true, subtree: true, characterData: true });
|
||||||
observer.observe(document.body, { childList: true, subtree: true, characterData: true });
|
|
||||||
|
|
||||||
const prev = (currentAttribute() && currentAttribute().current) || 0;
|
const prev = (currentAttribute() && currentAttribute().current) || 0;
|
||||||
startRequestInterceptor({
|
startRequestInterceptor({
|
||||||
prevValue: prev, currentAttr: lastAttr,
|
prevValue: prev, currentAttr: lastAttr,
|
||||||
onTrain: function (e) { store.recordTrain(e.attr, e.delta, e.ts); lastDelta = e.delta; render(); },
|
onTrain: function (e) { store.recordTrain(e.attr, e.delta, e.ts); lastDelta = e.delta; render(); },
|
||||||
onParseFail: function (url) { console.warn('[tat] could not parse train response from', url); },
|
onParseFail: function (url) { console.warn('[tat] could not parse train response from', url); },
|
||||||
});
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error('[tat] failed to start:', e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== self-test (only when location.hash === '#tat-test') =====
|
// ===== self-test (only when location.hash === '#tat-test') =====
|
||||||
|
|||||||
Reference in New Issue
Block a user