You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
153 lines
4.6 KiB
React
153 lines
4.6 KiB
React
5 years ago
|
"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");
|
||
|
|
||
|
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) {
|
||
|
if (enabledRef.current) {
|
||
|
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 ]);
|
||
|
|
||
|
return (
|
||
|
<div className="___scraping___tool___overlay 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} />
|
||
|
: null
|
||
|
}
|
||
|
</div>
|
||
|
);
|
||
|
}
|
||
|
|
||
|
function Picker({ candidates, onHover }) {
|
||
|
return (
|
||
|
<div className="___scraping___tool___candidatePicker">
|
||
|
{candidates.map((candidate) => {
|
||
|
return <PickerCandidate
|
||
|
key={uniqueElementId(candidate)}
|
||
|
element={candidate}
|
||
|
onEnter={() => onHover(candidate)}
|
||
|
onLeave={() => onHover(null)}
|
||
|
/>;
|
||
|
})}
|
||
|
</div>
|
||
|
);
|
||
|
}
|
||
|
|
||
|
function PickerCandidate({ element, onEnter, onLeave }) {
|
||
|
return (
|
||
|
<div className="___scraping___tool___candidate" onMouseEnter={onEnter} onMouseLeave={onLeave}>
|
||
|
{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} />
|
||
|
</>);
|
||
|
}
|
||
|
|
||
|
Promise.try(() => {
|
||
|
return documentReadyPromise();
|
||
|
}).then(() => {
|
||
|
let $overlay = document.querySelector(".___scraping___tool___overlay");
|
||
|
let $tool = document.querySelector(".___scraping___tool");
|
||
|
|
||
|
ReactDOM.render(<Overlay />, $overlay);
|
||
|
});
|