@nocobase/flow-engine 2.0.0-alpha.9 → 2.0.0-beta.2

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.
Files changed (305) hide show
  1. package/lib/BlockScopedFlowEngine.d.ts +23 -0
  2. package/lib/BlockScopedFlowEngine.js +92 -0
  3. package/lib/FlowDefinition.d.ts +6 -4
  4. package/lib/JSRunner.js +3 -0
  5. package/lib/ViewScopedFlowEngine.js +15 -1
  6. package/lib/acl/Acl.d.ts +12 -12
  7. package/lib/acl/Acl.js +78 -30
  8. package/lib/components/DynamicFlowsEditor.js +2 -4
  9. package/lib/components/FieldModelRenderer.js +10 -8
  10. package/lib/components/FieldSkeleton.d.ts +10 -0
  11. package/lib/components/FieldSkeleton.js +64 -0
  12. package/lib/components/FlowContextSelector.js +19 -3
  13. package/lib/components/FlowModelRenderer.d.ts +2 -1
  14. package/lib/components/FlowModelRenderer.js +34 -12
  15. package/lib/components/FormItem.js +5 -1
  16. package/lib/components/MobilePopup.d.ts +20 -0
  17. package/lib/components/MobilePopup.js +102 -0
  18. package/lib/components/MobilePopup.style.d.ts +17 -0
  19. package/lib/components/MobilePopup.style.js +186 -0
  20. package/lib/components/common/withFlowDesignMode.d.ts +1 -1
  21. package/lib/components/common/withFlowDesignMode.js +5 -5
  22. package/lib/components/index.d.ts +1 -0
  23. package/lib/components/index.js +3 -1
  24. package/lib/components/settings/independents/dropdown/FlowsDropdownButton.js +71 -53
  25. package/lib/components/settings/wrappers/component/SelectWithTitle.d.ts +19 -0
  26. package/lib/components/settings/wrappers/component/SelectWithTitle.js +136 -0
  27. package/lib/components/settings/wrappers/component/SwitchWithTitle.d.ts +10 -0
  28. package/lib/components/settings/wrappers/component/SwitchWithTitle.js +110 -0
  29. package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.js +221 -93
  30. package/lib/components/settings/wrappers/contextual/FlowsContextMenu.js +71 -54
  31. package/lib/components/settings/wrappers/contextual/FlowsFloatContextMenu.d.ts +2 -2
  32. package/lib/components/settings/wrappers/contextual/FlowsFloatContextMenu.js +63 -23
  33. package/lib/components/settings/wrappers/contextual/StepSettingsDialog.js +11 -6
  34. package/lib/components/settings/wrappers/embedded/FlowSettings.js +42 -28
  35. package/lib/components/settings/wrappers/embedded/FlowsSettings.js +3 -3
  36. package/lib/components/settings/wrappers/embedded/FlowsSettingsContent.js +52 -32
  37. package/lib/components/subModel/AddSubModelButton.d.ts +7 -0
  38. package/lib/components/subModel/AddSubModelButton.js +78 -8
  39. package/lib/components/subModel/LazyDropdown.js +14 -15
  40. package/lib/components/subModel/utils.d.ts +1 -1
  41. package/lib/components/subModel/utils.js +21 -11
  42. package/lib/components/variables/VariableInput.js +5 -3
  43. package/lib/components/variables/types.d.ts +2 -0
  44. package/lib/components/variables/utils.js +4 -2
  45. package/lib/data-source/index.d.ts +43 -4
  46. package/lib/data-source/index.js +104 -11
  47. package/lib/data-source/jioToJoiSchema.js +1 -0
  48. package/lib/emitter.d.ts +6 -0
  49. package/lib/emitter.js +12 -0
  50. package/lib/executor/FlowExecutor.js +48 -7
  51. package/lib/flow-registry/GlobalFlowRegistry.d.ts +1 -0
  52. package/lib/flow-registry/GlobalFlowRegistry.js +3 -0
  53. package/lib/flow-registry/InstanceFlowRegistry.d.ts +1 -0
  54. package/lib/flow-registry/InstanceFlowRegistry.js +3 -0
  55. package/lib/flowContext.d.ts +6 -0
  56. package/lib/flowContext.js +111 -30
  57. package/lib/flowEngine.d.ts +49 -0
  58. package/lib/flowEngine.js +265 -10
  59. package/lib/flowSettings.d.ts +4 -3
  60. package/lib/flowSettings.js +33 -11
  61. package/lib/hooks/useApplyAutoFlows.d.ts +1 -0
  62. package/lib/hooks/useApplyAutoFlows.js +2 -2
  63. package/lib/index.d.ts +4 -2
  64. package/lib/index.js +11 -5
  65. package/lib/locale/de-DE.json +62 -0
  66. package/lib/locale/en-US.json +57 -45
  67. package/lib/locale/es-ES.json +62 -0
  68. package/lib/locale/fr-FR.json +62 -0
  69. package/lib/locale/hu-HU.json +62 -0
  70. package/lib/locale/id-ID.json +62 -0
  71. package/lib/locale/index.d.ts +114 -90
  72. package/lib/locale/it-IT.json +62 -0
  73. package/lib/locale/ja-JP.json +62 -0
  74. package/lib/locale/ko-KR.json +62 -0
  75. package/lib/locale/nl-NL.json +62 -0
  76. package/lib/locale/pt-BR.json +62 -0
  77. package/lib/locale/ru-RU.json +62 -0
  78. package/lib/locale/tr-TR.json +62 -0
  79. package/lib/locale/uk-UA.json +62 -0
  80. package/lib/locale/vi-VN.json +62 -0
  81. package/lib/locale/zh-CN.json +58 -46
  82. package/lib/locale/zh-TW.json +62 -0
  83. package/lib/models/CollectionFieldModel.d.ts +6 -2
  84. package/lib/models/CollectionFieldModel.js +60 -14
  85. package/lib/models/flowModel.d.ts +43 -4
  86. package/lib/models/flowModel.js +128 -26
  87. package/lib/models/forkFlowModel.d.ts +6 -2
  88. package/lib/models/forkFlowModel.js +9 -2
  89. package/lib/provider.d.ts +3 -1
  90. package/lib/provider.js +4 -3
  91. package/lib/reactive/index.d.ts +10 -0
  92. package/lib/reactive/index.js +41 -0
  93. package/lib/reactive/observer.d.ts +19 -0
  94. package/lib/reactive/observer.js +109 -0
  95. package/lib/resources/baseRecordResource.d.ts +1 -0
  96. package/lib/resources/baseRecordResource.js +14 -3
  97. package/lib/resources/multiRecordResource.d.ts +4 -2
  98. package/lib/resources/multiRecordResource.js +15 -6
  99. package/lib/resources/singleRecordResource.js +6 -3
  100. package/lib/resources/sqlResource.d.ts +1 -0
  101. package/lib/resources/sqlResource.js +22 -25
  102. package/lib/runjs-context/contexts/base.js +42 -6
  103. package/lib/runjs-context/snippets/global/clipboard-copy-text.snippet.d.ts +11 -0
  104. package/lib/runjs-context/snippets/global/clipboard-copy-text.snippet.js +61 -0
  105. package/lib/runjs-context/snippets/index.js +3 -0
  106. package/lib/runjs-context/snippets/scene/block/render-antd-icons.snippet.d.ts +11 -0
  107. package/lib/runjs-context/snippets/scene/block/render-antd-icons.snippet.js +65 -0
  108. package/lib/runjs-context/snippets/scene/block/render-button-handler.snippet.js +6 -4
  109. package/lib/runjs-context/snippets/scene/block/render-info-card.snippet.js +15 -16
  110. package/lib/runjs-context/snippets/scene/block/render-react-jsx.snippet.d.ts +11 -0
  111. package/lib/runjs-context/snippets/scene/block/render-react-jsx.snippet.js +58 -0
  112. package/lib/runjs-context/snippets/scene/block/render-react.snippet.js +7 -7
  113. package/lib/runjs-context/snippets/scene/block/render-statistics.snippet.js +24 -29
  114. package/lib/runjs-context/snippets/scene/block/render-timeline.snippet.js +20 -21
  115. package/lib/scheduler/ModelOperationScheduler.d.ts +51 -0
  116. package/lib/scheduler/ModelOperationScheduler.js +262 -0
  117. package/lib/types.d.ts +42 -7
  118. package/lib/types.js +4 -3
  119. package/lib/utils/associationObjectVariable.d.ts +32 -0
  120. package/lib/utils/associationObjectVariable.js +157 -0
  121. package/lib/utils/createCollectionContextMeta.d.ts +1 -1
  122. package/lib/utils/createCollectionContextMeta.js +8 -4
  123. package/lib/utils/createEphemeralContext.d.ts +13 -0
  124. package/lib/utils/createEphemeralContext.js +140 -0
  125. package/lib/utils/flows.d.ts +10 -0
  126. package/lib/utils/flows.js +48 -0
  127. package/lib/utils/index.d.ts +7 -3
  128. package/lib/utils/index.js +20 -0
  129. package/lib/utils/jsxTransform.d.ts +15 -0
  130. package/lib/utils/jsxTransform.js +68 -0
  131. package/lib/utils/params-resolvers.js +3 -3
  132. package/lib/utils/parsePathnameToViewParams.d.ts +1 -1
  133. package/lib/utils/parsePathnameToViewParams.js +41 -5
  134. package/lib/utils/pruneFilter.d.ts +21 -0
  135. package/lib/utils/pruneFilter.js +52 -0
  136. package/lib/utils/safeGlobals.d.ts +5 -3
  137. package/lib/utils/safeGlobals.js +42 -1
  138. package/lib/utils/schema-utils.d.ts +6 -0
  139. package/lib/utils/schema-utils.js +71 -6
  140. package/lib/utils/serverContextParams.d.ts +3 -0
  141. package/lib/utils/serverContextParams.js +2 -0
  142. package/lib/utils/translation.d.ts +4 -1
  143. package/lib/utils/translation.js +6 -2
  144. package/lib/utils/variablesParams.d.ts +21 -5
  145. package/lib/utils/variablesParams.js +103 -34
  146. package/lib/views/DialogComponent.js +1 -5
  147. package/lib/views/DrawerComponent.js +18 -9
  148. package/lib/views/PageComponent.js +3 -4
  149. package/lib/views/ViewNavigation.d.ts +11 -15
  150. package/lib/views/ViewNavigation.js +37 -19
  151. package/lib/views/createViewMeta.d.ts +3 -2
  152. package/lib/views/createViewMeta.js +164 -53
  153. package/lib/views/useDialog.d.ts +2 -1
  154. package/lib/views/useDialog.js +36 -30
  155. package/lib/views/useDrawer.d.ts +2 -1
  156. package/lib/views/useDrawer.js +33 -26
  157. package/lib/views/usePage.d.ts +2 -1
  158. package/lib/views/usePage.js +40 -29
  159. package/package.json +6 -3
  160. package/src/BlockScopedFlowEngine.ts +88 -0
  161. package/src/JSRunner.ts +3 -0
  162. package/src/ViewScopedFlowEngine.ts +16 -0
  163. package/src/__tests__/JSRunner.test.ts +62 -53
  164. package/src/__tests__/blockScopedFlowEngine.test.ts +154 -0
  165. package/src/__tests__/createViewMeta.popup.test.ts +142 -0
  166. package/src/__tests__/flow-engine.test.ts +3 -0
  167. package/src/__tests__/flowContext.test.ts +70 -0
  168. package/src/__tests__/flowEngine.destroyModel.test.ts +74 -0
  169. package/src/__tests__/flowEngine.moveModel.test.ts +43 -0
  170. package/src/__tests__/flowEngine.removeModel.test.ts +72 -0
  171. package/src/__tests__/flowEngine.saveModel.test.ts +4 -0
  172. package/src/__tests__/flowModel.openView.navigation.test.ts +3 -2
  173. package/src/__tests__/flowSettings.open.test.tsx +2 -0
  174. package/src/__tests__/flowSettings.test.ts +2 -0
  175. package/src/__tests__/globalFlowRegistry.test.ts +1 -1
  176. package/src/__tests__/modelOperationScheduler.test.ts +346 -0
  177. package/src/__tests__/objectVariable.test.ts +464 -0
  178. package/src/__tests__/runjsRuntimeFeatures.test.ts +12 -0
  179. package/src/__tests__/viewScopedFlowEngine.test.ts +98 -0
  180. package/src/acl/Acl.tsx +85 -31
  181. package/src/acl/__tests__/Acl.test.tsx +43 -1
  182. package/src/components/DynamicFlowsEditor.tsx +0 -10
  183. package/src/components/FieldModelRenderer.tsx +15 -8
  184. package/src/components/FieldSkeleton.tsx +27 -0
  185. package/src/components/FlowContextSelector.tsx +20 -2
  186. package/src/components/FlowModelRenderer.tsx +46 -12
  187. package/src/components/FormItem.tsx +8 -1
  188. package/src/components/MobilePopup.style.ts +220 -0
  189. package/src/components/MobilePopup.tsx +86 -0
  190. package/src/components/__tests__/FlowModelRenderer.test.tsx +89 -0
  191. package/src/components/__tests__/flow-model-render-error-fallback.test.tsx +1 -1
  192. package/src/components/common/withFlowDesignMode.tsx +5 -5
  193. package/src/components/index.ts +1 -0
  194. package/src/components/settings/independents/dropdown/FlowsDropdownButton.tsx +34 -17
  195. package/src/components/settings/wrappers/component/SelectWithTitle.tsx +110 -0
  196. package/src/components/settings/wrappers/component/SwitchWithTitle.tsx +82 -0
  197. package/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx +260 -121
  198. package/src/components/settings/wrappers/contextual/FlowsContextMenu.tsx +34 -18
  199. package/src/components/settings/wrappers/contextual/FlowsFloatContextMenu.tsx +56 -18
  200. package/src/components/settings/wrappers/contextual/StepSettings.tsx +1 -2
  201. package/src/components/settings/wrappers/contextual/StepSettingsDialog.tsx +12 -6
  202. package/src/components/settings/wrappers/contextual/__tests__/DefaultSettingsIcon.test.tsx +565 -0
  203. package/src/components/settings/wrappers/embedded/FlowSettings.tsx +47 -35
  204. package/src/components/settings/wrappers/embedded/FlowsSettings.tsx +1 -1
  205. package/src/components/settings/wrappers/embedded/FlowsSettingsContent.tsx +64 -42
  206. package/src/components/subModel/AddSubModelButton.tsx +104 -9
  207. package/src/components/subModel/LazyDropdown.tsx +14 -14
  208. package/src/components/subModel/__tests__/AddSubModelButton.test.tsx +168 -7
  209. package/src/components/subModel/__tests__/utils.test.ts +12 -12
  210. package/src/components/subModel/utils.ts +25 -6
  211. package/src/components/variables/VariableInput.tsx +5 -3
  212. package/src/components/variables/types.ts +2 -0
  213. package/src/components/variables/utils.ts +7 -3
  214. package/src/data-source/index.ts +169 -11
  215. package/src/data-source/jioToJoiSchema.ts +1 -0
  216. package/src/emitter.ts +14 -0
  217. package/src/executor/FlowExecutor.ts +56 -8
  218. package/src/executor/__tests__/ctx-defs-injection.test.ts +197 -0
  219. package/src/flow-registry/GlobalFlowRegistry.ts +1 -0
  220. package/src/flow-registry/InstanceFlowRegistry.ts +1 -0
  221. package/src/flow-registry/__tests__/globalFlowRegistry.test.ts +54 -0
  222. package/src/flowContext.ts +144 -29
  223. package/src/flowEngine.ts +328 -8
  224. package/src/flowSettings.ts +47 -19
  225. package/src/hooks/useApplyAutoFlows.ts +3 -3
  226. package/src/index.ts +4 -2
  227. package/src/locale/de-DE.json +62 -0
  228. package/src/locale/en-US.json +57 -45
  229. package/src/locale/es-ES.json +62 -0
  230. package/src/locale/fr-FR.json +62 -0
  231. package/src/locale/hu-HU.json +62 -0
  232. package/src/locale/id-ID.json +62 -0
  233. package/src/locale/it-IT.json +62 -0
  234. package/src/locale/ja-JP.json +62 -0
  235. package/src/locale/ko-KR.json +62 -0
  236. package/src/locale/nl-NL.json +62 -0
  237. package/src/locale/pt-BR.json +62 -0
  238. package/src/locale/ru-RU.json +62 -0
  239. package/src/locale/tr-TR.json +62 -0
  240. package/src/locale/uk-UA.json +62 -0
  241. package/src/locale/vi-VN.json +62 -0
  242. package/src/locale/zh-CN.json +58 -46
  243. package/src/locale/zh-TW.json +62 -0
  244. package/src/models/CollectionFieldModel.tsx +79 -17
  245. package/src/models/__tests__/dispatchEvent.behavior.test.ts +169 -0
  246. package/src/models/__tests__/flowEngine.resolveUse.test.ts +170 -0
  247. package/src/models/__tests__/flowModel.getFlows.sort.test.ts +29 -5
  248. package/src/models/__tests__/flowModel.scheduleModelOperation.test.tsx +129 -0
  249. package/src/models/__tests__/flowModel.test.ts +65 -27
  250. package/src/models/__tests__/forkFlowModel.test.ts +40 -7
  251. package/src/models/flowModel.tsx +192 -30
  252. package/src/models/forkFlowModel.ts +11 -3
  253. package/src/provider.tsx +5 -5
  254. package/src/reactive/__tests__/observer.test.tsx +211 -0
  255. package/src/reactive/index.ts +11 -0
  256. package/src/reactive/observer.tsx +101 -0
  257. package/src/resources/baseRecordResource.ts +15 -3
  258. package/src/resources/multiRecordResource.ts +17 -8
  259. package/src/resources/singleRecordResource.ts +6 -3
  260. package/src/resources/sqlResource.ts +22 -26
  261. package/src/runjs-context/contexts/base.ts +47 -6
  262. package/src/runjs-context/snippets/global/clipboard-copy-text.snippet.ts +42 -0
  263. package/src/runjs-context/snippets/index.ts +3 -0
  264. package/src/runjs-context/snippets/scene/block/render-antd-icons.snippet.ts +46 -0
  265. package/src/runjs-context/snippets/scene/block/render-button-handler.snippet.ts +6 -4
  266. package/src/runjs-context/snippets/scene/block/render-info-card.snippet.ts +15 -16
  267. package/src/runjs-context/snippets/scene/block/render-react-jsx.snippet.ts +39 -0
  268. package/src/runjs-context/snippets/scene/block/render-react.snippet.ts +7 -7
  269. package/src/runjs-context/snippets/scene/block/render-statistics.snippet.ts +24 -29
  270. package/src/runjs-context/snippets/scene/block/render-timeline.snippet.ts +20 -21
  271. package/src/scheduler/ModelOperationScheduler.ts +304 -0
  272. package/src/types.ts +50 -4
  273. package/src/utils/__tests__/createCollectionContextMeta.test.ts +51 -0
  274. package/src/utils/__tests__/flows.test.ts +65 -0
  275. package/src/utils/__tests__/jsxTransform.test.ts +38 -0
  276. package/src/utils/__tests__/parsePathnameToViewParams.test.ts +25 -0
  277. package/src/utils/__tests__/pruneFilter.test.ts +38 -0
  278. package/src/utils/__tests__/safeGlobals.test.ts +23 -1
  279. package/src/utils/__tests__/utils.test.ts +114 -15
  280. package/src/utils/__tests__/variablesParams.test.ts +120 -0
  281. package/src/utils/associationObjectVariable.ts +180 -0
  282. package/src/utils/createCollectionContextMeta.ts +8 -3
  283. package/src/utils/createEphemeralContext.ts +142 -0
  284. package/src/utils/flows.ts +23 -0
  285. package/src/utils/index.ts +11 -2
  286. package/src/utils/jsxTransform.ts +39 -0
  287. package/src/utils/params-resolvers.ts +2 -2
  288. package/src/utils/parsePathnameToViewParams.ts +50 -6
  289. package/src/utils/pruneFilter.ts +41 -0
  290. package/src/utils/safeGlobals.ts +51 -4
  291. package/src/utils/schema-utils.ts +81 -3
  292. package/src/utils/serverContextParams.ts +5 -0
  293. package/src/utils/translation.ts +7 -2
  294. package/src/utils/variablesParams.ts +125 -42
  295. package/src/views/DialogComponent.tsx +1 -4
  296. package/src/views/DrawerComponent.tsx +19 -7
  297. package/src/views/PageComponent.tsx +2 -4
  298. package/src/views/ViewNavigation.ts +49 -43
  299. package/src/views/__tests__/FlowView.usePage.test.tsx +133 -0
  300. package/src/views/__tests__/ViewNavigation.test.ts +54 -34
  301. package/src/views/__tests__/useDialog.closeDestroy.test.tsx +132 -0
  302. package/src/views/createViewMeta.ts +179 -42
  303. package/src/views/useDialog.tsx +36 -24
  304. package/src/views/useDrawer.tsx +37 -24
  305. package/src/views/usePage.tsx +46 -27
