@stevecastaneda/paseo-progress 1.14.2 → 1.14.3
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/README.md +24 -17
- package/client/attachments.tsx +122 -28
- package/client/blocks.tsx +33 -8
- package/client/chat-row-view.tsx +19 -6
- package/client/chat-row.ts +13 -2
- package/client/dashboard-query.ts +9 -4
- package/client/deliverables.tsx +82 -17
- package/client/dialog-stack.tsx +104 -42
- package/client/directory.ts +1 -3
- package/client/errors.ts +6 -1
- package/client/history.tsx +233 -79
- package/client/i18n.ts +10 -5
- package/client/launcher.tsx +57 -15
- package/client/locales/en.ts +28 -22
- package/client/locales/zh-CN.ts +18 -17
- package/client/messages.ts +1 -0
- package/client/motion.tsx +38 -14
- package/client/panel-opened.ts +3 -1
- package/client/panel.tsx +442 -160
- package/client/pdf.tsx +18 -4
- package/client/pieces.ts +6 -1
- package/client/pill-popover.tsx +108 -23
- package/client/pills.tsx +35 -12
- package/client/preview.tsx +265 -79
- package/client/questions.tsx +173 -52
- package/client/row.tsx +14 -3
- package/client/section-title.tsx +11 -2
- package/client/settings.tsx +76 -24
- package/client/show-more.tsx +23 -6
- package/client/spinner.tsx +11 -7
- package/client/status-badge.tsx +9 -7
- package/client/tabs.tsx +98 -20
- package/client/ticket-dialog.tsx +241 -73
- package/client/time-bar.tsx +5 -2
- package/client/video.tsx +15 -6
- package/client/whats-new.tsx +94 -36
- package/client/when.tsx +2 -1
- package/client/write-up.tsx +79 -12
- package/dist/server/cli.js +199 -101
- package/index.client.tsx +28 -13
- package/index.server.ts +19 -1
- package/package.json +15 -14
- package/server/cli.ts +72 -32
- package/server/dashboard.ts +9 -13
- package/server/launcher.ts +33 -17
- package/server/lock.ts +14 -7
- package/server/open.ts +32 -98
- package/server/progress-file.ts +6 -6
- package/server/question-mode.ts +12 -6
- package/server/skill.ts +22 -16
- package/server/store.ts +26 -41
- package/server/system.ts +113 -0
- package/server/tool.ts +89 -44
- package/server/whats-new.ts +25 -13
- package/shared/attachments.ts +40 -8
- package/shared/blocks.ts +9 -6
- package/shared/dashboard.ts +46 -29
- package/shared/events.ts +12 -10
- package/shared/markdown.ts +16 -8
- package/shared/question-modes.ts +8 -5
- package/shared/rpc.ts +9 -3
- package/shared/show.ts +20 -8
- package/shared/ticket-story.ts +1 -3
- package/skills/paseo-progress/SKILL.md +1 -0
- package/tsconfig.json +3 -10
- package/client/page-opener.ts +0 -9
- package/client/page-screen.tsx +0 -97
package/client/dialog-stack.tsx
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import type { PluginWorkspacePanelProps } from "@getpaseo/plugin/client";
|
|
2
2
|
import { Icon, Modal } from "@getpaseo/plugin/client/ui";
|
|
3
3
|
import React, { useEffect, useRef, useState } from "react";
|
|
4
|
-
import {
|
|
5
|
-
import { opensAsPage } from "../shared/attachments";
|
|
4
|
+
import { isPage } from "../shared/attachments";
|
|
6
5
|
import type { Dashboard, Question } from "../shared/dashboard";
|
|
7
6
|
import { type Attachment, attachmentIcon, useAttachmentOpener } from "./attachments";
|
|
8
7
|
import { NarrowProvider } from "./narrow";
|
|
@@ -25,7 +24,17 @@ export type Step =
|
|
|
25
24
|
|
|
26
25
|
// Paseo gives each dialog its own backdrop, so opening a second one darkens
|
|
27
26
|
// the screen while the first fades out; steps swap content inside this one instead.
|
|
28
|
-
export function StackedDialog({
|
|
27
|
+
export function StackedDialog({
|
|
28
|
+
colors,
|
|
29
|
+
root,
|
|
30
|
+
onClose,
|
|
31
|
+
questions = [],
|
|
32
|
+
dashboard,
|
|
33
|
+
now,
|
|
34
|
+
live = true,
|
|
35
|
+
compact = false,
|
|
36
|
+
context,
|
|
37
|
+
}: {
|
|
29
38
|
colors: Colors;
|
|
30
39
|
root: Step | null;
|
|
31
40
|
onClose(): void;
|
|
@@ -50,18 +59,23 @@ export function StackedDialog({ colors, root, onClose, questions = [], dashboard
|
|
|
50
59
|
const group = useRef<Attachment[]>([]);
|
|
51
60
|
const push = (step: Step) => setSteps((current) => [...current, step]);
|
|
52
61
|
const replaceTop = (step: Step) => setSteps((current) => [...current.slice(0, -1), step]);
|
|
53
|
-
const opener = useAttachmentOpener({
|
|
62
|
+
const opener = useAttachmentOpener({
|
|
63
|
+
...context,
|
|
64
|
+
onPreview: (attachment) => push({ kind: "preview", attachment, group: galleryFiles(group.current), fullScreen: false }),
|
|
65
|
+
});
|
|
54
66
|
const openAttachment = (attachment: Attachment, from: Attachment[]) => {
|
|
55
67
|
group.current = from;
|
|
56
68
|
// A link or a page opens outside the dialog; close first so it isn't left behind it.
|
|
57
|
-
if (attachment.url ||
|
|
69
|
+
if (attachment.url || isPage(attachment)) onClose();
|
|
58
70
|
void opener.open(attachment);
|
|
59
71
|
};
|
|
60
72
|
// Opens the agent's chat in its workspace, with the dialog out of the way.
|
|
61
|
-
const openAgent = context.navigation
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
73
|
+
const openAgent = context.navigation
|
|
74
|
+
? (agentId: string) => {
|
|
75
|
+
onClose();
|
|
76
|
+
context.navigation!.openAgent({ agentId });
|
|
77
|
+
}
|
|
78
|
+
: undefined;
|
|
65
79
|
const copy = useCopy();
|
|
66
80
|
const top = steps.at(-1);
|
|
67
81
|
const back = steps.length > 1 ? () => setSteps((current) => current.slice(0, -1)) : undefined;
|
|
@@ -71,8 +85,13 @@ export function StackedDialog({ colors, root, onClose, questions = [], dashboard
|
|
|
71
85
|
const preview = top?.kind === "preview" ? top : undefined;
|
|
72
86
|
const setFullScreen = (on: boolean) => preview && replaceTop({ ...preview, fullScreen: on, leftFullScreen: !on });
|
|
73
87
|
// The ticket or question it opened on went away (answered elsewhere, or a new run): close rather than show a blank dialog.
|
|
74
|
-
const rootMissing =
|
|
75
|
-
|
|
88
|
+
const rootMissing =
|
|
89
|
+
root !== null &&
|
|
90
|
+
(root.kind === "ticket"
|
|
91
|
+
? !dashboard?.tickets.some((candidate) => candidate.id === root.id)
|
|
92
|
+
: root.kind === "question"
|
|
93
|
+
? !questions.some((candidate) => candidate.id === root.id)
|
|
94
|
+
: false);
|
|
76
95
|
useEffect(() => {
|
|
77
96
|
if (rootMissing) onClose();
|
|
78
97
|
}, [rootMissing]);
|
|
@@ -81,44 +100,88 @@ export function StackedDialog({ colors, root, onClose, questions = [], dashboard
|
|
|
81
100
|
const icon = ticket ? "ListChecks" : question ? "MessageCircleQuestion" : preview ? attachmentIcon(preview.attachment) : "ListChecks";
|
|
82
101
|
|
|
83
102
|
return (
|
|
84
|
-
<Modal
|
|
85
|
-
|
|
103
|
+
<Modal
|
|
104
|
+
title={title}
|
|
105
|
+
icon={<Icon name={icon} size={16} color={colors.foregroundMuted} />}
|
|
106
|
+
open={Boolean(root)}
|
|
107
|
+
onOpenChange={(next) => {
|
|
86
108
|
if (next) return;
|
|
87
109
|
// Esc leaves full screen first; the dialog stays open under it.
|
|
88
110
|
if (preview?.fullScreen) setFullScreen(false);
|
|
89
111
|
else onClose();
|
|
90
|
-
}}
|
|
112
|
+
}}
|
|
113
|
+
>
|
|
91
114
|
{/* One Modal.Content for every step: replacing it would re-present the sheet on phones.
|
|
92
115
|
Full screen turns its scrolling off. The image's fixed position reaches the window only
|
|
93
116
|
when no ancestor has a transform, and a scroll view has one. */}
|
|
94
117
|
<Modal.Content scrollable={!(preview && (previewLong || preview.fullScreen))}>
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
118
|
+
<ProgressLanguageProvider>
|
|
119
|
+
{/* A dialog has room for clock times, however narrow the panel that opened it. */}
|
|
120
|
+
<NarrowProvider value={false}>
|
|
121
|
+
{preview ? (
|
|
122
|
+
<PreviewContent
|
|
123
|
+
colors={colors}
|
|
124
|
+
attachment={preview.attachment}
|
|
125
|
+
workspaceId={context.workspaceId}
|
|
126
|
+
workspaceDirectory={context.workspaceDirectory}
|
|
127
|
+
onOpenOnHost={() => {
|
|
128
|
+
onClose();
|
|
129
|
+
void opener.openOnHost(preview.attachment);
|
|
130
|
+
}}
|
|
131
|
+
onCopyPath={() => void opener.copyPath(preview.attachment)}
|
|
132
|
+
onBack={back}
|
|
133
|
+
gallery={{
|
|
134
|
+
items: preview.group,
|
|
135
|
+
onSelect: (attachment) =>
|
|
136
|
+
replaceTop({
|
|
137
|
+
...preview,
|
|
138
|
+
attachment,
|
|
139
|
+
fullScreen: preview.fullScreen && previewKind(attachment.path ?? "") === "image",
|
|
140
|
+
leftFullScreen: false,
|
|
141
|
+
}),
|
|
142
|
+
}}
|
|
143
|
+
onLong={setPreviewLong}
|
|
144
|
+
ownScroll={previewLong || preview.fullScreen}
|
|
145
|
+
fullScreen={preview.fullScreen}
|
|
146
|
+
leftFullScreen={preview.leftFullScreen === true}
|
|
147
|
+
onFullScreen={setFullScreen}
|
|
148
|
+
onOpenEntry={openAttachment}
|
|
149
|
+
/>
|
|
150
|
+
) : (
|
|
151
|
+
<>
|
|
152
|
+
{ticket && dashboard ? (
|
|
153
|
+
<TicketStoryView
|
|
154
|
+
colors={colors}
|
|
155
|
+
dashboard={dashboard}
|
|
156
|
+
ticket={ticket}
|
|
157
|
+
now={now}
|
|
158
|
+
live={live}
|
|
159
|
+
compact={compact}
|
|
160
|
+
context={context}
|
|
161
|
+
onOpenAgent={openAgent}
|
|
162
|
+
onOpenAttachment={openAttachment}
|
|
163
|
+
onOpenQuestion={(id) => push({ kind: "question", id })}
|
|
164
|
+
/>
|
|
165
|
+
) : null}
|
|
166
|
+
{question ? (
|
|
167
|
+
<QuestionView
|
|
168
|
+
colors={colors}
|
|
169
|
+
question={question}
|
|
170
|
+
now={now}
|
|
171
|
+
root={context.workspaceDirectory}
|
|
172
|
+
onCopy={(letter) => {
|
|
173
|
+
void copy(question, letter);
|
|
174
|
+
onClose();
|
|
175
|
+
}}
|
|
176
|
+
onOpenAttachment={openAttachment}
|
|
177
|
+
>
|
|
178
|
+
{back ? <BackButton colors={colors} onPress={back} /> : null}
|
|
179
|
+
</QuestionView>
|
|
180
|
+
) : null}
|
|
181
|
+
</>
|
|
182
|
+
)}
|
|
183
|
+
</NarrowProvider>
|
|
184
|
+
</ProgressLanguageProvider>
|
|
122
185
|
</Modal.Content>
|
|
123
186
|
</Modal>
|
|
124
187
|
);
|
|
@@ -127,4 +190,3 @@ export function StackedDialog({ colors, root, onClose, questions = [], dashboard
|
|
|
127
190
|
function stepKey(step: Step): string {
|
|
128
191
|
return step.kind === "preview" ? `preview:${step.attachment.ref}` : `${step.kind}:${step.id}`;
|
|
129
192
|
}
|
|
130
|
-
|
package/client/directory.ts
CHANGED
|
@@ -7,9 +7,7 @@ type PaseoApi = ReturnType<typeof getPaseoClient>;
|
|
|
7
7
|
type Handler<Subscribe> = Subscribe extends (handler: infer H) => unknown ? H : never;
|
|
8
8
|
export type PaseoAgentUpdate = Parameters<Handler<PaseoApi["agents"]["subscribe"]>>[0];
|
|
9
9
|
export type PaseoWorkspaceUpdate = Parameters<Handler<PaseoApi["workspaces"]["subscribe"]>>[0];
|
|
10
|
-
type Message = Parameters<
|
|
11
|
-
Parameters<NonNullable<Awaited<ReturnType<PaseoApi["agents"]["list"]>>["subscription"]>["subscribe"]>[0]["update"]
|
|
12
|
-
>[0];
|
|
10
|
+
type Message = Parameters<Parameters<NonNullable<Awaited<ReturnType<PaseoApi["agents"]["list"]>>["subscription"]>["subscribe"]>[0]["update"]>[0];
|
|
13
11
|
type OwnedSubscription<T> = {
|
|
14
12
|
subscribe(observer: { snapshot(snapshot: T): void; update(message: Message): void; error?(error: unknown): void }): () => void;
|
|
15
13
|
release(): Promise<void>;
|
package/client/errors.ts
CHANGED
|
@@ -2,5 +2,10 @@
|
|
|
2
2
|
export function errorText(error: unknown, fallback: string): string {
|
|
3
3
|
const message = (error as { message?: unknown } | null)?.message;
|
|
4
4
|
if (typeof message !== "string") return fallback;
|
|
5
|
-
return
|
|
5
|
+
return (
|
|
6
|
+
message
|
|
7
|
+
.replace(/^Request failed: /, "")
|
|
8
|
+
.replace(/ requestType=\S+(?: code=\S+)?$/, "")
|
|
9
|
+
.trim() || fallback
|
|
10
|
+
);
|
|
6
11
|
}
|
package/client/history.tsx
CHANGED
|
@@ -13,12 +13,7 @@ type Colors = PluginWorkspacePanelProps["theme"]["colors"];
|
|
|
13
13
|
|
|
14
14
|
// Run history stays on this computer unless the repo opts in. This quiet row
|
|
15
15
|
// is the only place that changes it; nothing in the repo changes until then.
|
|
16
|
-
export function HistoryRow({ colors, workspaceId, directory, savedTo }: {
|
|
17
|
-
colors: Colors;
|
|
18
|
-
workspaceId: string;
|
|
19
|
-
directory: string;
|
|
20
|
-
savedTo: string | null;
|
|
21
|
-
}) {
|
|
16
|
+
export function HistoryRow({ colors, workspaceId, directory, savedTo }: { colors: Colors; workspaceId: string; directory: string; savedTo: string | null }) {
|
|
22
17
|
const { messages: m } = useProgressLanguage();
|
|
23
18
|
const [picking, setPicking] = useState(false);
|
|
24
19
|
const set = useRpc(setHistoryFolder);
|
|
@@ -35,35 +30,83 @@ export function HistoryRow({ colors, workspaceId, directory, savedTo }: {
|
|
|
35
30
|
// While saving, the button that started it says so.
|
|
36
31
|
const busy = mutation.isPending ? (mutation.variables === null ? "stop" : "pick") : null;
|
|
37
32
|
const actions: { label: string; busyLabel: string; kind: "pick" | "stop"; onPress(): void }[] = savedTo
|
|
38
|
-
? [
|
|
33
|
+
? [
|
|
34
|
+
{ label: m.changeFolder, busyLabel: m.saving, kind: "pick", onPress: () => setPicking(true) },
|
|
35
|
+
{ label: m.stopSaving, busyLabel: m.stopping, kind: "stop", onPress: () => mutation.mutate(null) },
|
|
36
|
+
]
|
|
39
37
|
: [{ label: m.saveToRepo, busyLabel: m.saving, kind: "pick", onPress: () => setPicking(true) }];
|
|
40
38
|
|
|
41
39
|
return (
|
|
42
40
|
<View style={{ flexDirection: "row", flexWrap: "wrap", alignItems: "center", columnGap: 8, rowGap: 4, marginHorizontal: 12, marginTop: 16 }}>
|
|
43
41
|
<View style={{ flexDirection: "row", alignItems: "flex-start", gap: 6, flex: 1, minWidth: 180 }}>
|
|
44
|
-
<View style={{ paddingTop: 2 }}
|
|
42
|
+
<View style={{ paddingTop: 2 }}>
|
|
43
|
+
<Icon name="History" size={13} color={colors.foregroundMuted} />
|
|
44
|
+
</View>
|
|
45
45
|
<Text style={{ flex: 1, color: colors.foregroundMuted, fontSize: 12, lineHeight: 17 }}>
|
|
46
|
-
{savedTo ?
|
|
46
|
+
{savedTo ? (
|
|
47
|
+
<>
|
|
48
|
+
{m.historyInRepo[0]}
|
|
49
|
+
<Text style={{ color: colors.foreground }}>{savedTo}</Text>
|
|
50
|
+
{m.historyInRepo[1]}
|
|
51
|
+
</>
|
|
52
|
+
) : (
|
|
53
|
+
m.localHistory
|
|
54
|
+
)}
|
|
47
55
|
</Text>
|
|
48
56
|
</View>
|
|
49
57
|
<View style={{ flexDirection: "row", gap: 4 }}>
|
|
50
58
|
{actions.map((action) => (
|
|
51
|
-
<PressScale
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
59
|
+
<PressScale
|
|
60
|
+
key={action.label}
|
|
61
|
+
accessibilityRole="button"
|
|
62
|
+
disabled={mutation.isPending}
|
|
63
|
+
onPress={action.onPress}
|
|
64
|
+
style={({ pressed, hovered }) => ({
|
|
65
|
+
paddingHorizontal: 8,
|
|
66
|
+
paddingVertical: 4,
|
|
67
|
+
borderRadius: 4,
|
|
68
|
+
opacity: mutation.isPending ? 0.7 : 1,
|
|
69
|
+
backgroundColor: pressed ? colors.border : hovered ? colors.surface2 : "transparent",
|
|
70
|
+
})}
|
|
71
|
+
>
|
|
72
|
+
<Text style={{ color: colors.foreground, fontSize: 12, lineHeight: 16, fontWeight: "600" }}>
|
|
73
|
+
{busy === action.kind ? action.busyLabel : action.label}
|
|
74
|
+
</Text>
|
|
55
75
|
</PressScale>
|
|
56
76
|
))}
|
|
57
77
|
</View>
|
|
58
|
-
<FolderPicker
|
|
59
|
-
|
|
78
|
+
<FolderPicker
|
|
79
|
+
key={picking ? "open" : "closed"}
|
|
80
|
+
colors={colors}
|
|
81
|
+
workspaceId={workspaceId}
|
|
82
|
+
directory={directory}
|
|
83
|
+
open={picking}
|
|
84
|
+
start={savedTo ?? ""}
|
|
85
|
+
onClose={() => setPicking(false)}
|
|
86
|
+
onPick={(folder) => {
|
|
87
|
+
setPicking(false);
|
|
88
|
+
mutation.mutate(folder);
|
|
89
|
+
}}
|
|
90
|
+
/>
|
|
60
91
|
</View>
|
|
61
92
|
);
|
|
62
93
|
}
|
|
63
94
|
|
|
95
|
+
function parentOf(path: string): string {
|
|
96
|
+
return path.split("/").slice(0, -1).join("/");
|
|
97
|
+
}
|
|
98
|
+
|
|
64
99
|
// Browses the repo's folders on the daemon host. A new folder only needs a
|
|
65
100
|
// name: it's made the first time a run is saved there.
|
|
66
|
-
function FolderPicker({
|
|
101
|
+
function FolderPicker({
|
|
102
|
+
colors,
|
|
103
|
+
workspaceId,
|
|
104
|
+
directory,
|
|
105
|
+
open,
|
|
106
|
+
start,
|
|
107
|
+
onClose,
|
|
108
|
+
onPick,
|
|
109
|
+
}: {
|
|
67
110
|
colors: Colors;
|
|
68
111
|
workspaceId: string;
|
|
69
112
|
directory: string;
|
|
@@ -74,7 +117,6 @@ function FolderPicker({ colors, workspaceId, directory, open, start, onClose, on
|
|
|
74
117
|
onPick(folder: string): void;
|
|
75
118
|
}) {
|
|
76
119
|
const { messages: m } = useProgressLanguage();
|
|
77
|
-
const parentOf = (path: string) => path.split("/").slice(0, -1).join("/");
|
|
78
120
|
const [at, setAt] = useState(() => parentOf(start));
|
|
79
121
|
const [naming, setNaming] = useState<string | null>(null);
|
|
80
122
|
const list = useRpc(listFolders);
|
|
@@ -85,78 +127,190 @@ function FolderPicker({ colors, workspaceId, directory, open, start, onClose, on
|
|
|
85
127
|
});
|
|
86
128
|
const join = (name: string) => (at ? `${at}/${name}` : name);
|
|
87
129
|
const crumbs = at ? at.split("/") : [];
|
|
88
|
-
const close = () => {
|
|
130
|
+
const close = () => {
|
|
131
|
+
setNaming(null);
|
|
132
|
+
setAt(parentOf(start));
|
|
133
|
+
onClose();
|
|
134
|
+
};
|
|
89
135
|
const newName = naming?.trim().replace(/\/+$/, "") ?? "";
|
|
90
136
|
|
|
91
137
|
return (
|
|
92
|
-
<Modal
|
|
138
|
+
<Modal
|
|
139
|
+
title={m.historyFolderTitle}
|
|
140
|
+
icon={<Icon name="FolderOpen" size={16} color={colors.foregroundMuted} />}
|
|
141
|
+
open={open}
|
|
142
|
+
onOpenChange={(next) => {
|
|
143
|
+
if (!next) close();
|
|
144
|
+
}}
|
|
145
|
+
>
|
|
93
146
|
{/* Paseo sizes a non-scrolling dialog to 85% of the window, so let it size to
|
|
94
147
|
its content; the list's own height cap keeps the buttons in view. */}
|
|
95
148
|
<Modal.Content>
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
{
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
149
|
+
<ProgressLanguageProvider>
|
|
150
|
+
<Text style={{ color: colors.foregroundMuted, fontSize: 12, lineHeight: 17 }}>{m.historyFolderHint}</Text>
|
|
151
|
+
<View style={{ flexDirection: "row", flexWrap: "wrap", alignItems: "center", gap: 2, paddingHorizontal: 4 }}>
|
|
152
|
+
{[m.repoTop, ...crumbs].map((crumb, index) => (
|
|
153
|
+
<React.Fragment key={index}>
|
|
154
|
+
{index ? <Icon name="ChevronRight" size={12} color={colors.foregroundMuted} /> : null}
|
|
155
|
+
<PressScale
|
|
156
|
+
accessibilityRole="button"
|
|
157
|
+
disabled={index === crumbs.length}
|
|
158
|
+
onPress={() => {
|
|
159
|
+
setNaming(null);
|
|
160
|
+
setAt(crumbs.slice(0, index).join("/"));
|
|
161
|
+
}}
|
|
162
|
+
style={({ pressed, hovered }) => ({
|
|
163
|
+
paddingHorizontal: 4,
|
|
164
|
+
paddingVertical: 2,
|
|
165
|
+
borderRadius: 4,
|
|
166
|
+
backgroundColor: index === crumbs.length ? "transparent" : pressed ? colors.border : hovered ? colors.surface2 : "transparent",
|
|
167
|
+
})}
|
|
168
|
+
>
|
|
169
|
+
<Text
|
|
170
|
+
style={{
|
|
171
|
+
color: index === crumbs.length ? colors.foreground : colors.foregroundMuted,
|
|
172
|
+
fontSize: 12,
|
|
173
|
+
lineHeight: 16,
|
|
174
|
+
fontWeight: index === crumbs.length ? "600" : "400",
|
|
175
|
+
}}
|
|
176
|
+
>
|
|
177
|
+
{crumb}
|
|
178
|
+
</Text>
|
|
179
|
+
</PressScale>
|
|
180
|
+
</React.Fragment>
|
|
181
|
+
))}
|
|
182
|
+
</View>
|
|
112
183
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
<
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
184
|
+
{/* Only the list scrolls, so Use and Cancel stay in view in a big repo. */}
|
|
185
|
+
<ScrollView style={{ maxHeight: 320, flexGrow: 0 }} contentContainerStyle={{ gap: 2 }}>
|
|
186
|
+
{listed.isPending ? (
|
|
187
|
+
<ActivityIndicator color={colors.foregroundMuted} accessibilityLabel={m.loadingFolders} style={{ alignSelf: "flex-start", padding: 8 }} />
|
|
188
|
+
) : null}
|
|
189
|
+
{listed.data?.folders.map((name) => (
|
|
190
|
+
<PressableRow
|
|
191
|
+
key={name}
|
|
192
|
+
colors={colors}
|
|
193
|
+
accessibilityRole="button"
|
|
194
|
+
accessibilityLabel={m.openItem(name)}
|
|
195
|
+
onPress={() => {
|
|
196
|
+
setNaming(null);
|
|
197
|
+
setAt(join(name));
|
|
198
|
+
}}
|
|
199
|
+
style={{ flexDirection: "row", alignItems: "center", gap: 8, paddingHorizontal: 8, paddingVertical: 6, borderRadius: 6 }}
|
|
200
|
+
>
|
|
201
|
+
<Icon name="Folder" size={14} color={colors.foregroundMuted} />
|
|
202
|
+
<Text numberOfLines={1} ellipsizeMode="middle" style={{ flex: 1, color: colors.foreground, fontSize: 12, lineHeight: 16 }}>
|
|
203
|
+
{name}
|
|
204
|
+
</Text>
|
|
205
|
+
<Icon name="ChevronRight" size={14} color={colors.foregroundMuted} />
|
|
206
|
+
</PressableRow>
|
|
207
|
+
))}
|
|
208
|
+
{listed.data && !listed.data.folders.length ? (
|
|
209
|
+
<Text style={{ color: colors.foregroundMuted, fontSize: 12, lineHeight: 16, paddingHorizontal: 8, paddingVertical: 6 }}>{m.noFolders}</Text>
|
|
210
|
+
) : null}
|
|
211
|
+
{listed.error ? (
|
|
212
|
+
<Text accessibilityRole="alert" style={{ color: colors.statusDanger, fontSize: 12, lineHeight: 17, paddingHorizontal: 8 }}>
|
|
213
|
+
{errorText(listed.error, m.listFoldersFailure)}
|
|
214
|
+
</Text>
|
|
215
|
+
) : null}
|
|
216
|
+
</ScrollView>
|
|
129
217
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
218
|
+
{naming === null ? (
|
|
219
|
+
<PressScale
|
|
220
|
+
accessibilityRole="button"
|
|
221
|
+
onPress={() => setNaming("")}
|
|
222
|
+
style={({ pressed, hovered }) => ({
|
|
223
|
+
alignSelf: "flex-start",
|
|
224
|
+
flexDirection: "row",
|
|
225
|
+
alignItems: "center",
|
|
226
|
+
gap: 6,
|
|
227
|
+
paddingHorizontal: 8,
|
|
228
|
+
paddingVertical: 5,
|
|
229
|
+
borderRadius: 6,
|
|
230
|
+
backgroundColor: pressed ? colors.border : hovered ? colors.surface2 : "transparent",
|
|
231
|
+
})}
|
|
232
|
+
>
|
|
233
|
+
<Icon name="FolderPlus" size={14} color={colors.foreground} />
|
|
234
|
+
<Text style={{ color: colors.foreground, fontSize: 12, lineHeight: 16, fontWeight: "600" }}>{m.newFolder}</Text>
|
|
144
235
|
</PressScale>
|
|
145
|
-
|
|
146
|
-
|
|
236
|
+
) : (
|
|
237
|
+
<View style={{ flexDirection: "row", alignItems: "center", gap: 6 }}>
|
|
238
|
+
<TextInput
|
|
239
|
+
autoFocus
|
|
240
|
+
value={naming}
|
|
241
|
+
onChangeText={setNaming}
|
|
242
|
+
onSubmitEditing={() => {
|
|
243
|
+
if (newName) onPick(join(newName));
|
|
244
|
+
}}
|
|
245
|
+
autoCapitalize="none"
|
|
246
|
+
autoCorrect={false}
|
|
247
|
+
spellCheck={false}
|
|
248
|
+
accessibilityLabel={m.newFolderName}
|
|
249
|
+
placeholder="progress-history"
|
|
250
|
+
placeholderTextColor={colors.foregroundMuted}
|
|
251
|
+
style={{
|
|
252
|
+
flex: 1,
|
|
253
|
+
color: colors.foreground,
|
|
254
|
+
fontSize: 12,
|
|
255
|
+
lineHeight: 16,
|
|
256
|
+
paddingHorizontal: 8,
|
|
257
|
+
paddingVertical: 6,
|
|
258
|
+
borderRadius: 6,
|
|
259
|
+
borderWidth: 1,
|
|
260
|
+
borderColor: colors.border,
|
|
261
|
+
backgroundColor: colors.surface1,
|
|
262
|
+
}}
|
|
263
|
+
/>
|
|
264
|
+
<PressScale
|
|
265
|
+
accessibilityRole="button"
|
|
266
|
+
disabled={!newName}
|
|
267
|
+
onPress={() => onPick(join(newName))}
|
|
268
|
+
style={({ pressed, hovered }) => ({
|
|
269
|
+
paddingHorizontal: 10,
|
|
270
|
+
paddingVertical: 6,
|
|
271
|
+
borderRadius: 6,
|
|
272
|
+
backgroundColor: colors.accent,
|
|
273
|
+
opacity: !newName ? 0.5 : pressed ? 0.7 : hovered ? 0.85 : 1,
|
|
274
|
+
})}
|
|
275
|
+
>
|
|
276
|
+
<Text style={{ color: colors.accentForeground, fontSize: 12, lineHeight: 16, fontWeight: "600" }}>{m.useNewFolder}</Text>
|
|
277
|
+
</PressScale>
|
|
278
|
+
</View>
|
|
279
|
+
)}
|
|
147
280
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
281
|
+
<View style={{ flexDirection: "row", flexWrap: "wrap", alignItems: "center", justifyContent: "flex-end", gap: 8 }}>
|
|
282
|
+
{at ? null : <Text style={{ flex: 1, minWidth: 140, color: colors.foregroundMuted, fontSize: 12, lineHeight: 17 }}>{m.pickFolderHint}</Text>}
|
|
283
|
+
<PressScale
|
|
284
|
+
accessibilityRole="button"
|
|
285
|
+
onPress={close}
|
|
286
|
+
style={({ pressed, hovered }) => ({
|
|
287
|
+
paddingHorizontal: 12,
|
|
288
|
+
paddingVertical: 6,
|
|
289
|
+
borderRadius: 6,
|
|
290
|
+
backgroundColor: pressed ? colors.border : hovered ? colors.surface2 : "transparent",
|
|
291
|
+
})}
|
|
292
|
+
>
|
|
293
|
+
<Text style={{ color: colors.foreground, fontSize: 12, lineHeight: 16, fontWeight: "600" }}>{m.cancel}</Text>
|
|
294
|
+
</PressScale>
|
|
295
|
+
<PressScale
|
|
296
|
+
accessibilityRole="button"
|
|
297
|
+
disabled={!at}
|
|
298
|
+
onPress={() => onPick(at)}
|
|
299
|
+
outerStyle={{ flexShrink: 1 }}
|
|
300
|
+
style={({ pressed, hovered }) => ({
|
|
301
|
+
paddingHorizontal: 12,
|
|
302
|
+
paddingVertical: 6,
|
|
303
|
+
borderRadius: 6,
|
|
304
|
+
backgroundColor: colors.accent,
|
|
305
|
+
opacity: !at ? 0.5 : pressed ? 0.7 : hovered ? 0.85 : 1,
|
|
306
|
+
})}
|
|
307
|
+
>
|
|
308
|
+
<Text numberOfLines={1} ellipsizeMode="middle" style={{ color: colors.accentForeground, fontSize: 12, lineHeight: 16, fontWeight: "600" }}>
|
|
309
|
+
{at ? m.useNamedFolder(crumbs[crumbs.length - 1]) : m.useFolder}
|
|
310
|
+
</Text>
|
|
311
|
+
</PressScale>
|
|
312
|
+
</View>
|
|
313
|
+
</ProgressLanguageProvider>
|
|
160
314
|
</Modal.Content>
|
|
161
315
|
</Modal>
|
|
162
316
|
);
|
package/client/i18n.ts
CHANGED
|
@@ -77,10 +77,15 @@ export function clientHeadline(locale: ProgressLocale, dashboard: Dashboard): He
|
|
|
77
77
|
|
|
78
78
|
export function timelineLabel(m: ProgressMessages, label: TimelineLabel): string {
|
|
79
79
|
switch (label.kind) {
|
|
80
|
-
case "added":
|
|
81
|
-
|
|
82
|
-
case "
|
|
83
|
-
|
|
84
|
-
case "
|
|
80
|
+
case "added":
|
|
81
|
+
return m.added;
|
|
82
|
+
case "reset":
|
|
83
|
+
return m.resetStatus;
|
|
84
|
+
case "stage":
|
|
85
|
+
return m.stageLabel(label.stage);
|
|
86
|
+
case "status":
|
|
87
|
+
return m.status[label.status];
|
|
88
|
+
case "waiting":
|
|
89
|
+
return m.waitingForTicket(label.id);
|
|
85
90
|
}
|
|
86
91
|
}
|