Skip to content

Commit

Permalink
use screenshot wrapper component
Browse files Browse the repository at this point in the history
  • Loading branch information
lixun910 committed Feb 10, 2025
1 parent ef510fb commit 16bb81b
Show file tree
Hide file tree
Showing 6 changed files with 15 additions and 242 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/nextjs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ jobs:
cd geoda-ai
yarn
yarn lint
yarn test
# - name: Create Sentry Release
# uses: getsentry/action-release@v1
# env:
Expand Down
1 change: 0 additions & 1 deletion geoda-ai/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@
"echarts-stat": "^1.2.0",
"framer-motion": "^11.11.10",
"geoda-wasm": "^0.0.5",
"html2canvas": "^1.4.1",
"jstat": "^1.9.6",
"lexical": "0.14.2",
"lodash": "^4.17.21",
Expand Down
4 changes: 2 additions & 2 deletions geoda-ai/src/actions/ai-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@ export const addDatasetToAI =
);

if (newMetaData.type === 'metadata') {
const textDatasetMeta = JSON.stringify(newMetaData);
const message = `Please use the metadata of the following datasets to help users applying spatial data analysis: ${textDatasetMeta}. Please try to correct the variable names if they are not correct.`;
// const textDatasetMeta = JSON.stringify(newMetaData);
// const message = `Please use the metadata of the following datasets to help users applying spatial data analysis: ${textDatasetMeta}. Please try to correct the variable names if they are not correct.`;

// // add dataset metadata as additional instructions for AI model
// await initOpenAI(
Expand Down
214 changes: 12 additions & 202 deletions geoda-ai/src/components/main-container.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
import {
setScreenCaptured,
setStartScreenCapture,
setUserAction,
setUserActionScreenshot
} from '@/actions';
import {MouseEvent, useEffect, useState} from 'react';
import {setScreenCaptured, setStartScreenCapture} from '@/actions';
import {useDispatch, useSelector} from 'react-redux';
import html2canvas from 'html2canvas';
import {ScreenshotWrapper} from '@openassistant/ui';

import {Navigator} from '@/components/navigator';
import {TableContainer} from '@/components/table/table-container';
Expand All @@ -19,201 +13,29 @@ const OpenFileModal = dynamic(() => import('@/components/open-file-modal'), {ssr
const GridLayout = dynamic(() => import('@/components/dashboard/grid-layout'), {ssr: false});
import {AddDatasetModal} from '@/components/open-file-modal';
import {GeoDaState} from '@/store';
import {takeSnapshotWithCrop} from '@/utils/ui-utils';

export default function MainContainerWithScreenCapture({projectUrl}: {projectUrl: string | null}) {
const dispatch = useDispatch();

const isGuidingUser = useSelector((state: GeoDaState) => state.root.uiState.isGuidingUser);

// get startScreenCapture from redux state
const startScreenCapture = useSelector(
(state: GeoDaState) => state.root.uiState.startScreenCapture
);

const [startX, setStartX] = useState(0);
const [startY, setStartY] = useState(0);
const [crossHairsTop, setCrossHairsTop] = useState(0);
const [crossHairsLeft, setCrossHairsLeft] = useState(0);
const [isMouseDown, setIsMouseDown] = useState(false);
const [windowWidth, setWindowWidth] = useState(0);
const [windowHeight, setWindowHeight] = useState(0);
const [borderWidth, setBorderWidth] = useState<number | string>(0);
const [cropPositionTop, setCropPositionTop] = useState(0);
const [cropPositionLeft, setCropPositionLeft] = useState(0);
const [cropWidth, setCropWidth] = useState(0);
const [cropHeight, setCropHeight] = useState(0);

const handleWindowResize = () => {
const windowWidth =
window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
const windowHeight =
window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
setWindowWidth(windowWidth);
setWindowHeight(windowHeight);
};

useEffect(() => {
// when the component mounts, set the window width and height
handleWindowResize();
window.addEventListener('resize', handleWindowResize);
// remove the event listener when the component unmounts
return () => window.removeEventListener('resize', handleWindowResize);
}, []);

const handleMouseMove = (e: MouseEvent<HTMLDivElement>) => {
if (startScreenCapture === false) {
// return and pass the event to the next element
return;
}

let cropTop = startY;
let cropLeft = startX;
const clientX = e.clientX;
const clientY = e.clientY;
const isStartTop = clientY >= startY;
const isStartBottom = clientY <= startY;
const isStartLeft = clientX >= startX;
const isStartRight = clientX <= startX;
const isStartTopLeft = isStartTop && isStartLeft;
const isStartTopRight = isStartTop && isStartRight;
const isStartBottomLeft = isStartBottom && isStartLeft;
const isStartBottomRight = isStartBottom && isStartRight;
let newBorderWidth = borderWidth;
let cropW = 0;
let cropH = 0;

if (isMouseDown) {
if (isStartTopLeft) {
newBorderWidth = `${startY}px ${windowWidth - clientX}px ${windowHeight - clientY}px ${startX}px`;
cropW = clientX - startX;
cropH = clientY - startY;
}

if (isStartTopRight) {
newBorderWidth = `${startY}px ${windowWidth - startX}px ${windowHeight - clientY}px ${clientX}px`;
cropW = startX - clientX;
cropH = clientY - startY;
cropLeft = clientX;
}

if (isStartBottomLeft) {
newBorderWidth = `${clientY}px ${windowWidth - clientX}px ${windowHeight - startY}px ${startX}px`;
cropW = clientX - startX;
cropH = startY - clientY;
cropTop = clientY;
}
if (isStartBottomRight) {
newBorderWidth = `${clientY}px ${windowWidth - startX}px ${windowHeight - startY}px ${clientX}px`;
cropW = startX - clientX;
cropH = startY - clientY;
cropTop = clientY;
cropLeft = clientX;
}
}
setCrossHairsTop(clientY);
setCrossHairsLeft(clientX);
setBorderWidth(newBorderWidth);
setCropWidth(cropW);
setCropHeight(cropH);
setCropPositionTop(cropTop);
setCropPositionLeft(cropLeft);
};

const handleMouseDown = (e: MouseEvent<HTMLDivElement>) => {
if (startScreenCapture === false) {
// return and pass the event to the next element
return;
}

const clientX = e.clientX;
const clientY = e.clientY;

setStartX(clientX);
setStartY(clientY);
setCropPositionTop(clientY);
setCropPositionLeft(clientX);
setIsMouseDown(true);
setBorderWidth(`${windowWidth}px ${windowHeight}px`);
};
const handleMoouseUp = () => {
if (startScreenCapture === false) {
// return and pass the event to the next element
return;
}
handleClickTakeScreenShot();
setIsMouseDown(false);
setBorderWidth(0);
// reset the startScreenCapture to false
dispatch(setStartScreenCapture(false));
};
const handleClickTakeScreenShot = () => {
const body = document.querySelector('body');
if (body) {
// get the scale of hdpi screen
const scale = window.devicePixelRatio;
html2canvas(body, {scale}).then(canvas => {
const croppedCanvas = document.createElement('canvas');
const croppedCanvasContext = croppedCanvas.getContext('2d');

croppedCanvas.width = cropWidth;
croppedCanvas.height = cropHeight;

if (croppedCanvasContext) {
croppedCanvasContext.drawImage(
canvas,
cropPositionLeft * scale,
cropPositionTop * scale,
cropWidth * scale,
cropHeight * scale,
0,
0,
cropWidth,
cropHeight
);
}

if (croppedCanvas.toDataURL) {
const dataURL = croppedCanvas.toDataURL();
dispatch(setScreenCaptured(dataURL));
// save the image to file
// const link = document.createElement('a');
// link.download = 'screenshot.png';
// link.href = dataURL;
// link.click();
}
});
}
setCrossHairsLeft(0);
setCrossHairsTop(0);
const onSetScreenCaptured = (value: string) => {
dispatch(setScreenCaptured(value));
};

const handleClick = async (e: MouseEvent<HTMLDivElement>) => {
if (isGuidingUser && (e.target instanceof HTMLElement || e.target instanceof SVGElement)) {
const nodeElement = e.target instanceof SVGElement ? e.target.parentElement : e.target;
if (nodeElement) {
// get the global position of nodeElement
const rect = nodeElement.getBoundingClientRect();
const x = rect.left + window.scrollX;
const y = rect.top + window.scrollY;
// get the size of the nodeElement
const width = rect.width;
const height = rect.height;
// make a screenshot of the clicked element
const screenshot = await takeSnapshotWithCrop(x, y, width, height);
dispatch(setUserActionScreenshot(screenshot));
// dispatch the outerHTML of the clicked element
dispatch(setUserAction(e.target.outerHTML));
}
}
const onSetStartScreenCapture = (value: boolean) => {
dispatch(setStartScreenCapture(value));
};

return (
<div
onClickCapture={handleClick}
onMouseMove={handleMouseMove}
onMouseDown={handleMouseDown}
onMouseUp={handleMoouseUp}
<ScreenshotWrapper
setScreenCaptured={onSetScreenCaptured}
startScreenCapture={startScreenCapture}
setStartScreenCapture={onSetStartScreenCapture}
saveScreenshot={true}
>
<div className="min-w-100 relative flex h-screen w-screen flex-row items-start border-none">
<Navigator />
Expand All @@ -231,18 +53,6 @@ export default function MainContainerWithScreenCapture({projectUrl}: {projectUrl
<AddDatasetModal />
<SaveProjectModal />
</div>
{startScreenCapture && (
<>
<div
className={`overlay ${isMouseDown && 'highlighting'}`}
style={{borderWidth: `${borderWidth}`}}
/>
<div
className="crosshairs"
style={{left: crossHairsLeft + 'px', top: crossHairsTop + 'px'}}
/>
</>
)}
</div>
</ScreenshotWrapper>
);
}
35 changes: 0 additions & 35 deletions geoda-ai/src/utils/ui-utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,41 +14,6 @@ export async function takeSnapshot(element: HTMLElement) {
return dataUrl;
}

// take a snapshot of the entire document and crop at the specified position and size
export async function takeSnapshotWithCrop(
cropPositionLeft: number,
cropPositionTop: number,
cropWidth: number,
cropHeight: number
) {
const scale = window.devicePixelRatio;
const body = document.body;

const canvas = await html2canvas(body, {scale});
const croppedCanvas = document.createElement('canvas');
const croppedCanvasContext = croppedCanvas.getContext('2d');

croppedCanvas.width = cropWidth;
croppedCanvas.height = cropHeight;

if (croppedCanvasContext) {
croppedCanvasContext.drawImage(
canvas,
cropPositionLeft * scale,
cropPositionTop * scale,
cropWidth * scale,
cropHeight * scale,
0,
0,
cropWidth,
cropHeight
);
}

const dataURL = croppedCanvas.toDataURL();
return dataURL;
}

/**
* Format a number according to the current locale
* @param value - The number to format
Expand Down
1 change: 0 additions & 1 deletion geoda-ai/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -17745,7 +17745,6 @@ __metadata:
framer-motion: ^11.11.10
fuzzy: ^0.1.3
geoda-wasm: ^0.0.5
html2canvas: ^1.4.1
jest: ^29.7.0
jest-dom: ^4.0.0
jest-environment-jsdom: ^29.7.0
Expand Down

0 comments on commit 16bb81b

Please sign in to comment.