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) {
70 const bounds = bbox.node().getBoundingClientRect();
74 icon: this.dataset['icon'],
75 x: (e.x - bounds.x) / bounds.width,
76 y: (e.y - bounds.y) / bounds.height,
79 const onDrag = function (e) {
80 const bounds = bbox.node().getBoundingClientRect();
82 icon: this.dataset['icon'],
83 x: (e.x - bounds.x) / bounds.width,
84 y: (e.y - bounds.y) / bounds.height,
87 const onEnd = function (e) {
88 const bounds = bbox.node().getBoundingClientRect();
90 const distance = Math.max(Math.abs(e.x - start.x), Math.abs(e.y - start.y));
93 icon: this.dataset['icon'],
94 x: (e.x - bounds.x) / bounds.width,
95 y: (e.y - bounds.y) / bounds.height,
97 if (this.classList.contains('map-pin')) {
99 this.classList.forEach(name => {
100 if (name.startsWith('map-pin-')) {
101 id = parseInt(name.substr(8), 10);
108 const selection = canvas.selectAll('.toggle-icon');
109 const draggable = drag()
112 .on('start', onStart)
115 selection.call(draggable);
117 selection.on('.drag', null);
119 }, [pins, removePin]);
122 xmlns="http://www.w3.org/2000/svg"
125 height={layout.height}
126 viewBox={`0 0 ${layout.width} ${layout.height}`}
127 onContextMenu={(e) => {
133 className="background"
137 height={layout.height}
139 <g className="items" transform={layout.itemsTransform}>
142 <g className="dungeons" transform={layout.dungeonsTransform}>
143 <Dungeons columns={layout.dungeonColumns} />
145 <g className="tracker-map" transform={layout.mapTransform}>
152 className={`map-pin map-pin-${pin.id}`}
153 controller={ToggleIcon.pinController(pin, removePin)}
157 `translate(${pin.x * layout.width} ${pin.y * layout.height}) scale(3)`
164 `translate(${dragging.x * layout.width} ${dragging.y * layout.height}) scale(4)`
166 <ZeldaIcon name={dragging.icon} svg />
172 export default Canvas;