mirror of
https://gitea.toothfairyai.com/ToothFairyAI/tf_code.git
synced 2026-04-13 04:04:44 +00:00
chore(app): solidjs refactoring (#13399)
This commit is contained in:
@@ -11,7 +11,6 @@ import { showToast } from "@opencode-ai/ui/toast"
|
||||
import { getFilename } from "@opencode-ai/util/path"
|
||||
import {
|
||||
createContext,
|
||||
createEffect,
|
||||
getOwner,
|
||||
Match,
|
||||
onCleanup,
|
||||
@@ -35,7 +34,6 @@ import { trimSessions } from "./global-sync/session-trim"
|
||||
import type { ProjectMeta } from "./global-sync/types"
|
||||
import { SESSION_RECENT_LIMIT } from "./global-sync/types"
|
||||
import { sanitizeProject } from "./global-sync/utils"
|
||||
import { usePlatform } from "./platform"
|
||||
import { formatServerError } from "@/utils/server-errors"
|
||||
|
||||
type GlobalStore = {
|
||||
@@ -54,7 +52,6 @@ type GlobalStore = {
|
||||
|
||||
function createGlobalSync() {
|
||||
const globalSDK = useGlobalSDK()
|
||||
const platform = usePlatform()
|
||||
const language = useLanguage()
|
||||
const owner = getOwner()
|
||||
if (!owner) throw new Error("GlobalSync must be created within owner")
|
||||
@@ -64,7 +61,7 @@ function createGlobalSync() {
|
||||
const sessionLoads = new Map<string, Promise<void>>()
|
||||
const sessionMeta = new Map<string, { limit: number }>()
|
||||
|
||||
const [projectCache, setProjectCache, , projectCacheReady] = persisted(
|
||||
const [projectCache, setProjectCache, projectInit] = persisted(
|
||||
Persist.global("globalSync.project", ["globalSync.project.v1"]),
|
||||
createStore({ value: [] as Project[] }),
|
||||
)
|
||||
@@ -80,6 +77,57 @@ function createGlobalSync() {
|
||||
reload: undefined,
|
||||
})
|
||||
|
||||
let active = true
|
||||
let projectWritten = false
|
||||
|
||||
onCleanup(() => {
|
||||
active = false
|
||||
})
|
||||
|
||||
const cacheProjects = () => {
|
||||
setProjectCache(
|
||||
"value",
|
||||
untrack(() => globalStore.project.map(sanitizeProject)),
|
||||
)
|
||||
}
|
||||
|
||||
const setProjects = (next: Project[] | ((draft: Project[]) => void)) => {
|
||||
projectWritten = true
|
||||
if (typeof next === "function") {
|
||||
setGlobalStore("project", produce(next))
|
||||
cacheProjects()
|
||||
return
|
||||
}
|
||||
setGlobalStore("project", next)
|
||||
cacheProjects()
|
||||
}
|
||||
|
||||
const setBootStore = ((...input: unknown[]) => {
|
||||
if (input[0] === "project" && Array.isArray(input[1])) {
|
||||
setProjects(input[1] as Project[])
|
||||
return input[1]
|
||||
}
|
||||
return (setGlobalStore as (...args: unknown[]) => unknown)(...input)
|
||||
}) as typeof setGlobalStore
|
||||
|
||||
const set = ((...input: unknown[]) => {
|
||||
if (input[0] === "project" && (Array.isArray(input[1]) || typeof input[1] === "function")) {
|
||||
setProjects(input[1] as Project[] | ((draft: Project[]) => void))
|
||||
return input[1]
|
||||
}
|
||||
return (setGlobalStore as (...args: unknown[]) => unknown)(...input)
|
||||
}) as typeof setGlobalStore
|
||||
|
||||
if (projectInit instanceof Promise) {
|
||||
void projectInit.then(() => {
|
||||
if (!active) return
|
||||
if (projectWritten) return
|
||||
const cached = projectCache.value
|
||||
if (cached.length === 0) return
|
||||
setGlobalStore("project", cached)
|
||||
})
|
||||
}
|
||||
|
||||
const setSessionTodo = (sessionID: string, todos: Todo[] | undefined) => {
|
||||
if (!sessionID) return
|
||||
if (!todos) {
|
||||
@@ -127,30 +175,6 @@ function createGlobalSync() {
|
||||
return sdk
|
||||
}
|
||||
|
||||
createEffect(() => {
|
||||
if (!projectCacheReady()) return
|
||||
if (globalStore.project.length !== 0) return
|
||||
const cached = projectCache.value
|
||||
if (cached.length === 0) return
|
||||
setGlobalStore("project", cached)
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
if (!projectCacheReady()) return
|
||||
const projects = globalStore.project
|
||||
if (projects.length === 0) {
|
||||
const cachedLength = untrack(() => projectCache.value.length)
|
||||
if (cachedLength !== 0) return
|
||||
}
|
||||
setProjectCache("value", projects.map(sanitizeProject))
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
if (globalStore.reload !== "complete") return
|
||||
setGlobalStore("reload", undefined)
|
||||
queue.refresh()
|
||||
})
|
||||
|
||||
async function loadSessions(directory: string) {
|
||||
const pending = sessionLoads.get(directory)
|
||||
if (pending) return pending
|
||||
@@ -259,13 +283,7 @@ function createGlobalSync() {
|
||||
event,
|
||||
project: globalStore.project,
|
||||
refresh: queue.refresh,
|
||||
setGlobalProject(next) {
|
||||
if (typeof next === "function") {
|
||||
setGlobalStore("project", produce(next))
|
||||
return
|
||||
}
|
||||
setGlobalStore("project", next)
|
||||
},
|
||||
setGlobalProject: setProjects,
|
||||
})
|
||||
if (event.type === "server.connected" || event.type === "global.disposed") {
|
||||
for (const directory of Object.keys(children.children)) {
|
||||
@@ -316,7 +334,7 @@ function createGlobalSync() {
|
||||
unknownError: language.t("error.chain.unknown"),
|
||||
invalidConfigurationError: language.t("error.server.invalidConfiguration"),
|
||||
formatMoreCount: (count) => language.t("common.moreCountSuffix", { count }),
|
||||
setGlobalStore,
|
||||
setGlobalStore: setBootStore,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -340,7 +358,9 @@ function createGlobalSync() {
|
||||
.update({ config })
|
||||
.then(bootstrap)
|
||||
.then(() => {
|
||||
setGlobalStore("reload", "complete")
|
||||
queue.refresh()
|
||||
setGlobalStore("reload", undefined)
|
||||
queue.refresh()
|
||||
})
|
||||
.catch((error) => {
|
||||
setGlobalStore("reload", undefined)
|
||||
@@ -350,7 +370,7 @@ function createGlobalSync() {
|
||||
|
||||
return {
|
||||
data: globalStore,
|
||||
set: setGlobalStore,
|
||||
set,
|
||||
get ready() {
|
||||
return globalStore.ready
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createRoot, createEffect, getOwner, onCleanup, runWithOwner, type Accessor, type Owner } from "solid-js"
|
||||
import { createRoot, getOwner, onCleanup, runWithOwner, type Owner } from "solid-js"
|
||||
import { createStore, type SetStoreFunction, type Store } from "solid-js/store"
|
||||
import { Persist, persisted } from "@/utils/persist"
|
||||
import type { VcsInfo } from "@opencode-ai/sdk/v2/client"
|
||||
@@ -131,8 +131,7 @@ export function createChildStoreManager(input: {
|
||||
)
|
||||
if (!vcs) throw new Error("Failed to create persisted cache")
|
||||
const vcsStore = vcs[0]
|
||||
const vcsReady = vcs[3]
|
||||
vcsCache.set(directory, { store: vcsStore, setStore: vcs[1], ready: vcsReady })
|
||||
vcsCache.set(directory, { store: vcsStore, setStore: vcs[1], ready: vcs[3] })
|
||||
|
||||
const meta = runWithOwner(input.owner, () =>
|
||||
persisted(
|
||||
@@ -154,10 +153,12 @@ export function createChildStoreManager(input: {
|
||||
|
||||
const init = () =>
|
||||
createRoot((dispose) => {
|
||||
const initialMeta = meta[0].value
|
||||
const initialIcon = icon[0].value
|
||||
const child = createStore<State>({
|
||||
project: "",
|
||||
projectMeta: meta[0].value,
|
||||
icon: icon[0].value,
|
||||
projectMeta: initialMeta,
|
||||
icon: initialIcon,
|
||||
provider: { all: [], connected: [], default: {} },
|
||||
config: {},
|
||||
path: { state: "", config: "", worktree: "", directory: "", home: "" },
|
||||
@@ -181,16 +182,27 @@ export function createChildStoreManager(input: {
|
||||
children[directory] = child
|
||||
disposers.set(directory, dispose)
|
||||
|
||||
createEffect(() => {
|
||||
if (!vcsReady()) return
|
||||
const onPersistedInit = (init: Promise<string> | string | null, run: () => void) => {
|
||||
if (!(init instanceof Promise)) return
|
||||
void init.then(() => {
|
||||
if (children[directory] !== child) return
|
||||
run()
|
||||
})
|
||||
}
|
||||
|
||||
onPersistedInit(vcs[2], () => {
|
||||
const cached = vcsStore.value
|
||||
if (!cached?.branch) return
|
||||
child[1]("vcs", (value) => value ?? cached)
|
||||
})
|
||||
createEffect(() => {
|
||||
|
||||
onPersistedInit(meta[2], () => {
|
||||
if (child[0].projectMeta !== initialMeta) return
|
||||
child[1]("projectMeta", meta[0].value)
|
||||
})
|
||||
createEffect(() => {
|
||||
|
||||
onPersistedInit(icon[2], () => {
|
||||
if (child[0].icon !== initialIcon) return
|
||||
child[1]("icon", icon[0].value)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -7,8 +7,10 @@ import { useServer } from "./server"
|
||||
import { usePlatform } from "./platform"
|
||||
import { Project } from "@opencode-ai/sdk/v2"
|
||||
import { Persist, persisted, removePersisted } from "@/utils/persist"
|
||||
import { decode64 } from "@/utils/base64"
|
||||
import { same } from "@/utils/same"
|
||||
import { createScrollPersistence, type SessionScroll } from "./layout-scroll"
|
||||
import { createPathHelpers } from "./file/path"
|
||||
|
||||
const AVATAR_COLOR_KEYS = ["pink", "mint", "orange", "purple", "cyan", "lime"] as const
|
||||
const DEFAULT_PANEL_WIDTH = 344
|
||||
@@ -96,6 +98,38 @@ function nextSessionTabsForOpen(current: SessionTabs | undefined, tab: string):
|
||||
return { all, active: tab }
|
||||
}
|
||||
|
||||
const sessionPath = (key: string) => {
|
||||
const dir = key.split("/")[0]
|
||||
if (!dir) return
|
||||
const root = decode64(dir)
|
||||
if (!root) return
|
||||
return createPathHelpers(() => root)
|
||||
}
|
||||
|
||||
const normalizeSessionTab = (path: ReturnType<typeof createPathHelpers> | undefined, tab: string) => {
|
||||
if (!tab.startsWith("file://")) return tab
|
||||
if (!path) return tab
|
||||
return path.tab(tab)
|
||||
}
|
||||
|
||||
const normalizeSessionTabList = (path: ReturnType<typeof createPathHelpers> | undefined, all: string[]) => {
|
||||
const seen = new Set<string>()
|
||||
return all.flatMap((tab) => {
|
||||
const value = normalizeSessionTab(path, tab)
|
||||
if (seen.has(value)) return []
|
||||
seen.add(value)
|
||||
return [value]
|
||||
})
|
||||
}
|
||||
|
||||
const normalizeStoredSessionTabs = (key: string, tabs: SessionTabs) => {
|
||||
const path = sessionPath(key)
|
||||
return {
|
||||
all: normalizeSessionTabList(path, tabs.all),
|
||||
active: tabs.active ? normalizeSessionTab(path, tabs.active) : tabs.active,
|
||||
}
|
||||
}
|
||||
|
||||
export const { use: useLayout, provider: LayoutProvider } = createSimpleContext({
|
||||
name: "Layout",
|
||||
init: () => {
|
||||
@@ -147,12 +181,46 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
||||
}
|
||||
})()
|
||||
|
||||
if (migratedSidebar === sidebar && migratedReview === review && migratedFileTree === fileTree) return value
|
||||
const sessionTabs = value.sessionTabs
|
||||
const migratedSessionTabs = (() => {
|
||||
if (!isRecord(sessionTabs)) return sessionTabs
|
||||
|
||||
let changed = false
|
||||
const next = Object.fromEntries(
|
||||
Object.entries(sessionTabs).map(([key, tabs]) => {
|
||||
if (!isRecord(tabs) || !Array.isArray(tabs.all)) return [key, tabs]
|
||||
|
||||
const current = {
|
||||
all: tabs.all.filter((tab): tab is string => typeof tab === "string"),
|
||||
active: typeof tabs.active === "string" ? tabs.active : undefined,
|
||||
}
|
||||
const normalized = normalizeStoredSessionTabs(key, current)
|
||||
if (current.all.length !== tabs.all.length) changed = true
|
||||
if (!same(current.all, normalized.all) || current.active !== normalized.active) changed = true
|
||||
if (tabs.active !== undefined && typeof tabs.active !== "string") changed = true
|
||||
return [key, normalized]
|
||||
}),
|
||||
)
|
||||
|
||||
if (!changed) return sessionTabs
|
||||
return next
|
||||
})()
|
||||
|
||||
if (
|
||||
migratedSidebar === sidebar &&
|
||||
migratedReview === review &&
|
||||
migratedFileTree === fileTree &&
|
||||
migratedSessionTabs === sessionTabs
|
||||
) {
|
||||
return value
|
||||
}
|
||||
|
||||
return {
|
||||
...value,
|
||||
sidebar: migratedSidebar,
|
||||
review: migratedReview,
|
||||
fileTree: migratedFileTree,
|
||||
sessionTabs: migratedSessionTabs,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -745,22 +813,26 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
||||
},
|
||||
tabs(sessionKey: string | Accessor<string>) {
|
||||
const key = createSessionKeyReader(sessionKey, ensureKey)
|
||||
const path = createMemo(() => sessionPath(key()))
|
||||
const tabs = createMemo(() => store.sessionTabs[key()] ?? { all: [] })
|
||||
const normalize = (tab: string) => normalizeSessionTab(path(), tab)
|
||||
const normalizeAll = (all: string[]) => normalizeSessionTabList(path(), all)
|
||||
return {
|
||||
tabs,
|
||||
active: createMemo(() => tabs().active),
|
||||
all: createMemo(() => tabs().all.filter((tab) => tab !== "review")),
|
||||
setActive(tab: string | undefined) {
|
||||
const session = key()
|
||||
const next = tab ? normalize(tab) : tab
|
||||
if (!store.sessionTabs[session]) {
|
||||
setStore("sessionTabs", session, { all: [], active: tab })
|
||||
setStore("sessionTabs", session, { all: [], active: next })
|
||||
} else {
|
||||
setStore("sessionTabs", session, "active", tab)
|
||||
setStore("sessionTabs", session, "active", next)
|
||||
}
|
||||
},
|
||||
setAll(all: string[]) {
|
||||
const session = key()
|
||||
const next = all.filter((tab) => tab !== "review")
|
||||
const next = normalizeAll(all).filter((tab) => tab !== "review")
|
||||
if (!store.sessionTabs[session]) {
|
||||
setStore("sessionTabs", session, { all: next, active: undefined })
|
||||
} else {
|
||||
@@ -769,7 +841,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
||||
},
|
||||
async open(tab: string) {
|
||||
const session = key()
|
||||
const next = nextSessionTabsForOpen(store.sessionTabs[session], tab)
|
||||
const next = nextSessionTabsForOpen(store.sessionTabs[session], normalize(tab))
|
||||
setStore("sessionTabs", session, next)
|
||||
},
|
||||
close(tab: string) {
|
||||
|
||||
Reference in New Issue
Block a user