"use strict" ;
const React = require ( "react" ) ;
const ReactDOM = require ( "react-dom" ) ;
const Promise = require ( "bluebird" ) ;
const documentReadyPromise = require ( "document-ready-promise" ) ;
const debounce = require ( "debounce" ) ;
const { expression } = require ( "dataprog" ) ;
const generateSelector = require ( "./generate-selector" ) ;
const useStateRef = require ( "./use-state-ref" ) ;
const useMemoizedPosition = require ( "./use-memoized-position" ) ;
const elementsFromPoint = require ( "./elements-from-point" ) ;
const uniqueElementId = require ( "./unique-element-id" ) ;
const getElementPosition = require ( "./get-element-position" ) ;
/* MARKER: Implement selection refining. The refining tree does NOT necessarily match the element selection tree, since overlapping elements are not necessarily DOM ancestors of one another. For refining, *all* identifiers should be shown: tag type, ID, classes. Item selection panel should be made a two-part panel (with the inactive part header-only and grayed out?), first item selection, then refining. */
function Overlay ( ) {
let [ scrollX , setScrollX ] = React . useState ( ) ;
let [ scrollY , setScrollY ] = React . useState ( ) ;
let [ hoveredElement , setHoveredElement ] = React . useState ( ) ;
let [ isHovering , setIsHovering , isHoveringRef ] = useStateRef ( true ) ;
let [ isPicking , setIsPicking , isPickingRef ] = useStateRef ( false ) ;
let [ elementList , setElementList ] = React . useState ( [ ] ) ;
let [ selectedElement , setSelectedElement ] = React . useState ( ) ;
let [ pickHoveredElement , setPickHoveredElement ] = React . useState ( ) ;
let [ pickedElement , setPickedElement ] = React . useState ( ) ;
let enabledRef = React . useRef ( true ) ;
React . useEffect ( ( ) => {
window . addEventListener ( "scroll" , debounce ( ( event ) => {
setScrollX ( window . scrollX ) ;
setScrollY ( window . scrollY ) ;
} ) , 20 ) ;
let allElements = document . querySelectorAll ( "*" ) ;
for ( let element of allElements ) {
/* TODO: Investigate whether switching to mousemove + elementFromPoint is more performant */
element . addEventListener ( "mouseover" , ( event ) => {
event . stopPropagation ( ) ;
if ( isHoveringRef . current ) {
setHoveredElement ( element ) ;
}
} ) ;
function clickHandler ( event ) {
/* TODO: Need to find a nicer way to exclude scraping tool UI from this interception, than looking at the class prefix */
if ( enabledRef . current && ! event . target . className . startsWith ( "___scraping___tool___" ) ) {
event . preventDefault ( ) ;
event . stopPropagation ( ) ;
if ( isHoveringRef . current ) {
setIsHovering ( false ) ;
setIsPicking ( true ) ;
let candidateElements = elementsFromPoint ( event . clientX , event . clientY ) ;
setElementList ( candidateElements ) ;
setSelectedElement ( candidateElements [ 0 ] ) ;
}
}
}
element . addEventListener ( "click" , clickHandler ) ;
element . addEventListener ( "mousedown" , clickHandler ) ;
element . addEventListener ( "mouseup" , clickHandler ) ;
}
} , [ ] ) ;
let hoveredPosition = useMemoizedPosition ( hoveredElement , [ scrollX , scrollY ] ) ;
let selectedPosition = useMemoizedPosition ( selectedElement , [ scrollX , scrollY ] ) ;
let pickedPosition = useMemoizedPosition ( pickedElement , [ scrollX , scrollY ] ) ;
let pickHoveredPosition = useMemoizedPosition ( pickHoveredElement , [ scrollX , scrollY ] ) ;
let secondaryMatches = React . useMemo ( ( ) => {
if ( selectedElement != null ) {
let elements = Array . from ( document . querySelectorAll ( generateSelector ( selectedElement ) ) ) ;
return elements
. filter ( ( element ) => element !== selectedElement )
. map ( ( element ) => {
return {
element : element ,
position : getElementPosition ( element )
} ;
} ) ;
} else {
return [ ] ;
}
} , [ selectedElement , scrollX , scrollY ] ) ;
return (
< div className = "___scraping___tool___overlay ___scraping___tool___active" >
{ ( hoveredPosition != null && isHovering )
? < HoverHighlight element = { hoveredElement } { ... hoveredPosition } / >
: null
}
{ ( selectedPosition != null )
? < PrimarySelectionHighlight element = { selectedElement } { ... selectedPosition } / >
: null
}
{ ( pickedPosition != null && isPicking )
? < HoverHighlight element = { pickedElement } { ... pickedPosition } / >
: null
}
{ ( pickHoveredPosition != null && isPicking )
? < HoverHighlight element = { pickHoveredElement } { ... pickHoveredPosition } / >
: null
}
{ ( isPicking )
? < Picker candidates = { elementList } onHover = { setPickHoveredElement } onSelect = { setSelectedElement } / > /* FIXME: setPickedElement eventually instead */
: null
}
{ secondaryMatches . map ( ( element ) => {
return < SecondarySelectionHighlight
key = { uniqueElementId ( element ) }
element = { element . element }
{ ... element . position }
/ >
} ) }
< / div >
) ;
}
function Picker ( { candidates , onHover , onSelect } ) {
return (
< div className = "___scraping___tool___candidatePicker" >
{ candidates . map ( ( candidate ) => {
return < PickerCandidate
key = { uniqueElementId ( candidate ) }
element = { candidate }
onEnter = { ( ) => onHover ( candidate ) }
onLeave = { ( ) => onHover ( null ) }
onClick = { ( ) => onSelect ( candidate ) }
/ > ;
} ) }
< / div >
) ;
}
function PickerCandidate ( { element , onEnter , onLeave , onClick } ) {
return (
< div className = "___scraping___tool___candidate" onMouseEnter = { onEnter } onMouseLeave = { onLeave } onClick = { onClick } >
{ generateSelector ( element ) }
< / div >
) ;
}
function HoverHighlight ( { x , y , width , height , element } ) {
let boxStyle = { left : x , top : y , width : width , height : height } ;
let tooltipStyle = { left : x , top : y + height } ;
return ( < >
< div className = "___scraping___tool___hover" style = { boxStyle } / >
{ ( element != null )
? < div className = "___scraping___tool___tooltip" style = { tooltipStyle } > { generateSelector ( element ) } < / div >
: null
}
< / > ) ;
}
function PrimarySelectionHighlight ( { x , y , width , height , element } ) {
let boxStyle = { left : x , top : y , width : width , height : height } ;
return ( < >
< div className = "___scraping___tool___selection" style = { boxStyle } / >
< / > ) ;
}
function SecondarySelectionHighlight ( { x , y , width , height , element } ) {
let boxStyle = { left : x , top : y , width : width , height : height } ;
return ( < >
< div className = "___scraping___tool___secondarySelection" style = { boxStyle } / >
< / > ) ;
}
Promise . try ( ( ) => {
return documentReadyPromise ( ) ;
} ) . then ( ( ) => {
let $overlay = document . querySelector ( ".___scraping___tool___overlay" ) ;
let $tool = document . querySelector ( ".___scraping___tool" ) ;
ReactDOM . render ( < Overlay / > , $overlay ) ;
} ) ;