@@ -0,0 +1,11 @@
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 '@formily/reactive';
11
+ export { observer, getPageActive } from './observer';
@@ -0,0 +1,101 @@
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 { observer as originalObserver, IObserverOptions, ReactFC } from '@formily/reactive-react';
11
+ import React, { useMemo, useEffect, useRef } from 'react';
12
+ import { useFlowContext } from '../FlowContextProvider';
13
+ import { autorun } from '@formily/reactive';
14
+ import { FlowEngineContext } from '..';
15
+
16
+ type ObserverComponentProps<P, Options extends IObserverOptions> = Options extends {
17
+ forwardRef: true;
18
+ }
19
+ ? P & {
20
+ ref?: 'ref' extends keyof P ? P['ref'] : React.RefAttributes<any>;
21
+ }
22
+ : React.PropsWithoutRef<P>;
23
+
24
+ export const observer = <P, Options extends IObserverOptions = IObserverOptions>(
25
+ Component: ReactFC<P>,
26
+ options?: Options,
27
+ ): React.MemoExoticComponent<ReactFC<ObserverComponentProps<P, Options>>> => {
28
+ const ComponentWithDefaultScheduler = React.memo((props: any) => {
29
+ const ctx = useFlowContext();
30
+ const ctxRef = useRef(ctx);
31
+ ctxRef.current = ctx;
32
+
33
+ // Store the pending disposer to avoid creating multiple listeners
34
+ const pendingDisposerRef = useRef<(() => void) | null>(null);
35
+
36
+ // Cleanup on unmount
37
+ useEffect(() => {
38
+ return () => {
39
+ if (pendingDisposerRef.current) {
40
+ pendingDisposerRef.current();
41
+ pendingDisposerRef.current = null;
42
+ }
43
+ };
44
+ }, []);
45
+
46
+ const ObservedComponent = useMemo(
47
+ () =>
48
+ originalObserver(Component, {
49
+ scheduler(updater) {
50
+ const pageActive = getPageActive(ctxRef.current);
51
+ const tabActive = ctxRef.current?.tabActive?.value;
52
+
53
+ if (pageActive === false || tabActive === false) {
54
+ // Avoid stack overflow
55
+ setTimeout(() => {
56
+ // If there is already a pending updater, do nothing
57
+ if (pendingDisposerRef.current) {
58
+ return;
59
+ }
60
+
61
+ // Delay the update until the page and tab become active
62
+ const disposer = autorun(() => {
63
+ if (
64
+ ctxRef.current?.pageActive?.value &&
65
+ (ctxRef.current?.tabActive?.value === true || ctxRef.current?.tabActive?.value === undefined)
66
+ ) {
67
+ updater();
68
+ disposer?.();
69
+ pendingDisposerRef.current = null;
70
+ }
71
+ });
72
+ pendingDisposerRef.current = disposer;
73
+ });
74
+ return;
75
+ }
76
+
77
+ // If we are updating immediately, clear any pending disposer
78
+ if (pendingDisposerRef.current) {
79
+ pendingDisposerRef.current();
80
+ pendingDisposerRef.current = null;
81
+ }
82
+
83
+ updater();
84
+ },
85
+ ...options,
86
+ }),
87
+ [],
88
+ );
89
+ return <ObservedComponent {...props} />;
90
+ });
91
+
92
+ ComponentWithDefaultScheduler.displayName = `ComponentWithDefaultScheduler`;
93
+
94
+ return ComponentWithDefaultScheduler as React.MemoExoticComponent<ReactFC<ObserverComponentProps<P, Options>>>;
95
+ };
96
+
97
+ export function getPageActive(context: FlowEngineContext): boolean | undefined {
98
+ return typeof context?.pageActive?.value === 'boolean'
99
+ ? context?.pageActive?.value
100
+ : context?.view?.inputArgs.pageActive;
101
+ }
@@ -95,6 +95,7 @@ export abstract class BaseRecordResource<TData = any> extends APIResource<TData>
95
95
  }
