3565 lines
111 KiB
TypeScript
3565 lines
111 KiB
TypeScript
import {
|
|
MapEngine,
|
|
type MapEngineGameInfoRow,
|
|
type MapEngineGameInfoSnapshot,
|
|
type MapEngineResultSnapshot,
|
|
type MapEngineStageRect,
|
|
type MapEngineViewState,
|
|
} from '../../engine/map/mapEngine'
|
|
import {
|
|
getBackendSessionContextFromLaunchEnvelope,
|
|
getDemoGameLaunchEnvelope,
|
|
resolveGameLaunchEnvelope,
|
|
type GameLaunchEnvelope,
|
|
type MapPageLaunchOptions,
|
|
} from '../../utils/gameLaunch'
|
|
import { finishSession, startSession, type BackendSessionFinishSummaryPayload } from '../../utils/backendApi'
|
|
import { loadBackendBaseUrl } from '../../utils/backendAuth'
|
|
import { loadRemoteMapConfig, type RemoteMapConfig } from '../../utils/remoteMapConfig'
|
|
import {
|
|
persistStoredMockDebugLogBridgeUrl,
|
|
setGlobalMockDebugBridgeChannelId,
|
|
setGlobalMockDebugBridgeEnabled,
|
|
setGlobalMockDebugBridgeUrl,
|
|
} from '../../utils/globalMockDebugBridge'
|
|
import { reportBackendClientLog } from '../../utils/backendClientLogs'
|
|
import { type H5ExperienceFallbackPayload, type H5ExperienceRequest } from '../../game/experience/h5Experience'
|
|
import { type TrackColorPreset } from '../../game/presentation/trackStyleConfig'
|
|
import { type GpsMarkerColorPreset } from '../../game/presentation/gpsMarkerStyleConfig'
|
|
import { type PlayerTelemetryProfile } from '../../game/telemetry/playerTelemetryProfile'
|
|
import {
|
|
DEFAULT_SETTING_LOCKS,
|
|
DEFAULT_STORED_USER_SETTINGS,
|
|
loadStoredUserSettings,
|
|
mergeStoredUserSettings,
|
|
persistStoredUserSettings,
|
|
resolveSystemSettingsState,
|
|
type SystemSettingsConfig,
|
|
type CenterScaleRulerAnchorMode,
|
|
type ResolvedSystemSettingsState,
|
|
type SideButtonPlacement,
|
|
type StoredUserSettings,
|
|
} from '../../game/core/systemSettingsState'
|
|
import {
|
|
compileRuntimeProfile,
|
|
} from '../../game/core/runtimeProfileCompiler'
|
|
import {
|
|
clearSessionRecoverySnapshot,
|
|
loadSessionRecoverySnapshot,
|
|
saveSessionRecoverySnapshot,
|
|
type SessionRecoverySnapshot,
|
|
} from '../../game/core/sessionRecovery'
|
|
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 = 'shown' | 'hidden'
|
|
type SideActionButtonState = 'muted' | 'default' | 'active'
|
|
type MapPageData = MapEngineViewState & {
|
|
showDebugPanel: boolean
|
|
showGameInfoPanel: boolean
|
|
showResultScene: boolean
|
|
showSystemSettingsPanel: boolean
|
|
showHeartRateDevicePicker: boolean
|
|
showCenterScaleRuler: boolean
|
|
showPunchHintBanner: boolean
|
|
punchHintFxClass: string
|
|
centerScaleRulerAnchorMode: CenterScaleRulerAnchorMode
|
|
statusBarHeight: number
|
|
topInsetHeight: number
|
|
hudPanelIndex: number
|
|
configSourceText: string
|
|
mockBridgeUrlDraft: string
|
|
mockHeartRateBridgeUrlDraft: string
|
|
mockDebugLogBridgeUrlDraft: string
|
|
mockChannelIdDraft: string
|
|
gameInfoTitle: string
|
|
gameInfoSubtitle: string
|
|
gameInfoLocalRows: MapEngineGameInfoRow[]
|
|
gameInfoGlobalRows: MapEngineGameInfoRow[]
|
|
resultSceneTitle: string
|
|
resultSceneSubtitle: string
|
|
resultSceneHeroLabel: string
|
|
resultSceneHeroValue: string
|
|
resultSceneRows: MapEngineGameInfoRow[]
|
|
resultSceneCountdownText: string
|
|
panelTimerText: string
|
|
panelTimerMode: 'elapsed' | 'countdown'
|
|
panelMileageText: string
|
|
panelTargetSummaryText: string
|
|
panelDistanceValueText: string
|
|
panelProgressText: string
|
|
panelSpeedValueText: string
|
|
panelTimerFxClass: string
|
|
panelMileageFxClass: string
|
|
panelSpeedFxClass: string
|
|
panelHeartRateFxClass: string
|
|
compassTicks: CompassTickData[]
|
|
compassLabels: CompassLabelData[]
|
|
sideButtonMode: SideButtonMode
|
|
sideButtonPlacement: SideButtonPlacement
|
|
autoRotateEnabled: boolean
|
|
lockAnimationLevel: boolean
|
|
lockTrackMode: boolean
|
|
lockTrackTailLength: boolean
|
|
lockTrackColor: boolean
|
|
lockTrackStyle: boolean
|
|
lockGpsMarkerVisible: boolean
|
|
lockGpsMarkerStyle: boolean
|
|
lockGpsMarkerSize: boolean
|
|
lockGpsMarkerColor: boolean
|
|
lockSideButtonPlacement: boolean
|
|
lockAutoRotate: boolean
|
|
lockCompassTuning: boolean
|
|
lockScaleRulerVisible: boolean
|
|
lockScaleRulerAnchor: boolean
|
|
lockNorthReference: boolean
|
|
lockHeartRateDevice: boolean
|
|
sideToggleIconSrc: string
|
|
sideButton2Class: string
|
|
sideButton4Class: string
|
|
sideButton11Class: string
|
|
sideButton12Class: 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
|
|
showStartEntryButton: boolean
|
|
}
|
|
|
|
function getGlobalTelemetryProfile(): PlayerTelemetryProfile | null {
|
|
const app = getApp<IAppOption>()
|
|
const profile = app.globalData && app.globalData.telemetryPlayerProfile
|
|
return profile ? { ...profile } : null
|
|
}
|
|
|
|
const INTERNAL_BUILD_VERSION = 'map-build-293'
|
|
const PUNCH_HINT_AUTO_HIDE_MS = 30000
|
|
const PUNCH_HINT_FX_DURATION_MS = 420
|
|
const PUNCH_HINT_HAPTIC_GAP_MS = 2400
|
|
const SESSION_RECOVERY_PERSIST_INTERVAL_MS = 5000
|
|
const RESULT_EXIT_REDIRECT_DELAY_MS = 3000
|
|
let currentGameLaunchEnvelope: GameLaunchEnvelope = getDemoGameLaunchEnvelope()
|
|
let mapEngine: MapEngine | null = null
|
|
let stageCanvasAttached = false
|
|
let gameInfoPanelSyncTimer = 0
|
|
let centerScaleRulerSyncTimer = 0
|
|
let contentAudioRecorder: WechatMiniprogram.RecorderManager | null = null
|
|
let contentAudioRecording = false
|
|
let centerScaleRulerUpdateTimer = 0
|
|
let punchHintDismissTimer = 0
|
|
let punchHintFxTimer = 0
|
|
let panelTimerFxTimer = 0
|
|
let panelMileageFxTimer = 0
|
|
let panelSpeedFxTimer = 0
|
|
let panelHeartRateFxTimer = 0
|
|
let sessionRecoveryPersistTimer = 0
|
|
let resultExitRedirectTimer = 0
|
|
let resultExitCountdownTimer = 0
|
|
let lastPunchHintHapticAt = 0
|
|
let currentSystemSettingsConfig: SystemSettingsConfig | undefined
|
|
let currentRemoteMapConfig: RemoteMapConfig | undefined
|
|
let systemSettingsLockLifetimeActive = false
|
|
let syncedBackendSessionStartId = ''
|
|
let syncedBackendSessionFinishId = ''
|
|
let shouldAutoRestoreRecoverySnapshot = false
|
|
let shouldAutoStartSessionOnEnter = false
|
|
let redirectedToResultPage = false
|
|
let pendingHeartRateSwitchDeviceName: string | null = null
|
|
const DEBUG_MOCK_CHANNEL_ID_STORAGE_KEY = 'cmr.debug.mockChannelId.v1'
|
|
const DEBUG_MOCK_AUTO_CONNECT_STORAGE_KEY = 'cmr.debug.autoConnectMockSources.v1'
|
|
let lastCenterScaleRulerStablePatch: Pick<
|
|
MapPageData,
|
|
| 'centerScaleRulerVisible'
|
|
| 'centerScaleRulerCenterXPx'
|
|
| 'centerScaleRulerZeroYPx'
|
|
| 'centerScaleRulerHeightPx'
|
|
| 'centerScaleRulerAxisBottomPx'
|
|
| 'centerScaleRulerZeroVisible'
|
|
| 'centerScaleRulerZeroLabel'
|
|
| 'centerScaleRulerMinorTicks'
|
|
| 'centerScaleRulerMajorMarks'
|
|
> = {
|
|
centerScaleRulerVisible: false,
|
|
centerScaleRulerCenterXPx: 0,
|
|
centerScaleRulerZeroYPx: 0,
|
|
centerScaleRulerHeightPx: 0,
|
|
centerScaleRulerAxisBottomPx: 0,
|
|
centerScaleRulerZeroVisible: false,
|
|
centerScaleRulerZeroLabel: '0 m',
|
|
centerScaleRulerMinorTicks: [],
|
|
centerScaleRulerMajorMarks: [],
|
|
}
|
|
let centerScaleRulerInputCache: Partial<Pick<
|
|
MapPageData,
|
|
'stageWidth'
|
|
| 'stageHeight'
|
|
| 'zoom'
|
|
| 'centerTileY'
|
|
| 'tileSizePx'
|
|
| 'previewScale'
|
|
>> = {}
|
|
|
|
const DEBUG_ONLY_VIEW_KEYS = new Set<string>([
|
|
'buildVersion',
|
|
'renderMode',
|
|
'projectionMode',
|
|
'mapReady',
|
|
'mapReadyText',
|
|
'mapName',
|
|
'configStatusText',
|
|
'deviceHeadingText',
|
|
'devicePoseText',
|
|
'headingConfidenceText',
|
|
'accelerometerText',
|
|
'gyroscopeText',
|
|
'deviceMotionText',
|
|
'compassSourceText',
|
|
'compassTuningProfile',
|
|
'compassTuningProfileText',
|
|
'northReferenceButtonText',
|
|
'autoRotateSourceText',
|
|
'autoRotateCalibrationText',
|
|
'northReferenceText',
|
|
'centerText',
|
|
'tileSource',
|
|
'visibleTileCount',
|
|
'readyTileCount',
|
|
'memoryTileCount',
|
|
'diskTileCount',
|
|
'memoryHitCount',
|
|
'diskHitCount',
|
|
'networkFetchCount',
|
|
'cacheHitRateText',
|
|
'locationSourceMode',
|
|
'locationSourceText',
|
|
'mockBridgeConnected',
|
|
'mockBridgeStatusText',
|
|
'mockBridgeUrlText',
|
|
'mockCoordText',
|
|
'mockSpeedText',
|
|
'gpsCoordText',
|
|
'heartRateSourceMode',
|
|
'heartRateSourceText',
|
|
'heartRateConnected',
|
|
'heartRateStatusText',
|
|
'heartRateDeviceText',
|
|
'heartRateScanText',
|
|
'heartRateDiscoveredDevices',
|
|
'mockHeartRateBridgeConnected',
|
|
'mockHeartRateBridgeStatusText',
|
|
'mockHeartRateBridgeUrlText',
|
|
'mockHeartRateText',
|
|
])
|
|
|
|
const CENTER_SCALE_RULER_DEP_KEYS = new Set<string>([
|
|
'showCenterScaleRuler',
|
|
'centerScaleRulerAnchorMode',
|
|
'stageWidth',
|
|
'stageHeight',
|
|
'topInsetHeight',
|
|
'zoom',
|
|
'centerTileY',
|
|
'tileSizePx',
|
|
'previewScale',
|
|
])
|
|
|
|
const CENTER_SCALE_RULER_CACHE_KEYS: Array<keyof typeof centerScaleRulerInputCache> = [
|
|
'stageWidth',
|
|
'stageHeight',
|
|
'zoom',
|
|
'centerTileY',
|
|
'tileSizePx',
|
|
'previewScale',
|
|
]
|
|
|
|
const RULER_ONLY_VIEW_KEYS = new Set<string>([
|
|
'zoom',
|
|
'centerTileX',
|
|
'centerTileY',
|
|
'tileSizePx',
|
|
'previewScale',
|
|
'stageWidth',
|
|
'stageHeight',
|
|
'stageLeft',
|
|
'stageTop',
|
|
])
|
|
|
|
const SIDE_BUTTON_DEP_KEYS = new Set<string>([
|
|
'sideButtonMode',
|
|
'showGameInfoPanel',
|
|
'showCenterScaleRuler',
|
|
'centerScaleRulerAnchorMode',
|
|
'skipButtonEnabled',
|
|
'gameSessionStatus',
|
|
'gpsLockEnabled',
|
|
'gpsLockAvailable',
|
|
])
|
|
|
|
function hasAnyPatchKey(patch: Record<string, unknown>, keys: Set<string>): boolean {
|
|
return Object.keys(patch).some((key) => keys.has(key))
|
|
}
|
|
|
|
function filterDebugOnlyPatch(
|
|
patch: Partial<MapPageData>,
|
|
includeDebugFields: boolean,
|
|
includeRulerFields: boolean,
|
|
): Partial<MapPageData> {
|
|
if (includeDebugFields && includeRulerFields) {
|
|
return patch
|
|
}
|
|
|
|
const filteredPatch: Partial<MapPageData> = {}
|
|
for (const [key, value] of Object.entries(patch)) {
|
|
if (!includeDebugFields && DEBUG_ONLY_VIEW_KEYS.has(key)) {
|
|
continue
|
|
}
|
|
if (!includeRulerFields && RULER_ONLY_VIEW_KEYS.has(key)) {
|
|
continue
|
|
}
|
|
{
|
|
;(filteredPatch as Record<string, unknown>)[key] = value
|
|
}
|
|
}
|
|
return filteredPatch
|
|
}
|
|
|
|
function clearGameInfoPanelSyncTimer() {
|
|
if (gameInfoPanelSyncTimer) {
|
|
clearTimeout(gameInfoPanelSyncTimer)
|
|
gameInfoPanelSyncTimer = 0
|
|
}
|
|
}
|
|
|
|
function clearCenterScaleRulerSyncTimer() {
|
|
if (centerScaleRulerSyncTimer) {
|
|
clearTimeout(centerScaleRulerSyncTimer)
|
|
centerScaleRulerSyncTimer = 0
|
|
}
|
|
}
|
|
|
|
function clearCenterScaleRulerUpdateTimer() {
|
|
if (centerScaleRulerUpdateTimer) {
|
|
clearTimeout(centerScaleRulerUpdateTimer)
|
|
centerScaleRulerUpdateTimer = 0
|
|
}
|
|
}
|
|
|
|
function clearPunchHintDismissTimer() {
|
|
if (punchHintDismissTimer) {
|
|
clearTimeout(punchHintDismissTimer)
|
|
punchHintDismissTimer = 0
|
|
}
|
|
}
|
|
|
|
function clearPunchHintFxTimer() {
|
|
if (punchHintFxTimer) {
|
|
clearTimeout(punchHintFxTimer)
|
|
punchHintFxTimer = 0
|
|
}
|
|
}
|
|
|
|
function clearHudFxTimer(key: 'timer' | 'mileage' | 'speed' | 'heartRate') {
|
|
const timerMap = {
|
|
timer: panelTimerFxTimer,
|
|
mileage: panelMileageFxTimer,
|
|
speed: panelSpeedFxTimer,
|
|
heartRate: panelHeartRateFxTimer,
|
|
}
|
|
const timer = timerMap[key]
|
|
if (timer) {
|
|
clearTimeout(timer)
|
|
}
|
|
if (key === 'timer') {
|
|
panelTimerFxTimer = 0
|
|
} else if (key === 'mileage') {
|
|
panelMileageFxTimer = 0
|
|
} else if (key === 'speed') {
|
|
panelSpeedFxTimer = 0
|
|
} else {
|
|
panelHeartRateFxTimer = 0
|
|
}
|
|
}
|
|
|
|
function updateCenterScaleRulerInputCache(patch: Partial<MapPageData>) {
|
|
for (const key of CENTER_SCALE_RULER_CACHE_KEYS) {
|
|
if (Object.prototype.hasOwnProperty.call(patch, key)) {
|
|
;(centerScaleRulerInputCache as Record<string, unknown>)[key] =
|
|
(patch as Record<string, unknown>)[key]
|
|
}
|
|
}
|
|
}
|
|
|
|
function updateStoredUserSettings(patch: Partial<StoredUserSettings>) {
|
|
persistStoredUserSettings(
|
|
mergeStoredUserSettings(loadStoredUserSettings(), patch),
|
|
)
|
|
}
|
|
|
|
function loadStoredMockChannelId(): string {
|
|
try {
|
|
const value = wx.getStorageSync(DEBUG_MOCK_CHANNEL_ID_STORAGE_KEY)
|
|
if (typeof value === 'string' && value.trim().length > 0) {
|
|
return value.trim()
|
|
}
|
|
} catch (_error) {
|
|
// Ignore storage read failures and fall back to default.
|
|
}
|
|
return 'default'
|
|
}
|
|
|
|
function persistMockChannelId(channelId: string) {
|
|
try {
|
|
wx.setStorageSync(DEBUG_MOCK_CHANNEL_ID_STORAGE_KEY, channelId)
|
|
} catch (_error) {
|
|
// Ignore storage write failures in debug preference persistence.
|
|
}
|
|
}
|
|
|
|
function loadMockAutoConnectEnabled(): boolean {
|
|
try {
|
|
return wx.getStorageSync(DEBUG_MOCK_AUTO_CONNECT_STORAGE_KEY) === true
|
|
} catch (_error) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
function persistMockAutoConnectEnabled(enabled: boolean) {
|
|
try {
|
|
wx.setStorageSync(DEBUG_MOCK_AUTO_CONNECT_STORAGE_KEY, enabled)
|
|
} catch (_error) {
|
|
// Ignore storage write failures in debug preference persistence.
|
|
}
|
|
}
|
|
|
|
function buildResolvedSystemSettingsPatch(
|
|
resolvedSettings: ResolvedSystemSettingsState,
|
|
): Partial<MapPageData> {
|
|
return {
|
|
...resolvedSettings.values,
|
|
...resolvedSettings.locks,
|
|
autoRotateEnabled: resolvedSettings.values.autoRotateEnabled,
|
|
sideButtonPlacement: resolvedSettings.values.sideButtonPlacement,
|
|
showCenterScaleRuler: resolvedSettings.values.showCenterScaleRuler,
|
|
centerScaleRulerAnchorMode: resolvedSettings.values.centerScaleRulerAnchorMode,
|
|
}
|
|
}
|
|
|
|
function isSystemSettingsLockLifetimeActive(): boolean {
|
|
return systemSettingsLockLifetimeActive
|
|
}
|
|
|
|
function clearSessionRecoveryPersistTimer() {
|
|
if (sessionRecoveryPersistTimer) {
|
|
clearInterval(sessionRecoveryPersistTimer)
|
|
sessionRecoveryPersistTimer = 0
|
|
}
|
|
}
|
|
|
|
function clearResultExitRedirectTimer() {
|
|
if (resultExitRedirectTimer) {
|
|
clearTimeout(resultExitRedirectTimer)
|
|
resultExitRedirectTimer = 0
|
|
}
|
|
}
|
|
|
|
function clearResultExitCountdownTimer() {
|
|
if (resultExitCountdownTimer) {
|
|
clearInterval(resultExitCountdownTimer)
|
|
resultExitCountdownTimer = 0
|
|
}
|
|
}
|
|
|
|
function navigateAwayFromMapAfterCancel() {
|
|
const pages = getCurrentPages()
|
|
if (pages.length > 1) {
|
|
wx.navigateBack({
|
|
delta: 1,
|
|
})
|
|
return
|
|
}
|
|
|
|
wx.redirectTo({
|
|
url: '/pages/home/home',
|
|
})
|
|
}
|
|
|
|
function hasExplicitLaunchOptions(options?: MapPageLaunchOptions | null): boolean {
|
|
if (!options) {
|
|
return false
|
|
}
|
|
|
|
return !!(
|
|
options.launchId
|
|
|| options.preset
|
|
|| options.configUrl
|
|
|| options.competitionId
|
|
|| options.eventId
|
|
|| options.sessionId
|
|
|| options.launchRequestId
|
|
)
|
|
}
|
|
|
|
function getCurrentBackendSessionContext(): { sessionId: string; sessionToken: string } | null {
|
|
return getBackendSessionContextFromLaunchEnvelope(currentGameLaunchEnvelope)
|
|
}
|
|
|
|
function getCurrentBackendBaseUrl(): string {
|
|
const app = getApp<IAppOption>()
|
|
if (app.globalData && app.globalData.backendBaseUrl) {
|
|
return app.globalData.backendBaseUrl
|
|
}
|
|
return loadBackendBaseUrl()
|
|
}
|
|
|
|
function buildSideButtonVisibility(mode: SideButtonMode) {
|
|
return {
|
|
sideButtonMode: mode,
|
|
showLeftButtonGroup: mode === 'shown',
|
|
showRightButtonGroups: false,
|
|
showBottomDebugButton: true,
|
|
}
|
|
}
|
|
|
|
function getNextSideButtonMode(currentMode: SideButtonMode): SideButtonMode {
|
|
return currentMode === 'shown' ? 'hidden' : 'shown'
|
|
}
|
|
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 === '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' | 'showSystemSettingsPanel' | 'showCenterScaleRuler' | 'centerScaleRulerAnchorMode' | 'skipButtonEnabled' | 'gameSessionStatus' | 'gpsLockEnabled' | 'gpsLockAvailable'>) {
|
|
const sideButton2State: SideActionButtonState = !data.gpsLockAvailable
|
|
? 'muted'
|
|
: data.gpsLockEnabled
|
|
? 'active'
|
|
: 'default'
|
|
const sideButton4State: SideActionButtonState = data.gameSessionStatus === 'running' ? 'active' : 'muted'
|
|
const sideButton11State: SideActionButtonState = data.showGameInfoPanel ? 'active' : 'default'
|
|
const sideButton12State: SideActionButtonState = data.showSystemSettingsPanel ? '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),
|
|
sideButton12Class: getSideActionButtonClass(sideButton12State),
|
|
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) {
|
|
lastCenterScaleRulerStablePatch = {
|
|
centerScaleRulerVisible: false,
|
|
centerScaleRulerCenterXPx: 0,
|
|
centerScaleRulerZeroYPx: 0,
|
|
centerScaleRulerHeightPx: 0,
|
|
centerScaleRulerAxisBottomPx: 0,
|
|
centerScaleRulerZeroVisible: false,
|
|
centerScaleRulerZeroLabel: '0 m',
|
|
centerScaleRulerMinorTicks: [] as ScaleRulerMinorTickData[],
|
|
centerScaleRulerMajorMarks: [] as ScaleRulerMajorMarkData[],
|
|
}
|
|
return { ...lastCenterScaleRulerStablePatch }
|
|
}
|
|
|
|
if (!data.stageWidth || !data.stageHeight) {
|
|
return { ...lastCenterScaleRulerStablePatch }
|
|
}
|
|
|
|
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 {
|
|
...lastCenterScaleRulerStablePatch,
|
|
centerScaleRulerVisible: true,
|
|
centerScaleRulerCenterXPx: Math.round(data.stageWidth / 2),
|
|
centerScaleRulerZeroYPx: zeroYPx,
|
|
centerScaleRulerHeightPx: lastCenterScaleRulerStablePatch.centerScaleRulerHeightPx || fallbackHeight,
|
|
centerScaleRulerAxisBottomPx: coveredBottomPx,
|
|
centerScaleRulerZeroVisible: data.centerScaleRulerAnchorMode !== 'compass-center',
|
|
}
|
|
}
|
|
|
|
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 {
|
|
...lastCenterScaleRulerStablePatch,
|
|
centerScaleRulerVisible: true,
|
|
centerScaleRulerCenterXPx: Math.round(data.stageWidth / 2),
|
|
centerScaleRulerZeroYPx: zeroYPx,
|
|
centerScaleRulerHeightPx: lastCenterScaleRulerStablePatch.centerScaleRulerHeightPx || fallbackHeight,
|
|
centerScaleRulerAxisBottomPx: coveredBottomPx,
|
|
centerScaleRulerZeroVisible: data.centerScaleRulerAnchorMode !== 'compass-center',
|
|
}
|
|
}
|
|
|
|
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),
|
|
})
|
|
}
|
|
}
|
|
|
|
lastCenterScaleRulerStablePatch = {
|
|
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,
|
|
}
|
|
return { ...lastCenterScaleRulerStablePatch }
|
|
}
|
|
|
|
function buildEmptyGameInfoSnapshot(): MapEngineGameInfoSnapshot {
|
|
return {
|
|
title: '当前游戏',
|
|
subtitle: '未开始',
|
|
localRows: [],
|
|
globalRows: [
|
|
{ label: '全球积分', value: '未接入' },
|
|
{ label: '全球排名', value: '未接入' },
|
|
{ label: '在线人数', value: '未接入' },
|
|
{ label: '队伍状态', value: '未接入' },
|
|
{ label: '实时广播', value: '未接入' },
|
|
],
|
|
}
|
|
}
|
|
|
|
function buildEmptyResultSceneSnapshot(): MapEngineResultSnapshot {
|
|
return {
|
|
title: '本局结果',
|
|
subtitle: '未开始',
|
|
heroLabel: '本局用时',
|
|
heroValue: '--',
|
|
rows: [],
|
|
}
|
|
}
|
|
|
|
function buildRuntimeSummaryRows(envelope: GameLaunchEnvelope): MapEngineGameInfoRow[] {
|
|
const runtime = envelope.runtime
|
|
const variantName = envelope.variant ? (envelope.variant.variantName || envelope.variant.variantId || null) : null
|
|
const variantRouteCode = envelope.variant ? (envelope.variant.routeCode || null) : null
|
|
if (!runtime) {
|
|
return []
|
|
}
|
|
|
|
const rows: MapEngineGameInfoRow[] = []
|
|
rows.push({ label: '运行绑定', value: runtime.runtimeBindingId || '--' })
|
|
rows.push({ label: '地点', value: runtime.placeName || runtime.placeId || '--' })
|
|
rows.push({ label: '地图', value: runtime.mapName || runtime.mapId || '--' })
|
|
rows.push({ label: '赛道集', value: runtime.courseSetId || '--' })
|
|
rows.push({ label: '赛道版本', value: runtime.courseVariantId || variantName || '--' })
|
|
rows.push({ label: 'RouteCode', value: runtime.routeCode || variantRouteCode || '--' })
|
|
rows.push({ label: '瓦片版本', value: runtime.tileReleaseId || '--' })
|
|
return rows
|
|
}
|
|
|
|
function buildLaunchConfigSummaryRows(envelope: GameLaunchEnvelope): MapEngineGameInfoRow[] {
|
|
const rows: MapEngineGameInfoRow[] = []
|
|
rows.push({ label: '配置标签', value: envelope.config.configLabel || '--' })
|
|
rows.push({ label: '配置URL', value: envelope.config.configUrl || '--' })
|
|
rows.push({ label: '配置Release', value: envelope.config.releaseId || '--' })
|
|
rows.push({
|
|
label: 'Launch Event',
|
|
value: envelope.business && envelope.business.eventId
|
|
? envelope.business.eventId
|
|
: '--',
|
|
})
|
|
rows.push({
|
|
label: 'Resolved Manifest',
|
|
value: envelope.resolvedRelease && envelope.resolvedRelease.manifestUrl
|
|
? envelope.resolvedRelease.manifestUrl
|
|
: '--',
|
|
})
|
|
rows.push({
|
|
label: 'Resolved Release',
|
|
value: envelope.resolvedRelease && envelope.resolvedRelease.releaseId
|
|
? envelope.resolvedRelease.releaseId
|
|
: '--',
|
|
})
|
|
return rows
|
|
}
|
|
|
|
Page({
|
|
data: {
|
|
showDebugPanel: false,
|
|
showGameInfoPanel: false,
|
|
showResultScene: false,
|
|
showSystemSettingsPanel: false,
|
|
showHeartRateDevicePicker: false,
|
|
showCenterScaleRuler: false,
|
|
statusBarHeight: 0,
|
|
topInsetHeight: 12,
|
|
hudPanelIndex: 0,
|
|
configSourceText: '顺序赛配置',
|
|
centerScaleRulerAnchorMode: DEFAULT_STORED_USER_SETTINGS.centerScaleRulerAnchorMode,
|
|
punchHintFxClass: '',
|
|
autoRotateEnabled: DEFAULT_STORED_USER_SETTINGS.autoRotateEnabled,
|
|
...DEFAULT_SETTING_LOCKS,
|
|
gameInfoTitle: '当前游戏',
|
|
gameInfoSubtitle: '未开始',
|
|
gameInfoLocalRows: [],
|
|
gameInfoGlobalRows: buildEmptyGameInfoSnapshot().globalRows,
|
|
resultSceneTitle: '本局结果',
|
|
resultSceneSubtitle: '未开始',
|
|
resultSceneHeroLabel: '本局用时',
|
|
resultSceneHeroValue: '--',
|
|
resultSceneRows: buildEmptyResultSceneSnapshot().rows,
|
|
resultSceneCountdownText: '',
|
|
panelTimerText: '00:00:00',
|
|
panelTimerMode: 'elapsed',
|
|
panelMileageText: '0m',
|
|
panelActionTagText: '目标',
|
|
panelDistanceTagText: '点距',
|
|
panelTargetSummaryText: '等待选择目标',
|
|
panelDistanceValueText: '--',
|
|
panelDistanceUnitText: '',
|
|
panelProgressText: '0/0',
|
|
showPunchHintBanner: true,
|
|
sideButtonPlacement: 'left',
|
|
gameSessionStatus: 'idle',
|
|
gameModeText: '顺序赛',
|
|
gpsLockEnabled: false,
|
|
gpsLockAvailable: false,
|
|
locationSourceMode: 'real',
|
|
locationSourceText: '真实定位',
|
|
mockBridgeConnected: false,
|
|
mockBridgeStatusText: '未连接',
|
|
mockBridgeUrlText: 'wss://gs.gotomars.xyz/mock-gps',
|
|
mockChannelIdText: 'default',
|
|
mockBridgeUrlDraft: 'wss://gs.gotomars.xyz/mock-gps',
|
|
mockChannelIdDraft: 'default',
|
|
mockCoordText: '--',
|
|
mockSpeedText: '--',
|
|
heartRateSourceMode: 'real',
|
|
heartRateSourceText: '真实心率',
|
|
mockHeartRateBridgeConnected: false,
|
|
mockHeartRateBridgeStatusText: '未连接',
|
|
mockHeartRateBridgeUrlText: 'wss://gs.gotomars.xyz/mock-hr',
|
|
mockHeartRateBridgeUrlDraft: 'wss://gs.gotomars.xyz/mock-hr',
|
|
mockHeartRateText: '--',
|
|
mockDebugLogBridgeConnected: false,
|
|
mockDebugLogBridgeStatusText: '已关闭 (wss://gs.gotomars.xyz/debug-log)',
|
|
mockDebugLogBridgeUrlText: 'wss://gs.gotomars.xyz/debug-log',
|
|
mockDebugLogBridgeUrlDraft: 'wss://gs.gotomars.xyz/debug-log',
|
|
heartRateScanText: '未扫描',
|
|
heartRateDiscoveredDevices: [],
|
|
panelSpeedValueText: '0',
|
|
panelTelemetryTone: 'blue',
|
|
trackDisplayMode: DEFAULT_STORED_USER_SETTINGS.trackDisplayMode,
|
|
trackTailLength: DEFAULT_STORED_USER_SETTINGS.trackTailLength,
|
|
trackColorPreset: DEFAULT_STORED_USER_SETTINGS.trackColorPreset,
|
|
trackStyleProfile: DEFAULT_STORED_USER_SETTINGS.trackStyleProfile,
|
|
gpsMarkerVisible: DEFAULT_STORED_USER_SETTINGS.gpsMarkerVisible,
|
|
gpsMarkerStyle: DEFAULT_STORED_USER_SETTINGS.gpsMarkerStyle,
|
|
gpsMarkerSize: DEFAULT_STORED_USER_SETTINGS.gpsMarkerSize,
|
|
gpsMarkerColorPreset: DEFAULT_STORED_USER_SETTINGS.gpsMarkerColorPreset,
|
|
gpsLogoStatusText: '未配置',
|
|
gpsLogoSourceText: '--',
|
|
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: '--',
|
|
compassSourceText: '无数据',
|
|
compassTuningProfile: DEFAULT_STORED_USER_SETTINGS.compassTuningProfile,
|
|
compassTuningProfileText: '平衡',
|
|
punchButtonText: '打点',
|
|
punchButtonEnabled: false,
|
|
skipButtonEnabled: false,
|
|
punchHintText: '等待进入检查点范围',
|
|
punchFeedbackVisible: false,
|
|
punchFeedbackText: '',
|
|
punchFeedbackTone: 'neutral',
|
|
contentCardVisible: false,
|
|
contentCardTemplate: 'story',
|
|
contentCardTitle: '',
|
|
contentCardBody: '',
|
|
contentCardActions: [],
|
|
contentQuizVisible: false,
|
|
contentQuizQuestionText: '',
|
|
contentQuizCountdownText: '',
|
|
contentQuizOptions: [],
|
|
contentQuizFeedbackVisible: false,
|
|
contentQuizFeedbackText: '',
|
|
contentQuizFeedbackTone: 'neutral',
|
|
punchButtonFxClass: '',
|
|
panelProgressFxClass: '',
|
|
panelDistanceFxClass: '',
|
|
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(),
|
|
showStartEntryButton: true,
|
|
...buildSideButtonVisibility('shown'),
|
|
...buildSideButtonState({
|
|
sideButtonMode: 'shown',
|
|
showGameInfoPanel: false,
|
|
showSystemSettingsPanel: false,
|
|
showCenterScaleRuler: false,
|
|
centerScaleRulerAnchorMode: 'screen-center',
|
|
skipButtonEnabled: false,
|
|
gameSessionStatus: 'idle',
|
|
gpsLockEnabled: false,
|
|
gpsLockAvailable: false,
|
|
}),
|
|
} as unknown as MapPageData,
|
|
|
|
onLoad(options: MapPageLaunchOptions) {
|
|
clearSessionRecoveryPersistTimer()
|
|
clearResultExitRedirectTimer()
|
|
clearResultExitCountdownTimer()
|
|
syncedBackendSessionStartId = ''
|
|
syncedBackendSessionFinishId = ''
|
|
redirectedToResultPage = false
|
|
shouldAutoRestoreRecoverySnapshot = options && options.recoverSession === '1'
|
|
shouldAutoStartSessionOnEnter = !!(options && options.autoStartOnEnter === '1')
|
|
const recoverySnapshot = loadSessionRecoverySnapshot()
|
|
if (shouldAutoRestoreRecoverySnapshot && recoverySnapshot) {
|
|
// Recovery should trust the persisted session envelope first so it can
|
|
// survive launchId stash misses and still reconstruct the original round.
|
|
currentGameLaunchEnvelope = recoverySnapshot.launchEnvelope
|
|
} else {
|
|
currentGameLaunchEnvelope = resolveGameLaunchEnvelope(options)
|
|
if (!hasExplicitLaunchOptions(options) && recoverySnapshot) {
|
|
currentGameLaunchEnvelope = recoverySnapshot.launchEnvelope
|
|
}
|
|
}
|
|
currentSystemSettingsConfig = undefined
|
|
currentRemoteMapConfig = undefined
|
|
systemSettingsLockLifetimeActive = false
|
|
const storedMockChannelId = loadStoredMockChannelId()
|
|
const shouldAutoConnectMockSources = loadMockAutoConnectEnabled()
|
|
const systemInfo = wx.getSystemInfoSync()
|
|
const statusBarHeight = systemInfo.statusBarHeight || 0
|
|
const menuButtonRect = wx.getMenuButtonBoundingClientRect()
|
|
const menuButtonBottom = menuButtonRect && typeof menuButtonRect.bottom === 'number' ? menuButtonRect.bottom : statusBarHeight
|
|
this.setData({
|
|
showStartEntryButton: !shouldAutoStartSessionOnEnter,
|
|
})
|
|
|
|
if (mapEngine) {
|
|
mapEngine.destroy()
|
|
mapEngine = null
|
|
}
|
|
|
|
mapEngine = new MapEngine(INTERNAL_BUILD_VERSION, {
|
|
onData: (patch) => {
|
|
const nextPatch = patch as Partial<MapPageData>
|
|
const includeDebugFields = this.data.showDebugPanel
|
|
const includeRulerFields = this.data.showCenterScaleRuler
|
|
let shouldSyncRuntimeSystemSettings = false
|
|
let nextLockLifetimeActive = isSystemSettingsLockLifetimeActive()
|
|
let heartRateSwitchToastText = ''
|
|
const nextData: Partial<MapPageData> = filterDebugOnlyPatch({
|
|
...nextPatch,
|
|
}, includeDebugFields, includeRulerFields)
|
|
|
|
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
|
|
}
|
|
|
|
if (
|
|
typeof nextPatch.mockDebugLogBridgeUrlText === 'string'
|
|
&& this.data.mockDebugLogBridgeUrlDraft === this.data.mockDebugLogBridgeUrlText
|
|
) {
|
|
nextData.mockDebugLogBridgeUrlDraft = nextPatch.mockDebugLogBridgeUrlText
|
|
}
|
|
|
|
if (
|
|
typeof nextPatch.mockChannelIdText === 'string'
|
|
&& this.data.mockChannelIdDraft === this.data.mockChannelIdText
|
|
) {
|
|
nextData.mockChannelIdDraft = nextPatch.mockChannelIdText
|
|
}
|
|
|
|
updateCenterScaleRulerInputCache(nextPatch)
|
|
|
|
const mergedData = {
|
|
...centerScaleRulerInputCache,
|
|
...this.data,
|
|
...nextData,
|
|
} as MapPageData
|
|
|
|
const derivedPatch: Partial<MapPageData> = {}
|
|
if (typeof nextPatch.orientationMode === 'string') {
|
|
nextData.autoRotateEnabled = nextPatch.orientationMode === 'heading-up'
|
|
}
|
|
if (
|
|
this.data.showCenterScaleRuler
|
|
&& hasAnyPatchKey(nextPatch as Record<string, unknown>, CENTER_SCALE_RULER_DEP_KEYS)
|
|
) {
|
|
clearCenterScaleRulerUpdateTimer()
|
|
Object.assign(derivedPatch, buildCenterScaleRulerPatch(mergedData))
|
|
}
|
|
|
|
if (hasAnyPatchKey(nextPatch as Record<string, unknown>, SIDE_BUTTON_DEP_KEYS)) {
|
|
Object.assign(derivedPatch, buildSideButtonState(mergedData))
|
|
}
|
|
|
|
if (typeof nextPatch.punchHintText === 'string') {
|
|
const nextHintText = nextPatch.punchHintText.trim()
|
|
if (nextHintText !== this.data.punchHintText) {
|
|
clearPunchHintDismissTimer()
|
|
clearPunchHintFxTimer()
|
|
nextData.showPunchHintBanner = nextHintText.length > 0
|
|
if (nextHintText.length > 0) {
|
|
nextData.punchHintFxClass = 'game-punch-hint--fx-enter'
|
|
punchHintFxTimer = setTimeout(() => {
|
|
punchHintFxTimer = 0
|
|
this.setData({
|
|
punchHintFxClass: '',
|
|
})
|
|
}, PUNCH_HINT_FX_DURATION_MS) as unknown as number
|
|
const now = Date.now()
|
|
if (mapEngine && now - lastPunchHintHapticAt >= PUNCH_HINT_HAPTIC_GAP_MS) {
|
|
mapEngine.playPunchHintHaptic()
|
|
lastPunchHintHapticAt = now
|
|
}
|
|
punchHintDismissTimer = setTimeout(() => {
|
|
punchHintDismissTimer = 0
|
|
this.setData({
|
|
showPunchHintBanner: false,
|
|
})
|
|
}, PUNCH_HINT_AUTO_HIDE_MS) as unknown as number
|
|
}
|
|
} else if (!nextHintText) {
|
|
clearPunchHintDismissTimer()
|
|
clearPunchHintFxTimer()
|
|
nextData.showPunchHintBanner = false
|
|
nextData.punchHintFxClass = ''
|
|
}
|
|
}
|
|
|
|
const nextAnimationLevel = typeof nextPatch.animationLevel === 'string'
|
|
? nextPatch.animationLevel
|
|
: this.data.animationLevel
|
|
let shouldSyncBackendSessionStart = false
|
|
let backendSessionFinishStatus: 'finished' | 'failed' | null = null
|
|
let shouldOpenResultExitPrompt = false
|
|
let resultPageSnapshot: MapEngineResultSnapshot | null = null
|
|
|
|
if (nextAnimationLevel === 'lite') {
|
|
clearHudFxTimer('timer')
|
|
clearHudFxTimer('mileage')
|
|
clearHudFxTimer('speed')
|
|
clearHudFxTimer('heartRate')
|
|
nextData.panelTimerFxClass = ''
|
|
nextData.panelMileageFxClass = ''
|
|
nextData.panelSpeedFxClass = ''
|
|
nextData.panelHeartRateFxClass = ''
|
|
} else {
|
|
if (typeof nextPatch.panelTimerText === 'string' && nextPatch.panelTimerText !== this.data.panelTimerText && this.data.panelTimerText !== '00:00:00') {
|
|
clearHudFxTimer('timer')
|
|
nextData.panelTimerFxClass = 'race-panel__timer--fx-tick'
|
|
panelTimerFxTimer = setTimeout(() => {
|
|
panelTimerFxTimer = 0
|
|
this.setData({ panelTimerFxClass: '' })
|
|
}, 320) as unknown as number
|
|
}
|
|
|
|
if (typeof nextPatch.panelMileageText === 'string' && nextPatch.panelMileageText !== this.data.panelMileageText && this.data.panelMileageText !== '0m') {
|
|
clearHudFxTimer('mileage')
|
|
nextData.panelMileageFxClass = 'race-panel__mileage-wrap--fx-update'
|
|
panelMileageFxTimer = setTimeout(() => {
|
|
panelMileageFxTimer = 0
|
|
this.setData({ panelMileageFxClass: '' })
|
|
}, 360) as unknown as number
|
|
}
|
|
|
|
if (typeof nextPatch.panelSpeedValueText === 'string' && nextPatch.panelSpeedValueText !== this.data.panelSpeedValueText && this.data.panelSpeedValueText !== '0') {
|
|
clearHudFxTimer('speed')
|
|
nextData.panelSpeedFxClass = 'race-panel__metric-group--fx-speed-update'
|
|
panelSpeedFxTimer = setTimeout(() => {
|
|
panelSpeedFxTimer = 0
|
|
this.setData({ panelSpeedFxClass: '' })
|
|
}, 360) as unknown as number
|
|
}
|
|
|
|
if (typeof nextPatch.panelHeartRateValueText === 'string' && nextPatch.panelHeartRateValueText !== this.data.panelHeartRateValueText && this.data.panelHeartRateValueText !== '--') {
|
|
clearHudFxTimer('heartRate')
|
|
nextData.panelHeartRateFxClass = 'race-panel__metric-group--fx-heart-rate-update'
|
|
panelHeartRateFxTimer = setTimeout(() => {
|
|
panelHeartRateFxTimer = 0
|
|
this.setData({ panelHeartRateFxClass: '' })
|
|
}, 400) as unknown as number
|
|
}
|
|
}
|
|
|
|
if (typeof nextPatch.gameSessionStatus === 'string') {
|
|
if (
|
|
nextPatch.gameSessionStatus !== this.data.gameSessionStatus
|
|
&& (nextPatch.gameSessionStatus === 'finished' || nextPatch.gameSessionStatus === 'failed')
|
|
) {
|
|
systemSettingsLockLifetimeActive = false
|
|
nextLockLifetimeActive = false
|
|
shouldSyncRuntimeSystemSettings = true
|
|
clearSessionRecoverySnapshot()
|
|
clearSessionRecoveryPersistTimer()
|
|
clearResultExitRedirectTimer()
|
|
clearResultExitCountdownTimer()
|
|
resultPageSnapshot = mapEngine ? mapEngine.getResultSceneSnapshot() : null
|
|
nextData.showResultScene = true
|
|
nextData.showDebugPanel = false
|
|
nextData.showGameInfoPanel = false
|
|
nextData.showSystemSettingsPanel = false
|
|
clearGameInfoPanelSyncTimer()
|
|
backendSessionFinishStatus = nextPatch.gameSessionStatus === 'finished' ? 'finished' : 'failed'
|
|
shouldOpenResultExitPrompt = true
|
|
if (resultPageSnapshot) {
|
|
nextData.resultSceneTitle = resultPageSnapshot.title
|
|
nextData.resultSceneSubtitle = resultPageSnapshot.subtitle
|
|
nextData.resultSceneHeroLabel = resultPageSnapshot.heroLabel
|
|
nextData.resultSceneHeroValue = resultPageSnapshot.heroValue
|
|
nextData.resultSceneRows = resultPageSnapshot.rows
|
|
}
|
|
nextData.resultSceneCountdownText = '3 秒后自动进入成绩页'
|
|
} else if (
|
|
nextPatch.gameSessionStatus !== this.data.gameSessionStatus
|
|
&& nextPatch.gameSessionStatus === 'idle'
|
|
&& !isSystemSettingsLockLifetimeActive()
|
|
) {
|
|
nextLockLifetimeActive = false
|
|
shouldSyncRuntimeSystemSettings = true
|
|
clearSessionRecoverySnapshot()
|
|
clearSessionRecoveryPersistTimer()
|
|
clearResultExitRedirectTimer()
|
|
clearResultExitCountdownTimer()
|
|
} else if (
|
|
nextPatch.gameSessionStatus !== this.data.gameSessionStatus
|
|
&& nextPatch.gameSessionStatus === 'running'
|
|
) {
|
|
shouldSyncBackendSessionStart = true
|
|
} else if (nextPatch.gameSessionStatus === 'running' || nextPatch.gameSessionStatus === 'idle') {
|
|
nextData.showResultScene = false
|
|
}
|
|
}
|
|
|
|
if (
|
|
pendingHeartRateSwitchDeviceName
|
|
&& nextPatch.heartRateConnected === true
|
|
&& typeof nextPatch.heartRateDeviceText === 'string'
|
|
) {
|
|
const connectedDeviceName = nextPatch.heartRateDeviceText.trim()
|
|
if (connectedDeviceName && connectedDeviceName === pendingHeartRateSwitchDeviceName) {
|
|
heartRateSwitchToastText = `已切换到 ${connectedDeviceName}`
|
|
nextData.statusText = `已切换心率带:${connectedDeviceName}`
|
|
pendingHeartRateSwitchDeviceName = null
|
|
}
|
|
}
|
|
|
|
if (Object.keys(nextData).length || Object.keys(derivedPatch).length) {
|
|
this.setData({
|
|
...nextData,
|
|
...derivedPatch,
|
|
}, () => {
|
|
if (typeof nextPatch.gameSessionStatus === 'string') {
|
|
this.syncSessionRecoveryLifecycle(nextPatch.gameSessionStatus)
|
|
}
|
|
if (shouldSyncBackendSessionStart) {
|
|
this.syncBackendSessionStart()
|
|
}
|
|
if (backendSessionFinishStatus) {
|
|
this.syncBackendSessionFinish(backendSessionFinishStatus)
|
|
}
|
|
if (shouldOpenResultExitPrompt && resultPageSnapshot) {
|
|
this.stashPendingResultSnapshot(resultPageSnapshot)
|
|
this.presentResultExitPrompt()
|
|
}
|
|
if (heartRateSwitchToastText) {
|
|
wx.showToast({
|
|
title: `${heartRateSwitchToastText},并设为首选设备`,
|
|
icon: 'none',
|
|
duration: 1800,
|
|
})
|
|
}
|
|
if (shouldSyncRuntimeSystemSettings) {
|
|
this.applyRuntimeSystemSettings(nextLockLifetimeActive)
|
|
}
|
|
if (this.data.showGameInfoPanel) {
|
|
this.scheduleGameInfoPanelSnapshotSync()
|
|
}
|
|
})
|
|
} else {
|
|
if (typeof nextPatch.gameSessionStatus === 'string') {
|
|
this.syncSessionRecoveryLifecycle(nextPatch.gameSessionStatus)
|
|
}
|
|
if (shouldSyncBackendSessionStart) {
|
|
this.syncBackendSessionStart()
|
|
}
|
|
if (backendSessionFinishStatus) {
|
|
this.syncBackendSessionFinish(backendSessionFinishStatus)
|
|
}
|
|
if (shouldOpenResultExitPrompt && resultPageSnapshot) {
|
|
this.stashPendingResultSnapshot(resultPageSnapshot)
|
|
this.presentResultExitPrompt()
|
|
}
|
|
if (shouldSyncRuntimeSystemSettings) {
|
|
this.applyRuntimeSystemSettings(nextLockLifetimeActive)
|
|
}
|
|
if (this.data.showGameInfoPanel) {
|
|
this.scheduleGameInfoPanelSnapshotSync()
|
|
}
|
|
}
|
|
},
|
|
onOpenH5Experience: (request) => {
|
|
this.openH5Experience(request)
|
|
},
|
|
})
|
|
|
|
mapEngine.applyTelemetryPlayerProfile(getGlobalTelemetryProfile())
|
|
|
|
const systemSettingsState = resolveSystemSettingsState(undefined, undefined, false)
|
|
const initialSystemSettings = systemSettingsState.values
|
|
mapEngine.applyCompiledSettingsProfile({
|
|
values: initialSystemSettings,
|
|
locks: systemSettingsState.locks,
|
|
lockLifetimeActive: false,
|
|
})
|
|
|
|
mapEngine.setDiagnosticUiEnabled(false)
|
|
centerScaleRulerInputCache = {
|
|
stageWidth: 0,
|
|
stageHeight: 0,
|
|
zoom: 0,
|
|
centerTileY: 0,
|
|
tileSizePx: 0,
|
|
previewScale: 1,
|
|
}
|
|
|
|
const initialEngineData = mapEngine.getInitialData()
|
|
|
|
this.setData({
|
|
...initialEngineData,
|
|
...buildResolvedSystemSettingsPatch(systemSettingsState),
|
|
showDebugPanel: false,
|
|
showGameInfoPanel: false,
|
|
showResultScene: false,
|
|
showSystemSettingsPanel: false,
|
|
statusBarHeight,
|
|
topInsetHeight: Math.max(statusBarHeight + 12, menuButtonBottom + 20),
|
|
hudPanelIndex: 0,
|
|
configSourceText: currentGameLaunchEnvelope.config.configLabel,
|
|
gameInfoTitle: '当前游戏',
|
|
gameInfoSubtitle: '未开始',
|
|
gameInfoLocalRows: [],
|
|
gameInfoGlobalRows: buildEmptyGameInfoSnapshot().globalRows,
|
|
resultSceneTitle: '本局结果',
|
|
resultSceneSubtitle: '未开始',
|
|
resultSceneHeroLabel: '本局用时',
|
|
resultSceneHeroValue: '--',
|
|
resultSceneRows: buildEmptyResultSceneSnapshot().rows,
|
|
resultSceneCountdownText: '',
|
|
panelTimerText: '00:00:00',
|
|
panelTimerMode: 'elapsed',
|
|
panelTimerFxClass: '',
|
|
panelMileageText: '0m',
|
|
panelMileageFxClass: '',
|
|
panelActionTagText: '目标',
|
|
panelDistanceTagText: '点距',
|
|
panelTargetSummaryText: '等待选择目标',
|
|
panelDistanceValueText: '--',
|
|
panelDistanceUnitText: '',
|
|
panelProgressText: '0/0',
|
|
showPunchHintBanner: true,
|
|
gameSessionStatus: 'idle',
|
|
gameModeText: '顺序赛',
|
|
gpsLockEnabled: false,
|
|
gpsLockAvailable: false,
|
|
locationSourceMode: 'real',
|
|
locationSourceText: '真实定位',
|
|
mockBridgeConnected: false,
|
|
mockBridgeStatusText: '未连接',
|
|
mockBridgeUrlText: 'wss://gs.gotomars.xyz/mock-gps',
|
|
mockChannelIdText: storedMockChannelId,
|
|
mockBridgeUrlDraft: 'wss://gs.gotomars.xyz/mock-gps',
|
|
mockChannelIdDraft: storedMockChannelId,
|
|
mockCoordText: '--',
|
|
mockSpeedText: '--',
|
|
heartRateSourceMode: 'real',
|
|
heartRateSourceText: '真实心率',
|
|
mockHeartRateBridgeConnected: false,
|
|
mockHeartRateBridgeStatusText: '未连接',
|
|
mockHeartRateBridgeUrlText: 'wss://gs.gotomars.xyz/mock-hr',
|
|
mockHeartRateBridgeUrlDraft: 'wss://gs.gotomars.xyz/mock-hr',
|
|
mockHeartRateText: '--',
|
|
mockDebugLogBridgeConnected: false,
|
|
mockDebugLogBridgeStatusText: '已关闭 (wss://gs.gotomars.xyz/debug-log)',
|
|
mockDebugLogBridgeUrlText: 'wss://gs.gotomars.xyz/debug-log',
|
|
mockDebugLogBridgeUrlDraft: 'wss://gs.gotomars.xyz/debug-log',
|
|
panelSpeedValueText: '0',
|
|
panelSpeedFxClass: '',
|
|
panelTelemetryTone: 'blue',
|
|
gpsLogoStatusText: '未配置',
|
|
gpsLogoSourceText: '--',
|
|
panelHeartRateZoneNameText: '--',
|
|
panelHeartRateZoneRangeText: '',
|
|
heartRateConnected: false,
|
|
heartRateStatusText: '心率带未连接',
|
|
heartRateDeviceText: '--',
|
|
panelHeartRateValueText: '--',
|
|
panelHeartRateFxClass: '',
|
|
panelHeartRateUnitText: '',
|
|
panelCaloriesValueText: '0',
|
|
panelCaloriesUnitText: 'kcal',
|
|
panelAverageSpeedValueText: '0',
|
|
panelAverageSpeedUnitText: 'km/h',
|
|
panelAccuracyValueText: '--',
|
|
panelAccuracyUnitText: '',
|
|
deviceHeadingText: '--',
|
|
devicePoseText: '竖持',
|
|
headingConfidenceText: '低',
|
|
accelerometerText: '--',
|
|
gyroscopeText: '--',
|
|
deviceMotionText: '--',
|
|
compassSourceText: '无数据',
|
|
compassTuningProfileText: initialEngineData.compassTuningProfileText || '平衡',
|
|
punchButtonText: '打点',
|
|
punchButtonEnabled: false,
|
|
skipButtonEnabled: false,
|
|
punchHintText: '等待进入检查点范围',
|
|
punchHintFxClass: '',
|
|
punchFeedbackVisible: false,
|
|
punchFeedbackText: '',
|
|
punchFeedbackTone: 'neutral',
|
|
contentCardVisible: false,
|
|
contentCardTemplate: 'story',
|
|
contentCardTitle: '',
|
|
contentCardBody: '',
|
|
contentCardActions: [],
|
|
contentQuizVisible: false,
|
|
contentQuizQuestionText: '',
|
|
contentQuizCountdownText: '',
|
|
contentQuizOptions: [],
|
|
contentQuizFeedbackVisible: false,
|
|
contentQuizFeedbackText: '',
|
|
contentQuizFeedbackTone: 'neutral',
|
|
punchButtonFxClass: '',
|
|
panelProgressFxClass: '',
|
|
panelDistanceFxClass: '',
|
|
punchFeedbackFxClass: '',
|
|
contentCardFxClass: '',
|
|
mapPulseVisible: false,
|
|
mapPulseLeftPx: 0,
|
|
mapPulseTopPx: 0,
|
|
mapPulseFxClass: '',
|
|
stageFxVisible: false,
|
|
stageFxClass: '',
|
|
compassTicks: buildCompassTicks(),
|
|
compassLabels: buildCompassLabels(),
|
|
...buildSideButtonVisibility('shown'),
|
|
...buildSideButtonState({
|
|
sideButtonMode: 'shown',
|
|
showGameInfoPanel: false,
|
|
showSystemSettingsPanel: false,
|
|
showCenterScaleRuler: initialSystemSettings.showCenterScaleRuler,
|
|
centerScaleRulerAnchorMode: initialSystemSettings.centerScaleRulerAnchorMode,
|
|
skipButtonEnabled: false,
|
|
gameSessionStatus: 'idle',
|
|
gpsLockEnabled: false,
|
|
gpsLockAvailable: false,
|
|
}),
|
|
...buildCenterScaleRulerPatch({
|
|
...(mapEngine.getInitialData() as MapPageData),
|
|
showCenterScaleRuler: initialSystemSettings.showCenterScaleRuler,
|
|
centerScaleRulerAnchorMode: initialSystemSettings.centerScaleRulerAnchorMode,
|
|
stageWidth: 0,
|
|
stageHeight: 0,
|
|
topInsetHeight: Math.max(statusBarHeight + 12, menuButtonBottom + 20),
|
|
zoom: 0,
|
|
centerTileY: 0,
|
|
tileSizePx: 0,
|
|
}),
|
|
}, () => {
|
|
if (shouldAutoConnectMockSources) {
|
|
this.handleConnectAllMockSources()
|
|
}
|
|
})
|
|
},
|
|
|
|
onReady() {
|
|
stageCanvasAttached = false
|
|
this.measureStageAndCanvas()
|
|
this.loadGameLaunchEnvelope(currentGameLaunchEnvelope)
|
|
const app = getApp<IAppOption>()
|
|
const pendingHeartRateAutoConnect = app.globalData ? app.globalData.pendingHeartRateAutoConnect : null
|
|
if (pendingHeartRateAutoConnect && pendingHeartRateAutoConnect.enabled && mapEngine) {
|
|
const pendingDeviceName = pendingHeartRateAutoConnect.deviceName || '心率带'
|
|
app.globalData.pendingHeartRateAutoConnect = null
|
|
mapEngine.handleConnectHeartRate()
|
|
this.setData({
|
|
statusText: `正在自动连接局前设备:${pendingDeviceName}`,
|
|
heartRateStatusText: `正在自动连接 ${pendingDeviceName}`,
|
|
heartRateDeviceText: pendingDeviceName,
|
|
})
|
|
}
|
|
},
|
|
|
|
onShow() {
|
|
if (mapEngine) {
|
|
this.applyCompiledRuntimeProfiles()
|
|
mapEngine.handleAppShow()
|
|
}
|
|
},
|
|
|
|
onHide() {
|
|
this.persistSessionRecoverySnapshot()
|
|
clearResultExitRedirectTimer()
|
|
clearResultExitCountdownTimer()
|
|
if (mapEngine) {
|
|
mapEngine.handleAppHide()
|
|
}
|
|
},
|
|
|
|
onUnload() {
|
|
this.persistSessionRecoverySnapshot()
|
|
clearSessionRecoveryPersistTimer()
|
|
clearResultExitRedirectTimer()
|
|
clearResultExitCountdownTimer()
|
|
syncedBackendSessionStartId = ''
|
|
syncedBackendSessionFinishId = ''
|
|
clearGameInfoPanelSyncTimer()
|
|
clearCenterScaleRulerSyncTimer()
|
|
clearCenterScaleRulerUpdateTimer()
|
|
clearPunchHintDismissTimer()
|
|
clearPunchHintFxTimer()
|
|
clearHudFxTimer('timer')
|
|
clearHudFxTimer('mileage')
|
|
clearHudFxTimer('speed')
|
|
clearHudFxTimer('heartRate')
|
|
if (mapEngine) {
|
|
mapEngine.destroy()
|
|
mapEngine = null
|
|
}
|
|
currentSystemSettingsConfig = undefined
|
|
currentRemoteMapConfig = undefined
|
|
systemSettingsLockLifetimeActive = false
|
|
currentGameLaunchEnvelope = getDemoGameLaunchEnvelope()
|
|
shouldAutoRestoreRecoverySnapshot = false
|
|
shouldAutoStartSessionOnEnter = false
|
|
redirectedToResultPage = false
|
|
stageCanvasAttached = false
|
|
},
|
|
|
|
loadGameLaunchEnvelope(envelope: GameLaunchEnvelope) {
|
|
this.loadMapConfigFromRemote(
|
|
envelope.config.configUrl,
|
|
envelope.config.configLabel,
|
|
)
|
|
},
|
|
|
|
persistSessionRecoverySnapshot() {
|
|
if (!mapEngine || !currentRemoteMapConfig) {
|
|
return false
|
|
}
|
|
|
|
const runtimeSnapshot = mapEngine.buildSessionRecoveryRuntimeSnapshot()
|
|
if (!runtimeSnapshot) {
|
|
return false
|
|
}
|
|
|
|
const snapshot: SessionRecoverySnapshot = {
|
|
schemaVersion: 1,
|
|
savedAt: Date.now(),
|
|
launchEnvelope: currentGameLaunchEnvelope,
|
|
configAppId: currentRemoteMapConfig.configAppId,
|
|
configVersion: currentRemoteMapConfig.configVersion,
|
|
runtime: runtimeSnapshot,
|
|
}
|
|
saveSessionRecoverySnapshot(snapshot)
|
|
return true
|
|
},
|
|
|
|
syncBackendSessionStart() {
|
|
const sessionContext = getCurrentBackendSessionContext()
|
|
if (!sessionContext || syncedBackendSessionStartId === sessionContext.sessionId) {
|
|
return
|
|
}
|
|
|
|
startSession({
|
|
baseUrl: getCurrentBackendBaseUrl(),
|
|
sessionId: sessionContext.sessionId,
|
|
sessionToken: sessionContext.sessionToken,
|
|
})
|
|
.then(() => {
|
|
syncedBackendSessionStartId = sessionContext.sessionId
|
|
})
|
|
.catch((error) => {
|
|
const message = error && error.message ? error.message : '未知错误'
|
|
this.setData({
|
|
statusText: `session start 上报失败: ${message}`,
|
|
})
|
|
})
|
|
},
|
|
|
|
syncBackendSessionFinish(statusOverride?: 'finished' | 'failed' | 'cancelled') {
|
|
const sessionContext = getCurrentBackendSessionContext()
|
|
if (!sessionContext || syncedBackendSessionFinishId === sessionContext.sessionId || !mapEngine) {
|
|
return
|
|
}
|
|
|
|
const finishSummary = mapEngine.getSessionFinishSummary(statusOverride)
|
|
if (!finishSummary) {
|
|
return
|
|
}
|
|
|
|
const summaryPayload: BackendSessionFinishSummaryPayload = {}
|
|
if (typeof finishSummary.finalDurationSec === 'number') {
|
|
summaryPayload.finalDurationSec = finishSummary.finalDurationSec
|
|
}
|
|
if (typeof finishSummary.finalScore === 'number') {
|
|
summaryPayload.finalScore = finishSummary.finalScore
|
|
}
|
|
if (typeof finishSummary.completedControls === 'number') {
|
|
summaryPayload.completedControls = finishSummary.completedControls
|
|
}
|
|
if (typeof finishSummary.totalControls === 'number') {
|
|
summaryPayload.totalControls = finishSummary.totalControls
|
|
}
|
|
if (typeof finishSummary.distanceMeters === 'number') {
|
|
summaryPayload.distanceMeters = finishSummary.distanceMeters
|
|
}
|
|
if (typeof finishSummary.averageSpeedKmh === 'number') {
|
|
summaryPayload.averageSpeedKmh = finishSummary.averageSpeedKmh
|
|
}
|
|
|
|
finishSession({
|
|
baseUrl: getCurrentBackendBaseUrl(),
|
|
sessionId: sessionContext.sessionId,
|
|
sessionToken: sessionContext.sessionToken,
|
|
status: finishSummary.status,
|
|
summary: summaryPayload,
|
|
})
|
|
.then(() => {
|
|
syncedBackendSessionFinishId = sessionContext.sessionId
|
|
})
|
|
.catch((error) => {
|
|
const message = error && error.message ? error.message : '未知错误'
|
|
this.setData({
|
|
statusText: `session finish 上报失败: ${message}`,
|
|
})
|
|
})
|
|
},
|
|
|
|
reportAbandonedRecoverySnapshot(snapshot: SessionRecoverySnapshot) {
|
|
const sessionContext = getBackendSessionContextFromLaunchEnvelope(snapshot.launchEnvelope)
|
|
if (!sessionContext) {
|
|
reportBackendClientLog({
|
|
level: 'warn',
|
|
category: 'session-recovery',
|
|
message: 'abandon recovery without valid session context',
|
|
eventId: snapshot.launchEnvelope.business && snapshot.launchEnvelope.business.eventId
|
|
? snapshot.launchEnvelope.business.eventId
|
|
: '',
|
|
releaseId: snapshot.launchEnvelope.config && snapshot.launchEnvelope.config.releaseId
|
|
? snapshot.launchEnvelope.config.releaseId
|
|
: '',
|
|
manifestUrl: snapshot.launchEnvelope.resolvedRelease && snapshot.launchEnvelope.resolvedRelease.manifestUrl
|
|
? snapshot.launchEnvelope.resolvedRelease.manifestUrl
|
|
: snapshot.launchEnvelope.config && snapshot.launchEnvelope.config.configUrl
|
|
? snapshot.launchEnvelope.config.configUrl
|
|
: '',
|
|
details: {
|
|
phase: 'abandon-no-session',
|
|
},
|
|
})
|
|
clearSessionRecoverySnapshot()
|
|
return
|
|
}
|
|
|
|
reportBackendClientLog({
|
|
level: 'info',
|
|
category: 'session-recovery',
|
|
message: 'abandon recovery requested',
|
|
eventId: snapshot.launchEnvelope.business && snapshot.launchEnvelope.business.eventId
|
|
? snapshot.launchEnvelope.business.eventId
|
|
: '',
|
|
releaseId: snapshot.launchEnvelope.config && snapshot.launchEnvelope.config.releaseId
|
|
? snapshot.launchEnvelope.config.releaseId
|
|
: '',
|
|
sessionId: sessionContext.sessionId,
|
|
manifestUrl: snapshot.launchEnvelope.resolvedRelease && snapshot.launchEnvelope.resolvedRelease.manifestUrl
|
|
? snapshot.launchEnvelope.resolvedRelease.manifestUrl
|
|
: snapshot.launchEnvelope.config && snapshot.launchEnvelope.config.configUrl
|
|
? snapshot.launchEnvelope.config.configUrl
|
|
: '',
|
|
details: {
|
|
phase: 'abandon-requested',
|
|
},
|
|
})
|
|
finishSession({
|
|
baseUrl: getCurrentBackendBaseUrl(),
|
|
sessionId: sessionContext.sessionId,
|
|
sessionToken: sessionContext.sessionToken,
|
|
status: 'cancelled',
|
|
summary: {},
|
|
})
|
|
.then(() => {
|
|
syncedBackendSessionFinishId = sessionContext.sessionId
|
|
reportBackendClientLog({
|
|
level: 'info',
|
|
category: 'session-recovery',
|
|
message: 'abandon recovery synced as cancelled',
|
|
eventId: snapshot.launchEnvelope.business && snapshot.launchEnvelope.business.eventId
|
|
? snapshot.launchEnvelope.business.eventId
|
|
: '',
|
|
releaseId: snapshot.launchEnvelope.config && snapshot.launchEnvelope.config.releaseId
|
|
? snapshot.launchEnvelope.config.releaseId
|
|
: '',
|
|
sessionId: sessionContext.sessionId,
|
|
manifestUrl: snapshot.launchEnvelope.resolvedRelease && snapshot.launchEnvelope.resolvedRelease.manifestUrl
|
|
? snapshot.launchEnvelope.resolvedRelease.manifestUrl
|
|
: snapshot.launchEnvelope.config && snapshot.launchEnvelope.config.configUrl
|
|
? snapshot.launchEnvelope.config.configUrl
|
|
: '',
|
|
details: {
|
|
phase: 'abandon-finished',
|
|
},
|
|
})
|
|
clearSessionRecoverySnapshot()
|
|
wx.showToast({
|
|
title: '已放弃上次对局',
|
|
icon: 'none',
|
|
duration: 1400,
|
|
})
|
|
})
|
|
.catch((error) => {
|
|
reportBackendClientLog({
|
|
level: 'warn',
|
|
category: 'session-recovery',
|
|
message: 'abandon recovery finish(cancelled) failed',
|
|
eventId: snapshot.launchEnvelope.business && snapshot.launchEnvelope.business.eventId
|
|
? snapshot.launchEnvelope.business.eventId
|
|
: '',
|
|
releaseId: snapshot.launchEnvelope.config && snapshot.launchEnvelope.config.releaseId
|
|
? snapshot.launchEnvelope.config.releaseId
|
|
: '',
|
|
sessionId: sessionContext.sessionId,
|
|
manifestUrl: snapshot.launchEnvelope.resolvedRelease && snapshot.launchEnvelope.resolvedRelease.manifestUrl
|
|
? snapshot.launchEnvelope.resolvedRelease.manifestUrl
|
|
: snapshot.launchEnvelope.config && snapshot.launchEnvelope.config.configUrl
|
|
? snapshot.launchEnvelope.config.configUrl
|
|
: '',
|
|
details: {
|
|
phase: 'abandon-failed',
|
|
message: error && error.message ? error.message : '未知错误',
|
|
},
|
|
})
|
|
clearSessionRecoverySnapshot()
|
|
const message = error && error.message ? error.message : '未知错误'
|
|
this.setData({
|
|
statusText: `放弃恢复已生效,后端取消上报失败: ${message}`,
|
|
})
|
|
wx.showToast({
|
|
title: '已放弃上次对局',
|
|
icon: 'none',
|
|
duration: 1400,
|
|
})
|
|
})
|
|
},
|
|
|
|
stashPendingResultSnapshot(snapshot: MapEngineResultSnapshot) {
|
|
const app = getApp<IAppOption>()
|
|
if (app.globalData) {
|
|
app.globalData.pendingResultSnapshot = snapshot
|
|
app.globalData.pendingResultLaunchEnvelope = currentGameLaunchEnvelope
|
|
}
|
|
},
|
|
|
|
redirectToResultPage() {
|
|
if (redirectedToResultPage) {
|
|
return
|
|
}
|
|
clearResultExitRedirectTimer()
|
|
clearResultExitCountdownTimer()
|
|
redirectedToResultPage = true
|
|
const sessionContext = getCurrentBackendSessionContext()
|
|
const resultUrl = sessionContext
|
|
? `/pages/result/result?sessionId=${encodeURIComponent(sessionContext.sessionId)}`
|
|
: '/pages/result/result'
|
|
wx.redirectTo({
|
|
url: resultUrl,
|
|
})
|
|
},
|
|
|
|
presentResultExitPrompt() {
|
|
clearResultExitRedirectTimer()
|
|
clearResultExitCountdownTimer()
|
|
|
|
let remainingSeconds = Math.ceil(RESULT_EXIT_REDIRECT_DELAY_MS / 1000)
|
|
this.setData({
|
|
showResultScene: true,
|
|
resultSceneCountdownText: `${remainingSeconds} 秒后自动进入成绩页`,
|
|
})
|
|
|
|
resultExitCountdownTimer = setInterval(() => {
|
|
remainingSeconds -= 1
|
|
if (remainingSeconds <= 0) {
|
|
clearResultExitCountdownTimer()
|
|
return
|
|
}
|
|
|
|
this.setData({
|
|
resultSceneCountdownText: `${remainingSeconds} 秒后自动进入成绩页`,
|
|
})
|
|
}, 1000) as unknown as number
|
|
|
|
resultExitRedirectTimer = setTimeout(() => {
|
|
resultExitRedirectTimer = 0
|
|
this.redirectToResultPage()
|
|
}, RESULT_EXIT_REDIRECT_DELAY_MS) as unknown as number
|
|
},
|
|
|
|
restoreRecoverySnapshot(snapshot: SessionRecoverySnapshot) {
|
|
systemSettingsLockLifetimeActive = true
|
|
this.applyRuntimeSystemSettings(true)
|
|
const restored = mapEngine ? mapEngine.restoreSessionRecoveryRuntimeSnapshot(snapshot.runtime) : false
|
|
if (!restored) {
|
|
reportBackendClientLog({
|
|
level: 'warn',
|
|
category: 'session-recovery',
|
|
message: 'recovery restore failed',
|
|
eventId: snapshot.launchEnvelope.business && snapshot.launchEnvelope.business.eventId
|
|
? snapshot.launchEnvelope.business.eventId
|
|
: '',
|
|
releaseId: snapshot.launchEnvelope.config && snapshot.launchEnvelope.config.releaseId
|
|
? snapshot.launchEnvelope.config.releaseId
|
|
: '',
|
|
sessionId: snapshot.launchEnvelope.business && snapshot.launchEnvelope.business.sessionId
|
|
? snapshot.launchEnvelope.business.sessionId
|
|
: '',
|
|
manifestUrl: snapshot.launchEnvelope.resolvedRelease && snapshot.launchEnvelope.resolvedRelease.manifestUrl
|
|
? snapshot.launchEnvelope.resolvedRelease.manifestUrl
|
|
: snapshot.launchEnvelope.config && snapshot.launchEnvelope.config.configUrl
|
|
? snapshot.launchEnvelope.config.configUrl
|
|
: '',
|
|
details: {
|
|
phase: 'restore-failed',
|
|
},
|
|
})
|
|
clearSessionRecoverySnapshot()
|
|
wx.showToast({
|
|
title: '恢复失败,已回到初始状态',
|
|
icon: 'none',
|
|
duration: 1600,
|
|
})
|
|
return false
|
|
}
|
|
|
|
this.setData({
|
|
showResultScene: false,
|
|
showDebugPanel: false,
|
|
showGameInfoPanel: false,
|
|
showSystemSettingsPanel: false,
|
|
showStartEntryButton: false,
|
|
})
|
|
const sessionContext = getCurrentBackendSessionContext()
|
|
if (sessionContext) {
|
|
syncedBackendSessionStartId = sessionContext.sessionId
|
|
}
|
|
reportBackendClientLog({
|
|
level: 'info',
|
|
category: 'session-recovery',
|
|
message: 'recovery restored',
|
|
eventId: snapshot.launchEnvelope.business && snapshot.launchEnvelope.business.eventId
|
|
? snapshot.launchEnvelope.business.eventId
|
|
: '',
|
|
releaseId: snapshot.launchEnvelope.config && snapshot.launchEnvelope.config.releaseId
|
|
? snapshot.launchEnvelope.config.releaseId
|
|
: '',
|
|
sessionId: snapshot.launchEnvelope.business && snapshot.launchEnvelope.business.sessionId
|
|
? snapshot.launchEnvelope.business.sessionId
|
|
: '',
|
|
manifestUrl: snapshot.launchEnvelope.resolvedRelease && snapshot.launchEnvelope.resolvedRelease.manifestUrl
|
|
? snapshot.launchEnvelope.resolvedRelease.manifestUrl
|
|
: snapshot.launchEnvelope.config && snapshot.launchEnvelope.config.configUrl
|
|
? snapshot.launchEnvelope.config.configUrl
|
|
: '',
|
|
details: {
|
|
phase: 'restored',
|
|
},
|
|
})
|
|
this.syncSessionRecoveryLifecycle('running')
|
|
return true
|
|
},
|
|
|
|
syncSessionRecoveryLifecycle(status: MapPageData['gameSessionStatus']) {
|
|
if (status === 'running') {
|
|
this.persistSessionRecoverySnapshot()
|
|
if (!sessionRecoveryPersistTimer) {
|
|
sessionRecoveryPersistTimer = setInterval(() => {
|
|
this.persistSessionRecoverySnapshot()
|
|
}, SESSION_RECOVERY_PERSIST_INTERVAL_MS) as unknown as number
|
|
}
|
|
return
|
|
}
|
|
|
|
clearSessionRecoveryPersistTimer()
|
|
},
|
|
|
|
maybePromptSessionRecoveryRestore(config: RemoteMapConfig) {
|
|
const snapshot = loadSessionRecoverySnapshot()
|
|
if (!snapshot || !mapEngine) {
|
|
return false
|
|
}
|
|
|
|
if (
|
|
snapshot.launchEnvelope.config.configUrl !== currentGameLaunchEnvelope.config.configUrl
|
|
|| snapshot.configAppId !== config.configAppId
|
|
) {
|
|
reportBackendClientLog({
|
|
level: 'warn',
|
|
category: 'session-recovery',
|
|
message: 'recovery snapshot dropped due to config mismatch',
|
|
eventId: snapshot.launchEnvelope.business && snapshot.launchEnvelope.business.eventId
|
|
? snapshot.launchEnvelope.business.eventId
|
|
: '',
|
|
releaseId: snapshot.launchEnvelope.config && snapshot.launchEnvelope.config.releaseId
|
|
? snapshot.launchEnvelope.config.releaseId
|
|
: '',
|
|
sessionId: snapshot.launchEnvelope.business && snapshot.launchEnvelope.business.sessionId
|
|
? snapshot.launchEnvelope.business.sessionId
|
|
: '',
|
|
manifestUrl: snapshot.launchEnvelope.resolvedRelease && snapshot.launchEnvelope.resolvedRelease.manifestUrl
|
|
? snapshot.launchEnvelope.resolvedRelease.manifestUrl
|
|
: snapshot.launchEnvelope.config && snapshot.launchEnvelope.config.configUrl
|
|
? snapshot.launchEnvelope.config.configUrl
|
|
: '',
|
|
details: {
|
|
phase: 'config-mismatch',
|
|
currentConfigUrl: currentGameLaunchEnvelope.config.configUrl,
|
|
snapshotConfigUrl: snapshot.launchEnvelope.config.configUrl,
|
|
currentConfigAppId: config.configAppId,
|
|
snapshotConfigAppId: snapshot.configAppId,
|
|
},
|
|
})
|
|
clearSessionRecoverySnapshot()
|
|
this.setData({
|
|
statusText: '检测到旧局恢复记录,但当前配置源已变化,已回到初始状态',
|
|
})
|
|
return false
|
|
}
|
|
|
|
if (shouldAutoRestoreRecoverySnapshot) {
|
|
shouldAutoRestoreRecoverySnapshot = false
|
|
reportBackendClientLog({
|
|
level: 'info',
|
|
category: 'session-recovery',
|
|
message: 'auto recovery requested',
|
|
eventId: snapshot.launchEnvelope.business && snapshot.launchEnvelope.business.eventId
|
|
? snapshot.launchEnvelope.business.eventId
|
|
: '',
|
|
releaseId: snapshot.launchEnvelope.config && snapshot.launchEnvelope.config.releaseId
|
|
? snapshot.launchEnvelope.config.releaseId
|
|
: '',
|
|
sessionId: snapshot.launchEnvelope.business && snapshot.launchEnvelope.business.sessionId
|
|
? snapshot.launchEnvelope.business.sessionId
|
|
: '',
|
|
manifestUrl: snapshot.launchEnvelope.resolvedRelease && snapshot.launchEnvelope.resolvedRelease.manifestUrl
|
|
? snapshot.launchEnvelope.resolvedRelease.manifestUrl
|
|
: snapshot.launchEnvelope.config && snapshot.launchEnvelope.config.configUrl
|
|
? snapshot.launchEnvelope.config.configUrl
|
|
: '',
|
|
details: {
|
|
phase: 'auto-restore',
|
|
},
|
|
})
|
|
this.restoreRecoverySnapshot(snapshot)
|
|
return true
|
|
}
|
|
|
|
this.setData({
|
|
showStartEntryButton: true,
|
|
})
|
|
wx.showModal({
|
|
title: '恢复对局',
|
|
content: '检测到上次有未正常结束的对局,是否继续恢复?',
|
|
confirmText: '继续恢复',
|
|
cancelText: '放弃',
|
|
success: (result) => {
|
|
if (!result.confirm) {
|
|
this.reportAbandonedRecoverySnapshot(snapshot)
|
|
return
|
|
}
|
|
|
|
this.restoreRecoverySnapshot(snapshot)
|
|
},
|
|
})
|
|
return true
|
|
},
|
|
|
|
maybeAutoStartSessionOnEnter() {
|
|
if (!shouldAutoStartSessionOnEnter || !mapEngine) {
|
|
return
|
|
}
|
|
|
|
shouldAutoStartSessionOnEnter = false
|
|
systemSettingsLockLifetimeActive = true
|
|
this.applyRuntimeSystemSettings(true)
|
|
this.setData({
|
|
showStartEntryButton: false,
|
|
})
|
|
mapEngine.handleStartGame()
|
|
},
|
|
|
|
compileCurrentRuntimeProfile(lockLifetimeActive = isSystemSettingsLockLifetimeActive()) {
|
|
if (!currentRemoteMapConfig) {
|
|
return null
|
|
}
|
|
|
|
return compileRuntimeProfile(currentRemoteMapConfig, {
|
|
playerTelemetryProfile: getGlobalTelemetryProfile(),
|
|
settingsLockLifetimeActive: lockLifetimeActive,
|
|
})
|
|
},
|
|
|
|
applyCompiledRuntimeProfiles(
|
|
lockLifetimeActive = isSystemSettingsLockLifetimeActive(),
|
|
options?: {
|
|
includeSettings?: boolean
|
|
includeMap?: boolean
|
|
includeGame?: boolean
|
|
includePresentation?: boolean
|
|
includeTelemetry?: boolean
|
|
includeFeedback?: boolean
|
|
},
|
|
) {
|
|
const currentEngine = mapEngine
|
|
if (!currentEngine) {
|
|
return null
|
|
}
|
|
|
|
const compiledProfile = this.compileCurrentRuntimeProfile(lockLifetimeActive)
|
|
if (!compiledProfile) {
|
|
return null
|
|
}
|
|
|
|
if (options && options.includeMap) {
|
|
currentEngine.applyCompiledMapProfile(compiledProfile.map)
|
|
}
|
|
if (options && options.includeSettings) {
|
|
currentEngine.applyCompiledSettingsProfile(compiledProfile.settings)
|
|
}
|
|
if (options && options.includeGame) {
|
|
currentEngine.applyCompiledGameProfile(compiledProfile.game)
|
|
}
|
|
if (options && options.includePresentation) {
|
|
currentEngine.applyCompiledPresentationProfile(compiledProfile.presentation)
|
|
}
|
|
if (!options || options.includeTelemetry !== false) {
|
|
currentEngine.applyCompiledTelemetryProfile(compiledProfile.telemetry)
|
|
}
|
|
if (!options || options.includeFeedback !== false) {
|
|
currentEngine.applyCompiledFeedbackProfile(compiledProfile.feedback)
|
|
}
|
|
return compiledProfile
|
|
},
|
|
|
|
applyRuntimeSystemSettings(lockLifetimeActive = isSystemSettingsLockLifetimeActive()) {
|
|
const currentEngine = mapEngine
|
|
if (!currentEngine) {
|
|
return null
|
|
}
|
|
|
|
const compiledProfile = this.applyCompiledRuntimeProfiles(lockLifetimeActive, {
|
|
includeSettings: true,
|
|
})
|
|
|| {
|
|
settings: resolveSystemSettingsState(
|
|
currentSystemSettingsConfig,
|
|
undefined,
|
|
lockLifetimeActive,
|
|
),
|
|
}
|
|
const resolvedSettings = compiledProfile.settings
|
|
|
|
const engineSnapshot = currentEngine.getInitialData() as Partial<MapPageData>
|
|
updateCenterScaleRulerInputCache(engineSnapshot)
|
|
const resolvedPatch = buildResolvedSystemSettingsPatch(resolvedSettings)
|
|
const mergedData = {
|
|
...centerScaleRulerInputCache,
|
|
...this.data,
|
|
...engineSnapshot,
|
|
...resolvedPatch,
|
|
} as MapPageData
|
|
|
|
this.setData({
|
|
...filterDebugOnlyPatch(engineSnapshot, this.data.showDebugPanel, resolvedSettings.values.showCenterScaleRuler),
|
|
...resolvedPatch,
|
|
...buildCenterScaleRulerPatch(mergedData),
|
|
...buildSideButtonState(mergedData),
|
|
})
|
|
return resolvedSettings
|
|
},
|
|
|
|
persistAndApplySystemSettings(
|
|
patch: Partial<StoredUserSettings>,
|
|
options?: {
|
|
applyCenterScaleRuler?: boolean
|
|
},
|
|
) {
|
|
updateStoredUserSettings(patch)
|
|
const lockLifetimeActive = isSystemSettingsLockLifetimeActive()
|
|
const resolvedSettings = this.applyRuntimeSystemSettings(lockLifetimeActive)
|
|
if (!resolvedSettings || !(options && options.applyCenterScaleRuler)) {
|
|
return resolvedSettings
|
|
}
|
|
|
|
this.applyCenterScaleRulerSettings(
|
|
resolvedSettings.values.showCenterScaleRuler,
|
|
resolvedSettings.values.centerScaleRulerAnchorMode,
|
|
)
|
|
return resolvedSettings
|
|
},
|
|
|
|
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)
|
|
this.applyConfiguredSystemSettings(config)
|
|
const compiledProfile = this.applyCompiledRuntimeProfiles(true, {
|
|
includeMap: true,
|
|
includeGame: true,
|
|
includePresentation: true,
|
|
})
|
|
if (compiledProfile) {
|
|
reportBackendClientLog({
|
|
level: 'info',
|
|
category: 'runtime-compiler',
|
|
message: 'compiled runtime profile applied',
|
|
eventId: currentGameLaunchEnvelope.business && currentGameLaunchEnvelope.business.eventId
|
|
? currentGameLaunchEnvelope.business.eventId
|
|
: '',
|
|
releaseId: currentGameLaunchEnvelope.config && currentGameLaunchEnvelope.config.releaseId
|
|
? currentGameLaunchEnvelope.config.releaseId
|
|
: '',
|
|
sessionId: currentGameLaunchEnvelope.business && currentGameLaunchEnvelope.business.sessionId
|
|
? currentGameLaunchEnvelope.business.sessionId
|
|
: '',
|
|
manifestUrl: currentGameLaunchEnvelope.resolvedRelease && currentGameLaunchEnvelope.resolvedRelease.manifestUrl
|
|
? currentGameLaunchEnvelope.resolvedRelease.manifestUrl
|
|
: currentGameLaunchEnvelope.config && currentGameLaunchEnvelope.config.configUrl
|
|
? currentGameLaunchEnvelope.config.configUrl
|
|
: '',
|
|
details: {
|
|
phase: 'compiled-runtime-applied',
|
|
schemaVersion: config.configSchemaVersion || '',
|
|
playfield: {
|
|
kind: config.playfieldKind || '',
|
|
},
|
|
game: {
|
|
mode: config.gameMode || '',
|
|
},
|
|
},
|
|
})
|
|
}
|
|
const recoveryHandled = this.maybePromptSessionRecoveryRestore(config)
|
|
if (!recoveryHandled) {
|
|
this.maybeAutoStartSessionOnEnter()
|
|
} else {
|
|
shouldAutoStartSessionOnEnter = false
|
|
}
|
|
})
|
|
.catch((error) => {
|
|
if (mapEngine !== currentEngine) {
|
|
return
|
|
}
|
|
|
|
const rawErrorMessage = error && error.message ? error.message : '未知错误'
|
|
const errorMessage = rawErrorMessage.indexOf('404') >= 0
|
|
? `release manifest 不存在或未发布 (${configLabel})`
|
|
: rawErrorMessage
|
|
this.setData({
|
|
configStatusText: `载入失败: ${errorMessage}`,
|
|
statusText: `远程地图配置载入失败: ${errorMessage} (${INTERNAL_BUILD_VERSION})`,
|
|
})
|
|
})
|
|
},
|
|
|
|
applyConfiguredSystemSettings(config: RemoteMapConfig) {
|
|
currentRemoteMapConfig = config
|
|
currentSystemSettingsConfig = config.systemSettingsConfig
|
|
systemSettingsLockLifetimeActive = true
|
|
this.applyRuntimeSystemSettings(true)
|
|
},
|
|
|
|
measureStageAndCanvas(onApplied?: () => void) {
|
|
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 (onApplied) {
|
|
onApplied()
|
|
}
|
|
|
|
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()
|
|
}
|
|
},
|
|
|
|
handleConnectAllMockSources() {
|
|
if (!mapEngine) {
|
|
return
|
|
}
|
|
const channelId = (this.data.mockChannelIdDraft || '').trim() || 'default'
|
|
this.setData({
|
|
mockChannelIdDraft: channelId,
|
|
})
|
|
persistMockChannelId(channelId)
|
|
persistMockAutoConnectEnabled(true)
|
|
setGlobalMockDebugBridgeChannelId(channelId)
|
|
setGlobalMockDebugBridgeUrl(this.data.mockDebugLogBridgeUrlDraft)
|
|
persistStoredMockDebugLogBridgeUrl(this.data.mockDebugLogBridgeUrlDraft)
|
|
setGlobalMockDebugBridgeEnabled(true)
|
|
mapEngine.handleSetMockChannelId(channelId)
|
|
mapEngine.handleSetMockLocationBridgeUrl(this.data.mockBridgeUrlDraft)
|
|
mapEngine.handleSetMockHeartRateBridgeUrl(this.data.mockHeartRateBridgeUrlDraft)
|
|
mapEngine.handleSetMockDebugLogBridgeUrl(this.data.mockDebugLogBridgeUrlDraft)
|
|
mapEngine.handleConnectMockLocationBridge()
|
|
mapEngine.handleSetMockLocationMode()
|
|
mapEngine.handleSetMockHeartRateMode()
|
|
mapEngine.handleConnectMockHeartRateBridge()
|
|
mapEngine.handleConnectMockDebugLogBridge()
|
|
},
|
|
|
|
handleOpenWebViewTest() {
|
|
wx.navigateTo({
|
|
url: '/pages/webview-test/webview-test',
|
|
})
|
|
},
|
|
|
|
handleMockChannelIdInput(event: WechatMiniprogram.Input) {
|
|
this.setData({
|
|
mockChannelIdDraft: event.detail.value,
|
|
})
|
|
},
|
|
|
|
handleSaveMockChannelId() {
|
|
const channelId = (this.data.mockChannelIdDraft || '').trim() || 'default'
|
|
this.setData({
|
|
mockChannelIdDraft: channelId,
|
|
})
|
|
persistMockChannelId(channelId)
|
|
setGlobalMockDebugBridgeChannelId(channelId)
|
|
if (mapEngine) {
|
|
mapEngine.handleSetMockChannelId(channelId)
|
|
}
|
|
},
|
|
|
|
handleMockBridgeUrlInput(event: WechatMiniprogram.Input) {
|
|
this.setData({
|
|
mockBridgeUrlDraft: event.detail.value,
|
|
})
|
|
},
|
|
|
|
handleSaveMockBridgeUrl() {
|
|
if (mapEngine) {
|
|
mapEngine.handleSetMockLocationBridgeUrl(this.data.mockBridgeUrlDraft)
|
|
}
|
|
},
|
|
|
|
handleDisconnectMockLocationBridge() {
|
|
persistMockAutoConnectEnabled(false)
|
|
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)
|
|
}
|
|
},
|
|
|
|
handleMockDebugLogBridgeUrlInput(event: WechatMiniprogram.Input) {
|
|
this.setData({
|
|
mockDebugLogBridgeUrlDraft: event.detail.value,
|
|
})
|
|
},
|
|
|
|
handleSaveMockDebugLogBridgeUrl() {
|
|
persistStoredMockDebugLogBridgeUrl(this.data.mockDebugLogBridgeUrlDraft)
|
|
setGlobalMockDebugBridgeUrl(this.data.mockDebugLogBridgeUrlDraft)
|
|
if (mapEngine) {
|
|
mapEngine.handleSetMockDebugLogBridgeUrl(this.data.mockDebugLogBridgeUrlDraft)
|
|
}
|
|
},
|
|
|
|
handleConnectMockDebugLogBridge() {
|
|
setGlobalMockDebugBridgeChannelId((this.data.mockChannelIdDraft || '').trim() || 'default')
|
|
setGlobalMockDebugBridgeUrl(this.data.mockDebugLogBridgeUrlDraft)
|
|
setGlobalMockDebugBridgeEnabled(true)
|
|
if (mapEngine) {
|
|
mapEngine.handleConnectMockDebugLogBridge()
|
|
}
|
|
},
|
|
|
|
handleDisconnectMockDebugLogBridge() {
|
|
persistMockAutoConnectEnabled(false)
|
|
setGlobalMockDebugBridgeEnabled(false)
|
|
if (mapEngine) {
|
|
mapEngine.handleDisconnectMockDebugLogBridge()
|
|
}
|
|
},
|
|
|
|
handleConnectMockHeartRateBridge() {
|
|
if (mapEngine) {
|
|
mapEngine.handleConnectMockHeartRateBridge()
|
|
}
|
|
},
|
|
|
|
handleDisconnectMockHeartRateBridge() {
|
|
persistMockAutoConnectEnabled(false)
|
|
if (mapEngine) {
|
|
mapEngine.handleDisconnectMockHeartRateBridge()
|
|
}
|
|
},
|
|
|
|
handleConnectHeartRate() {
|
|
if (this.data.lockHeartRateDevice || this.data.heartRateSourceMode !== 'real') {
|
|
return
|
|
}
|
|
if (mapEngine) {
|
|
mapEngine.handleConnectHeartRate()
|
|
}
|
|
},
|
|
|
|
handleOpenHeartRateDevicePicker() {
|
|
if (this.data.lockHeartRateDevice || this.data.heartRateSourceMode !== 'real') {
|
|
return
|
|
}
|
|
this.setData({
|
|
showHeartRateDevicePicker: true,
|
|
})
|
|
if (mapEngine) {
|
|
mapEngine.handleConnectHeartRate()
|
|
}
|
|
},
|
|
|
|
handleCloseHeartRateDevicePicker() {
|
|
this.setData({
|
|
showHeartRateDevicePicker: false,
|
|
})
|
|
},
|
|
|
|
handleDisconnectHeartRate() {
|
|
if (this.data.lockHeartRateDevice || this.data.heartRateSourceMode !== 'real') {
|
|
return
|
|
}
|
|
if (mapEngine) {
|
|
mapEngine.handleDisconnectHeartRate()
|
|
}
|
|
},
|
|
|
|
handleConnectHeartRateDevice(event: WechatMiniprogram.BaseEvent<{ deviceId?: string }>) {
|
|
if (mapEngine && event.currentTarget && event.currentTarget.dataset && event.currentTarget.dataset.deviceId) {
|
|
const targetDeviceId = event.currentTarget.dataset.deviceId
|
|
const targetDevice = this.data.heartRateDiscoveredDevices.find((item) => item.deviceId === targetDeviceId)
|
|
pendingHeartRateSwitchDeviceName = targetDevice ? targetDevice.name : null
|
|
mapEngine.handleConnectHeartRateDevice(targetDeviceId)
|
|
this.setData({
|
|
showHeartRateDevicePicker: false,
|
|
statusText: targetDevice
|
|
? `正在切换到 ${targetDevice.name}`
|
|
: '正在切换心率带设备',
|
|
})
|
|
}
|
|
},
|
|
|
|
handleClearPreferredHeartRateDevice() {
|
|
if (this.data.lockHeartRateDevice) {
|
|
return
|
|
}
|
|
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')
|
|
}
|
|
},
|
|
|
|
handleDebugSetSessionRemainingWarning() {
|
|
if (mapEngine) {
|
|
mapEngine.handleDebugSetSessionRemainingWarning()
|
|
}
|
|
},
|
|
|
|
handleDebugSetSessionRemainingOneMinute() {
|
|
if (mapEngine) {
|
|
mapEngine.handleDebugSetSessionRemainingOneMinute()
|
|
}
|
|
},
|
|
|
|
handleDebugTimeoutSession() {
|
|
if (mapEngine) {
|
|
mapEngine.handleDebugTimeoutSession()
|
|
}
|
|
},
|
|
|
|
handleClearDebugHeartRate() {
|
|
if (mapEngine) {
|
|
mapEngine.handleClearDebugHeartRate()
|
|
}
|
|
},
|
|
|
|
handleToggleOsmReference() {
|
|
if (mapEngine) {
|
|
mapEngine.handleToggleOsmReference()
|
|
}
|
|
},
|
|
|
|
handleStartGame() {
|
|
if (mapEngine) {
|
|
shouldAutoStartSessionOnEnter = false
|
|
systemSettingsLockLifetimeActive = true
|
|
this.applyRuntimeSystemSettings(true)
|
|
this.setData({
|
|
showStartEntryButton: false,
|
|
})
|
|
mapEngine.handleStartGame()
|
|
}
|
|
},
|
|
|
|
handleLoadClassicConfig() {
|
|
currentGameLaunchEnvelope = getDemoGameLaunchEnvelope('classic')
|
|
this.loadGameLaunchEnvelope(currentGameLaunchEnvelope)
|
|
},
|
|
|
|
handleLoadScoreOConfig() {
|
|
currentGameLaunchEnvelope = getDemoGameLaunchEnvelope('score-o')
|
|
this.loadGameLaunchEnvelope(currentGameLaunchEnvelope)
|
|
},
|
|
|
|
handleForceExitGame() {
|
|
if (!mapEngine || this.data.gameSessionStatus !== 'running') {
|
|
return
|
|
}
|
|
|
|
wx.showModal({
|
|
title: '确认退出',
|
|
content: '确认强制结束当前对局并返回开始前状态?',
|
|
confirmText: '确认退出',
|
|
cancelText: '取消',
|
|
success: (result) => {
|
|
if (result.confirm && mapEngine) {
|
|
clearResultExitRedirectTimer()
|
|
clearResultExitCountdownTimer()
|
|
this.syncBackendSessionFinish('cancelled')
|
|
clearSessionRecoverySnapshot()
|
|
clearSessionRecoveryPersistTimer()
|
|
systemSettingsLockLifetimeActive = false
|
|
mapEngine.handleForceExitGame()
|
|
wx.showToast({
|
|
title: '已退出当前对局',
|
|
icon: 'none',
|
|
duration: 1000,
|
|
})
|
|
setTimeout(() => {
|
|
navigateAwayFromMapAfterCancel()
|
|
}, 180)
|
|
}
|
|
},
|
|
})
|
|
},
|
|
|
|
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([
|
|
...buildRuntimeSummaryRows(currentGameLaunchEnvelope),
|
|
...buildLaunchConfigSummaryRows(currentGameLaunchEnvelope),
|
|
{ label: '比例尺开关', value: this.data.showCenterScaleRuler ? '开启' : '关闭' },
|
|
{ label: '比例尺锚点', value: this.data.centerScaleRulerAnchorMode === 'compass-center' ? '指北针圆心' : '屏幕中心' },
|
|
{ label: '按钮习惯', value: this.data.sideButtonPlacement === 'right' ? '右手' : '左手' },
|
|
{ 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,
|
|
})
|
|
},
|
|
|
|
syncResultSceneSnapshot() {
|
|
if (!mapEngine) {
|
|
return
|
|
}
|
|
|
|
const snapshot = mapEngine.getResultSceneSnapshot()
|
|
this.setData({
|
|
resultSceneTitle: snapshot.title,
|
|
resultSceneSubtitle: snapshot.subtitle,
|
|
resultSceneHeroLabel: snapshot.heroLabel,
|
|
resultSceneHeroValue: snapshot.heroValue,
|
|
resultSceneRows: snapshot.rows
|
|
.concat(buildRuntimeSummaryRows(currentGameLaunchEnvelope))
|
|
.concat(buildLaunchConfigSummaryRows(currentGameLaunchEnvelope)),
|
|
})
|
|
},
|
|
|
|
scheduleGameInfoPanelSnapshotSync() {
|
|
if (!this.data.showGameInfoPanel) {
|
|
clearGameInfoPanelSyncTimer()
|
|
return
|
|
}
|
|
|
|
if (gameInfoPanelSyncTimer) {
|
|
return
|
|
}
|
|
|
|
gameInfoPanelSyncTimer = setTimeout(() => {
|
|
gameInfoPanelSyncTimer = 0
|
|
if (this.data.showGameInfoPanel) {
|
|
this.syncGameInfoPanelSnapshot()
|
|
}
|
|
}, 400) as unknown as number
|
|
},
|
|
|
|
handleOpenGameInfoPanel() {
|
|
clearGameInfoPanelSyncTimer()
|
|
this.syncGameInfoPanelSnapshot()
|
|
this.setData({
|
|
showDebugPanel: false,
|
|
showSystemSettingsPanel: false,
|
|
showGameInfoPanel: true,
|
|
...buildSideButtonState({
|
|
sideButtonMode: this.data.sideButtonMode,
|
|
showGameInfoPanel: true,
|
|
showSystemSettingsPanel: 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,
|
|
}),
|
|
})
|
|
},
|
|
|
|
handleCloseGameInfoPanel() {
|
|
clearGameInfoPanelSyncTimer()
|
|
this.setData({
|
|
showGameInfoPanel: false,
|
|
...buildSideButtonState({
|
|
sideButtonMode: this.data.sideButtonMode,
|
|
showGameInfoPanel: false,
|
|
showSystemSettingsPanel: this.data.showSystemSettingsPanel,
|
|
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() {},
|
|
|
|
handleResultSceneTap() {},
|
|
|
|
handleCloseResultScene() {
|
|
this.redirectToResultPage()
|
|
},
|
|
|
|
handleRestartFromResult() {
|
|
this.redirectToResultPage()
|
|
},
|
|
|
|
handleOpenSystemSettingsPanel() {
|
|
clearGameInfoPanelSyncTimer()
|
|
this.setData({
|
|
showDebugPanel: false,
|
|
showGameInfoPanel: false,
|
|
showSystemSettingsPanel: true,
|
|
...buildSideButtonState({
|
|
sideButtonMode: this.data.sideButtonMode,
|
|
showGameInfoPanel: false,
|
|
showSystemSettingsPanel: 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,
|
|
}),
|
|
})
|
|
},
|
|
|
|
handleCloseSystemSettingsPanel() {
|
|
this.setData({
|
|
showSystemSettingsPanel: false,
|
|
...buildSideButtonState({
|
|
sideButtonMode: this.data.sideButtonMode,
|
|
showGameInfoPanel: this.data.showGameInfoPanel,
|
|
showSystemSettingsPanel: 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,
|
|
}),
|
|
})
|
|
},
|
|
|
|
handleSystemSettingsPanelTap() {},
|
|
|
|
handleSetAnimationLevelStandard() {
|
|
if (this.data.lockAnimationLevel || !mapEngine) {
|
|
return
|
|
}
|
|
this.persistAndApplySystemSettings({
|
|
animationLevel: 'standard',
|
|
})
|
|
},
|
|
|
|
handleSetAnimationLevelLite() {
|
|
if (this.data.lockAnimationLevel || !mapEngine) {
|
|
return
|
|
}
|
|
this.persistAndApplySystemSettings({
|
|
animationLevel: 'lite',
|
|
})
|
|
},
|
|
|
|
handleSetTrackModeNone() {
|
|
if (this.data.lockTrackMode || !mapEngine) {
|
|
return
|
|
}
|
|
this.persistAndApplySystemSettings({
|
|
trackDisplayMode: 'none',
|
|
})
|
|
},
|
|
|
|
handleSetTrackModeTail() {
|
|
if (this.data.lockTrackMode || !mapEngine) {
|
|
return
|
|
}
|
|
this.persistAndApplySystemSettings({
|
|
trackDisplayMode: 'tail',
|
|
})
|
|
},
|
|
|
|
handleSetTrackModeFull() {
|
|
if (this.data.lockTrackMode || !mapEngine) {
|
|
return
|
|
}
|
|
this.persistAndApplySystemSettings({
|
|
trackDisplayMode: 'full',
|
|
})
|
|
},
|
|
|
|
handleSetTrackTailLengthShort() {
|
|
if (this.data.lockTrackTailLength || !mapEngine) {
|
|
return
|
|
}
|
|
this.persistAndApplySystemSettings({
|
|
trackTailLength: 'short',
|
|
})
|
|
},
|
|
|
|
handleSetTrackTailLengthMedium() {
|
|
if (this.data.lockTrackTailLength || !mapEngine) {
|
|
return
|
|
}
|
|
this.persistAndApplySystemSettings({
|
|
trackTailLength: 'medium',
|
|
})
|
|
},
|
|
|
|
handleSetTrackTailLengthLong() {
|
|
if (this.data.lockTrackTailLength || !mapEngine) {
|
|
return
|
|
}
|
|
this.persistAndApplySystemSettings({
|
|
trackTailLength: 'long',
|
|
})
|
|
},
|
|
|
|
handleSetTrackColorPreset(event: WechatMiniprogram.TouchEvent) {
|
|
if (this.data.lockTrackColor || !mapEngine) {
|
|
return
|
|
}
|
|
const color = event.currentTarget.dataset.color as TrackColorPreset | undefined
|
|
if (!color) {
|
|
return
|
|
}
|
|
this.persistAndApplySystemSettings({
|
|
trackColorPreset: color,
|
|
})
|
|
},
|
|
|
|
handleSetTrackStyleClassic() {
|
|
if (this.data.lockTrackStyle || !mapEngine) {
|
|
return
|
|
}
|
|
this.persistAndApplySystemSettings({
|
|
trackStyleProfile: 'classic',
|
|
})
|
|
},
|
|
|
|
handleSetTrackStyleNeon() {
|
|
if (this.data.lockTrackStyle || !mapEngine) {
|
|
return
|
|
}
|
|
this.persistAndApplySystemSettings({
|
|
trackStyleProfile: 'neon',
|
|
})
|
|
},
|
|
|
|
handleSetGpsMarkerVisibleOn() {
|
|
if (this.data.lockGpsMarkerVisible || !mapEngine) {
|
|
return
|
|
}
|
|
this.persistAndApplySystemSettings({
|
|
gpsMarkerVisible: true,
|
|
})
|
|
},
|
|
|
|
handleSetGpsMarkerVisibleOff() {
|
|
if (this.data.lockGpsMarkerVisible || !mapEngine) {
|
|
return
|
|
}
|
|
this.persistAndApplySystemSettings({
|
|
gpsMarkerVisible: false,
|
|
})
|
|
},
|
|
|
|
handleSetGpsMarkerStyleDot() {
|
|
if (this.data.lockGpsMarkerStyle || !mapEngine) {
|
|
return
|
|
}
|
|
this.persistAndApplySystemSettings({
|
|
gpsMarkerStyle: 'dot',
|
|
})
|
|
},
|
|
|
|
handleSetGpsMarkerStyleBeacon() {
|
|
if (this.data.lockGpsMarkerStyle || !mapEngine) {
|
|
return
|
|
}
|
|
this.persistAndApplySystemSettings({
|
|
gpsMarkerStyle: 'beacon',
|
|
})
|
|
},
|
|
|
|
handleSetGpsMarkerStyleDisc() {
|
|
if (this.data.lockGpsMarkerStyle || !mapEngine) {
|
|
return
|
|
}
|
|
this.persistAndApplySystemSettings({
|
|
gpsMarkerStyle: 'disc',
|
|
})
|
|
},
|
|
|
|
handleSetGpsMarkerStyleBadge() {
|
|
if (this.data.lockGpsMarkerStyle || !mapEngine) {
|
|
return
|
|
}
|
|
this.persistAndApplySystemSettings({
|
|
gpsMarkerStyle: 'badge',
|
|
})
|
|
},
|
|
|
|
handleSetGpsMarkerSizeSmall() {
|
|
if (this.data.lockGpsMarkerSize || !mapEngine) {
|
|
return
|
|
}
|
|
this.persistAndApplySystemSettings({
|
|
gpsMarkerSize: 'small',
|
|
})
|
|
},
|
|
|
|
handleSetGpsMarkerSizeMedium() {
|
|
if (this.data.lockGpsMarkerSize || !mapEngine) {
|
|
return
|
|
}
|
|
this.persistAndApplySystemSettings({
|
|
gpsMarkerSize: 'medium',
|
|
})
|
|
},
|
|
|
|
handleSetGpsMarkerSizeLarge() {
|
|
if (this.data.lockGpsMarkerSize || !mapEngine) {
|
|
return
|
|
}
|
|
this.persistAndApplySystemSettings({
|
|
gpsMarkerSize: 'large',
|
|
})
|
|
},
|
|
|
|
handleSetGpsMarkerColorPreset(event: WechatMiniprogram.TouchEvent) {
|
|
if (this.data.lockGpsMarkerColor || !mapEngine) {
|
|
return
|
|
}
|
|
const color = event.currentTarget.dataset.color as GpsMarkerColorPreset | undefined
|
|
if (!color) {
|
|
return
|
|
}
|
|
this.persistAndApplySystemSettings({
|
|
gpsMarkerColorPreset: color,
|
|
})
|
|
},
|
|
|
|
handleSetSideButtonPlacementLeft() {
|
|
if (this.data.lockSideButtonPlacement) {
|
|
return
|
|
}
|
|
this.persistAndApplySystemSettings({
|
|
sideButtonPlacement: 'left',
|
|
})
|
|
},
|
|
|
|
handleSetSideButtonPlacementRight() {
|
|
if (this.data.lockSideButtonPlacement) {
|
|
return
|
|
}
|
|
this.persistAndApplySystemSettings({
|
|
sideButtonPlacement: 'right',
|
|
})
|
|
},
|
|
|
|
handleSetAutoRotateEnabledOn() {
|
|
if (this.data.lockAutoRotate || !mapEngine) {
|
|
return
|
|
}
|
|
this.persistAndApplySystemSettings({
|
|
autoRotateEnabled: true,
|
|
})
|
|
},
|
|
|
|
handleSetAutoRotateEnabledOff() {
|
|
if (this.data.lockAutoRotate || !mapEngine) {
|
|
return
|
|
}
|
|
this.persistAndApplySystemSettings({
|
|
autoRotateEnabled: false,
|
|
})
|
|
},
|
|
|
|
handleSetCompassTuningSmooth() {
|
|
if (this.data.lockCompassTuning || !mapEngine) {
|
|
return
|
|
}
|
|
this.persistAndApplySystemSettings({
|
|
compassTuningProfile: 'smooth',
|
|
})
|
|
},
|
|
|
|
handleSetCompassTuningBalanced() {
|
|
if (this.data.lockCompassTuning || !mapEngine) {
|
|
return
|
|
}
|
|
this.persistAndApplySystemSettings({
|
|
compassTuningProfile: 'balanced',
|
|
})
|
|
},
|
|
|
|
handleSetCompassTuningResponsive() {
|
|
if (this.data.lockCompassTuning || !mapEngine) {
|
|
return
|
|
}
|
|
this.persistAndApplySystemSettings({
|
|
compassTuningProfile: 'responsive',
|
|
})
|
|
},
|
|
|
|
handleSetNorthReferenceMagnetic() {
|
|
if (this.data.lockNorthReference || !mapEngine) {
|
|
return
|
|
}
|
|
this.persistAndApplySystemSettings({
|
|
northReferenceMode: 'magnetic',
|
|
})
|
|
},
|
|
|
|
handleSetNorthReferenceTrue() {
|
|
if (this.data.lockNorthReference || !mapEngine) {
|
|
return
|
|
}
|
|
this.persistAndApplySystemSettings({
|
|
northReferenceMode: 'true',
|
|
})
|
|
},
|
|
|
|
handleOverlayTouch() {},
|
|
|
|
handlePunchAction() {
|
|
if (!this.data.punchButtonEnabled) {
|
|
return
|
|
}
|
|
|
|
if (mapEngine) {
|
|
mapEngine.handlePunchAction()
|
|
}
|
|
},
|
|
|
|
handleOpenPendingContentCard() {
|
|
if (mapEngine) {
|
|
mapEngine.openPendingContentCard()
|
|
}
|
|
},
|
|
|
|
handleOpenContentCardAction(event: WechatMiniprogram.BaseEvent) {
|
|
if (!mapEngine) {
|
|
return
|
|
}
|
|
wx.showToast({
|
|
title: '点击CTA',
|
|
icon: 'none',
|
|
duration: 900,
|
|
})
|
|
const actionType = event.currentTarget.dataset.type
|
|
const action = typeof actionType === 'string' ? mapEngine.openCurrentContentCardAction(actionType) : null
|
|
if (action === 'detail') {
|
|
wx.showToast({
|
|
title: '打开详情',
|
|
icon: 'none',
|
|
duration: 900,
|
|
})
|
|
return
|
|
}
|
|
if (action === 'quiz') {
|
|
return
|
|
}
|
|
if (action === 'photo') {
|
|
wx.chooseMedia({
|
|
count: 1,
|
|
mediaType: ['image'],
|
|
sourceType: ['camera'],
|
|
success: () => {
|
|
if (mapEngine) {
|
|
mapEngine.handleContentCardPhotoCaptured()
|
|
}
|
|
},
|
|
})
|
|
return
|
|
}
|
|
if (action === 'audio') {
|
|
if (!contentAudioRecorder) {
|
|
contentAudioRecorder = wx.getRecorderManager()
|
|
contentAudioRecorder.onStop(() => {
|
|
contentAudioRecording = false
|
|
if (mapEngine) {
|
|
mapEngine.handleContentCardAudioRecorded()
|
|
}
|
|
})
|
|
}
|
|
const recorder = contentAudioRecorder
|
|
if (!contentAudioRecording) {
|
|
contentAudioRecording = true
|
|
recorder.start({
|
|
duration: 8000,
|
|
format: 'mp3',
|
|
} as any)
|
|
wx.showToast({
|
|
title: '开始录音',
|
|
icon: 'none',
|
|
duration: 800,
|
|
})
|
|
} else {
|
|
recorder.stop()
|
|
}
|
|
}
|
|
},
|
|
|
|
handleContentQuizAnswer(event: WechatMiniprogram.BaseEvent) {
|
|
if (!mapEngine) {
|
|
return
|
|
}
|
|
const optionKey = event.currentTarget.dataset.key
|
|
if (typeof optionKey === 'string') {
|
|
mapEngine.handleContentCardQuizAnswer(optionKey)
|
|
}
|
|
},
|
|
|
|
handleDismissTransientContentCard() {
|
|
if (mapEngine) {
|
|
mapEngine.closeContentCard()
|
|
}
|
|
},
|
|
|
|
handleContentCardTap() {
|
|
if (!mapEngine) {
|
|
return
|
|
}
|
|
if (!this.data.contentCardActions.length) {
|
|
mapEngine.closeContentCard()
|
|
}
|
|
},
|
|
|
|
openH5Experience(request: H5ExperienceRequest) {
|
|
wx.navigateTo({
|
|
url: '/pages/experience-webview/experience-webview',
|
|
success: (result) => {
|
|
const eventChannel = result.eventChannel
|
|
eventChannel.on('fallback', (payload: H5ExperienceFallbackPayload) => {
|
|
if (mapEngine) {
|
|
mapEngine.handleH5ExperienceFallback(payload)
|
|
}
|
|
})
|
|
eventChannel.on('close', () => {
|
|
if (mapEngine) {
|
|
mapEngine.handleH5ExperienceClosed()
|
|
}
|
|
})
|
|
eventChannel.on('submitResult', () => {
|
|
if (mapEngine) {
|
|
mapEngine.handleH5ExperienceClosed()
|
|
}
|
|
})
|
|
eventChannel.emit('init', request)
|
|
},
|
|
fail: () => {
|
|
if (mapEngine) {
|
|
mapEngine.handleH5ExperienceFallback(request.fallback)
|
|
}
|
|
},
|
|
})
|
|
},
|
|
|
|
handleCloseContentCard() {
|
|
if (mapEngine) {
|
|
mapEngine.closeContentCard()
|
|
}
|
|
},
|
|
|
|
handleClosePunchHint() {
|
|
clearPunchHintDismissTimer()
|
|
this.setData({
|
|
showPunchHintBanner: false,
|
|
})
|
|
},
|
|
|
|
handlePunchHintTap() {},
|
|
|
|
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,
|
|
showSystemSettingsPanel: this.data.showSystemSettingsPanel,
|
|
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 || this.data.lockAutoRotate) {
|
|
return
|
|
}
|
|
|
|
if (this.data.orientationMode === 'heading-up') {
|
|
this.persistAndApplySystemSettings({
|
|
autoRotateEnabled: false,
|
|
})
|
|
return
|
|
}
|
|
|
|
this.persistAndApplySystemSettings({
|
|
autoRotateEnabled: true,
|
|
})
|
|
},
|
|
handleToggleDebugPanel() {
|
|
const nextShowDebugPanel = !this.data.showDebugPanel
|
|
if (!nextShowDebugPanel) {
|
|
clearGameInfoPanelSyncTimer()
|
|
}
|
|
if (mapEngine) {
|
|
mapEngine.setDiagnosticUiEnabled(nextShowDebugPanel)
|
|
}
|
|
this.setData({
|
|
showDebugPanel: nextShowDebugPanel,
|
|
showGameInfoPanel: false,
|
|
showSystemSettingsPanel: false,
|
|
...buildSideButtonState({
|
|
sideButtonMode: this.data.sideButtonMode,
|
|
showGameInfoPanel: false,
|
|
showSystemSettingsPanel: 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() {
|
|
if (mapEngine) {
|
|
mapEngine.setDiagnosticUiEnabled(false)
|
|
}
|
|
this.setData({
|
|
showDebugPanel: false,
|
|
...buildSideButtonState({
|
|
sideButtonMode: this.data.sideButtonMode,
|
|
showGameInfoPanel: this.data.showGameInfoPanel,
|
|
showSystemSettingsPanel: this.data.showSystemSettingsPanel,
|
|
showCenterScaleRuler: this.data.showCenterScaleRuler,
|
|
centerScaleRulerAnchorMode: this.data.centerScaleRulerAnchorMode,
|
|
skipButtonEnabled: this.data.skipButtonEnabled,
|
|
gameSessionStatus: this.data.gameSessionStatus,
|
|
gpsLockEnabled: this.data.gpsLockEnabled,
|
|
gpsLockAvailable: this.data.gpsLockAvailable,
|
|
}),
|
|
})
|
|
},
|
|
|
|
applyCenterScaleRulerSettings(nextEnabled: boolean, nextAnchorMode: CenterScaleRulerAnchorMode) {
|
|
this.data.showCenterScaleRuler = nextEnabled
|
|
this.data.centerScaleRulerAnchorMode = nextAnchorMode
|
|
clearCenterScaleRulerSyncTimer()
|
|
clearCenterScaleRulerUpdateTimer()
|
|
|
|
const syncRulerFromEngine = () => {
|
|
if (!mapEngine) {
|
|
return
|
|
}
|
|
const engineSnapshot = mapEngine.getInitialData() as Partial<MapPageData>
|
|
updateCenterScaleRulerInputCache(engineSnapshot)
|
|
const mergedData = {
|
|
...centerScaleRulerInputCache,
|
|
...this.data,
|
|
showCenterScaleRuler: nextEnabled,
|
|
centerScaleRulerAnchorMode: nextAnchorMode,
|
|
} as MapPageData
|
|
|
|
this.setData({
|
|
...filterDebugOnlyPatch(engineSnapshot, this.data.showDebugPanel, nextEnabled),
|
|
showCenterScaleRuler: nextEnabled,
|
|
centerScaleRulerAnchorMode: nextAnchorMode,
|
|
...buildCenterScaleRulerPatch(mergedData),
|
|
...buildSideButtonState(mergedData),
|
|
})
|
|
}
|
|
|
|
if (!nextEnabled) {
|
|
syncRulerFromEngine()
|
|
return
|
|
}
|
|
|
|
this.setData({
|
|
showCenterScaleRuler: true,
|
|
centerScaleRulerAnchorMode: nextAnchorMode,
|
|
...buildSideButtonState({
|
|
...this.data,
|
|
showCenterScaleRuler: true,
|
|
centerScaleRulerAnchorMode: nextAnchorMode,
|
|
} as MapPageData),
|
|
})
|
|
|
|
this.measureStageAndCanvas(() => {
|
|
syncRulerFromEngine()
|
|
})
|
|
|
|
centerScaleRulerSyncTimer = setTimeout(() => {
|
|
centerScaleRulerSyncTimer = 0
|
|
if (!this.data.showCenterScaleRuler) {
|
|
return
|
|
}
|
|
syncRulerFromEngine()
|
|
}, 96) as unknown as number
|
|
},
|
|
|
|
handleSetCenterScaleRulerVisibleOn() {
|
|
if (this.data.lockScaleRulerVisible) {
|
|
return
|
|
}
|
|
this.persistAndApplySystemSettings({
|
|
showCenterScaleRuler: true,
|
|
centerScaleRulerAnchorMode: this.data.centerScaleRulerAnchorMode,
|
|
}, {
|
|
applyCenterScaleRuler: true,
|
|
})
|
|
},
|
|
|
|
handleSetCenterScaleRulerVisibleOff() {
|
|
if (this.data.lockScaleRulerVisible) {
|
|
return
|
|
}
|
|
this.persistAndApplySystemSettings({
|
|
showCenterScaleRuler: false,
|
|
centerScaleRulerAnchorMode: this.data.centerScaleRulerAnchorMode,
|
|
}, {
|
|
applyCenterScaleRuler: true,
|
|
})
|
|
},
|
|
|
|
handleSetCenterScaleRulerAnchorScreenCenter() {
|
|
if (this.data.lockScaleRulerAnchor) {
|
|
return
|
|
}
|
|
this.persistAndApplySystemSettings({
|
|
showCenterScaleRuler: this.data.showCenterScaleRuler,
|
|
centerScaleRulerAnchorMode: 'screen-center',
|
|
}, {
|
|
applyCenterScaleRuler: true,
|
|
})
|
|
},
|
|
|
|
handleSetCenterScaleRulerAnchorCompassCenter() {
|
|
if (this.data.lockScaleRulerAnchor) {
|
|
return
|
|
}
|
|
this.persistAndApplySystemSettings({
|
|
showCenterScaleRuler: this.data.showCenterScaleRuler,
|
|
centerScaleRulerAnchorMode: 'compass-center',
|
|
}, {
|
|
applyCenterScaleRuler: true,
|
|
})
|
|
},
|
|
|
|
handleToggleCenterScaleRulerAnchor() {
|
|
if (!this.data.showCenterScaleRuler || this.data.lockScaleRulerAnchor) {
|
|
return
|
|
}
|
|
|
|
const nextAnchorMode: CenterScaleRulerAnchorMode = this.data.centerScaleRulerAnchorMode === 'screen-center'
|
|
? 'compass-center'
|
|
: 'screen-center'
|
|
this.persistAndApplySystemSettings({
|
|
centerScaleRulerAnchorMode: nextAnchorMode,
|
|
showCenterScaleRuler: this.data.showCenterScaleRuler,
|
|
}, {
|
|
applyCenterScaleRuler: true,
|
|
})
|
|
},
|
|
|
|
handleDebugPanelTap() {},
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|