123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351 |
- import { storeToRefs } from "pinia"
- import { nanoid } from "nanoid"
- import { useMainStore, useSlidesStore } from "@/store"
- import { getImageSize } from "@/utils/image"
- 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 { CHART_DEFAULT_DATA } 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) => {
- createElement({
- type: "chart",
- id: nanoid(10),
- chartType: type,
- left: 300,
- top: 81.25,
- width: 800,
- height: 800,
- rotate: 0,
- themeColors: [theme.value.themeColor],
- 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 = 200
- const DEFAULT_CELL_HEIGHT = 72
- 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.themeColor,
- rowHeader: true,
- rowFooter: false,
- colHeader: false,
- colFooter: false
- },
- cellMinHeight: 72
- })
- }
- /**
- * 创建文本元素
- * @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.themeColor,
- 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.themeColor,
- style: data.style,
- width: 4
- }
- 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: 4,
- viewBox: [data.w, data.h],
- fixedRatio: true
- })
- }
- /**
- * 创建视频元素
- * @param src 视频地址
- */
- const createVideoElement = (src: string) => {
- createElement({
- type: "elf",
- subtype: "elf-video",
- id: nanoid(10),
- width: 1000,
- height: 600,
- rotate: 0,
- left: (viewportSize.value - 1000) / 2,
- top: (viewportSize.value * viewportRatio.value - 600) / 2,
- src,
- autoplay: false
- })
- }
- /**
- * 创建音频元素
- * @param src 音频地址
- */
- const createAudioElement = (src: string) => {
- createElement({
- type: "elf",
- subtype: "elf-audio",
- id: nanoid(10),
- width: 100,
- height: 100,
- rotate: 0,
- left: (viewportSize.value - 100) / 2,
- top: (viewportSize.value * viewportRatio.value - 100) / 2,
- loop: false,
- autoplay: false,
- fixedRatio: true,
- color: theme.value.themeColor,
- src
- })
- }
- /**
- * 创建云教练元素
- * @param url 云教练地址
- */
- const createCloudCoachElement = (sid: string) => {
- createElement({
- type: "elf",
- subtype: "elf-sing-play",
- id: nanoid(10),
- width: viewportSize.value,
- height: viewportSize.value * viewportRatio.value,
- rotate: 0,
- left: 0,
- top: 0,
- sid
- })
- }
- return {
- createImageElement,
- createChartElement,
- createTableElement,
- createTextElement,
- createShapeElement,
- createLineElement,
- createLatexElement,
- createVideoElement,
- createAudioElement,
- createCloudCoachElement
- }
- }
|