96
96
 
97
97
  async runAction<TData = any, TMeta = any>(action: string, options: any) {
98
+ const { rawResponse, ...rest } = options;
98
99
  const config = this.mergeRequestConfig(
99
100
  _.omit(this.request, ['params', 'data', 'method']),
100
101
  {
@@ -102,14 +103,18 @@ export abstract class BaseRecordResource<TData = any> extends APIResource<TData>
102
103
  url: this.buildURL(action),
103
104
  },
104
105
  this.runActionOptions?.[action],
105
- options,
106
+ rest,
106
107
  );
107
108
  if (['create', 'update', 'firstOrCreate', 'updateOrCreate'].includes(action)) {
108
109
  config.params = config.params || {};
109
110
  config.params.updateAssociationValues = this.getUpdateAssociationValues();
110
111
  }
111
112
  try {
112
- const { data } = await this.api.request(config);
113
+ const response = await this.api.request(config);
114
+ if (rawResponse) {
115
+ return response;
116
+ }
117
+ const { data } = response;
113
118
  if (!data?.data) {
114
119
  return data;
115
120
  }
@@ -227,8 +232,15 @@ export abstract class BaseRecordResource<TData = any> extends APIResource<TData>
227
232
  return this;
228
233
  }
229
234
 
235
+ jsonStringify(value: any): string {
236
+ if (value !== null && typeof value === 'object') {
237
+ return JSON.stringify(value);
238
+ }
239
+ return value;
240
+ }
241
+
230
242
  setFilterByTk(filterByTk: any) {
231
- return this.addRequestParameter('filterByTk', filterByTk);
243
+ return this.addRequestParameter('filterByTk', this.jsonStringify(filterByTk));
232
244
  }
233
245
 
234
246
  getFilterByTk(): any {
@@ -110,10 +110,14 @@ export class MultiRecordResource<TDataItem = any> extends BaseRecordResource<TDa
110
110
  }
111
111
  }
