summaryrefslogtreecommitdiffhomepage
path: root/packages/app/src/pages
diff options
context:
space:
mode:
Diffstat (limited to 'packages/app/src/pages')
-rw-r--r--packages/app/src/pages/directory-layout.tsx70
-rw-r--r--packages/app/src/pages/home.tsx8
-rw-r--r--packages/app/src/pages/layout.tsx62
-rw-r--r--packages/app/src/pages/session.tsx7
-rw-r--r--packages/app/src/pages/session/use-session-hash-scroll.ts18
5 files changed, 65 insertions, 100 deletions
diff --git a/packages/app/src/pages/directory-layout.tsx b/packages/app/src/pages/directory-layout.tsx
index 6d3b04be9..cd5e079a6 100644
--- a/packages/app/src/pages/directory-layout.tsx
+++ b/packages/app/src/pages/directory-layout.tsx
@@ -2,7 +2,8 @@ import { DataProvider } from "@opencode-ai/ui/context"
import { showToast } from "@opencode-ai/ui/toast"
import { base64Encode } from "@opencode-ai/util/encode"
import { useLocation, useNavigate, useParams } from "@solidjs/router"
-import { createEffect, createMemo, type ParentProps, Show } from "solid-js"
+import { createMemo, createResource, type ParentProps, Show } from "solid-js"
+import { useGlobalSDK } from "@/context/global-sdk"
import { useLanguage } from "@/context/language"
import { LocalProvider } from "@/context/local"
import { SDKProvider } from "@/context/sdk"
@@ -10,18 +11,10 @@ import { SyncProvider, useSync } from "@/context/sync"
import { decode64 } from "@/utils/base64"
function DirectoryDataProvider(props: ParentProps<{ directory: string }>) {
- const location = useLocation()
const navigate = useNavigate()
const sync = useSync()
const slug = createMemo(() => base64Encode(props.directory))
- createEffect(() => {
- const next = sync.data.path.directory
- if (!next || next === props.directory) return
- const path = location.pathname.slice(slug().length + 1)
- navigate(`/${base64Encode(next)}${path}${location.search}${location.hash}`, { replace: true })
- })
-
return (
<DataProvider
data={sync.data}
@@ -36,31 +29,50 @@ function DirectoryDataProvider(props: ParentProps<{ directory: string }>) {
export default function Layout(props: ParentProps) {
const params = useParams()
+ const location = useLocation()
const language = useLanguage()
+ const globalSDK = useGlobalSDK()
const navigate = useNavigate()
let invalid = ""
- const resolved = createMemo(() => {
- if (!params.dir) return ""
- return decode64(params.dir) ?? ""
- })
+ const [resolved] = createResource(
+ () => {
+ if (params.dir) return [location.pathname, params.dir] as const
+ },
+ async ([pathname, b64Dir]) => {
+ const directory = decode64(b64Dir)
- createEffect(() => {
- const dir = params.dir
- if (!dir) return
- if (resolved()) {
- invalid = ""
- return
- }
- if (invalid === dir) return
- invalid = dir
- showToast({
- variant: "error",
- title: language.t("common.requestFailed"),
- description: language.t("directory.error.invalidUrl"),
- })
- navigate("/", { replace: true })
- })
+ if (!directory) {
+ if (invalid === params.dir) return
+ invalid = b64Dir
+ showToast({
+ variant: "error",
+ title: language.t("common.requestFailed"),
+ description: language.t("directory.error.invalidUrl"),
+ })
+ navigate("/", { replace: true })
+ return
+ }
+
+ return await globalSDK
+ .createClient({
+ directory,
+ throwOnError: true,
+ })
+ .path.get()
+ .then((x) => {
+ const next = x.data?.directory ?? directory
+ invalid = ""
+ if (next === directory) return next
+ const path = pathname.slice(b64Dir.length + 1)
+ navigate(`/${base64Encode(next)}${path}${location.search}${location.hash}`, { replace: true })
+ })
+ .catch(() => {
+ invalid = ""
+ return directory
+ })
+ },
+ )
return (
<Show when={resolved()} keyed>
diff --git a/packages/app/src/pages/home.tsx b/packages/app/src/pages/home.tsx
index 4c795b968..ba3a2b942 100644
--- a/packages/app/src/pages/home.tsx
+++ b/packages/app/src/pages/home.tsx
@@ -113,14 +113,6 @@ export default function Home() {
</ul>
</div>
</Match>
- <Match when={!sync.ready}>
- <div class="mt-30 mx-auto flex flex-col items-center gap-3">
- <div class="text-12-regular text-text-weak">{language.t("common.loading")}</div>
- <Button class="px-3" onClick={chooseProject}>
- {language.t("command.project.open")}
- </Button>
- </div>
- </Match>
<Match when={true}>
<div class="mt-30 mx-auto flex flex-col items-center gap-3">
<Icon name="folder-add-left" size="large" />
diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx
index b5a96110f..01e151605 100644
--- a/packages/app/src/pages/layout.tsx
+++ b/packages/app/src/pages/layout.tsx
@@ -49,16 +49,21 @@ import { useNotification } from "@/context/notification"
import { usePermission } from "@/context/permission"
import { Binary } from "@opencode-ai/util/binary"
import { retry } from "@opencode-ai/util/retry"
-import { playSoundById } from "@/utils/sound"
+import { playSound, soundSrc } from "@/utils/sound"
import { createAim } from "@/utils/aim"
import { setNavigate } from "@/utils/notification-click"
import { Worktree as WorktreeState } from "@/utils/worktree"
import { setSessionHandoff } from "@/pages/session/handoff"
import { useDialog } from "@opencode-ai/ui/context/dialog"
-import { useTheme, type ColorScheme } from "@opencode-ai/ui/theme/context"
+import { useTheme, type ColorScheme } from "@opencode-ai/ui/theme"
+import { DialogSelectProvider } from "@/components/dialog-select-provider"
+import { DialogSelectServer } from "@/components/dialog-select-server"
+import { DialogSettings } from "@/components/dialog-settings"
import { useCommand, type CommandOption } from "@/context/command"
import { ConstrainDragXAxis, getDraggableId } from "@/utils/solid-dnd"
+import { DialogSelectDirectory } from "@/components/dialog-select-directory"
+import { DialogEditProject } from "@/components/dialog-edit-project"
import { DebugBar } from "@/components/debug-bar"
import { Titlebar } from "@/components/titlebar"
import { useServer } from "@/context/server"
@@ -105,8 +110,6 @@ export default function Layout(props: ParentProps) {
const pageReady = createMemo(() => ready())
let scrollContainerRef: HTMLDivElement | undefined
- let dialogRun = 0
- let dialogDead = false
const params = useParams()
const globalSDK = useGlobalSDK()
@@ -136,7 +139,7 @@ export default function Layout(props: ParentProps) {
dir: globalSync.peek(dir, { bootstrap: false })[0].path.directory || dir,
}
})
- const availableThemeEntries = createMemo(() => theme.ids().map((id) => [id, theme.themes()[id]] as const))
+ const availableThemeEntries = createMemo(() => Object.entries(theme.themes()))
const colorSchemeOrder: ColorScheme[] = ["system", "light", "dark"]
const colorSchemeKey: Record<ColorScheme, "theme.scheme.system" | "theme.scheme.light" | "theme.scheme.dark"> = {
system: "theme.scheme.system",
@@ -198,8 +201,6 @@ export default function Layout(props: ParentProps) {
})
onCleanup(() => {
- dialogDead = true
- dialogRun += 1
if (navLeave.current !== undefined) clearTimeout(navLeave.current)
clearTimeout(sortNowTimeout)
if (sortNowInterval) clearInterval(sortNowInterval)
@@ -335,9 +336,10 @@ export default function Layout(props: ParentProps) {
const nextIndex = currentIndex === -1 ? 0 : (currentIndex + direction + ids.length) % ids.length
const nextThemeId = ids[nextIndex]
theme.setTheme(nextThemeId)
+ const nextTheme = theme.themes()[nextThemeId]
showToast({
title: language.t("toast.theme.title"),
- description: theme.name(nextThemeId),
+ description: nextTheme?.name ?? nextThemeId,
})
}
@@ -492,7 +494,7 @@ export default function Layout(props: ParentProps) {
if (e.details.type === "permission.asked") {
if (settings.sounds.permissionsEnabled()) {
- void playSoundById(settings.sounds.permissions())
+ playSound(soundSrc(settings.sounds.permissions()))
}
if (settings.notifications.permissions()) {
void platform.notify(title, description, href)
@@ -1152,10 +1154,10 @@ export default function Layout(props: ParentProps) {
},
]
- for (const [id] of availableThemeEntries()) {
+ for (const [id, definition] of availableThemeEntries()) {
commands.push({
id: `theme.set.${id}`,
- title: language.t("command.theme.set", { theme: theme.name(id) }),
+ title: language.t("command.theme.set", { theme: definition.name ?? id }),
category: language.t("command.category.theme"),
onSelect: () => theme.commitPreview(),
onHighlight: () => {
@@ -1206,27 +1208,15 @@ export default function Layout(props: ParentProps) {
})
function connectProvider() {
- const run = ++dialogRun
- void import("@/components/dialog-select-provider").then((x) => {
- if (dialogDead || dialogRun !== run) return
- dialog.show(() => <x.DialogSelectProvider />)
- })
+ dialog.show(() => <DialogSelectProvider />)
}
function openServer() {
- const run = ++dialogRun
- void import("@/components/dialog-select-server").then((x) => {
- if (dialogDead || dialogRun !== run) return
- dialog.show(() => <x.DialogSelectServer />)
- })
+ dialog.show(() => <DialogSelectServer />)
}
function openSettings() {
- const run = ++dialogRun
- void import("@/components/dialog-settings").then((x) => {
- if (dialogDead || dialogRun !== run) return
- dialog.show(() => <x.DialogSettings />)
- })
+ dialog.show(() => <DialogSettings />)
}
function projectRoot(directory: string) {
@@ -1453,13 +1443,7 @@ export default function Layout(props: ParentProps) {
layout.sidebar.toggleWorkspaces(project.worktree)
}
- const showEditProjectDialog = (project: LocalProject) => {
- const run = ++dialogRun
- void import("@/components/dialog-edit-project").then((x) => {
- if (dialogDead || dialogRun !== run) return
- dialog.show(() => <x.DialogEditProject project={project} />)
- })
- }
+ const showEditProjectDialog = (project: LocalProject) => dialog.show(() => <DialogEditProject project={project} />)
async function chooseProject() {
function resolve(result: string | string[] | null) {
@@ -1480,14 +1464,10 @@ export default function Layout(props: ParentProps) {
})
resolve(result)
} else {
- const run = ++dialogRun
- void import("@/components/dialog-select-directory").then((x) => {
- if (dialogDead || dialogRun !== run) return
- dialog.show(
- () => <x.DialogSelectDirectory multiple={true} onSelect={resolve} />,
- () => resolve(null),
- )
- })
+ dialog.show(
+ () => <DialogSelectDirectory multiple={true} onSelect={resolve} />,
+ () => resolve(null),
+ )
}
}
diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx
index 2d3e31355..7a3b476e8 100644
--- a/packages/app/src/pages/session.tsx
+++ b/packages/app/src/pages/session.tsx
@@ -1184,6 +1184,8 @@ export default function Page() {
on(
() => sdk.directory,
() => {
+ void file.tree.list("")
+
const tab = activeFileTab()
if (!tab) return
const path = file.pathFromTab(tab)
@@ -1638,9 +1640,6 @@ export default function Page() {
sessionID: () => params.id,
messagesReady,
visibleUserMessages,
- historyMore,
- historyLoading,
- loadMore: (sessionID) => sync.session.history.loadMore(sessionID),
turnStart: historyWindow.turnStart,
currentMessageId: () => store.messageId,
pendingMessage: () => ui.pendingMessage,
@@ -1712,7 +1711,7 @@ export default function Page() {
<div class="flex-1 min-h-0 overflow-hidden">
<Switch>
<Match when={params.id}>
- <Show when={messagesReady()}>
+ <Show when={lastUserMessage()}>
<MessageTimeline
mobileChanges={mobileChanges()}
mobileFallback={reviewContent({
diff --git a/packages/app/src/pages/session/use-session-hash-scroll.ts b/packages/app/src/pages/session/use-session-hash-scroll.ts
index c582749d1..5fadb1f22 100644
--- a/packages/app/src/pages/session/use-session-hash-scroll.ts
+++ b/packages/app/src/pages/session/use-session-hash-scroll.ts
@@ -8,9 +8,6 @@ export const useSessionHashScroll = (input: {
sessionID: () => string | undefined
messagesReady: () => boolean
visibleUserMessages: () => UserMessage[]
- historyMore: () => boolean
- historyLoading: () => boolean
- loadMore: (sessionID: string) => Promise<void>
turnStart: () => number
currentMessageId: () => string | undefined
pendingMessage: () => string | undefined
@@ -184,21 +181,6 @@ export const useSessionHashScroll = (input: {
queue(() => scrollToMessage(msg, "auto"))
})
- createEffect(() => {
- const sessionID = input.sessionID()
- if (!sessionID || !input.messagesReady()) return
-
- visibleUserMessages()
-
- let targetId = input.pendingMessage()
- if (!targetId && !clearing) targetId = messageIdFromHash(location.hash)
- if (!targetId) return
- if (messageById().has(targetId)) return
- if (!input.historyMore() || input.historyLoading()) return
-
- void input.loadMore(sessionID)
- })
-
onMount(() => {
if (typeof window !== "undefined" && "scrollRestoration" in window.history) {
window.history.scrollRestoration = "manual"