ikanban-web 0.2.9 → 0.2.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/{ghostty-web-CveYg7XI.js → ghostty-web-BOsxUEnx.js} +1 -1
- package/dist/assets/home-DmwjxlRp.js +1 -0
- package/dist/assets/{index-BlcL7wgr.js → index-CE2ZqGRp.js} +103 -103
- package/dist/assets/index-Z4u1EVXA.css +1 -0
- package/dist/assets/session-FqpMK7PV.js +24 -0
- package/dist/index.html +2 -2
- package/package.json +2 -2
- package/src/app.tsx +82 -57
- package/src/components/dialog-command-palette.tsx +70 -0
- package/src/components/dialog-fork.tsx +1 -1
- package/src/components/dialog-select-file.tsx +1 -1
- package/src/components/prompt-input/submit.ts +1 -1
- package/src/components/titlebar.tsx +12 -1
- package/src/context/notification.tsx +2 -2
- package/src/i18n/en.ts +173 -83
- package/src/i18n/zh.ts +98 -47
- package/src/pages/directory-layout.tsx +2 -2
- package/src/pages/home.tsx +374 -89
- package/src/pages/layout/sidebar-items.tsx +3 -3
- package/src/pages/layout/sidebar-workspace.tsx +1 -1
- package/src/pages/layout.tsx +175 -237
- package/src/pages/session/message-timeline.tsx +6 -5
- package/src/pages/session/use-session-commands.tsx +1 -1
- package/src/pages/session.tsx +2 -1
- package/dist/assets/home-OHGVA1hY.js +0 -1
- package/dist/assets/index-B0TEjAod.css +0 -1
- package/dist/assets/session-CdIG8nTN.js +0 -24
package/src/pages/home.tsx
CHANGED
|
@@ -1,56 +1,188 @@
|
|
|
1
|
-
import { createMemo, For, Match, Switch } from "solid-js"
|
|
2
|
-
import { Button } from "ikanban-ui/button"
|
|
3
|
-
import { Logo } from "ikanban-ui/logo"
|
|
4
|
-
import { useLayout } from "@/context/layout"
|
|
5
|
-
import { useNavigate } from "@solidjs/router"
|
|
6
|
-
import { base64Encode } from "ikanban-utils/encode"
|
|
7
|
-
import { Icon } from "ikanban-ui/icon"
|
|
8
|
-
import { usePlatform } from "@/context/platform"
|
|
9
|
-
import { DateTime } from "luxon"
|
|
10
|
-
import { useDialog } from "ikanban-ui/context/dialog"
|
|
11
|
-
import { DialogSelectDirectory } from "@/components/dialog-select-directory"
|
|
12
|
-
import { DialogSelectServer } from "@/components/dialog-select-server"
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
1
|
+
import { createMemo, createEffect, createResource, For, Match, Switch } from "solid-js";
|
|
2
|
+
import { Button } from "ikanban-ui/button";
|
|
3
|
+
import { Logo } from "ikanban-ui/logo";
|
|
4
|
+
import { useLayout } from "@/context/layout";
|
|
5
|
+
import { useNavigate } from "@solidjs/router";
|
|
6
|
+
import { base64Encode } from "ikanban-utils/encode";
|
|
7
|
+
import { Icon } from "ikanban-ui/icon";
|
|
8
|
+
import { usePlatform } from "@/context/platform";
|
|
9
|
+
import { DateTime } from "luxon";
|
|
10
|
+
import { useDialog } from "ikanban-ui/context/dialog";
|
|
11
|
+
import { DialogSelectDirectory } from "@/components/dialog-select-directory";
|
|
12
|
+
import { DialogSelectServer } from "@/components/dialog-select-server";
|
|
13
|
+
import { useGlobalSDK } from "@/context/global-sdk";
|
|
14
|
+
import { useServer } from "@/context/server";
|
|
15
|
+
import { useGlobalSync } from "@/context/global-sync";
|
|
16
|
+
import { useLanguage } from "@/context/language";
|
|
17
|
+
import { IconButton } from "ikanban-ui/icon-button";
|
|
18
|
+
import type { Session } from "@opencode-ai/sdk/v2/client";
|
|
19
|
+
|
|
20
|
+
type BoardColumn = "progress" | "idle";
|
|
21
|
+
|
|
22
|
+
type BoardCard = {
|
|
23
|
+
session: Session;
|
|
24
|
+
projectDirectory: string;
|
|
25
|
+
updatedAt: number;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const homeStyles = {
|
|
29
|
+
border: { border: "1px solid var(--border-weak-base)" },
|
|
30
|
+
heroIcon: {
|
|
31
|
+
border: "1px solid var(--border-weak-base)",
|
|
32
|
+
background:
|
|
33
|
+
"linear-gradient(135deg, color-mix(in srgb, var(--text-interactive-base) 12%, var(--background-stronger)), var(--background-stronger))",
|
|
34
|
+
},
|
|
35
|
+
emptyPanel: {
|
|
36
|
+
"border-color": "var(--border-weak-base)",
|
|
37
|
+
background:
|
|
38
|
+
"linear-gradient(180deg, color-mix(in srgb, var(--surface-base-hover) 35%, var(--background-stronger)), var(--background-stronger))",
|
|
39
|
+
},
|
|
40
|
+
emptyIcon: {
|
|
41
|
+
border: "1px solid var(--border-weak-base)",
|
|
42
|
+
background:
|
|
43
|
+
"linear-gradient(135deg, color-mix(in srgb, var(--text-interactive-base) 10%, var(--surface-inset-base)), var(--surface-inset-base))",
|
|
44
|
+
},
|
|
45
|
+
card: {
|
|
46
|
+
border: "1px solid var(--border-weak-base)",
|
|
47
|
+
background: "var(--background-base)",
|
|
48
|
+
},
|
|
49
|
+
emptyState: {
|
|
50
|
+
"border-color": "var(--border-weak-base)",
|
|
51
|
+
background:
|
|
52
|
+
"linear-gradient(180deg, color-mix(in srgb, var(--surface-base-hover) 30%, var(--surface-inset-base)), var(--surface-inset-base))",
|
|
53
|
+
},
|
|
54
|
+
} as const;
|
|
16
55
|
|
|
17
56
|
export default function Home() {
|
|
18
|
-
const sync = useGlobalSync()
|
|
19
|
-
const layout = useLayout()
|
|
20
|
-
const platform = usePlatform()
|
|
21
|
-
const dialog = useDialog()
|
|
22
|
-
const navigate = useNavigate()
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
const
|
|
57
|
+
const sync = useGlobalSync();
|
|
58
|
+
const layout = useLayout();
|
|
59
|
+
const platform = usePlatform();
|
|
60
|
+
const dialog = useDialog();
|
|
61
|
+
const navigate = useNavigate();
|
|
62
|
+
const globalSDK = useGlobalSDK();
|
|
63
|
+
const server = useServer();
|
|
64
|
+
const language = useLanguage();
|
|
65
|
+
const projects = createMemo(() => {
|
|
27
66
|
return sync.data.project
|
|
28
67
|
.slice()
|
|
29
|
-
.sort(
|
|
30
|
-
|
|
31
|
-
|
|
68
|
+
.sort(
|
|
69
|
+
(a, b) =>
|
|
70
|
+
(b.time.updated ?? b.time.created) -
|
|
71
|
+
(a.time.updated ?? a.time.created),
|
|
72
|
+
);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const [rootSessions, { mutate: mutateRootSessions }] = createResource(
|
|
76
|
+
() => projects().map((project) => project.worktree),
|
|
77
|
+
async (directories) => {
|
|
78
|
+
const entries = await Promise.all(
|
|
79
|
+
directories.map(async (directory) => {
|
|
80
|
+
const result = await globalSDK.client.session.list({
|
|
81
|
+
directory,
|
|
82
|
+
roots: true,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const sessions = (result.data ?? []).filter(
|
|
86
|
+
(session): session is Session => !!session?.id && !session.time?.archived,
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
return [directory, sessions] as const;
|
|
90
|
+
}),
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
return Object.fromEntries(entries) as Record<string, Session[]>;
|
|
94
|
+
},
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
const boardColumns = createMemo<Record<BoardColumn, BoardCard[]>>(() => {
|
|
98
|
+
const now = Date.now();
|
|
99
|
+
const columns: Record<BoardColumn, BoardCard[]> = {
|
|
100
|
+
progress: [],
|
|
101
|
+
idle: [],
|
|
102
|
+
};
|
|
103
|
+
const sessionsByProject = rootSessions() ?? {};
|
|
104
|
+
|
|
105
|
+
for (const project of projects()) {
|
|
106
|
+
const [store] = sync.child(project.worktree);
|
|
107
|
+
const sessions = sessionsByProject[project.worktree] ?? [];
|
|
108
|
+
|
|
109
|
+
for (const session of sessions) {
|
|
110
|
+
const status = store.session_status[session.id];
|
|
111
|
+
const updatedAt = session.time.updated ?? session.time.created;
|
|
112
|
+
const card = {
|
|
113
|
+
session,
|
|
114
|
+
projectDirectory: project.worktree,
|
|
115
|
+
updatedAt,
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
if (status?.type === "busy" || status?.type === "retry") {
|
|
119
|
+
columns.progress.push(card);
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
columns.idle.push(card);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
for (const key of Object.keys(columns) as BoardColumn[]) {
|
|
128
|
+
columns[key].sort((a, b) => b.updatedAt - a.updatedAt);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return columns;
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
createEffect(() => {
|
|
135
|
+
for (const project of projects()) {
|
|
136
|
+
sync.child(project.worktree);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
32
139
|
|
|
33
140
|
const serverDotClass = createMemo(() => {
|
|
34
|
-
const healthy = server.healthy()
|
|
35
|
-
if (healthy === true) return "bg-icon-success-base"
|
|
36
|
-
if (healthy === false) return "bg-icon-critical-base"
|
|
37
|
-
return "bg-border-weak-base"
|
|
38
|
-
})
|
|
141
|
+
const healthy = server.healthy();
|
|
142
|
+
if (healthy === true) return "bg-icon-success-base";
|
|
143
|
+
if (healthy === false) return "bg-icon-critical-base";
|
|
144
|
+
return "bg-border-weak-base";
|
|
145
|
+
});
|
|
39
146
|
|
|
40
147
|
function openProject(directory: string) {
|
|
41
|
-
layout.projects.open(directory)
|
|
42
|
-
server.projects.touch(directory)
|
|
43
|
-
navigate(`/${base64Encode(directory)}`)
|
|
148
|
+
layout.projects.open(directory);
|
|
149
|
+
server.projects.touch(directory);
|
|
150
|
+
navigate(`/${base64Encode(directory)}`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function openSession(directory: string, sessionID: string) {
|
|
154
|
+
layout.projects.open(directory);
|
|
155
|
+
server.projects.touch(directory);
|
|
156
|
+
navigate(`/${base64Encode(directory)}/${sessionID}`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async function archiveSession(directory: string, sessionID: string) {
|
|
160
|
+
const [, setStore] = sync.child(directory, { bootstrap: false });
|
|
161
|
+
await globalSDK.client.session.update({
|
|
162
|
+
sessionID,
|
|
163
|
+
time: { archived: Date.now() },
|
|
164
|
+
});
|
|
165
|
+
mutateRootSessions((current) => {
|
|
166
|
+
if (!current?.[directory]) return current;
|
|
167
|
+
return {
|
|
168
|
+
...current,
|
|
169
|
+
[directory]: current[directory].filter((session) => session.id !== sessionID),
|
|
170
|
+
};
|
|
171
|
+
});
|
|
172
|
+
setStore(
|
|
173
|
+
"session",
|
|
174
|
+
(sessions) => sessions.filter((session) => session.id !== sessionID),
|
|
175
|
+
);
|
|
44
176
|
}
|
|
45
177
|
|
|
46
178
|
async function chooseProject() {
|
|
47
179
|
function resolve(result: string | string[] | null) {
|
|
48
180
|
if (Array.isArray(result)) {
|
|
49
181
|
for (const directory of result) {
|
|
50
|
-
openProject(directory)
|
|
182
|
+
openProject(directory);
|
|
51
183
|
}
|
|
52
184
|
} else if (result) {
|
|
53
|
-
openProject(result)
|
|
185
|
+
openProject(result);
|
|
54
186
|
}
|
|
55
187
|
}
|
|
56
188
|
|
|
@@ -58,74 +190,227 @@ export default function Home() {
|
|
|
58
190
|
const result = await platform.openDirectoryPickerDialog?.({
|
|
59
191
|
title: language.t("command.project.open"),
|
|
60
192
|
multiple: true,
|
|
61
|
-
})
|
|
62
|
-
resolve(result)
|
|
193
|
+
});
|
|
194
|
+
resolve(result);
|
|
63
195
|
} else {
|
|
64
196
|
dialog.show(
|
|
65
197
|
() => <DialogSelectDirectory multiple={true} onSelect={resolve} />,
|
|
66
198
|
() => resolve(null),
|
|
67
|
-
)
|
|
199
|
+
);
|
|
68
200
|
}
|
|
69
201
|
}
|
|
70
202
|
|
|
71
203
|
return (
|
|
72
|
-
<div class="mx-auto
|
|
73
|
-
<
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
204
|
+
<div class="mx-auto w-full max-w-7xl px-4 py-6 md:px-6 md:py-8">
|
|
205
|
+
<div class="flex flex-col gap-4 md:flex-row md:items-start md:justify-between">
|
|
206
|
+
<div class="min-w-0">
|
|
207
|
+
<div class="flex items-center gap-3">
|
|
208
|
+
<div
|
|
209
|
+
class="flex size-10 items-center justify-center rounded-2xl border shadow-xs-border-base"
|
|
210
|
+
style={homeStyles.heroIcon}
|
|
211
|
+
>
|
|
212
|
+
<Logo class="w-6 opacity-90" />
|
|
213
|
+
</div>
|
|
214
|
+
<div>
|
|
215
|
+
<div class="text-18-medium text-text-strong">
|
|
216
|
+
{language.t("home.sessionBoard")}
|
|
217
|
+
</div>
|
|
218
|
+
<div class="mt-0.5 text-12-regular text-text-weak">
|
|
219
|
+
{language.t("home.sessionBoard.description")}
|
|
220
|
+
</div>
|
|
221
|
+
</div>
|
|
222
|
+
</div>
|
|
223
|
+
</div>
|
|
224
|
+
|
|
225
|
+
<div class="flex items-center gap-2 self-start md:justify-end">
|
|
226
|
+
<Button
|
|
227
|
+
icon="folder-add-left"
|
|
228
|
+
size="normal"
|
|
229
|
+
class="pl-2 pr-3"
|
|
230
|
+
onClick={chooseProject}
|
|
231
|
+
>
|
|
232
|
+
{language.t("command.project.open")}
|
|
233
|
+
</Button>
|
|
234
|
+
<Button
|
|
235
|
+
size="normal"
|
|
236
|
+
variant="ghost"
|
|
237
|
+
class="pr-3 text-13-regular text-text-weak"
|
|
238
|
+
onClick={() => dialog.show(() => <DialogSelectServer />)}
|
|
239
|
+
>
|
|
240
|
+
<div
|
|
241
|
+
classList={{
|
|
242
|
+
"size-2 rounded-full": true,
|
|
243
|
+
[serverDotClass()]: true,
|
|
244
|
+
}}
|
|
245
|
+
/>
|
|
246
|
+
{server.name}
|
|
247
|
+
</Button>
|
|
248
|
+
</div>
|
|
249
|
+
</div>
|
|
250
|
+
|
|
88
251
|
<Switch>
|
|
89
|
-
<Match when={
|
|
90
|
-
<div class="mt-
|
|
91
|
-
<
|
|
92
|
-
<div class="
|
|
93
|
-
|
|
252
|
+
<Match when={projects().length > 0}>
|
|
253
|
+
<div class="mt-6 flex w-full flex-col gap-6">
|
|
254
|
+
<section class="flex flex-col gap-4">
|
|
255
|
+
<div class="grid gap-3 lg:grid-cols-2">
|
|
256
|
+
<BoardColumnView
|
|
257
|
+
title={language.t("home.sessionBoard.progress")}
|
|
258
|
+
icon="brain"
|
|
259
|
+
tone="progress"
|
|
260
|
+
cards={boardColumns().progress}
|
|
261
|
+
onOpen={openSession}
|
|
262
|
+
onArchive={archiveSession}
|
|
263
|
+
empty={language.t("home.sessionBoard.emptyProgress")}
|
|
264
|
+
/>
|
|
265
|
+
<BoardColumnView
|
|
266
|
+
title={language.t("home.sessionBoard.idle")}
|
|
267
|
+
icon="dash"
|
|
268
|
+
tone="idle"
|
|
269
|
+
cards={boardColumns().idle}
|
|
270
|
+
onOpen={openSession}
|
|
271
|
+
onArchive={archiveSession}
|
|
272
|
+
empty={language.t("home.sessionBoard.emptyIdle")}
|
|
273
|
+
/>
|
|
274
|
+
</div>
|
|
275
|
+
</section>
|
|
276
|
+
</div>
|
|
277
|
+
</Match>
|
|
278
|
+
<Match when={true}>
|
|
279
|
+
<div
|
|
280
|
+
class="mt-6 rounded-2xl border border-dashed px-6 py-10 shadow-xs-border-base"
|
|
281
|
+
style={homeStyles.emptyPanel}
|
|
282
|
+
>
|
|
283
|
+
<div class="mx-auto flex max-w-md flex-col items-center gap-3 text-center">
|
|
284
|
+
<div
|
|
285
|
+
class="flex size-12 items-center justify-center rounded-2xl border shadow-xs-border-base"
|
|
286
|
+
style={homeStyles.emptyIcon}
|
|
287
|
+
>
|
|
288
|
+
<Icon name="folder-add-left" size="large" />
|
|
289
|
+
</div>
|
|
290
|
+
<div class="flex flex-col gap-1 items-center justify-center">
|
|
291
|
+
<div class="text-14-medium text-text-strong">
|
|
292
|
+
{language.t("home.empty.title")}
|
|
293
|
+
</div>
|
|
294
|
+
<div class="text-12-regular text-text-weak">
|
|
295
|
+
{language.t("home.empty.description")}
|
|
296
|
+
</div>
|
|
297
|
+
</div>
|
|
298
|
+
<Button class="mt-1 px-3" onClick={chooseProject}>
|
|
94
299
|
{language.t("command.project.open")}
|
|
95
300
|
</Button>
|
|
96
301
|
</div>
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
302
|
+
</div>
|
|
303
|
+
</Match>
|
|
304
|
+
</Switch>
|
|
305
|
+
</div>
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function BoardColumnView(props: {
|
|
310
|
+
title: string;
|
|
311
|
+
icon: "brain" | "dash";
|
|
312
|
+
tone: BoardColumn;
|
|
313
|
+
cards: BoardCard[];
|
|
314
|
+
empty: string;
|
|
315
|
+
onOpen: (directory: string, sessionID: string) => void;
|
|
316
|
+
onArchive: (directory: string, sessionID: string) => void | Promise<void>;
|
|
317
|
+
}) {
|
|
318
|
+
const toneClass = () => {
|
|
319
|
+
if (props.tone === "progress")
|
|
320
|
+
return "bg-surface-base-hover text-icon-success-base border-border-weak-base";
|
|
321
|
+
return "bg-surface-inset-base text-icon-base border-border-weak-base";
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
const sectionStyle = () =>
|
|
325
|
+
props.tone === "progress"
|
|
326
|
+
? {
|
|
327
|
+
...homeStyles.border,
|
|
328
|
+
background:
|
|
329
|
+
"linear-gradient(180deg, color-mix(in srgb, var(--text-interactive-base) 8%, var(--background-stronger)), var(--background-stronger))",
|
|
330
|
+
}
|
|
331
|
+
: {
|
|
332
|
+
...homeStyles.border,
|
|
333
|
+
background:
|
|
334
|
+
"linear-gradient(180deg, color-mix(in srgb, var(--surface-inset-base) 55%, var(--background-stronger)), var(--background-stronger))",
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
return (
|
|
338
|
+
<section class="rounded-2xl p-3 md:p-4 min-h-72 shadow-xs-border-base" style={sectionStyle()}>
|
|
339
|
+
<div class="flex items-center justify-between gap-3">
|
|
340
|
+
<div class="flex items-center gap-2 min-w-0">
|
|
341
|
+
<div
|
|
342
|
+
class={`size-7 rounded-full border flex items-center justify-center ${toneClass()}`}
|
|
343
|
+
>
|
|
344
|
+
<Icon name={props.icon} size="small" />
|
|
345
|
+
</div>
|
|
346
|
+
<div class="text-14-medium text-text-strong">{props.title}</div>
|
|
347
|
+
</div>
|
|
348
|
+
<div class="text-12-mono text-text-weak">{props.cards.length}</div>
|
|
349
|
+
</div>
|
|
350
|
+
|
|
351
|
+
<div class="mt-3 flex flex-col gap-2">
|
|
352
|
+
<Switch>
|
|
353
|
+
<Match when={props.cards.length > 0}>
|
|
354
|
+
<For each={props.cards}>
|
|
355
|
+
{(card) => (
|
|
356
|
+
<div
|
|
357
|
+
class="overflow-hidden rounded-xl transition-colors"
|
|
358
|
+
style={homeStyles.card}
|
|
359
|
+
>
|
|
360
|
+
<div class="flex items-start justify-between gap-3 px-3 pt-3">
|
|
361
|
+
<div class="min-w-0 flex-1 pt-1">
|
|
362
|
+
<div class="truncate text-14-medium leading-tight text-text-strong">
|
|
363
|
+
{card.session.title}
|
|
364
|
+
</div>
|
|
365
|
+
</div>
|
|
366
|
+
<IconButton
|
|
367
|
+
icon="archive"
|
|
368
|
+
variant="ghost"
|
|
369
|
+
class="size-7 rounded-md"
|
|
370
|
+
aria-label="Archive session"
|
|
371
|
+
onClick={(event) => {
|
|
372
|
+
event.preventDefault();
|
|
373
|
+
event.stopPropagation();
|
|
374
|
+
void props.onArchive(card.projectDirectory, card.session.id);
|
|
375
|
+
}}
|
|
376
|
+
/>
|
|
377
|
+
</div>
|
|
100
378
|
<Button
|
|
101
|
-
size="large"
|
|
102
379
|
variant="ghost"
|
|
103
|
-
class="
|
|
104
|
-
onClick={() =>
|
|
380
|
+
class="flex h-auto w-full min-w-0 items-center justify-between gap-3 rounded-none border-0 bg-transparent px-3 pb-3 pt-2 text-left"
|
|
381
|
+
onClick={() =>
|
|
382
|
+
props.onOpen(card.projectDirectory, card.session.id)
|
|
383
|
+
}
|
|
105
384
|
>
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
385
|
+
<span class="min-w-0 flex-1 truncate text-12-regular text-text-weak">
|
|
386
|
+
{card.projectDirectory.replace(
|
|
387
|
+
/^.*?([^/\\]+(?:[/\\][^/\\]+)?)$/,
|
|
388
|
+
"$1",
|
|
389
|
+
)}
|
|
390
|
+
</span>
|
|
391
|
+
<div class="flex shrink-0 items-center gap-2 text-12-regular text-text-weak">
|
|
392
|
+
<span>{DateTime.fromMillis(card.updatedAt).toRelative()}</span>
|
|
393
|
+
<Icon
|
|
394
|
+
name="arrow-right"
|
|
395
|
+
size="small"
|
|
396
|
+
class="text-icon-weak shrink-0"
|
|
397
|
+
/>
|
|
109
398
|
</div>
|
|
110
399
|
</Button>
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
</
|
|
114
|
-
</
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
<div class="text-12-regular text-text-weak">{language.t("home.empty.description")}</div>
|
|
400
|
+
</div>
|
|
401
|
+
)}
|
|
402
|
+
</For>
|
|
403
|
+
</Match>
|
|
404
|
+
<Match when={true}>
|
|
405
|
+
<div
|
|
406
|
+
class="rounded-xl border border-dashed px-3 py-8 text-center text-12-regular text-text-weak"
|
|
407
|
+
style={homeStyles.emptyState}
|
|
408
|
+
>
|
|
409
|
+
{props.empty}
|
|
122
410
|
</div>
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
</Switch>
|
|
129
|
-
</div>
|
|
130
|
-
)
|
|
411
|
+
</Match>
|
|
412
|
+
</Switch>
|
|
413
|
+
</div>
|
|
414
|
+
</section>
|
|
415
|
+
);
|
|
131
416
|
}
|
|
@@ -101,7 +101,7 @@ const SessionRow = (props: {
|
|
|
101
101
|
cancelHoverPrefetch: () => void
|
|
102
102
|
}): JSX.Element => (
|
|
103
103
|
<A
|
|
104
|
-
href={`/${props.slug}
|
|
104
|
+
href={`/${props.slug}/${props.session.id}`}
|
|
105
105
|
class={`flex items-center justify-between gap-3 min-w-0 text-left w-full focus:outline-none transition-[padding] ${props.mobile ? "pr-7" : ""} group-hover/session:pr-7 group-focus-within/session:pr-7 group-active/session:pr-7 ${props.dense ? "py-0.5" : "py-1"}`}
|
|
106
106
|
onPointerEnter={props.scheduleHoverPrefetch}
|
|
107
107
|
onPointerLeave={props.cancelHoverPrefetch}
|
|
@@ -310,7 +310,7 @@ export const SessionItem = (props: SessionItemProps): JSX.Element => {
|
|
|
310
310
|
onMessageSelect={(message) => {
|
|
311
311
|
if (!isActive()) {
|
|
312
312
|
layout.pendingMessage.set(`${base64Encode(props.session.directory)}/${props.session.id}`, message.id)
|
|
313
|
-
navigate(
|
|
313
|
+
navigate(`/${props.slug}/${props.session.id}`)
|
|
314
314
|
return
|
|
315
315
|
}
|
|
316
316
|
window.history.replaceState(null, "", `#message-${message.id}`)
|
|
@@ -360,7 +360,7 @@ export const NewSessionItem = (props: {
|
|
|
360
360
|
const tooltip = () => props.mobile || !props.sidebarExpanded()
|
|
361
361
|
const item = (
|
|
362
362
|
<A
|
|
363
|
-
href={`/${props.slug}
|
|
363
|
+
href={`/${props.slug}`}
|
|
364
364
|
end
|
|
365
365
|
class={`flex items-center justify-between gap-3 min-w-0 text-left w-full focus:outline-none ${props.dense ? "py-0.5" : "py-1"}`}
|
|
366
366
|
onClick={() => {
|
|
@@ -437,7 +437,7 @@ export const SortableWorkspace = (props: {
|
|
|
437
437
|
root={props.project.worktree}
|
|
438
438
|
setHoverSession={props.ctx.setHoverSession}
|
|
439
439
|
clearHoverProjectSoon={props.ctx.clearHoverProjectSoon}
|
|
440
|
-
navigateToNewSession={() => navigate(`/${slug()}
|
|
440
|
+
navigateToNewSession={() => navigate(`/${slug()}`)}
|
|
441
441
|
/>
|
|
442
442
|
</div>
|
|
443
443
|
</div>
|