From aa87c39a2877cfb6ef91f5a7f49db9a750641e94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cchrisshank=E2=80=9D?= Date: Sat, 30 Nov 2024 21:14:53 -0800 Subject: [PATCH] working proximity --- demo/proximity-maps.html | 56 ++++++++++++++++++---- src/folk-proximity.ts | 100 ++++++++++++++++++++++++++++++++++----- src/folk-set.ts | 3 ++ 3 files changed, 138 insertions(+), 21 deletions(-) diff --git a/demo/proximity-maps.html b/demo/proximity-maps.html index a8df19f..4c48921 100644 --- a/demo/proximity-maps.html +++ b/demo/proximity-maps.html @@ -59,7 +59,7 @@ - + @@ -67,11 +67,11 @@ - + - + @@ -81,12 +81,6 @@ import { FolkWeather } from '../src/folk-weather.ts'; import { FolkCluster, FolkProximity } from '../src/folk-proximity.ts'; - FolkGeometry.define(); - FolkMap.define(); - FolkCluster.define(); - FolkProximity.define(); - FolkWeather.define(); - class GeoWiki extends HTMLElement { static tagName = 'geo-wiki'; @@ -150,6 +144,50 @@ } } + FolkCluster.registerElement({ + constructor: FolkMap, + events: { + recenter: (e) => ({ + lat: e.target.coordinates.lat, + lng: e.target.coordinates.lng, + }), + }, + onAdd: (element) => ({ + lat: element.coordinates.lat, + lng: element.coordinates.lng, + }), + }); + + FolkCluster.registerElement({ + constructor: FolkWeather, + onAdd: console.log, + onUpdate(element, data, changes) { + const lat = data.get('lat'); + const lng = data.get('lng'); + + if (lat && lng) { + element.coordinates = [lat, lng]; + } + }, + }); + + FolkCluster.registerElement({ + constructor: GeoWiki, + onUpdate(element, data, changes) { + const lat = data.get('lat'); + const lng = data.get('lng'); + + if (lat && lng) { + element.coordinates = [lat, lng]; + } + }, + }); + + FolkGeometry.define(); + FolkMap.define(); + FolkCluster.define(); + FolkProximity.define(); + FolkWeather.define(); GeoWiki.define(); diff --git a/src/folk-proximity.ts b/src/folk-proximity.ts index 6ddb767..7170686 100644 --- a/src/folk-proximity.ts +++ b/src/folk-proximity.ts @@ -2,6 +2,18 @@ import { collisionDetection } from './collision'; import { FolkHull } from './folk-hull'; import { FolkGeometry } from './canvas/fc-geometry.ts'; +interface ElementConstructor { + new (): E; +} + +export interface ElementConfig { + constructor: ElementConstructor; + events?: Record Record>; + onAdd?(element: E): void | Record; + onUpdate?(element: E, data: ReadonlyMap, updatedValues: Set): void; + onRemove?(element: E): void; +} + // TODO don't hard code this const PROXIMITY = 50; @@ -15,6 +27,12 @@ declare global { export class FolkCluster extends FolkHull { static tagName = 'folk-cluster'; + static #config = new Map(); + + static registerElement(config: ElementConfig) { + this.#config.set(config.constructor, config); + } + #data = new Map(); isElementInCluster(element: FolkGeometry) { @@ -28,18 +46,85 @@ export class FolkCluster extends FolkHull { return false; } - addElements(...elements) { + addElements(...elements: FolkGeometry[]) { this.sources = this.sourceElements .concat(elements) .map((el) => `#${el.id}`) .join(', '); + + let data = {}; + + for (const geometry of elements) { + const element = geometry.firstElementChild; + + if (element === null) continue; + + const config = this.#getConfig(element); + + if (config) { + for (const event of Object.keys(config.events || {})) { + element.addEventListener(event, this.#handleEvent); + } + + const newData = config.onAdd?.(element); + data = Object.assign(data, newData); + } + } + + this.#handleUpdate(data); } - removeElement(element) { + #handleEvent = (event: Event) => { + const config = this.#getConfig(event.currentTarget as Element); + + if (config) { + const data = config.events?.[event.type]?.(event); + if (data === undefined) return; + this.#handleUpdate(data); + } + }; + + #handleUpdate(data: Record) { + const keys = new Set(Object.keys(data)); + for (const key of keys) { + this.#data.set(key, data[key]); + } + + for (const geometry of this.sourceElements) { + const element = geometry.firstElementChild; + + if (element === null) continue; + + const config = this.#getConfig(element); + + config?.onUpdate?.(element, this.#data, keys); + } + } + + removeElement(geometry: FolkGeometry) { this.sources = this.sourceElements - .filter((el) => el !== element) + .filter((el) => el !== geometry) .map((el) => `#${el.id}`) .join(', '); + + const element = geometry.firstElementChild; + + if (element === null) return; + + const config = this.#getConfig(element); + + if (config) { + for (const event of Object.keys(config.events || {})) { + element.removeEventListener(event, this.#handleEvent); + } + + config.onRemove?.(element); + } + } + + #getConfig(element: Element) { + const config = (this.constructor as typeof FolkCluster).#config; + return config.get(element.constructor as ElementConstructor); } } @@ -58,15 +143,6 @@ export class FolkProximity extends HTMLElement { this.addEventListener('move', this.#handleProximity); this.addEventListener('resize', this.#handleProximity); - // document.addEventListener('recenter', (e) => { - // proximityMap.get(e.target.parentElement)?.forEach((el) => { - // const content = el.firstElementChild; - // if (content instanceof GeoWiki) { - // const { lat, lng } = e.target.coordinates; - // content.coordinates = [lat, lng]; - // } - // }); - // }); } #handleProximity = (e) => { diff --git a/src/folk-set.ts b/src/folk-set.ts index 389312a..e18607c 100644 --- a/src/folk-set.ts +++ b/src/folk-set.ts @@ -8,6 +8,8 @@ declare global { } } +const defaultRect = DOMRectReadOnly.fromRect(); + export class FolkSet extends HTMLElement { static tagName = 'folk-set'; @@ -60,6 +62,7 @@ export class FolkSet extends HTMLElement { this.unobserveSources(elementsToUnobserve); for (const el of elementsToObserve) { + this.#sourcesMap.set(el, defaultRect); clientRectObserver.observe(el, this.#sourcesCallback); }