@gadmin2n/schematics 0.0.136 → 0.0.138
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/dist/lib/application/files/gadmin2-game-angle-demo/Dockerfile.server +2 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/compose-ctl.sh +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/canvas.prisma +1 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/workflow.prisma +14 -3
- package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/User.ts +4 -4
- package/dist/lib/application/files/gadmin2-game-angle-demo/docker-compose.md +2 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/docker-compose.yml +13 -92
- package/dist/lib/application/files/gadmin2-game-angle-demo/readme.md +105 -7
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/ensure-database.js +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/package.json +5 -5
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/prisma.config.ts +9 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/scripts/notify-dev-servers.js +9 -8
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/scripts/wait-for-nest-ready.js +5 -5
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow-node-types.ts +43 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow.seed.ts +9 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflows.ts +24 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/canvas/canvas-export.dto.ts +27 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/canvas/canvas-export.service.spec.ts +430 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/canvas/canvas-export.service.ts +203 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/canvas/canvas.controller.ts +36 -3
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/canvas/canvas.module.ts +2 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.controller.spec.ts +54 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.controller.ts +245 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.module.ts +10 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.service.spec.ts +366 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.service.ts +110 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-execution.dto.ts +19 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.controller.ts +15 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.spec.ts +79 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.ts +41 -19
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/start-prod.sh +5 -5
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/README.md +37 -573
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/ecosystem.dev.config.cjs +12 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/package.json +2 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/header.tsx +3 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/config/routeRegistry.tsx +39 -26
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/i18n.ts +3 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/en/common.json +3 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/zh_CN/common.json +3 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasEditPage.tsx +23 -5
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasListPage.tsx +66 -10
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasPage.tsx +16 -7
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasToolbar.tsx +21 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/canvasApi.ts +30 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/components/ExportModal.tsx +84 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/components/ImportModal.tsx +166 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/hooks/useCanvasHistory.ts +52 -13
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/DslView.tsx +6 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/NodePropertyPanel.tsx +6 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/editor.tsx +17 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/instance-detail.tsx +18 -6
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/temporal-tenant/index.tsx +225 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/utilities/lazy-with-reload.ts +31 -0
- package/package.json +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-dsl-validate.spec.ts +0 -220
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-dsl-validate.ts +0 -132
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/TODO.md +0 -152
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/config/development-sql.yaml +0 -5
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/docker-compose.yml +0 -25
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/package.json +0 -13
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/.dockerignore +0 -12
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/.env +0 -7
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/Dockerfile +0 -79
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/GRACEFUL-DEPLOYMENT.md +0 -270
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/SANDBOX.md +0 -122
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/package-lock.json +0 -4285
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/package.json +0 -28
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/code-execute.ts +0 -51
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/db-execute.ts +0 -85
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/db-query.ts +0 -35
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/http-request.ts +0 -54
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/index.ts +0 -6
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/reporting.ts +0 -85
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/send-notification.ts +0 -47
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/config.ts +0 -13
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/condition.ts +0 -101
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/context.ts +0 -58
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/graph.ts +0 -184
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/helpers.ts +0 -133
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/node-types.ts +0 -96
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/types.ts +0 -77
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/validate.ts +0 -109
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/index.ts +0 -101
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/outbox-poller.ts +0 -382
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/activities/code-execute.test.ts +0 -44
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/activities/http-request.test.ts +0 -87
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/cron-trigger-workflow.test.ts +0 -20
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/helpers.test.ts +0 -225
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/node-type-consistency.test.ts +0 -101
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/validate.test.ts +0 -205
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/workflows/dsl-workflow.ts +0 -499
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/tsconfig.json +0 -19
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/vitest.config.ts +0 -8
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/yarn.lock +0 -1905
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import React, { useCallback, useEffect, useState } from 'react';
|
|
2
|
+
import { Button, Input, Modal, Space, message } from 'antd';
|
|
3
|
+
import { CopyOutlined, LoadingOutlined } from '@ant-design/icons';
|
|
4
|
+
import { generateExportToken } from '../canvasApi';
|
|
5
|
+
|
|
6
|
+
interface ExportModalProps {
|
|
7
|
+
open: boolean;
|
|
8
|
+
canvasId: string | null;
|
|
9
|
+
onClose: () => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function ExportModal({ open, canvasId, onClose }: ExportModalProps) {
|
|
13
|
+
const [url, setUrl] = useState<string>('');
|
|
14
|
+
const [loading, setLoading] = useState(false);
|
|
15
|
+
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (open && canvasId) {
|
|
18
|
+
setUrl('');
|
|
19
|
+
setLoading(true);
|
|
20
|
+
generateExportToken(canvasId)
|
|
21
|
+
.then((res) => setUrl(res.url))
|
|
22
|
+
.catch((e: any) =>
|
|
23
|
+
message.error(e?.message ?? 'Failed to generate export URL'),
|
|
24
|
+
)
|
|
25
|
+
.finally(() => setLoading(false));
|
|
26
|
+
}
|
|
27
|
+
}, [open, canvasId]);
|
|
28
|
+
|
|
29
|
+
const handleCopy = useCallback(async () => {
|
|
30
|
+
try {
|
|
31
|
+
await navigator.clipboard.writeText(url);
|
|
32
|
+
message.success('URL copied to clipboard');
|
|
33
|
+
} catch {
|
|
34
|
+
// Fallback for older browsers
|
|
35
|
+
const textarea = document.getElementById(
|
|
36
|
+
'canvas-export-url-input',
|
|
37
|
+
) as HTMLTextAreaElement;
|
|
38
|
+
textarea?.select();
|
|
39
|
+
document.execCommand('copy');
|
|
40
|
+
message.success('URL copied to clipboard');
|
|
41
|
+
}
|
|
42
|
+
}, [url]);
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<Modal
|
|
46
|
+
title="Export Canvas"
|
|
47
|
+
open={open}
|
|
48
|
+
onCancel={onClose}
|
|
49
|
+
footer={
|
|
50
|
+
<Space>
|
|
51
|
+
<Button onClick={onClose}>Close</Button>
|
|
52
|
+
<Button
|
|
53
|
+
type="primary"
|
|
54
|
+
icon={<CopyOutlined />}
|
|
55
|
+
onClick={handleCopy}
|
|
56
|
+
disabled={!url}
|
|
57
|
+
>
|
|
58
|
+
Copy URL
|
|
59
|
+
</Button>
|
|
60
|
+
</Space>
|
|
61
|
+
}
|
|
62
|
+
>
|
|
63
|
+
{loading ? (
|
|
64
|
+
<div style={{ textAlign: 'center', padding: 24 }}>
|
|
65
|
+
<LoadingOutlined style={{ fontSize: 24 }} />
|
|
66
|
+
<p>Generating export URL...</p>
|
|
67
|
+
</div>
|
|
68
|
+
) : (
|
|
69
|
+
<>
|
|
70
|
+
<p style={{ marginBottom: 8, color: '#666' }}>
|
|
71
|
+
Export URL (valid for 30 minutes):
|
|
72
|
+
</p>
|
|
73
|
+
<Input.TextArea
|
|
74
|
+
id="canvas-export-url-input"
|
|
75
|
+
value={url}
|
|
76
|
+
readOnly
|
|
77
|
+
autoSize={{ minRows: 3, maxRows: 5 }}
|
|
78
|
+
onFocus={(e) => e.target.select()}
|
|
79
|
+
/>
|
|
80
|
+
</>
|
|
81
|
+
)}
|
|
82
|
+
</Modal>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import React, { useCallback, useState } from 'react';
|
|
2
|
+
import { Button, Input, Modal, Space, Spin, message } from 'antd';
|
|
3
|
+
import { CloudDownloadOutlined, CheckOutlined } from '@ant-design/icons';
|
|
4
|
+
import ReactDiffViewer, { DiffMethod } from 'react-diff-viewer-continued';
|
|
5
|
+
import { importConfirm, importPreview } from '../canvasApi';
|
|
6
|
+
|
|
7
|
+
interface ImportModalProps {
|
|
8
|
+
open: boolean;
|
|
9
|
+
onClose: () => void;
|
|
10
|
+
onSuccess: () => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type Phase = 'input' | 'preview' | 'confirming';
|
|
14
|
+
|
|
15
|
+
export function ImportModal({ open, onClose, onSuccess }: ImportModalProps) {
|
|
16
|
+
const [sourceUrl, setSourceUrl] = useState('');
|
|
17
|
+
const [phase, setPhase] = useState<Phase>('input');
|
|
18
|
+
const [loading, setLoading] = useState(false);
|
|
19
|
+
const [currentJson, setCurrentJson] = useState('');
|
|
20
|
+
const [incomingJson, setIncomingJson] = useState('');
|
|
21
|
+
const [payload, setPayload] = useState<any>(null);
|
|
22
|
+
|
|
23
|
+
const reset = useCallback(() => {
|
|
24
|
+
setSourceUrl('');
|
|
25
|
+
setPhase('input');
|
|
26
|
+
setLoading(false);
|
|
27
|
+
setCurrentJson('');
|
|
28
|
+
setIncomingJson('');
|
|
29
|
+
setPayload(null);
|
|
30
|
+
}, []);
|
|
31
|
+
|
|
32
|
+
const handleClose = useCallback(() => {
|
|
33
|
+
reset();
|
|
34
|
+
onClose();
|
|
35
|
+
}, [reset, onClose]);
|
|
36
|
+
|
|
37
|
+
const handleFetchPreview = useCallback(async () => {
|
|
38
|
+
if (!sourceUrl.trim()) {
|
|
39
|
+
message.warning('Please paste the export URL');
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
setLoading(true);
|
|
44
|
+
try {
|
|
45
|
+
// Step 1: Fetch from source URL (cross-origin)
|
|
46
|
+
const response = await fetch(sourceUrl.trim());
|
|
47
|
+
if (!response.ok) {
|
|
48
|
+
const errorData = await response.json().catch(() => null);
|
|
49
|
+
throw new Error(
|
|
50
|
+
errorData?.message || `Failed to fetch: ${response.status}`,
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
const exportData = await response.json();
|
|
54
|
+
|
|
55
|
+
// Step 2: Send to our import-preview endpoint
|
|
56
|
+
const preview = await importPreview(exportData);
|
|
57
|
+
|
|
58
|
+
// Step 3: Format for diff viewer
|
|
59
|
+
setCurrentJson(
|
|
60
|
+
preview.current ? JSON.stringify(preview.current, null, 2) : '',
|
|
61
|
+
);
|
|
62
|
+
setIncomingJson(JSON.stringify(preview.incoming, null, 2));
|
|
63
|
+
setPayload(exportData);
|
|
64
|
+
setPhase('preview');
|
|
65
|
+
} catch (e: any) {
|
|
66
|
+
if (e.message?.includes('expired') || e.message?.includes('invalid')) {
|
|
67
|
+
message.error(
|
|
68
|
+
'Export link has expired or is invalid. Please generate a new one from the source environment.',
|
|
69
|
+
);
|
|
70
|
+
} else {
|
|
71
|
+
message.error(
|
|
72
|
+
e?.message ||
|
|
73
|
+
'Cannot reach the source environment. Please check the URL and try again.',
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
} finally {
|
|
77
|
+
setLoading(false);
|
|
78
|
+
}
|
|
79
|
+
}, [sourceUrl]);
|
|
80
|
+
|
|
81
|
+
const handleConfirm = useCallback(async () => {
|
|
82
|
+
if (!payload) return;
|
|
83
|
+
setPhase('confirming');
|
|
84
|
+
try {
|
|
85
|
+
const result = await importConfirm(payload);
|
|
86
|
+
|
|
87
|
+
message.success(`Canvas "${result.canvasName}" imported successfully!`);
|
|
88
|
+
reset();
|
|
89
|
+
onClose();
|
|
90
|
+
onSuccess();
|
|
91
|
+
} catch (e: any) {
|
|
92
|
+
message.error(e?.message ?? 'Import failed');
|
|
93
|
+
setPhase('preview');
|
|
94
|
+
}
|
|
95
|
+
}, [payload, reset, onClose, onSuccess]);
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<Modal
|
|
99
|
+
title="Import Canvas"
|
|
100
|
+
open={open}
|
|
101
|
+
onCancel={handleClose}
|
|
102
|
+
width={phase === 'preview' ? '90vw' : 520}
|
|
103
|
+
footer={
|
|
104
|
+
phase === 'input' ? (
|
|
105
|
+
<Space>
|
|
106
|
+
<Button onClick={handleClose}>Cancel</Button>
|
|
107
|
+
<Button
|
|
108
|
+
type="primary"
|
|
109
|
+
icon={<CloudDownloadOutlined />}
|
|
110
|
+
onClick={handleFetchPreview}
|
|
111
|
+
loading={loading}
|
|
112
|
+
>
|
|
113
|
+
Fetch & Preview
|
|
114
|
+
</Button>
|
|
115
|
+
</Space>
|
|
116
|
+
) : (
|
|
117
|
+
<Space>
|
|
118
|
+
<Button onClick={handleClose}>Cancel</Button>
|
|
119
|
+
<Button
|
|
120
|
+
type="primary"
|
|
121
|
+
icon={<CheckOutlined />}
|
|
122
|
+
onClick={handleConfirm}
|
|
123
|
+
loading={phase === 'confirming'}
|
|
124
|
+
>
|
|
125
|
+
Confirm Import
|
|
126
|
+
</Button>
|
|
127
|
+
</Space>
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
>
|
|
131
|
+
{phase === 'input' && (
|
|
132
|
+
<>
|
|
133
|
+
<p style={{ marginBottom: 8 }}>
|
|
134
|
+
Paste the export URL from the source environment:
|
|
135
|
+
</p>
|
|
136
|
+
<Input.TextArea
|
|
137
|
+
value={sourceUrl}
|
|
138
|
+
onChange={(e) => setSourceUrl(e.target.value)}
|
|
139
|
+
placeholder="https://dev.example.com/api/canvas/export/abc123?token=..."
|
|
140
|
+
autoSize={{ minRows: 3, maxRows: 5 }}
|
|
141
|
+
/>
|
|
142
|
+
</>
|
|
143
|
+
)}
|
|
144
|
+
|
|
145
|
+
{phase === 'preview' && (
|
|
146
|
+
<div style={{ maxHeight: '70vh', overflow: 'auto' }}>
|
|
147
|
+
<ReactDiffViewer
|
|
148
|
+
oldValue={currentJson}
|
|
149
|
+
newValue={incomingJson}
|
|
150
|
+
splitView
|
|
151
|
+
compareMethod={DiffMethod.LINES}
|
|
152
|
+
leftTitle="Current (this environment)"
|
|
153
|
+
rightTitle="Incoming (source environment)"
|
|
154
|
+
/>
|
|
155
|
+
</div>
|
|
156
|
+
)}
|
|
157
|
+
|
|
158
|
+
{phase === 'confirming' && (
|
|
159
|
+
<div style={{ textAlign: 'center', padding: 40 }}>
|
|
160
|
+
<Spin size="large" />
|
|
161
|
+
<p style={{ marginTop: 16 }}>Importing canvas...</p>
|
|
162
|
+
</div>
|
|
163
|
+
)}
|
|
164
|
+
</Modal>
|
|
165
|
+
);
|
|
166
|
+
}
|
|
@@ -7,7 +7,7 @@ export interface CanvasSnapshot {
|
|
|
7
7
|
layout: LayoutItem[];
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
/** Max number of undo steps retained before the oldest is dropped. */
|
|
10
|
+
/** Max number of undo/redo steps retained before the oldest is dropped. */
|
|
11
11
|
const MAX_HISTORY_SIZE = 50;
|
|
12
12
|
|
|
13
13
|
/**
|
|
@@ -19,12 +19,14 @@ const MAX_HISTORY_SIZE = 50;
|
|
|
19
19
|
const COALESCE_WINDOW_MS = 5000;
|
|
20
20
|
|
|
21
21
|
export function useCanvasHistory() {
|
|
22
|
-
const
|
|
22
|
+
const undoStackRef = useRef<CanvasSnapshot[]>([]);
|
|
23
|
+
const redoStackRef = useRef<CanvasSnapshot[]>([]);
|
|
23
24
|
// Timestamp of the last coalesce=true push that extended the current
|
|
24
25
|
// batch; null when the last push was discrete (coalesce=false) or no
|
|
25
26
|
// batch is in progress.
|
|
26
27
|
const lastCoalescedAtRef = useRef<number | null>(null);
|
|
27
28
|
const [canUndo, setCanUndo] = useState(false);
|
|
29
|
+
const [canRedo, setCanRedo] = useState(false);
|
|
28
30
|
|
|
29
31
|
const push = useCallback((snapshot: CanvasSnapshot, coalesce: boolean) => {
|
|
30
32
|
const now = Date.now();
|
|
@@ -40,26 +42,63 @@ export function useCanvasHistory() {
|
|
|
40
42
|
return;
|
|
41
43
|
}
|
|
42
44
|
|
|
43
|
-
|
|
44
|
-
if (
|
|
45
|
-
|
|
45
|
+
undoStackRef.current.push(snapshot);
|
|
46
|
+
if (undoStackRef.current.length > MAX_HISTORY_SIZE) {
|
|
47
|
+
undoStackRef.current.shift();
|
|
46
48
|
}
|
|
47
49
|
lastCoalescedAtRef.current = coalesce ? now : null;
|
|
48
50
|
setCanUndo(true);
|
|
49
|
-
}, []);
|
|
50
51
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
// A new edit invalidates whatever was undone before it.
|
|
53
|
+
if (redoStackRef.current.length > 0) {
|
|
54
|
+
redoStackRef.current = [];
|
|
55
|
+
setCanRedo(false);
|
|
56
|
+
}
|
|
56
57
|
}, []);
|
|
57
58
|
|
|
59
|
+
const undo = useCallback(
|
|
60
|
+
(current: CanvasSnapshot): CanvasSnapshot | undefined => {
|
|
61
|
+
const snapshot = undoStackRef.current.pop();
|
|
62
|
+
if (!snapshot) return undefined;
|
|
63
|
+
|
|
64
|
+
redoStackRef.current.push(current);
|
|
65
|
+
if (redoStackRef.current.length > MAX_HISTORY_SIZE) {
|
|
66
|
+
redoStackRef.current.shift();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
lastCoalescedAtRef.current = null;
|
|
70
|
+
setCanUndo(undoStackRef.current.length > 0);
|
|
71
|
+
setCanRedo(true);
|
|
72
|
+
return snapshot;
|
|
73
|
+
},
|
|
74
|
+
[],
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const redo = useCallback(
|
|
78
|
+
(current: CanvasSnapshot): CanvasSnapshot | undefined => {
|
|
79
|
+
const snapshot = redoStackRef.current.pop();
|
|
80
|
+
if (!snapshot) return undefined;
|
|
81
|
+
|
|
82
|
+
undoStackRef.current.push(current);
|
|
83
|
+
if (undoStackRef.current.length > MAX_HISTORY_SIZE) {
|
|
84
|
+
undoStackRef.current.shift();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
lastCoalescedAtRef.current = null;
|
|
88
|
+
setCanRedo(redoStackRef.current.length > 0);
|
|
89
|
+
setCanUndo(true);
|
|
90
|
+
return snapshot;
|
|
91
|
+
},
|
|
92
|
+
[],
|
|
93
|
+
);
|
|
94
|
+
|
|
58
95
|
const clear = useCallback(() => {
|
|
59
|
-
|
|
96
|
+
undoStackRef.current = [];
|
|
97
|
+
redoStackRef.current = [];
|
|
60
98
|
lastCoalescedAtRef.current = null;
|
|
61
99
|
setCanUndo(false);
|
|
100
|
+
setCanRedo(false);
|
|
62
101
|
}, []);
|
|
63
102
|
|
|
64
|
-
return { push,
|
|
103
|
+
return { push, undo, redo, clear, canUndo, canRedo };
|
|
65
104
|
}
|
|
@@ -24,6 +24,8 @@ export interface DslViewHandle {
|
|
|
24
24
|
copy: () => Promise<void>;
|
|
25
25
|
/** Whether textarea content differs from last applied DSL. */
|
|
26
26
|
isDirty: () => boolean;
|
|
27
|
+
/** Reset textarea to empty DSL template. */
|
|
28
|
+
clear: () => void;
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
function stringifyDsl(dsl: WorkflowDSL | null): string {
|
|
@@ -158,6 +160,10 @@ export const DslView = forwardRef<DslViewHandle, DslViewProps>(function DslView(
|
|
|
158
160
|
apply: handleApply,
|
|
159
161
|
copy: handleCopy,
|
|
160
162
|
isDirty: () => text !== initial,
|
|
163
|
+
clear: () => {
|
|
164
|
+
setText(EMPTY_TEMPLATE);
|
|
165
|
+
setError(null);
|
|
166
|
+
},
|
|
161
167
|
}),
|
|
162
168
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
163
169
|
[text, initial, onApply],
|
|
@@ -257,7 +257,12 @@ export function NodePropertyPanel({
|
|
|
257
257
|
for (const [key, prop] of Object.entries<any>(properties)) {
|
|
258
258
|
const existingValue = node.config?.[key];
|
|
259
259
|
if (existingValue !== undefined) {
|
|
260
|
-
mergedConfig[key] =
|
|
260
|
+
mergedConfig[key] =
|
|
261
|
+
key === 'approvers' && Array.isArray(existingValue)
|
|
262
|
+
? existingValue.map((a: any) =>
|
|
263
|
+
typeof a === 'string' ? { type: 'user', id: a } : a,
|
|
264
|
+
)
|
|
265
|
+
: existingValue;
|
|
261
266
|
} else if (prop.type === 'string') {
|
|
262
267
|
mergedConfig[key] = '';
|
|
263
268
|
} else if (prop.type === 'number') {
|
package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/editor.tsx
CHANGED
|
@@ -526,6 +526,7 @@ export default function WorkflowEditorPage() {
|
|
|
526
526
|
flexShrink: 0,
|
|
527
527
|
display: 'flex',
|
|
528
528
|
alignItems: 'center',
|
|
529
|
+
justifyContent: 'space-between',
|
|
529
530
|
}}
|
|
530
531
|
>
|
|
531
532
|
<Segmented
|
|
@@ -536,6 +537,22 @@ export default function WorkflowEditorPage() {
|
|
|
536
537
|
{ label: 'DSL', value: 'dsl' },
|
|
537
538
|
]}
|
|
538
539
|
/>
|
|
540
|
+
<Button
|
|
541
|
+
danger
|
|
542
|
+
size="small"
|
|
543
|
+
style={{ marginRight: 8 }}
|
|
544
|
+
onClick={() => {
|
|
545
|
+
if (viewMode === 'dsl') {
|
|
546
|
+
dslViewRef.current?.clear();
|
|
547
|
+
} else {
|
|
548
|
+
setDsl({ nodes: [], edges: [] });
|
|
549
|
+
setHasUnsavedChanges(true);
|
|
550
|
+
setSelectedNodeId(null);
|
|
551
|
+
}
|
|
552
|
+
}}
|
|
553
|
+
>
|
|
554
|
+
清空
|
|
555
|
+
</Button>
|
|
539
556
|
</div>
|
|
540
557
|
|
|
541
558
|
{/* Main area: Flow + Property Panel */}
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
StopOutlined,
|
|
24
24
|
} from '@ant-design/icons';
|
|
25
25
|
import { useNavigate, useParams } from 'react-router-dom';
|
|
26
|
+
import { useGetIdentity } from '@refinedev/core';
|
|
26
27
|
import { customRequest } from 'helpers/http';
|
|
27
28
|
import { EnhancedFlowRenderer } from './components/EnhancedFlowRenderer';
|
|
28
29
|
import type { WorkflowDSL } from './types';
|
|
@@ -103,6 +104,7 @@ function getStepStatus(
|
|
|
103
104
|
export default function WorkflowInstanceDetailPage() {
|
|
104
105
|
const { instanceId } = useParams<{ instanceId: string }>();
|
|
105
106
|
const navigate = useNavigate();
|
|
107
|
+
const { data: identity } = useGetIdentity<{ id: string }>();
|
|
106
108
|
const [data, setData] = useState<InstanceDetail | null>(null);
|
|
107
109
|
const [loading, setLoading] = useState(true);
|
|
108
110
|
const [actionLoading, setActionLoading] = useState(false);
|
|
@@ -175,14 +177,14 @@ export default function WorkflowInstanceDetailPage() {
|
|
|
175
177
|
const handleApprove = async (nodeId: string, approved: boolean) => {
|
|
176
178
|
setActionLoading(true);
|
|
177
179
|
try {
|
|
178
|
-
await customRequest(`workflow/instance/${instanceId}/
|
|
179
|
-
|
|
180
|
-
|
|
180
|
+
await customRequest(`workflow/instance/${instanceId}/approve`, 'POST', {
|
|
181
|
+
nodeId,
|
|
182
|
+
approved,
|
|
181
183
|
});
|
|
182
184
|
message.success(approved ? 'Approved' : 'Rejected');
|
|
183
185
|
fetchData();
|
|
184
186
|
} catch (e: any) {
|
|
185
|
-
message.error(e?.message || 'Failed to
|
|
187
|
+
message.error(e?.message || 'Failed to submit approval');
|
|
186
188
|
} finally {
|
|
187
189
|
setActionLoading(false);
|
|
188
190
|
}
|
|
@@ -370,8 +372,14 @@ export default function WorkflowInstanceDetailPage() {
|
|
|
370
372
|
size="small"
|
|
371
373
|
current={-1}
|
|
372
374
|
items={data.nodeExecutions.map((ne) => {
|
|
375
|
+
const approverIds: string[] =
|
|
376
|
+
(ne.input as any)?.approvers || [];
|
|
377
|
+
const canApprove =
|
|
378
|
+
!!identity?.id && approverIds.includes(identity.id);
|
|
373
379
|
const isApprovalPending =
|
|
374
|
-
ne.nodeType === 'approval' &&
|
|
380
|
+
ne.nodeType === 'approval' &&
|
|
381
|
+
ne.status === 'RUNNING' &&
|
|
382
|
+
canApprove;
|
|
375
383
|
const isSelected = selectedNodeId === ne.nodeId;
|
|
376
384
|
return {
|
|
377
385
|
title: (
|
|
@@ -724,7 +732,11 @@ export default function WorkflowInstanceDetailPage() {
|
|
|
724
732
|
|
|
725
733
|
{/* Approval actions */}
|
|
726
734
|
{selectedExecution.nodeType === 'approval' &&
|
|
727
|
-
selectedExecution.status === 'RUNNING' &&
|
|
735
|
+
selectedExecution.status === 'RUNNING' &&
|
|
736
|
+
!!identity?.id &&
|
|
737
|
+
(
|
|
738
|
+
(selectedExecution.input as any)?.approvers || []
|
|
739
|
+
).includes(identity.id) && (
|
|
728
740
|
<Space style={{ width: '100%' }}>
|
|
729
741
|
<Button
|
|
730
742
|
type="primary"
|