Download frontend/src/hooks/useUniformDisplayElement.ts from CatPtain/web_ppt: direct link, hf CLI and curl.
- Browser
- Download file 8.81 kB
-
https://huggingface.co/spaces/CatPtain/web_ppt/resolve/main/frontend/src/hooks/useUniformDisplayElement.ts
- Command line
-
hf download hf://spaces/CatPtain/web_ppt/frontend/src/hooks/useUniformDisplayElement.ts
-
curl -L -o useUniformDisplayElement.ts https://huggingface.co/spaces/CatPtain/web_ppt/resolve/main/frontend/src/hooks/useUniformDisplayElement.ts
8.81 kB
| import { computed } from 'vue' | |
| import { storeToRefs } from 'pinia' | |
| import { useMainStore, useSlidesStore } from '@/store' | |
| import type { PPTElement } from '@/types/slides' | |
| import { getElementRange, getElementListRange, getRectRotatedOffset } from '@/utils/element' | |
| import useHistorySnapshot from './useHistorySnapshot' | |
| interface ElementItem { | |
| min: number | |
| max: number | |
| el: PPTElement | |
| } | |
| interface GroupItem { | |
| groupId: string | |
| els: PPTElement[] | |
| } | |
| interface GroupElementsItem { | |
| min: number | |
| max: number | |
| els: PPTElement[] | |
| } | |
| type Item = ElementItem | GroupElementsItem | |
| interface ElementWithPos { | |
| pos: number | |
| el: PPTElement | |
| } | |
| interface LastPos { | |
| min: number | |
| max: number | |
| } | |
| export default () => { | |
| const slidesStore = useSlidesStore() | |
| const { activeElementIdList, activeElementList } = storeToRefs(useMainStore()) | |
| const { currentSlide } = storeToRefs(slidesStore) | |
| const { addHistorySnapshot } = useHistorySnapshot() | |
| const displayItemCount = computed(() => { | |
| let count = 0 | |
| const groupIdList: string[] = [] | |
| for (const el of activeElementList.value) { | |
| if (!el.groupId) count += 1 | |
| else if (!groupIdList.includes(el.groupId)) { | |
| groupIdList.push(el.groupId) | |
| count += 1 | |
| } | |
| } | |
| return count | |
| }) | |
| // 水平均匀排列 | |
| const uniformHorizontalDisplay = () => { | |
| const { minX, maxX } = getElementListRange(activeElementList.value) | |
| const copyOfActiveElementList: PPTElement[] = JSON.parse(JSON.stringify(activeElementList.value)) | |
| const newElementList: PPTElement[] = JSON.parse(JSON.stringify(currentSlide.value.elements)) | |
| // 分别获取普通元素和组合元素集合,并记录下每一项的范围 | |
| const singleElemetList: ElementItem[] = [] | |
| let groupList: GroupItem[] = [] | |
| for (const el of copyOfActiveElementList) { | |
| if (!el.groupId) { | |
| const { minX, maxX } = getElementRange(el) | |
| singleElemetList.push({ min: minX, max: maxX, el }) | |
| } | |
| else { | |
| const groupEl = groupList.find(item => item.groupId === el.groupId) | |
| if (!groupEl) groupList.push({ groupId: el.groupId, els: [el] }) | |
| else { | |
| groupList = groupList.map(item => item.groupId === el.groupId ? { ...item, els: [...item.els, el] } : item) | |
| } | |
| } | |
| } | |
| const formatedGroupList: GroupElementsItem[] = [] | |
| for (const groupItem of groupList) { | |
| const { minX, maxX } = getElementListRange(groupItem.els) | |
| formatedGroupList.push({ min: minX, max: maxX, els: groupItem.els }) | |
| } | |
| // 将普通元素和组合元素集合组合在一起,然后将每一项按位置(从左到右)排序 | |
| const list: Item[] = [...singleElemetList, ...formatedGroupList] | |
| list.sort((itemA, itemB) => itemA.min - itemB.min) | |
| // 计算元素均匀分布所需要的间隔: | |
| // (所选元素整体范围 - 所有所选元素宽度和) / (所选元素数 - 1) | |
| let totalWidth = 0 | |
| for (const item of list) { | |
| const width = item.max - item.min | |
| totalWidth += width | |
| } | |
| const span = ((maxX - minX) - totalWidth) / (list.length - 1) | |
| // 按位置顺序依次计算每一个元素的目标位置 | |
| // 第一项中的元素即为起点,无需计算 | |
| // 从第二项开始,每一项的位置应该为:上一项位置 + 上一项宽度 + 间隔 | |
| // 注意此处计算的位置(pos)并非元素最终的left值,而是目标位置范围最小值(元素旋转后的left值 ≠ 范围最小值) | |
| const sortedElementData: ElementWithPos[] = [] | |
| const firstItem = list[0] | |
| let lastPos: LastPos = { min: firstItem.min, max: firstItem.max } | |
| if ('el' in firstItem) { | |
| sortedElementData.push({ pos: firstItem.min, el: firstItem.el }) | |
| } | |
| else { | |
| for (const el of firstItem.els) { | |
| const { minX: pos } = getElementRange(el) | |
| sortedElementData.push({ pos, el }) | |
| } | |
| } | |
| for (let i = 1; i < list.length; i++) { | |
| const item = list[i] | |
| const lastWidth = lastPos.max - lastPos.min | |
| const currentPos = lastPos.min + lastWidth + span | |
| const currentWidth = item.max - item.min | |
| lastPos = { min: currentPos, max: currentPos + currentWidth } | |
| if ('el' in item) { | |
| sortedElementData.push({ pos: currentPos, el: item.el }) | |
| } | |
| else { | |
| for (const el of item.els) { | |
| const { minX } = getElementRange(el) | |
| const offset = minX - item.min | |
| sortedElementData.push({ pos: currentPos + offset, el }) | |
| } | |
| } | |
| } | |
| // 根据目标位置计算元素最终目标left值 | |
| // 对于旋转后的元素,需要计算旋转前后left的偏移来做校正 | |
| for (const element of newElementList) { | |
| if (!activeElementIdList.value.includes(element.id)) continue | |
| for (const sortedItem of sortedElementData) { | |
| if (sortedItem.el.id === element.id) { | |
| if ('rotate' in element && element.rotate) { | |
| const { offsetX } = getRectRotatedOffset({ | |
| left: element.left, | |
| top: element.top, | |
| width: element.width, | |
| height: element.height, | |
| rotate: element.rotate, | |
| }) | |
| element.left = sortedItem.pos - offsetX | |
| } | |
| else element.left = sortedItem.pos | |
| } | |
| } | |
| } | |
| slidesStore.updateSlide({ elements: newElementList }) | |
| addHistorySnapshot() | |
| } | |
| // 垂直均匀排列(逻辑类似水平均匀排列方法) | |
| const uniformVerticalDisplay = () => { | |
| const { minY, maxY } = getElementListRange(activeElementList.value) | |
| const copyOfActiveElementList: PPTElement[] = JSON.parse(JSON.stringify(activeElementList.value)) | |
| const newElementList: PPTElement[] = JSON.parse(JSON.stringify(currentSlide.value.elements)) | |
| const singleElemetList: ElementItem[] = [] | |
| let groupList: GroupItem[] = [] | |
| for (const el of copyOfActiveElementList) { | |
| if (!el.groupId) { | |
| const { minY, maxY } = getElementRange(el) | |
| singleElemetList.push({ min: minY, max: maxY, el }) | |
| } | |
| else { | |
| const groupEl = groupList.find(item => item.groupId === el.groupId) | |
| if (!groupEl) groupList.push({ groupId: el.groupId, els: [el] }) | |
| else { | |
| groupList = groupList.map(item => item.groupId === el.groupId ? { ...item, els: [...item.els, el] } : item) | |
| } | |
| } | |
| } | |
| const formatedGroupList: GroupElementsItem[] = [] | |
| for (const groupItem of groupList) { | |
| const { minY, maxY } = getElementListRange(groupItem.els) | |
| formatedGroupList.push({ min: minY, max: maxY, els: groupItem.els }) | |
| } | |
| const list: Item[] = [...singleElemetList, ...formatedGroupList] | |
| list.sort((itemA, itemB) => itemA.min - itemB.min) | |
| let totalHeight = 0 | |
| for (const item of list) { | |
| const height = item.max - item.min | |
| totalHeight += height | |
| } | |
| const span = ((maxY - minY) - totalHeight) / (list.length - 1) | |
| const sortedElementData: ElementWithPos[] = [] | |
| const firstItem = list[0] | |
| let lastPos: LastPos = { min: firstItem.min, max: firstItem.max } | |
| if ('el' in firstItem) { | |
| sortedElementData.push({ pos: firstItem.min, el: firstItem.el }) | |
| } | |
| else { | |
| for (const el of firstItem.els) { | |
| const { minY: pos } = getElementRange(el) | |
| sortedElementData.push({ pos, el }) | |
| } | |
| } | |
| for (let i = 1; i < list.length; i++) { | |
| const item = list[i] | |
| const lastHeight = lastPos.max - lastPos.min | |
| const currentPos = lastPos.min + lastHeight + span | |
| const currentHeight = item.max - item.min | |
| lastPos = { min: currentPos, max: currentPos + currentHeight } | |
| if ('el' in item) { | |
| sortedElementData.push({ pos: currentPos, el: item.el }) | |
| } | |
| else { | |
| for (const el of item.els) { | |
| const { minY } = getElementRange(el) | |
| const offset = minY - item.min | |
| sortedElementData.push({ pos: currentPos + offset, el }) | |
| } | |
| } | |
| } | |
| for (const element of newElementList) { | |
| if (!activeElementIdList.value.includes(element.id)) continue | |
| for (const sortedItem of sortedElementData) { | |
| if (sortedItem.el.id === element.id) { | |
| if ('rotate' in element && element.rotate) { | |
| const { offsetY } = getRectRotatedOffset({ | |
| left: element.left, | |
| top: element.top, | |
| width: element.width, | |
| height: element.height, | |
| rotate: element.rotate, | |
| }) | |
| element.top = sortedItem.pos - offsetY | |
| } | |
| else element.top = sortedItem.pos | |
| } | |
| } | |
| } | |
| slidesStore.updateSlide({ elements: newElementList }) | |
| addHistorySnapshot() | |
| } | |
| return { | |
| displayItemCount, | |
| uniformHorizontalDisplay, | |
| uniformVerticalDisplay, | |
| } | |
| } |