| | |
| | | Vue.component('map-canvas', { |
| | | template: ` |
| | | <div style="width: 100%; height: 100%; position: relative;"> |
| | | <div ref="pixiView"></div> |
| | | <div style="position: absolute; top: 20px; right: 50px;"> |
| | | <div>FPS:{{mapFps}}</div> |
| | | <div ref="pixiView" style="position: absolute; inset: 0;"></div> |
| | | <div :style="cycleCapacityPanelStyle()"> |
| | | <div style="display: flex; flex-direction: column; gap: 6px; align-items: flex-start;"> |
| | | <div v-for="item in cycleCapacity.loopList" |
| | | :key="'loop-' + item.loopNo" |
| | | @mouseenter="handleLoopCardEnter(item)" |
| | | @mouseleave="handleLoopCardLeave(item)" |
| | | style="padding: 6px 10px; border-radius: 4px; background: rgba(11, 35, 58, 0.72); color: #fff; font-size: 12px; line-height: 1.4; white-space: nowrap; pointer-events: auto;"> |
| | | 圈{{ item.loopNo }} | |
| | | 站点: {{ item.stationCount || 0 }} | |
| | | 任务: {{ item.taskCount || 0 }} | |
| | | 承载: {{ formatLoadPercent(item.currentLoad) }} |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div v-show="shelfTooltip.visible" |
| | | :style="shelfTooltipStyle()"> |
| | | {{ shelfTooltip.text }} |
| | | </div> |
| | | <div style="position: absolute; top: 18px; right: 34px; z-index: 30; display: flex; flex-direction: column; align-items: flex-end; gap: 8px;"> |
| | | <div :style="mapToolFpsStyle()">FPS {{ mapFps }}</div> |
| | | <button type="button" @click="toggleMapToolPanel" :style="mapToolToggleStyle(showMapToolPanel)">{{ showMapToolPanel ? '收起操作' : '地图操作' }}</button> |
| | | <div v-show="showMapToolPanel" :style="mapToolBarStyle()"> |
| | | <div :style="mapToolRowStyle()"> |
| | | <button type="button" @click="toggleStationDirection" :style="mapToolButtonStyle(showStationDirection)">{{ showStationDirection ? '隐藏站点方向' : '显示站点方向' }}</button> |
| | | <button type="button" @click="rotateMap" :style="mapToolButtonStyle(false)">旋转</button> |
| | | <button type="button" @click="toggleMirror" :style="mapToolButtonStyle(mapMirrorX)">{{ mapMirrorX ? '取消镜像' : '镜像' }}</button> |
| | | </div> |
| | | <div :style="mapToolRowStyle()"> |
| | | <button type="button" @click="openStationColorConfigPage" :style="mapToolButtonStyle(false)">站点颜色</button> |
| | | </div> |
| | | <div v-if="levList && levList.length > 1" :style="mapToolFloorSectionStyle()"> |
| | | <div :style="mapToolSectionLabelStyle()">楼层</div> |
| | | <div :style="mapToolFloorListStyle()"> |
| | | <button |
| | | v-for="floor in levList" |
| | | :key="'tool-floor-' + floor" |
| | | type="button" |
| | | @click="selectFloorFromTool(floor)" |
| | | :style="mapToolFloorButtonStyle(currentLev == floor)" |
| | | >{{ floor }}F</button> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | `, |
| | | props: ['lev', 'crnParam', 'rgvParam', 'devpParam', 'highlightOnParamChange'], |
| | | props: ['lev', 'levList', 'crnParam', 'rgvParam', 'devpParam', 'stationTaskRange', 'highlightOnParamChange', 'viewportPadding', 'hudPadding'], |
| | | data() { |
| | | return { |
| | | map: [], |
| | |
| | | pixiCrnMap: new Map(), |
| | | pixiDualCrnMap: new Map(), |
| | | pixiRgvMap: new Map(), |
| | | mapRoot: null, |
| | | mapRotation: 0, |
| | | mapMirrorX: false, |
| | | mapContentSize: { width: 0, height: 0 }, |
| | | mapConfigCodes: { |
| | | rotate: 'map_canvas_rotation', |
| | | mirror: 'map_canvas_mirror_x' |
| | | }, |
| | | pixiShelfMap: new Map(), |
| | | pixiTrackMap: new Map(), |
| | | pixiDevpTextureMap: new Map(), |
| | |
| | | pixiDevpTextureMap: new Map(), |
| | | pixiCrnColorTextureMap: new Map(), |
| | | pixiRgvColorTextureMap: new Map(), |
| | | shelfChunkList: [], |
| | | shelfChunkSize: 2048, |
| | | shelfCullPadding: 160, |
| | | shelfCullRaf: null, |
| | | crnList: [], |
| | | dualCrnList: [], |
| | | rgvList: [], |
| | | locListMap: new Map(), |
| | | locListLoaded: false, |
| | | locListLoading: false, |
| | | mapRowOffsets: [], |
| | | mapRowHeights: [], |
| | | mapColOffsets: [], |
| | | mapColWidths: [], |
| | | mapRowColOffsets: [], |
| | | mapRowColWidths: [], |
| | | mapRowShelfCells: [], |
| | | hoveredShelfCell: null, |
| | | hoverPointer: { x: 0, y: 0 }, |
| | | hoverRaf: null, |
| | | objectsContainer: null, |
| | | objectsContainer2: null, |
| | | tracksContainer: null, |
| | | tracksGraphics: null, |
| | | shelvesContainer: null, |
| | | graphicsCrn: null, |
| | | graphicsCrnTrack: null, |
| | | graphicsRgvTrack: null, |
| | | graphicsRgv: null, |
| | | shelfTooltip: { |
| | | visible: false, |
| | | x: 0, |
| | | y: 0, |
| | | text: '', |
| | | item: null |
| | | }, |
| | | shelfTooltipMinScale: 0.4, |
| | | containerResizeObserver: null, |
| | | timer: null, |
| | | adjustLabelTimer: null, |
| | | isSwitchingFloor: false |
| | | isSwitchingFloor: false, |
| | | cycleCapacity: { |
| | | loopList: [], |
| | | totalStationCount: 0, |
| | | taskStationCount: 0, |
| | | currentLoad: 0 |
| | | }, |
| | | showMapToolPanel: false, |
| | | showStationDirection: false, |
| | | hoverLoopNo: null, |
| | | hoverLoopStationIdSet: new Set(), |
| | | loopHighlightColor: 0xfff34d, |
| | | stationDirectionColor: 0xff5a36, |
| | | stationStatusColors: { |
| | | 'site-auto': 0x78ff81, |
| | | 'site-auto-run': 0xfa51f6, |
| | | 'site-auto-id': 0xc4c400, |
| | | 'site-auto-run-id': 0x30bffc, |
| | | 'site-enable-in': 0xA81DEE, |
| | | 'site-unauto': 0xb8b8b8, |
| | | 'machine-pakin': 0x30bffc, |
| | | 'machine-pakout': 0x97b400, |
| | | 'site-run-block': 0xe69138 |
| | | } |
| | | } |
| | | }, |
| | | mounted() { |
| | | this.currentLev = this.lev || 1; |
| | | this.createMap(); |
| | | this.startContainerResizeObserve(); |
| | | this.loadMapTransformConfig(); |
| | | this.loadStationColorConfig(); |
| | | this.loadLocList(); |
| | | this.connectWs(); |
| | | |
| | | setTimeout(() => { |
| | |
| | | this.getCrnInfo(); |
| | | this.getDualCrnInfo(); |
| | | this.getSiteInfo(); |
| | | this.getCycleCapacityInfo(); |
| | | this.getRgvInfo(); |
| | | }, 1000); |
| | | }, |
| | | beforeDestroy() { |
| | | if (this.timer) { clearInterval(this.timer); } |
| | | |
| | | if (this.hoverRaf) { cancelAnimationFrame(this.hoverRaf); this.hoverRaf = null; } |
| | | if (this.shelfCullRaf) { cancelAnimationFrame(this.shelfCullRaf); this.shelfCullRaf = null; } |
| | | if (window.gsap && this.pixiApp && this.pixiApp.stage) { window.gsap.killTweensOf(this.pixiApp.stage.position); } |
| | | if (this.pixiApp) { this.pixiApp.destroy(true, { children: true }); } |
| | | if (this.containerResizeObserver) { this.containerResizeObserver.disconnect(); this.containerResizeObserver = null; } |
| | | window.removeEventListener('resize', this.resizeToContainer); |
| | | if (this.wsReconnectTimer) { clearTimeout(this.wsReconnectTimer); this.wsReconnectTimer = null; } |
| | | if (this.ws && (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING)) { try { this.ws.close(); } catch (e) {} } |
| | |
| | | watch: { |
| | | lev(newLev) { |
| | | if (newLev != null) { this.changeFloor(newLev); } |
| | | }, |
| | | viewportPadding: { |
| | | deep: true, |
| | | handler(newVal, oldVal) { |
| | | if (this.mapContentSize && this.mapContentSize.width > 0 && this.mapContentSize.height > 0) { |
| | | this.adjustStageForViewportPadding(oldVal, newVal); |
| | | } |
| | | } |
| | | }, |
| | | crnParam: { |
| | | deep: true, |
| | |
| | | } |
| | | }, |
| | | methods: { |
| | | cycleCapacityPanelStyle() { |
| | | const hud = this.hudPadding || {}; |
| | | const left = Math.max(14, Number(hud.left) || 0); |
| | | const rightReserve = 220; |
| | | return { |
| | | position: 'absolute', |
| | | top: '12px', |
| | | left: left + 'px', |
| | | zIndex: 30, |
| | | pointerEvents: 'none', |
| | | maxWidth: 'calc(100% - ' + (left + rightReserve) + 'px)' |
| | | }; |
| | | }, |
| | | mapToolBarStyle() { |
| | | return { |
| | | display: 'flex', |
| | | flexDirection: 'column', |
| | | gap: '8px', |
| | | alignItems: 'stretch', |
| | | padding: '7px', |
| | | borderRadius: '14px', |
| | | background: 'rgba(255, 255, 255, 0.72)', |
| | | border: '1px solid rgba(160, 180, 205, 0.3)', |
| | | boxShadow: '0 8px 20px rgba(37, 64, 97, 0.08)', |
| | | backdropFilter: 'blur(4px)' |
| | | }; |
| | | }, |
| | | mapToolRowStyle() { |
| | | return { |
| | | display: 'flex', |
| | | gap: '8px', |
| | | alignItems: 'center', |
| | | justifyContent: 'flex-end', |
| | | flexWrap: 'wrap' |
| | | }; |
| | | }, |
| | | mapToolFloorSectionStyle() { |
| | | return { |
| | | display: 'flex', |
| | | flexDirection: 'column', |
| | | gap: '4px', |
| | | paddingTop: '6px', |
| | | borderTop: '1px solid rgba(160, 180, 205, 0.22)' |
| | | }; |
| | | }, |
| | | mapToolSectionLabelStyle() { |
| | | return { |
| | | color: '#6a7f95', |
| | | fontSize: '10px', |
| | | lineHeight: '14px', |
| | | textAlign: 'right' |
| | | }; |
| | | }, |
| | | mapToolFloorListStyle() { |
| | | return { |
| | | display: 'flex', |
| | | flexDirection: 'column', |
| | | gap: '4px', |
| | | alignItems: 'stretch' |
| | | }; |
| | | }, |
| | | mapToolFpsStyle() { |
| | | return { |
| | | padding: '4px 10px', |
| | | borderRadius: '999px', |
| | | background: 'rgba(255, 255, 255, 0.7)', |
| | | border: '1px solid rgba(160, 180, 205, 0.28)', |
| | | color: '#48617c', |
| | | fontSize: '12px', |
| | | lineHeight: '18px', |
| | | letterSpacing: '0.04em', |
| | | boxShadow: '0 6px 16px rgba(37, 64, 97, 0.06)', |
| | | userSelect: 'none' |
| | | }; |
| | | }, |
| | | mapToolToggleStyle(active) { |
| | | return { |
| | | appearance: 'none', |
| | | border: '1px solid ' + (active ? 'rgba(96, 132, 170, 0.36)' : 'rgba(160, 180, 205, 0.3)'), |
| | | background: active ? 'rgba(235, 243, 251, 0.96)' : 'rgba(255, 255, 255, 0.82)', |
| | | color: '#46617b', |
| | | height: '30px', |
| | | padding: '0 12px', |
| | | borderRadius: '999px', |
| | | fontSize: '12px', |
| | | lineHeight: '30px', |
| | | cursor: 'pointer', |
| | | whiteSpace: 'nowrap', |
| | | boxShadow: '0 6px 16px rgba(37, 64, 97, 0.06)' |
| | | }; |
| | | }, |
| | | mapToolButtonStyle(active) { |
| | | return { |
| | | appearance: 'none', |
| | | border: '1px solid ' + (active ? 'rgba(255, 136, 93, 0.38)' : 'rgba(160, 180, 205, 0.3)'), |
| | | background: active ? 'rgba(255, 119, 77, 0.16)' : 'rgba(255, 255, 255, 0.88)', |
| | | color: active ? '#d85a31' : '#4d647d', |
| | | height: '30px', |
| | | padding: '0 12px', |
| | | borderRadius: '10px', |
| | | fontSize: '12px', |
| | | lineHeight: '30px', |
| | | cursor: 'pointer', |
| | | transition: 'all 0.2s ease', |
| | | boxShadow: active ? '0 4px 10px rgba(255, 119, 77, 0.12)' : 'none', |
| | | whiteSpace: 'nowrap' |
| | | }; |
| | | }, |
| | | mapToolFloorButtonStyle(active) { |
| | | return { |
| | | appearance: 'none', |
| | | border: '1px solid ' + (active ? 'rgba(96, 132, 170, 0.36)' : 'rgba(160, 180, 205, 0.3)'), |
| | | background: active ? 'rgba(235, 243, 251, 0.96)' : 'rgba(255, 255, 255, 0.88)', |
| | | color: active ? '#27425c' : '#4d647d', |
| | | minWidth: '44px', |
| | | height: '26px', |
| | | padding: '0 10px', |
| | | borderRadius: '8px', |
| | | fontSize: '11px', |
| | | lineHeight: '26px', |
| | | cursor: 'pointer', |
| | | fontWeight: '700', |
| | | boxShadow: active ? '0 4px 12px rgba(37, 64, 97, 0.08)' : 'none', |
| | | whiteSpace: 'nowrap' |
| | | }; |
| | | }, |
| | | toggleMapToolPanel() { |
| | | this.showMapToolPanel = !this.showMapToolPanel; |
| | | }, |
| | | selectFloorFromTool(lev) { |
| | | if (lev == null || lev === this.currentLev) { return; } |
| | | this.$emit('switch-lev', lev); |
| | | }, |
| | | createMap() { |
| | | this.pixiApp = new PIXI.Application({ backgroundColor: 0xF5F7F9, antialias: false, powerPreference: 'high-performance', autoDensity: true, resolution: Math.min(window.devicePixelRatio || 1, 2) }); |
| | | PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.LINEAR; |
| | |
| | | this.pixiApp.view.style.display = 'block'; |
| | | this.resizeToContainer(); |
| | | window.addEventListener('resize', this.resizeToContainer); |
| | | this.graphicsCrnTrack = this.createTrackTexture(25,25); |
| | | this.graphicsRgvTrack = this.createTrackTexture(25,25); |
| | | this.graphicsCrnTrack = this.createTrackTexture(25, 25, 10); |
| | | this.graphicsRgvTrack = this.createTrackTexture(25, 25, 10); |
| | | this.objectsContainer = new PIXI.Container(); |
| | | this.objectsContainer2 = new PIXI.Container(); |
| | | this.tracksContainer = new PIXI.ParticleContainer(10000, { scale: true, position: true, rotation: false, uvs: false, alpha: false }); |
| | | this.shelvesContainer = new PIXI.ParticleContainer(10000, { scale: true, position: true, rotation: false, uvs: false, alpha: false }); |
| | | this.tracksGraphics = new PIXI.Graphics(); |
| | | this.shelvesContainer = new PIXI.Container(); |
| | | this.tracksContainer.autoResize = true; |
| | | this.shelvesContainer.autoResize = true; |
| | | this.pixiApp.stage.addChild(this.tracksContainer); |
| | | this.pixiApp.stage.addChild(this.shelvesContainer); |
| | | this.pixiApp.stage.addChild(this.objectsContainer); |
| | | this.pixiApp.stage.addChild(this.objectsContainer2); |
| | | this.mapRoot = new PIXI.Container(); |
| | | this.pixiApp.stage.addChild(this.mapRoot); |
| | | this.mapRoot.addChild(this.tracksGraphics); |
| | | this.mapRoot.addChild(this.tracksContainer); |
| | | this.mapRoot.addChild(this.shelvesContainer); |
| | | this.mapRoot.addChild(this.objectsContainer); |
| | | this.mapRoot.addChild(this.objectsContainer2); |
| | | this.pixiApp.renderer.roundPixels = true; |
| | | this.hoveredShelfCell = null; |
| | | this.hoverPointer = { x: 0, y: 0 }; |
| | | this.hoverRaf = null; |
| | | |
| | | |
| | | //*******************拖动画布******************* |
| | | |
| | | //*******************shelf hover******************* |
| | | this.pixiApp.renderer.plugins.interaction.on('pointermove', (event) => { |
| | | if (!this.isShelfTooltipAllowed()) { this.hideShelfTooltip(); return; } |
| | | if (!this.map || !this.mapRoot) { return; } |
| | | const pos = event.data.global; |
| | | this.hoverPointer.x = pos.x; |
| | | this.hoverPointer.y = pos.y; |
| | | if (this.hoverRaf) { return; } |
| | | this.hoverRaf = requestAnimationFrame(() => { |
| | | this.hoverRaf = null; |
| | | this.updateShelfHoverFromPointer(this.hoverPointer); |
| | | }); |
| | | }); |
| | | this.pixiApp.view.addEventListener('mouseleave', () => { |
| | | this.hoveredShelfCell = null; |
| | | this.hideShelfTooltip(); |
| | | }); |
| | | //*******************shelf hover******************* |
| | | let stageOriginalPos; |
| | | let mouseDownPoint; |
| | | let touchBlank = false; |
| | |
| | | const globalPos = event.data.global; |
| | | stageOriginalPos = [this.pixiApp.stage.position.x, this.pixiApp.stage.position.y]; |
| | | mouseDownPoint = [globalPos.x, globalPos.y]; |
| | | if (!event.target) { touchBlank = true; } |
| | | if (!event.target || (event.target && event.target._kind === 'shelf')) { touchBlank = true; } |
| | | }); |
| | | this.pixiApp.renderer.plugins.interaction.on('pointermove', (event) => { |
| | | const globalPos = event.data.global; |
| | |
| | | const dx = globalPos.x - mouseDownPoint[0]; |
| | | const dy = globalPos.y - mouseDownPoint[1]; |
| | | this.pixiApp.stage.position.set(stageOriginalPos[0] + dx, stageOriginalPos[1] + dy); |
| | | this.scheduleShelfChunkCulling(); |
| | | } |
| | | }); |
| | | this.pixiApp.renderer.plugins.interaction.on('pointerup', () => { touchBlank = false; }); |
| | | //*******************拖动画布******************* |
| | | |
| | | |
| | | //*******************缩放画布******************* |
| | | this.pixiApp.view.addEventListener('wheel', (event) => { |
| | |
| | | const rect = this.pixiApp.view.getBoundingClientRect(); |
| | | const sx = event.clientX - rect.left; |
| | | const sy = event.clientY - rect.top; |
| | | const oldZoom = this.pixiApp.stage.scale.x; |
| | | const oldZoomX = this.pixiApp.stage.scale.x || 1; |
| | | const oldZoomY = this.pixiApp.stage.scale.y || 1; |
| | | const oldZoomAbs = Math.abs(oldZoomX) || 1; |
| | | const delta = event.deltaY; |
| | | let newZoom = oldZoom * 0.999 ** delta; |
| | | const worldX = (sx - this.pixiApp.stage.position.x) / oldZoom; |
| | | const worldY = (sy - this.pixiApp.stage.position.y) / oldZoom; |
| | | const newPosX = sx - worldX * newZoom; |
| | | const newPosY = sy - worldY * newZoom; |
| | | this.pixiApp.stage.setTransform(newPosX, newPosY, newZoom, newZoom, 0, 0, 0, 0, 0); |
| | | this.scheduleAdjustLabels(); |
| | | let newZoomAbs = oldZoomAbs * 0.999 ** delta; |
| | | const mirrorX = this.mapMirrorX ? -1 : 1; |
| | | const newZoomX = mirrorX * newZoomAbs; |
| | | const newZoomY = newZoomAbs; |
| | | const worldX = (sx - this.pixiApp.stage.position.x) / oldZoomX; |
| | | const worldY = (sy - this.pixiApp.stage.position.y) / oldZoomY; |
| | | const newPosX = sx - worldX * newZoomX; |
| | | const newPosY = sy - worldY * newZoomY; |
| | | this.pixiApp.stage.setTransform(newPosX, newPosY, newZoomX, newZoomY, 0, 0, 0, 0, 0); |
| | | this.scheduleAdjustLabels(); |
| | | this.scheduleShelfChunkCulling(); |
| | | }); |
| | | //*******************缩放画布******************* |
| | | |
| | |
| | | }); |
| | | //*******************FPS******************* |
| | | }, |
| | | startContainerResizeObserve() { |
| | | if (typeof ResizeObserver === 'undefined' || !this.$el) { return; } |
| | | this.containerResizeObserver = new ResizeObserver(() => { |
| | | this.resizeToContainer(); |
| | | }); |
| | | this.containerResizeObserver.observe(this.$el); |
| | | }, |
| | | getViewportSize() { |
| | | if (!this.pixiApp || !this.pixiApp.renderer) { return { width: 0, height: 0 }; } |
| | | const screen = this.pixiApp.renderer.screen; |
| | | if (screen && screen.width > 0 && screen.height > 0) { |
| | | return { width: screen.width, height: screen.height }; |
| | | } |
| | | const rect = this.pixiApp.view ? this.pixiApp.view.getBoundingClientRect() : null; |
| | | return { width: rect ? rect.width : 0, height: rect ? rect.height : 0 }; |
| | | }, |
| | | getViewportPadding() { |
| | | return this.normalizeViewportPadding(this.viewportPadding); |
| | | }, |
| | | normalizeViewportPadding(padding) { |
| | | const source = padding || {}; |
| | | const normalize = (value) => { |
| | | const num = Number(value); |
| | | return isFinite(num) && num > 0 ? num : 0; |
| | | }; |
| | | return { |
| | | top: normalize(source.top), |
| | | right: normalize(source.right), |
| | | bottom: normalize(source.bottom), |
| | | left: normalize(source.left) |
| | | }; |
| | | }, |
| | | getViewportCenter(viewport, padding) { |
| | | const normalized = this.normalizeViewportPadding(padding); |
| | | const availableW = Math.max(1, viewport.width - normalized.left - normalized.right); |
| | | const availableH = Math.max(1, viewport.height - normalized.top - normalized.bottom); |
| | | return { |
| | | x: normalized.left + availableW / 2, |
| | | y: normalized.top + availableH / 2 |
| | | }; |
| | | }, |
| | | adjustStageForViewportPadding(oldPadding, newPadding) { |
| | | if (!this.pixiApp || !this.pixiApp.stage) { return; } |
| | | const viewport = this.getViewportSize(); |
| | | if (viewport.width <= 0 || viewport.height <= 0) { return; } |
| | | const prevCenter = this.getViewportCenter(viewport, oldPadding); |
| | | const nextCenter = this.getViewportCenter(viewport, newPadding); |
| | | const deltaX = nextCenter.x - prevCenter.x; |
| | | const deltaY = nextCenter.y - prevCenter.y; |
| | | if (Math.abs(deltaX) < 0.5 && Math.abs(deltaY) < 0.5) { |
| | | return; |
| | | } |
| | | const targetX = this.pixiApp.stage.position.x + deltaX; |
| | | const targetY = this.pixiApp.stage.position.y + deltaY; |
| | | if (window.gsap) { |
| | | window.gsap.killTweensOf(this.pixiApp.stage.position); |
| | | window.gsap.to(this.pixiApp.stage.position, { |
| | | x: targetX, |
| | | y: targetY, |
| | | duration: 0.18, |
| | | ease: 'power1.out', |
| | | onUpdate: () => { |
| | | this.scheduleAdjustLabels(); |
| | | this.scheduleShelfChunkCulling(); |
| | | }, |
| | | onComplete: () => { |
| | | this.scheduleAdjustLabels(); |
| | | this.scheduleShelfChunkCulling(); |
| | | } |
| | | }); |
| | | return; |
| | | } |
| | | this.pixiApp.stage.position.x = targetX; |
| | | this.pixiApp.stage.position.y = targetY; |
| | | this.scheduleAdjustLabels(); |
| | | this.scheduleShelfChunkCulling(); |
| | | }, |
| | | resizeToContainer() { |
| | | const w = this.$el.clientWidth || 0; |
| | | const h = this.$el.clientHeight || 0; |
| | | if (w > 0 && h > 0 && this.pixiApp) { |
| | | const vw = this.pixiApp.renderer && this.pixiApp.renderer.screen ? this.pixiApp.renderer.screen.width : 0; |
| | | const vh = this.pixiApp.renderer && this.pixiApp.renderer.screen ? this.pixiApp.renderer.screen.height : 0; |
| | | if (vw === w && vh === h) { return; } |
| | | this.pixiApp.renderer.resize(w, h); |
| | | if (this.mapContentSize && this.mapContentSize.width > 0 && this.mapContentSize.height > 0) { |
| | | this.applyMapTransform(true); |
| | | } |
| | | } |
| | | }, |
| | | getMap() { |
| | |
| | | }, |
| | | changeFloor(lev) { |
| | | this.currentLev = lev; |
| | | this.clearLoopStationHighlight(); |
| | | this.isSwitchingFloor = true; |
| | | this.hideShelfTooltip(); |
| | | this.hoveredShelfCell = null; |
| | | this.mapRowOffsets = []; |
| | | this.mapRowHeights = []; |
| | | this.mapColOffsets = []; |
| | | this.mapColWidths = []; |
| | | if (this.adjustLabelTimer) { clearTimeout(this.adjustLabelTimer); this.adjustLabelTimer = null; } |
| | | this.objectsContainer.removeChildren(); |
| | | this.objectsContainer2.removeChildren(); |
| | | if (this.tracksContainer) { this.tracksContainer.removeChildren(); } |
| | | if (this.shelvesContainer) { this.shelvesContainer.removeChildren(); } |
| | | if (this.tracksGraphics) { this.tracksGraphics.clear(); } |
| | | this.clearShelfChunks(); |
| | | this.crnList = []; |
| | | this.dualCrnList = []; |
| | | this.rgvList = []; |
| | |
| | | this.getMap(); |
| | | }, |
| | | createMapData(map) { |
| | | this.clearLoopStationHighlight(); |
| | | this.hideShelfTooltip(); |
| | | this.hoveredShelfCell = null; |
| | | this.mapRowOffsets = []; |
| | | this.mapRowHeights = []; |
| | | this.mapColOffsets = []; |
| | | this.mapColWidths = []; |
| | | if (window.gsap) { |
| | | this.pixiStaMap && this.pixiStaMap.forEach((s) => { try { window.gsap.killTweensOf(s); } catch (e) {} }); |
| | | this.pixiCrnMap && this.pixiCrnMap.forEach((s) => { try { window.gsap.killTweensOf(s); } catch (e) {} }); |
| | |
| | | this.objectsContainer.removeChildren(); |
| | | this.objectsContainer2.removeChildren(); |
| | | if (this.tracksContainer) { this.tracksContainer.removeChildren(); } |
| | | if (this.shelvesContainer) { this.shelvesContainer.removeChildren(); } |
| | | if (this.tracksGraphics) { this.tracksGraphics.clear(); } |
| | | this.clearShelfChunks(); |
| | | this.crnList = []; |
| | | this.dualCrnList = []; |
| | | this.rgvList = []; |
| | |
| | | let val = row[colIndex]; |
| | | let cellWidth = val.width; |
| | | let cellHeight = val.height; |
| | | val.rowIndex = rowIndex; |
| | | val.colIndex = colIndex; |
| | | if (val.isMergedPart) { |
| | | val.posX = anchorX; |
| | | val.posY = yOffsets[rowIndex]; |
| | |
| | | } |
| | | }); |
| | | |
| | | map.forEach((row, rowIndex) => { |
| | | for (let colIndex = 0; colIndex < row.length; colIndex++) { |
| | | const val = row[colIndex]; |
| | | if (!val || val.type !== 'devp' || val.type === 'merge') { continue; } |
| | | val.stationDirectionList = this.resolveStationDirectionList(map, rowIndex, colIndex, val); |
| | | } |
| | | }); |
| | | |
| | | this.buildShelfHitGrid(map, rowHeightScaled, yOffsets); |
| | | |
| | | this.drawTracks(map); |
| | | |
| | | map.forEach((item, index) => { |
| | | this.pixiStageList[index] = [item.length]; |
| | | for (let idx = 0; idx < item.length; idx++) { |
| | | let val = item[idx]; |
| | | val.rowIndex = index; |
| | | val.colIndex = idx; |
| | | if (val.type === 'merge') { continue; } |
| | | if (val.type == undefined || val.type === 'none') { continue; } |
| | | if (this.isTrackType(val)) { |
| | | this.collectTrackItem(val); |
| | | continue; |
| | | } |
| | | if (val.type === 'shelf') { continue; } |
| | | let sprite = this.getSprite(val, (e) => { |
| | | //回调 |
| | | }); |
| | | if (sprite == null) { continue; } |
| | | if (sprite._kind === 'shelf') { |
| | | this.shelvesContainer.addChild(sprite); |
| | | } else if (sprite._kind === 'crn-track' || sprite._kind === 'rgv-track') { |
| | | this.tracksContainer.addChild(sprite); |
| | | } else { |
| | | this.objectsContainer.addChild(sprite); |
| | | } |
| | | this.objectsContainer.addChild(sprite); |
| | | this.pixiStageList[index][idx] = sprite; |
| | | } |
| | | }); |
| | |
| | | sprite.textObj = text; |
| | | sprite.position.set(item.posX, item.posY); |
| | | sprite.interactive = true; // 必须要设置才能接收事件 |
| | | sprite.buttonMode = true; // 让光标在hover时变为手型指针 |
| | | sprite.buttonMode = true; // 让光标在hover时变为手型指事件 |
| | | sprite.on('pointerdown', () => { |
| | | if (window.gsap) { window.gsap.killTweensOf(sprite); } |
| | | sprite.alpha = 1; |
| | |
| | | sprite.textObj = text; |
| | | sprite.position.set(item.posX, item.posY); |
| | | sprite.interactive = true; // 必须要设置才能接收事件 |
| | | sprite.buttonMode = true; // 让光标在hover时变为手型指针 |
| | | sprite.buttonMode = true; // 让光标在hover时变为手型指事件 |
| | | sprite.on('pointerdown', () => { |
| | | if (window.gsap) { window.gsap.killTweensOf(sprite); } |
| | | sprite.alpha = 1; |
| | |
| | | if (bottom > contentH) { contentH = bottom; } |
| | | } |
| | | } |
| | | const vw = this.pixiApp.view.width; |
| | | const vh = this.pixiApp.view.height; |
| | | let scale = Math.min(vw / contentW, vh / contentH) * 0.95; |
| | | if (!isFinite(scale) || scale <= 0) { scale = 1; } |
| | | const posX = (vw - contentW * scale) / 2; |
| | | const posY = (vh - contentH * scale) / 2; |
| | | this.pixiApp.stage.setTransform(posX, posY, scale, scale, 0, 0, 0, 0, 0); |
| | | this.adjustLabelScale(); |
| | | this.mapContentSize = { width: contentW, height: contentH }; |
| | | this.buildShelfChunks(map, contentW, contentH); |
| | | this.applyMapTransform(true); |
| | | this.map = map; |
| | | this.isSwitchingFloor = false; |
| | | }, |
| | |
| | | if (!sites) { return; } |
| | | sites.forEach((item) => { |
| | | let id = item.siteId != null ? item.siteId : item.stationId; |
| | | let status = item.siteStatus != null ? item.siteStatus : item.stationStatus; |
| | | let workNo = item.workNo != null ? item.workNo : item.taskNo; |
| | | if (id == null) { return; } |
| | | let sta = this.pixiStaMap.get(parseInt(id)); |
| | |
| | | sta.statusObj = null; |
| | | if (sta.textObj.parent !== sta) { sta.addChild(sta.textObj); sta.textObj.position.set(sta.width / 2, sta.height / 2); } |
| | | } |
| | | if (status === "site-auto") { |
| | | this.updateColor(sta, 0x78ff81); |
| | | } else if (status === "site-auto-run" || status === "site-auto-id" || status === "site-auto-run-id") { |
| | | this.updateColor(sta, 0xfa51f6); |
| | | } else if (status === "site-unauto") { |
| | | this.updateColor(sta, 0xb8b8b8); |
| | | } else if (status === "machine-pakin") { |
| | | this.updateColor(sta, 0x30bffc); |
| | | } else if (status === "machine-pakout") { |
| | | this.updateColor(sta, 0x97b400); |
| | | } else if (status === "site-run-block") { |
| | | this.updateColor(sta, 0xe69138); |
| | | } else { |
| | | this.updateColor(sta, 0xb8b8b8); |
| | | } |
| | | this.setStationBaseColor(sta, this.getStationStatusColor(this.resolveStationStatus(item))); |
| | | }); |
| | | }, |
| | | getCrnInfo() { |
| | |
| | | getRgvInfo() { |
| | | if (this.isSwitchingFloor) { return; } |
| | | this.sendWs(JSON.stringify({ url: "/console/latest/data/rgv", data: {} })); |
| | | }, |
| | | getCycleCapacityInfo() { |
| | | if (this.isSwitchingFloor) { return; } |
| | | this.sendWs(JSON.stringify({ url: "/console/latest/data/station/cycle/capacity", data: {} })); |
| | | }, |
| | | setCycleCapacityInfo(res) { |
| | | const payload = res && res.code === 200 ? res.data : null; |
| | | if (res && res.code === 403) { parent.location.href = baseUrl + "/login"; return; } |
| | | if (!payload) { return; } |
| | | const loopList = Array.isArray(payload.loopList) ? payload.loopList : []; |
| | | this.cycleCapacity = { |
| | | loopList: loopList, |
| | | totalStationCount: payload.totalStationCount || 0, |
| | | taskStationCount: payload.taskStationCount || 0, |
| | | currentLoad: typeof payload.currentLoad === 'number' ? payload.currentLoad : parseFloat(payload.currentLoad || 0) |
| | | }; |
| | | if (this.hoverLoopNo != null) { |
| | | const targetLoop = loopList.find(v => v && v.loopNo === this.hoverLoopNo); |
| | | if (targetLoop) { |
| | | this.hoverLoopStationIdSet = this.buildStationIdSet(targetLoop.stationIdList); |
| | | this.applyLoopStationHighlight(); |
| | | } else { |
| | | this.clearLoopStationHighlight(); |
| | | } |
| | | } |
| | | }, |
| | | formatLoadPercent(load) { |
| | | let value = typeof load === 'number' ? load : parseFloat(load || 0); |
| | | if (!isFinite(value)) { value = 0; } |
| | | if (value < 0) { value = 0; } |
| | | if (value > 1) { value = 1; } |
| | | return (value * 100).toFixed(1) + "%"; |
| | | }, |
| | | setCrnInfo(res) { |
| | | let crns = Array.isArray(res) ? res : (res && res.code === 200 ? res.data : null); |
| | |
| | | if (this.wsReconnectTimer) { clearTimeout(this.wsReconnectTimer); this.wsReconnectTimer = null; } |
| | | this.wsReconnectAttempts = 0; |
| | | this.getMap(this.currentLev); |
| | | this.getCycleCapacityInfo(); |
| | | }, |
| | | webSocketOnError(e) { |
| | | this.scheduleReconnect(); |
| | |
| | | this.setDualCrnInfo(JSON.parse(result.data)); |
| | | } else if (result.url === "/console/latest/data/rgv") { |
| | | this.setRgvInfo(JSON.parse(result.data)); |
| | | } else if (result.url === "/console/latest/data/station/cycle/capacity") { |
| | | this.setCycleCapacityInfo(JSON.parse(result.data)); |
| | | } else if (typeof result.url === "string" && result.url.indexOf("/basMap/lev/") === 0) { |
| | | this.setMap(JSON.parse(result.data)); |
| | | } |
| | |
| | | } |
| | | return new PIXI.Sprite(texture); |
| | | }, |
| | | createTrackSprite(width, height) { |
| | | let idx = width + "-" + height; |
| | | createTrackSprite(width, height, mask) { |
| | | const trackMask = mask != null ? mask : 10; |
| | | let idx = width + "-" + height + "-" + trackMask; |
| | | let texture = this.pixiTrackMap.get(idx); |
| | | if (texture == undefined) { |
| | | texture = this.createTrackTexture(width, height); |
| | | texture = this.createTrackTexture(width, height, trackMask); |
| | | this.pixiTrackMap.set(idx, texture); |
| | | } |
| | | return new PIXI.Sprite(texture); |
| | |
| | | graphics.endFill(); |
| | | return graphics; |
| | | }, |
| | | createTrackTexture(width, height) { |
| | | createTrackTexture(width, height, mask) { |
| | | const TRACK_N = 1; |
| | | const TRACK_E = 2; |
| | | const TRACK_S = 4; |
| | | const TRACK_W = 8; |
| | | const trackMask = mask != null ? mask : (TRACK_E | TRACK_W); |
| | | const g = new PIXI.Graphics(); |
| | | const t = Math.max(2, Math.round(height * 0.08)); |
| | | const gap = Math.round(height * 0.30); |
| | | const mid = Math.round(height / 2); |
| | | const y1 = mid - Math.round(gap / 2); |
| | | const y2 = mid + Math.round(gap / 2); |
| | | const tHalf = Math.round(t / 2); |
| | | const topRailTopY = y1 - tHalf; |
| | | const bottomRailTopY = y2 - tHalf; |
| | | g.beginFill(0x666666); |
| | | g.drawRect(0, topRailTopY, width, t); |
| | | g.drawRect(0, bottomRailTopY, width, t); |
| | | g.endFill(); |
| | | g.beginFill(0x777777); |
| | | const sTop = topRailTopY + t; |
| | | const sBottom = bottomRailTopY; |
| | | const sHeight = Math.max(1, sBottom - sTop); |
| | | for (let i = 0; i < width; i += 5) { g.drawRect(i, sTop, 2, sHeight); } |
| | | g.endFill(); |
| | | const size = Math.max(1, Math.min(width, height)); |
| | | const rail = Math.max(2, Math.round(size * 0.12)); |
| | | const gap = Math.max(4, Math.round(size * 0.38)); |
| | | const midX = Math.round(width / 2); |
| | | const midY = Math.round(height / 2); |
| | | const y1 = midY - Math.round(gap / 2); |
| | | const y2 = midY + Math.round(gap / 2); |
| | | const x1 = midX - Math.round(gap / 2); |
| | | const x2 = midX + Math.round(gap / 2); |
| | | |
| | | const hasN = (trackMask & TRACK_N) !== 0; |
| | | const hasE = (trackMask & TRACK_E) !== 0; |
| | | const hasS = (trackMask & TRACK_S) !== 0; |
| | | const hasW = (trackMask & TRACK_W) !== 0; |
| | | |
| | | const hStart = hasW ? 0 : midX; |
| | | const hEnd = hasE ? width : midX; |
| | | const vStart = hasN ? 0 : midY; |
| | | const vEnd = hasS ? height : midY; |
| | | |
| | | const railColor = 0x555555; |
| | | const drawLine = (x1p, y1p, x2p, y2p, w, color) => { |
| | | g.lineStyle(w, color, 1); |
| | | g.moveTo(x1p, y1p); |
| | | g.lineTo(x2p, y2p); |
| | | }; |
| | | |
| | | const hasH = hasW || hasE; |
| | | const hasV = hasN || hasS; |
| | | const isCorner = hasH && hasV && !(hasW && hasE) && !(hasN && hasS); |
| | | if (hasH && !isCorner) { |
| | | const w = Math.max(1, hEnd - hStart); |
| | | g.beginFill(railColor); |
| | | g.drawRect(hStart, midY - Math.round(rail / 2), w, rail); |
| | | g.endFill(); |
| | | } |
| | | if (hasV && !isCorner) { |
| | | const h = Math.max(1, vEnd - vStart); |
| | | g.beginFill(railColor); |
| | | g.drawRect(midX - Math.round(rail / 2), vStart, rail, h); |
| | | g.endFill(); |
| | | } |
| | | if (isCorner) { |
| | | const cw = hasE; |
| | | const ch = hasS; |
| | | const cx = cw ? (width - 1) : 0; |
| | | const cy = ch ? (height - 1) : 0; |
| | | const angStart = (cw && ch) ? Math.PI : (cw ? Math.PI / 2 : (ch ? -Math.PI / 2 : 0)); |
| | | const angEnd = (cw && ch) ? Math.PI * 1.5 : (cw ? Math.PI : (ch ? 0 : Math.PI / 2)); |
| | | const rX = Math.abs(cx - midX); |
| | | const rY = Math.abs(cy - midY); |
| | | const rMid = Math.min(rX, rY); |
| | | g.lineStyle(rail, railColor, 1); |
| | | g.arc(cx, cy, rMid, angStart, angEnd); |
| | | g.lineStyle(0, 0, 0); |
| | | } |
| | | |
| | | // no sleepers; keep a single continuous line |
| | | const rt = PIXI.RenderTexture.create({ width: width, height: height }); |
| | | this.pixiApp.renderer.render(g, rt); |
| | | return rt; |
| | |
| | | const brightness = (r * 299 + g * 587 + b * 114) / 1000; |
| | | return brightness > 150 ? '#000000' : '#ffffff'; |
| | | }, |
| | | getStationStatusColor(status) { |
| | | const colorMap = this.stationStatusColors || this.getDefaultStationStatusColors(); |
| | | if (status && colorMap[status] != null) { return colorMap[status]; } |
| | | return colorMap['site-unauto'] != null ? colorMap['site-unauto'] : 0xb8b8b8; |
| | | }, |
| | | resolveStationStatus(item) { |
| | | const status = item && (item.siteStatus != null ? item.siteStatus : item.stationStatus); |
| | | const taskNo = this.parseStationTaskNo(item && (item.workNo != null ? item.workNo : item.taskNo)); |
| | | const autoing = !!(item && item.autoing); |
| | | const loading = !!(item && item.loading); |
| | | const runBlock = !!(item && item.runBlock); |
| | | const enableIn = !!(item && item.enableIn); |
| | | if (taskNo === 9998 || enableIn) { return 'site-enable-in'; } |
| | | if (autoing && loading && taskNo > 0 && !runBlock) { |
| | | const taskClass = this.getStationTaskClass(taskNo); |
| | | if (taskClass) { return taskClass; } |
| | | } |
| | | if (status) { return status; } |
| | | if (autoing && loading && taskNo > 0 && runBlock) { return 'site-run-block'; } |
| | | if (autoing && loading && taskNo > 0) { return 'site-auto-run-id'; } |
| | | if (autoing && loading) { return 'site-auto-run'; } |
| | | if (autoing && taskNo > 0) { return 'site-auto-id'; } |
| | | if (autoing) { return 'site-auto'; } |
| | | return 'site-unauto'; |
| | | }, |
| | | parseStationTaskNo(value) { |
| | | const taskNo = parseInt(value, 10); |
| | | return isNaN(taskNo) ? 0 : taskNo; |
| | | }, |
| | | getStationTaskClass(taskNo) { |
| | | if (!(taskNo > 0)) { return null; } |
| | | const range = this.stationTaskRange || {}; |
| | | if (this.isTaskNoInRange(taskNo, range.inbound)) { return 'machine-pakin'; } |
| | | if (this.isTaskNoInRange(taskNo, range.outbound)) { return 'machine-pakout'; } |
| | | return null; |
| | | }, |
| | | isTaskNoInRange(taskNo, range) { |
| | | if (!range) { return false; } |
| | | const start = parseInt(range.start, 10); |
| | | const end = parseInt(range.end, 10); |
| | | if (isNaN(start) || isNaN(end)) { return false; } |
| | | return taskNo >= start && taskNo <= end; |
| | | }, |
| | | getCrnStatusColor(status) { |
| | | if (status === "machine-auto") { return 0x21BA45; } |
| | | if (status === "machine-un-auto") { return 0xBBBBBB; } |
| | |
| | | } |
| | | sprite = new PIXI.Sprite(texture); |
| | | sprite._kind = 'devp'; |
| | | const directionOverlay = this.createStationDirectionOverlay(item.width, item.height, item.stationDirectionList); |
| | | if (directionOverlay) { |
| | | directionOverlay.visible = this.showStationDirection; |
| | | sprite.addChild(directionOverlay); |
| | | } |
| | | sprite.directionObj = directionOverlay; |
| | | sprite._stationDirectionList = Array.isArray(item.stationDirectionList) ? item.stationDirectionList.slice() : []; |
| | | let siteId = this.getStationId(value); |
| | | if (siteId === -1) { siteId = item.data; } |
| | | const style = new PIXI.TextStyle({ fontFamily: 'Arial', fontSize: 10, fill: '#000000', stroke: '#ffffff', strokeThickness: 1 }); |
| | |
| | | text.position.set(sprite.width / 2, sprite.height / 2); |
| | | sprite.addChild(text); |
| | | sprite.textObj = text; |
| | | if (siteId != null && siteId !== -1) { this.pixiStaMap.set(parseInt(siteId), sprite); } |
| | | const stationIdInt = parseInt(siteId, 10); |
| | | if (!isNaN(stationIdInt)) { this.pixiStaMap.set(stationIdInt, sprite); } |
| | | sprite._stationId = isNaN(stationIdInt) ? null : stationIdInt; |
| | | sprite._baseColor = 0x00ff7f; |
| | | sprite._loopHighlighted = false; |
| | | sprite.interactive = true; |
| | | sprite.buttonMode = true; |
| | | sprite.on('pointerdown', () => { |
| | |
| | | if (!isNaN(id)) { this.$emit('station-click', id); } |
| | | }); |
| | | } else if (item.type == 'crn') { |
| | | sprite = this.createTrackSprite(item.width, item.height); |
| | | sprite = this.createTrackSprite(item.width, item.height, item.trackMask); |
| | | sprite._kind = 'crn-track'; |
| | | if (this.getDeviceNo(value) > 0) { this.crnList.push(item); } |
| | | } else if (item.type == 'dualCrn') { |
| | | sprite = this.createTrackSprite(item.width, item.height); |
| | | sprite = this.createTrackSprite(item.width, item.height, item.trackMask); |
| | | sprite._kind = 'crn-track'; |
| | | if (this.getDeviceNo(value) > 0) { this.dualCrnList.push(item); } |
| | | } else if (item.type == 'rgv') { |
| | | sprite = this.createTrackSprite(item.width, item.height); |
| | | sprite = this.createTrackSprite(item.width, item.height, item.trackMask); |
| | | sprite._kind = 'rgv-track'; |
| | | if (this.getDeviceNo(value) > 0) { this.rgvList.push(item); } |
| | | } else { |
| | |
| | | } |
| | | sprite.position.set(item.posX, item.posY); |
| | | return sprite; |
| | | }, |
| | | collectTrackItem(item) { |
| | | const value = item.value; |
| | | if (item.type === 'crn') { |
| | | if (this.getDeviceNo(value) > 0) { this.crnList.push(item); } |
| | | } else if (item.type === 'dualCrn') { |
| | | if (this.getDeviceNo(value) > 0) { this.dualCrnList.push(item); } |
| | | } else if (item.type === 'rgv') { |
| | | if (this.getDeviceNo(value) > 0) { this.rgvList.push(item); } |
| | | } |
| | | }, |
| | | isTrackType(cell) { |
| | | return cell && (cell.type === 'crn' || cell.type === 'dualCrn' || cell.type === 'rgv'); |
| | | }, |
| | | resolveMergedCell(map, rowIndex, colIndex) { |
| | | if (!map || rowIndex < 0 || colIndex < 0) { return null; } |
| | | const row = map[rowIndex]; |
| | | if (!row || colIndex >= row.length) { return null; } |
| | | const cell = row[colIndex]; |
| | | if (!cell) { return null; } |
| | | if (!cell.isMergedPart && cell.type !== 'merge') { return cell; } |
| | | if (cell.isMergedPart) { |
| | | for (let c = colIndex - 1; c >= 0; c--) { |
| | | const left = row[c]; |
| | | if (!left) { continue; } |
| | | if (!left.isMergedPart && left.type !== 'merge' && left.posX === cell.posX) { return left; } |
| | | } |
| | | } |
| | | if (cell.type === 'merge') { |
| | | for (let r = rowIndex - 1; r >= 0; r--) { |
| | | const upRow = map[r]; |
| | | if (!upRow || colIndex >= upRow.length) { continue; } |
| | | const up = upRow[colIndex]; |
| | | if (!up) { continue; } |
| | | if (up.type !== 'merge') { return up; } |
| | | } |
| | | } |
| | | return null; |
| | | }, |
| | | getTrackMask(map, rowIndex, colIndex) { |
| | | const TRACK_N = 1; |
| | | const TRACK_E = 2; |
| | | const TRACK_S = 4; |
| | | const TRACK_W = 8; |
| | | const baseRow = map[rowIndex]; |
| | | if (!baseRow) { return 0; } |
| | | const base = baseRow[colIndex]; |
| | | if (!this.isTrackType(base)) { return 0; } |
| | | const rowSpan = base.rowSpan || 1; |
| | | const colSpan = base.colSpan || 1; |
| | | let mask = 0; |
| | | const n = this.resolveMergedCell(map, rowIndex - 1, colIndex); |
| | | const s = this.resolveMergedCell(map, rowIndex + rowSpan, colIndex); |
| | | const w = this.resolveMergedCell(map, rowIndex, colIndex - 1); |
| | | const e = this.resolveMergedCell(map, rowIndex, colIndex + colSpan); |
| | | if (n && n !== base && this.isTrackType(n)) { mask |= TRACK_N; } |
| | | if (e && e !== base && this.isTrackType(e)) { mask |= TRACK_E; } |
| | | if (s && s !== base && this.isTrackType(s)) { mask |= TRACK_S; } |
| | | if (w && w !== base && this.isTrackType(w)) { mask |= TRACK_W; } |
| | | if (mask === 0) { mask = TRACK_E | TRACK_W; } |
| | | return mask; |
| | | }, |
| | | drawTracks(map) { |
| | | if (!this.tracksGraphics) { return; } |
| | | this.tracksGraphics.clear(); |
| | | const rail = 3; |
| | | const color = 0x555555; |
| | | this.tracksGraphics.lineStyle({ width: rail, color: color, alpha: 1, cap: PIXI.LINE_CAP.ROUND, join: PIXI.LINE_JOIN.ROUND }); |
| | | const drawn = new Set(); |
| | | const toKey = (p) => { |
| | | const x = Math.round(p.x * 100) / 100; |
| | | const y = Math.round(p.y * 100) / 100; |
| | | return x + "," + y; |
| | | }; |
| | | const edgeKey = (a, b) => { |
| | | const ka = toKey(a); |
| | | const kb = toKey(b); |
| | | return ka < kb ? (ka + "|" + kb) : (kb + "|" + ka); |
| | | }; |
| | | const centerOf = (cell) => ({ x: cell.posX + cell.width / 2, y: cell.posY + cell.height / 2 }); |
| | | |
| | | for (let r = 0; r < map.length; r++) { |
| | | const row = map[r]; |
| | | if (!row) { continue; } |
| | | for (let c = 0; c < row.length; c++) { |
| | | const cell = row[c]; |
| | | if (!cell || cell.type === 'merge' || !this.isTrackType(cell) || cell.isMergedPart) { continue; } |
| | | const rowSpan = cell.rowSpan || 1; |
| | | const colSpan = cell.colSpan || 1; |
| | | const n = this.resolveMergedCell(map, r - 1, c); |
| | | const s = this.resolveMergedCell(map, r + rowSpan, c); |
| | | const w = this.resolveMergedCell(map, r, c - 1); |
| | | const e = this.resolveMergedCell(map, r, c + colSpan); |
| | | const hasN = n && this.isTrackType(n); |
| | | const hasE = e && this.isTrackType(e); |
| | | const hasS = s && this.isTrackType(s); |
| | | const hasW = w && this.isTrackType(w); |
| | | const count = (hasN ? 1 : 0) + (hasE ? 1 : 0) + (hasS ? 1 : 0) + (hasW ? 1 : 0); |
| | | const straight = (hasN && hasS) || (hasE && hasW); |
| | | if (count === 2 && !straight) { |
| | | const cPos = centerOf(cell); |
| | | let p1 = null; |
| | | let p2 = null; |
| | | if (hasN && hasE) { p1 = centerOf(n); p2 = centerOf(e); } |
| | | else if (hasE && hasS) { p1 = centerOf(e); p2 = centerOf(s); } |
| | | else if (hasS && hasW) { p1 = centerOf(s); p2 = centerOf(w); } |
| | | else if (hasW && hasN) { p1 = centerOf(w); p2 = centerOf(n); } |
| | | if (p1 && p2) { |
| | | const k1 = edgeKey(cPos, p1); |
| | | const k2 = edgeKey(cPos, p2); |
| | | if (!drawn.has(k1) || !drawn.has(k2)) { |
| | | this.tracksGraphics.moveTo(p1.x, p1.y); |
| | | this.tracksGraphics.lineTo(cPos.x, cPos.y); |
| | | this.tracksGraphics.lineTo(p2.x, p2.y); |
| | | } |
| | | drawn.add(k1); |
| | | drawn.add(k2); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | for (let r = 0; r < map.length; r++) { |
| | | const row = map[r]; |
| | | if (!row) { continue; } |
| | | for (let c = 0; c < row.length; c++) { |
| | | const cell = row[c]; |
| | | if (!cell || cell.type === 'merge' || !this.isTrackType(cell) || cell.isMergedPart) { continue; } |
| | | const cPos = centerOf(cell); |
| | | const rowSpan = cell.rowSpan || 1; |
| | | const colSpan = cell.colSpan || 1; |
| | | const e = this.resolveMergedCell(map, r, c + colSpan); |
| | | const s = this.resolveMergedCell(map, r + rowSpan, c); |
| | | if (e && this.isTrackType(e)) { |
| | | const p = centerOf(e); |
| | | const k = edgeKey(cPos, p); |
| | | if (!drawn.has(k)) { |
| | | this.tracksGraphics.moveTo(cPos.x, cPos.y); |
| | | this.tracksGraphics.lineTo(p.x, p.y); |
| | | drawn.add(k); |
| | | } |
| | | } |
| | | if (s && this.isTrackType(s)) { |
| | | const p = centerOf(s); |
| | | const k = edgeKey(cPos, p); |
| | | if (!drawn.has(k)) { |
| | | this.tracksGraphics.moveTo(cPos.x, cPos.y); |
| | | this.tracksGraphics.lineTo(p.x, p.y); |
| | | drawn.add(k); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | }, |
| | | updateColor(sprite, color) { |
| | | if (sprite && sprite._kind === 'devp') { |
| | |
| | | } |
| | | sprite.tint = color; |
| | | }, |
| | | setStationBaseColor(sprite, color) { |
| | | if (!sprite) { return; } |
| | | sprite._baseColor = color; |
| | | if (this.isStationInHoverLoop(sprite)) { |
| | | this.applyHighlightColor(sprite); |
| | | } else { |
| | | this.updateColor(sprite, color); |
| | | sprite._loopHighlighted = false; |
| | | } |
| | | }, |
| | | applyHighlightColor(sprite) { |
| | | if (!sprite) { return; } |
| | | this.updateColor(sprite, this.loopHighlightColor); |
| | | sprite._loopHighlighted = true; |
| | | }, |
| | | isStationInHoverLoop(sprite) { |
| | | if (!sprite || sprite._stationId == null || !this.hoverLoopStationIdSet) { return false; } |
| | | return this.hoverLoopStationIdSet.has(sprite._stationId); |
| | | }, |
| | | buildStationIdSet(stationIdList) { |
| | | const set = new Set(); |
| | | if (!Array.isArray(stationIdList)) { return set; } |
| | | stationIdList.forEach((id) => { |
| | | const v = parseInt(id, 10); |
| | | if (!isNaN(v)) { set.add(v); } |
| | | }); |
| | | return set; |
| | | }, |
| | | applyLoopStationHighlight() { |
| | | if (!this.pixiStaMap) { return; } |
| | | this.pixiStaMap.forEach((sprite) => { |
| | | if (!sprite) { return; } |
| | | if (this.isStationInHoverLoop(sprite)) { |
| | | this.applyHighlightColor(sprite); |
| | | } else if (sprite._loopHighlighted) { |
| | | const baseColor = (typeof sprite._baseColor === 'number') ? sprite._baseColor : 0xb8b8b8; |
| | | this.updateColor(sprite, baseColor); |
| | | sprite._loopHighlighted = false; |
| | | } |
| | | }); |
| | | }, |
| | | clearLoopStationHighlight() { |
| | | if (this.pixiStaMap) { |
| | | this.pixiStaMap.forEach((sprite) => { |
| | | if (!sprite || !sprite._loopHighlighted) { return; } |
| | | const baseColor = (typeof sprite._baseColor === 'number') ? sprite._baseColor : 0xb8b8b8; |
| | | this.updateColor(sprite, baseColor); |
| | | sprite._loopHighlighted = false; |
| | | }); |
| | | } |
| | | this.hoverLoopNo = null; |
| | | this.hoverLoopStationIdSet = new Set(); |
| | | }, |
| | | handleLoopCardEnter(loopItem) { |
| | | if (!loopItem) { return; } |
| | | this.hoverLoopNo = loopItem.loopNo; |
| | | this.hoverLoopStationIdSet = this.buildStationIdSet(loopItem.stationIdList); |
| | | this.applyLoopStationHighlight(); |
| | | }, |
| | | handleLoopCardLeave(loopItem) { |
| | | if (!loopItem) { |
| | | this.clearLoopStationHighlight(); |
| | | return; |
| | | } |
| | | if (this.hoverLoopNo === loopItem.loopNo) { |
| | | this.clearLoopStationHighlight(); |
| | | } |
| | | }, |
| | | isJson(str) { |
| | | try { JSON.parse(str); return true; } catch (e) { return false; } |
| | | }, |
| | |
| | | getStationId(obj) { |
| | | if (this.isJson(obj)) { let data = JSON.parse(obj); if (data.stationId == null || data.stationId == undefined) { return -1; } return data.stationId; } else { return -1; } |
| | | }, |
| | | parseMapValue(obj) { |
| | | if (obj == null) { return null; } |
| | | if (typeof obj === 'object') { return obj; } |
| | | if (!this.isJson(obj)) { return null; } |
| | | try { |
| | | return JSON.parse(obj); |
| | | } catch (e) { |
| | | return null; |
| | | } |
| | | }, |
| | | normalizeDirectionList(direction) { |
| | | const aliasMap = { |
| | | top: 'top', |
| | | up: 'top', |
| | | north: 'top', |
| | | bottom: 'bottom', |
| | | down: 'bottom', |
| | | south: 'bottom', |
| | | left: 'left', |
| | | west: 'left', |
| | | right: 'right', |
| | | east: 'right' |
| | | }; |
| | | let rawList = []; |
| | | if (Array.isArray(direction)) { |
| | | rawList = direction; |
| | | } else if (typeof direction === 'string') { |
| | | rawList = direction.split(/[,\s|/]+/); |
| | | } |
| | | const result = []; |
| | | const seen = new Set(); |
| | | rawList.forEach((item) => { |
| | | const key = aliasMap[String(item || '').trim().toLowerCase()]; |
| | | if (!key || seen.has(key)) { return; } |
| | | seen.add(key); |
| | | result.push(key); |
| | | }); |
| | | return result; |
| | | }, |
| | | resolveStationDirectionList(map, rowIndex, colIndex, item) { |
| | | const valueObj = this.parseMapValue(item && item.value); |
| | | const fromValue = this.normalizeDirectionList(valueObj && valueObj.direction); |
| | | if (fromValue.length > 0) { return fromValue; } |
| | | const rowSpan = item && item.rowSpan ? item.rowSpan : 1; |
| | | const colSpan = item && item.colSpan ? item.colSpan : 1; |
| | | const fallback = []; |
| | | const candidateList = [ |
| | | { key: 'top', cell: this.resolveMergedCell(map, rowIndex - 1, colIndex) }, |
| | | { key: 'right', cell: this.resolveMergedCell(map, rowIndex, colIndex + colSpan) }, |
| | | { key: 'bottom', cell: this.resolveMergedCell(map, rowIndex + rowSpan, colIndex) }, |
| | | { key: 'left', cell: this.resolveMergedCell(map, rowIndex, colIndex - 1) } |
| | | ]; |
| | | candidateList.forEach((candidate) => { |
| | | if (this.isStationDirectionNeighbor(candidate.cell)) { |
| | | fallback.push(candidate.key); |
| | | } |
| | | }); |
| | | return fallback; |
| | | }, |
| | | isStationDirectionNeighbor(cell) { |
| | | if (!cell) { return false; } |
| | | if (cell.type === 'devp') { return true; } |
| | | return this.isTrackType(cell); |
| | | }, |
| | | createStationDirectionOverlay(width, height, directionList) { |
| | | if (!Array.isArray(directionList) || directionList.length === 0) { return null; } |
| | | const container = new PIXI.Container(); |
| | | const arrowSize = Math.max(4, Math.min(width, height) * 0.22); |
| | | const margin = Math.max(2, Math.min(width, height) * 0.12); |
| | | directionList.forEach((direction) => { |
| | | const arrow = new PIXI.Graphics(); |
| | | this.drawStationDirectionArrow(arrow, width, height, direction, arrowSize, margin); |
| | | container.addChild(arrow); |
| | | }); |
| | | return container; |
| | | }, |
| | | drawStationDirectionArrow(graphics, width, height, direction, size, margin) { |
| | | if (!graphics) { return; } |
| | | const halfBase = Math.max(2, size * 0.45); |
| | | const stemLen = Math.max(3, size * 0.7); |
| | | const centerX = width / 2; |
| | | const centerY = height / 2; |
| | | graphics.beginFill(this.stationDirectionColor, 0.95); |
| | | if (direction === 'top') { |
| | | const tipY = margin; |
| | | const baseY = margin + size; |
| | | const stemY = Math.min(centerY - 2, baseY + stemLen); |
| | | if (stemY > baseY) { graphics.moveTo(centerX, stemY); graphics.lineTo(centerX, baseY); } |
| | | graphics.moveTo(centerX, tipY); |
| | | graphics.lineTo(centerX - halfBase, baseY); |
| | | graphics.lineTo(centerX + halfBase, baseY); |
| | | } else if (direction === 'right') { |
| | | const tipX = width - margin; |
| | | const baseX = width - margin - size; |
| | | const stemX = Math.max(centerX + 2, baseX - stemLen); |
| | | if (stemX < baseX) { graphics.moveTo(stemX, centerY); graphics.lineTo(baseX, centerY); } |
| | | graphics.moveTo(tipX, centerY); |
| | | graphics.lineTo(baseX, centerY - halfBase); |
| | | graphics.lineTo(baseX, centerY + halfBase); |
| | | } else if (direction === 'bottom') { |
| | | const tipY = height - margin; |
| | | const baseY = height - margin - size; |
| | | const stemY = Math.max(centerY + 2, baseY - stemLen); |
| | | if (stemY < baseY) { graphics.moveTo(centerX, stemY); graphics.lineTo(centerX, baseY); } |
| | | graphics.moveTo(centerX, tipY); |
| | | graphics.lineTo(centerX - halfBase, baseY); |
| | | graphics.lineTo(centerX + halfBase, baseY); |
| | | } else if (direction === 'left') { |
| | | const tipX = margin; |
| | | const baseX = margin + size; |
| | | const stemX = Math.min(centerX - 2, baseX + stemLen); |
| | | if (stemX > baseX) { graphics.moveTo(stemX, centerY); graphics.lineTo(baseX, centerY); } |
| | | graphics.moveTo(tipX, centerY); |
| | | graphics.lineTo(baseX, centerY - halfBase); |
| | | graphics.lineTo(baseX, centerY + halfBase); |
| | | } |
| | | graphics.closePath(); |
| | | graphics.endFill(); |
| | | }, |
| | | applyStationDirectionVisibility() { |
| | | if (!this.pixiStaMap) { return; } |
| | | this.pixiStaMap.forEach((sprite) => { |
| | | if (!sprite || !sprite.directionObj) { return; } |
| | | sprite.directionObj.visible = this.showStationDirection; |
| | | }); |
| | | }, |
| | | getTrackSiteNo(obj) { |
| | | if (this.isJson(obj)) { let data = JSON.parse(obj); if (data.trackSiteNo == null || data.trackSiteNo == undefined) { return -1; } return data.trackSiteNo; } else { return -1; } |
| | | }, |
| | | buildShelfHitGrid(map, rowHeights, rowOffsets) { |
| | | if (!map || !Array.isArray(map)) { return; } |
| | | this.mapRowOffsets = Array.isArray(rowOffsets) ? rowOffsets.slice() : []; |
| | | this.mapRowHeights = Array.isArray(rowHeights) ? rowHeights.slice() : []; |
| | | const rowColOffsets = []; |
| | | const rowColWidths = []; |
| | | const rowShelfCells = new Array(map.length); |
| | | let maxCols = 0; |
| | | for (let r = 0; r < map.length; r++) { |
| | | const row = map[r]; |
| | | if (row && row.length > maxCols) { maxCols = row.length; } |
| | | rowShelfCells[r] = []; |
| | | } |
| | | const colWidths = new Array(maxCols); |
| | | for (let c = 0; c < maxCols; c++) { |
| | | let w = null; |
| | | for (let r = 0; r < map.length; r++) { |
| | | const cell = map[r] && map[r][c]; |
| | | if (!cell) { continue; } |
| | | if (cell.cellWidth != null && cell.cellWidth !== '') { |
| | | const base = Number(cell.cellWidth); |
| | | if (isFinite(base) && base > 0) { w = base / 40; break; } |
| | | } |
| | | } |
| | | colWidths[c] = (w && isFinite(w) && w > 0) ? w : 25; |
| | | } |
| | | const colOffsets = new Array(maxCols); |
| | | let xCursor = 0; |
| | | for (let c = 0; c < maxCols; c++) { |
| | | colOffsets[c] = xCursor; |
| | | xCursor += colWidths[c]; |
| | | } |
| | | for (let r = 0; r < map.length; r++) { |
| | | const row = map[r]; |
| | | if (!row || row.length === 0) { |
| | | rowColOffsets[r] = []; |
| | | rowColWidths[r] = []; |
| | | continue; |
| | | } |
| | | const widths = new Array(row.length); |
| | | for (let c = 0; c < row.length; c++) { |
| | | const cell = row[c]; |
| | | let w = null; |
| | | if (cell && cell.cellWidth != null && cell.cellWidth !== '') { |
| | | const base = Number(cell.cellWidth); |
| | | if (isFinite(base) && base > 0) { w = base / 40; } |
| | | } |
| | | widths[c] = (w && isFinite(w) && w > 0) ? w : 25; |
| | | } |
| | | const offsets = new Array(row.length); |
| | | let x = 0; |
| | | for (let c = 0; c < row.length; c++) { |
| | | offsets[c] = x; |
| | | x += widths[c]; |
| | | } |
| | | rowColOffsets[r] = offsets; |
| | | rowColWidths[r] = widths; |
| | | } |
| | | this.mapColWidths = colWidths; |
| | | this.mapColOffsets = colOffsets; |
| | | this.mapRowColOffsets = rowColOffsets; |
| | | this.mapRowColWidths = rowColWidths; |
| | | this.mapRowShelfCells = rowShelfCells; |
| | | |
| | | for (let r = 0; r < map.length; r++) { |
| | | const row = map[r]; |
| | | if (!row) { continue; } |
| | | for (let c = 0; c < row.length; c++) { |
| | | const cell = row[c]; |
| | | if (!cell || cell.type !== 'shelf') { continue; } |
| | | const startRow = this.findIndexByOffsets(this.mapRowOffsets, this.mapRowHeights, cell.posY + 0.01); |
| | | const endRow = this.findIndexByOffsets(this.mapRowOffsets, this.mapRowHeights, cell.posY + cell.height - 0.01); |
| | | if (startRow < 0) { continue; } |
| | | const last = endRow >= 0 ? endRow : startRow; |
| | | for (let rr = startRow; rr <= last; rr++) { |
| | | if (!rowShelfCells[rr]) { rowShelfCells[rr] = []; } |
| | | rowShelfCells[rr].push(cell); |
| | | } |
| | | } |
| | | } |
| | | }, |
| | | clearShelfChunks() { |
| | | if (this.shelfCullRaf) { |
| | | cancelAnimationFrame(this.shelfCullRaf); |
| | | this.shelfCullRaf = null; |
| | | } |
| | | this.shelfChunkList = []; |
| | | if (!this.shelvesContainer) { return; } |
| | | const children = this.shelvesContainer.removeChildren(); |
| | | children.forEach((child) => { |
| | | if (child && typeof child.destroy === 'function') { |
| | | child.destroy({ children: true, texture: true, baseTexture: true }); |
| | | } |
| | | }); |
| | | }, |
| | | buildShelfChunks(map, contentW, contentH) { |
| | | this.clearShelfChunks(); |
| | | if (!this.pixiApp || !this.pixiApp.renderer || !this.shelvesContainer || !Array.isArray(map)) { return; } |
| | | const chunkSize = Math.max(256, parseInt(this.shelfChunkSize, 10) || 2048); |
| | | const chunkMap = new Map(); |
| | | for (let r = 0; r < map.length; r++) { |
| | | const row = map[r]; |
| | | if (!row) { continue; } |
| | | for (let c = 0; c < row.length; c++) { |
| | | const cell = row[c]; |
| | | if (!cell || cell.type !== 'shelf' || cell.type === 'merge') { continue; } |
| | | const startChunkX = Math.floor(cell.posX / chunkSize); |
| | | const endChunkX = Math.floor((cell.posX + Math.max(1, cell.width) - 0.01) / chunkSize); |
| | | const startChunkY = Math.floor(cell.posY / chunkSize); |
| | | const endChunkY = Math.floor((cell.posY + Math.max(1, cell.height) - 0.01) / chunkSize); |
| | | for (let chunkY = startChunkY; chunkY <= endChunkY; chunkY++) { |
| | | for (let chunkX = startChunkX; chunkX <= endChunkX; chunkX++) { |
| | | const key = chunkX + ',' + chunkY; |
| | | let list = chunkMap.get(key); |
| | | if (!list) { |
| | | list = []; |
| | | chunkMap.set(key, list); |
| | | } |
| | | list.push(cell); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | const chunkList = []; |
| | | chunkMap.forEach((cells, key) => { |
| | | const keyParts = key.split(','); |
| | | const chunkX = parseInt(keyParts[0], 10) || 0; |
| | | const chunkY = parseInt(keyParts[1], 10) || 0; |
| | | const chunkLeft = chunkX * chunkSize; |
| | | const chunkTop = chunkY * chunkSize; |
| | | const chunkWidth = Math.max(1, Math.min(chunkSize, contentW - chunkLeft)); |
| | | const chunkHeight = Math.max(1, Math.min(chunkSize, contentH - chunkTop)); |
| | | const graphics = new PIXI.Graphics(); |
| | | graphics.beginFill(0xb6e2e2); |
| | | graphics.lineStyle(1, 0xffffff, 1); |
| | | for (let i = 0; i < cells.length; i++) { |
| | | const cell = cells[i]; |
| | | graphics.drawRect(cell.posX - chunkLeft, cell.posY - chunkTop, cell.width, cell.height); |
| | | } |
| | | graphics.endFill(); |
| | | const texture = this.pixiApp.renderer.generateTexture( |
| | | graphics, |
| | | PIXI.SCALE_MODES.LINEAR, |
| | | 1, |
| | | new PIXI.Rectangle(0, 0, chunkWidth, chunkHeight) |
| | | ); |
| | | graphics.destroy(true); |
| | | const sprite = new PIXI.Sprite(texture); |
| | | sprite.position.set(chunkLeft, chunkTop); |
| | | sprite._chunkBounds = { |
| | | x: chunkLeft, |
| | | y: chunkTop, |
| | | width: chunkWidth, |
| | | height: chunkHeight |
| | | }; |
| | | this.shelvesContainer.addChild(sprite); |
| | | chunkList.push(sprite); |
| | | }); |
| | | this.shelfChunkList = chunkList; |
| | | this.updateVisibleShelfChunks(); |
| | | }, |
| | | getViewportLocalBounds(padding) { |
| | | if (!this.mapRoot || !this.pixiApp) { return null; } |
| | | const viewport = this.getViewportSize(); |
| | | const pad = Math.max(0, Number(padding) || 0); |
| | | const points = [ |
| | | new PIXI.Point(-pad, -pad), |
| | | new PIXI.Point(viewport.width + pad, -pad), |
| | | new PIXI.Point(-pad, viewport.height + pad), |
| | | new PIXI.Point(viewport.width + pad, viewport.height + pad) |
| | | ]; |
| | | let minX = Infinity; |
| | | let minY = Infinity; |
| | | let maxX = -Infinity; |
| | | let maxY = -Infinity; |
| | | points.forEach((point) => { |
| | | const local = this.mapRoot.toLocal(point); |
| | | if (local.x < minX) { minX = local.x; } |
| | | if (local.y < minY) { minY = local.y; } |
| | | if (local.x > maxX) { maxX = local.x; } |
| | | if (local.y > maxY) { maxY = local.y; } |
| | | }); |
| | | if (!isFinite(minX) || !isFinite(minY) || !isFinite(maxX) || !isFinite(maxY)) { return null; } |
| | | return { minX: minX, minY: minY, maxX: maxX, maxY: maxY }; |
| | | }, |
| | | updateVisibleShelfChunks() { |
| | | if (!this.shelfChunkList || this.shelfChunkList.length === 0) { return; } |
| | | const localBounds = this.getViewportLocalBounds(this.shelfCullPadding); |
| | | if (!localBounds) { return; } |
| | | for (let i = 0; i < this.shelfChunkList.length; i++) { |
| | | const sprite = this.shelfChunkList[i]; |
| | | const bounds = sprite && sprite._chunkBounds; |
| | | if (!bounds) { continue; } |
| | | const visible = bounds.x < localBounds.maxX && |
| | | bounds.x + bounds.width > localBounds.minX && |
| | | bounds.y < localBounds.maxY && |
| | | bounds.y + bounds.height > localBounds.minY; |
| | | if (sprite.visible !== visible) { |
| | | sprite.visible = visible; |
| | | } |
| | | } |
| | | }, |
| | | scheduleShelfChunkCulling() { |
| | | if (this.shelfCullRaf) { return; } |
| | | this.shelfCullRaf = requestAnimationFrame(() => { |
| | | this.shelfCullRaf = null; |
| | | this.updateVisibleShelfChunks(); |
| | | }); |
| | | }, |
| | | findIndexByOffsets(offsets, sizes, value) { |
| | | if (!offsets || !sizes || offsets.length === 0) { return -1; } |
| | | for (let i = 0; i < offsets.length; i++) { |
| | | const start = offsets[i]; |
| | | const end = start + (sizes[i] || 0); |
| | | if (value >= start && value < end) { return i; } |
| | | } |
| | | return -1; |
| | | }, |
| | | updateShelfHoverFromPointer(globalPos) { |
| | | if (!this.map || !this.mapRoot) { return; } |
| | | if (!this.mapRowOffsets.length || !this.mapColOffsets.length) { return; } |
| | | const local = this.mapRoot.toLocal(new PIXI.Point(globalPos.x, globalPos.y)); |
| | | const rowIndex = this.findIndexByOffsets(this.mapRowOffsets, this.mapRowHeights, local.y); |
| | | if (rowIndex < 0) { if (this.hoveredShelfCell) { this.hoveredShelfCell = null; this.hideShelfTooltip(); } return; } |
| | | let cell = null; |
| | | if (this.mapRowShelfCells && this.mapRowShelfCells[rowIndex]) { |
| | | const list = this.mapRowShelfCells[rowIndex]; |
| | | for (let i = 0; i < list.length; i++) { |
| | | const it = list[i]; |
| | | if (!it) { continue; } |
| | | if (local.x >= it.posX && local.x < it.posX + it.width && |
| | | local.y >= it.posY && local.y < it.posY + it.height) { |
| | | cell = it; |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | if (!cell || cell.type !== 'shelf') { if (this.hoveredShelfCell) { this.hoveredShelfCell = null; this.hideShelfTooltip(); } return; } |
| | | if (this.hoveredShelfCell !== cell) { |
| | | this.hoveredShelfCell = cell; |
| | | this.shelfTooltip.item = cell; |
| | | this.shelfTooltip.text = this.getShelfArrangeInfo(cell); |
| | | this.shelfTooltip.visible = true; |
| | | } |
| | | this.updateShelfTooltipPositionByGlobal(globalPos); |
| | | }, |
| | | normalizeLocTypeKey(value) { |
| | | if (value == null) { return null; } |
| | | const str = String(value).trim(); |
| | | if (!str) { return null; } |
| | | const parts = str.split('-').filter(p => p !== ''); |
| | | if (parts.length >= 3) { return parts.slice(0, parts.length - 1).join('-'); } |
| | | return str; |
| | | }, |
| | | loadLocList() { |
| | | if (!window.$ || typeof baseUrl === 'undefined') { return; } |
| | | if (this.locListLoading) { return; } |
| | | this.locListLoading = true; |
| | | $.ajax({ |
| | | url: baseUrl + "/console/map/locList", |
| | | headers: { 'token': localStorage.getItem('token') }, |
| | | dataType: 'json', |
| | | method: 'GET', |
| | | success: (res) => { |
| | | if (res && !Array.isArray(res)) { |
| | | if (res.code === 403) { parent.location.href = baseUrl + "/login"; return; } |
| | | if (res.code !== 200) { return; } |
| | | } |
| | | const list = Array.isArray(res) ? res : (res && res.code === 200 ? res.data : null); |
| | | if (!list || !Array.isArray(list)) { return; } |
| | | const map = new Map(); |
| | | list.forEach((item) => { |
| | | if (!item) { return; } |
| | | const locType = item.locType != null ? item.locType : item.loc_type; |
| | | if (locType != null && locType !== '') { |
| | | const normalizedType = this.normalizeLocTypeKey(locType); |
| | | if (normalizedType && !map.has(normalizedType)) { map.set(normalizedType, item); } |
| | | } |
| | | }); |
| | | this.locListMap = map; |
| | | this.locListLoaded = true; |
| | | if (this.shelfTooltip.visible) { |
| | | this.shelfTooltip.text = this.getShelfArrangeInfo(this.shelfTooltip.item); |
| | | } |
| | | }, |
| | | complete: () => { |
| | | this.locListLoading = false; |
| | | } |
| | | }); |
| | | }, |
| | | showShelfTooltip(e, item) { |
| | | if (!item) { return; } |
| | | if (!this.isShelfTooltipAllowed()) { this.hideShelfTooltip(); return; } |
| | | if (!this.locListLoaded && !this.locListLoading) { this.loadLocList(); } |
| | | this.shelfTooltip.item = item; |
| | | this.shelfTooltip.text = this.getShelfArrangeInfo(item); |
| | | this.updateShelfTooltipPosition(e); |
| | | this.shelfTooltip.visible = true; |
| | | }, |
| | | updateShelfTooltipPosition(e) { |
| | | if (!e || !e.data || !e.data.global) { return; } |
| | | this.updateShelfTooltipPositionByGlobal(e.data.global); |
| | | }, |
| | | updateShelfTooltipPositionByGlobal(globalPos) { |
| | | if (!this.isShelfTooltipAllowed()) { this.hideShelfTooltip(); return; } |
| | | if (!globalPos) { return; } |
| | | this.shelfTooltip.x = globalPos.x + 12; |
| | | this.shelfTooltip.y = globalPos.y + 12; |
| | | }, |
| | | hideShelfTooltip() { |
| | | this.shelfTooltip.visible = false; |
| | | this.shelfTooltip.item = null; |
| | | }, |
| | | isShelfTooltipAllowed() { |
| | | return this.getStageAbsScale() >= this.shelfTooltipMinScale; |
| | | }, |
| | | getStageAbsScale() { |
| | | if (!this.pixiApp || !this.pixiApp.stage) { return 1; } |
| | | return Math.abs(this.pixiApp.stage.scale.x || 1); |
| | | }, |
| | | updateShelfTooltipVisibilityByScale() { |
| | | if (this.shelfTooltip.visible && !this.isShelfTooltipAllowed()) { |
| | | this.hideShelfTooltip(); |
| | | this.hoveredShelfCell = null; |
| | | } |
| | | }, |
| | | getShelfArrangeInfo(item) { |
| | | const parts = []; |
| | | const matchKey = this.getShelfMatchKey(item); |
| | | if (matchKey != null) { parts.push('坐标:' + matchKey); } |
| | | const locInfo = (matchKey != null) ? this.locListMap.get(matchKey) : null; |
| | | if (locInfo) { |
| | | const locNo = locInfo.locNo != null ? locInfo.locNo : locInfo.loc_no; |
| | | const displayLocNo = this.stripLocLayer(locNo); |
| | | if (displayLocNo != null) { parts.push('排列:' + displayLocNo); } |
| | | } |
| | | return parts.join(' '); |
| | | }, |
| | | getShelfMatchKey(item) { |
| | | if (!item) { return null; } |
| | | const direct = item.locType != null ? item.locType : (item.loc_type != null ? item.loc_type : null); |
| | | const directKey = this.normalizeLocTypeKey(direct); |
| | | if (directKey) { return directKey; } |
| | | const rowIndex = item.rowIndex; |
| | | const colIndex = item.colIndex; |
| | | if (rowIndex == null || colIndex == null) { return null; } |
| | | const key0 = rowIndex + '-' + colIndex; |
| | | if (this.locListLoaded && this.locListMap && this.locListMap.size > 0) { |
| | | if (this.locListMap.has(key0)) { return key0; } |
| | | } |
| | | return null; |
| | | }, |
| | | stripLocLayer(locNo) { |
| | | if (locNo == null) { return null; } |
| | | const str = String(locNo).trim(); |
| | | if (!str) { return null; } |
| | | const parts = str.split('-').filter(p => p !== ''); |
| | | if (parts.length >= 3) { return parts.slice(0, parts.length - 1).join('-'); } |
| | | return str; |
| | | }, |
| | | shelfTooltipStyle() { |
| | | return { |
| | | position: 'absolute', |
| | | left: this.shelfTooltip.x + 'px', |
| | | top: this.shelfTooltip.y + 'px', |
| | | background: 'rgba(0,0,0,0.75)', |
| | | color: '#ffffff', |
| | | padding: '4px 8px', |
| | | borderRadius: '4px', |
| | | fontSize: '12px', |
| | | pointerEvents: 'none', |
| | | whiteSpace: 'nowrap', |
| | | zIndex: 10 |
| | | }; |
| | | }, |
| | | adjustLabelScale() { |
| | | const s = this.pixiApp && this.pixiApp.stage ? (this.pixiApp.stage.scale.x || 1) : 1; |
| | | const s = this.pixiApp && this.pixiApp.stage ? Math.abs(this.pixiApp.stage.scale.x || 1) : 1; |
| | | const minPx = 14; |
| | | const vw = this.pixiApp.view.width; |
| | | const vh = this.pixiApp.view.height; |
| | | const pos = this.pixiApp.stage.position; |
| | | const viewport = this.getViewportSize(); |
| | | const vw = viewport.width; |
| | | const vh = viewport.height; |
| | | const margin = 50; |
| | | const mirrorSign = this.mapMirrorX ? -1 : 1; |
| | | const inverseRotation = -((this.mapRotation % 360) * Math.PI / 180); |
| | | const tmpPoint = new PIXI.Point(); |
| | | this.pixiStaMap && this.pixiStaMap.forEach((sprite) => { |
| | | const textObj = sprite && sprite.textObj; |
| | | if (!textObj) { return; } |
| | |
| | | let scale = minPx / (base * s); |
| | | if (!isFinite(scale)) { scale = 1; } |
| | | scale = Math.max(0.8, Math.min(scale, 3)); |
| | | textObj.scale.set(scale); |
| | | textObj.scale.set(scale * mirrorSign, scale); |
| | | textObj.rotation = inverseRotation; |
| | | textObj.position.set(sprite.width / 2, sprite.height / 2); |
| | | const sx = pos.x + sprite.x * s; |
| | | const sy = pos.y + sprite.y * s; |
| | | const on = sx >= -margin && sy >= -margin && sx <= vw + margin && sy <= vh + margin; |
| | | sprite.getGlobalPosition(tmpPoint); |
| | | const on = tmpPoint.x >= -margin && tmpPoint.y >= -margin && tmpPoint.x <= vw + margin && tmpPoint.y <= vh + margin; |
| | | textObj.visible = (s >= 0.25) && on; |
| | | }); |
| | | this.pixiCrnMap && this.pixiCrnMap.forEach((sprite) => { |
| | |
| | | let scale = minPx / (base * s); |
| | | if (!isFinite(scale)) { scale = 1; } |
| | | scale = Math.max(0.8, Math.min(scale, 3)); |
| | | textObj.scale.set(scale); |
| | | textObj.scale.set(scale * mirrorSign, scale); |
| | | textObj.rotation = inverseRotation; |
| | | textObj.position.set(sprite.width / 2, sprite.height / 2); |
| | | const sx = pos.x + sprite.x * s; |
| | | const sy = pos.y + sprite.y * s; |
| | | const on = sx >= -margin && sy >= -margin && sx <= vw + margin && sy <= vh + margin; |
| | | sprite.getGlobalPosition(tmpPoint); |
| | | const on = tmpPoint.x >= -margin && tmpPoint.y >= -margin && tmpPoint.x <= vw + margin && tmpPoint.y <= vh + margin; |
| | | textObj.visible = (s >= 0.25) && on; |
| | | }); |
| | | this.pixiDualCrnMap && this.pixiDualCrnMap.forEach((sprite) => { |
| | |
| | | let scale = minPx / (base * s); |
| | | if (!isFinite(scale)) { scale = 1; } |
| | | scale = Math.max(0.8, Math.min(scale, 3)); |
| | | textObj.scale.set(scale); |
| | | textObj.scale.set(scale * mirrorSign, scale); |
| | | textObj.rotation = inverseRotation; |
| | | textObj.position.set(sprite.width / 2, sprite.height / 2); |
| | | const sx = pos.x + sprite.x * s; |
| | | const sy = pos.y + sprite.y * s; |
| | | const on = sx >= -margin && sy >= -margin && sx <= vw + margin && sy <= vh + margin; |
| | | sprite.getGlobalPosition(tmpPoint); |
| | | const on = tmpPoint.x >= -margin && tmpPoint.y >= -margin && tmpPoint.x <= vw + margin && tmpPoint.y <= vh + margin; |
| | | textObj.visible = (s >= 0.25) && on; |
| | | }); |
| | | this.pixiRgvMap && this.pixiRgvMap.forEach((sprite) => { |
| | |
| | | let scale = minPx / (base * s); |
| | | if (!isFinite(scale)) { scale = 1; } |
| | | scale = Math.max(0.8, Math.min(scale, 3)); |
| | | textObj.scale.set(scale); |
| | | textObj.scale.set(scale * mirrorSign, scale); |
| | | textObj.rotation = inverseRotation; |
| | | textObj.position.set(sprite.width / 2, sprite.height / 2); |
| | | const sx = pos.x + sprite.x * s; |
| | | const sy = pos.y * s + pos.y; |
| | | const on = sx >= -margin && sy >= -margin && sx <= vw + margin && sy <= vh + margin; |
| | | sprite.getGlobalPosition(tmpPoint); |
| | | const on = tmpPoint.x >= -margin && tmpPoint.y >= -margin && tmpPoint.x <= vw + margin && tmpPoint.y <= vh + margin; |
| | | textObj.visible = (s >= 0.25) && on; |
| | | }); |
| | | }, |
| | | rotateMap() { |
| | | this.mapRotation = (this.mapRotation + 90) % 360; |
| | | this.applyMapTransform(true); |
| | | this.saveMapTransformConfig(); |
| | | }, |
| | | toggleStationDirection() { |
| | | this.showStationDirection = !this.showStationDirection; |
| | | this.applyStationDirectionVisibility(); |
| | | }, |
| | | toggleMirror() { |
| | | this.mapMirrorX = !this.mapMirrorX; |
| | | this.applyMapTransform(true); |
| | | this.saveMapTransformConfig(); |
| | | }, |
| | | openStationColorConfigPage() { |
| | | if (typeof window === 'undefined') { return; } |
| | | const url = (typeof baseUrl !== 'undefined' ? baseUrl : '') + '/views/watch/stationColorConfig.html'; |
| | | const layerInstance = (window.top && window.top.layer) || window.layer; |
| | | if (layerInstance && typeof layerInstance.open === 'function') { |
| | | layerInstance.open({ |
| | | type: 2, |
| | | title: '站点颜色配置', |
| | | maxmin: true, |
| | | area: ['980px', '760px'], |
| | | shadeClose: false, |
| | | content: url |
| | | }); |
| | | return; |
| | | } |
| | | window.open(url, '_blank'); |
| | | }, |
| | | parseRotation(value) { |
| | | const num = parseInt(value, 10); |
| | | if (!isFinite(num)) { return 0; } |
| | | const rot = ((num % 360) + 360) % 360; |
| | | return (rot === 90 || rot === 180 || rot === 270) ? rot : 0; |
| | | }, |
| | | parseMirror(value) { |
| | | if (value === true || value === false) { return value; } |
| | | if (value == null) { return false; } |
| | | const str = String(value).toLowerCase(); |
| | | return str === '1' || str === 'true' || str === 'y'; |
| | | }, |
| | | getDefaultStationStatusColors() { |
| | | return { |
| | | 'site-auto': 0x78ff81, |
| | | 'site-auto-run': 0xfa51f6, |
| | | 'site-auto-id': 0xc4c400, |
| | | 'site-auto-run-id': 0x30bffc, |
| | | 'site-enable-in': 0xA81DEE, |
| | | 'site-unauto': 0xb8b8b8, |
| | | 'machine-pakin': 0x30bffc, |
| | | 'machine-pakout': 0x97b400, |
| | | 'site-run-block': 0xe69138 |
| | | }; |
| | | }, |
| | | parseColorConfigValue(value, fallback) { |
| | | if (typeof value === 'number' && isFinite(value)) { |
| | | return value; |
| | | } |
| | | const str = String(value == null ? '' : value).trim(); |
| | | if (!str) { return fallback; } |
| | | if (/^#[0-9a-fA-F]{6}$/.test(str)) { return parseInt(str.slice(1), 16); } |
| | | if (/^#[0-9a-fA-F]{3}$/.test(str)) { |
| | | const expanded = str.charAt(1) + str.charAt(1) + str.charAt(2) + str.charAt(2) + str.charAt(3) + str.charAt(3); |
| | | return parseInt(expanded, 16); |
| | | } |
| | | if (/^0x[0-9a-fA-F]{6}$/i.test(str)) { return parseInt(str.slice(2), 16); } |
| | | if (/^[0-9]+$/.test(str)) { |
| | | const num = parseInt(str, 10); |
| | | return isNaN(num) ? fallback : num; |
| | | } |
| | | return fallback; |
| | | }, |
| | | loadStationColorConfig() { |
| | | if (!window.$ || typeof baseUrl === 'undefined') { return; } |
| | | $.ajax({ |
| | | url: baseUrl + "/watch/stationColor/config/auth", |
| | | headers: { 'token': localStorage.getItem('token') }, |
| | | dataType: 'json', |
| | | method: 'GET', |
| | | success: (res) => { |
| | | if (!res || res.code !== 200 || !res.data) { |
| | | if (res && res.code === 403) { parent.location.href = baseUrl + "/login"; } |
| | | return; |
| | | } |
| | | this.applyStationColorConfigPayload(res.data); |
| | | } |
| | | }); |
| | | }, |
| | | applyStationColorConfigPayload(data) { |
| | | const defaults = this.getDefaultStationStatusColors(); |
| | | const nextColors = Object.assign({}, defaults); |
| | | const items = Array.isArray(data.items) ? data.items : []; |
| | | items.forEach((item) => { |
| | | if (!item || !item.status || defaults[item.status] == null) { return; } |
| | | nextColors[item.status] = this.parseColorConfigValue(item.color, defaults[item.status]); |
| | | }); |
| | | this.stationStatusColors = nextColors; |
| | | }, |
| | | buildMissingMapConfigList(byCode) { |
| | | const createList = []; |
| | | if (!byCode[this.mapConfigCodes.rotate]) { |
| | | createList.push({ |
| | | name: '地图旋转', |
| | | code: this.mapConfigCodes.rotate, |
| | | value: String(this.mapRotation || 0), |
| | | type: 1, |
| | | status: 1, |
| | | selectType: 'map' |
| | | }); |
| | | } |
| | | if (!byCode[this.mapConfigCodes.mirror]) { |
| | | createList.push({ |
| | | name: '地图镜像', |
| | | code: this.mapConfigCodes.mirror, |
| | | value: this.mapMirrorX ? '1' : '0', |
| | | type: 1, |
| | | status: 1, |
| | | selectType: 'map' |
| | | }); |
| | | } |
| | | return createList; |
| | | }, |
| | | createMapConfigs(createList) { |
| | | if (!window.$ || typeof baseUrl === 'undefined' || !Array.isArray(createList) || createList.length === 0) { return; } |
| | | createList.forEach((cfg) => { |
| | | $.ajax({ |
| | | url: baseUrl + "/config/add/auth", |
| | | headers: { 'token': localStorage.getItem('token') }, |
| | | method: 'POST', |
| | | data: cfg |
| | | }); |
| | | }); |
| | | }, |
| | | loadMapTransformConfig() { |
| | | if (!window.$ || typeof baseUrl === 'undefined') { return; } |
| | | $.ajax({ |
| | | url: baseUrl + "/config/listAll/auth", |
| | | headers: { 'token': localStorage.getItem('token') }, |
| | | dataType: 'json', |
| | | method: 'GET', |
| | | success: (res) => { |
| | | if (!res || res.code !== 200 || !Array.isArray(res.data)) { |
| | | if (res && res.code === 403) { parent.location.href = baseUrl + "/login"; } |
| | | return; |
| | | } |
| | | const byCode = {}; |
| | | res.data.forEach((item) => { |
| | | if (item && item.code) { byCode[item.code] = item; } |
| | | }); |
| | | const rotateCfg = byCode[this.mapConfigCodes.rotate]; |
| | | const mirrorCfg = byCode[this.mapConfigCodes.mirror]; |
| | | if (rotateCfg && rotateCfg.value != null) { |
| | | this.mapRotation = this.parseRotation(rotateCfg.value); |
| | | } |
| | | if (mirrorCfg && mirrorCfg.value != null) { |
| | | this.mapMirrorX = this.parseMirror(mirrorCfg.value); |
| | | } |
| | | this.createMapConfigs(this.buildMissingMapConfigList(byCode)); |
| | | if (this.mapContentSize && this.mapContentSize.width > 0) { |
| | | this.applyMapTransform(true); |
| | | } |
| | | } |
| | | }); |
| | | }, |
| | | saveMapTransformConfig() { |
| | | if (!window.$ || typeof baseUrl === 'undefined') { return; } |
| | | const updateList = [ |
| | | { code: this.mapConfigCodes.rotate, value: String(this.mapRotation || 0) }, |
| | | { code: this.mapConfigCodes.mirror, value: this.mapMirrorX ? '1' : '0' } |
| | | ]; |
| | | $.ajax({ |
| | | url: baseUrl + "/config/updateBatch", |
| | | headers: { 'token': localStorage.getItem('token') }, |
| | | data: JSON.stringify(updateList), |
| | | dataType: 'json', |
| | | contentType: 'application/json;charset=UTF-8', |
| | | method: 'POST' |
| | | }); |
| | | }, |
| | | getTransformedContentSize() { |
| | | const size = this.mapContentSize || { width: 0, height: 0 }; |
| | | const w = size.width || 0; |
| | | const h = size.height || 0; |
| | | const rot = ((this.mapRotation % 360) + 360) % 360; |
| | | const swap = rot === 90 || rot === 270; |
| | | return { width: swap ? h : w, height: swap ? w : h }; |
| | | }, |
| | | fitStageToContent() { |
| | | if (!this.pixiApp || !this.mapContentSize) { return; } |
| | | const size = this.getTransformedContentSize(); |
| | | const contentW = size.width || 0; |
| | | const contentH = size.height || 0; |
| | | if (contentW <= 0 || contentH <= 0) { return; } |
| | | const viewport = this.getViewportSize(); |
| | | const vw = viewport.width; |
| | | const vh = viewport.height; |
| | | const padding = this.getViewportPadding(); |
| | | const availableW = Math.max(1, vw - padding.left - padding.right); |
| | | const availableH = Math.max(1, vh - padding.top - padding.bottom); |
| | | let scale = Math.min(availableW / contentW, availableH / contentH) * 0.95; |
| | | if (!isFinite(scale) || scale <= 0) { scale = 1; } |
| | | const baseW = this.mapContentSize.width || contentW; |
| | | const baseH = this.mapContentSize.height || contentH; |
| | | const mirrorX = this.mapMirrorX ? -1 : 1; |
| | | const scaleX = scale * mirrorX; |
| | | const scaleY = scale; |
| | | const centerX = padding.left + availableW / 2; |
| | | const centerY = padding.top + availableH / 2; |
| | | const posX = centerX - (baseW / 2) * scaleX; |
| | | const posY = centerY - (baseH / 2) * scaleY; |
| | | this.pixiApp.stage.setTransform(posX, posY, scaleX, scaleY, 0, 0, 0, 0, 0); |
| | | }, |
| | | applyMapTransform(fitToView) { |
| | | if (!this.mapRoot || !this.mapContentSize) { return; } |
| | | const contentW = this.mapContentSize.width || 0; |
| | | const contentH = this.mapContentSize.height || 0; |
| | | if (contentW <= 0 || contentH <= 0) { return; } |
| | | this.mapRoot.pivot.set(contentW / 2, contentH / 2); |
| | | this.mapRoot.position.set(contentW / 2, contentH / 2); |
| | | this.mapRoot.rotation = (this.mapRotation % 360) * Math.PI / 180; |
| | | this.mapRoot.scale.set(1, 1); |
| | | if (fitToView) { this.fitStageToContent(); } |
| | | this.scheduleAdjustLabels(); |
| | | this.scheduleShelfChunkCulling(); |
| | | }, |
| | | scheduleAdjustLabels() { |
| | | if (this.adjustLabelTimer) { clearTimeout(this.adjustLabelTimer); } |
| | | this.adjustLabelTimer = setTimeout(() => { |
| | | this.adjustLabelScale(); |
| | | this.updateShelfTooltipVisibilityByScale(); |
| | | this.adjustLabelTimer = null; |
| | | }, 20); |
| | | } |
| | | } |
| | | }); |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |