@nocobase/client-v2 2.2.0-beta.1 → 2.2.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/BaseApplication.d.ts +3 -0
- package/es/PluginSettingsManager.d.ts +33 -0
- package/es/RouteRepository.d.ts +29 -6
- package/es/RouterManager.d.ts +15 -0
- package/es/authRedirect.d.ts +1 -0
- package/es/collection-field-interface/CollectionFieldInterface.d.ts +1 -0
- package/es/collection-field-interface/CollectionFieldInterfaceManager.d.ts +1 -0
- package/es/components/form/JsonTextArea.d.ts +2 -1
- package/es/components/form/VariableJsonTextArea.d.ts +19 -0
- package/es/components/form/index.d.ts +1 -0
- package/es/components/index.d.ts +1 -0
- package/es/entry-actions/EntryActionManager.d.ts +24 -0
- package/es/entry-actions/index.d.ts +9 -0
- package/es/flow/actions/afterSuccess.d.ts +8 -1
- package/es/flow/actions/index.d.ts +1 -1
- package/es/flow/admin-shell/BaseLayoutModel.d.ts +6 -0
- package/es/flow/admin-shell/BaseLayoutRouteCoordinator.d.ts +7 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.d.ts +2 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutMenuFlowUtils.d.ts +2 -1
- package/es/flow/admin-shell/admin-layout/AdminLayoutMenuModels.d.ts +2 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.d.ts +3 -2
- package/es/flow/admin-shell/admin-layout/AdminLayoutSlotModels.d.ts +5 -0
- package/es/flow/admin-shell/admin-layout/AppListRender.d.ts +2 -1
- package/es/flow/admin-shell/admin-layout/AppSwitcherActionPanelModel.d.ts +24 -0
- package/es/flow/admin-shell/admin-layout/index.d.ts +1 -0
- package/es/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.d.ts +6 -0
- package/es/flow/admin-shell/admin-layout/useApplications.d.ts +7 -2
- package/es/flow/components/FieldAssignRulesEditor.d.ts +3 -1
- package/es/flow/models/base/ActionModelCore.d.ts +2 -0
- package/es/flow/models/base/GridModel.d.ts +1 -1
- package/es/flow/models/blocks/filter-form/FilterFormBlockModel.d.ts +5 -0
- package/es/flow/models/blocks/form/FormBlockModel.d.ts +4 -0
- package/es/flow/models/blocks/form/QuickEditFormModel.d.ts +17 -2
- package/es/flow/models/blocks/form/submitHandler.d.ts +1 -1
- package/es/flow/models/blocks/form/value-runtime/rules.d.ts +21 -0
- package/es/flow/models/blocks/form/value-runtime/runtime.d.ts +30 -0
- package/es/flow/models/blocks/form/value-runtime/types.d.ts +2 -2
- package/es/flow/models/blocks/js-block/JSBlock.d.ts +1 -0
- package/es/flow/models/blocks/table/TableActionsColumnModel.d.ts +1 -0
- package/es/flow/models/blocks/table/TableBlockModel.d.ts +1 -0
- package/es/flow/models/blocks/table/dragSort/dragSortHooks.d.ts +1 -1
- package/es/flow/models/blocks/table/dragSort/dragSortSettings.d.ts +2 -1
- package/es/flow/models/blocks/table/dragSort/dragSortUtils.d.ts +6 -4
- package/es/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.d.ts +2 -0
- package/es/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.d.ts +6 -0
- package/es/flow/resolveViewParamsToViewList.d.ts +1 -1
- package/es/flow/routeTransientInputArgs.d.ts +14 -0
- package/es/flow/utils/dataScopeRowSnapshot.d.ts +59 -0
- package/es/flow/utils/dateTimeDisplayProps.d.ts +49 -0
- package/es/flow/utils/formValueDeps.d.ts +32 -0
- package/es/flow-compat/FieldValidation.d.ts +2 -1
- package/es/index.d.ts +4 -1
- package/es/index.mjs +315 -167
- package/es/utils/markdownSanitize.d.ts +11 -0
- package/lib/index.js +315 -167
- package/package.json +8 -7
- package/src/BaseApplication.tsx +12 -5
- package/src/PluginSettingsManager.ts +53 -0
- package/src/RouteRepository.ts +151 -24
- package/src/RouterManager.tsx +142 -1
- package/src/__tests__/PluginSettingsManager.test.ts +13 -0
- package/src/__tests__/RouteRepository.test.ts +239 -0
- package/src/__tests__/RouterManager.test.ts +125 -0
- package/src/__tests__/app.test.tsx +55 -0
- package/src/__tests__/authRedirect.test.ts +17 -0
- package/src/__tests__/exports.test.ts +16 -0
- package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +199 -0
- package/src/__tests__/plugin-manager.test.tsx +44 -2
- package/src/__tests__/settings-center.test.tsx +40 -1
- package/src/authRedirect.ts +38 -0
- package/src/collection-field-interface/CollectionFieldInterface.ts +1 -0
- package/src/collection-field-interface/CollectionFieldInterfaceManager.ts +1 -0
- package/src/collection-manager/field-configure.ts +1 -1
- package/src/collection-manager/interfaces/id.ts +1 -1
- package/src/collection-manager/interfaces/m2m.tsx +2 -2
- package/src/collection-manager/interfaces/m2o.tsx +2 -2
- package/src/collection-manager/interfaces/nanoid.ts +1 -1
- package/src/collection-manager/interfaces/o2m.tsx +2 -2
- package/src/collection-manager/interfaces/obo.tsx +2 -2
- package/src/collection-manager/interfaces/oho.tsx +2 -2
- package/src/collection-manager/interfaces/properties/index.ts +2 -2
- package/src/collection-manager/interfaces/uuid.ts +1 -1
- package/src/components/AppComponents.tsx +19 -3
- package/src/components/form/JsonTextArea.tsx +21 -11
- package/src/components/form/ScanInput/__tests__/useCodeScanner.test.tsx +145 -2
- package/src/components/form/ScanInput/useCodeScanner.ts +154 -2
- package/src/components/form/VariableJsonTextArea.tsx +175 -0
- package/src/components/form/__tests__/JsonTextArea.test.tsx +43 -0
- package/src/components/form/__tests__/VariableJsonTextArea.test.tsx +86 -0
- package/src/components/form/index.tsx +1 -0
- package/src/components/index.ts +1 -0
- package/src/entry-actions/EntryActionManager.ts +76 -0
- package/src/entry-actions/index.ts +10 -0
- package/src/flow/FlowPage.tsx +38 -3
- package/src/flow/__tests__/FlowPage.test.tsx +201 -27
- package/src/flow/__tests__/FlowRoute.test.tsx +1088 -105
- package/src/flow/__tests__/getKey.test.ts +7 -0
- package/src/flow/__tests__/resolveViewParamsToViewList.test.ts +34 -0
- package/src/flow/actions/__tests__/afterSuccess.test.ts +73 -0
- package/src/flow/actions/__tests__/dataScopeFilter.test.ts +120 -0
- package/src/flow/actions/__tests__/dataScopeFormValueClear.test.ts +1022 -0
- package/src/flow/actions/__tests__/fieldLinkageRules.scopeDepth.test.ts +150 -1
- package/src/flow/actions/__tests__/linkageAssignField.legacy.test.ts +135 -0
- package/src/flow/actions/__tests__/linkageRules.hiddenSync.restore.test.ts +19 -5
- package/src/flow/actions/__tests__/linkageRules.subFormSetFieldProps.test.ts +92 -0
- package/src/flow/actions/__tests__/linkageRulesRefresh.test.ts +7 -3
- package/src/flow/actions/__tests__/openView.defineProps.route.test.tsx +164 -0
- package/src/flow/actions/__tests__/subFormFieldLinkageRules.inputArgs.test.ts +2 -2
- package/src/flow/actions/afterSuccess.tsx +142 -3
- package/src/flow/actions/customVariable.tsx +1 -2
- package/src/flow/actions/dataScopeFilter.ts +20 -1
- package/src/flow/actions/dateTimeFormat.tsx +42 -28
- package/src/flow/actions/index.ts +1 -1
- package/src/flow/actions/linkageRules.tsx +123 -35
- package/src/flow/actions/linkageRulesFormValueRefresh.ts +22 -130
- package/src/flow/actions/linkageRulesRefresh.tsx +2 -6
- package/src/flow/actions/openView.tsx +59 -5
- package/src/flow/actions/runjs.tsx +2 -14
- package/src/flow/actions/validation.tsx +62 -30
- package/src/flow/admin-shell/BaseLayoutModel.tsx +149 -3
- package/src/flow/admin-shell/BaseLayoutRouteCoordinator.ts +187 -42
- package/src/flow/admin-shell/__tests__/AdminLayoutRouteCoordinator.test.ts +1020 -110
- package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +74 -13
- package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.test.tsx +486 -0
- package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.tsx +64 -8
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuFlowUtils.ts +5 -3
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuModels.tsx +61 -9
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.tsx +71 -8
- package/src/flow/admin-shell/admin-layout/AdminLayoutModel.tsx +1 -1
- package/src/flow/admin-shell/admin-layout/AdminLayoutSlotModels.tsx +23 -9
- package/src/flow/admin-shell/admin-layout/AppListRender.tsx +13 -2
- package/src/flow/admin-shell/admin-layout/AppSwitcherActionPanelModel.tsx +289 -0
- package/src/flow/admin-shell/admin-layout/HelpLite.tsx +1 -3
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutComponent.test.tsx +188 -0
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutMenuModels.test.ts +99 -0
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutModel.test.tsx +518 -2
- package/src/flow/admin-shell/admin-layout/__tests__/TopbarActionsBar.test.tsx +214 -2
- package/src/flow/admin-shell/admin-layout/index.ts +1 -0
- package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.test.ts +20 -0
- package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.ts +33 -5
- package/src/flow/admin-shell/admin-layout/useApplications.tsx +81 -12
- package/src/flow/common/Markdown/Display.tsx +14 -3
- package/src/flow/common/Markdown/Edit.tsx +19 -7
- package/src/flow/components/DefaultValue.tsx +1 -2
- package/src/flow/components/DynamicFlowsIcon.tsx +447 -126
- package/src/flow/components/FieldAssignRulesEditor.tsx +128 -21
- package/src/flow/components/FieldAssignValueInput.tsx +15 -11
- package/src/flow/components/FlowRoute.tsx +186 -25
- package/src/flow/components/__tests__/DynamicFlowsIcon.test.tsx +148 -2
- package/src/flow/components/__tests__/FieldAssignRulesEditor.test.tsx +508 -4
- package/src/flow/components/__tests__/FieldAssignValueInput.context.test.tsx +134 -0
- package/src/flow/components/__tests__/fieldAssignOptions.test.ts +151 -3
- package/src/flow/components/code-editor/__tests__/useCodeRunner.test.tsx +2 -17
- package/src/flow/components/code-editor/hooks/useCodeRunner.ts +2 -14
- package/src/flow/components/code-editor/runjsDiagnostics.ts +8 -45
- package/src/flow/components/fieldAssignOptions.ts +155 -27
- package/src/flow/flows/editMarkdownFlow.tsx +1 -1
- package/src/flow/getViewDiffAndUpdateHidden.tsx +1 -0
- package/src/flow/index.ts +1 -1
- package/src/flow/internal/components/Markdown/util.ts +2 -1
- package/src/flow/internal/utils/operatorSchemaHelper.ts +15 -1
- package/src/flow/models/actions/JSActionModel.tsx +2 -7
- package/src/flow/models/actions/JSCollectionActionModel.tsx +2 -7
- package/src/flow/models/actions/JSItemActionModel.tsx +2 -7
- package/src/flow/models/actions/JSRecordActionModel.tsx +2 -7
- package/src/flow/models/base/ActionModel.tsx +21 -8
- package/src/flow/models/base/ActionModelCore.tsx +11 -4
- package/src/flow/models/base/GridModel.tsx +38 -7
- package/src/flow/models/base/PageModel/PageModel.tsx +4 -1
- package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +28 -4
- package/src/flow/models/base/__tests__/ActionModel.test.ts +83 -0
- package/src/flow/models/base/__tests__/ActionModelCore.render.test.tsx +37 -0
- package/src/flow/models/base/__tests__/GridModel.dragSnapshotContainer.test.ts +239 -1
- package/src/flow/models/base/__tests__/transformRowsToSingleColumn.test.ts +48 -0
- package/src/flow/models/blocks/filter-form/FilterFormBlockModel.tsx +32 -3
- package/src/flow/models/blocks/filter-form/FilterFormItemModel.tsx +27 -12
- package/src/flow/models/blocks/filter-form/FilterFormJSActionModel.tsx +2 -7
- package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.defineChildren.test.ts +36 -0
- package/src/flow/models/blocks/filter-form/__tests__/defaultValues.wiring.test.ts +111 -0
- package/src/flow/models/blocks/filter-form/fields/FilterFormCustomFieldModel.tsx +1 -1
- package/src/flow/models/blocks/filter-manager/flow-actions/__tests__/customizeFilterRender.test.tsx +56 -1
- package/src/flow/models/blocks/form/EditFormModel.tsx +1 -0
- package/src/flow/models/blocks/form/FormActionModel.tsx +1 -1
- package/src/flow/models/blocks/form/FormBlockModel.tsx +7 -0
- package/src/flow/models/blocks/form/JSFormActionModel.tsx +2 -7
- package/src/flow/models/blocks/form/QuickEditFormModel.tsx +189 -36
- package/src/flow/models/blocks/form/__tests__/FormBlockModel.test.tsx +17 -0
- package/src/flow/models/blocks/form/__tests__/QuickEditFormModel.quickEdit.test.ts +350 -2
- package/src/flow/models/blocks/form/__tests__/submitHandler.test.ts +54 -1
- package/src/flow/models/blocks/form/submitHandler.ts +17 -3
- package/src/flow/models/blocks/form/value-runtime/__tests__/runtime.test.ts +940 -75
- package/src/flow/models/blocks/form/value-runtime/rules.ts +445 -13
- package/src/flow/models/blocks/form/value-runtime/runtime.ts +348 -13
- package/src/flow/models/blocks/form/value-runtime/types.ts +2 -2
- package/src/flow/models/blocks/js-block/JSBlock.tsx +225 -9
- package/src/flow/models/blocks/js-block/__tests__/JSBlockModel.test.tsx +150 -0
- package/src/flow/models/blocks/table/JSColumnModel.tsx +1 -9
- package/src/flow/models/blocks/table/TableActionsColumnModel.tsx +38 -14
- package/src/flow/models/blocks/table/TableBlockModel.tsx +38 -17
- package/src/flow/models/blocks/table/TableColumnModel.tsx +42 -5
- package/src/flow/models/blocks/table/__tests__/TableActionsColumnModel.test.tsx +111 -1
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.dragSort.test.ts +127 -0
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.mobileSettingsButtons.test.tsx +78 -0
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.rowSelection.test.tsx +1 -0
- package/src/flow/models/blocks/table/__tests__/TableColumnModel.test.tsx +210 -1
- package/src/flow/models/blocks/table/dragSort/dragSortHooks.tsx +28 -17
- package/src/flow/models/blocks/table/dragSort/dragSortSettings.ts +13 -6
- package/src/flow/models/blocks/table/dragSort/dragSortUtils.ts +15 -2
- package/src/flow/models/fields/AssociationFieldModel/AssociationFieldModel.tsx +1 -1
- package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/PopupSubTableFieldModel.tsx +30 -6
- package/src/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.tsx +61 -17
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.tsx +141 -19
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableField.tsx +13 -1
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/__tests__/SubTableColumnModel.rowRecord.test.ts +197 -0
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/__tests__/SubTableFieldModel.reset.test.ts +180 -0
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/index.tsx +6 -4
- package/src/flow/models/fields/AssociationFieldModel/__tests__/AssociationFieldModel.updateAssociation.test.ts +72 -0
- package/src/flow/models/fields/AssociationFieldModel/__tests__/RecordPickerFieldModel.itemContext.test.ts +23 -0
- package/src/flow/models/fields/ClickableFieldModel.tsx +5 -0
- package/src/flow/models/fields/DisplayDateTimeFieldModel.tsx +29 -1
- package/src/flow/models/fields/InputFieldModel.tsx +40 -2
- package/src/flow/models/fields/JSEditableFieldModel.tsx +2 -11
- package/src/flow/models/fields/JSFieldModel.tsx +2 -7
- package/src/flow/models/fields/JSItemModel.tsx +2 -14
- package/src/flow/models/fields/SelectFieldModel.tsx +6 -4
- package/src/flow/models/fields/TextareaFieldModel.tsx +72 -3
- package/src/flow/models/fields/__tests__/DisplayDateTimeFieldModel.test.tsx +96 -0
- package/src/flow/models/fields/__tests__/InputFieldModel.test.tsx +117 -1
- package/src/flow/models/fields/__tests__/SelectFieldModel.test.tsx +43 -0
- package/src/flow/models/fields/__tests__/TextareaFieldModel.test.tsx +131 -0
- package/src/flow/models/fields/mobile-components/MobileLazySelect.tsx +20 -10
- package/src/flow/models/fields/mobile-components/MobileSelect.tsx +24 -12
- package/src/flow/models/topbar/TopbarActionModel.tsx +168 -10
- package/src/flow/resolveViewParamsToViewList.tsx +11 -3
- package/src/flow/routeTransientInputArgs.ts +27 -0
- package/src/flow/utils/__tests__/dateTimeFormat.test.ts +300 -0
- package/src/flow/utils/dataScopeFormValueClear.ts +218 -81
- package/src/flow/utils/dataScopeRowSnapshot.ts +616 -0
- package/src/flow/utils/dateTimeDisplayProps.ts +135 -0
- package/src/flow/utils/formValueDeps.ts +170 -0
- package/src/flow-compat/FieldValidation.tsx +122 -60
- package/src/flow-compat/Popover.tsx +43 -4
- package/src/flow-compat/__tests__/Popover.test.tsx +34 -0
- package/src/index.ts +10 -1
- package/src/layout-manager/__tests__/LayoutRoute.test.tsx +41 -1
- package/src/nocobase-buildin-plugin/index.tsx +51 -15
- package/src/settings-center/AdminSettingsLayout.tsx +10 -2
- package/src/settings-center/SystemSettingsPage.tsx +1 -2
- package/src/settings-center/plugin-manager/PluginCard.tsx +2 -2
- package/src/settings-center/plugin-manager/index.tsx +3 -0
- package/src/settings-center/utils.tsx +0 -6
- package/src/utils/markdownSanitize.ts +88 -0
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { Html5Qrcode, Html5QrcodeScannerState, Html5QrcodeSupportedFormats } from 'html5-qrcode';
|
|
11
|
+
import jsQR from 'jsqr';
|
|
11
12
|
import { useCallback, useEffect, useState } from 'react';
|
|
12
13
|
import type { CodeFormatsToSupport } from './types';
|
|
13
14
|
|
|
@@ -26,6 +27,27 @@ type UseCodeScannerOptions = {
|
|
|
26
27
|
onScanFailure?: () => void;
|
|
27
28
|
};
|
|
28
29
|
|
|
30
|
+
type ImageDataVariant = {
|
|
31
|
+
imageData: ImageData;
|
|
32
|
+
maxSize: number;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
type JsQRImageTransform = {
|
|
36
|
+
contrast?: number;
|
|
37
|
+
threshold?: number;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const QR_SCAN_IMAGE_SIZES = [3200, 2400, 1600, 1000];
|
|
41
|
+
const QR_SCAN_IMAGE_TRANSFORMS: JsQRImageTransform[] = [
|
|
42
|
+
{},
|
|
43
|
+
{ contrast: 3, threshold: 105 },
|
|
44
|
+
{ contrast: 2, threshold: 105 },
|
|
45
|
+
{ contrast: 3, threshold: 120 },
|
|
46
|
+
{ contrast: 2, threshold: 120 },
|
|
47
|
+
{ contrast: 3, threshold: 90 },
|
|
48
|
+
{ contrast: 4, threshold: 105 },
|
|
49
|
+
];
|
|
50
|
+
|
|
29
51
|
export const DEFAULT_CODE_FORMATS: CodeFormatsToSupport = [
|
|
30
52
|
Html5QrcodeSupportedFormats.QR_CODE,
|
|
31
53
|
Html5QrcodeSupportedFormats.CODE_128,
|
|
@@ -62,6 +84,126 @@ async function stopScanner(scanner?: Html5Qrcode, options: { clear?: boolean } =
|
|
|
62
84
|
}
|
|
63
85
|
}
|
|
64
86
|
|
|
87
|
+
function isSafariBrowser() {
|
|
88
|
+
const { userAgent, vendor } = navigator;
|
|
89
|
+
return /Apple/i.test(vendor) && /Safari/i.test(userAgent) && !/CriOS|FxiOS|EdgiOS|Chrome/i.test(userAgent);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function loadImage(file: File) {
|
|
93
|
+
return new Promise<HTMLImageElement>((resolve, reject) => {
|
|
94
|
+
const image = new Image();
|
|
95
|
+
const url = URL.createObjectURL(file);
|
|
96
|
+
const cleanup = () => URL.revokeObjectURL(url);
|
|
97
|
+
|
|
98
|
+
image.onload = () => {
|
|
99
|
+
cleanup();
|
|
100
|
+
resolve(image);
|
|
101
|
+
};
|
|
102
|
+
image.onerror = (event) => {
|
|
103
|
+
cleanup();
|
|
104
|
+
reject(event);
|
|
105
|
+
};
|
|
106
|
+
image.onabort = (event) => {
|
|
107
|
+
cleanup();
|
|
108
|
+
reject(event);
|
|
109
|
+
};
|
|
110
|
+
image.src = url;
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function getScanImageSizes(naturalWidth: number, naturalHeight: number) {
|
|
115
|
+
const maxSize = Math.max(naturalWidth, naturalHeight);
|
|
116
|
+
const cappedMaxSize = Math.min(maxSize, QR_SCAN_IMAGE_SIZES[0]);
|
|
117
|
+
return Array.from(new Set([cappedMaxSize, ...QR_SCAN_IMAGE_SIZES.filter((size) => size < cappedMaxSize)])).sort(
|
|
118
|
+
(a, b) => b - a,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function getImageDataFromImage(image: HTMLImageElement, maxSize: number): ImageDataVariant | undefined {
|
|
123
|
+
const naturalWidth = image.naturalWidth || image.width;
|
|
124
|
+
const naturalHeight = image.naturalHeight || image.height;
|
|
125
|
+
|
|
126
|
+
const scale = Math.min(1, maxSize / Math.max(naturalWidth, naturalHeight));
|
|
127
|
+
const width = Math.max(1, Math.round(naturalWidth * scale));
|
|
128
|
+
const height = Math.max(1, Math.round(naturalHeight * scale));
|
|
129
|
+
const canvas = document.createElement('canvas');
|
|
130
|
+
canvas.width = width;
|
|
131
|
+
canvas.height = height;
|
|
132
|
+
const context = canvas.getContext('2d');
|
|
133
|
+
if (!context) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
context.drawImage(image, 0, 0, width, height);
|
|
138
|
+
return {
|
|
139
|
+
imageData: context.getImageData(0, 0, width, height),
|
|
140
|
+
maxSize,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function getImageDataVariants(file: File) {
|
|
145
|
+
const image = await loadImage(file);
|
|
146
|
+
const naturalWidth = image.naturalWidth || image.width;
|
|
147
|
+
const naturalHeight = image.naturalHeight || image.height;
|
|
148
|
+
|
|
149
|
+
if (!naturalWidth || !naturalHeight) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return getScanImageSizes(naturalWidth, naturalHeight)
|
|
154
|
+
.map((maxSize) => getImageDataFromImage(image, maxSize))
|
|
155
|
+
.filter((variant): variant is ImageDataVariant => !!variant);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function getTransformedImageData(imageData: ImageData, transform: JsQRImageTransform) {
|
|
159
|
+
const { contrast = 1, threshold } = transform;
|
|
160
|
+
if (contrast === 1 && threshold == null) {
|
|
161
|
+
return imageData.data;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const data = new Uint8ClampedArray(imageData.data);
|
|
165
|
+
for (let index = 0; index < data.length; index += 4) {
|
|
166
|
+
let luminance = data[index] * 0.299 + data[index + 1] * 0.587 + data[index + 2] * 0.114;
|
|
167
|
+
luminance = Math.max(0, Math.min(255, (luminance - 128) * contrast + 128));
|
|
168
|
+
if (threshold != null) {
|
|
169
|
+
luminance = luminance < threshold ? 0 : 255;
|
|
170
|
+
}
|
|
171
|
+
data[index] = luminance;
|
|
172
|
+
data[index + 1] = luminance;
|
|
173
|
+
data[index + 2] = luminance;
|
|
174
|
+
}
|
|
175
|
+
return data;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function scanFileWithJsQR(file: File, formatsToSupport?: CodeFormatsToSupport) {
|
|
179
|
+
const formats = formatsToSupport?.length ? formatsToSupport : DEFAULT_CODE_FORMATS;
|
|
180
|
+
if (!formats.includes(Html5QrcodeSupportedFormats.QR_CODE)) {
|
|
181
|
+
throw new Error('QR_CODE is not included in the requested formats');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const variants = await getImageDataVariants(file);
|
|
185
|
+
if (!variants?.length) {
|
|
186
|
+
throw new Error('Failed to prepare uploaded image for QR decoding');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
for (const { imageData } of variants) {
|
|
190
|
+
for (const transform of QR_SCAN_IMAGE_TRANSFORMS) {
|
|
191
|
+
const data = getTransformedImageData(imageData, transform);
|
|
192
|
+
const qrCode = jsQR(data, imageData.width, imageData.height, { inversionAttempts: 'attemptBoth' });
|
|
193
|
+
if (qrCode?.data) {
|
|
194
|
+
return qrCode.data;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
throw new Error('No QR code decoded by jsQR');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function shouldScanQrWithJsQR(formatsToSupport?: CodeFormatsToSupport) {
|
|
203
|
+
const formats = formatsToSupport?.length ? formatsToSupport : DEFAULT_CODE_FORMATS;
|
|
204
|
+
return formats.includes(Html5QrcodeSupportedFormats.QR_CODE);
|
|
205
|
+
}
|
|
206
|
+
|
|
65
207
|
export function useCodeScanner({
|
|
66
208
|
enabled,
|
|
67
209
|
elementId,
|
|
@@ -95,6 +237,16 @@ export function useCodeScanner({
|
|
|
95
237
|
|
|
96
238
|
const startScanFile = useCallback(
|
|
97
239
|
async (file: File) => {
|
|
240
|
+
if (isSafariBrowser() && shouldScanQrWithJsQR(formatsToSupport)) {
|
|
241
|
+
try {
|
|
242
|
+
const decodedText = await scanFileWithJsQR(file, formatsToSupport);
|
|
243
|
+
onScanSuccess(decodedText);
|
|
244
|
+
return;
|
|
245
|
+
} catch {
|
|
246
|
+
// Fall through to html5-qrcode so barcode uploads still work in Safari.
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
98
250
|
if (!scanner) {
|
|
99
251
|
return;
|
|
100
252
|
}
|
|
@@ -103,12 +255,12 @@ export function useCodeScanner({
|
|
|
103
255
|
try {
|
|
104
256
|
const result = await scanner.scanFileV2(file, false);
|
|
105
257
|
onScanSuccess(result.decodedText);
|
|
106
|
-
} catch
|
|
258
|
+
} catch {
|
|
107
259
|
onScanFailure?.();
|
|
108
260
|
await startScanCamera(scanner);
|
|
109
261
|
}
|
|
110
262
|
},
|
|
111
|
-
[onScanFailure, onScanSuccess, scanner, startScanCamera],
|
|
263
|
+
[formatsToSupport, onScanFailure, onScanSuccess, scanner, startScanCamera],
|
|
112
264
|
);
|
|
113
265
|
|
|
114
266
|
useEffect(() => {
|
|
@@ -0,0 +1,175 @@
|
|
|
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 { css, cx } from '@emotion/css';
|
|
11
|
+
import {
|
|
12
|
+
FlowContextSelector,
|
|
13
|
+
formatPathToValue as formatFlowPathToValue,
|
|
14
|
+
useFlowContext,
|
|
15
|
+
type MetaTreeNode,
|
|
16
|
+
} from '@nocobase/flow-engine';
|
|
17
|
+
import { Button, theme } from 'antd';
|
|
18
|
+
import type { TextAreaRef } from 'antd/es/input/TextArea';
|
|
19
|
+
import React, { useCallback, useMemo, useRef } from 'react';
|
|
20
|
+
import { JsonTextArea, type JsonTextAreaProps } from './JsonTextArea';
|
|
21
|
+
import { useFilteredMetaTree } from './VariableInput';
|
|
22
|
+
|
|
23
|
+
export interface VariableJsonTextAreaProps extends JsonTextAreaProps {
|
|
24
|
+
namespaces?: string[];
|
|
25
|
+
extraNodes?: MetaTreeNode[];
|
|
26
|
+
metaTree?: MetaTreeNode[] | (() => MetaTreeNode[] | Promise<MetaTreeNode[]>);
|
|
27
|
+
formatPathToValue?: (meta: MetaTreeNode) => string | undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const VariableJsonTextArea = React.memo((props: VariableJsonTextAreaProps) => {
|
|
31
|
+
const {
|
|
32
|
+
value,
|
|
33
|
+
onChange,
|
|
34
|
+
space = 2,
|
|
35
|
+
json5 = false,
|
|
36
|
+
showError = true,
|
|
37
|
+
className,
|
|
38
|
+
status,
|
|
39
|
+
onBlur,
|
|
40
|
+
namespaces,
|
|
41
|
+
extraNodes,
|
|
42
|
+
metaTree,
|
|
43
|
+
formatPathToValue: customFormatPathToValue,
|
|
44
|
+
...textAreaProps
|
|
45
|
+
} = props;
|
|
46
|
+
const { token } = theme.useToken();
|
|
47
|
+
const flowCtx = useFlowContext();
|
|
48
|
+
const filteredMetaTree = useFilteredMetaTree({ namespaces, extraNodes });
|
|
49
|
+
const ref = useRef<TextAreaRef>(null);
|
|
50
|
+
|
|
51
|
+
const insertAtCaret = useCallback((valueToInsert: string) => {
|
|
52
|
+
const textArea = ref.current?.resizableTextArea?.textArea;
|
|
53
|
+
if (!textArea) return;
|
|
54
|
+
|
|
55
|
+
const start = textArea.selectionStart ?? textArea.value.length;
|
|
56
|
+
const end = textArea?.selectionEnd ?? start;
|
|
57
|
+
const nextText = textArea.value.slice(0, start) + valueToInsert + textArea.value.slice(end);
|
|
58
|
+
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value')?.set;
|
|
59
|
+
|
|
60
|
+
nativeInputValueSetter?.call(textArea, nextText);
|
|
61
|
+
textArea.dispatchEvent(new Event('input', { bubbles: true }));
|
|
62
|
+
|
|
63
|
+
requestAnimationFrame(() => {
|
|
64
|
+
const nextPosition = start + valueToInsert.length;
|
|
65
|
+
textArea.setSelectionRange(start, nextPosition);
|
|
66
|
+
textArea.focus();
|
|
67
|
+
});
|
|
68
|
+
}, []);
|
|
69
|
+
|
|
70
|
+
const handleVariableSelected = useCallback(
|
|
71
|
+
(nextValue: string) => {
|
|
72
|
+
if (nextValue) {
|
|
73
|
+
insertAtCaret(nextValue);
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
[insertAtCaret],
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const selectorMetaTree = useMemo(
|
|
80
|
+
() => metaTree ?? filteredMetaTree ?? (() => flowCtx.getPropertyMetaTree?.() ?? []),
|
|
81
|
+
[filteredMetaTree, flowCtx, metaTree],
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
const formatPathToValue = useMemo(() => {
|
|
85
|
+
if (!customFormatPathToValue) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
return (meta: MetaTreeNode) => customFormatPathToValue(meta) ?? formatFlowPathToValue(meta);
|
|
89
|
+
}, [customFormatPathToValue]);
|
|
90
|
+
|
|
91
|
+
const wrapperClassName = useMemo(
|
|
92
|
+
() => css`
|
|
93
|
+
position: relative;
|
|
94
|
+
width: 100%;
|
|
95
|
+
|
|
96
|
+
.ant-input {
|
|
97
|
+
width: 100%;
|
|
98
|
+
}
|
|
99
|
+
`,
|
|
100
|
+
[],
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
const textAreaClassName = useMemo(
|
|
104
|
+
() => css`
|
|
105
|
+
font-size: ${token.fontSizeSM}px;
|
|
106
|
+
font-family: ${token.fontFamilyCode};
|
|
107
|
+
`,
|
|
108
|
+
[token.fontFamilyCode, token.fontSizeSM],
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
const selectorClassName = useMemo(
|
|
112
|
+
() => css`
|
|
113
|
+
position: absolute;
|
|
114
|
+
inset-block-start: 0;
|
|
115
|
+
inset-inline-end: 0;
|
|
116
|
+
z-index: 1;
|
|
117
|
+
line-height: 0;
|
|
118
|
+
|
|
119
|
+
.ant-btn {
|
|
120
|
+
font-size: ${token.fontSizeSM}px;
|
|
121
|
+
}
|
|
122
|
+
`,
|
|
123
|
+
[token.fontSizeSM],
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
const buttonClassName = useMemo(
|
|
127
|
+
() => css`
|
|
128
|
+
&:not(:hover) {
|
|
129
|
+
border-inline-end-color: transparent;
|
|
130
|
+
border-block-start-color: transparent;
|
|
131
|
+
background-color: transparent;
|
|
132
|
+
}
|
|
133
|
+
`,
|
|
134
|
+
[],
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
return (
|
|
138
|
+
<div className={wrapperClassName}>
|
|
139
|
+
<JsonTextArea
|
|
140
|
+
{...textAreaProps}
|
|
141
|
+
ref={ref}
|
|
142
|
+
value={value}
|
|
143
|
+
onChange={onChange}
|
|
144
|
+
space={space}
|
|
145
|
+
json5={json5}
|
|
146
|
+
showError={showError}
|
|
147
|
+
className={cx(textAreaClassName, className)}
|
|
148
|
+
status={status}
|
|
149
|
+
onBlur={onBlur}
|
|
150
|
+
disabled={textAreaProps.disabled}
|
|
151
|
+
/>
|
|
152
|
+
<div className={selectorClassName}>
|
|
153
|
+
<FlowContextSelector
|
|
154
|
+
metaTree={selectorMetaTree}
|
|
155
|
+
disabled={textAreaProps.disabled}
|
|
156
|
+
formatPathToValue={formatPathToValue}
|
|
157
|
+
onChange={handleVariableSelected}
|
|
158
|
+
>
|
|
159
|
+
<Button
|
|
160
|
+
aria-label="variable-json-switcher"
|
|
161
|
+
disabled={textAreaProps.disabled}
|
|
162
|
+
className={buttonClassName}
|
|
163
|
+
style={{ fontStyle: 'italic', fontFamily: token.fontFamilyCode }}
|
|
164
|
+
>
|
|
165
|
+
x
|
|
166
|
+
</Button>
|
|
167
|
+
</FlowContextSelector>
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
170
|
+
);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
VariableJsonTextArea.displayName = 'VariableJsonTextArea';
|
|
174
|
+
|
|
175
|
+
export const VariableJSON = VariableJsonTextArea;
|
|
@@ -0,0 +1,43 @@
|
|
|
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 { fireEvent, render, screen } from '@testing-library/react';
|
|
11
|
+
import React from 'react';
|
|
12
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
13
|
+
import { JsonTextArea } from '../JsonTextArea';
|
|
14
|
+
|
|
15
|
+
describe('JsonTextArea', () => {
|
|
16
|
+
it('formats object values and emits parsed JSON on blur', async () => {
|
|
17
|
+
const handleChange = vi.fn();
|
|
18
|
+
|
|
19
|
+
render(<JsonTextArea value={{ foo: 'bar' }} onChange={handleChange} />);
|
|
20
|
+
|
|
21
|
+
const textArea = await screen.findByRole('textbox');
|
|
22
|
+
expect(textArea).toHaveValue('{\n "foo": "bar"\n}');
|
|
23
|
+
|
|
24
|
+
fireEvent.change(textArea, { target: { value: '{"foo":"baz"}' } });
|
|
25
|
+
fireEvent.blur(textArea);
|
|
26
|
+
|
|
27
|
+
expect(handleChange).toHaveBeenCalledWith({ foo: 'baz' });
|
|
28
|
+
expect(textArea).toHaveValue('{\n "foo": "baz"\n}');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('shows JSON parse errors and does not emit invalid values', async () => {
|
|
32
|
+
const handleChange = vi.fn();
|
|
33
|
+
|
|
34
|
+
render(<JsonTextArea value={{}} onChange={handleChange} />);
|
|
35
|
+
|
|
36
|
+
const textArea = await screen.findByRole('textbox');
|
|
37
|
+
fireEvent.change(textArea, { target: { value: '{' } });
|
|
38
|
+
fireEvent.blur(textArea);
|
|
39
|
+
|
|
40
|
+
expect(handleChange).not.toHaveBeenCalled();
|
|
41
|
+
expect(await screen.findByText(/JSON/)).toBeInTheDocument();
|
|
42
|
+
});
|
|
43
|
+
});
|
|
@@ -0,0 +1,86 @@
|
|
|
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 { FlowContext, FlowContextProvider } from '@nocobase/flow-engine';
|
|
11
|
+
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
12
|
+
import React from 'react';
|
|
13
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
14
|
+
import { VariableJsonTextArea } from '../VariableJsonTextArea';
|
|
15
|
+
|
|
16
|
+
function createContextWithEnv() {
|
|
17
|
+
const ctx = new FlowContext();
|
|
18
|
+
(ctx as unknown as { t: (key: string) => string }).t = (key: string) => key;
|
|
19
|
+
|
|
20
|
+
ctx.defineProperty('$env', {
|
|
21
|
+
value: { API_KEY: 'secret' },
|
|
22
|
+
meta: {
|
|
23
|
+
title: 'Env',
|
|
24
|
+
type: 'object',
|
|
25
|
+
properties: {
|
|
26
|
+
API_KEY: { title: 'API Key', type: 'string' },
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
return ctx;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function renderWithCtx(ctx: FlowContext, node: React.ReactNode) {
|
|
35
|
+
return render(<FlowContextProvider context={ctx}>{node}</FlowContextProvider>);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
describe('VariableJsonTextArea', () => {
|
|
39
|
+
it('formats object values and emits parsed JSON on blur', async () => {
|
|
40
|
+
const ctx = createContextWithEnv();
|
|
41
|
+
const handleChange = vi.fn();
|
|
42
|
+
|
|
43
|
+
renderWithCtx(ctx, <VariableJsonTextArea value={{ foo: 'bar' }} onChange={handleChange} />);
|
|
44
|
+
|
|
45
|
+
const textArea = await screen.findByRole('textbox');
|
|
46
|
+
expect(textArea).toHaveValue('{\n "foo": "bar"\n}');
|
|
47
|
+
|
|
48
|
+
fireEvent.change(textArea, { target: { value: '{"foo":"baz"}' } });
|
|
49
|
+
fireEvent.blur(textArea);
|
|
50
|
+
|
|
51
|
+
expect(handleChange).toHaveBeenCalledWith({ foo: 'baz' });
|
|
52
|
+
expect(textArea).toHaveValue('{\n "foo": "baz"\n}');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('shows JSON parse errors and does not emit invalid values', async () => {
|
|
56
|
+
const ctx = createContextWithEnv();
|
|
57
|
+
const handleChange = vi.fn();
|
|
58
|
+
|
|
59
|
+
renderWithCtx(ctx, <VariableJsonTextArea value={{}} onChange={handleChange} />);
|
|
60
|
+
|
|
61
|
+
const textArea = await screen.findByRole('textbox');
|
|
62
|
+
fireEvent.change(textArea, { target: { value: '{' } });
|
|
63
|
+
fireEvent.blur(textArea);
|
|
64
|
+
|
|
65
|
+
expect(handleChange).not.toHaveBeenCalled();
|
|
66
|
+
expect(await screen.findByText(/JSON/)).toBeInTheDocument();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('inserts selected variables with the client-v2 template format', async () => {
|
|
70
|
+
const ctx = createContextWithEnv();
|
|
71
|
+
|
|
72
|
+
renderWithCtx(ctx, <VariableJsonTextArea value={null} namespaces={['$env']} onChange={() => undefined} />);
|
|
73
|
+
|
|
74
|
+
fireEvent.click(await screen.findByRole('button', { name: 'variable-json-switcher' }));
|
|
75
|
+
|
|
76
|
+
await waitFor(() => {
|
|
77
|
+
fireEvent.click(screen.getByText('Env'));
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
await waitFor(() => {
|
|
81
|
+
fireEvent.click(screen.getByText('API Key'));
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
expect(await screen.findByRole('textbox')).toHaveValue('{{ ctx.$env.API_KEY }}');
|
|
85
|
+
});
|
|
86
|
+
});
|
package/src/components/index.ts
CHANGED
|
@@ -12,6 +12,7 @@ export * from './BlankComponent';
|
|
|
12
12
|
export * from './form/table/dnd';
|
|
13
13
|
export * from './form';
|
|
14
14
|
export * from './Icon';
|
|
15
|
+
export * from './KeepAlive';
|
|
15
16
|
export * from './PoweredBy';
|
|
16
17
|
export * from './RouterContextCleaner';
|
|
17
18
|
export * from './SwitchLanguage';
|
|
@@ -0,0 +1,76 @@
|
|
|
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
|
+
define,
|
|
12
|
+
observable,
|
|
13
|
+
type FlowModelContext,
|
|
14
|
+
type SubModelItem,
|
|
15
|
+
type SubModelItemsType,
|
|
16
|
+
} from '@nocobase/flow-engine';
|
|
17
|
+
|
|
18
|
+
export type EntryActionScope = 'action-panel' | 'app-switcher' | string;
|
|
19
|
+
|
|
20
|
+
export type EntryActionProvider = (ctx: FlowModelContext) => SubModelItem[] | Promise<SubModelItem[]>;
|
|
21
|
+
|
|
22
|
+
type ProviderRecord = {
|
|
23
|
+
name: string;
|
|
24
|
+
scope: EntryActionScope;
|
|
25
|
+
provider: EntryActionProvider;
|
|
26
|
+
sort: number;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export class EntryActionManager {
|
|
30
|
+
private readonly providers = new Map<string, ProviderRecord>();
|
|
31
|
+
revision = 0;
|
|
32
|
+
|
|
33
|
+
constructor() {
|
|
34
|
+
define(this, {
|
|
35
|
+
revision: observable.ref,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
register(name: string, options: { scope: EntryActionScope; provider: EntryActionProvider; sort?: number }) {
|
|
40
|
+
this.providers.set(name, {
|
|
41
|
+
name,
|
|
42
|
+
scope: options.scope,
|
|
43
|
+
provider: options.provider,
|
|
44
|
+
sort: options.sort ?? 0,
|
|
45
|
+
});
|
|
46
|
+
this.invalidate();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
unregister(name: string) {
|
|
50
|
+
this.providers.delete(name);
|
|
51
|
+
this.invalidate();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
invalidate() {
|
|
55
|
+
this.revision += 1;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
getItems(scope: EntryActionScope): SubModelItemsType {
|
|
59
|
+
return async (ctx) => {
|
|
60
|
+
const providers = [...this.providers.values()]
|
|
61
|
+
.filter((item) => item.scope === scope)
|
|
62
|
+
.sort((a, b) => a.sort - b.sort);
|
|
63
|
+
const groups = await Promise.all(
|
|
64
|
+
providers.map(async (item) => {
|
|
65
|
+
try {
|
|
66
|
+
return await item.provider(ctx);
|
|
67
|
+
} catch (error) {
|
|
68
|
+
console.error(`[NocoBase] Failed to load entry action provider "${item.name}".`, error);
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
}),
|
|
72
|
+
);
|
|
73
|
+
return groups.flat();
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
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
|
+
export * from './EntryActionManager';
|
package/src/flow/FlowPage.tsx
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
useFlowModelById,
|
|
15
15
|
useFlowViewContext,
|
|
16
16
|
} from '@nocobase/flow-engine';
|
|
17
|
-
import type { FlowModel, FlowModelRendererProps, ModelConstructor } from '@nocobase/flow-engine';
|
|
17
|
+
import type { FlowEngineContext, FlowModel, FlowModelRendererProps, ModelConstructor } from '@nocobase/flow-engine';
|
|
18
18
|
import { useRequest } from 'ahooks';
|
|
19
19
|
import React from 'react';
|
|
20
20
|
import FlowRoute from './components/FlowRoute';
|
|
@@ -57,10 +57,38 @@ type FlowPageProps = {
|
|
|
57
57
|
showFlowSettings?: FlowModelRendererProps['showFlowSettings'];
|
|
58
58
|
};
|
|
59
59
|
|
|
60
|
+
type FlowPageViewContext = FlowEngineContext & {
|
|
61
|
+
view?: {
|
|
62
|
+
inputArgs?: {
|
|
63
|
+
filterByTk?: unknown;
|
|
64
|
+
isMobileLayout?: unknown;
|
|
65
|
+
sourceId?: unknown;
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const bindViewLayoutState = (model: FlowModel, ctx?: FlowPageViewContext | null) => {
|
|
71
|
+
const hasViewMobileLayout = typeof ctx?.view?.inputArgs?.isMobileLayout === 'boolean';
|
|
72
|
+
const hasContextMobileLayout = typeof ctx?.isMobileLayout === 'boolean';
|
|
73
|
+
if (!hasViewMobileLayout && !hasContextMobileLayout) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
model.context.defineProperty('isMobileLayout', {
|
|
78
|
+
get: () => {
|
|
79
|
+
if (typeof ctx?.isMobileLayout === 'boolean') {
|
|
80
|
+
return ctx.isMobileLayout;
|
|
81
|
+
}
|
|
82
|
+
return !!ctx?.view?.inputArgs?.isMobileLayout;
|
|
83
|
+
},
|
|
84
|
+
cache: false,
|
|
85
|
+
});
|
|
86
|
+
};
|
|
87
|
+
|
|
60
88
|
export const FlowPage = React.memo((props: FlowPageProps & Record<string, unknown>) => {
|
|
61
89
|
const { pageModelClass = 'ChildPageModel', parentId, onModelLoaded, defaultTabTitle, ...rest } = props;
|
|
62
90
|
const flowEngine = useFlowEngine();
|
|
63
|
-
const ctx = useFlowViewContext();
|
|
91
|
+
const ctx = useFlowViewContext<FlowPageViewContext>();
|
|
64
92
|
const { loading, data, error } = useRequest(
|
|
65
93
|
async () => {
|
|
66
94
|
const ModelClass = await flowEngine.getModelClassAsync(pageModelClass);
|
|
@@ -76,6 +104,10 @@ export const FlowPage = React.memo((props: FlowPageProps & Record<string, unknow
|
|
|
76
104
|
subType: 'object',
|
|
77
105
|
use: pageModelClass,
|
|
78
106
|
};
|
|
107
|
+
const isRuntimeRecordScopedPage =
|
|
108
|
+
!flowEngine.context.flowSettingsEnabled &&
|
|
109
|
+
(typeof ctx?.view?.inputArgs?.filterByTk !== 'undefined' ||
|
|
110
|
+
typeof ctx?.view?.inputArgs?.sourceId !== 'undefined');
|
|
79
111
|
if (shouldInjectDefaultChildTab) {
|
|
80
112
|
const tabTitle = defaultTabTitle || flowEngine.translate?.('Details');
|
|
81
113
|
options['subModels'] = {
|
|
@@ -102,9 +134,12 @@ export const FlowPage = React.memo((props: FlowPageProps & Record<string, unknow
|
|
|
102
134
|
},
|
|
103
135
|
};
|
|
104
136
|
}
|
|
105
|
-
const data =
|
|
137
|
+
const data =
|
|
138
|
+
(isRuntimeRecordScopedPage && (await flowEngine.loadModel({ ...options, refresh: true }))) ||
|
|
139
|
+
(await flowEngine.loadOrCreateModel(options, { skipSave: !flowEngine.context.flowSettingsEnabled }));
|
|
106
140
|
if (data?.uid && onModelLoaded) {
|
|
107
141
|
data.context.addDelegate(ctx);
|
|
142
|
+
bindViewLayoutState(data, ctx);
|
|
108
143
|
data.removeParentDelegate();
|
|
109
144
|
onModelLoaded(data.uid, data);
|
|
110
145
|
}
|