106 lines
2.6 KiB
TypeScript
106 lines
2.6 KiB
TypeScript
|
|
import { defineStore } from 'pinia'
|
||
|
|
import { ref, computed } from 'vue'
|
||
|
|
import type { GoalProgress, GoalStatus } from '@/types/goals'
|
||
|
|
import { GOALS } from '@/data/goals'
|
||
|
|
import { useAppStore } from './app'
|
||
|
|
|
||
|
|
const STORAGE_KEY = 'archipelago-goal-progress'
|
||
|
|
|
||
|
|
export const useGoalStore = defineStore('goals', () => {
|
||
|
|
const progress = ref<Record<string, GoalProgress>>({})
|
||
|
|
|
||
|
|
function load() {
|
||
|
|
try {
|
||
|
|
const raw = localStorage.getItem(STORAGE_KEY)
|
||
|
|
if (raw) progress.value = JSON.parse(raw)
|
||
|
|
} catch {
|
||
|
|
/* ignore corrupt data */
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function save() {
|
||
|
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(progress.value))
|
||
|
|
}
|
||
|
|
|
||
|
|
function getGoalStatus(goalId: string): GoalStatus {
|
||
|
|
const goal = GOALS.find((g) => g.id === goalId)
|
||
|
|
if (!goal) return 'not-started'
|
||
|
|
|
||
|
|
// Goals with no required apps use manual progress tracking
|
||
|
|
if (goal.requiredApps.length === 0) {
|
||
|
|
return progress.value[goalId]?.status || 'not-started'
|
||
|
|
}
|
||
|
|
|
||
|
|
const appStore = useAppStore()
|
||
|
|
const packages = appStore.packages
|
||
|
|
|
||
|
|
const allRunning = goal.requiredApps.every((appId) =>
|
||
|
|
Object.entries(packages).some(
|
||
|
|
([pkgId, pkg]) => pkgId === appId && pkg.state === 'running',
|
||
|
|
),
|
||
|
|
)
|
||
|
|
if (allRunning) return 'completed'
|
||
|
|
|
||
|
|
const anyInstalled = goal.requiredApps.some((appId) =>
|
||
|
|
Object.keys(packages).some((pkgId) => pkgId === appId),
|
||
|
|
)
|
||
|
|
if (anyInstalled || progress.value[goalId]) return 'in-progress'
|
||
|
|
|
||
|
|
return 'not-started'
|
||
|
|
}
|
||
|
|
|
||
|
|
const goalStatuses = computed(() => {
|
||
|
|
const statuses: Record<string, GoalStatus> = {}
|
||
|
|
for (const goal of GOALS) {
|
||
|
|
statuses[goal.id] = getGoalStatus(goal.id)
|
||
|
|
}
|
||
|
|
return statuses
|
||
|
|
})
|
||
|
|
|
||
|
|
function startGoal(goalId: string) {
|
||
|
|
progress.value[goalId] = {
|
||
|
|
goalId,
|
||
|
|
status: 'in-progress',
|
||
|
|
currentStepIndex: 0,
|
||
|
|
completedSteps: [],
|
||
|
|
startedAt: Date.now(),
|
||
|
|
}
|
||
|
|
save()
|
||
|
|
}
|
||
|
|
|
||
|
|
function completeStep(goalId: string, stepId: string) {
|
||
|
|
const p = progress.value[goalId]
|
||
|
|
if (!p) return
|
||
|
|
|
||
|
|
if (!p.completedSteps.includes(stepId)) {
|
||
|
|
p.completedSteps.push(stepId)
|
||
|
|
}
|
||
|
|
|
||
|
|
const goal = GOALS.find((g) => g.id === goalId)
|
||
|
|
if (goal && p.completedSteps.length >= goal.steps.length) {
|
||
|
|
p.status = 'completed'
|
||
|
|
} else {
|
||
|
|
p.currentStepIndex = Math.min(p.currentStepIndex + 1, (goal?.steps.length ?? 1) - 1)
|
||
|
|
}
|
||
|
|
|
||
|
|
save()
|
||
|
|
}
|
||
|
|
|
||
|
|
function resetGoal(goalId: string) {
|
||
|
|
delete progress.value[goalId]
|
||
|
|
save()
|
||
|
|
}
|
||
|
|
|
||
|
|
// Load on store creation
|
||
|
|
load()
|
||
|
|
|
||
|
|
return {
|
||
|
|
progress,
|
||
|
|
goalStatuses,
|
||
|
|
getGoalStatus,
|
||
|
|
startGoal,
|
||
|
|
completeStep,
|
||
|
|
resetGoal,
|
||
|
|
}
|
||
|
|
})
|