From: Daniel Karbach Date: Sun, 25 May 2025 12:02:32 +0000 (+0200) Subject: zootr draw connectors X-Git-Url: https://git.localhorst.tv/?a=commitdiff_plain;h=16e2a7032719159f94d110e635f86c92ec97250c;p=alttp.git zootr draw connectors --- diff --git a/resources/js/components/zootr/MixedPoolsTracker.js b/resources/js/components/zootr/MixedPoolsTracker.js index 3a62bdb..f27b547 100644 --- a/resources/js/components/zootr/MixedPoolsTracker.js +++ b/resources/js/components/zootr/MixedPoolsTracker.js @@ -2411,6 +2411,41 @@ const vecAdd = (a, b) => ({ x: a.x + b.x, y: a.y + b.y }); const vecMul = (v, f) => ({ x: v.x * f, y: v.y * f }); +const MAPS = AREAS + .filter((area) => !!area.map) + .map((area) => ({ + id: area.id, + name: area.name, + pos: area.map.pos, + size: area.map.size, + bg: { + src: area.map.bg.src, + pos: vecAdd(area.map.pos, area.map.bg.off), + size: vecMul(area.map.size, area.map.bg.scale), + }, + entrances: area.entrances + .filter((entrance) => entrance.pos) + .map((entrance) => ({ + id: `${area.id}.${entrance.id}`, + name: entranceFull({ ...entrance, area }), + pos: vecAdd(area.map.pos, entrance.pos), + color: entrance.bgColor, + })), + })); + +const getMapEntrance = (id) => { + if (!id) return null; + const dotPos = id.indexOf('.'); + if (dotPos === -1) { + return null; + } + const areaId = id.substring(0, dotPos); + const area = MAPS.find((a) => a.id === areaId); + if (!area) return null; + const entrance = area.entrances.find((e) => e.id === id); + return entrance; +}; + const SelectBox = ({ id, name, onChange, options, value }) => { const [open, setOpen] = React.useState(false); const [search, setSearch] = React.useState(''); @@ -2580,21 +2615,37 @@ EntranceRow.propTypes = { }; const MapEntrance = ({ entrance }) => { - const { connections, setConnection } = useTracker(); + const { + connections, + isDragging, + onMapEntranceClick, + setConnection, + } = useTracker(); + + const classNames = ['entrance']; + if (connections[entrance.id] === 'trash') classNames.push('is-trash'); + if (isDragging(entrance)) classNames.push('is-dragging'); return { - if (connections[entrance.id] === 'trash') { + onClick={(e) => { + onMapEntranceClick(entrance); + e.preventDefault(); + e.stopPropagation(); + }} + onContextMenu={(e) => { + if (connections[entrance.id]) { setConnection(entrance.id, null); } else { setConnection(entrance.id, 'trash'); } + e.preventDefault(); + e.stopPropagation(); }} > {entrance.name} @@ -2610,11 +2661,37 @@ MapEntrance.propTypes = { x: PropTypes.number, y: PropTypes.number, }) + }), +}; + +const MapConnector = ({ from, to }) => { + return ; +}; + +MapConnector.propTypes = { + from: PropTypes.shape({ + pos: PropTypes.shape({ + x: PropTypes.number, + y: PropTypes.number, + }) + }), + to: PropTypes.shape({ + pos: PropTypes.shape({ + x: PropTypes.number, + y: PropTypes.number, + }) }) }; const MixedPoolsTracker = () => { const [connections, setConnections] = React.useState({}); + const [dragging, setDragging] = React.useState(null); const setConnection = React.useCallback((src, dst) => { setConnections((c) => { @@ -2651,9 +2728,34 @@ const MixedPoolsTracker = () => { return options; }, []); + const isDragging = React.useCallback((entrance) => { + return dragging === entrance.id; + }, [dragging]); + + const onMapEntranceClick = React.useCallback((entrance) => { + if (dragging) { + if (dragging !== entrance.id) { + setConnection(dragging, entrance.id); + } + setDragging(null); + } else { + setDragging(entrance.id); + } + }, [dragging, setConnection, setDragging]); + const context = React.useMemo(() => ({ - connections, entrances, setConnection, - }), [connections, entrances, setConnection]); + connections, + entrances, + isDragging, + onMapEntranceClick, + setConnection, + }), [ + connections, + entrances, + isDragging, + onMapEntranceClick, + setConnection, + ]); const superGroups = React.useMemo(() => { const sg = [ @@ -2696,55 +2798,26 @@ const MixedPoolsTracker = () => { return sg; }, [connections]); - const maps = React.useMemo(() => { - return AREAS - .filter((area) => !!area.map) - .map((area) => ({ - id: area.id, - pos: area.map.pos, - size: area.map.size, - bg: { - src: area.map.bg.src, - pos: vecAdd(area.map.pos, area.map.bg.off), - size: vecMul(area.map.size, area.map.bg.scale), - }, - entrances: area.entrances - .filter((entrance) => entrance.pos) - .map((entrance) => ({ - id: `${area.id}.${entrance.id}`, - name: entranceFull({ ...entrance, area }), - pos: vecAdd(area.map.pos, entrance.pos), - color: entrance.bgColor, - })), - })); - }, []); - const connectors = React.useMemo(() => { const cs = []; Object.entries(connections).forEach(([from, to]) => { + const fromEntrance = getEntrance(from); + if (!fromEntrance) return; + if (from > to && !fromEntrance.oneway) return; + const fromMap = getMapEntrance(from); + if (!fromMap) return; + const toMap = getMapEntrance(to); + if (!toMap) return; + cs.push({ + from: fromMap, + to: toMap, + }); }); return cs; - }, [connections, maps]); + }, [connections]); return
-
- - {maps.map((map) => - - - {map.entrances.map((entrance) => - - )} - - )} - -
{superGroups.map((sg) =>
@@ -2801,6 +2874,36 @@ const MixedPoolsTracker = () => { )}
+
+ { setDragging(null); }} + onContextMenu={(e) => { e.preventDefault(); e.stopPropagation(); }} + > + {MAPS.map((map) => + + + {map.entrances.map((entrance) => + + )} + + )} + + {connectors.map((c) => + + )} + + +
; }; diff --git a/resources/sass/zootr.scss b/resources/sass/zootr.scss index 2ccd138..3070837 100644 --- a/resources/sass/zootr.scss +++ b/resources/sass/zootr.scss @@ -94,6 +94,13 @@ cursor: pointer; } .map { + svg { + max-width: 104em; + } + .connector { + stroke: #cc0000; + pointer-events: none; + } .entrance { &.is-trash { fill: #000000; @@ -104,6 +111,10 @@ stroke: #ffffff; filter: drop-shadow(0 0 1px #000000) drop-shadow(0 0 2px #000000); } + &.is-dragging { + stroke: #cc0000; + filter: drop-shadow(0 0 1px #cc0000) drop-shadow(0 0 2px #cc0000); + } } } }