@peers-app/peers-ui 0.19.8 → 0.19.9
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/list-screen.d.ts +5 -1
- package/dist/components/list-screen.js +3 -1
- package/dist/screens/variables/variable-details.js +46 -3
- package/dist/screens/variables/variable-list.js +26 -4
- package/package.json +3 -3
- package/src/components/list-screen.tsx +7 -2
- package/src/screens/variables/variable-details.tsx +93 -4
- package/src/screens/variables/variable-list.tsx +52 -15
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { type DataFilter, type SortBy, type Table } from "@peers-app/peers-sdk";
|
|
2
2
|
interface IProps<T extends Record<string, unknown>> {
|
|
3
3
|
table: Table<T>;
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Creates a new record from the search text when the user presses Enter. When omitted,
|
|
6
|
+
* the list is browse/search only (no create) and the placeholder drops "or create".
|
|
7
|
+
*/
|
|
8
|
+
newRecord?: (text: string) => Promise<T>;
|
|
5
9
|
getFilter?: (text: string) => DataFilter<T>;
|
|
6
10
|
sortBy?: SortBy<T>;
|
|
7
11
|
renderItem: (record: T) => JSX.Element;
|
|
@@ -93,13 +93,15 @@ function ListScreen(props) {
|
|
|
93
93
|
async function searchSubmit(evt) {
|
|
94
94
|
if (evt.key !== "Enter")
|
|
95
95
|
return;
|
|
96
|
+
if (!newRecord)
|
|
97
|
+
return;
|
|
96
98
|
const name = searchText.trim();
|
|
97
99
|
if (!name)
|
|
98
100
|
return;
|
|
99
101
|
searchTextObs("");
|
|
100
102
|
await newRecord(name);
|
|
101
103
|
}
|
|
102
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className: "container-fluid", children: [(0, jsx_runtime_1.jsx)("div", { className: "input-group mt-3 mb-3", children: (0, jsx_runtime_1.jsx)(input_1.Input, { value: searchTextObs, className: "form-control", placeholder:
|
|
104
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "container-fluid", children: [(0, jsx_runtime_1.jsx)("div", { className: "input-group mt-3 mb-3", children: (0, jsx_runtime_1.jsx)(input_1.Input, { value: searchTextObs, className: "form-control", placeholder: `${newRecord ? "Search or create" : "Search"} ${props.placeholderName || table.metaData.name}`, autoFocus: !!(0, globals_1.isDesktop)(), onKeyUp: (evt) => searchSubmit(evt) }) }), (0, jsx_runtime_1.jsx)("div", { className: "peers-list-container", children: (0, jsx_runtime_1.jsx)(lazy_list_1.LazyList, { resetTrigger: searchText, loadMore: loadMore, scrollThreshold: 0.6, renderItems: (notes) => {
|
|
103
105
|
return notes.map(props.renderItem);
|
|
104
106
|
}, loadingIndicator: (0, jsx_runtime_1.jsx)("div", { className: "d-flex justify-content-center", style: { height: 200 }, children: (0, jsx_runtime_1.jsx)(loading_indicator_1.LoadingIndicator, {}) }), endOfList: (0, jsx_runtime_1.jsx)("div", { className: "d-flex justify-content-center", style: { height: 200 } }) }) })] }));
|
|
105
107
|
}
|
|
@@ -59,15 +59,58 @@ const VariableValue = (props) => {
|
|
|
59
59
|
const { pvar } = props;
|
|
60
60
|
const [isSecret] = (0, hooks_1.useObservable)(pvar.qs.isSecret);
|
|
61
61
|
const scalarValue = (0, peers_sdk_1.observable)(pvar.value.value);
|
|
62
|
-
// WARNING:
|
|
62
|
+
// WARNING: the editable string path assumes the value is a string
|
|
63
63
|
(0, hooks_1.useSubscription)(pvar.qs.value, (value) => {
|
|
64
64
|
scalarValue(value?.value);
|
|
65
65
|
});
|
|
66
66
|
(0, hooks_1.useSubscription)(scalarValue, (value) => {
|
|
67
67
|
pvar.value = { value };
|
|
68
68
|
});
|
|
69
|
+
const valueLabel = ((0, jsx_runtime_1.jsxs)("div", { className: "d-flex align-items-center justify-content-between", children: [(0, jsx_runtime_1.jsx)("small", { children: "Value:" }), (0, jsx_runtime_1.jsx)(CopyValueButton, { pvar: pvar })] }));
|
|
69
70
|
if (isSecret) {
|
|
70
|
-
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [
|
|
71
|
+
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [valueLabel, (0, jsx_runtime_1.jsx)(input_1.Input, { value: scalarValue, type: "password", className: "form-control mb-3 p-0 ps-2", placeholder: "Variable value", title: "Variable value" })] }));
|
|
71
72
|
}
|
|
72
|
-
|
|
73
|
+
// The markdown editor only handles strings. System/package pvars may hold booleans,
|
|
74
|
+
// numbers, or objects, so render those read-only (still copyable via the button).
|
|
75
|
+
// Read directly (no subscription) to avoid a render/write-back loop with scalarValue.
|
|
76
|
+
const scalar = pvar.value?.value;
|
|
77
|
+
if (typeof scalar !== "string") {
|
|
78
|
+
const isObject = scalar !== null && typeof scalar === "object";
|
|
79
|
+
const display = scalar === undefined || scalar === null
|
|
80
|
+
? ""
|
|
81
|
+
: isObject
|
|
82
|
+
? JSON.stringify(scalar, null, 2)
|
|
83
|
+
: String(scalar);
|
|
84
|
+
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [valueLabel, isObject ? ((0, jsx_runtime_1.jsx)("pre", { className: "form-control mb-1 p-2", style: { whiteSpace: "pre-wrap", wordBreak: "break-word", maxHeight: 300 }, children: display })) : ((0, jsx_runtime_1.jsx)("input", { className: "form-control mb-1 p-0 ps-2", value: display, readOnly: true, title: "Variable value" })), (0, jsx_runtime_1.jsxs)("div", { className: "form-text text-muted mb-3", children: ["This ", typeof scalar, " value is read-only here \u2014 use Copy Value to copy it."] })] }));
|
|
85
|
+
}
|
|
86
|
+
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [valueLabel, (0, jsx_runtime_1.jsx)(editor_inline_1.MarkdownEditorInline, { value: scalarValue, hideToolbar: true }), (0, jsx_runtime_1.jsx)("br", {})] }));
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* Button that copies a persistent variable's value to the clipboard. Decryption (for
|
|
90
|
+
* secret values) and the clipboard write happen entirely on the server/main process via
|
|
91
|
+
* {@link rpcServerCalls.copyPvarValueToClipboard}, so the plaintext is never exposed to
|
|
92
|
+
* the UI. Shows transient success/error feedback on the icon.
|
|
93
|
+
*/
|
|
94
|
+
const CopyValueButton = (props) => {
|
|
95
|
+
const { pvar } = props;
|
|
96
|
+
const [status, setStatus] = (0, react_1.useState)("idle");
|
|
97
|
+
async function onCopy() {
|
|
98
|
+
try {
|
|
99
|
+
const userContext = await (0, peers_sdk_1.getUserContext)();
|
|
100
|
+
const groupId = userContext.currentlyActiveGroupId();
|
|
101
|
+
const result = await peers_sdk_1.rpcServerCalls.copyPvarValueToClipboard(pvar.persistentVarId, groupId);
|
|
102
|
+
setStatus(result.success ? "copied" : "error");
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
console.error("Failed to copy variable value", err);
|
|
106
|
+
setStatus("error");
|
|
107
|
+
}
|
|
108
|
+
setTimeout(() => setStatus("idle"), 1500);
|
|
109
|
+
}
|
|
110
|
+
const iconClass = status === "copied"
|
|
111
|
+
? "bi-check-lg text-success"
|
|
112
|
+
: status === "error"
|
|
113
|
+
? "bi-exclamation-triangle text-danger"
|
|
114
|
+
: "bi-clipboard";
|
|
115
|
+
return ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "btn btn-sm btn-outline-secondary", onClick: onCopy, title: "Copy value to clipboard", children: [(0, jsx_runtime_1.jsx)("i", { className: iconClass }), (0, jsx_runtime_1.jsx)("span", { className: "ms-1", children: "Copy Value" })] }));
|
|
73
116
|
};
|
|
@@ -5,9 +5,14 @@ const jsx_runtime_1 = require("react/jsx-runtime");
|
|
|
5
5
|
const peers_sdk_1 = require("@peers-app/peers-sdk");
|
|
6
6
|
const list_screen_1 = require("../../components/list-screen");
|
|
7
7
|
const markdown_with_mentions_1 = require("../../components/markdown-with-mentions");
|
|
8
|
+
const tabs_1 = require("../../components/tabs");
|
|
8
9
|
const globals_1 = require("../../globals");
|
|
9
10
|
const ui_loader_1 = require("../../ui-router/ui-loader");
|
|
10
|
-
function
|
|
11
|
+
function renderItem(persistentVar) {
|
|
12
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "container-fluid pb-4", children: [(0, jsx_runtime_1.jsx)("i", { className: (0, peers_sdk_1.getIconClassName)((0, peers_sdk_1.PersistentVars)()) }), "\u00A0", (0, jsx_runtime_1.jsx)("a", { href: `#variables/${persistentVar.persistentVarId}`, children: persistentVar.name || "<empty-name>" }), (0, jsx_runtime_1.jsx)("div", { style: { paddingLeft: "20px" }, children: (0, jsx_runtime_1.jsx)(markdown_with_mentions_1.MarkdownWithMentions, { content: persistentVar.description || "" }) })] }, persistentVar.persistentVarId));
|
|
13
|
+
}
|
|
14
|
+
/** List of user-created variables, with search-to-create. */
|
|
15
|
+
function UserVariableList() {
|
|
11
16
|
async function newRecord(name) {
|
|
12
17
|
const userContext = await (0, peers_sdk_1.getUserContext)();
|
|
13
18
|
const isPersonal = userContext.currentlyActiveGroupId() === userContext.userId;
|
|
@@ -29,11 +34,28 @@ function VariableList() {
|
|
|
29
34
|
userCreated: true,
|
|
30
35
|
};
|
|
31
36
|
}
|
|
32
|
-
function renderItem(persistentVar) {
|
|
33
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className: "container-fluid pb-4", children: [(0, jsx_runtime_1.jsx)("i", { className: (0, peers_sdk_1.getIconClassName)((0, peers_sdk_1.PersistentVars)()) }), "\u00A0", (0, jsx_runtime_1.jsx)("a", { href: `#variables/${persistentVar.persistentVarId}`, children: persistentVar.name || "<empty-name>" }), (0, jsx_runtime_1.jsx)("div", { style: { paddingLeft: "20px" }, children: (0, jsx_runtime_1.jsx)(markdown_with_mentions_1.MarkdownWithMentions, { content: persistentVar.description || "" }) })] }, persistentVar.persistentVarId));
|
|
34
|
-
}
|
|
35
37
|
return ((0, jsx_runtime_1.jsx)(list_screen_1.ListScreen, { table: (0, peers_sdk_1.PersistentVars)(), newRecord: newRecord, getFilter: getFilter, sortBy: ["name"], renderItem: renderItem, placeholderName: "environment variable" }));
|
|
36
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Browse/search list of non-user-created variables (system and package-owned pvars).
|
|
41
|
+
* These are read-only here (no create); selecting one opens the same detail screen.
|
|
42
|
+
*/
|
|
43
|
+
function SystemVariableList() {
|
|
44
|
+
function getFilter(searchText) {
|
|
45
|
+
return {
|
|
46
|
+
$or: [{ name: { $matchWords: searchText } }, { description: { $matchWords: searchText } }],
|
|
47
|
+
// $ne: true matches both `false` and unset (system/package pvars omit userCreated)
|
|
48
|
+
userCreated: { $ne: true },
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return ((0, jsx_runtime_1.jsx)(list_screen_1.ListScreen, { table: (0, peers_sdk_1.PersistentVars)(), getFilter: getFilter, sortBy: ["name"], renderItem: renderItem, placeholderName: "system variable" }));
|
|
52
|
+
}
|
|
53
|
+
function VariableList() {
|
|
54
|
+
return ((0, jsx_runtime_1.jsx)(tabs_1.Tabs, { tabs: [
|
|
55
|
+
{ name: "User", content: (0, jsx_runtime_1.jsx)(UserVariableList, {}) },
|
|
56
|
+
{ name: "System", content: (0, jsx_runtime_1.jsx)(SystemVariableList, {}) },
|
|
57
|
+
] }));
|
|
58
|
+
}
|
|
37
59
|
(0, ui_loader_1.registerInternalPeersUI)({
|
|
38
60
|
peersUIId: "00m5fshz2g6ea23v8z6y0a1ci",
|
|
39
61
|
component: VariableList,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peers-app/peers-ui",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.9",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/peers-app/peers-ui.git"
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"lint:fix": "biome check --write ."
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
|
-
"@peers-app/peers-sdk": "^0.19.
|
|
31
|
+
"@peers-app/peers-sdk": "^0.19.9",
|
|
32
32
|
"bootstrap": "^5.3.3",
|
|
33
33
|
"react": "^18.0.0",
|
|
34
34
|
"react-dom": "^18.0.0"
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"@babel/preset-env": "^7.24.5",
|
|
40
40
|
"@babel/preset-react": "^7.24.1",
|
|
41
41
|
"@babel/preset-typescript": "^7.27.1",
|
|
42
|
-
"@peers-app/peers-sdk": "0.19.
|
|
42
|
+
"@peers-app/peers-sdk": "0.19.9",
|
|
43
43
|
"@testing-library/dom": "^10.4.0",
|
|
44
44
|
"@testing-library/jest-dom": "^6.6.3",
|
|
45
45
|
"@testing-library/react": "^16.3.0",
|
|
@@ -16,7 +16,11 @@ import { LoadingIndicator } from "./loading-indicator";
|
|
|
16
16
|
|
|
17
17
|
interface IProps<T extends Record<string, unknown>> {
|
|
18
18
|
table: Table<T>;
|
|
19
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Creates a new record from the search text when the user presses Enter. When omitted,
|
|
21
|
+
* the list is browse/search only (no create) and the placeholder drops "or create".
|
|
22
|
+
*/
|
|
23
|
+
newRecord?: (text: string) => Promise<T>;
|
|
20
24
|
getFilter?: (text: string) => DataFilter<T>;
|
|
21
25
|
sortBy?: SortBy<T>;
|
|
22
26
|
renderItem: (record: T) => JSX.Element;
|
|
@@ -78,6 +82,7 @@ export function ListScreen<T extends Record<string, unknown>>(props: IProps<T>)
|
|
|
78
82
|
|
|
79
83
|
async function searchSubmit(evt: React.KeyboardEvent<HTMLInputElement>) {
|
|
80
84
|
if (evt.key !== "Enter") return;
|
|
85
|
+
if (!newRecord) return;
|
|
81
86
|
const name = searchText.trim();
|
|
82
87
|
if (!name) return;
|
|
83
88
|
searchTextObs("");
|
|
@@ -90,7 +95,7 @@ export function ListScreen<T extends Record<string, unknown>>(props: IProps<T>)
|
|
|
90
95
|
<Input
|
|
91
96
|
value={searchTextObs}
|
|
92
97
|
className="form-control"
|
|
93
|
-
placeholder={
|
|
98
|
+
placeholder={`${newRecord ? "Search or create" : "Search"} ${props.placeholderName || table.metaData.name}`}
|
|
94
99
|
autoFocus={!!isDesktop()}
|
|
95
100
|
onKeyUp={(evt) => searchSubmit(evt)}
|
|
96
101
|
/>
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
camelCaseToSpaces,
|
|
3
3
|
getIconClassName,
|
|
4
|
+
getUserContext,
|
|
4
5
|
type IDoc,
|
|
5
6
|
type IPersistentVar,
|
|
6
7
|
type Observable,
|
|
7
8
|
observable,
|
|
8
9
|
PersistentVars,
|
|
10
|
+
rpcServerCalls,
|
|
9
11
|
} from "@peers-app/peers-sdk";
|
|
10
|
-
import { useEffect } from "react";
|
|
12
|
+
import { useEffect, useState } from "react";
|
|
11
13
|
import { Checkbox } from "../../components/checkbox";
|
|
12
14
|
import { Input } from "../../components/input";
|
|
13
15
|
import { LoadingIndicator } from "../../components/loading-indicator";
|
|
@@ -162,7 +164,7 @@ const VariableValue = (props: { pvar: IDoc<IPersistentVar> }) => {
|
|
|
162
164
|
|
|
163
165
|
const [isSecret] = useObservable(pvar.qs.isSecret);
|
|
164
166
|
const scalarValue = observable(pvar.value.value);
|
|
165
|
-
// WARNING:
|
|
167
|
+
// WARNING: the editable string path assumes the value is a string
|
|
166
168
|
useSubscription(pvar.qs.value, (value) => {
|
|
167
169
|
scalarValue(value?.value);
|
|
168
170
|
});
|
|
@@ -170,10 +172,17 @@ const VariableValue = (props: { pvar: IDoc<IPersistentVar> }) => {
|
|
|
170
172
|
pvar.value = { value };
|
|
171
173
|
});
|
|
172
174
|
|
|
175
|
+
const valueLabel = (
|
|
176
|
+
<div className="d-flex align-items-center justify-content-between">
|
|
177
|
+
<small>Value:</small>
|
|
178
|
+
<CopyValueButton pvar={pvar} />
|
|
179
|
+
</div>
|
|
180
|
+
);
|
|
181
|
+
|
|
173
182
|
if (isSecret) {
|
|
174
183
|
return (
|
|
175
184
|
<>
|
|
176
|
-
|
|
185
|
+
{valueLabel}
|
|
177
186
|
<Input
|
|
178
187
|
value={scalarValue}
|
|
179
188
|
type="password"
|
|
@@ -185,11 +194,91 @@ const VariableValue = (props: { pvar: IDoc<IPersistentVar> }) => {
|
|
|
185
194
|
);
|
|
186
195
|
}
|
|
187
196
|
|
|
197
|
+
// The markdown editor only handles strings. System/package pvars may hold booleans,
|
|
198
|
+
// numbers, or objects, so render those read-only (still copyable via the button).
|
|
199
|
+
// Read directly (no subscription) to avoid a render/write-back loop with scalarValue.
|
|
200
|
+
const scalar = pvar.value?.value;
|
|
201
|
+
if (typeof scalar !== "string") {
|
|
202
|
+
const isObject = scalar !== null && typeof scalar === "object";
|
|
203
|
+
const display =
|
|
204
|
+
scalar === undefined || scalar === null
|
|
205
|
+
? ""
|
|
206
|
+
: isObject
|
|
207
|
+
? JSON.stringify(scalar, null, 2)
|
|
208
|
+
: String(scalar);
|
|
209
|
+
return (
|
|
210
|
+
<>
|
|
211
|
+
{valueLabel}
|
|
212
|
+
{isObject ? (
|
|
213
|
+
<pre
|
|
214
|
+
className="form-control mb-1 p-2"
|
|
215
|
+
style={{ whiteSpace: "pre-wrap", wordBreak: "break-word", maxHeight: 300 }}
|
|
216
|
+
>
|
|
217
|
+
{display}
|
|
218
|
+
</pre>
|
|
219
|
+
) : (
|
|
220
|
+
<input
|
|
221
|
+
className="form-control mb-1 p-0 ps-2"
|
|
222
|
+
value={display}
|
|
223
|
+
readOnly
|
|
224
|
+
title="Variable value"
|
|
225
|
+
/>
|
|
226
|
+
)}
|
|
227
|
+
<div className="form-text text-muted mb-3">
|
|
228
|
+
This {typeof scalar} value is read-only here — use Copy Value to copy it.
|
|
229
|
+
</div>
|
|
230
|
+
</>
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
188
234
|
return (
|
|
189
235
|
<>
|
|
190
|
-
|
|
236
|
+
{valueLabel}
|
|
191
237
|
<MarkdownEditorInline value={scalarValue} hideToolbar />
|
|
192
238
|
<br />
|
|
193
239
|
</>
|
|
194
240
|
);
|
|
195
241
|
};
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Button that copies a persistent variable's value to the clipboard. Decryption (for
|
|
245
|
+
* secret values) and the clipboard write happen entirely on the server/main process via
|
|
246
|
+
* {@link rpcServerCalls.copyPvarValueToClipboard}, so the plaintext is never exposed to
|
|
247
|
+
* the UI. Shows transient success/error feedback on the icon.
|
|
248
|
+
*/
|
|
249
|
+
const CopyValueButton = (props: { pvar: IDoc<IPersistentVar> }) => {
|
|
250
|
+
const { pvar } = props;
|
|
251
|
+
const [status, setStatus] = useState<"idle" | "copied" | "error">("idle");
|
|
252
|
+
|
|
253
|
+
async function onCopy() {
|
|
254
|
+
try {
|
|
255
|
+
const userContext = await getUserContext();
|
|
256
|
+
const groupId = userContext.currentlyActiveGroupId();
|
|
257
|
+
const result = await rpcServerCalls.copyPvarValueToClipboard(pvar.persistentVarId, groupId);
|
|
258
|
+
setStatus(result.success ? "copied" : "error");
|
|
259
|
+
} catch (err) {
|
|
260
|
+
console.error("Failed to copy variable value", err);
|
|
261
|
+
setStatus("error");
|
|
262
|
+
}
|
|
263
|
+
setTimeout(() => setStatus("idle"), 1500);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const iconClass =
|
|
267
|
+
status === "copied"
|
|
268
|
+
? "bi-check-lg text-success"
|
|
269
|
+
: status === "error"
|
|
270
|
+
? "bi-exclamation-triangle text-danger"
|
|
271
|
+
: "bi-clipboard";
|
|
272
|
+
|
|
273
|
+
return (
|
|
274
|
+
<button
|
|
275
|
+
type="button"
|
|
276
|
+
className="btn btn-sm btn-outline-secondary"
|
|
277
|
+
onClick={onCopy}
|
|
278
|
+
title="Copy value to clipboard"
|
|
279
|
+
>
|
|
280
|
+
<i className={iconClass}></i>
|
|
281
|
+
<span className="ms-1">Copy Value</span>
|
|
282
|
+
</button>
|
|
283
|
+
);
|
|
284
|
+
};
|
|
@@ -7,10 +7,26 @@ import {
|
|
|
7
7
|
} from "@peers-app/peers-sdk";
|
|
8
8
|
import { ListScreen } from "../../components/list-screen";
|
|
9
9
|
import { MarkdownWithMentions } from "../../components/markdown-with-mentions";
|
|
10
|
+
import { Tabs } from "../../components/tabs";
|
|
10
11
|
import { mainContentPath } from "../../globals";
|
|
11
12
|
import { registerInternalPeersUI } from "../../ui-router/ui-loader";
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
function renderItem(persistentVar: IPersistentVar) {
|
|
15
|
+
return (
|
|
16
|
+
<div key={persistentVar.persistentVarId} className="container-fluid pb-4">
|
|
17
|
+
<i className={getIconClassName(PersistentVars())}></i>
|
|
18
|
+
<a href={`#variables/${persistentVar.persistentVarId}`}>
|
|
19
|
+
{persistentVar.name || "<empty-name>"}
|
|
20
|
+
</a>
|
|
21
|
+
<div style={{ paddingLeft: "20px" }}>
|
|
22
|
+
<MarkdownWithMentions content={persistentVar.description || ""} />
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** List of user-created variables, with search-to-create. */
|
|
29
|
+
function UserVariableList() {
|
|
14
30
|
async function newRecord(name: string) {
|
|
15
31
|
const userContext = await getUserContext();
|
|
16
32
|
const isPersonal = userContext.currentlyActiveGroupId() === userContext.userId;
|
|
@@ -34,20 +50,6 @@ export function VariableList() {
|
|
|
34
50
|
};
|
|
35
51
|
}
|
|
36
52
|
|
|
37
|
-
function renderItem(persistentVar: IPersistentVar) {
|
|
38
|
-
return (
|
|
39
|
-
<div key={persistentVar.persistentVarId} className="container-fluid pb-4">
|
|
40
|
-
<i className={getIconClassName(PersistentVars())}></i>
|
|
41
|
-
<a href={`#variables/${persistentVar.persistentVarId}`}>
|
|
42
|
-
{persistentVar.name || "<empty-name>"}
|
|
43
|
-
</a>
|
|
44
|
-
<div style={{ paddingLeft: "20px" }}>
|
|
45
|
-
<MarkdownWithMentions content={persistentVar.description || ""} />
|
|
46
|
-
</div>
|
|
47
|
-
</div>
|
|
48
|
-
);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
53
|
return (
|
|
52
54
|
<ListScreen
|
|
53
55
|
table={PersistentVars()}
|
|
@@ -60,6 +62,41 @@ export function VariableList() {
|
|
|
60
62
|
);
|
|
61
63
|
}
|
|
62
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Browse/search list of non-user-created variables (system and package-owned pvars).
|
|
67
|
+
* These are read-only here (no create); selecting one opens the same detail screen.
|
|
68
|
+
*/
|
|
69
|
+
function SystemVariableList() {
|
|
70
|
+
function getFilter(searchText: string) {
|
|
71
|
+
return {
|
|
72
|
+
$or: [{ name: { $matchWords: searchText } }, { description: { $matchWords: searchText } }],
|
|
73
|
+
// $ne: true matches both `false` and unset (system/package pvars omit userCreated)
|
|
74
|
+
userCreated: { $ne: true },
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<ListScreen
|
|
80
|
+
table={PersistentVars()}
|
|
81
|
+
getFilter={getFilter}
|
|
82
|
+
sortBy={["name"]}
|
|
83
|
+
renderItem={renderItem}
|
|
84
|
+
placeholderName="system variable"
|
|
85
|
+
/>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function VariableList() {
|
|
90
|
+
return (
|
|
91
|
+
<Tabs
|
|
92
|
+
tabs={[
|
|
93
|
+
{ name: "User", content: <UserVariableList /> },
|
|
94
|
+
{ name: "System", content: <SystemVariableList /> },
|
|
95
|
+
]}
|
|
96
|
+
/>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
63
100
|
registerInternalPeersUI({
|
|
64
101
|
peersUIId: "00m5fshz2g6ea23v8z6y0a1ci",
|
|
65
102
|
component: VariableList,
|