@nocobase/client-v2 2.3.0-alpha.1 → 2.3.0-beta.11
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/components/form/ScanInput/useCodeScanner.d.ts +12 -2
- package/es/components/form/ScanInput/zxingWasmDecoder.d.ts +9 -0
- package/es/flow/actions/linkageRules.d.ts +25 -0
- package/es/flow/components/FieldAssignValueInput.d.ts +6 -30
- package/es/flow/components/FlowRoute.d.ts +2 -2
- package/es/flow/components/field-value-variable/DateVariableEditor.d.ts +24 -0
- package/es/flow/components/field-value-variable/FieldValueVariableInput.d.ts +32 -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/filter/VariableFilterItem.d.ts +7 -0
- package/es/flow/components/placeholders/BlockPlaceholder.d.ts +1 -0
- package/es/flow/internal/registerDeviceTypeContext.d.ts +10 -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/ActionModelCore.d.ts +4 -2
- package/es/flow/models/base/BlockGridModel.d.ts +3 -0
- 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 +12 -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/models/fields/DateTimeFieldModel/dateLimit.d.ts +15 -7
- package/es/flow-compat/passwordUtils.d.ts +1 -1
- package/es/index.d.ts +1 -0
- package/es/index.mjs +148 -145
- package/es/ui-operation/index.d.ts +15 -0
- package/es/ui-operation/ui-operation-codec.d.ts +10 -0
- package/lib/index.js +170 -167
- package/lib/locale/languageCodes.js +1 -0
- package/package.json +10 -8
- package/src/APIClient.ts +92 -0
- package/src/Application.tsx +3 -1
- package/src/__tests__/APIClient.test.tsx +58 -0
- package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +7 -2
- package/src/__tests__/settings-center.test.tsx +270 -6
- package/src/acl/ACLProvider.tsx +2 -0
- package/src/acl/__tests__/ACLProvider.test.tsx +92 -0
- package/src/collection-manager/__tests__/field-configure.test.ts +52 -0
- package/src/collection-manager/field-configure.ts +2 -2
- package/src/components/form/ScanInput/CodeScanner.tsx +64 -35
- package/src/components/form/ScanInput/__tests__/CodeScanner.test.tsx +107 -0
- package/src/components/form/ScanInput/__tests__/useCodeScanner.test.tsx +294 -24
- package/src/components/form/ScanInput/__tests__/zxingWasmDecoder.test.ts +78 -0
- package/src/components/form/ScanInput/useCodeScanner.ts +281 -23
- package/src/components/form/ScanInput/zxingWasmDecoder.ts +64 -0
- 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/__tests__/FlowRoute.test.tsx +126 -8
- package/src/flow/__tests__/PluginFlowEngine.test.ts +58 -0
- package/src/flow/actions/__tests__/actionLinkageRules.forkProps.test.ts +314 -0
- package/src/flow/actions/__tests__/dataScopeFormValueClear.test.ts +102 -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/__tests__/validation.test.ts +48 -0
- package/src/flow/actions/linkageRules.tsx +59 -16
- 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/admin-shell/admin-layout/__tests__/TopbarActionsBar.test.tsx +42 -23
- package/src/flow/common/Markdown/style.ts +4 -0
- package/src/flow/components/BlockItemCard.tsx +2 -2
- package/src/flow/components/FieldAssignValueInput.tsx +42 -621
- package/src/flow/components/FlowRoute.tsx +71 -29
- package/src/flow/components/field-value-variable/DateVariableEditor.tsx +181 -0
- package/src/flow/components/field-value-variable/FieldValueVariableInput.tsx +335 -0
- package/src/flow/components/field-value-variable/__tests__/FieldValueVariableInput.test.tsx +389 -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 +23 -6
- package/src/flow/components/filter/__tests__/VariableFilterItem.rightMetaTree.test.tsx +158 -0
- 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/index.ts +2 -0
- package/src/flow/internal/components/Markdown/DisplayMarkdown.tsx +14 -9
- package/src/flow/internal/components/Markdown/__tests__/DisplayMarkdown.test.tsx +46 -0
- package/src/flow/internal/registerDeviceTypeContext.ts +39 -0
- 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/ActionModelCore.tsx +11 -2
- package/src/flow/models/base/BlockGridModel.tsx +26 -0
- package/src/flow/models/base/CollectionBlockModel.tsx +38 -3
- package/src/flow/models/base/GridModel.tsx +0 -1
- package/src/flow/models/base/PageModel/PageTabModel.tsx +67 -17
- package/src/flow/models/base/PageModel/__tests__/PageTabModel.test.ts +509 -12
- package/src/flow/models/base/__tests__/ActionModelCore.render.test.tsx +49 -0
- package/src/flow/models/base/__tests__/BlockGridModel.selectSceneActivation.test.ts +124 -0
- package/src/flow/models/base/__tests__/CollectionBlockModel.addAppends.test.ts +41 -0
- package/src/flow/models/base/__tests__/CollectionBlockModel.initialBeforeRenderRefresh.test.ts +125 -9
- package/src/flow/models/blocks/assign-form/AssignFormGridModel.tsx +22 -1
- package/src/flow/models/blocks/assign-form/AssignFormItemModel.tsx +36 -50
- package/src/flow/models/blocks/assign-form/__tests__/assignFieldValuesFlow.editor.test.tsx +140 -0
- 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/FilterFormBlockModel.tsx +27 -3
- 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__/FilterFormGridModel.toggleFormFieldsCollapse.test.ts +29 -0
- 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-form/__tests__/defaultValues.wiring.test.ts +32 -4
- package/src/flow/models/blocks/filter-form/fields/FieldComponentProps.tsx +1 -1
- package/src/flow/models/blocks/filter-form/fields/__tests__/FieldComponentProps.options.test.tsx +61 -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 +73 -5
- package/src/flow/models/blocks/form/FormGridModel.tsx +4 -0
- package/src/flow/models/blocks/form/__tests__/FormBlockModel.test.tsx +153 -2
- package/src/flow/models/blocks/form/__tests__/popupLinkage.test.tsx +175 -0
- 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/js-block/JSBlock.tsx +1 -1
- package/src/flow/models/blocks/table/JSColumnModel.tsx +10 -1
- package/src/flow/models/blocks/table/TableBlockModel.tsx +28 -2
- package/src/flow/models/blocks/table/TableColumnModel.tsx +48 -9
- 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/__tests__/TableBlockModel.quickEditRefresh.test.ts +12 -0
- package/src/flow/models/blocks/table/__tests__/TableColumnModel.test.tsx +33 -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 +12 -3
- 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 +29 -3
- package/src/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.tsx +9 -136
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableField.tsx +5 -1
- package/src/flow/models/fields/AssociationFieldModel/__tests__/RecordPickerFieldModel.itemContext.test.ts +191 -3
- package/src/flow/models/fields/AssociationFieldModel/recordSelectShared.tsx +156 -0
- package/src/flow/models/fields/DateTimeFieldModel/__tests__/DateTimeNoTzFieldModel.dateLimit.test.tsx +149 -0
- package/src/flow/models/fields/DateTimeFieldModel/dateLimit.ts +149 -113
- package/src/flow/models/fields/DisplayAssociationField/DisplaySubTableFieldModel.tsx +42 -7
- package/src/flow/models/fields/DisplayAssociationField/__tests__/DisplaySubTableFieldModel.test.tsx +351 -0
- package/src/flow/models/fields/DisplayHtmlFieldModel.tsx +2 -1
- package/src/flow/models/fields/DisplayNumberFieldModel.tsx +1 -1
- package/src/flow/models/fields/JsonFieldModel.tsx +31 -8
- package/src/flow/models/fields/NumberFieldModel.tsx +1 -1
- package/src/flow/models/fields/RichTextFieldModel/__tests__/RichTextFieldModel.test.tsx +74 -0
- package/src/flow/models/fields/RichTextFieldModel/index.tsx +27 -6
- package/src/flow/models/fields/__tests__/DisplayNumberFieldModel.test.ts +33 -0
- package/src/flow/models/fields/__tests__/JsonFieldModel.test.ts +82 -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/flow/models/topbar/TopbarActionModel.tsx +5 -5
- package/src/flow/utils/dataScopeFormValueClear.ts +4 -3
- package/src/index.ts +1 -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/locale/languageCodes.ts +1 -0
- package/src/settings-center/AdminSettingsLayout.tsx +23 -2
- package/src/ui-operation/index.ts +33 -0
- package/src/ui-operation/ui-operation-codec.ts +54 -0
|
@@ -8,9 +8,11 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { type FlowEngine, useFlowContext, useFlowEngine } from '@nocobase/flow-engine';
|
|
11
|
+
import { Button, Result } from 'antd';
|
|
11
12
|
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
|
12
|
-
import {
|
|
13
|
+
import { useTranslation } from 'react-i18next';
|
|
13
14
|
import { useLocation, useNavigate, useParams } from 'react-router-dom';
|
|
15
|
+
import { getModernClientPrefix, stripModernClientPrefix } from '../../authRedirect';
|
|
14
16
|
import { useApp } from '../../hooks/useApp';
|
|
15
17
|
import { NocoBaseDesktopRouteType, type NocoBaseDesktopRoute } from '../../flow-compat';
|
|
16
18
|
import {
|
|
@@ -22,12 +24,14 @@ import { getLayoutModel, type BaseLayoutModel } from '../admin-shell/BaseLayoutM
|
|
|
22
24
|
import { useLayoutRoutePage } from '../admin-shell/useLayoutRoutePage';
|
|
23
25
|
import { AppNotFound } from '../../components';
|
|
24
26
|
import { useKeepAlive } from '../../components/KeepAlive';
|
|
27
|
+
import { registerDeviceTypeContext } from '../internal/registerDeviceTypeContext';
|
|
25
28
|
|
|
26
29
|
type FlowRouteGuardState = {
|
|
27
30
|
pageUid?: string;
|
|
28
31
|
pending: boolean;
|
|
29
32
|
allowBridge: boolean;
|
|
30
33
|
notFound: boolean;
|
|
34
|
+
legacyPageUnsupported?: boolean;
|
|
31
35
|
};
|
|
32
36
|
|
|
33
37
|
export type LegacyPageBehavior = 'redirect' | 'notFound' | 'bridge';
|
|
@@ -156,31 +160,9 @@ const BridgeFlowRoute = ({
|
|
|
156
160
|
const layoutContentRef = useRef<HTMLDivElement>(null);
|
|
157
161
|
|
|
158
162
|
useEffect(() => {
|
|
159
|
-
flowEngine.context.
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
meta: {
|
|
163
|
-
type: 'string',
|
|
164
|
-
title: flowEngine.translate('Current device type'),
|
|
165
|
-
interface: 'select',
|
|
166
|
-
uiSchema: {
|
|
167
|
-
enum: [
|
|
168
|
-
{ label: flowEngine.translate('Computer'), value: 'computer' },
|
|
169
|
-
{ label: flowEngine.translate('Mobile'), value: 'mobile' },
|
|
170
|
-
{ label: flowEngine.translate('Tablet'), value: 'tablet' },
|
|
171
|
-
{ label: flowEngine.translate('SmartTv'), value: 'smarttv' },
|
|
172
|
-
{ label: flowEngine.translate('Console'), value: 'console' },
|
|
173
|
-
{ label: flowEngine.translate('Wearable'), value: 'wearable' },
|
|
174
|
-
{ label: flowEngine.translate('Embedded'), value: 'embedded' },
|
|
175
|
-
],
|
|
176
|
-
'x-component': 'Select',
|
|
177
|
-
},
|
|
178
|
-
},
|
|
179
|
-
info: {
|
|
180
|
-
description: 'Current device type (computer/mobile/tablet/...).',
|
|
181
|
-
detail: 'string',
|
|
182
|
-
},
|
|
183
|
-
});
|
|
163
|
+
if (!flowEngine.context.getPropertyOptions('deviceType')) {
|
|
164
|
+
registerDeviceTypeContext(flowEngine);
|
|
165
|
+
}
|
|
184
166
|
}, [flowEngine]);
|
|
185
167
|
|
|
186
168
|
useLayoutRoutePage({
|
|
@@ -195,11 +177,58 @@ const BridgeFlowRoute = ({
|
|
|
195
177
|
return <div ref={layoutContentRef} />;
|
|
196
178
|
};
|
|
197
179
|
|
|
180
|
+
type RouteLocation = {
|
|
181
|
+
pathname: string;
|
|
182
|
+
search: string;
|
|
183
|
+
hash: string;
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const getLegacyPageHref = (app: { getPublicPath: () => string }, location: RouteLocation) => {
|
|
187
|
+
const modernPublicPath = app.getPublicPath();
|
|
188
|
+
const browserLocationMatchesPublicPath = window.location.pathname.startsWith(modernPublicPath);
|
|
189
|
+
const currentLocation = browserLocationMatchesPublicPath ? window.location : location;
|
|
190
|
+
const pathWithinModernClient = currentLocation.pathname.startsWith(modernPublicPath)
|
|
191
|
+
? currentLocation.pathname.slice(modernPublicPath.length)
|
|
192
|
+
: currentLocation.pathname.replace(/^\/+/, '');
|
|
193
|
+
return `${stripModernClientPrefix(modernPublicPath)}${pathWithinModernClient}${currentLocation.search}${
|
|
194
|
+
currentLocation.hash
|
|
195
|
+
}`;
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const LegacyPageUnsupported = ({
|
|
199
|
+
app,
|
|
200
|
+
location,
|
|
201
|
+
}: {
|
|
202
|
+
app: { getPublicPath: () => string };
|
|
203
|
+
location: RouteLocation;
|
|
204
|
+
}) => {
|
|
205
|
+
const { t } = useTranslation();
|
|
206
|
+
const modernClientPath = `/${getModernClientPrefix()}/`;
|
|
207
|
+
const withModernClientPath = (message: string) => message.replaceAll('{{modernClientPath}}', modernClientPath);
|
|
208
|
+
|
|
209
|
+
return (
|
|
210
|
+
<Result
|
|
211
|
+
status="warning"
|
|
212
|
+
title={withModernClientPath(t('This page is not supported in the {{modernClientPath}} branch'))}
|
|
213
|
+
subTitle={withModernClientPath(
|
|
214
|
+
t(
|
|
215
|
+
'The {{modernClientPath}} branch only supports new pages. This page is a legacy page. Please open it from the original entry.',
|
|
216
|
+
),
|
|
217
|
+
)}
|
|
218
|
+
extra={
|
|
219
|
+
<Button href={getLegacyPageHref(app, location)} type="primary">
|
|
220
|
+
{t('Open from the original entry')}
|
|
221
|
+
</Button>
|
|
222
|
+
}
|
|
223
|
+
/>
|
|
224
|
+
);
|
|
225
|
+
};
|
|
226
|
+
|
|
198
227
|
/**
|
|
199
228
|
* 管理后台动态页面路由组件。
|
|
200
229
|
*
|
|
201
|
-
* 负责读取当前路由页面 UID
|
|
202
|
-
*
|
|
230
|
+
* 负责读取当前路由页面 UID,并把页面生命周期桥接到 AdminLayout host model。
|
|
231
|
+
* 设备变量通常由 PluginFlowEngine 共享初始化提供;独立渲染时会在挂载后补充注册。
|
|
203
232
|
*
|
|
204
233
|
* @example
|
|
205
234
|
* ```tsx
|
|
@@ -377,7 +406,13 @@ const FlowRoute = (props: FlowRouteProps = {}) => {
|
|
|
377
406
|
|
|
378
407
|
if (target.reason === 'unsupportedV2Runtime') {
|
|
379
408
|
if (active && requestId === requestIdRef.current) {
|
|
380
|
-
setGuardState({
|
|
409
|
+
setGuardState({
|
|
410
|
+
pageUid,
|
|
411
|
+
pending: false,
|
|
412
|
+
allowBridge: false,
|
|
413
|
+
notFound: false,
|
|
414
|
+
legacyPageUnsupported: true,
|
|
415
|
+
});
|
|
381
416
|
}
|
|
382
417
|
return;
|
|
383
418
|
}
|
|
@@ -414,6 +449,10 @@ const FlowRoute = (props: FlowRouteProps = {}) => {
|
|
|
414
449
|
return <AppNotFound />;
|
|
415
450
|
}
|
|
416
451
|
|
|
452
|
+
if (guardState.legacyPageUnsupported) {
|
|
453
|
+
return <LegacyPageUnsupported app={app} location={location} />;
|
|
454
|
+
}
|
|
455
|
+
|
|
417
456
|
if (!guardState.allowBridge) {
|
|
418
457
|
return null;
|
|
419
458
|
}
|
|
@@ -421,11 +460,14 @@ const FlowRoute = (props: FlowRouteProps = {}) => {
|
|
|
421
460
|
return <BridgeFlowRoute pageUid={pageUid} active={active} getLayoutModel={getLayoutModel} />;
|
|
422
461
|
}, [
|
|
423
462
|
active,
|
|
463
|
+
app,
|
|
424
464
|
getLayoutModel,
|
|
425
465
|
guardState.allowBridge,
|
|
466
|
+
guardState.legacyPageUnsupported,
|
|
426
467
|
guardState.notFound,
|
|
427
468
|
guardState.pageUid,
|
|
428
469
|
guardState.pending,
|
|
470
|
+
location,
|
|
429
471
|
pageUid,
|
|
430
472
|
]);
|
|
431
473
|
|
|
@@ -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,335 @@
|
|
|
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
|
+
allowDateVariables?: boolean;
|
|
75
|
+
converters?: VariableInputProps['converters'];
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
function createDateVariableEditConfig(
|
|
79
|
+
config: CtxDateExpressionConfig,
|
|
80
|
+
source: DateVariableEditConfig[typeof DATE_VARIABLE_CONFIG_MARK] = 'editable',
|
|
81
|
+
): DateVariableEditConfig {
|
|
82
|
+
return { ...config, [DATE_VARIABLE_CONFIG_MARK]: source } as DateVariableEditConfig;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function isDateVariableEditConfig(value: unknown): value is DateVariableEditConfig {
|
|
86
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
|
|
87
|
+
const source = (value as Partial<DateVariableEditConfig>)[DATE_VARIABLE_CONFIG_MARK];
|
|
88
|
+
return source === 'editable' || source === 'restored';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function getDateNodeName(config: CtxDateExpressionConfig): string {
|
|
92
|
+
if (config.kind === 'exact') return 'exact';
|
|
93
|
+
if (config.kind === 'relative') return config.direction;
|
|
94
|
+
return config.preset;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function isDayjsValue(value: unknown): value is Dayjs {
|
|
98
|
+
return dayjs.isDayjs(value);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function createInitialDateConfig(
|
|
102
|
+
nodeName: string,
|
|
103
|
+
isDateLikeField: boolean,
|
|
104
|
+
componentProps: DateVariableComponentProps,
|
|
105
|
+
): DateVariableEditConfig | undefined {
|
|
106
|
+
let config: CtxDateExpressionConfig | undefined;
|
|
107
|
+
|
|
108
|
+
if (nodeName === 'exact') {
|
|
109
|
+
const normalized = normalizeDateVariableExactValue(dayjs(), componentProps);
|
|
110
|
+
const value = isDayjsValue(normalized) ? normalized.format('YYYY-MM-DD') : String(normalized || '');
|
|
111
|
+
config = { kind: 'exact', value };
|
|
112
|
+
} else if (nodeName === 'past' || nodeName === 'next') {
|
|
113
|
+
config = { kind: 'relative', direction: nodeName, amount: 1, unit: 'day' };
|
|
114
|
+
} else if (
|
|
115
|
+
DATE_PRESET_NODES.some((item) => item.name === nodeName) &&
|
|
116
|
+
!(nodeName === 'now' && componentProps.exactNormalizeMode === 'date')
|
|
117
|
+
) {
|
|
118
|
+
config = { kind: 'preset', preset: nodeName as CtxDatePreset };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (!config) return undefined;
|
|
122
|
+
const initialConfig = isDateLikeField ? config : { ...config, format: getDefaultDateVariableFormat(config) };
|
|
123
|
+
return createDateVariableEditConfig(initialConfig);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function normalizeDateConfigForStore(
|
|
127
|
+
config: CtxDateExpressionConfig,
|
|
128
|
+
componentProps: DateVariableComponentProps,
|
|
129
|
+
omitFormat: boolean,
|
|
130
|
+
): CtxDateExpressionConfig {
|
|
131
|
+
const rawFormat = typeof config.format === 'string' ? config.format : '';
|
|
132
|
+
const normalizedConfig = omitFormat
|
|
133
|
+
? { ...config, format: undefined }
|
|
134
|
+
: { ...config, format: rawFormat.trim() ? rawFormat : getDefaultDateVariableFormat(config) };
|
|
135
|
+
if (normalizedConfig.kind !== 'exact') return normalizedConfig;
|
|
136
|
+
const normalized = normalizeDateVariableExactValue(normalizedConfig.value, componentProps);
|
|
137
|
+
if (Array.isArray(normalized)) {
|
|
138
|
+
return { ...normalizedConfig, value: [String(normalized[0]), String(normalized[1])] };
|
|
139
|
+
}
|
|
140
|
+
return { ...normalizedConfig, value: String(normalized || '') };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export const FieldValueVariableInput: React.FC<FieldValueVariableInputProps> = ({
|
|
144
|
+
value,
|
|
145
|
+
onChange,
|
|
146
|
+
baseMetaTree,
|
|
147
|
+
constantComponent: ConstantComponent,
|
|
148
|
+
nullComponent: NullComponent,
|
|
149
|
+
runJSComponent: RunJSComponent,
|
|
150
|
+
isDateLikeField,
|
|
151
|
+
dateComponentProps,
|
|
152
|
+
allowRunJS = true,
|
|
153
|
+
allowDateVariables = true,
|
|
154
|
+
converters,
|
|
155
|
+
clearValue = '',
|
|
156
|
+
disabled = false,
|
|
157
|
+
...variableInputProps
|
|
158
|
+
}) => {
|
|
159
|
+
const DateEditor = React.useMemo<ValueEditorComponent>(() => {
|
|
160
|
+
const Component: ValueEditorComponent = (props) => (
|
|
161
|
+
<DateVariableEditor
|
|
162
|
+
value={isDateVariableEditConfig(props.value) ? props.value : undefined}
|
|
163
|
+
onChange={(nextConfig) => props.onChange?.(createDateVariableEditConfig(nextConfig))}
|
|
164
|
+
isDateLikeField={isDateLikeField}
|
|
165
|
+
dateComponentProps={dateComponentProps}
|
|
166
|
+
style={props.style}
|
|
167
|
+
disabled={props.disabled}
|
|
168
|
+
/>
|
|
169
|
+
);
|
|
170
|
+
return Component;
|
|
171
|
+
}, [dateComponentProps, isDateLikeField]);
|
|
172
|
+
|
|
173
|
+
const parsedDateConfig = allowDateVariables ? parseCtxDateExpressionConfig(value) : undefined;
|
|
174
|
+
const restoreLegacyNowForPureDate =
|
|
175
|
+
dateComponentProps.exactNormalizeMode === 'date' &&
|
|
176
|
+
parsedDateConfig?.kind === 'preset' &&
|
|
177
|
+
parsedDateConfig.preset === 'now';
|
|
178
|
+
|
|
179
|
+
const metaTree = React.useMemo<() => Promise<MetaTreeNode[]>>(() => {
|
|
180
|
+
return async () => {
|
|
181
|
+
const base = typeof baseMetaTree === 'function' ? await baseMetaTree() : baseMetaTree;
|
|
182
|
+
const presetNodes =
|
|
183
|
+
dateComponentProps.exactNormalizeMode === 'date' && !restoreLegacyNowForPureDate
|
|
184
|
+
? DATE_PRESET_NODES.filter((item) => item.name !== 'now')
|
|
185
|
+
: DATE_PRESET_NODES;
|
|
186
|
+
const dateChildren: MetaTreeNode[] = [
|
|
187
|
+
{
|
|
188
|
+
title: tExpr('Exact day'),
|
|
189
|
+
name: 'exact',
|
|
190
|
+
type: 'date',
|
|
191
|
+
paths: ['date', 'exact'],
|
|
192
|
+
render: (props) => <DateEditor {...props} />,
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
title: tExpr('Past'),
|
|
196
|
+
name: 'past',
|
|
197
|
+
type: 'date',
|
|
198
|
+
paths: ['date', 'past'],
|
|
199
|
+
render: (props) => <DateEditor {...props} />,
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
title: tExpr('Next'),
|
|
203
|
+
name: 'next',
|
|
204
|
+
type: 'date',
|
|
205
|
+
paths: ['date', 'next'],
|
|
206
|
+
render: (props) => <DateEditor {...props} />,
|
|
207
|
+
},
|
|
208
|
+
...presetNodes.map<MetaTreeNode>(({ name, title }) => ({
|
|
209
|
+
title: tExpr(title),
|
|
210
|
+
name,
|
|
211
|
+
type: 'date',
|
|
212
|
+
paths: ['date', name],
|
|
213
|
+
...(name === 'now' && dateComponentProps.exactNormalizeMode === 'date'
|
|
214
|
+
? { disabled: true, selectable: false }
|
|
215
|
+
: {}),
|
|
216
|
+
render: (props) => <DateEditor {...props} />,
|
|
217
|
+
})),
|
|
218
|
+
];
|
|
219
|
+
|
|
220
|
+
const specialNodes: MetaTreeNode[] = [
|
|
221
|
+
{
|
|
222
|
+
title: tExpr('Constant'),
|
|
223
|
+
name: 'constant',
|
|
224
|
+
type: 'string',
|
|
225
|
+
paths: ['constant'],
|
|
226
|
+
render: (props) => <ConstantComponent {...props} />,
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
title: tExpr('Null'),
|
|
230
|
+
name: 'null',
|
|
231
|
+
type: 'object',
|
|
232
|
+
paths: ['null'],
|
|
233
|
+
render: (props) => <NullComponent {...props} />,
|
|
234
|
+
},
|
|
235
|
+
...(allowDateVariables
|
|
236
|
+
? [
|
|
237
|
+
{
|
|
238
|
+
title: tExpr('Date'),
|
|
239
|
+
name: 'date',
|
|
240
|
+
type: 'date',
|
|
241
|
+
paths: ['date'],
|
|
242
|
+
selectable: false,
|
|
243
|
+
children: dateChildren,
|
|
244
|
+
} satisfies MetaTreeNode,
|
|
245
|
+
]
|
|
246
|
+
: []),
|
|
247
|
+
...(allowRunJS
|
|
248
|
+
? [
|
|
249
|
+
{
|
|
250
|
+
title: tExpr('RunJS'),
|
|
251
|
+
name: 'runjs',
|
|
252
|
+
type: 'object',
|
|
253
|
+
paths: ['runjs'],
|
|
254
|
+
render: (props) => <RunJSComponent {...props} />,
|
|
255
|
+
} satisfies MetaTreeNode,
|
|
256
|
+
]
|
|
257
|
+
: []),
|
|
258
|
+
];
|
|
259
|
+
return [...specialNodes, ...(Array.isArray(base) ? base : [])];
|
|
260
|
+
};
|
|
261
|
+
}, [
|
|
262
|
+
ConstantComponent,
|
|
263
|
+
DateEditor,
|
|
264
|
+
NullComponent,
|
|
265
|
+
RunJSComponent,
|
|
266
|
+
allowDateVariables,
|
|
267
|
+
allowRunJS,
|
|
268
|
+
baseMetaTree,
|
|
269
|
+
dateComponentProps.exactNormalizeMode,
|
|
270
|
+
restoreLegacyNowForPureDate,
|
|
271
|
+
]);
|
|
272
|
+
|
|
273
|
+
const displayValue = parsedDateConfig ? createDateVariableEditConfig(parsedDateConfig, 'restored') : value;
|
|
274
|
+
|
|
275
|
+
const handleChange = React.useCallback(
|
|
276
|
+
(nextValue: unknown) => {
|
|
277
|
+
if (disabled) return;
|
|
278
|
+
if (!isDateVariableEditConfig(nextValue)) {
|
|
279
|
+
onChange(nextValue);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
if (nextValue[DATE_VARIABLE_CONFIG_MARK] === 'restored') return;
|
|
283
|
+
const normalized = normalizeDateConfigForStore(nextValue, dateComponentProps, isDateLikeField);
|
|
284
|
+
onChange(serializeCtxDateExpressionConfig(normalized) || '');
|
|
285
|
+
},
|
|
286
|
+
[dateComponentProps, disabled, isDateLikeField, onChange],
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
return (
|
|
290
|
+
<VariableInput
|
|
291
|
+
{...variableInputProps}
|
|
292
|
+
value={displayValue}
|
|
293
|
+
onChange={handleChange}
|
|
294
|
+
metaTree={metaTree}
|
|
295
|
+
clearValue={clearValue}
|
|
296
|
+
disabled={disabled}
|
|
297
|
+
converters={{
|
|
298
|
+
renderInputComponent: (meta) => {
|
|
299
|
+
const external = converters?.renderInputComponent?.(meta ?? null);
|
|
300
|
+
if (external) return external;
|
|
301
|
+
const firstPath = meta?.paths?.[0];
|
|
302
|
+
if (firstPath === 'constant') return ConstantComponent;
|
|
303
|
+
if (firstPath === 'null') return NullComponent;
|
|
304
|
+
if (allowDateVariables && firstPath === 'date') return DateEditor;
|
|
305
|
+
if (allowRunJS && firstPath === 'runjs') return RunJSComponent;
|
|
306
|
+
return null;
|
|
307
|
+
},
|
|
308
|
+
resolveValueFromPath: (item) => {
|
|
309
|
+
const external = converters?.resolveValueFromPath?.(item);
|
|
310
|
+
if (external !== undefined) return external;
|
|
311
|
+
const firstPath = item?.paths?.[0];
|
|
312
|
+
if (firstPath === 'constant') return '';
|
|
313
|
+
if (firstPath === 'null') return null;
|
|
314
|
+
if (allowDateVariables && firstPath === 'date') {
|
|
315
|
+
return createInitialDateConfig(item.paths[1], isDateLikeField, dateComponentProps);
|
|
316
|
+
}
|
|
317
|
+
if (allowRunJS && firstPath === 'runjs') return { code: '', version: 'v2' };
|
|
318
|
+
return undefined;
|
|
319
|
+
},
|
|
320
|
+
resolvePathFromValue: (currentValue) => {
|
|
321
|
+
const external = converters?.resolvePathFromValue?.(currentValue);
|
|
322
|
+
if (external !== undefined) return external;
|
|
323
|
+
if (currentValue === null) return ['null'];
|
|
324
|
+
if (allowRunJS && isRunJSValue(currentValue)) return ['runjs'];
|
|
325
|
+
if (allowDateVariables && isDateVariableEditConfig(currentValue)) {
|
|
326
|
+
return ['date', getDateNodeName(currentValue)];
|
|
327
|
+
}
|
|
328
|
+
return typeof currentValue === 'string' && isVariableExpression(currentValue)
|
|
329
|
+
? parseValueToPath(currentValue)
|
|
330
|
+
: ['constant'];
|
|
331
|
+
},
|
|
332
|
+
}}
|
|
333
|
+
/>
|
|
334
|
+
);
|
|
335
|
+
};
|