112
112
 
113
- async create(data: TDataItem, options?: AxiosRequestConfig): Promise<void> {
113
+ async create(data: TDataItem, options?: AxiosRequestConfig & { refresh?: boolean }): Promise<void> {
114
114
  const config = this.mergeRequestConfig({ data }, this.createActionOptions, options);
115
- await this.runAction('create', config);
116
- await this.refresh();
115
+ const res = await this.runAction('create', config);
116
+ this.emit('saved', data);
117
+ if (options?.refresh !== false) {
118
+ await this.refresh();
119
+ }
120
+ return res;
117
121
  }
118
122
 
119
123
  async get(filterByTk: any): Promise<TDataItem | undefined> {
@@ -129,17 +133,19 @@ export class MultiRecordResource<TDataItem = any> extends BaseRecordResource<TDa
129
133
  }
130
134
 
131
135
  async update(filterByTk: string | number, data: Partial<TDataItem>, options?: AxiosRequestConfig): Promise<void> {
136
+ const result = data;
132
137
  const config = this.mergeRequestConfig(
133
138
  {
134
139
  params: {
135
140
  filterByTk,
136
141
  },
137
- data,
142
+ data: result,
138
143
  },
139
144
  this.updateActionOptions,
140
145
  options,
141
146
  );
142
147
  await this.runAction('update', config);
148
+ this.emit('saved', data);
143
149
  await this.refresh();
144
150
  }
145
151
 
@@ -152,20 +158,23 @@ export class MultiRecordResource<TDataItem = any> extends BaseRecordResource<TDa
152
158
  }
