@peers-app/peers-ui 0.28.3 → 0.28.5
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/components/data-grid/autocomplete.d.ts +31 -0
- package/dist/components/data-grid/autocomplete.js +199 -0
- package/dist/components/data-grid/data-grid.d.ts +56 -0
- package/dist/components/data-grid/data-grid.js +179 -0
- package/dist/components/data-grid/index.d.ts +3 -0
- package/dist/components/data-grid/index.js +7 -0
- package/dist/components/data-grid/reactive.d.ts +5 -0
- package/dist/components/data-grid/reactive.js +9 -0
- package/dist/components/router.js +2 -9
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/screens/settings/settings-page.js +1 -8
- package/dist/tabs-layout/tabs-layout.js +2 -1
- package/package.json +3 -3
- package/dist/screens/tools/tool-test-details.d.ts +0 -3
- package/dist/screens/tools/tool-test-details.js +0 -38
- package/dist/screens/tools/tool-test-list.d.ts +0 -3
- package/dist/screens/tools/tool-test-list.js +0 -43
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type Ref } from "react";
|
|
2
|
+
import { type GridValue } from "./reactive";
|
|
3
|
+
/** Single-selection lookup, independent of any table, ORM, or contract. */
|
|
4
|
+
export interface AutocompleteProps<T> {
|
|
5
|
+
/** Current selection; observable updates are reflected without recreating the control. */
|
|
6
|
+
value: GridValue<T | null>;
|
|
7
|
+
/** Local choices in display order, optionally supplied by a Peers observable. */
|
|
8
|
+
options?: GridValue<readonly T[]>;
|
|
9
|
+
/** Remote search. Stale responses are ignored and the signal is aborted on replacement. */
|
|
10
|
+
loadOptions?: (query: string, signal: AbortSignal) => Promise<readonly T[]>;
|
|
11
|
+
getOptionKey: (option: T) => string;
|
|
12
|
+
getOptionLabel: (option: T) => string;
|
|
13
|
+
/** Explicit write boundary; a writable local observable can be passed directly. */
|
|
14
|
+
onChange: (value: T | null) => void;
|
|
15
|
+
label: string;
|
|
16
|
+
placeholder?: string;
|
|
17
|
+
disabled?: boolean;
|
|
18
|
+
required?: boolean;
|
|
19
|
+
clearable?: boolean;
|
|
20
|
+
minLength?: number;
|
|
21
|
+
debounceMs?: number;
|
|
22
|
+
maxResults?: number;
|
|
23
|
+
inputRef?: Ref<HTMLInputElement>;
|
|
24
|
+
invalid?: boolean;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Searchable, clearable Bootstrap combobox inspired by JobTrak's Typeahead/FK controls.
|
|
28
|
+
* Arrow keys and Enter belong to the popup while open; Tab selects a highlighted result
|
|
29
|
+
* and continues naturally. The popup is portaled so a scrolling grid cannot clip it.
|
|
30
|
+
*/
|
|
31
|
+
export declare function Autocomplete<T>(props: AutocompleteProps<T>): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Autocomplete = Autocomplete;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const react_1 = require("react");
|
|
6
|
+
const react_dom_1 = require("react-dom");
|
|
7
|
+
const reactive_1 = require("./reactive");
|
|
8
|
+
const emptyOptions = [];
|
|
9
|
+
// Bootstrap button states remain available without drawing separate button boxes.
|
|
10
|
+
const indicatorStyle = {
|
|
11
|
+
width: "2rem",
|
|
12
|
+
height: "100%",
|
|
13
|
+
padding: 0,
|
|
14
|
+
border: 0,
|
|
15
|
+
"--bs-btn-color": "var(--bs-tertiary-color)",
|
|
16
|
+
"--bs-btn-hover-color": "var(--bs-secondary-color)",
|
|
17
|
+
"--bs-btn-active-color": "var(--bs-body-color)",
|
|
18
|
+
"--bs-btn-disabled-color": "var(--bs-tertiary-color)",
|
|
19
|
+
"--bs-btn-bg": "transparent",
|
|
20
|
+
"--bs-btn-hover-bg": "transparent",
|
|
21
|
+
"--bs-btn-active-bg": "transparent",
|
|
22
|
+
"--bs-btn-disabled-bg": "transparent",
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Searchable, clearable Bootstrap combobox inspired by JobTrak's Typeahead/FK controls.
|
|
26
|
+
* Arrow keys and Enter belong to the popup while open; Tab selects a highlighted result
|
|
27
|
+
* and continues naturally. The popup is portaled so a scrolling grid cannot clip it.
|
|
28
|
+
*/
|
|
29
|
+
function Autocomplete(props) {
|
|
30
|
+
const value = (0, reactive_1.useGridValue)(props.value);
|
|
31
|
+
const options = (0, reactive_1.useGridValue)(props.options ?? emptyOptions);
|
|
32
|
+
const id = (0, react_1.useId)();
|
|
33
|
+
const input = (0, react_1.useRef)(null);
|
|
34
|
+
const [open, setOpen] = (0, react_1.useState)(false);
|
|
35
|
+
const [query, setQuery] = (0, react_1.useState)(null);
|
|
36
|
+
const [active, setActive] = (0, react_1.useState)(-1);
|
|
37
|
+
const [remote, setRemote] = (0, react_1.useState)([]);
|
|
38
|
+
const [loading, setLoading] = (0, react_1.useState)(false);
|
|
39
|
+
const [error, setError] = (0, react_1.useState)("");
|
|
40
|
+
const [position, setPosition] = (0, react_1.useState)({ left: 0, top: 0, width: 0, maxHeight: 240 });
|
|
41
|
+
const search = query ?? "";
|
|
42
|
+
const eligible = search.length >= (props.minLength ?? 0);
|
|
43
|
+
const results = (props.loadOptions
|
|
44
|
+
? remote
|
|
45
|
+
: options.filter((option) => props.getOptionLabel(option).toLocaleLowerCase().includes(search.toLocaleLowerCase()))).slice(0, props.maxResults ?? 100);
|
|
46
|
+
(0, react_1.useEffect)(() => {
|
|
47
|
+
if (!open || props.disabled || !eligible || !props.loadOptions) {
|
|
48
|
+
setLoading(false);
|
|
49
|
+
setRemote([]);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const controller = new AbortController();
|
|
53
|
+
let current = true;
|
|
54
|
+
setLoading(true);
|
|
55
|
+
setRemote([]);
|
|
56
|
+
setError("");
|
|
57
|
+
const timer = setTimeout(async () => {
|
|
58
|
+
try {
|
|
59
|
+
const rows = await props.loadOptions?.(search, controller.signal);
|
|
60
|
+
if (current)
|
|
61
|
+
setRemote(rows ?? []);
|
|
62
|
+
}
|
|
63
|
+
catch (cause) {
|
|
64
|
+
if (current)
|
|
65
|
+
setError(cause instanceof Error ? cause.message : "Could not load choices");
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
if (current)
|
|
69
|
+
setLoading(false);
|
|
70
|
+
}
|
|
71
|
+
}, props.debounceMs ?? 150);
|
|
72
|
+
return () => {
|
|
73
|
+
current = false;
|
|
74
|
+
controller.abort();
|
|
75
|
+
clearTimeout(timer);
|
|
76
|
+
};
|
|
77
|
+
}, [open, props.disabled, search, eligible, props.loadOptions, props.debounceMs]);
|
|
78
|
+
(0, react_1.useLayoutEffect)(() => {
|
|
79
|
+
if (!open)
|
|
80
|
+
return;
|
|
81
|
+
const place = () => {
|
|
82
|
+
const rect = input.current?.parentElement?.getBoundingClientRect();
|
|
83
|
+
if (!rect)
|
|
84
|
+
return;
|
|
85
|
+
const below = window.innerHeight - rect.bottom - 8;
|
|
86
|
+
const height = Math.min(240, Math.max(100, below >= 140 ? below : rect.top - 8));
|
|
87
|
+
setPosition({
|
|
88
|
+
left: rect.left,
|
|
89
|
+
width: rect.width,
|
|
90
|
+
top: below >= 140 ? rect.bottom : Math.max(8, rect.top - height),
|
|
91
|
+
maxHeight: height,
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
place();
|
|
95
|
+
window.addEventListener("resize", place);
|
|
96
|
+
window.addEventListener("scroll", place, true);
|
|
97
|
+
return () => {
|
|
98
|
+
window.removeEventListener("resize", place);
|
|
99
|
+
window.removeEventListener("scroll", place, true);
|
|
100
|
+
};
|
|
101
|
+
}, [open]);
|
|
102
|
+
(0, react_1.useEffect)(() => {
|
|
103
|
+
if (open && active >= 0)
|
|
104
|
+
document.getElementById(`${id}-${active}`)?.scrollIntoView?.({ block: "nearest" });
|
|
105
|
+
}, [open, active, id]);
|
|
106
|
+
const close = () => {
|
|
107
|
+
setOpen(false);
|
|
108
|
+
setQuery(null);
|
|
109
|
+
setActive(-1);
|
|
110
|
+
};
|
|
111
|
+
const choose = (option) => {
|
|
112
|
+
props.onChange(option);
|
|
113
|
+
close();
|
|
114
|
+
};
|
|
115
|
+
const setRef = (node) => {
|
|
116
|
+
input.current = node;
|
|
117
|
+
if (typeof props.inputRef === "function")
|
|
118
|
+
props.inputRef(node);
|
|
119
|
+
else if (props.inputRef)
|
|
120
|
+
props.inputRef.current = node;
|
|
121
|
+
};
|
|
122
|
+
const show = open && !props.disabled;
|
|
123
|
+
const showClear = (props.clearable ?? !props.required) && value !== null;
|
|
124
|
+
const selectedLabel = value === null ? "" : props.getOptionLabel(value);
|
|
125
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "peers-autocomplete position-relative", style: { minWidth: 0, width: "100%" }, children: [(0, jsx_runtime_1.jsx)("input", { ref: setRef, className: `form-control ${props.invalid ? "is-invalid" : ""}`, style: {
|
|
126
|
+
minWidth: 0,
|
|
127
|
+
paddingRight: props.invalid
|
|
128
|
+
? showClear
|
|
129
|
+
? "6.25rem"
|
|
130
|
+
: "4.25rem"
|
|
131
|
+
: showClear
|
|
132
|
+
? "4.75rem"
|
|
133
|
+
: "2.75rem",
|
|
134
|
+
backgroundPosition: props.invalid
|
|
135
|
+
? `right ${showClear ? "4.75rem" : "2.75rem"} center`
|
|
136
|
+
: undefined,
|
|
137
|
+
}, role: "combobox", "aria-label": props.label, "aria-autocomplete": "list", "aria-expanded": show, "aria-controls": show ? id : undefined, "aria-activedescendant": show && active >= 0 && results[active] ? `${id}-${active}` : undefined, "aria-invalid": props.invalid || undefined, autoComplete: "off", value: query ?? selectedLabel, placeholder: props.placeholder, disabled: props.disabled, required: props.required, onFocus: (event) => {
|
|
138
|
+
event.target.select();
|
|
139
|
+
}, onBlur: close, onChange: (event) => {
|
|
140
|
+
setQuery(event.target.value);
|
|
141
|
+
setError("");
|
|
142
|
+
if (props.loadOptions) {
|
|
143
|
+
setRemote([]);
|
|
144
|
+
setLoading(true);
|
|
145
|
+
}
|
|
146
|
+
setOpen(true);
|
|
147
|
+
setActive(-1);
|
|
148
|
+
}, onKeyDown: (event) => {
|
|
149
|
+
if (event.nativeEvent.isComposing || event.ctrlKey || event.metaKey || event.altKey)
|
|
150
|
+
return;
|
|
151
|
+
if (event.key === "ArrowDown" || (event.key === "ArrowUp" && show)) {
|
|
152
|
+
event.preventDefault();
|
|
153
|
+
event.stopPropagation();
|
|
154
|
+
setOpen(true);
|
|
155
|
+
setActive((prior) => event.key === "ArrowDown"
|
|
156
|
+
? Math.min(prior + 1, results.length - 1)
|
|
157
|
+
: prior <= 0
|
|
158
|
+
? results.length - 1
|
|
159
|
+
: prior - 1);
|
|
160
|
+
}
|
|
161
|
+
else if (event.key === "Enter" && show) {
|
|
162
|
+
event.preventDefault();
|
|
163
|
+
event.stopPropagation();
|
|
164
|
+
const option = results[active >= 0 ? active : 0];
|
|
165
|
+
if (eligible && option && !loading && !error)
|
|
166
|
+
choose(option);
|
|
167
|
+
}
|
|
168
|
+
else if (event.key === "Escape" && (show || query !== null)) {
|
|
169
|
+
event.preventDefault();
|
|
170
|
+
event.stopPropagation();
|
|
171
|
+
close();
|
|
172
|
+
}
|
|
173
|
+
else if (event.key === "Tab" &&
|
|
174
|
+
eligible &&
|
|
175
|
+
show &&
|
|
176
|
+
active >= 0 &&
|
|
177
|
+
results[active] &&
|
|
178
|
+
!loading &&
|
|
179
|
+
!error) {
|
|
180
|
+
choose(results[active]);
|
|
181
|
+
}
|
|
182
|
+
} }), (0, jsx_runtime_1.jsxs)("span", { className: "position-absolute top-0 end-0 h-100 d-flex align-items-center", style: { paddingRight: "0.25rem" }, children: [showClear && ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "btn d-inline-flex align-items-center justify-content-center", style: indicatorStyle, tabIndex: -1, "aria-label": `Clear ${props.label}`, disabled: props.disabled, onMouseDown: (event) => event.preventDefault(), onClick: () => {
|
|
183
|
+
choose(null);
|
|
184
|
+
input.current?.focus();
|
|
185
|
+
}, children: (0, jsx_runtime_1.jsx)("svg", { width: "14", height: "14", viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", focusable: "false", children: (0, jsx_runtime_1.jsx)("path", { d: "m4 4 8 8M12 4l-8 8", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" }) }) })), (0, jsx_runtime_1.jsx)("span", { "aria-hidden": "true", style: {
|
|
186
|
+
height: "55%",
|
|
187
|
+
borderLeft: "1px solid var(--bs-border-color)",
|
|
188
|
+
marginInline: "0.125rem",
|
|
189
|
+
} }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "btn d-inline-flex align-items-center justify-content-center", style: indicatorStyle, tabIndex: -1, "aria-label": `Show ${props.label} choices`, disabled: props.disabled, onMouseDown: (event) => event.preventDefault(), onClick: () => {
|
|
190
|
+
input.current?.focus();
|
|
191
|
+
setQuery(null);
|
|
192
|
+
setActive(-1);
|
|
193
|
+
setOpen(!open);
|
|
194
|
+
}, children: (0, jsx_runtime_1.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", focusable: "false", children: (0, jsx_runtime_1.jsx)("path", { d: "m4 6 4 4 4-4", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }) })] }), show &&
|
|
195
|
+
(0, react_dom_1.createPortal)((0, jsx_runtime_1.jsx)("div", { id: id, role: "listbox", "aria-label": `${props.label} choices`, className: "border rounded bg-body text-body shadow", style: { position: "fixed", ...position, overflowY: "auto", zIndex: 1080 }, children: loading || error || !eligible || !results.length ? ((0, jsx_runtime_1.jsx)("div", { className: `p-2 ${error ? "text-danger" : "text-body-secondary"}`, role: "status", children: loading
|
|
196
|
+
? "Loading…"
|
|
197
|
+
: error ||
|
|
198
|
+
(!eligible ? `Type at least ${props.minLength} characters` : "No matches") })) : (results.map((option, index) => ((0, jsx_runtime_1.jsx)("div", { id: `${id}-${index}`, role: "option", "aria-selected": value !== null && props.getOptionKey(value) === props.getOptionKey(option), className: `px-3 py-2 ${active === index ? "text-bg-primary" : ""}`, style: { cursor: "pointer" }, onMouseEnter: () => setActive(index), onMouseDown: (event) => event.preventDefault(), onClick: () => choose(option), children: props.getOptionLabel(option) }, props.getOptionKey(option))))) }), document.body)] }));
|
|
199
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { type CSSProperties, type ReactNode } from "react";
|
|
2
|
+
import { type GridValue } from "./reactive";
|
|
3
|
+
/** Stable lookup value; labels can change without changing the stored relationship. */
|
|
4
|
+
export interface DataGridOption {
|
|
5
|
+
value: string;
|
|
6
|
+
label: string;
|
|
7
|
+
}
|
|
8
|
+
/** One entry in a multi-column sort, in priority order. */
|
|
9
|
+
export interface DataGridSort {
|
|
10
|
+
columnId: string;
|
|
11
|
+
direction: "asc" | "desc";
|
|
12
|
+
}
|
|
13
|
+
/** Declarative column, with optional editing, lookup, custom actions, and totals. */
|
|
14
|
+
export interface DataGridColumn<T> {
|
|
15
|
+
id: string;
|
|
16
|
+
label: string;
|
|
17
|
+
type?: "text" | "number" | "date" | "boolean" | "select";
|
|
18
|
+
width?: CSSProperties["width"];
|
|
19
|
+
/** Column width hint in pixels, including cell padding; editors fit the remaining space. */
|
|
20
|
+
minWidth?: number;
|
|
21
|
+
getValue?: (row: T) => unknown;
|
|
22
|
+
/** Omit to make this a display/action column. Numbers remain text until the owner validates. */
|
|
23
|
+
editable?: boolean | ((row: T) => boolean);
|
|
24
|
+
required?: boolean;
|
|
25
|
+
options?: GridValue<readonly DataGridOption[]>;
|
|
26
|
+
renderCell?: (row: T) => ReactNode;
|
|
27
|
+
sortValue?: (row: T) => string | number | null | undefined;
|
|
28
|
+
sortable?: boolean;
|
|
29
|
+
footer?: (rows: readonly T[]) => ReactNode;
|
|
30
|
+
align?: "left" | "right" | "center";
|
|
31
|
+
}
|
|
32
|
+
/** Persistence-free grid. Owners stage edits and validate/save through their own tools. */
|
|
33
|
+
export interface DataGridProps<T> {
|
|
34
|
+
rows: GridValue<readonly T[]>;
|
|
35
|
+
columns: readonly DataGridColumn<T>[];
|
|
36
|
+
rowKey: (row: T) => string;
|
|
37
|
+
rowLabel?: (row: T) => string;
|
|
38
|
+
label: string;
|
|
39
|
+
onCellChange: (row: T, columnId: string, value: string | boolean) => void;
|
|
40
|
+
/** Optional controlled or observable sort; use onSortChange as its explicit write boundary. */
|
|
41
|
+
sort?: GridValue<readonly DataGridSort[]>;
|
|
42
|
+
defaultSort?: readonly DataGridSort[];
|
|
43
|
+
onSortChange?: (sort: DataGridSort[]) => void;
|
|
44
|
+
disabled?: boolean;
|
|
45
|
+
getRowError?: (row: T) => string | undefined;
|
|
46
|
+
onAddRow?: () => void;
|
|
47
|
+
addRowLabel?: string;
|
|
48
|
+
className?: string;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* An always-editable, Bootstrap grid ported from JobTrak's spreadsheet interactions.
|
|
52
|
+
* Up/down move vertically; left/right move at caret boundaries or when all text is selected.
|
|
53
|
+
* Enter moves to the next row's first editor. Native Tab/Shift+Tab are preserved, and popup
|
|
54
|
+
* editors can consume keys. Sorting never mutates caller rows; focus retains a stable order.
|
|
55
|
+
*/
|
|
56
|
+
export declare function DataGrid<T>(props: DataGridProps<T>): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DataGrid = DataGrid;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const react_1 = require("react");
|
|
6
|
+
const autocomplete_1 = require("./autocomplete");
|
|
7
|
+
const reactive_1 = require("./reactive");
|
|
8
|
+
const noSort = [];
|
|
9
|
+
/**
|
|
10
|
+
* An always-editable, Bootstrap grid ported from JobTrak's spreadsheet interactions.
|
|
11
|
+
* Up/down move vertically; left/right move at caret boundaries or when all text is selected.
|
|
12
|
+
* Enter moves to the next row's first editor. Native Tab/Shift+Tab are preserved, and popup
|
|
13
|
+
* editors can consume keys. Sorting never mutates caller rows; focus retains a stable order.
|
|
14
|
+
*/
|
|
15
|
+
function DataGrid(props) {
|
|
16
|
+
const rows = (0, reactive_1.useGridValue)(props.rows);
|
|
17
|
+
const [localSort, setLocalSort] = (0, react_1.useState)(props.defaultSort ?? noSort);
|
|
18
|
+
const sort = (0, reactive_1.useGridValue)(props.sort ?? localSort);
|
|
19
|
+
const table = (0, react_1.useRef)(null);
|
|
20
|
+
const add = (0, react_1.useRef)(null);
|
|
21
|
+
const order = (0, react_1.useRef)([]);
|
|
22
|
+
const pendingNew = (0, react_1.useRef)(null);
|
|
23
|
+
const [focused, setFocused] = (0, react_1.useState)(false);
|
|
24
|
+
const sorted = (0, react_1.useMemo)(() => {
|
|
25
|
+
const next = [...rows];
|
|
26
|
+
if (focused && order.current.length) {
|
|
27
|
+
const rank = new Map(order.current.map((id, index) => [id, index]));
|
|
28
|
+
next.sort((a, b) => (rank.get(props.rowKey(a)) ?? Infinity) - (rank.get(props.rowKey(b)) ?? Infinity));
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
next.sort((a, b) => {
|
|
32
|
+
for (const spec of sort) {
|
|
33
|
+
const column = props.columns.find((item) => item.id === spec.columnId);
|
|
34
|
+
const av = column?.sortValue?.(a) ?? column?.getValue?.(a) ?? "";
|
|
35
|
+
const bv = column?.sortValue?.(b) ?? column?.getValue?.(b) ?? "";
|
|
36
|
+
const result = typeof av === "number" && typeof bv === "number"
|
|
37
|
+
? av - bv
|
|
38
|
+
: String(av).localeCompare(String(bv), undefined, {
|
|
39
|
+
numeric: true,
|
|
40
|
+
sensitivity: "base",
|
|
41
|
+
});
|
|
42
|
+
if (result)
|
|
43
|
+
return spec.direction === "asc" ? result : -result;
|
|
44
|
+
}
|
|
45
|
+
return 0;
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
order.current = next.map(props.rowKey);
|
|
49
|
+
return next;
|
|
50
|
+
}, [rows, sort, props.columns, props.rowKey, focused]);
|
|
51
|
+
const focusCell = (r, c) => {
|
|
52
|
+
const cell = table.current?.querySelector(`[data-grid-row="${r}"][data-grid-col="${c}"]`);
|
|
53
|
+
const target = cell?.querySelector("input:not(:disabled),textarea:not(:disabled),button:not(:disabled):not([tabindex='-1']),select:not(:disabled)");
|
|
54
|
+
if (!target)
|
|
55
|
+
return false;
|
|
56
|
+
target.focus();
|
|
57
|
+
target.select?.();
|
|
58
|
+
return true;
|
|
59
|
+
};
|
|
60
|
+
const focusNextRow = (r) => {
|
|
61
|
+
for (let row = r; row < sorted.length; row++) {
|
|
62
|
+
for (let col = 0; col < props.columns.length; col++) {
|
|
63
|
+
if (props.columns[col].editable && focusCell(row, col))
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (add.current && !add.current.disabled) {
|
|
68
|
+
add.current.focus();
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
return false;
|
|
72
|
+
};
|
|
73
|
+
const onKey = (event, r, c) => {
|
|
74
|
+
if (event.defaultPrevented ||
|
|
75
|
+
event.nativeEvent.isComposing ||
|
|
76
|
+
event.altKey ||
|
|
77
|
+
event.ctrlKey ||
|
|
78
|
+
event.metaKey ||
|
|
79
|
+
event.shiftKey)
|
|
80
|
+
return;
|
|
81
|
+
const target = event.target;
|
|
82
|
+
if (target.tagName === "BUTTON" && event.key === "Enter")
|
|
83
|
+
return;
|
|
84
|
+
let moved = false;
|
|
85
|
+
if (event.key === "Enter")
|
|
86
|
+
moved = focusNextRow(r + 1);
|
|
87
|
+
if (event.key === "ArrowUp" || event.key === "ArrowDown") {
|
|
88
|
+
const step = event.key === "ArrowUp" ? -1 : 1;
|
|
89
|
+
for (let row = r + step; row >= 0 && row < sorted.length; row += step) {
|
|
90
|
+
if (focusCell(row, c)) {
|
|
91
|
+
moved = true;
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (event.key === "ArrowLeft" || event.key === "ArrowRight") {
|
|
97
|
+
const textInput = target.tagName === "INPUT" || target.tagName === "TEXTAREA";
|
|
98
|
+
const all = textInput && target.selectionStart === 0 && target.selectionEnd === target.value.length;
|
|
99
|
+
const atEdge = !textInput ||
|
|
100
|
+
target.selectionStart === null ||
|
|
101
|
+
all ||
|
|
102
|
+
(event.key === "ArrowLeft"
|
|
103
|
+
? target.selectionStart === 0 && target.selectionEnd === 0
|
|
104
|
+
: target.selectionStart === target.value.length);
|
|
105
|
+
if (!atEdge)
|
|
106
|
+
return;
|
|
107
|
+
const step = event.key === "ArrowLeft" ? -1 : 1;
|
|
108
|
+
const count = props.columns.length;
|
|
109
|
+
for (let index = r * count + c + step; index >= 0 && index < sorted.length * count; index += step) {
|
|
110
|
+
if (focusCell(Math.floor(index / count), index % count)) {
|
|
111
|
+
moved = true;
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (moved) {
|
|
117
|
+
event.preventDefault();
|
|
118
|
+
event.stopPropagation();
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
const toggleSort = (column, append) => {
|
|
122
|
+
const prior = sort.find((entry) => entry.columnId === column.id);
|
|
123
|
+
const rest = append ? sort.filter((entry) => entry.columnId !== column.id) : [];
|
|
124
|
+
const next = prior?.direction === "desc"
|
|
125
|
+
? rest
|
|
126
|
+
: [...rest, { columnId: column.id, direction: prior ? "desc" : "asc" }];
|
|
127
|
+
setFocused(false);
|
|
128
|
+
setLocalSort(next);
|
|
129
|
+
props.onSortChange?.(next);
|
|
130
|
+
};
|
|
131
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: `table-responsive ${props.className ?? ""}`, children: (0, jsx_runtime_1.jsxs)("table", { ref: table, className: "table table-striped table-hover table-bordered align-middle mb-0", "aria-label": props.label, style: {
|
|
132
|
+
tableLayout: "fixed",
|
|
133
|
+
minWidth: props.columns.reduce((sum, column) => sum + (column.minWidth ?? (typeof column.width === "number" ? column.width : 80)), 0),
|
|
134
|
+
}, onFocus: () => setFocused(true), onBlur: (event) => {
|
|
135
|
+
if (!event.currentTarget.contains(event.relatedTarget))
|
|
136
|
+
setFocused(false);
|
|
137
|
+
}, children: [(0, jsx_runtime_1.jsx)("thead", { children: (0, jsx_runtime_1.jsx)("tr", { children: props.columns.map((column) => {
|
|
138
|
+
const spec = sort.find((entry) => entry.columnId === column.id);
|
|
139
|
+
const sortable = column.sortable !== false && Boolean(column.getValue || column.sortValue);
|
|
140
|
+
return ((0, jsx_runtime_1.jsx)("th", { scope: "col", style: { width: column.width, minWidth: column.minWidth, whiteSpace: "nowrap" }, "aria-sort": sort[0]?.columnId === column.id
|
|
141
|
+
? spec?.direction === "asc"
|
|
142
|
+
? "ascending"
|
|
143
|
+
: "descending"
|
|
144
|
+
: undefined, children: sortable ? ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "btn p-0 border-0 text-body fw-bold text-start", title: "Sort; Shift-click adds another column", onClick: (e) => toggleSort(column, e.shiftKey), children: [column.label, spec && ((0, jsx_runtime_1.jsxs)("span", { "aria-hidden": "true", children: [" ", spec.direction === "asc" ? "↓" : "↑", sort.length > 1 ? sort.indexOf(spec) + 1 : ""] }))] })) : (column.label) }, column.id));
|
|
145
|
+
}) }) }), (0, jsx_runtime_1.jsx)("tbody", { children: sorted.map((row, r) => {
|
|
146
|
+
const key = props.rowKey(row);
|
|
147
|
+
const label = props.rowLabel?.(row) || key;
|
|
148
|
+
const error = props.getRowError?.(row);
|
|
149
|
+
return ((0, jsx_runtime_1.jsx)(GridRow, { row: row, columns: props.columns, label: label, index: r, error: error, disabled: props.disabled, onChange: props.onCellChange, onKey: onKey, onMount: (node) => {
|
|
150
|
+
if (node && pendingNew.current && !pendingNew.current.has(key)) {
|
|
151
|
+
pendingNew.current = null;
|
|
152
|
+
const input = node.querySelector("input:not(:disabled)");
|
|
153
|
+
input?.focus();
|
|
154
|
+
input?.select?.();
|
|
155
|
+
}
|
|
156
|
+
} }, key));
|
|
157
|
+
}) }), (props.onAddRow || props.columns.some((column) => column.footer)) && ((0, jsx_runtime_1.jsxs)("tfoot", { children: [props.columns.some((column) => column.footer) && ((0, jsx_runtime_1.jsx)("tr", { children: props.columns.map((column) => ((0, jsx_runtime_1.jsx)("td", { className: "fw-bold", style: { textAlign: column.align }, children: column.footer?.(rows) }, column.id))) })), props.onAddRow && ((0, jsx_runtime_1.jsx)("tr", { children: (0, jsx_runtime_1.jsx)("td", { colSpan: props.columns.length, children: (0, jsx_runtime_1.jsxs)("button", { ref: add, type: "button", className: "btn btn-outline-secondary btn-sm", disabled: props.disabled, onClick: () => {
|
|
158
|
+
pendingNew.current = new Set(rows.map(props.rowKey));
|
|
159
|
+
props.onAddRow?.();
|
|
160
|
+
}, children: [(0, jsx_runtime_1.jsx)("i", { className: "bi bi-plus-lg", "aria-hidden": "true" }), " ", props.addRowLabel || "Add row"] }) }) }))] }))] }) }));
|
|
161
|
+
}
|
|
162
|
+
function GridRow({ row, columns, label, index, error, disabled, onChange, onKey, onMount, }) {
|
|
163
|
+
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("tr", { ref: onMount, className: error ? "table-danger" : undefined, children: columns.map((column, c) => ((0, jsx_runtime_1.jsx)("td", { "data-grid-row": index, "data-grid-col": c, style: { textAlign: column.align ?? (column.type === "number" ? "right" : undefined) }, onKeyDown: (event) => onKey(event, index, c), children: (0, jsx_runtime_1.jsx)(GridCell, { row: row, column: column, label: `${column.label} — ${label}`, disabled: disabled, invalid: !!error, onChange: (value) => onChange(row, column.id, value) }) }, column.id))) }), error && ((0, jsx_runtime_1.jsx)("tr", { className: "table-danger", children: (0, jsx_runtime_1.jsxs)("td", { colSpan: columns.length, role: "alert", children: [label, ": ", error] }) }))] }));
|
|
164
|
+
}
|
|
165
|
+
function GridCell({ row, column, label, disabled, invalid, onChange, }) {
|
|
166
|
+
const options = (0, reactive_1.useGridValue)(column.options ?? []);
|
|
167
|
+
const editable = typeof column.editable === "function" ? column.editable(row) : column.editable;
|
|
168
|
+
const raw = column.getValue?.(row);
|
|
169
|
+
if (!editable)
|
|
170
|
+
return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: column.renderCell?.(row) ?? (raw == null ? "" : String(raw)) });
|
|
171
|
+
if (column.type === "select")
|
|
172
|
+
return ((0, jsx_runtime_1.jsx)(autocomplete_1.Autocomplete, { label: label, value: options.find((option) => option.value === raw) ??
|
|
173
|
+
(raw ? { value: String(raw), label: `Unavailable (${String(raw)})` } : null), options: options, getOptionKey: (option) => option.value, getOptionLabel: (option) => option.label, onChange: (option) => onChange(option?.value ?? ""), disabled: disabled, required: column.required, invalid: invalid }));
|
|
174
|
+
return ((0, jsx_runtime_1.jsx)("input", { "aria-label": label, "aria-invalid": invalid || undefined, required: column.required, disabled: disabled, type: column.type === "boolean" ? "checkbox" : column.type === "date" ? "date" : "text", className: `${column.type === "boolean" ? "form-check-input" : "form-control"}${invalid ? " is-invalid" : ""}`, style: {
|
|
175
|
+
// Column sizing belongs to the table; reusing it here would consume the cell padding.
|
|
176
|
+
minWidth: 0,
|
|
177
|
+
textAlign: column.type === "number" ? "right" : undefined,
|
|
178
|
+
}, inputMode: column.type === "number" ? "decimal" : undefined, value: column.type === "boolean" ? undefined : String(raw ?? ""), checked: column.type === "boolean" ? Boolean(raw) : undefined, onChange: (event) => onChange(column.type === "boolean" ? event.target.checked : event.target.value) }));
|
|
179
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DataGrid = exports.Autocomplete = void 0;
|
|
4
|
+
var autocomplete_1 = require("./autocomplete");
|
|
5
|
+
Object.defineProperty(exports, "Autocomplete", { enumerable: true, get: function () { return autocomplete_1.Autocomplete; } });
|
|
6
|
+
var data_grid_1 = require("./data-grid");
|
|
7
|
+
Object.defineProperty(exports, "DataGrid", { enumerable: true, get: function () { return data_grid_1.DataGrid; } });
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ReadonlyObservable } from "@peers-app/peers-sdk";
|
|
2
|
+
/** A plain value or a Peers observable/computed, read without acquiring write authority. */
|
|
3
|
+
export type GridValue<T> = T | ReadonlyObservable<T>;
|
|
4
|
+
/** Subscribe through Peers' concurrent-safe React adapter; never write to the source. */
|
|
5
|
+
export declare function useGridValue<T>(source: GridValue<T>): T;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useGridValue = useGridValue;
|
|
4
|
+
const use_observable_1 = require("../../hooks/use-observable");
|
|
5
|
+
/** Subscribe through Peers' concurrent-safe React adapter; never write to the source. */
|
|
6
|
+
function useGridValue(source) {
|
|
7
|
+
// useObservable's public overload accepts a writable union; we use only its read side.
|
|
8
|
+
return (0, use_observable_1.useObservable)(source)[0];
|
|
9
|
+
}
|
|
@@ -51,7 +51,6 @@ const settings_page_1 = require("../screens/settings/settings-page");
|
|
|
51
51
|
// import { TaskList } from "../screens/tasks/task-list";
|
|
52
52
|
const tool_details_1 = require("../screens/tools/tool-details");
|
|
53
53
|
const tool_list_1 = require("../screens/tools/tool-list");
|
|
54
|
-
const tool_test_details_1 = require("../screens/tools/tool-test-details");
|
|
55
54
|
const variable_details_1 = require("../screens/variables/variable-details");
|
|
56
55
|
const variable_list_1 = require("../screens/variables/variable-list");
|
|
57
56
|
const workflow_details_1 = require("../screens/workflows/workflow-details");
|
|
@@ -129,16 +128,10 @@ function Router({ path: providedPath } = {}) {
|
|
|
129
128
|
const workflowId = parts[0];
|
|
130
129
|
return (0, jsx_runtime_1.jsx)(workflow_details_1.WorkflowDetails, { workflowId: workflowId });
|
|
131
130
|
}
|
|
132
|
-
|
|
131
|
+
// Saved tabs from the removed tool-test screens fall back to the catalog.
|
|
132
|
+
if (path === "tools" || /^tools\/tests(?:\/|$)/.test(path)) {
|
|
133
133
|
return (0, jsx_runtime_1.jsx)(tool_list_1.ToolList, {});
|
|
134
134
|
}
|
|
135
|
-
if (path.match(/^tools\/tests\//)) {
|
|
136
|
-
const parts = path.split("/");
|
|
137
|
-
parts.shift();
|
|
138
|
-
parts.shift();
|
|
139
|
-
const toolTestId = parts[0];
|
|
140
|
-
return (0, jsx_runtime_1.jsx)(tool_test_details_1.ToolTestDetails, { toolTestId: toolTestId });
|
|
141
|
-
}
|
|
142
135
|
if (path.match(/^tools\//)) {
|
|
143
136
|
const parts = path.split("/");
|
|
144
137
|
parts.shift();
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -15,6 +15,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
exports.updateActiveTabTitle = exports.goToTabPath = exports.closeCurrentTab = exports.activeTabs = exports.activeTabId = exports.setRootLayout = exports.selectedRootLayoutId = exports.getRootLayouts = exports.getRootLayout = exports.DEFAULT_ROOT_LAYOUT_ID = exports.TabsLayoutApp = exports.RootLayoutHost = exports.LayoutPicker = exports.unregisterGlobalOverlay = exports.registerGlobalOverlay = exports.globalOverlays = exports.openLink = exports.mainContentPath = exports.isDesktop = exports.Typeahead = exports.MarkdownEditor = void 0;
|
|
18
|
+
__exportStar(require("./components/data-grid"), exports);
|
|
18
19
|
__exportStar(require("./components/inverse-lazy-list"), exports);
|
|
19
20
|
var editor_1 = require("./components/markdown-editor/editor");
|
|
20
21
|
Object.defineProperty(exports, "MarkdownEditor", { enumerable: true, get: function () { return editor_1.MarkdownEditor; } });
|
|
@@ -84,7 +84,7 @@ const AppearanceSettingsTab = () => {
|
|
|
84
84
|
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("h5", { className: "mb-3", children: "Appearance" }), (0, jsx_runtime_1.jsx)("div", { className: "mb-3", children: (0, jsx_runtime_1.jsx)(color_mode_dropdown_1.ColorModeDropdown, {}) }), (0, jsx_runtime_1.jsx)("div", { className: "mb-3", children: (0, jsx_runtime_1.jsx)(layout_picker_1.LayoutPicker, {}) })] }));
|
|
85
85
|
};
|
|
86
86
|
const AdvancedSettingsTab = () => {
|
|
87
|
-
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("h5", { className: "mb-3", children: "Advanced Settings" }), (0, jsx_runtime_1.jsx)(PackagesRootDirectory, {}), (0, jsx_runtime_1.jsx)(ReloadPackagesOnPageRefresh, {}), (0, jsx_runtime_1.jsx)(
|
|
87
|
+
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("h5", { className: "mb-3", children: "Advanced Settings" }), (0, jsx_runtime_1.jsx)(PackagesRootDirectory, {}), (0, jsx_runtime_1.jsx)(ReloadPackagesOnPageRefresh, {}), (0, jsx_runtime_1.jsx)(ResetDeviceSyncInfos, {}), (0, jsx_runtime_1.jsx)(DeleteLocalDatabase, {}), (0, jsx_runtime_1.jsx)(CleanupOldDataDir, {}), (0, jsx_runtime_1.jsx)(ResetChangeTracking, {})] }));
|
|
88
88
|
};
|
|
89
89
|
const ProfileSection = () => {
|
|
90
90
|
const me = (0, hooks_1.usePromise)(async () => {
|
|
@@ -134,13 +134,6 @@ const ReloadPackagesOnPageRefresh = () => {
|
|
|
134
134
|
(0, peers_sdk_1.reloadPackagesOnPageRefresh)(checked);
|
|
135
135
|
} }), (0, jsx_runtime_1.jsx)(tooltip_1.Tooltip, { markdownContent: `Checking this will reload all packages very time the page is loaded. Particularly useful while developing packages.` })] }));
|
|
136
136
|
};
|
|
137
|
-
const AutoUpdatePeersCore = () => {
|
|
138
|
-
(0, hooks_1.useObservable)(peers_sdk_1.autoUpdatePeersCore);
|
|
139
|
-
return ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("label", { htmlFor: "autoUpdatePeersCore", className: "form-label small mt-2 me-2", children: "Auto-update peers-core on startup:" }), (0, jsx_runtime_1.jsx)("input", { type: "checkbox", checked: (0, peers_sdk_1.autoUpdatePeersCore)(), onChange: (e) => {
|
|
140
|
-
const checked = e.target.checked;
|
|
141
|
-
(0, peers_sdk_1.autoUpdatePeersCore)(checked);
|
|
142
|
-
} }), (0, jsx_runtime_1.jsx)(tooltip_1.Tooltip, { markdownContent: `When enabled, the peers-core system package is automatically updated from the bundled version on every app startup. Disable this if you want to manage peers-core manually.` })] }));
|
|
143
|
-
};
|
|
144
137
|
const ResetDeviceSyncInfos = () => {
|
|
145
138
|
return ((0, jsx_runtime_1.jsx)("div", { className: "mt-3", children: (0, jsx_runtime_1.jsx)("button", { className: "btn btn-secondary btn-sm ms-2", onClick: async () => {
|
|
146
139
|
try {
|
|
@@ -149,7 +149,8 @@ function TabsLayoutInternal({ fillContainer = false } = {}) {
|
|
|
149
149
|
alignItems: "center",
|
|
150
150
|
justifyContent: "center",
|
|
151
151
|
backgroundColor: _colorMode === "light" ? "rgba(255,255,255,0.85)" : "rgba(0,0,0,0.85)",
|
|
152
|
-
}, children: (0, jsx_runtime_1.jsxs)("div", { className: "text-center", children: [(0, jsx_runtime_1.jsx)("div", { className: "spinner-border text-secondary mb-3", role: "status" }), (0, jsx_runtime_1.jsx)("div", { className: `small ${_colorMode === "light" ? "text-muted" : "text-secondary"}`, children: "Switching group..." })] }) })), (0, jsx_runtime_1.jsx)("div", { className:
|
|
152
|
+
}, children: (0, jsx_runtime_1.jsxs)("div", { className: "text-center", children: [(0, jsx_runtime_1.jsx)("div", { className: "spinner-border text-secondary mb-3", role: "status" }), (0, jsx_runtime_1.jsx)("div", { className: `small ${_colorMode === "light" ? "text-muted" : "text-secondary"}`, children: "Switching group..." })] }) })), (0, jsx_runtime_1.jsx)("div", { className: "border-bottom", style: {
|
|
153
|
+
backgroundColor: "var(--bs-secondary-bg)",
|
|
153
154
|
borderBottomWidth: "1px",
|
|
154
155
|
borderBottomColor: _colorMode === "light" ? "#dee2e6" : "#495057",
|
|
155
156
|
}, children: isMobile ? ((0, jsx_runtime_1.jsx)(MobileTabsHeader, { tabs: tabs, activeTab: activeTab, onSwitch: switchTab, onClose: closeTab, colorMode: _colorMode })) : ((0, jsx_runtime_1.jsxs)("div", { className: "d-flex align-items-center px-1", style: { height: "36px" }, children: [(0, jsx_runtime_1.jsx)(group_switcher_1.GroupSwitcher, { colorMode: _colorMode, isMobile: false }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "btn btn-sm me-2 d-flex align-items-center", onClick: command_palette_1.openCommandPalette, title: "Search everything (Cmd+K)", style: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peers-app/peers-ui",
|
|
3
|
-
"version": "0.28.
|
|
3
|
+
"version": "0.28.5",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/peers-app/peers-ui.git"
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"lint:fix": "biome check --write ."
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
|
-
"@peers-app/peers-sdk": "^0.28.
|
|
39
|
+
"@peers-app/peers-sdk": "^0.28.5",
|
|
40
40
|
"bootstrap": "^5.3.3",
|
|
41
41
|
"react": "^18.0.0",
|
|
42
42
|
"react-dom": "^18.0.0"
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"@babel/preset-react": "^7.24.1",
|
|
48
48
|
"@babel/preset-typescript": "^7.27.1",
|
|
49
49
|
"@biomejs/biome": "2.3.11",
|
|
50
|
-
"@peers-app/peers-sdk": "0.28.
|
|
50
|
+
"@peers-app/peers-sdk": "0.28.5",
|
|
51
51
|
"@testing-library/dom": "^10.4.0",
|
|
52
52
|
"@testing-library/jest-dom": "^6.6.3",
|
|
53
53
|
"@testing-library/react": "^16.3.0",
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ToolTestDetails = void 0;
|
|
4
|
-
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
-
const peers_sdk_1 = require("@peers-app/peers-sdk");
|
|
6
|
-
const loading_indicator_1 = require("../../components/loading-indicator");
|
|
7
|
-
const editor_1 = require("../../components/markdown-editor/editor");
|
|
8
|
-
const save_button_1 = require("../../components/save-button");
|
|
9
|
-
const hooks_1 = require("../../hooks");
|
|
10
|
-
const tabs_state_1 = require("../../tabs-layout/tabs-state");
|
|
11
|
-
const ToolTestDetails = (props) => {
|
|
12
|
-
const { toolTestId } = props;
|
|
13
|
-
const tool = (0, hooks_1.useObservableState)(undefined);
|
|
14
|
-
const toolTest = (0, hooks_1.usePromise)(async () => {
|
|
15
|
-
const toolTestData = await (0, peers_sdk_1.ToolTests)().get(toolTestId);
|
|
16
|
-
if (!toolTestData)
|
|
17
|
-
return null;
|
|
18
|
-
const _tool = await (0, peers_sdk_1.Tools)().get(toolTestData.toolId);
|
|
19
|
-
tool(_tool);
|
|
20
|
-
const doc = (0, peers_sdk_1.ToolTests)().initDoc(toolTestData);
|
|
21
|
-
(0, tabs_state_1.updateActiveTabTitle)(_tool?.name ? `${_tool.name} Test` : "Tool Test");
|
|
22
|
-
return doc;
|
|
23
|
-
});
|
|
24
|
-
if (toolTest === null) {
|
|
25
|
-
return (0, jsx_runtime_1.jsx)("div", { children: "ToolTest not found" });
|
|
26
|
-
}
|
|
27
|
-
if (!toolTest) {
|
|
28
|
-
return (0, jsx_runtime_1.jsx)(loading_indicator_1.LoadingIndicator, {});
|
|
29
|
-
}
|
|
30
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className: "container-fluid mt-2", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsxs)("a", { href: `#tools/${toolTest.toolId}`, children: ["Tool: ", tool()?.name] }), (0, jsx_runtime_1.jsx)("div", { className: "float-end", children: (0, jsx_runtime_1.jsx)(save_button_1.SaveButton, { doc: toolTest }) })] }), (0, jsx_runtime_1.jsxs)("div", { style: { height: "calc(100vh - 55px)", overflowY: "auto", width: "100%" }, children: [(0, jsx_runtime_1.jsx)("small", { children: "Test description:" }), (0, jsx_runtime_1.jsx)("div", { style: {
|
|
31
|
-
border: "1px solid #ccc",
|
|
32
|
-
borderRadius: "5px",
|
|
33
|
-
}, children: (0, jsx_runtime_1.jsx)(editor_1.MarkdownEditor, { value: toolTest.qs.description, hideToolbar: true }) }), (0, jsx_runtime_1.jsx)("small", { children: "Code:" }), (0, jsx_runtime_1.jsx)("div", { style: {
|
|
34
|
-
border: "1px solid #ccc",
|
|
35
|
-
borderRadius: "5px",
|
|
36
|
-
}, children: (0, jsx_runtime_1.jsx)(editor_1.MarkdownEditor, { value: toolTest.qs.code, hideToolbar: true }) })] })] }));
|
|
37
|
-
};
|
|
38
|
-
exports.ToolTestDetails = ToolTestDetails;
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ToolTestList = void 0;
|
|
4
|
-
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
-
const peers_sdk_1 = require("@peers-app/peers-sdk");
|
|
6
|
-
const react_1 = require("react");
|
|
7
|
-
const input_1 = require("../../components/input");
|
|
8
|
-
const globals_1 = require("../../globals");
|
|
9
|
-
const hooks_1 = require("../../hooks");
|
|
10
|
-
const ToolTestList = (props) => {
|
|
11
|
-
const { toolId } = props;
|
|
12
|
-
const testsObs = (0, hooks_1.useObservableState)([]);
|
|
13
|
-
const searchText = (0, hooks_1.useObservableState)("");
|
|
14
|
-
(0, react_1.useEffect)(() => {
|
|
15
|
-
(0, peers_sdk_1.ToolTests)()
|
|
16
|
-
.list({ toolId })
|
|
17
|
-
.then((tests) => {
|
|
18
|
-
testsObs(tests);
|
|
19
|
-
});
|
|
20
|
-
}, [toolId, testsObs]);
|
|
21
|
-
const tests = testsObs().filter((test) => test.description.toLowerCase().includes(searchText().toLowerCase()));
|
|
22
|
-
async function createTest(evt) {
|
|
23
|
-
if (evt.key !== "Enter")
|
|
24
|
-
return;
|
|
25
|
-
evt.preventDefault();
|
|
26
|
-
// TODO maybe use LLM call to do this?
|
|
27
|
-
const test = await (0, peers_sdk_1.ToolTests)().insert({
|
|
28
|
-
toolTestId: (0, peers_sdk_1.newid)(),
|
|
29
|
-
toolId,
|
|
30
|
-
description: searchText(),
|
|
31
|
-
code: "",
|
|
32
|
-
input: {},
|
|
33
|
-
output: {},
|
|
34
|
-
});
|
|
35
|
-
testsObs([...tests, test]);
|
|
36
|
-
searchText("");
|
|
37
|
-
(0, globals_1.mainContentPath)(`tools/tests/${test.toolTestId}`);
|
|
38
|
-
}
|
|
39
|
-
return ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)(input_1.Input, { className: "form-control mb-2", value: searchText, onKeyDown: createTest, placeholder: "Search or create new test" }), testsObs().length === 0 && ((0, jsx_runtime_1.jsx)("div", { className: "text-center", children: (0, jsx_runtime_1.jsx)("p", { children: "No test for this tool yet" }) })), testsObs().length !== 0 && tests.length === 0 && ((0, jsx_runtime_1.jsx)("div", { className: "text-center", children: (0, jsx_runtime_1.jsx)("p", { children: "No matching tests found" }) })), tests.map((test, iTest) => {
|
|
40
|
-
return ((0, jsx_runtime_1.jsx)("p", { onDoubleClick: (_e) => (0, globals_1.mainContentPath)(`tools/tests/${test.toolTestId}`), children: test.description.split("\n")[0] }, iTest));
|
|
41
|
-
})] }));
|
|
42
|
-
};
|
|
43
|
-
exports.ToolTestList = ToolTestList;
|