import React, { useEffect, useState } from "react"; import Canvas from "ventilation/Canvas"; import Zoom from "ventilation/Zoom"; import { pond } from "protobuf-ts/pond"; import { CircularProgress, ListItemIcon, ListItemText, Menu, MenuItem, ToggleButton, Typography, useTheme } from "@mui/material"; import { Info, AddCircle, InfoRounded } from "@mui/icons-material"; import { VentShaft } from "./drawable/VentShaft"; import { VentCorner } from "./drawable/VentCorner"; import { Placeable } from "./drawable/Placeable"; import VentDialog from "./VentDialog"; import DeleteIcon from "@mui/icons-material/DeleteForever"; import ClockwiseIcon from "@mui/icons-material/RotateRight"; import CounterclockwiseIcon from "@mui/icons-material/RotateLeft"; import { useGlobalState, useMineAPI } from "providers"; import { Component, Device } from "models"; import { Sensor } from "./drawable/Sensor"; import { VentTBreak } from "./drawable/VentTBreak"; import InteractionSettings from "interactions/InteractionSettings"; import { UnitMeasurement } from "models/UnitMeasurement"; interface Props { width?: number; height?: number; mode?: Mode; undoRef: any; redoRef: any; saveRef: any; addBlockRef: any; devices: pond.Device[]; blocks: Placeable[]; setBlocks: React.Dispatch>; components: Component[]; setComponents: React.Dispatch>; componentPreferences: Map; setComponentPreferences: React.Dispatch< React.SetStateAction> >; componentDevices: Map; sensors: Sensor[]; setSensors: React.Dispatch>; loading: boolean; } enum Mode { NORMAL, MOVE, ADD, DELETE } enum ViewMode { NORMAL, INFO } export default function Editor(props: Props) { const { blocks, setBlocks, devices, components, componentDevices, componentPreferences, sensors, setSensors, setComponentPreferences, loading } = props; const [selected, setSelected] = useState([]); const [width, setWidth] = useState(props.width ? props.width : 800); const [height, setHeight] = useState(props.width ? props.width : 600); const [cameraOffsetX, setCameraOffsetX] = useState(0); const [cameraOffsetY, setCameraOffsetY] = useState(0); const [clickX, setClickX] = useState(0); const [clickY, setClickY] = useState(0); const [mode, setMode] = useState(props.mode ? props.mode : Mode.NORMAL); const [viewMode, setViewMode] = useState(ViewMode.NORMAL); const [mouseDown, setMouseDown] = useState(false); const [undoStates, setUndoStates] = useState([]); const [redoStates, setRedoStates] = useState([]); const [ctrl, setCtrl] = useState(false); const [moveOrigin, setMoveOrigin] = useState([]); const [mousePosX, setMousePosX] = useState(0); const [mousePosY, setMousePosY] = useState(0); const [hoveredBlock, setHoveredBlock] = useState(undefined); const [placementBlock, setPlacementBlock] = useState(undefined); const [anchorEl, setAnchorEl] = React.useState(null); const [anchorPosX, setAnchorPosX] = useState(0); const [anchorPosY, setAnchorPosY] = useState(0); const [ventType, setVentType] = useState(undefined); //const [placeableType, setPlaceableType] = useState(undefined); const [direction, setDirection] = useState(0); const [scale, setScale] = useState(1); const [interactionDevice, setInteractionDevice] = useState(); const [interactionComponent, setInteractionComponent] = useState(); const [interactionSensor, setInteractionSensor] = useState(); const [isAddInteractionOpen, setIsAddInteractionOpen] = useState(false); const mineAPI = useMineAPI(); const theme = useTheme(); const [componentMeasurements, setComponentMeasurements] = useState< undefined | Map >(undefined); const [{ user, as }] = useGlobalState(); const save = () => { let settings = pond.MineSettings.create(); props.devices.forEach(device => { if (device.settings) settings.devices.push(device.settings?.deviceId); }); mineAPI.addMine(settings, as).then((resp: any) => { console.log(resp); }); }; useEffect(() => { setWidth(props.width ? props.width : 800); setHeight(props.height ? props.height : 600); }, [props.width, props.height]); useEffect(() => { setMode(props.mode ? props.mode : Mode.NORMAL); }, [props.mode]); useEffect(() => { if (selected.length > 0) { selected[0].setDirection(direction); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [direction]); useEffect(() => { let compMeasurements: Map = new Map(); components.forEach(comp => { let measurements: UnitMeasurement[] = []; comp.lastMeasurement.forEach(m => { let um = UnitMeasurement.create(m, user); measurements.push(um); }); compMeasurements.set(comp.key(), measurements); }); setComponentMeasurements(compMeasurements); }, [components, user]); useEffect(() => { if ( componentMeasurements === undefined || componentPreferences.size < 1 || components.length < 1 || !user || componentDevices.size < 1 || componentMeasurements.size < 1 ) return; let sensors: Sensor[] = []; components.forEach(comp => { if (componentPreferences.get(comp.key())) { let p = componentPreferences.get(comp.key()); if (p?.sensors) p?.sensors.forEach((s, i) => { if (s.enabled) { let id = componentDevices.get(comp.key()); let device = devices.find(dev => dev.settings?.deviceId === id); let measurements = componentMeasurements.get(comp.key()); if (measurements !== undefined) sensors.push(Sensor.create(comp, Device.create(device), s, measurements, i, user)); } }); } }); setSensors(sensors); }, [ components, componentPreferences, setSensors, user, devices, componentDevices, componentMeasurements ]); const getOffsetX = (e: any): number => { if (e.offsetParent) { return e.offsetLeft + getOffsetX(e.offsetParent); } return e.offsetLeft; }; const getAbsoluteX = (e: any): number => { return e.pageX - getOffsetX(e.currentTarget); }; const getX = (e: any): number => { let offset = getOffsetX(e.currentTarget); offset = offset - cameraOffsetX * scale; return (e.pageX - offset) / scale; }; const getOffsetY = (e: any): number => { if (e.offsetParent) { return e.offsetTop + getOffsetY(e.offsetParent); } return e.offsetTop; }; const getAbsoluteY = (e: any): number => { return e.pageY - getOffsetY(e.currentTarget); }; const getY = (e: any): number => { let offset = getOffsetY(e.currentTarget); offset = offset - cameraOffsetY * scale; //offset = offset + mouseOffsetY; return (e.pageY - offset) / scale; }; const addPlaceable = (p: Placeable) => { let r = blocks; saveState(); if (selected.length > 0) { let xy = selected[0].getEndXAndY(); p.y = xy.y; p.x = xy.x; p.setAngle(selected[0].getEndAngle(), selected[0].getEndAngle()); } else { p.x = clickX; p.y = clickY; } selected[0] = p; r.push(p); setBlocks([...r]); }; useEffect(() => { props.addBlockRef.current = addPlaceable; // eslint-disable-next-line react-hooks/exhaustive-deps }, [selected, addPlaceable, blocks]); const add = (x?: number, y?: number) => { let r = blocks; saveState(); let p = VentShaft.create(); if (x && y) { let v = VentCorner.create(x, y, 32, 0); setBlocks([...r]); return v; } else if (Boolean(anchorEl)) { p.x = anchorPosX + cameraOffsetX; p.y = anchorPosY + cameraOffsetY; p.angle = 0; } else { p.x = p.x + cameraOffsetX; p.y = p.y + cameraOffsetY; } r.push(p); setBlocks([...r]); //return p; }; const saveState = () => { let u = undoStates; let newBlocks: Placeable[] = []; blocks.forEach(block => { let b = block.clone(); newBlocks.push(b); }); u.push(newBlocks); setRedoStates([]); }; const click = (e: any) => { if (hoveredBlock !== undefined) { setMode(hoveredBlock); setMouseDown(false); return; } selected.forEach(s => { s.selected = false; }); let hit = false; let r = blocks; let x = getX(e); let y = getY(e); setClickX(x); setClickY(y); let newBlocks: Placeable[] = []; if (mode === Mode.NORMAL) { r.forEach(block => { newBlocks.push(block.clone()); if (block.clickCheck(x, y, scale)) { hit = true; setSelected([block]); } }); sensors.forEach(sensor => { if (sensor.clickCheck(x, y, scale)) { hit = true; setSelected([sensor]); } }); } if (!hit) setSelected([]); if (e.button === 2) return; if (hit) setMoveOrigin(newBlocks); if (mode === Mode.ADD) { add(); } else if (mode === Mode.DELETE) { deleteBlock(x, y, true); } setMouseDown(true); }; const mouseUp = (_e: any) => { setMouseDown(false); if (mode === Mode.NORMAL && selected !== undefined) { let state = [...moveOrigin]; let states = [...undoStates]; states.push(state); setUndoStates([...states]); setRedoStates([]); selected.forEach(placeable => { if (placeable.type === pond.PlaceableType.PLACEABLE_TYPE_SENSOR) { let s = placeable as Sensor; let prefs = componentPreferences.get(s.getComponent().key()); if (prefs?.sensors[s.index]) { prefs.sensors[s.index].x = s.x; prefs.sensors[s.index].y = s.y; componentPreferences.set(s.getComponent().key(), prefs); setComponentPreferences(new Map(componentPreferences)); } } }); } //setSelected(undefined); }; const keyDown = (e: any) => { if (e.key === "1") { setMode(Mode.NORMAL); } else if (e.key === "2") { setMode(Mode.MOVE); } else if (e.key === "3") { setMode(Mode.ADD); } else if (e.key === "4") { setMode(Mode.DELETE); } else if (e.key === "Control") { setCtrl(true); } else if (e.key === "z" && ctrl) { undo(); } else if (e.key === "Z" && ctrl) { redo(); } else if (e.key === "s" && ctrl) { save(); } else if (e.key === "v") { if (viewMode === ViewMode.NORMAL) { setViewMode(ViewMode.INFO); } else { setViewMode(ViewMode.NORMAL); } } }; const keyUp = (e: any) => { if (e.key === "Control") { setCtrl(false); } }; const deleteBlock = (x: number, y: number, save?: boolean) => { if (save) saveState(); let b = [...blocks]; blocks.forEach((block, index) => { if (block.clickCheck(x + cameraOffsetX, y + cameraOffsetY, scale)) { b.splice(index, 1); return; } }); setBlocks(b); }; const drag = (e: any) => { setHoveredBlock(undefined); let x = getAbsoluteX(e); let y = getAbsoluteY(e); setMousePosX(x); setMousePosY(y); if (!mouseDown) { // Set position for placement block if (mode === Mode.ADD && placementBlock) { let p = placementBlock.clone() as Placeable; p.x = x; // - p.width() / 2; p.y = y; // - p.height() / 2; setPlacementBlock(p); } } else { // If the mouse is clicked down if (mode === Mode.MOVE) { setCameraOffsetX(clickX - x / scale); setCameraOffsetY(clickY - y / scale); } else if (selected.length > 0 && mode === Mode.NORMAL) { selected[0].drag(getX(e), getY(e), scale); blocks.forEach(block => { if (block.x !== selected[0].x && block.y !== selected[0].y) { selected[0].snap(block, 16); } }); } else if (mode === Mode.DELETE) { deleteBlock(x, y); } } }; const redo = () => { if (redoStates.length < 1) return; let rects = redoStates.pop(); let undos = undoStates; if (rects) { undos.push(rects); setUndoStates([...undos]); setBlocks(rects); } }; const undo = () => { let states = undoStates; let redo = redoStates; let rects = states.pop(); if (rects) { redo.push([...rects]); setRedoStates([...redo]); setUndoStates([...states]); setBlocks(rects); } }; useEffect(() => { props.saveRef.current = save; // eslint-disable-next-line react-hooks/exhaustive-deps }, [save]); useEffect(() => { props.undoRef.current = undo; // eslint-disable-next-line react-hooks/exhaustive-deps }, [undo]); useEffect(() => { props.redoRef.current = redo; // eslint-disable-next-line react-hooks/exhaustive-deps }, [redo]); const wheel = (e: WheelEvent) => { // Scroll down if (e.deltaY > 0) { let newScale = scale - 0.05; if (newScale > 0) { let mouseRatioX = mousePosX / width; let mouseRatioY = mousePosY / height; let newWidth = width / newScale; let dx = (width - newWidth) * mouseRatioX; let olddx = (width - width / scale) * mouseRatioX; let newHeight = height / newScale; let dy = (height - newHeight) * mouseRatioY; let olddy = (height - height / scale) * mouseRatioY; setScale(Math.round(newScale * 1000) / 1000); setCameraOffsetX(cameraOffsetX - olddx + dx); setCameraOffsetY(cameraOffsetY - olddy + dy); } } // Scroll up if (e.deltaY < 0) { let mouseRatioX = mousePosX / width; let mouseRatioY = mousePosY / height; let newScale = scale + 0.05; let newWidth = width / newScale; let dx = (width - newWidth) * mouseRatioX; let olddx = (width - width / scale) * mouseRatioX; let newHeight = height / newScale; let dy = (height - newHeight) * mouseRatioY; let olddy = (height - height / scale) * mouseRatioY; setScale(Math.round(newScale * 1000) / 1000); setCameraOffsetX(cameraOffsetX - olddx + dx); setCameraOffsetY(cameraOffsetY - olddy + dy); } // If the mouse is at the top, scroll mode selection /*if (mousePosY < 100) { // Scroll down if (e.deltaY > 0) { //if (mode === Mode.NORMAL) setMode(Mode.DELETE); //else setMode(mode - 1); console.log("scroll down") let newScale = scale - 0.1 setScale(newScale) } // Scroll up if (e.deltaY < 0) { //if (mode === Mode.DELETE) setMode(Mode.NORMAL); //else setMode(mode + 1); console.log("scroll up") let newScale = scale + 0.1 setScale(newScale) } }*/ }; const drawBlocks = (context: CanvasRenderingContext2D) => { blocks.forEach(block => { if (block.draw) block.draw(context, cameraOffsetX, cameraOffsetY, scale); }); selected.forEach(block => { block.highlight(context, cameraOffsetX, cameraOffsetY, scale); }); }; const drawSensors = (context: CanvasRenderingContext2D) => { /*components.forEach(comp => { let x = or(componentPreferences.get(comp.key())?.x, 10) let y = or(componentPreferences.get(comp.key())?.y, 10) context.arc(x - cameraOffsetX, y - cameraOffsetY, 25, 2 * Math.PI, 0, false) context.fill() })*/ sensors.forEach(sensor => { sensor.draw(context, cameraOffsetX, cameraOffsetY, scale); if (viewMode === ViewMode.INFO) sensor.highlight(context, cameraOffsetX, cameraOffsetY, scale); }); }; const draw = (context: CanvasRenderingContext2D) => { context.clearRect(0, 0, width, height); context.fillStyle = "rgba(150, 150, 255, 150)"; context.strokeStyle = "rgba(247, 202, 24, 1)"; drawBlocks(context); drawSensors(context); // Draw placement block if in add mode if (mode === Mode.ADD) { context.globalAlpha = 0.3; if (placementBlock) placementBlock.draw(context); context.globalAlpha = 1; } // Draw garbage can if in delete mode if (mode === Mode.DELETE) { let i = new Image(); i.src = "https://img.icons8.com/material-outlined/48/000000/delete-forever.png"; context.globalAlpha = 1; context.drawImage(i, mousePosX - 16, mousePosY - 16, 32, 32); } }; const contextMenu = (e: Event) => { e.preventDefault(); setAnchorPosX(getAbsoluteX(e)); setAnchorPosY(getAbsoluteY(e)); if (!anchorEl) setAnchorEl(e.currentTarget as any); }; /*const isTBreak = (p: Placeable) => { if (p.type === pond.PlaceableType.PLACEABLE_TYPE_SHAFT) { if (p.subtype === pond.ShaftType.SHAFT_TYPE_T_BREAK) { return true; } } return false; };*/ const isDefaultShaft = (p: Placeable) => { if (p.type === pond.PlaceableType.PLACEABLE_TYPE_SHAFT) { if (p.subtype === pond.ShaftType.SHAFT_TYPE_DEFAULT) { return true; } } return false; }; return (
{loading && (
Loading...
)}
{ if (viewMode === ViewMode.INFO) { setViewMode(ViewMode.NORMAL); } else { setViewMode(ViewMode.INFO); } }}>
{interactionDevice && interactionComponent && ( setIsAddInteractionOpen(false)} refreshCallback={() => {}} canEdit={true} sensor={interactionSensor} /> )} setAnchorEl(null)} // getContentAnchorEl={null} anchorOrigin={{ vertical: anchorPosY, horizontal: anchorPosX }} keepMounted onContextMenu={(e: any) => { e.preventDefault(); setAnchorEl(null); }} disableAutoFocusItem> {selected.length > 0 && selected[0].type === pond.PlaceableType.PLACEABLE_TYPE_SHAFT && ( { setAnchorEl(null); setVentType(selected[0].subtype); }} > )} {selected.length > 0 && selected[0].type === pond.PlaceableType.PLACEABLE_TYPE_SENSOR && ( { let s = selected[0] as Sensor; setAnchorEl(null); //setVentType(selected[0].subtype); setInteractionComponent(s.getComponent()); setInteractionDevice(s.getDevice()); setInteractionSensor(s); setIsAddInteractionOpen(true); }} > )} {!(selected.length > 0 && isDefaultShaft(selected[0])) && ( { setAnchorEl(null); let v = VentShaft.create(); addPlaceable(v); }} > )} { setAnchorEl(null); let v = VentCorner.create(); addPlaceable(v); }} > { let v = VentTBreak.create(); addPlaceable(v); setAnchorEl(null); }} > {selected.length > 0 && (selected[0].subtype === pond.ShaftType.SHAFT_TYPE_CORNER || selected[0].subtype === pond.ShaftType.SHAFT_TYPE_T_BREAK) && ( { selected[0].setDirection(0); setAnchorEl(null); }} > )} {selected.length > 0 && (selected[0].subtype === pond.ShaftType.SHAFT_TYPE_CORNER || selected[0].subtype === pond.ShaftType.SHAFT_TYPE_T_BREAK) && ( { selected[0].setDirection(1); setAnchorEl(null); }} > )} {selected.length > 0 && ( { saveState(); let b = [...blocks]; blocks.forEach((block, index) => { if ( selected[0].x === block.x && selected[0].y === block.y && block.angle === selected[0].angle && block.type === selected[0].type ) { b.splice(index, 1); return; } }); setAnchorEl(null); setSelected([]); setBlocks(b); }} > )} {selected.length > 0 && false && ( { setAnchorEl(null); let xy = selected[0].getEndXAndY(); let x = xy.x; let y = xy.y; //let startBlock = selected[0].clone(); let block = add(x, y); if (block) { //block.circle.selected = true; //block.startSnap = startBlock; //setSelected([block]); } //setMouseDown(true); }} > )}
); }