|
import tinycolor from 'tinycolor2' |
|
import { nanoid } from 'nanoid' |
|
import type { PPTElement, PPTLineElement, Slide } from '@/types/slides' |
|
|
|
interface RotatedElementData { |
|
left: number |
|
top: number |
|
width: number |
|
height: number |
|
rotate: number |
|
} |
|
|
|
interface IdMap { |
|
[id: string]: string |
|
} |
|
|
|
|
|
|
|
|
|
|
|
export const getRectRotatedRange = (element: RotatedElementData) => { |
|
const { left, top, width, height, rotate = 0 } = element |
|
|
|
const radius = Math.sqrt( Math.pow(width, 2) + Math.pow(height, 2) ) / 2 |
|
const auxiliaryAngle = Math.atan(height / width) * 180 / Math.PI |
|
|
|
const tlbraRadian = (180 - rotate - auxiliaryAngle) * Math.PI / 180 |
|
const trblaRadian = (auxiliaryAngle - rotate) * Math.PI / 180 |
|
|
|
const middleLeft = left + width / 2 |
|
const middleTop = top + height / 2 |
|
|
|
const xAxis = [ |
|
middleLeft + radius * Math.cos(tlbraRadian), |
|
middleLeft + radius * Math.cos(trblaRadian), |
|
middleLeft - radius * Math.cos(tlbraRadian), |
|
middleLeft - radius * Math.cos(trblaRadian), |
|
] |
|
const yAxis = [ |
|
middleTop - radius * Math.sin(tlbraRadian), |
|
middleTop - radius * Math.sin(trblaRadian), |
|
middleTop + radius * Math.sin(tlbraRadian), |
|
middleTop + radius * Math.sin(trblaRadian), |
|
] |
|
|
|
return { |
|
xRange: [Math.min(...xAxis), Math.max(...xAxis)], |
|
yRange: [Math.min(...yAxis), Math.max(...yAxis)], |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
export const getRectRotatedOffset = (element: RotatedElementData) => { |
|
const { xRange: originXRange, yRange: originYRange } = getRectRotatedRange({ |
|
left: element.left, |
|
top: element.top, |
|
width: element.width, |
|
height: element.height, |
|
rotate: 0, |
|
}) |
|
const { xRange: rotatedXRange, yRange: rotatedYRange } = getRectRotatedRange({ |
|
left: element.left, |
|
top: element.top, |
|
width: element.width, |
|
height: element.height, |
|
rotate: element.rotate, |
|
}) |
|
return { |
|
offsetX: rotatedXRange[0] - originXRange[0], |
|
offsetY: rotatedYRange[0] - originYRange[0], |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
export const getElementRange = (element: PPTElement) => { |
|
let minX, maxX, minY, maxY |
|
|
|
if (element.type === 'line') { |
|
minX = element.left |
|
maxX = element.left + Math.max(element.start[0], element.end[0]) |
|
minY = element.top |
|
maxY = element.top + Math.max(element.start[1], element.end[1]) |
|
} |
|
else if ('rotate' in element && element.rotate) { |
|
const { left, top, width, height, rotate } = element |
|
const { xRange, yRange } = getRectRotatedRange({ left, top, width, height, rotate }) |
|
minX = xRange[0] |
|
maxX = xRange[1] |
|
minY = yRange[0] |
|
maxY = yRange[1] |
|
} |
|
else { |
|
minX = element.left |
|
maxX = element.left + element.width |
|
minY = element.top |
|
maxY = element.top + element.height |
|
} |
|
return { minX, maxX, minY, maxY } |
|
} |
|
|
|
|
|
|
|
|
|
|
|
export const getElementListRange = (elementList: PPTElement[]) => { |
|
const leftValues: number[] = [] |
|
const topValues: number[] = [] |
|
const rightValues: number[] = [] |
|
const bottomValues: number[] = [] |
|
|
|
elementList.forEach(element => { |
|
const { minX, maxX, minY, maxY } = getElementRange(element) |
|
leftValues.push(minX) |
|
topValues.push(minY) |
|
rightValues.push(maxX) |
|
bottomValues.push(maxY) |
|
}) |
|
|
|
const minX = Math.min(...leftValues) |
|
const maxX = Math.max(...rightValues) |
|
const minY = Math.min(...topValues) |
|
const maxY = Math.max(...bottomValues) |
|
|
|
return { minX, maxX, minY, maxY } |
|
} |
|
|
|
|
|
|
|
|
|
|
|
export const getLineElementLength = (element: PPTLineElement) => { |
|
const deltaX = element.end[0] - element.start[0] |
|
const deltaY = element.end[1] - element.start[1] |
|
const len = Math.sqrt(deltaX * deltaX + deltaY * deltaY) |
|
return len |
|
} |
|
|
|
export interface AlignLine { |
|
value: number |
|
range: [number, number] |
|
} |
|
|
|
|
|
|
|
|
|
|
|
export const uniqAlignLines = (lines: AlignLine[]) => { |
|
const uniqLines: AlignLine[] = [] |
|
lines.forEach(line => { |
|
const index = uniqLines.findIndex(_line => _line.value === line.value) |
|
if (index === -1) uniqLines.push(line) |
|
else { |
|
const uniqLine = uniqLines[index] |
|
const rangeMin = Math.min(uniqLine.range[0], line.range[0]) |
|
const rangeMax = Math.max(uniqLine.range[1], line.range[1]) |
|
const range: [number, number] = [rangeMin, rangeMax] |
|
const _line = { value: line.value, range } |
|
uniqLines[index] = _line |
|
} |
|
}) |
|
return uniqLines |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
export const createSlideIdMap = (slides: Slide[]) => { |
|
const slideIdMap: IdMap = {} |
|
for (const slide of slides) { |
|
slideIdMap[slide.id] = nanoid(10) |
|
} |
|
return slideIdMap |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export const createElementIdMap = (elements: PPTElement[]) => { |
|
const groupIdMap: IdMap = {} |
|
const elIdMap: IdMap = {} |
|
for (const element of elements) { |
|
const groupId = element.groupId |
|
if (groupId && !groupIdMap[groupId]) { |
|
groupIdMap[groupId] = nanoid(10) |
|
} |
|
elIdMap[element.id] = nanoid(10) |
|
} |
|
return { |
|
groupIdMap, |
|
elIdMap, |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
export const getTableSubThemeColor = (themeColor: string) => { |
|
const rgba = tinycolor(themeColor) |
|
return [ |
|
rgba.setAlpha(0.3).toRgbString(), |
|
rgba.setAlpha(0.1).toRgbString(), |
|
] |
|
} |
|
|
|
|
|
|
|
|
|
|
|
export const getLineElementPath = (element: PPTLineElement) => { |
|
const start = element.start.join(',') |
|
const end = element.end.join(',') |
|
if (element.broken) { |
|
const mid = element.broken.join(',') |
|
return `M${start} L${mid} L${end}` |
|
} |
|
else if (element.broken2) { |
|
const { minX, maxX, minY, maxY } = getElementRange(element) |
|
if (maxX - minX >= maxY - minY) return `M${start} L${element.broken2[0]},${element.start[1]} L${element.broken2[0]},${element.end[1]} ${end}` |
|
return `M${start} L${element.start[0]},${element.broken2[1]} L${element.end[0]},${element.broken2[1]} ${end}` |
|
} |
|
else if (element.curve) { |
|
const mid = element.curve.join(',') |
|
return `M${start} Q${mid} ${end}` |
|
} |
|
else if (element.cubic) { |
|
const [c1, c2] = element.cubic |
|
const p1 = c1.join(',') |
|
const p2 = c2.join(',') |
|
return `M${start} C${p1} ${p2} ${end}` |
|
} |
|
return `M${start} L${end}` |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
export const isElementInViewport = (element: HTMLElement, parent: HTMLElement): boolean => { |
|
const elementRect = element.getBoundingClientRect() |
|
const parentRect = parent.getBoundingClientRect() |
|
|
|
return ( |
|
elementRect.top >= parentRect.top && |
|
elementRect.bottom <= parentRect.bottom |
|
) |
|
} |