@gadmin2n/schematics 0.0.96 → 0.0.98
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/DESIGN.md +348 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/PRODUCT.md +75 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/workflow.prisma +5 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow-node-types.ts +24 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-dsl-validate.spec.ts +220 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-dsl-validate.ts +129 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-export.dto.ts +1 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-export.service.ts +4 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.spec.ts +46 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.ts +27 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowNodeType/workflowNodeType.service.spec.ts +6 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/node-types.ts +43 -4
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/validate.ts +109 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/validate.test.ts +205 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/header.tsx +55 -56
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/layout.tsx +7 -3
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/logo.tsx +7 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/sider.tsx +179 -160
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/title.tsx +34 -31
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/constants/layout.ts +24 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/en/common.json +6 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/zh_CN/common.json +6 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/CustomNode.tsx +66 -51
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/EnhancedFlowRenderer.tsx +7 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/ExecutionStatusNode.tsx +66 -26
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/FlowRenderer.tsx +7 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/editor.tsx +9 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/hooks/useWorkflowAgent.ts +30 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/show.tsx +9 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/types.ts +1 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/utils/resolveOutputs.ts +27 -0
- package/package.json +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/package-lock.json +0 -15579
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/package-lock.json +0 -17555
package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/validate.ts
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DSL handle validator. Confirms each edge's sourceHandle matches the
|
|
3
|
+
* source node's declared outputs (or, for dynamic nodes like switch,
|
|
4
|
+
* matches a value derived from config).
|
|
5
|
+
*
|
|
6
|
+
* Used at workflow publish time (server-side via a duplicated copy)
|
|
7
|
+
* and optionally during DSL parsing on the worker side.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { WorkflowDSL, DslNode } from './types';
|
|
11
|
+
import { NODE_TYPE_META } from './node-types';
|
|
12
|
+
|
|
13
|
+
export type DslValidationCode =
|
|
14
|
+
| 'EXTRA_HANDLE' // node has [] outputs, but edge set sourceHandle
|
|
15
|
+
| 'MISSING_HANDLE' // node has named outputs, but edge has no sourceHandle (or empty string)
|
|
16
|
+
| 'UNKNOWN_HANDLE'; // edge sourceHandle is set but ∉ declared outputs
|
|
17
|
+
|
|
18
|
+
export interface DslValidationError {
|
|
19
|
+
edgeId: string;
|
|
20
|
+
source: string;
|
|
21
|
+
target: string;
|
|
22
|
+
code: DslValidationCode;
|
|
23
|
+
reason: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Validate every edge's sourceHandle against the source node's outputs metadata.
|
|
28
|
+
*
|
|
29
|
+
* Rules per source node's outputs:
|
|
30
|
+
* - outputs === null: dynamic — handles must come from deriveDynamicOutputs(node).
|
|
31
|
+
* Edge sourceHandle MUST be present and ∈ derived list, OR if the
|
|
32
|
+
* derived list is empty (config not yet filled), no constraint.
|
|
33
|
+
* - outputs === []: edge sourceHandle MUST be undefined (no named handles allowed).
|
|
34
|
+
* - outputs.length>0: edge sourceHandle MUST be present and ∈ outputs.
|
|
35
|
+
*
|
|
36
|
+
* Returns an array of errors (empty if DSL is valid).
|
|
37
|
+
*/
|
|
38
|
+
export function validateDslHandles(dsl: WorkflowDSL): DslValidationError[] {
|
|
39
|
+
const errors: DslValidationError[] = [];
|
|
40
|
+
const nodeMap = new Map<string, DslNode>(dsl.nodes.map((n) => [n.id, n]));
|
|
41
|
+
|
|
42
|
+
for (const edge of dsl.edges) {
|
|
43
|
+
const src = nodeMap.get(edge.source);
|
|
44
|
+
if (!src) continue; // separate concern (orphan edge); not this validator's job
|
|
45
|
+
|
|
46
|
+
const meta = NODE_TYPE_META[src.type];
|
|
47
|
+
if (!meta) continue; // guards against retired/unknown node types from older DSLs
|
|
48
|
+
|
|
49
|
+
const handles = meta.outputs === null
|
|
50
|
+
? deriveDynamicOutputs(src)
|
|
51
|
+
: meta.outputs;
|
|
52
|
+
|
|
53
|
+
if (meta.outputs !== null && handles.length === 0) {
|
|
54
|
+
// Single anonymous output: sourceHandle must be absent
|
|
55
|
+
if (edge.sourceHandle != null && edge.sourceHandle !== '') {
|
|
56
|
+
errors.push({
|
|
57
|
+
edgeId: edge.id,
|
|
58
|
+
source: edge.source,
|
|
59
|
+
target: edge.target,
|
|
60
|
+
code: 'EXTRA_HANDLE',
|
|
61
|
+
reason: `Node "${src.type}" has no named output handles; edge must not set sourceHandle (got "${edge.sourceHandle}").`,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Enumerated or dynamic non-empty: sourceHandle required and must match
|
|
68
|
+
if (handles.length === 0) {
|
|
69
|
+
// Dynamic + config not yet filled: skip rather than block edits in progress
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (edge.sourceHandle == null || edge.sourceHandle === '') {
|
|
73
|
+
errors.push({
|
|
74
|
+
edgeId: edge.id,
|
|
75
|
+
source: edge.source,
|
|
76
|
+
target: edge.target,
|
|
77
|
+
code: 'MISSING_HANDLE',
|
|
78
|
+
reason: `Node "${src.type}" requires a sourceHandle; choose one of: ${handles.join(', ')}.`,
|
|
79
|
+
});
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (!handles.includes(edge.sourceHandle)) {
|
|
83
|
+
errors.push({
|
|
84
|
+
edgeId: edge.id,
|
|
85
|
+
source: edge.source,
|
|
86
|
+
target: edge.target,
|
|
87
|
+
code: 'UNKNOWN_HANDLE',
|
|
88
|
+
reason: `Node "${src.type}" sourceHandle "${edge.sourceHandle}" is not in declared outputs: ${handles.join(', ')}.`,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return errors;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* For dynamic-output nodes (currently just `switch`), derive the list of
|
|
98
|
+
* handle ids from the node's config.
|
|
99
|
+
*/
|
|
100
|
+
function deriveDynamicOutputs(node: DslNode): string[] {
|
|
101
|
+
if (node.type === 'switch') {
|
|
102
|
+
const cases = (node.config as any)?.cases as Array<{ value?: string }> | undefined;
|
|
103
|
+
if (!Array.isArray(cases)) return [];
|
|
104
|
+
return cases
|
|
105
|
+
.map((c) => c?.value)
|
|
106
|
+
.filter((v): v is string => typeof v === 'string' && v.length > 0);
|
|
107
|
+
}
|
|
108
|
+
return [];
|
|
109
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { validateDslHandles } from '../dsl/validate';
|
|
3
|
+
import type { WorkflowDSL } from '../dsl/types';
|
|
4
|
+
|
|
5
|
+
// ─── helpers ─────────────────────────────────────────────────────────────────
|
|
6
|
+
|
|
7
|
+
function makeNode(
|
|
8
|
+
id: string,
|
|
9
|
+
type: string,
|
|
10
|
+
config: Record<string, any> = {},
|
|
11
|
+
): WorkflowDSL['nodes'][number] {
|
|
12
|
+
return {
|
|
13
|
+
id,
|
|
14
|
+
type: type as any,
|
|
15
|
+
label: id,
|
|
16
|
+
position: { x: 0, y: 0 },
|
|
17
|
+
config,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function makeEdge(
|
|
22
|
+
id: string,
|
|
23
|
+
source: string,
|
|
24
|
+
target: string,
|
|
25
|
+
sourceHandle?: string,
|
|
26
|
+
): WorkflowDSL['edges'][number] {
|
|
27
|
+
return sourceHandle === undefined
|
|
28
|
+
? { id, source, target }
|
|
29
|
+
: { id, source, target, sourceHandle };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ─── if_else (enumerated outputs) ────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
describe('validateDslHandles — if_else', () => {
|
|
35
|
+
it('accepts sourceHandle "true"', () => {
|
|
36
|
+
const dsl: WorkflowDSL = {
|
|
37
|
+
nodes: [makeNode('a', 'if_else'), makeNode('b', 'http_request')],
|
|
38
|
+
edges: [makeEdge('e1', 'a', 'b', 'true')],
|
|
39
|
+
};
|
|
40
|
+
expect(validateDslHandles(dsl)).toEqual([]);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('accepts sourceHandle "false"', () => {
|
|
44
|
+
const dsl: WorkflowDSL = {
|
|
45
|
+
nodes: [makeNode('a', 'if_else'), makeNode('b', 'http_request')],
|
|
46
|
+
edges: [makeEdge('e1', 'a', 'b', 'false')],
|
|
47
|
+
};
|
|
48
|
+
expect(validateDslHandles(dsl)).toEqual([]);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('rejects unknown sourceHandle "maybe"', () => {
|
|
52
|
+
const dsl: WorkflowDSL = {
|
|
53
|
+
nodes: [makeNode('a', 'if_else'), makeNode('b', 'http_request')],
|
|
54
|
+
edges: [makeEdge('e1', 'a', 'b', 'maybe')],
|
|
55
|
+
};
|
|
56
|
+
const errors = validateDslHandles(dsl);
|
|
57
|
+
expect(errors).toHaveLength(1);
|
|
58
|
+
expect(errors[0].edgeId).toBe('e1');
|
|
59
|
+
expect(errors[0].code).toBe('UNKNOWN_HANDLE');
|
|
60
|
+
expect(errors[0].reason).toMatch(/not in declared outputs/);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('rejects missing sourceHandle', () => {
|
|
64
|
+
const dsl: WorkflowDSL = {
|
|
65
|
+
nodes: [makeNode('a', 'if_else'), makeNode('b', 'http_request')],
|
|
66
|
+
edges: [makeEdge('e1', 'a', 'b')],
|
|
67
|
+
};
|
|
68
|
+
const errors = validateDslHandles(dsl);
|
|
69
|
+
expect(errors).toHaveLength(1);
|
|
70
|
+
expect(errors[0].edgeId).toBe('e1');
|
|
71
|
+
expect(errors[0].code).toBe('MISSING_HANDLE');
|
|
72
|
+
expect(errors[0].reason).toMatch(/requires a sourceHandle/);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('rejects empty-string sourceHandle as missing', () => {
|
|
76
|
+
const dsl: WorkflowDSL = {
|
|
77
|
+
nodes: [makeNode('a', 'if_else'), makeNode('b', 'http_request')],
|
|
78
|
+
edges: [makeEdge('e1', 'a', 'b', '')],
|
|
79
|
+
};
|
|
80
|
+
const errors = validateDslHandles(dsl);
|
|
81
|
+
expect(errors).toHaveLength(1);
|
|
82
|
+
expect(errors[0].edgeId).toBe('e1');
|
|
83
|
+
expect(errors[0].code).toBe('MISSING_HANDLE');
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// ─── cron_trigger (no named outputs) ─────────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
describe('validateDslHandles — cron_trigger (no named outputs)', () => {
|
|
90
|
+
it('rejects sourceHandle "foo" on a node with no named handles', () => {
|
|
91
|
+
const dsl: WorkflowDSL = {
|
|
92
|
+
nodes: [makeNode('t', 'cron_trigger'), makeNode('a', 'http_request')],
|
|
93
|
+
edges: [makeEdge('e1', 't', 'a', 'foo')],
|
|
94
|
+
};
|
|
95
|
+
const errors = validateDslHandles(dsl);
|
|
96
|
+
expect(errors).toHaveLength(1);
|
|
97
|
+
expect(errors[0].edgeId).toBe('e1');
|
|
98
|
+
expect(errors[0].code).toBe('EXTRA_HANDLE');
|
|
99
|
+
expect(errors[0].reason).toMatch(/no named output handles/);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('accepts no sourceHandle', () => {
|
|
103
|
+
const dsl: WorkflowDSL = {
|
|
104
|
+
nodes: [makeNode('t', 'cron_trigger'), makeNode('a', 'http_request')],
|
|
105
|
+
edges: [makeEdge('e1', 't', 'a')],
|
|
106
|
+
};
|
|
107
|
+
expect(validateDslHandles(dsl)).toEqual([]);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('accepts empty-string sourceHandle (treated as absent for [] nodes)', () => {
|
|
111
|
+
const dsl: WorkflowDSL = {
|
|
112
|
+
nodes: [makeNode('t', 'cron_trigger'), makeNode('a', 'http_request')],
|
|
113
|
+
edges: [makeEdge('e1', 't', 'a', '')],
|
|
114
|
+
};
|
|
115
|
+
expect(validateDslHandles(dsl)).toEqual([]);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// ─── switch (dynamic outputs) ────────────────────────────────────────────────
|
|
120
|
+
|
|
121
|
+
describe('validateDslHandles — switch (dynamic outputs)', () => {
|
|
122
|
+
it('accepts sourceHandle matching a case value', () => {
|
|
123
|
+
const dsl: WorkflowDSL = {
|
|
124
|
+
nodes: [
|
|
125
|
+
makeNode('s', 'switch', { cases: [{ value: 'a' }, { value: 'b' }] }),
|
|
126
|
+
makeNode('x', 'http_request'),
|
|
127
|
+
],
|
|
128
|
+
edges: [makeEdge('e1', 's', 'x', 'a')],
|
|
129
|
+
};
|
|
130
|
+
expect(validateDslHandles(dsl)).toEqual([]);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('rejects sourceHandle not in case values', () => {
|
|
134
|
+
const dsl: WorkflowDSL = {
|
|
135
|
+
nodes: [
|
|
136
|
+
makeNode('s', 'switch', { cases: [{ value: 'a' }] }),
|
|
137
|
+
makeNode('x', 'http_request'),
|
|
138
|
+
],
|
|
139
|
+
edges: [makeEdge('e1', 's', 'x', 'c')],
|
|
140
|
+
};
|
|
141
|
+
const errors = validateDslHandles(dsl);
|
|
142
|
+
expect(errors).toHaveLength(1);
|
|
143
|
+
expect(errors[0].edgeId).toBe('e1');
|
|
144
|
+
expect(errors[0].code).toBe('UNKNOWN_HANDLE');
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('skips validation when switch config has no cases (in-progress editor)', () => {
|
|
148
|
+
const dsl: WorkflowDSL = {
|
|
149
|
+
nodes: [makeNode('s', 'switch'), makeNode('x', 'http_request')],
|
|
150
|
+
edges: [makeEdge('e1', 's', 'x', 'anything')],
|
|
151
|
+
};
|
|
152
|
+
expect(validateDslHandles(dsl)).toEqual([]);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// ─── out-of-scope (orphan / unknown) ────────────────────────────────────────
|
|
157
|
+
|
|
158
|
+
describe('validateDslHandles — out-of-scope', () => {
|
|
159
|
+
it('ignores edge referencing missing source node', () => {
|
|
160
|
+
const dsl: WorkflowDSL = {
|
|
161
|
+
nodes: [makeNode('b', 'http_request')],
|
|
162
|
+
edges: [makeEdge('e1', 'ghost', 'b', 'true')],
|
|
163
|
+
};
|
|
164
|
+
expect(validateDslHandles(dsl)).toEqual([]);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('ignores edges from unknown node types', () => {
|
|
168
|
+
const dsl: WorkflowDSL = {
|
|
169
|
+
nodes: [makeNode('u', 'wholly_made_up_type'), makeNode('b', 'http_request')],
|
|
170
|
+
edges: [makeEdge('e1', 'u', 'b', 'whatever')],
|
|
171
|
+
};
|
|
172
|
+
expect(validateDslHandles(dsl)).toEqual([]);
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// ─── multiple errors collected ──────────────────────────────────────────────
|
|
177
|
+
|
|
178
|
+
describe('validateDslHandles — multiple errors', () => {
|
|
179
|
+
it('collects every error in one pass', () => {
|
|
180
|
+
const dsl: WorkflowDSL = {
|
|
181
|
+
nodes: [
|
|
182
|
+
makeNode('if', 'if_else'),
|
|
183
|
+
makeNode('cron', 'cron_trigger'),
|
|
184
|
+
makeNode('sw', 'switch', { cases: [{ value: 'a' }] }),
|
|
185
|
+
makeNode('x', 'http_request'),
|
|
186
|
+
],
|
|
187
|
+
edges: [
|
|
188
|
+
makeEdge('e1', 'if', 'x', 'maybe'), // bad enumerated
|
|
189
|
+
makeEdge('e2', 'if', 'x'), // missing required
|
|
190
|
+
makeEdge('e3', 'cron', 'x', 'foo'), // forbidden handle
|
|
191
|
+
makeEdge('e4', 'sw', 'x', 'c'), // bad dynamic
|
|
192
|
+
],
|
|
193
|
+
};
|
|
194
|
+
const errors = validateDslHandles(dsl);
|
|
195
|
+
expect(errors).toHaveLength(4);
|
|
196
|
+
expect(errors.map((e) => e.edgeId).sort()).toEqual(['e1', 'e2', 'e3', 'e4']);
|
|
197
|
+
const byEdge = Object.fromEntries(errors.map((e) => [e.edgeId, e.code]));
|
|
198
|
+
expect(byEdge).toEqual({
|
|
199
|
+
e1: 'UNKNOWN_HANDLE',
|
|
200
|
+
e2: 'MISSING_HANDLE',
|
|
201
|
+
e3: 'EXTRA_HANDLE',
|
|
202
|
+
e4: 'UNKNOWN_HANDLE',
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
});
|
package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/header.tsx
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
1
2
|
import { useGetLocale, useSetLocale, useGetIdentity } from '@refinedev/core';
|
|
2
3
|
import {
|
|
3
4
|
Layout,
|
|
@@ -7,14 +8,12 @@ import {
|
|
|
7
8
|
Avatar,
|
|
8
9
|
Typography,
|
|
9
10
|
theme,
|
|
10
|
-
Select,
|
|
11
11
|
} from 'antd';
|
|
12
12
|
import { DownOutlined } from '@ant-design/icons';
|
|
13
13
|
import { useTranslation } from 'react-i18next';
|
|
14
|
-
import { useContext } from 'react';
|
|
15
|
-
import { BusinessContext } from 'components/contexts/business';
|
|
16
14
|
import type { MenuProps } from 'antd';
|
|
17
15
|
import { agentAttrs } from 'components/agentPanel/agentAttributes';
|
|
16
|
+
import { HEADER_HEIGHT, Z_INDEX } from 'constants/layout';
|
|
18
17
|
|
|
19
18
|
type IUser = {
|
|
20
19
|
id: number;
|
|
@@ -26,81 +25,81 @@ const { Text } = Typography;
|
|
|
26
25
|
const { useToken } = theme;
|
|
27
26
|
|
|
28
27
|
export const Header: React.FC = () => {
|
|
29
|
-
const { i18n } = useTranslation();
|
|
28
|
+
const { i18n, t } = useTranslation();
|
|
30
29
|
const locale = useGetLocale();
|
|
31
30
|
const changeLanguage = useSetLocale();
|
|
32
31
|
const { token } = useToken();
|
|
33
32
|
const { data: user } = useGetIdentity<IUser>();
|
|
34
|
-
const { business, setBusiness, businessList } = useContext(BusinessContext);
|
|
35
33
|
|
|
36
34
|
const currentLocale = locale();
|
|
37
35
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
36
|
+
// Sort once per language list change, not on every render.
|
|
37
|
+
const langMenu = useMemo<MenuProps>(
|
|
38
|
+
() => ({
|
|
39
|
+
selectedKeys: currentLocale ? [currentLocale] : [],
|
|
40
|
+
items: [...(i18n.languages || [])].sort().map((lang: string) => ({
|
|
41
|
+
key: lang,
|
|
42
|
+
icon: (
|
|
43
|
+
<span style={{ marginRight: 8, display: 'inline-flex' }}>
|
|
44
|
+
<Avatar size={16} src={`/images/flags/${lang}.svg`} alt="" />
|
|
45
|
+
</span>
|
|
46
|
+
),
|
|
47
|
+
label: lang === 'en' ? 'English' : '中文',
|
|
48
|
+
})),
|
|
49
|
+
onClick: ({ key: lang }) => changeLanguage(lang),
|
|
50
|
+
}),
|
|
51
|
+
[i18n.languages, currentLocale, changeLanguage],
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const currentLanguageLabel = currentLocale === 'en' ? 'English' : '中文';
|
|
51
55
|
|
|
52
56
|
const headerStyles: React.CSSProperties = {
|
|
53
57
|
backgroundColor: token.colorBgElevated,
|
|
54
58
|
display: 'flex',
|
|
55
59
|
justifyContent: 'flex-end',
|
|
56
60
|
alignItems: 'center',
|
|
57
|
-
padding: '
|
|
58
|
-
height:
|
|
61
|
+
padding: '0 24px',
|
|
62
|
+
height: HEADER_HEIGHT,
|
|
59
63
|
position: 'sticky',
|
|
60
64
|
top: 0,
|
|
61
|
-
zIndex:
|
|
65
|
+
zIndex: Z_INDEX.STICKY,
|
|
66
|
+
// 与下方内容区切分。1px 软分隔线胜过纯白对纯白边界。
|
|
67
|
+
borderBottom: `1px solid ${token.colorBorderSecondary}`,
|
|
62
68
|
};
|
|
63
69
|
|
|
64
70
|
return (
|
|
65
71
|
<Layout.Header style={headerStyles} {...agentAttrs({ type: 'app-header' })}>
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
? { gameId: '-1', gameName: 'All games' }
|
|
86
|
-
: businessList.find((item) => item.gameId === gameId)!;
|
|
87
|
-
setBusiness(gameInfo);
|
|
88
|
-
window.location.reload();
|
|
89
|
-
}}
|
|
90
|
-
/> */}
|
|
91
|
-
<Dropdown menu={langMenu}>
|
|
92
|
-
<Button type="link">
|
|
93
|
-
<Space>
|
|
94
|
-
<Avatar size={16} src={`/images/flags/${currentLocale}.svg`} />
|
|
95
|
-
{currentLocale === 'en' ? 'English' : '中文'}
|
|
96
|
-
<DownOutlined />
|
|
72
|
+
<Dropdown
|
|
73
|
+
menu={langMenu}
|
|
74
|
+
placement="bottomRight"
|
|
75
|
+
trigger={['hover', 'click']}
|
|
76
|
+
>
|
|
77
|
+
<Button
|
|
78
|
+
type="text"
|
|
79
|
+
aria-label={t('header.changeLanguage', 'Change language')}
|
|
80
|
+
>
|
|
81
|
+
<Space size={6}>
|
|
82
|
+
<Avatar
|
|
83
|
+
size={16}
|
|
84
|
+
src={`/images/flags/${currentLocale}.svg`}
|
|
85
|
+
alt=""
|
|
86
|
+
/>
|
|
87
|
+
<span>{currentLanguageLabel}</span>
|
|
88
|
+
<DownOutlined
|
|
89
|
+
style={{ fontSize: 10, color: token.colorTextTertiary }}
|
|
90
|
+
/>
|
|
97
91
|
</Space>
|
|
98
92
|
</Button>
|
|
99
93
|
</Dropdown>
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
94
|
+
|
|
95
|
+
{(user?.name || user?.avatar) && (
|
|
96
|
+
<Space size="middle" style={{ marginLeft: 16 }}>
|
|
97
|
+
{user?.name && <Text strong>{user.name}</Text>}
|
|
98
|
+
{user?.avatar && (
|
|
99
|
+
<Avatar src={user.avatar} alt={user?.name ?? 'User avatar'} />
|
|
100
|
+
)}
|
|
101
|
+
</Space>
|
|
102
|
+
)}
|
|
104
103
|
</Layout.Header>
|
|
105
104
|
);
|
|
106
105
|
};
|
package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/layout.tsx
CHANGED
|
@@ -4,10 +4,14 @@ import { Grid, Layout as AntdLayout } from 'antd';
|
|
|
4
4
|
import { Header } from './header';
|
|
5
5
|
import { Sider } from './sider';
|
|
6
6
|
import { OffLayoutArea } from 'components/offLayoutArea';
|
|
7
|
+
import { HEADER_HEIGHT } from 'constants/layout';
|
|
7
8
|
|
|
8
9
|
export const Layout: React.FC<React.PropsWithChildren> = ({ children }) => {
|
|
9
10
|
const breakpoint = Grid.useBreakpoint();
|
|
10
|
-
|
|
11
|
+
// 在 Ant Design 的命名里, breakpoint.sm = true 表示 viewport >= 576px
|
|
12
|
+
// (即 *不是* 极窄手机). 用 isAtLeastSm 命名比反语义的 isSmall 清楚得多.
|
|
13
|
+
// useBreakpoint 首次渲染可能返回空对象, 默认按桌面处理避免抖动.
|
|
14
|
+
const isAtLeastSm = breakpoint.sm ?? true;
|
|
11
15
|
|
|
12
16
|
return (
|
|
13
17
|
<ThemedLayoutContextProvider>
|
|
@@ -17,8 +21,8 @@ export const Layout: React.FC<React.PropsWithChildren> = ({ children }) => {
|
|
|
17
21
|
<Header />
|
|
18
22
|
<AntdLayout.Content
|
|
19
23
|
style={{
|
|
20
|
-
padding:
|
|
21
|
-
height:
|
|
24
|
+
padding: isAtLeastSm ? 32 : 16,
|
|
25
|
+
height: `calc(100vh - ${HEADER_HEIGHT}px)`,
|
|
22
26
|
overflowY: 'auto',
|
|
23
27
|
}}
|
|
24
28
|
>
|
package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/logo.tsx
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* App 品牌图标。fill 使用 currentColor,让父级通过 `color` 控制颜色,
|
|
5
|
+
* 因此可被深色 / 浅色主题、selected / disabled 等不同上下文复用。
|
|
6
|
+
*/
|
|
3
7
|
export const Logo = (props: React.SVGProps<SVGSVGElement>) => (
|
|
4
8
|
<svg
|
|
5
9
|
xmlns="http://www.w3.org/2000/svg"
|
|
@@ -7,10 +11,12 @@ export const Logo = (props: React.SVGProps<SVGSVGElement>) => (
|
|
|
7
11
|
height={24}
|
|
8
12
|
viewBox="0 0 24 24"
|
|
9
13
|
fill="none"
|
|
14
|
+
role="img"
|
|
15
|
+
aria-hidden="true"
|
|
10
16
|
{...props}
|
|
11
17
|
>
|
|
12
18
|
<path
|
|
13
|
-
fill="
|
|
19
|
+
fill="currentColor"
|
|
14
20
|
fillRule="evenodd"
|
|
15
21
|
d="M12 24c6.627 0 12-5.373 12-12S18.627 0 12 0 0 5.373 0 12s5.373 12 12 12Zm3.744-18.41c.182-.647-.446-1.03-1.02-.621l-8.008 5.705c-.622.443-.524 1.326.147 1.326h2.109v-.016h4.11l-3.35 1.181-1.476 5.245c-.182.647.446 1.03 1.02.621l8.008-5.705c.622-.443.524-1.326-.147-1.326H13.94l1.805-6.41Z"
|
|
16
22
|
clipRule="evenodd"
|