feat: 收敛玩法运行时配置并加入故障恢复

This commit is contained in:
2026-04-01 13:04:26 +08:00
parent 1635a11780
commit 3ef841ecc7
73 changed files with 8820 additions and 2122 deletions

View File

@@ -0,0 +1,292 @@
import { type AnimationLevel } from '../../utils/animationLevel'
import { type TrackColorPreset, type TrackDisplayMode, type TrackStyleProfile, type TrackTailLengthPreset } from '../presentation/trackStyleConfig'
import { type GpsMarkerColorPreset, type GpsMarkerSizePreset, type GpsMarkerStyleId } from '../presentation/gpsMarkerStyleConfig'
export type SideButtonPlacement = 'left' | 'right'
export type CenterScaleRulerAnchorMode = 'screen-center' | 'compass-center'
export type UserNorthReferenceMode = 'magnetic' | 'true'
export type CompassTuningProfile = 'smooth' | 'balanced' | 'responsive'
export type SettingLockKey =
| 'lockAnimationLevel'
| 'lockTrackMode'
| 'lockTrackTailLength'
| 'lockTrackColor'
| 'lockTrackStyle'
| 'lockGpsMarkerVisible'
| 'lockGpsMarkerStyle'
| 'lockGpsMarkerSize'
| 'lockGpsMarkerColor'
| 'lockSideButtonPlacement'
| 'lockAutoRotate'
| 'lockCompassTuning'
| 'lockScaleRulerVisible'
| 'lockScaleRulerAnchor'
| 'lockNorthReference'
| 'lockHeartRateDevice'
export type StoredUserSettings = {
animationLevel?: AnimationLevel
trackDisplayMode?: TrackDisplayMode
trackTailLength?: TrackTailLengthPreset
trackColorPreset?: TrackColorPreset
trackStyleProfile?: TrackStyleProfile
gpsMarkerVisible?: boolean
gpsMarkerStyle?: GpsMarkerStyleId
gpsMarkerSize?: GpsMarkerSizePreset
gpsMarkerColorPreset?: GpsMarkerColorPreset
autoRotateEnabled?: boolean
compassTuningProfile?: CompassTuningProfile
northReferenceMode?: UserNorthReferenceMode
sideButtonPlacement?: SideButtonPlacement
showCenterScaleRuler?: boolean
centerScaleRulerAnchorMode?: CenterScaleRulerAnchorMode
}
export interface SystemSettingsConfig {
values: Partial<StoredUserSettings>
locks: Partial<Record<SettingLockKey, boolean>>
}
export type ResolvedSystemSettingsState = {
values: Required<StoredUserSettings>
locks: Record<SettingLockKey, boolean>
}
export const USER_SETTINGS_STORAGE_KEY = 'cmr_user_settings_v1'
export const DEFAULT_STORED_USER_SETTINGS: Required<StoredUserSettings> = {
animationLevel: 'standard',
trackDisplayMode: 'full',
trackTailLength: 'medium',
trackColorPreset: 'mint',
trackStyleProfile: 'neon',
gpsMarkerVisible: true,
gpsMarkerStyle: 'beacon',
gpsMarkerSize: 'medium',
gpsMarkerColorPreset: 'cyan',
autoRotateEnabled: true,
compassTuningProfile: 'balanced',
northReferenceMode: 'magnetic',
sideButtonPlacement: 'left',
showCenterScaleRuler: false,
centerScaleRulerAnchorMode: 'screen-center',
}
export const DEFAULT_SETTING_LOCKS: Record<SettingLockKey, boolean> = {
lockAnimationLevel: false,
lockTrackMode: false,
lockTrackTailLength: false,
lockTrackColor: false,
lockTrackStyle: false,
lockGpsMarkerVisible: false,
lockGpsMarkerStyle: false,
lockGpsMarkerSize: false,
lockGpsMarkerColor: false,
lockSideButtonPlacement: false,
lockAutoRotate: false,
lockCompassTuning: false,
lockScaleRulerVisible: false,
lockScaleRulerAnchor: false,
lockNorthReference: false,
lockHeartRateDevice: false,
}
export const SETTING_LOCK_VALUE_MAP: Record<SettingLockKey, keyof StoredUserSettings | null> = {
lockAnimationLevel: 'animationLevel',
lockTrackMode: 'trackDisplayMode',
lockTrackTailLength: 'trackTailLength',
lockTrackColor: 'trackColorPreset',
lockTrackStyle: 'trackStyleProfile',
lockGpsMarkerVisible: 'gpsMarkerVisible',
lockGpsMarkerStyle: 'gpsMarkerStyle',
lockGpsMarkerSize: 'gpsMarkerSize',
lockGpsMarkerColor: 'gpsMarkerColorPreset',
lockSideButtonPlacement: 'sideButtonPlacement',
lockAutoRotate: 'autoRotateEnabled',
lockCompassTuning: 'compassTuningProfile',
lockScaleRulerVisible: 'showCenterScaleRuler',
lockScaleRulerAnchor: 'centerScaleRulerAnchorMode',
lockNorthReference: 'northReferenceMode',
lockHeartRateDevice: null,
}
function normalizeStoredUserSettings(raw: unknown): StoredUserSettings {
if (!raw || typeof raw !== 'object') {
return {}
}
const normalized = raw as Record<string, unknown>
const settings: StoredUserSettings = {}
if (normalized.animationLevel === 'standard' || normalized.animationLevel === 'lite') {
settings.animationLevel = normalized.animationLevel
}
if (normalized.trackDisplayMode === 'none' || normalized.trackDisplayMode === 'full' || normalized.trackDisplayMode === 'tail') {
settings.trackDisplayMode = normalized.trackDisplayMode
}
if (normalized.trackTailLength === 'short' || normalized.trackTailLength === 'medium' || normalized.trackTailLength === 'long') {
settings.trackTailLength = normalized.trackTailLength
}
if (normalized.trackStyleProfile === 'classic' || normalized.trackStyleProfile === 'neon') {
settings.trackStyleProfile = normalized.trackStyleProfile
}
if (typeof normalized.gpsMarkerVisible === 'boolean') {
settings.gpsMarkerVisible = normalized.gpsMarkerVisible
}
if (
normalized.gpsMarkerStyle === 'dot'
|| normalized.gpsMarkerStyle === 'beacon'
|| normalized.gpsMarkerStyle === 'disc'
|| normalized.gpsMarkerStyle === 'badge'
) {
settings.gpsMarkerStyle = normalized.gpsMarkerStyle
}
if (normalized.gpsMarkerSize === 'small' || normalized.gpsMarkerSize === 'medium' || normalized.gpsMarkerSize === 'large') {
settings.gpsMarkerSize = normalized.gpsMarkerSize
}
if (
normalized.gpsMarkerColorPreset === 'mint'
|| normalized.gpsMarkerColorPreset === 'cyan'
|| normalized.gpsMarkerColorPreset === 'sky'
|| normalized.gpsMarkerColorPreset === 'blue'
|| normalized.gpsMarkerColorPreset === 'violet'
|| normalized.gpsMarkerColorPreset === 'pink'
|| normalized.gpsMarkerColorPreset === 'orange'
|| normalized.gpsMarkerColorPreset === 'yellow'
) {
settings.gpsMarkerColorPreset = normalized.gpsMarkerColorPreset
}
if (
normalized.trackColorPreset === 'mint'
|| normalized.trackColorPreset === 'cyan'
|| normalized.trackColorPreset === 'sky'
|| normalized.trackColorPreset === 'blue'
|| normalized.trackColorPreset === 'violet'
|| normalized.trackColorPreset === 'pink'
|| normalized.trackColorPreset === 'orange'
|| normalized.trackColorPreset === 'yellow'
) {
settings.trackColorPreset = normalized.trackColorPreset
}
if (normalized.northReferenceMode === 'magnetic' || normalized.northReferenceMode === 'true') {
settings.northReferenceMode = normalized.northReferenceMode
}
if (typeof normalized.autoRotateEnabled === 'boolean') {
settings.autoRotateEnabled = normalized.autoRotateEnabled
}
if (normalized.compassTuningProfile === 'smooth' || normalized.compassTuningProfile === 'balanced' || normalized.compassTuningProfile === 'responsive') {
settings.compassTuningProfile = normalized.compassTuningProfile
}
if (normalized.sideButtonPlacement === 'left' || normalized.sideButtonPlacement === 'right') {
settings.sideButtonPlacement = normalized.sideButtonPlacement
}
if (typeof normalized.showCenterScaleRuler === 'boolean') {
settings.showCenterScaleRuler = normalized.showCenterScaleRuler
}
if (normalized.centerScaleRulerAnchorMode === 'screen-center' || normalized.centerScaleRulerAnchorMode === 'compass-center') {
settings.centerScaleRulerAnchorMode = normalized.centerScaleRulerAnchorMode
}
return settings
}
export function loadStoredUserSettings(storageKey = USER_SETTINGS_STORAGE_KEY): StoredUserSettings {
try {
return normalizeStoredUserSettings(wx.getStorageSync(storageKey))
} catch {
return {}
}
}
export function persistStoredUserSettings(
settings: StoredUserSettings,
storageKey = USER_SETTINGS_STORAGE_KEY,
): void {
try {
wx.setStorageSync(storageKey, settings)
} catch {}
}
export function mergeStoredUserSettings(
current: StoredUserSettings,
patch: Partial<StoredUserSettings>,
): StoredUserSettings {
return {
...current,
...patch,
}
}
export function buildInitialSystemSettingsState(
stored: StoredUserSettings,
config?: Partial<SystemSettingsConfig>,
): ResolvedSystemSettingsState {
const values = {
...DEFAULT_STORED_USER_SETTINGS,
...(config && config.values ? config.values : {}),
}
const locks = {
...DEFAULT_SETTING_LOCKS,
...(config && config.locks ? config.locks : {}),
}
const resolvedValues: Required<StoredUserSettings> = {
...values,
}
for (const [lockKey, isLocked] of Object.entries(locks) as Array<[SettingLockKey, boolean]>) {
const valueKey = SETTING_LOCK_VALUE_MAP[lockKey]
if (!valueKey) {
continue
}
if (!isLocked && stored[valueKey] !== undefined) {
;(resolvedValues as Record<string, unknown>)[valueKey] = stored[valueKey]
}
}
for (const [key, value] of Object.entries(stored) as Array<[keyof StoredUserSettings, StoredUserSettings[keyof StoredUserSettings]]>) {
const matchingLockKey = (Object.keys(SETTING_LOCK_VALUE_MAP) as SettingLockKey[])
.find((lockKey) => SETTING_LOCK_VALUE_MAP[lockKey] === key)
if (matchingLockKey && locks[matchingLockKey]) {
continue
}
if (value !== undefined) {
;(resolvedValues as Record<string, unknown>)[key] = value
}
}
return {
values: resolvedValues,
locks,
}
}
export function buildRuntimeSettingLocks(
locks: Partial<Record<SettingLockKey, boolean>> | undefined,
runtimeActive: boolean,
): Partial<Record<SettingLockKey, boolean>> {
const sourceLocks = locks || {}
if (runtimeActive) {
return { ...sourceLocks }
}
const unlocked: Partial<Record<SettingLockKey, boolean>> = {}
for (const key of Object.keys(sourceLocks) as SettingLockKey[]) {
unlocked[key] = false
}
return unlocked
}
export function resolveSystemSettingsState(
config?: Partial<SystemSettingsConfig>,
storageKey = USER_SETTINGS_STORAGE_KEY,
runtimeActive = false,
): ResolvedSystemSettingsState {
return buildInitialSystemSettingsState(
loadStoredUserSettings(storageKey),
{
values: config && config.values ? config.values : {},
locks: buildRuntimeSettingLocks(config && config.locks ? config.locks : {}, runtimeActive),
},
)
}