@flowgram-vue/type-editor 0.2.0
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/LICENSE +22 -0
- package/dist/index.cjs +0 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +0 -0
- package/dist/index.js.map +1 -0
- package/package.json +63 -0
- package/src/components/index.ts +8 -0
- package/src/components/type-editor/columns/index.ts +404 -0
- package/src/components/type-editor/common.ts +44 -0
- package/src/components/type-editor/formatter/index.ts +44 -0
- package/src/components/type-editor/hooks/active-pos.ts +20 -0
- package/src/components/type-editor/hooks/disabled.ts +22 -0
- package/src/components/type-editor/hooks/formatter-value.ts +53 -0
- package/src/components/type-editor/hooks/index.ts +8 -0
- package/src/components/type-editor/index.ts +9 -0
- package/src/components/type-editor/mode/declare-assign.ts +102 -0
- package/src/components/type-editor/mode/index.ts +15 -0
- package/src/components/type-editor/mode/type-definition.ts +46 -0
- package/src/components/type-editor/table.vue +293 -0
- package/src/components/type-editor/type-editor.vue +107 -0
- package/src/components/type-editor/type.ts +142 -0
- package/src/components/type-editor/utils.ts +173 -0
- package/src/components/type-selector/index.ts +51 -0
- package/src/components/type-selector/type-selector.vue +100 -0
- package/src/contexts/index.ts +106 -0
- package/src/env.d.ts +10 -0
- package/src/index.ts +15 -0
- package/src/json-schema-exports.ts +18 -0
- package/src/preset/index.ts +6 -0
- package/src/preset/object-type-editor.vue +91 -0
- package/src/services/clipboard-service.ts +93 -0
- package/src/services/index.ts +11 -0
- package/src/services/shortcut-service.ts +9 -0
- package/src/services/type-editor-service.ts +396 -0
- package/src/services/type-operation-service.ts +99 -0
- package/src/services/type-registry-manager.ts +14 -0
- package/src/services/utils.ts +28 -0
- package/src/styles.css +235 -0
- package/src/type-registry/array.ts +18 -0
- package/src/type-registry/boolean.ts +30 -0
- package/src/type-registry/index.ts +22 -0
- package/src/type-registry/integer.ts +24 -0
- package/src/type-registry/number.ts +23 -0
- package/src/type-registry/object.ts +13 -0
- package/src/type-registry/string.ts +21 -0
- package/src/types/index.ts +7 -0
- package/src/types/registry.ts +52 -0
- package/src/types/type-editor.ts +150 -0
- package/src/utils/index.ts +6 -0
- package/src/utils/monitor-data/index.ts +7 -0
- package/src/utils/monitor-data/monitor-data.ts +43 -0
- package/src/utils/monitor-data/use-monitor-data.ts +29 -0
- package/src/utils/registry-adapter.ts +83 -0
- package/src/utils/toast.ts +16 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { nanoid } from 'nanoid';
|
|
7
|
+
import { IJsonSchema } from '@flowgram-vue/json-schema';
|
|
8
|
+
|
|
9
|
+
import { TypeEditorSpecialConfig } from '../../types';
|
|
10
|
+
import { disableFixIndexFormatter, emptyKeyFormatter } from './formatter';
|
|
11
|
+
import { SUFFIX, COMPONENT_ID_PREFIX } from './common';
|
|
12
|
+
|
|
13
|
+
const genNewTypeSchema = <TypeSchema extends Partial<IJsonSchema>>(
|
|
14
|
+
index: number
|
|
15
|
+
): [string, TypeSchema] => {
|
|
16
|
+
const newKey = genEmptyKey();
|
|
17
|
+
return [
|
|
18
|
+
newKey,
|
|
19
|
+
{
|
|
20
|
+
type: 'string',
|
|
21
|
+
extra: {
|
|
22
|
+
index,
|
|
23
|
+
},
|
|
24
|
+
} as TypeSchema,
|
|
25
|
+
];
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const traverseIJsonSchema = <TypeSchema extends Partial<IJsonSchema>>(
|
|
29
|
+
root: TypeSchema | undefined,
|
|
30
|
+
cb: (type: TypeSchema) => void
|
|
31
|
+
): void => {
|
|
32
|
+
if (root) {
|
|
33
|
+
cb(root);
|
|
34
|
+
if (root.items) {
|
|
35
|
+
traverseIJsonSchema(root.items as TypeSchema, cb);
|
|
36
|
+
}
|
|
37
|
+
if (root.additionalProperties) {
|
|
38
|
+
traverseIJsonSchema(root.additionalProperties as TypeSchema, cb);
|
|
39
|
+
}
|
|
40
|
+
if (root.properties) {
|
|
41
|
+
Object.values(root.properties).forEach((v) => {
|
|
42
|
+
traverseIJsonSchema(v as TypeSchema, cb);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const jsonParse = (jsonString?: string) => {
|
|
49
|
+
try {
|
|
50
|
+
return JSON.parse(jsonString || '');
|
|
51
|
+
} catch {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const sortProperties = <TypeSchema extends Partial<IJsonSchema>>(typeSchema: TypeSchema) => {
|
|
57
|
+
const { properties = {} } = typeSchema;
|
|
58
|
+
const originKeys = Object.keys(properties);
|
|
59
|
+
const sortKeys = originKeys.sort(
|
|
60
|
+
(a, b) => (properties[a].extra?.index || 0) - (properties[b].extra?.index || 0)
|
|
61
|
+
);
|
|
62
|
+
for (let i = 0; i < sortKeys.length; i++) {
|
|
63
|
+
const key = sortKeys[i];
|
|
64
|
+
fixFlowIndex(properties[key]);
|
|
65
|
+
properties[key].extra!.index = i;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const fixFlowIndex = <TypeSchema extends Partial<IJsonSchema>>(type: TypeSchema, idx = 0): void => {
|
|
70
|
+
if (!type) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (!type.extra) {
|
|
74
|
+
type.extra = {};
|
|
75
|
+
}
|
|
76
|
+
if (type.extra.index === undefined) {
|
|
77
|
+
type.extra.index = idx;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const getInitialSchema = <TypeSchema extends Partial<IJsonSchema>>(): TypeSchema =>
|
|
82
|
+
({
|
|
83
|
+
type: 'object',
|
|
84
|
+
properties: {},
|
|
85
|
+
}) as TypeSchema;
|
|
86
|
+
|
|
87
|
+
const clone = <T>(val: T): T => (val ? JSON.parse(JSON.stringify(val)) : val);
|
|
88
|
+
|
|
89
|
+
const isTempState = <TypeSchema extends Partial<IJsonSchema>>(
|
|
90
|
+
type: TypeSchema,
|
|
91
|
+
customValidateName?: (value: string) => string
|
|
92
|
+
): boolean => {
|
|
93
|
+
let error = false;
|
|
94
|
+
traverseIJsonSchema(type, (c) => {
|
|
95
|
+
if (c.properties) {
|
|
96
|
+
Object.keys(c.properties).forEach((key) => {
|
|
97
|
+
const res = isEmptyKey(key) || customValidateName?.(key);
|
|
98
|
+
if (res) {
|
|
99
|
+
error = true;
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
return error;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const genEmptyKey = () => SUFFIX + nanoid();
|
|
108
|
+
const isEmptyKey = (key: string) => key.startsWith(SUFFIX);
|
|
109
|
+
const formateKey = (key: string) => (isEmptyKey(key) ? '' : key);
|
|
110
|
+
const deFormateKey = (key: string, originKey?: string) => (!key ? originKey || genEmptyKey() : key);
|
|
111
|
+
|
|
112
|
+
const formateTypeSchema = <TypeSchema extends Partial<IJsonSchema>>(
|
|
113
|
+
typeSchema: TypeSchema,
|
|
114
|
+
config: TypeEditorSpecialConfig<TypeSchema>
|
|
115
|
+
): TypeSchema => {
|
|
116
|
+
const newSchema = JSON.parse(JSON.stringify(typeSchema));
|
|
117
|
+
const formatters = [emptyKeyFormatter];
|
|
118
|
+
if (config.disableFixIndex) {
|
|
119
|
+
formatters.push(disableFixIndexFormatter);
|
|
120
|
+
}
|
|
121
|
+
traverseIJsonSchema(newSchema, (type) => {
|
|
122
|
+
formatters.forEach((formatter) => {
|
|
123
|
+
formatter(type);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
return newSchema;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const valueToTypeSchema = <TypeSchema extends Partial<IJsonSchema>>(value: unknown): TypeSchema => {
|
|
130
|
+
switch (typeof value) {
|
|
131
|
+
case 'string':
|
|
132
|
+
return { type: 'string' } as TypeSchema;
|
|
133
|
+
case 'bigint':
|
|
134
|
+
case 'number':
|
|
135
|
+
return { type: 'number' } as TypeSchema;
|
|
136
|
+
case 'boolean':
|
|
137
|
+
return { type: 'boolean' } as TypeSchema;
|
|
138
|
+
case 'object': {
|
|
139
|
+
if (value) {
|
|
140
|
+
if (Array.isArray(value)) {
|
|
141
|
+
return { type: 'array', items: valueToTypeSchema(value[0]) } as TypeSchema;
|
|
142
|
+
}
|
|
143
|
+
const object: IJsonSchema = { type: 'object', properties: {} };
|
|
144
|
+
Object.keys(value as object).forEach((k) => {
|
|
145
|
+
object.properties![k] = valueToTypeSchema((value as Record<string, unknown>)[k]);
|
|
146
|
+
});
|
|
147
|
+
return object as TypeSchema;
|
|
148
|
+
}
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
default:
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
return { type: 'string' } as TypeSchema;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
export const typeEditorUtils = {
|
|
158
|
+
genNewTypeSchema,
|
|
159
|
+
sortProperties,
|
|
160
|
+
traverseIJsonSchema,
|
|
161
|
+
fixFlowIndex,
|
|
162
|
+
genEmptyKey,
|
|
163
|
+
jsonParse,
|
|
164
|
+
clone,
|
|
165
|
+
formateTypeSchema,
|
|
166
|
+
isTempState,
|
|
167
|
+
valueToTypeSchema,
|
|
168
|
+
deFormateKey,
|
|
169
|
+
formateKey,
|
|
170
|
+
getInitialSchema,
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
export const getComponentId = (id: string): string => `${COMPONENT_ID_PREFIX}-${id}`;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { defineComponent, h, ref } from 'vue';
|
|
7
|
+
import { IJsonSchema } from '@flowgram-vue/json-schema';
|
|
8
|
+
|
|
9
|
+
import { TypeEditorProvider } from '../../contexts';
|
|
10
|
+
import TypeSelectorInner from './type-selector.vue';
|
|
11
|
+
import type { DisableTypeInfo } from '../../types';
|
|
12
|
+
import type { TypeRegistryCreatorsAdapter } from '../../contexts';
|
|
13
|
+
|
|
14
|
+
export const TypeSelector = defineComponent({
|
|
15
|
+
name: 'TypeSelector',
|
|
16
|
+
props: {
|
|
17
|
+
value: Object as () => IJsonSchema,
|
|
18
|
+
disableTypes: Array as () => DisableTypeInfo[],
|
|
19
|
+
defaultOpen: Boolean,
|
|
20
|
+
disabled: Boolean,
|
|
21
|
+
typeRegistryCreators: Array as () => TypeRegistryCreatorsAdapter<IJsonSchema>[],
|
|
22
|
+
},
|
|
23
|
+
emits: ['change', 'dropdownVisibleChange', 'blur'],
|
|
24
|
+
setup(props, { emit }) {
|
|
25
|
+
const ready = ref(false);
|
|
26
|
+
return () =>
|
|
27
|
+
h(
|
|
28
|
+
TypeEditorProvider,
|
|
29
|
+
{
|
|
30
|
+
typeRegistryCreators: props.typeRegistryCreators,
|
|
31
|
+
onInit: () => {
|
|
32
|
+
ready.value = true;
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
() =>
|
|
36
|
+
ready.value
|
|
37
|
+
? h(TypeSelectorInner, {
|
|
38
|
+
value: props.value,
|
|
39
|
+
disableTypes: props.disableTypes,
|
|
40
|
+
defaultOpen: props.defaultOpen,
|
|
41
|
+
disabled: props.disabled,
|
|
42
|
+
typeRegistryCreators: props.typeRegistryCreators,
|
|
43
|
+
onChange: (val: IJsonSchema | undefined, ctx: { source: string }) =>
|
|
44
|
+
emit('change', val, ctx),
|
|
45
|
+
onDropdownVisibleChange: (v: boolean) => emit('dropdownVisibleChange', v),
|
|
46
|
+
onBlur: () => emit('blur'),
|
|
47
|
+
})
|
|
48
|
+
: null
|
|
49
|
+
);
|
|
50
|
+
},
|
|
51
|
+
});
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
import { computed, onMounted, ref } from 'vue';
|
|
7
|
+
import { IJsonSchema } from '@flowgram-vue/json-schema';
|
|
8
|
+
|
|
9
|
+
import { useTypeDefinitionManager } from '../../contexts';
|
|
10
|
+
import type { DisableTypeInfo } from '../../types';
|
|
11
|
+
import type { TypeRegistryCreatorsAdapter } from '../../contexts';
|
|
12
|
+
|
|
13
|
+
defineOptions({ name: 'TypeSelector' });
|
|
14
|
+
|
|
15
|
+
const props = defineProps<{
|
|
16
|
+
value?: IJsonSchema;
|
|
17
|
+
disableTypes?: DisableTypeInfo[];
|
|
18
|
+
defaultOpen?: boolean;
|
|
19
|
+
disabled?: boolean;
|
|
20
|
+
typeRegistryCreators?: TypeRegistryCreatorsAdapter<IJsonSchema>[];
|
|
21
|
+
}>();
|
|
22
|
+
|
|
23
|
+
const emit = defineEmits<{
|
|
24
|
+
change: [val: IJsonSchema | undefined, ctx: { source: 'type-selector' | 'custom-panel' }];
|
|
25
|
+
'dropdown-visible-change': [visible: boolean];
|
|
26
|
+
blur: [];
|
|
27
|
+
}>();
|
|
28
|
+
|
|
29
|
+
const typeService = useTypeDefinitionManager();
|
|
30
|
+
const open = ref(!!props.defaultOpen);
|
|
31
|
+
const query = ref('');
|
|
32
|
+
|
|
33
|
+
const options = computed(() => {
|
|
34
|
+
const disabled = new Map((props.disableTypes || []).map((d) => [d.type, d.reason]));
|
|
35
|
+
return typeService.getTypeRegistriesWithParentType().map((config) => ({
|
|
36
|
+
type: config.type,
|
|
37
|
+
label: config.label,
|
|
38
|
+
icon: config.icon,
|
|
39
|
+
disabled: disabled.get(config.type),
|
|
40
|
+
schema: config.getDefaultSchema?.() || { type: config.type },
|
|
41
|
+
}));
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const filtered = computed(() => {
|
|
45
|
+
const q = query.value.toLowerCase();
|
|
46
|
+
if (!q) {
|
|
47
|
+
return options.value;
|
|
48
|
+
}
|
|
49
|
+
return options.value.filter((o) => o.label.toLowerCase().includes(q) || o.type.includes(q));
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const currentLabel = computed(() => {
|
|
53
|
+
if (!props.value) {
|
|
54
|
+
return '';
|
|
55
|
+
}
|
|
56
|
+
return typeService.getComplexText(props.value);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
onMounted(() => {
|
|
60
|
+
if (props.defaultOpen) {
|
|
61
|
+
emit('dropdown-visible-change', true);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const select = (opt: (typeof options.value)[number]) => {
|
|
66
|
+
if (opt.disabled) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
emit('change', opt.schema as IJsonSchema, { source: 'type-selector' });
|
|
70
|
+
open.value = false;
|
|
71
|
+
emit('dropdown-visible-change', false);
|
|
72
|
+
};
|
|
73
|
+
</script>
|
|
74
|
+
|
|
75
|
+
<template>
|
|
76
|
+
<div class="fg-type-selector">
|
|
77
|
+
<input
|
|
78
|
+
class="fg-type-input"
|
|
79
|
+
:disabled="props.disabled"
|
|
80
|
+
:placeholder="currentLabel"
|
|
81
|
+
:value="query"
|
|
82
|
+
@focus="open = true; emit('dropdown-visible-change', true)"
|
|
83
|
+
@blur="emit('blur')"
|
|
84
|
+
@input="query = ($event.target as HTMLInputElement).value"
|
|
85
|
+
/>
|
|
86
|
+
<div v-if="open && !props.disabled" class="fg-type-selector-panel">
|
|
87
|
+
<div
|
|
88
|
+
v-for="opt in filtered"
|
|
89
|
+
:key="opt.type"
|
|
90
|
+
class="fg-type-selector-item"
|
|
91
|
+
:class="{ 'is-disabled': !!opt.disabled }"
|
|
92
|
+
:title="opt.disabled"
|
|
93
|
+
@mousedown.prevent="select(opt)"
|
|
94
|
+
>
|
|
95
|
+
<component :is="() => opt.icon" />
|
|
96
|
+
<span>{{ opt.label }}</span>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
</template>
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Container, type interfaces } from 'inversify';
|
|
7
|
+
import { IJsonSchema, JsonSchemaTypeRegistryCreator } from '@flowgram-vue/json-schema';
|
|
8
|
+
import {
|
|
9
|
+
defineComponent,
|
|
10
|
+
inject,
|
|
11
|
+
provide,
|
|
12
|
+
type InjectionKey,
|
|
13
|
+
type PropType,
|
|
14
|
+
} from 'vue';
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
getTypeDefinitionAdapter,
|
|
18
|
+
type ITypeDefinitionAdapter,
|
|
19
|
+
registryFormatter,
|
|
20
|
+
} from '../utils/registry-adapter';
|
|
21
|
+
import { TypeEditorRegistry } from '../types';
|
|
22
|
+
import { defaultTypeRegistryCreators } from '../type-registry';
|
|
23
|
+
import { TypeEditorRegistryManager } from '../services/type-registry-manager';
|
|
24
|
+
import {
|
|
25
|
+
ClipboardService,
|
|
26
|
+
ShortcutsService,
|
|
27
|
+
TypeEditorOperationService,
|
|
28
|
+
TypeEditorService,
|
|
29
|
+
} from '../services';
|
|
30
|
+
|
|
31
|
+
export type TypeRegistryCreatorsAdapter<TypeSchema extends Partial<IJsonSchema>> = (
|
|
32
|
+
param: Parameters<JsonSchemaTypeRegistryCreator<TypeSchema, TypeEditorRegistry<TypeSchema>>>[0] &
|
|
33
|
+
ITypeDefinitionAdapter<TypeSchema>
|
|
34
|
+
) => ReturnType<JsonSchemaTypeRegistryCreator<TypeSchema, TypeEditorRegistry<TypeSchema>>>;
|
|
35
|
+
|
|
36
|
+
export const TypeEditorContext = {
|
|
37
|
+
typeRegistryCreators: [] as TypeRegistryCreatorsAdapter<IJsonSchema>[],
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
interface Context {
|
|
41
|
+
container: Container;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const TypeContextKey: InjectionKey<Context> = Symbol('TypeEditorContainer');
|
|
45
|
+
|
|
46
|
+
export function useService<T>(identifier: interfaces.ServiceIdentifier<T>): T {
|
|
47
|
+
const ctx = inject(TypeContextKey);
|
|
48
|
+
if (!ctx) {
|
|
49
|
+
throw new Error('useService() must be used inside TypeEditorProvider');
|
|
50
|
+
}
|
|
51
|
+
return ctx.container.get(identifier) as T;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const TypeEditorProvider = defineComponent({
|
|
55
|
+
name: 'TypeEditorProvider',
|
|
56
|
+
props: {
|
|
57
|
+
typeRegistryCreators: {
|
|
58
|
+
type: Array as PropType<TypeRegistryCreatorsAdapter<IJsonSchema>[]>,
|
|
59
|
+
default: () => [],
|
|
60
|
+
},
|
|
61
|
+
onInit: {
|
|
62
|
+
type: Function as PropType<() => void>,
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
setup(props, { slots }) {
|
|
66
|
+
const container = new Container();
|
|
67
|
+
container.bind(TypeEditorService).toSelf().inSingletonScope();
|
|
68
|
+
container.bind(TypeEditorOperationService).toSelf().inSingletonScope();
|
|
69
|
+
container.bind(TypeEditorRegistryManager).toSelf().inSingletonScope();
|
|
70
|
+
container.bind(ShortcutsService).toSelf().inSingletonScope();
|
|
71
|
+
container.bind(ClipboardService).toSelf().inSingletonScope();
|
|
72
|
+
|
|
73
|
+
provide(TypeContextKey, { container });
|
|
74
|
+
|
|
75
|
+
const typeManager = container.get<TypeEditorRegistryManager<IJsonSchema>>(
|
|
76
|
+
TypeEditorRegistryManager
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
defaultTypeRegistryCreators.forEach((creator) => {
|
|
80
|
+
typeManager.register(
|
|
81
|
+
creator as unknown as JsonSchemaTypeRegistryCreator<
|
|
82
|
+
IJsonSchema,
|
|
83
|
+
TypeEditorRegistry<IJsonSchema>
|
|
84
|
+
>
|
|
85
|
+
);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const adapter = getTypeDefinitionAdapter(typeManager);
|
|
89
|
+
(props.typeRegistryCreators || []).forEach((creator) => {
|
|
90
|
+
typeManager.register(creator({ typeManager, ...adapter }));
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
typeManager.getAllTypeRegistries().forEach((registry) => {
|
|
94
|
+
const res = registryFormatter(registry, typeManager);
|
|
95
|
+
typeManager.register(res);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
typeManager.triggerChanges();
|
|
99
|
+
props.onInit?.();
|
|
100
|
+
|
|
101
|
+
return () => slots.default?.();
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
export const useTypeDefinitionManager = <TypeSchema extends Partial<IJsonSchema>>() =>
|
|
106
|
+
useService<TypeEditorRegistryManager<TypeSchema>>(TypeEditorRegistryManager);
|
package/src/env.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
declare module '*.vue' {
|
|
7
|
+
import type { DefineComponent } from 'vue';
|
|
8
|
+
const component: DefineComponent<object, object, unknown>;
|
|
9
|
+
export default component;
|
|
10
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import './styles.css';
|
|
7
|
+
|
|
8
|
+
export * from './types';
|
|
9
|
+
export { TypeEditorContext } from './contexts';
|
|
10
|
+
export { columnConfigs as typeEditorColumnConfigs } from './components/type-editor/columns';
|
|
11
|
+
export * from './components';
|
|
12
|
+
export * from './services/type-editor-service';
|
|
13
|
+
export * from './services/type-registry-manager';
|
|
14
|
+
export * from '@flowgram-vue/json-schema';
|
|
15
|
+
export * from './preset';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* Re-export json-schema public types/runtime without `container-module.ts`.
|
|
6
|
+
* That file currently fails `tsc` (inversify `bind().to()` variance) in
|
|
7
|
+
* `@flowgram-vue/json-schema`; importing the package barrel would fail this
|
|
8
|
+
* package's ts-check. Consumers that need `jsonSchemaContainerModule` should
|
|
9
|
+
* import it from `@flowgram-vue/json-schema` directly after that package is fixed.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export * from '../../../variable-engine/json-schema/src/json-schema';
|
|
13
|
+
export * from '../../../variable-engine/json-schema/src/base';
|
|
14
|
+
export {
|
|
15
|
+
useTypeManager,
|
|
16
|
+
TypePresetProvider,
|
|
17
|
+
TypePresetKey,
|
|
18
|
+
} from '../../../variable-engine/json-schema/src/composables/use-type-manager';
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
import { computed } from 'vue';
|
|
7
|
+
import { IJsonSchema } from '@flowgram-vue/json-schema';
|
|
8
|
+
|
|
9
|
+
import { TypeEditorColumnType, type TypeEditorColumnViewConfig } from '../types';
|
|
10
|
+
import { ToolbarKey } from '../components/type-editor/type';
|
|
11
|
+
import TypeEditor from '../components/type-editor/type-editor.vue';
|
|
12
|
+
|
|
13
|
+
defineOptions({ name: 'ObjectTypeEditor' });
|
|
14
|
+
|
|
15
|
+
const defaultViewConfigs: TypeEditorColumnViewConfig[] = [
|
|
16
|
+
{ type: TypeEditorColumnType.Key, visible: true },
|
|
17
|
+
{ type: TypeEditorColumnType.Type, visible: true },
|
|
18
|
+
{ type: TypeEditorColumnType.Description, visible: true },
|
|
19
|
+
{ type: TypeEditorColumnType.Required, visible: true },
|
|
20
|
+
{ type: TypeEditorColumnType.Default, visible: true },
|
|
21
|
+
{ type: TypeEditorColumnType.Operate, visible: true },
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
const props = defineProps<{
|
|
25
|
+
value?: IJsonSchema;
|
|
26
|
+
readonly?: boolean;
|
|
27
|
+
config?: {
|
|
28
|
+
rootKey?: string;
|
|
29
|
+
viewConfigs?: TypeEditorColumnViewConfig[];
|
|
30
|
+
};
|
|
31
|
+
}>();
|
|
32
|
+
|
|
33
|
+
const emit = defineEmits<{
|
|
34
|
+
change: [value?: IJsonSchema];
|
|
35
|
+
}>();
|
|
36
|
+
|
|
37
|
+
const rootKey = computed(() => props.config?.rootKey || 'outputs');
|
|
38
|
+
const viewConfigs = computed(() => props.config?.viewConfigs || defaultViewConfigs);
|
|
39
|
+
|
|
40
|
+
const wrapValue = computed<IJsonSchema>(() => ({
|
|
41
|
+
type: 'object',
|
|
42
|
+
properties: { [rootKey.value]: props.value || { type: 'object' } },
|
|
43
|
+
}));
|
|
44
|
+
|
|
45
|
+
const disableEditColumn = computed(() => {
|
|
46
|
+
if (!props.readonly) {
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
return viewConfigs.value.map((v) => ({
|
|
50
|
+
column: v.type,
|
|
51
|
+
reason: 'This field is not editable.',
|
|
52
|
+
}));
|
|
53
|
+
});
|
|
54
|
+
</script>
|
|
55
|
+
|
|
56
|
+
<template>
|
|
57
|
+
<div>
|
|
58
|
+
<TypeEditor
|
|
59
|
+
:readonly="props.readonly"
|
|
60
|
+
mode="type-definition"
|
|
61
|
+
:toolbar-config="[ToolbarKey.Import, ToolbarKey.UndoRedo]"
|
|
62
|
+
:root-level="1"
|
|
63
|
+
:value="wrapValue"
|
|
64
|
+
:disable-edit-column="disableEditColumn"
|
|
65
|
+
:view-configs="defaultViewConfigs"
|
|
66
|
+
:on-change="(v) => emit('change', v?.properties?.[rootKey])"
|
|
67
|
+
:on-custom-set-value="
|
|
68
|
+
(newType) => ({
|
|
69
|
+
type: 'object',
|
|
70
|
+
properties: { [rootKey]: newType },
|
|
71
|
+
})
|
|
72
|
+
"
|
|
73
|
+
:get-root-schema="(type) => type.properties![rootKey]"
|
|
74
|
+
:on-edit-row-data-source="
|
|
75
|
+
(dataSource) => {
|
|
76
|
+
if (dataSource[0]) {
|
|
77
|
+
dataSource[0].disableEditColumn = [
|
|
78
|
+
{ column: TypeEditorColumnType.Key, reason: 'This field is not editable.' },
|
|
79
|
+
{ column: TypeEditorColumnType.Type, reason: 'This field is not editable.' },
|
|
80
|
+
{ column: TypeEditorColumnType.Required, reason: 'This field is not editable.' },
|
|
81
|
+
{ column: TypeEditorColumnType.Default, reason: 'This field is not editable.' },
|
|
82
|
+
{ column: TypeEditorColumnType.Operate, reason: 'This field is not editable.' },
|
|
83
|
+
];
|
|
84
|
+
dataSource[0].cannotDrag = true;
|
|
85
|
+
}
|
|
86
|
+
return dataSource;
|
|
87
|
+
}
|
|
88
|
+
"
|
|
89
|
+
/>
|
|
90
|
+
</div>
|
|
91
|
+
</template>
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { injectable } from 'inversify';
|
|
7
|
+
import { Event, Emitter } from '@flowgram-vue/utils';
|
|
8
|
+
|
|
9
|
+
@injectable()
|
|
10
|
+
export class ClipboardService {
|
|
11
|
+
public readonly onClipboardChangedEmitter = new Emitter<string>();
|
|
12
|
+
|
|
13
|
+
readonly onClipboardChanged: Event<string> = this.onClipboardChangedEmitter.event;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 读取浏览器数据
|
|
17
|
+
*/
|
|
18
|
+
private get data(): Promise<string> {
|
|
19
|
+
return navigator.clipboard.readText();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
private async saveReadData(): Promise<{
|
|
23
|
+
error?: string;
|
|
24
|
+
data?: string;
|
|
25
|
+
}> {
|
|
26
|
+
try {
|
|
27
|
+
const data = await this.data;
|
|
28
|
+
return {
|
|
29
|
+
data,
|
|
30
|
+
};
|
|
31
|
+
} catch (error) {
|
|
32
|
+
return {
|
|
33
|
+
error: error as string,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 设置剪切板数据
|
|
40
|
+
*/
|
|
41
|
+
public async writeData(newStrData: string): Promise<void> {
|
|
42
|
+
const oldSaveData = await this.saveReadData();
|
|
43
|
+
|
|
44
|
+
// 读取错误可能是没有读取权限,此时不校验是否相等,直接写入剪切板
|
|
45
|
+
if (oldSaveData.error || oldSaveData.data !== newStrData) {
|
|
46
|
+
if (navigator.clipboard && window.isSecureContext) {
|
|
47
|
+
await navigator.clipboard.writeText(newStrData);
|
|
48
|
+
const event = document.createEvent('Event');
|
|
49
|
+
event.initEvent('onchange');
|
|
50
|
+
(event as unknown as { value: string }).value = newStrData;
|
|
51
|
+
navigator.clipboard.dispatchEvent(event);
|
|
52
|
+
} else {
|
|
53
|
+
const textarea = document.createElement('textarea');
|
|
54
|
+
textarea.value = newStrData;
|
|
55
|
+
|
|
56
|
+
// 视区以外渲染 dom,无法 display none,否则无文本 copy
|
|
57
|
+
textarea.style.display = 'absolute';
|
|
58
|
+
textarea.style.left = '-99999999px';
|
|
59
|
+
|
|
60
|
+
document.body.prepend(textarea);
|
|
61
|
+
|
|
62
|
+
// highlight the content of the textarea element
|
|
63
|
+
textarea.select();
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
document.execCommand('copy');
|
|
67
|
+
} catch (err) {
|
|
68
|
+
console.log(err);
|
|
69
|
+
} finally {
|
|
70
|
+
textarea.remove();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
this.onClipboardChangedEmitter.fire(newStrData);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* 获取剪切板数据
|
|
80
|
+
*/
|
|
81
|
+
|
|
82
|
+
public async readData(): Promise<string> {
|
|
83
|
+
const res = await this.saveReadData();
|
|
84
|
+
if (res.error) {
|
|
85
|
+
throw Error(res.error);
|
|
86
|
+
}
|
|
87
|
+
return res.data || '';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
public clearData(): void {
|
|
91
|
+
this.writeData('');
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import 'reflect-metadata';
|
|
7
|
+
export * from './type-editor-service';
|
|
8
|
+
export * from './shortcut-service';
|
|
9
|
+
export * from './clipboard-service';
|
|
10
|
+
export * from './type-operation-service';
|
|
11
|
+
export * from './type-registry-manager';
|