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 const compact = config.compactKeysanity && count === 4;
60 return !compact && count > 2
61 ? LAYOUTS.manyDungeonItemsVertical : LAYOUTS.defaultVertical;
63 return LAYOUTS.defaultHorizontal;
67 React.useEffect(() => {
68 const canvas = select('.canvas');
69 const bbox = canvas.select('.background');
70 const start = { x: 0, y: 0 };
71 const onStart = function (e) {
75 const onDrag = function (e) {
76 const bounds = bbox.node().getBoundingClientRect();
77 const distance = Math.max(Math.abs(e.x - start.x), Math.abs(e.y - start.y));
80 icon: this.dataset['icon'],
81 x: (e.x - bounds.x) / bounds.width,
82 y: (e.y - bounds.y) / bounds.height,
88 const onEnd = function (e) {
89 const bounds = bbox.node().getBoundingClientRect();
91 const distance = Math.max(Math.abs(e.x - start.x), Math.abs(e.y - start.y));
94 icon: this.dataset['icon'],
95 x: (e.x - bounds.x) / bounds.width,
96 y: (e.y - bounds.y) / bounds.height,
98 if (this.classList.contains('map-pin')) {
100 this.classList.forEach(name => {
101 if (name.startsWith('map-pin-')) {
102 id = parseInt(name.substr(8), 10);
109 const selection = canvas.selectAll('.toggle-icon');
110 const draggable = drag()
113 .on('start', onStart)
116 selection.call(draggable);
118 selection.on('.drag', null);
120 }, [pins, removePin]);
123 xmlns="http://www.w3.org/2000/svg"
126 height={layout.height}
127 viewBox={`0 0 ${layout.width} ${layout.height}`}
128 onContextMenu={(e) => {
134 className="background"
138 height={layout.height}
140 <g className="items" transform={layout.itemsTransform}>
143 <g className="dungeons" transform={layout.dungeonsTransform}>
144 <Dungeons columns={layout.dungeonColumns} />
146 <g className="tracker-map" transform={layout.mapTransform}>
153 className={`map-pin map-pin-${pin.id}`}
154 controller={ToggleIcon.pinController(pin, removePin)}
158 `translate(${pin.x * layout.width} ${pin.y * layout.height}) scale(3)`
165 `translate(${dragging.x * layout.width} ${dragging.y * layout.height}) scale(4)`
167 <ZeldaIcon name={dragging.icon} svg />
173 export default Canvas;