153
159
 
154
160
  async destroy(
155
- filterByTk: string | number | string[] | number[] | TDataItem | TDataItem[],
161
+ filterByTk: string | number | string[] | number[] | TDataItem | TDataItem[] | object,
156
162
  options?: AxiosRequestConfig,
157
163
  ): Promise<void> {
158
164
  const config = this.mergeRequestConfig(
159
165
  {
160
166
  params: {
161
- filterByTk: _.castArray(filterByTk).map((item) => {
162
- return typeof item === 'object' ? item['id'] : item; // TODO: ID 字段还需要根据实际情况更改
163
- }),
167
+ filterByTk: this.jsonStringify(filterByTk),
164
168
  },
165
169
  },
166
170
  options,
167
171
  );
168
172
  await this.runAction('destroy', config);
173
+ const currentPage = this.getPage();
174
+ const lastPage = Math.ceil((this.getCount() - _.castArray(filterByTk).length) / this.getPageSize());
175
+ if (currentPage > lastPage) {
176
+ this.setPage(lastPage || 1);
177
+ }
169
178
  await this.refresh();
170
179
  }
171
180
 
@@ -33,26 +33,29 @@ export class SingleRecordResource<TData = any> extends BaseRecordResource<TData>
33
33
  async save(data: TData, options?: AxiosRequestConfig & { refresh?: boolean }): Promise<void> {
34
34
  const config = this.mergeRequestConfig(this.saveActionOptions, _.omit(options, ['refresh']));
35
35
  let actionName = 'create';
36
+ const result = data;
36
37
  // 如果有 filterByTk,则表示是更新操作
37
38
  if (!this.isNewRecord) {
38
39
  config.params = config.params || {};
39
40
  config.params.filterByTk = this.getFilterByTk();
40
41
  actionName = 'update';
41
42
  }
42
- await this.runAction(actionName, {
43
+ const res = await this.runAction(actionName, {
43
44
  ...config,
44
- data,
45
+ data: result,
45
46
  });
47
+ this.emit('saved', data);
46
48
  if (options?.refresh !== false) {
47
49
  await this.refresh();
48
50
  }
51
+ return res;
49
52
  }
50
53
 
51
54
  async destroy(options?: AxiosRequestConfig): Promise<void> {
52
55
  const config = this.mergeRequestConfig(
53
56
  {
54
57
  params: {
55
- filterByTk: this.request.params.filterByTk,
58
+ filterByTk: this.jsonStringify(this.request.params.filterByTk),
56
59
  },
57
60
  },
58
61
  options,
@@ -11,6 +11,7 @@ import { observable } from '@formily/reactive';
11
11
  import _ from 'lodash';
12
12
  import { FlowContext, FlowEngineContext } from '../flowContext';
13
13
  import { BaseRecordResource } from './baseRecordResource';
14
+ import { parseLiquidContext, transformSQL } from '@nocobase/utils/client';
14
15
 
15
16
  type SQLRunOptions = {
16
17
  bind?: Record<string, any>;
@@ -25,20 +26,6 @@ type SQLSaveOptions = {
25
26
  dataSourceKey?: string;
26
27
  };
27
28
 
28
- function transformSQL(template: string) {
29
- let index = 1;
30
- const bind = {};
31
-
32
- const sql = template.replace(/{{\s*([^}]+)\s*}}/g, (_, expr) => {
33
- const key = `__var${index}`;
34
- bind[key] = `{{${expr.trim()}}}`;
35
- index++;
36
- return `$${key}`;
37
- });
38
-
39
- return { sql, bind };
40
- }
41
-
42
29
  export class FlowSQLRepository {
43
30
  protected ctx: FlowEngineContext;
44
31
 
@@ -56,16 +43,17 @@ export class FlowSQLRepository {
56
43
  }
57
44
 
58
45
  async run(sql: string, options: SQLRunOptions = {}) {
59
- const result = transformSQL(sql);
60
- const bind = await this.ctx.resolveJsonTemplate(result.bind);
46
+ const { sql: transformedSQL, bind, liquidContext } = await transformSQL(sql);
47
+ const resolved = await this.ctx.resolveJsonTemplate({ bind, liquidContext });
48
+ const parsedSQL = await parseLiquidContext(transformedSQL, resolved.liquidContext);
61
49
  const { data } = await this.ctx.api.request({
62
50
  method: 'POST',
63
51
  url: 'flowSql:run',
64
52
  data: {
65
- sql: result.sql,
53
+ sql: parsedSQL,
66
54
  ...options,
67
55
  bind: {
68
- ...bind,
56
+ ...resolved.bind,
69
57
  ...options.bind,
70
58
  },
71
59
  },
@@ -91,7 +79,7 @@ export class FlowSQLRepository {
91
79
  uid,
92
80
  },
93
81
  });
94
- const bind = await this.ctx.resolveJsonTemplate(response.data.data || {});
82
+ const { bind, liquidContext } = await this.ctx.resolveJsonTemplate(response.data.data || {});
95
83
  const { data } = await this.ctx.api.request({
96
84
  method: 'POST',
97
85
  url: 'flowSql:runById',
@@ -100,8 +88,9 @@ export class FlowSQLRepository {
100
88
  ...options,
101
89
  bind: {
102
90
  ...bind,
103
- ...options.bind,
91
+ ...options?.bind,
104
92
  },
93
+ liquidContext,
105
94
  },
106
95
  });
107
96
  return data?.data;
@@ -233,20 +222,25 @@ export class SQLResource<TData = any> extends BaseRecordResource<TData> {
233
222
  return this;
234
223
  }
235
224
 
225
+ setLiquidContext(liquidContext: Record<string, any>) {
226
+ this.request.data.liquidContext = liquidContext;
227
+ return this;
228
+ }
229
+
236
230
  async run() {
237
231
  return this._debugEnabled ? await this.runBySQL() : await this.runById();
238
232
  }
239
233
 
240
234
  async runBySQL() {
241
235
  const sql = this._sql;
242
- const result = transformSQL(sql);
243
- const bind = await this.context.resolveJsonTemplate(result.bind);
236
+ const { sql: transformedSQL, bind, liquidContext } = await transformSQL(sql);
237
+ const resolved = await this.context.resolveJsonTemplate({ bind, liquidContext });
244
238
  const options = _.cloneDeep({
245
239
  method: 'post',
246
240
  ...this.getRefreshRequestOptions(),
247
241
  });
248
- options.data.bind = bind;
249
- options.data.sql = result.sql;
242
+ options.data.sql = await parseLiquidContext(transformedSQL, resolved.liquidContext);
243
+ options.data.bind = resolved.bind;
250
244
  return await this.runAction<TData, any>('run', options);
251
245
  }
252
246
 
@@ -257,8 +251,9 @@ export class SQLResource<TData = any> extends BaseRecordResource<TData> {
257
251
  uid: this.request.data.uid,
258
252
  },
259
253
  });
260
- const bind = await this.context.resolveJsonTemplate(data);
254
+ const { bind, liquidContext } = await this.context.resolveJsonTemplate(data);
261
255
  this.setBind(bind);
256
+ this.setLiquidContext(liquidContext);
262
257
  return await this.runAction<TData, any>('runById', {
263
258
  method: 'post',
264
259
  ...this.getRefreshRequestOptions(),
@@ -281,10 +276,11 @@ export class SQLResource<TData = any> extends BaseRecordResource<TData> {
281
276
  try {
282
277
  this.clearError();
283
278
  this.loading = true;
279
+ this.emit('loading');
284
280
  const { data, meta } = await this.run();
285
281
  this.setData(data).setMeta(meta);
286
- this.emit('refresh');
287
282
  this.loading = false;
283
+ this.emit('refresh');
288
284
  resolve();
289
285
  } catch (error) {
290
286
  this.setError(error);
@@ -44,12 +44,33 @@ export function defineBaseContextMeta() {
44
44
  language: 'Current active language code.',
45
45
  },
46
46
  },
47
- React: 'React namespace providing React library functions and hooks (available in RunJS environment)',
48
- ReactDOM: 'ReactDOM client API including createRoot for rendering React components',
49
- antd: 'Ant Design component library',
47
+ React:
48
+ 'React namespace providing React library functions and hooks (available in RunJS environment). Recommended access path: `ctx.libs.React`.',
49
+ ReactDOM:
50
+ 'ReactDOM client API including createRoot for rendering React components. Also available via `ctx.libs.ReactDOM`.',
51
+ antd: 'Ant Design component library. Recommended access path: `ctx.libs.antd`.',
52
+ libs: {
53
+ description: 'Namespace for third-party and shared libraries. Includes React, ReactDOM, Ant Design, and dayjs.',
54
+ detail: 'Libraries namespace',
55
+ properties: {
56
+ React: 'React namespace (same as ctx.React).',
57
+ ReactDOM: 'ReactDOM client API (same as ctx.ReactDOM).',
58
+ antd: 'Ant Design component library (same as ctx.antd).',
59
+ dayjs: 'dayjs date-time utility library.',
60
+ antdIcons: 'Ant Design icons library. Example: `ctx.libs.antdIcons.PlusOutlined`.',
61
+ },
62
+ },
50
63
  },
51
64
  methods: {
52
65
  t: 'Internationalization function for translating text. Parameters: (key: string, variables?: object) => string. Example: `ctx.t("Hello {name}", { name: "World" })`',
66
+ render: {
67
+ description:
68
+ 'Render into container. Accepts ReactElement, DOM Node/Fragment, or HTML string. Parameters: (vnode: ReactElement | Node | DocumentFragment | string, container?: HTMLElement|ElementProxy) => Root|null. Example: `ctx.render(<div>Hello</div>)` or `ctx.render("<b>hi</b>")`',
69
+ detail: 'ReactDOM Root',
70
+ completion: {
71
+ insertText: `ctx.render(<div />)`,
72
+ },
73
+ },
53
74
  requireAsync:
54
75
  'Asynchronously load external libraries from URL. Parameters: (url: string) => Promise<any>. Example: `const lodash = await ctx.requireAsync("https://cdn.jsdelivr.net/npm/lodash")`',
55
76
  importAsync:
@@ -121,12 +142,32 @@ export function defineBaseContextMeta() {
121
142
  language: '当前激活的语言代码',
122
143
  },
123
144
  },
124
- React: 'React 命名空间,提供 React 函数与 hooks(RunJS 环境中可用)',
125
- ReactDOM: 'ReactDOM 客户端 API,含 createRoot 等渲染方法',
126
- antd: 'Ant Design 组件库(RunJS 环境中可用)',
145
+ React: 'React 命名空间,提供 React 函数与 hooks(RunJS 环境中可用)。推荐使用 `ctx.libs.React` 访问。',
146
+ ReactDOM: 'ReactDOM 客户端 API,含 createRoot 等渲染方法。推荐通过 `ctx.libs.ReactDOM` 访问。',
147
+ antd: 'Ant Design 组件库(RunJS 环境中可用)。推荐使用 `ctx.libs.antd` 访问。',
148
+ libs: {
149
+ description:
150
+ '第三方/通用库的统一命名空间,包含 React、ReactDOM、Ant Design、dayjs 等。后续新增库会优先挂在此处。',
151
+ detail: '通用库命名空间',
152
+ properties: {
153
+ React: 'React 命名空间(等价于 ctx.React)。',
154
+ ReactDOM: 'ReactDOM 客户端 API(等价于 ctx.ReactDOM)。',
155
+ antd: 'Ant Design 组件库(等价于 ctx.antd)。',
156
+ dayjs: 'dayjs 日期时间工具库。',
157
+ antdIcons: 'Ant Design 图标库。 例如:`ctx.libs.antdIcons.PlusOutlined`。',
158
+ },
159
+ },
127
160
  },
128
161
  methods: {
129
162
  t: '国际化函数,用于翻译文案。参数:(key: string, variables?: object) => string。示例:`ctx.t("你好 {name}", { name: "世界" })`',
163
+ render: {
164
+ description:
165
+ '渲染到容器。vnode 支持 ReactElement、DOM 节点/片段、或 HTML 字符串。参数:(vnode: ReactElement | Node | DocumentFragment | string, container?: HTMLElement|ElementProxy) => Root|null。示例:`ctx.render(<div />)` 或 `ctx.render("<b>hi</b>")`',
166
+ detail: 'ReactDOM Root',
167
+ completion: {
168
+ insertText: `ctx.render(<div />)`,
169
+ },
170
+ },
130
171
  requireAsync:
131
172
  '按 URL 异步加载外部库。参数:(url: string) => Promise<any>。示例:`const lodash = await ctx.requireAsync("https://cdn.jsdelivr.net/npm/lodash")`',
132
173
  importAsync:
@@ -0,0 +1,42 @@
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 type { SnippetModule } from '../types';
11
+
12
+ const snippet: SnippetModule = {
13
+ contexts: ['*'],
14
+ prefix: 'sn-clipboard-copy',
15
+ label: 'Copy text to clipboard (function)',
16
+ description: 'A reusable function that copies a given string to the clipboard.',
17
+ locales: {
18
+ 'zh-CN': {
19
+ label: '复制文本到剪贴板(函数)',
20
+ description: '通用函数:接受一个字符串参数并复制到剪贴板。',
21
+ },
22
+ },
23
+ content: `
24
+ // A general utility function that copies text to clipboard.
25
+ // Usage:
26
+ // const ok = await copyTextToClipboard('Hello');
27
+ // if (ok) { /* success */ } else { /* handle failure */ }
28
+ async function copyTextToClipboard(text) {
29
+ const s = String(text ?? '');
30
+ try {
31
+ if (navigator?.clipboard?.writeText) {
32
+ await navigator.clipboard.writeText(s);
33
+ return true;
34
+ }
35
+ } catch (_) {
36
+ // Fallback below
37
+ }
38
+ }
39
+ `,
40
+ };
41
+
42
+ export default snippet;
@@ -22,10 +22,13 @@ const snippets: Record<string, () => Promise<any>> = {
22
22
  'global/open-view-drawer': () => import('./global/open-view-drawer.snippet'),
23
23
  'global/open-view-dialog': () => import('./global/open-view-dialog.snippet'),
24
24
  'global/query-selector': () => import('./global/query-selector.snippet'),
25
+ 'global/clipboard-copy-text': () => import('./global/clipboard-copy-text.snippet'),
25
26
  // libs
26
27
  'scene/block/echarts-init': () => import('./scene/block/echarts-init.snippet'),
27
28
  // scene/block
28
29
  'scene/block/render-react': () => import('./scene/block/render-react.snippet'),
30
+ 'scene/block/render-react-jsx': () => import('./scene/block/render-react-jsx.snippet'),
31
+ 'scene/block/render-antd-icons': () => import('./scene/block/render-antd-icons.snippet'),
29
32
  'scene/block/render-button-handler': () => import('./scene/block/render-button-handler.snippet'),
30
33
  'scene/block/add-event-listener': () => import('./scene/block/add-event-listener.snippet'),
31
34
  'scene/block/chartjs-bar': () => import('./scene/block/chartjs-bar.snippet'),
@@ -0,0 +1,46 @@
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 type { SnippetModule } from '../../types';
11
+ import { JSBlockRunJSContext } from '../../../contexts/JSBlockRunJSContext';
12
+
13
+ const snippet: SnippetModule = {
14
+ contexts: [JSBlockRunJSContext],
15
+ prefix: 'sn-jsb-antd-icons',
16
+ label: 'Render Ant Design icons',
17
+ description: 'Render Ant Design icons with buttons inside the block container',
18
+ locales: {
19
+ 'zh-CN': {
20
+ label: '渲染 Ant Design 图标',
21
+ description: '在区块容器中使用 Ant Design 图标与按钮进行渲染',
22
+ },
23
+ },
24
+ content: `
25
+ // Render Ant Design icons with buttons via ctx.libs
26
+ const { React, antd, antdIcons } = ctx.libs;
27
+ const { Button, Space } = antd;
28
+ const { PlusOutlined, EditOutlined, DeleteOutlined } = antdIcons;
29
+
30
+ const IconButtons = () => (
31
+ <Space style={{ padding: 12 }}>
32
+ <Button type="primary" icon={<PlusOutlined />}>
33
+ {ctx.t('Add')}
34
+ </Button>
35
+ <Button icon={<EditOutlined />}>{ctx.t('Edit')}</Button>
36
+ <Button danger icon={<DeleteOutlined />}>
37
+ {ctx.t('Delete')}
38
+ </Button>
39
+ </Space>
40
+ );
41
+
42
+ ctx.render(<IconButtons />);
43
+ `,
44
+ };
45
+
46
+ export default snippet;
@@ -22,11 +22,13 @@ const snippet: SnippetModule = {
22
22
  },
23
23
  },
24
24
  content: `
25
- const { React, ReactDOM, antd } = ctx;
26
- const { Button } = antd;
25
+ const { Button } = ctx.libs.antd;
27
26
 
28
- const node = React.createElement(Button, { type: 'primary', onClick: () => ctx.message.success(ctx.t('Clicked!')) }, ctx.t('Button'));
29
- ReactDOM.createRoot(ctx.element).render(node);
27
+ ctx.render(
28
+ <Button type="primary" onClick={() => ctx.message.success(ctx.t('Clicked!'))}>
29
+ {ctx.t('Button')}
30
+ </Button>
31
+ );
30
32
  `,
31
33
  };
32
34
 
@@ -22,29 +22,28 @@ const snippet: SnippetModule = {
22
22
  },
23
23
  },
24
24
  content: `
25
- const { Card, Descriptions, Tag, Typography } = ctx.antd;
26
- const { createElement: h } = ctx.React;
25
+ const { Card, Descriptions, Tag } = ctx.libs.antd;
27
26
 
28
27
  if (!ctx.record) {
29
- ctx.element.innerHTML = '<div style="padding:16px;color:#999;">' + ctx.t('No record data') + '</div>';
28
+ ctx.render('<div style="padding:16px;color:#999;">' + ctx.t('No record data') + '</div>');
30
29
  return;
31
30
  }
32
31
 
33
32
  const record = ctx.record;
34
33
 
35
- ctx.ReactDOM.createRoot(ctx.element).render(
36
- h(Card, { title: ctx.t('Record Details'), bordered: true, style: { margin: 0 } },
37
- h(Descriptions, { column: 2, size: 'small' },
38
- h(Descriptions.Item, { label: ctx.t('ID') }, record.id || '-'),
39
- h(Descriptions.Item, { label: ctx.t('Status') },
40
- h(Tag, { color: record.status === 'active' ? 'green' : 'default' }, record.status || '-')
41
- ),
42
- h(Descriptions.Item, { label: ctx.t('Title') }, record.title || '-'),
43
- h(Descriptions.Item, { label: ctx.t('Created At') },
44
- record.createdAt ? new Date(record.createdAt).toLocaleString() : '-'
45
- )
46
- )
47
- )
34
+ ctx.render(
35
+ <Card title={ctx.t('Record Details')} bordered style={{ margin: 0 }}>
36
+ <Descriptions column={2} size="small">
37
+ <Descriptions.Item label={ctx.t('ID')}>{record.id || '-'}</Descriptions.Item>
38
+ <Descriptions.Item label={ctx.t('Status')}>
39
+ <Tag color={record.status === 'active' ? 'green' : 'default'}>{record.status || '-'}</Tag>
40
+ </Descriptions.Item>
41
+ <Descriptions.Item label={ctx.t('Title')}>{record.title || '-'}</Descriptions.Item>
42
+ <Descriptions.Item label={ctx.t('Created At')}>
43
+ {record.createdAt ? new Date(record.createdAt).toLocaleString() : '-'}
44
+ </Descriptions.Item>
45
+ </Descriptions>
46
+ </Card>
48
47
  );
49
48
 
50
49
  `,
@@ -0,0 +1,39 @@
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 type { SnippetModule } from '../../types';
11
+ import { JSBlockRunJSContext } from '../../../contexts/JSBlockRunJSContext';
12
+
13
+ const snippet: SnippetModule = {
14
+ contexts: [JSBlockRunJSContext],
15
+ prefix: 'sn-react-jsx',
16
+ label: 'Render React (JSX)',
17
+ description: 'Render a simple React component using JSX syntax',
18
+ locales: {
19
+ 'zh-CN': {
20
+ label: '渲染 React(JSX)',
21
+ description: '使用 JSX 语法渲染一个简单的 React 组件',
22
+ },
23
+ },
24
+ content: `
25
+ // Render a React component with JSX
26
+ const { React } = ctx.libs;
27
+
28
+ const App = () => (
29
+ <div style={{ padding: 12 }}>
30
+ <h3 style={{ margin: 0, color: '#1890ff' }}>Hello JSX</h3>
31
+ <div style={{ color: '#555' }}>This block is rendered by JSX.</div>
32
+ </div>
33
+ );
34
+
35
+ ctx.render(<App />);
36
+ `,
37
+ };
38
+
39
+ export default snippet;