tmagic-editor/packages/editor/src/layouts/page-bar/PageBarScrollContainer.vue

193 lines
5.3 KiB
Vue

<template>
<div class="m-editor-page-bar" ref="pageBar">
<slot name="prepend"></slot>
<div v-if="canScroll" class="m-editor-page-bar-item m-editor-page-bar-item-icon" @click="scroll('left')">
<Icon :icon="ArrowLeftBold"></Icon>
</div>
<div
v-if="(type === NodeType.PAGE && pageLength) || (type === NodeType.PAGE_FRAGMENT && pageFragmentLength)"
class="m-editor-page-bar-items"
ref="itemsContainer"
:style="`width: ${itemsContainerWidth}px`"
>
<slot></slot>
</div>
<div v-if="canScroll" class="m-editor-page-bar-item m-editor-page-bar-item-icon" @click="scroll('right')">
<Icon :icon="ArrowRightBold"></Icon>
</div>
</div>
</template>
<script setup lang="ts">
import {
computed,
type ComputedRef,
inject,
nextTick,
onBeforeUnmount,
onMounted,
ref,
watch,
type WatchStopHandle,
} from 'vue';
import { ArrowLeftBold, ArrowRightBold } from '@element-plus/icons-vue';
import Sortable, { SortableEvent } from 'sortablejs';
import { Id, NodeType } from '@tmagic/schema';
import Icon from '@editor/components/Icon.vue';
import type { PageBarSortOptions, Services } from '@editor/type';
defineOptions({
name: 'MEditorPageBarScrollContainer',
});
const props = defineProps<{
type: NodeType.PAGE | NodeType.PAGE_FRAGMENT;
pageBarSortOptions?: PageBarSortOptions;
}>();
const services = inject<Services>('services');
const editorService = services?.editorService;
const uiService = services?.uiService;
const itemsContainer = ref<HTMLElement>();
const canScroll = ref(false);
const showAddPageButton = computed(() => uiService?.get('showAddPageButton'));
const showPageListButton = computed(() => uiService?.get('showPageListButton'));
const itemsContainerWidth = ref(0);
const setCanScroll = () => {
// 减去新增、左移、右移三个按钮的宽度
// 37 = icon width 16 + padding 10 * 2 + border-right 1
itemsContainerWidth.value =
(pageBar.value?.clientWidth || 0) -
37 * 2 -
(showAddPageButton.value ? 37 : 21) -
(showPageListButton.value ? 37 : 0);
nextTick(() => {
if (itemsContainer.value) {
canScroll.value = itemsContainer.value.scrollWidth - itemsContainerWidth.value > 1;
}
});
};
const resizeObserver = new ResizeObserver(() => {
setCanScroll();
});
const pageBar = ref<HTMLDivElement>();
onMounted(() => {
pageBar.value && resizeObserver.observe(pageBar.value);
});
onBeforeUnmount(() => {
resizeObserver.disconnect();
});
let translateLeft = 0;
const scroll = (type: 'left' | 'right' | 'start' | 'end') => {
if (!itemsContainer.value) return;
const maxScrollLeft = itemsContainer.value.scrollWidth - itemsContainerWidth.value;
if (type === 'left') {
translateLeft += 100;
if (translateLeft > 0) {
translateLeft = 0;
}
} else if (type === 'right') {
translateLeft -= 100;
if (-translateLeft > maxScrollLeft) {
translateLeft = -maxScrollLeft;
}
} else if (type === 'start') {
translateLeft = 0;
} else if (type === 'end') {
translateLeft = -maxScrollLeft;
}
itemsContainer.value.style.transform = `translate(${translateLeft}px, 0px)`;
};
const pageLength = computed(() => editorService?.get('pageLength') || 0);
const pageFragmentLength = computed(() => editorService?.get('pageFragmentLength') || 0);
const crateWatchLength = (length: ComputedRef<number>) =>
watch(
length,
(length = 0, preLength = 0) => {
setTimeout(() => {
setCanScroll();
if (length < preLength) {
scroll('start');
} else {
scroll('end');
}
if (length > 1) {
const el = document.querySelector('.m-editor-page-bar-items') as HTMLElement;
let beforeDragList: Id[] = [];
const options = {
...{
dataIdAttr: 'page-id', // 获取排序后的数据
onStart: async (event: SortableEvent) => {
if (typeof props.pageBarSortOptions?.beforeStart === 'function') {
await props.pageBarSortOptions.beforeStart(event, sortable);
}
beforeDragList = sortable.toArray();
},
onUpdate: async (event: SortableEvent) => {
await editorService?.sort(
beforeDragList[event.oldIndex as number],
beforeDragList[event.newIndex as number],
);
if (typeof props.pageBarSortOptions?.afterUpdate === 'function') {
await props.pageBarSortOptions.afterUpdate(event, sortable);
}
},
},
...{
...(props.pageBarSortOptions ? props.pageBarSortOptions : {}),
},
};
if (!el) return;
const sortable = new Sortable(el, options);
}
});
},
{
immediate: true,
},
);
let unWatchPageLength: WatchStopHandle | null;
let unWatchPageFragmentLength: WatchStopHandle | null;
watch(
() => props.type,
(type) => {
if (type === NodeType.PAGE) {
unWatchPageFragmentLength?.();
unWatchPageFragmentLength = null;
unWatchPageLength = crateWatchLength(pageLength);
} else {
unWatchPageLength?.();
unWatchPageLength = null;
unWatchPageFragmentLength = crateWatchLength(pageFragmentLength);
}
},
{
immediate: true,
},
);
</script>