* feat(ui/tracker): start tag n watch * fix(tracker): test coverage, fix some watcher api * fix(tracker): add intersectionobserver, adjust tests * feat(tracker): relay + apollo plugins * feat(ui): tags search * feat(ui): tags name edit * feat(ui): tags search icon * feat(ui): icons for tabs in player * feat(ui): save and find button * feat(tracker): save tags in session storage (just in case) * feat(ui): improve loading * feat(ui): fix icon names gen * feat(ui): fix typo
158 lines
4.2 KiB
TypeScript
158 lines
4.2 KiB
TypeScript
import type Screen from './Screen';
|
|
import styles from './marker.module.css';
|
|
import { finder } from '@medv/finder';
|
|
|
|
const metaCharsMap = {
|
|
'&': '&',
|
|
'<': '<',
|
|
'>': '>',
|
|
'"': '"',
|
|
"'": ''',
|
|
'/': '/',
|
|
'`': '`',
|
|
'=': '=',
|
|
};
|
|
|
|
function escapeHtml(str: string) {
|
|
return String(str).replace(/[&<>"'`=\/]/g, function (s) {
|
|
// @ts-ignore
|
|
return metaCharsMap[s];
|
|
});
|
|
}
|
|
|
|
function escapeRegExp(string: string) {
|
|
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
}
|
|
|
|
function safeString(string: string) {
|
|
return escapeHtml(escapeRegExp(string));
|
|
}
|
|
|
|
export default class Marker {
|
|
private _target: Element | null = null;
|
|
private selector: string | null = null;
|
|
private readonly tooltip: HTMLDivElement;
|
|
private readonly tooltipSelector: HTMLDivElement;
|
|
private readonly tooltipHint: HTMLDivElement;
|
|
private marker: HTMLDivElement;
|
|
|
|
constructor(private readonly overlay: HTMLElement, private readonly screen: Screen) {
|
|
this.tooltip = document.createElement('div');
|
|
this.tooltip.className = styles.tooltip;
|
|
this.tooltipSelector = document.createElement('div');
|
|
this.tooltipHint = document.createElement('div');
|
|
this.tooltipHint.innerText = '(click to tag element)'
|
|
this.tooltipHint.className = styles.tooltipHint;
|
|
this.tooltip.append(this.tooltipSelector, this.tooltipHint);
|
|
|
|
const marker = document.createElement('div');
|
|
marker.className = styles.marker;
|
|
const markerL = document.createElement('div');
|
|
const markerR = document.createElement('div');
|
|
const markerT = document.createElement('div');
|
|
const markerB = document.createElement('div');
|
|
markerL.className = styles.markerL;
|
|
markerR.className = styles.markerR;
|
|
markerT.className = styles.markerT;
|
|
markerB.className = styles.markerB;
|
|
marker.appendChild(markerL);
|
|
marker.appendChild(markerR);
|
|
marker.appendChild(markerT);
|
|
marker.appendChild(markerB);
|
|
|
|
marker.appendChild(this.tooltip);
|
|
|
|
overlay.appendChild(marker);
|
|
this.marker = marker;
|
|
}
|
|
|
|
get target() {
|
|
return this._target;
|
|
}
|
|
|
|
mark(element: Element | null) {
|
|
if (this._target === element) {
|
|
return;
|
|
}
|
|
this._target = element;
|
|
this.selector = null;
|
|
this.redraw();
|
|
}
|
|
|
|
unmark() {
|
|
this.mark(null);
|
|
}
|
|
|
|
private autodefineTarget() {
|
|
if (this.selector && this.screen.document) {
|
|
try {
|
|
const fitTargets = this.screen.document.querySelectorAll(this.selector);
|
|
if (fitTargets.length === 0) {
|
|
this._target = null;
|
|
} else {
|
|
this._target = fitTargets[0];
|
|
// const cursorTarget = this.screen.getCursorTarget();
|
|
// fitTargets.forEach((target) => {
|
|
// if (target.contains(cursorTarget)) {
|
|
// this._target = target;
|
|
// }
|
|
// });
|
|
}
|
|
} catch (e) {
|
|
console.info(e);
|
|
}
|
|
} else {
|
|
this._target = null;
|
|
}
|
|
}
|
|
|
|
markBySelector(selector: string) {
|
|
this.selector = selector;
|
|
this.lastSelector = selector;
|
|
this.autodefineTarget();
|
|
this.redraw();
|
|
}
|
|
|
|
lastSelector = '';
|
|
private getTagString(el: Element) {
|
|
if (!this.screen.document) return '';
|
|
const selector = finder(el, {
|
|
root: this.screen.document.body,
|
|
seedMinLength: 3,
|
|
optimizedMinLength: 2,
|
|
threshold: 1000,
|
|
maxNumberOfTries: 10_000,
|
|
});
|
|
this.lastSelector = selector;
|
|
return selector
|
|
}
|
|
|
|
redraw() {
|
|
if (this.selector) {
|
|
this.autodefineTarget();
|
|
}
|
|
if (!this._target) {
|
|
this.marker.style.display = 'none';
|
|
return;
|
|
}
|
|
const rect = this._target.getBoundingClientRect();
|
|
this.marker.style.display = 'block';
|
|
this.marker.style.left = rect.left + 'px';
|
|
this.marker.style.top = rect.top + 'px';
|
|
this.marker.style.width = rect.width + 'px';
|
|
this.marker.style.height = rect.height + 'px';
|
|
|
|
const replayScale = this.screen.getScale()
|
|
if (replayScale < 1) {
|
|
const upscale = (1 / replayScale).toFixed(3);
|
|
const yShift = ((1 - replayScale)/2) * 100;
|
|
this.tooltip.style.transform = `scale(${upscale}) translateY(-${yShift + 0.5}%)`
|
|
}
|
|
this.tooltipSelector.textContent = this.getTagString(this._target);
|
|
}
|
|
|
|
|
|
clean() {
|
|
this.marker.remove();
|
|
}
|
|
}
|