mirror of
https://gitea.toothfairyai.com/ToothFairyAI/tf_code.git
synced 2026-03-30 05:43:55 +00:00
wip(app): i18n
This commit is contained in:
parent
92beae1410
commit
a68e5a1c17
@ -202,4 +202,81 @@ export const dict = {
|
|||||||
"toast.session.unshare.success.description": "Session unshared successfully!",
|
"toast.session.unshare.success.description": "Session unshared successfully!",
|
||||||
"toast.session.unshare.failed.title": "Failed to unshare session",
|
"toast.session.unshare.failed.title": "Failed to unshare session",
|
||||||
"toast.session.unshare.failed.description": "An error occurred while unsharing the session",
|
"toast.session.unshare.failed.description": "An error occurred while unsharing the session",
|
||||||
|
|
||||||
|
"toast.update.title": "Update available",
|
||||||
|
"toast.update.description": "A new version of OpenCode ({{version}}) is now available to install.",
|
||||||
|
"toast.update.action.installRestart": "Install and restart",
|
||||||
|
"toast.update.action.notYet": "Not yet",
|
||||||
|
|
||||||
|
"notification.permission.title": "Permission required",
|
||||||
|
"notification.permission.description": "{{sessionTitle}} in {{projectName}} needs permission",
|
||||||
|
"notification.question.title": "Question",
|
||||||
|
"notification.question.description": "{{sessionTitle}} in {{projectName}} has a question",
|
||||||
|
"notification.action.goToSession": "Go to session",
|
||||||
|
|
||||||
|
"home.recentProjects": "Recent projects",
|
||||||
|
"home.empty.title": "No recent projects",
|
||||||
|
"home.empty.description": "Get started by opening a local project",
|
||||||
|
|
||||||
|
"session.tab.session": "Session",
|
||||||
|
"session.tab.review": "Review",
|
||||||
|
"session.tab.context": "Context",
|
||||||
|
"session.review.filesChanged": "{{count}} Files Changed",
|
||||||
|
"session.review.loadingChanges": "Loading changes...",
|
||||||
|
"session.review.empty": "No changes in this session yet",
|
||||||
|
"session.messages.renderEarlier": "Render earlier messages",
|
||||||
|
"session.messages.loadingEarlier": "Loading earlier messages...",
|
||||||
|
"session.messages.loadEarlier": "Load earlier messages",
|
||||||
|
"session.messages.loading": "Loading messages...",
|
||||||
|
|
||||||
|
"session.context.addToContext": "Add {{selection}} to context",
|
||||||
|
|
||||||
|
"prompt.loading": "Loading prompt...",
|
||||||
|
"terminal.loading": "Loading terminal...",
|
||||||
|
|
||||||
|
"common.closeTab": "Close tab",
|
||||||
|
"common.dismiss": "Dismiss",
|
||||||
|
"common.requestFailed": "Request failed",
|
||||||
|
"common.moreOptions": "More options",
|
||||||
|
"common.rename": "Rename",
|
||||||
|
"common.reset": "Reset",
|
||||||
|
"common.delete": "Delete",
|
||||||
|
"common.close": "Close",
|
||||||
|
"common.edit": "Edit",
|
||||||
|
"common.loadMore": "Load more",
|
||||||
|
|
||||||
|
"sidebar.settings": "Settings",
|
||||||
|
"sidebar.help": "Help",
|
||||||
|
"sidebar.workspaces.enable": "Enable workspaces",
|
||||||
|
"sidebar.workspaces.disable": "Disable workspaces",
|
||||||
|
"sidebar.gettingStarted.title": "Getting started",
|
||||||
|
"sidebar.gettingStarted.line1": "OpenCode includes free models so you can start immediately.",
|
||||||
|
"sidebar.gettingStarted.line2": "Connect any provider to use models, inc. Claude, GPT, Gemini etc.",
|
||||||
|
"sidebar.project.recentSessions": "Recent sessions",
|
||||||
|
"sidebar.project.viewAllSessions": "View all sessions",
|
||||||
|
|
||||||
|
"workspace.new": "New workspace",
|
||||||
|
"workspace.type.local": "local",
|
||||||
|
"workspace.type.sandbox": "sandbox",
|
||||||
|
"workspace.create.failed.title": "Failed to create workspace",
|
||||||
|
"workspace.delete.failed.title": "Failed to delete workspace",
|
||||||
|
"workspace.resetting.title": "Resetting workspace",
|
||||||
|
"workspace.resetting.description": "This may take a minute.",
|
||||||
|
"workspace.reset.failed.title": "Failed to reset workspace",
|
||||||
|
"workspace.reset.success.title": "Workspace reset",
|
||||||
|
"workspace.reset.success.description": "Workspace now matches the default branch.",
|
||||||
|
"workspace.status.checking": "Checking for unmerged changes...",
|
||||||
|
"workspace.status.error": "Unable to verify git status.",
|
||||||
|
"workspace.status.clean": "No unmerged changes detected.",
|
||||||
|
"workspace.status.dirty": "Unmerged changes detected in this workspace.",
|
||||||
|
"workspace.delete.title": "Delete workspace",
|
||||||
|
"workspace.delete.confirm": "Delete workspace \"{{name}}\"?",
|
||||||
|
"workspace.delete.button": "Delete workspace",
|
||||||
|
"workspace.reset.title": "Reset workspace",
|
||||||
|
"workspace.reset.confirm": "Reset workspace \"{{name}}\"?",
|
||||||
|
"workspace.reset.button": "Reset workspace",
|
||||||
|
"workspace.reset.archived.none": "No active sessions will be archived.",
|
||||||
|
"workspace.reset.archived.one": "1 session will be archived.",
|
||||||
|
"workspace.reset.archived.many": "{{count}} sessions will be archived.",
|
||||||
|
"workspace.reset.note": "This will reset the workspace to match the default branch.",
|
||||||
}
|
}
|
||||||
|
|||||||
@ -206,4 +206,81 @@ export const dict = {
|
|||||||
"toast.session.unshare.success.description": "会话已成功取消分享",
|
"toast.session.unshare.success.description": "会话已成功取消分享",
|
||||||
"toast.session.unshare.failed.title": "取消分享失败",
|
"toast.session.unshare.failed.title": "取消分享失败",
|
||||||
"toast.session.unshare.failed.description": "取消分享会话时发生错误",
|
"toast.session.unshare.failed.description": "取消分享会话时发生错误",
|
||||||
|
|
||||||
|
"toast.update.title": "有可用更新",
|
||||||
|
"toast.update.description": "OpenCode 有新版本 ({{version}}) 可安装。",
|
||||||
|
"toast.update.action.installRestart": "安装并重启",
|
||||||
|
"toast.update.action.notYet": "稍后",
|
||||||
|
|
||||||
|
"notification.permission.title": "需要权限",
|
||||||
|
"notification.permission.description": "{{sessionTitle}}({{projectName}})需要权限",
|
||||||
|
"notification.question.title": "问题",
|
||||||
|
"notification.question.description": "{{sessionTitle}}({{projectName}})有一个问题",
|
||||||
|
"notification.action.goToSession": "前往会话",
|
||||||
|
|
||||||
|
"home.recentProjects": "最近项目",
|
||||||
|
"home.empty.title": "没有最近项目",
|
||||||
|
"home.empty.description": "通过打开本地项目开始使用",
|
||||||
|
|
||||||
|
"session.tab.session": "会话",
|
||||||
|
"session.tab.review": "审查",
|
||||||
|
"session.tab.context": "上下文",
|
||||||
|
"session.review.filesChanged": "{{count}} 个文件变更",
|
||||||
|
"session.review.loadingChanges": "正在加载更改...",
|
||||||
|
"session.review.empty": "此会话暂无更改",
|
||||||
|
"session.messages.renderEarlier": "显示更早的消息",
|
||||||
|
"session.messages.loadingEarlier": "正在加载更早的消息...",
|
||||||
|
"session.messages.loadEarlier": "加载更早的消息",
|
||||||
|
"session.messages.loading": "正在加载消息...",
|
||||||
|
|
||||||
|
"session.context.addToContext": "将 {{selection}} 添加到上下文",
|
||||||
|
|
||||||
|
"prompt.loading": "正在加载提示...",
|
||||||
|
"terminal.loading": "正在加载终端...",
|
||||||
|
|
||||||
|
"common.closeTab": "关闭标签页",
|
||||||
|
"common.dismiss": "忽略",
|
||||||
|
"common.requestFailed": "请求失败",
|
||||||
|
"common.moreOptions": "更多选项",
|
||||||
|
"common.rename": "重命名",
|
||||||
|
"common.reset": "重置",
|
||||||
|
"common.delete": "删除",
|
||||||
|
"common.close": "关闭",
|
||||||
|
"common.edit": "编辑",
|
||||||
|
"common.loadMore": "加载更多",
|
||||||
|
|
||||||
|
"sidebar.settings": "设置",
|
||||||
|
"sidebar.help": "帮助",
|
||||||
|
"sidebar.workspaces.enable": "启用工作区",
|
||||||
|
"sidebar.workspaces.disable": "禁用工作区",
|
||||||
|
"sidebar.gettingStarted.title": "入门",
|
||||||
|
"sidebar.gettingStarted.line1": "OpenCode 提供免费模型,你可以立即开始使用。",
|
||||||
|
"sidebar.gettingStarted.line2": "连接任意提供商即可使用更多模型,如 Claude、GPT、Gemini 等。",
|
||||||
|
"sidebar.project.recentSessions": "最近会话",
|
||||||
|
"sidebar.project.viewAllSessions": "查看全部会话",
|
||||||
|
|
||||||
|
"workspace.new": "新建工作区",
|
||||||
|
"workspace.type.local": "本地",
|
||||||
|
"workspace.type.sandbox": "沙盒",
|
||||||
|
"workspace.create.failed.title": "创建工作区失败",
|
||||||
|
"workspace.delete.failed.title": "删除工作区失败",
|
||||||
|
"workspace.resetting.title": "正在重置工作区",
|
||||||
|
"workspace.resetting.description": "这可能需要一点时间。",
|
||||||
|
"workspace.reset.failed.title": "重置工作区失败",
|
||||||
|
"workspace.reset.success.title": "工作区已重置",
|
||||||
|
"workspace.reset.success.description": "工作区已与默认分支保持一致。",
|
||||||
|
"workspace.status.checking": "正在检查未合并的更改...",
|
||||||
|
"workspace.status.error": "无法验证 git 状态。",
|
||||||
|
"workspace.status.clean": "未检测到未合并的更改。",
|
||||||
|
"workspace.status.dirty": "检测到未合并的更改。",
|
||||||
|
"workspace.delete.title": "删除工作区",
|
||||||
|
"workspace.delete.confirm": "删除工作区 \"{{name}}\"?",
|
||||||
|
"workspace.delete.button": "删除工作区",
|
||||||
|
"workspace.reset.title": "重置工作区",
|
||||||
|
"workspace.reset.confirm": "重置工作区 \"{{name}}\"?",
|
||||||
|
"workspace.reset.button": "重置工作区",
|
||||||
|
"workspace.reset.archived.none": "不会归档任何活跃会话。",
|
||||||
|
"workspace.reset.archived.one": "将归档 1 个会话。",
|
||||||
|
"workspace.reset.archived.many": "将归档 {{count}} 个会话。",
|
||||||
|
"workspace.reset.note": "这将把工作区重置为与默认分支一致。",
|
||||||
} satisfies Partial<Record<Keys, string>>
|
} satisfies Partial<Record<Keys, string>>
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import { DialogSelectDirectory } from "@/components/dialog-select-directory"
|
|||||||
import { DialogSelectServer } from "@/components/dialog-select-server"
|
import { DialogSelectServer } from "@/components/dialog-select-server"
|
||||||
import { useServer } from "@/context/server"
|
import { useServer } from "@/context/server"
|
||||||
import { useGlobalSync } from "@/context/global-sync"
|
import { useGlobalSync } from "@/context/global-sync"
|
||||||
|
import { useLanguage } from "@/context/language"
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const sync = useGlobalSync()
|
const sync = useGlobalSync()
|
||||||
@ -20,6 +21,7 @@ export default function Home() {
|
|||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const server = useServer()
|
const server = useServer()
|
||||||
|
const language = useLanguage()
|
||||||
const homedir = createMemo(() => sync.data.path.home)
|
const homedir = createMemo(() => sync.data.path.home)
|
||||||
|
|
||||||
function openProject(directory: string) {
|
function openProject(directory: string) {
|
||||||
@ -41,7 +43,7 @@ export default function Home() {
|
|||||||
|
|
||||||
if (platform.openDirectoryPickerDialog && server.isLocal()) {
|
if (platform.openDirectoryPickerDialog && server.isLocal()) {
|
||||||
const result = await platform.openDirectoryPickerDialog?.({
|
const result = await platform.openDirectoryPickerDialog?.({
|
||||||
title: "Open project",
|
title: language.t("command.project.open"),
|
||||||
multiple: true,
|
multiple: true,
|
||||||
})
|
})
|
||||||
resolve(result)
|
resolve(result)
|
||||||
@ -74,13 +76,13 @@ export default function Home() {
|
|||||||
</Button>
|
</Button>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Match when={sync.data.project.length > 0}>
|
<Match when={sync.data.project.length > 0}>
|
||||||
<div class="mt-20 w-full flex flex-col gap-4">
|
<div class="mt-20 w-full flex flex-col gap-4">
|
||||||
<div class="flex gap-2 items-center justify-between pl-3">
|
<div class="flex gap-2 items-center justify-between pl-3">
|
||||||
<div class="text-14-medium text-text-strong">Recent projects</div>
|
<div class="text-14-medium text-text-strong">{language.t("home.recentProjects")}</div>
|
||||||
<Button icon="folder-add-left" size="normal" class="pl-2 pr-3" onClick={chooseProject}>
|
<Button icon="folder-add-left" size="normal" class="pl-2 pr-3" onClick={chooseProject}>
|
||||||
Open project
|
{language.t("command.project.open")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<ul class="flex flex-col gap-2">
|
<ul class="flex flex-col gap-2">
|
||||||
<For
|
<For
|
||||||
each={sync.data.project
|
each={sync.data.project
|
||||||
@ -108,12 +110,12 @@ export default function Home() {
|
|||||||
<div class="mt-30 mx-auto flex flex-col items-center gap-3">
|
<div class="mt-30 mx-auto flex flex-col items-center gap-3">
|
||||||
<Icon name="folder-add-left" size="large" />
|
<Icon name="folder-add-left" size="large" />
|
||||||
<div class="flex flex-col gap-1 items-center justify-center">
|
<div class="flex flex-col gap-1 items-center justify-center">
|
||||||
<div class="text-14-medium text-text-strong">No recent projects</div>
|
<div class="text-14-medium text-text-strong">{language.t("home.empty.title")}</div>
|
||||||
<div class="text-12-regular text-text-weak">Get started by opening a local project</div>
|
<div class="text-12-regular text-text-weak">{language.t("home.empty.description")}</div>
|
||||||
</div>
|
</div>
|
||||||
<div />
|
<div />
|
||||||
<Button class="px-3" onClick={chooseProject}>
|
<Button class="px-3" onClick={chooseProject}>
|
||||||
Open project
|
{language.t("command.project.open")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Match>
|
</Match>
|
||||||
|
|||||||
@ -300,18 +300,18 @@ export default function Layout(props: ParentProps) {
|
|||||||
toastId = showToast({
|
toastId = showToast({
|
||||||
persistent: true,
|
persistent: true,
|
||||||
icon: "download",
|
icon: "download",
|
||||||
title: "Update available",
|
title: language.t("toast.update.title"),
|
||||||
description: `A new version of OpenCode (${version}) is now available to install.`,
|
description: language.t("toast.update.description", { version: version ?? "" }),
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
label: "Install and restart",
|
label: language.t("toast.update.action.installRestart"),
|
||||||
onClick: async () => {
|
onClick: async () => {
|
||||||
await platform.update!()
|
await platform.update!()
|
||||||
await platform.restart!()
|
await platform.restart!()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Not yet",
|
label: language.t("toast.update.action.notYet"),
|
||||||
onClick: "dismiss",
|
onClick: "dismiss",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -325,27 +325,17 @@ export default function Layout(props: ParentProps) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
const alerts = {
|
|
||||||
"permission.asked": {
|
|
||||||
title: "Permission required",
|
|
||||||
icon: "checklist" as const,
|
|
||||||
description: (sessionTitle: string, projectName: string) =>
|
|
||||||
`${sessionTitle} in ${projectName} needs permission`,
|
|
||||||
},
|
|
||||||
"question.asked": {
|
|
||||||
title: "Question",
|
|
||||||
icon: "bubble-5" as const,
|
|
||||||
description: (sessionTitle: string, projectName: string) => `${sessionTitle} in ${projectName} has a question`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const toastBySession = new Map<string, number>()
|
const toastBySession = new Map<string, number>()
|
||||||
const alertedAtBySession = new Map<string, number>()
|
const alertedAtBySession = new Map<string, number>()
|
||||||
const cooldownMs = 5000
|
const cooldownMs = 5000
|
||||||
|
|
||||||
const unsub = globalSDK.event.listen((e) => {
|
const unsub = globalSDK.event.listen((e) => {
|
||||||
if (e.details?.type !== "permission.asked" && e.details?.type !== "question.asked") return
|
if (e.details?.type !== "permission.asked" && e.details?.type !== "question.asked") return
|
||||||
const config = alerts[e.details.type]
|
const title =
|
||||||
|
e.details.type === "permission.asked"
|
||||||
|
? language.t("notification.permission.title")
|
||||||
|
: language.t("notification.question.title")
|
||||||
|
const icon = e.details.type === "permission.asked" ? ("checklist" as const) : ("bubble-5" as const)
|
||||||
const directory = e.name
|
const directory = e.name
|
||||||
const props = e.details.properties
|
const props = e.details.properties
|
||||||
if (e.details.type === "permission.asked" && permission.autoResponds(e.details.properties, directory)) return
|
if (e.details.type === "permission.asked" && permission.autoResponds(e.details.properties, directory)) return
|
||||||
@ -354,9 +344,12 @@ export default function Layout(props: ParentProps) {
|
|||||||
const session = store.session.find((s) => s.id === props.sessionID)
|
const session = store.session.find((s) => s.id === props.sessionID)
|
||||||
const sessionKey = `${directory}:${props.sessionID}`
|
const sessionKey = `${directory}:${props.sessionID}`
|
||||||
|
|
||||||
const sessionTitle = session?.title ?? "New session"
|
const sessionTitle = session?.title ?? language.t("command.session.new")
|
||||||
const projectName = getFilename(directory)
|
const projectName = getFilename(directory)
|
||||||
const description = config.description(sessionTitle, projectName)
|
const description =
|
||||||
|
e.details.type === "permission.asked"
|
||||||
|
? language.t("notification.permission.description", { sessionTitle, projectName })
|
||||||
|
: language.t("notification.question.description", { sessionTitle, projectName })
|
||||||
const href = `/${base64Encode(directory)}/session/${props.sessionID}`
|
const href = `/${base64Encode(directory)}/session/${props.sessionID}`
|
||||||
|
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
@ -367,13 +360,13 @@ export default function Layout(props: ParentProps) {
|
|||||||
if (e.details.type === "permission.asked") {
|
if (e.details.type === "permission.asked") {
|
||||||
playSound(soundSrc(settings.sounds.permissions()))
|
playSound(soundSrc(settings.sounds.permissions()))
|
||||||
if (settings.notifications.permissions()) {
|
if (settings.notifications.permissions()) {
|
||||||
void platform.notify(config.title, description, href)
|
void platform.notify(title, description, href)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.details.type === "question.asked") {
|
if (e.details.type === "question.asked") {
|
||||||
if (settings.notifications.agent()) {
|
if (settings.notifications.agent()) {
|
||||||
void platform.notify(config.title, description, href)
|
void platform.notify(title, description, href)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -387,16 +380,16 @@ export default function Layout(props: ParentProps) {
|
|||||||
|
|
||||||
const toastId = showToast({
|
const toastId = showToast({
|
||||||
persistent: true,
|
persistent: true,
|
||||||
icon: config.icon,
|
icon,
|
||||||
title: config.title,
|
title,
|
||||||
description,
|
description,
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
label: "Go to session",
|
label: language.t("notification.action.goToSession"),
|
||||||
onClick: () => navigate(href),
|
onClick: () => navigate(href),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Dismiss",
|
label: language.t("common.dismiss"),
|
||||||
onClick: "dismiss",
|
onClick: "dismiss",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -1024,7 +1017,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
|
|
||||||
if (platform.openDirectoryPickerDialog && server.isLocal()) {
|
if (platform.openDirectoryPickerDialog && server.isLocal()) {
|
||||||
const result = await platform.openDirectoryPickerDialog?.({
|
const result = await platform.openDirectoryPickerDialog?.({
|
||||||
title: "Open project",
|
title: language.t("command.project.open"),
|
||||||
multiple: true,
|
multiple: true,
|
||||||
})
|
})
|
||||||
resolve(result)
|
resolve(result)
|
||||||
@ -1042,7 +1035,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
if (data?.message) return data.message
|
if (data?.message) return data.message
|
||||||
}
|
}
|
||||||
if (err instanceof Error) return err.message
|
if (err instanceof Error) return err.message
|
||||||
return "Request failed"
|
return language.t("common.requestFailed")
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteWorkspace = async (directory: string) => {
|
const deleteWorkspace = async (directory: string) => {
|
||||||
@ -1057,7 +1050,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
.then((x) => x.data)
|
.then((x) => x.data)
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
showToast({
|
showToast({
|
||||||
title: "Failed to delete workspace",
|
title: language.t("workspace.delete.failed.title"),
|
||||||
description: errorMessage(err),
|
description: errorMessage(err),
|
||||||
})
|
})
|
||||||
return false
|
return false
|
||||||
@ -1079,9 +1072,15 @@ export default function Layout(props: ParentProps) {
|
|||||||
const current = currentProject()
|
const current = currentProject()
|
||||||
if (!current) return
|
if (!current) return
|
||||||
if (directory === current.worktree) return
|
if (directory === current.worktree) return
|
||||||
|
|
||||||
setBusy(directory, true)
|
setBusy(directory, true)
|
||||||
|
|
||||||
|
const progress = showToast({
|
||||||
|
persistent: true,
|
||||||
|
title: language.t("workspace.resetting.title"),
|
||||||
|
description: language.t("workspace.resetting.description"),
|
||||||
|
})
|
||||||
|
const dismiss = () => toaster.dismiss(progress)
|
||||||
|
|
||||||
const sessions = await globalSDK.client.session
|
const sessions = await globalSDK.client.session
|
||||||
.list({ directory })
|
.list({ directory })
|
||||||
.then((x) => x.data ?? [])
|
.then((x) => x.data ?? [])
|
||||||
@ -1092,7 +1091,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
.then((x) => x.data)
|
.then((x) => x.data)
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
showToast({
|
showToast({
|
||||||
title: "Failed to reset workspace",
|
title: language.t("workspace.reset.failed.title"),
|
||||||
description: errorMessage(err),
|
description: errorMessage(err),
|
||||||
})
|
})
|
||||||
return false
|
return false
|
||||||
@ -1100,6 +1099,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
setBusy(directory, false)
|
setBusy(directory, false)
|
||||||
|
dismiss()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1121,14 +1121,15 @@ export default function Layout(props: ParentProps) {
|
|||||||
await globalSDK.client.instance.dispose({ directory }).catch(() => undefined)
|
await globalSDK.client.instance.dispose({ directory }).catch(() => undefined)
|
||||||
|
|
||||||
setBusy(directory, false)
|
setBusy(directory, false)
|
||||||
|
dismiss()
|
||||||
|
|
||||||
const href = `/${base64Encode(directory)}/session`
|
const href = `/${base64Encode(directory)}/session`
|
||||||
navigate(href)
|
navigate(href)
|
||||||
layout.mobileSidebar.hide()
|
layout.mobileSidebar.hide()
|
||||||
|
|
||||||
showToast({
|
showToast({
|
||||||
title: "Workspace reset",
|
title: language.t("workspace.reset.success.title"),
|
||||||
description: "Workspace now matches the default branch.",
|
description: language.t("workspace.reset.success.description"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1164,25 +1165,27 @@ export default function Layout(props: ParentProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const description = () => {
|
const description = () => {
|
||||||
if (data.status === "loading") return "Checking for unmerged changes..."
|
if (data.status === "loading") return language.t("workspace.status.checking")
|
||||||
if (data.status === "error") return "Unable to verify git status."
|
if (data.status === "error") return language.t("workspace.status.error")
|
||||||
if (!data.dirty) return "No unmerged changes detected."
|
if (!data.dirty) return language.t("workspace.status.clean")
|
||||||
return "Unmerged changes detected in this workspace."
|
return language.t("workspace.status.dirty")
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog title="Delete workspace" fit>
|
<Dialog title={language.t("workspace.delete.title")} fit>
|
||||||
<div class="flex flex-col gap-4 px-2.5 pb-3">
|
<div class="flex flex-col gap-4 px-2.5 pb-3">
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<span class="text-14-regular text-text-strong">Delete workspace "{name()}"?</span>
|
<span class="text-14-regular text-text-strong">
|
||||||
|
{language.t("workspace.delete.confirm", { name: name() })}
|
||||||
|
</span>
|
||||||
<span class="text-12-regular text-text-weak">{description()}</span>
|
<span class="text-12-regular text-text-weak">{description()}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-end gap-2">
|
<div class="flex justify-end gap-2">
|
||||||
<Button variant="ghost" size="large" onClick={() => dialog.close()}>
|
<Button variant="ghost" size="large" onClick={() => dialog.close()}>
|
||||||
Cancel
|
{language.t("common.cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="primary" size="large" disabled={data.status === "loading"} onClick={handleDelete}>
|
<Button variant="primary" size="large" disabled={data.status === "loading"} onClick={handleDelete}>
|
||||||
Delete workspace
|
{language.t("workspace.delete.button")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -1235,34 +1238,36 @@ export default function Layout(props: ParentProps) {
|
|||||||
const archivedCount = () => state.sessions.length
|
const archivedCount = () => state.sessions.length
|
||||||
|
|
||||||
const description = () => {
|
const description = () => {
|
||||||
if (state.status === "loading") return "Checking for unmerged changes..."
|
if (state.status === "loading") return language.t("workspace.status.checking")
|
||||||
if (state.status === "error") return "Unable to verify git status."
|
if (state.status === "error") return language.t("workspace.status.error")
|
||||||
if (!state.dirty) return "No unmerged changes detected."
|
if (!state.dirty) return language.t("workspace.status.clean")
|
||||||
return "Unmerged changes detected in this workspace."
|
return language.t("workspace.status.dirty")
|
||||||
}
|
}
|
||||||
|
|
||||||
const archivedLabel = () => {
|
const archivedLabel = () => {
|
||||||
const count = archivedCount()
|
const count = archivedCount()
|
||||||
if (count === 0) return "No active sessions will be archived."
|
if (count === 0) return language.t("workspace.reset.archived.none")
|
||||||
const label = count === 1 ? "1 session" : `${count} sessions`
|
if (count === 1) return language.t("workspace.reset.archived.one")
|
||||||
return `${label} will be archived.`
|
return language.t("workspace.reset.archived.many", { count })
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog title="Reset workspace" fit>
|
<Dialog title={language.t("workspace.reset.title")} fit>
|
||||||
<div class="flex flex-col gap-4 px-2.5 pb-3">
|
<div class="flex flex-col gap-4 px-2.5 pb-3">
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<span class="text-14-regular text-text-strong">Reset workspace "{name()}"?</span>
|
<span class="text-14-regular text-text-strong">
|
||||||
|
{language.t("workspace.reset.confirm", { name: name() })}
|
||||||
|
</span>
|
||||||
<span class="text-12-regular text-text-weak">
|
<span class="text-12-regular text-text-weak">
|
||||||
{description()} {archivedLabel()} This will reset the workspace to match the default branch.
|
{description()} {archivedLabel()} {language.t("workspace.reset.note")}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-end gap-2">
|
<div class="flex justify-end gap-2">
|
||||||
<Button variant="ghost" size="large" onClick={() => dialog.close()}>
|
<Button variant="ghost" size="large" onClick={() => dialog.close()}>
|
||||||
Cancel
|
{language.t("common.cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="primary" size="large" disabled={state.status === "loading"} onClick={handleReset}>
|
<Button variant="primary" size="large" disabled={state.status === "loading"} onClick={handleReset}>
|
||||||
Reset workspace
|
{language.t("workspace.reset.button")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -1536,7 +1541,10 @@ export default function Layout(props: ParentProps) {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<HoverCard openDelay={1000} closeDelay={0} placement="right-start" gutter={28} trigger={item}>
|
<HoverCard openDelay={1000} closeDelay={0} placement="right-start" gutter={28} trigger={item}>
|
||||||
<Show when={hoverReady()} fallback={<div class="text-12-regular text-text-weak">Loading messages…</div>}>
|
<Show
|
||||||
|
when={hoverReady()}
|
||||||
|
fallback={<div class="text-12-regular text-text-weak">{language.t("session.messages.loading")}</div>}
|
||||||
|
>
|
||||||
<MessageNav
|
<MessageNav
|
||||||
messages={hoverMessages() ?? []}
|
messages={hoverMessages() ?? []}
|
||||||
current={undefined}
|
current={undefined}
|
||||||
@ -1561,7 +1569,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
>
|
>
|
||||||
<TooltipKeybind
|
<TooltipKeybind
|
||||||
placement={props.mobile ? "bottom" : "right"}
|
placement={props.mobile ? "bottom" : "right"}
|
||||||
title="Archive session"
|
title={language.t("command.session.archive")}
|
||||||
keybind={command.keybind("session.archive")}
|
keybind={command.keybind("session.archive")}
|
||||||
gutter={8}
|
gutter={8}
|
||||||
>
|
>
|
||||||
@ -1604,7 +1612,8 @@ export default function Layout(props: ParentProps) {
|
|||||||
if (!directory) return
|
if (!directory) return
|
||||||
|
|
||||||
const [workspaceStore] = globalSync.child(directory)
|
const [workspaceStore] = globalSync.child(directory)
|
||||||
const kind = directory === project.worktree ? "local" : "sandbox"
|
const kind =
|
||||||
|
directory === project.worktree ? language.t("workspace.type.local") : language.t("workspace.type.sandbox")
|
||||||
const name = workspaceLabel(directory, workspaceStore.vcs?.branch, project.id)
|
const name = workspaceLabel(directory, workspaceStore.vcs?.branch, project.id)
|
||||||
return `${kind} : ${name}`
|
return `${kind} : ${name}`
|
||||||
})
|
})
|
||||||
@ -1671,7 +1680,9 @@ export default function Layout(props: ParentProps) {
|
|||||||
<Spinner class="size-[15px]" />
|
<Spinner class="size-[15px]" />
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-14-medium text-text-base shrink-0">{local() ? "local" : "sandbox"} :</span>
|
<span class="text-14-medium text-text-base shrink-0">
|
||||||
|
{local() ? language.t("workspace.type.local") : language.t("workspace.type.sandbox")} :
|
||||||
|
</span>
|
||||||
<Show
|
<Show
|
||||||
when={!local()}
|
when={!local()}
|
||||||
fallback={
|
fallback={
|
||||||
@ -1737,7 +1748,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DropdownMenu open={menuOpen()} onOpenChange={setMenuOpen}>
|
<DropdownMenu open={menuOpen()} onOpenChange={setMenuOpen}>
|
||||||
<Tooltip value="More options" placement="top">
|
<Tooltip value={language.t("common.moreOptions")} placement="top">
|
||||||
<DropdownMenu.Trigger as={IconButton} icon="dot-grid" variant="ghost" class="size-6 rounded-md" />
|
<DropdownMenu.Trigger as={IconButton} icon="dot-grid" variant="ghost" class="size-6 rounded-md" />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<DropdownMenu.Portal>
|
<DropdownMenu.Portal>
|
||||||
@ -1750,7 +1761,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DropdownMenu.Item onSelect={() => navigate(`/${slug()}/session`)}>
|
<DropdownMenu.Item onSelect={() => navigate(`/${slug()}/session`)}>
|
||||||
<DropdownMenu.ItemLabel>New session</DropdownMenu.ItemLabel>
|
<DropdownMenu.ItemLabel>{language.t("command.session.new")}</DropdownMenu.ItemLabel>
|
||||||
</DropdownMenu.Item>
|
</DropdownMenu.Item>
|
||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
disabled={local()}
|
disabled={local()}
|
||||||
@ -1759,24 +1770,28 @@ export default function Layout(props: ParentProps) {
|
|||||||
setMenuOpen(false)
|
setMenuOpen(false)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DropdownMenu.ItemLabel>Rename</DropdownMenu.ItemLabel>
|
<DropdownMenu.ItemLabel>{language.t("common.rename")}</DropdownMenu.ItemLabel>
|
||||||
</DropdownMenu.Item>
|
</DropdownMenu.Item>
|
||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
disabled={local() || busy()}
|
disabled={local() || busy()}
|
||||||
onSelect={() => dialog.show(() => <DialogResetWorkspace directory={props.directory} />)}
|
onSelect={() => dialog.show(() => <DialogResetWorkspace directory={props.directory} />)}
|
||||||
>
|
>
|
||||||
<DropdownMenu.ItemLabel>Reset</DropdownMenu.ItemLabel>
|
<DropdownMenu.ItemLabel>{language.t("common.reset")}</DropdownMenu.ItemLabel>
|
||||||
</DropdownMenu.Item>
|
</DropdownMenu.Item>
|
||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
disabled={local() || busy()}
|
disabled={local() || busy()}
|
||||||
onSelect={() => dialog.show(() => <DialogDeleteWorkspace directory={props.directory} />)}
|
onSelect={() => dialog.show(() => <DialogDeleteWorkspace directory={props.directory} />)}
|
||||||
>
|
>
|
||||||
<DropdownMenu.ItemLabel>Delete</DropdownMenu.ItemLabel>
|
<DropdownMenu.ItemLabel>{language.t("common.delete")}</DropdownMenu.ItemLabel>
|
||||||
</DropdownMenu.Item>
|
</DropdownMenu.Item>
|
||||||
</DropdownMenu.Content>
|
</DropdownMenu.Content>
|
||||||
</DropdownMenu.Portal>
|
</DropdownMenu.Portal>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
<TooltipKeybind placement="right" title="New session" keybind={command.keybind("session.new")}>
|
<TooltipKeybind
|
||||||
|
placement="right"
|
||||||
|
title={language.t("command.session.new")}
|
||||||
|
keybind={command.keybind("session.new")}
|
||||||
|
>
|
||||||
<IconButton
|
<IconButton
|
||||||
icon="plus-small"
|
icon="plus-small"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@ -1799,7 +1814,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
icon="edit"
|
icon="edit"
|
||||||
class="hidden _flex w-full text-left justify-start text-text-base rounded-md px-3"
|
class="hidden _flex w-full text-left justify-start text-text-base rounded-md px-3"
|
||||||
>
|
>
|
||||||
New session
|
{language.t("command.session.new")}
|
||||||
</Button>
|
</Button>
|
||||||
<Show when={loading()}>
|
<Show when={loading()}>
|
||||||
<SessionSkeleton />
|
<SessionSkeleton />
|
||||||
@ -1818,7 +1833,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
;(e.currentTarget as HTMLButtonElement).blur()
|
;(e.currentTarget as HTMLButtonElement).blur()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Load more
|
{language.t("common.loadMore")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
@ -1842,7 +1857,8 @@ export default function Layout(props: ParentProps) {
|
|||||||
|
|
||||||
const label = (directory: string) => {
|
const label = (directory: string) => {
|
||||||
const [data] = globalSync.child(directory)
|
const [data] = globalSync.child(directory)
|
||||||
const kind = directory === props.project.worktree ? "local" : "sandbox"
|
const kind =
|
||||||
|
directory === props.project.worktree ? language.t("workspace.type.local") : language.t("workspace.type.sandbox")
|
||||||
const name = workspaceLabel(directory, data.vcs?.branch, props.project.id)
|
const name = workspaceLabel(directory, data.vcs?.branch, props.project.id)
|
||||||
return `${kind} : ${name}`
|
return `${kind} : ${name}`
|
||||||
}
|
}
|
||||||
@ -1893,9 +1909,9 @@ export default function Layout(props: ParentProps) {
|
|||||||
trigger={trigger}
|
trigger={trigger}
|
||||||
onOpenChange={setOpen}
|
onOpenChange={setOpen}
|
||||||
>
|
>
|
||||||
<div class="-m-3 p-2 flex flex-col w-72">
|
<div class="-m-3 p-2 flex flex-col w-72">
|
||||||
<div class="px-4 pt-2 pb-1 text-14-medium text-text-strong truncate">{displayName(props.project)}</div>
|
<div class="px-4 pt-2 pb-1 text-14-medium text-text-strong truncate">{displayName(props.project)}</div>
|
||||||
<div class="px-4 pb-2 text-12-medium text-text-weak">Recent sessions</div>
|
<div class="px-4 pb-2 text-12-medium text-text-weak">{language.t("sidebar.project.recentSessions")}</div>
|
||||||
<div class="px-2 pb-2 flex flex-col gap-2">
|
<div class="px-2 pb-2 flex flex-col gap-2">
|
||||||
<Show
|
<Show
|
||||||
when={workspaceEnabled()}
|
when={workspaceEnabled()}
|
||||||
@ -1951,7 +1967,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
navigateToProject(props.project.worktree)
|
navigateToProject(props.project.worktree)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
View all sessions
|
{language.t("sidebar.project.viewAllSessions")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -2002,7 +2018,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
;(e.currentTarget as HTMLButtonElement).blur()
|
;(e.currentTarget as HTMLButtonElement).blur()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Load more
|
{language.t("common.loadMore")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
@ -2033,7 +2049,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
.then((x) => x.data)
|
.then((x) => x.data)
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
showToast({
|
showToast({
|
||||||
title: "Failed to create workspace",
|
title: language.t("workspace.create.failed.title"),
|
||||||
description: errorMessage(err),
|
description: errorMessage(err),
|
||||||
})
|
})
|
||||||
return undefined
|
return undefined
|
||||||
@ -2080,7 +2096,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
placement={sidebarProps.mobile ? "bottom" : "right"}
|
placement={sidebarProps.mobile ? "bottom" : "right"}
|
||||||
value={
|
value={
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span>Open project</span>
|
<span>{language.t("command.project.open")}</span>
|
||||||
<Show when={!sidebarProps.mobile}>
|
<Show when={!sidebarProps.mobile}>
|
||||||
<span class="text-icon-base text-12-medium">{command.keybind("project.open")}</span>
|
<span class="text-icon-base text-12-medium">{command.keybind("project.open")}</span>
|
||||||
</Show>
|
</Show>
|
||||||
@ -2098,12 +2114,12 @@ export default function Layout(props: ParentProps) {
|
|||||||
<div class="shrink-0 w-full pt-3 pb-3 flex flex-col items-center gap-2">
|
<div class="shrink-0 w-full pt-3 pb-3 flex flex-col items-center gap-2">
|
||||||
<TooltipKeybind
|
<TooltipKeybind
|
||||||
placement={sidebarProps.mobile ? "bottom" : "right"}
|
placement={sidebarProps.mobile ? "bottom" : "right"}
|
||||||
title="Settings"
|
title={language.t("sidebar.settings")}
|
||||||
keybind={command.keybind("settings.open")}
|
keybind={command.keybind("settings.open")}
|
||||||
>
|
>
|
||||||
<IconButton icon="settings-gear" variant="ghost" size="large" onClick={openSettings} />
|
<IconButton icon="settings-gear" variant="ghost" size="large" onClick={openSettings} />
|
||||||
</TooltipKeybind>
|
</TooltipKeybind>
|
||||||
<Tooltip placement={sidebarProps.mobile ? "bottom" : "right"} value="Help">
|
<Tooltip placement={sidebarProps.mobile ? "bottom" : "right"} value={language.t("sidebar.help")}>
|
||||||
<IconButton
|
<IconButton
|
||||||
icon="help"
|
icon="help"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@ -2161,20 +2177,22 @@ export default function Layout(props: ParentProps) {
|
|||||||
class="shrink-0 size-6 rounded-md opacity-0 group-hover/project:opacity-100 data-[expanded]:opacity-100 data-[expanded]:bg-surface-base-active"
|
class="shrink-0 size-6 rounded-md opacity-0 group-hover/project:opacity-100 data-[expanded]:opacity-100 data-[expanded]:bg-surface-base-active"
|
||||||
/>
|
/>
|
||||||
<DropdownMenu.Portal>
|
<DropdownMenu.Portal>
|
||||||
<DropdownMenu.Content class="mt-1">
|
<DropdownMenu.Content class="mt-1">
|
||||||
<DropdownMenu.Item onSelect={() => dialog.show(() => <DialogEditProject project={p} />)}>
|
<DropdownMenu.Item onSelect={() => dialog.show(() => <DialogEditProject project={p} />)}>
|
||||||
<DropdownMenu.ItemLabel>Edit</DropdownMenu.ItemLabel>
|
<DropdownMenu.ItemLabel>{language.t("common.edit")}</DropdownMenu.ItemLabel>
|
||||||
</DropdownMenu.Item>
|
</DropdownMenu.Item>
|
||||||
<DropdownMenu.Item onSelect={() => layout.sidebar.toggleWorkspaces(p.worktree)}>
|
<DropdownMenu.Item onSelect={() => layout.sidebar.toggleWorkspaces(p.worktree)}>
|
||||||
<DropdownMenu.ItemLabel>
|
<DropdownMenu.ItemLabel>
|
||||||
{layout.sidebar.workspaces(p.worktree)() ? "Disable workspaces" : "Enable workspaces"}
|
{layout.sidebar.workspaces(p.worktree)()
|
||||||
</DropdownMenu.ItemLabel>
|
? language.t("sidebar.workspaces.disable")
|
||||||
</DropdownMenu.Item>
|
: language.t("sidebar.workspaces.enable")}
|
||||||
<DropdownMenu.Separator />
|
</DropdownMenu.ItemLabel>
|
||||||
<DropdownMenu.Item onSelect={() => closeProject(p.worktree)}>
|
</DropdownMenu.Item>
|
||||||
<DropdownMenu.ItemLabel>Close</DropdownMenu.ItemLabel>
|
<DropdownMenu.Separator />
|
||||||
</DropdownMenu.Item>
|
<DropdownMenu.Item onSelect={() => closeProject(p.worktree)}>
|
||||||
</DropdownMenu.Content>
|
<DropdownMenu.ItemLabel>{language.t("common.close")}</DropdownMenu.ItemLabel>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
</DropdownMenu.Content>
|
||||||
</DropdownMenu.Portal>
|
</DropdownMenu.Portal>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
@ -2185,7 +2203,11 @@ export default function Layout(props: ParentProps) {
|
|||||||
fallback={
|
fallback={
|
||||||
<>
|
<>
|
||||||
<div class="py-4 px-3">
|
<div class="py-4 px-3">
|
||||||
<TooltipKeybind title="New session" keybind={command.keybind("session.new")} placement="top">
|
<TooltipKeybind
|
||||||
|
title={language.t("command.session.new")}
|
||||||
|
keybind={command.keybind("session.new")}
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
<Button
|
<Button
|
||||||
size="large"
|
size="large"
|
||||||
icon="plus-small"
|
icon="plus-small"
|
||||||
@ -2195,7 +2217,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
layout.mobileSidebar.hide()
|
layout.mobileSidebar.hide()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
New session
|
{language.t("command.session.new")}
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipKeybind>
|
</TooltipKeybind>
|
||||||
</div>
|
</div>
|
||||||
@ -2208,12 +2230,12 @@ export default function Layout(props: ParentProps) {
|
|||||||
<>
|
<>
|
||||||
<div class="py-4 px-3">
|
<div class="py-4 px-3">
|
||||||
<TooltipKeybind
|
<TooltipKeybind
|
||||||
title="New workspace"
|
title={language.t("workspace.new")}
|
||||||
keybind={command.keybind("workspace.new")}
|
keybind={command.keybind("workspace.new")}
|
||||||
placement="top"
|
placement="top"
|
||||||
>
|
>
|
||||||
<Button size="large" icon="plus-small" class="w-full" onClick={createWorkspace}>
|
<Button size="large" icon="plus-small" class="w-full" onClick={createWorkspace}>
|
||||||
New workspace
|
{language.t("workspace.new")}
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipKeybind>
|
</TooltipKeybind>
|
||||||
</div>
|
</div>
|
||||||
@ -2255,9 +2277,9 @@ export default function Layout(props: ParentProps) {
|
|||||||
<div class="shrink-0 px-2 py-3 border-t border-border-weak-base">
|
<div class="shrink-0 px-2 py-3 border-t border-border-weak-base">
|
||||||
<div class="rounded-md bg-background-base shadow-xs-border-base">
|
<div class="rounded-md bg-background-base shadow-xs-border-base">
|
||||||
<div class="p-3 flex flex-col gap-2">
|
<div class="p-3 flex flex-col gap-2">
|
||||||
<div class="text-12-medium text-text-strong">Getting started</div>
|
<div class="text-12-medium text-text-strong">{language.t("sidebar.gettingStarted.title")}</div>
|
||||||
<div class="text-text-base">OpenCode includes free models so you can start immediately.</div>
|
<div class="text-text-base">{language.t("sidebar.gettingStarted.line1")}</div>
|
||||||
<div class="text-text-base">Connect any provider to use models, inc. Claude, GPT, Gemini etc.</div>
|
<div class="text-text-base">{language.t("sidebar.gettingStarted.line2")}</div>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
class="flex w-full text-left justify-start text-12-medium text-text-strong stroke-[1.5px] rounded-md rounded-t-none shadow-none border-t border-border-weak-base px-3"
|
class="flex w-full text-left justify-start text-12-medium text-text-strong stroke-[1.5px] rounded-md rounded-t-none shadow-none border-t border-border-weak-base px-3"
|
||||||
@ -2265,7 +2287,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
icon="plus"
|
icon="plus"
|
||||||
onClick={connectProvider}
|
onClick={connectProvider}
|
||||||
>
|
>
|
||||||
Connect provider
|
{language.t("command.provider.connect")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1205,7 +1205,7 @@ export default function Page() {
|
|||||||
classes={{ button: "w-full" }}
|
classes={{ button: "w-full" }}
|
||||||
onClick={() => setStore("mobileTab", "session")}
|
onClick={() => setStore("mobileTab", "session")}
|
||||||
>
|
>
|
||||||
Session
|
{language.t("session.tab.session")}
|
||||||
</Tabs.Trigger>
|
</Tabs.Trigger>
|
||||||
<Tabs.Trigger
|
<Tabs.Trigger
|
||||||
value="review"
|
value="review"
|
||||||
@ -1214,8 +1214,10 @@ export default function Page() {
|
|||||||
onClick={() => setStore("mobileTab", "review")}
|
onClick={() => setStore("mobileTab", "review")}
|
||||||
>
|
>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Match when={hasReview()}>{reviewCount()} Files Changed</Match>
|
<Match when={hasReview()}>
|
||||||
<Match when={true}>Review</Match>
|
{language.t("session.review.filesChanged", { count: reviewCount() })}
|
||||||
|
</Match>
|
||||||
|
<Match when={true}>{language.t("session.tab.review")}</Match>
|
||||||
</Switch>
|
</Switch>
|
||||||
</Tabs.Trigger>
|
</Tabs.Trigger>
|
||||||
</Tabs.List>
|
</Tabs.List>
|
||||||
@ -1245,7 +1247,9 @@ export default function Page() {
|
|||||||
<Match when={hasReview()}>
|
<Match when={hasReview()}>
|
||||||
<Show
|
<Show
|
||||||
when={diffsReady()}
|
when={diffsReady()}
|
||||||
fallback={<div class="px-4 py-4 text-text-weak">Loading changes...</div>}
|
fallback={
|
||||||
|
<div class="px-4 py-4 text-text-weak">{language.t("session.review.loadingChanges")}</div>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<SessionReviewTab
|
<SessionReviewTab
|
||||||
diffs={diffs}
|
diffs={diffs}
|
||||||
@ -1265,13 +1269,13 @@ export default function Page() {
|
|||||||
</Show>
|
</Show>
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={true}>
|
<Match when={true}>
|
||||||
<div class="h-full px-4 pb-30 flex flex-col items-center justify-center text-center gap-6">
|
<div class="h-full px-4 pb-30 flex flex-col items-center justify-center text-center gap-6">
|
||||||
<Mark class="w-14 opacity-10" />
|
<Mark class="w-14 opacity-10" />
|
||||||
<div class="text-14-regular text-text-weak max-w-56">No changes in this session yet</div>
|
<div class="text-14-regular text-text-weak max-w-56">{language.t("session.review.empty")}</div>
|
||||||
</div>
|
</div>
|
||||||
</Match>
|
</Match>
|
||||||
</Switch>
|
</Switch>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div class="relative w-full h-full min-w-0">
|
<div class="relative w-full h-full min-w-0">
|
||||||
@ -1334,7 +1338,7 @@ export default function Page() {
|
|||||||
class="text-12-medium opacity-50"
|
class="text-12-medium opacity-50"
|
||||||
onClick={() => setStore("turnStart", 0)}
|
onClick={() => setStore("turnStart", 0)}
|
||||||
>
|
>
|
||||||
Render earlier messages
|
{language.t("session.messages.renderEarlier")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
@ -1352,7 +1356,9 @@ export default function Page() {
|
|||||||
sync.session.history.loadMore(id)
|
sync.session.history.loadMore(id)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{historyLoading() ? "Loading earlier messages..." : "Load earlier messages"}
|
{historyLoading()
|
||||||
|
? language.t("session.messages.loadingEarlier")
|
||||||
|
: language.t("session.messages.loadEarlier")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
@ -1436,7 +1442,7 @@ export default function Page() {
|
|||||||
when={prompt.ready()}
|
when={prompt.ready()}
|
||||||
fallback={
|
fallback={
|
||||||
<div class="w-full min-h-32 md:min-h-40 rounded-md border border-border-weak-base bg-background-base/50 px-4 py-3 text-text-weak whitespace-pre-wrap pointer-events-none">
|
<div class="w-full min-h-32 md:min-h-40 rounded-md border border-border-weak-base bg-background-base/50 px-4 py-3 text-text-weak whitespace-pre-wrap pointer-events-none">
|
||||||
{handoff.prompt || "Loading prompt..."}
|
{handoff.prompt || language.t("prompt.loading")}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@ -1482,11 +1488,11 @@ export default function Page() {
|
|||||||
<Show when={diffs()}>
|
<Show when={diffs()}>
|
||||||
<DiffChanges changes={diffs()} variant="bars" />
|
<DiffChanges changes={diffs()} variant="bars" />
|
||||||
</Show>
|
</Show>
|
||||||
<div class="flex items-center gap-1.5">
|
<div class="flex items-center gap-1.5">
|
||||||
<div>Review</div>
|
<div>{language.t("session.tab.review")}</div>
|
||||||
<Show when={info()?.summary?.files}>
|
<Show when={info()?.summary?.files}>
|
||||||
<div class="text-12-medium text-text-strong h-4 px-2 flex flex-col items-center justify-center rounded-full bg-surface-base">
|
<div class="text-12-medium text-text-strong h-4 px-2 flex flex-col items-center justify-center rounded-full bg-surface-base">
|
||||||
{info()?.summary?.files ?? 0}
|
{info()?.summary?.files ?? 0}
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
@ -1497,7 +1503,7 @@ export default function Page() {
|
|||||||
<Tabs.Trigger
|
<Tabs.Trigger
|
||||||
value="context"
|
value="context"
|
||||||
closeButton={
|
closeButton={
|
||||||
<Tooltip value="Close tab" placement="bottom">
|
<Tooltip value={language.t("common.closeTab")} placement="bottom">
|
||||||
<IconButton icon="close" variant="ghost" onClick={() => tabs().close("context")} />
|
<IconButton icon="close" variant="ghost" onClick={() => tabs().close("context")} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
}
|
}
|
||||||
@ -1506,7 +1512,7 @@ export default function Page() {
|
|||||||
>
|
>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<SessionContextUsage variant="indicator" />
|
<SessionContextUsage variant="indicator" />
|
||||||
<div>Context</div>
|
<div>{language.t("session.tab.context")}</div>
|
||||||
</div>
|
</div>
|
||||||
</Tabs.Trigger>
|
</Tabs.Trigger>
|
||||||
</Show>
|
</Show>
|
||||||
@ -1515,7 +1521,7 @@ export default function Page() {
|
|||||||
</SortableProvider>
|
</SortableProvider>
|
||||||
<div class="bg-background-base h-full flex items-center justify-center border-b border-border-weak-base px-3">
|
<div class="bg-background-base h-full flex items-center justify-center border-b border-border-weak-base px-3">
|
||||||
<TooltipKeybind
|
<TooltipKeybind
|
||||||
title="Open file"
|
title={language.t("command.file.open")}
|
||||||
keybind={command.keybind("file.open")}
|
keybind={command.keybind("file.open")}
|
||||||
class="flex items-center"
|
class="flex items-center"
|
||||||
>
|
>
|
||||||
@ -1537,7 +1543,9 @@ export default function Page() {
|
|||||||
<Match when={hasReview()}>
|
<Match when={hasReview()}>
|
||||||
<Show
|
<Show
|
||||||
when={diffsReady()}
|
when={diffsReady()}
|
||||||
fallback={<div class="px-6 py-4 text-text-weak">Loading changes...</div>}
|
fallback={
|
||||||
|
<div class="px-6 py-4 text-text-weak">{language.t("session.review.loadingChanges")}</div>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<SessionReviewTab
|
<SessionReviewTab
|
||||||
diffs={diffs}
|
diffs={diffs}
|
||||||
@ -1553,13 +1561,13 @@ export default function Page() {
|
|||||||
</Show>
|
</Show>
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={true}>
|
<Match when={true}>
|
||||||
<div class="h-full px-6 pb-30 flex flex-col items-center justify-center text-center gap-6">
|
<div class="h-full px-6 pb-30 flex flex-col items-center justify-center text-center gap-6">
|
||||||
<Mark class="w-14 opacity-10" />
|
<Mark class="w-14 opacity-10" />
|
||||||
<div class="text-14-regular text-text-weak max-w-56">No changes in this session yet</div>
|
<div class="text-14-regular text-text-weak max-w-56">{language.t("session.review.empty")}</div>
|
||||||
</div>
|
</div>
|
||||||
</Match>
|
</Match>
|
||||||
</Switch>
|
</Switch>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
</Tabs.Content>
|
</Tabs.Content>
|
||||||
</Show>
|
</Show>
|
||||||
@ -1735,7 +1743,9 @@ export default function Page() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon name="plus-small" size="small" />
|
<Icon name="plus-small" size="small" />
|
||||||
<span>Add {selectionLabel()} to context</span>
|
<span>
|
||||||
|
{language.t("session.context.addToContext", { selection: selectionLabel() ?? "" })}
|
||||||
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -1792,7 +1802,7 @@ export default function Page() {
|
|||||||
/>
|
/>
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={state()?.loading}>
|
<Match when={state()?.loading}>
|
||||||
<div class="px-6 py-4 text-text-weak">Loading...</div>
|
<div class="px-6 py-4 text-text-weak">{language.t("common.loading")}...</div>
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={state()?.error}>
|
<Match when={state()?.error}>
|
||||||
{(err) => <div class="px-6 py-4 text-text-weak">{err()}</div>}
|
{(err) => <div class="px-6 py-4 text-text-weak">{err()}</div>}
|
||||||
@ -1847,13 +1857,13 @@ export default function Page() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
<div class="flex-1" />
|
<div class="flex-1" />
|
||||||
<div class="text-text-weak pr-2">Loading...</div>
|
<div class="text-text-weak pr-2">{language.t("common.loading")}...</div>
|
||||||
</div>
|
|
||||||
<div class="flex-1 flex items-center justify-center text-text-weak">Loading terminal...</div>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
<div class="flex-1 flex items-center justify-center text-text-weak">{language.t("terminal.loading")}</div>
|
||||||
>
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
<DragDropProvider
|
<DragDropProvider
|
||||||
onDragStart={handleTerminalDragStart}
|
onDragStart={handleTerminalDragStart}
|
||||||
onDragEnd={handleTerminalDragEnd}
|
onDragEnd={handleTerminalDragEnd}
|
||||||
@ -1869,7 +1879,7 @@ export default function Page() {
|
|||||||
</SortableProvider>
|
</SortableProvider>
|
||||||
<div class="h-full flex items-center justify-center">
|
<div class="h-full flex items-center justify-center">
|
||||||
<TooltipKeybind
|
<TooltipKeybind
|
||||||
title="New terminal"
|
title={language.t("command.terminal.new")}
|
||||||
keybind={command.keybind("terminal.new")}
|
keybind={command.keybind("terminal.new")}
|
||||||
class="flex items-center"
|
class="flex items-center"
|
||||||
>
|
>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user