9 |
10 |
11 |
125 |
126 |
--------------------------------------------------------------------------------
/API.md:
--------------------------------------------------------------------------------
1 | # Console API
2 |
3 | *This is work in progress and subject to change. Please don't rely on it for anything critical*
4 |
5 | The `city-roads` provides additional set of operations for the software engineers, allowing them
6 | to execute arbitrary OpenStreetMap queries and visualize results.
7 |
8 | ## Methods
9 |
10 | This section describes available console API methods.
11 |
12 | ### `scene.load()`
13 |
14 | Allows you to load more city roads into the current scene. Before we dive into details, let's explore what
15 | it takes to render Tokyo and Seattle next to each other.
16 |
17 | 
18 |
19 | First, open [city roads](https://anvaka.github.io/city-roads/)
20 | and load `Seattle` roads. Then open [developer console](https://developers.google.com/web/tools/chrome-devtools/open) and run the following command:
21 |
22 | ``` js
23 | scene.load(Query.Road, 'Tokyo'); // load every single road in Tokyo
24 | ```
25 |
26 | Monitor your `Networks` tab and see when request is done. Tokyo bounding box is very large,
27 | so it will appear very far away on the top left corner. Let's move Tokyo grid next to Seattle:
28 |
29 | ``` js
30 | // Find the loaded layer with Tokyo:
31 | tokyo = scene.queryLayer('Tokyo');
32 |
33 | // Exact offset numbers can be found by experimenting
34 | tokyo.moveBy(/* xOffset = */ 718000, /* yOffset = */ 745000)
35 | ```
36 |
37 | `scene.load()` has the following signature:
38 |
39 | ``` js
40 | function load(wayFilter: String, loadOptions: LoadOptions);
41 | ```
42 |
43 | * `wayFilter` is used to filter out OpenStreetMap ways. You can find a list of well-known filters [here](https://github.com/anvaka/city-roads/blob/f543a712a0b88b12751aad691baa5eb9d6c0c664/src/lib/Query.js#L6-L24). If you need
44 | to know more to create custom filters, here is a complete [language guide](https://wiki.openstreetmap.org/wiki/Overpass_API/Overpass_QL). You can also get good insight into key/value distribution for ways by exploring [taginfo](https://taginfo.openstreetmap.org/tags) (make sure to sort by Ways in descending order to get the most popular combinations);
45 | * `loadOptions` allows you to have granular control over the bounding box of the loaded results. If this
46 | value is a string, then it is converted to a geocoded area id with nominatim, and then the first match
47 | is used as a bounding box. This may not be enough sometimes, so you can provide a specific area id, or
48 | a bounding box, by passing an object. For example:
49 |
50 | ``` js
51 | scene.load(Query.Road, {areaId: 3600237385}); // Explicitly set area id to Seattle
52 |
53 | scene.load(Query.Building, { // Load all buildings...
54 | bbox: [ // ...in the given bounding box
55 | "-15.8477", /* south lat */
56 | "-47.9841", /* west lon */
57 | "-15.7330", /* north lat */
58 | "-47.7970" /* east lon */
59 | ]});
60 | ```
61 |
62 | ### scene.queryLayerAll()
63 |
64 | Returns all layers added to the scene. This is what it takes to assign different colors to each layer:
65 |
66 | ``` js
67 | allLayers = scene.queryLayerAll()
68 | allLayers[0].color = 'deepskyblue'; // color can be a name.
69 | allLayers[1].color = 'rgb(255, 12, 43)'; // or a any other expression (rgb, hex, hsl, etc.)
70 | ```
71 |
72 | ### `scene.clear()`
73 |
74 | Clears the current scene, allowing you to start from scratch.
75 |
76 |
77 | ### `scene.saveToPNG(fileName: string)`
78 |
79 | To save the current scene as a PNG file run
80 |
81 | ``` js
82 | scene.saveToPNG('hello'); // hello.png is saved
83 | ```
84 |
85 | ### `scene.saveToSVG(fileName: string, options?: Object)`
86 |
87 | This command allows you to save the scene as an SVG file.
88 |
89 | ``` js
90 | scene.saveToSVG('hello'); // hello.svg is saved
91 | ```
92 |
93 | If you are planning to use a pen-plotter or a laser cutter, you can also
94 | greatly reduce the print time, by removing very short paths from the final
95 | export. To do so, pass `minLength` option:
96 |
97 | ``` js
98 | scene.saveToSVG('hello', {minLength: 2});
99 | // All paths with length shorter than 2px are removed from the final SVG.
100 | ```
101 |
102 | ## Examples
103 |
104 | Here are a few example of working with the API.
105 |
106 | ### Loading all bikeways in the current city
107 |
108 | ``` js
109 | var bikes = scene.load('way[highway="cycleway"]', {layer: scene.queryLayer()})
110 | // Make lines 4 pixels wide
111 | bikes.lineWidth = 4
112 | // and red
113 | bikes.color = 'red'
114 | ```
115 |
116 | ### Loading all bus routes in the current city
117 |
118 | This script will get all bus routes in the current city, and render them 4px wide, with
119 | red color:
120 |
121 | ``` js
122 | var areaId = scene.queryLayer().getQueryBounds().areaId;
123 | var bus = scene.load('', {
124 | layer: scene.queryLayer(),
125 | raw: `[out:json][timeout:250];
126 | area(${areaId});(._; )->.area;
127 | (nwr[route=bus](area.area););
128 | out body;>;out skel qt;`
129 | });
130 |
131 | bus.color='red';
132 | bus.lineWidth = 4;
133 | ```
134 |
135 | If you want a specific bus number, pass additional `ref=bus_number`. For example, bus route #24:
136 |
137 | ``` js
138 | var areaId = scene.queryLayer().getQueryBounds().areaId;
139 | var bus = scene.load('', {
140 | layer: scene.queryLayer(),
141 | raw: `[out:json][timeout:250];
142 | area(${areaId});(._; )->.area;
143 | (nwr[route=bus][ref=24](area.area););
144 | out body;>;out skel qt;`
145 | });
146 |
147 | bus.color = 'green';
148 | bus.lineWidth = 4;
149 | ```
150 |
151 |
--------------------------------------------------------------------------------
/src/lib/saveFile.js:
--------------------------------------------------------------------------------
1 | // import protobufExport from './protobufExport.js';
2 | import svgExport from './svgExport.js';
3 |
4 | export function toSVG(scene, options) {
5 | options = options || {};
6 | let svg = svgExport(scene, {
7 | printable: collectPrintable(),
8 | ...options
9 | });
10 | let blob = new Blob([svg], {type: "image/svg+xml"});
11 | let url = window.URL.createObjectURL(blob);
12 | let fileName = getFileName(options.name, '.svg');
13 | // For some reason, safari doesn't like when download happens on the same
14 | // event loop cycle. Pushing it to the next one.
15 | setTimeout(() => {
16 | let a = document.createElement("a");
17 | a.href = url;
18 | a.download = fileName;
19 | a.click();
20 | revokeLater(url);
21 | }, 30)
22 | }
23 |
24 | export function toPNG(scene, options) {
25 | options = options || {};
26 |
27 | getPrintableCanvas(scene).then((printableCanvas) => {
28 | let fileName = getFileName(options.name, '.png');
29 |
30 | printableCanvas.toBlob(function(blob) {
31 | let url = window.URL.createObjectURL(blob);
32 | let a = document.createElement("a");
33 | a.href = url;
34 | a.download = fileName;
35 | a.click();
36 | revokeLater(url);
37 | }, 'image/png')
38 | })
39 | }
40 |
41 | export function getPrintableCanvas(scene) {
42 | let cityCanvas = getCanvas();
43 | let width = cityCanvas.width;
44 | let height = cityCanvas.height;
45 |
46 | let printable = document.createElement('canvas');
47 | let ctx = printable.getContext('2d');
48 | printable.width = width;
49 | printable.height = height;
50 | scene.render();
51 | ctx.drawImage(cityCanvas, 0, 0, cityCanvas.width, cityCanvas.height, 0, 0, width, height);
52 |
53 | return Promise.all(collectPrintable().map(label => drawTextLabel(label, ctx))).then(() => {
54 | return printable;
55 | });
56 | }
57 |
58 | export function getCanvas() {
59 | return document.querySelector('#canvas')
60 | }
61 |
62 | function getFileName(name, extension) {
63 | let fileName = escapeFileName(name || new Date().toISOString());
64 | return fileName + (extension || '');
65 | }
66 |
67 | function escapeFileName(str) {
68 | if (!str) return '';
69 |
70 | return str.replace(/[#%&{}\\/?*><$!'":@+`|=]/g, '_');
71 | }
72 |
73 |
74 | function drawTextLabel(element, ctx) {
75 | if (!element) return Promise.resolve();
76 |
77 | return new Promise((resolve, reject) => {
78 | let dpr = window.devicePixelRatio || 1;
79 |
80 | if (element.element instanceof SVGSVGElement) {
81 | let svg = element.element;
82 | let rect = element.bounds;
83 | let image = new Image();
84 | image.width = rect.width * dpr;
85 | image.height = rect.height * dpr;
86 | image.onload = () => {
87 | ctx.drawImage(image, rect.left * dpr, rect.top * dpr, image.width, image.height);
88 | svg.removeAttribute('width');
89 | svg.removeAttribute('height');
90 | resolve();
91 | };
92 |
93 | // Need to set width, otherwise firefox doesn't work: https://stackoverflow.com/questions/28690643/firefox-error-rendering-an-svg-image-to-html5-canvas-with-drawimage
94 | svg.setAttribute('width', image.width);
95 | svg.setAttribute('height', image.height);
96 | image.src = 'data:image/svg+xml;base64,' + btoa(new XMLSerializer().serializeToString(svg));
97 | } else {
98 | ctx.save();
99 |
100 | ctx.font = dpr * element.fontSize + 'px ' + element.fontFamily;
101 | ctx.fillStyle = element.color;
102 | ctx.textAlign = 'end'
103 | ctx.fillText(
104 | element.text,
105 | (element.bounds.right - element.paddingRight) * dpr,
106 | (element.bounds.bottom - element.paddingBottom) * dpr
107 | )
108 | ctx.restore();
109 | resolve();
110 | }
111 | });
112 | }
113 |
114 | function collectPrintable() {
115 | return Array.from(document.querySelectorAll('.printable')).map(element => {
116 | let computedStyle = window.getComputedStyle(element);
117 | let bounds = element.getBoundingClientRect();
118 | let fontSize = Number.parseInt(computedStyle.fontSize, 10);
119 | let paddingRight = Number.parseInt(computedStyle.paddingRight, 10);
120 | // TODO: I don't know why I need to multiply by 2, it's just
121 | // not aligned right if I don't multiply. Need to figure out this.
122 | let paddingBottom = Number.parseInt(computedStyle.paddingBottom, 10) * 2;
123 |
124 | return {
125 | text: element.innerText,
126 | bounds,
127 | fontSize,
128 | paddingBottom,
129 | paddingRight,
130 | color: computedStyle.color,
131 | fontFamily: computedStyle.fontFamily,
132 | fill: computedStyle.color,
133 | element
134 | }
135 | });
136 | }
137 |
138 | function revokeLater(url) {
139 | // In iOS immediately revoked URLs cause "WebKitBlobResource error 1." error
140 | // Setting a timeout to revoke URL in the future fixes the error:
141 | setTimeout(() => {
142 | window.URL.revokeObjectURL(url);
143 | }, 45000);
144 | }
145 |
146 | // function toProtobuf() {
147 | // if (!lastGrid) return;
148 |
149 | // let arrayBuffer = protobufExport(lastGrid);
150 | // let blob = new Blob([arrayBuffer.buffer], {type: "application/octet-stream"});
151 | // let url = window.URL.createObjectURL(blob);
152 | // let a = document.createElement("a");
153 | // a.href = url;
154 | // a.download = lastGrid.id + '.pbf';
155 | // a.click();
156 | // revokeLater(url);
157 | // }
158 |
--------------------------------------------------------------------------------
/src/components/vue3-color/common/Hue.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
148 |
149 |
185 |
--------------------------------------------------------------------------------
/src/lib/createScene.js:
--------------------------------------------------------------------------------
1 | import bus from './bus';
2 | import GridLayer from './GridLayer';
3 | import Query from './Query';
4 | import LoadOptions from './LoadOptions.js';
5 | import config from '../config.js';
6 | import tinycolor from 'tinycolor2';
7 | import eventify from 'ngraph.events';
8 | import {toSVG, toPNG} from './saveFile.js';
9 | import * as wgl from 'w-gl';
10 |
11 | /**
12 | * This file is responsible for rendering of the grid. It uses my silly 2d webgl
13 | * renderer which is not very well documented, neither popular, yet it is very
14 | * fast.
15 | */
16 |
17 | export default function createScene(canvas) {
18 | let scene = wgl.createScene(canvas);
19 | let lastLineColor = config.getDefaultLineColor();
20 | scene.on('transform', triggerTransform);
21 | scene.on('append-child', triggerAdd);
22 | scene.on('remove-child', triggerRemove);
23 |
24 | scene.setClearColor(0xf7/0xff, 0xf2/0xff, 0xe8/0xff, 1.0);
25 | let camera = scene.getCameraController();
26 | if (camera.setMoveSpeed) {
27 | camera.setMoveSpeed(200);
28 | camera.setRotationSpeed(Math.PI/500);
29 | }
30 |
31 | let gl = scene.getGL();
32 | gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
33 |
34 | let slowDownZoom = false;
35 | let layers = [];
36 | let backgroundColor = config.getBackgroundColor();
37 |
38 | listenToEvents();
39 |
40 | let sceneAPI = {
41 | /**
42 | * Requests the scene to perform immediate re-render
43 | */
44 | render() {
45 | scene.renderFrame(true);
46 | },
47 |
48 | /**
49 | * Removes all layers in the scene
50 | */
51 | clear() {
52 | layers.forEach(layer => layer.destroy());
53 | layers = [];
54 | scene.clear();
55 | },
56 |
57 | /**
58 | * Returns all layers in the scene.
59 | */
60 | queryLayerAll,
61 |
62 | /**
63 | * Same as `queryLayerAll(filter)` but returns the first found
64 | * match. If no matches found - returns undefined.
65 | */
66 | queryLayer,
67 |
68 | getRenderer() {
69 | return scene;
70 | },
71 |
72 | getWGL() {
73 | // Let the plugins use the same version of wgl library
74 | return wgl;
75 | },
76 |
77 | version() {
78 | return '0.0.2'; // here be dragons
79 | },
80 |
81 | /**
82 | * Destroys the scene, cleans up all resources.
83 | */
84 | dispose() {
85 | scene.clear();
86 | scene.dispose();
87 | sceneAPI.fire('dispose', sceneAPI);
88 | unsubscribeFromEvents();
89 | },
90 |
91 | /**
92 | * Uniformly sets color to all loaded grid layer.
93 | */
94 | set lineColor(color) {
95 | layers.forEach(layer => {
96 | layer.color = color;
97 | });
98 | lastLineColor = tinycolor(color);
99 | bus.fire('line-color', lastLineColor);
100 | sceneAPI.fire('line-color', lastLineColor);
101 | },
102 |
103 | get lineColor() {
104 | let firstLayer = queryLayer();
105 | return (firstLayer && firstLayer.color) || lastLineColor;
106 | },
107 |
108 | /**
109 | * Sets the background color of the scene
110 | */
111 | set background(rawColor) {
112 | backgroundColor = tinycolor(rawColor);
113 | let c = backgroundColor.toRgb();
114 | scene.setClearColor(c.r/0xff, c.g/0xff, c.b/0xff, c.a);
115 | scene.renderFrame();
116 | bus.fire('background-color', backgroundColor);
117 | sceneAPI.fire('background-color', backgroundColor);
118 | },
119 |
120 | get background() {
121 | return backgroundColor;
122 | },
123 |
124 | add,
125 |
126 | /**
127 | * Executes an OverPass query and loads results into scene.
128 | */
129 | load,
130 |
131 | saveToPNG,
132 |
133 | saveToSVG
134 | };
135 |
136 | return eventify(sceneAPI); // Public bit is over. Below are just implementation details.
137 |
138 | /**
139 | * Experimental API. Can be changed/removed at any point.
140 | */
141 | function load(queryFilter, rawOptions) {
142 | let options = LoadOptions.parse(sceneAPI, queryFilter, rawOptions);
143 |
144 | let layer = new GridLayer();
145 | layer.id = options.place;
146 |
147 | // TODO: Cancellation logic?
148 | Query.runFromOptions(options).then(grid => {
149 | grid.setProjector(options.projector);
150 | layer.setGrid(grid);
151 | }).catch(e => {
152 | console.error(`Could not execute:
153 | ${queryFilter}
154 | The error was:`);
155 | console.error(e);
156 | layer.destroy();
157 | });
158 |
159 | add(layer);
160 | return layer;
161 | }
162 |
163 | function queryLayerAll(filter) {
164 | if (!filter) return layers;
165 |
166 | return layers.filter(layer => {
167 | return layer.id === filter;
168 | });
169 | }
170 |
171 | function queryLayer(filter) {
172 | let result = queryLayerAll(filter);
173 | if (result) return result[0];
174 | }
175 |
176 | function add(gridLayer) {
177 | if (layers.indexOf(gridLayer) > -1) return; // O(n).
178 |
179 | gridLayer.bindToScene(scene);
180 | layers.push(gridLayer);
181 |
182 | if (layers.length === 1) {
183 | // TODO: Should I do this for other layers?
184 | let viewBox = gridLayer.getViewBox();
185 | if (viewBox) {
186 | scene.setViewBox(viewBox);
187 | }
188 | }
189 | }
190 |
191 | function saveToPNG(name) {
192 | return toPNG(sceneAPI, {name});
193 | }
194 |
195 | function saveToSVG(name, options) {
196 | return toSVG(sceneAPI, Object.assign({}, {name}, options));
197 | }
198 |
199 | function triggerTransform(t) {
200 | bus.fire('scene-transform');
201 | }
202 |
203 | function triggerAdd(e) {
204 | sceneAPI.fire('layer-added', e);
205 | }
206 |
207 | function triggerRemove(e) {
208 | sceneAPI.fire('layer-removed', e);
209 | }
210 |
211 | function listenToEvents() {
212 | document.addEventListener('keydown', onKeyDown, true);
213 | document.addEventListener('keyup', onKeyUp, true);
214 | }
215 |
216 | function unsubscribeFromEvents() {
217 | document.removeEventListener('keydown', onKeyDown, true);
218 | document.removeEventListener('keyup', onKeyUp, true);
219 | }
220 |
221 | function onKeyDown(e) {
222 | if (e.shiftKey) {
223 | slowDownZoom = true;
224 | if (camera.setSpeed) camera.setSpeed(0.1);
225 | }
226 | }
227 |
228 | function onKeyUp(e) {
229 | if (!e.shiftKey && slowDownZoom) {
230 | if (camera.setSpeed) camera.setSpeed(1);
231 | slowDownZoom = false;
232 | }
233 | }
234 | }
--------------------------------------------------------------------------------
/src/createOverlayManager.js:
--------------------------------------------------------------------------------
1 | export default function createOverlayManager() {
2 | let overlay;
3 | let downEvent = {
4 | clickedElement: null,
5 | x: 0,
6 | y: 0,
7 | time: Date.now(),
8 | left: 0,
9 | right: 0
10 | };
11 |
12 | document.addEventListener('mousedown', handleMouseDown);
13 | document.addEventListener('mouseup', handleMouseUp);
14 | document.addEventListener('touchstart', handleTouchStart, {passive: false, capture: true});
15 | document.addEventListener('touchend', handleTouchEnd, true);
16 | document.addEventListener('touchcancel', handleTouchEnd, true);
17 |
18 | return {
19 | track,
20 | dispose,
21 | clear
22 | }
23 |
24 | function clear() {
25 | const activeOverlays = document.querySelectorAll('.overlay-active');
26 | for (let i = 0; i < activeOverlays.length; ++i) {
27 | deselect(activeOverlays[i]);
28 | }
29 | }
30 |
31 | function handleMouseDown(e) {
32 | onPointerDown(e.clientX, e.clientY, e);
33 | }
34 |
35 | function handleMouseMove(e) {
36 | onPointerMove(e.clientX, e.clientY);
37 | }
38 |
39 | function handleMouseUp(e) {
40 | onPointerUp(e.clientX, e.clientY)
41 | }
42 |
43 | function handleTouchStart(e) {
44 | if (e.touches.length > 1) return;
45 |
46 | let touch = e.touches[0];
47 | onPointerDown(touch.clientX, touch.clientY, e);
48 | }
49 |
50 | function handleTouchEnd(e) {
51 | if (e.changedTouches.length > 1) return;
52 | let touch = e.changedTouches[0];
53 | let gotSomethingSelected = onPointerUp(touch.clientX, touch.clientY);
54 | if (gotSomethingSelected) {
55 | e.preventDefault();
56 | e.stopPropagation();
57 | }
58 | }
59 |
60 | function handleTouchMove(e) {
61 | if (e.touches.length > 1) return;
62 | let touch = e.touches[0];
63 | onPointerMove(touch.clientX, touch.clientY);
64 | e.preventDefault();
65 | e.stopPropagation();
66 | }
67 |
68 | function onPointerDown(x, y, e) {
69 | let foundElement = findTrackedElementUnderCursor(x, y)
70 | let activeOverlays = document.querySelectorAll('.overlay-active');
71 |
72 | for (let i = 0; i < activeOverlays.length; ++i) {
73 | let el = activeOverlays[i];
74 | if (el !== foundElement) deselect(el);
75 | }
76 | if (activeOverlays.length === 1) downEvent.clickedElement = activeOverlays[0];
77 |
78 | let secondTimeClicking = foundElement && foundElement === downEvent.clickedElement;
79 | if (secondTimeClicking) {
80 | if (!downEvent.clickedElement.contains(e.target)) {
81 | foundElement = null;
82 | secondTimeClicking = false;
83 | }
84 | }
85 | let shouldAddOverlay = secondTimeClicking && !foundElement.classList.contains('exclusive');
86 | if (shouldAddOverlay) {
87 | // prepare for move!
88 | addDragOverlay();
89 | e.preventDefault();
90 | e.stopPropagation();
91 | } else {
92 | downEvent.clickedElement = foundElement;
93 | }
94 |
95 | downEvent.x = x;
96 | downEvent.y = y;
97 | downEvent.time = Date.now();
98 | if (foundElement) {
99 | let bBox = foundElement.getBoundingClientRect();
100 | downEvent.dx = bBox.right - downEvent.x;
101 | downEvent.dy = bBox.bottom - downEvent.y;
102 | } else {
103 | clear();
104 | }
105 | }
106 |
107 | function onPointerUp(x, y) {
108 | if (!downEvent.clickedElement) return;
109 | removeOverlay();
110 |
111 | if (isSingleClick(x, y)) {
112 | // forward focus, we didn't move the element
113 | select(downEvent.clickedElement, x, y);
114 | return true;
115 | } else {
116 | downEvent.clickedElement = null;
117 | }
118 | }
119 |
120 | function onPointerMove(x, y) {
121 | if (!downEvent.clickedElement) return;
122 |
123 | let style = downEvent.clickedElement.style;
124 | style.right = 100*(window.innerWidth - x - downEvent.dx)/window.innerWidth + '%';
125 | style.bottom = 100*(window.innerHeight - y - downEvent.dy)/window.innerHeight + '%';
126 | }
127 |
128 | function addDragOverlay() {
129 | removeOverlay();
130 |
131 | overlay = document.createElement('div');
132 | overlay.classList.add('drag-overlay');
133 | document.body.appendChild(overlay);
134 |
135 | document.addEventListener('mousemove', handleMouseMove, true);
136 | document.addEventListener('touchmove', handleTouchMove, {passive: false, capture: true});
137 | }
138 |
139 | function removeOverlay() {
140 | if (overlay) {
141 | document.body.removeChild(overlay);
142 | overlay = null;
143 | }
144 |
145 | document.removeEventListener('mousemove', handleMouseMove, true);
146 | document.removeEventListener('touchmove', handleTouchMove, {passive: false, capture: true});
147 | }
148 |
149 | function isSingleClick(x, y) {
150 | let timeDiff = Date.now() - downEvent.time;
151 | if (timeDiff > 300) return false; // took too long for a single click;
152 |
153 | // should release roughly in the same place where pressed:
154 | return Math.hypot(x - downEvent.x, y - downEvent.y) < 40;
155 | }
156 |
157 | function findTrackedElementUnderCursor(x, y) {
158 | let autoTrack = document.querySelectorAll('.can-drag');
159 | for (let i = 0; i < autoTrack.length; ++i) {
160 | let el = autoTrack[i];
161 | let rect = getRectangle(el);
162 | if (intersects(x, y, rect)) return el;
163 | }
164 | }
165 |
166 | function deselect(el) {
167 | el.style.pointerEvents = 'none';
168 | el.classList.remove('overlay-active');
169 | el.classList.remove('exclusive')
170 | }
171 |
172 | function select(el, x, y) {
173 | if (!el) return;
174 |
175 | el.style.pointerEvents = '';
176 |
177 | if (el.classList.contains('overlay-active')) {
178 | // When they click second time, we want to forward focus to the element
179 | // (if they support focus forwarding)
180 | if (el.receiveFocus) el.receiveFocus();
181 | // and make the element exclusive owner of the mouse/pointer
182 | // (so that native interaction can occur and we don't interfere with dragging)
183 | el.classList.add('exclusive')
184 | } else {
185 | // When they click first time, we enter to "drag around" mode
186 | el.classList.add('overlay-active');
187 | if (el.classList.contains('can-resize')) {
188 | // el.resizer = renderResizeHandlers(el);
189 | }
190 | }
191 | }
192 |
193 |
194 | function intersects(x, y, rect) {
195 | return !(x < rect.left || x > rect.right || y < rect.top || y > rect.bottom);
196 | }
197 |
198 | function getRectangle(x) {
199 | return x.getBoundingClientRect();
200 | }
201 |
202 | function track(domElement, options) {
203 | domElement.style.pointerEvents = 'none'
204 | domElement.classList.add('can-drag');
205 |
206 | if (options) {
207 | if (options.receiveFocus) domElement.receiveFocus = options.receiveFocus;
208 | }
209 | }
210 |
211 | function dispose() {
212 | document.removeEventListener('mousedown', handleMouseDown);
213 | document.removeEventListener('mouseup', handleMouseUp);
214 | document.removeEventListener('touchstart', handleTouchStart);
215 | document.removeEventListener('touchend', handleTouchEnd);
216 | document.removeEventListener('touchcancel', handleTouchEnd);
217 | downEvent.clickedElement = undefined;
218 | removeOverlay();
219 | }
220 | }
221 |
222 | function renderResizeHandlers(el) {
223 | el.getBoundingClientRect(el)
224 | }
--------------------------------------------------------------------------------
/src/components/vue3-color/Sketch.vue:
--------------------------------------------------------------------------------
1 |
2 |