1269 lines
37 KiB
TypeScript
1269 lines
37 KiB
TypeScript
import {
|
|
MapEngine,
|
|
type MapEngineGameInfoRow,
|
|
type MapEngineGameInfoSnapshot,
|
|
type MapEngineStageRect,
|
|
type MapEngineViewState,
|
|
} from '../../engine/map/mapEngine'
|
|
import { loadRemoteMapConfig } from '../../utils/remoteMapConfig'
|
|
type CompassTickData = {
|
|
angle: number
|
|
long: boolean
|
|
major: boolean
|
|
}
|
|
type CompassLabelData = {
|
|
text: string
|
|
angle: number
|
|
rotateBack: number
|
|
radius: number
|
|
className: string
|
|
}
|
|
type ScaleRulerMinorTickData = {
|
|
key: string
|
|
topPx: number
|
|
long: boolean
|
|
}
|
|
type ScaleRulerMajorMarkData = {
|
|
key: string
|
|
topPx: number
|
|
label: string
|
|
}
|
|
type SideButtonMode = 'all' | 'left' | 'right' | 'hidden'
|
|
type SideActionButtonState = 'muted' | 'default' | 'active'
|
|
type CenterScaleRulerAnchorMode = 'screen-center' | 'compass-center'
|
|
type MapPageData = MapEngineViewState & {
|
|
showDebugPanel: boolean
|
|
showGameInfoPanel: boolean
|
|
showCenterScaleRuler: boolean
|
|
centerScaleRulerAnchorMode: CenterScaleRulerAnchorMode
|
|
statusBarHeight: number
|
|
topInsetHeight: number
|
|
hudPanelIndex: number
|
|
configSourceText: string
|
|
mockBridgeUrlDraft: string
|
|
mockHeartRateBridgeUrlDraft: string
|
|
gameInfoTitle: string
|
|
gameInfoSubtitle: string
|
|
gameInfoLocalRows: MapEngineGameInfoRow[]
|
|
gameInfoGlobalRows: MapEngineGameInfoRow[]
|
|
panelTimerText: string
|
|
panelMileageText: string
|
|
panelDistanceValueText: string
|
|
panelProgressText: string
|
|
panelSpeedValueText: string
|
|
compassTicks: CompassTickData[]
|
|
compassLabels: CompassLabelData[]
|
|
sideButtonMode: SideButtonMode
|
|
sideToggleIconSrc: string
|
|
sideButton2Class: string
|
|
sideButton4Class: string
|
|
sideButton11Class: string
|
|
sideButton13Class: string
|
|
sideButton14Class: string
|
|
sideButton16Class: string
|
|
centerScaleRulerVisible: boolean
|
|
centerScaleRulerCenterXPx: number
|
|
centerScaleRulerZeroYPx: number
|
|
centerScaleRulerHeightPx: number
|
|
centerScaleRulerAxisBottomPx: number
|
|
centerScaleRulerZeroVisible: boolean
|
|
centerScaleRulerZeroLabel: string
|
|
centerScaleRulerMinorTicks: ScaleRulerMinorTickData[]
|
|
centerScaleRulerMajorMarks: ScaleRulerMajorMarkData[]
|
|
showLeftButtonGroup: boolean
|
|
showRightButtonGroups: boolean
|
|
showBottomDebugButton: boolean
|
|
}
|
|
const INTERNAL_BUILD_VERSION = 'map-build-252'
|
|
const CLASSIC_REMOTE_GAME_CONFIG_URL = 'https://oss-mbh5.colormaprun.com/gotomars/event/classic-sequential.json'
|
|
const SCORE_O_REMOTE_GAME_CONFIG_URL = 'https://oss-mbh5.colormaprun.com/gotomars/event/score-o.json'
|
|
let mapEngine: MapEngine | null = null
|
|
let stageCanvasAttached = false
|
|
function buildSideButtonVisibility(mode: SideButtonMode) {
|
|
return {
|
|
sideButtonMode: mode,
|
|
showLeftButtonGroup: mode === 'all' || mode === 'left' || mode === 'right',
|
|
showRightButtonGroups: mode === 'all' || mode === 'right',
|
|
showBottomDebugButton: mode !== 'hidden',
|
|
}
|
|
}
|
|
|
|
function getNextSideButtonMode(currentMode: SideButtonMode): SideButtonMode {
|
|
if (currentMode === 'all') {
|
|
return 'left'
|
|
}
|
|
if (currentMode === 'left') {
|
|
return 'right'
|
|
}
|
|
if (currentMode === 'right') {
|
|
return 'hidden'
|
|
}
|
|
return 'left'
|
|
}
|
|
function buildCompassTicks(): CompassTickData[] {
|
|
const ticks: CompassTickData[] = []
|
|
for (let angle = 0; angle < 360; angle += 5) {
|
|
ticks.push({
|
|
angle,
|
|
long: angle % 15 === 0,
|
|
major: angle % 45 === 0,
|
|
})
|
|
}
|
|
return ticks
|
|
}
|
|
function buildCompassLabels(): CompassLabelData[] {
|
|
return [
|
|
{ text: '\u5317', angle: 0, rotateBack: 0, radius: 68, className: 'compass-widget__mark--cardinal compass-widget__mark--north' },
|
|
{ text: '\u4e1c\u5317', angle: 45, rotateBack: 0, radius: 58, className: 'compass-widget__mark--intermediate compass-widget__mark--northeast' },
|
|
{ text: '\u4e1c', angle: 90, rotateBack: 0, radius: 68, className: 'compass-widget__mark--cardinal' },
|
|
{ text: '\u4e1c\u5357', angle: 135, rotateBack: 0, radius: 58, className: 'compass-widget__mark--intermediate' },
|
|
{ text: '\u5357', angle: 180, rotateBack: 0, radius: 68, className: 'compass-widget__mark--cardinal' },
|
|
{ text: '\u897f\u5357', angle: 225, rotateBack: 0, radius: 58, className: 'compass-widget__mark--intermediate' },
|
|
{ text: '\u897f', angle: 270, rotateBack: 0, radius: 68, className: 'compass-widget__mark--cardinal' },
|
|
{ text: '\u897f\u5317', angle: 315, rotateBack: 0, radius: 58, className: 'compass-widget__mark--intermediate compass-widget__mark--northwest' },
|
|
]
|
|
}
|
|
function getFallbackStageRect(): MapEngineStageRect {
|
|
const systemInfo = wx.getSystemInfoSync()
|
|
const width = Math.max(320, systemInfo.windowWidth)
|
|
const height = Math.max(280, systemInfo.windowHeight)
|
|
|
|
return {
|
|
width,
|
|
height,
|
|
left: 0,
|
|
top: 0,
|
|
}
|
|
}
|
|
|
|
function getSideToggleIconSrc(mode: SideButtonMode): string {
|
|
if (mode === 'left') {
|
|
return '../../assets/btn_more2.png'
|
|
}
|
|
if (mode === 'hidden') {
|
|
return '../../assets/btn_more1.png'
|
|
}
|
|
return '../../assets/btn_more3.png'
|
|
}
|
|
|
|
function getSideActionButtonClass(state: SideActionButtonState): string {
|
|
if (state === 'muted') {
|
|
return 'map-side-button map-side-button--muted'
|
|
}
|
|
if (state === 'active') {
|
|
return 'map-side-button map-side-button--active'
|
|
}
|
|
return 'map-side-button map-side-button--default'
|
|
}
|
|
|
|
function buildSideButtonState(data: Pick<MapPageData, 'sideButtonMode' | 'showGameInfoPanel' | 'showCenterScaleRuler' | 'centerScaleRulerAnchorMode' | 'skipButtonEnabled' | 'gameSessionStatus' | 'gpsLockEnabled' | 'gpsLockAvailable'>) {
|
|
const sideButton2State: SideActionButtonState = !data.gpsLockAvailable
|
|
? 'muted'
|
|
: data.gpsLockEnabled
|
|
? 'active'
|
|
: 'default'
|
|
const sideButton4State: SideActionButtonState = data.gameSessionStatus === 'idle' ? 'default' : 'active'
|
|
const sideButton11State: SideActionButtonState = data.showGameInfoPanel ? 'active' : 'default'
|
|
const sideButton13State: SideActionButtonState = data.showCenterScaleRuler ? 'active' : 'default'
|
|
const sideButton14State: SideActionButtonState = !data.showCenterScaleRuler
|
|
? 'muted'
|
|
: data.centerScaleRulerAnchorMode === 'compass-center'
|
|
? 'active'
|
|
: 'default'
|
|
const sideButton16State: SideActionButtonState = data.skipButtonEnabled ? 'default' : 'muted'
|
|
|
|
return {
|
|
sideToggleIconSrc: getSideToggleIconSrc(data.sideButtonMode),
|
|
sideButton2Class: getSideActionButtonClass(sideButton2State),
|
|
sideButton4Class: getSideActionButtonClass(sideButton4State),
|
|
sideButton11Class: getSideActionButtonClass(sideButton11State),
|
|
sideButton13Class: getSideActionButtonClass(sideButton13State),
|
|
sideButton14Class: getSideActionButtonClass(sideButton14State),
|
|
sideButton16Class: getSideActionButtonClass(sideButton16State),
|
|
}
|
|
}
|
|
|
|
function getRpxUnitInPx(): number {
|
|
const systemInfo = wx.getSystemInfoSync()
|
|
return systemInfo.windowWidth / 750
|
|
}
|
|
|
|
function worldTileYToLat(worldTileY: number, zoom: number): number {
|
|
const scale = Math.pow(2, zoom)
|
|
const n = Math.PI - (2 * Math.PI * worldTileY) / scale
|
|
return (180 / Math.PI) * Math.atan(Math.sinh(n))
|
|
}
|
|
|
|
function getNiceDistanceMeters(rawDistanceMeters: number): number {
|
|
if (!Number.isFinite(rawDistanceMeters) || rawDistanceMeters <= 0) {
|
|
return 50
|
|
}
|
|
|
|
const exponent = Math.floor(Math.log10(rawDistanceMeters))
|
|
const base = Math.pow(10, exponent)
|
|
const normalized = rawDistanceMeters / base
|
|
|
|
if (normalized <= 1) {
|
|
return base
|
|
}
|
|
if (normalized <= 2) {
|
|
return 2 * base
|
|
}
|
|
if (normalized <= 5) {
|
|
return 5 * base
|
|
}
|
|
return 10 * base
|
|
}
|
|
|
|
function formatScaleDistanceLabel(distanceMeters: number): string {
|
|
if (distanceMeters >= 1000) {
|
|
const distanceKm = distanceMeters / 1000
|
|
const formatted = distanceKm >= 10 ? distanceKm.toFixed(0) : distanceKm.toFixed(1)
|
|
return `${formatted.replace(/\.0$/, '')} km`
|
|
}
|
|
|
|
return `${Math.round(distanceMeters)} m`
|
|
}
|
|
|
|
function buildCenterScaleRulerPatch(data: Pick<MapPageData, 'showCenterScaleRuler' | 'centerScaleRulerAnchorMode' | 'stageWidth' | 'stageHeight' | 'topInsetHeight' | 'zoom' | 'centerTileY' | 'tileSizePx' | 'previewScale'>) {
|
|
if (!data.showCenterScaleRuler) {
|
|
return {
|
|
centerScaleRulerVisible: false,
|
|
centerScaleRulerCenterXPx: 0,
|
|
centerScaleRulerZeroYPx: 0,
|
|
centerScaleRulerHeightPx: 0,
|
|
centerScaleRulerAxisBottomPx: 0,
|
|
centerScaleRulerZeroVisible: false,
|
|
centerScaleRulerZeroLabel: '0 m',
|
|
centerScaleRulerMinorTicks: [] as ScaleRulerMinorTickData[],
|
|
centerScaleRulerMajorMarks: [] as ScaleRulerMajorMarkData[],
|
|
}
|
|
}
|
|
|
|
if (!data.stageWidth || !data.stageHeight) {
|
|
return {
|
|
centerScaleRulerVisible: false,
|
|
centerScaleRulerCenterXPx: 0,
|
|
centerScaleRulerZeroYPx: 0,
|
|
centerScaleRulerHeightPx: 0,
|
|
centerScaleRulerAxisBottomPx: 0,
|
|
centerScaleRulerZeroVisible: false,
|
|
centerScaleRulerZeroLabel: '0 m',
|
|
centerScaleRulerMinorTicks: [] as ScaleRulerMinorTickData[],
|
|
centerScaleRulerMajorMarks: [] as ScaleRulerMajorMarkData[],
|
|
}
|
|
}
|
|
|
|
const topPadding = 12
|
|
const rpxUnitPx = getRpxUnitInPx()
|
|
const compassBottomPaddingPx = 248 * rpxUnitPx
|
|
const compassDialRadiusPx = (196 * rpxUnitPx) / 2
|
|
const compassHeadingOverlayHeightPx = 40 * rpxUnitPx
|
|
const compassOcclusionPaddingPx = 10 * rpxUnitPx
|
|
const zeroYPx = data.centerScaleRulerAnchorMode === 'compass-center'
|
|
? Math.round(data.stageHeight - compassBottomPaddingPx - compassDialRadiusPx)
|
|
: Math.round(data.stageHeight / 2)
|
|
const fallbackHeight = Math.max(zeroYPx - topPadding, 160)
|
|
const coveredBottomPx = data.centerScaleRulerAnchorMode === 'compass-center'
|
|
? Math.round(compassDialRadiusPx + compassHeadingOverlayHeightPx + compassOcclusionPaddingPx)
|
|
: 0
|
|
|
|
if (
|
|
!data.tileSizePx
|
|
|| !Number.isFinite(data.zoom)
|
|
|| !Number.isFinite(data.centerTileY)
|
|
) {
|
|
return {
|
|
centerScaleRulerVisible: true,
|
|
centerScaleRulerCenterXPx: Math.round(data.stageWidth / 2),
|
|
centerScaleRulerZeroYPx: zeroYPx,
|
|
centerScaleRulerHeightPx: fallbackHeight,
|
|
centerScaleRulerAxisBottomPx: coveredBottomPx,
|
|
centerScaleRulerZeroVisible: data.centerScaleRulerAnchorMode !== 'compass-center',
|
|
centerScaleRulerZeroLabel: '0 m',
|
|
centerScaleRulerMinorTicks: [] as ScaleRulerMinorTickData[],
|
|
centerScaleRulerMajorMarks: [] as ScaleRulerMajorMarkData[],
|
|
}
|
|
}
|
|
|
|
const centerLat = worldTileYToLat(data.centerTileY + 0.5, data.zoom)
|
|
const metersPerTile = Math.cos(centerLat * Math.PI / 180) * 40075016.686 / Math.pow(2, data.zoom)
|
|
const metersPerPixel = metersPerTile / data.tileSizePx
|
|
const effectivePreviewScale = Number.isFinite(data.previewScale) && data.previewScale > 0 ? data.previewScale : 1
|
|
const effectiveMetersPerPixel = metersPerPixel / effectivePreviewScale
|
|
const rulerHeight = Math.floor(zeroYPx - topPadding)
|
|
|
|
if (!Number.isFinite(effectiveMetersPerPixel) || effectiveMetersPerPixel <= 0 || rulerHeight < 120) {
|
|
return {
|
|
centerScaleRulerVisible: true,
|
|
centerScaleRulerCenterXPx: Math.round(data.stageWidth / 2),
|
|
centerScaleRulerZeroYPx: zeroYPx,
|
|
centerScaleRulerHeightPx: Math.max(rulerHeight, fallbackHeight),
|
|
centerScaleRulerAxisBottomPx: coveredBottomPx,
|
|
centerScaleRulerZeroVisible: data.centerScaleRulerAnchorMode !== 'compass-center',
|
|
centerScaleRulerZeroLabel: '0 m',
|
|
centerScaleRulerMinorTicks: [] as ScaleRulerMinorTickData[],
|
|
centerScaleRulerMajorMarks: [] as ScaleRulerMajorMarkData[],
|
|
}
|
|
}
|
|
|
|
const labelDistanceMeters = getNiceDistanceMeters(effectiveMetersPerPixel * 80)
|
|
const minorDistanceMeters = labelDistanceMeters / 8
|
|
const minorStepPx = minorDistanceMeters / effectiveMetersPerPixel
|
|
const visibleTopLimitPx = rulerHeight - coveredBottomPx
|
|
const minorTicks: ScaleRulerMinorTickData[] = []
|
|
const majorMarks: ScaleRulerMajorMarkData[] = []
|
|
|
|
for (let index = 1; index <= 200; index += 1) {
|
|
const topPx = Math.round(rulerHeight - index * minorStepPx)
|
|
if (topPx < 0) {
|
|
break
|
|
}
|
|
if (topPx >= visibleTopLimitPx) {
|
|
continue
|
|
}
|
|
|
|
const isHalfMajor = index % 4 === 0
|
|
const isLabelMajor = index % 8 === 0
|
|
minorTicks.push({
|
|
key: `minor-${index}`,
|
|
topPx,
|
|
long: isHalfMajor,
|
|
})
|
|
|
|
if (isLabelMajor) {
|
|
majorMarks.push({
|
|
key: `major-${index}`,
|
|
topPx,
|
|
label: formatScaleDistanceLabel((index / 8) * labelDistanceMeters),
|
|
})
|
|
}
|
|
}
|
|
|
|
return {
|
|
centerScaleRulerVisible: true,
|
|
centerScaleRulerCenterXPx: Math.round(data.stageWidth / 2),
|
|
centerScaleRulerZeroYPx: zeroYPx,
|
|
centerScaleRulerHeightPx: rulerHeight,
|
|
centerScaleRulerAxisBottomPx: coveredBottomPx,
|
|
centerScaleRulerZeroVisible: data.centerScaleRulerAnchorMode !== 'compass-center',
|
|
centerScaleRulerZeroLabel: '0 m',
|
|
centerScaleRulerMinorTicks: minorTicks,
|
|
centerScaleRulerMajorMarks: majorMarks,
|
|
}
|
|
}
|
|
|
|
function buildEmptyGameInfoSnapshot(): MapEngineGameInfoSnapshot {
|
|
return {
|
|
title: '当前游戏',
|
|
subtitle: '未开始',
|
|
localRows: [],
|
|
globalRows: [
|
|
{ label: '全球积分', value: '未接入' },
|
|
{ label: '全球排名', value: '未接入' },
|
|
{ label: '在线人数', value: '未接入' },
|
|
{ label: '队伍状态', value: '未接入' },
|
|
{ label: '实时广播', value: '未接入' },
|
|
],
|
|
}
|
|
}
|
|
|
|
Page({
|
|
data: {
|
|
showDebugPanel: false,
|
|
showGameInfoPanel: false,
|
|
showCenterScaleRuler: false,
|
|
statusBarHeight: 0,
|
|
topInsetHeight: 12,
|
|
hudPanelIndex: 0,
|
|
configSourceText: '顺序赛配置',
|
|
centerScaleRulerAnchorMode: 'screen-center',
|
|
gameInfoTitle: '当前游戏',
|
|
gameInfoSubtitle: '未开始',
|
|
gameInfoLocalRows: [],
|
|
gameInfoGlobalRows: buildEmptyGameInfoSnapshot().globalRows,
|
|
panelTimerText: '00:00:00',
|
|
panelMileageText: '0m',
|
|
panelActionTagText: '目标',
|
|
panelDistanceTagText: '点距',
|
|
panelDistanceValueText: '--',
|
|
panelDistanceUnitText: '',
|
|
panelProgressText: '0/0',
|
|
gameSessionStatus: 'idle',
|
|
gameModeText: '顺序赛',
|
|
gpsLockEnabled: false,
|
|
gpsLockAvailable: false,
|
|
locationSourceMode: 'real',
|
|
locationSourceText: '真实定位',
|
|
mockBridgeConnected: false,
|
|
mockBridgeStatusText: '未连接',
|
|
mockBridgeUrlText: 'wss://gs.gotomars.xyz/mock-gps',
|
|
mockBridgeUrlDraft: 'wss://gs.gotomars.xyz/mock-gps',
|
|
mockCoordText: '--',
|
|
mockSpeedText: '--',
|
|
heartRateSourceMode: 'real',
|
|
heartRateSourceText: '真实心率',
|
|
mockHeartRateBridgeConnected: false,
|
|
mockHeartRateBridgeStatusText: '未连接',
|
|
mockHeartRateBridgeUrlText: 'wss://gs.gotomars.xyz/mock-gps',
|
|
mockHeartRateBridgeUrlDraft: 'wss://gs.gotomars.xyz/mock-gps',
|
|
mockHeartRateText: '--',
|
|
heartRateScanText: '未扫描',
|
|
heartRateDiscoveredDevices: [],
|
|
panelSpeedValueText: '0',
|
|
panelTelemetryTone: 'blue',
|
|
panelHeartRateZoneNameText: '--',
|
|
panelHeartRateZoneRangeText: '',
|
|
heartRateConnected: false,
|
|
heartRateStatusText: '心率带未连接',
|
|
heartRateDeviceText: '--',
|
|
panelHeartRateValueText: '--',
|
|
panelHeartRateUnitText: '',
|
|
panelCaloriesValueText: '0',
|
|
panelCaloriesUnitText: 'kcal',
|
|
panelAverageSpeedValueText: '0',
|
|
panelAverageSpeedUnitText: 'km/h',
|
|
panelAccuracyValueText: '--',
|
|
panelAccuracyUnitText: '',
|
|
deviceHeadingText: '--',
|
|
devicePoseText: '竖持',
|
|
headingConfidenceText: '低',
|
|
accelerometerText: '--',
|
|
gyroscopeText: '--',
|
|
deviceMotionText: '--',
|
|
punchButtonText: '打点',
|
|
punchButtonEnabled: false,
|
|
skipButtonEnabled: false,
|
|
punchHintText: '等待进入检查点范围',
|
|
punchFeedbackVisible: false,
|
|
punchFeedbackText: '',
|
|
punchFeedbackTone: 'neutral',
|
|
contentCardVisible: false,
|
|
contentCardTitle: '',
|
|
contentCardBody: '',
|
|
punchButtonFxClass: '',
|
|
punchFeedbackFxClass: '',
|
|
contentCardFxClass: '',
|
|
mapPulseVisible: false,
|
|
mapPulseLeftPx: 0,
|
|
mapPulseTopPx: 0,
|
|
mapPulseFxClass: '',
|
|
stageFxVisible: false,
|
|
stageFxClass: '',
|
|
centerScaleRulerVisible: false,
|
|
centerScaleRulerCenterXPx: 0,
|
|
centerScaleRulerZeroYPx: 0,
|
|
centerScaleRulerHeightPx: 0,
|
|
centerScaleRulerAxisBottomPx: 0,
|
|
centerScaleRulerZeroVisible: false,
|
|
centerScaleRulerZeroLabel: '0 m',
|
|
centerScaleRulerMinorTicks: [],
|
|
centerScaleRulerMajorMarks: [],
|
|
compassTicks: buildCompassTicks(),
|
|
compassLabels: buildCompassLabels(),
|
|
...buildSideButtonVisibility('left'),
|
|
...buildSideButtonState({
|
|
sideButtonMode: 'left',
|
|
showGameInfoPanel: false,
|
|
showCenterScaleRuler: false,
|
|
centerScaleRulerAnchorMode: 'screen-center',
|
|
skipButtonEnabled: false,
|
|
gameSessionStatus: 'idle',
|
|
gpsLockEnabled: false,
|
|
gpsLockAvailable: false,
|
|
}),
|
|
} as unknown as MapPageData,
|
|
|
|
onLoad() {
|
|
const systemInfo = wx.getSystemInfoSync()
|
|
const statusBarHeight = systemInfo.statusBarHeight || 0
|
|
const menuButtonRect = wx.getMenuButtonBoundingClientRect()
|
|
const menuButtonBottom = menuButtonRect && typeof menuButtonRect.bottom === 'number' ? menuButtonRect.bottom : statusBarHeight
|
|
|
|
if (mapEngine) {
|
|
mapEngine.destroy()
|
|
mapEngine = null
|
|
}
|
|
|
|
mapEngine = new MapEngine(INTERNAL_BUILD_VERSION, {
|
|
onData: (patch) => {
|
|
const nextPatch = patch as Partial<MapPageData>
|
|
const nextData: Partial<MapPageData> = {
|
|
...nextPatch,
|
|
}
|
|
|
|
if (
|
|
typeof nextPatch.mockBridgeUrlText === 'string'
|
|
&& this.data.mockBridgeUrlDraft === this.data.mockBridgeUrlText
|
|
) {
|
|
nextData.mockBridgeUrlDraft = nextPatch.mockBridgeUrlText
|
|
}
|
|
|
|
if (
|
|
typeof nextPatch.mockHeartRateBridgeUrlText === 'string'
|
|
&& this.data.mockHeartRateBridgeUrlDraft === this.data.mockHeartRateBridgeUrlText
|
|
) {
|
|
nextData.mockHeartRateBridgeUrlDraft = nextPatch.mockHeartRateBridgeUrlText
|
|
}
|
|
|
|
const mergedData = {
|
|
...this.data,
|
|
...nextData,
|
|
} as MapPageData
|
|
|
|
this.setData({
|
|
...nextData,
|
|
...buildCenterScaleRulerPatch(mergedData),
|
|
...buildSideButtonState(mergedData),
|
|
})
|
|
|
|
if (this.data.showGameInfoPanel) {
|
|
this.syncGameInfoPanelSnapshot()
|
|
}
|
|
},
|
|
})
|
|
|
|
this.setData({
|
|
...mapEngine.getInitialData(),
|
|
showDebugPanel: false,
|
|
showGameInfoPanel: false,
|
|
statusBarHeight,
|
|
topInsetHeight: Math.max(statusBarHeight + 12, menuButtonBottom + 20),
|
|
hudPanelIndex: 0,
|
|
configSourceText: '顺序赛配置',
|
|
gameInfoTitle: '当前游戏',
|
|
gameInfoSubtitle: '未开始',
|
|
gameInfoLocalRows: [],
|
|
gameInfoGlobalRows: buildEmptyGameInfoSnapshot().globalRows,
|
|
panelTimerText: '00:00:00',
|
|
panelMileageText: '0m',
|
|
panelActionTagText: '目标',
|
|
panelDistanceTagText: '点距',
|
|
panelDistanceValueText: '--',
|
|
panelDistanceUnitText: '',
|
|
panelProgressText: '0/0',
|
|
gameSessionStatus: 'idle',
|
|
gameModeText: '顺序赛',
|
|
gpsLockEnabled: false,
|
|
gpsLockAvailable: false,
|
|
locationSourceMode: 'real',
|
|
locationSourceText: '真实定位',
|
|
mockBridgeConnected: false,
|
|
mockBridgeStatusText: '未连接',
|
|
mockBridgeUrlText: 'wss://gs.gotomars.xyz/mock-gps',
|
|
mockBridgeUrlDraft: 'wss://gs.gotomars.xyz/mock-gps',
|
|
mockCoordText: '--',
|
|
mockSpeedText: '--',
|
|
heartRateSourceMode: 'real',
|
|
heartRateSourceText: '真实心率',
|
|
mockHeartRateBridgeConnected: false,
|
|
mockHeartRateBridgeStatusText: '未连接',
|
|
mockHeartRateBridgeUrlText: 'wss://gs.gotomars.xyz/mock-gps',
|
|
mockHeartRateBridgeUrlDraft: 'wss://gs.gotomars.xyz/mock-gps',
|
|
mockHeartRateText: '--',
|
|
panelSpeedValueText: '0',
|
|
panelTelemetryTone: 'blue',
|
|
panelHeartRateZoneNameText: '--',
|
|
panelHeartRateZoneRangeText: '',
|
|
heartRateConnected: false,
|
|
heartRateStatusText: '心率带未连接',
|
|
heartRateDeviceText: '--',
|
|
panelHeartRateValueText: '--',
|
|
panelHeartRateUnitText: '',
|
|
panelCaloriesValueText: '0',
|
|
panelCaloriesUnitText: 'kcal',
|
|
panelAverageSpeedValueText: '0',
|
|
panelAverageSpeedUnitText: 'km/h',
|
|
panelAccuracyValueText: '--',
|
|
panelAccuracyUnitText: '',
|
|
deviceHeadingText: '--',
|
|
devicePoseText: '竖持',
|
|
headingConfidenceText: '低',
|
|
accelerometerText: '--',
|
|
gyroscopeText: '--',
|
|
deviceMotionText: '--',
|
|
punchButtonText: '打点',
|
|
punchButtonEnabled: false,
|
|
skipButtonEnabled: false,
|
|
punchHintText: '等待进入检查点范围',
|
|
punchFeedbackVisible: false,
|
|
punchFeedbackText: '',
|
|
punchFeedbackTone: 'neutral',
|
|
contentCardVisible: false,
|
|
contentCardTitle: '',
|
|
contentCardBody: '',
|
|
punchButtonFxClass: '',
|
|
punchFeedbackFxClass: '',
|
|
contentCardFxClass: '',
|
|
mapPulseVisible: false,
|
|
mapPulseLeftPx: 0,
|
|
mapPulseTopPx: 0,
|
|
mapPulseFxClass: '',
|
|
stageFxVisible: false,
|
|
stageFxClass: '',
|
|
compassTicks: buildCompassTicks(),
|
|
compassLabels: buildCompassLabels(),
|
|
...buildSideButtonVisibility('left'),
|
|
...buildSideButtonState({
|
|
sideButtonMode: 'left',
|
|
showGameInfoPanel: false,
|
|
showCenterScaleRuler: false,
|
|
centerScaleRulerAnchorMode: 'screen-center',
|
|
skipButtonEnabled: false,
|
|
gameSessionStatus: 'idle',
|
|
gpsLockEnabled: false,
|
|
gpsLockAvailable: false,
|
|
}),
|
|
...buildCenterScaleRulerPatch({
|
|
...(mapEngine.getInitialData() as MapPageData),
|
|
showCenterScaleRuler: false,
|
|
centerScaleRulerAnchorMode: 'screen-center',
|
|
stageWidth: 0,
|
|
stageHeight: 0,
|
|
topInsetHeight: Math.max(statusBarHeight + 12, menuButtonBottom + 20),
|
|
zoom: 0,
|
|
centerTileY: 0,
|
|
tileSizePx: 0,
|
|
}),
|
|
})
|
|
},
|
|
|
|
onReady() {
|
|
stageCanvasAttached = false
|
|
this.measureStageAndCanvas()
|
|
this.loadMapConfigFromRemote(CLASSIC_REMOTE_GAME_CONFIG_URL, '顺序赛配置')
|
|
},
|
|
|
|
onShow() {
|
|
if (mapEngine) {
|
|
mapEngine.handleAppShow()
|
|
}
|
|
},
|
|
|
|
onHide() {
|
|
if (mapEngine) {
|
|
mapEngine.handleAppHide()
|
|
}
|
|
},
|
|
|
|
onUnload() {
|
|
if (mapEngine) {
|
|
mapEngine.destroy()
|
|
mapEngine = null
|
|
}
|
|
stageCanvasAttached = false
|
|
},
|
|
|
|
loadMapConfigFromRemote(configUrl: string, configLabel: string) {
|
|
const currentEngine = mapEngine
|
|
if (!currentEngine) {
|
|
return
|
|
}
|
|
|
|
this.setData({
|
|
configSourceText: configLabel,
|
|
configStatusText: `加载中: ${configLabel}`,
|
|
})
|
|
|
|
loadRemoteMapConfig(configUrl)
|
|
.then((config) => {
|
|
if (mapEngine !== currentEngine) {
|
|
return
|
|
}
|
|
|
|
currentEngine.applyRemoteMapConfig(config)
|
|
})
|
|
.catch((error) => {
|
|
if (mapEngine !== currentEngine) {
|
|
return
|
|
}
|
|
|
|
const errorMessage = error && error.message ? error.message : '未知错误'
|
|
this.setData({
|
|
configStatusText: `载入失败: ${errorMessage}`,
|
|
statusText: `远程地图配置载入失败: ${errorMessage} (${INTERNAL_BUILD_VERSION})`,
|
|
})
|
|
})
|
|
},
|
|
|
|
measureStageAndCanvas() {
|
|
const page = this
|
|
const applyStage = (rawRect?: Partial<WechatMiniprogram.BoundingClientRectCallbackResult>) => {
|
|
const fallbackRect = getFallbackStageRect()
|
|
const rect: MapEngineStageRect = {
|
|
width: rawRect && typeof rawRect.width === 'number' ? rawRect.width : fallbackRect.width,
|
|
height: rawRect && typeof rawRect.height === 'number' ? rawRect.height : fallbackRect.height,
|
|
left: rawRect && typeof rawRect.left === 'number' ? rawRect.left : fallbackRect.left,
|
|
top: rawRect && typeof rawRect.top === 'number' ? rawRect.top : fallbackRect.top,
|
|
}
|
|
|
|
const currentEngine = mapEngine
|
|
if (!currentEngine) {
|
|
return
|
|
}
|
|
|
|
currentEngine.setStage(rect)
|
|
|
|
if (stageCanvasAttached) {
|
|
return
|
|
}
|
|
|
|
const canvasQuery = wx.createSelectorQuery().in(page)
|
|
canvasQuery.select('#mapCanvas').fields({ node: true, size: true })
|
|
canvasQuery.select('#routeLabelCanvas').fields({ node: true, size: true })
|
|
canvasQuery.exec((canvasRes) => {
|
|
const canvasRef = canvasRes[0] as any
|
|
const labelCanvasRef = canvasRes[1] as any
|
|
if (!canvasRef || !canvasRef.node) {
|
|
page.setData({
|
|
statusText: `WebGL 引擎初始化失败 (${INTERNAL_BUILD_VERSION})`,
|
|
})
|
|
return
|
|
}
|
|
|
|
const dpr = wx.getSystemInfoSync().pixelRatio || 1
|
|
try {
|
|
currentEngine.attachCanvas(
|
|
canvasRef.node,
|
|
rect.width,
|
|
rect.height,
|
|
dpr,
|
|
labelCanvasRef && labelCanvasRef.node ? labelCanvasRef.node : undefined,
|
|
)
|
|
stageCanvasAttached = true
|
|
} catch (error) {
|
|
page.setData({
|
|
statusText: `WebGL 鍒濆鍖栧け璐?(${INTERNAL_BUILD_VERSION})`,
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
const query = wx.createSelectorQuery().in(page)
|
|
query.select('.map-stage').boundingClientRect()
|
|
query.exec((res) => {
|
|
const rect = res[0] as WechatMiniprogram.BoundingClientRectCallbackResult | undefined
|
|
applyStage(rect)
|
|
})
|
|
},
|
|
|
|
handleTouchStart(event: WechatMiniprogram.TouchEvent) {
|
|
if (mapEngine) {
|
|
mapEngine.handleTouchStart(event)
|
|
}
|
|
},
|
|
|
|
handleTouchMove(event: WechatMiniprogram.TouchEvent) {
|
|
if (mapEngine) {
|
|
mapEngine.handleTouchMove(event)
|
|
}
|
|
},
|
|
|
|
handleTouchEnd(event: WechatMiniprogram.TouchEvent) {
|
|
if (mapEngine) {
|
|
mapEngine.handleTouchEnd(event)
|
|
}
|
|
},
|
|
|
|
handleTouchCancel() {
|
|
if (mapEngine) {
|
|
mapEngine.handleTouchCancel()
|
|
}
|
|
},
|
|
|
|
handleRecenter() {
|
|
if (mapEngine) {
|
|
mapEngine.handleRecenter()
|
|
}
|
|
},
|
|
|
|
handleRotateStep() {
|
|
if (mapEngine) {
|
|
mapEngine.handleRotateStep()
|
|
}
|
|
},
|
|
|
|
handleRotationReset() {
|
|
if (mapEngine) {
|
|
mapEngine.handleRotationReset()
|
|
}
|
|
},
|
|
|
|
handleSetManualMode() {
|
|
if (mapEngine) {
|
|
mapEngine.handleSetManualMode()
|
|
}
|
|
},
|
|
|
|
handleSetNorthUpMode() {
|
|
if (mapEngine) {
|
|
mapEngine.handleSetNorthUpMode()
|
|
}
|
|
},
|
|
|
|
handleSetHeadingUpMode() {
|
|
if (mapEngine) {
|
|
mapEngine.handleSetHeadingUpMode()
|
|
}
|
|
},
|
|
|
|
handleCycleNorthReferenceMode() {
|
|
if (mapEngine) {
|
|
mapEngine.handleCycleNorthReferenceMode()
|
|
}
|
|
},
|
|
|
|
handleAutoRotateCalibrate() {
|
|
if (mapEngine) {
|
|
mapEngine.handleAutoRotateCalibrate()
|
|
}
|
|
},
|
|
|
|
handleToggleGpsTracking() {
|
|
if (mapEngine) {
|
|
mapEngine.handleToggleGpsTracking()
|
|
}
|
|
},
|
|
|
|
handleSetRealLocationMode() {
|
|
if (mapEngine) {
|
|
mapEngine.handleSetRealLocationMode()
|
|
}
|
|
},
|
|
|
|
handleSetMockLocationMode() {
|
|
if (mapEngine) {
|
|
mapEngine.handleSetMockLocationMode()
|
|
}
|
|
},
|
|
|
|
handleConnectMockLocationBridge() {
|
|
if (mapEngine) {
|
|
mapEngine.handleConnectMockLocationBridge()
|
|
}
|
|
},
|
|
|
|
handleMockBridgeUrlInput(event: WechatMiniprogram.Input) {
|
|
this.setData({
|
|
mockBridgeUrlDraft: event.detail.value,
|
|
})
|
|
},
|
|
|
|
handleSaveMockBridgeUrl() {
|
|
if (mapEngine) {
|
|
mapEngine.handleSetMockLocationBridgeUrl(this.data.mockBridgeUrlDraft)
|
|
}
|
|
},
|
|
|
|
handleDisconnectMockLocationBridge() {
|
|
if (mapEngine) {
|
|
mapEngine.handleDisconnectMockLocationBridge()
|
|
}
|
|
},
|
|
|
|
handleSetRealHeartRateMode() {
|
|
if (mapEngine) {
|
|
mapEngine.handleSetRealHeartRateMode()
|
|
}
|
|
},
|
|
|
|
handleSetMockHeartRateMode() {
|
|
if (mapEngine) {
|
|
mapEngine.handleSetMockHeartRateMode()
|
|
}
|
|
},
|
|
|
|
handleMockHeartRateBridgeUrlInput(event: WechatMiniprogram.Input) {
|
|
this.setData({
|
|
mockHeartRateBridgeUrlDraft: event.detail.value,
|
|
})
|
|
},
|
|
|
|
handleSaveMockHeartRateBridgeUrl() {
|
|
if (mapEngine) {
|
|
mapEngine.handleSetMockHeartRateBridgeUrl(this.data.mockHeartRateBridgeUrlDraft)
|
|
}
|
|
},
|
|
|
|
handleConnectMockHeartRateBridge() {
|
|
if (mapEngine) {
|
|
mapEngine.handleConnectMockHeartRateBridge()
|
|
}
|
|
},
|
|
|
|
handleDisconnectMockHeartRateBridge() {
|
|
if (mapEngine) {
|
|
mapEngine.handleDisconnectMockHeartRateBridge()
|
|
}
|
|
},
|
|
|
|
handleConnectHeartRate() {
|
|
if (mapEngine) {
|
|
mapEngine.handleConnectHeartRate()
|
|
}
|
|
},
|
|
|
|
handleDisconnectHeartRate() {
|
|
if (mapEngine) {
|
|
mapEngine.handleDisconnectHeartRate()
|
|
}
|
|
},
|
|
|
|
handleConnectHeartRateDevice(event: WechatMiniprogram.BaseEvent<{ deviceId?: string }>) {
|
|
if (mapEngine && event.currentTarget && event.currentTarget.dataset && event.currentTarget.dataset.deviceId) {
|
|
mapEngine.handleConnectHeartRateDevice(event.currentTarget.dataset.deviceId)
|
|
}
|
|
},
|
|
|
|
handleClearPreferredHeartRateDevice() {
|
|
if (mapEngine) {
|
|
mapEngine.handleClearPreferredHeartRateDevice()
|
|
}
|
|
},
|
|
|
|
handleDebugHeartRateBlue() {
|
|
if (mapEngine) {
|
|
mapEngine.handleDebugHeartRateTone('blue')
|
|
}
|
|
},
|
|
|
|
handleDebugHeartRatePurple() {
|
|
if (mapEngine) {
|
|
mapEngine.handleDebugHeartRateTone('purple')
|
|
}
|
|
},
|
|
|
|
handleDebugHeartRateGreen() {
|
|
if (mapEngine) {
|
|
mapEngine.handleDebugHeartRateTone('green')
|
|
}
|
|
},
|
|
|
|
handleDebugHeartRateYellow() {
|
|
if (mapEngine) {
|
|
mapEngine.handleDebugHeartRateTone('yellow')
|
|
}
|
|
},
|
|
|
|
handleDebugHeartRateOrange() {
|
|
if (mapEngine) {
|
|
mapEngine.handleDebugHeartRateTone('orange')
|
|
}
|
|
},
|
|
|
|
handleDebugHeartRateRed() {
|
|
if (mapEngine) {
|
|
mapEngine.handleDebugHeartRateTone('red')
|
|
}
|
|
},
|
|
|
|
handleClearDebugHeartRate() {
|
|
if (mapEngine) {
|
|
mapEngine.handleClearDebugHeartRate()
|
|
}
|
|
},
|
|
|
|
handleToggleOsmReference() {
|
|
if (mapEngine) {
|
|
mapEngine.handleToggleOsmReference()
|
|
}
|
|
},
|
|
|
|
handleStartGame() {
|
|
if (mapEngine) {
|
|
mapEngine.handleStartGame()
|
|
}
|
|
},
|
|
|
|
handleLoadClassicConfig() {
|
|
this.loadMapConfigFromRemote(CLASSIC_REMOTE_GAME_CONFIG_URL, '顺序赛配置')
|
|
},
|
|
|
|
handleLoadScoreOConfig() {
|
|
this.loadMapConfigFromRemote(SCORE_O_REMOTE_GAME_CONFIG_URL, '积分赛配置')
|
|
},
|
|
|
|
handleForceExitGame() {
|
|
if (!mapEngine || this.data.gameSessionStatus === 'idle') {
|
|
return
|
|
}
|
|
|
|
wx.showModal({
|
|
title: '确认退出',
|
|
content: '确认强制结束当前对局并返回开始前状态?',
|
|
confirmText: '确认退出',
|
|
cancelText: '取消',
|
|
success: (result) => {
|
|
if (result.confirm && mapEngine) {
|
|
mapEngine.handleForceExitGame()
|
|
}
|
|
},
|
|
})
|
|
},
|
|
|
|
handleSkipAction() {
|
|
if (!mapEngine || !this.data.skipButtonEnabled) {
|
|
return
|
|
}
|
|
|
|
if (!mapEngine.shouldConfirmSkipAction()) {
|
|
mapEngine.handleSkipAction()
|
|
return
|
|
}
|
|
|
|
wx.showModal({
|
|
title: '确认跳点',
|
|
content: '确认跳过当前检查点并切换到下一个目标点?',
|
|
confirmText: '确认跳过',
|
|
cancelText: '取消',
|
|
success: (result) => {
|
|
if (result.confirm && mapEngine) {
|
|
mapEngine.handleSkipAction()
|
|
}
|
|
},
|
|
})
|
|
},
|
|
|
|
handleClearMapTestArtifacts() {
|
|
if (mapEngine) {
|
|
mapEngine.handleClearMapTestArtifacts()
|
|
}
|
|
},
|
|
|
|
syncGameInfoPanelSnapshot() {
|
|
if (!mapEngine) {
|
|
return
|
|
}
|
|
|
|
const snapshot = mapEngine.getGameInfoSnapshot()
|
|
const localRows = snapshot.localRows.concat([
|
|
{ label: '比例尺开关', value: this.data.showCenterScaleRuler ? '开启' : '关闭' },
|
|
{ label: '比例尺锚点', value: this.data.centerScaleRulerAnchorMode === 'compass-center' ? '指北针圆心' : '屏幕中心' },
|
|
{ label: '比例尺可见', value: this.data.centerScaleRulerVisible ? 'true' : 'false' },
|
|
{ label: '比例尺中心X', value: `${this.data.centerScaleRulerCenterXPx}px` },
|
|
{ label: '比例尺零点Y', value: `${this.data.centerScaleRulerZeroYPx}px` },
|
|
{ label: '比例尺高度', value: `${this.data.centerScaleRulerHeightPx}px` },
|
|
{ label: '比例尺主刻度数', value: String(this.data.centerScaleRulerMajorMarks.length) },
|
|
])
|
|
this.setData({
|
|
gameInfoTitle: snapshot.title,
|
|
gameInfoSubtitle: snapshot.subtitle,
|
|
gameInfoLocalRows: localRows,
|
|
gameInfoGlobalRows: snapshot.globalRows,
|
|
})
|
|
},
|
|
|
|
handleOpenGameInfoPanel() {
|
|
this.syncGameInfoPanelSnapshot()
|
|
this.setData({
|
|
showDebugPanel: false,
|
|
showGameInfoPanel: true,
|
|
...buildSideButtonState({
|
|
sideButtonMode: this.data.sideButtonMode,
|
|
showGameInfoPanel: true,
|
|
showCenterScaleRuler: this.data.showCenterScaleRuler,
|
|
centerScaleRulerAnchorMode: this.data.centerScaleRulerAnchorMode,
|
|
skipButtonEnabled: this.data.skipButtonEnabled,
|
|
gameSessionStatus: this.data.gameSessionStatus,
|
|
gpsLockEnabled: this.data.gpsLockEnabled,
|
|
gpsLockAvailable: this.data.gpsLockAvailable,
|
|
}),
|
|
})
|
|
},
|
|
|
|
handleCloseGameInfoPanel() {
|
|
this.setData({
|
|
showGameInfoPanel: false,
|
|
...buildSideButtonState({
|
|
sideButtonMode: this.data.sideButtonMode,
|
|
showGameInfoPanel: false,
|
|
showCenterScaleRuler: this.data.showCenterScaleRuler,
|
|
centerScaleRulerAnchorMode: this.data.centerScaleRulerAnchorMode,
|
|
skipButtonEnabled: this.data.skipButtonEnabled,
|
|
gameSessionStatus: this.data.gameSessionStatus,
|
|
gpsLockEnabled: this.data.gpsLockEnabled,
|
|
gpsLockAvailable: this.data.gpsLockAvailable,
|
|
}),
|
|
})
|
|
},
|
|
|
|
handleGameInfoPanelTap() {},
|
|
|
|
handleOverlayTouch() {},
|
|
|
|
handlePunchAction() {
|
|
if (!this.data.punchButtonEnabled) {
|
|
return
|
|
}
|
|
|
|
if (mapEngine) {
|
|
mapEngine.handlePunchAction()
|
|
}
|
|
},
|
|
|
|
handleCloseContentCard() {
|
|
if (mapEngine) {
|
|
mapEngine.closeContentCard()
|
|
}
|
|
},
|
|
|
|
handleHudPanelChange(event: WechatMiniprogram.CustomEvent<{ current: number }>) {
|
|
this.setData({
|
|
hudPanelIndex: event.detail.current || 0,
|
|
})
|
|
},
|
|
|
|
handleCycleSideButtons() {
|
|
const nextMode = getNextSideButtonMode(this.data.sideButtonMode)
|
|
this.setData({
|
|
...buildSideButtonVisibility(nextMode),
|
|
...buildSideButtonState({
|
|
sideButtonMode: nextMode,
|
|
showGameInfoPanel: this.data.showGameInfoPanel,
|
|
showCenterScaleRuler: this.data.showCenterScaleRuler,
|
|
centerScaleRulerAnchorMode: this.data.centerScaleRulerAnchorMode,
|
|
skipButtonEnabled: this.data.skipButtonEnabled,
|
|
gameSessionStatus: this.data.gameSessionStatus,
|
|
gpsLockEnabled: this.data.gpsLockEnabled,
|
|
gpsLockAvailable: this.data.gpsLockAvailable,
|
|
}),
|
|
})
|
|
},
|
|
handleToggleGpsLock() {
|
|
if (mapEngine) {
|
|
mapEngine.handleToggleGpsLock()
|
|
}
|
|
},
|
|
handleToggleMapRotateMode() {
|
|
if (!mapEngine) {
|
|
return
|
|
}
|
|
|
|
if (this.data.orientationMode === 'heading-up') {
|
|
mapEngine.handleSetManualMode()
|
|
return
|
|
}
|
|
|
|
mapEngine.handleSetHeadingUpMode()
|
|
},
|
|
handleToggleDebugPanel() {
|
|
this.setData({
|
|
showDebugPanel: !this.data.showDebugPanel,
|
|
showGameInfoPanel: false,
|
|
...buildSideButtonState({
|
|
sideButtonMode: this.data.sideButtonMode,
|
|
showGameInfoPanel: false,
|
|
showCenterScaleRuler: this.data.showCenterScaleRuler,
|
|
centerScaleRulerAnchorMode: this.data.centerScaleRulerAnchorMode,
|
|
skipButtonEnabled: this.data.skipButtonEnabled,
|
|
gameSessionStatus: this.data.gameSessionStatus,
|
|
gpsLockEnabled: this.data.gpsLockEnabled,
|
|
gpsLockAvailable: this.data.gpsLockAvailable,
|
|
}),
|
|
})
|
|
},
|
|
|
|
handleCloseDebugPanel() {
|
|
this.setData({
|
|
showDebugPanel: false,
|
|
...buildSideButtonState({
|
|
sideButtonMode: this.data.sideButtonMode,
|
|
showGameInfoPanel: this.data.showGameInfoPanel,
|
|
showCenterScaleRuler: this.data.showCenterScaleRuler,
|
|
centerScaleRulerAnchorMode: this.data.centerScaleRulerAnchorMode,
|
|
skipButtonEnabled: this.data.skipButtonEnabled,
|
|
gameSessionStatus: this.data.gameSessionStatus,
|
|
gpsLockEnabled: this.data.gpsLockEnabled,
|
|
gpsLockAvailable: this.data.gpsLockAvailable,
|
|
}),
|
|
})
|
|
},
|
|
|
|
handleToggleCenterScaleRuler() {
|
|
const nextEnabled = !this.data.showCenterScaleRuler
|
|
this.data.showCenterScaleRuler = nextEnabled
|
|
const mergedData = {
|
|
...this.data,
|
|
showCenterScaleRuler: nextEnabled,
|
|
} as MapPageData
|
|
|
|
this.setData({
|
|
showCenterScaleRuler: nextEnabled,
|
|
...buildCenterScaleRulerPatch(mergedData),
|
|
...buildSideButtonState(mergedData),
|
|
})
|
|
},
|
|
|
|
handleToggleCenterScaleRulerAnchor() {
|
|
if (!this.data.showCenterScaleRuler) {
|
|
return
|
|
}
|
|
|
|
const nextAnchorMode: CenterScaleRulerAnchorMode = this.data.centerScaleRulerAnchorMode === 'screen-center'
|
|
? 'compass-center'
|
|
: 'screen-center'
|
|
this.data.centerScaleRulerAnchorMode = nextAnchorMode
|
|
const mergedData = {
|
|
...this.data,
|
|
centerScaleRulerAnchorMode: nextAnchorMode,
|
|
} as MapPageData
|
|
|
|
this.setData({
|
|
centerScaleRulerAnchorMode: nextAnchorMode,
|
|
...buildCenterScaleRulerPatch(mergedData),
|
|
...buildSideButtonState(mergedData),
|
|
})
|
|
|
|
if (this.data.showGameInfoPanel) {
|
|
this.syncGameInfoPanelSnapshot()
|
|
}
|
|
},
|
|
|
|
handleDebugPanelTap() {},
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|