mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2025-06-20 23:44:23 +08:00
parent
dd3075be56
commit
de8ef8dc58
147
packages/editor/src/components/ScrollBar.vue
Normal file
147
packages/editor/src/components/ScrollBar.vue
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="bar" class="m-editor-scroll-bar" :class="isHorizontal ? 'horizontal' : 'vertical'">
|
||||||
|
<div ref="thumb" class="m-editor-scroll-bar-thumb" :style="thumbStyle"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, onMounted, onUnmounted, ref } from 'vue';
|
||||||
|
import Gesto from 'gesto';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
size: number;
|
||||||
|
scrollSize: number;
|
||||||
|
isHorizontal?: boolean;
|
||||||
|
pos: number;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits(['scroll']);
|
||||||
|
|
||||||
|
const bar = ref<HTMLDivElement>();
|
||||||
|
const thumb = ref<HTMLDivElement>();
|
||||||
|
|
||||||
|
const thumbSize = computed(() => props.size * (props.size / props.scrollSize));
|
||||||
|
const thumbPos = computed(() => (props.pos / props.scrollSize) * props.size);
|
||||||
|
|
||||||
|
const thumbStyle = computed(() => ({
|
||||||
|
[props.isHorizontal ? 'width' : 'height']: `${thumbSize.value}px`,
|
||||||
|
transform: `translate${props.isHorizontal ? 'X' : 'Y'}(${thumbPos.value}px)`,
|
||||||
|
}));
|
||||||
|
|
||||||
|
let gesto: Gesto;
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (!thumb.value) return;
|
||||||
|
const thumbEl = thumb.value;
|
||||||
|
gesto = new Gesto(thumbEl, {
|
||||||
|
container: window,
|
||||||
|
});
|
||||||
|
|
||||||
|
gesto
|
||||||
|
.on('dragStart', (e) => {
|
||||||
|
e.inputEvent.stopPropagation();
|
||||||
|
e.inputEvent.preventDefault();
|
||||||
|
})
|
||||||
|
.on('drag', (e) => {
|
||||||
|
scrollBy(getDelta(e));
|
||||||
|
});
|
||||||
|
|
||||||
|
bar.value?.addEventListener('wheel', wheelHandler, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
if (gesto) gesto.off();
|
||||||
|
bar.value?.removeEventListener('wheel', wheelHandler, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
const wheelHandler = (e: WheelEvent) => {
|
||||||
|
const delta = props.isHorizontal ? e.deltaX : e.deltaY;
|
||||||
|
if (delta) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
scrollBy(delta);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getDelta = (e: any) => {
|
||||||
|
const ratio = (props.isHorizontal ? e.deltaX : e.deltaY) / props.size;
|
||||||
|
return props.scrollSize * ratio;
|
||||||
|
};
|
||||||
|
|
||||||
|
const scrollBy = (delta: number) => {
|
||||||
|
if (delta < 0) {
|
||||||
|
if (props.pos <= 0) {
|
||||||
|
emit('scroll', 0);
|
||||||
|
} else {
|
||||||
|
emit('scroll', -Math.min(-delta, props.pos));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const leftPos = props.size - (thumbSize.value + thumbPos.value);
|
||||||
|
if (leftPos <= 0) {
|
||||||
|
emit('scroll', 0);
|
||||||
|
} else {
|
||||||
|
emit('scroll', Math.min(delta, leftPos));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.m-editor-scroll-bar {
|
||||||
|
position: absolute;
|
||||||
|
background-color: transparent;
|
||||||
|
opacity: 0.3;
|
||||||
|
transition: background-color 0.2s linear, opacity 0.2s linear;
|
||||||
|
|
||||||
|
.m-editor-scroll-bar-thumb {
|
||||||
|
background-color: #aaa;
|
||||||
|
border-radius: 6px;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.horizontal {
|
||||||
|
width: 100%;
|
||||||
|
height: 15px;
|
||||||
|
bottom: 0;
|
||||||
|
|
||||||
|
.m-editor-scroll-bar-thumb {
|
||||||
|
height: 6px;
|
||||||
|
transition: background-color 0.2s linear, height 0.2s ease-in-out;
|
||||||
|
bottom: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.vertical {
|
||||||
|
height: 100%;
|
||||||
|
width: 15px;
|
||||||
|
right: 5px;
|
||||||
|
|
||||||
|
.m-editor-scroll-bar-thumb {
|
||||||
|
width: 6px;
|
||||||
|
transition: background-color 0.2s linear, width 0.2s ease-in-out;
|
||||||
|
right: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
background-color: #eee;
|
||||||
|
opacity: 0.9;
|
||||||
|
|
||||||
|
.m-editor-scroll-bar-thumb {
|
||||||
|
background-color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.horizontal {
|
||||||
|
.m-editor-scroll-bar-thumb {
|
||||||
|
height: 11px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.vertical {
|
||||||
|
.m-editor-scroll-bar-thumb {
|
||||||
|
width: 11px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -3,64 +3,109 @@
|
|||||||
<div ref="el" :style="style">
|
<div ref="el" :style="style">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<ScrollBar
|
||||||
|
v-if="scrollHeight > wrapHeight"
|
||||||
|
:scroll-size="scrollHeight"
|
||||||
|
:size="wrapHeight"
|
||||||
|
:pos="vOffset"
|
||||||
|
@scroll="vScrollHandler"
|
||||||
|
></ScrollBar>
|
||||||
|
<ScrollBar
|
||||||
|
v-if="scrollWidth > wrapWidth"
|
||||||
|
:is-horizontal="true"
|
||||||
|
:scroll-size="scrollWidth"
|
||||||
|
:pos="hOffset"
|
||||||
|
:size="wrapWidth"
|
||||||
|
@scroll="hScrollHandler"
|
||||||
|
></ScrollBar>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { computed, defineComponent, onMounted, onUnmounted, ref, watch } from 'vue';
|
import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
|
||||||
|
|
||||||
|
import { ScrollViewerEvent } from '../type';
|
||||||
import { ScrollViewer } from '../utils/scroll-viewer';
|
import { ScrollViewer } from '../utils/scroll-viewer';
|
||||||
|
|
||||||
export default defineComponent({
|
import ScrollBar from './ScrollBar.vue';
|
||||||
name: 'm-editor-scroll-viewer',
|
|
||||||
|
|
||||||
props: {
|
const props = withDefaults(
|
||||||
width: Number,
|
defineProps<{
|
||||||
height: Number,
|
width?: number;
|
||||||
zoom: {
|
height?: number;
|
||||||
type: Number,
|
wrapWidth?: number;
|
||||||
default: 1,
|
wrapHeight?: number;
|
||||||
},
|
zoom?: number;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
wrapWidth: 0,
|
||||||
|
wrapHeight: 0,
|
||||||
|
zoom: 1,
|
||||||
},
|
},
|
||||||
|
);
|
||||||
|
|
||||||
setup(props) {
|
const container = ref<HTMLDivElement>();
|
||||||
const container = ref<HTMLDivElement>();
|
const el = ref<HTMLDivElement>();
|
||||||
const el = ref<HTMLDivElement>();
|
const style = computed(
|
||||||
let scrollViewer: ScrollViewer;
|
() => `
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
if (!container.value || !el.value) return;
|
|
||||||
scrollViewer = new ScrollViewer({
|
|
||||||
container: container.value,
|
|
||||||
target: el.value,
|
|
||||||
zoom: props.zoom,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
scrollViewer.destroy();
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.zoom,
|
|
||||||
() => {
|
|
||||||
scrollViewer.setZoom(props.zoom);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
container,
|
|
||||||
el,
|
|
||||||
|
|
||||||
style: computed(
|
|
||||||
() => `
|
|
||||||
width: ${props.width}px;
|
width: ${props.width}px;
|
||||||
height: ${props.height}px;
|
height: ${props.height}px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
margin-top: 30px;
|
margin-top: 30px;
|
||||||
`,
|
`,
|
||||||
),
|
);
|
||||||
};
|
|
||||||
|
const scrollWidth = ref(0);
|
||||||
|
const scrollHeight = ref(0);
|
||||||
|
|
||||||
|
let scrollViewer: ScrollViewer;
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (!container.value || !el.value) return;
|
||||||
|
scrollViewer = new ScrollViewer({
|
||||||
|
container: container.value,
|
||||||
|
target: el.value,
|
||||||
|
zoom: props.zoom,
|
||||||
|
});
|
||||||
|
|
||||||
|
scrollViewer.on('scroll', (data: ScrollViewerEvent) => {
|
||||||
|
hOffset.value = data.scrollLeft;
|
||||||
|
vOffset.value = data.scrollTop;
|
||||||
|
scrollWidth.value = data.scrollWidth;
|
||||||
|
scrollHeight.value = data.scrollHeight;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
scrollViewer.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.zoom,
|
||||||
|
() => {
|
||||||
|
scrollViewer.setZoom(props.zoom);
|
||||||
},
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const vOffset = ref(0);
|
||||||
|
const vScrollHandler = (delta: number) => {
|
||||||
|
vOffset.value += delta;
|
||||||
|
scrollViewer.scrollTo({
|
||||||
|
top: vOffset.value,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const hOffset = ref(0);
|
||||||
|
const hScrollHandler = (delta: number) => {
|
||||||
|
hOffset.value += delta;
|
||||||
|
scrollViewer.scrollTo({
|
||||||
|
left: hOffset.value,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
container,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -4,12 +4,14 @@
|
|||||||
ref="stageWrap"
|
ref="stageWrap"
|
||||||
:width="stageRect?.width"
|
:width="stageRect?.width"
|
||||||
:height="stageRect?.height"
|
:height="stageRect?.height"
|
||||||
|
:wrap-width="stageContainerRect?.width"
|
||||||
|
:wrap-height="stageContainerRect?.height"
|
||||||
:zoom="zoom"
|
:zoom="zoom"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="m-editor-stage-container"
|
class="m-editor-stage-container"
|
||||||
ref="stageContainer"
|
ref="stageContainer"
|
||||||
:style="`transform: scale(${zoom})`"
|
:style="`transform: scale(${zoom});`"
|
||||||
@contextmenu="contextmenuHandler"
|
@contextmenu="contextmenuHandler"
|
||||||
@drop="dropHandler"
|
@drop="dropHandler"
|
||||||
@dragover="dragoverHandler"
|
@dragover="dragoverHandler"
|
||||||
@ -25,25 +27,11 @@ import { computed, inject, markRaw, onMounted, onUnmounted, ref, toRaw, watch, w
|
|||||||
import { cloneDeep } from 'lodash-es';
|
import { cloneDeep } from 'lodash-es';
|
||||||
|
|
||||||
import type { MApp, MContainer, MNode, MPage } from '@tmagic/schema';
|
import type { MApp, MContainer, MNode, MPage } from '@tmagic/schema';
|
||||||
import StageCore, {
|
import StageCore, { calcValueByFontsize, getOffset, Runtime } from '@tmagic/stage';
|
||||||
calcValueByFontsize,
|
|
||||||
getOffset,
|
|
||||||
GuidesType,
|
|
||||||
Runtime,
|
|
||||||
SortEventData,
|
|
||||||
UpdateEventData,
|
|
||||||
} from '@tmagic/stage';
|
|
||||||
|
|
||||||
import ScrollViewer from '../../components/ScrollViewer.vue';
|
import ScrollViewer from '../../components/ScrollViewer.vue';
|
||||||
import {
|
import { Layout, Services, StageOptions, StageRect } from '../../type';
|
||||||
H_GUIDE_LINE_STORAGE_KEY,
|
import { useStage } from '../../utils/stage';
|
||||||
Layout,
|
|
||||||
Services,
|
|
||||||
StageOptions,
|
|
||||||
StageRect,
|
|
||||||
V_GUIDE_LINE_STORAGE_KEY,
|
|
||||||
} from '../../type';
|
|
||||||
import { getGuideLineFromCache } from '../../utils';
|
|
||||||
|
|
||||||
import ViewerMenu from './ViewerMenu.vue';
|
import ViewerMenu from './ViewerMenu.vue';
|
||||||
|
|
||||||
@ -59,94 +47,24 @@ const menu = ref<InstanceType<typeof ViewerMenu>>();
|
|||||||
|
|
||||||
const isMultiSelect = computed(() => services?.editorService.get('nodes')?.length > 1);
|
const isMultiSelect = computed(() => services?.editorService.get('nodes')?.length > 1);
|
||||||
const stageRect = computed(() => services?.uiService.get<StageRect>('stageRect'));
|
const stageRect = computed(() => services?.uiService.get<StageRect>('stageRect'));
|
||||||
const uiSelectMode = computed(() => services?.uiService.get<boolean>('uiSelectMode'));
|
const stageContainerRect = computed(() => services?.uiService.get<StageRect>('stageContainerRect'));
|
||||||
const root = computed(() => services?.editorService.get<MApp>('root'));
|
const root = computed(() => services?.editorService.get<MApp>('root'));
|
||||||
const page = computed(() => services?.editorService.get<MPage>('page'));
|
const page = computed(() => services?.editorService.get<MPage>('page'));
|
||||||
const zoom = computed(() => services?.uiService.get<number>('zoom') || 1);
|
const zoom = computed(() => services?.uiService.get<number>('zoom') || 1);
|
||||||
const node = computed(() => services?.editorService.get<MNode>('node'));
|
const node = computed(() => services?.editorService.get<MNode>('node'));
|
||||||
|
|
||||||
const getGuideLineKey = (key: string) => `${key}_${root.value?.id}_${page.value?.id}`;
|
|
||||||
|
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
if (stage) return;
|
if (stage) return;
|
||||||
|
|
||||||
if (!stageContainer.value) return;
|
if (!stageContainer.value) return;
|
||||||
if (!(stageOptions?.runtimeUrl || stageOptions?.render) || !root.value) return;
|
if (!(stageOptions?.runtimeUrl || stageOptions?.render) || !root.value) return;
|
||||||
|
|
||||||
stage = new StageCore({
|
stage = useStage(stageOptions);
|
||||||
render: stageOptions.render,
|
|
||||||
runtimeUrl: stageOptions.runtimeUrl,
|
|
||||||
zoom: zoom.value,
|
|
||||||
autoScrollIntoView: stageOptions.autoScrollIntoView,
|
|
||||||
isContainer: stageOptions.isContainer,
|
|
||||||
containerHighlightClassName: stageOptions.containerHighlightClassName,
|
|
||||||
containerHighlightDuration: stageOptions.containerHighlightDuration,
|
|
||||||
containerHighlightType: stageOptions.containerHighlightType,
|
|
||||||
canSelect: (el, event, stop) => {
|
|
||||||
const elCanSelect = stageOptions.canSelect(el);
|
|
||||||
// 在组件联动过程中不能再往下选择,返回并触发 ui-select
|
|
||||||
if (uiSelectMode.value && elCanSelect && event.type === 'mousedown') {
|
|
||||||
document.dispatchEvent(new CustomEvent('ui-select', { detail: el }));
|
|
||||||
return stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
return elCanSelect;
|
|
||||||
},
|
|
||||||
moveableOptions: stageOptions.moveableOptions,
|
|
||||||
updateDragEl: stageOptions.updateDragEl,
|
|
||||||
});
|
|
||||||
|
|
||||||
services?.editorService.set('stage', markRaw(stage));
|
services?.editorService.set('stage', markRaw(stage));
|
||||||
|
|
||||||
stage?.mount(stageContainer.value);
|
stage?.mount(stageContainer.value);
|
||||||
|
|
||||||
stage.mask.setGuides([
|
|
||||||
getGuideLineFromCache(getGuideLineKey(H_GUIDE_LINE_STORAGE_KEY)),
|
|
||||||
getGuideLineFromCache(getGuideLineKey(V_GUIDE_LINE_STORAGE_KEY)),
|
|
||||||
]);
|
|
||||||
|
|
||||||
stage?.on('select', (el: HTMLElement) => {
|
|
||||||
services?.editorService.select(el.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
stage?.on('highlight', (el: HTMLElement) => {
|
|
||||||
services?.editorService.highlight(el.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
stage?.on('multiSelect', (els: HTMLElement[]) => {
|
|
||||||
services?.editorService.multiSelect(els.map((el) => el.id));
|
|
||||||
});
|
|
||||||
|
|
||||||
stage?.on('update', (ev: UpdateEventData) => {
|
|
||||||
if (ev.parentEl) {
|
|
||||||
for (const data of ev.data) {
|
|
||||||
services?.editorService.moveToContainer({ id: data.el.id, style: data.style }, ev.parentEl.id);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
services?.editorService.update(ev.data.map((data) => ({ id: data.el.id, style: data.style })));
|
|
||||||
});
|
|
||||||
|
|
||||||
stage?.on('sort', (ev: SortEventData) => {
|
|
||||||
services?.editorService.sort(ev.src, ev.dist);
|
|
||||||
});
|
|
||||||
|
|
||||||
stage?.on('changeGuides', (e) => {
|
|
||||||
services?.uiService.set('showGuides', true);
|
|
||||||
|
|
||||||
if (!root.value || !page.value) return;
|
|
||||||
|
|
||||||
const storageKey = getGuideLineKey(
|
|
||||||
e.type === GuidesType.HORIZONTAL ? H_GUIDE_LINE_STORAGE_KEY : V_GUIDE_LINE_STORAGE_KEY,
|
|
||||||
);
|
|
||||||
if (e.guides.length) {
|
|
||||||
globalThis.localStorage.setItem(storageKey, JSON.stringify(e.guides));
|
|
||||||
} else {
|
|
||||||
globalThis.localStorage.removeItem(storageKey);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!node.value?.id) return;
|
if (!node.value?.id) return;
|
||||||
stage?.on('runtime-ready', (rt) => {
|
stage?.on('runtime-ready', (rt) => {
|
||||||
runtime = rt;
|
runtime = rt;
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="m-editor-workspace" tabindex="1" ref="workspace">
|
<div class="m-editor-workspace" tabindex="1" ref="workspace">
|
||||||
<slot name="stage">
|
<slot name="stage">
|
||||||
<magic-stage :key="page?.id"></magic-stage>
|
<MagicStage :key="page?.id"></MagicStage>
|
||||||
</slot>
|
</slot>
|
||||||
|
|
||||||
<slot name="workspace-content"></slot>
|
<slot name="workspace-content"></slot>
|
||||||
|
|
||||||
<page-bar>
|
<PageBar>
|
||||||
<template #page-bar-title="{ page }"><slot name="page-bar-title" :page="page"></slot></template>
|
<template #page-bar-title="{ page }"><slot name="page-bar-title" :page="page"></slot></template>
|
||||||
<template #page-bar-popover="{ page }"><slot name="page-bar-popover" :page="page"></slot></template>
|
<template #page-bar-popover="{ page }"><slot name="page-bar-popover" :page="page"></slot></template>
|
||||||
</page-bar>
|
</PageBar>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { computed, defineComponent, inject, onMounted, onUnmounted, ref } from 'vue';
|
import { computed, inject, onMounted, onUnmounted, ref } from 'vue';
|
||||||
import KeyController from 'keycon';
|
import KeyController from 'keycon';
|
||||||
|
|
||||||
import type { MNode, MPage } from '@tmagic/schema';
|
import type { MNode, MPage } from '@tmagic/schema';
|
||||||
@ -25,148 +25,133 @@ import type { Services } from '../../type';
|
|||||||
import PageBar from './PageBar.vue';
|
import PageBar from './PageBar.vue';
|
||||||
import MagicStage from './Stage.vue';
|
import MagicStage from './Stage.vue';
|
||||||
|
|
||||||
export default defineComponent({
|
const services = inject<Services>('services');
|
||||||
name: 'm-editor-workspace',
|
const workspace = ref<HTMLDivElement>();
|
||||||
|
const nodes = computed(() => services?.editorService.get<MNode[]>('nodes'));
|
||||||
|
const page = computed(() => services?.editorService.get<MPage>('page'));
|
||||||
|
|
||||||
components: {
|
const mouseenterHandler = () => {
|
||||||
PageBar,
|
workspace.value?.focus();
|
||||||
MagicStage,
|
};
|
||||||
},
|
|
||||||
|
|
||||||
setup() {
|
const mouseleaveHandler = () => {
|
||||||
const services = inject<Services>('services');
|
workspace.value?.blur();
|
||||||
const workspace = ref<HTMLDivElement>();
|
};
|
||||||
const nodes = computed(() => services?.editorService.get<MNode[]>('nodes'));
|
|
||||||
let keycon: KeyController;
|
|
||||||
|
|
||||||
const mouseenterHandler = () => {
|
let keycon: KeyController;
|
||||||
workspace.value?.focus();
|
|
||||||
};
|
|
||||||
|
|
||||||
const mouseleaveHandler = () => {
|
onMounted(() => {
|
||||||
workspace.value?.blur();
|
workspace.value?.addEventListener('mouseenter', mouseenterHandler);
|
||||||
};
|
workspace.value?.addEventListener('mouseleave', mouseleaveHandler);
|
||||||
|
|
||||||
onMounted(() => {
|
keycon = new KeyController(workspace.value);
|
||||||
workspace.value?.addEventListener('mouseenter', mouseenterHandler);
|
|
||||||
workspace.value?.addEventListener('mouseleave', mouseleaveHandler);
|
|
||||||
|
|
||||||
keycon = new KeyController(workspace.value);
|
const isMac = /mac os x/.test(navigator.userAgent.toLowerCase());
|
||||||
|
|
||||||
const isMac = /mac os x/.test(navigator.userAgent.toLowerCase());
|
const ctrl = isMac ? 'meta' : 'ctrl';
|
||||||
|
|
||||||
const ctrl = isMac ? 'meta' : 'ctrl';
|
keycon
|
||||||
|
.keyup('delete', (e) => {
|
||||||
keycon
|
e.inputEvent.preventDefault();
|
||||||
.keyup('delete', (e) => {
|
if (!nodes.value || isPage(nodes.value[0])) return;
|
||||||
e.inputEvent.preventDefault();
|
services?.editorService.remove(nodes.value);
|
||||||
if (!nodes.value || isPage(nodes.value[0])) return;
|
})
|
||||||
services?.editorService.remove(nodes.value);
|
.keyup('backspace', (e) => {
|
||||||
})
|
e.inputEvent.preventDefault();
|
||||||
.keyup('backspace', (e) => {
|
if (!nodes.value || isPage(nodes.value[0])) return;
|
||||||
e.inputEvent.preventDefault();
|
services?.editorService.remove(nodes.value);
|
||||||
if (!nodes.value || isPage(nodes.value[0])) return;
|
})
|
||||||
services?.editorService.remove(nodes.value);
|
.keydown([ctrl, 'c'], (e) => {
|
||||||
})
|
e.inputEvent.preventDefault();
|
||||||
.keydown([ctrl, 'c'], (e) => {
|
nodes.value && services?.editorService.copy(nodes.value);
|
||||||
e.inputEvent.preventDefault();
|
})
|
||||||
nodes.value && services?.editorService.copy(nodes.value);
|
.keydown([ctrl, 'v'], (e) => {
|
||||||
})
|
e.inputEvent.preventDefault();
|
||||||
.keydown([ctrl, 'v'], (e) => {
|
nodes.value && services?.editorService.paste({ offsetX: 10, offsetY: 10 });
|
||||||
e.inputEvent.preventDefault();
|
})
|
||||||
nodes.value && services?.editorService.paste({ offsetX: 10, offsetY: 10 });
|
.keydown([ctrl, 'x'], (e) => {
|
||||||
})
|
e.inputEvent.preventDefault();
|
||||||
.keydown([ctrl, 'x'], (e) => {
|
if (!nodes.value || isPage(nodes.value[0])) return;
|
||||||
e.inputEvent.preventDefault();
|
services?.editorService.copy(nodes.value);
|
||||||
if (!nodes.value || isPage(nodes.value[0])) return;
|
services?.editorService.remove(nodes.value);
|
||||||
services?.editorService.copy(nodes.value);
|
})
|
||||||
services?.editorService.remove(nodes.value);
|
.keydown([ctrl, 'z'], (e) => {
|
||||||
})
|
e.inputEvent.preventDefault();
|
||||||
.keydown([ctrl, 'z'], (e) => {
|
services?.editorService.undo();
|
||||||
e.inputEvent.preventDefault();
|
})
|
||||||
services?.editorService.undo();
|
.keydown([ctrl, 'shift', 'z'], (e) => {
|
||||||
})
|
e.inputEvent.preventDefault();
|
||||||
.keydown([ctrl, 'shift', 'z'], (e) => {
|
services?.editorService.redo();
|
||||||
e.inputEvent.preventDefault();
|
})
|
||||||
services?.editorService.redo();
|
.keydown('up', (e) => {
|
||||||
})
|
e.inputEvent.preventDefault();
|
||||||
.keydown('up', (e) => {
|
services?.editorService.move(0, -1);
|
||||||
e.inputEvent.preventDefault();
|
})
|
||||||
services?.editorService.move(0, -1);
|
.keydown('down', (e) => {
|
||||||
})
|
e.inputEvent.preventDefault();
|
||||||
.keydown('down', (e) => {
|
services?.editorService.move(0, 1);
|
||||||
e.inputEvent.preventDefault();
|
})
|
||||||
services?.editorService.move(0, 1);
|
.keydown('left', (e) => {
|
||||||
})
|
e.inputEvent.preventDefault();
|
||||||
.keydown('left', (e) => {
|
services?.editorService.move(-1, 0);
|
||||||
e.inputEvent.preventDefault();
|
})
|
||||||
services?.editorService.move(-1, 0);
|
.keydown('right', (e) => {
|
||||||
})
|
e.inputEvent.preventDefault();
|
||||||
.keydown('right', (e) => {
|
services?.editorService.move(1, 0);
|
||||||
e.inputEvent.preventDefault();
|
})
|
||||||
services?.editorService.move(1, 0);
|
.keydown([ctrl, 'up'], (e) => {
|
||||||
})
|
e.inputEvent.preventDefault();
|
||||||
.keydown([ctrl, 'up'], (e) => {
|
services?.editorService.move(0, -10);
|
||||||
e.inputEvent.preventDefault();
|
})
|
||||||
services?.editorService.move(0, -10);
|
.keydown([ctrl, 'down'], (e) => {
|
||||||
})
|
e.inputEvent.preventDefault();
|
||||||
.keydown([ctrl, 'down'], (e) => {
|
services?.editorService.move(0, 10);
|
||||||
e.inputEvent.preventDefault();
|
})
|
||||||
services?.editorService.move(0, 10);
|
.keydown([ctrl, 'left'], (e) => {
|
||||||
})
|
e.inputEvent.preventDefault();
|
||||||
.keydown([ctrl, 'left'], (e) => {
|
services?.editorService.move(-10, 0);
|
||||||
e.inputEvent.preventDefault();
|
})
|
||||||
services?.editorService.move(-10, 0);
|
.keydown([ctrl, 'right'], (e) => {
|
||||||
})
|
e.inputEvent.preventDefault();
|
||||||
.keydown([ctrl, 'right'], (e) => {
|
services?.editorService.move(10, 0);
|
||||||
e.inputEvent.preventDefault();
|
})
|
||||||
services?.editorService.move(10, 0);
|
.keydown('tab', (e) => {
|
||||||
})
|
e.inputEvent.preventDefault();
|
||||||
.keydown('tab', (e) => {
|
services?.editorService.selectNextNode();
|
||||||
e.inputEvent.preventDefault();
|
})
|
||||||
services?.editorService.selectNextNode();
|
.keydown([ctrl, 'tab'], (e) => {
|
||||||
})
|
e.inputEvent.preventDefault();
|
||||||
.keydown([ctrl, 'tab'], (e) => {
|
services?.editorService.selectNextPage();
|
||||||
e.inputEvent.preventDefault();
|
})
|
||||||
services?.editorService.selectNextPage();
|
.keydown([ctrl, '='], (e) => {
|
||||||
})
|
e.inputEvent.preventDefault();
|
||||||
.keydown([ctrl, '='], (e) => {
|
services?.uiService.zoom(0.1);
|
||||||
e.inputEvent.preventDefault();
|
})
|
||||||
services?.uiService.zoom(0.1);
|
.keydown([ctrl, 'numpadplus'], (e) => {
|
||||||
})
|
e.inputEvent.preventDefault();
|
||||||
.keydown([ctrl, 'numpadplus'], (e) => {
|
services?.uiService.zoom(0.1);
|
||||||
e.inputEvent.preventDefault();
|
})
|
||||||
services?.uiService.zoom(0.1);
|
.keydown([ctrl, '-'], (e) => {
|
||||||
})
|
e.inputEvent.preventDefault();
|
||||||
.keydown([ctrl, '-'], (e) => {
|
services?.uiService.zoom(-0.1);
|
||||||
e.inputEvent.preventDefault();
|
})
|
||||||
services?.uiService.zoom(-0.1);
|
.keydown([ctrl, 'numpad-'], (e) => {
|
||||||
})
|
e.inputEvent.preventDefault();
|
||||||
.keydown([ctrl, 'numpad-'], (e) => {
|
services?.uiService.zoom(-0.1);
|
||||||
e.inputEvent.preventDefault();
|
})
|
||||||
services?.uiService.zoom(-0.1);
|
.keydown([ctrl, '0'], (e) => {
|
||||||
})
|
e.inputEvent.preventDefault();
|
||||||
.keydown([ctrl, '0'], (e) => {
|
services?.uiService.set('zoom', services.uiService.calcZoom());
|
||||||
e.inputEvent.preventDefault();
|
})
|
||||||
services?.uiService.set('zoom', services.uiService.calcZoom());
|
.keydown([ctrl, '1'], (e) => {
|
||||||
})
|
e.inputEvent.preventDefault();
|
||||||
.keydown([ctrl, '1'], (e) => {
|
services?.uiService.set('zoom', 1);
|
||||||
e.inputEvent.preventDefault();
|
|
||||||
services?.uiService.set('zoom', 1);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
workspace.value?.removeEventListener('mouseenter', mouseenterHandler);
|
workspace.value?.removeEventListener('mouseenter', mouseenterHandler);
|
||||||
workspace.value?.removeEventListener('mouseleave', mouseleaveHandler);
|
workspace.value?.removeEventListener('mouseleave', mouseleaveHandler);
|
||||||
keycon.destroy();
|
keycon.destroy();
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
workspace,
|
|
||||||
|
|
||||||
page: computed(() => services?.editorService.get<MPage>('page')),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -296,3 +296,10 @@ export enum Keys {
|
|||||||
|
|
||||||
export const H_GUIDE_LINE_STORAGE_KEY = '$MagicStageHorizontalGuidelinesData';
|
export const H_GUIDE_LINE_STORAGE_KEY = '$MagicStageHorizontalGuidelinesData';
|
||||||
export const V_GUIDE_LINE_STORAGE_KEY = '$MagicStageVerticalGuidelinesData';
|
export const V_GUIDE_LINE_STORAGE_KEY = '$MagicStageVerticalGuidelinesData';
|
||||||
|
|
||||||
|
export interface ScrollViewerEvent {
|
||||||
|
scrollLeft: number;
|
||||||
|
scrollTop: number;
|
||||||
|
scrollHeight: number;
|
||||||
|
scrollWidth: number;
|
||||||
|
}
|
||||||
|
@ -20,3 +20,4 @@ export * from './config';
|
|||||||
export * from './props';
|
export * from './props';
|
||||||
export * from './logger';
|
export * from './logger';
|
||||||
export * from './editor';
|
export * from './editor';
|
||||||
|
export * from './stage';
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Keys } from '../type';
|
import { EventEmitter } from 'events';
|
||||||
|
|
||||||
interface ScrollViewerOptions {
|
interface ScrollViewerOptions {
|
||||||
container: HTMLDivElement;
|
container: HTMLDivElement;
|
||||||
@ -6,10 +6,7 @@ interface ScrollViewerOptions {
|
|||||||
zoom: number;
|
zoom: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ScrollViewer {
|
export class ScrollViewer extends EventEmitter {
|
||||||
private enter = false;
|
|
||||||
private targetEnter = false;
|
|
||||||
private keydown = false;
|
|
||||||
private container: HTMLDivElement;
|
private container: HTMLDivElement;
|
||||||
private target: HTMLDivElement;
|
private target: HTMLDivElement;
|
||||||
private zoom = 1;
|
private zoom = 1;
|
||||||
@ -17,200 +14,135 @@ export class ScrollViewer {
|
|||||||
private scrollLeft = 0;
|
private scrollLeft = 0;
|
||||||
private scrollTop = 0;
|
private scrollTop = 0;
|
||||||
|
|
||||||
private x = 0;
|
private scrollHeight = 0;
|
||||||
private y = 0;
|
private scrollWidth = 0;
|
||||||
|
|
||||||
private resizeObserver = new ResizeObserver((entries) => {
|
private width = 0;
|
||||||
for (const { contentRect } of entries) {
|
private height = 0;
|
||||||
const { width, height } = contentRect;
|
|
||||||
const targetRect = this.target.getBoundingClientRect();
|
|
||||||
const targetWidth = targetRect.width * this.zoom;
|
|
||||||
const targetMarginTop = Number(this.target.style.marginTop) || 0;
|
|
||||||
const targetHeight = (targetRect.height + targetMarginTop) * this.zoom;
|
|
||||||
|
|
||||||
if (targetWidth < width) {
|
private translateXCorrectionValue = 0;
|
||||||
(this.target as any)._left = 0;
|
private translateYCorrectionValue = 0;
|
||||||
}
|
|
||||||
if (targetHeight < height) {
|
|
||||||
(this.target as any)._top = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.scroll();
|
private resizeObserver = new ResizeObserver(() => {
|
||||||
}
|
this.setSize();
|
||||||
|
this.setScrollSize();
|
||||||
});
|
});
|
||||||
|
|
||||||
constructor(options: ScrollViewerOptions) {
|
constructor(options: ScrollViewerOptions) {
|
||||||
|
super();
|
||||||
|
|
||||||
this.container = options.container;
|
this.container = options.container;
|
||||||
this.target = options.target;
|
this.target = options.target;
|
||||||
this.zoom = options.zoom;
|
this.zoom = options.zoom;
|
||||||
|
|
||||||
globalThis.addEventListener('keydown', this.keydownHandler);
|
this.container.addEventListener('wheel', this.wheelHandler, false);
|
||||||
globalThis.addEventListener('keyup', this.keyupHandler);
|
|
||||||
|
|
||||||
this.container.addEventListener('mouseenter', this.mouseEnterHandler);
|
|
||||||
this.container.addEventListener('mouseleave', this.mouseLeaveHandler);
|
|
||||||
this.target.addEventListener('mouseenter', this.targetMouseEnterHandler);
|
|
||||||
this.target.addEventListener('mouseleave', this.targetMouseLeaveHandler);
|
|
||||||
|
|
||||||
this.container.addEventListener('wheel', this.wheelHandler);
|
|
||||||
|
|
||||||
|
this.setSize();
|
||||||
|
this.setScrollSize();
|
||||||
this.resizeObserver.observe(this.container);
|
this.resizeObserver.observe(this.container);
|
||||||
}
|
}
|
||||||
|
|
||||||
public destroy() {
|
public destroy() {
|
||||||
this.resizeObserver.disconnect();
|
this.resizeObserver.disconnect();
|
||||||
|
this.container.removeEventListener('wheel', this.wheelHandler, false);
|
||||||
this.container.removeEventListener('mouseenter', this.mouseEnterHandler);
|
this.removeAllListeners();
|
||||||
this.container.removeEventListener('mouseleave', this.mouseLeaveHandler);
|
|
||||||
this.target.removeEventListener('mouseenter', this.targetMouseEnterHandler);
|
|
||||||
this.target.removeEventListener('mouseleave', this.targetMouseLeaveHandler);
|
|
||||||
globalThis.removeEventListener('keydown', this.keydownHandler);
|
|
||||||
globalThis.removeEventListener('keyup', this.keyupHandler);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public setZoom(zoom: number) {
|
public setZoom(zoom: number) {
|
||||||
this.zoom = zoom;
|
this.zoom = zoom;
|
||||||
|
this.setScrollSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
private scroll() {
|
public scrollTo({ left, top }: { left?: number; top?: number }) {
|
||||||
const scrollLeft = (this.target as any)._left;
|
if (typeof left !== 'undefined') {
|
||||||
const scrollTop = (this.target as any)._top;
|
this.scrollLeft = left;
|
||||||
|
}
|
||||||
|
|
||||||
this.target.style.transform = `translate(${scrollLeft}px, ${scrollTop}px)`;
|
if (typeof top !== 'undefined') {
|
||||||
}
|
this.scrollTop = top;
|
||||||
|
}
|
||||||
|
|
||||||
private removeHandler() {
|
const translateX = -this.scrollLeft + this.translateXCorrectionValue;
|
||||||
this.target.style.cursor = '';
|
const translateY = -this.scrollTop + this.translateYCorrectionValue;
|
||||||
this.target.removeEventListener('mousedown', this.mousedownHandler);
|
this.target.style.transform = `translate(${translateX}px, ${translateY}px)`;
|
||||||
document.removeEventListener('mousemove', this.mousemoveHandler);
|
|
||||||
document.removeEventListener('mouseup', this.mouseupHandler);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private wheelHandler = (event: WheelEvent) => {
|
private wheelHandler = (event: WheelEvent) => {
|
||||||
if (this.targetEnter) return;
|
|
||||||
|
|
||||||
const { deltaX, deltaY, currentTarget } = event;
|
const { deltaX, deltaY, currentTarget } = event;
|
||||||
|
|
||||||
if (currentTarget !== this.container) return;
|
if (currentTarget !== this.container) return;
|
||||||
|
|
||||||
this.setScrollOffset(deltaX, deltaY);
|
let top: number | undefined;
|
||||||
this.scroll();
|
if (this.scrollHeight > this.height) {
|
||||||
this.scrollLeft = (this.target as any)._left;
|
top = this.scrollTop + this.getPos(deltaY, this.scrollTop, this.scrollHeight, this.height);
|
||||||
this.scrollTop = (this.target as any)._top;
|
|
||||||
};
|
|
||||||
|
|
||||||
private mouseEnterHandler = () => {
|
|
||||||
this.enter = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
private mouseLeaveHandler = () => {
|
|
||||||
this.enter = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
private targetMouseEnterHandler = () => {
|
|
||||||
this.targetEnter = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
private targetMouseLeaveHandler = () => {
|
|
||||||
this.targetEnter = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
private mousedownHandler = (event: MouseEvent) => {
|
|
||||||
if (!this.keydown) return;
|
|
||||||
|
|
||||||
event.stopImmediatePropagation();
|
|
||||||
event.stopPropagation();
|
|
||||||
|
|
||||||
this.target.style.cursor = 'grabbing';
|
|
||||||
|
|
||||||
this.x = event.clientX;
|
|
||||||
this.y = event.clientY;
|
|
||||||
|
|
||||||
document.addEventListener('mousemove', this.mousemoveHandler);
|
|
||||||
document.addEventListener('mouseup', this.mouseupHandler);
|
|
||||||
};
|
|
||||||
|
|
||||||
private mouseupHandler = () => {
|
|
||||||
this.x = 0;
|
|
||||||
this.y = 0;
|
|
||||||
|
|
||||||
this.scrollLeft = (this.target as any)._left;
|
|
||||||
this.scrollTop = (this.target as any)._top;
|
|
||||||
this.removeHandler();
|
|
||||||
};
|
|
||||||
|
|
||||||
private mousemoveHandler = (event: MouseEvent) => {
|
|
||||||
event.stopImmediatePropagation();
|
|
||||||
event.stopPropagation();
|
|
||||||
|
|
||||||
const deltaX = event.clientX - this.x;
|
|
||||||
const deltaY = event.clientY - this.y;
|
|
||||||
|
|
||||||
this.setScrollOffset(deltaX, deltaY);
|
|
||||||
this.scroll();
|
|
||||||
};
|
|
||||||
|
|
||||||
private keydownHandler = (event: KeyboardEvent) => {
|
|
||||||
if (event.code === Keys.ESCAPE && this.enter) {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopImmediatePropagation();
|
|
||||||
event.stopPropagation();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.code !== Keys.ESCAPE || !this.enter || this.keydown) {
|
let left: number | undefined;
|
||||||
return;
|
if (this.scrollWidth > this.width) {
|
||||||
|
left = this.scrollLeft + this.getPos(deltaX, this.scrollLeft, this.scrollWidth, this.width);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.keydown = true;
|
this.scrollTo({ left, top });
|
||||||
|
this.emit('scroll', {
|
||||||
this.target.style.cursor = 'grab';
|
scrollLeft: this.scrollLeft,
|
||||||
this.container.addEventListener('mousedown', this.mousedownHandler);
|
scrollTop: this.scrollTop,
|
||||||
|
scrollHeight: this.scrollHeight,
|
||||||
|
scrollWidth: this.scrollWidth,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
private keyupHandler = (event: KeyboardEvent) => {
|
private getPos(delta: number, scrollPos: number, scrollSize: number, size: number) {
|
||||||
if (event.code !== Keys.ESCAPE || !this.keydown) {
|
let pos = 0;
|
||||||
return;
|
if (delta < 0) {
|
||||||
}
|
if (scrollPos > 0) {
|
||||||
|
pos = Math.max(delta, -scrollPos);
|
||||||
event.preventDefault();
|
}
|
||||||
event.stopImmediatePropagation();
|
} else {
|
||||||
event.stopPropagation();
|
const leftPos = scrollSize - size - scrollPos;
|
||||||
|
if (leftPos > 0) {
|
||||||
this.keydown = false;
|
pos = Math.min(delta, leftPos);
|
||||||
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
this.removeHandler();
|
|
||||||
};
|
|
||||||
|
|
||||||
private setScrollOffset(deltaX: number, deltaY: number) {
|
|
||||||
const { width, height } = this.container.getBoundingClientRect();
|
|
||||||
const targetRect = this.target.getBoundingClientRect();
|
|
||||||
|
|
||||||
const targetWidth = targetRect.width * this.zoom;
|
|
||||||
const targetHeight = targetRect.height * this.zoom;
|
|
||||||
|
|
||||||
let y = 0;
|
|
||||||
|
|
||||||
if (targetHeight > height) {
|
|
||||||
if (deltaY > 0) {
|
|
||||||
y = this.scrollTop + Math.min(targetHeight - height - this.scrollTop, deltaY);
|
|
||||||
} else {
|
|
||||||
y = this.scrollTop + Math.max(-(targetHeight - height + this.scrollTop), deltaY);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return pos;
|
||||||
let x = 0;
|
|
||||||
|
|
||||||
if (targetWidth > width) {
|
|
||||||
if (deltaX > 0) {
|
|
||||||
x = this.scrollLeft + Math.min(targetWidth - width - this.scrollLeft, deltaX);
|
|
||||||
} else {
|
|
||||||
x = this.scrollLeft + Math.max(-(targetWidth - width + this.scrollLeft), deltaX);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(this.target as any)._left = x;
|
|
||||||
(this.target as any)._top = y;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private setScrollSize = () => {
|
||||||
|
const targetRect = this.target.getBoundingClientRect();
|
||||||
|
this.scrollWidth = targetRect.width * this.zoom + 100;
|
||||||
|
const targetMarginTop = Number(this.target.style.marginTop) || 0;
|
||||||
|
this.scrollHeight = (targetRect.height + targetMarginTop) * this.zoom + 100;
|
||||||
|
|
||||||
|
let left: number | undefined;
|
||||||
|
let top: number | undefined;
|
||||||
|
if (this.scrollWidth < this.width) {
|
||||||
|
left = 0;
|
||||||
|
this.translateXCorrectionValue = 0;
|
||||||
|
} else {
|
||||||
|
this.translateXCorrectionValue = (this.scrollWidth - this.width) / 2;
|
||||||
|
}
|
||||||
|
if (this.scrollHeight < this.height) {
|
||||||
|
top = 0;
|
||||||
|
this.translateYCorrectionValue = 0;
|
||||||
|
} else {
|
||||||
|
this.translateYCorrectionValue = (this.scrollHeight - this.height) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.scrollTo({
|
||||||
|
left,
|
||||||
|
top,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.emit('scroll', {
|
||||||
|
scrollLeft: this.scrollLeft,
|
||||||
|
scrollTop: this.scrollTop,
|
||||||
|
scrollHeight: this.scrollHeight,
|
||||||
|
scrollWidth: this.scrollWidth,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private setSize = () => {
|
||||||
|
const { width, height } = this.container.getBoundingClientRect();
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
91
packages/editor/src/utils/stage.ts
Normal file
91
packages/editor/src/utils/stage.ts
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
import { MApp, MPage } from '@tmagic/schema';
|
||||||
|
import StageCore, { GuidesType, SortEventData, UpdateEventData } from '@tmagic/stage';
|
||||||
|
|
||||||
|
import editorService from '../services/editor';
|
||||||
|
import uiService from '../services/ui';
|
||||||
|
import { H_GUIDE_LINE_STORAGE_KEY, StageOptions, V_GUIDE_LINE_STORAGE_KEY } from '../type';
|
||||||
|
|
||||||
|
import { getGuideLineFromCache } from './editor';
|
||||||
|
|
||||||
|
const root = computed(() => editorService.get<MApp>('root'));
|
||||||
|
const page = computed(() => editorService.get<MPage>('page'));
|
||||||
|
const zoom = computed(() => uiService.get<number>('zoom') || 1);
|
||||||
|
const uiSelectMode = computed(() => uiService.get<boolean>('uiSelectMode'));
|
||||||
|
|
||||||
|
const getGuideLineKey = (key: string) => `${key}_${root.value?.id}_${page.value?.id}`;
|
||||||
|
|
||||||
|
export const useStage = (stageOptions: StageOptions) => {
|
||||||
|
const stage = new StageCore({
|
||||||
|
render: stageOptions.render,
|
||||||
|
runtimeUrl: stageOptions.runtimeUrl,
|
||||||
|
zoom: zoom.value,
|
||||||
|
autoScrollIntoView: stageOptions.autoScrollIntoView,
|
||||||
|
isContainer: stageOptions.isContainer,
|
||||||
|
containerHighlightClassName: stageOptions.containerHighlightClassName,
|
||||||
|
containerHighlightDuration: stageOptions.containerHighlightDuration,
|
||||||
|
containerHighlightType: stageOptions.containerHighlightType,
|
||||||
|
canSelect: (el, event, stop) => {
|
||||||
|
const elCanSelect = stageOptions.canSelect(el);
|
||||||
|
// 在组件联动过程中不能再往下选择,返回并触发 ui-select
|
||||||
|
if (uiSelectMode.value && elCanSelect && event.type === 'mousedown') {
|
||||||
|
document.dispatchEvent(new CustomEvent('ui-select', { detail: el }));
|
||||||
|
return stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
return elCanSelect;
|
||||||
|
},
|
||||||
|
moveableOptions: stageOptions.moveableOptions,
|
||||||
|
updateDragEl: stageOptions.updateDragEl,
|
||||||
|
});
|
||||||
|
|
||||||
|
stage.mask.setGuides([
|
||||||
|
getGuideLineFromCache(getGuideLineKey(H_GUIDE_LINE_STORAGE_KEY)),
|
||||||
|
getGuideLineFromCache(getGuideLineKey(V_GUIDE_LINE_STORAGE_KEY)),
|
||||||
|
]);
|
||||||
|
|
||||||
|
stage.on('select', (el: HTMLElement) => {
|
||||||
|
editorService.select(el.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
stage.on('highlight', (el: HTMLElement) => {
|
||||||
|
editorService.highlight(el.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
stage.on('multiSelect', (els: HTMLElement[]) => {
|
||||||
|
editorService.multiSelect(els.map((el) => el.id));
|
||||||
|
});
|
||||||
|
|
||||||
|
stage.on('update', (ev: UpdateEventData) => {
|
||||||
|
if (ev.parentEl) {
|
||||||
|
for (const data of ev.data) {
|
||||||
|
editorService.moveToContainer({ id: data.el.id, style: data.style }, ev.parentEl.id);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
editorService.update(ev.data.map((data) => ({ id: data.el.id, style: data.style })));
|
||||||
|
});
|
||||||
|
|
||||||
|
stage.on('sort', (ev: SortEventData) => {
|
||||||
|
editorService.sort(ev.src, ev.dist);
|
||||||
|
});
|
||||||
|
|
||||||
|
stage.on('changeGuides', (e) => {
|
||||||
|
uiService.set('showGuides', true);
|
||||||
|
|
||||||
|
if (!root.value || !page.value) return;
|
||||||
|
|
||||||
|
const storageKey = getGuideLineKey(
|
||||||
|
e.type === GuidesType.HORIZONTAL ? H_GUIDE_LINE_STORAGE_KEY : V_GUIDE_LINE_STORAGE_KEY,
|
||||||
|
);
|
||||||
|
if (e.guides.length) {
|
||||||
|
globalThis.localStorage.setItem(storageKey, JSON.stringify(e.guides));
|
||||||
|
} else {
|
||||||
|
globalThis.localStorage.removeItem(storageKey);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return stage;
|
||||||
|
};
|
Loading…
x
Reference in New Issue
Block a user