@tmagic/editor 1.5.0 → 1.5.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.
- package/dist/style.css +153 -6
- package/dist/tmagic-editor.js +907 -528
- package/dist/tmagic-editor.umd.cjs +932 -552
- package/package.json +10 -10
- package/src/Editor.vue +18 -5
- package/src/components/CodeBlockEditor.vue +3 -3
- package/src/components/CodeParams.vue +2 -2
- package/src/components/ContentMenu.vue +18 -18
- package/src/components/FloatingBox.vue +3 -3
- package/src/components/Resizer.vue +4 -4
- package/src/components/ScrollBar.vue +3 -3
- package/src/components/ScrollViewer.vue +3 -3
- package/src/components/SplitView.vue +2 -2
- package/src/components/ToolButton.vue +2 -1
- package/src/editorProps.ts +3 -1
- package/src/fields/DataSourceFieldSelect/Index.vue +6 -6
- package/src/fields/DataSourceInput.vue +2 -2
- package/src/fields/DataSourceMethods.vue +2 -2
- package/src/fields/DataSourceMocks.vue +0 -1
- package/src/fields/EventSelect.vue +0 -1
- package/src/fields/KeyValue.vue +2 -2
- package/src/hooks/use-code-block-edit.ts +2 -2
- package/src/hooks/use-getso.ts +7 -7
- package/src/index.ts +2 -1
- package/src/layouts/CodeEditor.vue +2 -2
- package/src/layouts/Framework.vue +5 -8
- package/src/layouts/NavMenu.vue +2 -2
- package/src/layouts/page-bar/PageBar.vue +3 -3
- package/src/layouts/page-bar/PageBarScrollContainer.vue +3 -3
- package/src/layouts/props-panel/FormPanel.vue +123 -0
- package/src/layouts/props-panel/PropsPanel.vue +146 -0
- package/src/layouts/props-panel/use-style-panel.ts +29 -0
- package/src/layouts/sidebar/Sidebar.vue +4 -1
- package/src/layouts/sidebar/code-block/CodeBlockList.vue +12 -1
- package/src/layouts/sidebar/code-block/CodeBlockListPanel.vue +35 -4
- package/src/layouts/sidebar/code-block/useContentMenu.ts +83 -0
- package/src/layouts/sidebar/data-source/DataSourceList.vue +8 -8
- package/src/layouts/sidebar/data-source/DataSourceListPanel.vue +40 -4
- package/src/layouts/sidebar/data-source/useContentMenu.ts +81 -0
- package/src/layouts/sidebar/layer/LayerMenu.vue +7 -13
- package/src/layouts/sidebar/layer/LayerPanel.vue +11 -4
- package/src/layouts/sidebar/layer/use-click.ts +2 -2
- package/src/layouts/sidebar/layer/use-keybinding.ts +2 -2
- package/src/layouts/workspace/Workspace.vue +9 -2
- package/src/layouts/workspace/viewer/NodeListMenu.vue +3 -3
- package/src/layouts/workspace/viewer/Stage.vue +18 -6
- package/src/layouts/workspace/viewer/StageOverlay.vue +2 -2
- package/src/layouts/workspace/viewer/ViewerMenu.vue +4 -6
- package/src/services/storage.ts +2 -1
- package/src/services/ui.ts +1 -0
- package/src/theme/common/var.scss +12 -10
- package/src/theme/component-list-panel.scss +9 -7
- package/src/theme/content-menu.scss +7 -5
- package/src/theme/data-source.scss +3 -1
- package/src/theme/floating-box.scss +4 -2
- package/src/theme/framework.scss +7 -5
- package/src/theme/index.scss +4 -5
- package/src/theme/key-value.scss +2 -2
- package/src/theme/layer-panel.scss +3 -1
- package/src/theme/layout.scss +1 -1
- package/src/theme/nav-menu.scss +7 -5
- package/src/theme/page-bar.scss +17 -15
- package/src/theme/props-panel.scss +81 -1
- package/src/theme/resizer.scss +1 -1
- package/src/theme/sidebar.scss +4 -2
- package/src/theme/stage.scss +3 -1
- package/src/theme/theme.scss +28 -28
- package/src/theme/tree.scss +14 -12
- package/src/type.ts +8 -0
- package/src/utils/content-menu.ts +2 -2
- package/src/utils/props.ts +45 -3
- package/types/index.d.ts +1796 -1230
- package/src/layouts/PropsPanel.vue +0 -131
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
</template>
|
|
25
25
|
|
|
26
26
|
<script setup lang="ts">
|
|
27
|
-
import { computed, inject, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
|
27
|
+
import { computed, inject, nextTick, onBeforeUnmount, onMounted, ref, useTemplateRef, watch } from 'vue';
|
|
28
28
|
import { ArrowLeftBold, ArrowRightBold } from '@element-plus/icons-vue';
|
|
29
29
|
import Sortable, { type SortableEvent } from 'sortablejs';
|
|
30
30
|
|
|
@@ -46,7 +46,7 @@ const services = inject<Services>('services');
|
|
|
46
46
|
const editorService = services?.editorService;
|
|
47
47
|
const uiService = services?.uiService;
|
|
48
48
|
|
|
49
|
-
const itemsContainer =
|
|
49
|
+
const itemsContainer = useTemplateRef<HTMLElement>('itemsContainer');
|
|
50
50
|
const canScroll = ref(false);
|
|
51
51
|
|
|
52
52
|
const showAddPageButton = computed(() => uiService?.get('showAddPageButton'));
|
|
@@ -75,7 +75,7 @@ const resizeObserver = new ResizeObserver(() => {
|
|
|
75
75
|
setCanScroll();
|
|
76
76
|
});
|
|
77
77
|
|
|
78
|
-
const pageBar =
|
|
78
|
+
const pageBar = useTemplateRef<HTMLDivElement>('pageBar');
|
|
79
79
|
onMounted(() => {
|
|
80
80
|
pageBar.value && resizeObserver.observe(pageBar.value);
|
|
81
81
|
});
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="m-editor-props-form-panel">
|
|
3
|
+
<slot name="props-form-panel-header"></slot>
|
|
4
|
+
|
|
5
|
+
<TMagicScrollbar>
|
|
6
|
+
<MForm
|
|
7
|
+
ref="configForm"
|
|
8
|
+
:class="propsPanelSize"
|
|
9
|
+
:popper-class="`m-editor-props-panel-popper ${propsPanelSize}`"
|
|
10
|
+
:label-width="labelWidth"
|
|
11
|
+
:label-position="labelPosition"
|
|
12
|
+
:size="propsPanelSize"
|
|
13
|
+
:init-values="values"
|
|
14
|
+
:config="config"
|
|
15
|
+
:extend-state="extendState"
|
|
16
|
+
@change="submit"
|
|
17
|
+
@error="errorHandler"
|
|
18
|
+
></MForm>
|
|
19
|
+
</TMagicScrollbar>
|
|
20
|
+
|
|
21
|
+
<TMagicButton
|
|
22
|
+
v-if="!disabledShowSrc"
|
|
23
|
+
class="m-editor-props-panel-src-icon"
|
|
24
|
+
circle
|
|
25
|
+
title="源码"
|
|
26
|
+
:type="showSrc ? 'primary' : ''"
|
|
27
|
+
@click="showSrc = !showSrc"
|
|
28
|
+
>
|
|
29
|
+
<MIcon :icon="DocumentIcon"></MIcon>
|
|
30
|
+
</TMagicButton>
|
|
31
|
+
|
|
32
|
+
<CodeEditor
|
|
33
|
+
v-if="showSrc"
|
|
34
|
+
class="m-editor-props-panel-src-code"
|
|
35
|
+
:height="`${editorContentHeight}px`"
|
|
36
|
+
:init-values="codeValueKey ? values[codeValueKey] : values"
|
|
37
|
+
:options="codeOptions"
|
|
38
|
+
:parse="true"
|
|
39
|
+
@save="saveCode"
|
|
40
|
+
></CodeEditor>
|
|
41
|
+
</div>
|
|
42
|
+
</template>
|
|
43
|
+
|
|
44
|
+
<script setup lang="ts">
|
|
45
|
+
import { computed, getCurrentInstance, inject, onMounted, ref, useTemplateRef, watchEffect } from 'vue';
|
|
46
|
+
import { Document as DocumentIcon } from '@element-plus/icons-vue';
|
|
47
|
+
|
|
48
|
+
import { TMagicButton, TMagicScrollbar } from '@tmagic/design';
|
|
49
|
+
import type { ContainerChangeEventData, FormConfig, FormState, FormValue } from '@tmagic/form';
|
|
50
|
+
import { MForm } from '@tmagic/form';
|
|
51
|
+
|
|
52
|
+
import MIcon from '@editor/components/Icon.vue';
|
|
53
|
+
import { useEditorContentHeight } from '@editor/hooks/use-editor-content-height';
|
|
54
|
+
import type { Services } from '@editor/type';
|
|
55
|
+
|
|
56
|
+
import CodeEditor from '../CodeEditor.vue';
|
|
57
|
+
|
|
58
|
+
defineSlots<{
|
|
59
|
+
'props-form-panel-header'(props: {}): any;
|
|
60
|
+
}>();
|
|
61
|
+
|
|
62
|
+
defineOptions({
|
|
63
|
+
name: 'MEditorFormPanel',
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const props = defineProps<{
|
|
67
|
+
config: FormConfig;
|
|
68
|
+
values: FormValue;
|
|
69
|
+
disabledShowSrc?: boolean;
|
|
70
|
+
labelWidth?: string;
|
|
71
|
+
codeValueKey?: string;
|
|
72
|
+
labelPosition?: string;
|
|
73
|
+
extendState?: (state: FormState) => Record<string, any> | Promise<Record<string, any>>;
|
|
74
|
+
}>();
|
|
75
|
+
|
|
76
|
+
const emit = defineEmits<{
|
|
77
|
+
submit: [values: any, eventData?: ContainerChangeEventData];
|
|
78
|
+
'submit-error': [e: any];
|
|
79
|
+
'form-error': [e: any];
|
|
80
|
+
mounted: [internalInstance: any];
|
|
81
|
+
}>();
|
|
82
|
+
|
|
83
|
+
const services = inject<Services>('services');
|
|
84
|
+
const codeOptions = inject('codeOptions', {});
|
|
85
|
+
|
|
86
|
+
const showSrc = ref(false);
|
|
87
|
+
const propsPanelSize = computed(() => services?.uiService.get('propsPanelSize') || 'small');
|
|
88
|
+
const { height: editorContentHeight } = useEditorContentHeight();
|
|
89
|
+
const stage = computed(() => services?.editorService.get('stage'));
|
|
90
|
+
|
|
91
|
+
const configForm = useTemplateRef<InstanceType<typeof MForm>>('configForm');
|
|
92
|
+
|
|
93
|
+
watchEffect(() => {
|
|
94
|
+
if (configForm.value) {
|
|
95
|
+
configForm.value.formState.stage = stage.value;
|
|
96
|
+
configForm.value.formState.services = services;
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const internalInstance = getCurrentInstance();
|
|
101
|
+
onMounted(() => {
|
|
102
|
+
emit('mounted', internalInstance);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const submit = async (v: FormValue, eventData: ContainerChangeEventData) => {
|
|
106
|
+
try {
|
|
107
|
+
const values = await configForm.value?.submitForm();
|
|
108
|
+
emit('submit', values, eventData);
|
|
109
|
+
} catch (e: any) {
|
|
110
|
+
emit('submit-error', e);
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const errorHandler = (e: any) => {
|
|
115
|
+
emit('form-error', e);
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const saveCode = (values: any) => {
|
|
119
|
+
emit('submit', props.codeValueKey ? { [props.codeValueKey]: values } : values);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
defineExpose({ configForm, submit });
|
|
123
|
+
</script>
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="m-editor-props-panel" v-show="nodes.length === 1">
|
|
3
|
+
<slot name="props-panel-header"></slot>
|
|
4
|
+
<FormPanel
|
|
5
|
+
ref="propertyFormPanel"
|
|
6
|
+
class="m-editor-props-property-panel"
|
|
7
|
+
:class="{ 'show-style-panel': showStylePanel }"
|
|
8
|
+
:config="curFormConfig"
|
|
9
|
+
:values="values"
|
|
10
|
+
:disabledShowSrc="disabledShowSrc"
|
|
11
|
+
:extendState="extendState"
|
|
12
|
+
@submit="submit"
|
|
13
|
+
@submit-error="errorHandler"
|
|
14
|
+
@form-error="errorHandler"
|
|
15
|
+
@mounted="mountedHandler"
|
|
16
|
+
></FormPanel>
|
|
17
|
+
|
|
18
|
+
<FormPanel
|
|
19
|
+
v-if="showStylePanel"
|
|
20
|
+
class="m-editor-props-style-panel"
|
|
21
|
+
label-position="top"
|
|
22
|
+
code-value-key="style"
|
|
23
|
+
:config="styleFormConfig"
|
|
24
|
+
:values="values"
|
|
25
|
+
:disabledShowSrc="disabledShowSrc"
|
|
26
|
+
:extendState="extendState"
|
|
27
|
+
@submit="submit"
|
|
28
|
+
@submit-error="errorHandler"
|
|
29
|
+
@form-error="errorHandler"
|
|
30
|
+
>
|
|
31
|
+
<template #props-form-panel-header>
|
|
32
|
+
<div class="m-editor-props-style-panel-title">
|
|
33
|
+
<span>样式</span>
|
|
34
|
+
<div>
|
|
35
|
+
<TMagicButton link size="small" @click="closeStylePanelHandler"><MIcon :icon="Close"></MIcon></TMagicButton>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
</template>
|
|
39
|
+
</FormPanel>
|
|
40
|
+
|
|
41
|
+
<TMagicButton
|
|
42
|
+
v-if="!showStylePanel"
|
|
43
|
+
class="m-editor-props-panel-style-icon"
|
|
44
|
+
circle
|
|
45
|
+
:type="showStylePanel ? 'primary' : ''"
|
|
46
|
+
@click="showStylePanelHandler"
|
|
47
|
+
>
|
|
48
|
+
<MIcon :icon="Sugar"></MIcon>
|
|
49
|
+
</TMagicButton>
|
|
50
|
+
</div>
|
|
51
|
+
</template>
|
|
52
|
+
|
|
53
|
+
<script lang="ts" setup>
|
|
54
|
+
import { computed, inject, onBeforeUnmount, ref, useTemplateRef, watchEffect } from 'vue';
|
|
55
|
+
import { Close, Sugar } from '@element-plus/icons-vue';
|
|
56
|
+
|
|
57
|
+
import type { MNode } from '@tmagic/core';
|
|
58
|
+
import { TMagicButton } from '@tmagic/design';
|
|
59
|
+
import type { ContainerChangeEventData, FormState, FormValue } from '@tmagic/form';
|
|
60
|
+
|
|
61
|
+
import MIcon from '@editor/components/Icon.vue';
|
|
62
|
+
import type { PropsPanelSlots, Services } from '@editor/type';
|
|
63
|
+
import { styleTabConfig } from '@editor/utils';
|
|
64
|
+
|
|
65
|
+
import FormPanel from './FormPanel.vue';
|
|
66
|
+
import { useStylePanel } from './use-style-panel';
|
|
67
|
+
|
|
68
|
+
defineSlots<PropsPanelSlots>();
|
|
69
|
+
|
|
70
|
+
defineOptions({
|
|
71
|
+
name: 'MEditorPropsPanel',
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
defineProps<{
|
|
75
|
+
disabledShowSrc?: boolean;
|
|
76
|
+
extendState?: (state: FormState) => Record<string, any> | Promise<Record<string, any>>;
|
|
77
|
+
}>();
|
|
78
|
+
|
|
79
|
+
const emit = defineEmits<{
|
|
80
|
+
'submit-error': [e: any];
|
|
81
|
+
'form-error': [e: any];
|
|
82
|
+
mounted: [internalInstance: InstanceType<typeof FormPanel>];
|
|
83
|
+
}>();
|
|
84
|
+
|
|
85
|
+
const services = inject<Services>('services');
|
|
86
|
+
|
|
87
|
+
const values = ref<FormValue>({});
|
|
88
|
+
// ts类型应该是FormConfig, 但是打包时会出错,所以暂时用any
|
|
89
|
+
const curFormConfig = ref<any>([]);
|
|
90
|
+
const node = computed(() => services?.editorService.get('node'));
|
|
91
|
+
const nodes = computed(() => services?.editorService.get('nodes') || []);
|
|
92
|
+
|
|
93
|
+
const styleFormConfig = [
|
|
94
|
+
{
|
|
95
|
+
tabPosition: 'right',
|
|
96
|
+
items: styleTabConfig.items,
|
|
97
|
+
},
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
const init = async () => {
|
|
101
|
+
if (!node.value) {
|
|
102
|
+
curFormConfig.value = [];
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const type = node.value.type || (node.value.items ? 'container' : 'text');
|
|
107
|
+
curFormConfig.value = (await services?.propsService.getPropsConfig(type)) || [];
|
|
108
|
+
values.value = node.value;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
watchEffect(init);
|
|
112
|
+
services?.propsService.on('props-configs-change', init);
|
|
113
|
+
|
|
114
|
+
onBeforeUnmount(() => {
|
|
115
|
+
services?.propsService.off('props-configs-change', init);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const submit = async (v: MNode, eventData?: ContainerChangeEventData) => {
|
|
119
|
+
try {
|
|
120
|
+
if (!v.id) {
|
|
121
|
+
v.id = values.value.id;
|
|
122
|
+
}
|
|
123
|
+
services?.editorService.update(v, { changeRecords: eventData?.changeRecords });
|
|
124
|
+
} catch (e: any) {
|
|
125
|
+
emit('submit-error', e);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const errorHandler = (e: any) => {
|
|
130
|
+
emit('form-error', e);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const mountedHandler = (e: InstanceType<typeof FormPanel>) => {
|
|
134
|
+
emit('mounted', e);
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const { showStylePanel, showStylePanelHandler, closeStylePanelHandler } = useStylePanel(services);
|
|
138
|
+
|
|
139
|
+
const propertyFormPanelRef = useTemplateRef<InstanceType<typeof FormPanel>>('propertyFormPanel');
|
|
140
|
+
defineExpose({
|
|
141
|
+
getFormState() {
|
|
142
|
+
return propertyFormPanelRef.value?.configForm?.formState;
|
|
143
|
+
},
|
|
144
|
+
submit,
|
|
145
|
+
});
|
|
146
|
+
</script>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { computed } from 'vue';
|
|
2
|
+
|
|
3
|
+
import { Protocol } from '@editor/services/storage';
|
|
4
|
+
import { Services } from '@editor/type';
|
|
5
|
+
|
|
6
|
+
export const useStylePanel = (services?: Services) => {
|
|
7
|
+
const showStylePanelStorageKey = 'props-panel-show-style-panel';
|
|
8
|
+
const showStylePanelStorageValue = services?.storageService.getItem(showStylePanelStorageKey, {
|
|
9
|
+
protocol: Protocol.BOOLEAN,
|
|
10
|
+
});
|
|
11
|
+
if (typeof showStylePanelStorageValue === 'boolean') {
|
|
12
|
+
services?.uiService.set('showStylePanel', showStylePanelStorageValue);
|
|
13
|
+
}
|
|
14
|
+
const showStylePanel = computed(() => services?.uiService.get('showStylePanel') ?? true);
|
|
15
|
+
const showStylePanelHandler = () => {
|
|
16
|
+
services?.uiService.set('showStylePanel', true);
|
|
17
|
+
services?.storageService.setItem(showStylePanelStorageKey, true, { protocol: Protocol.BOOLEAN });
|
|
18
|
+
};
|
|
19
|
+
const closeStylePanelHandler = () => {
|
|
20
|
+
services?.uiService.set('showStylePanel', false);
|
|
21
|
+
services?.storageService.setItem(showStylePanelStorageKey, false, { protocol: Protocol.BOOLEAN });
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
showStylePanel,
|
|
26
|
+
showStylePanelHandler,
|
|
27
|
+
closeStylePanelHandler,
|
|
28
|
+
};
|
|
29
|
+
};
|
|
@@ -158,6 +158,7 @@ import { useEditorContentHeight } from '@editor/hooks/use-editor-content-height'
|
|
|
158
158
|
import { useFloatBox } from '@editor/hooks/use-float-box';
|
|
159
159
|
import {
|
|
160
160
|
ColumnLayout,
|
|
161
|
+
CustomContentMenuFunction,
|
|
161
162
|
type MenuButton,
|
|
162
163
|
type MenuComponent,
|
|
163
164
|
type Services,
|
|
@@ -185,7 +186,7 @@ const props = withDefaults(
|
|
|
185
186
|
layerContentMenu: (MenuButton | MenuComponent)[];
|
|
186
187
|
indent?: number;
|
|
187
188
|
nextLevelIndentIncrement?: number;
|
|
188
|
-
customContentMenu
|
|
189
|
+
customContentMenu: CustomContentMenuFunction;
|
|
189
190
|
}>(),
|
|
190
191
|
{
|
|
191
192
|
data: () => ({
|
|
@@ -254,6 +255,7 @@ const getItemConfig = (data: SideItem): SideComponent => {
|
|
|
254
255
|
props: {
|
|
255
256
|
indent: props.indent,
|
|
256
257
|
nextLevelIndentIncrement: props.nextLevelIndentIncrement,
|
|
258
|
+
customContentMenu: props.customContentMenu,
|
|
257
259
|
},
|
|
258
260
|
slots: {},
|
|
259
261
|
},
|
|
@@ -266,6 +268,7 @@ const getItemConfig = (data: SideItem): SideComponent => {
|
|
|
266
268
|
props: {
|
|
267
269
|
indent: props.indent,
|
|
268
270
|
nextLevelIndentIncrement: props.nextLevelIndentIncrement,
|
|
271
|
+
customContentMenu: props.customContentMenu,
|
|
269
272
|
},
|
|
270
273
|
slots: {},
|
|
271
274
|
},
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
:indent="indent"
|
|
6
6
|
:next-level-indent-increment="nextLevelIndentIncrement"
|
|
7
7
|
@node-click="clickHandler"
|
|
8
|
+
@node-contextmenu="nodeContentMenuHandler"
|
|
8
9
|
>
|
|
9
10
|
<template #tree-node-label="{ data }">
|
|
10
11
|
<div
|
|
@@ -62,6 +63,7 @@ const props = defineProps<{
|
|
|
62
63
|
const emit = defineEmits<{
|
|
63
64
|
edit: [id: string];
|
|
64
65
|
remove: [id: string];
|
|
66
|
+
'node-contextmenu': [event: MouseEvent, data: TreeNodeData];
|
|
65
67
|
}>();
|
|
66
68
|
|
|
67
69
|
const services = inject<Services>('services');
|
|
@@ -162,12 +164,21 @@ const deleteCode = async (id: string) => {
|
|
|
162
164
|
if (typeof props.customError === 'function') {
|
|
163
165
|
props.customError(id, existBinds ? CodeDeleteErrorType.BIND : CodeDeleteErrorType.UNDELETEABLE);
|
|
164
166
|
} else {
|
|
165
|
-
|
|
167
|
+
if (existBinds) {
|
|
168
|
+
tMagicMessage.error('代码块存在绑定关系,不可删除');
|
|
169
|
+
} else {
|
|
170
|
+
tMagicMessage.error('代码块不可删除');
|
|
171
|
+
}
|
|
166
172
|
}
|
|
167
173
|
}
|
|
168
174
|
};
|
|
169
175
|
|
|
176
|
+
const nodeContentMenuHandler = (event: MouseEvent, data: TreeNodeData) => {
|
|
177
|
+
emit('node-contextmenu', event, data);
|
|
178
|
+
};
|
|
179
|
+
|
|
170
180
|
defineExpose({
|
|
171
181
|
filter: filterTextChangeHandler,
|
|
182
|
+
deleteCode,
|
|
172
183
|
});
|
|
173
184
|
</script>
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
:next-level-indent-increment="nextLevelIndentIncrement"
|
|
19
19
|
@edit="editCode"
|
|
20
20
|
@remove="deleteCode"
|
|
21
|
+
@node-contextmenu="nodeContentMenuHandler"
|
|
21
22
|
>
|
|
22
23
|
<template #code-block-panel-tool="{ id, data }">
|
|
23
24
|
<slot name="code-block-panel-tool" :id="id" :data="data"></slot>
|
|
@@ -32,20 +33,40 @@
|
|
|
32
33
|
:content="codeConfig"
|
|
33
34
|
@submit="submitCodeBlockHandler"
|
|
34
35
|
></CodeBlockEditor>
|
|
36
|
+
|
|
37
|
+
<Teleport to="body">
|
|
38
|
+
<ContentMenu
|
|
39
|
+
v-if="menuData.length"
|
|
40
|
+
:menu-data="menuData"
|
|
41
|
+
ref="menu"
|
|
42
|
+
style="overflow: initial"
|
|
43
|
+
@hide="contentMenuHideHandler"
|
|
44
|
+
></ContentMenu>
|
|
45
|
+
</Teleport>
|
|
35
46
|
</template>
|
|
36
47
|
|
|
37
48
|
<script setup lang="ts">
|
|
38
|
-
import { computed, inject,
|
|
49
|
+
import { computed, inject, useTemplateRef } from 'vue';
|
|
39
50
|
|
|
40
51
|
import type { Id } from '@tmagic/core';
|
|
41
52
|
import { TMagicButton, TMagicScrollbar } from '@tmagic/design';
|
|
42
53
|
|
|
43
54
|
import CodeBlockEditor from '@editor/components/CodeBlockEditor.vue';
|
|
55
|
+
import ContentMenu from '@editor/components/ContentMenu.vue';
|
|
44
56
|
import SearchInput from '@editor/components/SearchInput.vue';
|
|
45
57
|
import { useCodeBlockEdit } from '@editor/hooks/use-code-block-edit';
|
|
46
|
-
import type {
|
|
58
|
+
import type {
|
|
59
|
+
CodeBlockListPanelSlots,
|
|
60
|
+
CodeDeleteErrorType,
|
|
61
|
+
CustomContentMenuFunction,
|
|
62
|
+
EventBus,
|
|
63
|
+
MenuButton,
|
|
64
|
+
MenuComponent,
|
|
65
|
+
Services,
|
|
66
|
+
} from '@editor/type';
|
|
47
67
|
|
|
48
68
|
import CodeBlockList from './CodeBlockList.vue';
|
|
69
|
+
import { useContentMenu } from './useContentMenu';
|
|
49
70
|
|
|
50
71
|
defineSlots<CodeBlockListPanelSlots>();
|
|
51
72
|
|
|
@@ -53,10 +74,11 @@ defineOptions({
|
|
|
53
74
|
name: 'MEditorCodeBlockListPanel',
|
|
54
75
|
});
|
|
55
76
|
|
|
56
|
-
defineProps<{
|
|
77
|
+
const props = defineProps<{
|
|
57
78
|
indent?: number;
|
|
58
79
|
nextLevelIndentIncrement?: number;
|
|
59
80
|
customError?: (id: Id, errorType: CodeDeleteErrorType) => any;
|
|
81
|
+
customContentMenu: CustomContentMenuFunction;
|
|
60
82
|
}>();
|
|
61
83
|
|
|
62
84
|
const eventBus = inject<EventBus>('eventBus');
|
|
@@ -67,7 +89,7 @@ const editable = computed(() => codeBlockService?.getEditStatus());
|
|
|
67
89
|
const { codeBlockEditor, codeConfig, editCode, deleteCode, createCodeBlock, submitCodeBlockHandler } =
|
|
68
90
|
useCodeBlockEdit(codeBlockService);
|
|
69
91
|
|
|
70
|
-
const codeBlockList =
|
|
92
|
+
const codeBlockList = useTemplateRef<InstanceType<typeof CodeBlockList>>('codeBlockList');
|
|
71
93
|
|
|
72
94
|
const filterTextChangeHandler = (val: string) => {
|
|
73
95
|
codeBlockList.value?.filter(val);
|
|
@@ -76,4 +98,13 @@ const filterTextChangeHandler = (val: string) => {
|
|
|
76
98
|
eventBus?.on('edit-code', (id: string) => {
|
|
77
99
|
editCode(id);
|
|
78
100
|
});
|
|
101
|
+
|
|
102
|
+
const {
|
|
103
|
+
nodeContentMenuHandler,
|
|
104
|
+
menuData: contentMenuData,
|
|
105
|
+
contentMenuHideHandler,
|
|
106
|
+
} = useContentMenu((id: string) => {
|
|
107
|
+
codeBlockList.value?.deleteCode(id);
|
|
108
|
+
});
|
|
109
|
+
const menuData = computed<(MenuButton | MenuComponent)[]>(() => props.customContentMenu(contentMenuData, 'code-block'));
|
|
79
110
|
</script>
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { inject, markRaw, useTemplateRef } from 'vue';
|
|
2
|
+
import { CopyDocument, Delete, Edit } from '@element-plus/icons-vue';
|
|
3
|
+
import { cloneDeep } from 'lodash-es';
|
|
4
|
+
|
|
5
|
+
import ContentMenu from '@editor/components/ContentMenu.vue';
|
|
6
|
+
import type { EventBus, MenuButton, MenuComponent, Services, TreeNodeData } from '@editor/type';
|
|
7
|
+
|
|
8
|
+
export const useContentMenu = (deleteCode: (id: string) => void) => {
|
|
9
|
+
const eventBus = inject<EventBus>('eventBus');
|
|
10
|
+
const menuRef = useTemplateRef<InstanceType<typeof ContentMenu>>('menu');
|
|
11
|
+
|
|
12
|
+
let selectId = '';
|
|
13
|
+
|
|
14
|
+
const menuData: (MenuButton | MenuComponent)[] = [
|
|
15
|
+
{
|
|
16
|
+
type: 'button',
|
|
17
|
+
text: '编辑',
|
|
18
|
+
icon: Edit,
|
|
19
|
+
display: (services) => services?.codeBlockService?.getEditStatus() ?? true,
|
|
20
|
+
handler: () => {
|
|
21
|
+
if (!selectId) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
eventBus?.emit('edit-code', selectId);
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
type: 'button',
|
|
30
|
+
text: '复制并粘贴至当前',
|
|
31
|
+
icon: markRaw(CopyDocument),
|
|
32
|
+
handler: async ({ codeBlockService }: Services) => {
|
|
33
|
+
if (!selectId) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const codeBlock = codeBlockService.getCodeContentById(selectId);
|
|
38
|
+
if (!codeBlock) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const newCodeId = await codeBlockService.getUniqueId();
|
|
43
|
+
|
|
44
|
+
codeBlockService.setCodeDslById(newCodeId, cloneDeep(codeBlock));
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
type: 'button',
|
|
49
|
+
text: '删除',
|
|
50
|
+
icon: Delete,
|
|
51
|
+
handler: () => {
|
|
52
|
+
if (!selectId) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
deleteCode(selectId);
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
const nodeContentMenuHandler = (event: MouseEvent, data: TreeNodeData) => {
|
|
62
|
+
event.preventDefault();
|
|
63
|
+
|
|
64
|
+
if (data.type === 'code') {
|
|
65
|
+
menuRef.value?.show(event);
|
|
66
|
+
if (data.id) {
|
|
67
|
+
selectId = `${data.id}`;
|
|
68
|
+
} else {
|
|
69
|
+
selectId = '';
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const contentMenuHideHandler = () => {
|
|
75
|
+
selectId = '';
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
menuData,
|
|
80
|
+
nodeContentMenuHandler,
|
|
81
|
+
contentMenuHideHandler,
|
|
82
|
+
};
|
|
83
|
+
};
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
:indent="indent"
|
|
6
6
|
:next-level-indent-increment="nextLevelIndentIncrement"
|
|
7
7
|
@node-click="clickHandler"
|
|
8
|
+
@node-contextmenu="nodeContentMenuHandler"
|
|
8
9
|
>
|
|
9
10
|
<template #tree-node-label="{ data }">
|
|
10
11
|
<div
|
|
@@ -37,13 +38,13 @@ import { computed, inject } from 'vue';
|
|
|
37
38
|
import { Close, Edit, View } from '@element-plus/icons-vue';
|
|
38
39
|
|
|
39
40
|
import { DepData, DepTargetType, Id, MNode } from '@tmagic/core';
|
|
40
|
-
import {
|
|
41
|
+
import { TMagicTag, TMagicTooltip } from '@tmagic/design';
|
|
41
42
|
|
|
42
43
|
import Icon from '@editor/components/Icon.vue';
|
|
43
44
|
import Tree from '@editor/components/Tree.vue';
|
|
44
45
|
import { useFilter } from '@editor/hooks/use-filter';
|
|
45
46
|
import { useNodeStatus } from '@editor/hooks/use-node-status';
|
|
46
|
-
import type { DataSourceListSlots, Services } from '@editor/type';
|
|
47
|
+
import type { DataSourceListSlots, Services, TreeNodeData } from '@editor/type';
|
|
47
48
|
|
|
48
49
|
defineSlots<DataSourceListSlots>();
|
|
49
50
|
|
|
@@ -59,6 +60,7 @@ defineProps<{
|
|
|
59
60
|
const emit = defineEmits<{
|
|
60
61
|
edit: [id: string];
|
|
61
62
|
remove: [id: string];
|
|
63
|
+
'node-contextmenu': [event: MouseEvent, data: TreeNodeData];
|
|
62
64
|
}>();
|
|
63
65
|
|
|
64
66
|
const { depService, editorService, dataSourceService } = inject<Services>('services') || {};
|
|
@@ -159,12 +161,6 @@ const editHandler = (id: string) => {
|
|
|
159
161
|
};
|
|
160
162
|
|
|
161
163
|
const removeHandler = async (id: string) => {
|
|
162
|
-
await tMagicMessageBox.confirm('确定删除?', '提示', {
|
|
163
|
-
confirmButtonText: '确定',
|
|
164
|
-
cancelButtonText: '取消',
|
|
165
|
-
type: 'warning',
|
|
166
|
-
});
|
|
167
|
-
|
|
168
164
|
emit('remove', id);
|
|
169
165
|
};
|
|
170
166
|
|
|
@@ -181,6 +177,10 @@ const clickHandler = (event: MouseEvent, data: any) => {
|
|
|
181
177
|
}
|
|
182
178
|
};
|
|
183
179
|
|
|
180
|
+
const nodeContentMenuHandler = (event: MouseEvent, data: TreeNodeData) => {
|
|
181
|
+
emit('node-contextmenu', event, data);
|
|
182
|
+
};
|
|
183
|
+
|
|
184
184
|
defineExpose({
|
|
185
185
|
filter: filterTextChangeHandler,
|
|
186
186
|
});
|