@aglyn/plugins-logic 1.0.0-beta.143

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.
Files changed (36) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +7 -0
  3. package/package.json +45 -0
  4. package/src/index.d.ts +19 -0
  5. package/src/index.js +20 -0
  6. package/src/index.js.map +1 -0
  7. package/src/lib/components/host-functions-card.component.d.ts +19 -0
  8. package/src/lib/components/host-functions-card.component.js +1075 -0
  9. package/src/lib/components/host-functions-card.component.js.map +1 -0
  10. package/src/lib/components/host-reference-health-card.component.d.ts +15 -0
  11. package/src/lib/components/host-reference-health-card.component.js +279 -0
  12. package/src/lib/components/host-reference-health-card.component.js.map +1 -0
  13. package/src/lib/components/host-variables-card.component.d.ts +18 -0
  14. package/src/lib/components/host-variables-card.component.js +630 -0
  15. package/src/lib/components/host-variables-card.component.js.map +1 -0
  16. package/src/lib/components/logic-console-page.d.ts +12 -0
  17. package/src/lib/components/logic-console-page.js +68 -0
  18. package/src/lib/components/logic-console-page.js.map +1 -0
  19. package/src/lib/components/where-used-dialog.component.d.ts +20 -0
  20. package/src/lib/components/where-used-dialog.component.js +103 -0
  21. package/src/lib/components/where-used-dialog.component.js.map +1 -0
  22. package/src/lib/constants/bundle-common.d.ts +8 -0
  23. package/src/lib/constants/bundle-common.js +9 -0
  24. package/src/lib/constants/bundle-common.js.map +1 -0
  25. package/src/lib/model/index.d.ts +23 -0
  26. package/src/lib/model/index.js +23 -0
  27. package/src/lib/model/index.js.map +1 -0
  28. package/src/lib/model/parameter-options.d.ts +26 -0
  29. package/src/lib/model/parameter-options.js +29 -0
  30. package/src/lib/model/parameter-options.js.map +1 -0
  31. package/src/lib/model/reference-audit.d.ts +75 -0
  32. package/src/lib/model/reference-audit.js +155 -0
  33. package/src/lib/model/reference-audit.js.map +1 -0
  34. package/src/lib/plugin.d.ts +29 -0
  35. package/src/lib/plugin.js +82 -0
  36. package/src/lib/plugin.js.map +1 -0
