@xpufx/paseo-top 0.4.4 → 0.4.7
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/OVERVIEW.md +18 -0
- package/client/host-ui.tsx +184 -0
- package/client/multi-host-view.tsx +57 -43
- package/client/multi-host.ts +36 -13
- package/client/pill-labels.ts +94 -1
- package/client/pill.tsx +692 -616
- package/client/resources-query.ts +1 -1
- package/client/settings-events.ts +1 -1
- package/client/settings-ui.tsx +86 -9
- package/client/surface.tsx +308 -276
- package/client/telemetry-copy.ts +8 -2
- package/client/telemetry.tsx +372 -321
- package/client/turn-panel.tsx +126 -104
- package/client/vendor/paseo-plugin-helper/{host.ts → core/host.ts} +9 -3
- package/client/vendor/paseo-plugin-helper/core/index.ts +76 -0
- package/client/vendor/paseo-plugin-helper/core/plugin-load-count.ts +89 -0
- package/client/vendor/paseo-plugin-helper/{query-refresh.ts → core/query-refresh.ts} +2 -2
- package/client/vendor/paseo-plugin-helper/{query.ts → core/query.ts} +2 -2
- package/client/vendor/paseo-plugin-helper/{settings.ts → core/settings.ts} +1 -1
- package/client/vendor/paseo-plugin-helper/{shared-settings.ts → core/shared-settings.ts} +2 -2
- package/client/vendor/paseo-plugin-helper/{utils → lifecycle}/clipboard.ts +2 -2
- package/client/vendor/paseo-plugin-helper/lifecycle/flair.ts +26 -0
- package/client/vendor/paseo-plugin-helper/{theme/color-utils.ts → lifecycle/host-color.ts} +95 -38
- package/client/vendor/paseo-plugin-helper/lifecycle/host-theme.tsx +109 -0
- package/client/vendor/paseo-plugin-helper/lifecycle/index.ts +23 -0
- package/client/vendor/paseo-plugin-helper/lifecycle/multi-host.ts +66 -0
- package/client/vendor/paseo-plugin-helper/{panel.tsx → lifecycle/panel.tsx} +7 -7
- package/client/vendor/paseo-plugin-helper/{pill.tsx → lifecycle/pill.tsx} +51 -18
- package/client/vendor/paseo-plugin-helper/lifecycle/providers.tsx +46 -0
- package/client/vendor/paseo-plugin-helper/lifecycle/scroll-owner.ts +26 -0
- package/client/vendor/paseo-plugin-helper/{surface.tsx → lifecycle/surface.tsx} +6 -6
- package/client/vendor/paseo-plugin-helper/ui/color.ts +1 -0
- package/client/vendor/paseo-plugin-helper/ui/content.tsx +1326 -0
- package/client/vendor/paseo-plugin-helper/ui/controls.tsx +1018 -0
- package/client/vendor/paseo-plugin-helper/{components/DataTable.tsx → ui/data.tsx} +50 -58
- package/client/vendor/paseo-plugin-helper/ui/index.ts +44 -0
- package/client/vendor/paseo-plugin-helper/ui/layout.tsx +263 -0
- package/client/vendor/paseo-plugin-helper/ui/modal.tsx +111 -0
- package/client/vendor/paseo-plugin-helper/ui/plugin-reload-banner.tsx +119 -0
- package/client/vendor/paseo-plugin-helper/{settings-screen.tsx → ui/settings-screen.tsx} +3 -3
- package/client/vendor/paseo-plugin-helper/ui/settings.ts +36 -0
- package/client/vendor/paseo-plugin-helper/ui/text.tsx +821 -0
- package/client/vendor/paseo-plugin-helper/ui/theme.tsx +1 -0
- package/client/vendor/permission-audit/client.tsx +706 -0
- package/client/vendor/permission-audit/index.tsx +1 -0
- package/index.client.tsx +58 -37
- package/index.server.ts +256 -234
- package/npm-shrinkwrap.json +44 -0
- package/package.json +8 -4
- package/paseo-plugin.json +2 -0
- package/screenshots/1-paseo-top-modal-system.jpg +0 -0
- package/screenshots/2-paseo-top-modal-workspace.jpg +0 -0
- package/screenshots/3-paseo-top-modal-settings.jpg +0 -0
- package/screenshots/4-paseo-top-modal-about.jpg +0 -0
- package/screenshots/paseo-top-dashboard.jpg +0 -0
- package/screenshots/paseo-top-general-view.jpg +0 -0
- package/server/resources.ts +98 -0
- package/server/vendor/paseo-plugin-helper/custom-pills.ts +70 -0
- package/server/vendor/paseo-plugin-helper/index.ts +2 -0
- package/server/vendor/paseo-plugin-helper/logger.ts +5 -16
- package/server/vendor/paseo-plugin-helper/rpc-guard.ts +2 -2
- package/server/vendor/paseo-plugin-helper/storage-guard.ts +294 -0
- package/server/vendor/paseo-plugin-helper/storage.ts +36 -2
- package/server/vendor/paseo-plugin-helper/telemetry.ts +486 -0
- package/server/vendor/paseo-plugin-helper/tickets.ts +18 -67
- package/server/vendor/permission-audit/index.ts +1 -0
- package/server/vendor/permission-audit/server.ts +839 -0
- package/shared/helper-version.ts +1 -1
- package/shared/multi-host.ts +153 -159
- package/shared/resources.ts +145 -0
- package/shared/vendor/paseo-plugin-helper/async.ts +14 -7
- package/shared/vendor/paseo-plugin-helper/features.ts +244 -0
- package/shared/vendor/paseo-plugin-helper/forge.ts +27 -0
- package/shared/vendor/paseo-plugin-helper/index.ts +1 -0
- package/shared/vendor/paseo-plugin-helper/tickets.ts +122 -28
- package/shared/vendor/permission-audit/filter.ts +61 -0
- package/shared/vendor/permission-audit/index.ts +2 -0
- package/shared/vendor/permission-audit/shared.ts +119 -0
- package/shared/version.ts +1 -1
- package/client/vendor/paseo-plugin-helper/components/AboutSection.tsx +0 -493
- package/client/vendor/paseo-plugin-helper/components/AttentionBeacon.tsx +0 -250
- package/client/vendor/paseo-plugin-helper/components/Badge.tsx +0 -168
- package/client/vendor/paseo-plugin-helper/components/Button.tsx +0 -204
- package/client/vendor/paseo-plugin-helper/components/Card.tsx +0 -257
- package/client/vendor/paseo-plugin-helper/components/CodeBlock.tsx +0 -196
- package/client/vendor/paseo-plugin-helper/components/Collapsible.tsx +0 -277
- package/client/vendor/paseo-plugin-helper/components/CommandBox.tsx +0 -172
- package/client/vendor/paseo-plugin-helper/components/CopyButton.tsx +0 -180
- package/client/vendor/paseo-plugin-helper/components/EmptyState.tsx +0 -97
- package/client/vendor/paseo-plugin-helper/components/HighlightedText.tsx +0 -70
- package/client/vendor/paseo-plugin-helper/components/InlineButton.tsx +0 -88
- package/client/vendor/paseo-plugin-helper/components/InteractiveRow.tsx +0 -142
- package/client/vendor/paseo-plugin-helper/components/KeyValue.tsx +0 -446
- package/client/vendor/paseo-plugin-helper/components/MetricGauge.tsx +0 -247
- package/client/vendor/paseo-plugin-helper/components/ProgressBar.tsx +0 -117
- package/client/vendor/paseo-plugin-helper/components/Responsive.tsx +0 -53
- package/client/vendor/paseo-plugin-helper/components/SearchInput.tsx +0 -132
- package/client/vendor/paseo-plugin-helper/components/SectionHeader.tsx +0 -87
- package/client/vendor/paseo-plugin-helper/components/Select.tsx +0 -283
- package/client/vendor/paseo-plugin-helper/components/StatusDot.tsx +0 -80
- package/client/vendor/paseo-plugin-helper/components/Tabs.tsx +0 -319
- package/client/vendor/paseo-plugin-helper/components/TextInput.tsx +0 -150
- package/client/vendor/paseo-plugin-helper/components/Toggle.tsx +0 -163
- package/client/vendor/paseo-plugin-helper/components/TruncatedText.tsx +0 -157
- package/client/vendor/paseo-plugin-helper/components/index.ts +0 -26
- package/client/vendor/paseo-plugin-helper/custom-pills.tsx +0 -225
- package/client/vendor/paseo-plugin-helper/forge-icon.tsx +0 -79
- package/client/vendor/paseo-plugin-helper/icon.tsx +0 -39
- package/client/vendor/paseo-plugin-helper/index.ts +0 -30
- package/client/vendor/paseo-plugin-helper/layout/ActionBar.tsx +0 -49
- package/client/vendor/paseo-plugin-helper/layout/FormRow.tsx +0 -103
- package/client/vendor/paseo-plugin-helper/layout/Grid.tsx +0 -65
- package/client/vendor/paseo-plugin-helper/layout/ModalBody.tsx +0 -368
- package/client/vendor/paseo-plugin-helper/layout/ModalContent.tsx +0 -51
- package/client/vendor/paseo-plugin-helper/layout/Row.tsx +0 -39
- package/client/vendor/paseo-plugin-helper/layout/Stack.tsx +0 -39
- package/client/vendor/paseo-plugin-helper/layout/index.ts +0 -7
- package/client/vendor/paseo-plugin-helper/styles/index.ts +0 -1
- package/client/vendor/paseo-plugin-helper/styles/recipes.ts +0 -550
- package/client/vendor/paseo-plugin-helper/theme/flair.ts +0 -76
- package/client/vendor/paseo-plugin-helper/theme/host-variables.ts +0 -121
- package/client/vendor/paseo-plugin-helper/theme/index.ts +0 -7
- package/client/vendor/paseo-plugin-helper/theme/provider.tsx +0 -178
- package/client/vendor/paseo-plugin-helper/theme/responsive.ts +0 -213
- package/client/vendor/paseo-plugin-helper/theme/tokens.ts +0 -200
- package/client/vendor/paseo-plugin-helper/theme/useResponsive.ts +0 -57
- package/client/vendor/paseo-plugin-helper/tickets.tsx +0 -766
- /package/client/vendor/paseo-plugin-helper/{command-center.ts → core/command-center.ts} +0 -0
- /package/client/vendor/paseo-plugin-helper/{snapshot.ts → core/snapshot.ts} +0 -0
- /package/client/vendor/paseo-plugin-helper/{utils → lifecycle}/haptics.ts +0 -0
|
@@ -1,277 +0,0 @@
|
|
|
1
|
-
import React, { useState } from "react";
|
|
2
|
-
import {
|
|
3
|
-
Pressable,
|
|
4
|
-
StyleSheet,
|
|
5
|
-
Text,
|
|
6
|
-
View,
|
|
7
|
-
type StyleProp,
|
|
8
|
-
type ViewStyle,
|
|
9
|
-
} from "react-native";
|
|
10
|
-
import { getClientHost } from "../host";
|
|
11
|
-
import { usePluginTheme } from "../theme/provider";
|
|
12
|
-
import type { SurfaceStyle } from "../theme/flair";
|
|
13
|
-
import type { ThemeColors } from "../../../../shared/vendor/paseo-plugin-helper/types";
|
|
14
|
-
|
|
15
|
-
export interface CollapsibleProps {
|
|
16
|
-
title?: string | React.ReactNode;
|
|
17
|
-
subtitle?: string | React.ReactNode;
|
|
18
|
-
children: React.ReactNode;
|
|
19
|
-
initiallyExpanded?: boolean;
|
|
20
|
-
isExpanded?: boolean;
|
|
21
|
-
onToggle?: (expanded: boolean) => void;
|
|
22
|
-
badge?: React.ReactNode;
|
|
23
|
-
headerRight?: React.ReactNode;
|
|
24
|
-
summary?: React.ReactNode;
|
|
25
|
-
icon?: string;
|
|
26
|
-
style?: StyleProp<ViewStyle>;
|
|
27
|
-
headerStyle?: StyleProp<ViewStyle>;
|
|
28
|
-
contentStyle?: StyleProp<ViewStyle>;
|
|
29
|
-
variant?: SurfaceStyle;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function resolveCollapsibleChevron(expanded: boolean): string {
|
|
33
|
-
return expanded ? "ChevronDown" : "ChevronRight";
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Container background for a Collapsible, honoring the same `SurfaceStyle`
|
|
38
|
-
* contract as `Card`:
|
|
39
|
-
* - "flat" (default): surface0, the normal page surface.
|
|
40
|
-
* - "elevated": surface1, so the card visibly lifts off the page.
|
|
41
|
-
* - "tinted": a faint accent wash.
|
|
42
|
-
*
|
|
43
|
-
* Before this existed, `variant="elevated"` only changed the border radius and
|
|
44
|
-
* the container stayed `surface0` — so a card placed on an already-`surface0`
|
|
45
|
-
* timeline read as a bleeding shaded band with no elevation (#208).
|
|
46
|
-
*/
|
|
47
|
-
export function resolveCollapsibleSurface(
|
|
48
|
-
colors: ThemeColors,
|
|
49
|
-
alpha: (color: string, opacity: number) => string,
|
|
50
|
-
variant: SurfaceStyle = "flat",
|
|
51
|
-
): { backgroundColor: string; borderColor: string } {
|
|
52
|
-
switch (variant) {
|
|
53
|
-
case "elevated":
|
|
54
|
-
return { backgroundColor: colors.surface1, borderColor: colors.border };
|
|
55
|
-
case "tinted":
|
|
56
|
-
return {
|
|
57
|
-
backgroundColor: alpha(colors.accent, 0.04),
|
|
58
|
-
borderColor: alpha(colors.accent, 0.2),
|
|
59
|
-
};
|
|
60
|
-
case "flat":
|
|
61
|
-
default:
|
|
62
|
-
return { backgroundColor: colors.surface0, borderColor: colors.border };
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Header stripe background. Kept for backwards compatibility; prefer
|
|
68
|
-
* {@link resolveCollapsibleSurface} for the container and use this only for the
|
|
69
|
-
* pressed/unpressed header delta.
|
|
70
|
-
*/
|
|
71
|
-
export function resolveCollapsibleHeaderBackground(
|
|
72
|
-
colors: ThemeColors,
|
|
73
|
-
pressed: boolean,
|
|
74
|
-
): string {
|
|
75
|
-
return pressed ? colors.surface1 : colors.surface0;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function renderTitle(title: string | React.ReactNode, titleStyle: object) {
|
|
79
|
-
if (typeof title === "string" || title === undefined) {
|
|
80
|
-
return title ? <Text style={titleStyle}>{title}</Text> : null;
|
|
81
|
-
}
|
|
82
|
-
return <View style={styles.titleSlot}>{title}</View>;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function renderSubtitle(subtitle: string | React.ReactNode, subtitleStyle: object) {
|
|
86
|
-
if (typeof subtitle === "string") {
|
|
87
|
-
return <Text style={subtitleStyle}>{subtitle}</Text>;
|
|
88
|
-
}
|
|
89
|
-
return subtitle ?? null;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export function Collapsible({
|
|
93
|
-
title,
|
|
94
|
-
subtitle,
|
|
95
|
-
children,
|
|
96
|
-
initiallyExpanded = false,
|
|
97
|
-
isExpanded: controlledExpanded,
|
|
98
|
-
onToggle,
|
|
99
|
-
badge,
|
|
100
|
-
headerRight,
|
|
101
|
-
summary,
|
|
102
|
-
icon,
|
|
103
|
-
style,
|
|
104
|
-
headerStyle,
|
|
105
|
-
contentStyle,
|
|
106
|
-
variant,
|
|
107
|
-
}: CollapsibleProps) {
|
|
108
|
-
const { Icon } = getClientHost();
|
|
109
|
-
const { colors, resolveRadius, isCompact, touchTargetMin, alpha } = usePluginTheme();
|
|
110
|
-
const [internalExpanded, setInternalExpanded] = useState(initiallyExpanded);
|
|
111
|
-
|
|
112
|
-
const isExpanded = controlledExpanded !== undefined ? controlledExpanded : internalExpanded;
|
|
113
|
-
const radius = resolveRadius(variant === "elevated" ? "lg" : "md");
|
|
114
|
-
const surface = resolveCollapsibleSurface(colors, alpha, variant);
|
|
115
|
-
|
|
116
|
-
const handlePress = () => {
|
|
117
|
-
const next = !isExpanded;
|
|
118
|
-
if (controlledExpanded === undefined) {
|
|
119
|
-
setInternalExpanded(next);
|
|
120
|
-
}
|
|
121
|
-
onToggle?.(next);
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
const titleStyle = {
|
|
125
|
-
color: colors.foreground,
|
|
126
|
-
fontSize: isCompact ? 12 : 13,
|
|
127
|
-
};
|
|
128
|
-
const subtitleStyle = {
|
|
129
|
-
color: colors.foregroundMuted,
|
|
130
|
-
fontSize: isCompact ? 11 : 12,
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
return (
|
|
134
|
-
<View
|
|
135
|
-
style={[
|
|
136
|
-
styles.container,
|
|
137
|
-
{
|
|
138
|
-
borderColor: surface.borderColor,
|
|
139
|
-
borderRadius: radius,
|
|
140
|
-
backgroundColor: surface.backgroundColor,
|
|
141
|
-
},
|
|
142
|
-
style,
|
|
143
|
-
]}
|
|
144
|
-
>
|
|
145
|
-
<Pressable
|
|
146
|
-
onPress={handlePress}
|
|
147
|
-
accessibilityRole="button"
|
|
148
|
-
accessibilityState={{ expanded: isExpanded }}
|
|
149
|
-
style={({ pressed }) => [
|
|
150
|
-
styles.header,
|
|
151
|
-
{
|
|
152
|
-
minHeight: Math.max(touchTargetMin, 36),
|
|
153
|
-
// Transparent by default: the container already carries the surface
|
|
154
|
-
// color. Painting the header with an opaque surface color is what
|
|
155
|
-
// made the first line read as a shaded band (#208). Only the
|
|
156
|
-
// pressed state tints, as interaction feedback.
|
|
157
|
-
backgroundColor: pressed ? alpha(colors.foreground, 0.06) : "transparent",
|
|
158
|
-
cursor: "pointer" as never,
|
|
159
|
-
},
|
|
160
|
-
headerStyle,
|
|
161
|
-
]}
|
|
162
|
-
>
|
|
163
|
-
<View style={styles.headerLeft}>
|
|
164
|
-
<View
|
|
165
|
-
style={[
|
|
166
|
-
styles.chevronBadge,
|
|
167
|
-
{
|
|
168
|
-
backgroundColor: alpha(colors.accent, 0.12),
|
|
169
|
-
borderColor: alpha(colors.accent, 0.25),
|
|
170
|
-
},
|
|
171
|
-
]}
|
|
172
|
-
>
|
|
173
|
-
<Icon
|
|
174
|
-
name={resolveCollapsibleChevron(isExpanded)}
|
|
175
|
-
size={14}
|
|
176
|
-
color={colors.foregroundMuted}
|
|
177
|
-
/>
|
|
178
|
-
</View>
|
|
179
|
-
{icon && <Icon name={icon} size={14} color={colors.accent} />}
|
|
180
|
-
<View style={styles.titleBlock}>
|
|
181
|
-
<View style={styles.titleRow}>
|
|
182
|
-
{renderTitle(title, [styles.title, titleStyle])}
|
|
183
|
-
{badge && <View style={styles.badgeSlot}>{badge}</View>}
|
|
184
|
-
</View>
|
|
185
|
-
{subtitle ? (
|
|
186
|
-
<View style={styles.subtitleSlot}>
|
|
187
|
-
{renderSubtitle(subtitle, [styles.subtitle, subtitleStyle])}
|
|
188
|
-
</View>
|
|
189
|
-
) : null}
|
|
190
|
-
{summary ? <View style={styles.summarySlot}>{summary}</View> : null}
|
|
191
|
-
</View>
|
|
192
|
-
</View>
|
|
193
|
-
|
|
194
|
-
{(headerRight || summary === undefined) && (
|
|
195
|
-
<View style={styles.headerRight}>{headerRight}</View>
|
|
196
|
-
)}
|
|
197
|
-
</Pressable>
|
|
198
|
-
|
|
199
|
-
{isExpanded && (
|
|
200
|
-
<View
|
|
201
|
-
style={[
|
|
202
|
-
styles.content,
|
|
203
|
-
{ borderTopColor: colors.border, borderTopWidth: 1 },
|
|
204
|
-
contentStyle,
|
|
205
|
-
]}
|
|
206
|
-
>
|
|
207
|
-
{children}
|
|
208
|
-
</View>
|
|
209
|
-
)}
|
|
210
|
-
</View>
|
|
211
|
-
);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
const styles = StyleSheet.create({
|
|
215
|
-
container: {
|
|
216
|
-
borderWidth: 1,
|
|
217
|
-
overflow: "hidden",
|
|
218
|
-
},
|
|
219
|
-
header: {
|
|
220
|
-
flexDirection: "row",
|
|
221
|
-
alignItems: "center",
|
|
222
|
-
justifyContent: "space-between",
|
|
223
|
-
paddingHorizontal: 12,
|
|
224
|
-
paddingVertical: 8,
|
|
225
|
-
},
|
|
226
|
-
headerLeft: {
|
|
227
|
-
flexDirection: "row",
|
|
228
|
-
alignItems: "center",
|
|
229
|
-
gap: 8,
|
|
230
|
-
flex: 1,
|
|
231
|
-
},
|
|
232
|
-
chevronBadge: {
|
|
233
|
-
width: 22,
|
|
234
|
-
height: 22,
|
|
235
|
-
borderRadius: 7,
|
|
236
|
-
borderWidth: 1,
|
|
237
|
-
alignItems: "center",
|
|
238
|
-
justifyContent: "center",
|
|
239
|
-
},
|
|
240
|
-
titleBlock: {
|
|
241
|
-
flex: 1,
|
|
242
|
-
},
|
|
243
|
-
titleRow: {
|
|
244
|
-
flexDirection: "row",
|
|
245
|
-
alignItems: "center",
|
|
246
|
-
gap: 8,
|
|
247
|
-
},
|
|
248
|
-
titleSlot: {
|
|
249
|
-
flexDirection: "row",
|
|
250
|
-
alignItems: "center",
|
|
251
|
-
},
|
|
252
|
-
badgeSlot: {
|
|
253
|
-
flexDirection: "row",
|
|
254
|
-
alignItems: "center",
|
|
255
|
-
},
|
|
256
|
-
subtitleSlot: {
|
|
257
|
-
marginTop: 2,
|
|
258
|
-
},
|
|
259
|
-
summarySlot: {
|
|
260
|
-
marginTop: 4,
|
|
261
|
-
flexDirection: "row",
|
|
262
|
-
},
|
|
263
|
-
headerRight: {
|
|
264
|
-
flexDirection: "row",
|
|
265
|
-
alignItems: "center",
|
|
266
|
-
marginLeft: 8,
|
|
267
|
-
},
|
|
268
|
-
title: {
|
|
269
|
-
fontWeight: "600",
|
|
270
|
-
},
|
|
271
|
-
subtitle: {
|
|
272
|
-
fontWeight: "400",
|
|
273
|
-
},
|
|
274
|
-
content: {
|
|
275
|
-
padding: 12,
|
|
276
|
-
},
|
|
277
|
-
});
|
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
import React, { useState } from "react";
|
|
2
|
-
import {
|
|
3
|
-
Platform,
|
|
4
|
-
Pressable,
|
|
5
|
-
StyleSheet,
|
|
6
|
-
Text,
|
|
7
|
-
View,
|
|
8
|
-
type StyleProp,
|
|
9
|
-
type TextStyle,
|
|
10
|
-
type ViewStyle,
|
|
11
|
-
} from "react-native";
|
|
12
|
-
import { getClientHost } from "../host";
|
|
13
|
-
import { usePluginTheme } from "../theme/provider";
|
|
14
|
-
import { copyToClipboard } from "../utils/clipboard";
|
|
15
|
-
|
|
16
|
-
export interface CommandBoxProps {
|
|
17
|
-
/** argv array — program is rendered bold, args muted */
|
|
18
|
-
argv?: string[];
|
|
19
|
-
/** Optional override to display a pre-joined string instead of argv */
|
|
20
|
-
command?: string;
|
|
21
|
-
style?: StyleProp<ViewStyle>;
|
|
22
|
-
textStyle?: StyleProp<TextStyle>;
|
|
23
|
-
/** Accessibility label override for the copy button */
|
|
24
|
-
copyLabel?: string;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function formatCommandLine(argv: string[]): string {
|
|
28
|
-
return argv.map((arg) => (arg.includes(" ") ? JSON.stringify(arg) : arg)).join(" ");
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function CommandBox({
|
|
32
|
-
argv = [],
|
|
33
|
-
command,
|
|
34
|
-
style,
|
|
35
|
-
textStyle,
|
|
36
|
-
copyLabel = "Copy command",
|
|
37
|
-
}: CommandBoxProps): React.ReactElement | null {
|
|
38
|
-
const { colors, resolveRadius } = usePluginTheme();
|
|
39
|
-
const { Icon, useToast } = getClientHost();
|
|
40
|
-
const toast = useToast();
|
|
41
|
-
const [copied, setCopied] = useState(false);
|
|
42
|
-
|
|
43
|
-
const fullCommand = command ?? formatCommandLine(argv);
|
|
44
|
-
const [prog, ...rest] = argv;
|
|
45
|
-
|
|
46
|
-
const handleCopy = async () => {
|
|
47
|
-
if (!fullCommand) return;
|
|
48
|
-
const ok = await copyToClipboard(fullCommand, {
|
|
49
|
-
toast,
|
|
50
|
-
toastMessage: "Command",
|
|
51
|
-
});
|
|
52
|
-
if (ok) {
|
|
53
|
-
setCopied(true);
|
|
54
|
-
setTimeout(() => setCopied(false), 2000);
|
|
55
|
-
}
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
const fontFamily = Platform.select({
|
|
59
|
-
ios: "Menlo",
|
|
60
|
-
android: "monospace",
|
|
61
|
-
default: "monospace",
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
const radius = resolveRadius("sm");
|
|
65
|
-
|
|
66
|
-
return (
|
|
67
|
-
<View
|
|
68
|
-
style={[
|
|
69
|
-
styles.container,
|
|
70
|
-
{
|
|
71
|
-
backgroundColor: colors.surface2,
|
|
72
|
-
borderColor: colors.border,
|
|
73
|
-
borderRadius: radius,
|
|
74
|
-
},
|
|
75
|
-
style,
|
|
76
|
-
]}
|
|
77
|
-
>
|
|
78
|
-
<View style={styles.textContainer}>
|
|
79
|
-
<Text
|
|
80
|
-
style={[
|
|
81
|
-
styles.prompt,
|
|
82
|
-
{
|
|
83
|
-
color: colors.statusWarning,
|
|
84
|
-
fontFamily,
|
|
85
|
-
},
|
|
86
|
-
]}
|
|
87
|
-
>
|
|
88
|
-
$
|
|
89
|
-
</Text>
|
|
90
|
-
<Text
|
|
91
|
-
selectable
|
|
92
|
-
numberOfLines={2}
|
|
93
|
-
style={[
|
|
94
|
-
styles.commandText,
|
|
95
|
-
{
|
|
96
|
-
color: colors.foreground,
|
|
97
|
-
fontFamily,
|
|
98
|
-
},
|
|
99
|
-
textStyle,
|
|
100
|
-
]}
|
|
101
|
-
>
|
|
102
|
-
{prog ? (
|
|
103
|
-
<>
|
|
104
|
-
<Text style={styles.bold}>{prog}</Text>
|
|
105
|
-
{rest.length > 0 ? (
|
|
106
|
-
<Text style={{ color: colors.foregroundMuted }}>
|
|
107
|
-
{" " + rest.map((a) => (a.includes(" ") ? JSON.stringify(a) : a)).join(" ")}
|
|
108
|
-
</Text>
|
|
109
|
-
) : null}
|
|
110
|
-
</>
|
|
111
|
-
) : (
|
|
112
|
-
command ?? ""
|
|
113
|
-
)}
|
|
114
|
-
</Text>
|
|
115
|
-
</View>
|
|
116
|
-
<Pressable
|
|
117
|
-
onPress={handleCopy}
|
|
118
|
-
accessibilityRole="button"
|
|
119
|
-
accessibilityLabel={copyLabel}
|
|
120
|
-
hitSlop={8}
|
|
121
|
-
style={({ pressed }) => [
|
|
122
|
-
styles.copyButton,
|
|
123
|
-
{
|
|
124
|
-
backgroundColor: pressed ? colors.surface1 : "transparent",
|
|
125
|
-
borderRadius: Math.max(2, radius - 2),
|
|
126
|
-
},
|
|
127
|
-
]}
|
|
128
|
-
>
|
|
129
|
-
<Icon
|
|
130
|
-
name={copied ? "Check" : "Copy"}
|
|
131
|
-
size={13}
|
|
132
|
-
color={copied ? colors.statusSuccess : colors.foregroundMuted}
|
|
133
|
-
/>
|
|
134
|
-
</Pressable>
|
|
135
|
-
</View>
|
|
136
|
-
);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const styles = StyleSheet.create({
|
|
140
|
-
container: {
|
|
141
|
-
flexDirection: "row",
|
|
142
|
-
alignItems: "center",
|
|
143
|
-
justifyContent: "space-between",
|
|
144
|
-
borderWidth: 1,
|
|
145
|
-
paddingHorizontal: 10,
|
|
146
|
-
paddingVertical: 6,
|
|
147
|
-
gap: 8,
|
|
148
|
-
},
|
|
149
|
-
textContainer: {
|
|
150
|
-
flexDirection: "row",
|
|
151
|
-
alignItems: "center",
|
|
152
|
-
flex: 1,
|
|
153
|
-
overflow: "hidden",
|
|
154
|
-
gap: 6,
|
|
155
|
-
},
|
|
156
|
-
prompt: {
|
|
157
|
-
fontWeight: "700",
|
|
158
|
-
fontSize: 12,
|
|
159
|
-
},
|
|
160
|
-
commandText: {
|
|
161
|
-
fontSize: 12,
|
|
162
|
-
flex: 1,
|
|
163
|
-
},
|
|
164
|
-
bold: {
|
|
165
|
-
fontWeight: "700",
|
|
166
|
-
},
|
|
167
|
-
copyButton: {
|
|
168
|
-
padding: 4,
|
|
169
|
-
alignItems: "center",
|
|
170
|
-
justifyContent: "center",
|
|
171
|
-
},
|
|
172
|
-
});
|
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
import React, { useEffect, useRef, useState } from "react";
|
|
2
|
-
import {
|
|
3
|
-
Pressable,
|
|
4
|
-
StyleSheet,
|
|
5
|
-
Text,
|
|
6
|
-
type StyleProp,
|
|
7
|
-
type TextStyle,
|
|
8
|
-
type ViewStyle,
|
|
9
|
-
} from "react-native";
|
|
10
|
-
import { getClientHost } from "../host";
|
|
11
|
-
import { usePluginTheme } from "../theme/provider";
|
|
12
|
-
import { copyToClipboard } from "../utils/clipboard";
|
|
13
|
-
|
|
14
|
-
export type CopyButtonSize = "sm" | "md";
|
|
15
|
-
export type CopyButtonVariant = "ghost" | "secondary";
|
|
16
|
-
|
|
17
|
-
export interface CopyButtonFeedback {
|
|
18
|
-
icon: string;
|
|
19
|
-
label: string;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Pure idle/copied visual state, split out so the feedback contract is
|
|
24
|
-
* testable without a renderer or a clipboard. Callers may override the idle
|
|
25
|
-
* `icon`/`label`; the copied state is always the Check/"Copied!" affordance.
|
|
26
|
-
*/
|
|
27
|
-
export function resolveCopyButtonFeedback(
|
|
28
|
-
copied: boolean,
|
|
29
|
-
options: { icon?: string; label?: string; copiedLabel?: string } = {},
|
|
30
|
-
): CopyButtonFeedback {
|
|
31
|
-
const idleIcon = options.icon ?? "Copy";
|
|
32
|
-
const idleLabel = options.label ?? "Copy";
|
|
33
|
-
const copiedLabel = options.copiedLabel ?? "Copied!";
|
|
34
|
-
return copied ? { icon: "Check", label: copiedLabel } : { icon: idleIcon, label: idleLabel };
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export interface CopyButtonProps {
|
|
38
|
-
/** Literal text to copy. Ignored when `getText` is provided. */
|
|
39
|
-
text?: string;
|
|
40
|
-
/** Lazy/Promise text source, resolved at press time. Takes precedence over `text`. */
|
|
41
|
-
getText?: () => string | Promise<string>;
|
|
42
|
-
/** Idle button label. Defaults to "Copy". */
|
|
43
|
-
label?: string;
|
|
44
|
-
/** Label shown after a successful copy. Defaults to "Copied!". */
|
|
45
|
-
copiedLabel?: string;
|
|
46
|
-
/** Icon name for the idle state. Defaults to "Copy". */
|
|
47
|
-
icon?: string;
|
|
48
|
-
size?: CopyButtonSize;
|
|
49
|
-
variant?: CopyButtonVariant;
|
|
50
|
-
accessibilityLabel?: string;
|
|
51
|
-
/** Forwarded to the clipboard toast (e.g. "timeline card"). */
|
|
52
|
-
toastMessage?: string;
|
|
53
|
-
/** How long the Check/"Copied!" feedback stays up. Defaults to 2000ms. */
|
|
54
|
-
feedbackDurationMs?: number;
|
|
55
|
-
disabled?: boolean;
|
|
56
|
-
style?: StyleProp<ViewStyle>;
|
|
57
|
-
textStyle?: StyleProp<TextStyle>;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Explicit copy affordance for styled/plugin surfaces.
|
|
62
|
-
*
|
|
63
|
-
* Web's selection-copy handler only rebuilds clipboard content for
|
|
64
|
-
* `[data-testid="assistant-message"]` selections, so styled timeline, telemetry,
|
|
65
|
-
* and panel content copies nothing (xpufx-org/paseo#278). This bypasses that
|
|
66
|
-
* gate with the helper's host `copyText` / `copyToClipboard` path and shows
|
|
67
|
-
* Check/"Copied!" feedback, matching CodeBlock's pattern.
|
|
68
|
-
*/
|
|
69
|
-
export function CopyButton({
|
|
70
|
-
text,
|
|
71
|
-
getText,
|
|
72
|
-
label,
|
|
73
|
-
copiedLabel,
|
|
74
|
-
icon,
|
|
75
|
-
size = "sm",
|
|
76
|
-
variant = "ghost",
|
|
77
|
-
accessibilityLabel,
|
|
78
|
-
toastMessage,
|
|
79
|
-
feedbackDurationMs = 2000,
|
|
80
|
-
disabled = false,
|
|
81
|
-
style,
|
|
82
|
-
textStyle,
|
|
83
|
-
}: CopyButtonProps): React.ReactElement | null {
|
|
84
|
-
const { Icon, useToast } = getClientHost();
|
|
85
|
-
const { colors, resolveRadius, touchTargetMin, alpha } = usePluginTheme();
|
|
86
|
-
const toast = useToast();
|
|
87
|
-
const [copied, setCopied] = useState(false);
|
|
88
|
-
const timer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
|
|
89
|
-
|
|
90
|
-
useEffect(() => {
|
|
91
|
-
return () => {
|
|
92
|
-
if (timer.current) clearTimeout(timer.current);
|
|
93
|
-
};
|
|
94
|
-
}, []);
|
|
95
|
-
|
|
96
|
-
const hasSource = getText !== undefined || text !== undefined;
|
|
97
|
-
if (!hasSource) return null;
|
|
98
|
-
|
|
99
|
-
const feedback = resolveCopyButtonFeedback(copied, { icon, label, copiedLabel });
|
|
100
|
-
const iconNode = (
|
|
101
|
-
<Icon
|
|
102
|
-
name={feedback.icon}
|
|
103
|
-
size={size === "sm" ? 12 : 14}
|
|
104
|
-
color={copied ? colors.statusSuccess : colors.foregroundMuted}
|
|
105
|
-
/>
|
|
106
|
-
);
|
|
107
|
-
|
|
108
|
-
const secondary = variant === "secondary";
|
|
109
|
-
const bg = secondary ? colors.surface1 : "transparent";
|
|
110
|
-
const border = secondary ? colors.border : "transparent";
|
|
111
|
-
const radius = resolveRadius(size === "sm" ? "sm" : "md");
|
|
112
|
-
|
|
113
|
-
const handleCopy = async () => {
|
|
114
|
-
if (disabled) return;
|
|
115
|
-
let value: string | undefined;
|
|
116
|
-
try {
|
|
117
|
-
value = getText ? await getText() : text;
|
|
118
|
-
} catch {
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
if (value === undefined || value === null) return;
|
|
122
|
-
|
|
123
|
-
const ok = await copyToClipboard(String(value), { toast, toastMessage });
|
|
124
|
-
if (!ok) return;
|
|
125
|
-
|
|
126
|
-
setCopied(true);
|
|
127
|
-
if (timer.current) clearTimeout(timer.current);
|
|
128
|
-
timer.current = setTimeout(() => setCopied(false), feedbackDurationMs);
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
return (
|
|
132
|
-
<Pressable
|
|
133
|
-
onPress={handleCopy}
|
|
134
|
-
disabled={disabled}
|
|
135
|
-
accessibilityRole="button"
|
|
136
|
-
accessibilityLabel={accessibilityLabel || label || "Copy"}
|
|
137
|
-
hitSlop={Math.max(0, (touchTargetMin - 28) / 2)}
|
|
138
|
-
style={({ pressed }) => [
|
|
139
|
-
styles.base,
|
|
140
|
-
{
|
|
141
|
-
backgroundColor: pressed && !disabled ? alpha(colors.surface2, secondary ? 1 : 0.7) : bg,
|
|
142
|
-
borderColor: border,
|
|
143
|
-
borderWidth: border !== "transparent" ? 1 : 0,
|
|
144
|
-
borderRadius: radius,
|
|
145
|
-
paddingVertical: size === "sm" ? 3 : 5,
|
|
146
|
-
paddingHorizontal: size === "sm" ? 8 : 10,
|
|
147
|
-
opacity: disabled ? 0.45 : 1,
|
|
148
|
-
},
|
|
149
|
-
style,
|
|
150
|
-
]}
|
|
151
|
-
>
|
|
152
|
-
{iconNode}
|
|
153
|
-
{feedback.label !== "" ? (
|
|
154
|
-
<Text
|
|
155
|
-
style={[
|
|
156
|
-
styles.text,
|
|
157
|
-
{
|
|
158
|
-
fontSize: size === "sm" ? 11 : 12,
|
|
159
|
-
color: copied ? colors.statusSuccess : colors.foregroundMuted,
|
|
160
|
-
},
|
|
161
|
-
textStyle,
|
|
162
|
-
]}
|
|
163
|
-
>
|
|
164
|
-
{feedback.label}
|
|
165
|
-
</Text>
|
|
166
|
-
) : null}
|
|
167
|
-
</Pressable>
|
|
168
|
-
);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const styles = StyleSheet.create({
|
|
172
|
-
base: {
|
|
173
|
-
flexDirection: "row",
|
|
174
|
-
alignItems: "center",
|
|
175
|
-
gap: 4,
|
|
176
|
-
},
|
|
177
|
-
text: {
|
|
178
|
-
fontWeight: "500",
|
|
179
|
-
},
|
|
180
|
-
});
|