1
This commit is contained in:
commit
6fdf336b9e
128 files changed
+36789
No files matched your search
@@ -0,0 +1,158 @@
|
||||
import { DEFAULT_PROJECT_ID, QUICK_PROJECT_ID, normalizeProjectId } from '@/lib/workspace'
|
||||
import { i18n } from '@/i18n'
|
||||
|
||||
const PROJECT_REGISTRY_KEY = 'jgjs-project-registry-v1'
|
||||
|
||||
export interface ProjectMeta {
|
||||
id: string
|
||||
name: string
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
lastOpenedAt: string
|
||||
}
|
||||
|
||||
type ProjectRegistryPayload = {
|
||||
projects: ProjectMeta[]
|
||||
}
|
||||
|
||||
const nowIso = () => new Date().toISOString()
|
||||
const lastEditedTouchAt = new Map<string, number>()
|
||||
|
||||
const defaultProjects = (): ProjectMeta[] => {
|
||||
return []
|
||||
}
|
||||
|
||||
const sanitizeProject = (item: Partial<ProjectMeta> | null | undefined): ProjectMeta | null => {
|
||||
if (!item || typeof item !== 'object') return null
|
||||
const id = normalizeProjectId(item.id)
|
||||
const name = String(item.name || '').trim() || (
|
||||
id === QUICK_PROJECT_ID
|
||||
? i18n.global.t('quickCalc.projectName')
|
||||
: i18n.global.t('tab.messages.projectNamePrefix', { id })
|
||||
)
|
||||
const createdAt = typeof item.createdAt === 'string' && item.createdAt ? item.createdAt : nowIso()
|
||||
const updatedAt = typeof item.updatedAt === 'string' && item.updatedAt ? item.updatedAt : createdAt
|
||||
const lastOpenedAt =
|
||||
typeof item.lastOpenedAt === 'string' && item.lastOpenedAt ? item.lastOpenedAt : updatedAt
|
||||
return { id, name, createdAt, updatedAt, lastOpenedAt }
|
||||
}
|
||||
|
||||
const readPayload = (): ProjectRegistryPayload => {
|
||||
try {
|
||||
const raw = localStorage.getItem(PROJECT_REGISTRY_KEY)
|
||||
if (!raw) return { projects: defaultProjects() }
|
||||
const parsed = JSON.parse(raw) as Partial<ProjectRegistryPayload>
|
||||
const projects = Array.isArray(parsed?.projects)
|
||||
? parsed.projects.map(item => sanitizeProject(item)).filter((item): item is ProjectMeta => Boolean(item))
|
||||
: []
|
||||
if (projects.length === 0) return { projects: defaultProjects() }
|
||||
return { projects }
|
||||
} catch {
|
||||
return { projects: defaultProjects() }
|
||||
}
|
||||
}
|
||||
|
||||
const writePayload = (payload: ProjectRegistryPayload) => {
|
||||
localStorage.setItem(PROJECT_REGISTRY_KEY, JSON.stringify(payload))
|
||||
}
|
||||
|
||||
export const listProjects = () => {
|
||||
const payload = readPayload()
|
||||
return payload.projects
|
||||
.filter(item => item.id !== QUICK_PROJECT_ID && item.id !== DEFAULT_PROJECT_ID)
|
||||
.slice()
|
||||
.sort((a, b) => new Date(b.lastOpenedAt).getTime() - new Date(a.lastOpenedAt).getTime())
|
||||
}
|
||||
|
||||
export const upsertProject = (
|
||||
projectIdRaw: string,
|
||||
nameRaw?: string,
|
||||
options?: {
|
||||
touchUpdatedAt?: boolean
|
||||
touchLastOpenedAt?: boolean
|
||||
}
|
||||
) => {
|
||||
const id = normalizeProjectId(projectIdRaw)
|
||||
if (id === QUICK_PROJECT_ID || id === DEFAULT_PROJECT_ID) return
|
||||
const name = String(nameRaw || '').trim()
|
||||
const payload = readPayload()
|
||||
const now = nowIso()
|
||||
const touchUpdatedAt = options?.touchUpdatedAt === true
|
||||
const touchLastOpenedAt = options?.touchLastOpenedAt !== false
|
||||
const index = payload.projects.findIndex(item => item.id === id)
|
||||
if (index < 0) {
|
||||
payload.projects.push({
|
||||
id,
|
||||
name: name || (
|
||||
id === QUICK_PROJECT_ID
|
||||
? i18n.global.t('quickCalc.projectName')
|
||||
: i18n.global.t('tab.messages.projectNamePrefix', { id })
|
||||
),
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
lastOpenedAt: now
|
||||
})
|
||||
} else {
|
||||
const current = payload.projects[index]
|
||||
payload.projects[index] = {
|
||||
...current,
|
||||
name: name || current.name,
|
||||
updatedAt: touchUpdatedAt ? now : current.updatedAt,
|
||||
lastOpenedAt: touchLastOpenedAt ? now : current.lastOpenedAt
|
||||
}
|
||||
}
|
||||
writePayload(payload)
|
||||
}
|
||||
|
||||
export const touchProjectEdited = (
|
||||
projectIdRaw: string,
|
||||
options?: {
|
||||
throttleMs?: number
|
||||
}
|
||||
) => {
|
||||
const id = normalizeProjectId(projectIdRaw)
|
||||
if (id === QUICK_PROJECT_ID || id === DEFAULT_PROJECT_ID) return false
|
||||
const throttleMs = Math.max(0, Number(options?.throttleMs ?? 5000))
|
||||
const nowMs = Date.now()
|
||||
const lastTouch = lastEditedTouchAt.get(id) ?? 0
|
||||
if (throttleMs > 0 && nowMs - lastTouch < throttleMs) return false
|
||||
|
||||
const payload = readPayload()
|
||||
const index = payload.projects.findIndex(item => item.id === id)
|
||||
if (index < 0) return false
|
||||
const now = nowIso()
|
||||
const current = payload.projects[index]
|
||||
payload.projects[index] = {
|
||||
...current,
|
||||
updatedAt: now,
|
||||
lastOpenedAt: now
|
||||
}
|
||||
writePayload(payload)
|
||||
lastEditedTouchAt.set(id, nowMs)
|
||||
return true
|
||||
}
|
||||
|
||||
export const createProject = (nameRaw?: string) => {
|
||||
const payload = readPayload()
|
||||
const now = nowIso()
|
||||
let id = ''
|
||||
do {
|
||||
id = normalizeProjectId(`p-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`)
|
||||
} while (payload.projects.some(item => item.id === id))
|
||||
const name = String(nameRaw || '').trim() || i18n.global.t('tab.messages.projectNamePrefix', { id: payload.projects.length + 1 })
|
||||
const project: ProjectMeta = { id, name, createdAt: now, updatedAt: now, lastOpenedAt: now }
|
||||
payload.projects.push(project)
|
||||
writePayload(payload)
|
||||
return project
|
||||
}
|
||||
|
||||
export const deleteProject = (projectIdRaw: string) => {
|
||||
const id = normalizeProjectId(projectIdRaw)
|
||||
if (id === QUICK_PROJECT_ID || id === DEFAULT_PROJECT_ID) return false
|
||||
const payload = readPayload()
|
||||
const nextProjects = payload.projects.filter(item => item.id !== id)
|
||||
if (nextProjects.length === payload.projects.length) return false
|
||||
payload.projects = nextProjects
|
||||
writePayload(payload)
|
||||
return true
|
||||
}
|
||||
Reference in new issue
Block a user