@nocobase/client-v2 3.0.0-alpha.1 → 3.0.0-alpha.12
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/es/APIClient.d.ts +12 -0
- package/es/acl/aclCheckReadiness.d.ts +11 -0
- package/es/authRedirect.d.ts +6 -2
- package/es/flow/actions/linkageRules.d.ts +24 -0
- package/es/flow/components/FieldAssignValueInput.d.ts +4 -30
- package/es/flow/components/field-value-variable/DateVariableEditor.d.ts +24 -0
- package/es/flow/components/field-value-variable/FieldValueVariableInput.d.ts +31 -0
- package/es/flow/components/field-value-variable/dateValue.d.ts +36 -0
- package/es/flow/components/field-value-variable/index.d.ts +11 -0
- package/es/flow/components/placeholders/BlockPlaceholder.d.ts +1 -0
- package/es/flow/internal/utils/operatorSchemaHelper.d.ts +2 -2
- package/es/flow/models/actions/UpdateRecordActionUtils.d.ts +8 -2
- package/es/flow/models/base/CollectionBlockModel.d.ts +3 -0
- package/es/flow/models/blocks/details/DetailsBlockModel.d.ts +3 -0
- package/es/flow/models/blocks/filter-form/FilterFormGridModel.d.ts +1 -0
- package/es/flow/models/blocks/filter-form/FilterFormSubmitActionModel.d.ts +3 -1
- package/es/flow/models/blocks/form/FormBlockModel.d.ts +7 -0
- package/es/flow/models/blocks/form/FormGridModel.d.ts +6 -0
- package/es/flow/models/blocks/form/value-runtime/rules.d.ts +1 -0
- package/es/flow/models/blocks/table/JSColumnModel.d.ts +2 -0
- package/es/flow/models/blocks/table/TableBlockModel.d.ts +1 -0
- package/es/flow/models/blocks/table/TableColumnModel.d.ts +11 -0
- package/es/flow/models/blocks/table/utils.d.ts +1 -0
- package/es/flow/models/fields/AssociationFieldModel/RecordPickerFieldModel.d.ts +2 -1
- package/es/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.d.ts +0 -17
- package/es/flow/models/fields/AssociationFieldModel/recordSelectShared.d.ts +41 -0
- package/es/flow-compat/passwordUtils.d.ts +1 -1
- package/es/index.d.ts +2 -1
- package/es/index.mjs +140 -140
- package/es/nocobase-buildin-plugin/index.d.ts +1 -0
- package/es/settings-app/SettingsGroupNav.d.ts +3 -2
- package/es/settings-app/SettingsSearch.d.ts +1 -1
- package/es/ui-operation/index.d.ts +15 -0
- package/es/ui-operation/ui-operation-codec.d.ts +10 -0
- package/lib/index.js +145 -145
- package/package.json +7 -7
- package/src/APIClient.ts +92 -0
- package/src/Application.tsx +3 -1
- package/src/__tests__/APIClient.test.tsx +58 -0
- package/src/__tests__/app.test.tsx +122 -3
- package/src/__tests__/authRedirect.test.ts +17 -0
- package/src/__tests__/browserChecker.test.ts +81 -33
- package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +37 -0
- package/src/__tests__/settings-center.test.tsx +312 -11
- package/src/__tests__/settings-layout-root.test.tsx +346 -6
- package/src/__tests__/settings-runtime-paths.test.ts +1 -1
- package/src/__tests__/settings-search-shortcut.test.tsx +321 -0
- package/src/__tests__/settings-shell.test.tsx +62 -1
- package/src/acl/ACLProvider.tsx +34 -3
- package/src/acl/__tests__/ACLProvider.test.tsx +92 -0
- package/src/acl/aclCheckReadiness.ts +44 -0
- package/src/authRedirect.ts +13 -5
- package/src/components/AppComponents.tsx +16 -7
- package/src/components/form/filter/CollectionFilterItem.tsx +4 -2
- package/src/components/form/filter/__tests__/CollectionFilterItem.test.tsx +17 -0
- package/src/components/form/table/Table.tsx +81 -6
- package/src/components/form/table/__tests__/Table.columnWidth.test.tsx +433 -0
- package/src/flow/actions/__tests__/linkageRules.actionStates.test.ts +33 -0
- package/src/flow/actions/__tests__/linkageRules.subFormSetFieldProps.test.ts +72 -0
- package/src/flow/actions/linkageRules.tsx +33 -10
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.tsx +78 -59
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutMenuModels.test.ts +228 -0
- package/src/flow/components/FieldAssignValueInput.tsx +38 -621
- package/src/flow/components/field-value-variable/DateVariableEditor.tsx +181 -0
- package/src/flow/components/field-value-variable/FieldValueVariableInput.tsx +326 -0
- package/src/flow/components/field-value-variable/__tests__/FieldValueVariableInput.test.tsx +380 -0
- package/src/flow/components/field-value-variable/dateValue.ts +223 -0
- package/src/flow/components/field-value-variable/index.ts +12 -0
- package/src/flow/components/filter/VariableFilterItem.tsx +12 -3
- package/src/flow/components/filter/__tests__/VariableFilterItem.test.tsx +125 -0
- package/src/flow/components/placeholders/BlockPlaceholder.tsx +70 -23
- package/src/flow/components/placeholders/__tests__/BlockPlaceholder.test.tsx +57 -7
- package/src/flow/internal/utils/operatorSchemaHelper.ts +3 -2
- package/src/flow/models/actions/UpdateRecordActionModel.tsx +18 -1
- package/src/flow/models/actions/UpdateRecordActionUtils.ts +18 -6
- package/src/flow/models/actions/__tests__/UpdateRecordActionModel.test.ts +76 -4
- package/src/flow/models/base/CollectionBlockModel.tsx +32 -1
- package/src/flow/models/base/__tests__/CollectionBlockModel.initialBeforeRenderRefresh.test.ts +125 -9
- package/src/flow/models/blocks/assign-form/AssignFormItemModel.tsx +28 -53
- package/src/flow/models/blocks/details/DetailsBlockModel.tsx +8 -3
- package/src/flow/models/blocks/details/__tests__/DetailsBlockModel.initialPaginationChangeRefresh.test.ts +32 -1
- package/src/flow/models/blocks/filter-form/FilterFormGridModel.tsx +36 -5
- package/src/flow/models/blocks/filter-form/FilterFormItemModel.tsx +129 -14
- package/src/flow/models/blocks/filter-form/FilterFormSubmitActionModel.tsx +57 -4
- package/src/flow/models/blocks/filter-form/__tests__/FilterFormGridModel.onModelCreated.test.ts +174 -1
- package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.defineChildren.test.ts +359 -1
- package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.getFilterValue.test.ts +72 -0
- package/src/flow/models/blocks/filter-form/__tests__/FilterFormSubmitActionModel.lifecycle.test.ts +90 -0
- package/src/flow/models/blocks/filter-manager/FilterManager.ts +5 -0
- package/src/flow/models/blocks/filter-manager/__tests__/FilterManager.test.ts +40 -0
- package/src/flow/models/blocks/form/FormBlockModel.tsx +47 -0
- package/src/flow/models/blocks/form/FormGridModel.tsx +4 -0
- package/src/flow/models/blocks/form/__tests__/FormBlockModel.test.tsx +8 -2
- package/src/flow/models/blocks/form/__tests__/runJsFormSubmit.test.ts +131 -0
- package/src/flow/models/blocks/form/value-runtime/__tests__/runtime.test.ts +435 -0
- package/src/flow/models/blocks/form/value-runtime/rules.ts +67 -14
- package/src/flow/models/blocks/form/value-runtime/runtime.ts +43 -19
- package/src/flow/models/blocks/table/JSColumnModel.tsx +10 -1
- package/src/flow/models/blocks/table/TableBlockModel.tsx +25 -1
- package/src/flow/models/blocks/table/TableColumnModel.tsx +18 -8
- package/src/flow/models/blocks/table/__tests__/JSColumnModel.test.tsx +52 -5
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.filterReset.test.ts +78 -0
- package/src/flow/models/blocks/table/utils.ts +7 -0
- package/src/flow/models/fields/AssociationFieldModel/CascadeSelectFieldModel.tsx +33 -1
- package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/PopupSubTableFieldModel.tsx +8 -0
- package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/__tests__/popupContext.test.ts +120 -0
- package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/actions/PopupSubTableEditActionModel.tsx +10 -2
- package/src/flow/models/fields/AssociationFieldModel/RecordPickerFieldModel.tsx +24 -1
- package/src/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.tsx +9 -136
- package/src/flow/models/fields/AssociationFieldModel/__tests__/RecordPickerFieldModel.itemContext.test.ts +167 -3
- package/src/flow/models/fields/AssociationFieldModel/recordSelectShared.tsx +156 -0
- package/src/flow/models/fields/DisplayNumberFieldModel.tsx +1 -1
- package/src/flow/models/fields/NumberFieldModel.tsx +1 -1
- package/src/flow/models/fields/__tests__/DisplayNumberFieldModel.test.ts +33 -0
- package/src/flow/models/fields/__tests__/NumberFieldModel.test.tsx +47 -0
- package/src/flow/models/fields/mobile-components/MobileLazySelect.tsx +6 -0
- package/src/flow/models/fields/mobile-components/MobileSelect.tsx +27 -2
- package/src/flow/models/fields/mobile-components/__tests__/MobileSelect.test.tsx +139 -13
- package/src/index.ts +2 -0
- package/src/layout-manager/LayoutContentRoute.tsx +2 -1
- package/src/layout-manager/LayoutRoute.tsx +2 -1
- package/src/layout-manager/__tests__/LayoutRoute.test.tsx +87 -1
- package/src/nocobase-buildin-plugin/index.tsx +14 -1
- package/src/settings-app/SettingsGroupNav.tsx +11 -11
- package/src/settings-app/SettingsSearch.tsx +115 -10
- package/src/settings-app/SettingsShell.tsx +23 -6
- package/src/settings-app/runtimePaths.ts +0 -1
- package/src/settings-app/settingsTheme.ts +82 -14
- package/src/settings-center/AdminSettingsLayout.tsx +132 -107
- package/src/settings-center/__tests__/groups.test.ts +25 -0
- package/src/settings-center/groups.ts +1 -1
- package/src/ui-operation/index.ts +33 -0
- package/src/ui-operation/ui-operation-codec.ts +54 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { useFlowContext, type CtxDateExpressionConfig, type CtxDateRelativeUnit } from '@nocobase/flow-engine';
|
|
11
|
+
import { AutoComplete, Input, InputNumber, Select, Space } from 'antd';
|
|
12
|
+
import type { Dayjs } from 'dayjs';
|
|
13
|
+
import React from 'react';
|
|
14
|
+
import { FieldAssignExactDatePicker, type ExactDatePickerValue } from '../FieldAssignExactDatePicker';
|
|
15
|
+
import { toExactPickerDisplayValue, type DateVariableComponentProps } from './dateValue';
|
|
16
|
+
|
|
17
|
+
const DATE_FORMAT_OPTIONS = [
|
|
18
|
+
'YYYY-MM-DD',
|
|
19
|
+
'YYYY-MM-DD HH:mm:ss',
|
|
20
|
+
'YYYY/MM/DD',
|
|
21
|
+
'MM/DD/YYYY',
|
|
22
|
+
'DD/MM/YYYY',
|
|
23
|
+
'YYYYMMDD',
|
|
24
|
+
].map((value) => ({ value }));
|
|
25
|
+
|
|
26
|
+
const PRESET_LABELS: Record<string, string> = {
|
|
27
|
+
now: 'Now',
|
|
28
|
+
today: 'Today',
|
|
29
|
+
yesterday: 'Yesterday',
|
|
30
|
+
tomorrow: 'Tomorrow',
|
|
31
|
+
thisWeek: 'This Week',
|
|
32
|
+
lastWeek: 'Last Week',
|
|
33
|
+
nextWeek: 'Next Week',
|
|
34
|
+
thisMonth: 'This Month',
|
|
35
|
+
lastMonth: 'Last Month',
|
|
36
|
+
nextMonth: 'Next Month',
|
|
37
|
+
thisQuarter: 'This Quarter',
|
|
38
|
+
lastQuarter: 'Last Quarter',
|
|
39
|
+
nextQuarter: 'Next Quarter',
|
|
40
|
+
thisYear: 'This Year',
|
|
41
|
+
lastYear: 'Last Year',
|
|
42
|
+
nextYear: 'Next Year',
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export function getDefaultDateVariableFormat(config: CtxDateExpressionConfig): string {
|
|
46
|
+
return config.kind === 'preset' && config.preset === 'now' ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function serializeExactDatePickerValue(
|
|
50
|
+
value: ExactDatePickerValue,
|
|
51
|
+
showTime: boolean,
|
|
52
|
+
): string | [string, string] {
|
|
53
|
+
const serialize = (item: Dayjs) => (showTime ? item.toISOString() : item.format('YYYY-MM-DD'));
|
|
54
|
+
if (Array.isArray(value)) {
|
|
55
|
+
return [serialize(value[0]), serialize(value[1])];
|
|
56
|
+
}
|
|
57
|
+
return value ? serialize(value) : '';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
type DateVariableEditorProps = {
|
|
61
|
+
value?: CtxDateExpressionConfig;
|
|
62
|
+
onChange?: (value: CtxDateExpressionConfig) => void;
|
|
63
|
+
isDateLikeField: boolean;
|
|
64
|
+
dateComponentProps: DateVariableComponentProps;
|
|
65
|
+
style?: React.CSSProperties;
|
|
66
|
+
disabled?: boolean;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const DateFormatEditor: React.FC<{
|
|
70
|
+
value: string;
|
|
71
|
+
placeholder: string;
|
|
72
|
+
onChange: (value: string) => void;
|
|
73
|
+
disabled?: boolean;
|
|
74
|
+
}> = ({ value, placeholder, onChange, disabled }) => {
|
|
75
|
+
const ctx = useFlowContext();
|
|
76
|
+
return (
|
|
77
|
+
<Space.Compact style={{ width: '100%' }}>
|
|
78
|
+
<Input
|
|
79
|
+
value={ctx.t('Format')}
|
|
80
|
+
readOnly
|
|
81
|
+
disabled={disabled}
|
|
82
|
+
tabIndex={-1}
|
|
83
|
+
aria-hidden
|
|
84
|
+
style={{ width: 88, pointerEvents: 'none' }}
|
|
85
|
+
/>
|
|
86
|
+
<AutoComplete
|
|
87
|
+
value={value}
|
|
88
|
+
placeholder={placeholder}
|
|
89
|
+
options={DATE_FORMAT_OPTIONS}
|
|
90
|
+
onChange={(nextValue) => onChange(nextValue.slice(0, 128))}
|
|
91
|
+
disabled={disabled}
|
|
92
|
+
style={{ flex: 1, minWidth: 0 }}
|
|
93
|
+
aria-label={ctx.t('Format')}
|
|
94
|
+
/>
|
|
95
|
+
</Space.Compact>
|
|
96
|
+
);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export const DateVariableEditor: React.FC<DateVariableEditorProps> = ({
|
|
100
|
+
value,
|
|
101
|
+
onChange,
|
|
102
|
+
isDateLikeField,
|
|
103
|
+
dateComponentProps,
|
|
104
|
+
style,
|
|
105
|
+
disabled = false,
|
|
106
|
+
}) => {
|
|
107
|
+
const ctx = useFlowContext();
|
|
108
|
+
if (!value) return null;
|
|
109
|
+
|
|
110
|
+
const updateFormat = (format: string) => onChange?.({ ...value, format });
|
|
111
|
+
const format = value.format || '';
|
|
112
|
+
|
|
113
|
+
const renderValueEditor = () => {
|
|
114
|
+
if (value.kind === 'exact') {
|
|
115
|
+
const isRange = Array.isArray(value.value);
|
|
116
|
+
return (
|
|
117
|
+
<FieldAssignExactDatePicker
|
|
118
|
+
picker={dateComponentProps.picker}
|
|
119
|
+
showTime={dateComponentProps.showTime}
|
|
120
|
+
timeFormat={dateComponentProps.timeFormat}
|
|
121
|
+
format={dateComponentProps.format}
|
|
122
|
+
isRange={isRange}
|
|
123
|
+
value={toExactPickerDisplayValue(value.value, {
|
|
124
|
+
format: dateComponentProps.format,
|
|
125
|
+
isRange,
|
|
126
|
+
})}
|
|
127
|
+
onChange={(nextValue) => {
|
|
128
|
+
onChange?.({ ...value, value: serializeExactDatePickerValue(nextValue, dateComponentProps.showTime) });
|
|
129
|
+
}}
|
|
130
|
+
disabled={disabled}
|
|
131
|
+
style={{ width: '100%' }}
|
|
132
|
+
/>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (value.kind === 'relative') {
|
|
137
|
+
return (
|
|
138
|
+
<Space.Compact style={{ width: '100%' }}>
|
|
139
|
+
<InputNumber
|
|
140
|
+
min={1}
|
|
141
|
+
precision={0}
|
|
142
|
+
value={value.amount}
|
|
143
|
+
disabled={disabled}
|
|
144
|
+
onChange={(amount) => onChange?.({ ...value, amount: typeof amount === 'number' ? amount : 1 })}
|
|
145
|
+
aria-label={ctx.t(value.direction === 'past' ? 'Past' : 'Next')}
|
|
146
|
+
style={{ flex: '1 1 auto', minWidth: 80 }}
|
|
147
|
+
/>
|
|
148
|
+
<Select
|
|
149
|
+
value={value.unit}
|
|
150
|
+
disabled={disabled}
|
|
151
|
+
onChange={(unit: CtxDateRelativeUnit) => onChange?.({ ...value, unit })}
|
|
152
|
+
aria-label={ctx.t('Unit')}
|
|
153
|
+
style={{ flex: '0 0 130px', minWidth: 130 }}
|
|
154
|
+
options={[
|
|
155
|
+
{ value: 'day', label: ctx.t('Day') },
|
|
156
|
+
{ value: 'week', label: ctx.t('Calendar week') },
|
|
157
|
+
{ value: 'month', label: ctx.t('Calendar Month') },
|
|
158
|
+
{ value: 'year', label: ctx.t('Calendar Year') },
|
|
159
|
+
]}
|
|
160
|
+
/>
|
|
161
|
+
</Space.Compact>
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return <Input value={ctx.t(PRESET_LABELS[value.preset] || value.preset)} readOnly disabled={disabled} />;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
return (
|
|
169
|
+
<div style={{ display: 'flex', flex: 1, flexDirection: 'column', gap: 8, width: '100%', minWidth: 0, ...style }}>
|
|
170
|
+
{renderValueEditor()}
|
|
171
|
+
{!isDateLikeField && (
|
|
172
|
+
<DateFormatEditor
|
|
173
|
+
value={format}
|
|
174
|
+
placeholder={getDefaultDateVariableFormat(value)}
|
|
175
|
+
onChange={updateFormat}
|
|
176
|
+
disabled={disabled}
|
|
177
|
+
/>
|
|
178
|
+
)}
|
|
179
|
+
</div>
|
|
180
|
+
);
|
|
181
|
+
};
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
isRunJSValue,
|
|
12
|
+
isVariableExpression,
|
|
13
|
+
parseCtxDateExpressionConfig,
|
|
14
|
+
parseValueToPath,
|
|
15
|
+
serializeCtxDateExpressionConfig,
|
|
16
|
+
tExpr,
|
|
17
|
+
VariableInput,
|
|
18
|
+
type CtxDateExpressionConfig,
|
|
19
|
+
type CtxDatePreset,
|
|
20
|
+
type MetaTreeNode,
|
|
21
|
+
type VariableInputProps,
|
|
22
|
+
} from '@nocobase/flow-engine';
|
|
23
|
+
import { dayjs } from '@nocobase/utils/client';
|
|
24
|
+
import type { Dayjs } from 'dayjs';
|
|
25
|
+
import React from 'react';
|
|
26
|
+
import { DateVariableEditor, getDefaultDateVariableFormat } from './DateVariableEditor';
|
|
27
|
+
import { normalizeDateVariableExactValue, type DateVariableComponentProps } from './dateValue';
|
|
28
|
+
|
|
29
|
+
const DATE_PRESET_NODES: Array<{ name: CtxDatePreset; title: string }> = [
|
|
30
|
+
{ name: 'now', title: 'Now' },
|
|
31
|
+
{ name: 'today', title: 'Today' },
|
|
32
|
+
{ name: 'yesterday', title: 'Yesterday' },
|
|
33
|
+
{ name: 'tomorrow', title: 'Tomorrow' },
|
|
34
|
+
{ name: 'thisWeek', title: 'This Week' },
|
|
35
|
+
{ name: 'lastWeek', title: 'Last Week' },
|
|
36
|
+
{ name: 'nextWeek', title: 'Next Week' },
|
|
37
|
+
{ name: 'thisMonth', title: 'This Month' },
|
|
38
|
+
{ name: 'lastMonth', title: 'Last Month' },
|
|
39
|
+
{ name: 'nextMonth', title: 'Next Month' },
|
|
40
|
+
{ name: 'thisQuarter', title: 'This Quarter' },
|
|
41
|
+
{ name: 'lastQuarter', title: 'Last Quarter' },
|
|
42
|
+
{ name: 'nextQuarter', title: 'Next Quarter' },
|
|
43
|
+
{ name: 'thisYear', title: 'This Year' },
|
|
44
|
+
{ name: 'lastYear', title: 'Last Year' },
|
|
45
|
+
{ name: 'nextYear', title: 'Next Year' },
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
type ValueEditorComponent = React.ComponentType<{
|
|
49
|
+
value?: unknown;
|
|
50
|
+
onChange?: (value: unknown) => void;
|
|
51
|
+
style?: React.CSSProperties;
|
|
52
|
+
disabled?: boolean;
|
|
53
|
+
}>;
|
|
54
|
+
|
|
55
|
+
const DATE_VARIABLE_CONFIG_MARK = Symbol('field-value-date-variable-config');
|
|
56
|
+
|
|
57
|
+
type DateVariableEditConfig = CtxDateExpressionConfig & {
|
|
58
|
+
[DATE_VARIABLE_CONFIG_MARK]: 'editable' | 'restored';
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export type FieldValueVariableInputProps = Omit<
|
|
62
|
+
VariableInputProps,
|
|
63
|
+
'value' | 'onChange' | 'metaTree' | 'converters'
|
|
64
|
+
> & {
|
|
65
|
+
value?: unknown;
|
|
66
|
+
onChange: (value: unknown) => void;
|
|
67
|
+
baseMetaTree: MetaTreeNode[] | (() => MetaTreeNode[] | Promise<MetaTreeNode[]>);
|
|
68
|
+
constantComponent: ValueEditorComponent;
|
|
69
|
+
nullComponent: ValueEditorComponent;
|
|
70
|
+
runJSComponent: ValueEditorComponent;
|
|
71
|
+
isDateLikeField: boolean;
|
|
72
|
+
dateComponentProps: DateVariableComponentProps;
|
|
73
|
+
allowRunJS?: boolean;
|
|
74
|
+
converters?: VariableInputProps['converters'];
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
function createDateVariableEditConfig(
|
|
78
|
+
config: CtxDateExpressionConfig,
|
|
79
|
+
source: DateVariableEditConfig[typeof DATE_VARIABLE_CONFIG_MARK] = 'editable',
|
|
80
|
+
): DateVariableEditConfig {
|
|
81
|
+
return { ...config, [DATE_VARIABLE_CONFIG_MARK]: source } as DateVariableEditConfig;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function isDateVariableEditConfig(value: unknown): value is DateVariableEditConfig {
|
|
85
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
|
|
86
|
+
const source = (value as Partial<DateVariableEditConfig>)[DATE_VARIABLE_CONFIG_MARK];
|
|
87
|
+
return source === 'editable' || source === 'restored';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function getDateNodeName(config: CtxDateExpressionConfig): string {
|
|
91
|
+
if (config.kind === 'exact') return 'exact';
|
|
92
|
+
if (config.kind === 'relative') return config.direction;
|
|
93
|
+
return config.preset;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function isDayjsValue(value: unknown): value is Dayjs {
|
|
97
|
+
return dayjs.isDayjs(value);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function createInitialDateConfig(
|
|
101
|
+
nodeName: string,
|
|
102
|
+
isDateLikeField: boolean,
|
|
103
|
+
componentProps: DateVariableComponentProps,
|
|
104
|
+
): DateVariableEditConfig | undefined {
|
|
105
|
+
let config: CtxDateExpressionConfig | undefined;
|
|
106
|
+
|
|
107
|
+
if (nodeName === 'exact') {
|
|
108
|
+
const normalized = normalizeDateVariableExactValue(dayjs(), componentProps);
|
|
109
|
+
const value = isDayjsValue(normalized) ? normalized.format('YYYY-MM-DD') : String(normalized || '');
|
|
110
|
+
config = { kind: 'exact', value };
|
|
111
|
+
} else if (nodeName === 'past' || nodeName === 'next') {
|
|
112
|
+
config = { kind: 'relative', direction: nodeName, amount: 1, unit: 'day' };
|
|
113
|
+
} else if (
|
|
114
|
+
DATE_PRESET_NODES.some((item) => item.name === nodeName) &&
|
|
115
|
+
!(nodeName === 'now' && componentProps.exactNormalizeMode === 'date')
|
|
116
|
+
) {
|
|
117
|
+
config = { kind: 'preset', preset: nodeName as CtxDatePreset };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (!config) return undefined;
|
|
121
|
+
const initialConfig = isDateLikeField ? config : { ...config, format: getDefaultDateVariableFormat(config) };
|
|
122
|
+
return createDateVariableEditConfig(initialConfig);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function normalizeDateConfigForStore(
|
|
126
|
+
config: CtxDateExpressionConfig,
|
|
127
|
+
componentProps: DateVariableComponentProps,
|
|
128
|
+
omitFormat: boolean,
|
|
129
|
+
): CtxDateExpressionConfig {
|
|
130
|
+
const rawFormat = typeof config.format === 'string' ? config.format : '';
|
|
131
|
+
const normalizedConfig = omitFormat
|
|
132
|
+
? { ...config, format: undefined }
|
|
133
|
+
: { ...config, format: rawFormat.trim() ? rawFormat : getDefaultDateVariableFormat(config) };
|
|
134
|
+
if (normalizedConfig.kind !== 'exact') return normalizedConfig;
|
|
135
|
+
const normalized = normalizeDateVariableExactValue(normalizedConfig.value, componentProps);
|
|
136
|
+
if (Array.isArray(normalized)) {
|
|
137
|
+
return { ...normalizedConfig, value: [String(normalized[0]), String(normalized[1])] };
|
|
138
|
+
}
|
|
139
|
+
return { ...normalizedConfig, value: String(normalized || '') };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export const FieldValueVariableInput: React.FC<FieldValueVariableInputProps> = ({
|
|
143
|
+
value,
|
|
144
|
+
onChange,
|
|
145
|
+
baseMetaTree,
|
|
146
|
+
constantComponent: ConstantComponent,
|
|
147
|
+
nullComponent: NullComponent,
|
|
148
|
+
runJSComponent: RunJSComponent,
|
|
149
|
+
isDateLikeField,
|
|
150
|
+
dateComponentProps,
|
|
151
|
+
allowRunJS = true,
|
|
152
|
+
converters,
|
|
153
|
+
clearValue = '',
|
|
154
|
+
disabled = false,
|
|
155
|
+
...variableInputProps
|
|
156
|
+
}) => {
|
|
157
|
+
const DateEditor = React.useMemo<ValueEditorComponent>(() => {
|
|
158
|
+
const Component: ValueEditorComponent = (props) => (
|
|
159
|
+
<DateVariableEditor
|
|
160
|
+
value={isDateVariableEditConfig(props.value) ? props.value : undefined}
|
|
161
|
+
onChange={(nextConfig) => props.onChange?.(createDateVariableEditConfig(nextConfig))}
|
|
162
|
+
isDateLikeField={isDateLikeField}
|
|
163
|
+
dateComponentProps={dateComponentProps}
|
|
164
|
+
style={props.style}
|
|
165
|
+
disabled={props.disabled}
|
|
166
|
+
/>
|
|
167
|
+
);
|
|
168
|
+
return Component;
|
|
169
|
+
}, [dateComponentProps, isDateLikeField]);
|
|
170
|
+
|
|
171
|
+
const parsedDateConfig = parseCtxDateExpressionConfig(value);
|
|
172
|
+
const restoreLegacyNowForPureDate =
|
|
173
|
+
dateComponentProps.exactNormalizeMode === 'date' &&
|
|
174
|
+
parsedDateConfig?.kind === 'preset' &&
|
|
175
|
+
parsedDateConfig.preset === 'now';
|
|
176
|
+
|
|
177
|
+
const metaTree = React.useMemo<() => Promise<MetaTreeNode[]>>(() => {
|
|
178
|
+
return async () => {
|
|
179
|
+
const base = typeof baseMetaTree === 'function' ? await baseMetaTree() : baseMetaTree;
|
|
180
|
+
const presetNodes =
|
|
181
|
+
dateComponentProps.exactNormalizeMode === 'date' && !restoreLegacyNowForPureDate
|
|
182
|
+
? DATE_PRESET_NODES.filter((item) => item.name !== 'now')
|
|
183
|
+
: DATE_PRESET_NODES;
|
|
184
|
+
const dateChildren: MetaTreeNode[] = [
|
|
185
|
+
{
|
|
186
|
+
title: tExpr('Exact day'),
|
|
187
|
+
name: 'exact',
|
|
188
|
+
type: 'date',
|
|
189
|
+
paths: ['date', 'exact'],
|
|
190
|
+
render: (props) => <DateEditor {...props} />,
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
title: tExpr('Past'),
|
|
194
|
+
name: 'past',
|
|
195
|
+
type: 'date',
|
|
196
|
+
paths: ['date', 'past'],
|
|
197
|
+
render: (props) => <DateEditor {...props} />,
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
title: tExpr('Next'),
|
|
201
|
+
name: 'next',
|
|
202
|
+
type: 'date',
|
|
203
|
+
paths: ['date', 'next'],
|
|
204
|
+
render: (props) => <DateEditor {...props} />,
|
|
205
|
+
},
|
|
206
|
+
...presetNodes.map<MetaTreeNode>(({ name, title }) => ({
|
|
207
|
+
title: tExpr(title),
|
|
208
|
+
name,
|
|
209
|
+
type: 'date',
|
|
210
|
+
paths: ['date', name],
|
|
211
|
+
...(name === 'now' && dateComponentProps.exactNormalizeMode === 'date'
|
|
212
|
+
? { disabled: true, selectable: false }
|
|
213
|
+
: {}),
|
|
214
|
+
render: (props) => <DateEditor {...props} />,
|
|
215
|
+
})),
|
|
216
|
+
];
|
|
217
|
+
|
|
218
|
+
const specialNodes: MetaTreeNode[] = [
|
|
219
|
+
{
|
|
220
|
+
title: tExpr('Constant'),
|
|
221
|
+
name: 'constant',
|
|
222
|
+
type: 'string',
|
|
223
|
+
paths: ['constant'],
|
|
224
|
+
render: (props) => <ConstantComponent {...props} />,
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
title: tExpr('Null'),
|
|
228
|
+
name: 'null',
|
|
229
|
+
type: 'object',
|
|
230
|
+
paths: ['null'],
|
|
231
|
+
render: (props) => <NullComponent {...props} />,
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
title: tExpr('Date'),
|
|
235
|
+
name: 'date',
|
|
236
|
+
type: 'date',
|
|
237
|
+
paths: ['date'],
|
|
238
|
+
selectable: false,
|
|
239
|
+
children: dateChildren,
|
|
240
|
+
},
|
|
241
|
+
...(allowRunJS
|
|
242
|
+
? [
|
|
243
|
+
{
|
|
244
|
+
title: tExpr('RunJS'),
|
|
245
|
+
name: 'runjs',
|
|
246
|
+
type: 'object',
|
|
247
|
+
paths: ['runjs'],
|
|
248
|
+
render: (props) => <RunJSComponent {...props} />,
|
|
249
|
+
} satisfies MetaTreeNode,
|
|
250
|
+
]
|
|
251
|
+
: []),
|
|
252
|
+
];
|
|
253
|
+
return [...specialNodes, ...(Array.isArray(base) ? base : [])];
|
|
254
|
+
};
|
|
255
|
+
}, [
|
|
256
|
+
ConstantComponent,
|
|
257
|
+
DateEditor,
|
|
258
|
+
NullComponent,
|
|
259
|
+
RunJSComponent,
|
|
260
|
+
allowRunJS,
|
|
261
|
+
baseMetaTree,
|
|
262
|
+
dateComponentProps.exactNormalizeMode,
|
|
263
|
+
restoreLegacyNowForPureDate,
|
|
264
|
+
]);
|
|
265
|
+
|
|
266
|
+
const displayValue = parsedDateConfig ? createDateVariableEditConfig(parsedDateConfig, 'restored') : value;
|
|
267
|
+
|
|
268
|
+
const handleChange = React.useCallback(
|
|
269
|
+
(nextValue: unknown) => {
|
|
270
|
+
if (disabled) return;
|
|
271
|
+
if (!isDateVariableEditConfig(nextValue)) {
|
|
272
|
+
onChange(nextValue);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
if (nextValue[DATE_VARIABLE_CONFIG_MARK] === 'restored') return;
|
|
276
|
+
const normalized = normalizeDateConfigForStore(nextValue, dateComponentProps, isDateLikeField);
|
|
277
|
+
onChange(serializeCtxDateExpressionConfig(normalized) || '');
|
|
278
|
+
},
|
|
279
|
+
[dateComponentProps, disabled, isDateLikeField, onChange],
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
return (
|
|
283
|
+
<VariableInput
|
|
284
|
+
{...variableInputProps}
|
|
285
|
+
value={displayValue}
|
|
286
|
+
onChange={handleChange}
|
|
287
|
+
metaTree={metaTree}
|
|
288
|
+
clearValue={clearValue}
|
|
289
|
+
disabled={disabled}
|
|
290
|
+
converters={{
|
|
291
|
+
renderInputComponent: (meta) => {
|
|
292
|
+
const external = converters?.renderInputComponent?.(meta ?? null);
|
|
293
|
+
if (external) return external;
|
|
294
|
+
const firstPath = meta?.paths?.[0];
|
|
295
|
+
if (firstPath === 'constant') return ConstantComponent;
|
|
296
|
+
if (firstPath === 'null') return NullComponent;
|
|
297
|
+
if (firstPath === 'date') return DateEditor;
|
|
298
|
+
if (allowRunJS && firstPath === 'runjs') return RunJSComponent;
|
|
299
|
+
return null;
|
|
300
|
+
},
|
|
301
|
+
resolveValueFromPath: (item) => {
|
|
302
|
+
const external = converters?.resolveValueFromPath?.(item);
|
|
303
|
+
if (external !== undefined) return external;
|
|
304
|
+
const firstPath = item?.paths?.[0];
|
|
305
|
+
if (firstPath === 'constant') return '';
|
|
306
|
+
if (firstPath === 'null') return null;
|
|
307
|
+
if (firstPath === 'date') {
|
|
308
|
+
return createInitialDateConfig(item.paths[1], isDateLikeField, dateComponentProps);
|
|
309
|
+
}
|
|
310
|
+
if (allowRunJS && firstPath === 'runjs') return { code: '', version: 'v2' };
|
|
311
|
+
return undefined;
|
|
312
|
+
},
|
|
313
|
+
resolvePathFromValue: (currentValue) => {
|
|
314
|
+
const external = converters?.resolvePathFromValue?.(currentValue);
|
|
315
|
+
if (external !== undefined) return external;
|
|
316
|
+
if (currentValue === null) return ['null'];
|
|
317
|
+
if (allowRunJS && isRunJSValue(currentValue)) return ['runjs'];
|
|
318
|
+
if (isDateVariableEditConfig(currentValue)) return ['date', getDateNodeName(currentValue)];
|
|
319
|
+
return typeof currentValue === 'string' && isVariableExpression(currentValue)
|
|
320
|
+
? parseValueToPath(currentValue)
|
|
321
|
+
: ['constant'];
|
|
322
|
+
},
|
|
323
|
+
}}
|
|
324
|
+
/>
|
|
325
|
+
);
|
|
326
|
+
};
|