@@ -0,0 +1,630 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ 'use client';
17
+ import { _ as _extends } from "@swc/helpers/_/_extends";
18
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
19
+ import { checkQuota, formatVariableValue, HOST_VARIABLE_TYPE_LABELS, pluginDocsHelp, VARIABLE_NAME_PATTERN } from "@aglyn/aglyn";
20
+ /*
21
+ * The MODULE, not the barrel, for the two PURE helpers. Every spec that renders
22
+ * this card mocks `@aglyn/tenant-feature-instance` wholesale to stage its
23
+ * Firestore hooks, and a query builder imported through that barrel disappears
24
+ * under the mock — which fails as "not a function" in the component rather than
25
+ * as anything about the test's subject. Neither of these is a hook.
26
+ */ import { ceilingedWindow, collectionCeiling } from "@aglyn/tenant-feature-instance/hooks/host-collection-queries";
27
+ import { CardDisplay, useConfirmationContext } from "@aglyn/shared-ui-jsx";
28
+ import { ListPagination } from "@aglyn/shared-ui-jsx/components/list-pagination.component";
29
+ import { TABLE_PAGE_SIZE_DEFAULT } from "@aglyn/shared-ui-jsx/const/table-pagination";
30
+ import { useSnackbar } from "@aglyn/shared-ui-snackstack";
31
+ import { Timestamp } from "@aglyn/shared-util-timestamp";
32
+ import { Alert, Button, Dialog, DialogActions, DialogContent, DialogTitle, MenuItem, Stack, Switch, TextField, Typography } from "@mui/material";
33
+ import { collection, doc, getCountFromServer, setDoc, updateDoc } from "firebase/firestore";
34
+ import { useCallback, useEffect, useMemo, useState } from "react";
35
+ import { useFirestore, useFirestoreCollection, useHostResourceApi, useUser, writeGuardedBySeed } from "@aglyn/tenant-feature-instance";
36
+ import WhereUsedDialog from "./where-used-dialog.component.js";
37
+ import { fetchWhereUsed, summarizeDependents } from "@aglyn/aglyn/app-utils/where-used";
38
+ /**
39
+ * How many variables the card reads.
40
+ *
41
+ * A CEILING, not a page size — see the query, which explains why this list
42
+ * cannot be paged by the server without making the duplicate-name check lie.
43
+ */ const VARIABLE_CEILING = 100;
44
+ /**
45
+ * How many workflows the computed-variable picker offers.
46
+ *
47
+ * Paid for only while the editor is open, which is what makes a ceiling this
48
+ * size affordable: it is one reader mid-edit rather than every visitor to the
49
+ * page.
50
+ */ const WORKFLOW_OPTION_CEILING = 100;
51
+ /** Per-type value editor: mirrors the mockup's type list. */ function VariableValueField(props) {
52
+ const { draft, onChange } = props;
53
+ switch(draft.type){
54
+ case 'boolean':
55
+ return /*#__PURE__*/ _jsxs(Stack, {
56
+ direction: "row",
57
+ spacing: 1,
58
+ sx: {
59
+ alignItems: 'center'
60
+ },
61
+ children: [
62
+ /*#__PURE__*/ _jsx(Switch, {
63
+ checked: draft.value === 'true',
64
+ onChange: (event)=>onChange(event.target.checked ? 'true' : 'false')
65
+ }),
66
+ /*#__PURE__*/ _jsx(Typography, {
67
+ variant: "body2",
68
+ children: draft.value === 'true' ? 'True' : 'False'
69
+ })
70
+ ]
71
+ });
72
+ case 'date':
73
+ case 'time':
74
+ return /*#__PURE__*/ _jsx(TextField, {
75
+ label: "Value",
76
+ type: draft.type,
77
+ value: draft.value,
78
+ onChange: (event)=>onChange(event.target.value),
79
+ size: "small",
80
+ slotProps: {
81
+ inputLabel: {
82
+ shrink: true
83
+ }
84
+ }
85
+ });
86
+ case 'number':
87
+ return /*#__PURE__*/ _jsx(TextField, {
88
+ label: "Value",
89
+ value: draft.value,
90
+ onChange: (event)=>onChange(event.target.value.replace(/[^0-9.eE+-]/g, '')),
91
+ size: "small"
92
+ });
93
+ case 'dictionary':
94
+ case 'collection':
95
+ return /*#__PURE__*/ _jsx(TextField, {
96
+ label: "Value (JSON)",
97
+ placeholder: draft.type === 'dictionary' ? '{"key": "value"}' : '["a", "b"]',
98
+ value: draft.value,
99
+ onChange: (event)=>onChange(event.target.value),
100
+ size: "small",
101
+ multiline: true,
102
+ minRows: 3
103
+ });
104
+ default:
105
+ return /*#__PURE__*/ _jsx(TextField, {
106
+ label: "Value",
107
+ value: draft.value,
108
+ onChange: (event)=>onChange(event.target.value),
109
+ size: "small",
110
+ multiline: true
111
+ });
112
+ }
113
+ }
114
+ /**
115
+ * Host variables (Component Builder, AGL-91): the mockup's Edit Variable
116
+ * dialog — name + typed value — persisted to `hosts/{hostId}/variables`.
117
+ * Any string prop can reference a variable with `{{name}}`; the org
118
+ * compose pipeline resolves it at render. Soft delete keeps old tokens
119
+ * rendering literally rather than breaking published pages.
120
+ */ export function HostVariablesCard(props) {
121
+ var _ref, _ref1, _ref2;
122
+ var _workflowOptions_find;
123
+ const { hostId } = props;
124
+ const firestore = useFirestore();
125
+ const { data: user } = useUser();
126
+ const createHostResource = useHostResourceApi();
127
+ const { enqueueSnackbar } = useSnackbar();
128
+ const { confirm } = useConfirmationContext();
129
+ const { org } = props;
130
+ // Where-used dependents dialog (AGL-187).
131
+ const [usage, setUsage] = useState(null);
132
+ const [usageLoading, setUsageLoading] = useState(null);
133
+ const { data: variableDocs, status: variablesStatus, /**
134
+ * The rows this editor is seeded from are unconfirmed by the server
135
+ * (AGL-1358). Editing copies a whole stored variable into `draft` and
136
+ * writes all of it back, so `merge: true` protects nothing — every field
137
+ * is in the payload. `value` is the literal every `{{name}}` binding on
138
+ * every published page resolves to, and `workflowId` is what rebinds a
139
+ * computed variable to its source (AGL-261), so a cached seed can point
140
+ * a live binding back at a value or a workflow the author replaced.
141
+ */ fromCache: variablesFromCache } = useFirestoreCollection(/*
142
+ * ORDERED AND CEILINGED, deliberately not paged by the query (AGL-2501).
143
+ *
144
+ * `limit(100)` alone is answered in DOCUMENT-ID order, so the window is a
145
+ * pseudo-random hundred that the `localeCompare` below arranges into a
146
+ * convincing A-to-Z list. `variablesPerHost` runs to 5,000, so a Scale
147
+ * site with three hundred variables sees a hundred of them and nothing
148
+ * said so.
149
+ *
150
+ * `collectionCeiling` does not change WHICH hundred — document-id order is
151
+ * what the bare cap already returned. What it changes is that the order is
152
+ * NAMED, so the obvious next edit is caught: ordering on `name` would not
153
+ * mis-sort this list, it would HIDE from it every variable written without
154
+ * one, and `/api/hosts/resources` validates no field for presence while
155
+ * `IMPORTABLE_FIELDS.variables` copies a name only if the exported
156
+ * document carried it. The document name cannot be absent.
157
+ *
158
+ * The QUERY is not paged because `nameTaken` below tests the draft against
159
+ * these rows. Case-insensitive uniqueness is what keeps legacy `{{name}}`
160
+ * token resolution unambiguous (AGL-185), and on a ten-row server page
161
+ * that check would compare a new variable against a tenth of the site and
162
+ * cheerfully create the duplicate it exists to prevent.
163
+ *
164
+ * The check is still only as complete as the ceiling, and that is now
165
+ * SAID rather than assumed: the notice under the list appears exactly when
166
+ * the probe finds a variable this card did not read.
167
+ */ ()=>collectionCeiling(collection(firestore, 'hosts', hostId, 'variables'), VARIABLE_CEILING), [
168
+ firestore,
169
+ hostId
170
+ ], {
171
+ idField: '$id'
172
+ });
173
+ const { rows: readVariables, truncated: variablesTruncated } = ceilingedWindow(variableDocs, VARIABLE_CEILING);
174
+ const [draft, setDraft] = useState(null);
175
+ /**
176
+ * The editor has been opened at least once in this session.
177
+ *
178
+ * A LATCH rather than `draft` itself, because the workflow picker below keys its
179
+ * listeners on it. Tracking the dialog would tear that subscription down
180
+ * on Cancel and pay for them again on the next Edit, so a merchant working
181
+ * through ten variables would buy the same window ten times — worse than
182
+ * the mount-time read this replaces. Latched, a reader who never edits pays
183
+ * nothing and a reader who edits pays once.
184
+ */ const [editorOpened, setEditorOpened] = useState(false);
185
+ if (draft && !editorOpened) setEditorOpened(true);
186
+ /*
187
+ * Workflow picker options (AGL-261): computed variables reference the
188
+ * workflow by doc id instead of a typed name.
189
+ *
190
+ * READ WHEN THE EDITOR OPENS, not when the card mounts (AGL-2501). Nothing
191
+ * outside the dialog below touches these rows — the table shows a computed
192
+ * variable's stored `workflowName`, never a name looked up here — so an
193
+ * unconditional listener would charge every visitor to this page for a
194
+ * hundred workflows to fill a select most of them never see.
195
+ *
196
+ * Ceilinged and ordered for the same reason as the variables above: a bare
197
+ * `limit` is answered in document-id order, so the `localeCompare` below
198
+ * would arrange a pseudo-random hundred into a convincing alphabet and a
199
+ * workflow past it could not be picked, with nothing saying why.
200
+ */ const { data: workflowDocs } = useFirestoreCollection(()=>editorOpened ? collectionCeiling(collection(firestore, 'hosts', hostId, 'workflows'), WORKFLOW_OPTION_CEILING) : null, [
201
+ firestore,
202
+ hostId,
203
+ editorOpened
204
+ ], {
205
+ idField: '$id'
206
+ });
207
+ const { rows: readWorkflows, truncated: workflowOptionsTruncated } = ceilingedWindow(workflowDocs, WORKFLOW_OPTION_CEILING);
208
+ // Sorting the whole ceiling, not a page of it.
209
+ const workflowOptions = readWorkflows.filter((workflow)=>!workflow.deletedAt && workflow.name).map((workflow)=>({
210
+ id: workflow.$id,
211
+ name: workflow.name
212
+ })).sort((a, b)=>a.name.localeCompare(b.name));
213
+ /*
214
+ * Sorting is safe HERE in a way it is not on a paged list: these rows are
215
+ * the whole collection below the ceiling, not a slice of one, so the order
216
+ * on screen is the order of everything the card holds.
217
+ */ const variables = useMemo(()=>readVariables.filter((variable)=>!variable.deletedAt).sort((a, b)=>{
218
+ var _a_name, _b_name;
219
+ return String((_a_name = a.name) != null ? _a_name : '').localeCompare(String((_b_name = b.name) != null ? _b_name : ''));
220
+ }), [
221
+ readVariables
222
+ ]);
223
+ // The page is a SLICE, because the rows are already in hand and `nameTaken`
224
+ // needs all of them. What the card lacked was not a cheaper read but a
225
+ // control: a hundred variables rendered as one wall.
226
+ const [page, setPage] = useState(0);
227
+ const [pageSize, setPageSize] = useState(TABLE_PAGE_SIZE_DEFAULT);
228
+ const visibleVariables = useMemo(()=>variables.slice(page * pageSize, page * pageSize + pageSize), [
229
+ variables,
230
+ page,
231
+ pageSize
232
+ ]);
233
+ /**
234
+ * The variable HEAD-COUNT is a server aggregate, not the length of the
235
+ * capped listener (AGL-1716, the AGL-1706 shape).
236
+ *
237
+ * The listener is `limit(100)` and `variables.length` was handed to
238
+ * `checkQuota(org, 'variablesPerHost', …)`. The bands are 3 / 25 / 100 /
239
+ * 1,000 / 5,000, so the window ties Pro's 100 and hides Business's 1,000
240
+ * and Scale's 5,000 entirely: on those plans the gate compared 100 against
241
+ * thousands and could never refuse, while `api/hosts/resources` counted
242
+ * the collection and did. The card offered headroom that did not exist and
243
+ * then failed the create.
244
+ *
245
+ * UNFILTERED, matching the enforcing route: it runs a plain
246
+ * `collection('variables').count()` for this quota — its `softDeletes`
247
+ * branch governs only the flat per-host webhook cap — so a soft-deleted
248
+ * variable has always counted toward `variablesPerHost` server-side while
249
+ * this card excluded it. The card now asks the route's question in the
250
+ * route's terms, which is the only thing that makes a console gate a
251
+ * useful prediction of the route's verdict.
252
+ *
253
+ * THE LIST KEEPS ITS CAP. Only a create moves the number (deletes here are
254
+ * soft, and the route counts them), so that is where it is re-read.
255
+ */ const [variableCountEpoch, setVariableCountEpoch] = useState(0);
256
+ const [serverVariableCount, setServerVariableCount] = useState(null);
257
+ useEffect(()=>{
258
+ let active = true;
259
+ void getCountFromServer(collection(firestore, 'hosts', hostId, 'variables')).then((snapshot)=>{
260
+ if (active) setServerVariableCount(snapshot.data().count);
261
+ }).catch(()=>{
262
+ // Falls back to the loaded rows below — a LOWER bound, and this
263
+ // card's prior behaviour. Deliberately not 0: `checkQuota` answers
264
+ // from whatever it is handed, and 0 used is a confident wrong
265
+ // number in the flattering direction.
266
+ });
267
+ return ()=>{
268
+ active = false;
269
+ };
270
+ }, [
271
+ firestore,
272
+ hostId,
273
+ variableCountEpoch
274
+ ]);
275
+ const variableCount = serverVariableCount != null ? serverVariableCount : variables.length;
276
+ const validName = VARIABLE_NAME_PATTERN.test((_ref = draft == null ? void 0 : draft.name) != null ? _ref : '');
277
+ // Case-insensitive (AGL-185): names must stay unambiguous for legacy
278
+ // {{name}} token resolution and picker display.
279
+ const nameTaken = Boolean(draft && variables.some((variable)=>{
280
+ var _variable_name;
281
+ return String((_variable_name = variable.name) != null ? _variable_name : '').toLowerCase() === draft.name.trim().toLowerCase() && variable.$id !== draft.id;
282
+ }));
283
+ const handleSave = useCallback(async ()=>{
284
+ if (!draft || !validName || nameTaken) return;
285
+ const fields = {
286
+ name: draft.name,
287
+ type: draft.type,
288
+ value: draft.value,
289
+ workflowId: draft.workflowId.trim(),
290
+ workflowName: draft.workflowName.trim()
291
+ };
292
+ try {
293
+ if (draft.id) {
294
+ /**
295
+ * Edit stays client-direct (no quota consumed) — and is refused when
296
+ * its seed was never confirmed by the server (AGL-1358). `merge:
297
+ * true` protects nothing here: `fields` is every editable key of the
298
+ * stored row, copied off the listener whether or not it was touched.
299
+ *
300
+ * The guard WRAPS the write. An early return is a shape you can keep
301
+ * while losing the protection; here the write is only reachable
302
+ * through the verdict.
303
+ */ const verdict = await writeGuardedBySeed({
304
+ subject: 'variable',
305
+ unreadable: variablesStatus === 'error',
306
+ fromCache: variablesFromCache
307
+ }, async ()=>{
308
+ await setDoc(doc(firestore, 'hosts', hostId, 'variables', draft.id), _extends({}, fields, {
309
+ updatedAt: Timestamp.now()
310
+ }), {
311
+ merge: true
312
+ });
313
+ });
314
+ // Before `setDraft(null)`, so a refusal keeps the dialog open with
315
+ // what was typed. A save that silently does nothing sends the user
316
+ // back to retype a form that will be refused again just as quietly.
317
+ if (!verdict.ok) {
318
+ return void enqueueSnackbar(verdict.message, {
319
+ variant: 'warning',
320
+ persist: false
321
+ });
322
+ }
323
+ } else {
324
+ // New variable rides the quota-enforcing resources API (AGL-473).
325
+ await createHostResource({
326
+ hostId,
327
+ resource: 'variable',
328
+ data: fields
329
+ });
330
+ // A create moves the count and the aggregate is a one-shot — the
331
+ // listener refreshes the ROWS for free, the count has to be asked
332
+ // again or the cap drifts stale for the rest of the session.
333
+ setVariableCountEpoch((epoch)=>epoch + 1);
334
+ }
335
+ setDraft(null);
336
+ enqueueSnackbar(`Saved — use {{${draft.name}}} in any text to bind it`, {
337
+ variant: 'success',
338
+ persist: false
339
+ });
340
+ } catch (error) {
341
+ var _ref;
342
+ console.error(error);
343
+ enqueueSnackbar((_ref = error == null ? void 0 : error.message) != null ? _ref : 'An error has occurred', {
344
+ variant: 'error',
345
+ allowDuplicate: true
346
+ });
347
+ }
348
+ }, [
349
+ draft,
350
+ validName,
351
+ nameTaken,
352
+ firestore,
353
+ hostId,
354
+ createHostResource,
355
+ enqueueSnackbar,
356
+ variablesStatus,
357
+ variablesFromCache
358
+ ]);
359
+ const handleShowUsage = useCallback((variable)=>async ()=>{
360
+ setUsageLoading(variable.$id);
361
+ const result = await fetchWhereUsed(user, {
362
+ hostId,
363
+ kind: 'variable',
364
+ id: variable.$id,
365
+ name: variable.name
366
+ });
367
+ setUsageLoading(null);
368
+ setUsage({
369
+ name: variable.name,
370
+ result
371
+ });
372
+ }, [
373
+ user,
374
+ hostId
375
+ ]);
376
+ const handleDelete = useCallback((variable)=>async ()=>{
377
+ // Real dependents warning (AGL-187) instead of the generic copy.
378
+ const scan = await fetchWhereUsed(user, {
379
+ hostId,
380
+ kind: 'variable',
381
+ id: variable.$id,
382
+ name: variable.name
383
+ });
384
+ const confirmed = await confirm({
385
+ title: 'Delete this variable?',
386
+ description: scan.total ? `"${variable.name}" is used in ${summarizeDependents(scan)}. ` + 'Those bindings will render as empty or literal tokens after ' + 'the next publish.' : `"${variable.name}" is not referenced by any published ` + 'screen, layout, or workflow.',
387
+ confirmationText: 'Delete',
388
+ confirmationButtonProps: {
389
+ color: 'error'
390
+ }
391
+ }).then(()=>true).catch(()=>false);
392
+ if (!confirmed) return;
393
+ await updateDoc(doc(firestore, 'hosts', hostId, 'variables', variable.$id), {
394
+ deletedAt: Timestamp.now()
395
+ });
396
+ enqueueSnackbar('Variable deleted', {
397
+ variant: 'success',
398
+ persist: false
399
+ });
400
+ }, [
401
+ user,
402
+ confirm,
403
+ firestore,
404
+ hostId,
405
+ enqueueSnackbar
406
+ ]);
407
+ return /*#__PURE__*/ _jsxs(CardDisplay, {
408
+ header: 'Variables',
409
+ help: pluginDocsHelp('bindings', {
410
+ anchor: '#typed-variables'
411
+ }),
412
+ contentGutterX: true,
413
+ contentGutterY: true,
414
+ children: [
415
+ /*#__PURE__*/ _jsxs(Stack, {
416
+ spacing: 1,
417
+ children: [
418
+ variables.length === 0 ? /*#__PURE__*/ _jsx(Typography, {
419
+ variant: "body2",
420
+ color: "text.secondary",
421
+ children: 'Define typed values once and bind them anywhere with ' + '{{name}} — change the variable, every page updates on the ' + 'next publish.'
422
+ }) : visibleVariables.map((variable)=>{
423
+ var _HOST_VARIABLE_TYPE_LABELS_variable_type;
424
+ return /*#__PURE__*/ _jsxs(Stack, {
425
+ direction: "row",
426
+ spacing: 1,
427
+ sx: {
428
+ alignItems: 'center'
429
+ },
430
+ children: [
431
+ /*#__PURE__*/ _jsxs(Stack, {
432
+ sx: {
433
+ flex: 1,
434
+ minWidth: 0
435
+ },
436
+ children: [
437
+ /*#__PURE__*/ _jsx(Typography, {
438
+ variant: "body2",
439
+ noWrap: true,
440
+ children: `{{${variable.name}}}`
441
+ }),
442
+ /*#__PURE__*/ _jsx(Typography, {
443
+ variant: "caption",
444
+ color: "text.secondary",
445
+ noWrap: true,
446
+ children: `${(_HOST_VARIABLE_TYPE_LABELS_variable_type = HOST_VARIABLE_TYPE_LABELS[variable.type]) != null ? _HOST_VARIABLE_TYPE_LABELS_variable_type : variable.type} · ${formatVariableValue(variable) || '—'}`
447
+ })
448
+ ]
449
+ }),
450
+ /*#__PURE__*/ _jsx(Button, {
451
+ size: "small",
452
+ disabled: usageLoading === variable.$id,
453
+ onClick: handleShowUsage(variable),
454
+ children: usageLoading === variable.$id ? 'Scanning…' : 'Usage'
455
+ }),
456
+ /*#__PURE__*/ _jsx(Button, {
457
+ size: "small",
458
+ onClick: ()=>{
459
+ var _variable_name, _variable_type, _variable_value, _variable_workflowId, _variable_workflowName;
460
+ return setDraft({
461
+ id: variable.$id,
462
+ name: (_variable_name = variable.name) != null ? _variable_name : '',
463
+ type: (_variable_type = variable.type) != null ? _variable_type : 'text',
464
+ value: (_variable_value = variable.value) != null ? _variable_value : '',
465
+ workflowId: (_variable_workflowId = variable.workflowId) != null ? _variable_workflowId : '',
466
+ workflowName: (_variable_workflowName = variable.workflowName) != null ? _variable_workflowName : ''
467
+ });
468
+ },
469
+ children: 'Edit'
470
+ }),
471
+ /*#__PURE__*/ _jsx(Button, {
472
+ size: "small",
473
+ color: "error",
474
+ onClick: handleDelete(variable),
475
+ children: 'Delete'
476
+ })
477
+ ]
478
+ }, variable.$id);
479
+ }),
480
+ variables.length === 0 ? null : /*#__PURE__*/ _jsx(ListPagination, {
481
+ page: page,
482
+ pageSize: pageSize,
483
+ rowCount: visibleVariables.length,
484
+ // The variables the card HOLDS. Not `variableCount`, which is the
485
+ // site's total including soft-deleted rows and answers the quota's
486
+ // question rather than the reader's.
487
+ count: variables.length,
488
+ onPageChange: setPage,
489
+ onPageSizeChange: setPageSize
490
+ }),
491
+ variablesTruncated ? /*#__PURE__*/ _jsx(Alert, {
492
+ severity: "info",
493
+ children: `Showing ${VARIABLE_CEILING} variables, ordered by id. This site ` + 'has more — the duplicate-name check below only covers the ' + 'ones listed here, so a name may already be taken by one that ' + 'is not.'
494
+ }) : null,
495
+ /*#__PURE__*/ _jsx(Button, {
496
+ size: "small",
497
+ color: "primary",
498
+ sx: {
499
+ alignSelf: 'flex-start'
500
+ },
501
+ onClick: ()=>{
502
+ // Plan cap (AGL-99): dark-launch — plan-less workspaces uncapped.
503
+ // The site's variables, not this page of them (AGL-1716).
504
+ const quota = checkQuota(org, 'variablesPerHost', variableCount);
505
+ if (!quota.allowed) {
506
+ return void enqueueSnackbar(`Variable limit reached (${quota.limit}) — upgrade in Billing`, {
507
+ variant: 'warning',
508
+ persist: false
509
+ });
510
+ }
511
+ setDraft({
512
+ id: null,
513
+ name: '',
514
+ type: 'text',
515
+ value: '',
516
+ workflowId: '',
517
+ workflowName: ''
518
+ });
519
+ },
520
+ children: 'Add variable'
521
+ })
522
+ ]
523
+ }),
524
+ /*#__PURE__*/ _jsxs(Dialog, {
525
+ open: Boolean(draft),
526
+ onClose: ()=>setDraft(null),
527
+ maxWidth: "xs",
528
+ fullWidth: true,
529
+ children: [
530
+ /*#__PURE__*/ _jsx(DialogTitle, {
531
+ children: (draft == null ? void 0 : draft.id) ? 'Edit Variable' : 'Add Variable'
532
+ }),
533
+ /*#__PURE__*/ _jsxs(DialogContent, {
534
+ sx: {
535
+ display: 'flex',
536
+ flexDirection: 'column',
537
+ gap: 2,
538
+ pt: 1
539
+ },
540
+ children: [
541
+ /*#__PURE__*/ _jsx(TextField, {
542
+ label: "Name",
543
+ helperText: nameTaken ? 'A variable with this name already exists' : 'Used to identify the variable',
544
+ error: Boolean(draft == null ? void 0 : draft.name) && !validName || nameTaken,
545
+ value: (_ref1 = draft == null ? void 0 : draft.name) != null ? _ref1 : '',
546
+ onChange: (event)=>setDraft((prev)=>prev ? _extends({}, prev, {
547
+ name: event.target.value.replace(/[^a-zA-Z0-9_]/g, '')
548
+ }) : prev),
549
+ size: "small",
550
+ autoFocus: true,
551
+ sx: {
552
+ mt: 1
553
+ }
554
+ }),
555
+ /*#__PURE__*/ _jsx(TextField, {
556
+ label: "Type",
557
+ value: (_ref2 = draft == null ? void 0 : draft.type) != null ? _ref2 : 'text',
558
+ onChange: (event)=>setDraft((prev)=>prev ? _extends({}, prev, {
559
+ type: event.target.value,
560
+ value: ''
561
+ }) : prev),
562
+ size: "small",
563
+ select: true,
564
+ children: Object.entries(HOST_VARIABLE_TYPE_LABELS).map(([value, label])=>/*#__PURE__*/ _jsx(MenuItem, {
565
+ value: value,
566
+ children: label
567
+ }, value))
568
+ }),
569
+ draft ? /*#__PURE__*/ _jsx(VariableValueField, {
570
+ draft: draft,
571
+ onChange: (value)=>setDraft((prev)=>prev ? _extends({}, prev, {
572
+ value
573
+ }) : prev)
574
+ }) : null,
575
+ /*#__PURE__*/ _jsxs(TextField, {
576
+ select: true,
577
+ label: "Computed from workflow (optional)",
578
+ helperText: workflowOptionsTruncated ? `Showing ${WORKFLOW_OPTION_CEILING} workflows, ordered by ` + 'id. This site has more, so one of them is not offered here.' : 'Pick a workflow — its result becomes this ' + 'variable\u2019s value at render; the value above is the ' + 'fallback (AGL-129)',
579
+ value: (draft == null ? void 0 : draft.workflowId) || ((_workflowOptions_find = workflowOptions.find((option)=>option.name === (draft == null ? void 0 : draft.workflowName))) == null ? void 0 : _workflowOptions_find.id) || '',
580
+ onChange: (event)=>setDraft((prev)=>{
581
+ var _ref;
582
+ var _workflowOptions_find;
583
+ return prev ? _extends({}, prev, {
584
+ workflowId: event.target.value,
585
+ workflowName: (_ref = (_workflowOptions_find = workflowOptions.find((option)=>option.id === event.target.value)) == null ? void 0 : _workflowOptions_find.name) != null ? _ref : ''
586
+ }) : prev;
587
+ }),
588
+ size: "small",
589
+ children: [
590
+ /*#__PURE__*/ _jsx(MenuItem, {
591
+ value: "",
592
+ children: 'None'
593
+ }),
594
+ workflowOptions.map((option)=>/*#__PURE__*/ _jsx(MenuItem, {
595
+ value: option.id,
596
+ children: option.name
597
+ }, option.id))
598
+ ]
599
+ })
600
+ ]
601
+ }),
602
+ /*#__PURE__*/ _jsxs(DialogActions, {
603
+ children: [
604
+ /*#__PURE__*/ _jsx(Button, {
605
+ onClick: ()=>setDraft(null),
606
+ children: 'Cancel'
607
+ }),
608
+ /*#__PURE__*/ _jsx(Button, {
609
+ variant: "contained",
610
+ color: "primary",
611
+ disabled: !validName || nameTaken,
612
+ onClick: handleSave,
613
+ children: 'Save variable'
614
+ })
615
+ ]
616
+ })
617
+ ]
618
+ }),
619
+ /*#__PURE__*/ _jsx(WhereUsedDialog, {
620
+ hostId: hostId,
621
+ usage: usage,
622
+ onClose: ()=>setUsage(null)
623
+ })
624
+ ]
625
+ });
626
+ }
627
+ HostVariablesCard.displayName = 'HostVariablesCard';
628
+ export default HostVariablesCard;
629
+
630
+ //# sourceMappingURL=host-variables-card.component.js.map