@ai-matrx/records-ui 0.113.36 → 0.113.37
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/CHANGELOG.md +6 -0
- package/dist/first-page.cjs +2 -1
- package/dist/first-page.cjs.map +1 -1
- package/dist/first-page.d.cts +5 -0
- package/dist/first-page.d.ts +5 -0
- package/dist/first-page.js +2 -1
- package/dist/first-page.js.map +1 -1
- package/dist/index.cjs +284 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -3
- package/dist/index.d.ts +10 -3
- package/dist/index.js +296 -40
- package/dist/index.js.map +1 -1
- package/dist/object-actions.cjs.map +1 -1
- package/dist/object-actions.d.cts +1 -1
- package/dist/object-actions.d.ts +1 -1
- package/dist/object-actions.js.map +1 -1
- package/dist/{objectActions-B7AUBkcl.d.cts → objectActions-CV8VEz58.d.cts} +1 -1
- package/dist/{objectActions-B7AUBkcl.d.ts → objectActions-CV8VEz58.d.ts} +1 -1
- package/dist/pickers.cjs +169 -75
- package/dist/pickers.cjs.map +1 -1
- package/dist/pickers.js +130 -36
- package/dist/pickers.js.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -700,13 +700,20 @@ var NOT_ANSWERED_YET = whatYouMayDo(null, false);
|
|
|
700
700
|
var KERNEL_REASON = "This is one of the tables the platform ships, so its columns are not changed from a screen. Make your own table to change columns.";
|
|
701
701
|
function whatYouMayDoWithTable(table, level, known) {
|
|
702
702
|
const base2 = whatYouMayDo(level, known);
|
|
703
|
-
if (!table?.is_kernel) return base2;
|
|
703
|
+
if (!table?.is_kernel) return table && tableIsLocked(table) ? lockedStructure(base2) : base2;
|
|
704
704
|
return {
|
|
705
705
|
...base2,
|
|
706
706
|
structure: false,
|
|
707
707
|
why: (what) => what === "structure" ? KERNEL_REASON : base2.why(what)
|
|
708
708
|
};
|
|
709
709
|
}
|
|
710
|
+
function tableIsLocked(table) {
|
|
711
|
+
return table?.metadata?.["locked"] === true;
|
|
712
|
+
}
|
|
713
|
+
var DATABASE_LOCKED_REASON = "This database is locked, so its columns and settings are read-only. Unlock it to change them.";
|
|
714
|
+
function lockedStructure(base2) {
|
|
715
|
+
return { ...base2, structure: false, why: (what) => what === "structure" ? DATABASE_LOCKED_REASON : base2.why(what) };
|
|
716
|
+
}
|
|
710
717
|
|
|
711
718
|
// src/host.tsx
|
|
712
719
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
@@ -1255,6 +1262,7 @@ var import_data_table2 = require("@ai-matrx/design-system/data-table");
|
|
|
1255
1262
|
|
|
1256
1263
|
// src/personTime.ts
|
|
1257
1264
|
var import_react4 = require("react");
|
|
1265
|
+
var import_table_style = require("@ai-matrx/design-system/data-table/table-style");
|
|
1258
1266
|
var zone = null;
|
|
1259
1267
|
var listeners = /* @__PURE__ */ new Set();
|
|
1260
1268
|
function valid(z) {
|
|
@@ -1270,6 +1278,7 @@ function setPersonTimeZone(next) {
|
|
|
1270
1278
|
const clean = valid(next) ? next : null;
|
|
1271
1279
|
if (clean === zone) return;
|
|
1272
1280
|
zone = clean;
|
|
1281
|
+
(0, import_table_style.setColorRuleTimeZone)(clean);
|
|
1273
1282
|
listeners.forEach((l) => l());
|
|
1274
1283
|
}
|
|
1275
1284
|
function getPersonTimeZone() {
|
|
@@ -3419,6 +3428,28 @@ function descriptionPatch(table, text) {
|
|
|
3419
3428
|
else delete metadata["description"];
|
|
3420
3429
|
return { metadata };
|
|
3421
3430
|
}
|
|
3431
|
+
function lockPatch(table, locked) {
|
|
3432
|
+
const metadata = { ...table.metadata ?? {} };
|
|
3433
|
+
if (locked) metadata["locked"] = true;
|
|
3434
|
+
else delete metadata["locked"];
|
|
3435
|
+
return { metadata };
|
|
3436
|
+
}
|
|
3437
|
+
async function saveTableLock(client, tableId, locked) {
|
|
3438
|
+
const read = await client.tableRead({ table_id: tableId });
|
|
3439
|
+
if (!read.ok) return { ok: false, says: refusalSentence(read.error) };
|
|
3440
|
+
if (!read.data) return { ok: false, says: "This table could not be read, so it was not locked." };
|
|
3441
|
+
const table = read.data;
|
|
3442
|
+
const written = await (0, writeOnTheWinningVersion_exports.updateOnTheWinningVersion)(
|
|
3443
|
+
client,
|
|
3444
|
+
tableId,
|
|
3445
|
+
lockPatch(table, locked),
|
|
3446
|
+
await (0, recordVersions_exports.readTableVersionNow)(client, tableId),
|
|
3447
|
+
{ metadata: table.metadata ?? {} }
|
|
3448
|
+
);
|
|
3449
|
+
if (!written.ok) return { ok: false, says: refusalSentence(written.error) };
|
|
3450
|
+
(0, import_react15.tableShapeChanged)(tableId);
|
|
3451
|
+
return { ok: true };
|
|
3452
|
+
}
|
|
3422
3453
|
function TableDetails({
|
|
3423
3454
|
tableId,
|
|
3424
3455
|
table,
|
|
@@ -3500,6 +3531,20 @@ function TableDetails({
|
|
|
3500
3531
|
}
|
|
3501
3532
|
)
|
|
3502
3533
|
] }),
|
|
3534
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex items-center justify-between gap-2", "data-table-lock": "", children: [
|
|
3535
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-muted-foreground", children: "Lock database" }),
|
|
3536
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
3537
|
+
import_design_system8.Button,
|
|
3538
|
+
{
|
|
3539
|
+
size: "sm",
|
|
3540
|
+
variant: "outline",
|
|
3541
|
+
disabled: busy,
|
|
3542
|
+
onClick: () => void write(lockPatch(table, true), { metadata: table.metadata ?? {} }),
|
|
3543
|
+
"data-table-lock-on": "",
|
|
3544
|
+
children: "Lock"
|
|
3545
|
+
}
|
|
3546
|
+
)
|
|
3547
|
+
] }),
|
|
3503
3548
|
refused ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "text-destructive", children: refused }) : null
|
|
3504
3549
|
] });
|
|
3505
3550
|
}
|
|
@@ -4140,7 +4185,7 @@ var import_react65 = require("@ai-matrx/records/react");
|
|
|
4140
4185
|
var import_design_system37 = require("@ai-matrx/design-system");
|
|
4141
4186
|
|
|
4142
4187
|
// src/presentation.ts
|
|
4143
|
-
var
|
|
4188
|
+
var import_table_style2 = require("@ai-matrx/design-system/data-table/table-style");
|
|
4144
4189
|
var import_data_table3 = require("@ai-matrx/design-system/data-table");
|
|
4145
4190
|
var import_field_formats2 = require("@ai-matrx/design-system/field-formats");
|
|
4146
4191
|
var import_records12 = require("@ai-matrx/records");
|
|
@@ -4273,8 +4318,8 @@ function parseGridPresentation(raw) {
|
|
|
4273
4318
|
}
|
|
4274
4319
|
if (!isRecord(value)) return {};
|
|
4275
4320
|
const out = {};
|
|
4276
|
-
const style = (0,
|
|
4277
|
-
if (!(0,
|
|
4321
|
+
const style = (0, import_table_style2.parseTableStyle)(value["style"]);
|
|
4322
|
+
if (!(0, import_table_style2.tableStyleIsEmpty)(style)) out.style = style;
|
|
4278
4323
|
if (isRecord(value["formats"])) {
|
|
4279
4324
|
const formats = {};
|
|
4280
4325
|
for (const [key, cfg] of Object.entries(value["formats"])) {
|
|
@@ -4328,6 +4373,7 @@ function parseGridPresentation(raw) {
|
|
|
4328
4373
|
if (typeof value["boardHideEmpty"] === "boolean") out.boardHideEmpty = value["boardHideEmpty"];
|
|
4329
4374
|
if (typeof value["boardColorColumns"] === "boolean") out.boardColorColumns = value["boardColorColumns"];
|
|
4330
4375
|
if (typeof value["boardCounts"] === "boolean") out.boardCounts = value["boardCounts"];
|
|
4376
|
+
if (value["locked"] === true) out.locked = true;
|
|
4331
4377
|
if (Array.isArray(value["boardLanesShut"])) {
|
|
4332
4378
|
const shut = value["boardLanesShut"].filter((k) => typeof k === "string");
|
|
4333
4379
|
if (shut.length > 0) out.boardLanesShut = shut;
|
|
@@ -4414,7 +4460,7 @@ function parseGrouping(raw) {
|
|
|
4414
4460
|
return out;
|
|
4415
4461
|
}
|
|
4416
4462
|
function presentationIsEmpty(p) {
|
|
4417
|
-
return (p.style === void 0 || (0,
|
|
4463
|
+
return (p.style === void 0 || (0, import_table_style2.tableStyleIsEmpty)(p.style)) && Object.keys(p.formats ?? {}).length === 0 && (p.frozen?.length ?? 0) === 0 && p.grouping === void 0 && Object.keys(p.widths ?? {}).length === 0 && p.rowHeight === void 0 && (p.hiddenFields?.length ?? 0) === 0 && p.cardHidden === void 0 && (p.columnOrder?.length ?? 0) === 0 && p.gallerySize === void 0 && p.boardSize === void 0 && p.boardPreview === void 0 && p.boardHideEmpty === void 0 && p.boardColorColumns === void 0 && p.boardCounts === void 0 && (p.boardLanesShut?.length ?? 0) === 0 && p.locked === void 0 && p.wrap === void 0 && p.fit === void 0 && p.freezeFirst === void 0 && p.footer === void 0 && Object.keys(p.summaries ?? {}).length === 0;
|
|
4418
4464
|
}
|
|
4419
4465
|
function presentationDocument(p) {
|
|
4420
4466
|
return presentationIsEmpty(p) ? "" : JSON.stringify(p);
|
|
@@ -7283,6 +7329,48 @@ var CONDITION_OPS = [
|
|
|
7283
7329
|
{ op: "gte", label: "is at least" },
|
|
7284
7330
|
{ op: "lte", label: "is at most" }
|
|
7285
7331
|
];
|
|
7332
|
+
var FilterTermsContext = (0, import_react42.createContext)(false);
|
|
7333
|
+
var RELATIVE_DAY_TERMS = [
|
|
7334
|
+
{ kind: "today", label: "is today" },
|
|
7335
|
+
{ kind: "tomorrow", label: "is tomorrow" },
|
|
7336
|
+
{ kind: "yesterday", label: "is yesterday" },
|
|
7337
|
+
{ kind: "this_week", label: "is this week" },
|
|
7338
|
+
{ kind: "next_week", label: "is next week" },
|
|
7339
|
+
{ kind: "last_week", label: "is last week" },
|
|
7340
|
+
{ kind: "this_month", label: "is this month" },
|
|
7341
|
+
{ kind: "next_month", label: "is next month" },
|
|
7342
|
+
{ kind: "last_month", label: "is last month" },
|
|
7343
|
+
{ kind: "before_today", label: "is before today" },
|
|
7344
|
+
{ kind: "next_days", label: "is in the next \u2026 days", takesDays: true },
|
|
7345
|
+
{ kind: "past_days", label: "is in the past \u2026 days", takesDays: true }
|
|
7346
|
+
];
|
|
7347
|
+
function relativeDayNode(fieldId, kind, days) {
|
|
7348
|
+
const zone2 = getPersonTimeZone();
|
|
7349
|
+
const window2 = { relative: kind };
|
|
7350
|
+
if (RELATIVE_DAY_TERMS.find((t) => t.kind === kind)?.takesDays) window2["n"] = days ?? 7;
|
|
7351
|
+
if (zone2) window2["tz"] = zone2;
|
|
7352
|
+
return { op: "within", args: [{ field: fieldId }, { const: window2 }] };
|
|
7353
|
+
}
|
|
7354
|
+
function relativeDayOf(expr) {
|
|
7355
|
+
const node = asNode(expr);
|
|
7356
|
+
if (!node || node["op"] !== "within") return null;
|
|
7357
|
+
const [a, b] = argsOf(node);
|
|
7358
|
+
const window2 = asNode(b?.["const"]);
|
|
7359
|
+
if (!a || typeof a["field"] !== "string" || !window2 || typeof window2["relative"] !== "string") return null;
|
|
7360
|
+
const days = typeof window2["n"] === "number" ? window2["n"] : null;
|
|
7361
|
+
return { kind: window2["relative"], days, field: a["field"] };
|
|
7362
|
+
}
|
|
7363
|
+
function meNode(fieldId, negate) {
|
|
7364
|
+
return { op: negate ? "ne" : "eq", args: [{ field: fieldId }, { const: { me: true } }] };
|
|
7365
|
+
}
|
|
7366
|
+
function meOf(expr) {
|
|
7367
|
+
const node = asNode(expr);
|
|
7368
|
+
if (!node || node["op"] !== "eq" && node["op"] !== "ne") return null;
|
|
7369
|
+
const [a, b] = argsOf(node);
|
|
7370
|
+
const value = asNode(b?.["const"]);
|
|
7371
|
+
if (!a || typeof a["field"] !== "string" || !value || value["me"] !== true) return null;
|
|
7372
|
+
return { which: node["op"] === "eq" ? "me" : "not_me", field: a["field"] };
|
|
7373
|
+
}
|
|
7286
7374
|
function pointsAtARecord(f) {
|
|
7287
7375
|
return !!f?.field && f.field.type === "relation" && !!f.field.relation_target;
|
|
7288
7376
|
}
|
|
@@ -7353,6 +7441,7 @@ function ChoiceValue({ field: field2, value, onChange }) {
|
|
|
7353
7441
|
}
|
|
7354
7442
|
function conditionIsSimple(expr) {
|
|
7355
7443
|
if (expr === null || expr === void 0) return true;
|
|
7444
|
+
if (relativeDayOf(expr) !== null || meOf(expr) !== null) return true;
|
|
7356
7445
|
const node = expr;
|
|
7357
7446
|
if (typeof node.op !== "string" || !CONDITION_OPS.some((c) => c.op === node.op)) return false;
|
|
7358
7447
|
if (!Array.isArray(node.args)) return false;
|
|
@@ -7397,6 +7486,14 @@ function conditionInWords(expr, fields) {
|
|
|
7397
7486
|
const args = Array.isArray(n["args"]) ? n["args"] : [];
|
|
7398
7487
|
if (op2 === "not") return `not ${walk(args[0])}`;
|
|
7399
7488
|
if (op2 === "present") return `${walk(args[0])} has been answered`;
|
|
7489
|
+
const dayRange = relativeDayOf(n);
|
|
7490
|
+
if (dayRange) {
|
|
7491
|
+
const term = RELATIVE_DAY_TERMS.find((t) => t.kind === dayRange.kind);
|
|
7492
|
+
const said3 = term ? term.label.replace("\u2026", String(dayRange.days ?? 7)) : "is in a day range";
|
|
7493
|
+
return `${name(dayRange.field)} ${said3}`;
|
|
7494
|
+
}
|
|
7495
|
+
const reader = meOf(n);
|
|
7496
|
+
if (reader) return `${name(reader.field)} ${reader.which === "me" ? "is me" : "is not me"}`;
|
|
7400
7497
|
const word = said2[op2] ?? op2;
|
|
7401
7498
|
if (op2 === "and" || op2 === "or") return args.map(walk).join(` ${word} `);
|
|
7402
7499
|
if (args.length === 2) return `${walk(args[0])} ${word} ${walk(args[1])}`;
|
|
@@ -7418,11 +7515,31 @@ function conditionValue(typed2) {
|
|
|
7418
7515
|
}
|
|
7419
7516
|
function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", className }) {
|
|
7420
7517
|
const node = expr ?? null;
|
|
7421
|
-
const
|
|
7518
|
+
const filterTerms = (0, import_react42.useContext)(FilterTermsContext);
|
|
7519
|
+
const relative = relativeDayOf(expr);
|
|
7520
|
+
const reader = meOf(expr);
|
|
7521
|
+
const op2 = relative ? `rel:${relative.kind}` : reader ? reader.which : node?.op ?? "";
|
|
7422
7522
|
const onField = node?.args?.[0]?.field ?? "";
|
|
7423
|
-
const value = node?.args?.[1]?.const ?? "";
|
|
7523
|
+
const value = relative || reader ? "" : node?.args?.[1]?.const ?? "";
|
|
7424
7524
|
const chosen = fields.find((f) => f.id === onField);
|
|
7525
|
+
const chosenKind = chosen?.field ? String(editorKindFor(chosen.field)) : "";
|
|
7526
|
+
const extraTerms = !filterTerms ? [] : chosenKind === "datetime" ? RELATIVE_DAY_TERMS.map((t) => ({ op: `rel:${t.kind}`, label: t.label })) : chosenKind === "member" ? [{ op: "me", label: "is me" }, { op: "not_me", label: "is not me" }] : [];
|
|
7527
|
+
const relativeTerm = relative ? RELATIVE_DAY_TERMS.find((t) => t.kind === relative.kind) : void 0;
|
|
7425
7528
|
function write(next) {
|
|
7529
|
+
const picked = next.op;
|
|
7530
|
+
const fieldNow = next.field ?? onField;
|
|
7531
|
+
if (fieldNow && picked !== void 0 && (picked.startsWith("rel:") || picked === "me" || picked === "not_me")) {
|
|
7532
|
+
onChange(picked === "me" || picked === "not_me" ? meNode(fieldNow, picked === "not_me") : relativeDayNode(fieldNow, picked.slice(4), relative?.days ?? void 0));
|
|
7533
|
+
return;
|
|
7534
|
+
}
|
|
7535
|
+
if (fieldNow && next.days !== void 0 && relative) {
|
|
7536
|
+
onChange(relativeDayNode(fieldNow, relative.kind, next.days));
|
|
7537
|
+
return;
|
|
7538
|
+
}
|
|
7539
|
+
if (next.field !== void 0 && (relative || reader)) {
|
|
7540
|
+
onChange(next.field ? { op: CONDITION_OPS[0].op, args: [{ field: next.field }, { const: "" }] } : null);
|
|
7541
|
+
return;
|
|
7542
|
+
}
|
|
7426
7543
|
const chosenOp = next.op ?? (op2 || (next.field ? CONDITION_OPS[0].op : ""));
|
|
7427
7544
|
const chosenField = next.field ?? onField;
|
|
7428
7545
|
const chosenValue = next.value ?? value;
|
|
@@ -7464,11 +7581,21 @@ function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", cla
|
|
|
7464
7581
|
className: "h-8 w-auto min-w-[6rem] text-xs",
|
|
7465
7582
|
value: op2,
|
|
7466
7583
|
onChange: (next) => write({ op: next }),
|
|
7467
|
-
options: CONDITION_OPS.map((c) => ({ value: c.op, label: c.label })),
|
|
7584
|
+
options: [...CONDITION_OPS, ...extraTerms].map((c) => ({ value: c.op, label: c.label })),
|
|
7468
7585
|
attrs: { "data-condition": "comparison" }
|
|
7469
7586
|
}
|
|
7470
7587
|
),
|
|
7471
|
-
|
|
7588
|
+
relative ? relativeTerm?.takesDays ? /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
|
|
7589
|
+
import_design_system26.BasicInput,
|
|
7590
|
+
{
|
|
7591
|
+
className: "h-8 w-16 text-xs",
|
|
7592
|
+
type: "number",
|
|
7593
|
+
min: 0,
|
|
7594
|
+
"aria-label": "Number of days",
|
|
7595
|
+
value: String(relative.days ?? 7),
|
|
7596
|
+
onChange: (e) => write({ days: Math.max(0, Math.floor(Number(e.target.value) || 0)) })
|
|
7597
|
+
}
|
|
7598
|
+
) : null : reader || op2 === "present" ? null : pointsAtARecord(chosen) ? (
|
|
7472
7599
|
// A RECORD IS PICKED BY ITS WORDS, NEVER TYPED AS AN ID. The same picker the
|
|
7473
7600
|
// cell uses, on the same `relation_words_many` the cell reads, so a filter and
|
|
7474
7601
|
// the column it filters can never disagree about what a record is called. It
|
|
@@ -13261,7 +13388,7 @@ function rulesLeaf(drafts, fields) {
|
|
|
13261
13388
|
const out = [];
|
|
13262
13389
|
for (const d of drafts) {
|
|
13263
13390
|
const field2 = fields.find((f) => f.key === d.field);
|
|
13264
|
-
if (!field2) continue;
|
|
13391
|
+
if (!field2 || import_data_table11.COLOR_RULE_DATE_OPS.includes(d.op)) continue;
|
|
13265
13392
|
out.push({
|
|
13266
13393
|
...d.id ? { id: d.id } : {},
|
|
13267
13394
|
field: field2.id,
|
|
@@ -13273,6 +13400,22 @@ function rulesLeaf(drafts, fields) {
|
|
|
13273
13400
|
}
|
|
13274
13401
|
return out;
|
|
13275
13402
|
}
|
|
13403
|
+
function draftsFromView(rules, fields) {
|
|
13404
|
+
return (rules ?? []).filter((rule) => fields.some((f) => f.key === rule.field)).map((rule) => ({ id: rule.id, field: rule.field, op: rule.op, value: rule.value ?? "", color: rule.color, target: rule.target }));
|
|
13405
|
+
}
|
|
13406
|
+
function viewRulesFrom(drafts) {
|
|
13407
|
+
return drafts.map((d, i) => ({
|
|
13408
|
+
id: d.id ?? `rule-${Date.now().toString(36)}-${i}`,
|
|
13409
|
+
field: d.field,
|
|
13410
|
+
op: d.op,
|
|
13411
|
+
...import_data_table11.OPS_WITH_VALUE.includes(d.op) ? { value: d.value } : {},
|
|
13412
|
+
color: d.color,
|
|
13413
|
+
target: d.target
|
|
13414
|
+
}));
|
|
13415
|
+
}
|
|
13416
|
+
function isDateField2(field2) {
|
|
13417
|
+
return field2 ? String(editorKindFor(field2)) === "datetime" : false;
|
|
13418
|
+
}
|
|
13276
13419
|
function swatches(prefix, current, write) {
|
|
13277
13420
|
return [
|
|
13278
13421
|
...import_data_table11.STYLE_COLORS.map((color) => ({
|
|
@@ -13284,7 +13427,7 @@ function swatches(prefix, current, write) {
|
|
|
13284
13427
|
];
|
|
13285
13428
|
}
|
|
13286
13429
|
function useGridColours(args) {
|
|
13287
|
-
const { enabled, tableId, fields, decorations, mayColour, whyNot, notify } = args;
|
|
13430
|
+
const { enabled, tableId, fields, decorations, mayColour, whyNot, notify, view } = args;
|
|
13288
13431
|
const client = (0, import_react74.useRecordsClient)();
|
|
13289
13432
|
const [rulesOpen, setRulesOpen] = (0, import_react73.useState)(null);
|
|
13290
13433
|
const dialogOptions = useChoiceOptions(fields, rulesOpen !== null ? fields.filter(isChoiceField).map((f) => f.key) : []);
|
|
@@ -13432,6 +13575,16 @@ function useGridColours(args) {
|
|
|
13432
13575
|
decorations: decorations.data,
|
|
13433
13576
|
options: dialogOptions,
|
|
13434
13577
|
onClose: () => setRulesOpen(null),
|
|
13578
|
+
...view ? {
|
|
13579
|
+
viewRules: {
|
|
13580
|
+
drafts: draftsFromView(view.rules, fields),
|
|
13581
|
+
onSave: async (drafts) => {
|
|
13582
|
+
view.save(viewRulesFrom(drafts));
|
|
13583
|
+
notify?.success(drafts.length === 0 ? "This view's colour rules are cleared." : "This view's colour rules are kept.");
|
|
13584
|
+
setRulesOpen(null);
|
|
13585
|
+
}
|
|
13586
|
+
}
|
|
13587
|
+
} : {},
|
|
13435
13588
|
onSave: async (drafts) => {
|
|
13436
13589
|
const kept = await write(["rules"], rulesLeaf(drafts, fields), drafts.length === 0 ? "The colour rules are cleared." : "The colour rules are kept.");
|
|
13437
13590
|
if (kept) setRulesOpen(null);
|
|
@@ -13475,17 +13628,27 @@ function ColourRulesDialog({
|
|
|
13475
13628
|
decorations,
|
|
13476
13629
|
options,
|
|
13477
13630
|
onClose,
|
|
13478
|
-
onSave
|
|
13631
|
+
onSave,
|
|
13632
|
+
viewRules
|
|
13479
13633
|
}) {
|
|
13480
13634
|
const [drafts, setDrafts] = (0, import_react73.useState)(null);
|
|
13481
13635
|
const [busy, setBusy] = (0, import_react73.useState)(false);
|
|
13482
|
-
const
|
|
13636
|
+
const [scope, setScope] = (0, import_react73.useState)("table");
|
|
13637
|
+
const keptTable = (0, import_react73.useMemo)(() => draftsFrom(decorations, fields), [decorations, fields]);
|
|
13638
|
+
const held = scope === "view" && viewRules ? viewRules.drafts : keptTable;
|
|
13639
|
+
const opsFor = (key) => {
|
|
13640
|
+
const field2 = fields.find((f) => f.key === key);
|
|
13641
|
+
if (choicesOf(key)) return CHOICE_OPS;
|
|
13642
|
+
const dateTests = scope === "view" && isDateField2(field2);
|
|
13643
|
+
return import_data_table11.COLOR_RULE_OPS.filter((op2) => dateTests || !import_data_table11.COLOR_RULE_DATE_OPS.includes(op2));
|
|
13644
|
+
};
|
|
13483
13645
|
const rows = drafts ?? held;
|
|
13484
13646
|
const set = (i, patch) => setDrafts(
|
|
13485
13647
|
rows.map((r, j) => {
|
|
13486
13648
|
if (j !== i) return r;
|
|
13487
13649
|
const next = { ...r, ...patch };
|
|
13488
13650
|
if (patch.field !== void 0 && choicesOf(next.field) && !CHOICE_OPS.includes(next.op)) return { ...next, op: "is", value: "" };
|
|
13651
|
+
if (patch.field !== void 0 && !opsFor(next.field).includes(next.op)) return { ...next, op: "is", value: "" };
|
|
13489
13652
|
return next;
|
|
13490
13653
|
})
|
|
13491
13654
|
);
|
|
@@ -13501,9 +13664,28 @@ function ColourRulesDialog({
|
|
|
13501
13664
|
return /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_design_system41.Dialog, { open, onOpenChange: (next) => !next ? close() : void 0, children: /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)(import_design_system41.DialogContent, { className: "sm:max-w-2xl", "data-records-colour-rules": "", children: [
|
|
13502
13665
|
/* @__PURE__ */ (0, import_jsx_runtime48.jsxs)(import_design_system41.DialogHeader, { children: [
|
|
13503
13666
|
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_design_system41.DialogTitle, { children: "Colour rules" }),
|
|
13504
|
-
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_design_system41.DialogDescription, { children: "The first rule a row matches wins. A rule tints the whole row, or only the cell it reads. Everyone who opens this table sees them." })
|
|
13667
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_design_system41.DialogDescription, { children: scope === "view" ? "The first rule a row matches wins. These rules belong to this view alone, and a date can be tested against today." : "The first rule a row matches wins. A rule tints the whole row, or only the cell it reads. Everyone who opens this table sees them." })
|
|
13505
13668
|
] }),
|
|
13506
13669
|
/* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "flex flex-col gap-2", children: [
|
|
13670
|
+
viewRules ? /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "flex items-center gap-1.5 text-xs", "data-records-colour-rules-scope": "", children: [
|
|
13671
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)("span", { className: "text-muted-foreground", children: "Keep them on" }),
|
|
13672
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
|
|
13673
|
+
PickOne,
|
|
13674
|
+
{
|
|
13675
|
+
label: "Where the rules are kept",
|
|
13676
|
+
className: SELECT2,
|
|
13677
|
+
value: scope,
|
|
13678
|
+
onChange: (picked) => {
|
|
13679
|
+
setScope(picked === "view" ? "view" : "table");
|
|
13680
|
+
setDrafts(null);
|
|
13681
|
+
},
|
|
13682
|
+
options: [
|
|
13683
|
+
{ value: "table", label: "the table, for everyone" },
|
|
13684
|
+
{ value: "view", label: "this view" }
|
|
13685
|
+
]
|
|
13686
|
+
}
|
|
13687
|
+
)
|
|
13688
|
+
] }) : null,
|
|
13507
13689
|
rows.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("p", { className: "text-xs text-muted-foreground", children: "No rules yet." }) : null,
|
|
13508
13690
|
rows.map((rule, i) => /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "flex flex-wrap items-center gap-1.5 text-xs", "data-records-colour-rule": "", children: [
|
|
13509
13691
|
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)("span", { className: "text-muted-foreground", children: i === 0 ? "When" : "Else when" }),
|
|
@@ -13525,7 +13707,7 @@ function ColourRulesDialog({
|
|
|
13525
13707
|
className: SELECT2,
|
|
13526
13708
|
value: rule.op,
|
|
13527
13709
|
onChange: (picked) => set(i, { op: picked }),
|
|
13528
|
-
options: (
|
|
13710
|
+
options: opsFor(rule.field).map((op2) => ({
|
|
13529
13711
|
value: op2,
|
|
13530
13712
|
label: import_data_table11.COLOR_RULE_OP_LABELS[op2]
|
|
13531
13713
|
}))
|
|
@@ -13602,7 +13784,7 @@ function ColourRulesDialog({
|
|
|
13602
13784
|
disabled: busy || drafts === null,
|
|
13603
13785
|
onClick: async () => {
|
|
13604
13786
|
setBusy(true);
|
|
13605
|
-
await onSave(rows);
|
|
13787
|
+
await (scope === "view" && viewRules ? viewRules.onSave(rows) : onSave(rows));
|
|
13606
13788
|
setBusy(false);
|
|
13607
13789
|
setDrafts(null);
|
|
13608
13790
|
},
|
|
@@ -15614,7 +15796,12 @@ function useMergedGridControls(args) {
|
|
|
15614
15796
|
decorations: args.decorations ?? noDecorations,
|
|
15615
15797
|
mayColour: editable && rights.write,
|
|
15616
15798
|
whyNot: editable ? rights.why("write") : "Editing is off here.",
|
|
15617
|
-
notify
|
|
15799
|
+
notify,
|
|
15800
|
+
// THIS VIEW'S OWN RULES: kept on the look like every other view setting (and saved to the view the same way).
|
|
15801
|
+
view: {
|
|
15802
|
+
rules: presentation.style?.rules,
|
|
15803
|
+
save: (rules) => writeLook({ ...presentation, style: { ...presentation.style ?? { version: 1 }, rules } })
|
|
15804
|
+
}
|
|
15618
15805
|
});
|
|
15619
15806
|
const [held, setHeld] = (0, import_react90.useState)(null);
|
|
15620
15807
|
const keptOrder = presentation.columnOrder;
|
|
@@ -27083,7 +27270,9 @@ var GLYPHS = {
|
|
|
27083
27270
|
"archive": [["rect", { "width": "20", "height": "5", "x": "2", "y": "3", "rx": "1" }], ["path", { "d": "M4 8v11a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8" }], ["path", { "d": "M10 12h4" }]],
|
|
27084
27271
|
"link-2": [["path", { "d": "M9 17H7A5 5 0 0 1 7 7h2" }], ["path", { "d": "M15 7h2a5 5 0 1 1 0 10h-2" }], ["line", { "x1": "8", "x2": "16", "y1": "12", "y2": "12" }]],
|
|
27085
27272
|
"layout-grid": [["rect", { "width": "7", "height": "7", "x": "3", "y": "3", "rx": "1" }], ["rect", { "width": "7", "height": "7", "x": "14", "y": "3", "rx": "1" }], ["rect", { "width": "7", "height": "7", "x": "14", "y": "14", "rx": "1" }], ["rect", { "width": "7", "height": "7", "x": "3", "y": "14", "rx": "1" }]],
|
|
27086
|
-
"git-merge": [["circle", { "cx": "18", "cy": "18", "r": "3" }], ["circle", { "cx": "6", "cy": "6", "r": "3" }], ["path", { "d": "M6 21V9a9 9 0 0 0 9 9" }]]
|
|
27273
|
+
"git-merge": [["circle", { "cx": "18", "cy": "18", "r": "3" }], ["circle", { "cx": "6", "cy": "6", "r": "3" }], ["path", { "d": "M6 21V9a9 9 0 0 0 9 9" }]],
|
|
27274
|
+
"lock": [["rect", { "width": "18", "height": "11", "x": "3", "y": "11", "rx": "2", "ry": "2" }], ["path", { "d": "M7 11V7a5 5 0 0 1 10 0v4" }]],
|
|
27275
|
+
"lock-open": [["rect", { "width": "18", "height": "11", "x": "3", "y": "11", "rx": "2", "ry": "2" }], ["path", { "d": "M7 11V7a5 5 0 0 1 9.9-1" }]]
|
|
27087
27276
|
};
|
|
27088
27277
|
function ObjectActionGlyph({ name, ...props }) {
|
|
27089
27278
|
return /* @__PURE__ */ (0, import_jsx_runtime85.jsx)(
|
|
@@ -29645,7 +29834,29 @@ function TableSettingsPanel({
|
|
|
29645
29834
|
const [enriching, setEnriching] = (0, import_react165.useState)(null);
|
|
29646
29835
|
if (table.error) return /* @__PURE__ */ (0, import_jsx_runtime99.jsx)(RefusalNotice, { error: table.error, className });
|
|
29647
29836
|
if (fields.error) return /* @__PURE__ */ (0, import_jsx_runtime99.jsx)(RefusalNotice, { error: fields.error, className });
|
|
29648
|
-
if (!rights.structure)
|
|
29837
|
+
if (!rights.structure) {
|
|
29838
|
+
if (tableIsLocked(table.data)) {
|
|
29839
|
+
return /* @__PURE__ */ (0, import_jsx_runtime99.jsxs)("div", { className: (0, import_design_system86.cn)("flex flex-col items-start gap-2 text-xs text-muted-foreground", className), "data-table-locked": "", children: [
|
|
29840
|
+
/* @__PURE__ */ (0, import_jsx_runtime99.jsx)("p", { children: DATABASE_LOCKED_REASON }),
|
|
29841
|
+
/* @__PURE__ */ (0, import_jsx_runtime99.jsx)(
|
|
29842
|
+
import_design_system86.Button,
|
|
29843
|
+
{
|
|
29844
|
+
size: "sm",
|
|
29845
|
+
variant: "outline",
|
|
29846
|
+
onClick: () => {
|
|
29847
|
+
void saveTableLock(client, tableId, false).then((done) => {
|
|
29848
|
+
if (done.ok) table.reload();
|
|
29849
|
+
else ui.notify?.error(done.says);
|
|
29850
|
+
});
|
|
29851
|
+
},
|
|
29852
|
+
"data-table-unlock": "",
|
|
29853
|
+
children: "Unlock database"
|
|
29854
|
+
}
|
|
29855
|
+
)
|
|
29856
|
+
] });
|
|
29857
|
+
}
|
|
29858
|
+
return /* @__PURE__ */ (0, import_jsx_runtime99.jsx)("p", { className: (0, import_design_system86.cn)("text-xs text-muted-foreground", className), children: rights.why("structure") });
|
|
29859
|
+
}
|
|
29649
29860
|
const rows = [...fields.data ?? []].sort((a, b) => (a.sort ?? 0) - (b.sort ?? 0));
|
|
29650
29861
|
const remove = async (field2) => {
|
|
29651
29862
|
const gone = await shape.retire({ field_id: field2.id });
|
|
@@ -36709,20 +36920,23 @@ function useSeedGuard() {
|
|
|
36709
36920
|
var import_react213 = require("@ai-matrx/records/react");
|
|
36710
36921
|
|
|
36711
36922
|
// src/viewActions.ts
|
|
36923
|
+
var VIEW_LOCKED_REASON = "This view is locked. Unlock it to change it.";
|
|
36712
36924
|
var NOOP3 = () => {
|
|
36713
36925
|
};
|
|
36714
|
-
function viewActions(whyNot, host) {
|
|
36926
|
+
function viewActions(whyNot, host, locked = false) {
|
|
36715
36927
|
const one = (base2, run) => {
|
|
36716
36928
|
if (whyNot !== null) return { ...base2, disabledReason: whyNot, run: NOOP3 };
|
|
36717
36929
|
if (!run) return { ...base2, disabledReason: ACTION_NOT_HERE_REASON, run: NOOP3 };
|
|
36718
36930
|
return { ...base2, run };
|
|
36719
36931
|
};
|
|
36932
|
+
const whenUnlocked = (action3) => locked && action3.disabledReason === void 0 ? { ...action3, disabledReason: VIEW_LOCKED_REASON, run: NOOP3 } : action3;
|
|
36933
|
+
const lockVerb = host.toggleLock ? [one({ id: "lock", label: locked ? "Unlock view" : "Lock view", icon: locked ? "lock-open" : "lock", group: "organize" }, host.toggleLock)] : [];
|
|
36720
36934
|
return [
|
|
36721
|
-
one({ id: "rename", label: "Rename view\u2026", icon: "pencil", group: "organize", verb: "rename" }, host.rename),
|
|
36935
|
+
whenUnlocked(one({ id: "rename", label: "Rename view\u2026", icon: "pencil", group: "organize", verb: "rename" }, host.rename)),
|
|
36722
36936
|
one({ id: "duplicate", label: "Duplicate view", icon: "copy", group: "organize", verb: "duplicate" }, host.duplicate),
|
|
36723
|
-
|
|
36724
|
-
|
|
36725
|
-
host.archive
|
|
36937
|
+
...lockVerb,
|
|
36938
|
+
whenUnlocked(
|
|
36939
|
+
one({ id: "archive", label: "Archive view", icon: "archive", group: "danger", destructive: true, verb: "archive" }, host.archive)
|
|
36726
36940
|
)
|
|
36727
36941
|
];
|
|
36728
36942
|
}
|
|
@@ -36841,6 +37055,17 @@ function ViewBar({ tableId, activeViewId, onActiveView, seed, settings, onViews,
|
|
|
36841
37055
|
setRenaming(null);
|
|
36842
37056
|
await load();
|
|
36843
37057
|
}
|
|
37058
|
+
async function toggleLock(view) {
|
|
37059
|
+
const presentation = { ...view.presentation ?? {} };
|
|
37060
|
+
if (presentation.locked === true) delete presentation.locked;
|
|
37061
|
+
else presentation.locked = true;
|
|
37062
|
+
const written = await saveViewPatch(client, tableId, view.id, { presentation });
|
|
37063
|
+
if (!written.ok) {
|
|
37064
|
+
setError(written.error);
|
|
37065
|
+
return;
|
|
37066
|
+
}
|
|
37067
|
+
await load();
|
|
37068
|
+
}
|
|
36844
37069
|
async function duplicate(view) {
|
|
36845
37070
|
const { id: _id, movedFrom: _moved, ...spec } = view;
|
|
36846
37071
|
const written = await declareView(client, {
|
|
@@ -36996,8 +37221,9 @@ function ViewBar({ tableId, activeViewId, onActiveView, seed, settings, onViews,
|
|
|
36996
37221
|
) : viewActions(rights.admin ? null : rightsReason(rights, "structure") ?? ACTION_NO_ACCESS_REASON, {
|
|
36997
37222
|
rename: () => setRenaming(current.name),
|
|
36998
37223
|
duplicate: () => duplicate(current),
|
|
36999
|
-
archive: () => archive(current)
|
|
37000
|
-
|
|
37224
|
+
archive: () => archive(current),
|
|
37225
|
+
toggleLock: () => toggleLock(current)
|
|
37226
|
+
}, current.presentation?.locked === true).map((action3) => /* @__PURE__ */ (0, import_jsx_runtime122.jsxs)(
|
|
37001
37227
|
"button",
|
|
37002
37228
|
{
|
|
37003
37229
|
type: "button",
|
|
@@ -49151,7 +49377,7 @@ function ViewFilter({ tableId, view, onApply, className }) {
|
|
|
49151
49377
|
"This view asks: ",
|
|
49152
49378
|
conditionInWords(saved, conditionFields),
|
|
49153
49379
|
". It cannot be drawn here; clear it to start a new filter."
|
|
49154
|
-
] }) : /* @__PURE__ */ (0, import_jsx_runtime164.jsx)(
|
|
49380
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime164.jsx)(FilterTermsContext.Provider, { value: true, children: /* @__PURE__ */ (0, import_jsx_runtime164.jsx)(
|
|
49155
49381
|
ConditionGroup,
|
|
49156
49382
|
{
|
|
49157
49383
|
lead: "Show records where",
|
|
@@ -49160,7 +49386,7 @@ function ViewFilter({ tableId, view, onApply, className }) {
|
|
|
49160
49386
|
fields: conditionFields,
|
|
49161
49387
|
onChange: setDraft
|
|
49162
49388
|
}
|
|
49163
|
-
),
|
|
49389
|
+
) }),
|
|
49164
49390
|
view.ruleId ? null : /* @__PURE__ */ (0, import_jsx_runtime164.jsxs)("div", { className: "flex items-center gap-1", children: [
|
|
49165
49391
|
changed ? /* @__PURE__ */ (0, import_jsx_runtime164.jsx)(
|
|
49166
49392
|
import_design_system147.Button,
|
|
@@ -49933,10 +50159,31 @@ function TablePage({
|
|
|
49933
50159
|
setSurface((now) => ({ ...now, main: isLayout ? "records" : named }));
|
|
49934
50160
|
}, [activeView]);
|
|
49935
50161
|
const [viewSaveRefused, setViewSaveRefused] = (0, import_react291.useState)(null);
|
|
50162
|
+
const viewLocked = view?.presentation?.locked === true;
|
|
50163
|
+
const unlockView = (0, import_react291.useCallback)(async () => {
|
|
50164
|
+
const current = view;
|
|
50165
|
+
if (!current) return;
|
|
50166
|
+
const presentation = { ...current.presentation ?? {} };
|
|
50167
|
+
delete presentation.locked;
|
|
50168
|
+
const written = await saveViewPatch(client, tableId, current.id, { presentation });
|
|
50169
|
+
if (!written.ok) {
|
|
50170
|
+
recordsUi.notify?.error(refusalSentence(written.error));
|
|
50171
|
+
return;
|
|
50172
|
+
}
|
|
50173
|
+
setView({ ...current, presentation });
|
|
50174
|
+
setViewsEpoch((n) => n + 1);
|
|
50175
|
+
}, [client, tableId, view, recordsUi.notify]);
|
|
50176
|
+
const sayLocked = (0, import_react291.useCallback)(() => {
|
|
50177
|
+
recordsUi.notify?.success(VIEW_LOCKED_REASON, { label: "Unlock", run: () => void unlockView() });
|
|
50178
|
+
}, [recordsUi.notify, unlockView]);
|
|
49936
50179
|
const patchView = (0, import_react291.useCallback)(
|
|
49937
50180
|
async (asked2) => {
|
|
49938
50181
|
const current = view;
|
|
49939
50182
|
if (!current) return;
|
|
50183
|
+
if (viewLocked) {
|
|
50184
|
+
sayLocked();
|
|
50185
|
+
return;
|
|
50186
|
+
}
|
|
49940
50187
|
const part = (0, import_views3.lookPart)(asked2);
|
|
49941
50188
|
if (Object.keys(part).length === 0) return;
|
|
49942
50189
|
const before = looks?.[current.id];
|
|
@@ -49947,7 +50194,7 @@ function TablePage({
|
|
|
49947
50194
|
if (kept.ok) return;
|
|
49948
50195
|
setViewSaveRefused({ error: kept.error, patch: asked2, lookBefore: before });
|
|
49949
50196
|
},
|
|
49950
|
-
[client, tableId, view, looks]
|
|
50197
|
+
[client, tableId, view, looks, viewLocked, sayLocked]
|
|
49951
50198
|
);
|
|
49952
50199
|
const arrival = (0, import_react291.useRef)(viewAddress ?? null);
|
|
49953
50200
|
const [gridQuery, setGridQuery] = (0, import_react291.useState)(null);
|
|
@@ -50020,6 +50267,10 @@ function TablePage({
|
|
|
50020
50267
|
const current = view;
|
|
50021
50268
|
const mine = current && looks ? looks[current.id] : void 0;
|
|
50022
50269
|
if (!current || !mine) return;
|
|
50270
|
+
if (viewLocked) {
|
|
50271
|
+
sayLocked();
|
|
50272
|
+
return;
|
|
50273
|
+
}
|
|
50023
50274
|
setLookRefused(null);
|
|
50024
50275
|
const { patch, left } = (0, import_views3.saveToViewPlan)(current, mine);
|
|
50025
50276
|
if (Object.keys(viewPatchDocument(patch)).length > 0) {
|
|
@@ -50066,6 +50317,10 @@ function TablePage({
|
|
|
50066
50317
|
const designate = async () => {
|
|
50067
50318
|
const current = view;
|
|
50068
50319
|
if (!current || !layoutShowing) return;
|
|
50320
|
+
if (viewLocked) {
|
|
50321
|
+
sayLocked();
|
|
50322
|
+
return;
|
|
50323
|
+
}
|
|
50069
50324
|
setDesignateRefused(null);
|
|
50070
50325
|
const written = await (0, import_views2.designateView)(client, tableId, current.id, layoutShowing);
|
|
50071
50326
|
if (!written.ok) {
|