diff --git a/packages/app/src/app/components/workspace-picker.tsx b/packages/app/src/app/components/workspace-picker.tsx
index b754d0db..ea25d519 100644
--- a/packages/app/src/app/components/workspace-picker.tsx
+++ b/packages/app/src/app/components/workspace-picker.tsx
@@ -1,6 +1,6 @@
import { For, Show, createEffect, createMemo } from "solid-js";
-import { Check, Globe, Loader2, Plus, Search, Trash2, Upload } from "lucide-solid";
+import { Check, Globe, Loader2, Plus, Search, Trash2, Upload, X } from "lucide-solid";
import { t, currentLocale } from "../../i18n";
import type { WorkspaceInfo } from "../lib/tauri";
@@ -35,6 +35,17 @@ export default function WorkspacePicker(props: {
});
const totalCount = createMemo(() => props.workspaces.length);
+ const filteredCount = createMemo(() => filtered().length);
+ const query = createMemo(() => props.search.trim());
+ const hasSearch = createMemo(() => query().length > 0);
+ const countLabel = createMemo(() => (totalCount() ? `${filteredCount()}/${totalCount()}` : `${filteredCount()}`));
+ const emptyTitle = createMemo(() => {
+ if (!totalCount()) return translate("dashboard.workspaces_empty");
+ if (hasSearch()) {
+ return translate("dashboard.workspaces_no_results").replace("{query}", query());
+ }
+ return translate("dashboard.workspaces_empty");
+ });
let searchInputRef: HTMLInputElement | undefined;
createEffect(() => {
@@ -62,90 +73,123 @@ export default function WorkspacePicker(props: {
placeholder={translate("dashboard.find_workspace")}
value={props.search}
onInput={(e) => props.onSearch(e.currentTarget.value)}
- class="w-full bg-gray-1 border border-gray-6 rounded-lg py-1.5 pl-9 pr-3 text-sm text-gray-12 focus:outline-none focus:border-gray-7"
+ class="w-full bg-gray-1 border border-gray-6 rounded-lg py-1.5 pl-9 pr-9 text-sm text-gray-12 focus:outline-none focus:border-gray-7"
/>
+
+
+
- {translate("dashboard.workspaces")} ({totalCount()})
+ {translate("dashboard.workspaces")} ({countLabel()})
-
- {(ws) => (
-
-
-
-
+ {translate("dashboard.workspaces_clear_search")}
+
-
-
+
+
+ {translate("dashboard.workspaces_empty_hint")}
+
- {
- event.stopPropagation();
- props.onForget(ws.id);
- }}
- class="p-1 rounded-md text-gray-9 hover:text-gray-12 hover:bg-gray-3 transition-colors"
- title={translate("dashboard.forget_workspace")}
- >
-
-
- )}
-
+ }
+ >
+
+ {(ws) => (
+
+
{
+ const result = props.onSelect(ws.id);
+ if (result instanceof Promise) {
+ result.then((ok) => {
+ if (ok !== false) props.onClose();
+ });
+ return;
+ }
+ if (result !== false) props.onClose();
+ }}
+ class="flex-1 text-left min-w-0"
+ >
+
+
{ws.name}
+
+
+
+ {translate("dashboard.remote")}
+
+
+ {ws.remoteType === "openwork"
+ ? translate("dashboard.remote_connection_openwork")
+ : translate("dashboard.remote_connection_direct")}
+
+
+
+
+ {ws.workspaceType === "remote"
+ ? ws.remoteType === "openwork"
+ ? ws.openworkHostUrl ?? ws.baseUrl ?? ws.path
+ : ws.baseUrl ?? ws.path
+ : ws.path}
+
+
+
+ {ws.openworkWorkspaceName ?? ws.directory}
+
+
+
+
+
+
+
+
+
+
{
+ event.stopPropagation();
+ props.onForget(ws.id);
+ }}
+ class="p-1 rounded-md text-gray-9 hover:text-gray-12 hover:bg-gray-3 transition-colors"
+ title={translate("dashboard.forget_workspace")}
+ >
+
+
+
+ )}
+
+
diff --git a/packages/app/src/app/pages/dashboard.tsx b/packages/app/src/app/pages/dashboard.tsx
index c6b32b1d..5560c3f6 100644
--- a/packages/app/src/app/pages/dashboard.tsx
+++ b/packages/app/src/app/pages/dashboard.tsx
@@ -825,6 +825,7 @@ export default function DashboardView(props: DashboardViewProps) {
refreshJobs={props.refreshScheduledJobs}
deleteJob={props.deleteScheduledJob}
isWindows={props.isWindows}
+ openPlugins={() => props.setTab("plugins")}
/>
diff --git a/packages/app/src/app/pages/scheduled.tsx b/packages/app/src/app/pages/scheduled.tsx
index f9e2225a..8221599a 100644
--- a/packages/app/src/app/pages/scheduled.tsx
+++ b/packages/app/src/app/pages/scheduled.tsx
@@ -7,6 +7,7 @@ import Button from "../components/button";
import {
Calendar,
Clock,
+ Cpu,
FolderOpen,
RefreshCw,
Terminal,
@@ -23,6 +24,7 @@ export type ScheduledTasksViewProps = {
refreshJobs: (options?: { force?: boolean }) => void;
deleteJob: (name: string) => Promise | void;
isWindows: boolean;
+ openPlugins: () => void;
};
const toRelative = (value?: string | null) => {
@@ -194,9 +196,15 @@ export default function ScheduledTasksView(props: ScheduledTasksViewProps) {
- No scheduled tasks yet. Add the opencode-scheduler plugin and create a job to
- see it here.
+
+
+ No scheduled tasks yet. Add the opencode-scheduler plugin and create a job to
+ see it here.
+
+
+
+ Open plugins
+
}
>
diff --git a/packages/app/src/i18n/locales/en.ts b/packages/app/src/i18n/locales/en.ts
index f9fa7fd5..8ca2c5bc 100644
--- a/packages/app/src/i18n/locales/en.ts
+++ b/packages/app/src/i18n/locales/en.ts
@@ -16,6 +16,10 @@ export default {
"dashboard.runs": "Runs",
"dashboard.find_workspace": "Find workspace...",
"dashboard.workspaces": "Workspaces",
+ "dashboard.workspaces_empty": "No workspaces yet.",
+ "dashboard.workspaces_empty_hint": "Create or import a workspace to get started.",
+ "dashboard.workspaces_no_results": "No matches for \"{query}\".",
+ "dashboard.workspaces_clear_search": "Clear search",
"dashboard.new_workspace": "New Workspace...",
"dashboard.new_remote_workspace": "Add Remote Workspace...",
"dashboard.forget_workspace": "Forget workspace",
diff --git a/packages/app/src/i18n/locales/zh.ts b/packages/app/src/i18n/locales/zh.ts
index 246b8425..3a26f84b 100644
--- a/packages/app/src/i18n/locales/zh.ts
+++ b/packages/app/src/i18n/locales/zh.ts
@@ -16,6 +16,10 @@ export default {
"dashboard.runs": "运行",
"dashboard.find_workspace": "查找工作区...",
"dashboard.workspaces": "工作区",
+ "dashboard.workspaces_empty": "还没有工作区。",
+ "dashboard.workspaces_empty_hint": "创建或导入一个工作区以开始使用。",
+ "dashboard.workspaces_no_results": "没有匹配“{query}”的工作区。",
+ "dashboard.workspaces_clear_search": "清除搜索",
"dashboard.new_workspace": "新建工作区...",
"dashboard.new_remote_workspace": "添加远程工作区...",
"dashboard.forget_workspace": "忘记工作区",