1 import { drag } from 'd3-drag';
2 import { select } from 'd3-selection';
3 import React from 'react';
5 import Dungeons from './Dungeons';
6 import Items from './Items';
7 import Map from './Map';
8 import ToggleIcon from './ToggleIcon';
9 import ZeldaIcon from '../common/ZeldaIcon';
10 import { shouldShowDungeonItem } from '../../helpers/tracker';
11 import { useTracker } from '../../hooks/tracker';
17 itemsTransform: 'translate(1 1) scale(22)',
19 dungeonsTransform: 'translate(1 39) scale(98)',
20 mapTransform: 'translate(24 0) scale(76)',
25 itemsTransform: 'translate(10 1) scale(30)',
27 dungeonsTransform: 'translate(1 51) scale(48)',
28 mapTransform: 'translate(50 0) scale(50)',
30 manyDungeonItemsVertical: {
33 itemsTransform: 'translate(1 1) scale(27)',
35 dungeonsTransform: 'translate(1 48) scale(24)',
36 mapTransform: 'translate(30 0) scale(50)',
40 const Canvas = () => {
41 const [dragging, setDragging] = React.useState(null);
42 const { addPin, config, pins, removePin } = useTracker();
44 const layout = React.useMemo(() => {
45 if (config.mapLayout === 'vertical') {
47 if (shouldShowDungeonItem(config, 'Map')) {
50 if (shouldShowDungeonItem(config, 'Compass')) {
53 if (shouldShowDungeonItem(config, 'Small')) {
56 if (shouldShowDungeonItem(config, 'Big')) {
59 return count > 2 ? LAYOUTS.manyDungeonItemsVertical : LAYOUTS.defaultVertical;
61 return LAYOUTS.defaultHorizontal;
65 React.useEffect(() => {
66 const canvas = select('.canvas');
67 const bbox = canvas.select('.background');
68 const start = { x: 0, y: 0 };
69 const onStart = function (e) {
73 const onDrag = function (e) {
74 const bounds = bbox.node().getBoundingClientRect();
75 const distance = Math.max(Math.abs(e.x - start.x), Math.abs(e.y - start.y));
78 icon: this.dataset['icon'],
79 x: (e.x - bounds.x) / bounds.width,
80 y: (e.y - bounds.y) / bounds.height,
86 const onEnd = function (e) {
87 const bounds = bbox.node().getBoundingClientRect();
89 const distance = Math.max(Math.abs(e.x - start.x), Math.abs(e.y - start.y));
92 icon: this.dataset['icon'],
93 x: (e.x - bounds.x) / bounds.width,
94 y: (e.y - bounds.y) / bounds.height,
96 if (this.classList.contains('map-pin')) {
98 this.classList.forEach(name => {
99 if (name.startsWith('map-pin-')) {
100 id = parseInt(name.substr(8), 10);
107 const selection = canvas.selectAll('.toggle-icon');
108 const draggable = drag()
111 .on('start', onStart)
114 selection.call(draggable);
116 selection.on('.drag', null);
118 }, [pins, removePin]);
121 xmlns="http://www.w3.org/2000/svg"
124 height={layout.height}
125 viewBox={`0 0 ${layout.width} ${layout.height}`}
126 onContextMenu={(e) => {
132 className="background"
136 height={layout.height}
138 <g className="items" transform={layout.itemsTransform}>
141 <g className="dungeons" transform={layout.dungeonsTransform}>
142 <Dungeons columns={layout.dungeonColumns} />
144 <g className="tracker-map" transform={layout.mapTransform}>
151 className={`map-pin map-pin-${pin.id}`}
152 controller={ToggleIcon.pinController(pin, removePin)}
156 `translate(${pin.x * layout.width} ${pin.y * layout.height}) scale(3)`
163 `translate(${dragging.x * layout.width} ${dragging.y * layout.height}) scale(4)`
165 <ZeldaIcon name={dragging.icon} svg />
171 export default Canvas;