fix(ui): escape user strings, lazy drag listeners, drop dead warn field
This commit is contained in:
@@ -65,6 +65,16 @@ function fmtDate(d) {
|
|||||||
return d.toLocaleDateString('en-US', { weekday: 'short', day: '2-digit', month: 'short', year: 'numeric' });
|
return d.toLocaleDateString('en-US', { weekday: 'short', day: '2-digit', month: 'short', year: 'numeric' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function esc(s) {
|
||||||
|
if (s == null) return '';
|
||||||
|
return String(s)
|
||||||
|
.replace(/&/g, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/'/g, ''');
|
||||||
|
}
|
||||||
|
|
||||||
export class Dialog {
|
export class Dialog {
|
||||||
constructor({ onTargetChange, onModeChange, onPosChange, onClose } = {}) {
|
constructor({ onTargetChange, onModeChange, onPosChange, onClose } = {}) {
|
||||||
this.onTargetChange = onTargetChange;
|
this.onTargetChange = onTargetChange;
|
||||||
@@ -74,7 +84,6 @@ export class Dialog {
|
|||||||
this.root = null;
|
this.root = null;
|
||||||
this.dragState = null;
|
this.dragState = null;
|
||||||
this.mode = 'free';
|
this.mode = 'free';
|
||||||
this.warn = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mount({ initialMode = 'free', initialPos = { x: 0, y: 0 } } = {}) {
|
mount({ initialMode = 'free', initialPos = { x: 0, y: 0 } } = {}) {
|
||||||
@@ -136,31 +145,34 @@ export class Dialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_wireHeaderDrag() {
|
_wireHeaderDrag() {
|
||||||
const onDown = (e) => {
|
const self = this;
|
||||||
if (this.mode !== 'free') return;
|
this.root.addEventListener('mousedown', (e) => {
|
||||||
|
if (self.mode !== 'free') return;
|
||||||
if (e.target.classList.contains('tat-close')) return;
|
if (e.target.classList.contains('tat-close')) return;
|
||||||
const rect = this.root.getBoundingClientRect();
|
const rect = self.root.getBoundingClientRect();
|
||||||
this.dragState = { dx: e.clientX - rect.left, dy: e.clientY - rect.top };
|
self.dragState = { dx: e.clientX - rect.left, dy: e.clientY - rect.top };
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
};
|
|
||||||
const onMove = (e) => {
|
const onMove = (ev) => {
|
||||||
if (!this.dragState) return;
|
if (!self.dragState) return;
|
||||||
const x = e.clientX - this.dragState.dx;
|
const x = ev.clientX - self.dragState.dx;
|
||||||
const y = e.clientY - this.dragState.dy;
|
const y = ev.clientY - self.dragState.dy;
|
||||||
this.root.style.left = `${x}px`;
|
self.root.style.left = x + 'px';
|
||||||
this.root.style.top = `${y}px`;
|
self.root.style.top = y + 'px';
|
||||||
this.root.style.right = 'auto';
|
self.root.style.right = 'auto';
|
||||||
this.root.style.bottom = 'auto';
|
self.root.style.bottom = 'auto';
|
||||||
};
|
};
|
||||||
const onUp = () => {
|
const onUp = () => {
|
||||||
if (!this.dragState) return;
|
if (!self.dragState) return;
|
||||||
const rect = this.root.getBoundingClientRect();
|
const r = self.root.getBoundingClientRect();
|
||||||
this.dragState = null;
|
self.dragState = null;
|
||||||
this.onPosChange && this.onPosChange({ x: rect.left, y: rect.top });
|
self.onPosChange && self.onPosChange({ x: r.left, y: r.top });
|
||||||
|
document.removeEventListener('mousemove', onMove);
|
||||||
|
document.removeEventListener('mouseup', onUp);
|
||||||
};
|
};
|
||||||
this.root.addEventListener('mousedown', onDown);
|
|
||||||
document.addEventListener('mousemove', onMove);
|
document.addEventListener('mousemove', onMove);
|
||||||
document.addEventListener('mouseup', onUp);
|
document.addEventListener('mouseup', onUp);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render(state) {
|
render(state) {
|
||||||
@@ -169,7 +181,7 @@ export class Dialog {
|
|||||||
if (error) {
|
if (error) {
|
||||||
this.root.innerHTML = `
|
this.root.innerHTML = `
|
||||||
<div class="tat-header"><strong>⚙ Attribute Tracker</strong><span class="tat-close">✕</span></div>
|
<div class="tat-header"><strong>⚙ Attribute Tracker</strong><span class="tat-close">✕</span></div>
|
||||||
<div class="tat-error">${error}<button data-action="reload">Reload</button></div>
|
<div class="tat-error">${esc(error)}<button data-action="reload">Reload</button></div>
|
||||||
`;
|
`;
|
||||||
this.root.querySelector('[data-action="reload"]').onclick = () => location.reload();
|
this.root.querySelector('[data-action="reload"]').onclick = () => location.reload();
|
||||||
this.root.querySelector('.tat-close').onclick = () => this.onClose && this.onClose();
|
this.root.querySelector('.tat-close').onclick = () => this.onClose && this.onClose();
|
||||||
@@ -192,7 +204,7 @@ export class Dialog {
|
|||||||
<strong>⚙ Attribute Tracker</strong>
|
<strong>⚙ Attribute Tracker</strong>
|
||||||
<span class="tat-close" title="Hide for this session">✕</span>
|
<span class="tat-close" title="Hide for this session">✕</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="tat-row"><span>Attribute</span><span><strong>${attr || '—'}</strong> · <em>${gym || '—'}</em></span></div>
|
<div class="tat-row"><span>Attribute</span><span><strong>${esc(attr)}</strong> · <em>${esc(gym)}</em></span></div>
|
||||||
<div class="tat-row"><span>Current</span><span>${fmtFull(current)}</span></div>
|
<div class="tat-row"><span>Current</span><span>${fmtFull(current)}</span></div>
|
||||||
<div class="tat-row tat-target">
|
<div class="tat-row tat-target">
|
||||||
<span>Target</span>
|
<span>Target</span>
|
||||||
@@ -211,7 +223,7 @@ export class Dialog {
|
|||||||
<div class="tat-row"><span>Trains to go</span><span>≈ ${fmtFull(est.trainsToGo)}</span></div>
|
<div class="tat-row"><span>Trains to go</span><span>≈ ${fmtFull(est.trainsToGo)}</span></div>
|
||||||
<div class="tat-row"><span>ETA</span><span>${est.days > 0 ? `~ ${fmtFull(est.days)} days (${fmtDate(est.eta)})` : '—'}</span></div>
|
<div class="tat-row"><span>ETA</span><span>${est.days > 0 ? `~ ${fmtFull(est.days)} days (${fmtDate(est.eta)})` : '—'}</span></div>
|
||||||
<div class="tat-modes">${modes}</div>
|
<div class="tat-modes">${modes}</div>
|
||||||
${warn ? `<div class="tat-warn">⚠ ${warn}</div>` : ''}
|
${warn ? `<div class="tat-warn">⚠ ${esc(warn)}</div>` : ''}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
this.root.querySelector('.tat-close').onclick = () => this.onClose && this.onClose();
|
this.root.querySelector('.tat-close').onclick = () => this.onClose && this.onClose();
|
||||||
|
|||||||
Reference in New Issue
Block a user