| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362 |
- import { storeToRefs } from 'pinia'
- import { nanoid } from 'nanoid'
- import { useMainStore, useSlidesStore } from '@/store'
- import { getImageSize } from '@/utils/image'
- import message from '@/utils/message'
- import { lang } from '@/main'
- import type { PPTLineElement, PPTElement, TableCell, TableCellStyle, PPTShapeElement, ChartType } from '@/types/slides'
- import { type ShapePoolItem, SHAPE_PATH_FORMULAS } from '@/configs/shapes'
- import type { LinePoolItem } from '@/configs/lines'
- import { getChartDefaultData } from '@/configs/chart'
- import useHistorySnapshot from '@/hooks/useHistorySnapshot'
- interface CommonElementPosition {
- top: number
- left: number
- width: number
- height: number
- }
- interface LineElementPosition {
- top: number
- left: number
- start: [number, number]
- end: [number, number]
- }
- interface CreateTextData {
- content?: string
- vertical?: boolean
- }
- export default () => {
- const mainStore = useMainStore()
- const slidesStore = useSlidesStore()
- const { creatingElement } = storeToRefs(mainStore)
- const { theme, viewportRatio, viewportSize } = storeToRefs(slidesStore)
- const { addHistorySnapshot } = useHistorySnapshot()
- // 创建(插入)一个元素并将其设置为被选中元素
- const createElement = (element: PPTElement, callback?: () => void) => {
- slidesStore.addElement(element)
- mainStore.setActiveElementIdList([element.id])
- if (creatingElement.value) mainStore.setCreatingElement(null)
- setTimeout(() => {
- mainStore.setEditorareaFocus(true)
- }, 0)
- if (callback) callback()
- addHistorySnapshot()
- }
- /**
- * 创建图片元素
- * @param src 图片地址
- */
- const createImageElement = (src: string) => {
- getImageSize(src).then(({ width, height }) => {
- const scale = height / width
-
- if (scale < viewportRatio.value && width > viewportSize.value) {
- width = viewportSize.value
- height = width * scale
- }
- else if (height > viewportSize.value * viewportRatio.value) {
- height = viewportSize.value * viewportRatio.value
- width = height / scale
- }
- createElement({
- type: 'image',
- id: nanoid(10),
- src,
- width,
- height,
- left: (viewportSize.value - width) / 2,
- top: (viewportSize.value * viewportRatio.value - height) / 2,
- fixedRatio: true,
- rotate: 0,
- })
- })
- }
-
- /**
- * 创建图表元素
- * @param chartType 图表类型
- */
- const createChartElement = (type: ChartType) => {
- const CHART_DEFAULT_DATA = getChartDefaultData()
- createElement({
- type: 'chart',
- id: nanoid(10),
- chartType: type,
- left: 300,
- top: 81.25,
- width: 400,
- height: 400,
- rotate: 0,
- themeColors: theme.value.themeColors,
- textColor: theme.value.fontColor,
- data: CHART_DEFAULT_DATA[type],
- })
- }
-
- /**
- * 创建表格元素
- * @param row 行数
- * @param col 列数
- */
- const createTableElement = (row: number, col: number) => {
- const style: TableCellStyle = {
- fontname: theme.value.fontName,
- color: theme.value.fontColor,
- }
- const data: TableCell[][] = []
- for (let i = 0; i < row; i++) {
- const rowCells: TableCell[] = []
- for (let j = 0; j < col; j++) {
- rowCells.push({ id: nanoid(10), colspan: 1, rowspan: 1, text: '', style })
- }
- data.push(rowCells)
- }
- const DEFAULT_CELL_WIDTH = 100
- const DEFAULT_CELL_HEIGHT = 36
- const colWidths: number[] = new Array(col).fill(1 / col)
- const width = col * DEFAULT_CELL_WIDTH
- const height = row * DEFAULT_CELL_HEIGHT
- createElement({
- type: 'table',
- id: nanoid(10),
- width,
- height,
- colWidths,
- rotate: 0,
- data,
- left: (viewportSize.value - width) / 2,
- top: (viewportSize.value * viewportRatio.value - height) / 2,
- outline: {
- width: 2,
- style: 'solid',
- color: '#eeece1',
- },
- theme: {
- color: theme.value.themeColors[0],
- rowHeader: true,
- rowFooter: false,
- colHeader: false,
- colFooter: false,
- },
- cellMinHeight: 36,
- })
- }
-
- /**
- * 创建文本元素
- * @param position 位置大小信息
- * @param content 文本内容
- */
- const createTextElement = (position: CommonElementPosition, data?: CreateTextData) => {
- const { left, top, width, height } = position
- const content = data?.content || ''
- const vertical = data?.vertical || false
- const id = nanoid(10)
- createElement({
- type: 'text',
- id,
- left,
- top,
- width,
- height,
- content,
- rotate: 0,
- defaultFontName: theme.value.fontName,
- defaultColor: theme.value.fontColor,
- vertical,
- }, () => {
- setTimeout(() => {
- const editorRef: HTMLElement | null = document.querySelector(`#editable-element-${id} .ProseMirror`)
- if (editorRef) editorRef.focus()
- }, 0)
- })
- }
-
- /**
- * 创建形状元素
- * @param position 位置大小信息
- * @param data 形状路径信息
- */
- const createShapeElement = (position: CommonElementPosition, data: ShapePoolItem, supplement: Partial<PPTShapeElement> = {}) => {
- const { left, top, width, height } = position
- const newElement: PPTShapeElement = {
- type: 'shape',
- id: nanoid(10),
- left,
- top,
- width,
- height,
- viewBox: data.viewBox,
- path: data.path,
- fill: theme.value.themeColors[0],
- fixedRatio: false,
- rotate: 0,
- ...supplement,
- }
- if (data.withborder) newElement.outline = theme.value.outline
- if (data.special) newElement.special = true
- if (data.pathFormula) {
- newElement.pathFormula = data.pathFormula
- newElement.viewBox = [width, height]
- const pathFormula = SHAPE_PATH_FORMULAS[data.pathFormula]
- if ('editable' in pathFormula && pathFormula.editable) {
- newElement.path = pathFormula.formula(width, height, pathFormula.defaultValue!)
- newElement.keypoints = pathFormula.defaultValue
- }
- else newElement.path = pathFormula.formula(width, height)
- }
- createElement(newElement)
- }
-
- /**
- * 创建线条元素
- * @param position 位置大小信息
- * @param data 线条的路径和样式
- */
- const createLineElement = (position: LineElementPosition, data: LinePoolItem) => {
- const { left, top, start, end } = position
- const newElement: PPTLineElement = {
- type: 'line',
- id: nanoid(10),
- left,
- top,
- start,
- end,
- points: data.points,
- color: theme.value.themeColors[0],
- style: data.style,
- width: 2,
- }
- if (data.isBroken) newElement.broken = [(start[0] + end[0]) / 2, (start[1] + end[1]) / 2]
- if (data.isBroken2) newElement.broken2 = [(start[0] + end[0]) / 2, (start[1] + end[1]) / 2]
- if (data.isCurve) newElement.curve = [(start[0] + end[0]) / 2, (start[1] + end[1]) / 2]
- if (data.isCubic) newElement.cubic = [[(start[0] + end[0]) / 2, (start[1] + end[1]) / 2], [(start[0] + end[0]) / 2, (start[1] + end[1]) / 2]]
- createElement(newElement)
- }
-
- /**
- * 创建LaTeX元素
- * @param svg SVG代码
- */
- const createLatexElement = (data: { path: string; latex: string; w: number; h: number; }) => {
- createElement({
- type: 'latex',
- id: nanoid(10),
- width: data.w,
- height: data.h,
- rotate: 0,
- left: (viewportSize.value - data.w) / 2,
- top: (viewportSize.value * viewportRatio.value - data.h) / 2,
- path: data.path,
- latex: data.latex,
- color: theme.value.fontColor,
- strokeWidth: 2,
- viewBox: [data.w, data.h],
- fixedRatio: true,
- })
- }
-
- /**
- * 创建视频元素
- * @param src 视频地址
- */
- const createVideoElement = (src: string) => {
- createElement({
- type: 'video',
- id: nanoid(10),
- width: 500,
- height: 300,
- rotate: 0,
- left: (viewportSize.value - 500) / 2,
- top: (viewportSize.value * viewportRatio.value - 300) / 2,
- src,
- autoplay: false,
- })
- }
-
- /**
- * 创建音频元素
- * @param src 音频地址
- */
- const createAudioElement = (src: string) => {
- createElement({
- type: 'audio',
- id: nanoid(10),
- width: 50,
- height: 50,
- rotate: 0,
- left: (viewportSize.value - 50) / 2,
- top: (viewportSize.value * viewportRatio.value - 50) / 2,
- loop: false,
- autoplay: false,
- fixedRatio: true,
- color: theme.value.themeColors[0],
- src,
- })
- }
- /**
- * 创建网页元素
- * @param url 网页链接地址
- */
- const createFrameElement = (url: string, type: number, id?: string) => {
- // 检查当前幻灯片是否已经包含网页元素
- const { currentSlide } = storeToRefs(useSlidesStore())
- const hasWebpage = currentSlide.value?.elements?.some(element => element.type === 'frame')
-
- if (hasWebpage) {
- message.error(lang.ssSlideLearn)
- return
- }
-
- console.log(viewportSize)
- const width = viewportSize.value
- const height = width * viewportRatio.value
- createElement({
- type: 'frame',
- id: nanoid(10),
- webId: id || '',
- url,
- width: width,
- height: height,
- // left: (viewportSize.value - 800) / 2,
- // top: (viewportSize.value * viewportRatio.value - 480) / 2,
- left: 0,
- top: 0,
- rotate: 0,
- toolType: type,
- })
- }
- return {
- createImageElement,
- createChartElement,
- createTableElement,
- createTextElement,
- createShapeElement,
- createLineElement,
- createLatexElement,
- createVideoElement,
- createAudioElement,
- createFrameElement,
- }
- }
|