@rakun-kit/manager-react 1.4.3 → 1.4.4
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/README.md +196 -196
- package/dist/cjs/components/editor/themes/editor-theme.css +91 -91
- package/dist/cjs/components/ui/sortable.js +4 -4
- package/dist/cjs/router/dashboard/[contentType]/[edit]/_fields/DynamicDataControl.js +217 -24
- package/dist/cjs/router/dashboard/[contentType]/[edit]/_fields/DynamicDataControl.js.map +1 -1
- package/dist/cjs/state/theme.js +12 -12
- package/dist/esm/components/editor/themes/editor-theme.css +91 -91
- package/dist/esm/components/ui/sortable.js +4 -4
- package/dist/esm/router/dashboard/[contentType]/[edit]/_fields/DynamicDataControl.d.ts.map +1 -1
- package/dist/esm/router/dashboard/[contentType]/[edit]/_fields/DynamicDataControl.js +219 -29
- package/dist/esm/router/dashboard/[contentType]/[edit]/_fields/DynamicDataControl.js.map +1 -1
- package/dist/esm/state/theme.js +12 -12
- package/dist/styles.css +51 -12
- package/dist/styles.css.d.ts +3 -3
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +192 -192
|
@@ -1,91 +1,91 @@
|
|
|
1
|
-
.EditorTheme__code {
|
|
2
|
-
background-color: transparent;
|
|
3
|
-
font-family: Menlo, Consolas, Monaco, monospace;
|
|
4
|
-
display: block;
|
|
5
|
-
padding: 8px 8px 8px 52px;
|
|
6
|
-
line-height: 1.53;
|
|
7
|
-
font-size: 13px;
|
|
8
|
-
margin: 0;
|
|
9
|
-
margin-top: 8px;
|
|
10
|
-
margin-bottom: 8px;
|
|
11
|
-
overflow-x: auto;
|
|
12
|
-
border: 1px solid #ccc;
|
|
13
|
-
position: relative;
|
|
14
|
-
border-radius: 8px;
|
|
15
|
-
tab-size: 2;
|
|
16
|
-
}
|
|
17
|
-
.EditorTheme__code:before {
|
|
18
|
-
content: attr(data-gutter);
|
|
19
|
-
position: absolute;
|
|
20
|
-
background-color: transparent;
|
|
21
|
-
border-right: 1px solid #ccc;
|
|
22
|
-
left: 0;
|
|
23
|
-
top: 0;
|
|
24
|
-
padding: 8px;
|
|
25
|
-
color: #777;
|
|
26
|
-
white-space: pre-wrap;
|
|
27
|
-
text-align: right;
|
|
28
|
-
min-width: 25px;
|
|
29
|
-
}
|
|
30
|
-
.EditorTheme__table {
|
|
31
|
-
border-collapse: collapse;
|
|
32
|
-
border-spacing: 0;
|
|
33
|
-
overflow-y: scroll;
|
|
34
|
-
overflow-x: scroll;
|
|
35
|
-
table-layout: fixed;
|
|
36
|
-
width: fit-content;
|
|
37
|
-
width: 100%;
|
|
38
|
-
margin: 0px 0px 30px 0px;
|
|
39
|
-
}
|
|
40
|
-
.EditorTheme__tokenComment {
|
|
41
|
-
color: slategray;
|
|
42
|
-
}
|
|
43
|
-
.EditorTheme__tokenPunctuation {
|
|
44
|
-
color: #999;
|
|
45
|
-
}
|
|
46
|
-
.EditorTheme__tokenProperty {
|
|
47
|
-
color: #905;
|
|
48
|
-
}
|
|
49
|
-
.EditorTheme__tokenSelector {
|
|
50
|
-
color: #690;
|
|
51
|
-
}
|
|
52
|
-
.EditorTheme__tokenOperator {
|
|
53
|
-
color: #9a6e3a;
|
|
54
|
-
}
|
|
55
|
-
.EditorTheme__tokenAttr {
|
|
56
|
-
color: #07a;
|
|
57
|
-
}
|
|
58
|
-
.EditorTheme__tokenVariable {
|
|
59
|
-
color: #e90;
|
|
60
|
-
}
|
|
61
|
-
.EditorTheme__tokenFunction {
|
|
62
|
-
color: #dd4a68;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
.Collapsible__container {
|
|
66
|
-
background-color: var(--background);
|
|
67
|
-
border: 1px solid #ccc;
|
|
68
|
-
border-radius: 0.5rem;
|
|
69
|
-
margin-bottom: 0.5rem;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
.Collapsible__title{
|
|
73
|
-
padding: 0.25rem;
|
|
74
|
-
padding-left: 1rem;
|
|
75
|
-
position: relative;
|
|
76
|
-
font-weight: bold;
|
|
77
|
-
outline: none;
|
|
78
|
-
cursor: pointer;
|
|
79
|
-
list-style-type: disclosure-closed;
|
|
80
|
-
list-style-position: inside;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
.Collapsible__title p{
|
|
84
|
-
display: inline-flex;
|
|
85
|
-
}
|
|
86
|
-
.Collapsible__title::marker{
|
|
87
|
-
color: lightgray;
|
|
88
|
-
}
|
|
89
|
-
.Collapsible__container[open] >.Collapsible__title {
|
|
90
|
-
list-style-type: disclosure-open;
|
|
91
|
-
}
|
|
1
|
+
.EditorTheme__code {
|
|
2
|
+
background-color: transparent;
|
|
3
|
+
font-family: Menlo, Consolas, Monaco, monospace;
|
|
4
|
+
display: block;
|
|
5
|
+
padding: 8px 8px 8px 52px;
|
|
6
|
+
line-height: 1.53;
|
|
7
|
+
font-size: 13px;
|
|
8
|
+
margin: 0;
|
|
9
|
+
margin-top: 8px;
|
|
10
|
+
margin-bottom: 8px;
|
|
11
|
+
overflow-x: auto;
|
|
12
|
+
border: 1px solid #ccc;
|
|
13
|
+
position: relative;
|
|
14
|
+
border-radius: 8px;
|
|
15
|
+
tab-size: 2;
|
|
16
|
+
}
|
|
17
|
+
.EditorTheme__code:before {
|
|
18
|
+
content: attr(data-gutter);
|
|
19
|
+
position: absolute;
|
|
20
|
+
background-color: transparent;
|
|
21
|
+
border-right: 1px solid #ccc;
|
|
22
|
+
left: 0;
|
|
23
|
+
top: 0;
|
|
24
|
+
padding: 8px;
|
|
25
|
+
color: #777;
|
|
26
|
+
white-space: pre-wrap;
|
|
27
|
+
text-align: right;
|
|
28
|
+
min-width: 25px;
|
|
29
|
+
}
|
|
30
|
+
.EditorTheme__table {
|
|
31
|
+
border-collapse: collapse;
|
|
32
|
+
border-spacing: 0;
|
|
33
|
+
overflow-y: scroll;
|
|
34
|
+
overflow-x: scroll;
|
|
35
|
+
table-layout: fixed;
|
|
36
|
+
width: fit-content;
|
|
37
|
+
width: 100%;
|
|
38
|
+
margin: 0px 0px 30px 0px;
|
|
39
|
+
}
|
|
40
|
+
.EditorTheme__tokenComment {
|
|
41
|
+
color: slategray;
|
|
42
|
+
}
|
|
43
|
+
.EditorTheme__tokenPunctuation {
|
|
44
|
+
color: #999;
|
|
45
|
+
}
|
|
46
|
+
.EditorTheme__tokenProperty {
|
|
47
|
+
color: #905;
|
|
48
|
+
}
|
|
49
|
+
.EditorTheme__tokenSelector {
|
|
50
|
+
color: #690;
|
|
51
|
+
}
|
|
52
|
+
.EditorTheme__tokenOperator {
|
|
53
|
+
color: #9a6e3a;
|
|
54
|
+
}
|
|
55
|
+
.EditorTheme__tokenAttr {
|
|
56
|
+
color: #07a;
|
|
57
|
+
}
|
|
58
|
+
.EditorTheme__tokenVariable {
|
|
59
|
+
color: #e90;
|
|
60
|
+
}
|
|
61
|
+
.EditorTheme__tokenFunction {
|
|
62
|
+
color: #dd4a68;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.Collapsible__container {
|
|
66
|
+
background-color: var(--background);
|
|
67
|
+
border: 1px solid #ccc;
|
|
68
|
+
border-radius: 0.5rem;
|
|
69
|
+
margin-bottom: 0.5rem;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.Collapsible__title{
|
|
73
|
+
padding: 0.25rem;
|
|
74
|
+
padding-left: 1rem;
|
|
75
|
+
position: relative;
|
|
76
|
+
font-weight: bold;
|
|
77
|
+
outline: none;
|
|
78
|
+
cursor: pointer;
|
|
79
|
+
list-style-type: disclosure-closed;
|
|
80
|
+
list-style-position: inside;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.Collapsible__title p{
|
|
84
|
+
display: inline-flex;
|
|
85
|
+
}
|
|
86
|
+
.Collapsible__title::marker{
|
|
87
|
+
color: lightgray;
|
|
88
|
+
}
|
|
89
|
+
.Collapsible__container[open] >.Collapsible__title {
|
|
90
|
+
list-style-type: disclosure-open;
|
|
91
|
+
}
|
|
@@ -127,10 +127,10 @@ function Sortable(props) {
|
|
|
127
127
|
},
|
|
128
128
|
}), [value]);
|
|
129
129
|
const screenReaderInstructions = React.useMemo(() => ({
|
|
130
|
-
draggable: `
|
|
131
|
-
To pick up a sortable item, press space or enter.
|
|
132
|
-
While dragging, use the ${orientation === "vertical" ? "up and down" : orientation === "horizontal" ? "left and right" : "arrow"} keys to move the item.
|
|
133
|
-
Press space or enter again to drop the item in its new position, or press escape to cancel.
|
|
130
|
+
draggable: `
|
|
131
|
+
To pick up a sortable item, press space or enter.
|
|
132
|
+
While dragging, use the ${orientation === "vertical" ? "up and down" : orientation === "horizontal" ? "left and right" : "arrow"} keys to move the item.
|
|
133
|
+
Press space or enter again to drop the item in its new position, or press escape to cancel.
|
|
134
134
|
`,
|
|
135
135
|
}), [orientation]);
|
|
136
136
|
const contextValue = React.useMemo(() => ({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DynamicDataControl.d.ts","sourceRoot":"","sources":["../../../../../../../src/router/dashboard/[contentType]/[edit]/_fields/DynamicDataControl.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAEV,uBAAuB,
|
|
1
|
+
{"version":3,"file":"DynamicDataControl.d.ts","sourceRoot":"","sources":["../../../../../../../src/router/dashboard/[contentType]/[edit]/_fields/DynamicDataControl.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAEV,uBAAuB,EAIvB,kBAAkB,EAClB,mBAAmB,EAIpB,MAAM,wBAAwB,CAAA;AAuG/B,eAAO,MAAM,qBAAqB,GAChC,aAAa,kBAAkB,EAC/B,OAAO,mBAAmB,YAM3B,CAAA;AAwxCD,eAAO,MAAM,kBAAkB,GAAI,qHAWhC;IACD,WAAW,EAAE,kBAAkB,CAAA;IAC/B,mBAAmB,CAAC,EAAE,kBAAkB,CAAA;IACxC,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,mBAAmB,CAAA;IAC1B,YAAY,EAAE,kBAAkB,EAAE,CAAA;IAClC,QAAQ,EAAE,uBAAuB,GAAG,SAAS,CAAA;IAC7C,QAAQ,EAAE,CAAC,QAAQ,EAAE,uBAAuB,GAAG,SAAS,KAAK,IAAI,CAAA;IACjE,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACtC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAA;CAC/C,mDAiOA,CAAA"}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { getListField, isDynamicDataSourceContentTypeAllowed, isTranslatableObject, } from '@rakun-kit/core/client';
|
|
4
|
-
import { Cable, HelpCircle, Link2, ListFilter, X } from 'lucide-react';
|
|
4
|
+
import { Cable, ChevronRight, HelpCircle, Link2, ListFilter, X } from 'lucide-react';
|
|
5
5
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
6
6
|
import { Badge } from '../../../../../components/ui/badge.js';
|
|
7
7
|
import { Button } from '../../../../../components/ui/button.js';
|
|
8
|
+
import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from '../../../../../components/ui/collapsible.js';
|
|
8
9
|
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '../../../../../components/ui/dialog.js';
|
|
9
10
|
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue, } from '../../../../../components/ui/select.js';
|
|
10
11
|
import { Tooltip, TooltipContent, TooltipTrigger, } from '../../../../../components/ui/tooltip.js';
|
|
@@ -27,6 +28,7 @@ const cleanDynamicBindings = (bindings) => {
|
|
|
27
28
|
};
|
|
28
29
|
const isRecord = (value) => !!value && typeof value === 'object' && !Array.isArray(value);
|
|
29
30
|
const isListField = (field) => field.config.ui === 'List' || field.config.ui === 'Iterator';
|
|
31
|
+
const isRelatedCollectionSource = (source) => !!source && 'kind' in source && source.kind === 'relatedCollection';
|
|
30
32
|
const isDynamicVisibleField = (field) => (field.visibility ?? 'all') === 'all' && field.isDynamic !== false;
|
|
31
33
|
export const isDynamicFieldEnabled = (contentType, field) => {
|
|
32
34
|
return (contentType.dynamicData !== false &&
|
|
@@ -56,6 +58,8 @@ const getFieldKind = (field) => {
|
|
|
56
58
|
}
|
|
57
59
|
if (field.config.type === 'Relation')
|
|
58
60
|
return 'object';
|
|
61
|
+
if (field.config.ui === 'SimpleList')
|
|
62
|
+
return 'array';
|
|
59
63
|
if (isListField(field))
|
|
60
64
|
return 'array';
|
|
61
65
|
return 'unknown';
|
|
@@ -65,11 +69,31 @@ const isCompatibleSourceKind = (sourceKind, targetField) => {
|
|
|
65
69
|
return sourceKind !== 'object' && sourceKind !== 'array';
|
|
66
70
|
return sourceKind === getFieldKind(targetField);
|
|
67
71
|
};
|
|
72
|
+
const getMultipleFileField = (field) => field.config.type === 'File' &&
|
|
73
|
+
field.isMultiple
|
|
74
|
+
? field
|
|
75
|
+
: undefined;
|
|
76
|
+
const areFieldKindsCompatible = (sourceField, targetField) => {
|
|
77
|
+
const sourceKind = getFieldKind(sourceField);
|
|
78
|
+
if (!targetField)
|
|
79
|
+
return isCompatibleSourceKind(sourceKind);
|
|
80
|
+
const targetKind = getFieldKind(targetField);
|
|
81
|
+
if (sourceKind !== targetKind)
|
|
82
|
+
return false;
|
|
83
|
+
if (sourceKind !== 'array')
|
|
84
|
+
return true;
|
|
85
|
+
const sourceFile = getMultipleFileField(sourceField);
|
|
86
|
+
const targetFile = getMultipleFileField(targetField);
|
|
87
|
+
if (!sourceFile && !targetFile)
|
|
88
|
+
return true;
|
|
89
|
+
if (!sourceFile || !targetFile)
|
|
90
|
+
return false;
|
|
91
|
+
return (sourceFile.mediaType === 'Any' ||
|
|
92
|
+
targetFile.mediaType === 'Any' ||
|
|
93
|
+
sourceFile.mediaType === targetFile.mediaType);
|
|
94
|
+
};
|
|
68
95
|
const fieldLabel = (path) => path.startsWith('_seo.') ? `seo.${path.slice('_seo.'.length)}` : path;
|
|
69
|
-
const isSeoPath = (path) => path === '_seo' ||
|
|
70
|
-
path === 'seo' ||
|
|
71
|
-
path.startsWith('_seo.') ||
|
|
72
|
-
path.startsWith('seo.');
|
|
96
|
+
const isSeoPath = (path) => path.split('.').some((segment) => segment === '_seo' || segment === 'seo');
|
|
73
97
|
const fileFieldOptions = (path, targetField) => {
|
|
74
98
|
const options = [
|
|
75
99
|
{ label: fieldLabel(`${path}.url`), value: `${path}.url`, kind: 'string' },
|
|
@@ -117,10 +141,22 @@ const nestedSourceFieldOptions = ({ contentType, prefix = '', targetField, depth
|
|
|
117
141
|
depth: depth + 1,
|
|
118
142
|
});
|
|
119
143
|
}
|
|
144
|
+
if (field.config.type === 'File' &&
|
|
145
|
+
field.isMultiple) {
|
|
146
|
+
if (!areFieldKindsCompatible(field, targetField))
|
|
147
|
+
return [];
|
|
148
|
+
return [
|
|
149
|
+
{
|
|
150
|
+
label: fieldLabel(path),
|
|
151
|
+
value: path,
|
|
152
|
+
kind,
|
|
153
|
+
},
|
|
154
|
+
];
|
|
155
|
+
}
|
|
120
156
|
if (field.config.type === 'File') {
|
|
121
157
|
return fileFieldOptions(path, targetField);
|
|
122
158
|
}
|
|
123
|
-
if (!
|
|
159
|
+
if (!areFieldKindsCompatible(field, targetField))
|
|
124
160
|
return [];
|
|
125
161
|
return [
|
|
126
162
|
{
|
|
@@ -145,6 +181,26 @@ const createSource = (contentType, value, id) => value === '$href'
|
|
|
145
181
|
: { contentType, id, path: value };
|
|
146
182
|
const sourceValue = (source) => source?.virtual === 'href' ? '$href' : source?.path || '';
|
|
147
183
|
const sourceLabel = (source) => source?.virtual === 'href' ? 'href' : fieldLabel(source?.path || '');
|
|
184
|
+
const mappingSourceSummary = (source, currentSourceName) => {
|
|
185
|
+
if (!source) {
|
|
186
|
+
return {
|
|
187
|
+
mode: 'Not configured',
|
|
188
|
+
detail: 'Choose a mapping source',
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
if (!isRelatedCollectionSource(source)) {
|
|
192
|
+
return {
|
|
193
|
+
mode: 'Direct field',
|
|
194
|
+
detail: `${currentSourceName}.${sourceLabel(source)}`,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
const sort = Object.entries(source.sort ?? {})[0];
|
|
198
|
+
const sortSummary = sort ? ` · sort ${sort[0]} ${sort[1]}` : '';
|
|
199
|
+
return {
|
|
200
|
+
mode: 'Related collection',
|
|
201
|
+
detail: `${source.contentType}.${source.relation} → ${currentSourceName} · collect ${source.path} · limit ${source.limit}${sortSummary}`,
|
|
202
|
+
};
|
|
203
|
+
};
|
|
148
204
|
const sourceContentTypeLabel = (source, documentContentTypeName) => source.contentType === documentContentTypeName && !source.id
|
|
149
205
|
? 'Current document'
|
|
150
206
|
: source.contentType;
|
|
@@ -212,6 +268,7 @@ const bindingSummary = ({ list, fieldBinding, listBinding, documentContentTypeNa
|
|
|
212
268
|
return `${sourceContentTypeLabel(fieldBinding, documentContentTypeName)} -> ${sourceLabel(fieldBinding)}`;
|
|
213
269
|
};
|
|
214
270
|
const PanelSection = ({ title, children, }) => (_jsxs("div", { className: 'grid gap-3 rounded-md border border-border bg-muted/20 p-3', children: [_jsx("div", { className: 'text-xs font-semibold uppercase text-muted-foreground', children: title }), children] }));
|
|
271
|
+
const ControlLabel = ({ children, help, }) => (_jsxs("div", { className: 'flex items-center gap-1.5', children: [_jsx(Label, { children: children }), _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("button", { type: 'button', className: 'rounded-sm text-muted-foreground outline-none hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring', "aria-label": help, children: _jsx(HelpCircle, { className: 'h-3.5 w-3.5' }) }) }), _jsx(TooltipContent, { side: 'top', sideOffset: 6, className: 'max-w-xs', children: help })] })] }));
|
|
215
272
|
const FieldBindingEditor = ({ contentTypes, documentContentType, currentDocumentSourceEnabled, targetField, binding, onChange, }) => {
|
|
216
273
|
const { language } = useLanguage();
|
|
217
274
|
const [sourceType, setSourceType] = useState(binding?.contentType || '');
|
|
@@ -241,6 +298,9 @@ const FieldBindingEditor = ({ contentTypes, documentContentType, currentDocument
|
|
|
241
298
|
selectedContentType?.dynamicDataSource === true,
|
|
242
299
|
});
|
|
243
300
|
const items = (sourceItemsQuery.data?.items ?? []);
|
|
301
|
+
const hasCurrentDocumentItem = currentDocumentSourceEnabled && sourceType === documentContentType.name;
|
|
302
|
+
const hasItemOptions = hasCurrentDocumentItem || items.length > 0;
|
|
303
|
+
const fieldOptions = sourceFieldOptions(selectedContentType, targetField);
|
|
244
304
|
const emit = (nextType = sourceType, nextId = sourceId, nextPath = fieldPath) => {
|
|
245
305
|
const usesCurrentDocument = currentDocumentSourceEnabled &&
|
|
246
306
|
nextType === documentContentType.name &&
|
|
@@ -251,7 +311,7 @@ const FieldBindingEditor = ({ contentTypes, documentContentType, currentDocument
|
|
|
251
311
|
}
|
|
252
312
|
onChange(createSource(nextType, nextPath, usesCurrentDocument ? undefined : nextId));
|
|
253
313
|
};
|
|
254
|
-
return (_jsx(PanelSection, { title: 'Source value', children: _jsxs("div", { className: 'grid gap-3 md:grid-cols-[1fr_1.4fr_1fr]', children: [_jsxs(Label, { className: 'grid gap-1.5', children: ["Source", _jsxs(Select, { value: sourceType, onValueChange: (value) => {
|
|
314
|
+
return (_jsx(PanelSection, { title: 'Source value', children: _jsxs("div", { className: 'grid gap-3 md:grid-cols-[1fr_1.4fr_1fr]', children: [_jsxs(Label, { className: 'grid gap-1.5', children: ["Source", _jsxs(Select, { disabled: contentTypes.length === 0, value: sourceType, onValueChange: (value) => {
|
|
255
315
|
const nextId = currentDocumentSourceEnabled &&
|
|
256
316
|
value === documentContentType.name
|
|
257
317
|
? CURRENT_DOCUMENT_ID
|
|
@@ -260,24 +320,23 @@ const FieldBindingEditor = ({ contentTypes, documentContentType, currentDocument
|
|
|
260
320
|
setSourceId(nextId);
|
|
261
321
|
setFieldPath('');
|
|
262
322
|
onChange(undefined);
|
|
263
|
-
}, children: [_jsx(SelectTrigger, { className: 'w-full', children: _jsx(SelectValue, { placeholder: 'Content type' }) }), _jsx(SelectContent, { children: _jsx(SelectGroup, { children: contentTypes.map((ct) => (_jsx(SelectItem, { value: ct.name, children: currentDocumentSourceEnabled &&
|
|
323
|
+
}, children: [_jsx(SelectTrigger, { className: 'w-full', children: _jsx(SelectValue, { placeholder: contentTypes.length > 0 ? 'Content type' : 'No content types' }) }), _jsx(SelectContent, { children: _jsx(SelectGroup, { children: contentTypes.map((ct) => (_jsx(SelectItem, { value: ct.name, children: currentDocumentSourceEnabled &&
|
|
264
324
|
ct.name === documentContentType.name
|
|
265
325
|
? `${ct.name} (current document)`
|
|
266
|
-
: ct.name }, ct.name))) }) })] })] }), _jsxs(Label, { className: 'grid gap-1.5', children: ["Item", _jsxs(Select, { disabled: !sourceType, value: sourceId, onValueChange: (value) => {
|
|
326
|
+
: ct.name }, ct.name))) }) })] })] }), _jsxs(Label, { className: 'grid gap-1.5', children: ["Item", _jsxs(Select, { disabled: !sourceType || !hasItemOptions, value: sourceId, onValueChange: (value) => {
|
|
267
327
|
setSourceId(value);
|
|
268
328
|
emit(sourceType, value, fieldPath);
|
|
269
|
-
}, children: [_jsx(SelectTrigger, { className: 'w-full', children: _jsx(SelectValue, { placeholder: 'Item' }) }), _jsx(SelectContent, { children: _jsxs(SelectGroup, { children: [
|
|
270
|
-
sourceType === documentContentType.name ? (_jsx(SelectItem, { value: CURRENT_DOCUMENT_ID, children: "Current document" })) : null, items.map((item) => {
|
|
329
|
+
}, children: [_jsx(SelectTrigger, { className: 'w-full', children: _jsx(SelectValue, { placeholder: hasItemOptions ? 'Item' : 'No items available' }) }), _jsx(SelectContent, { children: _jsxs(SelectGroup, { children: [hasCurrentDocumentItem ? (_jsx(SelectItem, { value: CURRENT_DOCUMENT_ID, children: "Current document" })) : null, items.map((item) => {
|
|
271
330
|
const label = selectedContentType
|
|
272
331
|
? getListField(item, selectedContentType.listFields ?? [])
|
|
273
332
|
: item._id;
|
|
274
333
|
return (_jsx(SelectItem, { value: item._id, children: isTranslatableObject(label)
|
|
275
334
|
? label[language.code]
|
|
276
335
|
: String(label || item._id) }, item._id));
|
|
277
|
-
})] }) })] })] }), _jsxs(Label, { className: 'grid gap-1.5', children: ["Field", _jsxs(Select, { disabled: !sourceType || !sourceId, value: fieldPath, onValueChange: (value) => {
|
|
336
|
+
})] }) })] })] }), _jsxs(Label, { className: 'grid gap-1.5', children: ["Field", _jsxs(Select, { disabled: !sourceType || !sourceId || fieldOptions.length === 0, value: fieldPath, onValueChange: (value) => {
|
|
278
337
|
setFieldPath(value);
|
|
279
338
|
emit(sourceType, sourceId, value);
|
|
280
|
-
}, children: [_jsx(SelectTrigger, { className: 'w-full', children: _jsx(SelectValue, { placeholder: 'Field' }) }), _jsx(SelectContent, { children: _jsx(SelectGroup, { children:
|
|
339
|
+
}, children: [_jsx(SelectTrigger, { className: 'w-full', children: _jsx(SelectValue, { placeholder: fieldOptions.length > 0 ? 'Field' : 'No compatible fields' }) }), _jsx(SelectContent, { children: _jsx(SelectGroup, { children: fieldOptions.map((field) => (_jsx(SelectItem, { value: field.value, children: field.label }, field.value))) }) })] })] })] }) }));
|
|
281
340
|
};
|
|
282
341
|
const getListTargetContentType = (field, itemName) => {
|
|
283
342
|
const entry = field.fields.find((item) => item.name === itemName);
|
|
@@ -285,15 +344,140 @@ const getListTargetContentType = (field, itemName) => {
|
|
|
285
344
|
return undefined;
|
|
286
345
|
return entry.field.contentType;
|
|
287
346
|
};
|
|
347
|
+
const getRelationContentType = (field) => {
|
|
348
|
+
if (field.isTranslatable)
|
|
349
|
+
return undefined;
|
|
350
|
+
if (field.config.type === 'Relation') {
|
|
351
|
+
return field.contentType;
|
|
352
|
+
}
|
|
353
|
+
if (field.config.ui === 'SimpleList') {
|
|
354
|
+
const itemField = field.field;
|
|
355
|
+
if (itemField.config.type === 'Relation') {
|
|
356
|
+
return itemField.contentType;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return undefined;
|
|
360
|
+
};
|
|
361
|
+
const getRelatedRelationOptions = (contentType, currentSource) => Object.entries(contentType.fields).flatMap(([name, field]) => {
|
|
362
|
+
if (!isDynamicVisibleField(field))
|
|
363
|
+
return [];
|
|
364
|
+
const relationContentType = getRelationContentType(field);
|
|
365
|
+
return relationContentType?.name === currentSource.name ? [name] : [];
|
|
366
|
+
});
|
|
367
|
+
const getRelatedPathOptions = (contentType, targetField) => sourceFieldOptions(contentType, targetField).filter((option) => option.kind === 'array' && option.value !== '$href');
|
|
368
|
+
const getSortFieldOptions = (contentType) => Object.entries(contentType.fields).flatMap(([name, field]) => {
|
|
369
|
+
if (!isDynamicVisibleField(field) ||
|
|
370
|
+
field.isTranslatable ||
|
|
371
|
+
name.startsWith('_') ||
|
|
372
|
+
isSeoPath(name)) {
|
|
373
|
+
return [];
|
|
374
|
+
}
|
|
375
|
+
const kind = getFieldKind(field);
|
|
376
|
+
const isSingleSelect = field.config.type === 'Select' &&
|
|
377
|
+
!field.isMultiple;
|
|
378
|
+
const isSortable = (field.config.type === 'String' && field.config.ui !== 'RichText') ||
|
|
379
|
+
field.config.type === 'Number' ||
|
|
380
|
+
field.config.type === 'Boolean' ||
|
|
381
|
+
field.config.type === 'Date' ||
|
|
382
|
+
isSingleSelect;
|
|
383
|
+
return isSortable ? [{ label: fieldLabel(name), value: name, kind }] : [];
|
|
384
|
+
});
|
|
385
|
+
const createRelatedCollectionSource = ({ contentType, currentSource, targetField, }) => {
|
|
386
|
+
const relation = getRelatedRelationOptions(contentType, currentSource)[0];
|
|
387
|
+
const path = getRelatedPathOptions(contentType, targetField)[0]?.value;
|
|
388
|
+
if (!relation || !path)
|
|
389
|
+
return undefined;
|
|
390
|
+
return {
|
|
391
|
+
kind: 'relatedCollection',
|
|
392
|
+
contentType: contentType.name,
|
|
393
|
+
relation,
|
|
394
|
+
path,
|
|
395
|
+
limit: 10,
|
|
396
|
+
};
|
|
397
|
+
};
|
|
398
|
+
const MappingSourceEditor = ({ contentTypes, currentSource, targetField, source, onChange, }) => {
|
|
399
|
+
const relatedSource = isRelatedCollectionSource(source) ? source : undefined;
|
|
400
|
+
const directSource = source && !isRelatedCollectionSource(source) ? source : undefined;
|
|
401
|
+
const directFieldOptions = sourceFieldOptions(currentSource, targetField);
|
|
402
|
+
const relatedContentTypes = contentTypes.filter((contentType) => getRelatedRelationOptions(contentType, currentSource).length > 0 &&
|
|
403
|
+
getRelatedPathOptions(contentType, targetField).length > 0);
|
|
404
|
+
const selectedRelatedContentType = relatedSource
|
|
405
|
+
? relatedContentTypes.find((contentType) => contentType.name === relatedSource.contentType)
|
|
406
|
+
: undefined;
|
|
407
|
+
const relationOptions = selectedRelatedContentType
|
|
408
|
+
? getRelatedRelationOptions(selectedRelatedContentType, currentSource)
|
|
409
|
+
: [];
|
|
410
|
+
const pathOptions = selectedRelatedContentType
|
|
411
|
+
? getRelatedPathOptions(selectedRelatedContentType, targetField)
|
|
412
|
+
: [];
|
|
413
|
+
const sortOptions = selectedRelatedContentType
|
|
414
|
+
? getSortFieldOptions(selectedRelatedContentType)
|
|
415
|
+
: [];
|
|
416
|
+
const sortEntry = Object.entries(relatedSource?.sort ?? {})[0];
|
|
417
|
+
const sortField = sortEntry?.[0] ?? '';
|
|
418
|
+
const sortDirection = sortEntry?.[1] ?? 'desc';
|
|
419
|
+
if (!relatedSource) {
|
|
420
|
+
return (_jsxs("div", { className: 'grid gap-3 md:grid-cols-[0.7fr_1.3fr]', children: [_jsxs("div", { className: 'grid content-start gap-1.5', children: [_jsx(ControlLabel, { help: 'Choose a direct field or query a related collection.', children: "Mapping mode" }), _jsxs(Select, { value: 'field', onValueChange: (value) => {
|
|
421
|
+
if (value !== 'relatedCollection')
|
|
422
|
+
return;
|
|
423
|
+
const next = relatedContentTypes
|
|
424
|
+
.map((contentType) => createRelatedCollectionSource({
|
|
425
|
+
contentType,
|
|
426
|
+
currentSource,
|
|
427
|
+
targetField,
|
|
428
|
+
}))
|
|
429
|
+
.find(Boolean);
|
|
430
|
+
onChange(next);
|
|
431
|
+
}, children: [_jsx(SelectTrigger, { className: 'w-full', children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: 'field', children: "Direct field" }), relatedContentTypes.length > 0 ? (_jsx(SelectItem, { value: 'relatedCollection', children: "Related collection" })) : null] })] })] }), _jsxs("div", { className: 'grid content-start gap-1.5', children: [_jsxs(ControlLabel, { help: 'Its value will be assigned to this mapped property.', children: ["Field on ", currentSource.name] }), _jsxs(Select, { disabled: directFieldOptions.length === 0, value: sourceValue(directSource), onValueChange: (value) => onChange(createSource(currentSource.name, value)), children: [_jsx(SelectTrigger, { className: 'w-full', children: _jsx(SelectValue, { placeholder: directFieldOptions.length > 0
|
|
432
|
+
? 'Select a source field'
|
|
433
|
+
: 'No compatible fields' }) }), _jsx(SelectContent, { children: _jsx(SelectGroup, { children: directFieldOptions.map((item) => (_jsx(SelectItem, { value: item.value, children: item.label }, item.value))) }) })] })] })] }));
|
|
434
|
+
}
|
|
435
|
+
return (_jsxs("div", { className: 'grid gap-3', children: [_jsxs("div", { className: 'grid gap-3 sm:grid-cols-2 lg:grid-cols-4', children: [_jsxs("div", { className: 'grid content-start gap-1.5', children: [_jsx(ControlLabel, { help: 'Query records related to the current source item.', children: "Mapping mode" }), _jsxs(Select, { value: 'relatedCollection', onValueChange: () => onChange(undefined), children: [_jsx(SelectTrigger, { className: 'w-full', children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: 'field', children: "Direct field" }), _jsx(SelectItem, { value: 'relatedCollection', children: "Related collection" })] })] })] }), _jsxs("div", { className: 'grid content-start gap-1.5', children: [_jsx(ControlLabel, { help: 'Content type whose records will be searched.', children: "Collection to query" }), _jsxs(Select, { disabled: relatedContentTypes.length === 0, value: relatedSource.contentType, onValueChange: (value) => {
|
|
436
|
+
const contentType = relatedContentTypes.find((item) => item.name === value);
|
|
437
|
+
if (!contentType)
|
|
438
|
+
return;
|
|
439
|
+
onChange(createRelatedCollectionSource({
|
|
440
|
+
contentType,
|
|
441
|
+
currentSource,
|
|
442
|
+
targetField,
|
|
443
|
+
}));
|
|
444
|
+
}, children: [_jsx(SelectTrigger, { className: 'w-full', children: _jsx(SelectValue, { placeholder: relatedContentTypes.length > 0
|
|
445
|
+
? 'Select a content type'
|
|
446
|
+
: 'No compatible collections' }) }), _jsx(SelectContent, { children: relatedContentTypes.map((contentType) => (_jsx(SelectItem, { value: contentType.name, children: contentType.name }, contentType.name))) })] })] }), _jsxs("div", { className: 'grid content-start gap-1.5', children: [_jsxs(ControlLabel, { help: `Field on ${relatedSource.contentType} that points to ${currentSource.name}.`, children: ["Relation to ", currentSource.name] }), _jsxs(Select, { disabled: relationOptions.length === 0, value: relatedSource.relation, onValueChange: (relation) => onChange({ ...relatedSource, relation }), children: [_jsx(SelectTrigger, { className: 'w-full', children: _jsx(SelectValue, { placeholder: relationOptions.length > 0
|
|
447
|
+
? 'Select a relation field'
|
|
448
|
+
: 'No compatible relations' }) }), _jsx(SelectContent, { children: relationOptions.map((relation) => (_jsx(SelectItem, { value: relation, children: relation }, relation))) })] })] }), _jsxs("div", { className: 'grid content-start gap-1.5', children: [_jsx(ControlLabel, { help: 'Arrays from matching records are flattened into one result.', children: "Array field to collect" }), _jsxs(Select, { disabled: pathOptions.length === 0, value: relatedSource.path, onValueChange: (path) => onChange({ ...relatedSource, path }), children: [_jsx(SelectTrigger, { className: 'w-full', children: _jsx(SelectValue, { placeholder: pathOptions.length > 0
|
|
449
|
+
? 'Select an array field'
|
|
450
|
+
: 'No compatible array fields' }) }), _jsx(SelectContent, { children: pathOptions.map((option) => (_jsx(SelectItem, { value: option.value, children: option.label }, option.value))) })] })] })] }), _jsxs("div", { className: 'grid gap-3 sm:grid-cols-[0.7fr_1fr_0.7fr]', children: [_jsxs("div", { className: 'grid content-start gap-1.5', children: [_jsx(ControlLabel, { help: `Number of ${relatedSource.contentType} records queried per ${currentSource.name}. Minimum 1, maximum 100.`, children: "Maximum related records" }), _jsx(Input, { type: 'number', min: 1, max: 100, value: String(relatedSource.limit), onChange: (event) => onChange({
|
|
451
|
+
...relatedSource,
|
|
452
|
+
limit: Math.min(100, Math.max(1, Number(event.target.value || 10))),
|
|
453
|
+
}) })] }), _jsxs("div", { className: 'grid content-start gap-1.5', children: [_jsx(ControlLabel, { help: 'Optional. Only direct scalar fields can be used.', children: "Sort related records by" }), _jsxs(Select, { value: sortField || '__none__', onValueChange: (value) => onChange({
|
|
454
|
+
...relatedSource,
|
|
455
|
+
sort: value === '__none__'
|
|
456
|
+
? undefined
|
|
457
|
+
: { [value]: sortDirection },
|
|
458
|
+
}), children: [_jsx(SelectTrigger, { className: 'w-full', children: _jsx(SelectValue, { placeholder: 'No sort' }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: '__none__', children: "No sort" }), sortOptions.map((option) => (_jsx(SelectItem, { value: option.value, children: option.label }, option.value)))] })] })] }), _jsxs("div", { className: 'grid content-start gap-1.5', children: [_jsx(ControlLabel, { help: 'Applied before collecting and flattening the arrays.', children: "Sort direction" }), _jsxs(Select, { disabled: !sortField, value: sortDirection, onValueChange: (direction) => sortField
|
|
459
|
+
? onChange({
|
|
460
|
+
...relatedSource,
|
|
461
|
+
sort: { [sortField]: direction },
|
|
462
|
+
})
|
|
463
|
+
: undefined, children: [_jsx(SelectTrigger, { className: 'w-full', children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: 'asc', children: "Ascending" }), _jsx(SelectItem, { value: 'desc', children: "Descending" })] })] })] })] })] }));
|
|
464
|
+
};
|
|
288
465
|
const ListBindingEditor = ({ contentTypes, field, binding, onChange, }) => {
|
|
289
466
|
const [sourceType, setSourceType] = useState(binding?.contentType || '');
|
|
290
467
|
const [itemName, setItemName] = useState(binding?.itemName || field.fields[0]?.name || '');
|
|
291
468
|
const [filterState, setFilterState] = useState(readFilterState(binding?.query?.filter));
|
|
469
|
+
const [openMappingField, setOpenMappingField] = useState(null);
|
|
292
470
|
const selectedSource = contentTypes.find((ct) => ct.name === sourceType);
|
|
293
471
|
const targetContentType = getListTargetContentType(field, itemName);
|
|
294
472
|
const targetFields = useMemo(() => targetContentType
|
|
295
473
|
? Object.entries(targetContentType.fields).filter(([, targetField]) => isDynamicVisibleField(targetField))
|
|
296
474
|
: [], [targetContentType]);
|
|
475
|
+
const sortFieldOptions = selectedSource
|
|
476
|
+
? getSortFieldOptions(selectedSource)
|
|
477
|
+
: [];
|
|
478
|
+
const filterFieldOptions = selectedSource
|
|
479
|
+
? sourceFieldOptions(selectedSource).filter((item) => item.value !== '$href')
|
|
480
|
+
: [];
|
|
297
481
|
const emit = (patch) => {
|
|
298
482
|
const next = {
|
|
299
483
|
contentType: sourceType,
|
|
@@ -317,13 +501,13 @@ const ListBindingEditor = ({ contentTypes, field, binding, onChange, }) => {
|
|
|
317
501
|
},
|
|
318
502
|
});
|
|
319
503
|
};
|
|
320
|
-
return (_jsxs("div", { className: 'grid gap-3', children: [_jsx(PanelSection, { title: 'Collection', children: _jsxs("div", { className: 'grid gap-3 md:grid-cols-2', children: [_jsxs(Label, { className: 'grid gap-1.5', children: ["Source", _jsxs(Select, { value: sourceType, onValueChange: (value) => {
|
|
504
|
+
return (_jsxs("div", { className: 'grid gap-3', children: [_jsx(PanelSection, { title: 'Collection', children: _jsxs("div", { className: 'grid gap-3 md:grid-cols-2', children: [_jsxs(Label, { className: 'grid gap-1.5', children: ["Source", _jsxs(Select, { disabled: contentTypes.length === 0, value: sourceType, onValueChange: (value) => {
|
|
321
505
|
setSourceType(value);
|
|
322
506
|
emit({ contentType: value, map: {} });
|
|
323
|
-
}, children: [_jsx(SelectTrigger, { className: 'w-full', children: _jsx(SelectValue, { placeholder: 'Content type' }) }), _jsx(SelectContent, { children: _jsx(SelectGroup, { children: contentTypes.map((ct) => (_jsx(SelectItem, { value: ct.name, children: ct.name }, ct.name))) }) })] })] }), _jsxs(Label, { className: 'grid gap-1.5', children: ["Item", _jsxs(Select, { disabled: !sourceType, value: itemName, onValueChange: (value) => {
|
|
507
|
+
}, children: [_jsx(SelectTrigger, { className: 'w-full', children: _jsx(SelectValue, { placeholder: contentTypes.length > 0 ? 'Content type' : 'No content types' }) }), _jsx(SelectContent, { children: _jsx(SelectGroup, { children: contentTypes.map((ct) => (_jsx(SelectItem, { value: ct.name, children: ct.name }, ct.name))) }) })] })] }), _jsxs(Label, { className: 'grid gap-1.5', children: ["Item", _jsxs(Select, { disabled: !sourceType || field.fields.length === 0, value: itemName, onValueChange: (value) => {
|
|
324
508
|
setItemName(value);
|
|
325
509
|
emit({ itemName: value, map: {} });
|
|
326
|
-
}, children: [_jsx(SelectTrigger, { className: 'w-full', children: _jsx(SelectValue, { placeholder: 'Item type' }) }), _jsx(SelectContent, { children: _jsx(SelectGroup, { children: field.fields.map((item) => (_jsx(SelectItem, { value: item.name, children: item.name }, item.name))) }) })] })] })] }) }), _jsx(PanelSection, { title: 'Query', children: _jsxs("div", { className: 'grid gap-3 md:grid-cols-[0.7fr_1fr_1.1fr]', children: [_jsxs(Label, { className: 'grid gap-1.5', children: ["Limit", _jsx(Input, { type: 'number', min: 1, value: String(binding?.query?.options?.limit ?? 10), onChange: (event) => emit({
|
|
510
|
+
}, children: [_jsx(SelectTrigger, { className: 'w-full', children: _jsx(SelectValue, { placeholder: field.fields.length > 0 ? 'Item type' : 'No item types' }) }), _jsx(SelectContent, { children: _jsx(SelectGroup, { children: field.fields.map((item) => (_jsx(SelectItem, { value: item.name, children: item.name }, item.name))) }) })] })] })] }) }), _jsx(PanelSection, { title: 'Query', children: _jsxs("div", { className: 'grid gap-3 md:grid-cols-[0.7fr_1fr_1.1fr]', children: [_jsxs(Label, { className: 'grid gap-1.5', children: ["Limit", _jsx(Input, { type: 'number', min: 1, value: String(binding?.query?.options?.limit ?? 10), onChange: (event) => emit({
|
|
327
511
|
query: {
|
|
328
512
|
...binding?.query,
|
|
329
513
|
options: {
|
|
@@ -331,7 +515,7 @@ const ListBindingEditor = ({ contentTypes, field, binding, onChange, }) => {
|
|
|
331
515
|
limit: Number(event.target.value || 10),
|
|
332
516
|
},
|
|
333
517
|
},
|
|
334
|
-
}) })] }), _jsxs(Label, { className: 'grid gap-1.5', children: ["Sort", _jsxs(Select, { disabled:
|
|
518
|
+
}) })] }), _jsxs(Label, { className: 'grid gap-1.5', children: ["Sort", _jsxs(Select, { disabled: sortFieldOptions.length === 0, value: Object.keys(binding?.query?.options?.sort ?? {})[0] ?? '', onValueChange: (value) => emit({
|
|
335
519
|
query: {
|
|
336
520
|
...binding?.query,
|
|
337
521
|
options: {
|
|
@@ -339,17 +523,17 @@ const ListBindingEditor = ({ contentTypes, field, binding, onChange, }) => {
|
|
|
339
523
|
sort: value ? { [value]: 'desc' } : undefined,
|
|
340
524
|
},
|
|
341
525
|
},
|
|
342
|
-
}), children: [_jsx(SelectTrigger, { className: 'w-full', children: _jsx(SelectValue, { placeholder:
|
|
343
|
-
|
|
344
|
-
.map((item) => (_jsx(SelectItem, { value: item.value, children: item.label }, item.value))) }) })] })] }), _jsxs("div", { className: 'grid gap-1.5', children: [_jsxs("div", { className: 'flex items-center justify-between gap-2', children: [_jsx("span", { className: 'text-sm font-medium', children: "Filter" }), _jsxs(Button, { type: 'button', size: 'icon', variant: 'ghost', className: 'size-7', onClick: () => updateFilter(undefined), children: [_jsx(X, { className: 'h-4 w-4' }), _jsx("span", { className: 'sr-only', children: "Clear filter" })] })] }), _jsxs("div", { className: 'grid gap-2 sm:grid-cols-[1fr_0.9fr_1fr]', children: [_jsxs(Select, { disabled:
|
|
526
|
+
}), children: [_jsx(SelectTrigger, { className: 'w-full', children: _jsx(SelectValue, { placeholder: sortFieldOptions.length > 0
|
|
527
|
+
? 'Field'
|
|
528
|
+
: 'No sortable fields' }) }), _jsx(SelectContent, { children: _jsx(SelectGroup, { children: sortFieldOptions.map((item) => (_jsx(SelectItem, { value: item.value, children: item.label }, item.value))) }) })] })] }), _jsxs("div", { className: 'grid gap-1.5', children: [_jsxs("div", { className: 'flex items-center justify-between gap-2', children: [_jsx("span", { className: 'text-sm font-medium', children: "Filter" }), _jsxs(Button, { type: 'button', size: 'icon', variant: 'ghost', className: 'size-7', onClick: () => updateFilter(undefined), children: [_jsx(X, { className: 'h-4 w-4' }), _jsx("span", { className: 'sr-only', children: "Clear filter" })] })] }), _jsxs("div", { className: 'grid gap-2 sm:grid-cols-[1fr_0.9fr_1fr]', children: [_jsxs(Select, { disabled: filterFieldOptions.length === 0, value: filterState?.field || '', onValueChange: (value) => updateFilter(value
|
|
345
529
|
? {
|
|
346
530
|
field: value,
|
|
347
531
|
operator: filterState?.operator ?? 'equals',
|
|
348
532
|
value: filterState?.value ?? '',
|
|
349
533
|
}
|
|
350
|
-
: undefined), children: [_jsx(SelectTrigger, { className: 'w-full', children: _jsx(SelectValue, { placeholder:
|
|
351
|
-
|
|
352
|
-
.map((item) => (_jsx(SelectItem, { value: item.value, children: item.label }, item.value))) }) })] }), _jsxs(Select, { disabled: !filterState?.field, value: filterState?.operator || 'equals', onValueChange: (value) => filterState
|
|
534
|
+
: undefined), children: [_jsx(SelectTrigger, { className: 'w-full', children: _jsx(SelectValue, { placeholder: filterFieldOptions.length > 0
|
|
535
|
+
? 'Field'
|
|
536
|
+
: 'No filterable fields' }) }), _jsx(SelectContent, { children: _jsx(SelectGroup, { children: filterFieldOptions.map((item) => (_jsx(SelectItem, { value: item.value, children: item.label }, item.value))) }) })] }), _jsxs(Select, { disabled: !filterState?.field, value: filterState?.operator || 'equals', onValueChange: (value) => filterState
|
|
353
537
|
? updateFilter({
|
|
354
538
|
...filterState,
|
|
355
539
|
operator: value,
|
|
@@ -365,12 +549,18 @@ const ListBindingEditor = ({ contentTypes, field, binding, onChange, }) => {
|
|
|
365
549
|
})
|
|
366
550
|
: undefined }))] })] })] }) }), _jsx(PanelSection, { title: 'Mapping', children: _jsx("div", { className: 'grid gap-2', children: targetFields.map(([targetField, targetFieldConfig]) => {
|
|
367
551
|
const source = binding?.map?.[targetField];
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
552
|
+
const summary = mappingSourceSummary(source, selectedSource?.name ?? 'Source');
|
|
553
|
+
const isOpen = openMappingField === targetField;
|
|
554
|
+
return (_jsx(Collapsible, { open: isOpen, onOpenChange: (open) => setOpenMappingField(open ? targetField : null), children: _jsxs("div", { className: 'overflow-hidden rounded-md border border-border bg-background/70 transition-colors hover:border-foreground/20', children: [_jsx(CollapsibleTrigger, { asChild: true, children: _jsxs("button", { type: 'button', disabled: !selectedSource, className: 'group grid w-full grid-cols-[minmax(6rem,0.45fr)_auto_minmax(0,1fr)_auto] items-center gap-3 px-3 py-2.5 text-left outline-none transition-colors hover:bg-muted/50 focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50', children: [_jsx("span", { className: 'truncate text-sm font-semibold', children: targetField }), _jsx(Badge, { variant: source ? 'secondary' : 'outline', className: 'max-w-40 truncate', children: summary.mode }), _jsx("span", { className: 'truncate text-sm text-muted-foreground', children: selectedSource
|
|
555
|
+
? summary.detail
|
|
556
|
+
: 'Select a source collection first' }), _jsx(ChevronRight, { className: 'h-4 w-4 text-muted-foreground transition-transform group-data-[state=open]:rotate-90' }), _jsxs("span", { className: 'sr-only', children: ["Configure ", targetField, " mapping"] })] }) }), _jsx(CollapsibleContent, { children: _jsx("div", { className: 'border-t border-border bg-muted/20 p-3', children: selectedSource ? (_jsx(MappingSourceEditor, { contentTypes: contentTypes, currentSource: selectedSource, targetField: targetFieldConfig, source: source, onChange: (nextSource) => {
|
|
557
|
+
const map = { ...(binding?.map ?? {}) };
|
|
558
|
+
if (nextSource)
|
|
559
|
+
map[targetField] = nextSource;
|
|
560
|
+
else
|
|
561
|
+
delete map[targetField];
|
|
562
|
+
emit({ map });
|
|
563
|
+
} })) : null }) })] }) }, targetField));
|
|
374
564
|
}) }) })] }));
|
|
375
565
|
};
|
|
376
566
|
export const DynamicDataControl = ({ contentType, documentContentType, fieldName, field, contentTypes, bindings, onChange, open, onOpenChange, mode = 'full', }) => {
|