@ai-matrx/records-ui 0.115.78 → 0.115.80
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 +29 -1
- package/dist/index.cjs +220 -70
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -1
- package/dist/index.d.ts +14 -1
- package/dist/index.js +578 -428
- package/dist/index.js.map +1 -1
- package/dist/pickers.cjs +129 -49
- package/dist/pickers.cjs.map +1 -1
- package/dist/pickers.js +162 -82
- package/dist/pickers.js.map +1 -1
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,37 @@
|
|
|
1
1
|
# Changelog — @ai-matrx/records-ui
|
|
2
2
|
|
|
3
|
-
## 0.115.
|
|
3
|
+
## 0.115.80
|
|
4
4
|
|
|
5
5
|
UNVERIFIED: this release has no Unreleased entry and the source comparison could not be made (the caller supplied no source comparison). Whether source changed is unknown; consumer action is unknown.
|
|
6
6
|
Check the package's gitHead on npm against this version's gitHead: `git diff <previous gitHead> <this gitHead> -- apps/shared/records-ui/src`.
|
|
7
7
|
|
|
8
|
+
## 0.115.79
|
|
9
|
+
|
|
10
|
+
SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
|
|
11
|
+
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
|
|
12
|
+
Diff: `git diff c3135c57b9 aa17cdaf9c -- apps/shared/records-ui/src`
|
|
13
|
+
|
|
14
|
+
Changed shipped source files (7):
|
|
15
|
+
- `apps/shared/records-ui/src/ImportWizard.tsx`
|
|
16
|
+
- `apps/shared/records-ui/src/TablePage.tsx`
|
|
17
|
+
- `apps/shared/records-ui/src/ViewBar.tsx`
|
|
18
|
+
- `apps/shared/records-ui/src/gridAttachments.tsx`
|
|
19
|
+
- `apps/shared/records-ui/src/gridColumns.tsx`
|
|
20
|
+
- `apps/shared/records-ui/src/importRuleCheck.ts`
|
|
21
|
+
- `apps/shared/records-ui/src/viewActions.ts`
|
|
22
|
+
|
|
23
|
+
## 0.115.78
|
|
24
|
+
|
|
25
|
+
SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
|
|
26
|
+
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
|
|
27
|
+
Diff: `git diff 254d4d3f8b c3135c57b9 -- apps/shared/records-ui/src`
|
|
28
|
+
|
|
29
|
+
Changed shipped source files (4):
|
|
30
|
+
- `apps/shared/records-ui/src/Grid.tsx`
|
|
31
|
+
- `apps/shared/records-ui/src/ViewSwitcher.tsx`
|
|
32
|
+
- `apps/shared/records-ui/src/newRowValues.tsx`
|
|
33
|
+
- `apps/shared/records-ui/src/recordMoves.tsx`
|
|
34
|
+
|
|
8
35
|
## 0.115.77
|
|
9
36
|
|
|
10
37
|
SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
|
|
@@ -19,6 +46,7 @@ Changed shipped source files (3):
|
|
|
19
46
|
## 0.115.76
|
|
20
47
|
### Unreleased
|
|
21
48
|
|
|
49
|
+
- `ViewFilter` / `Condition` (NOTION-Q): a view's filter asks a multi-select column Notion's list questions (contains, does not contain, is empty, is not empty) and offers the fields of every table a relation points at ("Client › Client Type is / is not Past Client", the store's `linked` node: "is" = some linked record is, "is not" = none is). New exports `LIST_TERMS`, `linkedNode`, `linkedOf`; `ConditionField.via`. Needs the store migration `notionq_a_a_multi_pick_and_a_linked_field_filter_a_list`.
|
|
22
50
|
- `ViewSwitcher newRowValues`: the host hands the values a new row starts with (Spaces: a view filtered to "This page's row" hands the relation), and every add path of every layout — the grid's add line and toolbar, board and pipeline "+", gallery, list, calendar, timeline — writes them with the row. A path's own values (a board column's stage) win. Absent: a blank row, as before.
|
|
23
51
|
- The formula editor's function list follows the store's vocabulary: Airtable's remaining functions (ROUNDUP, FLOOR, COUNTA,
|
|
24
52
|
IS_BEFORE, DATETIME_DIFF, WORKDAY_DIFF, ARRAYFLATTEN, REGEX_EXTRACT, RECORD_ID, …) are offered, and CREATED_TIME() and
|
package/dist/index.cjs
CHANGED
|
@@ -6886,46 +6886,63 @@ function AttachmentChip({ id, onRemove, onPreview }) {
|
|
|
6886
6886
|
const words2 = read?.name ?? (read ? FILE_CHIP_UNREAD : "\u2026");
|
|
6887
6887
|
const href = read?.fileId ? host.hrefForFile?.({ fileId: read.fileId }) : void 0;
|
|
6888
6888
|
const title = onPreview ? `Preview ${words2}` : href ? `Open ${words2}` : read?.why ?? FILE_CHIP_NO_PAGE;
|
|
6889
|
-
return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
|
|
6890
|
-
|
|
6891
|
-
|
|
6892
|
-
|
|
6893
|
-
|
|
6894
|
-
|
|
6895
|
-
|
|
6889
|
+
return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
|
|
6890
|
+
import_design_system22.Badge,
|
|
6891
|
+
{
|
|
6892
|
+
variant: "secondary",
|
|
6893
|
+
className: (0, import_design_system22.cn)("max-w-[14rem] gap-1 text-[11px]", onPreview && "cursor-pointer"),
|
|
6894
|
+
"data-records-file-chip": id,
|
|
6895
|
+
title,
|
|
6896
|
+
...onPreview ? {
|
|
6896
6897
|
onClick: (event) => {
|
|
6897
6898
|
event.stopPropagation();
|
|
6898
6899
|
onPreview();
|
|
6899
6900
|
},
|
|
6900
6901
|
onMouseDown: (event) => event.stopPropagation(),
|
|
6901
|
-
onDoubleClick: (event) => event.stopPropagation()
|
|
6902
|
-
|
|
6903
|
-
|
|
6904
|
-
|
|
6905
|
-
|
|
6906
|
-
|
|
6907
|
-
|
|
6908
|
-
|
|
6909
|
-
|
|
6910
|
-
|
|
6911
|
-
|
|
6912
|
-
|
|
6913
|
-
|
|
6914
|
-
|
|
6915
|
-
|
|
6916
|
-
|
|
6917
|
-
|
|
6918
|
-
|
|
6919
|
-
|
|
6920
|
-
|
|
6921
|
-
|
|
6922
|
-
|
|
6923
|
-
|
|
6924
|
-
|
|
6925
|
-
|
|
6926
|
-
|
|
6927
|
-
|
|
6928
|
-
|
|
6902
|
+
onDoubleClick: (event) => event.stopPropagation()
|
|
6903
|
+
} : {},
|
|
6904
|
+
children: [
|
|
6905
|
+
onPreview ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
|
|
6906
|
+
"button",
|
|
6907
|
+
{
|
|
6908
|
+
type: "button",
|
|
6909
|
+
className: "truncate underline-offset-2 hover:underline",
|
|
6910
|
+
"data-records-file-preview": id,
|
|
6911
|
+
onClick: (event) => {
|
|
6912
|
+
event.stopPropagation();
|
|
6913
|
+
onPreview();
|
|
6914
|
+
},
|
|
6915
|
+
onMouseDown: (event) => event.stopPropagation(),
|
|
6916
|
+
onDoubleClick: (event) => event.stopPropagation(),
|
|
6917
|
+
children: words2
|
|
6918
|
+
}
|
|
6919
|
+
) : href ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
|
|
6920
|
+
import_outbound_react4.OutboundLink,
|
|
6921
|
+
{
|
|
6922
|
+
href,
|
|
6923
|
+
className: "truncate underline-offset-2 hover:underline",
|
|
6924
|
+
"data-records-file-link": read?.fileId ?? "",
|
|
6925
|
+
onClick: (event) => event.stopPropagation(),
|
|
6926
|
+
onMouseDown: (event) => event.stopPropagation(),
|
|
6927
|
+
children: words2
|
|
6928
|
+
}
|
|
6929
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "truncate", children: words2 }),
|
|
6930
|
+
onRemove ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
|
|
6931
|
+
"button",
|
|
6932
|
+
{
|
|
6933
|
+
type: "button",
|
|
6934
|
+
"aria-label": `Remove ${words2}`,
|
|
6935
|
+
className: "shrink-0 rounded px-0.5 text-muted-foreground hover:text-foreground",
|
|
6936
|
+
onClick: (event) => {
|
|
6937
|
+
event.stopPropagation();
|
|
6938
|
+
onRemove();
|
|
6939
|
+
},
|
|
6940
|
+
children: "\xD7"
|
|
6941
|
+
}
|
|
6942
|
+
) : null
|
|
6943
|
+
]
|
|
6944
|
+
}
|
|
6945
|
+
);
|
|
6929
6946
|
}
|
|
6930
6947
|
function AttachmentChips({ value, onRemove }) {
|
|
6931
6948
|
const host = useRecordsUi();
|
|
@@ -6966,6 +6983,18 @@ function AttachmentViewer({
|
|
|
6966
6983
|
{
|
|
6967
6984
|
className: "sm:max-w-3xl",
|
|
6968
6985
|
"data-records-attachment-viewer": id,
|
|
6986
|
+
tabIndex: -1,
|
|
6987
|
+
onOpenAutoFocus: (event) => {
|
|
6988
|
+
event.preventDefault();
|
|
6989
|
+
event.currentTarget.focus();
|
|
6990
|
+
},
|
|
6991
|
+
onCloseAutoFocus: (event) => {
|
|
6992
|
+
const chip = document.querySelector(`[data-records-file-preview="${ids[index]}"]`);
|
|
6993
|
+
if (chip) {
|
|
6994
|
+
event.preventDefault();
|
|
6995
|
+
chip.focus();
|
|
6996
|
+
}
|
|
6997
|
+
},
|
|
6969
6998
|
onKeyDown: (event) => {
|
|
6970
6999
|
if (event.key === "ArrowLeft") {
|
|
6971
7000
|
event.preventDefault();
|
|
@@ -7977,6 +8006,33 @@ var CONDITION_OPS = [
|
|
|
7977
8006
|
{ op: "gte", label: "is at least" },
|
|
7978
8007
|
{ op: "lte", label: "is at most" }
|
|
7979
8008
|
];
|
|
8009
|
+
var LIST_TERMS = [
|
|
8010
|
+
{ op: "contains", label: "contains" },
|
|
8011
|
+
{ op: "not_contains", label: "does not contain" },
|
|
8012
|
+
{ op: "is_empty", label: "is empty" },
|
|
8013
|
+
{ op: "is_not_empty", label: "is not empty" }
|
|
8014
|
+
];
|
|
8015
|
+
var VALUELESS_OPS = /* @__PURE__ */ new Set(["present", "is_empty", "is_not_empty"]);
|
|
8016
|
+
var LINK_SEP = ">";
|
|
8017
|
+
function linkedNode(viaId, innerId, op2, value) {
|
|
8018
|
+
return {
|
|
8019
|
+
op: "linked",
|
|
8020
|
+
match: op2 === "eq" ? "any" : "none",
|
|
8021
|
+
args: [{ field: viaId }, { op: "eq", args: [{ field: innerId }, { const: value }] }]
|
|
8022
|
+
};
|
|
8023
|
+
}
|
|
8024
|
+
function linkedOf(expr) {
|
|
8025
|
+
const node = asNode(expr);
|
|
8026
|
+
if (!node || node["op"] !== "linked") return null;
|
|
8027
|
+
const [rel, cond] = argsOf(node);
|
|
8028
|
+
const innerNode = asNode(cond);
|
|
8029
|
+
const [a, b] = innerNode ? argsOf(innerNode) : [];
|
|
8030
|
+
const match = node["match"] ?? "any";
|
|
8031
|
+
if (!rel || typeof rel["field"] !== "string" || !innerNode || innerNode["op"] !== "eq") return null;
|
|
8032
|
+
if (!a || typeof a["field"] !== "string" || !b || !("const" in b)) return null;
|
|
8033
|
+
if (match !== "any" && match !== "none") return null;
|
|
8034
|
+
return { via: rel["field"], inner: a["field"], op: match === "any" ? "eq" : "ne", value: b["const"] };
|
|
8035
|
+
}
|
|
7980
8036
|
var FilterTermsContext = (0, import_react46.createContext)(false);
|
|
7981
8037
|
var RELATIVE_DAY_TERMS = [
|
|
7982
8038
|
{ kind: "today", label: "is today" },
|
|
@@ -8089,13 +8145,15 @@ function ChoiceValue({ field: field2, value, onChange }) {
|
|
|
8089
8145
|
}
|
|
8090
8146
|
function conditionIsSimple(expr) {
|
|
8091
8147
|
if (expr === null || expr === void 0) return true;
|
|
8092
|
-
if (relativeDayOf(expr) !== null || meOf(expr) !== null) return true;
|
|
8148
|
+
if (relativeDayOf(expr) !== null || meOf(expr) !== null || linkedOf(expr) !== null) return true;
|
|
8093
8149
|
const node = expr;
|
|
8094
|
-
if (typeof node.op !== "string" || !CONDITION_OPS.some((c) => c.op === node.op))
|
|
8150
|
+
if (typeof node.op !== "string" || !(CONDITION_OPS.some((c) => c.op === node.op) || LIST_TERMS.some((c) => c.op === node.op))) {
|
|
8151
|
+
return false;
|
|
8152
|
+
}
|
|
8095
8153
|
if (!Array.isArray(node.args)) return false;
|
|
8096
8154
|
const first = node.args[0];
|
|
8097
8155
|
if (!first || typeof first.field !== "string") return false;
|
|
8098
|
-
if (node.op
|
|
8156
|
+
if (VALUELESS_OPS.has(node.op)) return node.args.length === 1;
|
|
8099
8157
|
const second = node.args[1];
|
|
8100
8158
|
return node.args.length === 2 && !!second && "const" in second;
|
|
8101
8159
|
}
|
|
@@ -8113,7 +8171,13 @@ function conditionInWords(expr, fields) {
|
|
|
8113
8171
|
lte: "is at most",
|
|
8114
8172
|
and: "and",
|
|
8115
8173
|
or: "or",
|
|
8116
|
-
matches: "matches"
|
|
8174
|
+
matches: "matches",
|
|
8175
|
+
contains: "contains",
|
|
8176
|
+
not_contains: "does not contain",
|
|
8177
|
+
has_any: "has any of",
|
|
8178
|
+
has_all: "has all of",
|
|
8179
|
+
has_none: "has none of",
|
|
8180
|
+
is_exactly: "is exactly"
|
|
8117
8181
|
};
|
|
8118
8182
|
const walk = (node) => {
|
|
8119
8183
|
if (node === null || node === void 0) return "nothing";
|
|
@@ -8134,6 +8198,12 @@ function conditionInWords(expr, fields) {
|
|
|
8134
8198
|
const args = Array.isArray(n["args"]) ? n["args"] : [];
|
|
8135
8199
|
if (op2 === "not") return `not ${walk(args[0])}`;
|
|
8136
8200
|
if (op2 === "present") return `${walk(args[0])} has been answered`;
|
|
8201
|
+
if (op2 === "is_empty") return `${walk(args[0])} is empty`;
|
|
8202
|
+
if (op2 === "is_not_empty") return `${walk(args[0])} is not empty`;
|
|
8203
|
+
if (op2 === "linked") {
|
|
8204
|
+
const how = n["match"] === "every" ? "every" : n["match"] === "none" ? "none of" : "any";
|
|
8205
|
+
return `${how} linked record in ${walk(args[0])}: ${walk(args[1])}`;
|
|
8206
|
+
}
|
|
8137
8207
|
const dayRange = relativeDayOf(n);
|
|
8138
8208
|
if (dayRange) {
|
|
8139
8209
|
const term = RELATIVE_DAY_TERMS.find((t) => t.kind === dayRange.kind);
|
|
@@ -8166,12 +8236,14 @@ function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", cla
|
|
|
8166
8236
|
const filterTerms = (0, import_react46.useContext)(FilterTermsContext);
|
|
8167
8237
|
const relative = relativeDayOf(expr);
|
|
8168
8238
|
const reader = meOf(expr);
|
|
8169
|
-
const
|
|
8170
|
-
const
|
|
8171
|
-
const
|
|
8239
|
+
const linked = linkedOf(expr);
|
|
8240
|
+
const op2 = relative ? `rel:${relative.kind}` : reader ? reader.which : linked ? linked.op : node?.op ?? "";
|
|
8241
|
+
const onField = linked ? `${linked.via}${LINK_SEP}${linked.inner}` : node?.args?.[0]?.field ?? "";
|
|
8242
|
+
const value = relative || reader ? "" : linked ? linked.value : node?.args?.[1]?.const ?? "";
|
|
8172
8243
|
const chosen = fields.find((f) => f.id === onField);
|
|
8173
8244
|
const chosenKind = chosen?.field ? String(editorKindFor(chosen.field)) : "";
|
|
8174
|
-
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" }] : [];
|
|
8245
|
+
const extraTerms = !filterTerms || chosen?.via ? [] : 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" }] : chosenKind === "multi_select" ? LIST_TERMS : [];
|
|
8246
|
+
const baseTerms = chosen?.via ? CONDITION_OPS.filter((c) => c.op === "eq" || c.op === "ne") : CONDITION_OPS;
|
|
8175
8247
|
const relativeTerm = relative ? RELATIVE_DAY_TERMS.find((t) => t.kind === relative.kind) : void 0;
|
|
8176
8248
|
function write(next) {
|
|
8177
8249
|
const picked = next.op;
|
|
@@ -8184,8 +8256,11 @@ function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", cla
|
|
|
8184
8256
|
onChange(relativeDayNode(fieldNow, relative.kind, next.days));
|
|
8185
8257
|
return;
|
|
8186
8258
|
}
|
|
8187
|
-
if (next.field !== void 0 && (relative || reader)) {
|
|
8188
|
-
|
|
8259
|
+
if (next.field !== void 0 && (relative || reader || linked)) {
|
|
8260
|
+
const target2 = fields.find((f) => f.id === next.field);
|
|
8261
|
+
onChange(
|
|
8262
|
+
!next.field ? null : target2?.via ? linkedNode(target2.via.id, target2.via.fieldId, "eq", "") : { op: CONDITION_OPS[0].op, args: [{ field: next.field }, { const: "" }] }
|
|
8263
|
+
);
|
|
8189
8264
|
return;
|
|
8190
8265
|
}
|
|
8191
8266
|
const chosenOp = next.op ?? (op2 || (next.field ? CONDITION_OPS[0].op : ""));
|
|
@@ -8195,8 +8270,13 @@ function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", cla
|
|
|
8195
8270
|
onChange(null);
|
|
8196
8271
|
return;
|
|
8197
8272
|
}
|
|
8273
|
+
const target = fields.find((f) => f.id === chosenField);
|
|
8274
|
+
if (target?.via) {
|
|
8275
|
+
onChange(linkedNode(target.via.id, target.via.fieldId, chosenOp === "ne" ? "ne" : "eq", chosenValue));
|
|
8276
|
+
return;
|
|
8277
|
+
}
|
|
8198
8278
|
onChange(
|
|
8199
|
-
chosenOp
|
|
8279
|
+
VALUELESS_OPS.has(chosenOp) ? { op: chosenOp, args: [{ field: chosenField }] } : { op: chosenOp, args: [{ field: chosenField }, { const: chosenValue }] }
|
|
8200
8280
|
);
|
|
8201
8281
|
}
|
|
8202
8282
|
return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: className ?? "flex flex-wrap items-center gap-1 text-xs", children: [
|
|
@@ -8229,7 +8309,7 @@ function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", cla
|
|
|
8229
8309
|
className: "h-8 w-auto min-w-[6rem] text-xs",
|
|
8230
8310
|
value: op2,
|
|
8231
8311
|
onChange: (next) => write({ op: next }),
|
|
8232
|
-
options: [...
|
|
8312
|
+
options: [...baseTerms, ...extraTerms].map((c) => ({ value: c.op, label: c.label })),
|
|
8233
8313
|
attrs: { "data-condition": "comparison" }
|
|
8234
8314
|
}
|
|
8235
8315
|
),
|
|
@@ -8243,7 +8323,7 @@ function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", cla
|
|
|
8243
8323
|
value: String(relative.days ?? 7),
|
|
8244
8324
|
onChange: (e) => write({ days: Math.max(0, Math.floor(Number(e.target.value) || 0)) })
|
|
8245
8325
|
}
|
|
8246
|
-
) : null : reader || op2
|
|
8326
|
+
) : null : reader || VALUELESS_OPS.has(op2) ? null : pointsAtARecord(chosen) ? (
|
|
8247
8327
|
// A RECORD IS PICKED BY ITS WORDS, NEVER TYPED AS AN ID. The same picker the
|
|
8248
8328
|
// cell uses, on the same `relation_words_many` the cell reads, so a filter and
|
|
8249
8329
|
// the column it filters can never disagree about what a record is called. It
|
|
@@ -8261,7 +8341,7 @@ function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", cla
|
|
|
8261
8341
|
allowCreate: false
|
|
8262
8342
|
}
|
|
8263
8343
|
)
|
|
8264
|
-
) : isChoiceColumn(chosen) && (op2 === "eq" || op2 === "ne") ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(ChoiceValue, { field: chosen.field, value: String(value ?? ""), onChange: (word) => write({ value: word }) }) : /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
|
|
8344
|
+
) : isChoiceColumn(chosen) && (op2 === "eq" || op2 === "ne" || op2 === "contains" || op2 === "not_contains") ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(ChoiceValue, { field: chosen.field, value: String(value ?? ""), onChange: (word) => write({ value: word }) }) : /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
|
|
8265
8345
|
import_design_system28.BasicInput,
|
|
8266
8346
|
{
|
|
8267
8347
|
className: "h-8 w-28 text-xs",
|
|
@@ -20700,6 +20780,8 @@ function columnForField(field2, editing, mayWriteRow, whyNotRow, look, onAskWhoC
|
|
|
20700
20780
|
] }) : name
|
|
20701
20781
|
),
|
|
20702
20782
|
label: name,
|
|
20783
|
+
// The field's own description, one hover away on every header (not only the type mark's glyph).
|
|
20784
|
+
...fieldHelpText(field2) ? { helpText: fieldHelpText(field2) } : {},
|
|
20703
20785
|
accessorFn: (row) => row.document?.[field2.key],
|
|
20704
20786
|
// A column the store keeps no value for is not offered a sort it would refuse (`sortRefusal`).
|
|
20705
20787
|
sortable: sortRefusal(field2) === null,
|
|
@@ -21853,9 +21935,9 @@ var import_records43 = require("@ai-matrx/records");
|
|
|
21853
21935
|
|
|
21854
21936
|
// src/importRuleCheck.ts
|
|
21855
21937
|
var import_core13 = require("@ai-matrx/records/core");
|
|
21856
|
-
function
|
|
21938
|
+
function refusedCellsOf(fields, rows, mapping, choiceOptions, relationNames) {
|
|
21857
21939
|
const byKey = new Map(fields.map((f) => [f.key, f]));
|
|
21858
|
-
const
|
|
21940
|
+
const cells = [];
|
|
21859
21941
|
for (const [header, key] of Object.entries(mapping)) {
|
|
21860
21942
|
const field2 = byKey.get(key);
|
|
21861
21943
|
if (!field2) continue;
|
|
@@ -21865,9 +21947,8 @@ function checkImportRules(fields, rows, mapping, choiceOptions, relationNames) {
|
|
|
21865
21947
|
if (isChoice && !options) continue;
|
|
21866
21948
|
const named = relationNames?.get(key);
|
|
21867
21949
|
if (kind2 === "relation" && !named) continue;
|
|
21868
|
-
let refused = 0;
|
|
21869
21950
|
let judged = 0;
|
|
21870
|
-
|
|
21951
|
+
const mine = [];
|
|
21871
21952
|
rows.forEach((row, i) => {
|
|
21872
21953
|
const raw = String(row[header] ?? "").trim();
|
|
21873
21954
|
if (raw === "") return;
|
|
@@ -21883,17 +21964,20 @@ function checkImportRules(fields, rows, mapping, choiceOptions, relationNames) {
|
|
|
21883
21964
|
);
|
|
21884
21965
|
if (predicted.length > 0) why = predicted[0].message;
|
|
21885
21966
|
}
|
|
21886
|
-
if (why !== null) {
|
|
21887
|
-
refused += 1;
|
|
21888
|
-
if (!first) first = { why, line: i + 1, raw };
|
|
21889
|
-
}
|
|
21967
|
+
if (why !== null) mine.push({ header, line: i + 1, raw, why });
|
|
21890
21968
|
});
|
|
21891
|
-
|
|
21892
|
-
|
|
21893
|
-
|
|
21894
|
-
|
|
21969
|
+
for (const c of mine) cells.push({ ...c, judged, field: field2 });
|
|
21970
|
+
}
|
|
21971
|
+
return cells;
|
|
21972
|
+
}
|
|
21973
|
+
function checkImportRules(fields, rows, mapping, choiceOptions, relationNames) {
|
|
21974
|
+
const verdicts = /* @__PURE__ */ new Map();
|
|
21975
|
+
for (const c of refusedCellsOf(fields, rows, mapping, choiceOptions, relationNames)) {
|
|
21976
|
+
const have = verdicts.get(c.header);
|
|
21977
|
+
if (have) have.refused += 1;
|
|
21978
|
+
else verdicts.set(c.header, { header: c.header, field: c.field, refused: 1, judged: c.judged, firstWhy: c.why, firstLine: c.line, firstRaw: c.raw });
|
|
21895
21979
|
}
|
|
21896
|
-
return verdicts;
|
|
21980
|
+
return [...verdicts.values()];
|
|
21897
21981
|
}
|
|
21898
21982
|
|
|
21899
21983
|
// src/ImportWizard.tsx
|
|
@@ -22305,7 +22389,7 @@ function ImportWizard({ tableId, source, onDone, onWrote, autoRun, className })
|
|
|
22305
22389
|
},
|
|
22306
22390
|
[client, openRun]
|
|
22307
22391
|
);
|
|
22308
|
-
const declared2 = fields.data ?? [];
|
|
22392
|
+
const declared2 = (0, import_react122.useMemo)(() => fields.data ?? [], [fields.data]);
|
|
22309
22393
|
const titleKey = table.data?.title_field ?? null;
|
|
22310
22394
|
const titleHeader = titleFrom ?? (titleKey ? Object.entries(mapping).find(([h, key]) => key === titleKey && h !== TITLE_COPY)?.[0] ?? null : null);
|
|
22311
22395
|
const distinctRows = (0, import_react122.useMemo)(() => {
|
|
@@ -22599,6 +22683,12 @@ function ImportWizard({ tableId, source, onDone, onWrote, autoRun, className })
|
|
|
22599
22683
|
() => exactDuplicateRows(effectiveRows, (plan?.columns ?? []).map((c) => c.header).filter((h) => !leftOut.has(h))),
|
|
22600
22684
|
[effectiveRows, plan, leftOut]
|
|
22601
22685
|
);
|
|
22686
|
+
const [goAheadAnyway, setGoAheadAnyway] = (0, import_react122.useState)(false);
|
|
22687
|
+
const refusedCells = (0, import_react122.useMemo)(
|
|
22688
|
+
() => goAheadAnyway ? refusedCellsOf(declared2, effectiveRows, mapping, choiceIndex, relationIndex) : [],
|
|
22689
|
+
[goAheadAnyway, declared2, effectiveRows, mapping, choiceIndex, relationIndex]
|
|
22690
|
+
);
|
|
22691
|
+
const [keptAside, setKeptAside] = (0, import_react122.useState)([]);
|
|
22602
22692
|
const rowsToSend = (0, import_react122.useCallback)(
|
|
22603
22693
|
(index) => {
|
|
22604
22694
|
const repeated = new Set(skipRepeats ? repeats.map((r) => r.row) : []);
|
|
@@ -22608,6 +22698,7 @@ function ImportWizard({ tableId, source, onDone, onWrote, autoRun, className })
|
|
|
22608
22698
|
if (repeated.has(at)) return;
|
|
22609
22699
|
const out = {};
|
|
22610
22700
|
for (const [header, value] of Object.entries(row)) if (!leftOut.has(header)) out[header] = value;
|
|
22701
|
+
for (const c of refusedCells) if (c.line === at + 1 && c.header in out) out[c.header] = "";
|
|
22611
22702
|
if (titleFrom) out[TITLE_COPY] = String(row[titleFrom] ?? "").trim();
|
|
22612
22703
|
for (const { header, field: field2 } of mappedRelations) {
|
|
22613
22704
|
const named = index.get(field2.key);
|
|
@@ -22637,7 +22728,7 @@ function ImportWizard({ tableId, source, onDone, onWrote, autoRun, className })
|
|
|
22637
22728
|
return { rows: sent, lines };
|
|
22638
22729
|
},
|
|
22639
22730
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- choiceFor reads unlinkedChoice
|
|
22640
|
-
[effectiveRows, combined, titleFrom, leftOut, mappedRelations, unlinkedChoice, dateAsks, dateMiss, repeats, skipRepeats]
|
|
22731
|
+
[effectiveRows, combined, titleFrom, leftOut, mappedRelations, unlinkedChoice, dateAsks, dateMiss, repeats, skipRepeats, refusedCells]
|
|
22641
22732
|
);
|
|
22642
22733
|
const run = (0, import_react122.useCallback)(async () => {
|
|
22643
22734
|
if (!plan) return;
|
|
@@ -22705,6 +22796,7 @@ function ImportWizard({ tableId, source, onDone, onWrote, autoRun, className })
|
|
|
22705
22796
|
index = reread;
|
|
22706
22797
|
}
|
|
22707
22798
|
const { rows: sendRows, lines: sendLines } = rowsToSend(index);
|
|
22799
|
+
setKeptAside(refusedCells.map(({ header, line, raw, why }) => ({ header, line, raw, why })));
|
|
22708
22800
|
setSentLines(sendLines);
|
|
22709
22801
|
setRepeatsSkipped(effectiveRows.length - sendRows.length);
|
|
22710
22802
|
let sendMapping = mapping;
|
|
@@ -22838,7 +22930,7 @@ function ImportWizard({ tableId, source, onDone, onWrote, autoRun, className })
|
|
|
22838
22930
|
setPhase("done");
|
|
22839
22931
|
onWrote?.(finished.data.rows_written);
|
|
22840
22932
|
void loadRuns();
|
|
22841
|
-
}, [newColumns, makesColumns, kindChoice, guessedKind, addChoiceWords, client, dedupeKey, effectiveRows, fileBytes, fileHash, fileName, format, leftOut, loadRuns, mappedRelations, mapping, newChoiceWords, nudge, onDuplicate, onWrote, plan, readRelationNames, relationAsks, relationIndex, rights.admin, rowsToSend, tableId, unmapped, unlinkedChoice]);
|
|
22933
|
+
}, [newColumns, makesColumns, kindChoice, guessedKind, addChoiceWords, client, dedupeKey, effectiveRows, fileBytes, fileHash, fileName, format, leftOut, loadRuns, mappedRelations, mapping, newChoiceWords, nudge, onDuplicate, onWrote, plan, readRelationNames, refusedCells, relationAsks, relationIndex, rights.admin, rowsToSend, tableId, unmapped, unlinkedChoice]);
|
|
22842
22934
|
const counts = (0, import_react122.useMemo)(() => {
|
|
22843
22935
|
let landed = 0;
|
|
22844
22936
|
let duplicate = 0;
|
|
@@ -22867,8 +22959,8 @@ function ImportWizard({ tableId, source, onDone, onWrote, autoRun, className })
|
|
|
22867
22959
|
);
|
|
22868
22960
|
const ruleVerdictOf = (0, import_react122.useMemo)(() => new Map(ruleVerdicts.map((v) => [v.header, v])), [ruleVerdicts]);
|
|
22869
22961
|
const refusedByRules = ruleVerdicts.reduce((n, v) => n + v.refused, 0);
|
|
22870
|
-
const
|
|
22871
|
-
(0, import_react122.useEffect)(() => setGoAheadAnyway(false), [
|
|
22962
|
+
const verdictKey = ruleVerdicts.map((v) => `${v.header}:${v.refused}/${v.judged}`).join("|");
|
|
22963
|
+
(0, import_react122.useEffect)(() => setGoAheadAnyway(false), [verdictKey]);
|
|
22872
22964
|
const [autoRan, setAutoRan] = (0, import_react122.useState)(false);
|
|
22873
22965
|
(0, import_react122.useEffect)(() => {
|
|
22874
22966
|
if (!autoRun || autoRan || !source || phase !== "ready" || !plan) return;
|
|
@@ -23458,6 +23550,23 @@ function ImportWizard({ tableId, source, onDone, onWrote, autoRun, className })
|
|
|
23458
23550
|
] })
|
|
23459
23551
|
}
|
|
23460
23552
|
) }) : null,
|
|
23553
|
+
afterWrite && keptAside.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime77.jsxs)("div", { "data-import-kept-aside": "", className: "flex flex-col gap-0.5", children: [
|
|
23554
|
+
/* @__PURE__ */ (0, import_jsx_runtime77.jsxs)("p", { children: [
|
|
23555
|
+
keptAside.length,
|
|
23556
|
+
" cell",
|
|
23557
|
+
keptAside.length === 1 ? " was" : "s were",
|
|
23558
|
+
" kept aside and left empty; every other value in those rows was written."
|
|
23559
|
+
] }),
|
|
23560
|
+
/* @__PURE__ */ (0, import_jsx_runtime77.jsx)("ul", { className: "flex flex-col gap-0.5 text-muted-foreground", children: keptAside.map((c) => /* @__PURE__ */ (0, import_jsx_runtime77.jsxs)("li", { children: [
|
|
23561
|
+
c.header,
|
|
23562
|
+
", ",
|
|
23563
|
+
source ? `row ${c.line}` : `line ${lineInYourFile(c.line)}`,
|
|
23564
|
+
": ",
|
|
23565
|
+
c.raw,
|
|
23566
|
+
" \u2014 ",
|
|
23567
|
+
c.why
|
|
23568
|
+
] }, `${c.header}:${c.line}`)) })
|
|
23569
|
+
] }) : null,
|
|
23461
23570
|
repeatsSkipped > 0 ? /* @__PURE__ */ (0, import_jsx_runtime77.jsxs)("p", { className: "text-muted-foreground", "data-import-repeats-skipped": "", children: [
|
|
23462
23571
|
repeatsSkipped,
|
|
23463
23572
|
" repeated row",
|
|
@@ -39885,6 +39994,9 @@ function settingsToCopy(source, target) {
|
|
|
39885
39994
|
const same3 = source.layout === target.layout;
|
|
39886
39995
|
return {
|
|
39887
39996
|
where: source.where ?? null,
|
|
39997
|
+
// The flat filter map a view may carry instead of (or beside) `where`: every stored filter shape travels.
|
|
39998
|
+
filters: source.filters ?? null,
|
|
39999
|
+
// saveViewPatch sends null to the door, which clears it
|
|
39888
40000
|
sorts: source.sorts ?? [],
|
|
39889
40001
|
groupField: source.groupField ?? null,
|
|
39890
40002
|
presentation: look,
|
|
@@ -39919,7 +40031,7 @@ function SortableRow({ id, disabled, children }) {
|
|
|
39919
40031
|
}
|
|
39920
40032
|
);
|
|
39921
40033
|
}
|
|
39922
|
-
function ViewBar({ tableId, activeViewId, onActiveView, seed, settings, onViews, initialViews, className }) {
|
|
40034
|
+
function ViewBar({ tableId, activeViewId, onActiveView, seed, settings, onViews, onSettingsCopied, initialViews, className }) {
|
|
39923
40035
|
const client = (0, import_react233.useRecordsClient)();
|
|
39924
40036
|
const claimSeed = useSeedGuard();
|
|
39925
40037
|
const table = (0, import_react234.useTable)(tableId);
|
|
@@ -40092,6 +40204,7 @@ function ViewBar({ tableId, activeViewId, onActiveView, seed, settings, onViews,
|
|
|
40092
40204
|
return;
|
|
40093
40205
|
}
|
|
40094
40206
|
setCopying(false);
|
|
40207
|
+
onSettingsCopied?.(into.id);
|
|
40095
40208
|
await load();
|
|
40096
40209
|
notify?.success(`Copied settings from \u201C${source.name}\u201D`);
|
|
40097
40210
|
}
|
|
@@ -53095,10 +53208,39 @@ function ViewFilter({ tableId, view, onApply, className }) {
|
|
|
53095
53208
|
(0, import_react310.useEffect)(() => {
|
|
53096
53209
|
setDraft(saved);
|
|
53097
53210
|
}, [view?.id, savedKey]);
|
|
53098
|
-
const
|
|
53099
|
-
|
|
53211
|
+
const client = (0, import_react311.useOptionalRecordsClient)();
|
|
53212
|
+
const relations = (0, import_react310.useMemo)(
|
|
53213
|
+
() => (fields.data ?? []).filter((f) => f.type === "relation" && !!f.relation_target),
|
|
53100
53214
|
[fields.data]
|
|
53101
53215
|
);
|
|
53216
|
+
const [linkedFields, setLinkedFields] = (0, import_react310.useState)({});
|
|
53217
|
+
(0, import_react310.useEffect)(() => {
|
|
53218
|
+
if (!open || !client) return;
|
|
53219
|
+
let cancelled = false;
|
|
53220
|
+
for (const rel of relations) {
|
|
53221
|
+
const target = String(rel.relation_target);
|
|
53222
|
+
void client.fields({ table_id: target }).then((got) => {
|
|
53223
|
+
if (cancelled || !got.ok) return;
|
|
53224
|
+
setLinkedFields((now) => ({ ...now, [rel.id]: got.data ?? [] }));
|
|
53225
|
+
});
|
|
53226
|
+
}
|
|
53227
|
+
return () => {
|
|
53228
|
+
cancelled = true;
|
|
53229
|
+
};
|
|
53230
|
+
}, [open, client, relations]);
|
|
53231
|
+
const conditionFields = (0, import_react310.useMemo)(() => {
|
|
53232
|
+
const own3 = (fields.data ?? []).map((f) => ({ id: f.id, key: f.key, label: f.label || f.key, field: f }));
|
|
53233
|
+
const reached = relations.flatMap(
|
|
53234
|
+
(rel) => (linkedFields[rel.id] ?? []).filter((inner2) => inner2.type !== "relation").map((inner2) => ({
|
|
53235
|
+
id: `${rel.id}>${inner2.id}`,
|
|
53236
|
+
key: `${rel.key}.${inner2.key}`,
|
|
53237
|
+
label: `${rel.label || rel.key} \u203A ${inner2.label || inner2.key}`,
|
|
53238
|
+
field: inner2,
|
|
53239
|
+
via: { id: rel.id, fieldId: inner2.id }
|
|
53240
|
+
}))
|
|
53241
|
+
);
|
|
53242
|
+
return [...own3, ...reached];
|
|
53243
|
+
}, [fields.data, relations, linkedFields]);
|
|
53102
53244
|
if (!view) return null;
|
|
53103
53245
|
const count = conditionCount(saved);
|
|
53104
53246
|
const changed2 = JSON.stringify(draft) !== savedKey;
|
|
@@ -54534,6 +54676,14 @@ function TablePage({
|
|
|
54534
54676
|
}
|
|
54535
54677
|
},
|
|
54536
54678
|
onViews: setViews,
|
|
54679
|
+
onSettingsCopied: (copiedId) => {
|
|
54680
|
+
setAddressResetEpoch((n) => n + 1);
|
|
54681
|
+
setLooks((all) => {
|
|
54682
|
+
const { [copiedId]: _gone, ...rest } = all ?? {};
|
|
54683
|
+
return rest;
|
|
54684
|
+
});
|
|
54685
|
+
void keepLook(client, tableId, copiedId, null);
|
|
54686
|
+
},
|
|
54537
54687
|
...seededViews && viewsEpoch === 0 ? { initialViews: seededViews } : {},
|
|
54538
54688
|
seed: seedViews ?? [defaultView(tableId)],
|
|
54539
54689
|
settings: view && main === "records" && shownLayout === "grid" ? /* @__PURE__ */ (0, import_jsx_runtime178.jsxs)("div", { className: "flex flex-col gap-0.5 px-1 py-1", role: "radiogroup", "aria-label": FOOTER_WORDS.label, children: [
|