freat: renaming

This commit is contained in:
2026-03-15 14:11:11 +01:00
parent 2326bbe035
commit 04c039bdde
18 changed files with 455 additions and 462 deletions

View File

@@ -1,16 +1,19 @@
/**
* Platform context: projects list and handlers for create/delete/rename/updateLastEdited/reorder/restore.
* Used by AppSidebar, ProjectsTablePage, and canvas route.
* Platform context: recollections list and handlers for create/delete/rename/updateLastEdited/reorder/restore.
* Used by AppSidebar, RecollectionsPage, and canvas route.
*/
import React, { createContext, useCallback, useContext, useMemo, useState } from 'react'
import type { Project } from './types'
import { saveGraphToStorage } from '@/app/pleroma/projectGraphStorage'
import { PROJECT_VERSION } from '@/app/pleroma/projectGraphStorage'
import type { Recollection } from './types'
import { saveGraphToStorage, RECOLLECTION_VERSION } from '@/app/recollections/recollectionGraphStorage'
const STORAGE_KEY = 'zui_platform_projects'
const ORDER_STORAGE_KEY = 'zui_platform_project_order'
const RECENT_STORAGE_KEY = 'zui_platform_recent_project_ids'
const STORAGE_KEY = 'zui_platform_recollections'
const ORDER_STORAGE_KEY = 'zui_platform_recollection_order'
const RECENT_STORAGE_KEY = 'zui_platform_recent_recollection_ids'
const LEGACY_STORAGE_KEY = 'zui_platform_emanations'
const LEGACY_ORDER_STORAGE_KEY = 'zui_platform_emanation_order'
const LEGACY_RECENT_STORAGE_KEY = 'zui_platform_recent_emanation_ids'
const CANVAS_MINIMAP_KEY = 'zui_canvas_show_minimap'
const AI_CONNECTION_KEY = 'zui_ai_connection'
const RECENT_MAX = 5
@@ -72,8 +75,15 @@ function saveShowMinimap(value: boolean) {
function loadOrder(): string[] {
try {
const raw = localStorage.getItem(ORDER_STORAGE_KEY)
if (!raw) return []
let raw = localStorage.getItem(ORDER_STORAGE_KEY)
if (!raw) {
const legacy = localStorage.getItem(LEGACY_ORDER_STORAGE_KEY)
if (legacy) {
localStorage.setItem(ORDER_STORAGE_KEY, legacy)
localStorage.removeItem(LEGACY_ORDER_STORAGE_KEY)
raw = legacy
} else return []
}
const parsed = JSON.parse(raw) as unknown
return Array.isArray(parsed) ? parsed.filter((id): id is string => typeof id === 'string') : []
} catch {
@@ -87,8 +97,15 @@ function saveOrder(ids: string[]) {
function loadRecentIds(): string[] {
try {
const raw = localStorage.getItem(RECENT_STORAGE_KEY)
if (!raw) return []
let raw = localStorage.getItem(RECENT_STORAGE_KEY)
if (!raw) {
const legacy = localStorage.getItem(LEGACY_RECENT_STORAGE_KEY)
if (legacy) {
localStorage.setItem(RECENT_STORAGE_KEY, legacy)
localStorage.removeItem(LEGACY_RECENT_STORAGE_KEY)
raw = legacy
} else return []
}
const parsed = JSON.parse(raw) as unknown
return Array.isArray(parsed) ? parsed.filter((id): id is string => typeof id === 'string').slice(0, RECENT_MAX) : []
} catch {
@@ -100,41 +117,48 @@ function saveRecentIds(ids: string[]) {
localStorage.setItem(RECENT_STORAGE_KEY, JSON.stringify(ids))
}
function loadProjects(): Project[] {
function loadRecollections(): Recollection[] {
try {
const raw = localStorage.getItem(STORAGE_KEY)
if (!raw) return []
let raw = localStorage.getItem(STORAGE_KEY)
if (!raw) {
const legacy = localStorage.getItem(LEGACY_STORAGE_KEY)
if (legacy) {
localStorage.setItem(STORAGE_KEY, legacy)
localStorage.removeItem(LEGACY_STORAGE_KEY)
raw = legacy
} else return []
}
const parsed = JSON.parse(raw) as unknown
if (!Array.isArray(parsed)) return []
return parsed
.filter(
(p): p is Project =>
(p): p is Recollection =>
p &&
typeof p === 'object' &&
typeof (p as Project).id === 'string' &&
typeof (p as Project).name === 'string' &&
typeof (p as Project).iconId === 'string' &&
typeof (p as Project).createdAt === 'number'
typeof (p as Recollection).id === 'string' &&
typeof (p as Recollection).name === 'string' &&
typeof (p as Recollection).iconId === 'string' &&
typeof (p as Recollection).createdAt === 'number'
)
.map((p) => ({
...p,
lastEditedAt: typeof (p as Project).lastEditedAt === 'number' ? (p as Project).lastEditedAt : p.createdAt,
lastEditedAt: typeof (p as Recollection).lastEditedAt === 'number' ? (p as Recollection).lastEditedAt : p.createdAt,
}))
} catch {
return []
}
}
function saveProjects(projects: Project[]) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(projects))
function saveRecollections(recollections: Recollection[]) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(recollections))
}
export type GraphSnapshot = { nodes: unknown[]; edges: unknown[] }
/** Projects sorted by projectOrder, then by lastEditedAt desc for any not in order */
export function sortProjectsByOrder(projects: Project[], order: string[]): Project[] {
const byId = new Map(projects.map((p) => [p.id, p]))
const ordered: Project[] = []
/** Recollections sorted by recollectionOrder, then by lastEditedAt desc for any not in order */
export function sortRecollectionsByOrder(recollections: Recollection[], order: string[]): Recollection[] {
const byId = new Map(recollections.map((p) => [p.id, p]))
const ordered: Recollection[] = []
const seen = new Set<string>()
for (const id of order) {
const p = byId.get(id)
@@ -143,27 +167,27 @@ export function sortProjectsByOrder(projects: Project[], order: string[]): Proje
seen.add(id)
}
}
const rest = projects
const rest = recollections
.filter((p) => !seen.has(p.id))
.sort((a, b) => (b.lastEditedAt ?? b.createdAt) - (a.lastEditedAt ?? a.createdAt))
return [...ordered, ...rest]
}
export type KosmosContextValue = {
projects: Project[]
projectOrder: string[]
/** Projects in display order (sidebar order, then lastEditedAt) */
orderedProjects: Project[]
/** Last RECENT_MAX accessed project IDs (most recent first) */
recentProjectIds: string[]
recordProjectAccess: (id: string) => void
persist: (next: Project[]) => void
createProject: (project: Project) => void
deleteProject: (id: string) => void
renameProject: (id: string, name: string) => void
recollections: Recollection[]
recollectionOrder: string[]
/** Recollections in display order (sidebar order, then lastEditedAt) */
orderedRecollections: Recollection[]
/** Last RECENT_MAX accessed recollection IDs (most recent first) */
recentRecollectionIds: string[]
recordRecollectionAccess: (id: string) => void
persist: (next: Recollection[]) => void
createRecollection: (recollection: Recollection) => void
deleteRecollection: (id: string) => void
renameRecollection: (id: string, name: string) => void
updateLastEdited: (id: string) => void
reorderProjects: (orderedIds: string[]) => void
restoreProject: (project: Project, graphSnapshot: GraphSnapshot | null) => void
reorderRecollections: (orderedIds: string[]) => void
restoreRecollection: (recollection: Recollection, graphSnapshot: GraphSnapshot | null) => void
/** Canvas: show React Flow minimap (persisted) */
showMinimap: boolean
setShowMinimap: (value: boolean) => void
@@ -175,9 +199,9 @@ export type KosmosContextValue = {
const KosmosContext = createContext<KosmosContextValue | null>(null)
export function KosmosProvider({ children }: { children: React.ReactNode }) {
const [projects, setProjects] = useState<Project[]>(loadProjects)
const [projectOrder, setProjectOrder] = useState<string[]>(loadOrder)
const [recentProjectIds, setRecentProjectIds] = useState<string[]>(loadRecentIds)
const [recollections, setRecollections] = useState<Recollection[]>(loadRecollections)
const [recollectionOrder, setRecollectionOrder] = useState<string[]>(loadOrder)
const [recentRecollectionIds, setRecentRecollectionIds] = useState<string[]>(loadRecentIds)
const [showMinimap, setShowMinimapState] = useState<boolean>(loadShowMinimap)
const [aiConnection, setAiConnectionState] = useState<AiConnection>(loadAiConnection)
@@ -191,128 +215,128 @@ export function KosmosProvider({ children }: { children: React.ReactNode }) {
saveAiConnection(value)
}, [])
const persist = useCallback((next: Project[]) => {
setProjects(next)
saveProjects(next)
const persist = useCallback((next: Recollection[]) => {
setRecollections(next)
saveRecollections(next)
}, [])
const createProject = useCallback(
(project: Project) => {
const withEdited = { ...project, lastEditedAt: project.createdAt }
persist([...projects, withEdited])
setProjectOrder((prev) => {
const next = prev.includes(project.id) ? prev : [project.id, ...prev]
const createRecollection = useCallback(
(recollection: Recollection) => {
const withEdited = { ...recollection, lastEditedAt: recollection.createdAt }
persist([...recollections, withEdited])
setRecollectionOrder((prev) => {
const next = prev.includes(recollection.id) ? prev : [recollection.id, ...prev]
saveOrder(next)
return next
})
},
[projects, persist]
[recollections, persist]
)
const deleteProject = useCallback(
const deleteRecollection = useCallback(
(id: string) => {
const next = projects.filter((p) => p.id !== id)
const next = recollections.filter((p) => p.id !== id)
persist(next)
setProjectOrder((prev) => {
setRecollectionOrder((prev) => {
const nextOrder = prev.filter((oid) => oid !== id)
saveOrder(nextOrder)
return nextOrder
})
setRecentProjectIds((prev) => {
setRecentRecollectionIds((prev) => {
const next = prev.filter((oid) => oid !== id)
saveRecentIds(next)
return next
})
},
[projects, persist]
[recollections, persist]
)
const renameProject = useCallback(
const renameRecollection = useCallback(
(id: string, name: string) => {
const next = projects.map((p) => (p.id === id ? { ...p, name } : p))
const next = recollections.map((p) => (p.id === id ? { ...p, name } : p))
persist(next)
},
[projects, persist]
[recollections, persist]
)
const updateLastEdited = useCallback(
(id: string) => {
const now = Date.now()
const next = projects.map((p) => (p.id === id ? { ...p, lastEditedAt: now } : p))
const next = recollections.map((p) => (p.id === id ? { ...p, lastEditedAt: now } : p))
persist(next)
},
[projects, persist]
[recollections, persist]
)
const reorderProjects = useCallback((orderedIds: string[]) => {
setProjectOrder(orderedIds)
const reorderRecollections = useCallback((orderedIds: string[]) => {
setRecollectionOrder(orderedIds)
saveOrder(orderedIds)
}, [])
const recordProjectAccess = useCallback((id: string) => {
setRecentProjectIds((prev) => {
const recordRecollectionAccess = useCallback((id: string) => {
setRecentRecollectionIds((prev) => {
const next = [id, ...prev.filter((x) => x !== id)].slice(0, RECENT_MAX)
saveRecentIds(next)
return next
})
}, [])
const restoreProject = useCallback(
(project: Project, graphSnapshot: GraphSnapshot | null) => {
persist([...projects, project])
setProjectOrder((prev) => {
const next = prev.includes(project.id) ? prev : [project.id, ...prev]
const restoreRecollection = useCallback(
(recollection: Recollection, graphSnapshot: GraphSnapshot | null) => {
persist([...recollections, recollection])
setRecollectionOrder((prev) => {
const next = prev.includes(recollection.id) ? prev : [recollection.id, ...prev]
saveOrder(next)
return next
})
if (graphSnapshot) {
saveGraphToStorage(project.id, {
version: PROJECT_VERSION,
saveGraphToStorage(recollection.id, {
version: RECOLLECTION_VERSION,
nodes: graphSnapshot.nodes,
edges: graphSnapshot.edges,
})
}
},
[projects, persist]
[recollections, persist]
)
const orderedProjects = useMemo(
() => sortProjectsByOrder(projects, projectOrder),
[projects, projectOrder]
const orderedRecollections = useMemo(
() => sortRecollectionsByOrder(recollections, recollectionOrder),
[recollections, recollectionOrder]
)
const value: KosmosContextValue = useMemo(
() => ({
projects,
projectOrder,
orderedProjects,
recentProjectIds,
recordProjectAccess,
recollections,
recollectionOrder,
orderedRecollections,
recentRecollectionIds,
recordRecollectionAccess,
persist,
createProject,
deleteProject,
renameProject,
createRecollection,
deleteRecollection,
renameRecollection,
updateLastEdited,
reorderProjects,
restoreProject,
reorderRecollections,
restoreRecollection,
showMinimap,
setShowMinimap,
aiConnection,
setAiConnection,
}),
[
projects,
projectOrder,
orderedProjects,
recentProjectIds,
recordProjectAccess,
recollections,
recollectionOrder,
orderedRecollections,
recentRecollectionIds,
recordRecollectionAccess,
persist,
createProject,
deleteProject,
renameProject,
createRecollection,
deleteRecollection,
renameRecollection,
updateLastEdited,
reorderProjects,
restoreProject,
reorderRecollections,
restoreRecollection,
showMinimap,
setShowMinimap,
aiConnection,