@ -2,267 +2,239 @@
// All rights reserved.
// yes - this is really a hack, if you are a front-ender and want to help, please get in touch!
$ ( document ) . ready ( function ( ) {
var current _selected _i ;
var state _clicked = false ;
var c ;
// greyed out fill context
var xctx ;
// redline highlight context
var ctx ;
var current _default _xpath = [ ] ;
var x _scale = 1 ;
var y _scale = 1 ;
var selector _image ;
var selector _image _rect ;
var selector _data ;
$ ( '#visualselector-tab' ) . click ( function ( ) {
$ ( "img#selector-background" ) . off ( 'load' ) ;
state _clicked = false ;
current _selected _i = false ;
bootstrap _visualselector ( ) ;
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 clear _reset ( ) {
state _clicked = false ;
function clearReset ( ) {
ctx . clearRect ( 0 , 0 , c . width , c . height ) ;
if ( $ ( "#include_filters" ) . val ( ) . length ) {
if ( $includeFiltersElem . val ( ) . length ) {
alert ( "Existing filters under the 'Filters & Triggers' tab were cleared." ) ;
}
$ ( "#include_filters" ) . val ( '' ) ;
$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' , function ( event ) {
if ( $ ( "img#selector-background" ) . is ( ":visible" ) ) {
if ( event . key == "Escape" ) {
clear _reset ( ) ;
$ ( 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 ( ) ;
}
}
} ) ;
// Handle clearing button/link
$ ( '#clear-selector' ) . on ( 'click' , function ( event ) {
clear _reset ( ) ;
$ ( '#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 ;
} ) ;
// For when the page loads
if ( ! window . location . hash || window . location . hash != '#visualselector' ) {
$ ( "img#selector-background" ) . attr ( 'src' , '' ) ;
if ( ! window . location . hash || window . location . hash !== '#visualselector' ) {
$selectorBackgroundElem . attr ( 'src' , '' ) ;
return ;
}
bootstrapVisualSelector ( ) ;
bootstrap _visualselector ( ) ;
function bootstrap _visualselector ( ) {
if ( 1 ) {
// bootstrap it, this will trigger everything else
$ ( "img#selector-background" ) . on ( "error" , function ( ) {
$ ( '.fetching-update-notice' ) . html ( "<strong>Ooops!</strong> The VisualSelector tool needs atleast one fetched page, please unpause the watch and/or wait for the watch to complete fetching and then reload this page." ) ;
$ ( '.fetching-update-notice' ) . css ( 'color' , '#bb0000' ) ;
$ ( '#selector-current-xpath' ) . hide ( ) ;
$ ( '#clear-selector' ) . hide ( ) ;
} ) . bind ( 'load' , function ( ) {
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" ) ;
// greyed out fill context
xctx = c . getContext ( "2d" ) ;
// redline highlight context
ctx = c . getContext ( "2d" ) ;
if ( $ ( "#include_filters" ) . val ( ) . trim ( ) . length ) {
current _default _xpath = $ ( "#include_filters" ) . val ( ) . split ( /\r?\n/g ) ;
} else {
current _default _xpath = [ ] ;
}
fetch _data ( ) ;
$ ( '#selector-canvas' ) . off ( "mousemove mousedown" ) ;
// screenshot_url defined in the edit.html template
} ) . attr ( "src" , screenshot _url ) ;
}
// Tell visualSelector that the image should update
var s = $ ( "img#selector-background" ) . attr ( 'src' ) + "?" + new Date ( ) . getTime ( ) ;
$ ( "img#selector-background" ) . attr ( 'src' , s )
fetchData ( ) ;
$selectorCanvasElem . off ( "mousemove mousedown" ) ;
} )
. attr ( "src" , screenshot _url ) ;
let s = ` ${ $selectorBackgroundElem . attr ( 'src' ) } ? ${ new Date ( ) . getTime ( ) } ` ;
$selectorBackgroundElem . attr ( 'src' , s ) ;
}
// This is fired once the img src is loaded in bootstrap_visualselector()
function fetch _data ( ) {
// Image is ready
$ ( '.fetching-update-notice' ) . html ( "Fetching element data.." ) ;
function fetchData ( ) {
$fetchingUpdateNoticeElem . html ( "Fetching element data.." ) ;
$ . ajax ( {
url : watch _visual _selector _data _url ,
context : document . body
} ) . done ( function ( data ) {
$ ( '.fetching-update-notice' ) . html ( "Rendering.." ) ;
selector _data = data ;
} ) . done ( ( data ) => {
$fetchingUpdateNoticeElem . html ( "Rendering.." ) ;
selectorData = data ;
sortScrapedElementsBySize ( ) ;
console . log ( "Reported browser width from backend: " + data [ 'browser_width' ] ) ;
state _clicked = false ;
set _scale ( ) ;
reflow _selector ( ) ;
$ ( '.fetching-update-notice' ) . fadeOut ( ) ;
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 ) ) ) ;
function set _scale ( ) {
// Convert the Set back to an array and join with newline characters
let textboxFilterText = Array . from ( uniqueSelections ) . join ( "\n" ) ;
// some things to check if the scaling doesnt work
// - that the widths/sizes really are about the actual screen size cat elements.json |grep -o width......|sort|uniq
$ ( "#selector-wrapper" ) . show ( ) ;
selector _image = $ ( "img#selector-background" ) [ 0 ] ;
selector _image _rect = selector _image . getBoundingClientRect ( ) ;
$includeFiltersElem . val ( textboxFilterText ) ;
}
// Make the overlayed canvas the same size as the image
$ ( '#selector-canvas' ) . attr ( 'height' , selector _image _rect . height ) . attr ( 'width' , selector _image _rect . width ) ;
$ ( '#selector-wrapper' ) . attr ( 'width' , selector _image _rect . width ) ;
function setScale ( ) {
$selectorWrapperElem . show ( ) ;
selectorImage = $selectorBackgroundElem [ 0 ] ;
selectorImageRect = selectorImage . getBoundingClientRect ( ) ;
x _scale = selector _image _rect . width / selector _image . naturalWidth ;
y _scale = selector _image _rect . height / selector _image . naturalHeight ;
$selectorCanvasElem . attr ( {
'height' : selectorImageRect . height ,
'width' : selectorImageRect . width
} ) ;
$selectorWrapperElem . attr ( 'width' , selectorImageRect . width ) ;
$ ( '#visual-selector-heading' ) . css ( 'max-width' , selectorImageRect . width + "px" )
ctx . strokeStyle = 'rgba(255,0,0, 0.9)' ;
ctx . fillStyle = 'rgba(255,0,0, 0.1)' ;
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: " + x _scale + " by y:" + y _scale ) ;
$ ( "#selector-current-xpath" ) . css ( 'max-width' , selector _image _rect . width ) ;
console . log ( " Scaling set x: " + xScale + " by y:" + yS cale) ;
$ ( "#selector-current-xpath" ) . css ( 'max-width' , selector ImageR ect. width ) ;
}
function reflow _s elector( ) {
$ ( window ) . resize ( function ( ) {
set _s cale( ) ;
highlight _current _selected _i ( ) ;
function reflow S elector( ) {
$ ( window ) . resize ( ( ) => {
set S cale( ) ;
highlight CurrentSelected ( ) ;
} ) ;
var selector _currnt _xpath _text = $ ( "#selector-current-xpath span" ) ;
set _scale ( ) ;
console . log ( selector _data [ 'size_pos' ] . length + " selectors found" ) ;
// highlight the default one if we can find it in the xPath list
// or the xpath matches the default one
found = false ;
if ( current _default _xpath . length ) {
// Find the first one that matches
// @todo In the future paint all that match
for ( const c of current _default _xpath ) {
for ( var i = selector _data [ 'size_pos' ] . length ; i !== 0 ; i -- ) {
if ( selector _data [ 'size_pos' ] [ i - 1 ] . xpath . trim ( ) === c . trim ( ) ) {
console . log ( "highlighting " + c ) ;
current _selected _i = i - 1 ;
highlight _current _selected _i ( ) ;
found = true ;
break ;
}
}
if ( found ) {
break ;
}
}
if ( ! found ) {
alert ( "Unfortunately your existing CSS/xPath Filter was no longer found!" ) ;
}
highlight _matching _filters ( ) ;
}
setScale ( ) ;
console . log ( selectorData [ 'size_pos' ] . length + " selectors found" ) ;
$ ( '#selector-canvas' ) . bind ( 'mousemove' , function ( e ) {
if ( state _clicked ) {
return ;
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 ) ;
}
ctx . clearRect ( 0 , 0 , c . width , c . height ) ;
current _selected _i = null ;
} ) ;
highlightCurrentSelected ( ) ;
updateFiltersText ( ) ;
$selectorCanvasElem . bind ( 'mousemove' , handleMouseMove . debounce ( 5 ) ) ;
$selectorCanvasElem . bind ( 'mousedown' , handleMouseDown . debounce ( 5 ) ) ;
$selectorCanvasElem . bind ( 'mouseleave' , highlightCurrentSelected . debounce ( 5 ) ) ;
// Add in offset
if ( ( typeof e . offsetX === "undefined" || typeof e . offsetY === "undefined" ) || ( e . offsetX === 0 && e . offsetY === 0 ) ) {
var targetOffset = $ ( e . target ) . offset ( ) ;
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 ;
}
// Reverse order - the most specific one should be deeper/"laster"
// Basically, find the most 'deepest'
var found = 0 ;
ctx . fillStyle = 'rgba(205,0,0,0.35)' ;
// Will be sorted by smallest width*height first
for ( var i = 0 ; i <= selector _data [ 'size_pos' ] . length ; i ++ ) {
// draw all of them? let them choose somehow?
var sel = selector _data [ 'size_pos' ] [ i ] ;
// If we are in a bounding-box
if ( e . offsetY > sel . top * y _scale && e . offsetY < sel . top * y _scale + sel . height * y _scale
&&
e . offsetX > sel . left * y _scale && e . offsetX < sel . left * y _scale + sel . width * y _scale
) {
// FOUND ONE
set _current _selected _text ( sel . xpath ) ;
ctx . strokeRect ( sel . left * x _scale , sel . top * y _scale , sel . width * x _scale , sel . height * y _scale ) ;
ctx . fillRect ( sel . left * x _scale , sel . top * y _scale , sel . width * x _scale , sel . height * y _scale ) ;
// no need to keep digging
// @todo or, O to go out/up, I to go in
// or double click to go up/out the selector?
current _selected _i = i ;
found += 1 ;
break ;
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 ( ) ;
}
}
} )
}
} . debounce ( 5 ) ) ;
function set _current _selected _t ext( s ) {
selector_currnt _xpath _text [ 0 ] . innerHTML = s ;
function setCurrentSelectedText ( s ) {
$selectorCurrentXpathElem [ 0 ] . innerHTML = s ;
}
function highlight _current _selected _i ( ) {
if ( state _clicked ) {
state _clicked = false ;
xctx . clearRect ( 0 , 0 , c . width , c . height ) ;
return ;
}
var sel = selector _data [ 'size_pos' ] [ current _selected _i ] ;
if ( sel [ 0 ] == '/' ) {
// @todo - not sure just checking / is right
$ ( "#include_filters" ) . val ( 'xpath:' + sel . xpath ) ;
} else {
$ ( "#include_filters" ) . val ( sel . xpath ) ;
}
xctx . fillStyle = 'rgba(205,205,205,0.95)' ;
xctx . strokeStyle = 'rgba(225,0,0,0.9)' ;
xctx . lineWidth = 3 ;
xctx . fillRect ( 0 , 0 , c . width , c . height ) ;
// Clear out what only should be seen (make a clear/clean spot)
xctx . clearRect ( sel . left * x _scale , sel . top * y _scale , sel . width * x _scale , sel . height * y _scale ) ;
xctx . strokeRect ( sel . left * x _scale , sel . top * y _scale , sel . width * x _scale , sel . height * y _scale ) ;
state _clicked = true ;
set _current _selected _text ( sel . xpath ) ;
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 highlight _matching _filters ( ) {
selector _data [ 'size_pos' ] . forEach ( sel => {
if ( sel . highlight _as _custom _filter ) {
xctx . fillStyle = 'rgba(205,205,205,0.95)' ;
xctx . strokeStyle = 'rgba(225,0,0,0.95)' ;
xctx . lineWidth = 1 ;
xctx . clearRect ( sel . left * x _scale , sel . top * y _scale , sel . width * x _scale , sel . height * y _scale ) ;
xctx . strokeRect ( sel . left * x _scale , sel . top * y _scale , sel . width * x _scale , sel . height * y _scale ) ;
}
} ) ;
}
function highlightCurrentSelected ( ) {
xctx . fillStyle = FILL _STYLE _GREYED _OUT ;
xctx . strokeStyle = STROKE _STYLE _REDLINE ;
xctx . lineWidth = 3 ;
xctx . clearRect ( 0 , 0 , c . width , c . height ) ;
$ ( '#selector-canvas' ) . bind ( 'mousedown' , function ( e ) {
highlight _current _selected _i ( ) ;
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 ) ;
} ) ;
}
} ) ;
} ) ;