You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
260 lines
9.4 KiB
260 lines
9.4 KiB
// Copyright (C) 2021 Leigh Morresi (dgtlmoon@gmail.com)
|
|
// All rights reserved.
|
|
// yes - this is really a hack, if you are a front-ender and want to help, please get in touch!
|
|
|
|
let runInClearMode = false;
|
|
|
|
$(document).ready(() => {
|
|
let currentSelections = [];
|
|
let currentSelection = null;
|
|
let appendToList = false;
|
|
let c, xctx, ctx;
|
|
let xScale = 1, yScale = 1;
|
|
let selectorImage, selectorImageRect, selectorData;
|
|
|
|
|
|
// Global jQuery selectors with "Elem" appended
|
|
const $selectorCanvasElem = $('#selector-canvas');
|
|
const $includeFiltersElem = $("#include_filters");
|
|
const $selectorBackgroundElem = $("img#selector-background");
|
|
const $selectorCurrentXpathElem = $("#selector-current-xpath span");
|
|
const $fetchingUpdateNoticeElem = $('.fetching-update-notice');
|
|
const $selectorWrapperElem = $("#selector-wrapper");
|
|
|
|
// Color constants
|
|
const FILL_STYLE_HIGHLIGHT = 'rgba(205,0,0,0.35)';
|
|
const FILL_STYLE_GREYED_OUT = 'rgba(205,205,205,0.95)';
|
|
const STROKE_STYLE_HIGHLIGHT = 'rgba(255,0,0, 0.9)';
|
|
const FILL_STYLE_REDLINE = 'rgba(255,0,0, 0.1)';
|
|
const STROKE_STYLE_REDLINE = 'rgba(225,0,0,0.9)';
|
|
|
|
$('#visualselector-tab').click(() => {
|
|
$selectorBackgroundElem.off('load');
|
|
currentSelections = [];
|
|
bootstrapVisualSelector();
|
|
});
|
|
|
|
function clearReset() {
|
|
ctx.clearRect(0, 0, c.width, c.height);
|
|
|
|
if ($includeFiltersElem.val().length) {
|
|
alert("Existing filters under the 'Filters & Triggers' tab were cleared.");
|
|
}
|
|
$includeFiltersElem.val('');
|
|
|
|
currentSelections = [];
|
|
|
|
// Means we ignore the xpaths from the scraper marked as sel.highlight_as_custom_filter (it matched a previous selector)
|
|
runInClearMode = true;
|
|
|
|
highlightCurrentSelected();
|
|
}
|
|
|
|
function splitToList(v) {
|
|
return v.split('\n').map(line => line.trim()).filter(line => line.length > 0);
|
|
}
|
|
|
|
function sortScrapedElementsBySize() {
|
|
// Sort the currentSelections array by area (width * height) in descending order
|
|
selectorData['size_pos'].sort((a, b) => {
|
|
const areaA = a.width * a.height;
|
|
const areaB = b.width * b.height;
|
|
return areaB - areaA;
|
|
});
|
|
}
|
|
|
|
$(document).on('keydown keyup', (event) => {
|
|
if (event.code === 'ShiftLeft' || event.code === 'ShiftRight') {
|
|
appendToList = event.type === 'keydown';
|
|
}
|
|
|
|
if (event.type === 'keydown') {
|
|
if ($selectorBackgroundElem.is(":visible") && event.key === "Escape") {
|
|
clearReset();
|
|
}
|
|
}
|
|
});
|
|
|
|
$('#clear-selector').on('click', () => {
|
|
clearReset();
|
|
});
|
|
// So if they start switching between visualSelector and manual filters, stop it from rendering old filters
|
|
$('li.tab a').on('click', () => {
|
|
runInClearMode = true;
|
|
});
|
|
|
|
if (!window.location.hash || window.location.hash !== '#visualselector') {
|
|
$selectorBackgroundElem.attr('src', '');
|
|
return;
|
|
}
|
|
|
|
bootstrapVisualSelector();
|
|
|
|
function bootstrapVisualSelector() {
|
|
$selectorBackgroundElem
|
|
.on("error", () => {
|
|
$fetchingUpdateNoticeElem.html("<strong>Ooops!</strong> The VisualSelector tool needs at least one fetched page, please unpause the watch and/or wait for the watch to complete fetching and then reload this page.")
|
|
.css('color', '#bb0000');
|
|
$('#selector-current-xpath, #clear-selector').hide();
|
|
})
|
|
.on('load', () => {
|
|
console.log("Loaded background...");
|
|
c = document.getElementById("selector-canvas");
|
|
xctx = c.getContext("2d");
|
|
ctx = c.getContext("2d");
|
|
fetchData();
|
|
$selectorCanvasElem.off("mousemove mousedown");
|
|
})
|
|
.attr("src", screenshot_url);
|
|
|
|
let s = `${$selectorBackgroundElem.attr('src')}?${new Date().getTime()}`;
|
|
$selectorBackgroundElem.attr('src', s);
|
|
}
|
|
|
|
function alertIfFilterNotFound() {
|
|
let existingFilters = splitToList($includeFiltersElem.val());
|
|
let sizePosXpaths = selectorData['size_pos'].map(sel => sel.xpath);
|
|
|
|
for (let filter of existingFilters) {
|
|
if (!sizePosXpaths.includes(filter)) {
|
|
alert(`One or more of your existing filters was not found and will be removed when a new filter is selected.`);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
function fetchData() {
|
|
$fetchingUpdateNoticeElem.html("Fetching element data..");
|
|
|
|
$.ajax({
|
|
url: watch_visual_selector_data_url,
|
|
context: document.body
|
|
}).done((data) => {
|
|
$fetchingUpdateNoticeElem.html("Rendering..");
|
|
selectorData = data;
|
|
|
|
sortScrapedElementsBySize();
|
|
console.log(`Reported browser width from backend: ${data['browser_width']}`);
|
|
|
|
// Little sanity check for the user, alert them if something missing
|
|
alertIfFilterNotFound();
|
|
|
|
setScale();
|
|
reflowSelector();
|
|
$fetchingUpdateNoticeElem.fadeOut();
|
|
});
|
|
}
|
|
|
|
function updateFiltersText() {
|
|
// Assuming currentSelections is already defined and contains the selections
|
|
let uniqueSelections = new Set(currentSelections.map(sel => (sel[0] === '/' ? `xpath:${sel.xpath}` : sel.xpath)));
|
|
|
|
if (currentSelections.length > 0) {
|
|
// Convert the Set back to an array and join with newline characters
|
|
let textboxFilterText = Array.from(uniqueSelections).join("\n");
|
|
$includeFiltersElem.val(textboxFilterText);
|
|
}
|
|
}
|
|
|
|
function setScale() {
|
|
$selectorWrapperElem.show();
|
|
selectorImage = $selectorBackgroundElem[0];
|
|
selectorImageRect = selectorImage.getBoundingClientRect();
|
|
|
|
$selectorCanvasElem.attr({
|
|
'height': selectorImageRect.height,
|
|
'width': selectorImageRect.width
|
|
});
|
|
$selectorWrapperElem.attr('width', selectorImageRect.width);
|
|
$('#visual-selector-heading').css('max-width', selectorImageRect.width + "px")
|
|
|
|
xScale = selectorImageRect.width / selectorImage.naturalWidth;
|
|
yScale = selectorImageRect.height / selectorImage.naturalHeight;
|
|
|
|
ctx.strokeStyle = STROKE_STYLE_HIGHLIGHT;
|
|
ctx.fillStyle = FILL_STYLE_REDLINE;
|
|
ctx.lineWidth = 3;
|
|
console.log("Scaling set x: " + xScale + " by y:" + yScale);
|
|
$("#selector-current-xpath").css('max-width', selectorImageRect.width);
|
|
}
|
|
|
|
function reflowSelector() {
|
|
$(window).resize(() => {
|
|
setScale();
|
|
highlightCurrentSelected();
|
|
});
|
|
|
|
setScale();
|
|
|
|
console.log(selectorData['size_pos'].length + " selectors found");
|
|
|
|
let existingFilters = splitToList($includeFiltersElem.val());
|
|
|
|
selectorData['size_pos'].forEach(sel => {
|
|
if ((!runInClearMode && sel.highlight_as_custom_filter) || existingFilters.includes(sel.xpath)) {
|
|
console.log("highlighting " + c);
|
|
currentSelections.push(sel);
|
|
}
|
|
});
|
|
|
|
|
|
highlightCurrentSelected();
|
|
updateFiltersText();
|
|
|
|
$selectorCanvasElem.bind('mousemove', handleMouseMove.debounce(5));
|
|
$selectorCanvasElem.bind('mousedown', handleMouseDown.debounce(5));
|
|
$selectorCanvasElem.bind('mouseleave', highlightCurrentSelected.debounce(5));
|
|
|
|
function handleMouseMove(e) {
|
|
if (!e.offsetX && !e.offsetY) {
|
|
const targetOffset = $(e.target).offset();
|
|
e.offsetX = e.pageX - targetOffset.left;
|
|
e.offsetY = e.pageY - targetOffset.top;
|
|
}
|
|
|
|
ctx.fillStyle = FILL_STYLE_HIGHLIGHT;
|
|
|
|
selectorData['size_pos'].forEach(sel => {
|
|
if (e.offsetY > sel.top * yScale && e.offsetY < sel.top * yScale + sel.height * yScale &&
|
|
e.offsetX > sel.left * yScale && e.offsetX < sel.left * yScale + sel.width * yScale) {
|
|
setCurrentSelectedText(sel.xpath);
|
|
drawHighlight(sel);
|
|
currentSelections.push(sel);
|
|
currentSelection = sel;
|
|
highlightCurrentSelected();
|
|
currentSelections.pop();
|
|
}
|
|
})
|
|
}
|
|
|
|
|
|
function setCurrentSelectedText(s) {
|
|
$selectorCurrentXpathElem[0].innerHTML = s;
|
|
}
|
|
|
|
function drawHighlight(sel) {
|
|
ctx.strokeRect(sel.left * xScale, sel.top * yScale, sel.width * xScale, sel.height * yScale);
|
|
ctx.fillRect(sel.left * xScale, sel.top * yScale, sel.width * xScale, sel.height * yScale);
|
|
}
|
|
|
|
function handleMouseDown() {
|
|
// If we are in 'appendToList' mode, grow the list, if not, just 1
|
|
currentSelections = appendToList ? [...currentSelections, currentSelection] : [currentSelection];
|
|
highlightCurrentSelected();
|
|
updateFiltersText();
|
|
}
|
|
|
|
}
|
|
|
|
function highlightCurrentSelected() {
|
|
xctx.fillStyle = FILL_STYLE_GREYED_OUT;
|
|
xctx.strokeStyle = STROKE_STYLE_REDLINE;
|
|
xctx.lineWidth = 3;
|
|
xctx.clearRect(0, 0, c.width, c.height);
|
|
|
|
currentSelections.forEach(sel => {
|
|
//xctx.clearRect(sel.left * xScale, sel.top * yScale, sel.width * xScale, sel.height * yScale);
|
|
xctx.strokeRect(sel.left * xScale, sel.top * yScale, sel.width * xScale, sel.height * yScale);
|
|
});
|
|
}
|
|
}); |