From a6ebdf914c53eec31717593863b00815673c60f5 Mon Sep 17 00:00:00 2001 From: Bryan Housel Date: Wed, 14 Aug 2024 16:49:44 -0400 Subject: [PATCH] Stop hardcoding the pseudoclasses, cleanup For recap, these classes are how we adjust the style of features now that we can't use CSS classes to do it. They are strings that are owned by each layer. We created a bunch of them like 'drawing', 'highlight', 'selected', etc. and then at render time we call `syncFeatureClasses` to update each feature with what kind of classes it should have. Working on the streetlevel imagery I realized that I want more pseudo classes to highlight image viewcones, and the list is getting long, so it's time to stop hardcoding these special properties and handle them dynamically. This also gives us an oppotrunity to clean up the ones that were confusing, redundand, or not really used. So here's what has changed: - Removed all the AbstractFeature getter/setters and now have `setClass/unsetClass/hasClass` - AbstractFeature now has a private `_classes` Set - setClass/unsetClass now take care of dirtying the style and label - `feature.highlighted = true` -> `feature.setClass('highlight)` - `feature.drawing = false` -> `feature.unsetClass('drawing')` - etc.. - Perfer shorter names for the classes, e.g. "select" instead of "selected" - Some renames and adjustment of arglists in the PixiScene and AbstractLayer - `classData(dataID, classID)` -> `setClass(classID, dataID)` - `unclassData(dataID, classID)` -> `unsetClass(classID, dataID) - etc.. the class name comes first, like `setClass('hover', 'w123')` - Updated lots of documentation - 'active' was used for interactivity, but it didn't really work, it's removed now - this only really affects `DragBehavior`, when dragging things - We also had `feature.allowInteraction` it does the same thing, we just use that now This paves the way for me to do the thing I want to do with the photo viewcones without adding more hardcoded classes. --- modules/behaviors/DragBehavior.js | 13 +- modules/core/PhotoSystem.js | 6 +- modules/modes/DragNodeMode.js | 14 +- modules/modes/DragNoteMode.js | 7 - modules/modes/DrawAreaMode.js | 18 +- modules/modes/DrawLineMode.js | 18 +- modules/pixi/AbstractFeature.js | 184 ++++++------------- modules/pixi/AbstractLayer.js | 211 ++++++++++++---------- modules/pixi/PixiFeatureLine.js | 22 +-- modules/pixi/PixiFeaturePoint.js | 31 ++-- modules/pixi/PixiFeaturePolygon.js | 20 +- modules/pixi/PixiGeometry.js | 10 +- modules/pixi/PixiLayerKartaPhotos.js | 2 +- modules/pixi/PixiLayerMapillaryPhotos.js | 2 +- modules/pixi/PixiLayerOsm.js | 6 +- modules/pixi/PixiLayerStreetsidePhotos.js | 2 +- modules/pixi/PixiRenderer.js | 12 +- modules/pixi/PixiScene.js | 24 +-- modules/util/util.js | 9 +- 19 files changed, 278 insertions(+), 333 deletions(-) diff --git a/modules/behaviors/DragBehavior.js b/modules/behaviors/DragBehavior.js index 250495667a..1383495b01 100644 --- a/modules/behaviors/DragBehavior.js +++ b/modules/behaviors/DragBehavior.js @@ -80,8 +80,7 @@ export class DragBehavior extends AbstractBehavior { const target = this.dragTarget; if (eventData && target) { - target.layer.unclassData(target.dataID, 'active'); - target.feature.active = false; + target.feature.allowInteraction = true; this.dragTarget = null; this.emit('cancel', eventData); } @@ -174,8 +173,7 @@ export class DragBehavior extends AbstractBehavior { // This lets us catch events for what other objects it passes over as the user drags it. const target = Object.assign({}, down.target); // shallow copy this.dragTarget = target; - target.layer.classData(target.dataID, 'active'); - target.feature.active = true; + target.feature.allowInteraction = false; // What are we dragging? const data = target.data; @@ -244,14 +242,12 @@ export class DragBehavior extends AbstractBehavior { } } - this.lastDown = null; this.lastMove = null; const target = this.dragTarget; if (target) { - target.layer.unclassData(target.dataID, 'active'); - target.feature.active = false; + target.feature.allowInteraction = true; this.dragTarget = null; this.emit('end', up); } @@ -273,8 +269,7 @@ export class DragBehavior extends AbstractBehavior { const target = this.dragTarget; if (target) { - target.layer.unclassData(target.dataID, 'active'); - target.feature.active = false; + target.feature.allowInteraction = true; this.dragTarget = null; this.emit('cancel', cancel); } diff --git a/modules/core/PhotoSystem.js b/modules/core/PhotoSystem.js index 5fc7ab0c42..a46599d430 100644 --- a/modules/core/PhotoSystem.js +++ b/modules/core/PhotoSystem.js @@ -313,7 +313,7 @@ export class PhotoSystem extends AbstractSystem { // Clear out any existing display classes for (const oldLayerID of this._LAYERIDS) { const oldLayer = scene.layers.get(oldLayerID); - oldLayer?.clearClass('selected'); + oldLayer?.clearClass('select'); oldLayer?.clearClass('selectphoto'); } @@ -324,8 +324,8 @@ export class PhotoSystem extends AbstractSystem { // If we're selecting a photo then make sure its layer is enabled too. scene.enableLayers(layerID); - scene.classData(layerID, photoID, 'selected'); - scene.classData(layerID, photoID, 'selectphoto'); + scene.setClass('select', layerID, photoID); + scene.setClass('selectphoto', layerID, photoID); // Try to show the viewer with the image selected.. service.startAsync() diff --git a/modules/modes/DragNodeMode.js b/modules/modes/DragNodeMode.js index b8ea712980..57adbb4e7e 100644 --- a/modules/modes/DragNodeMode.js +++ b/modules/modes/DragNodeMode.js @@ -54,6 +54,7 @@ export class DragNodeMode extends AbstractMode { const editor = context.systems.editor; const filters = context.systems.filters; const l10n = context.systems.l10n; + const map = context.systems.map; const ui = context.systems.ui; this._reselectIDs = options.reselectIDs ?? []; @@ -99,12 +100,12 @@ export class DragNodeMode extends AbstractMode { this.dragNode = entity; this._startLoc = entity.loc; + this._selectedData.set(entity.id, entity); - // Set the 'drawing' class so that the dragNode and any parent ways won't emit events - const scene = context.scene(); - scene.classData('osm', this.dragNode.id, 'drawing'); + const layer = map.scene.layers.get('osm'); + layer.setClass('drawing', this.dragNode.id); for (const parent of graph.parentWays(this.dragNode)) { - scene.classData('osm', parent.id, 'drawing'); + layer.setClass('drawing', parent.id); } // `_clickLoc` is used later to calculate a drag offset, @@ -143,8 +144,9 @@ export class DragNodeMode extends AbstractMode { this._selectedData.clear(); const context = this.context; - - context.scene().clearClass('drawing'); + const map = context.systems.map; + const layer = map.scene.layers.get('osm'); + layer.clearClass('drawing'); context.behaviors.drag .off('move', this._move) diff --git a/modules/modes/DragNoteMode.js b/modules/modes/DragNoteMode.js index e5f4215b7d..d7140d565d 100644 --- a/modules/modes/DragNoteMode.js +++ b/modules/modes/DragNoteMode.js @@ -51,10 +51,6 @@ export class DragNoteMode extends AbstractMode { this._startLoc = note.loc; this._selectedData.set(this.dragNote.id, this.dragNote); - // Set the 'drawing' class so that the dragNote won't emit events - const scene = context.scene(); - scene.classData('notes', this.dragNote.id, 'drawing'); - // `_clickLoc` is used later to calculate a drag offset, // to correct for where "on the pin" the user grabbed the target. const point = context.behaviors.drag.lastDown.coord.map; @@ -88,9 +84,6 @@ export class DragNoteMode extends AbstractMode { this._selectedData.clear(); const context = this.context; - - context.scene().clearClass('drawing'); - context.behaviors.drag .off('move', this._move) .off('end', this._end) diff --git a/modules/modes/DrawAreaMode.js b/modules/modes/DrawAreaMode.js index 4bd6cf5e78..0e374db808 100644 --- a/modules/modes/DrawAreaMode.js +++ b/modules/modes/DrawAreaMode.js @@ -129,9 +129,10 @@ export class DrawAreaMode extends AbstractMode { const context = this.context; const editor = context.systems.editor; - const scene = context.systems.map.scene; + const map = context.systems.map; + const layer = map.scene.layers.get('osm'); + const eventManager = map.renderer.events; - const eventManager = context.systems.map.renderer.events; eventManager.setCursor('grab'); context.behaviors.hover @@ -177,7 +178,8 @@ export class DrawAreaMode extends AbstractMode { this._lastPoint = null; this._selectedData.clear(); - scene.clearClass('drawing'); + + layer.clearClass('drawing'); window.setTimeout(() => { context.behaviors.mapInteraction.doubleClickEnabled = true; @@ -196,9 +198,10 @@ export class DrawAreaMode extends AbstractMode { _refreshEntities() { const context = this.context; const editor = context.systems.editor; - const scene = context.systems.map.scene; + const map = context.systems.map; + const layer = map.scene.layers.get('osm'); - scene.clearClass('drawing'); + layer.clearClass('drawing'); this._selectedData.clear(); const graph = editor.staging.graph; @@ -209,20 +212,19 @@ export class DrawAreaMode extends AbstractMode { // Sanity check - Bail out if any of these are missing. if (!drawWay || !lastNode || !firstNode) { - // debugger; this._cancel(); return; } // `drawNode` may or may not exist, it will be recreated after the user moves the pointer. if (drawNode) { - scene.classData('osm', drawNode.id, 'drawing'); + layer.setClass('drawing', drawNode.id); // Nudging at the edge of the map is allowed after the drawNode exists. context.behaviors.mapNudge.allow(); } - scene.classData('osm', drawWay.id, 'drawing'); + layer.setClass('drawing', drawWay.id); this._selectedData.set(drawWay.id, drawWay); } diff --git a/modules/modes/DrawLineMode.js b/modules/modes/DrawLineMode.js index 4efca275c3..73c210dd3a 100644 --- a/modules/modes/DrawLineMode.js +++ b/modules/modes/DrawLineMode.js @@ -165,9 +165,10 @@ export class DrawLineMode extends AbstractMode { const context = this.context; const editor = context.systems.editor; - const scene = context.systems.map.scene; + const map = context.systems.map; + const layer = map.scene.layers.get('osm'); + const eventManager = map.renderer.events; - const eventManager = context.systems.map.renderer.events; eventManager.setCursor('grab'); context.behaviors.hover @@ -214,7 +215,8 @@ export class DrawLineMode extends AbstractMode { this._lastPoint = null; this._selectedData.clear(); - scene.clearClass('drawing'); + + layer.clearClass('drawing'); window.setTimeout(() => { context.behaviors.mapInteraction.doubleClickEnabled = true; @@ -233,9 +235,10 @@ export class DrawLineMode extends AbstractMode { _refreshEntities() { const context = this.context; const editor = context.systems.editor; - const scene = context.scene(); + const map = context.systems.map; + const layer = map.scene.layers.get('osm'); - scene.clearClass('drawing'); + layer.clearClass('drawing'); this._selectedData.clear(); const graph = editor.staging.graph; @@ -246,21 +249,20 @@ export class DrawLineMode extends AbstractMode { // Sanity check - Bail out if any of these are missing. if (!drawWay || !lastNode || !firstNode) { - // debugger; this._cancel(); return; } // `drawNode` may or may not exist, it will be recreated after the user moves the pointer. if (drawNode) { - scene.classData('osm', drawNode.id, 'drawing'); + layer.setClass('drawing', drawNode.id); // Nudging at the edge of the map is allowed after the drawNode exists. context.behaviors.mapNudge.allow(); } // todo - we do want to allow connecting a line to itself in some situations - scene.classData('osm', drawWay.id, 'drawing'); + layer.setClass('drawing', drawWay.id); this._selectedData.set(drawWay.id, drawWay); } diff --git a/modules/pixi/AbstractFeature.js b/modules/pixi/AbstractFeature.js index 0cea7226da..8544c3cf51 100644 --- a/modules/pixi/AbstractFeature.js +++ b/modules/pixi/AbstractFeature.js @@ -23,21 +23,13 @@ import { PixiGeometry } from './PixiGeometry.js'; * `lod` Level of detail for the Feature last time it was styled (0 = off, 1 = simplified, 2 = full) * `halo` A PIXI.DisplayObject() that contains the graphics for the Feature's halo (if it has one) * `sceneBounds` PIXI.Rectangle() where 0,0 is the origin of the scene - * - * PseudoClasses: - * `active` - * `drawing` - * `highlighted` - * `hovered` - * `selected` - * `selectphoto` */ export class AbstractFeature { /** * @constructor - * @param layer The Layer that owns this Feature - * @param featureID Unique string to use for the name of this Feature + * @param {Layer} layer - The Layer that owns this Feature + * @param {string} featureID - Unique string to use for the name of this Feature */ constructor(layer, featureID) { this.type = 'unknown'; @@ -73,12 +65,7 @@ export class AbstractFeature { this._data = null; // pseudoclasses, @see `AbstractLayer.syncFeatureClasses()` - this._active = false; - this._drawing = false; - this._highlighted = false; - this._hovered = false; - this._selected = false; - this._selectphoto = false; + this._classes = new Set(); // We will manage our own bounds for now because we can probably do this // faster than Pixi's built in bounds calculations. @@ -132,8 +119,8 @@ export class AbstractFeature { * Every Feature should have an `update()` function that redraws the Feature at the given viewport and zoom. * When the Feature is updated, its `dirty` flags should be set to `false`. * Override in a subclass with needed logic. It will be passed: - * @param viewport Pixi viewport to use for rendering - * @param zoom Effective zoom to use for rendering + * @param {Viewport} viewport - Pixi viewport to use for rendering + * @param {number} zoom - Effective zoom to use for rendering * @abstract */ update(viewport, zoom) { @@ -222,113 +209,17 @@ export class AbstractFeature { this._allowInteraction = val; if (this.container) { - this.container.eventMode = (this._allowInteraction && !this._active) ? 'static' : 'none'; + this.container.eventMode = this._allowInteraction ? 'static' : 'none'; } } - /** - * active - * @see `AbstractLayer.syncFeatureClasses()` - * @param `true` to apply the 'active' pseudoclass - */ - get active() { - return this._active; - } - set active(val) { - if (val === this._active) return; // no change - this._active = val; - - if (this.container) { - this.container.eventMode = (this._allowInteraction && !this._active) ? 'static' : 'none'; - } - } - - - /** - * drawing - * @see `AbstractLayer.syncFeatureClasses()` - * @param val `true` to apply the 'drawing' pseudoclass - */ - get drawing() { - return this._drawing; - } - set drawing(val) { - if (val === this._drawing) return; // no change - this._drawing = val; - this._styleDirty = true; - } - - - /** - * highlighted - * @see `AbstractLayer.syncFeatureClasses()` - * @param val `true` to apply the 'highlighted' pseudoclass - */ - get highlighted() { - return this._highlighted; - } - set highlighted(val) { - if (val === this._highlighted) return; // no change - this._highlighted = val; - this._styleDirty = true; - this._labelDirty = true; - } - - - /** - * hovered - * @see `AbstractLayer.syncFeatureClasses()` - * @param val `true` to apply the 'hovered' pseudoclass - */ - get hovered() { - return this._hovered; - } - set hovered(val) { - if (val === this._hovered) return; // no change - this._hovered = val; - this._styleDirty = true; - } - - - /** - * selected - * @see `AbstractLayer.syncFeatureClasses()` - * @param val `true` to apply the 'selected' pseudoclass - */ - get selected() { - return this._selected; - } - set selected(val) { - if (val === this._selected) return; // no change - this._selected = val; - this._styleDirty = true; - this._labelDirty = true; - } - - - /** - * selectphoto - * @see `AbstractLayer.syncFeatureClasses()` - * @param val `true` to apply the 'selectphoto' pseudoclass - */ - get selectphoto() { - return this._selectphoto; - } - set selectphoto(val) { - if (val === this._selectphoto) return; // no change - this._selectphoto = val; - this._styleDirty = true; - this._labelDirty = true; - } - - /** * style - * @param obj Style `Object` (contents depends on the Feature type) + * @param {Object} obj - Style `Object` (contents depends on the Feature type) * - * 'point' - see AbstractFeaturePoint.js - * 'line'/'polygon' - see styles.js + * 'point' - @see AbstractFeaturePoint.js + * 'line'/'polygon' - @see styles.js */ get style() { return this._style; @@ -341,7 +232,7 @@ export class AbstractFeature { /** * label - * @param str String containing the label to use + * @param {string} str - the label to use */ get label() { return this._label; @@ -374,11 +265,56 @@ export class AbstractFeature { } + /** + * setClass + * Sets a pseudoclass. + * Pseudoclasses are special values that can affecct the styling of a feature. + * (They do the same thing that CSS classes do). + * When changing the value of the class we'll also dirty the feature so that it gets redrawn on the next pass. + * @param {string} classID - the pseudoclass to set + */ + setClass(classID) { + const hasClass = this._classes.has(classID); + if (hasClass) return; // nothing to do + + this._classes.add(classID); + this._styleDirty = true; + this._labelDirty = true; + } + + + /** + * unsetClass + * Unsets a pseudoclass. + * Pseudoclasses are special values that can affecct the styling of a feature. + * (They do the same thing that CSS classes do). + * When changing the value of the class we'll also dirty the feature so that it gets redrawn on the next pass. + * @param {string} classID - the pseudoclass to remove + */ + unsetClass(classID) { + const hasClass = this._classes.has(classID); + if (!hasClass) return; // nothing to do + + this._classes.delete(classID); + this._styleDirty = true; + this._labelDirty = true; + } + + + /** + * hasClass + * @param {string} classID - the class to check + * @return {boolean} `true` if the feature has this class, `false` if not + */ + hasClass(classID) { + return this._classes.has(classID); + } + /** * setData * This binds the data element to the feature, also lets the layer know about it. - * @param dataID `String` identifer for this data element (e.g. 'n123') - * @param data `Object` data to bind to the feature (e.g. an OSM Node) + * @param {string} dataID - Identifer for this data element (e.g. 'n123') + * @param {*} data - data to bind to the feature (e.g. an OSM Node) */ setData(dataID, data) { this._dataID = dataID; @@ -390,8 +326,8 @@ export class AbstractFeature { /** * addChildData * Adds a mapping from parent data to child data. - * @param parentID `String` dataID of the parent (e.g. 'r123') - * @param childID `String` dataID of the child (e.g. 'w123') + * @param {string} parentID - dataID of the parent (e.g. 'r123') + * @param {string} childID - dataID of the child (e.g. 'w123') */ addChildData(parentID, childID) { this.layer.addChildData(parentID, childID); @@ -401,7 +337,7 @@ export class AbstractFeature { /** * clearChildData * Removes all child dataIDs for the given parent dataID - * @param parentID `String` dataID of the parent (e.g. 'r123') + * @param {string} parentID - dataID of the parent (e.g. 'r123') */ clearChildData(parentID) { this.layer.clearChildData(parentID); diff --git a/modules/pixi/AbstractLayer.js b/modules/pixi/AbstractLayer.js index 3d0cf61a4a..38cd129c13 100644 --- a/modules/pixi/AbstractLayer.js +++ b/modules/pixi/AbstractLayer.js @@ -11,25 +11,26 @@ function asSet(vals) { * It creates a container to hold the Layer data. * * Notes on identifiers: - * - `layerID` - A unique identifier for the layer, for example 'osm' - * - `featureID` - A unique identifier for the feature, for example 'osm-w-123-fill' - * - `dataID` - A feature may have data bound to it, for example OSM identifier like 'w-123' - * - `classID` - A class identifier like 'hovered' or 'selected' + * All identifiers should be strings, to avoid JavaScript comparison surprises (e.g. `'0' !== 0`) + * `layerID` A unique identifier for the layer, for example 'osm' + * `featureID` A unique identifier for the feature, for example 'osm-w-123-fill' + * `dataID` A feature may have data bound to it, for example OSM identifier like 'w-123' + * `classID` A pseudoclass identifier like 'hover' or 'select' * * Properties you can access: - * `id` Unique string to use for the name of this Layer - * `supported` Is this Layer supported? (i.e. do we even show it in lists?) - * `zIndex` Where this Layer sits compared to other Layers - * `enabled` Whether the the user has chosen to see the Layer - * `features` `Map(featureID -> Feature)` of all features on this Layer - * `retained` `Map(featureID -> Integer frame)` last seen + * `id` Unique string to use for the name of this Layer + * `supported` Is this Layer supported? (i.e. do we even show it in lists?) + * `zIndex` Where this Layer sits compared to other Layers + * `enabled` Whether the the user has chosen to see the Layer + * `features` `Map` of all features on this Layer + * `retained` `Map` last seen */ export class AbstractLayer { /** * @constructor - * @param scene The Scene that owns this Layer - * @param layerID Unique string to use for the name of this Layer + * @param {PixiScene} scene - The Scene that owns this Layer + * @param {string} layerID - Unique string to use for the name of this Layer */ constructor(scene, layerID) { this.scene = scene; @@ -40,28 +41,26 @@ export class AbstractLayer { this._enabled = false; // Whether the user has chosen to see the layer // Collection of Features on this Layer - this.features = new Map(); // Map (featureID -> Feature) - this.retained = new Map(); // Map (featureID -> frame last seen) + this.features = new Map(); // Map + this.retained = new Map(); // Map // Feature <-> Data // These lookups capture which features are bound to which data. - this._featureHasData = new Map(); // Map (featureID -> dataID) - this._dataHasFeature = new Map(); // Map (dataID -> Set(featureID)) + this._featureHasData = new Map(); // Map + this._dataHasFeature = new Map(); // Map> // Parent Data <-> Child Data // We establish a parent-child data hierarchy (like what the DOM used to do for us) // For example, we need this to know which ways make up a multipolygon relation. - this._parentHasChildren = new Map(); // Map (parent dataID -> Set(child dataID)) - this._childHasParents = new Map(); // Map (child dataID -> Set(parent dataID)) + this._parentHasChildren = new Map(); // Map> + this._childHasParents = new Map(); // Map> // Data <-> Class // Data classes are strings (like what CSS classes used to do for us) - // Currently supported "selected", "hovered", "drawing" - // Can set other ones but it won't do anything (but won't break anything either) // Counterintuitively, the Layer needs to be the source of truth for these data classes, - // because a Feature can be "selected" or "drawing" even before it has been created, or after destroyed - this._dataHasClass = new Map(); // Map (dataID -> Set(classID)) - this._classHasData = new Map(); // Map (classID -> Set(dataID)) + // because a Feature can be 'selected' or 'drawing' even before it has been created, or after destroyed + this._dataHasClass = new Map(); // Map> + this._classHasData = new Map(); // Map> } @@ -92,9 +91,9 @@ export class AbstractLayer { * render * Every Layer should have a render function that manages the Features in view. * Override in a subclass with needed logic. It will be passed: - * @param frame Integer frame being rendered - * @param viewport Pixi viewport to use for rendering - * @param zoom Effective zoom to use for rendering + * @param {number} frame - Integer frame being rendered + * @param {Viewport} viewport - Pixi viewport to use for rendering + * @param {number} zoom - Effective zoom to use for rendering * @abstract */ render() { @@ -104,7 +103,7 @@ export class AbstractLayer { /** * cull * Make invisible any Features that were not seen during the current frame - * @param frame Integer frame being rendered + * @param {number} frame - Integer frame being rendered */ cull(frame) { for (const [featureID, feature] of this.features) { @@ -125,7 +124,7 @@ export class AbstractLayer { /** * addFeature * Add a feature to the layer cache. - * @param feature A Feature derived from `AbstractFeature` (point, line, multipolygon) + * @param {Feature} feature - A Feature derived from `AbstractFeature` (point, line, multipolygon) */ addFeature(feature) { this.features.set(feature.id, feature); @@ -135,7 +134,7 @@ export class AbstractLayer { /** * removeFeature * Remove a Feature from the layer cache. - * @param feature A Feature derived from `AbstractFeature` (point, line, multipolygon) + * @param {Feature} feature - A Feature derived from `AbstractFeature` (point, line, multipolygon) */ removeFeature(feature) { this.unbindData(feature.id); @@ -148,8 +147,8 @@ export class AbstractLayer { * retainFeature * Retain the feature for the given frame. * Features that are not retained may be automatically culled (made invisible) or removed. - * @param feature A Feature derived from `AbstractFeature` (point, line, multipolygon) - * @param frame Integer frame being rendered + * @param {Feature} feature - A Feature derived from `AbstractFeature` (point, line, multipolygon) + * @param {number} frame - Integer frame being rendered */ retainFeature(feature, frame) { if (feature.lod > 0) { @@ -162,48 +161,11 @@ export class AbstractLayer { } - /** - * syncFeatureClasses - * Set the feature's various state properties (e.g. selected, hovered, etc.) - * Counterintuitively, the Layer needs to be the source of truth for these properties, - * because a Feature can be "selected" or "drawing" even before it has been created. - * - * Setting these state properties may dirty the feature if the it causes the state to change. - * Therefore this should be called after the Feature has been created but before any updates happen. - * - * @param feature A Feature derived from `AbstractFeature` (point, line, multipolygon) - */ - syncFeatureClasses(feature) { - const featureID = feature.id; - const dataID = this._featureHasData.get(featureID); - if (!dataID) return; - - const classList = this._dataHasClass.get(dataID) ?? new Set(); - - // - // Trying to document all the supported pseudo-classes here: - // - // 'active': prevents events from firing, e.g. when dragging - // 'drawing': removes the hitarea, and avoids hover, e.g. it prevents snapping - // 'highlighted': adds a blue glowfilter - // 'hovered': adds a yellow glowfilter - // 'selected': adds a dashed line halo - // 'selectphoto': styling for the currently selected photo - - feature.active = classList.has('active'); - feature.drawing = classList.has('drawing'); - feature.highlighted = classList.has('highlighted'); - feature.hovered = classList.has('hovered'); - feature.selected = classList.has('selected'); - feature.selectphoto = classList.has('selectphoto'); - } - - /** * bindData * Adds (or replaces) a data binding from featureID to a dataID - * @param featureID `String` featureID (e.g. 'osm-w-123-fill') - * @param dataID `String` dataID (e.g. 'w-123') + * @param {string} featureID - featureID (e.g. 'osm-w-123-fill') + * @param {string} dataID - dataID (e.g. 'w-123') */ bindData(featureID, dataID) { this.unbindData(featureID); @@ -222,7 +184,7 @@ export class AbstractLayer { /** * unbindData * Removes the data binding for a given featureID - * @param featureID `String` featureID (e.g. 'osm-w-123-fill') + * @param {string} featureID - featureID (e.g. 'osm-w-123-fill') */ unbindData(featureID) { const dataID = this._featureHasData.get(featureID); @@ -249,8 +211,8 @@ export class AbstractLayer { /** * addChildData * Adds a mapping from parent data to child data. - * @param parentID `String` dataID of the parent (e.g. 'r123') - * @param childID `String` dataID of the child (e.g. 'w123') + * @param {string} parentID - dataID of the parent (e.g. 'r123') + * @param {string} childID - dataID of the child (e.g. 'w123') */ addChildData(parentID, childID) { let childIDs = this._parentHasChildren.get(parentID); @@ -272,8 +234,8 @@ export class AbstractLayer { /** * removeChildData * Removes mapping from parent data to child data. - * @param parentID `String` dataID (e.g. 'r123') - * @param childID `String` dataID to remove as a child (e.g. 'w1') + * @param {string} parentID - dataID (e.g. 'r123') + * @param {string} childID - dataID to remove as a child (e.g. 'w1') */ removeChildData(parentID, childID) { let childIDs = this._parentHasChildren.get(parentID); @@ -300,7 +262,7 @@ export class AbstractLayer { /** * clearChildData * Removes all child dataIDs for the given parent dataID - * @param parentID `String` dataID (e.g. 'r123') + * @param {string} parentID - dataID (e.g. 'r123') */ clearChildData(parentID) { const childIDs = this._parentHasChildren.get(parentID) ?? new Set(); @@ -313,9 +275,9 @@ export class AbstractLayer { /** * getSelfAndDescendants * Recursively get a result `Set` including the given dataID and all dataIDs in the child hierarchy. - * @param dataID `String` dataID (e.g. 'r123') - * @param result? `Set` containing the results (e.g. ['r123','w123','n123']) - * @return `Set` including the dataID and all dataIDs in the child hierarchy + * @param {string} dataID - dataID (e.g. 'r123') + * @param {Set} result? - `Set` containing the results (e.g. ['r123','w123','n123']) + * @return {Set} `Set` including the dataID and all dataIDs in the child hierarchy */ getSelfAndDescendants(dataID, result) { if (result instanceof Set) { @@ -338,9 +300,9 @@ export class AbstractLayer { /** * getSelfAndAncestors * Recursively get a result `Set` including the given dataID and all dataIDs in the parent hierarchy - * @param dataID `String` dataID (e.g. 'n123') - * @param result? `Set` containing the results (e.g. ['n123','w123','r123']) - * @return `Set` including the dataID and all dataIDs in the parent hierarchy + * @param {string} dataID - dataID (e.g. 'n123') + * @param {Set} result? - `Set` containing the results (e.g. ['n123','w123','r123']) + * @return {Set} `Set` including the dataID and all dataIDs in the parent hierarchy */ getSelfAndAncestors(dataID, result) { if (result instanceof Set) { @@ -363,9 +325,9 @@ export class AbstractLayer { /** * getSelfAndSiblings * Get a result `Set` including the dataID and all sibling dataIDs in the parent-child hierarchy - * @param dataID `String` dataID (e.g. 'n123') - * @param result? `Set` containing the results (e.g. ['n121','n122','n123','n124']) - * @return `Set` including the dataID and all dataIDs adjacent in the parent-child hierarchy + * @param {string} dataID - `String` dataID (e.g. 'n123') + * @param {Set} result? - `Set` containing the results (e.g. ['n121','n122','n123','n124']) + * @return {Set} `Set` including the dataID and all dataIDs adjacent in the parent-child hierarchy */ getSelfAndSiblings(dataID, result) { if (result instanceof Set) { @@ -387,12 +349,12 @@ export class AbstractLayer { /** - * classData - * Sets a dataID as being classed a certain way (e.g. 'hovered') - * @param dataID `String` dataID (e.g. 'r123') - * @param classID `String` classID (e.g. 'hovered') + * setClass + * Sets a dataID as being classed a certain way (e.g. 'hover') + * @param {string} classID - classID to set (e.g. 'hover') + * @param {string} dataID - dataID (e.g. 'r123') */ - classData(dataID, classID) { + setClass(classID, dataID) { let classIDs = this._dataHasClass.get(dataID); if (!classIDs) { classIDs = new Set(); @@ -410,12 +372,12 @@ export class AbstractLayer { /** - * unclassData - * Unsets a dataID from being classed a certain way (e.g. 'hovered') - * @param dataID `String` dataID (e.g. 'r123') - * @param classID `String` classID (e.g. 'hovered') + * unsetClass + * Unsets a dataID from being classed a certain way (e.g. 'hover') + * @param {string} classID - classID to unset (e.g. 'hover') + * @param {string} dataID - dataID (e.g. 'r123') */ - unclassData(dataID, classID) { + unsetClass(classID, dataID) { let classIDs = this._dataHasClass.get(dataID); if (classIDs) { classIDs.delete(classID); @@ -437,13 +399,64 @@ export class AbstractLayer { /** * clearClass * Clear out all uses of the given classID. - * @param classID `String` classID (e.g. 'hovered') + * @param {string} classID - classID to clear (e.g. 'hover') */ clearClass(classID) { const dataIDs = this._classHasData.get(classID) ?? new Set(); for (const dataID of dataIDs) { - this.unclassData(dataID, classID); + this.unsetClass(classID, dataID); + } + } + + + /** + * getDataWithClass + * Returns the dataIDs that are currently classed with the given classID + * @param {string} classID - classID to check (e.g. 'hover') + * @return {Set} dataIDs the dataIDs that currently have this classID set + */ + getDataWithClass(classID) { + const dataIDs = this._classHasData.get(classID) ?? new Set(); + return new Set(dataIDs); // copy + } + + + /** + * syncFeatureClasses + * This updates the feature's classes (e.g. 'select', 'hover', etc.) to match the Layer classes. + * + * Counterintuitively, the Layer needs to be the source of truth for these classes, + * because a Feature can be 'selected' or 'drawing' even before it has been created, or after destroyed. + * + * Syncing these classes will dirty the feature if the it causes a change. + * Therefore this should be called after the Feature has been created, but before any updates happen. + * + * @param {Feature} feature - A Feature derived from `AbstractFeature` (point, line, multipolygon) + */ + syncFeatureClasses(feature) { + const featureID = feature.id; + const dataID = this._featureHasData.get(featureID); + if (!dataID) return; + + const layerClasses = this._dataHasClass.get(dataID) ?? new Set(); + const featureClasses = feature._classes; + + for (const classID of featureClasses) { + if (layerClasses.has(classID)) continue; // no change + feature.unsetClass(classID); // remove extra class from feature } + for (const classID of layerClasses) { + if (featureClasses.has(classID)) continue; // no change + feature.setClass(classID); // add missing class to feature + } + + // Trying to document all the supported pseudoclasses here: + // + // 'drawing': removes the hitarea, and avoids hover, e.g. it prevents snapping + // 'highlight': adds a blue glowfilter + // 'hover': adds a yellow glowfilter + // 'select': adds a dashed line halo + // 'selectphoto': styling for the currently selected photo } @@ -462,7 +475,7 @@ export class AbstractLayer { * dirtyFeatures * Mark specific features features as `dirty` * During the next "app" pass, dirty features will be rebuilt. - * @param featureIDs A `Set` or `Array` of featureIDs, or single `String` featureID + * @param {Set|Array|string} featureIDs - A `Set` or `Array` of featureIDs, or single `String` featureID */ dirtyFeatures(featureIDs) { for (const featureID of asSet(featureIDs)) { // coax ids into a Set @@ -477,7 +490,7 @@ export class AbstractLayer { * dirtyData * Mark any features bound to a given dataID as `dirty` * During the next "app" pass, dirty features will be rebuilt. - * @param dataIDs A `Set` or `Array` of dataIDs, or single `String` dataID + * @param {Set|Array|string} dataIDs - A `Set` or `Array` of dataIDs, or single `String` dataID */ dirtyData(dataIDs) { for (const dataID of asSet(dataIDs)) { // coax ids into a Set diff --git a/modules/pixi/PixiFeatureLine.js b/modules/pixi/PixiFeatureLine.js index 629ec3880c..5b9007139f 100644 --- a/modules/pixi/PixiFeatureLine.js +++ b/modules/pixi/PixiFeatureLine.js @@ -26,8 +26,8 @@ export class PixiFeatureLine extends AbstractFeature { /** * @constructor - * @param layer The Layer that owns this Feature - * @param featureID Unique string to use for the name of this Feature + * @param {Layer} layer - The Layer that owns this Feature + * @param {string} featureID - Unique string to use for the name of this Feature */ constructor(layer, featureID) { super(layer, featureID); @@ -72,8 +72,8 @@ export class PixiFeatureLine extends AbstractFeature { /** * update - * @param viewport Pixi viewport to use for rendering - * @param zoom Effective zoom to use for rendering + * @param {Viewport} viewport - Pixi viewport to use for rendering + * @param {number} zoom - Effective zoom to use for rendering */ update(viewport, zoom) { if (!this.dirty) return; // nothing to do @@ -272,7 +272,7 @@ export class PixiFeatureLine extends AbstractFeature { // // Fix for Rapid#648: If we're drawing, we don't need to hit ourselves. // bhousel 3/23 sometimes we do! -// if (this.drawing) { +// if (this._classes.has('drawing')) { // this.container.hitArea = null; // return; // } @@ -297,9 +297,9 @@ export class PixiFeatureLine extends AbstractFeature { * Show/Hide halo (expects `this._bufferdata` to be already set up by `updateHitArea` as a PIXI.Polygon) */ updateHalo() { - const showHover = (this.visible && this.hovered); - const showSelect = (this.visible && this.selected); - const showHighlight = (this.visible && this.highlighted); + const showHover = (this.visible && this._classes.has('hover')); + const showSelect = (this.visible && this._classes.has('select')); + const showHighlight = (this.visible && this._classes.has('highlight')); // Hover if (showHover) { if (!this.container.filters) { @@ -356,10 +356,10 @@ export class PixiFeatureLine extends AbstractFeature { /** * style - * @param obj Style `Object` (contents depends on the Feature type) + * @param {Object} obj - Style `Object` (contents depends on the Feature type) * - * 'point' - see `PixiFeaturePoint.js` - * 'line'/'polygon' - see `StyleSystem.js` + * 'point' - @see `PixiFeaturePoint.js` + * 'line'/'polygon' - @see `StyleSystem.js` */ get style() { return this._style; diff --git a/modules/pixi/PixiFeaturePoint.js b/modules/pixi/PixiFeaturePoint.js index 1ca04350af..a2763c33d2 100644 --- a/modules/pixi/PixiFeaturePoint.js +++ b/modules/pixi/PixiFeaturePoint.js @@ -22,8 +22,8 @@ export class PixiFeaturePoint extends AbstractFeature { /** * @constructor - * @param layer The Layer that owns this Feature - * @param featureID Unique string to use for the name of this Feature + * @param {Layer} layer - The Layer that owns this Feature + * @param {string} featureID - Unique string to use for the name of this Feature */ constructor(layer, featureID) { super(layer, featureID); @@ -69,8 +69,8 @@ export class PixiFeaturePoint extends AbstractFeature { /** * update - * @param viewport Pixi viewport to use for rendering - * @param zoom Effective zoom to use for rendering + * @param {Viewport} viewport - Pixi viewport to use for rendering + * @param {number} zoom - Effective zoom to use for rendering */ update(viewport, zoom) { if (!this.dirty) return; // nothing to do @@ -93,8 +93,8 @@ export class PixiFeaturePoint extends AbstractFeature { /** * updateGeometry - * @param viewport Pixi viewport to use for rendering - * @param zoom Effective zoom to use for rendering + * @param {Viewport} viewport - Pixi viewport to use for rendering + * @param {number} zoom - Effective zoom to use for rendering */ updateGeometry(viewport, zoom) { if (!this.geometry.dirty) return; @@ -114,7 +114,8 @@ export class PixiFeaturePoint extends AbstractFeature { /** * updateStyle - * @param zoom Effective zoom to use for rendering + * @param {Viewport} viewport - Pixi viewport to use for rendering + * @param {number} zoom - Effective zoom to use for rendering */ updateStyle(viewport, zoom) { if (!this._styleDirty) return; @@ -186,7 +187,7 @@ export class PixiFeaturePoint extends AbstractFeature { vfSprite.anchor.set(0.5, 0.5); // middle, middle // Make the active photo image pop out at the user - if (this.selectphoto) { + if (this._classes.has('selectphoto')) { this.container.zIndex = 99000; } @@ -274,7 +275,7 @@ export class PixiFeaturePoint extends AbstractFeature { if (!this.visible) return; // Fix for Rapid#648: If we're drawing, we don't need to hit ourselves. - if (this.drawing) { + if (this._classes.has('drawing')) { this.container.hitArea = null; return; } @@ -312,9 +313,9 @@ export class PixiFeaturePoint extends AbstractFeature { * Show/Hide halo (requires `this.container.hitArea` to be already set up by `updateHitArea` as a supported shape) */ updateHalo() { - const showHover = (this.visible && this.hovered); - const showSelect = (this.visible && this.selected && !this.virtual); - const showHighlight = (this.visible && this.highlighted); + const showHover = (this.visible && this._classes.has('hover')); + const showSelect = (this.visible && this._classes.has('select') && !this.virtual); + const showHighlight = (this.visible && this._classes.has('highlight')); // Hover if (showHover) { @@ -374,10 +375,10 @@ export class PixiFeaturePoint extends AbstractFeature { /** * style - * @param obj Style `Object` (contents depends on the Feature type) + * @param {Object} obj - Style `Object` (contents depends on the Feature type) * - * 'point' - see `PixiFeaturePoint.js` - * 'line'/'polygon' - see `StyleSystem.js` + * 'point' - @see `PixiFeaturePoint.js` + * 'line'/'polygon' - @see `StyleSystem.js` */ get style() { return this._style; diff --git a/modules/pixi/PixiFeaturePolygon.js b/modules/pixi/PixiFeaturePolygon.js index f7b5e8a885..215f575a0c 100644 --- a/modules/pixi/PixiFeaturePolygon.js +++ b/modules/pixi/PixiFeaturePolygon.js @@ -27,8 +27,8 @@ export class PixiFeaturePolygon extends AbstractFeature { /** * @constructor - * @param layer The Layer that owns this Feature - * @param featureID Unique string to use for the name of this Feature + * @param {Layer} layer - The Layer that owns this Feature + * @param {string} featureID - Unique string to use for the name of this Feature */ constructor(layer, featureID) { super(layer, featureID); @@ -109,8 +109,8 @@ export class PixiFeaturePolygon extends AbstractFeature { /** * update - * @param viewport Pixi viewport to use for rendering - * @param zoom Effective zoom to use for rendering + * @param {Viewport} viewport - Pixi viewport to use for rendering + * @param {number} zoom - Effective zoom to use for rendering */ update(viewport, zoom) { if (!this.dirty) return; // nothing to do @@ -414,9 +414,9 @@ export class PixiFeaturePolygon extends AbstractFeature { */ updateHalo() { const wireframeMode = this.context.systems.map.wireframeMode; - const showHover = (this.visible && this.hovered); - const showSelect = (this.visible && this.selected); - const showHighlight = (this.visible && this.highlighted); + const showHover = (this.visible && this._classes.has('hover')); + const showSelect = (this.visible && this._classes.has('select')); + const showHighlight = (this.visible && this._classes.has('highlight')); // Hover if (showHover) { @@ -473,10 +473,10 @@ export class PixiFeaturePolygon extends AbstractFeature { /** * style - * @param obj Style `Object` (contents depends on the Feature type) + * @param {Object} obj - Style `Object` (contents depends on the Feature type) * - * 'point' - see `PixiFeaturePoint.js` - * 'line'/'polygon' - see `StyleSystem.js` + * 'point' - @see `PixiFeaturePoint.js` + * 'line'/'polygon' - @see `StyleSystem.js` */ get style() { return this._style; diff --git a/modules/pixi/PixiGeometry.js b/modules/pixi/PixiGeometry.js index bc24eac12f..93a35e09fc 100644 --- a/modules/pixi/PixiGeometry.js +++ b/modules/pixi/PixiGeometry.js @@ -90,8 +90,7 @@ export class PixiGeometry { /** * update - * @param viewport Pixi viewport to use for rendering - * @param zoom Effective zoom to use for rendering + * @param {Viewport} viewport - Pixi viewport to use for rendering */ update(viewport) { if (!this.dirty || !this.origCoords || !this.origExtent) return; // nothing to do @@ -253,7 +252,7 @@ export class PixiGeometry { /** * setCoords - * @param data Geometry `Array` (contents depends on the Feature type) + * @param {Array<*>} data - Geometry `Array` (contents depends on the Feature type) * * 'point' - Single wgs84 coordinate * [lon, lat] @@ -296,8 +295,8 @@ export class PixiGeometry { /** * _inferType * Determines what kind of geometry we were passed. - * @param arr Geometry `Array` (contents depends on the Feature type) - * @return 'point', 'line', 'polygon' or null + * @param {Array<*>} arr - Geometry `Array` (contents depends on the Feature type) + * @return {string?} 'point', 'line', 'polygon' or null */ _inferType(data) { const a = Array.isArray(data) && data[0]; @@ -312,5 +311,4 @@ export class PixiGeometry { return null; } - } diff --git a/modules/pixi/PixiLayerKartaPhotos.js b/modules/pixi/PixiLayerKartaPhotos.js index 74714db3da..254aee861f 100644 --- a/modules/pixi/PixiLayerKartaPhotos.js +++ b/modules/pixi/PixiLayerKartaPhotos.js @@ -191,7 +191,7 @@ export class PixiLayerKartaPhotos extends AbstractLayer { if (feature.dirty) { const style = Object.assign({}, MARKERSTYLE); // todo handle pano - if (feature.selectphoto) { // selected photo style + if (feature.hasClass('selectphoto')) { // selected photo style // style.viewfieldAngles = [this._viewerCompassAngle ?? d.ca]; if (Number.isFinite(d.ca)) { style.viewfieldAngles = [d.ca]; // ca = camera angle diff --git a/modules/pixi/PixiLayerMapillaryPhotos.js b/modules/pixi/PixiLayerMapillaryPhotos.js index 01dca03729..2bdeed40b0 100644 --- a/modules/pixi/PixiLayerMapillaryPhotos.js +++ b/modules/pixi/PixiLayerMapillaryPhotos.js @@ -278,7 +278,7 @@ export class PixiLayerMapillaryPhotos extends AbstractLayer { if (feature.dirty) { const style = Object.assign({}, MARKERSTYLE); - if (feature.selectphoto) { // selected photo style + if (feature.hasClass('selectphoto')) { // selected photo style style.viewfieldAngles = [this._viewerBearing ?? d.ca]; style.viewfieldName = 'viewfield'; style.viewfieldTint = MAPILLARY_SELECTED; diff --git a/modules/pixi/PixiLayerOsm.js b/modules/pixi/PixiLayerOsm.js index bfd1b5a5cd..338f87761e 100644 --- a/modules/pixi/PixiLayerOsm.js +++ b/modules/pixi/PixiLayerOsm.js @@ -188,9 +188,9 @@ export class PixiLayerOsm extends AbstractLayer { // and parent-child data links have been established. // Gather ids related for the selected/hovered/drawing features. - const selectedIDs = this._classHasData.get('selected') ?? new Set(); - const hoveredIDs = this._classHasData.get('hovered') ?? new Set(); - const drawingIDs = this._classHasData.get('drawing') ?? new Set(); + const selectedIDs = this.getDataWithClass('select'); + const hoveredIDs = this.getDataWithClass('hover'); + const drawingIDs = this.getDataWithClass('drawing'); const dataIDs = new Set([...selectedIDs, ...hoveredIDs, ...drawingIDs]); // Experiment: avoid showing child vertices/midpoints for too small parents diff --git a/modules/pixi/PixiLayerStreetsidePhotos.js b/modules/pixi/PixiLayerStreetsidePhotos.js index ef9683ba34..84bfddf7f4 100644 --- a/modules/pixi/PixiLayerStreetsidePhotos.js +++ b/modules/pixi/PixiLayerStreetsidePhotos.js @@ -233,7 +233,7 @@ export class PixiLayerStreetsidePhotos extends AbstractLayer { const yaw = viewer?.getYaw() ?? 0; const fov = viewer?.getHfov() ?? 45; - if (feature.selectphoto) { // selected photo style + if (feature.hasClass('selectphoto')) { // selected photo style style.viewfieldAngles = [d.ca + yaw]; style.viewfieldName = 'viewfield'; style.viewfieldTint = STREETSIDE_SELECTED; diff --git a/modules/pixi/PixiRenderer.js b/modules/pixi/PixiRenderer.js index 286efdf5e6..45ae526b54 100644 --- a/modules/pixi/PixiRenderer.js +++ b/modules/pixi/PixiRenderer.js @@ -177,7 +177,7 @@ export class PixiRenderer extends EventEmitter { * Respond to any change in selection (called on mode change) */ _onModeChange(mode) { - this.scene.clearClass('selected'); + this.scene.clearClass('select'); for (const [datumID, datum] of this.context.selectedData()) { let layerID = null; @@ -191,14 +191,14 @@ export class PixiRenderer extends EventEmitter { layerID = 'rapid'; } else if (datum.__featurehash__) { // custom data layerID = 'custom-data'; - } else if (mode.id === 'select-osm') { // an OSM feature + } else if (mode.id === 'select-osm' || mode.id === 'drag-node') { // an OSM feature layerID = 'osm'; } else { // other selectable things (photos?) - we will not select-style them for now :( } if (layerID) { - this.scene.classData(layerID, datumID, 'selected'); + this.scene.setClass('select', layerID, datumID); } } @@ -225,12 +225,12 @@ export class PixiRenderer extends EventEmitter { ui.sidebar.hover(hoverData ? [hoverData] : []); } - scene.clearClass('hovered'); + scene.clearClass('hover'); if (layer && dataID) { // Only set hover class if this target isn't currently drawing - const drawingIDs = layer._classHasData.get('drawing') ?? new Set(); + const drawingIDs = layer.getDataWithClass('drawing'); if (!drawingIDs.has(dataID)) { - scene.classData(layer.id, dataID, 'hovered'); + layer.setClass('hover', dataID); } } diff --git a/modules/pixi/PixiScene.js b/modules/pixi/PixiScene.js index 8fee0d2522..0606b377a1 100644 --- a/modules/pixi/PixiScene.js +++ b/modules/pixi/PixiScene.js @@ -41,7 +41,7 @@ function asSet(vals) { * - `layerID` - A unique identifier for the layer, for example 'osm' * - `featureID` - A unique identifier for the feature, for example 'osm-w-123-fill' * - `dataID` - A feature may have data bound to it, for example OSM identifier like 'w-123' - * - `classID` - A class identifier like 'hovered' or 'selected' + * - `classID` - A pseudoclass identifier like 'hover' or 'select' * * Properties you can access: * `groups` `Map (groupID -> PIXI.Container)` of all groups @@ -236,33 +236,33 @@ export class PixiScene extends EventEmitter { /** - * classData - * Sets a dataID as being classed a certain way (e.g. 'hovered') + * setClass + * Sets a dataID as being classed a certain way (e.g. 'hover') + * @param classID `String` classID (e.g. 'hover') * @param layerID `String` layerID (e.g. 'osm') * @param dataID `String` dataID (e.g. 'r123') - * @param classID `String` classID (e.g. 'hovered') */ - classData(layerID, dataID, classID) { - this.layers.get(layerID)?.classData(dataID, classID); + setClass(classID, layerID, dataID) { + this.layers.get(layerID)?.setClass(classID, dataID); } /** - * unclassData - * Unsets a dataID from being classed a certain way (e.g. 'hovered') + * unsetClass + * Unsets a dataID from being classed a certain way (e.g. 'hover') + * @param classID `String` classID (e.g. 'hover') * @param layerID `String` layerID (e.g. 'osm') * @param dataID `String` dataID (e.g. 'r123') - * @param classID `String` classID (e.g. 'hovered') */ - unclassData(layerID, dataID, classID) { - this.layers.get(layerID)?.unclassData(dataID, classID); + unsetClass(classID, layerID, dataID) { + this.layers.get(layerID)?.unsetClass(classID, dataID); } /** * clearClass * Clear out all uses of the given classID across all layers. - * @param classID `String` classID (e.g. 'hovered') + * @param classID `String` classID (e.g. 'hover') */ clearClass(classID) { for (const layer of this.layers.values()) { diff --git a/modules/util/util.js b/modules/util/util.js index 409dffc16c..8947ad061b 100644 --- a/modules/util/util.js +++ b/modules/util/util.js @@ -79,25 +79,28 @@ export function geojsonExtent(geojson) { export function utilHighlightEntities(entityIDs, highlighted, context) { const editor = context.systems.editor; const map = context.systems.map; + const scene = map.scene; if (!scene) return; // called too soon? + const layer = scene.layers.get('osm'); + if (highlighted) { for (const entityID of entityIDs) { - scene.classData('osm', entityID, 'highlighted'); + layer.setClass('highlight', entityID); // When highlighting a relation, try to highlight its members. if (entityID[0] === 'r') { const relation = editor.staging.graph.hasEntity(entityID); if (!relation) continue; for (const member of relation.members) { - scene.classData('osm', member.id, 'highlighted'); + layer.setClass('highlight', member.id); } } } } else { - scene.clearClass('highlighted'); + layer.clearClass('highlight'); } map.immediateRedraw();