@@ -1,5 +1,5 @@
import { forwardRef , lazy , Suspense , useCallback , useEffect , useImperativeHandle , useMemo , useRef , useState , type CSSProperties , type KeyboardEvent , type PointerEvent } from "react" ;
import { Button , Checker , ColorField , ControlRow , Dropdown , Icon , IconButton , Inspector , InspectorSelectField , RangeControl , SegmentedControl , Window , WorkspaceWindow } from "@nodedc/ui-react" ;
import { Button , Checker , ColorField , ControlRow , Dropdown , Icon , IconButton , Inspector , InspectorSelectField , RangeControl , SegmentedControl , WorkspaceWindow } from "@nodedc/ui-react" ;
import type { SelectOption , WorkspaceWindowRect } from "@nodedc/ui-react" ;
import type {
CameraSpiralState ,
@@ -12,6 +12,7 @@ import type {
GridSectorSelection ,
} from "./CesiumMapRenderer.js" ;
import { mapRuntimeEntityId , useMapDataProductRuntime } from "./useMapDataProductRuntime.js" ;
import type { MapRuntimeFact } from "./useMapDataProductRuntime.js" ;
import {
compareMapRuntimeFacts ,
mapFactMatchesFilters ,
@@ -48,6 +49,7 @@ import {
graticuleSectorAt ,
graticuleSectorSummary ,
localSectorAt ,
localSectorAtGeodetic ,
localSectorSummary ,
localVolumeAt ,
type GraticuleSectorAddress ,
@@ -309,6 +311,50 @@ function gridSectorCenterLabel(selection: GridSectorSelection) {
: ` ${ formatGridMetric ( selection . center . latitude , 6 ) } °, ${ formatGridMetric ( selection . center . longitude , 6 ) } ° ` ;
}
const MAP_SCOPE_PROVIDER_FIELD = "position_source" ;
const MAP_SCOPE_OBJECT_KIND_FIELD = "object_kind" ;
const MAP_SCOPE_MISSING_VALUE = "__nodedc_missing__" ;
function normalizedSectorScopeValue ( value : unknown ) {
if ( typeof value !== "string" ) return null ;
const normalized = value . trim ( ) ;
if ( ! normalized || normalized . length > 120 || /[\u0000-\u001f\u007f]/ . test ( normalized ) ) return null ;
return normalized ;
}
function sectorScopeValueLabel ( value : string ) {
if ( value === MAP_SCOPE_MISSING_VALUE ) return "Не указано" ;
return value . replace ( /[_-]+/g , " " ) . replace ( /\s+/g , " " ) . trim ( ) ;
}
function mapFactSectorScopeValue ( fact : MapRuntimeFact , field : string ) {
return normalizedSectorScopeValue ( fact . attributes [ field ] ) ? ? MAP_SCOPE_MISSING_VALUE ;
}
function mapFactInsideGridSector (
fact : MapRuntimeFact ,
selection : GridSectorSelection ,
profiles : SectorGridLodProfile [ ] ,
origin : { latitude : number ; longitude : number } ,
) {
if ( fact . geometry ? . type !== "Point" ) return false ;
const [ longitude , latitude ] = fact . geometry . coordinates ;
const profile = profiles [ selection . lod - 1 ] ;
if ( ! profile || profile . mode !== selection . mode ) return false ;
if ( selection . mode === "graticule" ) {
return graticuleSectorAt ( { longitude , latitude } , {
lod : selection.lod ,
stepDegrees : profile.graticuleStepDegrees ,
} ) . id === selection . id ;
}
return localSectorAtGeodetic ( { longitude , latitude } , {
lod : selection.lod ,
originLatitude : origin.latitude ,
originLongitude : origin.longitude ,
stepMeters : profile.stepKm * 1 _000 ,
} ) . id === selection . id ;
}
function beginGatewayHealthEpoch ( order : GatewayHealthOrder ) {
order . nextEpoch += 1 ;
order . latestStartedEpoch = order . nextEpoch ;
@@ -415,6 +461,8 @@ export type MapSubjectWindowState = {
zIndex : number ;
} ;
type MapWorkspaceWindowId = "settings" | "layers" | "sector" | "subject-card" | ` binding: ${ string } ` ;
export type MapSubjectState = {
bindingId : string ;
visible : boolean ;
@@ -594,17 +642,31 @@ function defaultSubjectWindowState(index: number): MapSubjectWindowState {
}
const defaultLayersWindowRect : WorkspaceWindowRect = {
x : 1024 ,
x : 24 ,
y : 72 ,
width : 336 ,
height : 500 ,
} ;
const defaultSettingsWindowRect : WorkspaceWindowRect = {
x : 930 ,
y : 72 ,
width : 420 ,
height : 530 ,
} ;
const defaultSectorWindowRect : WorkspaceWindowRect = {
x : 24 ,
y : 72 ,
width : 380 ,
height : 530 ,
} ;
const defaultSubjectCardRect : WorkspaceWindowRect = {
x : 940 ,
y : 52 ,
y : 72 ,
width : 390 ,
height : 560 ,
height : 520 ,
} ;
function initialSubjectState ( bindings : MapDataProductBinding [ ] , saved : MapSubjectState [ ] | undefined ) {
@@ -665,10 +727,12 @@ export const MapFixturePreview = forwardRef<MapFixturePreviewHandle, {
const [ subjectCardRect , setSubjectCardRect ] = useState < WorkspaceWindowRect > ( defaultSubjectCardRect ) ;
const [ subjectCardMaximized , setSubjectCardMaximized ] = useState ( false ) ;
const [ subjectCardZIndex , setSubjectCardZIndex ] = useState ( 140 ) ;
const [ subjectCardActive , setSubjectCardActive ] = useState ( false ) ;
const [ subjectCardTabId , setSubjectCardTabId ] = useState ( "overview" ) ;
const [ expandedFacetRows , setExpandedFacetRows ] = useState < Record < string , boolean > > ( { } ) ;
const [ inspectorOpen , setInspectorOpen ] = useState ( false ) ;
const [ settingsWindowRect , setSettingsWindowRect ] = useState < WorkspaceWindowRect > ( defaultSettingsWindowRect ) ;
const [ settingsWindowMaximized , setSettingsWindowMaximized ] = useState ( false ) ;
const [ settingsWindowZIndex , setSettingsWindowZIndex ] = useState ( 13 ) ;
const [ inspectorOpenSections , setInspectorOpenSections ] = useState < string [ ] > ( ( ) = > (
initialLayout ? . inspectorOpenSections ? ? [ "map-base" ]
) ) ;
@@ -676,7 +740,14 @@ export const MapFixturePreview = forwardRef<MapFixturePreviewHandle, {
const [ layersWindowRect , setLayersWindowRect ] = useState < WorkspaceWindowRect > ( defaultLayersWindowRect ) ;
const [ layersWindowMaximized , setLayersWindowMaximized ] = useState ( false ) ;
const [ layersWindowZIndex , setLayersWindowZIndex ] = useState ( 12 ) ;
const [ layersWindowActive , setLayersWindowActive ] = useState ( false ) ;
const [ sectorWindowRect , setSectorWindowRect ] = useState < WorkspaceWindowRect > ( defaultSectorWindowRect ) ;
const [ sectorWindowMaximized , setSectorWindowMaximized ] = useState ( false ) ;
const [ sectorWindowZIndex , setSectorWindowZIndex ] = useState ( 142 ) ;
const [ hideObjectsOutsideSector , setHideObjectsOutsideSector ] = useState ( false ) ;
const [ sectorExcludedBindingIds , setSectorExcludedBindingIds ] = useState < string [ ] > ( [ ] ) ;
const [ sectorExcludedProviders , setSectorExcludedProviders ] = useState < string [ ] > ( [ ] ) ;
const [ sectorExcludedObjectKinds , setSectorExcludedObjectKinds ] = useState < string [ ] > ( [ ] ) ;
const [ activeWorkspaceWindowId , setActiveWorkspaceWindowId ] = useState < MapWorkspaceWindowId > ( ) ;
const [ toolbarOpen , setToolbarOpen ] = useState ( Boolean ( features . toolbar ) ) ;
const [ searchOpen , setSearchOpen ] = useState ( false ) ;
const [ searchQuery , setSearchQuery ] = useState ( "" ) ;
@@ -734,7 +805,6 @@ export const MapFixturePreview = forwardRef<MapFixturePreviewHandle, {
const [ subjectStates , setSubjectStates ] = useState < Record < string , MapSubjectState > > ( ( ) = > (
initialSubjectState ( initialLayout ? . dataProductBindings ? ? [ ] , initialLayout ? . subjectStates )
) ) ;
const [ activeSubjectBindingId , setActiveSubjectBindingId ] = useState < string > ( ) ;
const presentationFilters = useMemo < MapPresentationFilters > ( ( ) = > Object . fromEntries (
Object . entries ( subjectStates ) . map ( ( [ bindingId , state ] ) = > [ bindingId , {
visible : state.visible ,
@@ -765,6 +835,11 @@ export const MapFixturePreview = forwardRef<MapFixturePreviewHandle, {
. . . presentationFilters ,
. . . referencePresentationFilters ,
} ) , [ presentationFilters , referencePresentationFilters ] ) ;
const sectorGridLodProfiles = mapSettings . gridLodProfiles as SectorGridLodProfile [ ] ;
const fixedSectorGridOrigin = useMemo ( ( ) = > ( {
latitude : mapSettings.gridCenterLatitude ,
longitude : mapSettings.gridCenterLongitude ,
} ) , [ mapSettings . gridCenterLatitude , mapSettings . gridCenterLongitude ] ) ;
const primaryBindingIds = useMemo ( ( ) = > new Set (
dataProductBindings . filter ( ( binding ) = > ! binding . joinToBindingId ) . map ( ( binding ) = > binding . id ) ,
) , [ dataProductBindings ] ) ;
@@ -804,6 +879,77 @@ export const MapFixturePreview = forwardRef<MapFixturePreviewHandle, {
} ) ;
} )
) , [ dataProductBindings , presentationProfiles , primaryRuntimeBindings ] ) ;
const sectorSpatialEntities = useMemo ( ( ) = > selectedGridSector
? selectable . filter ( ( entity ) = > mapFactInsideGridSector (
entity . fact ,
selectedGridSector ,
sectorGridLodProfiles ,
fixedSectorGridOrigin ,
) )
: [ ] , [ fixedSectorGridOrigin , sectorGridLodProfiles , selectable , selectedGridSector ] ) ;
const sectorBindingOptions = useMemo ( ( ) = > [ . . . dataProductBindings ]
. filter ( ( binding ) = > ! binding . joinToBindingId )
. sort ( ( left , right ) = > ( left . order ? ? 0 ) - ( right . order ? ? 0 ) || left . id . localeCompare ( right . id ) )
. map ( ( binding ) = > ( {
value : binding.id ,
label : binding.displayName?.trim ( ) || binding . id ,
count : sectorSpatialEntities.filter ( ( entity ) = > entity . bindingId === binding . id ) . length ,
} ) ) , [ dataProductBindings , sectorSpatialEntities ] ) ;
const sectorProviderFacetAvailable = useMemo ( ( ) = > dataProductBindings . some ( ( binding ) = > (
! binding . joinToBindingId && binding . fieldProjection . includes ( MAP_SCOPE_PROVIDER_FIELD )
) ) , [ dataProductBindings ] ) ;
const sectorObjectKindFacetAvailable = useMemo ( ( ) = > dataProductBindings . some ( ( binding ) = > (
! binding . joinToBindingId && binding . fieldProjection . includes ( MAP_SCOPE_OBJECT_KIND_FIELD )
) ) , [ dataProductBindings ] ) ;
const sectorProviderOptions = useMemo ( ( ) = > {
if ( ! sectorProviderFacetAvailable ) return [ ] ;
const counts = new Map < string , number > ( ) ;
sectorSpatialEntities . forEach ( ( { fact } ) = > {
const value = mapFactSectorScopeValue ( fact , MAP_SCOPE_PROVIDER_FIELD ) ;
counts . set ( value , ( counts . get ( value ) ? ? 0 ) + 1 ) ;
} ) ;
return [ . . . counts ] . map ( ( [ value , count ] ) = > ( { value , count , label : sectorScopeValueLabel ( value ) } ) )
. sort ( ( left , right ) = > left . label . localeCompare ( right . label , "ru" ) ) ;
} , [ sectorProviderFacetAvailable , sectorSpatialEntities ] ) ;
const sectorObjectKindOptions = useMemo ( ( ) = > {
if ( ! sectorObjectKindFacetAvailable ) return [ ] ;
const counts = new Map < string , number > ( ) ;
sectorSpatialEntities . forEach ( ( { fact } ) = > {
const value = mapFactSectorScopeValue ( fact , MAP_SCOPE_OBJECT_KIND_FIELD ) ;
counts . set ( value , ( counts . get ( value ) ? ? 0 ) + 1 ) ;
} ) ;
return [ . . . counts ] . map ( ( [ value , count ] ) = > ( { value , count , label : sectorScopeValueLabel ( value ) } ) )
. sort ( ( left , right ) = > left . label . localeCompare ( right . label , "ru" ) ) ;
} , [ sectorObjectKindFacetAvailable , sectorSpatialEntities ] ) ;
const sectorVisibleEntities = useMemo ( ( ) = > sectorSpatialEntities . filter ( ( entity ) = > {
if ( sectorExcludedBindingIds . includes ( entity . bindingId ) ) return false ;
if ( sectorExcludedProviders . includes ( mapFactSectorScopeValue ( entity . fact , MAP_SCOPE_PROVIDER_FIELD ) ) ) return false ;
if ( sectorExcludedObjectKinds . includes ( mapFactSectorScopeValue ( entity . fact , MAP_SCOPE_OBJECT_KIND_FIELD ) ) ) return false ;
const binding = dataProductBindings . find ( ( candidate ) = > candidate . id === entity . bindingId ) ;
const profile = mapPresentationProfileForFact (
presentationProfiles ,
binding ? . presentationProfileId ,
entity . fact . semanticType ,
) ;
return Boolean ( profile && mapFactMatchesFilters ( entity . fact , profile , presentationFilters , entity . bindingId ) ) ;
} ) , [ dataProductBindings , presentationFilters , presentationProfiles , sectorExcludedBindingIds , sectorExcludedObjectKinds , sectorExcludedProviders , sectorSpatialEntities ] ) ;
const sectorScopedPrimaryRuntimeBindings = useMemo ( ( ) = > {
if ( ! selectedGridSector ) return primaryRuntimeBindings ;
return primaryRuntimeBindings . map ( ( binding ) = > ( {
. . . binding ,
facts : binding.facts.filter ( ( fact ) = > {
if ( sectorExcludedBindingIds . includes ( binding . bindingId ) ) return false ;
if ( sectorExcludedProviders . includes ( mapFactSectorScopeValue ( fact , MAP_SCOPE_PROVIDER_FIELD ) ) ) return false ;
if ( sectorExcludedObjectKinds . includes ( mapFactSectorScopeValue ( fact , MAP_SCOPE_OBJECT_KIND_FIELD ) ) ) return false ;
return ! hideObjectsOutsideSector || mapFactInsideGridSector (
fact ,
selectedGridSector ,
sectorGridLodProfiles ,
fixedSectorGridOrigin ,
) ;
} ) ,
} ) ) ;
} , [ fixedSectorGridOrigin , hideObjectsOutsideSector , primaryRuntimeBindings , sectorExcludedBindingIds , sectorExcludedObjectKinds , sectorExcludedProviders , sectorGridLodProfiles , selectedGridSector ] ) ;
const presentationSummaries = useMemo ( ( ) = > [ . . . dataProductBindings ]
. filter ( ( binding ) = > ! binding . joinToBindingId )
. sort ( ( left , right ) = > ( left . order ? ? 0 ) - ( right . order ? ? 0 ) || left . id . localeCompare ( right . id ) )
@@ -832,7 +978,7 @@ export const MapFixturePreview = forwardRef<MapFixturePreviewHandle, {
} ] ;
} ) , [ presentationProfiles , referenceLayers , referenceRuntimeBindings ] ) ;
const objectLayerCount = presentationSummaries . length + referenceObjectSummaries . length ;
const filteredTargets = useMemo ( ( ) = > primaryRuntimeBindings . flatMap ( ( binding ) = > {
const filteredTargets = useMemo ( ( ) = > sectorScopedPrimaryRuntimeBindings . flatMap ( ( binding ) = > {
const bindingConfig = dataProductBindings . find ( ( candidate ) = > candidate . id === binding . bindingId ) ;
return binding . facts . flatMap ( ( fact ) = > {
const profile = mapPresentationProfileForFact (
@@ -850,7 +996,7 @@ export const MapFixturePreview = forwardRef<MapFixturePreviewHandle, {
renderable : mapRuntimeFactIsRenderable ( fact , profile ) ,
} ] ;
} ) ;
} ) . sort ( ( left , right ) = > left . title . localeCompare ( right . title , "ru" ) ) , [ dataProductBindings , presentationFilters , presentationProfiles , primaryRuntimeBindings ] ) ;
} ) . sort ( ( left , right ) = > left . title . localeCompare ( right . title , "ru" ) ) , [ dataProductBindings , presentationFilters , presentationProfiles , sectorScopedPrimaryRuntimeBindings ] ) ;
const visibleTargetEntityIds = useMemo ( ( ) = > (
filteredTargets . filter ( ( target ) = > target . renderable ) . map ( ( target ) = > target . entityId )
) , [ filteredTargets ] ) ;
@@ -927,7 +1073,6 @@ export const MapFixturePreview = forwardRef<MapFixturePreviewHandle, {
const selectedGridLodIndex = Math . max ( 0 , Math . min ( 4 , Number . parseInt ( selectedGridLod , 10 ) || 0 ) ) ;
const activeGridLod = ( mapSettings . gridLodProfiles [ selectedGridLodIndex ]
? ? DEFAULT_GRID_LOD_PROFILES [ selectedGridLodIndex ] ) as SectorGridLodProfile ;
const sectorGridLodProfiles = mapSettings . gridLodProfiles as SectorGridLodProfile [ ] ;
const gridSectorDefinitionKey = useMemo ( ( ) = > JSON . stringify ( {
origin : [ mapSettings . gridCenterLatitude , mapSettings . gridCenterLongitude ] ,
profiles : sectorGridLodProfiles.map ( ( profile ) = > ( {
@@ -942,10 +1087,6 @@ export const MapFixturePreview = forwardRef<MapFixturePreviewHandle, {
volumeBandHeightMeters : profile.volumeBandHeightMeters ,
} ) ) ,
} ) , [ mapSettings . gridCenterLatitude , mapSettings . gridCenterLongitude , sectorGridLodProfiles ] ) ;
const fixedSectorGridOrigin = useMemo ( ( ) = > ( {
latitude : mapSettings.gridCenterLatitude ,
longitude : mapSettings.gridCenterLongitude ,
} ) , [ mapSettings . gridCenterLatitude , mapSettings . gridCenterLongitude ] ) ;
const selectedGridParentLod = useMemo ( ( ) = > selectedGridSector
? gridSectorParentLodSelection ( selectedGridSector , sectorGridLodProfiles , fixedSectorGridOrigin )
: null , [ fixedSectorGridOrigin , sectorGridLodProfiles , selectedGridSector ] ) ;
@@ -971,6 +1112,10 @@ export const MapFixturePreview = forwardRef<MapFixturePreviewHandle, {
useEffect ( ( ) = > {
setSelectedGridSector ( null ) ;
setSectorExcludedBindingIds ( [ ] ) ;
setSectorExcludedProviders ( [ ] ) ;
setSectorExcludedObjectKinds ( [ ] ) ;
setActiveWorkspaceWindowId ( ( current ) = > current === "sector" ? undefined : current ) ;
} , [ gridSectorDefinitionKey ] ) ;
const minimumGridLodHeight = selectedGridLodIndex === 0
? 0.1
@@ -1208,7 +1353,7 @@ export const MapFixturePreview = forwardRef<MapFixturePreviewHandle, {
} ) ,
} ) , [ dataProductBindings , inspectorOpenSections , mapCamera , mapHeight , mapSettings , pinBindings , presentationProfiles , presentationSummaries , referenceLayers , subjectDetailProfiles , subjectStates ] ) ;
const updateSubjectState = ( bindingId : string , update : ( state : MapSubjectState ) = > MapSubjectState ) = > {
const updateSubjectState = useCallback ( ( bindingId : string , update : ( state : MapSubjectState ) = > MapSubjectState ) = > {
setSubjectStates ( ( current ) = > {
const index = dataProductBindings . findIndex ( ( binding ) = > binding . id === bindingId ) ;
const state = current [ bindingId ] ? ? {
@@ -1219,7 +1364,7 @@ export const MapFixturePreview = forwardRef<MapFixturePreviewHandle, {
} ;
return { . . . current , [ bindingId ] : update ( state ) } ;
} ) ;
} ;
} , [ dataProductBindings ] ) ;
const togglePresentationFilter = ( bindingId : string , field : string , value : string ) = > {
updateSubjectState ( bindingId , ( state ) = > {
@@ -1236,40 +1381,101 @@ export const MapFixturePreview = forwardRef<MapFixturePreviewHandle, {
updateSubjectState ( bindingId , ( state ) = > ( { . . . state , visible : ! state . visible } ) ) ;
} ;
const activateWorkspaceWindow = useCallback ( ( windowId : MapWorkspaceWindowId ) = > {
if ( activeWorkspaceWindowId === windowId ) return ;
const nextZIndex = Math . max (
20 ,
settingsWindowZIndex ,
layersWindowZIndex ,
sectorWindowZIndex ,
subjectCardZIndex ,
. . . Object . values ( subjectStates ) . map ( ( state ) = > state . window . zIndex ) ,
) + 1 ;
if ( windowId === "settings" ) setSettingsWindowZIndex ( nextZIndex ) ;
else if ( windowId === "layers" ) setLayersWindowZIndex ( nextZIndex ) ;
else if ( windowId === "sector" ) setSectorWindowZIndex ( nextZIndex ) ;
else if ( windowId === "subject-card" ) setSubjectCardZIndex ( nextZIndex ) ;
else if ( windowId . startsWith ( "binding:" ) ) {
const bindingId = windowId . slice ( "binding:" . length ) ;
updateSubjectState ( bindingId , ( state ) = > ( {
. . . state ,
window : { . . . state . window , zIndex : nextZIndex } ,
} ) ) ;
}
setActiveWorkspaceWindowId ( windowId ) ;
} , [ activeWorkspaceWindowId , layersWindowZIndex , sectorWindowZIndex , settingsWindowZIndex , subjectCardZIndex , subjectStates , updateSubjectState ] ) ;
const clearActiveWorkspaceWindow = ( windowId : MapWorkspaceWindowId ) = > {
setActiveWorkspaceWindowId ( ( current ) = > current === windowId ? undefined : current ) ;
} ;
const openSubjectWindow = ( bindingId : string ) = > {
const nextZIndex = Math . max ( 20 , layersWindowZIndex , subjectCardZIndex , . . . Object . values ( subjectStates ) . map ( ( state ) = > state . window . zIndex ) ) + 1 ;
updateSubjectState ( bindingId , ( state ) = > ( {
. . . state ,
window : { . . . state . window , open : true , zIndex : nextZIndex } ,
window : { . . . state . window , open : true } ,
} ) ) ;
setLayersWindowActive ( false ) ;
setSubjectCardActive ( false ) ;
setActiveSubjectBindingId ( bindingId ) ;
activateWorkspaceWindow ( ` binding: ${ bindingId } ` ) ;
} ;
const closeSubjectWindow = ( bindingId : string ) = > {
updateSubjectState ( bindingId , ( state ) = > ( { . . . state , window : { . . . state . window , open : false } } ) ) ;
setActiveSubjectBindingId ( ( current ) = > current === bindingId ? undefined : current ) ;
clearActiveWorkspaceWindow ( ` binding: ${ bindingId } ` ) ;
} ;
const activateLayersWindow = ( ) = > {
const nextZIndex = Math . max ( 20 , layersWindowZIndex , subjectCardZIndex , . . . Object . values ( subjectStates ) . map ( ( state ) = > state . window . zIndex ) ) + 1 ;
setLayersWindowZIndex ( nextZIndex ) ;
setLayersWindowActive ( true ) ;
setSubjectCardActive ( false ) ;
setActiveSubjectBindingId ( undefined ) ;
activateWorkspaceWindow ( "layers" ) ;
} ;
const toggleLayersWindow = ( ) = > {
if ( layersOpen ) {
setLayersOpen ( false ) ;
setLayersWindowActive ( false ) ;
clearActiveWorkspaceWindow ( "layers" ) ;
return ;
}
setLayersOpen ( true ) ;
activateLayersWindow ( ) ;
} ;
const openSettingsWindow = ( ) = > {
setInspectorOpen ( true ) ;
activateWorkspaceWindow ( "settings" ) ;
} ;
const deactivateGridSector = ( ) = > {
setSelectedGridSector ( null ) ;
setSectorExcludedBindingIds ( [ ] ) ;
setSectorExcludedProviders ( [ ] ) ;
setSectorExcludedObjectKinds ( [ ] ) ;
clearActiveWorkspaceWindow ( "sector" ) ;
} ;
const handleGridSectorSelect = ( selection : GridSectorSelection | null ) = > {
if ( ! selection ) {
deactivateGridSector ( ) ;
return ;
}
setSelectedGridSector ( selection ) ;
activateWorkspaceWindow ( "sector" ) ;
} ;
const setSectorBindingEnabled = ( bindingId : string , enabled : boolean ) = > {
setSectorExcludedBindingIds ( ( current ) = > enabled
? current . filter ( ( value ) = > value !== bindingId )
: [ . . . new Set ( [ . . . current , bindingId ] ) ] ) ;
} ;
const setSectorProviderEnabled = ( provider : string , enabled : boolean ) = > {
setSectorExcludedProviders ( ( current ) = > enabled
? current . filter ( ( value ) = > value !== provider )
: [ . . . new Set ( [ . . . current , provider ] ) ] ) ;
} ;
const setSectorObjectKindEnabled = ( objectKind : string , enabled : boolean ) = > {
setSectorExcludedObjectKinds ( ( current ) = > enabled
? current . filter ( ( value ) = > value !== objectKind )
: [ . . . new Set ( [ . . . current , objectKind ] ) ] ) ;
} ;
const updatePresentationProfile = (
profileId : string ,
update : ( profile : MapPresentationProfile ) = > MapPresentationProfile ,
@@ -1297,11 +1503,8 @@ export const MapFixturePreview = forwardRef<MapFixturePreviewHandle, {
: ( profile ? . defaultTabId ? ? "overview" )
) ) ;
setSubjectCardOpen ( true ) ;
setSubjectCardActive ( true ) ;
setLayersWindowActive ( false ) ;
setActiveSubjectBindingId ( undefined ) ;
setSubjectCardZIndex ( ( current ) = > Math . max ( current , layersWindowZIndex , . . . Object . values ( subjectStates ) . map ( ( state ) = > state . window . zIndex ) ) + 1 ) ;
} , [ dataProductBindings , layersWindowZIndex , selectable , subjectDetailProfiles , subjectStates ] ) ;
activateWorkspaceWindow ( "subject-card" ) ;
} , [ activateWorkspaceWindow , dataProductBindings , selectable , subjectDetailProfiles ] ) ;
const handleSelectAndFocus = useCallback ( ( entityId : string ) = > {
handleSelect ( entityId ) ;
@@ -2027,7 +2230,7 @@ export const MapFixturePreview = forwardRef<MapFixturePreviewHandle, {
key = { rendererRevision }
ref = { mapRendererRef }
onSelect = { handleSelect }
onGridSectorSelect = { setSelectedGridSector }
onGridSectorSelect = { handleGridSectorSelect }
selectedGridSector = { selectedGridSector }
onGatewayHealth = { handleRendererGatewayHealth }
onProviderStatus = { setProviderStatus }
@@ -2037,14 +2240,14 @@ export const MapFixturePreview = forwardRef<MapFixturePreviewHandle, {
onSpiralStateChange = { handleSpiralStateChange }
initialCamera = { mapCamera ? ? undefined }
presentation = { presentation }
runtimeBindings = { [ . . . primaryRuntimeBindings , . . . referenceRuntimeBindings ] }
runtimeBindings = { [ . . . sectorScopedPrimaryRuntimeBindings , . . . referenceRuntimeBindings ] }
presentationProfiles = { presentationProfiles }
presentationFilters = { rendererPresentationFilters }
/ >
< / Suspense >
< div className = "catalog-map-fixture__actions" >
< IconButton label = "Настройки карты" aria-pressed = { inspectorOpen } data-active = { inspectorOpen || undefined } onClick = { ( ) = > setInspectorOpen ( true ) } > < Icon name = "settings" / > < / IconButton >
< IconButton label = "Настройки карты" aria-pressed = { inspectorOpen } data-active = { inspectorOpen || undefined } onClick = { openSettingsWindow } > < Icon name = "settings" / > < / IconButton >
< IconButton label = "Слои карты" aria-pressed = { layersOpen } data-active = { layersOpen || undefined } onClick = { toggleLayersWindow } > < Icon name = "grid" / > < / IconButton >
{ features . toolbar ? < IconButton label = "Toolbar" aria-pressed = { toolbarOpen } data-active = { toolbarOpen || undefined } onClick = { ( ) = > setToolbarOpen ( ( value ) = > ! value ) } > < Icon name = "panel" / > < / IconButton > : null }
{ features . assistant ? < IconButton label = "Assistant" aria-pressed = { assistantOpen } data-active = { assistantOpen || undefined } onClick = { ( ) = > setAssistantOpen ( ( value ) = > ! value ) } > < Icon name = "apps" / > < / IconButton > : null }
@@ -2060,10 +2263,10 @@ export const MapFixturePreview = forwardRef<MapFixturePreviewHandle, {
onActivate = { activateLayersWindow }
onClose = { ( ) = > {
setLayersOpen ( false ) ;
setLayersWindowActive ( false ) ;
clearActiveWorkspaceWindow ( "layers" ) ;
} }
title = "Слои карты"
active = { layersWindowActive }
active = { activeWorkspaceWindowId === "layers" }
zIndex = { layersWindowZIndex }
minWidth = { 320 }
minHeight = { 360 }
@@ -2089,6 +2292,136 @@ export const MapFixturePreview = forwardRef<MapFixturePreviewHandle, {
< / WorkspaceWindow >
) : null }
{ selectedGridSector ? (
< WorkspaceWindow
boundsRef = { workspaceRef }
rect = { sectorWindowRect }
onRectChange = { setSectorWindowRect }
maximized = { sectorWindowMaximized }
onMaximizedChange = { setSectorWindowMaximized }
onActivate = { ( ) = > activateWorkspaceWindow ( "sector" ) }
onClose = { deactivateGridSector }
title = { ` Активный сектор · LOD ${ selectedGridSector . lod } ` }
subtitle = { selectedGridSector . label }
status = { ` ${ sectorVisibleEntities . length } / ${ sectorSpatialEntities . length } ` }
active = { activeWorkspaceWindowId === "sector" }
zIndex = { sectorWindowZIndex }
minWidth = { 340 }
minHeight = { 380 }
footer = { (
< Button variant = "secondary" size = "compact" width = "full" onClick = { deactivateGridSector } >
Д е а к т и в и р о в а т ь с е к т о р
< / Button >
) }
className = "catalog-map-fixture__sector-window catalog-map-fixture__map-glass-window"
aria-label = { ` Активный сектор: ${ selectedGridSector . id } ` }
>
< div className = "catalog-map-sector-window" >
< section className = "catalog-map-sector-window__summary" aria-label = "Параметры сектора" >
< code title = { selectedGridSector . id } > { selectedGridSector . id } < / code >
< span > { gridSectorBoundsLabel ( selectedGridSector ) } < / span >
< span > { formatGridSectorArea ( selectedGridSector . areaSquareMeters ) } < / span >
< Button
variant = "secondary"
size = "compact"
width = "full"
icon = { < Icon name = { gridSectorCopyState === "copied" ? "check" : "copy" } / > }
onClick = { ( ) = > void copySelectedGridSectorId ( ) }
> { gridSectorCopyState === "copied" ? "ID скопирован" : "Копировать stable ID" } < / Button >
< / section >
< Checker
checked = { hideObjectsOutsideSector }
label = "Скрыть объекты за сектором"
onChange = { setHideObjectsOutsideSector }
/ >
< section className = "catalog-map-sector-window__filters" aria-labelledby = "map-sector-domains-title" >
< div className = "catalog-map-sector-window__section-title" >
< strong id = "map-sector-domains-title" > Д о м е н ы д а н н ы х < / strong >
< small > { sectorBindingOptions . length } < / small >
< / div >
{ sectorBindingOptions . map ( ( option ) = > (
< Checker
key = { option . value }
checked = { ! sectorExcludedBindingIds . includes ( option . value ) }
label = { ` ${ option . label } · ${ option . count } ` }
onChange = { ( enabled ) = > setSectorBindingEnabled ( option . value , enabled ) }
/ >
) ) }
< / section >
{ sectorProviderOptions . length ? (
< section className = "catalog-map-sector-window__filters" aria-labelledby = "map-sector-providers-title" >
< div className = "catalog-map-sector-window__section-title" >
< strong id = "map-sector-providers-title" > П р о в а й д е р ы < / strong >
< small > { sectorProviderOptions . length } < / small >
< / div >
{ sectorProviderOptions . map ( ( option ) = > (
< Checker
key = { option . value }
checked = { ! sectorExcludedProviders . includes ( option . value ) }
label = { ` ${ option . label } · ${ option . count } ` }
title = { option . value === MAP_SCOPE_MISSING_VALUE ? undefined : option . value }
onChange = { ( enabled ) = > setSectorProviderEnabled ( option . value , enabled ) }
/ >
) ) }
< / section >
) : null }
{ sectorObjectKindOptions . length ? (
< section className = "catalog-map-sector-window__filters" aria-labelledby = "map-sector-kinds-title" >
< div className = "catalog-map-sector-window__section-title" >
< strong id = "map-sector-kinds-title" > Т и п ы о б ъ е к т о в < / strong >
< small > { sectorObjectKindOptions . length } < / small >
< / div >
{ sectorObjectKindOptions . map ( ( option ) = > (
< Checker
key = { option . value }
checked = { ! sectorExcludedObjectKinds . includes ( option . value ) }
label = { ` ${ option . label } · ${ option . count } ` }
title = { option . value === MAP_SCOPE_MISSING_VALUE ? undefined : option . value }
onChange = { ( enabled ) = > setSectorObjectKindEnabled ( option . value , enabled ) }
/ >
) ) }
< / section >
) : null }
< section className = "catalog-map-sector-window__objects" aria-labelledby = "map-sector-objects-title" >
< div className = "catalog-map-sector-window__section-title" >
< strong id = "map-sector-objects-title" > О б ъ е к т ы с е к т о р а < / strong >
< small > { sectorVisibleEntities . length } / { sectorSpatialEntities . length } < / small >
< / div >
< div className = "catalog-map-sector-window__object-list" >
{ sectorVisibleEntities . map ( ( entity ) = > {
const provider = normalizedSectorScopeValue ( entity . fact . attributes [ MAP_SCOPE_PROVIDER_FIELD ] ) ;
const objectKind = normalizedSectorScopeValue ( entity . fact . attributes [ MAP_SCOPE_OBJECT_KIND_FIELD ] ) ;
return (
< button
type = "button"
key = { entity . id }
data-selected = { entity . id === selectedId || undefined }
onClick = { ( ) = > handleSelectAndFocus ( entity . id ) }
>
< span > { entity . title } < / span >
< code > { entity . fact . sourceId } < / code >
< small > { [ provider , objectKind , entity . status ] . filter ( Boolean ) . join ( " · " ) } < / small >
< / button >
) ;
} ) }
{ ! sectorVisibleEntities . length ? (
< small className = "catalog-map-sector-window__empty" >
{ sectorSpatialEntities . length
? "Объекты скрыты текущими фильтрами."
: "В секторе нет точечных объектов подключённых Data Products." }
< / small >
) : null }
< / div >
< / section >
< / div >
< / WorkspaceWindow >
) : null }
{ toolbarOpen ? (
< div className = "catalog-map-fixture__toolbar" aria-label = "Map toolbar" data-search-open = { searchOpen || undefined } >
< Dropdown
@@ -2256,7 +2589,7 @@ export const MapFixturePreview = forwardRef<MapFixturePreviewHandle, {
onClose = { ( ) = > closeSubjectWindow ( summary . bindingId ) }
title = { summary . displayName }
subtitle = { ` ${ summary . total } всего · ${ visibleCount } на карте ` }
active = { activeSubjectBindingId === summary . bindingId }
active = { activeWorkspaceWindowId === ` binding: ${ summary . bindingId } ` }
zIndex = { state . window . zIndex }
minWidth = { 240 }
minHeight = { 220 }
@@ -2343,20 +2676,14 @@ export const MapFixturePreview = forwardRef<MapFixturePreviewHandle, {
onRectChange = { setSubjectCardRect }
maximized = { subjectCardMaximized }
onMaximizedChange = { setSubjectCardMaximized }
onActivate = { ( ) = > {
const nextZIndex = Math . max ( subjectCardZIndex , layersWindowZIndex , . . . Object . values ( subjectStates ) . map ( ( state ) = > state . window . zIndex ) ) + 1 ;
setSubjectCardZIndex ( nextZIndex ) ;
setSubjectCardActive ( true ) ;
setLayersWindowActive ( false ) ;
setActiveSubjectBindingId ( undefined ) ;
} }
onActivate = { ( ) = > activateWorkspaceWindow ( "subject-card" ) }
onClose = { ( ) = > {
setSubjectCardOpen ( false ) ;
setSubjectCardActive ( false ) ;
clearActiveWorkspaceWindow ( "subject-card" ) ;
} }
title = { selectedSubjectCard . title }
subtitle = { selectedSubjectCard . sourceId }
active = { subjectCardActive }
active = { activeWorkspaceWindowId === "subject-card" }
zIndex = { subjectCardZIndex }
minWidth = { 320 }
minHeight = { 320 }
@@ -2414,25 +2741,35 @@ export const MapFixturePreview = forwardRef<MapFixturePreviewHandle, {
{ assistantOpen ? < div className = "catalog-map-fixture__assistant" > < strong > NODE . DC Assistant < / strong > < span > К о н т е к с т в ы б р а н н о й с у щ н о с т и г о т о в к п е р е д а ч е . < / span > < / div > : null }
< button type = "button" className = "catalog-map-fixture__resize" aria-label = "Изменить высоту карты" onPointerDown = { startResize } > < span / > < / button >
< Window
open = { inspectorOpen && Boolean ( features . inspector ) }
title = "Настройки карты"
subtitle = "MAP / draggable inspector"
placement = "end"
draggable
closeOnBackdrop = { false }
lockBodyScroll = { false }
trapFocus = { false }
className = "catalog-map-fixture__map-settings-window"
onClose = { ( ) = > setInspectorOpen ( false ) }
>
< Inspector
sections = { inspectorSections }
openSections = { inspectorOpenSections }
singleOpen
onOpenSectionsChange = { setInspectorOpenSections }
/ >
< / Window >
{ inspectorOpen && Boolean ( features . inspector ) ? (
< WorkspaceWindow
boundsRef = { workspaceRef }
rect = { settingsWindowRect }
onRectChange = { setSettingsWindowRect }
maximized = { settingsWindowMaximized }
onMaximizedChange = { setSettingsWindowMaximized }
onActivate = { ( ) = > activateWorkspaceWindow ( "settings" ) }
onClose = { ( ) = > {
setInspectorOpen ( false ) ;
clearActiveWorkspaceWindow ( "settings" ) ;
} }
title = "Настройки карты"
subtitle = "MAP / inspector"
active = { activeWorkspaceWindowId === "settings" }
zIndex = { settingsWindowZIndex }
minWidth = { 360 }
minHeight = { 380 }
className = "catalog-map-fixture__map-settings-window catalog-map-fixture__map-glass-window"
aria-label = "Настройки карты"
>
< Inspector
sections = { inspectorSections }
openSections = { inspectorOpenSections }
singleOpen
onOpenSectionsChange = { setInspectorOpenSections }
/ >
< / WorkspaceWindow >
) : null }
< / div >
) ;
} ) ;