diff --git a/torn-attribute-tracker.user.js b/torn-attribute-tracker.user.js index d9c0c51..062b9f0 100644 --- a/torn-attribute-tracker.user.js +++ b/torn-attribute-tracker.user.js @@ -315,7 +315,7 @@ this.mode = opts.initialMode || 'free'; if (this.mode === 'free') { root.style.bottom = '20px'; - root.style.right = '20px'; + root.style.left = '20px'; if (opts.initialPos && (opts.initialPos.x || opts.initialPos.y)) { root.style.transform = 'translate(' + opts.initialPos.x + 'px, ' + opts.initialPos.y + 'px)'; } @@ -327,7 +327,7 @@ this.mode = mode; if (!this.root) return; 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 { this.root.style.top = '20px'; this.root.style.left = '50%'; this.root.style.transform = 'translateX(-50%)'; } } @@ -446,87 +446,102 @@ function start() { if (window.__tat_started) return; window.__tat_started = true; - const store = new Store({ storage: localStorage, onWarn: function (m) { console.warn(m); } }); - const prefs = store.getPrefs(); + try { + const store = new Store({ storage: localStorage, onWarn: function (m) { console.warn(m); } }); + const prefs = store.getPrefs(); - const dialog = new Dialog({ - onTargetChange: function (v) { - const a = currentAttribute(); if (!a) return; store.setTarget(a.attr, v); render(); - }, - onModeChange: function (m) { store.setMode(m); prefs.mode = m; applyMode(); }, - onPosChange: function (pos) { store.setPos(pos); }, - onClose: function () { dialog.destroy(); }, - }); + // State that applyMode() and render() may touch on first call. + // Declared up-front to avoid TDZ ReferenceError if applyMode()'s + // anchor-miss branch fires before the natural declaration point. + let lastSnapshot = null; + let lastAttr = null; + let lastDelta = 0; + let anchorError = null; - dialog.mount({ initialMode: prefs.mode, initialPos: prefs.pos }); - applyMode(); + // One-time migration: dialog now defaults to bottom-left, so reset any + // 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; - let lastAttr = null; - let lastDelta = 0; - let anchorError = null; + const dialog = new Dialog({ + onTargetChange: function (v) { + const a = currentAttribute(); if (!a) return; store.setTarget(a.attr, v); render(); + }, + onModeChange: function (m) { store.setMode(m); prefs.mode = m; applyMode(); }, + onPosChange: function (pos) { store.setPos(pos); }, + onClose: function () { dialog.destroy(); }, + }); - 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, - }; - } + dialog.mount({ initialMode: prefs.mode, initialPos: prefs.pos }); + applyMode(); - 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() { - if (prefs.mode === 'anchored') { - const el = findAnchorElement(); - if (el) { - const rect = el.getBoundingClientRect(); - dialog.setMode('anchored', { canAnchor: true }); - dialog._positionAnchored(rect); - if (typeof ResizeObserver !== 'undefined') { - const ro = new ResizeObserver(function () { - if (prefs.mode === 'anchored') dialog._positionAnchored(el.getBoundingClientRect()); - }); - ro.observe(el); + 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); + if (typeof ResizeObserver !== 'undefined') { + const ro = new ResizeObserver(function () { + 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; } - // 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; + anchorError = null; + dialog.setMode('free'); } - anchorError = null; - dialog.setMode('free'); - } - render(); + render(); - let pending = false; - const observer = new MutationObserver(function () { - if (pending) return; - pending = true; - requestAnimationFrame(function () { - pending = false; - const a = currentAttribute(); - if (a && (a.attr !== lastAttr || a.current !== (lastSnapshot && lastSnapshot.current))) render(); + let pending = false; + const observer = new MutationObserver(function () { + if (pending) return; + pending = true; + requestAnimationFrame(function () { + pending = false; + const a = currentAttribute(); + 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; - startRequestInterceptor({ - prevValue: prev, currentAttr: lastAttr, - 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); }, - }); + const prev = (currentAttribute() && currentAttribute().current) || 0; + startRequestInterceptor({ + prevValue: prev, currentAttr: lastAttr, + 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); }, + }); + } catch (e) { + console.error('[tat] failed to start:', e); + } } // ===== self-test (only when location.hash === '#tat-test') =====