From c82354139a2b5a88d1c2ffd59293ba2dc5cd637d Mon Sep 17 00:00:00 2001 From: Alexander Artemev Date: Thu, 28 Mar 2024 02:32:20 +0700 Subject: [PATCH] Plugin Refactoring PreFinal --- demo/index.ts | 13 +- demo/mocks.ts | 139 +++++---- package-lock.json | 14 +- package.json | 2 +- src/constants.ts | 9 +- src/control/index.ts | 5 +- src/control/types.ts | 10 +- src/defaultOptions.ts | 10 - src/labelGroups.ts | 61 ++++ src/plugin.ts | 67 +++-- src/poiGroups.ts | 155 ---------- src/realtyScene/realtyScene.ts | 274 +++++++----------- src/types/events.ts | 43 +-- src/types/plugin.ts | 51 +--- src/types/realtyScene.ts | 22 +- src/utils/events.ts | 39 ++- src/utils/realtyScene.ts | 51 ++++ test/mocks/index.ts | 8 +- test/mocks/labels.ts | 44 +++ test/mocks/models.ts | 16 +- test/mocks/poi.ts | 39 --- test/mocks/realtyScene.ts | 124 ++++---- test/puppeteer/config.ts | 2 +- .../plugin/_constructor-snap.png | Bin 6989 -> 0 bytes .../plugin/add_label_group-snap.png | Bin 0 -> 5461 bytes .../__screenshots__/plugin/add_model-snap.png | Bin 13716 -> 14244 bytes .../plugin/add_models-snap.png | Bin 17876 -> 16289 bytes .../plugin/add_models_partially-snap.png | Bin 13716 -> 14244 bytes .../plugin/add_poi_group-snap.png | Bin 5466 -> 0 bytes .../plugin/add_realty_scene-snap.png | Bin 40709 -> 42141 bytes .../plugin/change_style-snap.png | Bin 13700 -> 14180 bytes ...p-snap.png => remove_label_group-snap.png} | Bin test/screenshots/plugin.screen.ts | 41 +-- tsconfig.json | 2 + 34 files changed, 577 insertions(+), 664 deletions(-) create mode 100644 src/labelGroups.ts delete mode 100644 src/poiGroups.ts create mode 100644 src/utils/realtyScene.ts create mode 100644 test/mocks/labels.ts delete mode 100644 test/mocks/poi.ts delete mode 100644 test/screenshots/__screenshots__/plugin/_constructor-snap.png create mode 100644 test/screenshots/__screenshots__/plugin/add_label_group-snap.png delete mode 100644 test/screenshots/__screenshots__/plugin/add_poi_group-snap.png rename test/screenshots/__screenshots__/plugin/{remove_poi_group-snap.png => remove_label_group-snap.png} (100%) diff --git a/demo/index.ts b/demo/index.ts index ac753ed..02863fc 100644 --- a/demo/index.ts +++ b/demo/index.ts @@ -6,7 +6,7 @@ import { REALTY_SCENE, REALTY_SCENE_1 } from './mocks'; let isDarkTheme = false; async function start() { - const mapglAPI = await load('https://mapgl.2gis.com/api/js/v0.0.323'); + const mapglAPI = await load(); const map = new mapglAPI.Map('container', { center: [47.245286302641034, 56.134743473834099], @@ -15,6 +15,7 @@ async function start() { pitch: 45, rotation: 330, enableTrackResize: true, + webglVersion: 1, }); (window as any).map = map; @@ -23,16 +24,8 @@ async function start() { modelsLoadStrategy: 'dontWaitAll', modelsBaseUrl: 'https://disk.2gis.com/digital-twin/models_s3/realty_ads/zgktechnology/', floorsControl: { position: 'centerRight' }, - poiConfig: { - primary: { - fontSize: 14, - }, - secondary: { - fontSize: 14, - }, - }, hoverOptions: { - color: '#FFEFEF', + color: '#FFF3F3', }, groundCoveringColor: 'rgba(0, 0, 0, 0.8)', }); diff --git a/demo/mocks.ts b/demo/mocks.ts index 7281603..9bb11c6 100644 --- a/demo/mocks.ts +++ b/demo/mocks.ts @@ -31,46 +31,68 @@ export const REALTY_SCENE: BuildingOptions[] = [ rotation: -57.5, }, isUnderground: true, - poiGroups: [ + labelGroups: [ { id: '1111', - type: 'primary', + image: { + url: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjgiIGhlaWdodD0iMjgiIHZpZXdCb3g9IjAgMCAyOCAyOCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMjgiIGhlaWdodD0iMjgiIHJ4PSI0IiBmaWxsPSIjZWFlYWVhIi8+PHJlY3QgeD0iMSIgeT0iMSIgd2lkdGg9IjI2IiBoZWlnaHQ9IjI2IiByeD0iMyIgZmlsbD0id2hpdGUiLz48L3N2Zz4=', + size: [38, 38], + stretchX: [[4, 24]], + stretchY: [[4, 24]], + padding: [5, 10, 5, 10], + }, minZoom: 19.5, elevation: 5, fontSize: 12, fontColor: '#3a3a3a', - data: [ + labels: [ { coordinates: [47.245048150280994, 56.134470449142164], - label: '3к\n78.4 м²', + text: '3к\n78.4 м²', userData: { url: 'https://a101.ru/kvartiry/360810/', }, }, { coordinates: [47.24520807647288, 56.13443854463778], - label: '2к\n67 м²', + text: '2к\n67 м²', userData: { url: 'https://a101.ru/kvartiry/360810/', }, }, + ], + }, + { + id: '2222', + image: { + url: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzAiIGhlaWdodD0iMzAiIHZpZXdCb3g9IjAgMCAzMCAzMCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMzAiIGhlaWdodD0iMzAiIHJ4PSI1IiBmaWxsPSIjNEZBQjhBIi8+PHJlY3QgeD0iMSIgeT0iMSIgd2lkdGg9IjI4IiBoZWlnaHQ9IjI4IiByeD0iNCIgZmlsbD0id2hpdGUiLz48cmVjdCB4PSIyIiB5PSIyIiB3aWR0aD0iMjYiIGhlaWdodD0iMjYiIHJ4PSIzIiBmaWxsPSIjNEZBQjhBIi8+PC9zdmc+', + size: [38, 38], + stretchX: [[4, 24]], + stretchY: [[4, 24]], + padding: [5, 10, 5, 10], + }, + minZoom: 19.5, + elevation: 5, + fontSize: 12, + fontColor: '#3a3a3a', + labels: [ { coordinates: [47.245350349632965, 56.134414208205776], - label: '1к\n40 м²', + text: '1к\n40 м²', userData: { url: 'https://a101.ru/kvartiry/360810/', }, }, { coordinates: [47.24542896512635, 56.13448965532694], - label: '3к\n90 м²', + text: '3к\n90 м²', userData: { url: 'https://a101.ru/kvartiry/360810/', }, }, { coordinates: [47.24510451854659, 56.134541185948585], - label: '3к\n77.2 м²', + text: '3к\n77.2 м²', userData: { url: 'https://a101.ru/kvartiry/360810/', }, @@ -89,46 +111,52 @@ export const REALTY_SCENE: BuildingOptions[] = [ zoom: 19.5, rotation: -62.6, }, - poiGroups: [ + labelGroups: [ { id: '1111', - type: 'primary', + image: { + url: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjRweCIgaGVpZ2h0PSIyNHB4IiB2aWV3Qm94PSIwIDAgMjQgMjQiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+PGcgaWQ9IlBhZ2UtMSIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCI+PGcgaWQ9ImV4aXQiIGZpbGw9IiM2MDVFNTAiIGZpbGwtcnVsZT0ibm9uemVybyI+PHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiByeD0iNSI+PC9yZWN0PjwvZz48L2c+PC9zdmc+', + size: [38, 38], + stretchX: [[4, 24]], + stretchY: [[4, 24]], + padding: [5, 10, 5, 10], + }, minZoom: 19, elevation: 35, fontSize: 12, - fontColor: '#3a3a3a', - data: [ + fontColor: '#fff', + labels: [ { coordinates: [47.245048150280994, 56.134470449142164], - label: '3к\n78.4 м²', + text: '3к\n78.4 м²', userData: { url: 'https://a101.ru/kvartiry/360810/', }, }, { coordinates: [47.24520807647288, 56.13443854463778], - label: '2к\n67 м²', + text: '2к\n67 м²', userData: { url: 'https://a101.ru/kvartiry/360810/', }, }, { coordinates: [47.245350349632965, 56.134414208205776], - label: '1к\n40 м²', + text: '1к\n40 м²', userData: { url: 'https://a101.ru/kvartiry/360810/', }, }, { coordinates: [47.24542896512635, 56.13448965532694], - label: '3к\n90 м²', + text: '3к\n90 м²', userData: { url: 'https://a101.ru/kvartiry/360810/', }, }, { coordinates: [47.24510451854659, 56.134541185948585], - label: '3к\n77.2 м²', + text: '3к\n77.2 м²', userData: { url: 'https://a101.ru/kvartiry/360810/', }, @@ -168,74 +196,59 @@ export const REALTY_SCENE: BuildingOptions[] = [ zoom: 20, rotation: -130, }, - poiGroups: [ + labelGroups: [ { id: '1111', - type: 'primary', + image: { + url: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjgiIGhlaWdodD0iMjgiIHZpZXdCb3g9IjAgMCAyOCAyOCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMjgiIGhlaWdodD0iMjgiIHJ4PSI0IiBmaWxsPSIjZWFlYWVhIi8+PHJlY3QgeD0iMSIgeT0iMSIgd2lkdGg9IjI2IiBoZWlnaHQ9IjI2IiByeD0iMyIgZmlsbD0id2hpdGUiLz48L3N2Zz4=', + size: [38, 38], + stretchX: [[4, 24]], + stretchY: [[4, 24]], + padding: [5, 10, 5, 10], + }, minZoom: 19.7, elevation: 7, fontSize: 12, fontColor: '#3a3a3a', - data: [ + labels: [ { coordinates: [47.24452417991248, 56.13469284843933], - label: '1к\n27 м²', + text: '1к\n27 м²', userData: { url: 'https://a101.ru/kvartiry/360810/', }, }, { coordinates: [47.24457199258783, 56.13477179423035], - label: '2к\n54.4 м²', + text: '2к\n54.4 м²', userData: { url: 'https://a101.ru/kvartiry/360810/', }, }, { coordinates: [47.244491707517696, 56.13463324895681], - label: '1к\n27 м²', + text: '1к\n27 м²', userData: { url: 'https://a101.ru/kvartiry/360810/', }, }, { coordinates: [47.244467722972786, 56.13455859493207], - label: '3к\n67 м²', + text: '3к\n67 м²', userData: { url: 'https://a101.ru/kvartiry/360810/', }, }, { coordinates: [47.24459718584492, 56.13483803780593], - label: '1к\n30 м²', + text: '1к\n30 м²', userData: { url: 'https://a101.ru/kvartiry/360810/', }, }, { coordinates: [47.24474865936005, 56.13481340001352], - label: '2к\n45 м²', - userData: { - url: 'https://a101.ru/kvartiry/360810/', - }, - }, - { - coordinates: [47.244714550432995, 56.13474141463477], - label: '3к\n54.4 м²', - userData: { - url: 'https://a101.ru/kvartiry/360810/', - }, - }, - { - coordinates: [47.24464159162246, 56.134578465378226], - label: '1к\n33 м²', - userData: { - url: 'https://a101.ru/kvartiry/360810/', - }, - }, - { - coordinates: [47.24461054223749, 56.13451937931448], - label: '2к\n45 м²', + text: '2к\n45 м²', userData: { url: 'https://a101.ru/kvartiry/360810/', }, @@ -254,74 +267,80 @@ export const REALTY_SCENE: BuildingOptions[] = [ zoom: 19.2, rotation: -130, }, - poiGroups: [ + labelGroups: [ { id: '1111', - type: 'primary', + image: { + url: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjRweCIgaGVpZ2h0PSIyNHB4IiB2aWV3Qm94PSIwIDAgMjQgMjQiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+PGcgaWQ9IlBhZ2UtMSIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCI+PGcgaWQ9ImV4aXQiIGZpbGw9IiM2MDVFNTAiIGZpbGwtcnVsZT0ibm9uemVybyI+PHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiByeD0iNSI+PC9yZWN0PjwvZz48L2c+PC9zdmc+', + size: [38, 38], + stretchX: [[4, 24]], + stretchY: [[4, 24]], + padding: [5, 10, 5, 10], + }, minZoom: 18.9, elevation: 53, fontSize: 12, - fontColor: '#3a3a3a', - data: [ + fontColor: '#fff', + labels: [ { coordinates: [47.24452417991248, 56.13469284843933], - label: '1к\n27 м²', + text: '1к\n27 м²', userData: { url: 'https://a101.ru/kvartiry/360810/', }, }, { coordinates: [47.24457199258783, 56.13477179423035], - label: '2к\n54.4 м²', + text: '2к\n54.4 м²', userData: { url: 'https://a101.ru/kvartiry/360810/', }, }, { coordinates: [47.244491707517696, 56.13463324895681], - label: '1к\n27 м²', + text: '1к\n27 м²', userData: { url: 'https://a101.ru/kvartiry/360810/', }, }, { coordinates: [47.244467722972786, 56.13455859493207], - label: '3к\n67 м²', + text: '3к\n67 м²', userData: { url: 'https://a101.ru/kvartiry/360810/', }, }, { coordinates: [47.24459718584492, 56.13483803780593], - label: '1к\n30 м²', + text: '1к\n30 м²', userData: { url: 'https://a101.ru/kvartiry/360810/', }, }, { coordinates: [47.24474865936005, 56.13481340001352], - label: '2к\n45 м²', + text: '2к\n45 м²', userData: { url: 'https://a101.ru/kvartiry/360810/', }, }, { coordinates: [47.244714550432995, 56.13474141463477], - label: '3к\n54.4 м²', + text: '3к\n54.4 м²', userData: { url: 'https://a101.ru/kvartiry/360810/', }, }, { coordinates: [47.24464159162246, 56.134578465378226], - label: '1к\n33 м²', + text: '1к\n33 м²', userData: { url: 'https://a101.ru/kvartiry/360810/', }, }, { coordinates: [47.24461054223749, 56.13451937931448], - label: '2к\n45 м²', + text: '2к\n45 м²', userData: { url: 'https://a101.ru/kvartiry/360810/', }, diff --git a/package-lock.json b/package-lock.json index e034cf3..5d3a9b5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "1.3.1", "license": "BSD-2-Clause", "devDependencies": { - "@2gis/mapgl": "1.37.2", + "@2gis/mapgl": "1.46.0", "@documentalist/compiler": "^2.8.1", "@types/geojson": "^7946.0.10", "@types/jest": "^27.4.0", @@ -36,9 +36,9 @@ } }, "node_modules/@2gis/mapgl": { - "version": "1.37.2", - "resolved": "https://registry.npmjs.org/@2gis/mapgl/-/mapgl-1.37.2.tgz", - "integrity": "sha512-JrngAj++tHpbnE1BlJCjGXTyZWpZeRR14no8tqRR3sqNmHfo5vWbr1Z0+kcSWpyfUGrlmd+JnG5ppP8f8NeS3A==", + "version": "1.46.0", + "resolved": "https://registry.npmjs.org/@2gis/mapgl/-/mapgl-1.46.0.tgz", + "integrity": "sha512-fHgVnzNXMof3cZ1vBcZMMGcUdjtQNOG5pymJyaeVg01rkHEEvhg8zupcjYlqnCDaATR33mItV/f0/N9bGiMKmg==", "dev": true, "dependencies": { "@types/geojson": "^7946.0.7" @@ -10553,9 +10553,9 @@ }, "dependencies": { "@2gis/mapgl": { - "version": "1.37.2", - "resolved": "https://registry.npmjs.org/@2gis/mapgl/-/mapgl-1.37.2.tgz", - "integrity": "sha512-JrngAj++tHpbnE1BlJCjGXTyZWpZeRR14no8tqRR3sqNmHfo5vWbr1Z0+kcSWpyfUGrlmd+JnG5ppP8f8NeS3A==", + "version": "1.46.0", + "resolved": "https://registry.npmjs.org/@2gis/mapgl/-/mapgl-1.46.0.tgz", + "integrity": "sha512-fHgVnzNXMof3cZ1vBcZMMGcUdjtQNOG5pymJyaeVg01rkHEEvhg8zupcjYlqnCDaATR33mItV/f0/N9bGiMKmg==", "dev": true, "requires": { "@types/geojson": "^7946.0.7" diff --git a/package.json b/package.json index c96d9f7..e7babad 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "author": "2GIS WebMaps Team", "license": "BSD-2-Clause", "devDependencies": { - "@2gis/mapgl": "1.37.2", + "@2gis/mapgl": "1.46.0", "@documentalist/compiler": "^2.8.1", "@types/geojson": "^7946.0.10", "@types/jest": "^27.4.0", diff --git a/src/constants.ts b/src/constants.ts index 21cb673..197fdb1 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,5 +1,7 @@ import type { GeoJsonSourceOptions } from '@2gis/mapgl/types'; +export const PLUGIN_PREFIX = '__mapglPlugins_mapgl-gltf2'; + export const GROUND_COVERING_SOURCE_DATA: GeoJsonSourceOptions['data'] = { type: 'Feature', properties: {}, @@ -17,9 +19,10 @@ export const GROUND_COVERING_SOURCE_DATA: GeoJsonSourceOptions['data'] = { }, }; -export const GROUND_COVERING_SOURCE_PURPOSE = '__mapglPlugins_mapgl-gltf'; +export const GROUND_COVERING_SOURCE_PURPOSE = `${PLUGIN_PREFIX}-covering`; +export const GROUND_COVERING_LAYER_ID = `${PLUGIN_PREFIX}-covering`; export const GROUND_COVERING_LAYER = { - id: '__mapglPlugins_mapgl-gltf', + id: GROUND_COVERING_LAYER_ID, type: 'polygon', style: { color: ['to-color', ['sourceAttr', 'color']], @@ -30,3 +33,5 @@ export const GROUND_COVERING_LAYER = { ['to-boolean', ['sourceAttr', 'color']], ], }; + +export const pluginEvents = ['click', 'mousemove', 'mouseover', 'mouseout'] as const; diff --git a/src/control/index.ts b/src/control/index.ts index 6c2e730..04317ec 100644 --- a/src/control/index.ts +++ b/src/control/index.ts @@ -5,7 +5,6 @@ import icon_building from 'raw-loader!./icon_building.svg'; import icon_parking from 'raw-loader!./icon_parking.svg'; import classes from './control.module.css'; import { Control } from './control'; -import { Id } from '../types'; const content = /* HTML */ `
@@ -120,7 +119,7 @@ export class GltfFloorControl extends Control { }); } - private _controlHandler = (modelId: Id) => () => { + private _controlHandler = (modelId: string) => () => { this._switchCurrentFloorLevel(modelId); this.emit('floorchange', { @@ -128,7 +127,7 @@ export class GltfFloorControl extends Control { }); }; - private _switchCurrentFloorLevel(modelId: Id) { + private _switchCurrentFloorLevel(modelId: string) { if (this._currentFloorId === undefined) { return; } diff --git a/src/control/types.ts b/src/control/types.ts index 453ce22..7b64598 100644 --- a/src/control/types.ts +++ b/src/control/types.ts @@ -1,10 +1,8 @@ -import { Id } from '../types/plugin'; - /** * Floor level data */ export interface FloorLevel { - modelId: Id; // id модели этажа или здания + modelId: string; // id модели этажа или здания text: string; icon?: 'parking' | 'building' | string; } @@ -13,8 +11,8 @@ export interface FloorLevel { * Options for the method show */ export interface ControlShowOptions { - buildingModelId: Id; - activeModelId: Id; + buildingModelId: string; + activeModelId: string; floorLevels?: FloorLevel[]; } @@ -22,7 +20,7 @@ export interface ControlShowOptions { * Event that emitted on button presses of the control */ export interface FloorChangeEvent { - modelId: Id; // id модели этажа или здания + modelId: string; // id модели этажа или здания } export interface ControlEventTable { diff --git a/src/defaultOptions.ts b/src/defaultOptions.ts index d47089c..cd80f5c 100644 --- a/src/defaultOptions.ts +++ b/src/defaultOptions.ts @@ -6,16 +6,6 @@ export const defaultOptions: Required = { }, modelsBaseUrl: '', modelsLoadStrategy: 'waitAll', - poiConfig: { - primary: { - fontSize: 14, - fontColor: '#000000', - }, - secondary: { - fontSize: 14, - fontColor: '#000000', - }, - }, floorsControl: { position: 'centerLeft', }, diff --git a/src/labelGroups.ts b/src/labelGroups.ts new file mode 100644 index 0000000..f265b90 --- /dev/null +++ b/src/labelGroups.ts @@ -0,0 +1,61 @@ +import type { Map as MapGL, Label } from '@2gis/mapgl/types'; +import type { BuildingState, LabelGroupOptions } from './types/plugin'; +import { GltfPlugin } from './plugin'; +// import { pluginEvents } from './constants'; +// import { createLabelEvenData } from './utils/events'; + +export class LabelGroups { + private labelsByGroupId: Map = new Map(); + + constructor(private map: MapGL, private plugin: GltfPlugin) {} + + public add(groupOptions: LabelGroupOptions, state?: BuildingState) { + const { id } = groupOptions; + if (this.labelsByGroupId.has(id)) { + console.error( + `Poi group with id "${id}" already exists. Please use different identifiers for poi groups`, + ); + return; + } + + const { image, minZoom, maxZoom, fontColor: color, fontSize } = groupOptions; + const labels = groupOptions.labels.map((labelOptions) => { + const { coordinates, text, userData } = labelOptions; + const label = new mapgl.Label(this.map, { + coordinates, // + label.elevation ?? groupOptions.elevation + text, + userData, + image, + minZoom, + maxZoom, + color, + fontSize, + relativeAnchor: [0.5, 1], + zIndex: 1, // чтобы были выше моделей + }); + + // pluginEvents.forEach((eventType) => { + // label.on(eventType, (ev) => { + // this.plugin.emit(eventType, createLabelEvenData(ev, labelOptions, state)); + // }); + // }); + + return label; + }); + + this.labelsByGroupId.set(id, labels); + } + + public remove(id: string) { + const labels = this.labelsByGroupId.get(id); + this.labelsByGroupId.delete(id); + labels?.forEach((label) => label.destroy()); + } + + public destroy() { + this.labelsByGroupId.forEach((labels) => { + labels.forEach((label) => label.destroy()); + }); + this.labelsByGroupId.clear(); + } +} diff --git a/src/plugin.ts b/src/plugin.ts index fb7fbd5..039bab1 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -1,7 +1,7 @@ -import type { Map as MapGL } from '@2gis/mapgl/types'; +import type { DynamicObjectEventTable, GltfModel, Map as MapGL } from '@2gis/mapgl/types'; import type { BuildingOptions } from './types/realtyScene'; import type { GltfPluginEventTable } from './types/events'; -import type { Id, PluginOptions, ModelOptions, BuildingState } from './types/plugin'; +import type { PluginOptions, ModelOptions, BuildingState, LabelGroupOptions } from './types/plugin'; import { applyOptionalDefaults } from './utils/common'; import { Evented } from './external/evented'; @@ -9,11 +9,12 @@ import { defaultOptions } from './defaultOptions'; import { concatUrl, isAbsoluteUrl } from './utils/url'; import { createModelEventData } from './utils/events'; import { RealtyScene } from './realtyScene/realtyScene'; -import { GROUND_COVERING_LAYER } from './constants'; import { ModelStatus } from './types/plugin'; +import { pluginEvents } from './constants'; +import { LabelGroups } from './labelGroups'; interface Model { - instance: any; // GltfModel + instance: GltfModel; options: ModelOptions; isLoaded: boolean; } @@ -29,7 +30,8 @@ const MODEL_DEFAULTS = { export class GltfPlugin extends Evented { private map: MapGL; private options: Required; - private models: Map; + private models: Map; + private labelGroups: LabelGroups; private realtyScene?: RealtyScene; /** @@ -61,15 +63,18 @@ export class GltfPlugin extends Evented { this.map = map; this.options = applyOptionalDefaults(pluginOptions ?? {}, defaultOptions); this.models = new Map(); + this.labelGroups = new LabelGroups(this.map, this); + } - map.on('styleload', () => { - this.map.addLayer(GROUND_COVERING_LAYER); // мб унести отсюда в RealtyScene, нужно подумать - // this.poiGroups.onMapStyleUpdate(); + public destroy() { + this.models.forEach((model) => { + model.instance.destroy(); }); + this.models.clear(); + this.labelGroups.destroy(); + this.realtyScene?.destroy(); } - // public destroy() {} - public setOptions(pluginOptions: Pick, 'groundCoveringColor'>) { Object.keys(pluginOptions).forEach((option) => { switch (option) { @@ -86,7 +91,7 @@ export class GltfPlugin extends Evented { return this.addModels([modelToLoad], hideOnLoad ? [] : [modelToLoad.modelId]); } - public async addModels(modelsToLoad: ModelOptions[], modelIdsToShow?: Id[]) { + public async addModels(modelsToLoad: ModelOptions[], modelIdsToShow?: string[]) { const loadingModels = modelsToLoad .filter((options) => { if (this.models.has(options.modelId)) { @@ -138,17 +143,15 @@ export class GltfPlugin extends Evented { this.models.set(options.modelId, model); return new Promise((resolve) => { - instance.once('modelloaded', () => { + instance.once('modelloaded' as keyof DynamicObjectEventTable, () => { model.isLoaded = true; resolve(model); }); - (['click', 'mousemove', 'mouseover', 'mouseout'] as const).forEach( - (eventType) => { - instance.on(eventType, (ev) => { - this.emit(eventType, createModelEventData(ev, options)); - }); - }, - ); + pluginEvents.forEach((eventType) => { + instance.on(eventType, (ev) => { + this.emit(eventType, createModelEventData(ev, options)); + }); + }); }); }); @@ -165,7 +168,7 @@ export class GltfPlugin extends Evented { }); } - public getModelStatus(id: Id) { + public getModelStatus(id: string) { const model = this.models.get(id); if (!model) { return ModelStatus.NoModel; @@ -174,7 +177,7 @@ export class GltfPlugin extends Evented { return !model.isLoaded ? ModelStatus.Loading : ModelStatus.Loaded; } - public removeModel(id: Id) { + public removeModel(id: string) { const model = this.models.get(id); if (model) { model.instance.destroy(); @@ -182,35 +185,39 @@ export class GltfPlugin extends Evented { } } - public removeModels(ids: Id[]) { + public removeModels(ids: string[]) { ids.forEach((id) => this.removeModel(id)); } - public showModel(id: Id) { + public showModel(id: string) { this.models.get(id)?.instance.show(); } - public showModels(ids: Id[]) { + public showModels(ids: string[]) { ids.forEach((id) => this.showModel(id)); } - public hideModel(id: Id) { + public hideModel(id: string) { this.models.get(id)?.instance.hide(); } - public hideModels(ids: Id[]) { + public hideModels(ids: string[]) { ids.forEach((id) => this.hideModel(id)); } + public addLabelGroup(options: LabelGroupOptions, state?: BuildingState) { + this.labelGroups.add(options, state); + } + + public removeLabelGroup(id: string) { + this.labelGroups.remove(id); + } + public async addRealtyScene(scene: BuildingOptions[], state?: BuildingState) { this.realtyScene = new RealtyScene(this, this.map, this.options); return this.realtyScene.init(scene, state); } - // public showRealtyScene() {} - - // public hideRealtyScene() {} - public removeRealtyScene() { this.realtyScene?.destroy(); this.realtyScene = undefined; diff --git a/src/poiGroups.ts b/src/poiGroups.ts deleted file mode 100644 index 0f69cb5..0000000 --- a/src/poiGroups.ts +++ /dev/null @@ -1,155 +0,0 @@ -import type { FeatureCollection, Feature, Point } from 'geojson'; -import type { Map as MapGL, GeoJsonSource } from '@2gis/mapgl/types'; - -import type { Id, PluginOptions, BuildingState, PoiGroupOptions, PoiOptions } from './types/plugin'; -import type { PoiGeoJsonProperties } from './types/events'; - -type FeaturePoint = Feature; - -export class PoiGroups { - private poiSources = new Map(); - private activePoiGroupOptions?: PoiGroupOptions; - - constructor(private map: MapGL, private poiConfig: PluginOptions['poiConfig']) {} - - public onMapStyleUpdate() { - this.map.addIcon('km_pillar_gray_border', { - url: 'https://disk.2gis.com/styles/d7e8aed1-4d3f-472a-a1e4-337f4b31ab8a/km_pillar_gray_border', - // @ts-ignore - width: 38, - height: 38, - stretchX: [[4, 24]], - stretchY: [[4, 24]], - }); - - if (this.activePoiGroupOptions) { - this.addPoiStyleLayer(this.activePoiGroupOptions); - } - } - - public async add(groupOptions: PoiGroupOptions, state?: BuildingState) { - this.activePoiGroupOptions = groupOptions; - const { id, data } = groupOptions; - const actualId = String(id); - if (this.poiSources.get(actualId) !== undefined) { - throw new Error( - `Poi group with id "${actualId}" already exists. Please use different identifiers for poi groups`, - ); - } - - const geoJson = this.createGeoJson(data, groupOptions, state); - - // create source with poi - const source = new mapgl.GeoJsonSource(this.map, { - data: geoJson, - attributes: { - dataType: actualId, - }, - }); - this.poiSources.set(actualId, source); - - // add style layer for poi - this.addPoiStyleLayer(groupOptions); - } - - public remove(origId: Id) { - this.activePoiGroupOptions = undefined; - const id = String(origId); - const source = this.poiSources.get(id); - this.poiSources.delete(id); - source?.destroy(); - this.map.removeLayer('plugin-poi-' + id); - } - - private createGeoJson( - poiOptions: PoiOptions[], - groupOptions: PoiGroupOptions, - state?: BuildingState, - ): FeatureCollection { - const { elevation } = groupOptions; - const features: FeaturePoint[] = poiOptions.map((opts) => ({ - type: 'Feature', - properties: { - // main properties - type: 'immersive_poi', - label: opts.label, - userData: opts.userData, - elevation: elevation, - coordinates: opts.coordinates, - // auxilary properties - modelId: state?.modelId, - floorId: state?.floorId, - }, - geometry: { - type: 'Point', - coordinates: opts.coordinates, - }, - })); - - return { - type: 'FeatureCollection', - features, - }; - } - - private addPoiStyleLayer(groupOptions: PoiGroupOptions) { - const { id, type, minZoom = -Infinity, maxZoom = +Infinity } = groupOptions; - let { fontSize, fontColor } = groupOptions; - const actualId = String(id); - let style; - - if (fontColor === undefined) { - fontColor = - type === 'primary' - ? this.poiConfig?.primary?.fontColor ?? '#3a3a3a' - : this.poiConfig?.secondary?.fontColor ?? '#3a3a3a'; - } - if (fontSize === undefined) { - fontSize = - type === 'primary' - ? this.poiConfig?.primary?.fontSize ?? 14 - : this.poiConfig?.secondary?.fontSize ?? 12; - } - - if (type === 'primary') { - style = { - iconPriority: 7000, - allowElevation: true, - elevation: ['get', 'elevation'], - iconImage: 'km_pillar_gray_border', - iconAnchor: [0.5, 1], - iconOffset: [0, 0], - iconTextFont: 'Noto_Sans', - iconTextColor: fontColor, - iconTextField: ['get', 'label'], - iconTextPadding: [5, 10, 5, 10], - iconTextFontSize: fontSize, - duplicationSpacing: 1, - }; - } else { - style = { - allowElevation: true, - elevation: ['get', 'elevation'], - duplicationSpacing: 1, - textField: ['get', 'label'], - textFont: 'Noto_Sans', - textFontSize: fontSize, - textColor: fontColor, - textPriority: 6000, - }; - } - - this.map.addLayer({ - type: 'point', - id: 'plugin-poi-' + actualId, - filter: [ - 'all', - ['match', ['sourceAttr', 'dataType'], [actualId], true, false], - ['match', ['get', 'type'], ['immersive_poi'], true, false], - ], - style, - minzoom: minZoom, - maxzoom: maxZoom, - }); - } -} diff --git a/src/realtyScene/realtyScene.ts b/src/realtyScene/realtyScene.ts index 3cba58f..2189e21 100644 --- a/src/realtyScene/realtyScene.ts +++ b/src/realtyScene/realtyScene.ts @@ -7,38 +7,37 @@ import classes from './realtyScene.module.css'; import { ModelStatus, type BuildingState, - type Id, type ModelOptions, type PluginOptions, } from '../types/plugin'; import type { BuildingOptions, MapOptions, - BuildingFloorOptions, PopupOptions, + BuildingOptionsInternal, + BuildingFloorOptionsInternal, + RealtySceneState, } from '../types/realtyScene'; -import type { FloorLevel, FloorChangeEvent } from '../control/types'; -import type { GltfPluginModelEvent, GltfPluginPoiEvent } from '../types/events'; -import { GROUND_COVERING_SOURCE_DATA, GROUND_COVERING_SOURCE_PURPOSE } from '../constants'; - -interface RealtySceneState { - activeModelId?: Id; - - // id здания мапится на опции здания или опции этажа этого здания - buildingVisibility: Map; -} - -type BuildingOptionsInternal = Omit & { - floors: FloorLevel[]; -}; -type BuildingFloorOptionsInternal = BuildingFloorOptions & { - buildingOptions: ModelOptions; -}; +import type { FloorChangeEvent } from '../control/types'; +import type { GltfPluginModelEvent, GltfPluginLabelEvent } from '../types/events'; +import { + GROUND_COVERING_SOURCE_DATA, + GROUND_COVERING_SOURCE_PURPOSE, + GROUND_COVERING_LAYER, + GROUND_COVERING_LAYER_ID, +} from '../constants'; +import { + getBuildingModelOptions, + getFloorModelId, + getFloorModelOptions, + getFloorPoiGroupId, + isObject, +} from '../utils/realtyScene'; export class RealtyScene { - private buildings = new Map(); - private floors = new Map(); - private undergroundFloors = new Set(); + private buildings = new Map(); + private floors = new Map(); + private undergroundFloors = new Set(); private state: RealtySceneState = { activeModelId: undefined, buildingVisibility: new Map(), @@ -48,8 +47,6 @@ export class RealtyScene { private control: GltfFloorControl; private popup?: HtmlMarker; - // private poiGroups: PoiGroups; - constructor( private plugin: GltfPlugin, private map: MapGL, @@ -57,7 +54,6 @@ export class RealtyScene { ) { const { position } = this.options.floorsControl; this.control = new GltfFloorControl(this.map, { position }); - // this.poiGroups = new PoiGroups(this.map, this.options.poiConfig); this.groundCoveringSource = new mapgl.GeoJsonSource(map, { maxZoom: 2, data: GROUND_COVERING_SOURCE_DATA, @@ -65,9 +61,12 @@ export class RealtyScene { purpose: GROUND_COVERING_SOURCE_PURPOSE, }, }); + + this.map.addLayer(GROUND_COVERING_LAYER); + map.on('styleload', this.onStyleLoad); } - private getBuildingModelId(id: Id | undefined) { + private getBuildingModelId(id: string | undefined) { if (id === undefined) { return; } @@ -84,39 +83,61 @@ export class RealtyScene { private setState(newState: RealtySceneState) { const prevState = this.state; - const buildingVisibility: RealtySceneState['buildingVisibility'] = new Map(); + + // т.к. стейт может меняться асинхронно и иногда нужно показывать + // предыдущую модель некоторое время, реальный стейт заполняется тут, + // а выставление нужного будет отложено на время загрузки модели + const buildingVisibility: Map = new Map(); + this.buildings.forEach((_, buildingId) => { const prevModelOptions = prevState.buildingVisibility.get(buildingId); const newModelOptions = newState.buildingVisibility.get(buildingId); + // если опции не изменились, то ничего не делаем if (prevModelOptions?.modelId === newModelOptions?.modelId) { buildingVisibility.set(buildingId, prevModelOptions); return; } - if ( - prevModelOptions && - (!newModelOptions || - this.plugin.getModelStatus(newModelOptions.modelId) === ModelStatus.Loaded) - ) { - this.plugin.hideModel(prevModelOptions.modelId); - buildingVisibility.set(buildingId, undefined); - + if (prevModelOptions) { + // если нужно отобразить подземный этаж, но его модель не готова, то ничего не скрываем if ( - prevState.activeModelId !== undefined && - prevState.activeModelId === prevModelOptions.modelId && - this.undergroundFloors.has(prevModelOptions.modelId) + !newModelOptions && + newState.activeModelId !== undefined && + this.undergroundFloors.has(newState.activeModelId) && + this.plugin.getModelStatus(newState.activeModelId) !== ModelStatus.Loaded ) { - this.switchOffGroundCovering(); + buildingVisibility.set(buildingId, prevModelOptions); + } else if ( + // если новая модель готова или предыдущую нужно просто скрыть, то скрываем ее + !newModelOptions || + this.plugin.getModelStatus(newModelOptions.modelId) === ModelStatus.Loaded + ) { + this.plugin.hideModel(prevModelOptions.modelId); + buildingVisibility.set(buildingId, undefined); + + if (this.undergroundFloors.has(prevModelOptions.modelId)) { + this.switchOffGroundCovering(); + } + + const floorOptions = this.floors.get(prevModelOptions.modelId); + if (floorOptions) { + floorOptions.labelGroups?.forEach((group) => { + this.plugin.removeLabelGroup(group.id); + }); + } } } if (newModelOptions) { const modelStatus = this.plugin.getModelStatus(newModelOptions.modelId); + + // если новая модель готова, то показываем ее if (modelStatus === ModelStatus.Loaded) { this.plugin.showModel(newModelOptions.modelId); buildingVisibility.set(buildingId, newModelOptions); + // если модель активна, то применяем опции карты и включаем подложку, если нужно if ( newState.activeModelId !== undefined && newState.activeModelId === newModelOptions.modelId @@ -132,6 +153,16 @@ export class RealtyScene { if (this.undergroundFloors.has(newModelOptions.modelId)) { this.switchOnGroundCovering(); } + + const floorOptions = this.floors.get(newModelOptions.modelId); + if (floorOptions) { + floorOptions.labelGroups?.forEach((group) => { + this.plugin.addLabelGroup(group, { + buildingId, + floorId: floorOptions.id, + }); + }); + } } } else { if (modelStatus === ModelStatus.NoModel) { @@ -140,39 +171,18 @@ export class RealtyScene { return; } - const currModelOptions = this.state.buildingVisibility.get(buildingId); - if (currModelOptions) { - this.plugin.hideModel(currModelOptions.modelId); - if (this.undergroundFloors.has(currModelOptions.modelId)) { - this.switchOffGroundCovering(); - } - } - - this.plugin.showModel(newModelOptions.modelId); - this.state.buildingVisibility.set(buildingId, newModelOptions); - - const options = - this.buildings.get(newModelOptions.modelId) ?? - this.floors.get(newModelOptions.modelId); - - if (options) { - this.setMapOptions(options.mapOptions); - } - - if (this.undergroundFloors.has(newModelOptions.modelId)) { - this.switchOnGroundCovering(); - } + // откладываем выставление нужного стейта до момента загрузки модели + this.setState(newState); }); } + // если новые модели не готовы, то пока показываем предыдущие buildingVisibility.set(buildingId, prevModelOptions); } } }); - // this.addFloorPoi(activeFloor); - // this.clearPoiGroups(); - + // контрол реагирует на изменения стейта сразу, без учета загрузки модели, т.к. завязан на здание в целом const prevBuildingModelId = this.getBuildingModelId(prevState.activeModelId); const newBuildingModelId = this.getBuildingModelId(newState.activeModelId); @@ -204,7 +214,7 @@ export class RealtyScene { public async init(scene: BuildingOptions[], state?: BuildingState) { // Приводим стейт пользователя к внутреннему виду id - let activeModelId: Id | undefined = state + let activeModelId: string | undefined = state ? state.floorId ? getFloorModelId(state.buildingId, state.floorId) : state.buildingId @@ -228,6 +238,10 @@ export class RealtyScene { this.floors.set(floorModelId, { ...floor, + labelGroups: (floor.labelGroups ?? []).map((group) => ({ + ...group, + id: getFloorPoiGroupId(building.modelId, floor.id, group.id), + })), buildingOptions: buildingOptions, }); @@ -246,8 +260,8 @@ export class RealtyScene { ? activeModelId : undefined; - const modelsToLoad: Map = new Map(); - const buildingVisibility: Map = new Map(); + const modelsToLoad: Map = new Map(); + const buildingVisibility: Map = new Map(); this.buildings.forEach((options, id) => { const modelOptions = getBuildingModelOptions(options); @@ -257,6 +271,7 @@ export class RealtyScene { if (activeModelId) { const floorOptions = this.floors.get(activeModelId); + if (floorOptions) { if (this.undergroundFloors.has(activeModelId)) { buildingVisibility.clear(); // показываем только подземный этаж @@ -300,14 +315,19 @@ export class RealtyScene { } public destroy() { + this.map.off('styleload', this.onStyleLoad); this.plugin.off('click', this.onSceneClick); this.plugin.off('mouseover', this.onSceneMouseOver); this.plugin.off('mouseout', this.onSceneMouseOut); this.control.off('floorchange', this.floorChangeHandler); + this.floors.forEach(({ labelGroups }) => { + labelGroups?.forEach(({ id }) => { + this.plugin.removeLabelGroup(id); + }); + }); this.plugin.removeModels([...this.buildings.keys(), ...this.floors.keys()]); - - // this.clearPoiGroups(); + this.map.removeLayer(GROUND_COVERING_LAYER_ID); this.groundCoveringSource.destroy(); this.undergroundFloors.clear(); @@ -347,7 +367,11 @@ export class RealtyScene { } } - private onSceneMouseOut = (ev: GltfPluginPoiEvent | GltfPluginModelEvent) => { + private onStyleLoad() { + this.map.addLayer(GROUND_COVERING_LAYER); + } + + private onSceneMouseOut = (ev: GltfPluginLabelEvent | GltfPluginModelEvent) => { if (ev.target.type !== 'model') { return; } @@ -355,8 +379,8 @@ export class RealtyScene { this.popup?.destroy(); }; - private onSceneMouseOver = ({ target }: GltfPluginPoiEvent | GltfPluginModelEvent) => { - if (target.type === 'poi' || target.modelId === undefined) { + private onSceneMouseOver = ({ target }: GltfPluginLabelEvent | GltfPluginModelEvent) => { + if (target.type === 'label' || target.modelId === undefined) { return; } @@ -369,16 +393,17 @@ export class RealtyScene { coordinates: options.popupOptions.coordinates, html: getPopupHtml(options.popupOptions), interactive: false, + zIndex: 1000, }); }; - private onSceneClick = ({ target }: GltfPluginPoiEvent | GltfPluginModelEvent) => { + private onSceneClick = ({ target }: GltfPluginLabelEvent | GltfPluginModelEvent) => { if (target.type === 'model') { const options = this.buildings.get(target.modelId); if (options) { this.buildingClickHandler(target.modelId); } - } else if (target.type === 'poi') { + } else if (target.type === 'label') { const userData = target.data.userData; if (isObject(userData) && typeof userData.url === 'string') { const a = document.createElement('a'); @@ -390,7 +415,7 @@ export class RealtyScene { }; private floorChangeHandler = (ev: FloorChangeEvent) => { - const buildingVisibility: Map = new Map(); + const buildingVisibility: Map = new Map(); this.buildings.forEach((options, id) => { buildingVisibility.set(id, getBuildingModelOptions(options)); }); @@ -420,14 +445,14 @@ export class RealtyScene { } }; - private buildingClickHandler = (modelId: Id) => { + private buildingClickHandler = (modelId: string) => { const buildingOptions = this.buildings.get(modelId); if (!buildingOptions) { return; } let activeModelId = modelId; - const buildingVisibility: Map = new Map(); + const buildingVisibility: Map = new Map(); this.buildings.forEach((options, id) => { buildingVisibility.set(id, getBuildingModelOptions(options)); }); @@ -455,55 +480,6 @@ export class RealtyScene { }); }; - // private addFloorPoi(floorOptions?: BuildingFloorOptions) { - // if (floorOptions === undefined) { - // return; - // } - - // this.activeModelId = floorOptions.id; - - // this.setMapOptions(floorOptions?.mapOptions); - - // this.clearPoiGroups(); - - // floorOptions.poiGroups?.forEach((poiGroup) => { - // if (this.activeBuilding?.modelId) { - // this.plugin.addPoiGroup(poiGroup, { - // modelId: this.activeBuilding?.modelId, - // floorId: floorOptions.id, - // }); - // this.activePoiGroupIds.push(poiGroup.id); - // } - // }); - // } - - // private clearPoiGroups() { - // this.activePoiGroupIds.forEach((id) => { - // this.plugin.removePoiGroup(id); - // }); - - // this.activePoiGroupIds = []; - // } - - // /** - // * Add the group of poi to the map - // * - // * @param options Options of the group of poi to add to the map - // * @param state State of the active building to connect with added the group of poi - // */ - // public async addPoiGroup(options: PoiGroupOptions, state?: BuildingState) { - // this.poiGroups.add(options, state); - // } - - // /** - // * Remove the group of poi from the map - // * - // * @param id Identifier of the group of poi to remove - // */ - // public removePoiGroup(id: Id) { - // this.poiGroups.remove(id); - // } - private switchOffGroundCovering() { const attrs = { ...this.groundCoveringSource.getAttributes() }; delete attrs['color']; @@ -518,54 +494,8 @@ export class RealtyScene { } } -function getBuildingModelOptions(building: BuildingOptionsInternal): ModelOptions { - return { - modelId: building.modelId, - coordinates: building.coordinates, - modelUrl: building.modelUrl, - rotateX: building.rotateX, - rotateY: building.rotateY, - rotateZ: building.rotateZ, - offsetX: building.offsetX, - offsetY: building.offsetY, - offsetZ: building.offsetZ, - scale: building.scale, - linkedIds: building.linkedIds, - interactive: building.interactive, - }; -} - -function getFloorModelOptions({ - buildingOptions, - id, - modelUrl, -}: BuildingFloorOptionsInternal): ModelOptions { - return { - modelId: getFloorModelId(buildingOptions.modelId, id), - coordinates: buildingOptions.coordinates, - modelUrl, - rotateX: buildingOptions.rotateX, - rotateY: buildingOptions.rotateY, - rotateZ: buildingOptions.rotateZ, - offsetX: buildingOptions.offsetX, - offsetY: buildingOptions.offsetY, - offsetZ: buildingOptions.offsetZ, - scale: buildingOptions.scale, - linkedIds: buildingOptions.linkedIds, - interactive: buildingOptions.interactive, - }; -} - -function getFloorModelId(buildingModelId: string, floorId: string) { - return `${buildingModelId}_${floorId}`; -} - const getPopupHtml = ({ description, title }: PopupOptions) => `

${title}

${description ? `

${description}

` : ''}
`; - -function isObject(value: unknown): value is Record { - return typeof value === 'object' && value !== null && !Array.isArray(value); -} diff --git a/src/types/events.ts b/src/types/events.ts index 6db4834..22482c0 100644 --- a/src/types/events.ts +++ b/src/types/events.ts @@ -1,21 +1,4 @@ -import type { Id, ModelOptions, PoiOptions } from './plugin'; - -export type PoiGeoJsonProperties = PoiOptions & { - /** - * Identifier of the building's model - */ - modelId?: Id; - - /** - * Identifier of the floor's model - */ - floorId?: Id; - - /** - * Type of the poi - */ - type?: string; -}; +import type { ModelOptions, LabelOptions } from './plugin'; export interface ModelTarget { /** @@ -31,29 +14,29 @@ export interface ModelTarget { /** * Identifier of the building's or floor's model */ - modelId: Id; + modelId: string; } -export interface PoiTarget { +export interface LabelTarget { /** * Type of the target */ - type: 'poi'; + type: 'label'; /** * The targeted poi */ - data: PoiGeoJsonProperties; + data: LabelOptions; /** * Identifier of the building's model */ - modelId?: Id; + buildingId?: string; /** * Identifier of the current floor */ - floorId?: Id; + floorId?: string; } /** @@ -79,11 +62,11 @@ interface GltfPluginPointerEvent { /** * The event type for pointer-related plugin events emitted by the poi */ -export interface GltfPluginPoiEvent extends GltfPluginPointerEvent { +export interface GltfPluginLabelEvent extends GltfPluginPointerEvent { /** * Target of the poi event */ - target: PoiTarget; + target: LabelTarget; } /** @@ -103,17 +86,17 @@ export interface GltfPluginEventTable { /** * Emitted when model or poi are clicked */ - click: GltfPluginPoiEvent | GltfPluginModelEvent; + click: GltfPluginLabelEvent | GltfPluginModelEvent; /** * Emitted when the user moves the pointer over the model or the poi */ - mousemove: GltfPluginPoiEvent | GltfPluginModelEvent; + mousemove: GltfPluginLabelEvent | GltfPluginModelEvent; /** * Emitted when the user hovers over the model or the poi */ - mouseover: GltfPluginPoiEvent | GltfPluginModelEvent; + mouseover: GltfPluginLabelEvent | GltfPluginModelEvent; /** * Emitted when the user moves the mouse away from the model or the poi */ - mouseout: GltfPluginPoiEvent | GltfPluginModelEvent; + mouseout: GltfPluginLabelEvent | GltfPluginModelEvent; } diff --git a/src/types/plugin.ts b/src/types/plugin.ts index ef78479..bd6ea2d 100644 --- a/src/types/plugin.ts +++ b/src/types/plugin.ts @@ -1,18 +1,4 @@ -export type Id = string; - -/** - * Configuration of the poi - */ -export interface PoiConfigGranular { - /** - * Size of the font - */ - fontSize?: number; - /** - * Color of the font - */ - fontColor?: string; -} +import type { LabelImage } from '@2gis/mapgl/types'; /** * Possible positions of the control. @@ -45,7 +31,7 @@ export interface HoverOptions { * Hover color * @default '#ffffff' */ - color?: string; + color: string; } /** @@ -62,19 +48,6 @@ export interface PluginOptions { * - waitAll - show models only when all models are ready for the rendering */ modelsLoadStrategy?: 'dontWaitAll' | 'waitAll'; - /** - * Configuration of poi - */ - poiConfig?: { - /** - * Configuration the primary poi - */ - primary?: PoiConfigGranular; - /** - * Configuration the secondary poi - */ - secondary?: PoiConfigGranular; - }; /** * Settings for floors' control */ @@ -111,7 +84,7 @@ export interface ModelOptions { /** * Identifier of the model should be unique for every model */ - modelId: Id; + modelId: string; /** * Geographical coordinates [longitude, latitude] */ @@ -165,7 +138,7 @@ export interface ModelOptions { /** * Options for a poi */ -export interface PoiOptions { +export interface LabelOptions { /** * Coordinate of the poi */ @@ -177,7 +150,7 @@ export interface PoiOptions { /** * Elevation of the poi */ - label: string; + text: string; /** * User specific data */ @@ -187,15 +160,11 @@ export interface PoiOptions { /** * Options for a poi group */ -export interface PoiGroupOptions { +export interface LabelGroupOptions { /** * Identifier of the poi group to add */ - id: Id; - /** - * Type of the poi - */ - type: 'primary' | 'secondary'; + id: string; /** * Elevation of the group of poi */ @@ -203,7 +172,11 @@ export interface PoiGroupOptions { /** * Array of poi to add on the map */ - data: PoiOptions[]; + labels: LabelOptions[]; + /** + * Type of the poi + */ + image?: LabelImage; /** * Minimum display styleZoom of the poi group */ diff --git a/src/types/realtyScene.ts b/src/types/realtyScene.ts index e566032..c9d3328 100644 --- a/src/types/realtyScene.ts +++ b/src/types/realtyScene.ts @@ -1,4 +1,5 @@ -import type { Id, PoiGroupOptions, ModelOptions } from './plugin'; +import { FloorLevel } from '../control/types'; +import type { ModelOptions, LabelGroupOptions } from './plugin'; /** * Options for the map @@ -29,7 +30,7 @@ export interface BuildingFloorOptions { /** * Identifier of the floor's plan */ - id: Id; + id: string; /** * Text to add to the floors' control */ @@ -45,7 +46,7 @@ export interface BuildingFloorOptions { /** * List of poi groups connected with the floor's plan */ - poiGroups?: PoiGroupOptions[]; + labelGroups?: LabelGroupOptions[]; /** * Map's options to apply after selecting the particular floor */ @@ -93,3 +94,18 @@ export interface BuildingOptions extends ModelOptions { */ popupOptions?: PopupOptions; } + +export interface RealtySceneState { + activeModelId?: string; + + // id здания мапится на опции здания или опции этажа этого здания + buildingVisibility: Map; +} + +export type BuildingOptionsInternal = Omit & { + floors: FloorLevel[]; +}; + +export type BuildingFloorOptionsInternal = BuildingFloorOptions & { + buildingOptions: ModelOptions; +}; diff --git a/src/utils/events.ts b/src/utils/events.ts index acab555..3845889 100644 --- a/src/utils/events.ts +++ b/src/utils/events.ts @@ -1,8 +1,15 @@ -import { MapPointerEvent } from '@2gis/mapgl/types'; -import { GltfPluginModelEvent, Id, ModelOptions, ModelTarget } from '../types'; +import type { DynamicObjectPointerEvent, GltfModel, Label } from '@2gis/mapgl/types'; +import { + BuildingState, + GltfPluginLabelEvent, + GltfPluginModelEvent, + LabelOptions, + LabelTarget, + ModelOptions, +} from '../types'; export const createModelEventData = ( - ev: MapPointerEvent, + ev: DynamicObjectPointerEvent, data: ModelOptions, ): GltfPluginModelEvent => ({ originalEvent: ev.originalEvent, @@ -14,3 +21,29 @@ export const createModelEventData = ( data, }, }); + +export const createLabelEvenData = ( + ev: DynamicObjectPointerEvent