/** * @license * Copyright 2019 Google LLC * SPDX-License-Identifier: Apache-2.0 */ /** * @fileoverview Object in charge of managing markers and the cursor. */ /** * Object in charge of managing markers and the cursor. * @class */ import * as goog from '../closure/goog/goog.js'; goog.declareModuleId('Blockly.MarkerManager'); import type {Cursor} from './keyboard_nav/cursor.js'; import type {Marker} from './keyboard_nav/marker.js'; import type {WorkspaceSvg} from './workspace_svg.js'; /** * Class to manage the multiple markers and the cursor on a workspace. * @alias Blockly.MarkerManager */ export class MarkerManager { /** The name of the local marker. */ static readonly LOCAL_MARKER = 'local_marker_1'; /** The cursor. */ private cursor_: Cursor|null = null; /** The cursor's SVG element. */ private cursorSvg_: SVGElement|null = null; private markers_: {[key: string]: Marker}; /** The marker's SVG element. */ private markerSvg_: SVGElement|null = null; /** * @param workspace The workspace for the marker manager. * @internal */ constructor(private readonly workspace: WorkspaceSvg) { /** The map of markers for the workspace. */ this.markers_ = Object.create(null); } /** * Register the marker by adding it to the map of markers. * @param id A unique identifier for the marker. * @param marker The marker to register. */ registerMarker(id: string, marker: Marker) { if (this.markers_[id]) { this.unregisterMarker(id); } marker.setDrawer( this.workspace.getRenderer().makeMarkerDrawer(this.workspace, marker)); this.setMarkerSvg(marker.getDrawer().createDom()); this.markers_[id] = marker; } /** * Unregister the marker by removing it from the map of markers. * @param id The ID of the marker to unregister. */ unregisterMarker(id: string) { const marker = this.markers_[id]; if (marker) { marker.dispose(); delete this.markers_[id]; } else { throw Error( 'Marker with ID ' + id + ' does not exist. ' + 'Can only unregister markers that exist.'); } } /** * Get the cursor for the workspace. * @return The cursor for this workspace. */ getCursor(): Cursor|null { return this.cursor_; } /** * Get a single marker that corresponds to the given ID. * @param id A unique identifier for the marker. * @return The marker that corresponds to the given ID, or null if none * exists. */ getMarker(id: string): Marker|null { return this.markers_[id] || null; } /** * Sets the cursor and initializes the drawer for use with keyboard * navigation. * @param cursor The cursor used to move around this workspace. */ setCursor(cursor: Cursor) { if (this.cursor_ && this.cursor_.getDrawer()) { this.cursor_.getDrawer().dispose(); } this.cursor_ = cursor; if (this.cursor_) { const drawer = this.workspace.getRenderer().makeMarkerDrawer( this.workspace, this.cursor_); this.cursor_.setDrawer(drawer); this.setCursorSvg(this.cursor_.getDrawer().createDom()); } } /** * Add the cursor SVG to this workspace SVG group. * @param cursorSvg The SVG root of the cursor to be added to the workspace * SVG group. * @internal */ setCursorSvg(cursorSvg: SVGElement|null) { if (!cursorSvg) { this.cursorSvg_ = null; return; } this.workspace.getBlockCanvas()!.appendChild(cursorSvg); this.cursorSvg_ = cursorSvg; } /** * Add the marker SVG to this workspaces SVG group. * @param markerSvg The SVG root of the marker to be added to the workspace * SVG group. * @internal */ setMarkerSvg(markerSvg: SVGElement|null) { if (!markerSvg) { this.markerSvg_ = null; return; } if (this.workspace.getBlockCanvas()) { if (this.cursorSvg_) { this.workspace.getBlockCanvas()!.insertBefore( markerSvg, this.cursorSvg_); } else { this.workspace.getBlockCanvas()!.appendChild(markerSvg); } } } /** * Redraw the attached cursor SVG if needed. * @internal */ updateMarkers() { if (this.workspace.keyboardAccessibilityMode && this.cursorSvg_) { this.workspace.getCursor()!.draw(); } } /** * Dispose of the marker manager. * Go through and delete all markers associated with this marker manager. * @suppress {checkTypes} * @internal */ dispose() { const markerIds = Object.keys(this.markers_); for (let i = 0, markerId; markerId = markerIds[i]; i++) { this.unregisterMarker(markerId); } // AnyDuringMigration because: Type 'null' is not assignable to type '{ // [key: string]: Marker; }'. this.markers_ = null as AnyDuringMigration; if (this.cursor_) { this.cursor_.dispose(); this.cursor_ = null; } } }