\n\nexport const getFontsize = (view: EditorView) => {\n const marks = getMarkAttrs(view)\n const fontsize = getAttrValue(marks, 'fontsize', 'fontsize') || _defaultAttrs.fontsize\n return parseInt(fontsize)\n}\n\nexport const defaultRichTextAttrs: TextAttrs = {\n bold: false,\n em: false,\n underline: false,\n strikethrough: false,\n superscript: false,\n subscript: false,\n code: false,\n color: '#000',\n backcolor: '#000',\n fontsize: '20px',\n fontname: '微软雅黑',\n link: '',\n align: 'left',\n bulletList: false,\n orderedList: false,\n blockquote: false,\n}","/**\n * 判断操作系统是否存在某字体\n * @param fontName 字体名\n */\nexport const isSupportFont = (fontName: string) => {\n if (typeof fontName !== 'string') return false\n\n const arial = 'Arial'\n if (fontName.toLowerCase() === arial.toLowerCase()) return true\n\n const size = 100\n const width = 100\n const height = 100\n const str = 'a'\n\n const canvas = document.createElement('canvas')\n const ctx = canvas.getContext('2d')\n\n if (!ctx) return false\n\n canvas.width = width\n canvas.height = height\n ctx.textAlign = 'center'\n ctx.fillStyle = 'black'\n ctx.textBaseline = 'middle'\n\n const getDotArray = (_fontFamily: string) => {\n ctx.clearRect(0, 0, width, height)\n ctx.font = `${size}px ${_fontFamily}, ${arial}`\n ctx.fillText(str, width / 2, height / 2)\n const imageData = ctx.getImageData(0, 0, width, height).data\n return [].slice.call(imageData).filter(item => item !== 0)\n }\n\n return getDotArray(arial).join('') !== getDotArray(fontName).join('')\n}","import { Slide } from '@/types/slides'\n\nexport const slides: Slide[] = [\n {\n id: 'test-slide-1',\n elements: [\n {\n type: 'shape',\n id: '4cbRxp',\n left: 0,\n top: 200,\n width: 546,\n height: 362.5,\n viewBox: [200, 200],\n path: 'M 0 0 L 0 200 L 200 200 Z',\n fill: '#5b9bd5',\n fixedRatio: false,\n opacity: 0.7,\n rotate: 0\n },\n {\n type: 'shape',\n id: 'ookHrf',\n left: 0,\n top: 0,\n width: 300,\n height: 320,\n viewBox: [200, 200],\n path: 'M 0 0 L 0 200 L 200 200 Z',\n fill: '#5b9bd5',\n fixedRatio: false,\n flipV: true,\n rotate: 0\n },\n {\n type: 'text',\n id: 'idn7Mx',\n left: 355,\n top: 65.25,\n width: 585,\n height: 188,\n lineHeight: 1.2,\n content: 'PPTIST
',\n rotate: 0,\n defaultFontName: 'Microsoft Yahei',\n defaultColor: '#333'\n },\n {\n type: 'text',\n id: '7stmVP',\n left: 355,\n top: 253.25,\n width: 585,\n height: 56,\n content: '基于 Vue 3.x + TypeScript 的在线演示文稿应用
',\n rotate: 0,\n defaultFontName: 'Microsoft Yahei',\n defaultColor: '#333'\n },\n {\n type: 'line',\n id: 'FnpZs4',\n left: 361,\n top: 238,\n start: [0, 0],\n end: [549, 0],\n points: ['', ''],\n color: '#5b9bd5',\n style: 'solid',\n width: 2,\n },\n ],\n background: {\n type: 'solid',\n color: '#ffffff',\n },\n },\n {\n id: 'test-slide-2',\n elements: [\n {\n type: 'text',\n id: 'ptNnUJ',\n left: 145,\n top: 148,\n width: 711,\n height: 77,\n lineHeight: 1.2,\n content: '在此处添加标题
',\n rotate: 0,\n defaultFontName: 'Microsoft Yahei',\n defaultColor: '#333',\n }, \n {\n type: 'text',\n id: 'mRHvQN',\n left: 207.50000000000003,\n top: 249.84259259259264,\n width: 585,\n height: 56,\n content: '在此处添加副标题
',\n rotate: 0,\n defaultFontName: 'Microsoft Yahei',\n defaultColor: '#333',\n }, \n {\n type: 'line',\n id: '7CQDwc',\n left: 323.09259259259267,\n top: 238.33333333333334,\n start: [0, 0],\n end: [354.8148148148148, 0],\n points: ['', ''],\n color: '#5b9bd5',\n style: 'solid',\n width: 4\n }, \n {\n type: 'shape',\n id: '09wqWw',\n left: -27.648148148148138,\n top: 432.73148148148147,\n width: 1056.2962962962963,\n height: 162.96296296296296,\n viewBox: [200, 200],\n path: 'M 0 20 C 40 -40 60 60 100 20 C 140 -40 160 60 200 20 L 200 180 C 140 240 160 140 100 180 C 40 240 60 140 0 180 L 0 20 Z',\n fill: '#5b9bd5',\n fixedRatio: false,\n rotate: 0\n }\n ],\n background: {\n type: 'solid',\n color: '#fff',\n },\n },\n {\n id: 'test-slide-3',\n elements: [\n {\n type: 'shape',\n id: 'vSheCJ',\n left: 183.5185185185185,\n top: 175.5092592592593,\n width: 605.1851851851851,\n height: 185.18518518518516,\n viewBox: [200, 200],\n path: 'M 0 0 L 200 0 L 200 200 L 0 200 Z',\n fill: '#5b9bd5',\n fixedRatio: false,\n rotate: 0\n }, \n {\n type: 'shape',\n id: 'Mpwv7x',\n left: 211.29629629629628,\n top: 201.80555555555557,\n width: 605.1851851851851,\n height: 185.18518518518516,\n viewBox: [200, 200],\n path: 'M 0 0 L 200 0 L 200 200 L 0 200 Z',\n fill: '#5b9bd5',\n fixedRatio: false,\n rotate: 0,\n opacity: 0.7\n }, \n {\n type: 'text',\n id: 'WQOTAp',\n left: 304.9074074074074,\n top: 198.10185185185182,\n width: 417.9629629629629,\n height: 140,\n content: '感谢观看
',\n rotate: 0,\n defaultFontName: 'Microsoft Yahei',\n defaultColor: '#333',\n wordSpace: 5\n }\n ],\n background: {\n type: 'solid',\n color: '#fff',\n },\n },\n]","import { SlideTheme } from '@/types/slides'\n\nexport const theme: SlideTheme = {\n themeColor: '#5b9bd5',\n fontColor: '#333',\n fontName: 'Microsoft Yahei',\n backgroundColor: '#fff',\n}","/* eslint-disable max-lines */\n\nimport { Slide } from '@/types/slides'\n\nexport const layouts: Slide[] = [\n {\n id: 'template',\n elements: [\n {\n type: 'shape',\n id: '4cbRxp',\n left: 0,\n top: 200,\n width: 546,\n height: 362.5,\n viewBox: [200, 200],\n path: 'M 0 0 L 0 200 L 200 200 Z',\n fill: '{{themeColor}}',\n fixedRatio: false,\n opacity: 0.7,\n rotate: 0\n },\n {\n type: 'shape',\n id: 'ookHrf',\n left: 0,\n top: 0,\n width: 300,\n height: 320,\n viewBox: [200, 200],\n path: 'M 0 0 L 0 200 L 200 200 Z',\n fill: '{{themeColor}}',\n fixedRatio: false,\n flipV: true,\n rotate: 0\n },\n {\n type: 'text',\n id: 'AkIh3E',\n left: 355,\n top: 95.11111111111111,\n width: 585,\n height: 116,\n lineHeight: 1.2,\n content: '输入标题
',\n rotate: 0,\n defaultFontName: '{{fontName}}',\n defaultColor: '{{fontColor}}',\n wordSpace: 6\n },\n {\n type: 'text',\n id: '7stmVP',\n left: 355,\n top: 253.25,\n width: 585,\n height: 56,\n content: '请在此处输入副标题
',\n rotate: 0,\n defaultFontName: '{{fontName}}',\n defaultColor: '{{fontColor}}',\n },\n {\n type: 'line',\n id: 'FnpZs4',\n left: 361,\n top: 238,\n start: [0, 0],\n end: [549, 0],\n points: ['', ''],\n color: '{{themeColor}}',\n style: 'solid',\n width: 2,\n },\n ],\n background: {\n type: 'solid',\n color: '{{backgroundColor}}',\n },\n },\n {\n id: 'template',\n elements: [\n {\n type: 'text',\n id: 'ptNnUJ',\n left: 145,\n top: 148,\n width: 711,\n height: 77,\n lineHeight: 1.2,\n content: '在此处添加标题
',\n rotate: 0,\n defaultFontName: '{{fontName}}',\n defaultColor: '{{fontColor}}',\n }, \n {\n type: 'text',\n id: 'mRHvQN',\n left: 207.50000000000003,\n top: 249.84259259259264,\n width: 585,\n height: 56,\n content: '在此处添加副标题
',\n rotate: 0,\n defaultFontName: '{{fontName}}',\n defaultColor: '{{fontColor}}',\n }, \n {\n type: 'line',\n id: '7CQDwc',\n left: 323.09259259259267,\n top: 238.33333333333334,\n start: [0, 0],\n end: [354.8148148148148, 0],\n points: ['', ''],\n color: '{{themeColor}}',\n style: 'solid',\n width: 4\n }, \n {\n type: 'shape',\n id: '09wqWw',\n left: -27.648148148148138,\n top: 432.73148148148147,\n width: 1056.2962962962963,\n height: 162.96296296296296,\n viewBox: [200, 200],\n path: 'M 0 20 C 40 -40 60 60 100 20 C 140 -40 160 60 200 20 L 200 180 C 140 240 160 140 100 180 C 40 240 60 140 0 180 L 0 20 Z',\n fill: '{{themeColor}}',\n fixedRatio: false,\n rotate: 0\n }\n ],\n background: {\n type: 'solid',\n color: '{{backgroundColor}}',\n },\n },\n {\n id: 'template',\n elements: [\n {\n type: 'shape',\n id: 'vSheCJ',\n left: 183.5185185185185,\n top: 175.5092592592593,\n width: 605.1851851851851,\n height: 185.18518518518516,\n viewBox: [200, 200],\n path: 'M 0 0 L 200 0 L 200 200 L 0 200 Z',\n fill: '{{themeColor}}',\n fixedRatio: false,\n rotate: 0\n }, \n {\n type: 'shape',\n id: 'Mpwv7x',\n left: 211.29629629629628,\n top: 201.80555555555557,\n width: 605.1851851851851,\n height: 185.18518518518516,\n viewBox: [200, 200],\n path: 'M 0 0 L 200 0 L 200 200 L 0 200 Z',\n fill: '{{themeColor}}',\n fixedRatio: false,\n rotate: 0,\n opacity: 0.7\n }, \n {\n type: 'text',\n id: 'WQOTAp',\n left: 304.9074074074074,\n top: 198.10185185185182,\n width: 417.9629629629629,\n height: 140,\n content: '感谢观看
',\n rotate: 0,\n defaultFontName: '{{fontName}}',\n defaultColor: '{{fontColor}}',\n wordSpace: 5\n }\n ],\n background: {\n type: 'solid',\n color: '{{backgroundColor}}',\n },\n },\n {\n id: 'MZVO1kkj',\n elements: [\n {\n type: 'shape',\n id: 'cql0h8',\n left: 0,\n top: 0,\n width: 352.59259259259255,\n height: 562.5,\n viewBox: [200, 200],\n path: 'M 0 0 L 200 0 L 200 200 L 0 200 Z',\n fill: '{{themeColor}}',\n fixedRatio: false,\n rotate: 0\n },\n {\n type: 'shape',\n id: '_RTaF4',\n left: 171.4814814814814,\n top: 100.13888888888887,\n width: 362.22222222222223,\n height: 362.22222222222223,\n viewBox: [200, 200],\n path: 'M 100 0 A 50 50 0 1 1 100 200 A 50 50 0 1 1 100 0 Z',\n fill: 'rgba(255,255,255,0)',\n fixedRatio: false,\n rotate: 0,\n outline: {\n width: 10,\n color: '{{backgroundColor}}',\n style: 'solid'\n }\n },\n {\n type: 'shape',\n id: 'UZfo8N',\n left: 216.66666666666663,\n top: 145.32407407407408,\n width: 271.85185185185185,\n height: 271.85185185185185,\n viewBox: [200, 200],\n path: 'M 100 0 A 50 50 0 1 1 100 200 A 50 50 0 1 1 100 0 Z',\n fill: '{{backgroundColor}}',\n fixedRatio: false,\n rotate: 0,\n text: {\n content: '01
',\n defaultFontName: '{{fontName}}',\n defaultColor: '{{fontColor}}',\n align: 'middle'\n }\n },\n {\n type: 'text',\n id: 'ysqtBg',\n left: 561.4814814814814,\n top: 100.1388888888889,\n width: 359.25925925925924,\n height: 80,\n content: '在此处输入标题
',\n rotate: 0,\n defaultFontName: '{{fontName}}',\n defaultColor: '{{fontColor}}',\n },\n {\n type: 'text',\n id: 'lXsoHa',\n left: 572.5925925925925,\n top: 202.3611111111111,\n width: 257.77777777777777,\n height: 260,\n content: '在此处输入内容
在此处输入内容
在此处输入内容
在此处输入内容
在此处输入内容
在此处输入内容
',\n rotate: 0,\n defaultFontName: '{{fontName}}',\n defaultColor: '{{fontColor}}',\n lineHeight: 2,\n fill: '{{subColor}}'\n }\n ],\n background: {\n type: 'solid',\n color: '{{backgroundColor}}',\n },\n },\n {\n id: 'template',\n elements: [\n {\n type: 'shape',\n id: 'EBBnTr',\n left: 360.5996472663139,\n top: 141.8496472663139,\n width: 278.80070546737215,\n height: 278.80070546737215,\n viewBox: [200, 200],\n path: 'M 100 0 A 50 50 0 1 1 100 200 A 50 50 0 1 1 100 0 Z',\n fill: '{{themeColor}}',\n fixedRatio: true,\n rotate: 0,\n outline: {\n width: 0,\n color: '{{backgroundColor}}',\n style: 'solid'\n }\n }, \n {\n type: 'shape',\n id: 'gDIWDH',\n left: 456.4373897707231,\n top: 98.287037037037,\n width: 87.12522045855381,\n height: 87.12522045855381,\n viewBox: [200, 200],\n path: 'M 100 0 A 50 50 0 1 1 100 200 A 50 50 0 1 1 100 0 Z',\n fill: '{{themeColor}}',\n fixedRatio: true,\n rotate: 0,\n outline: {\n width: 4,\n color: '{{backgroundColor}}',\n style: 'solid'\n },\n text: {\n content: '1
',\n defaultFontName: '{{fontName}}',\n defaultColor: '{{fontColor}}',\n align: 'middle'\n }\n }, \n {\n type: 'shape',\n id: 'DUWT7E',\n left: 317.037037037037,\n top: 237.68738977072314,\n width: 87.12522045855381,\n height: 87.12522045855381,\n viewBox: [200, 200],\n path: 'M 100 0 A 50 50 0 1 1 100 200 A 50 50 0 1 1 100 0 Z',\n fill: '{{themeColor}}',\n fixedRatio: true,\n rotate: 0,\n outline: {\n width: 4,\n color: '{{backgroundColor}}',\n style: 'solid'\n },\n text: {\n content: '4
',\n defaultFontName: '{{fontName}}',\n defaultColor: '{{fontColor}}',\n align: 'middle'\n }\n }, \n {\n type: 'shape',\n id: 'pbhn38',\n left: 456.43738977072303,\n top: 377.08774250440916,\n width: 87.12522045855381,\n height: 87.12522045855381,\n viewBox: [200, 200],\n path: 'M 100 0 A 50 50 0 1 1 100 200 A 50 50 0 1 1 100 0 Z',\n fill: '{{themeColor}}',\n fixedRatio: true,\n rotate: 0,\n outline: {\n width: 4,\n color: '{{backgroundColor}}',\n style: 'solid'\n },\n text: {\n content: '3
',\n defaultFontName: '{{fontName}}',\n defaultColor: '{{fontColor}}',\n align: 'middle'\n }\n }, \n {\n type: 'shape',\n id: 'CvMKrO',\n left: 595.8377425044091,\n top: 237.6873897707231,\n width: 87.12522045855381,\n height: 87.12522045855381,\n viewBox: [200, 200],\n path: 'M 100 0 A 50 50 0 1 1 100 200 A 50 50 0 1 1 100 0 Z',\n fill: '{{themeColor}}',\n fixedRatio: true,\n rotate: 0,\n outline: {\n width: 4,\n color: '{{backgroundColor}}',\n style: 'solid'\n },\n text: {\n content: '2
',\n defaultFontName: '{{fontName}}',\n defaultColor: '{{fontColor}}',\n align: 'middle'\n }\n }, \n {\n type: 'text',\n id: 'adudHB',\n left: 402.962962962963,\n top: 39.39814814814815,\n width: 194.07407407407408,\n height: 50,\n content: '在此输入内容
',\n rotate: 0,\n defaultFontName: '{{fontName}}',\n defaultColor: '{{fontColor}}',\n }, \n {\n type: 'text',\n id: '9UpDwg',\n left: 402.962962962963,\n top: 473.1018518518518,\n width: 194.07407407407408,\n height: 50,\n content: '在此输入内容
',\n rotate: 0,\n defaultFontName: '{{fontName}}',\n defaultColor: '{{fontColor}}',\n }, \n {\n type: 'text',\n id: 'GERdpB',\n left: 111.48148148148151,\n top: 256.25,\n width: 194.07407407407408,\n height: 50,\n content: '在此输入内容
',\n rotate: 0,\n defaultFontName: '{{fontName}}',\n defaultColor: '{{fontColor}}',\n }, \n {\n type: 'text',\n id: 'G5qoho',\n left: 691.1111111111111,\n top: 256.25,\n width: 194.07407407407408,\n height: 50,\n content: '在此输入内容
',\n rotate: 0,\n defaultFontName: '{{fontName}}',\n defaultColor: '{{fontColor}}',\n }, \n {\n type: 'shape',\n id: 'vdZcI6',\n left: 415.18518518518516,\n top: 196.4351851851852,\n width: 169.62962962962962,\n height: 169.62962962962962,\n viewBox: [200, 200],\n path: 'M 100 0 A 50 50 0 1 1 100 200 A 50 50 0 1 1 100 0 Z',\n fill: '{{backgroundColor}}',\n fixedRatio: false,\n rotate: 0\n }\n ],\n background: {\n type: 'solid',\n color: '{{backgroundColor}}',\n },\n },\n {\n id: 'template',\n elements: [\n {\n type: 'shape',\n id: 'tYUmrx',\n left: 156.66666666666683,\n top: 149.02777777777771,\n width: 264.4444444444445,\n height: 264.4444444444445,\n viewBox: [200, 200],\n path: 'M 100 0 A 50 50 0 1 1 100 200 A 50 50 0 1 1 100 0 Z',\n fill: '{{themeColor}}',\n fixedRatio: false,\n rotate: 0,\n text: {\n content: '01
',\n defaultFontName: '{{fontName}}',\n defaultColor: '{{fontColor}}',\n align: 'middle'\n }\n },\n {\n type: 'shape',\n id: '0GVHf8',\n left: 342.2222222222223,\n top: 217.17592592592587,\n width: 128.14814814814812,\n height: 128.14814814814812,\n viewBox: [200, 200],\n path: 'M 100 0 A 50 50 0 1 1 100 200 A 50 50 0 1 1 100 0 Z',\n fill: '{{backgroundColor}}',\n fixedRatio: false,\n rotate: 0\n },\n {\n type: 'text',\n id: 'BO33Sv',\n left: 378.8888888888889,\n top: 235.24999999999994,\n width: 464.4444444444444,\n height: 92,\n content: '在此处添加标题
',\n rotate: 0,\n defaultFontName: '{{fontName}}',\n defaultColor: '{{fontColor}}',\n }\n ],\n background: {\n type: 'solid',\n color: '{{backgroundColor}}',\n },\n },\n {\n id: 'template',\n elements: [\n {\n type: 'text',\n id: 'Hj7ttp',\n left: 69.35185185185185,\n top: 49.21759259259262,\n width: 420,\n height: 63,\n lineHeight: 1.2,\n content: '1.请输入标题
',\n rotate: 0,\n defaultFontName: '{{fontName}}',\n defaultColor: '{{fontColor}}',\n fill: '{{themeColor}}',\n },\n {\n type: 'text',\n id: 'FmKMNB',\n left: 69.35185185185185,\n top: 129.28240740740745,\n width: 420,\n height: 384,\n content: '在此处输入内容
在此处输入内容
在此处输入内容
在此处输入内容
在此处输入内容
在此处输入内容
在此处输入内容
在此处输入内容
在此处输入内容
',\n rotate: 0,\n defaultFontName: '{{fontName}}',\n defaultColor: '{{fontColor}}',\n fill: '{{subColor}}',\n },\n {\n type: 'text',\n id: 'rI7ZeO',\n left: 510.64814814814815,\n top: 49.21759259259262,\n width: 420,\n height: 63,\n lineHeight: 1.2,\n content: '2.请输入标题
',\n rotate: 0,\n defaultFontName: '{{fontName}}',\n defaultColor: '{{fontColor}}',\n fill: '{{themeColor}}',\n },\n {\n type: 'text',\n id: 'KspwGc',\n left: 510.64814814814815,\n top: 129.28240740740745,\n width: 420,\n height: 384,\n content: '在此处输入内容
在此处输入内容
在此处输入内容
在此处输入内容
在此处输入内容
在此处输入内容
在此处输入内容
在此处输入内容
在此处输入内容
',\n rotate: 0,\n defaultFontName: '{{fontName}}',\n defaultColor: '{{fontColor}}',\n fill: '{{subColor}}',\n },\n ],\n background: {\n type: 'solid',\n color: '{{backgroundColor}}',\n },\n },\n {\n id: 'template',\n elements: [\n {\n type: 'text',\n id: 'Rx63Jo',\n left: 69.35185185185179,\n top: 51.71759259259262,\n width: 420,\n height: 58,\n lineHeight: 1.2,\n content: '1.请输入标题
',\n rotate: 0,\n defaultFontName: '{{fontName}}',\n defaultColor: '{{fontColor}}',\n fill: '{{themeColor}}',\n },\n {\n type: 'text',\n id: 'ulyuzE',\n left: 69.35185185185179,\n top: 131.78240740740745,\n width: 420,\n height: 129,\n content: '在此处输入内容
在此处输入内容
在此处输入内容
',\n rotate: 0,\n defaultFontName: '{{fontName}}',\n defaultColor: '{{fontColor}}',\n fill: '{{subColor}}',\n },\n {\n type: 'text',\n id: 'kr35Ca',\n left: 510.6481481481481,\n top: 51.71759259259262,\n width: 420,\n height: 58,\n lineHeight: 1.2,\n content: '2.请输入标题
',\n rotate: 0,\n defaultFontName: '{{fontName}}',\n defaultColor: '{{fontColor}}',\n fill: '{{themeColor}}',\n },\n {\n type: 'text',\n id: 'BNQSpC',\n left: 510.6481481481481,\n top: 131.78240740740745,\n width: 420,\n height: 129,\n content: '在此处输入内容
在此处输入内容
在此处输入内容
',\n rotate: 0,\n defaultFontName: '{{fontName}}',\n defaultColor: '{{fontColor}}',\n fill: '{{subColor}}',\n },\n {\n type: 'text',\n id: 'Vr38Nu',\n left: 69.35185185185185,\n top: 301.71759259259255,\n width: 420,\n height: 58,\n lineHeight: 1.2,\n content: '3.请输入标题
',\n rotate: 0,\n defaultFontName: '{{fontName}}',\n defaultColor: '{{fontColor}}',\n fill: '{{themeColor}}',\n },\n {\n type: 'text',\n id: 'IwKRSu',\n left: 69.35185185185185,\n top: 381.7824074074074,\n width: 420,\n height: 129,\n content: '在此处输入内容
在此处输入内容
在此处输入内容
',\n rotate: 0,\n defaultFontName: '{{fontName}}',\n defaultColor: '{{fontColor}}',\n fill: '{{subColor}}',\n },\n {\n type: 'text',\n id: '0Opr1v',\n left: 510.64814814814815,\n top: 301.71759259259255,\n width: 420,\n height: 58,\n lineHeight: 1.2,\n content: '4.请输入标题
',\n rotate: 0,\n defaultFontName: '{{fontName}}',\n defaultColor: '{{fontColor}}',\n fill: '{{themeColor}}',\n },\n {\n type: 'text',\n id: '4L9Uzz',\n left: 510.64814814814815,\n top: 381.7824074074074,\n width: 420,\n height: 129,\n content: '在此处输入内容
在此处输入内容
在此处输入内容
',\n rotate: 0,\n defaultFontName: '{{fontName}}',\n defaultColor: '{{fontColor}}',\n fill: '{{subColor}}',\n },\n ],\n background: {\n type: 'solid',\n color: '{{backgroundColor}}',\n },\n },\n {\n id: 'template',\n elements: [\n {\n type: 'text',\n id: 'GdEGxg',\n left: 134.53703703703704,\n top: 127.25,\n width: 152.77777777777777,\n height: 308,\n lineHeight: 1.8,\n content: '请在此处输入标题
',\n rotate: 0,\n defaultFontName: '{{fontName}}',\n defaultColor: '{{fontColor}}',\n wordSpace: 8,\n fill: '{{themeColor}}',\n },\n {\n type: 'text',\n id: 'y5sAfw',\n left: 332.8703703703704,\n top: 127.25,\n width: 532.5925925925926,\n height: 50,\n content: '请在此处输入内容1
',\n rotate: 0,\n defaultFontName: '{{fontName}}',\n defaultColor: '{{fontColor}}',\n fill: '{{subColor}}',\n },\n {\n type: 'text',\n id: 'VeuocM',\n left: 332.8703703703704,\n top: 212.0648148148148,\n width: 532.5925925925926,\n height: 50,\n content: '请在此处输入内容2
',\n rotate: 0,\n defaultFontName: '{{fontName}}',\n defaultColor: '{{fontColor}}',\n fill: '{{subColor}}',\n },\n {\n type: 'text',\n id: 'RyFWQe',\n left: 332.8703703703704,\n top: 296.8796296296296,\n width: 532.5925925925926,\n height: 50,\n content: '请在此处输入内容3
',\n rotate: 0,\n defaultFontName: '{{fontName}}',\n defaultColor: '{{fontColor}}',\n fill: '{{subColor}}',\n },\n {\n type: 'text',\n id: 'Q56viI',\n left: 332.8703703703704,\n top: 381.69444444444446,\n width: 532.5925925925926,\n height: 50,\n content: '请在此处输入内容4
',\n rotate: 0,\n defaultFontName: '{{fontName}}',\n defaultColor: '{{fontColor}}',\n fill: '{{subColor}}',\n },\n ],\n background: {\n type: 'solid',\n color: '{{backgroundColor}}',\n },\n },\n {\n id: 'template',\n elements: [\n {\n type: 'shape',\n id: 'SUWirT',\n left: 73.8888888888889,\n top: 64.21296296296302,\n width: 49.629629629629626,\n height: 49.629629629629626,\n viewBox: [200, 200],\n path: 'M 100 0 A 50 50 0 1 1 100 200 A 50 50 0 1 1 100 0 Z',\n fill: '{{themeColor}}',\n fixedRatio: false,\n rotate: 0,\n text: {\n content: '1
',\n defaultFontName: '{{fontName}}',\n defaultColor: '{{fontColor}}',\n align: 'middle'\n }\n }, \n {\n type: 'text',\n id: 'YjzN1M',\n left: 148.70370370370372,\n top: 64.21296296296302,\n width: 323.7037037037037,\n height: 120,\n content: '在此输入内容
在此输入内容
在此输入内容
',\n rotate: 0,\n defaultFontName: '{{fontName}}',\n defaultColor: '{{fontColor}}',\n fill: '{{subColor}}'\n }, \n {\n type: 'shape',\n id: 'fS09I7',\n left: 527.5925925925926,\n top: 64.21296296296302,\n width: 49.629629629629626,\n height: 49.629629629629626,\n viewBox: [200, 200],\n path: 'M 100 0 A 50 50 0 1 1 100 200 A 50 50 0 1 1 100 0 Z',\n fill: '{{themeColor}}',\n fixedRatio: false,\n rotate: 0,\n text: {\n content: '2
',\n defaultFontName: '{{fontName}}',\n defaultColor: '{{fontColor}}',\n align: 'middle'\n }\n }, \n {\n type: 'text',\n id: 'qCnfB1',\n left: 602.4074074074074,\n top: 64.21296296296302,\n width: 323.7037037037037,\n height: 120,\n content: '在此输入内容
在此输入内容
在此输入内容
',\n rotate: 0,\n defaultFontName: '{{fontName}}',\n defaultColor: '{{fontColor}}',\n fill: '{{subColor}}'\n },\n {\n type: 'shape',\n id: 'difAAT',\n left: 73.8888888888889,\n top: 221.25000000000003,\n width: 49.629629629629626,\n height: 49.629629629629626,\n viewBox: [200, 200],\n path: 'M 100 0 A 50 50 0 1 1 100 200 A 50 50 0 1 1 100 0 Z',\n fill: '{{themeColor}}',\n fixedRatio: false,\n rotate: 0,\n text: {\n content: '3
',\n defaultFontName: '{{fontName}}',\n defaultColor: '{{fontColor}}',\n align: 'middle'\n }\n }, \n {\n type: 'text',\n id: 'EUlvMo',\n left: 148.70370370370372,\n top: 221.25000000000003,\n width: 323.7037037037037,\n height: 120,\n content: '在此输入内容
在此输入内容
在此输入内容
',\n rotate: 0,\n defaultFontName: '{{fontName}}',\n defaultColor: '{{fontColor}}',\n fill: '{{subColor}}'\n }, \n {\n type: 'shape',\n id: 'US_9jB',\n left: 527.5925925925926,\n top: 221.25000000000003,\n width: 49.629629629629626,\n height: 49.629629629629626,\n viewBox: [200, 200],\n path: 'M 100 0 A 50 50 0 1 1 100 200 A 50 50 0 1 1 100 0 Z',\n fill: '{{themeColor}}',\n fixedRatio: false,\n rotate: 0,\n text: {\n content: '4
',\n defaultFontName: '{{fontName}}',\n defaultColor: '{{fontColor}}',\n align: 'middle'\n }\n }, \n {\n type: 'text',\n id: '243MnQ',\n left: 602.4074074074074,\n top: 221.25000000000003,\n width: 323.7037037037037,\n height: 120,\n content: '在此输入内容
在此输入内容
在此输入内容
',\n rotate: 0,\n defaultFontName: '{{fontName}}',\n defaultColor: '{{fontColor}}',\n fill: '{{subColor}}'\n }, \n {\n type: 'shape',\n id: 'Y_KUj0',\n left: 73.8888888888889,\n top: 378.287037037037,\n width: 49.629629629629626,\n height: 49.629629629629626,\n viewBox: [200, 200],\n path: 'M 100 0 A 50 50 0 1 1 100 200 A 50 50 0 1 1 100 0 Z',\n fill: '{{themeColor}}',\n fixedRatio: false,\n rotate: 0,\n text: {\n content: '5
',\n defaultFontName: '{{fontName}}',\n defaultColor: '{{fontColor}}',\n align: 'middle'\n }\n }, \n {\n type: 'text',\n id: '9GglMe',\n left: 148.70370370370372,\n top: 378.287037037037,\n width: 323.7037037037037,\n height: 120,\n content: '在此输入内容
在此输入内容
在此输入内容
',\n rotate: 0,\n defaultFontName: '{{fontName}}',\n defaultColor: '{{fontColor}}',\n fill: '{{subColor}}'\n }, \n {\n type: 'shape',\n id: 'eSInje',\n left: 527.5925925925926,\n top: 378.287037037037,\n width: 49.629629629629626,\n height: 49.629629629629626,\n viewBox: [200, 200],\n path: 'M 100 0 A 50 50 0 1 1 100 200 A 50 50 0 1 1 100 0 Z',\n fill: '{{themeColor}}',\n fixedRatio: false,\n rotate: 0,\n text: {\n content: '6
',\n defaultFontName: '{{fontName}}',\n defaultColor: '{{fontColor}}',\n align: 'middle'\n }\n }, \n {\n type: 'text',\n id: '0S3yUg',\n left: 602.4074074074074,\n top: 378.287037037037,\n width: 323.7037037037037,\n height: 120,\n content: '在此输入内容
在此输入内容
在此输入内容
',\n rotate: 0,\n defaultFontName: '{{fontName}}',\n defaultColor: '{{fontColor}}',\n fill: '{{subColor}}'\n }\n ],\n background: {\n type: 'solid',\n color: '{{backgroundColor}}',\n },\n },\n]","import { defineStore } from 'pinia'\nimport tinycolor from 'tinycolor2'\nimport { omit } from 'lodash'\nimport { Slide, SlideTheme, PPTElement, PPTAnimation } from '@/types/slides'\nimport { slides } from '@/mocks/slides'\nimport { theme } from '@/mocks/theme'\nimport { layouts } from '@/mocks/layout'\n\ninterface RemoveElementPropData {\n id: string;\n propName: string | string[];\n}\n\ninterface UpdateElementData {\n id: string | string[];\n props: Partial;\n}\n\ninterface FormatedAnimation {\n animations: PPTAnimation[];\n autoNext: boolean;\n}\n\nexport interface SlidesState {\n theme: SlideTheme;\n slides: Slide[];\n slideIndex: number;\n viewportRatio: number;\n}\n\nexport const useSlidesStore = defineStore('slides', {\n state: (): SlidesState => ({\n theme: theme, // 主题样式\n slides: slides, // 幻灯片页面数据\n slideIndex: 0, // 当前页面索引\n viewportRatio: 0.5625, // 可是区域比例,默认16:9\n }),\n\n getters: {\n currentSlide(state) {\n return state.slides[state.slideIndex]\n },\n \n currentSlideAnimations(state) {\n const currentSlide = state.slides[state.slideIndex]\n if (!currentSlide?.animations) return []\n\n const els = currentSlide.elements\n const elIds = els.map(el => el.id)\n return currentSlide.animations.filter(animation => elIds.includes(animation.elId))\n },\n\n // 格式化的当前页动画\n // 将触发条件为“与上一动画同时”的项目向上合并到序列中的同一位置\n // 为触发条件为“上一动画之后”项目的上一项添加自动向下执行标记\n formatedAnimations(state) {\n const currentSlide = state.slides[state.slideIndex]\n if (!currentSlide?.animations) return []\n\n const els = currentSlide.elements\n const elIds = els.map(el => el.id)\n const animations = currentSlide.animations.filter(animation => elIds.includes(animation.elId))\n\n const formatedAnimations: FormatedAnimation[] = []\n for (const animation of animations) {\n if (animation.trigger === 'click' || !formatedAnimations.length) {\n formatedAnimations.push({ animations: [animation], autoNext: false })\n }\n else if (animation.trigger === 'meantime') {\n const last = formatedAnimations[formatedAnimations.length - 1]\n last.animations = last.animations.filter(item => item.elId !== animation.elId)\n last.animations.push(animation)\n formatedAnimations[formatedAnimations.length - 1] = last\n }\n else if (animation.trigger === 'auto') {\n const last = formatedAnimations[formatedAnimations.length - 1]\n last.autoNext = true\n formatedAnimations[formatedAnimations.length - 1] = last\n formatedAnimations.push({ animations: [animation], autoNext: false })\n }\n }\n return formatedAnimations\n },\n \n layouts(state) {\n const {\n themeColor,\n fontColor,\n fontName,\n backgroundColor,\n } = state.theme\n \n const subColor = tinycolor(fontColor).isDark() ? 'rgba(230, 230, 230, 0.5)' : 'rgba(180, 180, 180, 0.5)'\n \n const layoutsString = JSON.stringify(layouts)\n .replaceAll('{{themeColor}}', themeColor)\n .replaceAll('{{fontColor}}', fontColor)\n .replaceAll('{{fontName}}', fontName)\n .replaceAll('{{backgroundColor}}', backgroundColor)\n .replaceAll('{{subColor}}', subColor)\n \n return JSON.parse(layoutsString)\n },\n },\n\n actions: {\n setTheme(themeProps: Partial) {\n this.theme = { ...this.theme, ...themeProps }\n },\n \n setViewportRatio(viewportRatio: number) {\n this.viewportRatio = viewportRatio\n },\n \n setSlides(slides: Slide[]) {\n this.slides = slides\n },\n \n addSlide(slide: Slide | Slide[]) {\n const slides = Array.isArray(slide) ? slide : [slide]\n const addIndex = this.slideIndex + 1\n this.slides.splice(addIndex, 0, ...slides)\n this.slideIndex = addIndex\n },\n \n updateSlide(props: Partial) {\n const slideIndex = this.slideIndex\n this.slides[slideIndex] = { ...this.slides[slideIndex], ...props }\n },\n \n deleteSlide(slideId: string | string[]) {\n const slidesId = Array.isArray(slideId) ? slideId : [slideId]\n \n const deleteSlidesIndex = []\n for (let i = 0; i < slidesId.length; i++) {\n const index = this.slides.findIndex(item => item.id === slidesId[i])\n deleteSlidesIndex.push(index)\n }\n let newIndex = Math.min(...deleteSlidesIndex)\n \n const maxIndex = this.slides.length - slidesId.length - 1\n if (newIndex > maxIndex) newIndex = maxIndex\n \n this.slideIndex = newIndex\n this.slides = this.slides.filter(item => !slidesId.includes(item.id))\n },\n \n updateSlideIndex(index: number) {\n this.slideIndex = index\n },\n \n addElement(element: PPTElement | PPTElement[]) {\n const elements = Array.isArray(element) ? element : [element]\n const currentSlideEls = this.slides[this.slideIndex].elements\n const newEls = [...currentSlideEls, ...elements]\n this.slides[this.slideIndex].elements = newEls\n },\n\n deleteElement(elementId: string | string[]) {\n const elementIdList = Array.isArray(elementId) ? elementId : [elementId]\n const currentSlideEls = this.slides[this.slideIndex].elements\n const newEls = currentSlideEls.filter(item => !elementIdList.includes(item.id))\n this.slides[this.slideIndex].elements = newEls\n },\n \n updateElement(data: UpdateElementData) {\n const { id, props } = data\n const elIdList = typeof id === 'string' ? [id] : id\n \n const slideIndex = this.slideIndex\n const slide = this.slides[slideIndex]\n const elements = slide.elements.map(el => {\n return elIdList.includes(el.id) ? { ...el, ...props } : el\n })\n this.slides[slideIndex].elements = (elements as PPTElement[])\n },\n \n removeElementProps(data: RemoveElementPropData) {\n const { id, propName } = data\n const propsNames = typeof propName === 'string' ? [propName] : propName\n \n const slideIndex = this.slideIndex\n const slide = this.slides[slideIndex]\n const elements = slide.elements.map(el => {\n return el.id === id ? omit(el, propsNames) : el\n })\n this.slides[slideIndex].elements = (elements as PPTElement[])\n },\n },\n})","import { defineStore } from 'pinia'\nimport { CreatingElement } from '@/types/edit'\nimport { ToolbarStates } from '@/types/toolbar'\nimport { DialogForExportTypes } from '@/types/export'\nimport { SYS_FONTS } from '@/configs/font'\nimport { TextAttrs, defaultRichTextAttrs } from '@/utils/prosemirror/utils'\nimport { isSupportFont } from '@/utils/font'\n\nimport { useSlidesStore } from './slides'\n\n\n\nexport interface MainState {\n activeElementIdList: string[];\n handleElementId: string;\n activeGroupElementId: string;\n canvasPercentage: number;\n canvasScale: number;\n canvasDragged: boolean;\n thumbnailsFocus: boolean;\n editorAreaFocus: boolean;\n disableHotkeys: boolean;\n showGridLines: boolean;\n showRuler: boolean;\n creatingElement: CreatingElement | null;\n availableFonts: typeof SYS_FONTS;\n toolbarState: ToolbarStates;\n clipingImageElementId: string;\n isScaling: boolean;\n richTextAttrs: TextAttrs;\n selectedTableCells: string[];\n selectedSlidesIndex: number[];\n dialogForExport: DialogForExportTypes;\n}\n\nexport const useMainStore = defineStore('main', {\n state: (): MainState => ({\n activeElementIdList: [], // 被选中的元素ID集合,包含 handleElementId\n handleElementId: '', // 正在操作的元素ID\n activeGroupElementId: '', // 组合元素成员中,被选中可独立操作的元素ID\n canvasPercentage: 90, // 画布可视区域百分比\n canvasScale: 1, // 画布缩放比例(基于宽度1000px)\n canvasDragged: false, // 画布被拖拽移动\n thumbnailsFocus: false, // 左侧导航缩略图区域聚焦\n editorAreaFocus: false, // 编辑区域聚焦\n disableHotkeys: false, // 禁用快捷键\n showGridLines: false, // 显示网格线\n showRuler: false, // 显示标尺\n creatingElement: null, // 正在插入的元素信息,需要通过绘制插入的元素(文字、形状、线条)\n availableFonts: SYS_FONTS, // 当前环境可用字体\n toolbarState: ToolbarStates.SLIDE_DESIGN, // 右侧工具栏状态\n clipingImageElementId: '', // 当前正在裁剪的图片ID \n richTextAttrs: defaultRichTextAttrs, // 富文本状态\n selectedTableCells: [], // 选中的表格单元格\n isScaling: false, // 正在进行元素缩放\n selectedSlidesIndex: [], // 当前被选中的页面索引集合\n dialogForExport: '', // 导出面板\n }),\n\n getters: {\n activeElementList(state) {\n const slidesStore = useSlidesStore()\n const currentSlide = slidesStore.currentSlide\n if (!currentSlide || !currentSlide.elements) return []\n return currentSlide.elements.filter(element => state.activeElementIdList.includes(element.id))\n },\n \n handleElement(state) {\n const slidesStore = useSlidesStore()\n const currentSlide = slidesStore.currentSlide\n if (!currentSlide || !currentSlide.elements) return null\n return currentSlide.elements.find(element => state.handleElementId === element.id) || null\n },\n },\n\n actions: {\n setActiveElementIdList(activeElementIdList: string[]) {\n if (activeElementIdList.length === 1) this.handleElementId = activeElementIdList[0]\n else this.handleElementId = ''\n \n this.activeElementIdList = activeElementIdList\n },\n \n setHandleElementId(handleElementId: string) {\n this.handleElementId = handleElementId\n },\n \n setActiveGroupElementId(activeGroupElementId: string) {\n this.activeGroupElementId = activeGroupElementId\n },\n \n setCanvasPercentage(percentage: number) {\n this.canvasPercentage = percentage\n },\n \n setCanvasScale(scale: number) {\n this.canvasScale = scale\n },\n \n setCanvasDragged(isDragged: boolean) {\n this.canvasDragged = isDragged\n },\n \n setThumbnailsFocus(isFocus: boolean) {\n this.thumbnailsFocus = isFocus\n },\n \n setEditorareaFocus(isFocus: boolean) {\n this.editorAreaFocus = isFocus\n },\n \n setDisableHotkeysState(disable: boolean) {\n this.disableHotkeys = disable\n },\n \n setGridLinesState(show: boolean) {\n this.showGridLines = show\n },\n \n setRulerState(show: boolean) {\n this.showRuler = show\n },\n \n setCreatingElement(element: CreatingElement | null) {\n this.creatingElement = element\n },\n \n setAvailableFonts() {\n this.availableFonts = SYS_FONTS.filter(font => isSupportFont(font.value))\n },\n \n setToolbarState(toolbarState: ToolbarStates) {\n this.toolbarState = toolbarState\n },\n \n setClipingImageElementId(elId: string) {\n this.clipingImageElementId = elId\n },\n \n setRichtextAttrs(attrs: TextAttrs) {\n this.richTextAttrs = attrs\n },\n \n setSelectedTableCells(cells: string[]) {\n this.selectedTableCells = cells\n },\n \n setScalingState(isScaling: boolean) {\n this.isScaling = isScaling\n },\n \n updateSelectedSlidesIndex(selectedSlidesIndex: number[]) {\n this.selectedSlidesIndex = selectedSlidesIndex\n },\n\n setDialogForExport(type: DialogForExportTypes) {\n this.dialogForExport = type\n },\n },\n})","import Dexie from 'dexie'\nimport { Slide } from '@/types/slides'\n\nexport interface Snapshot {\n index: number;\n slides: Slide[];\n}\n\nclass SnapshotDatabase extends Dexie {\n public snapshots: Dexie.Table\n\n public constructor() {\n super('SnapshotDatabase')\n this.version(1).stores({\n snapshots: '++id'\n })\n this.snapshots = this.table('snapshots')\n }\n}\n\nexport const snapshotDB = new SnapshotDatabase()","import { defineStore } from 'pinia'\nimport { IndexableTypeArray } from 'dexie'\nimport { snapshotDB, Snapshot } from '@/utils/database'\n\nimport { useSlidesStore } from './slides'\nimport { useMainStore } from './main'\n\nexport interface ScreenState {\n snapshotCursor: number;\n snapshotLength: number;\n}\n\nexport const useSnapshotStore = defineStore('snapshot', {\n state: (): ScreenState => ({\n snapshotCursor: -1, // 历史快照指针\n snapshotLength: 0, // 历史快照长度\n }),\n\n getters: {\n canUndo(state) {\n return state.snapshotCursor > 0\n },\n canRedo(state) {\n return state.snapshotCursor < state.snapshotLength - 1\n },\n },\n\n actions: {\n setSnapshotCursor(cursor: number) {\n this.snapshotCursor = cursor\n },\n setSnapshotLength(length: number) {\n this.snapshotLength = length\n },\n\n async initSnapshotDatabase() {\n const slidesStore = useSlidesStore()\n\n const snapshots: Snapshot[] = await snapshotDB.snapshots.orderBy('id').toArray()\n const lastSnapshot = snapshots.slice(-1)[0]\n \n if (lastSnapshot) {\n snapshotDB.snapshots.clear()\n }\n \n const newFirstSnapshot = {\n index: slidesStore.slideIndex,\n slides: slidesStore.slides,\n }\n await snapshotDB.snapshots.add(newFirstSnapshot)\n this.setSnapshotCursor(0)\n this.setSnapshotLength(1)\n },\n \n async addSnapshot() {\n const slidesStore = useSlidesStore()\n\n // 获取当前indexeddb中全部快照的ID\n const allKeys = await snapshotDB.snapshots.orderBy('id').keys()\n \n let needDeleteKeys: IndexableTypeArray = []\n \n // 记录需要删除的快照ID\n // 若当前快照指针不处在最后一位,那么再添加快照时,应该将当前指针位置后面的快照全部删除,对应的实际情况是:\n // 用户撤回多次后,再进行操作(添加快照),此时原先被撤销的快照都应该被删除\n if (this.snapshotCursor >= 0 && this.snapshotCursor < allKeys.length - 1) {\n needDeleteKeys = allKeys.slice(this.snapshotCursor + 1)\n }\n \n // 添加新快照\n const snapshot = {\n index: slidesStore.slideIndex,\n slides: slidesStore.slides,\n }\n await snapshotDB.snapshots.add(snapshot)\n \n // 计算当前快照长度,用于设置快照指针的位置(此时指针应该处在最后一位,即:快照长度 - 1)\n let snapshotLength = allKeys.length - needDeleteKeys.length + 1\n \n // 快照数量超过长度限制时,应该将头部多余的快照删除\n const snapshotLengthLimit = 20\n if (snapshotLength > snapshotLengthLimit) {\n needDeleteKeys.push(allKeys[0])\n snapshotLength--\n }\n \n // 快照数大于1时,需要保证撤回操作后维持页面焦点不变:也就是将倒数第二个快照对应的索引设置为当前页的索引\n // https://github.com/pipipi-pikachu/PPTist/issues/27\n if (snapshotLength >= 2) {\n snapshotDB.snapshots.update(allKeys[snapshotLength - 2] as number, { index: slidesStore.slideIndex })\n }\n \n await snapshotDB.snapshots.bulkDelete(needDeleteKeys)\n \n this.setSnapshotCursor(snapshotLength - 1)\n this.setSnapshotLength(snapshotLength)\n },\n \n async unDo() {\n if (this.snapshotCursor <= 0) return\n\n const slidesStore = useSlidesStore()\n const mainStore = useMainStore()\n \n const snapshotCursor = this.snapshotCursor - 1\n const snapshots: Snapshot[] = await snapshotDB.snapshots.orderBy('id').toArray()\n const snapshot = snapshots[snapshotCursor]\n const { index, slides } = snapshot\n \n const slideIndex = index > slides.length - 1 ? slides.length - 1 : index\n \n slidesStore.setSlides(slides)\n slidesStore.updateSlideIndex(slideIndex)\n this.setSnapshotCursor(snapshotCursor)\n mainStore.setActiveElementIdList([])\n },\n \n async reDo() {\n if (this.snapshotCursor >= this.snapshotLength - 1) return\n\n const slidesStore = useSlidesStore()\n const mainStore = useMainStore()\n \n const snapshotCursor = this.snapshotCursor + 1\n const snapshots: Snapshot[] = await snapshotDB.snapshots.orderBy('id').toArray()\n const snapshot = snapshots[snapshotCursor]\n const { index, slides } = snapshot\n \n const slideIndex = index > slides.length - 1 ? slides.length - 1 : index\n \n slidesStore.setSlides(slides)\n slidesStore.updateSlideIndex(slideIndex)\n this.setSnapshotCursor(snapshotCursor)\n mainStore.setActiveElementIdList([])\n },\n },\n})","import { defineStore } from 'pinia'\n\nexport interface KeyboardState {\n ctrlKeyState: boolean;\n shiftKeyState: boolean;\n spaceKeyState: boolean;\n}\n\nexport const useKeyboardStore = defineStore('keyboard', {\n state: (): KeyboardState => ({\n ctrlKeyState: false, // ctrl键按下状态\n shiftKeyState: false, // shift键按下状态\n spaceKeyState: false, // space键按下状态\n }),\n\n getters: {\n ctrlOrShiftKeyActive(state) {\n return state.ctrlKeyState || state.shiftKeyState\n },\n },\n\n actions: {\n setCtrlKeyState(active: boolean) {\n this.ctrlKeyState = active\n },\n setShiftKeyState(active: boolean) {\n this.shiftKeyState = active\n },\n setSpaceKeyState(active: boolean) {\n this.spaceKeyState = active\n },\n },\n})","import { defineStore } from 'pinia'\n\nexport interface ScreenState {\n screening: boolean;\n}\n\nexport const useScreenStore = defineStore('screen', {\n state: (): ScreenState => ({\n screening: false, // 是否进入放映状态\n }),\n\n actions: {\n setScreening(screening: boolean) {\n this.screening = screening\n },\n },\n})","import { padStart } from 'lodash'\n\n/**\n * 补足数字位数\n * @param digit 数字\n * @param len 位数\n */\nexport const fillDigit = (digit: number, len: number) => {\n return padStart('' + digit, len, '0')\n}\n\n/**\n * 判断设备\n */\nexport const isPC = () => {\n return !navigator.userAgent.match(/(iPhone|iPod|iPad|Android|Mobile|BlackBerry|Symbian|Windows Phone)/i)\n}","import { resolveComponent as _resolveComponent, createVNode as _createVNode, normalizeStyle as _normalizeStyle, createElementVNode as _createElementVNode, withCtx as _withCtx, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-5ff9efcc\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = { class: \"pptist-editor\" }\nconst _hoisted_2 = { class: \"layout-content\" }\nconst _hoisted_3 = { class: \"layout-content-center\" }\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _component_EditorHeader = _resolveComponent(\"EditorHeader\")!\n const _component_Thumbnails = _resolveComponent(\"Thumbnails\")!\n const _component_CanvasTool = _resolveComponent(\"CanvasTool\")!\n const _component_Canvas = _resolveComponent(\"Canvas\")!\n const _component_Remark = _resolveComponent(\"Remark\")!\n const _component_Toolbar = _resolveComponent(\"Toolbar\")!\n const _component_ExportDialog = _resolveComponent(\"ExportDialog\")!\n const _component_Modal = _resolveComponent(\"Modal\")!\n\n return (_openBlock(), _createElementBlock(_Fragment, null, [\n _createElementVNode(\"div\", _hoisted_1, [\n _createVNode(_component_EditorHeader, { class: \"layout-header\" }),\n _createElementVNode(\"div\", _hoisted_2, [\n _createVNode(_component_Thumbnails, { class: \"layout-content-left\" }),\n _createElementVNode(\"div\", _hoisted_3, [\n _createVNode(_component_CanvasTool, { class: \"center-top\" }),\n _createVNode(_component_Canvas, {\n class: \"center-body\",\n style: _normalizeStyle({ height: `calc(100% - ${_ctx.remarkHeight + 40}px)` })\n }, null, 8, [\"style\"]),\n _createVNode(_component_Remark, {\n class: \"center-bottom\",\n height: _ctx.remarkHeight,\n \"onUpdate:height\": _cache[0] || (_cache[0] = ($event: any) => ((_ctx.remarkHeight) = $event)),\n style: _normalizeStyle({ height: `${_ctx.remarkHeight}px` })\n }, null, 8, [\"height\", \"style\"])\n ]),\n _createVNode(_component_Toolbar, { class: \"layout-content-right\" })\n ])\n ]),\n _createVNode(_component_Modal, {\n visible: !!_ctx.dialogForExport,\n footer: null,\n centered: \"\",\n closable: false,\n width: 680,\n destroyOnClose: \"\",\n onCancel: _cache[1] || (_cache[1] = ($event: any) => (_ctx.closeExportDialog()))\n }, {\n default: _withCtx(() => [\n _createVNode(_component_ExportDialog)\n ]),\n _: 1\n }, 8, [\"visible\"])\n ], 64))\n}","import { IBarChartOptions, ILineChartOptions, IPieChartOptions } from 'chartist'\n\nexport const enum ShapePathFormulasKeys {\n ROUND_RECT = 'roundRect',\n ROUND_RECT_DIAGONAL = 'roundRectDiagonal',\n ROUND_RECT_SINGLE = 'roundRectSingle',\n ROUND_RECT_SAMESIDE = 'roundRectSameSide',\n CUT_RECT_DIAGONAL = 'cutRectDiagonal',\n CUT_RECT_SINGLE = 'cutRectSingle',\n CUT_RECT_SAMESIDE = 'cutRectSameSide',\n MESSAGE = 'message',\n ROUND_MESSAGE = 'roundMessage',\n L = 'L',\n RING_RECT = 'ringRect',\n PLUS = 'plus',\n}\n\nexport const enum ElementTypes {\n TEXT = 'text',\n IMAGE = 'image',\n SHAPE = 'shape',\n LINE = 'line',\n CHART = 'chart',\n TABLE = 'table',\n LATEX = 'latex',\n VIDEO = 'video',\n AUDIO = 'audio',\n}\n\n/**\n * 元素阴影\n * \n * h: 水平偏移量\n * \n * v: 垂直偏移量\n * \n * blur: 模糊程度\n * \n * color: 阴影颜色\n */\nexport interface PPTElementShadow {\n h: number;\n v: number;\n blur: number;\n color: string;\n}\n\n/**\n * 元素边框\n * \n * style?: 边框样式(实线或虚线)\n * \n * width?: 边框宽度\n * \n * color?: 边框颜色\n */\nexport interface PPTElementOutline {\n style?: 'dashed' | 'solid';\n width?: number;\n color?: string;\n}\n\n/**\n * 元素超链接\n * \n * type: 链接类型(网页、幻灯片页面)\n * \n * target: 目标地址(网页链接、幻灯片页面ID)\n */\nexport interface PPTElementLink {\n type: 'web' | 'slide';\n target: string;\n}\n\n\n/**\n * 元素通用属性\n * \n * id: 元素ID\n * \n * left: 元素水平方向位置(距离画布左侧)\n * \n * top: 元素垂直方向位置(距离画布顶部)\n * \n * lock?: 锁定元素\n * \n * groupId?: 组合ID(拥有相同组合ID的元素即为同一组合元素成员)\n * \n * width: 元素宽度\n * \n * height: 元素高度\n * \n * rotate: 旋转角度\n * \n * link?: 超链接\n */\ninterface PPTBaseElement {\n id: string;\n left: number;\n top: number;\n lock?: boolean;\n groupId?: string;\n width: number;\n height: number;\n rotate: number;\n link?: PPTElementLink;\n}\n\n\n/**\n * 文本元素\n * \n * type: 元素类型(text)\n * \n * content: 文本内容(HTML字符串)\n * \n * defaultFontName: 默认字体(会被文本内容中的HTML内联样式覆盖)\n * \n * defaultColor: 默认颜色(会被文本内容中的HTML内联样式覆盖)\n * \n * outline?: 边框\n * \n * fill?: 填充色\n * \n * lineHeight?: 行高(倍),默认1.5\n * \n * wordSpace?: 字间距,默认0\n * \n * opacity?: 不透明度,默认1\n * \n * shadow?: 阴影\n */\nexport interface PPTTextElement extends PPTBaseElement {\n type: 'text';\n content: string;\n defaultFontName: string;\n defaultColor: string;\n outline?: PPTElementOutline;\n fill?: string;\n lineHeight?: number;\n wordSpace?: number;\n opacity?: number;\n shadow?: PPTElementShadow;\n}\n\n\n/**\n * 图片翻转、形状翻转\n * \n * flipH?: 水平翻转\n * \n * flipV?: 垂直翻转\n */\nexport interface ImageOrShapeFlip {\n flipH?: boolean;\n flipV?: boolean;\n}\n\n/**\n * 图片滤镜\n * \n * https://developer.mozilla.org/zh-CN/docs/Web/CSS/filter\n * \n * 'blur'?: 模糊,默认0(px)\n * \n * 'brightness'?: 亮度,默认100(%)\n * \n * 'contrast'?: 对比度,默认100(%)\n * \n * 'grayscale'?: 灰度,默认0(%)\n * \n * 'saturate'?: 饱和度,默认100(%)\n * \n * 'hue-rotate'?: 色相旋转,默认0(deg)\n * \n * 'opacity'?: 不透明度,默认100(%)\n */\nexport interface ImageElementFilters {\n 'blur'?: string;\n 'brightness'?: string;\n 'contrast'?: string;\n 'grayscale'?: string;\n 'saturate'?: string;\n 'hue-rotate'?: string;\n 'opacity'?: string;\n}\n\n/**\n * 图片裁剪\n * \n * range: 裁剪范围,例如:[[10, 10], [90, 90]] 表示裁取原图从左上角 10%, 10% 到 90%, 90% 的范围\n * \n * shape: 裁剪形状,见 configs/imageClip.ts CLIPPATHS \n */\nexport interface ImageElementClip {\n range: [[number, number], [number, number]];\n shape: string;\n}\n\n/**\n * 图片元素\n * \n * type: 元素类型(image)\n * \n * fixedRatio: 固定图片宽高比例\n * \n * src: 图片地址\n * \n * outline?: 边框\n * \n * filters?: 图片滤镜\n * \n * clip?: 裁剪信息\n * \n * flipH?: 水平翻转\n * \n * flipV?: 垂直翻转\n * \n * shadow?: 阴影\n */\nexport interface PPTImageElement extends PPTBaseElement {\n type: 'image';\n fixedRatio: boolean;\n src: string;\n outline?: PPTElementOutline;\n filters?: ImageElementFilters;\n clip?: ImageElementClip;\n flipH?: boolean;\n flipV?: boolean;\n shadow?: PPTElementShadow;\n}\n\n\n/**\n * 形状渐变\n * \n * type: 渐变类型(径向、线性)\n * \n * color: 渐变颜色\n * \n * rotate: 渐变角度(线性渐变)\n */\nexport interface ShapeGradient {\n type: 'linear' | 'radial';\n color: [string, string];\n rotate: number;\n}\n\n/**\n * 形状内文本\n * \n * content: 文本内容(HTML字符串)\n * \n * defaultFontName: 默认字体(会被文本内容中的HTML内联样式覆盖)\n * \n * defaultColor: 默认颜色(会被文本内容中的HTML内联样式覆盖)\n * \n * align: 文本对齐方向(垂直方向)\n */\nexport interface ShapeText {\n content: string;\n defaultFontName: string;\n defaultColor: string;\n align: 'top' | 'middle' | 'bottom';\n}\n\n/**\n * 形状元素\n * \n * type: 元素类型(shape)\n * \n * viewBox: SVG的viewBox属性,例如 [1000, 1000] 表示 '0 0 1000 1000'\n * \n * path: 形状路径,SVG path 的 d 属性\n * \n * fixedRatio: 固定形状宽高比例\n * \n * fill: 填充,不存在渐变时生效\n * \n * gradient?: 渐变,该属性存在时将优先作为填充\n * \n * outline?: 边框\n * \n * opacity?: 不透明度\n * \n * flipH?: 水平翻转\n * \n * flipV?: 垂直翻转\n * \n * shadow?: 阴影\n * \n * special?: 特殊形状(标记一些难以解析的形状,例如路径使用了 L Q C A 以外的类型,该类形状在导出后将变为图片的形式)\n * \n * text?: 形状内文本\n * \n * pathFormula?: 形状路径计算公式\n * 一般情况下,形状的大小变化时仅由宽高基于 viewBox 的缩放比例来调整形状,而 viewBox 本身和 path 不会变化,\n * 但也有一些形状希望能更精确的控制一些关键点的位置,此时就需要提供路径计算公式,通过在缩放时更新 viewBox 并重新计算 path 来重新绘制形状\n */\nexport interface PPTShapeElement extends PPTBaseElement {\n type: 'shape';\n viewBox: [number, number];\n path: string;\n fixedRatio: boolean;\n fill: string;\n gradient?: ShapeGradient;\n outline?: PPTElementOutline;\n opacity?: number;\n flipH?: boolean;\n flipV?: boolean;\n shadow?: PPTElementShadow;\n special?: boolean;\n text?: ShapeText;\n pathFormula?: ShapePathFormulasKeys;\n}\n\n\nexport type LinePoint = '' | 'arrow' | 'dot' \n\n/**\n * 线条元素\n * \n * type: 元素类型(line)\n * \n * start: 起点位置([x, y])\n * \n * end: 终点位置([x, y])\n * \n * style: 线条样式(实线、虚线)\n * \n * color: 线条颜色\n * \n * points: 端点样式([起点样式, 终点样式],可选:无、箭头、圆点)\n * \n * shadow?: 阴影\n * \n * broken?: 折线控制点位置([x, y])\n * \n * curve?: 二次曲线控制点位置([x, y])\n * \n * curve?: 三次曲线控制点位置([[x1, y1], [x2, y2]])\n */\nexport interface PPTLineElement extends Omit {\n type: 'line';\n start: [number, number];\n end: [number, number];\n style: 'solid' | 'dashed';\n color: string;\n points: [LinePoint, LinePoint];\n shadow?: PPTElementShadow;\n broken?: [number, number];\n curve?: [number, number];\n cubic?: [[number, number], [number, number]];\n}\n\n\nexport type PresetChartType = 'bar' | 'horizontalBar' | 'line' | 'area' | 'scatter' | 'pie' | 'ring'\nexport type ChartType = 'bar' | 'line' | 'pie'\nexport type ChartOptions = ILineChartOptions & IBarChartOptions & IPieChartOptions\nexport interface ChartData {\n labels: string[];\n legends: string[];\n series: number[][];\n}\n\n/**\n * 图表元素\n * \n * type: 元素类型(chart)\n * \n * fill?: 填充色\n * \n * chartType: 图表基础类型(bar/line/pie),所有图表类型都是由这三种基本类型衍生而来\n * \n * data: 图表数据\n * \n * options?: 图表配置项\n * \n * outline?: 边框\n * \n * themeColor: 主题色\n * \n * gridColor?: 网格&坐标颜色\n * \n * legend?: 图例/位置\n */\nexport interface PPTChartElement extends PPTBaseElement {\n type: 'chart';\n fill?: string;\n chartType: ChartType;\n data: ChartData;\n options?: ChartOptions;\n outline?: PPTElementOutline;\n themeColor: string[];\n gridColor?: string;\n legend?: '' | 'top' | 'bottom',\n}\n\n\n/**\n * 表格单元格样式\n * \n * bold?: 加粗\n * \n * em?: 斜体\n * \n * underline?: 下划线\n * \n * strikethrough?: 删除线\n * \n * color?: 字体颜色\n * \n * backcolor?: 填充色\n * \n * fontsize?: 字体大小\n * \n * fontname?: 字体\n * \n * align?: 对齐方式\n */\nexport interface TableCellStyle {\n bold?: boolean;\n em?: boolean;\n underline?: boolean;\n strikethrough?: boolean;\n color?: string;\n backcolor?: string;\n fontsize?: string;\n fontname?: string;\n align?: 'left' | 'center' | 'right';\n}\n\n\n/**\n * 表格单元格\n * \n * id: 单元格ID\n * \n * colspan: 合并列数\n * \n * rowspan: 合并行数\n * \n * text: 文字内容\n * \n * style?: 单元格样式\n */\nexport interface TableCell {\n id: string;\n colspan: number;\n rowspan: number;\n text: string;\n style?: TableCellStyle;\n}\n\n/**\n * 表格主题\n * \n * color: 主题色\n * \n * rowHeader: 标题行\n * \n * rowFooter: 汇总行\n * \n * colHeader: 第一列\n * \n * colFooter: 最后一列\n */\nexport interface TableTheme {\n color: string;\n rowHeader: boolean;\n rowFooter: boolean;\n colHeader: boolean;\n colFooter: boolean;\n}\n\n/**\n * 表格元素\n * \n * type: 元素类型(table)\n * \n * outline: 边框\n * \n * theme?: 主题\n * \n * colWidths: 列宽数组,如[30, 50, 20]表示三列宽度分别为30%, 50%, 20%\n * \n * data: 表格数据\n */\nexport interface PPTTableElement extends PPTBaseElement {\n type: 'table';\n outline: PPTElementOutline;\n theme?: TableTheme;\n colWidths: number[];\n data: TableCell[][];\n}\n\n\n/**\n * LaTeX元素(公式)\n * \n * type: 元素类型(latex)\n * \n * latex: latex代码\n * \n * path: svg path\n * \n * color: 颜色\n * \n * strokeWidth: 路径宽度\n * \n * viewBox: SVG的viewBox属性\n * \n * fixedRatio: 固定形状宽高比例\n */\nexport interface PPTLatexElement extends PPTBaseElement {\n type: 'latex';\n latex: string;\n path: string;\n color: string;\n strokeWidth: number;\n viewBox: [number, number];\n fixedRatio: boolean;\n}\n\n/**\n * 视频元素\n * \n * type: 元素类型(video)\n * \n * src: 视频地址\n * \n * poster: 预览封面\n */\nexport interface PPTVideoElement extends PPTBaseElement {\n type: 'video';\n src: string;\n poster?: string;\n}\n\n/**\n * 音频元素\n * \n * type: 元素类型(audio)\n * \n * fixedRatio: 固定图标宽高比例\n * \n * color: 图标颜色\n * \n * loop: 循环播放\n * \n * autoplay: 自动播放\n * \n * src: 音频地址\n */\nexport interface PPTAudioElement extends PPTBaseElement {\n type: 'audio';\n fixedRatio: boolean;\n color: string,\n loop: boolean,\n autoplay: boolean,\n src: string;\n}\n\n\nexport type PPTElement = PPTTextElement | PPTImageElement | PPTShapeElement | PPTLineElement | PPTChartElement | PPTTableElement | PPTLatexElement | PPTVideoElement | PPTAudioElement\n\n\n/**\n * 元素动画\n * \n * id: 动画id\n * \n * elId: 元素ID\n * \n * effect: 动画效果\n * \n * type: 动画类型(入场、退场、强调)\n * \n * duration: 动画持续时间\n * \n * trigger: 动画触发方式(click - 单击时、meantime - 与上一动画同时、auto - 上一动画之后)\n */\nexport interface PPTAnimation {\n id: string;\n elId: string;\n effect: string;\n type: 'in' | 'out' | 'attention';\n duration: number;\n trigger: 'click' | 'meantime' | 'auto';\n}\n\n/**\n * 幻灯片背景\n * \n * type: 背景类型(纯色、图片、渐变)\n * \n * color?: 背景颜色(纯色)\n * \n * image?: 图片地址(图片)\n * \n * imageSize?: 图片填充方式\n * \n * gradientType?: 渐变类型(线性、径向)\n * \n * gradientColor?: 渐变颜色\n * \n * gradientRotate?: 渐变角度(线性)\n */\nexport interface SlideBackground {\n type: 'solid' | 'image' | 'gradient';\n color?: string;\n image?: string;\n imageSize?: 'cover' | 'contain' | 'repeat';\n gradientType?: 'linear' | 'radial';\n gradientColor?: [string, string];\n gradientRotate?: number;\n}\n\n\nexport type TurningMode = 'no' | 'fade' | 'slideX' | 'slideY'\n\n/**\n * 幻灯片页面\n * \n * id: 页面ID\n * \n * elements: 元素集合\n * \n * remark?: 备注\n * \n * background?: 页面背景\n * \n * animations?: 元素动画集合\n * \n * turningMode?: 翻页方式\n */\nexport interface Slide {\n id: string;\n elements: PPTElement[];\n remark?: string;\n background?: SlideBackground;\n animations?: PPTAnimation[];\n turningMode?: TurningMode;\n}\n\n/**\n * 幻灯片主题\n * \n * backgroundColor: 页面背景颜色\n * \n * themeColor: 主题色,用于默认创建的形状颜色等\n * \n * fontColor: 字体颜色\n * \n * fontName: 字体\n */\nexport interface SlideTheme {\n backgroundColor: string;\n themeColor: string;\n fontColor: string;\n fontName: string;\n}","import CryptoJS from 'crypto-js'\n\nconst CRYPTO_KEY = 'pptist'\n\n/**\n * 加密\n * @param msg 待加密字符串\n */\nexport const encrypt = (msg: string) => {\n return CryptoJS.AES.encrypt(msg, CRYPTO_KEY).toString()\n}\n\n/**\n * 解密\n * @param ciphertext 待解密字符串\n */\nexport const decrypt = (ciphertext: string) => {\n const bytes = CryptoJS.AES.decrypt(ciphertext, CRYPTO_KEY)\n return bytes.toString(CryptoJS.enc.Utf8)\n}","import Clipboard from 'clipboard'\nimport { decrypt } from '@/utils/crypto'\n\n/**\n * 复制文本到剪贴板\n * @param text 文本内容\n */\nexport const copyText = (text: string) => {\n return new Promise((resolve, reject) => {\n const fakeElement = document.createElement('button')\n const clipboard = new Clipboard(fakeElement, {\n text: () => text,\n action: () => 'copy',\n container: document.body,\n })\n clipboard.on('success', e => {\n clipboard.destroy()\n resolve(e)\n })\n clipboard.on('error', e => {\n clipboard.destroy()\n reject(e)\n })\n document.body.appendChild(fakeElement)\n fakeElement.click()\n document.body.removeChild(fakeElement)\n })\n}\n\n// 读取剪贴板\nexport const readClipboard = (): Promise => {\n return new Promise((resolve, reject) => {\n if (navigator.clipboard?.readText) {\n navigator.clipboard.readText().then(text => {\n if (!text) reject('剪贴板为空或者不包含文本')\n return resolve(text)\n })\n }\n else reject('浏览器不支持或禁止访问剪贴板,请使用快捷键 Ctrl + V')\n })\n}\n\n// 解析加密后的剪贴板内容\nexport const pasteCustomClipboardString = (text: string) => {\n let clipboardData\n try {\n clipboardData = JSON.parse(decrypt(text))\n }\n catch {\n clipboardData = text\n }\n\n return clipboardData\n}\n\n// 尝试解析剪贴板内容是否为Excel表格(或类似的)数据格式\nexport const pasteExcelClipboardString = (text: string): string[][] | null => {\n const lines: string[] = text.split('\\r\\n')\n\n if (lines[lines.length - 1] === '') lines.pop()\n\n let colCount = -1\n const data: string[][] = []\n for (const index in lines) {\n data[index] = lines[index].split('\\t')\n\n if (data[index].length === 1) return null\n if (colCount === -1) colCount = data[index].length\n else if (colCount !== data[index].length) return null\n }\n return data\n}","import tinycolor from 'tinycolor2'\r\nimport { nanoid } from 'nanoid'\r\nimport { PPTElement, PPTLineElement } from '@/types/slides'\r\n\r\ninterface RotatedElementData {\r\n left: number;\r\n top: number;\r\n width: number;\r\n height: number;\r\n rotate: number;\r\n}\r\n\r\n/**\r\n * 计算元素在画布中的矩形范围旋转后的新位置范围\r\n * @param element 元素的位置大小和旋转角度信息\r\n */\r\nexport const getRectRotatedRange = (element: RotatedElementData) => {\r\n const { left, top, width, height, rotate = 0 } = element\r\n\r\n const radius = Math.sqrt( Math.pow(width, 2) + Math.pow(height, 2) ) / 2\r\n const auxiliaryAngle = Math.atan(height / width) * 180 / Math.PI\r\n\r\n const tlbraRadian = (180 - rotate - auxiliaryAngle) * Math.PI / 180\r\n const trblaRadian = (auxiliaryAngle - rotate) * Math.PI / 180\r\n\r\n const middleLeft = left + width / 2\r\n const middleTop = top + height / 2\r\n\r\n const xAxis = [\r\n middleLeft + radius * Math.cos(tlbraRadian),\r\n middleLeft + radius * Math.cos(trblaRadian),\r\n middleLeft - radius * Math.cos(tlbraRadian),\r\n middleLeft - radius * Math.cos(trblaRadian),\r\n ]\r\n const yAxis = [\r\n middleTop - radius * Math.sin(tlbraRadian),\r\n middleTop - radius * Math.sin(trblaRadian),\r\n middleTop + radius * Math.sin(tlbraRadian),\r\n middleTop + radius * Math.sin(trblaRadian),\r\n ]\r\n\r\n return {\r\n xRange: [Math.min(...xAxis), Math.max(...xAxis)],\r\n yRange: [Math.min(...yAxis), Math.max(...yAxis)],\r\n }\r\n}\r\n\r\n/**\r\n * 计算元素在画布中的矩形范围旋转后的新位置与旋转之前位置的偏离距离\r\n * @param element 元素的位置大小和旋转角度信息\r\n */\r\nexport const getRectRotatedOffset = (element: RotatedElementData) => {\r\n const { xRange: originXRange, yRange: originYRange } = getRectRotatedRange({\r\n left: element.left,\r\n top: element.top,\r\n width: element.width,\r\n height: element.height,\r\n rotate: 0,\r\n })\r\n const { xRange: rotatedXRange, yRange: rotatedYRange } = getRectRotatedRange({\r\n left: element.left,\r\n top: element.top,\r\n width: element.width,\r\n height: element.height,\r\n rotate: element.rotate,\r\n })\r\n return {\r\n offsetX: rotatedXRange[0] - originXRange[0],\r\n offsetY: rotatedYRange[0] - originYRange[0],\r\n }\r\n}\r\n\r\n/**\r\n * 计算元素在画布中的位置范围\r\n * @param element 元素信息\r\n */\r\nexport const getElementRange = (element: PPTElement) => {\r\n let minX, maxX, minY, maxY\r\n\r\n if (element.type === 'line') {\r\n minX = element.left\r\n maxX = element.left + Math.max(element.start[0], element.end[0])\r\n minY = element.top\r\n maxY = element.top + Math.max(element.start[1], element.end[1])\r\n }\r\n else if ('rotate' in element && element.rotate) {\r\n const { left, top, width, height, rotate } = element\r\n const { xRange, yRange } = getRectRotatedRange({ left, top, width, height, rotate })\r\n minX = xRange[0]\r\n maxX = xRange[1]\r\n minY = yRange[0]\r\n maxY = yRange[1]\r\n }\r\n else {\r\n minX = element.left\r\n maxX = element.left + element.width\r\n minY = element.top\r\n maxY = element.top + element.height\r\n }\r\n return { minX, maxX, minY, maxY }\r\n}\r\n\r\n/**\r\n * 计算一组元素在画布中的位置范围\r\n * @param elementList 一组元素信息\r\n */\r\nexport const getElementListRange = (elementList: PPTElement[]) => {\r\n const leftValues: number[] = []\r\n const topValues: number[] = []\r\n const rightValues: number[] = []\r\n const bottomValues: number[] = []\r\n\r\n elementList.forEach(element => {\r\n const { minX, maxX, minY, maxY } = getElementRange(element)\r\n leftValues.push(minX)\r\n topValues.push(minY)\r\n rightValues.push(maxX)\r\n bottomValues.push(maxY)\r\n })\r\n\r\n const minX = Math.min(...leftValues)\r\n const maxX = Math.max(...rightValues)\r\n const minY = Math.min(...topValues)\r\n const maxY = Math.max(...bottomValues)\r\n\r\n return { minX, maxX, minY, maxY }\r\n}\r\n\r\nexport interface AlignLine {\r\n value: number;\r\n range: [number, number];\r\n}\r\n\r\n/**\r\n * 将一组对齐吸附线进行去重:同位置的的多条对齐吸附线仅留下一条,取该位置所有对齐吸附线的最大值和最小值为新的范围\r\n * @param lines 一组对齐吸附线信息\r\n */\r\nexport const uniqAlignLines = (lines: AlignLine[]) => {\r\n const uniqLines: AlignLine[] = []\r\n lines.forEach(line => {\r\n const index = uniqLines.findIndex(_line => _line.value === line.value)\r\n if (index === -1) uniqLines.push(line)\r\n else {\r\n const uniqLine = uniqLines[index]\r\n const rangeMin = Math.min(uniqLine.range[0], line.range[0])\r\n const rangeMax = Math.max(uniqLine.range[1], line.range[1])\r\n const range: [number, number] = [rangeMin, rangeMax]\r\n const _line = { value: line.value, range }\r\n uniqLines[index] = _line\r\n }\r\n })\r\n return uniqLines\r\n}\r\n\r\n/**\r\n * 以元素列表为基础,为每一个元素生成新的ID,并关联到旧ID形成一个字典\r\n * 主要用于复制元素时,维持数据中各处元素ID原有的关系\r\n * 例如:原本两个组合的元素拥有相同的groupId,复制后依然会拥有另一个相同的groupId\r\n * @param elements 元素列表数据\r\n */\r\nexport const createElementIdMap = (elements: PPTElement[]) => {\r\n const groupIdMap = {}\r\n const elIdMap = {}\r\n for (const element of elements) {\r\n const groupId = element.groupId\r\n if (groupId && !groupIdMap[groupId]) {\r\n groupIdMap[groupId] = nanoid(10)\r\n }\r\n elIdMap[element.id] = nanoid(10)\r\n }\r\n return {\r\n groupIdMap,\r\n elIdMap,\r\n }\r\n}\r\n\r\n/**\r\n * 根据表格的主题色,获取对应用于配色的子颜色\r\n * @param themeColor 主题色\r\n */\r\nexport const getTableSubThemeColor = (themeColor: string) => {\r\n const rgba = tinycolor(themeColor)\r\n return [\r\n rgba.setAlpha(0.3).toRgbString(),\r\n rgba.setAlpha(0.1).toRgbString(),\r\n ]\r\n}\r\n\r\n/**\r\n * 获取线条元素路径字符串\r\n * @param element 线条元素\r\n */\r\nexport const getLineElementPath = (element: PPTLineElement) => {\r\n const start = element.start.join(',')\r\n const end = element.end.join(',')\r\n if (element.broken) {\r\n const mid = element.broken.join(',')\r\n return `M${start} L${mid} L${end}`\r\n }\r\n else if (element.curve) {\r\n const mid = element.curve.join(',')\r\n return `M${start} Q${mid} ${end}`\r\n }\r\n else if (element.cubic) {\r\n const [c1, c2] = element.cubic\r\n const p1 = c1.join(',')\r\n const p2 = c2.join(',')\r\n return `M${start} C${p1} ${p2} ${end}`\r\n }\r\n return `M${start} L${end}`\r\n}","/**\r\n * 将普通文本转为带段落信息的HTML字符串\r\n * @param text 文本\r\n */\r\nexport const parseText2Paragraphs = (text: string) => {\r\n const htmlText = text.replace(/[\\n\\r]+/g, '
')\r\n const paragraphs = htmlText.split('
')\r\n let string = ''\r\n for (const paragraph of paragraphs) {\r\n if (paragraph) string += `${paragraph}
`\r\n }\r\n return string\r\n}","import { debounce, throttle} from 'lodash'\nimport { useSnapshotStore } from '@/store'\n\nexport default () => {\n const snapshotStore = useSnapshotStore()\n\n // 添加历史快照(历史记录)\n const addHistorySnapshot = debounce(function() {\n snapshotStore.addSnapshot()\n }, 300, { trailing: true })\n\n // 重做\n const redo = throttle(function() {\n snapshotStore.reDo()\n }, 100, { leading: true, trailing: false })\n\n // 撤销\n const undo = throttle(function() {\n snapshotStore.unDo()\n }, 100, { leading: true, trailing: false })\n\n return {\n addHistorySnapshot,\n redo,\n undo,\n }\n}","interface ImageSize {\n width: number;\n height: number;\n}\n\n/**\n * 获取图片的原始宽高\n * @param src 图片地址\n */\nexport const getImageSize = (src: string): Promise => {\n return new Promise(resolve => {\n const img = document.createElement('img')\n img.src = src\n img.style.opacity = '0'\n document.body.appendChild(img)\n\n img.onload = () => {\n const imgWidth = img.clientWidth\n const imgHeight = img.clientHeight\n \n img.onload = null\n img.onerror = null\n\n document.body.removeChild(img)\n\n resolve({ width: imgWidth, height: imgHeight })\n }\n\n img.onerror = () => {\n img.onload = null\n img.onerror = null\n }\n })\n}\n\n/**\n * 读取图片文件的dataURL\n * @param file 图片文件\n */\nexport const getImageDataURL = (file: File): Promise => {\n return new Promise(resolve => {\n const reader = new FileReader()\n reader.addEventListener('load', () => {\n resolve(reader.result as string)\n })\n reader.readAsDataURL(file)\n })\n}","export const VIEWPORT_SIZE = 1000","import { ShapePathFormulasKeys } from '@/types/slides'\n\nexport interface ShapePoolItem {\n viewBox: [number, number];\n path: string;\n special?: boolean;\n pathFormula?: ShapePathFormulasKeys;\n outlined?: boolean;\n}\n\ninterface ShapeListItem {\n type: string;\n children: ShapePoolItem[];\n}\n\nexport const SHAPE_PATH_FORMULAS = {\n [ShapePathFormulasKeys.ROUND_RECT]: (width: number, height: number) => {\n const radius = Math.min(width, height) / 8\n return `M ${radius} 0 L ${width - radius} 0 Q ${width} 0 ${width} ${radius} L ${width} ${height - radius} Q ${width} ${height} ${width - radius} ${height} L ${radius} ${height} Q 0 ${height} 0 ${height - radius} L 0 ${radius} Q 0 0 ${radius} 0 Z`\n },\n [ShapePathFormulasKeys.CUT_RECT_DIAGONAL]: (width: number, height: number) => {\n const radius = Math.min(width, height) / 5\n return `M 0 ${height - radius} L 0 0 L ${width - radius} 0 L ${width} ${radius} L ${width} ${height} L ${radius} ${height} Z`\n },\n [ShapePathFormulasKeys.CUT_RECT_SINGLE]: (width: number, height: number) => {\n const radius = Math.min(width, height) / 5\n return `M 0 ${height} L 0 0 L ${width - radius} 0 L ${width} ${radius} L ${width} ${height} Z`\n },\n [ShapePathFormulasKeys.CUT_RECT_SAMESIDE]: (width: number, height: number) => {\n const radius = Math.min(width, height) / 5\n return `M 0 ${radius} L ${radius} 0 L ${width - radius} 0 L ${width} ${radius} L ${width} ${height} L 0 ${height} Z`\n },\n [ShapePathFormulasKeys.ROUND_RECT_DIAGONAL]: (width: number, height: number) => {\n const radius = Math.min(width, height) / 8\n return `M 0 0 L ${width - radius} 0 Q ${width} 0 ${width} ${radius} L ${width} ${height} L ${radius} ${height} Q 0 ${height} 0 ${height - radius} L 0 0 Z`\n },\n [ShapePathFormulasKeys.ROUND_RECT_SINGLE]: (width: number, height: number) => {\n const radius = Math.min(width, height) / 8\n return `M 0 0 L ${width - radius} 0 Q ${width} 0 ${width} ${radius} L ${width} ${height} L 0 ${height} L 0 0 Z`\n },\n [ShapePathFormulasKeys.ROUND_RECT_SAMESIDE]: (width: number, height: number) => {\n const radius = Math.min(width, height) / 8\n return `M 0 ${radius} Q 0 0 ${radius} 0 L ${width - radius} 0 Q ${width} 0 ${width} ${radius} L ${width} ${height} L 0 ${height} Z`\n },\n [ShapePathFormulasKeys.MESSAGE]: (width: number, height: number) => {\n const arrowWidth = width / 5\n const arrowheight = height / 5\n return `M 0 0 L ${width} 0 L ${width} ${height - arrowheight} L ${width / 2} ${height - arrowheight} L ${width / 2 - arrowWidth} ${height} L ${width / 2 - arrowWidth} ${height - arrowheight} L 0 ${height - arrowheight} Z`\n },\n [ShapePathFormulasKeys.ROUND_MESSAGE]: (width: number, height: number) => {\n const radius = Math.min(width, height) / 8\n const arrowWidth = width / 5\n const arrowheight = height / 5\n return `M 0 ${radius} Q 0 0 ${radius} 0 L ${width - radius} 0 Q ${width} 0 ${width} ${radius} L ${width} ${height - radius - arrowheight} Q ${width} ${height - arrowheight} ${width - radius} ${height - arrowheight} L ${width / 2} ${height - arrowheight} L ${width / 2 - arrowWidth} ${height} L ${width / 2 - arrowWidth} ${height - arrowheight} L ${radius} ${height - arrowheight} Q 0 ${height - arrowheight} 0 ${height - radius - arrowheight} L 0 ${radius} Z`\n },\n [ShapePathFormulasKeys.L]: (width: number, height: number) => {\n const lineWidth = Math.min(width, height) / 4\n return `M 0 0 L 0 ${height} L ${width} ${height} L ${width} ${height - lineWidth} L ${lineWidth} ${height - lineWidth} L ${lineWidth} 0 Z`\n },\n [ShapePathFormulasKeys.RING_RECT]: (width: number, height: number) => {\n const lineWidth = Math.min(width, height) / 4\n return `M 0 0 ${width} 0 ${width} ${height} L 0 ${height} L 0 0 Z M ${lineWidth} ${lineWidth} L ${lineWidth} ${height - lineWidth} L ${width - lineWidth} ${height - lineWidth} L ${width - lineWidth} ${lineWidth} Z`\n },\n [ShapePathFormulasKeys.PLUS]: (width: number, height: number) => {\n const lineWidth = Math.min(width, height) / 4\n return `M ${width / 2 - lineWidth / 2} 0 L ${width / 2 - lineWidth / 2} ${height / 2 - lineWidth / 2} L 0 ${height / 2 - lineWidth / 2} L 0 ${height / 2 + lineWidth / 2} L ${width / 2 - lineWidth / 2} ${height / 2 + lineWidth / 2} L ${width / 2 - lineWidth / 2} ${height} L ${width / 2 + lineWidth / 2} ${height} L ${width / 2 + lineWidth / 2} ${height / 2 + lineWidth / 2} L ${width} ${height / 2 + lineWidth / 2} L ${width} ${height / 2 - lineWidth / 2} L ${width / 2 + lineWidth / 2} ${height / 2 - lineWidth / 2} L ${width / 2 + lineWidth / 2} 0 Z`\n },\n}\n\nexport const SHAPE_LIST: ShapeListItem[] = [\n {\n type: '矩形',\n children: [\n {\n viewBox: [200, 200],\n path: 'M 0 0 L 200 0 L 200 200 L 0 200 Z'\n },\n {\n viewBox: [200, 200],\n path: 'M 50 0 L 150 0 Q 200 0 200 50 L 200 150 Q 200 200 150 200 L 50 200 Q 0 200 0 150 L 0 50 Q 0 0 50 0 Z',\n pathFormula: ShapePathFormulasKeys.ROUND_RECT,\n },\n {\n viewBox: [200, 200],\n path: 'M 0 150 L 0 0 L 150 0 L 200 50 L 200 200 L 50 200 Z',\n pathFormula: ShapePathFormulasKeys.CUT_RECT_DIAGONAL,\n },\n {\n viewBox: [200, 200],\n path: 'M 0 200 L 0 0 L 150 0 L 200 50 L 200 200 Z',\n pathFormula: ShapePathFormulasKeys.CUT_RECT_SINGLE,\n },\n {\n viewBox: [200, 200],\n path: 'M 0 50 L 50 0 L 150 0 L 200 50 L 200 200 L 0 200 Z',\n pathFormula: ShapePathFormulasKeys.CUT_RECT_SAMESIDE,\n },\n {\n viewBox: [200, 200],\n path: 'M 0 0 L 150 0 Q 200 0 200 50 L 200 200 L 50 200 Q 0 200 0 150 L 0 0 Z',\n pathFormula: ShapePathFormulasKeys.ROUND_RECT_DIAGONAL,\n },\n {\n viewBox: [200, 200],\n path: 'M 0 0 L 150 0 Q 200 0 200 50 L 200 200 L 0 200 L 0 0 Z',\n pathFormula: ShapePathFormulasKeys.ROUND_RECT_SINGLE,\n },\n {\n viewBox: [200, 200],\n path: 'M 0 50 Q 0 0 50 0 L 150 0 Q 200 0 200 50 L 200 200 L 0 200 Z',\n pathFormula: ShapePathFormulasKeys.ROUND_RECT_SAMESIDE,\n },\n ]\n },\n\n {\n type: '常用形状',\n children: [\n {\n viewBox: [200, 200],\n path: 'M 100 0 A 50 50 0 1 1 100 200 A 50 50 0 1 1 100 0 Z'\n },\n {\n viewBox: [200, 200],\n path: 'M 0 200 A 50 100 0 1 1 200 200 L 0 200 Z',\n },\n {\n viewBox: [200, 200],\n path: 'M 40 20 A 100 100 0 1 0 200 100 L 100 100 L 40 20 Z'\n },\n {\n viewBox: [200, 200],\n path: 'M 100 0 A 100 100 102 1 0 200 100 L 100 100 L 100 0 Z'\n },\n {\n viewBox: [200, 200],\n path: 'M 160 20 A 100 100 0 1 0 200 100 L 100 100 L 160 20 Z',\n },\n {\n viewBox: [200, 200],\n path: 'M 100 0 A 100 100 102 1 0 200 100 L 100 0 Z'\n },\n {\n viewBox: [200, 200],\n path: 'M 100 0 A 100 100 102 1 0 200 100 L 200 0 L 100 0 Z'\n },\n {\n viewBox: [200, 200],\n path: 'M 0 0 L 200 0 Q 200 200 0 200 L 0 0 Z'\n },\n {\n viewBox: [200, 200],\n path: 'M 100 0 L 0 200 L 200 200 L 100 0 Z'\n },\n {\n viewBox: [200, 200],\n path: 'M 0 0 L 0 200 L 200 200 Z'\n },\n {\n viewBox: [200, 200],\n path: 'M 50 0 L 200 0 L 150 200 L 0 200 L 50 0 Z'\n },\n {\n viewBox: [200, 200],\n path: 'M 0 0 L 150 0 L 200 200 L 50 200 L 0 0 Z'\n },\n {\n viewBox: [200, 200],\n path: 'M 50 0 L 150 0 L 200 200 L 0 200 L 50 0 Z'\n },\n {\n viewBox: [200, 200],\n path: 'M 100 0 L 0 100 L 100 200 L 200 100 L 100 0 Z'\n },\n {\n viewBox: [200, 200],\n path: 'M 100 0 L 0 90 L 50 200 L 150 200 L 200 90 L 100 0 Z'\n },\n {\n viewBox: [200, 200],\n path: 'M 100 0 L 0 60 L 0 140 L 100 200 L 200 140 L 200 60 L 100 0 Z'\n },\n {\n viewBox: [200, 200],\n path: 'M 60 0 L 140 0 L 200 60 L 200 140 L 140 200 L 60 200 L 0 140 L 0 60 L 60 0 Z'\n },\n {\n viewBox: [200, 200],\n path: 'M 75 0 L 125 0 L 175 25 L 200 75 L 200 125 L 175 175 L 125 200 L 75 200 L 25 175 L 0 125 L 0 75 L 25 25 L 75 0 Z'\n },\n {\n viewBox: [200, 200],\n path: 'M 100 0 L 0 50 L 0 200 L 200 200 L 200 50 L 100 0 Z'\n },\n {\n viewBox: [200, 200],\n path: 'M 150 0 A 50 100 0 1 1 150 200 L 0 200 L 0 0 L 150 0 Z'\n },\n {\n viewBox: [200, 200],\n path: 'M 50 0 A 25 50 0 1 0 50 200 L 150 200 A 25 50 0 1 0 150 0 L 50 0 Z'\n },\n {\n viewBox: [200, 200],\n path: 'M 150 0 A 50 100 0 1 1 150 200 L 0 200 A 50 100 0 0 0 0 0 L 150 0 Z'\n },\n {\n viewBox: [200, 200],\n path: 'M 200 100 L 150 0 L 0 0 L 50 100 L 0 200 L 150 200 L 200 100 Z'\n },\n {\n viewBox: [200, 200],\n path: 'M 200 0 L 200 200 L 0 200 L 0 100 L 200 0 Z'\n },\n {\n viewBox: [200, 200],\n path: 'M 0 0 L 200 100 L 200 200 L 0 200 L 0 0 Z'\n },\n {\n viewBox: [200, 200],\n path: 'M 200 0 L 100 0 L 0 100 L 0 200 L 200 0 Z'\n },\n {\n viewBox: [200, 200],\n path: 'M 50 0 L 150 0 L 150 50 L 200 50 L 200 150 L 150 150 L 150 200 L 50 200 L 50 150 L 0 150 L 0 50 L 50 50 L 50 0 Z'\n },\n {\n viewBox: [200, 200],\n path: 'M 0 0 L 0 200 L 200 200 L 200 140 L 60 140 L 60 0 L 0 0 Z',\n pathFormula: ShapePathFormulasKeys.L\n },\n {\n viewBox: [200, 200],\n path: 'M0 0 L200 0 L200 200 L0 200 L0 0 Z M50 50 L50 150 L150 150 L150 50 Z',\n pathFormula: ShapePathFormulasKeys.RING_RECT,\n },\n {\n viewBox: [200, 200],\n path: 'M0 100 A100 100 0 1 1 0 101 Z M150 100 A50 50 0 1 0 150 101 Z',\n },\n {\n viewBox: [200, 200],\n path: 'M 70 0 L 70 70 L 0 70 L 0 130 L 70 130 L 70 200 L 130 200 L 130 130 L 200 130 L 200 70 L 130 70 L 130 0 L 70 0 Z',\n pathFormula: ShapePathFormulasKeys.PLUS,\n },\n {\n viewBox: [200, 200],\n path: 'M 40 0 L 0 40 L 60 100 L 0 160 L 40 200 L 100 140 L 160 200 L 200 160 L 140 100 L 200 40 L 160 0 L 100 60 L 40 0 Z'\n },\n {\n viewBox: [200, 200],\n path: 'M 0 0 L 200 0 L 200 160 L 100 160 L 60 200 L 60 160 L 0 160 Z',\n pathFormula: ShapePathFormulasKeys.MESSAGE,\n },\n {\n viewBox: [200, 200],\n path: 'M 0 40 Q 0 0 40 0 L 160 0 Q 200 0 200 40 L 200 120 Q 200 160 160 160 L 100 160 L 60 200 L 60 160 L 40 160 Q 0 160 0 120 L 0 40 Z',\n pathFormula: ShapePathFormulasKeys.ROUND_MESSAGE,\n },\n {\n viewBox: [200, 200],\n path: 'M 180 160 A 100 100 0 1 0 100 200 L 200 200 L 200 160 L 180 160 Z'\n },\n {\n viewBox: [200, 200],\n path: 'M 200 0 L 0 0 L 200 200 L 0 200 L 200 0 Z'\n },\n {\n viewBox: [200, 200],\n path: 'M 0 20 C 60 60 140 -40 200 20 L 200 180 C 140 140 60 240 0 180 L 0 20 Z',\n },\n {\n viewBox: [200, 200],\n path: 'M 0 20 C 40 -40 60 60 100 20 C 140 -40 160 60 200 20 L 200 180 C 140 240 160 140 100 180 C 40 240 60 140 0 180 L 0 20 Z',\n },\n {\n viewBox: [200, 200],\n path: 'M 100 0 Q 0 50 0 175 Q 100 225 200 175 Q 200 50 100 0 Z',\n },\n {\n viewBox: [200, 200],\n path: 'M 0 100 A 50 50 0 1 1 200 100 L 100 200 L 0 100 Z',\n },\n {\n viewBox: [200, 200],\n path: 'M 100 0 L 60 60 L 0 100 L 60 140 L 100 200 L 140 140 L 200 100 L 140 60 L 100 0 Z',\n },\n {\n viewBox: [200, 200],\n path: 'M 100 0 L 80 40 L 20 20 L 40 80 L 0 100 L 40 120 L 20 180 L 80 160 L 100 200 L 120 160 L 180 180 L 160 120 L 200 100 L 160 80 L 180 20 L 120 40 L 100 0 Z',\n },\n ],\n },\n \n {\n type: '箭头',\n children: [\n {\n viewBox: [200, 200],\n path: 'M 100 0 L 0 100 L 50 100 L 50 200 L 150 200 L 150 100 L 200 100 L 100 0 Z'\n },\n {\n viewBox: [200, 200],\n path: 'M 100 200 L 200 100 L 150 100 L 150 0 L 50 0 L 50 100 L 0 100 L 100 200 Z'\n },\n {\n viewBox: [200, 200],\n path: 'M 0 100 L 100 0 L 100 50 L 200 50 L 200 150 L 100 150 L 100 200 L 0 100 Z'\n },\n {\n viewBox: [200, 200],\n path: 'M 200 100 L 100 0 L 100 50 L 0 50 L 0 150 L 100 150 L 100 200 L 200 100 Z'\n },\n {\n viewBox: [200, 200],\n path: 'M 100 0 L 0 60 L 60 60 L 60 140 L 0 140 L 100 200 L 200 140 L 140 140 L 140 60 L 200 60 L 100 0 Z'\n },\n {\n viewBox: [200, 200],\n path: 'M 0 100 L 60 0 L 60 60 L 140 60 L 140 0 L 200 100 L 140 200 L 140 140 L 60 140 L 60 200 L 0 100 Z'\n },\n {\n viewBox: [200, 200],\n path: 'M 100 0 L 60 40 L 80 40 L 80 80 L 40 80 L 40 60 L 0 100 L 40 140 L 40 120 L 80 120 L 80 160 L 60 160 L 100 200 L 140 160 L 120 160 L 120 120 L 160 120 L 160 140 L 200 100 L 160 60 L 160 80 L 120 80 L 120 40 L 140 40 L 100 0 Z'\n },\n {\n viewBox: [200, 200],\n path: 'M 0 100 L 100 0 L 100 50 L 200 50 L 150 100 L 200 150 L 100 150 L 100 200 L 0 100 Z'\n },\n {\n viewBox: [200, 200],\n path: 'M 200 100 L 100 0 L 100 50 L 0 50 L 50 100 L 0 150 L 100 150 L 100 200 L 200 100 Z'\n },\n {\n viewBox: [200, 200],\n path: 'M 0 100 L 80 20 L 80 80 L 120 80 L 120 0 L 200 0 L 200 200 L 120 200 L 120 120 L 80 120 L 80 180 L 0 100 Z'\n },\n {\n viewBox: [200, 200],\n path: 'M 200 100 L 120 20 L 120 80 L 80 80 L 80 0 L 0 0 L 0 200 L 80 200 L 80 120 L 120 120 L 120 180 L 200 100 Z'\n },\n {\n viewBox: [200, 200],\n path: 'M 0 0 L 120 0 L 200 100 L 120 200 L 0 200 L 80 100 L 0 0 Z'\n },\n {\n viewBox: [200, 200],\n path: 'M 80 0 L 200 0 L 120 100 L 200 200 L 80 200 L 0 100 L 80 0 Z'\n },\n {\n viewBox: [200, 200],\n path: 'M 0 0 L 140 0 L 200 100 L 140 200 L 0 200 L 0 100 L 0 0 Z'\n },\n {\n viewBox: [200, 200],\n path: 'M 60 0 L 200 0 L 200 100 L 200 200 L 60 200 L 0 100 L 60 0 Z'\n },\n {\n viewBox: [200, 200],\n path: 'M 0 0 L 200 100 L 0 200 L 60 100 L 0 0 Z'\n },\n {\n viewBox: [200, 200],\n path: 'M 200 0 L 0 100 L 200 200 L 140 100 L 200 0 Z'\n },\n {\n viewBox: [200, 200],\n path: 'M 0 0 L 80 0 L 200 100 L 80 200 L 0 200 L 120 100 L 0 0 Z'\n },\n {\n viewBox: [200, 200],\n path: 'M 200 0 L 120 0 L 0 100 L 120 200 L 200 200 L 80 100 L 200 0 Z'\n },\n {\n viewBox: [200, 200],\n path: 'M 0 200 L 180 200 L 180 40 L 200 40 L 160 0 L 120 40 L 140 40 L 140 160 L 0 160 L 0 200 Z'\n },\n {\n viewBox: [200, 200],\n path: 'M 0 200 L 0 20 L 160 20 L 160 0 L 200 40 L 160 80 L 160 60 L 40 60 L 40 200 L 0 200 Z'\n },\n {\n viewBox: [200, 200],\n path: 'M 40 180 L 180 180 L 180 40 L 200 40 L 160 0 L 120 40 L 140 40 L 140 140 L 40 140 L 40 120 L 0 160 L 40 200 L 40 180 Z'\n },\n {\n viewBox: [1024, 1024],\n path: 'M398.208 302.912V64L0 482.112l398.208 418.176V655.36c284.48 0 483.584 95.552 625.792 304.64-56.896-298.688-227.584-597.312-625.792-657.088z',\n special: true,\n },\n {\n viewBox: [1024, 1024],\n path: 'M625.792 302.912V64L1024 482.112l-398.208 418.176V655.36C341.312 655.36 142.208 750.912 0 960c56.896-298.688 227.584-597.312 625.792-657.088z',\n special: true,\n },\n ],\n },\n\n {\n type: '其他形状',\n children: [\n {\n viewBox: [1024, 1024],\n path: 'M995.336 243.4016c-15.7584-36.5736-38.3376-69.26639999-66.91440001-97.37280001-28.5768-27.98879999-61.73999999-49.8624-98.78399999-65.26799998-38.22-15.876-78.6744-23.8728-120.4224-23.87280001-57.97680001 0-114.5424 15.876-163.69919999 45.864-11.76 7.17360001-22.932 15.05279999-33.51600001 23.63760001-10.584-8.5848-21.75600001-16.46400001-33.51600001-23.63760001-49.1568-29.98799999-105.7224-45.86399999-163.69919999-45.864-41.74799999 0-82.2024 7.9968-120.4224 23.87280001-36.9264 15.28799999-70.2072 37.27919999-98.78399999 65.26799998-28.6944 28.10640001-51.156 60.79919999-66.91440001 97.37280001-16.34639999 37.9848-24.696 78.3216-24.696 119.83439999 0 39.1608 7.9968 79.96800001 23.8728 121.48080001 13.28880001 34.692 32.34000001 70.67760001 56.6832 107.016 38.57279999 57.5064 91.61040001 117.4824 157.4664 178.28160001 109.1328 100.78319999 217.2072 170.4024 221.79359999 173.22479998l27.87120001 17.8752c12.348 7.8792 28.224 7.8792 40.572 0l27.87119999-17.8752c4.58639999-2.94 112.54319999-72.44159999 221.79360001-173.22479998 65.85599999-60.79919999 118.89359999-120.7752 157.4664-178.28160001 24.3432-36.33839999 43.512-72.324 56.68319999-107.016 15.876-41.5128 23.8728-82.32 23.87280001-121.48080001 0.1176-41.5128-8.232-81.8496-24.5784-119.83439999z',\n special: true,\n },\n {\n viewBox: [1024, 1024],\n path: 'M985.20746667 343.50079998l-303.32586667-44.08319999L546.28693333 24.5248c-3.70346666-7.5264-9.79626667-13.6192-17.32266665-17.32266668-18.87573334-9.3184-41.81333333-1.55306667-51.25120001 17.32266668L342.1184 299.41759999l-303.32586667 44.08319999c-8.36266667 1.19466667-16.00853333 5.13706667-21.8624 11.11040001-14.69440001 15.17226667-14.45546667 39.30453334 0.71679999 54.1184l219.46026668 213.9648-51.84853333 302.1312c-1.43359999 8.24320001-0.11946667 16.8448 3.82293333 24.25173333 9.79626667 18.6368 32.9728 25.92426667 51.6096 16.00853334L512 822.44266665l271.3088 142.64320001c7.40693333 3.9424 16.00853333 5.25653333 24.25173333 3.82293333 20.78719999-3.584 34.7648-23.296 31.1808-44.0832l-51.84853333-302.1312 219.46026668-213.9648c5.97333334-5.85386666 9.91573333-13.49973334 11.11039999-21.8624 3.2256-20.90666667-11.34933333-40.26026667-32.256-43.36640001z',\n special: true,\n },\n {\n viewBox: [1024, 1024],\n path: 'M852.65066667 405.84533333C800.54044445 268.40177778 667.76177778 170.66666667 512.22755555 170.66666667S223.91466667 268.288 171.80444445 405.73155555C74.29688889 431.33155555 2.27555555 520.07822222 2.27555555 625.77777778c0 125.72444445 101.83111111 227.55555555 227.44177778 227.55555555h564.56533334C919.89333333 853.33333333 1021.72444445 751.50222222 1021.72444445 625.77777778c0-105.472-71.79377778-194.21866667-169.07377778-219.93244445z',\n special: true,\n },\n {\n viewBox: [1024, 1024],\n path: 'M926.25224691 323.7371485H654.6457886L898.88200917 15.14388241c5.05486373-6.53433603 0.49315743-16.02761669-7.76722963-16.02761668H418.30008701c-3.45210206 0-6.78091476 1.84934039-8.50696579 4.93157436L90.35039154 555.76772251c-3.82197013 6.53433603 0.86302552 14.7947231 8.50696578 14.79472311h215.01664245l-110.22068713 440.88274851c-2.34249783 9.61657002 9.24670194 16.39748478 16.39748477 9.49328065L933.03316167 340.62779071c6.41104668-6.0411786 2.09591911-16.8906422-6.78091476-16.89064221z',\n special: true,\n },\n {\n viewBox: [1024, 1024],\n path: 'M878.47822222 463.30311111c-22.18666667-49.83466667-53.93066667-93.98044445-94.32177777-131.072l-33.10933334-30.37866666c-4.89244445-4.32355555-12.62933333-2.38933333-14.79111111 3.75466666l-14.79111111 42.43911111c-9.216 26.624-26.16888889 53.81688889-50.176 80.55466667-1.59288889 1.70666667-3.41333333 2.16177778-4.66488889 2.27555556-1.25155555 0.11377778-3.18577778-0.11377778-4.89244445-1.70666667-1.59288889-1.36533333-2.38933333-3.41333333-2.27555555-5.46133333 4.20977778-68.49422222-16.27022222-145.74933333-61.09866667-229.83111112C561.26577778 124.01777778 509.72444445 69.51822222 445.32622222 31.51644445l-46.99022222-27.648c-6.144-3.64088889-13.99466667 1.13777778-13.65333333 8.30577777l2.50311111 54.61333333c1.70666667 37.31911111-2.61688889 70.31466667-12.85688889 97.73511112-12.51555555 33.56444445-30.49244445 64.73955555-53.47555556 92.72888888-16.15644445 19.56977778-34.24711111 37.20533333-54.04444444 52.45155556-47.90044445 36.75022222-87.38133333 84.65066667-114.11911111 138.24C125.72444445 502.10133333 111.50222222 562.74488889 111.50222222 623.50222222c0 53.70311111 10.58133333 105.69955555 31.51644445 154.73777778 20.25244445 47.21777778 49.152 89.77066667 85.90222222 126.17955555 36.864 36.40888889 79.64444445 65.08088889 127.31733333 84.992C405.61777778 1010.11911111 457.95555555 1020.58666667 512 1020.58666667s106.38222222-10.46755555 155.76177778-31.06133334c47.67288889-19.91111111 90.56711111-48.46933333 127.31733333-84.992 36.864-36.40888889 65.76355555-78.96177778 85.90222222-126.17955555 20.93511111-49.03822222 31.51644445-101.03466667 31.51644445-154.73777778 0-55.52355555-11.37777778-109.45422222-34.01955556-160.31288889z',\n special: true,\n },\n {\n viewBox: [1024, 1024],\n path: 'M968.20337778 20.11591112H705.44042667c-22.17301333 0-41.92483556 15.16430222-47.14951111 37.33731555C642.36202666 124.73685332 582.08711111 173.03324444 512 173.03324444s-130.36202666-48.29639112-146.29091556-115.58001777c-5.22467555-22.17301333-24.84906667-37.33731556-47.14951111-37.33731555H55.79662222c-30.96576 0-56.06968889 25.10392889-56.06968888 56.06968888v321.12639999c0 30.96576 25.10392889 56.06968889 56.06968888 56.06968889h95.57333334v494.43271112c0 30.96576 25.10392889 56.06968889 56.06968889 56.06968888h609.1207111c30.96576 0 56.06968889-25.10392889 56.06968889-56.06968888V453.38168888h95.57333334c30.96576 0 56.06968889-25.10392889 56.06968888-56.06968889V76.1856c0-30.96576-25.10392889-56.06968889-56.06968888-56.06968888z',\n special: true,\n },\n {\n viewBox: [1024, 1024],\n path: 'M980.94648889 239.80714666H523.46880001L373.99210666 96.82944c-1.91146667-1.78403556-4.46008889-2.80348444-7.00871111-2.80348445H43.05351111c-22.55530667 0-40.77795555 18.22264888-40.77795555 40.77795557v754.39217776c0 22.55530667 18.22264888 40.77795555 40.77795555 40.77795557h937.89297778c22.55530667 0 40.77795555-18.22264888 40.77795555-40.77795557V280.58510222c0-22.55530667-18.22264888-40.77795555-40.77795555-40.77795556z',\n special: true,\n },\n {\n viewBox: [1024, 1024],\n path: 'M972.60904597 164.57058577L841.30587843 33.39070759c-18.86327195-18.86327195-44.1375906-29.34286748-70.64480282-29.3428675-26.75379095 0-51.90482023 10.47959553-70.76809219 29.3428675L558.60337778 174.68031322c-18.86327195 18.86327195-29.34286748 44.1375906-29.34286749 70.64480283 0 26.75379095 10.47959553 51.90482023 29.34286749 70.76809218l103.31648301 103.31648302c-24.28800376 53.50758189-57.69942011 101.59043198-99.24793416 143.13894603-41.42522469 41.67180341-89.63136414 75.08321976-143.13894603 99.61780223L316.21649759 558.84995649c-18.86327195-18.86327195-44.1375906-29.34286748-70.64480283-29.34286747-26.75379095 0-51.90482023 10.47959553-70.76809217 29.34286747L33.39070759 700.01627278c-18.86327195 18.86327195-29.34286748 44.1375906-29.3428675 70.76809217 0 26.75379095 10.47959553 51.90482023 29.3428675 70.76809219l131.05658883 131.05658883c30.08260365 30.205893 71.63111769 47.34311394 114.28923598 47.34311394 9.00012323 0 17.63037836-0.73973616 26.13734414-2.21920846 166.19405621-27.37023774 331.03192945-115.76870829 464.06114804-248.67463751C901.84095379 636.27567408 990.11613498 471.56109018 1017.85624079 304.87387654c8.38367642-50.91850535-8.50696579-103.31648302-45.24719482-140.30329077z',\n special: true,\n },\n {\n viewBox: [1024, 1024],\n path: 'M910.60451556 640.96028445c-20.38897778-65.49959112-43.83630221-120.54983112-79.89930667-210.64362666C836.31217778 193.67708444 737.93535999 2.27555556 511.36284444 2.27555556 282.24170667 2.27555556 186.03121778 197.50001778 192.14791111 430.31665779c-36.19043555 90.22122667-59.51032888 144.88917333-79.89930667 210.64362666-43.32657778 139.53706668-29.30915556 197.26336001-18.60494222 198.53767111 22.9376 2.80348444 89.32920888-105.00323556 89.32920889-105.00323556 0 62.44124445 32.11264001 143.86972444 101.69002667 202.61546667-33.64181333 10.32192-109.20846222 38.10190221-91.24067556 68.55793777 14.52714667 24.59420444 250.01984 15.67402668 317.94062222 8.02816 67.92078222 7.64586667 303.41347556 16.56604444 317.94062223-8.02816 17.96778667-30.32860444-57.72629333-58.23601779-91.24067555-68.55793777 69.57738667-58.87317334 101.69002667-140.30165333 101.69002667-202.61546667 0 0 66.39160889 107.80672 89.32920888 105.00323556 10.83164445-1.40174222 24.84906667-59.12803556-18.47751111-198.53767111z',\n special: true,\n },\n {\n viewBox: [1024, 1024],\n path: 'M1016.86992592 199.24764445c-37.13706667 16.01991111-77.55093333 27.54939259-119.17842962 32.03982222 42.96248889-25.60758518 75.60912592-66.02145185 91.02222222-114.08118519-39.68568889 23.66577778-84.58998518 41.02068148-131.31472593 50.00154074C819.53374815 126.79395555 765.76995555 101.79318518 706.18074075 101.79318518c-114.688 0-206.92385185 92.96402963-206.92385186 207.04521482 0 16.01991111 1.94180741 32.03982222 5.09724444 47.45291852-171.72859259-8.98085925-324.88865185-91.02222222-426.71217778-216.63288889-17.96171852 30.82619259-28.15620741 66.02145185-28.1562074 104.49351112 0 71.84687408 36.53025185 135.19834075 92.23585185 172.45677036-33.98162963-1.33499259-66.02145185-10.92266667-93.57084445-26.33576296v2.54862222c0 100.6098963 71.1186963 183.98625185 165.90317037 203.1616-17.3549037 4.49042963-35.92343703 7.03905185-54.49197037 7.03905185-13.47128889 0-26.2144-1.33499259-39.07887407-3.15543704C146.69748148 681.90814815 223.03478518 741.49736297 313.93564445 743.43917037c-71.1186963 55.7056-160.19911111 88.4736-256.9253926 88.4736-17.3549037 0-33.37481482-0.60681482-50.00154074-2.54862222C98.75911111 888.22518518 207.62168889 922.20681482 324.85831111 922.20681482 705.45256297 922.20681482 913.71140741 606.90583703 913.71140741 333.23235555c0-8.98085925 0-17.96171852-0.60681482-26.94257777 40.2925037-29.4912 75.60912592-66.02145185 103.76533333-107.04213333z',\n special: true,\n },\n {\n viewBox: [1024, 1024],\n path: 'M917.96720197 1.08889505H106.03279803C53.56084718 1.08889505 9.37393998 45.27580225 9.37393998 97.74775309v5.52336372c0 19.33177108 8.28504494 41.42522469 22.0934536 55.23363205l331.40179753 392.15879462v325.87843379c0 16.57008987 8.28504494 30.37849854 22.09345359 35.90186098l209.88780469 104.94390299 2.76168121 2.76168121c27.61681602 11.04672615 55.23363335-8.28504494 55.23363335-38.66354218V550.66354348l331.40179753-392.15879462c35.90186097-41.42522469 30.37849854-102.18222047-11.04672616-135.32240022-11.04672615-13.80840865-33.14017975-22.0934536-55.23363335-22.09345359z',\n special: true,\n },\n {\n viewBox: [1024, 1024],\n path: 'M491.70164031 97.48884502a25.89076502 25.89076502 0 0 1 40.59671938 0L745.66415762 367.01171317a25.89076502 25.89076502 0 0 0 30.49932208 7.72839349l208.00640948-89.14190458a25.89076502 25.89076502 0 0 1 35.56096592 29.06238339l-115.18801541 554.96855704A103.56306132 103.56306132 0 0 1 803.14165689 952.14301275H220.85834311a103.56306132 103.56306132 0 0 1-101.4011828-82.51387024l-115.18801541-554.96855704a25.89076502 25.89076502 0 0 1 35.54802012-29.06238339l208.01935528 89.14190458a25.89076502 25.89076502 0 0 0 30.49932208-7.72839349l213.36579793-269.52286815z',\n special: true,\n },\n {\n viewBox: [1024, 1024],\n path: 'M643.02466884 387.7801525c19.85376751-88.69205333 33.718272-152.84087467 41.61900049-192.57389433C704.52292267 95.17283515 652.90057916 2.27555515 550.58614084 2.27555515c-92.26012484 0-138.59407685 45.84971417-165.91530666 137.49816969l-0.70087152 2.67605334c-16.40038399 74.13942085-41.47882668 131.61085116-74.6746315 172.73287031a189.06953915 189.06953915 0 0 1-143.04142182 70.44391902l-26.17434983 0.5606965C77.66380049 387.52529067 27.76177817 438.90551468 27.76177817 501.84374084V881.55022182c0 77.4144 62.25009818 140.17422182 139.05282766 140.17422303h492.82707951c101.23127467 0 191.59267516-63.995904 225.93535999-159.98976l102.37815468-286.22301868c26.04691951-72.82688-11.39234134-153.15945284-83.63303784-179.42300483a138.04612267 138.04612267 0 0 0-47.17499733-8.30850884H643.02466884z',\n special: true,\n },\n {\n viewBox: [1024, 1024],\n path: 'M512 512c140.82958222 0 254.86222222-114.03264 254.86222222-254.86222222S652.82958222 2.27555555 512 2.27555555a254.78940445 254.78940445 0 0 0-254.86222222 254.86222223C257.13777778 397.96736 371.17041778 512 512 512z m0 72.81777778c-170.10232889 0-509.72444445 97.57582222-509.72444445 291.27111111v145.63555556h1019.4488889v-145.63555556c0-193.69528889-339.62211555-291.27111111-509.72444445-291.27111111z',\n special: true,\n },\n {\n viewBox: [1024, 1024],\n path: 'M1019.81297778 564.50161779l-138.89991111-472.51456c-8.66531556-25.99594668-29.43658667-43.45400889-57.21656889-43.45400891s-50.33528889 15.67402668-59.00060446 41.66997334l-92.00526221 274.48661334H351.69166222L259.6864 90.33045333c-8.66531556-25.99594668-31.22062222-41.66997333-59.00060444-41.66997332s-50.33528889 17.33063112-57.2165689 43.45400887L4.69674667 564.50161779c-5.22467555 17.33063112 1.78403556 36.44529778 15.67402667 46.89464887l491.11950221 368.27591113 492.77610666-368.27591113c13.76256-10.32192 20.77127111-29.43658667 15.54659557-46.89464887z',\n special: true,\n },\n {\n viewBox: [1024, 1024],\n path: 'M927.78951111 340.39277037c-12.01493333-47.81700741 12.01493333-124.03294815 89.08041481-150.97552592l-82.40545184-4.36906667s-31.19028148-109.22666667-174.27721483-118.9357037c-143.08693333-9.8304-236.65777778-3.64088889-236.65777777-3.6408889s106.07122963 67.47780741 63.5941926 187.74850371c-31.06891852 63.71555555-79.85682963 116.02299259-132.04290371 175.61220741-1.57771852 1.57771852-3.03407408 3.15543703-4.2477037 4.49042962C278.25493333 624.86755555 7.13007408 934.34311111 7.13007408 934.34311111c298.43152592 78.15774815 498.43768889-7.64586667 616.76657777-110.56165926 24.87940741-0.24272592 43.5693037-0.36408889 56.19105185-0.36408888 164.8109037 0 304.13558518-142.72284445 298.43152593-301.4656-3.88361482-109.1053037-38.71478518-133.74198518-50.72971852-181.5589926z',\n special: true,\n },\n {\n viewBox: [1024, 1024],\n path: 'M997.8886764 504.17210418L537.2729208 43.89182982c-13.97838539-13.97838539-36.56745619-13.97838539-50.5458416 0L26.1113236 504.17210418c-13.41924998 13.41924998-21.02349164 31.64706454-21.02349163 50.65766867 0 39.47496036 32.09437288 71.56933323 71.56933324 71.56933323h48.53295408V954.83524937c0 19.79339373 15.99127289 35.78466661 35.78466663 35.78466662H440.43066677V740.12724968h125.24633315v250.49266631h297.34821416c19.79339373 0 35.78466661-15.99127289 35.78466663-35.78466662V626.39910608h48.53295408c19.01060414 0 37.23841869-7.49241457 50.65766869-21.02349163 27.84494371-27.95677079 27.84494371-73.24673948-0.11182708-101.20351027z',\n special: true,\n },\n {\n viewBox: [1024, 1024],\n path: 'M1009.13013121 349.27572283L674.72427717 14.86986879c-8.82158299-8.82158299-20.35749924-13.16451618-31.89341544-13.16451618s-23.07183245 4.34293316-31.89341547 13.16451618L392.29790453 233.6451272c-16.5574327-1.90003326-33.25058207-2.71433322-49.94373146-2.71433324-99.34459624 0-198.68919249 32.70771543-280.25490606 98.12314628-20.90036589 16.69314938-22.52896582 48.04369819-3.66434987 67.04403081l246.59717401 246.59717401-292.33368895 292.06225564c-3.52863319 3.52863319-5.83581644 8.27871636-6.24296642 13.30023282l-4.61436649 50.48659809c-1.22144996 12.75736619 8.95729967 23.6146991 21.57894918 23.6146991 0.6785833 0 1.35716662 0 2.03574992-0.13571666l50.48659809-4.61436649c5.02151649-0.40714999 9.77159962-2.71433322 13.30023282-6.24296643l292.33368896-292.33368896 246.59717402 246.59717401c8.82158299 8.82158299 20.35749924 13.16451618 31.89341544 13.16451618 13.16451618 0 26.19331567-5.70009979 35.15061536-16.82886604 76.40848044-95.40881307 108.16617924-214.83947521 95.27309638-330.33435417l218.63954175-218.63954173c17.50744934-17.37173267 17.50744934-45.8722316 0-63.51539759z',\n special: true,\n },\n {\n viewBox: [1024, 1024],\n path: 'M976.62005979 160.47737905c-0.39452595-0.39452595-80.35178503 78.64217259-239.47725131 237.50462156l-111.6508437-111.65084369 237.89914752-237.89914752c-125.19623464-75.35445635-286.03131335-56.02268482-390.31767264 48.26367449-81.92988882 81.92988882-112.57140424 200.15616502-83.37648398 310.09739626l2.36715569 8.81107954-372.82702222 372.69551356c-8.15353628 8.15353628-8.15353628 21.56741857 0 29.72095487l185.95323084 185.95323084c8.15353628 8.15353628 21.56741857 8.15353628 29.72095485 0l372.56400493-372.56400493 8.81107953 2.3671557c110.07273989 29.32642892 228.29901608-1.18357785 310.36041356-83.24497533 104.41786795-104.2863593 123.74963948-265.12143802 49.97328693-390.05465535z',\n special: true,\n },\n ],\n },\n\n {\n type: '线性',\n children: [\n {\n viewBox: [1024, 1024],\n path: 'M953.5488 832.61667556c-24.08448-57.08913778-58.74574221-108.31644445-102.70947556-152.28017777-43.96373333-43.96373333-95.19104-78.49756444-152.28017777-102.70947558-0.50972445-0.25486222-1.01944888-0.38229333-1.52917334-0.63715555C776.41955556 519.64586667 828.02915556 426.23886221 828.02915556 320.85333332c0-174.58062221-141.44853334-316.02915556-316.02915556-316.02915554S195.97084444 146.27271111 195.97084444 320.85333332c0 105.38552889 51.6096 198.79253333 130.99918223 256.26396447-0.50972445 0.25486222-1.01944888 0.38229333-1.52917334 0.63715555-57.08913778 24.08448-108.31644445 58.61831112-152.28017777 102.70947554-43.96373333 43.96373333-78.49756444 95.19104-102.70947556 152.28017779C46.74901333 888.55893332 34.13333334 947.8144 32.85902222 1008.72647111c-0.12743111 5.7344 4.46008889 10.44935111 10.19448889 10.44935111h76.45866667c5.60696888 0 10.06705778-4.46008889 10.19448889-9.93962666 2.54862221-98.37681778 42.05226667-190.50951112 111.88451555-260.34176001 72.25344-72.25344 168.20906666-112.01194667 270.40881778-112.01194667s198.15537778 39.75850667 270.40881778 112.01194667C852.24106667 818.72668444 891.74471111 910.85937779 894.29333333 1009.23619556c0.12743111 5.60696888 4.58752 9.93962667 10.19448889 9.93962666h76.45866667c5.7344 0 10.32192-4.71495112 10.19448889-10.44935111-1.27431111-60.91207112-13.88999112-120.16753779-37.59217778-176.10979555zM512 540.03484444c-58.49088 0-113.54112-22.81016889-154.95623111-64.22527999S292.81848888 379.34421333 292.81848888 320.85333332c0-58.49088 22.81016889-113.54112 64.22528001-154.9562311S453.50912 101.67182221 512 101.67182221s113.54112 22.81016889 154.95623111 64.22528001S731.18151112 262.36245333 731.18151112 320.85333332c0 58.49088-22.81016889 113.54112-64.22528001 154.95623113S570.49088 540.03484444 512 540.03484444z',\n special: true,\n outlined: true,\n },\n {\n viewBox: [1024, 1024],\n path: 'M985.31555555 111.50222222H38.68444445c-20.13866667 0-36.40888889 16.27022222-36.4088889 36.40888889v728.17777778c0 20.13866667 16.27022222 36.40888889 36.4088889 36.40888889h946.6311111c20.13866667 0 36.40888889-16.27022222 36.4088889-36.40888889V147.91111111c0-20.13866667-16.27022222-36.40888889-36.4088889-36.40888889z m-45.5111111 126.06577778V830.57777778H84.19555555V237.568l-31.40266666-24.46222222 44.71466666-57.45777778 48.6968889 37.888h731.70488888l48.69688889-37.888 44.71466667 57.45777778-31.51644444 24.46222222z M877.90933333 193.42222222L512 477.86666667 146.09066667 193.42222222l-48.69688889-37.888-44.71466667 57.45777778 31.40266667 24.46222222 388.66488889 302.19377778c22.98311111 17.86311111 55.18222222 17.86311111 78.16533333 0L939.80444445 237.568l31.40266666-24.46222222-44.71466666-57.45777778-48.58311112 37.77422222z',\n special: true,\n outlined: true,\n },\n {\n viewBox: [1024, 1024],\n path: 'M985.31555555 88.74666667H38.68444445c-20.13866667 0-36.40888889 16.27022222-36.4088889 36.40888888v564.33777778c0 20.13866667 16.27022222 36.40888889 36.4088889 36.40888889h432.35555555v127.43111111H275.34222222c-10.01244445 0-18.20444445 8.192-18.20444444 18.20444445v54.61333333c0 5.00622222 4.096 9.10222222 9.10222222 9.10222222h491.52c5.00622222 0 9.10222222-4.096 9.10222222-9.10222222v-54.61333333c0-10.01244445-8.192-18.20444445-18.20444444-18.20444445H552.96V725.90222222h432.35555555c20.13866667 0 36.40888889-16.27022222 36.4088889-36.40888889V125.15555555c0-20.13866667-16.27022222-36.40888889-36.4088889-36.40888888z m-45.5111111 555.23555555H84.19555555V170.66666667h855.6088889v473.31555555z',\n special: true,\n outlined: true,\n },\n {\n viewBox: [1024, 1024],\n path: 'M512 2.27555555C230.51377778 2.27555555 2.27555555 230.51377778 2.27555555 512s228.23822222 509.72444445 509.72444445 509.72444445 509.72444445-228.23822222 509.72444445-509.72444445S793.48622222 2.27555555 512 2.27555555z m0 932.97777778c-233.69955555 0-423.25333333-189.55377778-423.25333333-423.25333333 0-101.26222222 35.61244445-194.33244445 95.00444444-267.15022222l595.39911111 595.39911111C706.33244445 899.64088889 613.26222222 935.25333333 512 935.25333333z m328.24888889-156.10311111L244.84977778 183.75111111C317.66755555 124.35911111 410.73777778 88.74666667 512 88.74666667c233.69955555 0 423.25333333 189.55377778 423.25333333 423.25333333 0 101.26222222-35.61244445 194.33244445-95.00444444 267.15022222z',\n special: true,\n outlined: true,\n },\n {\n viewBox: [1024, 1024],\n path: 'M901.80266667 257.82044445L656.95288889 12.97066667c-6.82666667-6.82666667-16.04266667-10.69511111-25.71377778-10.69511112H147.91111111c-20.13866667 0-36.40888889 16.27022222-36.40888889 36.4088889v946.6311111c0 20.13866667 16.27022222 36.40888889 36.40888889 36.4088889h728.17777778c20.13866667 0 36.40888889-16.27022222 36.40888889-36.4088889V283.648c0-9.67111111-3.86844445-19.00088889-10.69511111-25.82755555zM828.52977778 300.37333333H614.4V86.24355555L828.52977778 300.37333333z m2.048 639.43111112H193.42222222V84.19555555h343.60888889v245.76c0 26.39644445 21.39022222 47.78666667 47.78666667 47.78666667h245.76v562.06222223z',\n special: true,\n outlined: true,\n },\n {\n viewBox: [1024, 1024],\n path: 'M981.07392 55.79662222H42.92608c-31.22062222 0-50.71758221 34.02410666-35.04355556 61.16693334L304.28728889 620.82616888V927.42542221c0 22.55530667 18.09521779 40.77795555 40.52309333 40.77795557h334.37923556c22.42787556 0 40.52309333-18.22264888 40.52309333-40.77795557V620.82616888L1016.24490667 116.96355556c15.54659555-27.14282666-3.95036444-61.16693333-35.17098667-61.16693334zM628.47203556 876.45297779H395.52796444V677.66044445h233.07150222v198.79253334z m12.23338666-301.50200891l-12.10595556 21.15356445h-233.19893332l-12.10595556-21.15356445L130.59868445 147.54702221h762.8026311L640.70542222 574.95096888z',\n special: true,\n outlined: true,\n },\n {\n viewBox: [1024, 1024],\n path: 'M980.62285431 4.54099753H654.39920987c-4.2719763 0-7.76722963 3.49525333-7.76722962 7.76722964v72.4941432c0 4.2719763 3.49525333 7.76722963 7.76722962 7.76722963h207.64393877L604.04167111 350.57107753c-64.72691358-49.83972347-143.69374815-76.7661195-226.67365136-76.7661195-99.54999309 0-193.27456395 38.83614815-263.5679921 109.25903012S4.54099753 547.08198717 4.54099753 646.63198025s38.83614815 193.27456395 109.25903012 263.5679921C184.09345581 980.62285431 277.81802667 1019.45900247 377.36801975 1019.45900247s193.27456395-38.83614815 263.5679921-109.25903012C711.35889383 839.90654419 750.19504197 746.18197333 750.19504197 646.63198025c0-82.9799032-26.92639605-161.68783013-76.63666567-226.41474372L931.4304 162.34521283V369.60079013c0 4.2719763 3.49525333 7.76722963 7.76722963 7.76722962h72.4941432c4.2719763 0 7.76722963-3.49525333 7.76722964-7.76722962V43.37714569c0-21.35988148-17.47626667-38.83614815-38.83614816-38.83614816zM377.36801975 921.07409383c-151.33152395 0-274.44211358-123.11058963-274.44211358-274.44211358s123.11058963-274.44211358 274.44211358-274.44211358 274.44211358 123.11058963 274.44211358 274.44211358-123.11058963 274.44211358-274.44211358 274.44211358z',\n special: true,\n outlined: true,\n },\n {\n viewBox: [1024, 1024],\n path: 'M591.98717801 512l405.34042913-483.16579151c6.79427767-8.02960089 1.08090782-20.22841761-9.41933951-20.2284176h-123.22349044c-7.25752386 0-14.20621693 3.24272343-18.99309439 8.80167789L511.38233839 415.95362022 177.07299399 17.40746878c-4.63246205-5.55895447-11.58115512-8.80167789-18.99309439-8.80167789H34.85640916c-10.50024731 0-16.21361717 12.19881672-9.41933952 20.2284176L430.77749876 512 25.43706964 995.16579151c-6.79427767 8.02960089-1.08090782 20.22841761 9.41933952 20.2284176h123.22349044c7.25752386 0 14.20621693-3.24272343 18.99309439-8.80167789l334.3093444-398.54615144 334.30934441 398.54615144c4.63246205 5.55895447 11.58115512 8.80167789 18.99309439 8.80167789h123.22349044c10.50024731 0 16.21361717-12.19881672 9.41933951-20.2284176L591.98717801 512z',\n special: true,\n outlined: true,\n },\n ],\n }\n]","import { ChartType } from '@/types/slides'\n\ninterface ChartTypes {\n [propName: string]: ChartType;\n}\n\nexport const CHART_TYPES: ChartTypes = {\n bar: 'bar',\n horizontalBar: 'bar',\n line: 'line',\n area: 'line',\n scatter: 'line',\n pie: 'pie',\n ring: 'pie',\n}","import { storeToRefs } from 'pinia'\nimport { nanoid } from 'nanoid'\nimport { useMainStore, useSlidesStore } from '@/store'\nimport { getImageSize } from '@/utils/image'\nimport { VIEWPORT_SIZE } from '@/configs/canvas'\nimport { PPTLineElement, PPTElement, TableCell, TableCellStyle, PPTShapeElement, PPTChartElement, ChartOptions, PresetChartType } from '@/types/slides'\nimport { ShapePoolItem, SHAPE_PATH_FORMULAS } from '@/configs/shapes'\nimport { LinePoolItem } from '@/configs/lines'\nimport { CHART_TYPES } from '@/configs/chartTypes'\nimport useHistorySnapshot from '@/hooks/useHistorySnapshot'\n\ninterface CommonElementPosition {\n top: number;\n left: number;\n width: number;\n height: number;\n}\n\ninterface LineElementPosition {\n top: number;\n left: number;\n start: [number, number];\n end: [number, number];\n}\n\nexport default () => {\n const mainStore = useMainStore()\n const slidesStore = useSlidesStore()\n const { creatingElement } = storeToRefs(mainStore)\n const { theme, viewportRatio } = storeToRefs(slidesStore)\n\n const { addHistorySnapshot } = useHistorySnapshot()\n\n // 创建(插入)一个元素并将其设置为被选中元素\n const createElement = (element: PPTElement, callback?: () => void) => {\n slidesStore.addElement(element)\n mainStore.setActiveElementIdList([element.id])\n\n if (creatingElement.value) mainStore.setCreatingElement(null)\n\n setTimeout(() => {\n mainStore.setEditorareaFocus(true)\n }, 0)\n\n if (callback) callback()\n\n addHistorySnapshot()\n }\n\n /**\n * 创建图片元素\n * @param src 图片地址\n */\n const createImageElement = (src: string) => {\n getImageSize(src).then(({ width, height }) => {\n const scale = height / width\n \n if (scale < viewportRatio.value && width > VIEWPORT_SIZE) {\n width = VIEWPORT_SIZE\n height = width * scale\n }\n else if (height > VIEWPORT_SIZE * viewportRatio.value) {\n height = VIEWPORT_SIZE * viewportRatio.value\n width = height / scale\n }\n\n createElement({\n type: 'image',\n id: nanoid(10),\n src,\n width,\n height,\n left: (VIEWPORT_SIZE - width) / 2,\n top: (VIEWPORT_SIZE * viewportRatio.value - height) / 2,\n fixedRatio: true,\n rotate: 0,\n })\n })\n }\n \n /**\n * 创建图表元素\n * @param chartType 图表类型\n */\n const createChartElement = (type: PresetChartType) => {\n const newElement: PPTChartElement = {\n type: 'chart',\n id: nanoid(10),\n chartType: CHART_TYPES[type],\n left: 300,\n top: 81.25,\n width: 400,\n height: 400,\n rotate: 0,\n themeColor: [theme.value.themeColor],\n gridColor: theme.value.fontColor,\n data: {\n labels: ['类别1', '类别2', '类别3', '类别4', '类别5'],\n legends: ['系列1'],\n series: [\n [12, 19, 5, 2, 18],\n ],\n },\n }\n\n let options: ChartOptions = {}\n if (type === 'horizontalBar') options = { horizontalBars: true }\n else if (type === 'area') options = { showArea: true }\n else if (type === 'scatter') options = { showLine: false }\n else if (type === 'ring') options = { donut: true }\n\n createElement({\n ...newElement,\n options,\n })\n }\n \n /**\n * 创建表格元素\n * @param row 行数\n * @param col 列数\n */\n const createTableElement = (row: number, col: number) => {\n const style: TableCellStyle = {\n fontname: theme.value.fontName,\n color: theme.value.fontColor,\n }\n const data: TableCell[][] = []\n for (let i = 0; i < row; i++) {\n const rowCells: TableCell[] = []\n for (let j = 0; j < col; j++) {\n rowCells.push({ id: nanoid(10), colspan: 1, rowspan: 1, text: '', style })\n }\n data.push(rowCells)\n }\n\n const DEFAULT_CELL_WIDTH = 100\n const DEFAULT_CELL_HEIGHT = 36\n\n const colWidths: number[] = new Array(col).fill(1 / col)\n\n const width = col * DEFAULT_CELL_WIDTH\n const height = row * DEFAULT_CELL_HEIGHT\n\n createElement({\n type: 'table',\n id: nanoid(10),\n width,\n height,\n colWidths,\n rotate: 0,\n data,\n left: (VIEWPORT_SIZE - width) / 2,\n top: (VIEWPORT_SIZE * viewportRatio.value - height) / 2,\n outline: {\n width: 2,\n style: 'solid',\n color: '#eeece1',\n },\n theme: {\n color: theme.value.themeColor,\n rowHeader: true,\n rowFooter: false,\n colHeader: false,\n colFooter: false,\n },\n })\n }\n \n /**\n * 创建文本元素\n * @param position 位置大小信息\n * @param content 文本内容\n */\n const createTextElement = (position: CommonElementPosition, content = '') => {\n const { left, top, width, height } = position\n const id = nanoid(10)\n createElement({\n type: 'text',\n id,\n left, \n top, \n width, \n height,\n content,\n rotate: 0,\n defaultFontName: theme.value.fontName,\n defaultColor: theme.value.fontColor,\n }, () => {\n setTimeout(() => {\n const editorRef: HTMLElement | null = document.querySelector(`#editable-element-${id} .ProseMirror`)\n if (editorRef) editorRef.focus()\n }, 0)\n })\n }\n \n /**\n * 创建形状元素\n * @param position 位置大小信息\n * @param data 形状路径信息\n */\n const createShapeElement = (position: CommonElementPosition, data: ShapePoolItem) => {\n const { left, top, width, height } = position\n const newElement: PPTShapeElement = {\n type: 'shape',\n id: nanoid(10),\n left, \n top, \n width, \n height,\n viewBox: data.viewBox,\n path: data.path,\n fill: theme.value.themeColor,\n fixedRatio: false,\n rotate: 0,\n }\n if (data.special) newElement.special = true\n if (data.pathFormula) {\n newElement.pathFormula = data.pathFormula\n newElement.viewBox = [width, height]\n newElement.path = SHAPE_PATH_FORMULAS[data.pathFormula](width, height)\n }\n createElement(newElement)\n }\n \n /**\n * 创建线条元素\n * @param position 位置大小信息\n * @param data 线条的路径和样式\n */\n const createLineElement = (position: LineElementPosition, data: LinePoolItem) => {\n const { left, top, start, end } = position\n\n const newElement: PPTLineElement = {\n type: 'line',\n id: nanoid(10),\n left, \n top, \n start,\n end,\n points: data.points,\n color: theme.value.themeColor,\n style: data.style,\n width: 2,\n }\n if (data.isBroken) newElement.broken = [(start[0] + end[0]) / 2, (start[1] + end[1]) / 2]\n if (data.isCurve) newElement.curve = [(start[0] + end[0]) / 2, (start[1] + end[1]) / 2]\n 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]]\n createElement(newElement)\n }\n \n /**\n * 创建LaTeX元素\n * @param svg SVG代码\n */\n const createLatexElement = (data: { path: string; latex: string; w: number; h: number; }) => {\n createElement({\n type: 'latex',\n id: nanoid(10),\n width: data.w,\n height: data.h,\n rotate: 0,\n left: (VIEWPORT_SIZE - data.w) / 2,\n top: (VIEWPORT_SIZE * viewportRatio.value - data.h) / 2,\n path: data.path,\n latex: data.latex,\n color: theme.value.fontColor,\n strokeWidth: 2,\n viewBox: [data.w, data.h],\n fixedRatio: true,\n })\n }\n \n /**\n * 创建视频元素\n * @param src 视频地址\n */\n const createVideoElement = (src: string) => {\n createElement({\n type: 'video',\n id: nanoid(10),\n width: 500,\n height: 300,\n rotate: 0,\n left: (VIEWPORT_SIZE - 500) / 2,\n top: (VIEWPORT_SIZE * viewportRatio.value - 300) / 2,\n src,\n })\n }\n \n /**\n * 创建音频元素\n * @param src 音频地址\n */\n const createAudioElement = (src: string) => {\n createElement({\n type: 'audio',\n id: nanoid(10),\n width: 50,\n height: 50,\n rotate: 0,\n left: (VIEWPORT_SIZE - 50) / 2,\n top: (VIEWPORT_SIZE * viewportRatio.value - 50) / 2,\n loop: false,\n autoplay: false,\n fixedRatio: true,\n color: theme.value.themeColor,\n src,\n })\n }\n\n return {\n createImageElement,\n createChartElement,\n createTableElement,\n createTextElement,\n createShapeElement,\n createLineElement,\n createLatexElement,\n createVideoElement,\n createAudioElement,\n }\n}","import { storeToRefs } from 'pinia'\nimport { nanoid } from 'nanoid'\nimport { useSlidesStore, useMainStore } from '@/store'\nimport { pasteCustomClipboardString } from '@/utils/clipboard'\nimport { PPTElement, Slide } from '@/types/slides'\nimport { createElementIdMap } from '@/utils/element'\nimport { parseText2Paragraphs } from '@/utils/textParser'\nimport useHistorySnapshot from '@/hooks/useHistorySnapshot'\nimport useCreateElement from '@/hooks/useCreateElement'\n\ninterface PasteTextClipboardDataOptions {\n onlySlide?: boolean;\n onlyElements?: boolean;\n}\n\nexport default () => {\n const mainStore = useMainStore()\n const slidesStore = useSlidesStore()\n const { currentSlide } = storeToRefs(slidesStore)\n\n const { addHistorySnapshot } = useHistorySnapshot()\n const { createTextElement } = useCreateElement()\n\n /**\n * 粘贴元素(一组)\n * @param elements 元素列表数据\n */\n const addElementsFromClipboard = (elements: PPTElement[]) => {\n const { groupIdMap, elIdMap } = createElementIdMap(elements)\n const currentSlideElementIdList = currentSlide.value.elements.map(el => el.id)\n \n for (const element of elements) {\n const inCurrentSlide = currentSlideElementIdList.includes(element.id)\n \n element.id = elIdMap[element.id]\n\n if (inCurrentSlide) {\n element.left = element.left + 10\n element.top = element.top + 10\n }\n\n if (element.groupId) element.groupId = groupIdMap[element.groupId]\n }\n slidesStore.addElement(elements)\n mainStore.setActiveElementIdList(Object.values(elIdMap))\n addHistorySnapshot()\n }\n\n /**\n * 粘贴页面\n * @param slide 页面数据\n */\n const addSlidesFromClipboard = (slides: Slide[]) => {\n const newSlides = slides.map(slide => {\n const { groupIdMap, elIdMap } = createElementIdMap(slide.elements)\n\n for (const element of slide.elements) {\n element.id = elIdMap[element.id]\n if (element.groupId) element.groupId = groupIdMap[element.groupId]\n }\n // 动画id替换\n if (slide.animations) {\n for (const animation of slide.animations) {\n animation.id = nanoid(10)\n animation.elId = elIdMap[animation.elId]\n }\n }\n return {\n ...slide,\n id: nanoid(10),\n }\n })\n slidesStore.addSlide(newSlides)\n addHistorySnapshot()\n }\n\n /**\n * 粘贴普通文本:创建为新的文本元素\n * @param text 文本\n */\n const createTextElementFromClipboard = (text: string) => {\n createTextElement({\n left: 0,\n top: 0,\n width: 600,\n height: 50,\n }, text)\n }\n\n /**\n * 解析剪贴板内容,根据解析结果选择合适的粘贴方式\n * @param text 剪贴板内容\n * @param options 配置项:onlySlide -- 仅处理页面粘贴;onlyElements -- 仅处理元素粘贴;\n */\n const pasteTextClipboardData = (text: string, options?: PasteTextClipboardDataOptions) => {\n const onlySlide = options?.onlySlide || false\n const onlyElements = options?.onlyElements || false\n\n const clipboardData = pasteCustomClipboardString(text)\n\n // 元素或页面\n if (typeof clipboardData === 'object') {\n const { type, data } = clipboardData\n\n if (type === 'elements' && !onlySlide) addElementsFromClipboard(data)\n else if (type === 'slides' && !onlyElements) addSlidesFromClipboard(data)\n }\n\n // 普通文本\n else if (!onlyElements && !onlySlide) {\n const string = parseText2Paragraphs(clipboardData)\n createTextElementFromClipboard(string)\n }\n }\n\n return {\n addSlidesFromClipboard,\n pasteTextClipboardData,\n }\n}","import { computed } from 'vue'\nimport { storeToRefs } from 'pinia'\nimport { nanoid } from 'nanoid'\nimport { useMainStore, useSlidesStore } from '@/store'\nimport { Slide } from '@/types/slides'\nimport { copyText, readClipboard } from '@/utils/clipboard'\nimport { encrypt } from '@/utils/crypto'\nimport { createElementIdMap } from '@/utils/element'\nimport { KEYS } from '@/configs/hotkey'\nimport { message } from 'ant-design-vue'\nimport usePasteTextClipboardData from '@/hooks/usePasteTextClipboardData'\nimport useHistorySnapshot from '@/hooks/useHistorySnapshot'\n\nexport default () => {\n const mainStore = useMainStore()\n const slidesStore = useSlidesStore()\n const { selectedSlidesIndex: _selectedSlidesIndex, activeElementIdList } = storeToRefs(mainStore)\n const { currentSlide, slides, theme, slideIndex } = storeToRefs(slidesStore)\n\n const selectedSlidesIndex = computed(() => [..._selectedSlidesIndex.value, slideIndex.value])\n const selectedSlides = computed(() => slides.value.filter((item, index) => selectedSlidesIndex.value.includes(index)))\n const selectedSlidesId = computed(() => selectedSlides.value.map(item => item.id))\n\n const { pasteTextClipboardData, addSlidesFromClipboard } = usePasteTextClipboardData()\n const { addHistorySnapshot } = useHistorySnapshot()\n\n // 重置幻灯片\n const resetSlides = () => {\n const emptySlide: Slide = {\n id: nanoid(10),\n elements: [],\n background: {\n type: 'solid',\n color: theme.value.backgroundColor,\n },\n }\n slidesStore.updateSlideIndex(0)\n mainStore.setActiveElementIdList([])\n slidesStore.setSlides([emptySlide])\n }\n\n /**\n * 移动页面焦点\n * @param command 移动页面焦点命令:上移、下移\n */\n const updateSlideIndex = (command: string) => {\n if (command === KEYS.UP && slideIndex.value > 0) {\n if (activeElementIdList.value.length) mainStore.setActiveElementIdList([])\n slidesStore.updateSlideIndex(slideIndex.value - 1)\n }\n else if (command === KEYS.DOWN && slideIndex.value < slides.value.length - 1) {\n if (activeElementIdList.value.length) mainStore.setActiveElementIdList([])\n slidesStore.updateSlideIndex(slideIndex.value + 1)\n }\n }\n\n // 将当前页面数据加密后复制到剪贴板\n const copySlide = () => {\n const text = encrypt(JSON.stringify({\n type: 'slides',\n data: selectedSlides.value,\n }))\n\n copyText(text).then(() => {\n mainStore.setThumbnailsFocus(true)\n })\n }\n\n // 尝试将剪贴板页面数据解密后添加到下一页(粘贴)\n const pasteSlide = () => {\n readClipboard().then(text => {\n pasteTextClipboardData(text, { onlySlide: true })\n }).catch(err => message.warning(err))\n }\n\n // 创建一页空白页并添加到下一页\n const createSlide = () => {\n const emptySlide: Slide = {\n id: nanoid(10),\n elements: [],\n background: {\n type: 'solid',\n color: theme.value.backgroundColor,\n },\n }\n mainStore.setActiveElementIdList([])\n slidesStore.addSlide(emptySlide)\n addHistorySnapshot()\n }\n\n // 根据模板创建新页面\n const createSlideByTemplate = (slide: Slide) => {\n const { groupIdMap, elIdMap } = createElementIdMap(slide.elements)\n\n for (const element of slide.elements) {\n element.id = elIdMap[element.id]\n if (element.groupId) element.groupId = groupIdMap[element.groupId]\n }\n const newSlide = {\n ...slide,\n id: nanoid(10),\n }\n mainStore.setActiveElementIdList([])\n slidesStore.addSlide(newSlide)\n addHistorySnapshot()\n }\n\n // 将当前页复制一份到下一页\n const copyAndPasteSlide = () => {\n const slide = JSON.parse(JSON.stringify(currentSlide.value))\n addSlidesFromClipboard([slide])\n }\n\n // 删除当前页,若将删除全部页面,则执行重置幻灯片操作\n const deleteSlide = (targetSlidesId = selectedSlidesId.value) => {\n if (slides.value.length === targetSlidesId.length) resetSlides()\n else slidesStore.deleteSlide(targetSlidesId)\n\n mainStore.updateSelectedSlidesIndex([])\n\n addHistorySnapshot()\n }\n\n // 将当前页复制后删除(剪切)\n // 由于复制操作会导致多选状态消失,所以需要提前将需要删除的页面ID进行缓存\n const cutSlide = () => {\n const targetSlidesId = [...selectedSlidesId.value]\n copySlide()\n deleteSlide(targetSlidesId)\n }\n\n // 选中全部幻灯片\n const selectAllSlide = () => {\n const newSelectedSlidesIndex = Array.from(Array(slides.value.length), (item, index) => index)\n mainStore.setActiveElementIdList([])\n mainStore.updateSelectedSlidesIndex(newSelectedSlidesIndex)\n }\n\n return {\n resetSlides,\n updateSlideIndex,\n copySlide,\n pasteSlide,\n createSlide,\n createSlideByTemplate,\n copyAndPasteSlide,\n deleteSlide,\n cutSlide,\n selectAllSlide,\n }\n}","import { storeToRefs } from 'pinia'\nimport { useMainStore, useSlidesStore } from '@/store'\nimport { PPTElement } from '@/types/slides'\nimport useHistorySnapshot from '@/hooks/useHistorySnapshot'\n\nexport default () => {\n const mainStore = useMainStore()\n const slidesStore = useSlidesStore()\n const { activeElementIdList } = storeToRefs(mainStore)\n const { currentSlide } = storeToRefs(slidesStore)\n\n const { addHistorySnapshot } = useHistorySnapshot()\n\n // 锁定选中的元素,并清空选中元素状态\n const lockElement = () => {\n const newElementList: PPTElement[] = JSON.parse(JSON.stringify(currentSlide.value.elements))\n \n for (const element of newElementList) {\n if (activeElementIdList.value.includes(element.id)) element.lock = true\n }\n slidesStore.updateSlide({ elements: newElementList })\n mainStore.setActiveElementIdList([])\n addHistorySnapshot()\n }\n\n /**\n * 解除元素的锁定状态,并将其设置为当前选择元素\n * @param handleElement 需要解锁的元素\n */\n const unlockElement = (handleElement: PPTElement) => {\n const newElementList: PPTElement[] = JSON.parse(JSON.stringify(currentSlide.value.elements))\n\n if (handleElement.groupId) {\n const groupElementIdList = []\n for (const element of newElementList) {\n if (element.groupId === handleElement.groupId) {\n element.lock = false\n groupElementIdList.push(element.id)\n }\n }\n slidesStore.updateSlide({ elements: newElementList })\n mainStore.setActiveElementIdList(groupElementIdList)\n }\n else {\n for (const element of newElementList) {\n if (element.id === handleElement.id) {\n element.lock = false\n break\n }\n }\n slidesStore.updateSlide({ elements: newElementList })\n mainStore.setActiveElementIdList([handleElement.id])\n }\n addHistorySnapshot()\n }\n\n return {\n lockElement,\n unlockElement,\n }\n}","import { storeToRefs } from 'pinia'\nimport { useMainStore, useSlidesStore } from '@/store'\nimport { PPTElement } from '@/types/slides'\nimport useHistorySnapshot from '@/hooks/useHistorySnapshot'\n\nexport default () => {\n const mainStore = useMainStore()\n const slidesStore = useSlidesStore()\n const { activeElementIdList, activeGroupElementId } = storeToRefs(mainStore)\n const { currentSlide } = storeToRefs(slidesStore)\n\n const { addHistorySnapshot } = useHistorySnapshot()\n\n // 删除全部选中元素\n // 组合元素成员中,存在被选中可独立操作的元素时,优先删除该元素。否则默认删除所有被选中的元素\n const deleteElement = () => {\n if (!activeElementIdList.value.length) return\n\n let newElementList: PPTElement[] = []\n if (activeGroupElementId.value) {\n newElementList = currentSlide.value.elements.filter(el => el.id !== activeGroupElementId.value)\n }\n else {\n newElementList = currentSlide.value.elements.filter(el => !activeElementIdList.value.includes(el.id))\n }\n\n mainStore.setActiveElementIdList([])\n slidesStore.updateSlide({ elements: newElementList })\n addHistorySnapshot()\n }\n\n // 删除内面内全部元素(无论是否选中)\n const deleteAllElements = () => {\n if (!currentSlide.value.elements.length) return\n mainStore.setActiveElementIdList([])\n slidesStore.updateSlide({ elements: [] })\n addHistorySnapshot()\n }\n\n return {\n deleteElement,\n deleteAllElements,\n }\n}","import { computed } from 'vue'\nimport { storeToRefs } from 'pinia'\nimport { nanoid } from 'nanoid'\nimport { useMainStore, useSlidesStore } from '@/store'\nimport { PPTElement } from '@/types/slides'\nimport useHistorySnapshot from '@/hooks/useHistorySnapshot'\n\nexport default () => {\n const mainStore = useMainStore()\n const slidesStore = useSlidesStore()\n const { activeElementIdList, activeElementList, handleElementId } = storeToRefs(mainStore)\n const { currentSlide } = storeToRefs(slidesStore)\n\n const { addHistorySnapshot } = useHistorySnapshot()\n\n /**\n * 判断当前选中的元素是否可以组合\n */\n const canCombine = computed(() => {\n if (activeElementList.value.length < 2) return false\n\n const firstGroupId = activeElementList.value[0].groupId\n if (!firstGroupId) return true\n\n const inSameGroup = activeElementList.value.every(el => (el.groupId && el.groupId) === firstGroupId)\n return !inSameGroup\n })\n\n /**\n * 组合当前选中的元素:给当前选中的元素赋予一个相同的分组ID\n */\n const combineElements = () => {\n if (!activeElementList.value.length) return\n\n // 生成一个新元素列表进行后续操作\n let newElementList: PPTElement[] = JSON.parse(JSON.stringify(currentSlide.value.elements))\n\n // 生成分组ID\n const groupId = nanoid(10)\n\n // 收集需要组合的元素列表,并赋上唯一分组ID\n const combineElementList: PPTElement[] = []\n for (const element of newElementList) {\n if (activeElementIdList.value.includes(element.id)) {\n element.groupId = groupId\n combineElementList.push(element)\n }\n }\n\n // 确保该组合内所有元素成员的层级是连续的,具体操作方法为:\n // 先获取到该组合内最上层元素的层级,将本次需要组合的元素从新元素列表中移除,\n // 再根据最上层元素的层级位置,将上面收集到的需要组合的元素列表一起插入到新元素列表中合适的位置\n const combineElementMaxLevel = newElementList.findIndex(_element => _element.id === combineElementList[combineElementList.length - 1].id)\n const combineElementIdList = combineElementList.map(_element => _element.id)\n newElementList = newElementList.filter(_element => !combineElementIdList.includes(_element.id))\n\n const insertLevel = combineElementMaxLevel - combineElementList.length + 1\n newElementList.splice(insertLevel, 0, ...combineElementList)\n\n slidesStore.updateSlide({ elements: newElementList })\n addHistorySnapshot()\n }\n\n /**\n * 取消组合元素:移除选中元素的分组ID\n */\n const uncombineElements = () => {\n if (!activeElementList.value.length) return\n const hasElementInGroup = activeElementList.value.some(item => item.groupId)\n if (!hasElementInGroup) return\n \n const newElementList: PPTElement[] = JSON.parse(JSON.stringify(currentSlide.value.elements))\n for (const element of newElementList) {\n if (activeElementIdList.value.includes(element.id) && element.groupId) delete element.groupId\n }\n slidesStore.updateSlide({ elements: newElementList })\n\n // 取消组合后,需要重置激活元素状态\n // 默认重置为当前正在操作的元素,如果不存在则重置为空\n const handleElementIdList = handleElementId.value ? [handleElementId.value] : []\n mainStore.setActiveElementIdList(handleElementIdList)\n\n addHistorySnapshot()\n }\n\n return {\n canCombine,\n combineElements,\n uncombineElements,\n }\n}","import { storeToRefs } from 'pinia'\nimport { useMainStore } from '@/store'\nimport { copyText, readClipboard } from '@/utils/clipboard'\nimport { encrypt } from '@/utils/crypto'\nimport { message } from 'ant-design-vue'\nimport usePasteTextClipboardData from '@/hooks/usePasteTextClipboardData'\nimport useDeleteElement from './useDeleteElement'\n\nexport default () => {\n const mainStore = useMainStore()\n const { activeElementIdList, activeElementList } = storeToRefs(mainStore)\n\n const { pasteTextClipboardData } = usePasteTextClipboardData()\n const { deleteElement } = useDeleteElement()\n\n // 将选中元素数据加密后复制到剪贴板\n const copyElement = () => {\n if (!activeElementIdList.value.length) return\n\n const text = encrypt(JSON.stringify({\n type: 'elements',\n data: activeElementList.value,\n }))\n\n copyText(text).then(() => {\n mainStore.setEditorareaFocus(true)\n })\n }\n\n // 将选中元素复制后删除(剪切)\n const cutElement = () => {\n copyElement()\n deleteElement()\n }\n\n // 尝试将剪贴板元素数据解密后进行粘贴\n const pasteElement = () => {\n readClipboard().then(text => {\n pasteTextClipboardData(text)\n }).catch(err => message.warning(err))\n }\n\n // 将选中元素复制后立刻粘贴\n const quickCopyElement = () => {\n copyElement()\n pasteElement()\n }\n\n return {\n copyElement,\n cutElement,\n pasteElement,\n quickCopyElement,\n }\n}","import { storeToRefs } from 'pinia'\r\nimport { useMainStore, useSlidesStore } from '@/store'\r\n\r\nexport default () => {\r\n const mainStore = useMainStore()\r\n const { currentSlide } = storeToRefs(useSlidesStore())\r\n\r\n // 将当前页面全部元素设置为被选择状态\r\n const selectAllElement = () => {\r\n const unlockedElements = currentSlide.value.elements.filter(el => !el.lock)\r\n const newActiveElementIdList = unlockedElements.map(el => el.id)\r\n mainStore.setActiveElementIdList(newActiveElementIdList)\r\n }\r\n\r\n return {\r\n selectAllElement,\r\n }\r\n}","import { storeToRefs } from 'pinia'\r\nimport { useMainStore, useSlidesStore } from '@/store'\r\nimport { PPTElement } from '@/types/slides'\r\nimport { KEYS } from '@/configs/hotkey'\r\nimport useHistorySnapshot from '@/hooks/useHistorySnapshot'\r\n\r\nexport default () => {\r\n const slidesStore = useSlidesStore()\r\n const { activeElementIdList, activeGroupElementId } = storeToRefs(useMainStore())\r\n const { currentSlide } = storeToRefs(slidesStore)\r\n\r\n const { addHistorySnapshot } = useHistorySnapshot()\r\n\r\n /**\r\n * 将元素向指定方向移动指定的距离\r\n * 组合元素成员中,存在被选中可独立操作的元素时,优先移动该元素。否则默认移动所有被选中的元素\r\n * @param command 移动方向\r\n * @param step 移动距离\r\n */\r\n const moveElement = (command: string, step = 1) => {\r\n let newElementList: PPTElement[] = []\r\n\r\n const move = (el: PPTElement) => {\r\n let { left, top } = el\r\n switch (command) {\r\n case KEYS.LEFT: \r\n left = left - step\r\n break\r\n case KEYS.RIGHT: \r\n left = left + step\r\n break\r\n case KEYS.UP: \r\n top = top - step\r\n break\r\n case KEYS.DOWN: \r\n top = top + step\r\n break\r\n default: break\r\n }\r\n return { ...el, left, top }\r\n }\r\n\r\n if (activeGroupElementId.value) {\r\n newElementList = currentSlide.value.elements.map(el => {\r\n return activeGroupElementId.value === el.id ? move(el) : el\r\n })\r\n }\r\n else {\r\n newElementList = currentSlide.value.elements.map(el => {\r\n return activeElementIdList.value.includes(el.id) ? move(el) : el\r\n })\r\n }\r\n\r\n slidesStore.updateSlide({ elements: newElementList })\r\n addHistorySnapshot()\r\n }\r\n\r\n return {\r\n moveElement,\r\n }\r\n}","import { storeToRefs } from 'pinia'\nimport { useSlidesStore } from '@/store'\nimport { PPTElement } from '@/types/slides'\nimport { ElementOrderCommands } from '@/types/edit'\nimport useHistorySnapshot from '@/hooks/useHistorySnapshot'\n\nexport default () => {\n const slidesStore = useSlidesStore()\n const { currentSlide } = storeToRefs(slidesStore)\n\n const { addHistorySnapshot } = useHistorySnapshot()\n\n /**\n * 获取组合元素层级范围\n * @param elementList 本页所有元素列表\n * @param combineElementList 组合元素列表\n */\n const getCombineElementLevelRange = (elementList: PPTElement[], combineElementList: PPTElement[]) => {\n return {\n minLevel: elementList.findIndex(_element => _element.id === combineElementList[0].id),\n maxLevel: elementList.findIndex(_element => _element.id === combineElementList[combineElementList.length - 1].id),\n }\n }\n\n /**\n * 上移一层\n * @param elementList 本页所有元素列表\n * @param element 当前操作的元素\n */\n const moveUpElement = (elementList: PPTElement[], element: PPTElement) => {\n const copyOfElementList: PPTElement[] = JSON.parse(JSON.stringify(elementList))\n\n // 如果被操作的元素是组合元素成员,需要将该组合全部成员一起进行移动\n if (element.groupId) {\n\n // 获取到该组合全部成员,以及所有成员的层级范围\n const combineElementList = copyOfElementList.filter(_element => _element.groupId === element.groupId)\n const { minLevel, maxLevel } = getCombineElementLevelRange(elementList, combineElementList)\n\n // 已经处在顶层,无法继续移动\n if (maxLevel === elementList.length - 1) return\n\n // 通过组合成员范围的最大值,获取到该组合上一层的元素,然后将该组合元素从元素列表中移除(并缓存被移除的元素列表)\n // 若上层元素处在另一个组合中,则将上述被移除的组合元素插入到该上层组合上方\n // 若上层元素不处于任何分组中,则将上述被移除的组合元素插入到该上层元素上方\n const nextElement = copyOfElementList[maxLevel + 1]\n const movedElementList = copyOfElementList.splice(minLevel, combineElementList.length)\n\n if (nextElement.groupId) {\n const nextCombineElementList = copyOfElementList.filter(_element => _element.groupId === nextElement.groupId)\n copyOfElementList.splice(minLevel + nextCombineElementList.length, 0, ...movedElementList)\n }\n else copyOfElementList.splice(minLevel + 1, 0, ...movedElementList)\n }\n\n // 如果被操作的元素不是组合元素成员\n else {\n\n // 获取该元素在列表中的层级\n const level = elementList.findIndex(item => item.id === element.id)\n\n // 已经处在顶层,无法继续移动\n if (level === elementList.length - 1) return\n\n // 获取到该组合上一层的元素,然后将该组合元素从元素列表中移除(并缓存被移除的元素列表)\n const nextElement = copyOfElementList[level + 1]\n const movedElement = copyOfElementList.splice(level, 1)[0]\n\n // 通过组合成员范围的最大值,获取到该组合上一层的元素,然后将该组合元素从元素列表中移除(并缓存被移除的元素列表)\n // 若上层元素处在另一个组合中,则将上述被移除的组合元素插入到该上层组合上方\n // 若上层元素不处于任何分组中,则将上述被移除的组合元素插入到该上层元素上方\n if (nextElement.groupId) {\n const combineElementList = copyOfElementList.filter(_element => _element.groupId === nextElement.groupId)\n copyOfElementList.splice(level + combineElementList.length, 0, movedElement)\n }\n else copyOfElementList.splice(level + 1, 0, movedElement)\n }\n\n return copyOfElementList\n }\n\n /**\n * 下移一层,操作方式同上移\n * @param elementList 本页所有元素列表\n * @param element 当前操作的元素\n */\n const moveDownElement = (elementList: PPTElement[], element: PPTElement) => {\n const copyOfElementList: PPTElement[] = JSON.parse(JSON.stringify(elementList))\n\n if (element.groupId) {\n const combineElementList = copyOfElementList.filter(_element => _element.groupId === element.groupId)\n const { minLevel } = getCombineElementLevelRange(elementList, combineElementList)\n if (minLevel === 0) return\n\n const prevElement = copyOfElementList[minLevel - 1]\n const movedElementList = copyOfElementList.splice(minLevel, combineElementList.length)\n\n if (prevElement.groupId) {\n const prevCombineElementList = copyOfElementList.filter(_element => _element.groupId === prevElement.groupId)\n copyOfElementList.splice(minLevel - prevCombineElementList.length, 0, ...movedElementList)\n }\n else copyOfElementList.splice(minLevel - 1, 0, ...movedElementList)\n }\n\n else {\n const level = elementList.findIndex(item => item.id === element.id)\n if (level === 0) return\n\n const prevElement = copyOfElementList[level - 1]\n const movedElement = copyOfElementList.splice(level, 1)[0]\n\n if (prevElement.groupId) {\n const combineElementList = copyOfElementList.filter(_element => _element.groupId === prevElement.groupId)\n copyOfElementList.splice(level - combineElementList.length, 0, movedElement)\n }\n else copyOfElementList.splice(level - 1, 0, movedElement)\n }\n\n return copyOfElementList\n }\n\n /**\n * 置顶层\n * @param elementList 本页所有元素列表\n * @param element 当前操作的元素\n */\n const moveTopElement = (elementList: PPTElement[], element: PPTElement) => {\n const copyOfElementList: PPTElement[] = JSON.parse(JSON.stringify(elementList))\n\n // 如果被操作的元素是组合元素成员,需要将该组合全部成员一起进行移动\n if (element.groupId) {\n\n // 获取到该组合全部成员,以及所有成员的层级范围\n const combineElementList = copyOfElementList.filter(_element => _element.groupId === element.groupId)\n const { minLevel, maxLevel } = getCombineElementLevelRange(elementList, combineElementList)\n\n // 已经处在顶层,无法继续移动\n if (maxLevel === elementList.length - 1) return null\n\n // 将该组合元素从元素列表中移除,然后将被移除的元素添加到元素列表顶部\n const movedElementList = copyOfElementList.splice(minLevel, combineElementList.length)\n copyOfElementList.push(...movedElementList)\n }\n\n // 如果被操作的元素不是组合元素成员\n else {\n\n // 获取该元素在列表中的层级\n const level = elementList.findIndex(item => item.id === element.id)\n\n // 已经处在顶层,无法继续移动\n if (level === elementList.length - 1) return null\n\n // 将该组合元素从元素列表中移除,然后将被移除的元素添加到元素列表底部\n copyOfElementList.splice(level, 1)\n copyOfElementList.push(element)\n }\n\n return copyOfElementList\n }\n\n /**\n * 置底层,操作方式同置顶\n * @param elementList 本页所有元素列表\n * @param element 当前操作的元素\n */\n const moveBottomElement = (elementList: PPTElement[], element: PPTElement) => {\n const copyOfElementList: PPTElement[] = JSON.parse(JSON.stringify(elementList))\n\n if (element.groupId) {\n const combineElementList = copyOfElementList.filter(_element => _element.groupId === element.groupId)\n const { minLevel } = getCombineElementLevelRange(elementList, combineElementList)\n if (minLevel === 0) return\n\n const movedElementList = copyOfElementList.splice(minLevel, combineElementList.length)\n copyOfElementList.unshift(...movedElementList)\n }\n\n else {\n const level = elementList.findIndex(item => item.id === element.id)\n if (level === 0) return\n\n copyOfElementList.splice(level, 1)\n copyOfElementList.unshift(element)\n }\n\n return copyOfElementList\n }\n\n /**\n * 调整元素层级\n * @param element 需要调整层级的元素\n * @param command 调整命令:上移、下移、置顶、置底\n */\n const orderElement = (element: PPTElement, command: ElementOrderCommands) => {\n let newElementList\n \n if (command === ElementOrderCommands.UP) newElementList = moveUpElement(currentSlide.value.elements, element)\n else if (command === ElementOrderCommands.DOWN) newElementList = moveDownElement(currentSlide.value.elements, element)\n else if (command === ElementOrderCommands.TOP) newElementList = moveTopElement(currentSlide.value.elements, element)\n else if (command === ElementOrderCommands.BOTTOM) newElementList = moveBottomElement(currentSlide.value.elements, element)\n\n if (!newElementList) return\n\n slidesStore.updateSlide({ elements: newElementList })\n addHistorySnapshot()\n }\n\n return {\n orderElement,\n }\n}","// 进入全屏\nexport const enterFullscreen = () => {\n const docElm = document.documentElement\n if (docElm.requestFullscreen) docElm.requestFullscreen() \n else if (docElm.mozRequestFullScreen) docElm.mozRequestFullScreen() \n else if (docElm.webkitRequestFullScreen) docElm.webkitRequestFullScreen()\n else if (docElm.msRequestFullscreen) docElm.msRequestFullscreen()\n}\n\n// 退出全屏\nexport const exitFullscreen = () => {\n if (document.exitFullscreen) document.exitFullscreen()\n else if (document.mozCancelFullScreen) document.mozCancelFullScreen()\n else if (document.webkitExitFullscreen) document.webkitExitFullscreen()\n else if (document.msExitFullscreen) document.msExitFullscreen()\n}\n\n// 判断是否全屏\nexport const isFullscreen = () => {\n const fullscreenElement = \n document.fullscreenElement ||\n document.mozFullScreenElement ||\n document.webkitFullscreenElement ||\n document.msFullscreenElement ||\n document.webkitCurrentFullScreenElement\n return !!fullscreenElement\n}","import { useScreenStore, useSlidesStore } from '@/store'\nimport { enterFullscreen, exitFullscreen, isFullscreen } from '@/utils/fullscreen'\n\nexport default () => {\n const screenStore = useScreenStore()\n const slidesStore = useSlidesStore()\n\n // 进入放映状态(从当前页开始)\n const enterScreening = () => {\n enterFullscreen()\n screenStore.setScreening(true)\n }\n\n // 进入放映状态(从第一页开始)\n const enterScreeningFromStart = () => {\n slidesStore.updateSlideIndex(0)\n enterScreening()\n }\n\n // 退出放映状态\n const exitScreening = () => {\n screenStore.setScreening(false)\n if (isFullscreen()) exitFullscreen()\n }\n\n return {\n enterScreening,\n enterScreeningFromStart,\n exitScreening,\n }\n}","import { computed } from 'vue'\nimport { storeToRefs } from 'pinia'\nimport { useMainStore } from '@/store'\n\nexport default () => {\n const mainStore = useMainStore()\n const { canvasPercentage, canvasScale, canvasDragged } = storeToRefs(mainStore)\n\n const canvasScalePercentage = computed(() => Math.round(canvasScale.value * 100) + '%')\n\n /**\n * 缩放画布百分比\n * @param command 缩放命令:放大、缩小\n */\n const scaleCanvas = (command: '+' | '-') => {\n let percentage = canvasPercentage.value\n const step = 5\n const max = 200\n const min = 30\n if (command === '+' && percentage <= max) percentage += step\n if (command === '-' && percentage >= min) percentage -= step\n\n mainStore.setCanvasPercentage(percentage)\n }\n\n /**\n * 设置画布缩放比例\n * 但不是直接设置该值,而是通过设置画布可视区域百分比来动态计算\n * @param value 目标画布缩放比例\n */\n const setCanvasScalePercentage = (value: number) => {\n const percentage = Math.round(value / canvasScale.value * canvasPercentage.value) / 100\n mainStore.setCanvasPercentage(percentage)\n }\n\n /**\n * 重置画布尺寸和位置\n */\n const resetCanvas = () => {\n mainStore.setCanvasPercentage(90)\n if (canvasDragged) mainStore.setCanvasDragged(false)\n }\n\n return {\n canvasScalePercentage,\n setCanvasScalePercentage,\n scaleCanvas,\n resetCanvas,\n }\n}","import { onMounted, onUnmounted } from 'vue'\nimport { storeToRefs } from 'pinia'\nimport { useMainStore, useSlidesStore, useKeyboardStore } from '@/store'\nimport { ElementOrderCommands } from '@/types/edit'\nimport { KEYS } from '@/configs/hotkey'\n\nimport useSlideHandler from './useSlideHandler'\nimport useLockElement from './useLockElement'\nimport useDeleteElement from './useDeleteElement'\nimport useCombineElement from './useCombineElement'\nimport useCopyAndPasteElement from './useCopyAndPasteElement'\nimport useSelectAllElement from './useSelectAllElement'\nimport useMoveElement from './useMoveElement'\nimport useOrderElement from './useOrderElement'\nimport useHistorySnapshot from './useHistorySnapshot'\nimport useScreening from './useScreening'\nimport useScaleCanvas from './useScaleCanvas'\n\nexport default () => {\n const mainStore = useMainStore()\n const keyboardStore = useKeyboardStore()\n const {\n activeElementIdList,\n disableHotkeys,\n handleElement,\n handleElementId,\n editorAreaFocus,\n thumbnailsFocus,\n } = storeToRefs(mainStore)\n const { currentSlide } = storeToRefs(useSlidesStore())\n const { ctrlKeyState, shiftKeyState, spaceKeyState } = storeToRefs(keyboardStore)\n\n const {\n updateSlideIndex,\n copySlide,\n createSlide,\n deleteSlide,\n cutSlide,\n copyAndPasteSlide,\n selectAllSlide,\n } = useSlideHandler()\n\n const { combineElements, uncombineElements } = useCombineElement()\n const { deleteElement } = useDeleteElement()\n const { lockElement } = useLockElement()\n const { copyElement, cutElement, quickCopyElement } = useCopyAndPasteElement()\n const { selectAllElement } = useSelectAllElement()\n const { moveElement } = useMoveElement()\n const { orderElement } = useOrderElement()\n const { redo, undo } = useHistorySnapshot()\n const { enterScreening, enterScreeningFromStart } = useScreening()\n const { scaleCanvas, resetCanvas } = useScaleCanvas()\n\n const copy = () => {\n if (activeElementIdList.value.length) copyElement()\n else if (thumbnailsFocus.value) copySlide()\n }\n\n const cut = () => {\n if (activeElementIdList.value.length) cutElement()\n else if (thumbnailsFocus.value) cutSlide()\n }\n\n const quickCopy = () => {\n if (activeElementIdList.value.length) quickCopyElement()\n else if (thumbnailsFocus.value) copyAndPasteSlide()\n }\n\n const selectAll = () => {\n if (editorAreaFocus.value) selectAllElement()\n if (thumbnailsFocus.value) selectAllSlide()\n }\n\n const lock = () => {\n if (!editorAreaFocus.value) return\n lockElement()\n }\n const combine = () => {\n if (!editorAreaFocus.value) return\n combineElements()\n }\n\n const uncombine = () => {\n if (!editorAreaFocus.value) return\n uncombineElements()\n }\n\n const remove = () => {\n if (activeElementIdList.value.length) deleteElement()\n else if (thumbnailsFocus.value) deleteSlide()\n }\n\n const move = (key: string) => {\n if (activeElementIdList.value.length) moveElement(key)\n else if (key === KEYS.UP || key === KEYS.DOWN) updateSlideIndex(key)\n }\n\n const moveSlide = (key: string) => {\n if (key === KEYS.PAGEUP) updateSlideIndex(KEYS.UP)\n else if (key === KEYS.PAGEDOWN) updateSlideIndex(KEYS.DOWN)\n }\n\n const order = (command: ElementOrderCommands) => {\n if (!handleElement.value) return\n orderElement(handleElement.value, command)\n }\n\n const create = () => {\n if (!thumbnailsFocus.value) return\n createSlide()\n }\n\n const tabActiveElement = () => {\n if (!currentSlide.value.elements.length) return\n if (!handleElementId.value) {\n const firstElement = currentSlide.value.elements[0]\n mainStore.setActiveElementIdList([firstElement.id])\n return\n }\n const currentIndex = currentSlide.value.elements.findIndex(el => el.id === handleElementId.value)\n const nextIndex = currentIndex >= currentSlide.value.elements.length - 1 ? 0 : currentIndex + 1\n const nextElementId = currentSlide.value.elements[nextIndex].id\n\n mainStore.setActiveElementIdList([nextElementId])\n }\n\n const keydownListener = (e: KeyboardEvent) => {\n const { ctrlKey, shiftKey, altKey, metaKey } = e\n const ctrlOrMetaKeyActive = ctrlKey || metaKey\n \n const key = e.key.toUpperCase()\n\n if (ctrlOrMetaKeyActive && !ctrlKeyState.value) keyboardStore.setCtrlKeyState(true)\n if (shiftKey && !shiftKeyState.value) keyboardStore.setShiftKeyState(true)\n if (!disableHotkeys.value && key === KEYS.SPACE) keyboardStore.setSpaceKeyState(true)\n\n \n if (ctrlOrMetaKeyActive && key === KEYS.P) {\n e.preventDefault()\n mainStore.setDialogForExport('pdf')\n return\n }\n if (shiftKey && key === KEYS.F5) {\n e.preventDefault()\n enterScreening()\n keyboardStore.setShiftKeyState(false)\n return\n }\n if (key === KEYS.F5) {\n e.preventDefault()\n enterScreeningFromStart()\n return\n }\n \n if (!editorAreaFocus.value && !thumbnailsFocus.value) return \n\n if (ctrlOrMetaKeyActive && key === KEYS.C) {\n if (disableHotkeys.value) return\n e.preventDefault()\n copy()\n }\n if (ctrlOrMetaKeyActive && key === KEYS.X) {\n if (disableHotkeys.value) return\n e.preventDefault()\n cut()\n }\n if (ctrlOrMetaKeyActive && key === KEYS.D) {\n if (disableHotkeys.value) return\n e.preventDefault()\n quickCopy()\n }\n if (ctrlOrMetaKeyActive && key === KEYS.Z) {\n if (disableHotkeys.value) return\n e.preventDefault()\n undo()\n }\n if (ctrlOrMetaKeyActive && key === KEYS.Y) {\n if (disableHotkeys.value) return\n e.preventDefault()\n redo()\n }\n if (ctrlOrMetaKeyActive && key === KEYS.A) {\n if (disableHotkeys.value) return\n e.preventDefault()\n selectAll()\n }\n if (ctrlOrMetaKeyActive && key === KEYS.L) {\n if (disableHotkeys.value) return\n e.preventDefault()\n lock()\n }\n if (!shiftKey && ctrlOrMetaKeyActive && key === KEYS.G) {\n if (disableHotkeys.value) return\n e.preventDefault()\n combine()\n }\n if (shiftKey && ctrlOrMetaKeyActive && key === KEYS.G) {\n if (disableHotkeys.value) return\n e.preventDefault()\n uncombine()\n }\n if (altKey && key === KEYS.F) {\n if (disableHotkeys.value) return\n e.preventDefault()\n order(ElementOrderCommands.TOP)\n }\n if (altKey && key === KEYS.B) {\n if (disableHotkeys.value) return\n e.preventDefault()\n order(ElementOrderCommands.BOTTOM)\n }\n if (key === KEYS.DELETE || key === KEYS.BACKSPACE) {\n if (disableHotkeys.value) return\n e.preventDefault()\n remove()\n }\n if (key === KEYS.UP) {\n if (disableHotkeys.value) return\n e.preventDefault()\n move(KEYS.UP)\n }\n if (key === KEYS.DOWN) {\n if (disableHotkeys.value) return\n e.preventDefault()\n move(KEYS.DOWN)\n }\n if (key === KEYS.LEFT) {\n if (disableHotkeys.value) return\n e.preventDefault()\n move(KEYS.LEFT)\n }\n if (key === KEYS.RIGHT) {\n if (disableHotkeys.value) return\n e.preventDefault()\n move(KEYS.RIGHT)\n }\n if (key === KEYS.PAGEUP) {\n if (disableHotkeys.value) return\n e.preventDefault()\n moveSlide(KEYS.PAGEUP)\n }\n if (key === KEYS.PAGEDOWN) {\n if (disableHotkeys.value) return\n e.preventDefault()\n moveSlide(KEYS.PAGEDOWN)\n }\n if (key === KEYS.ENTER) {\n if (disableHotkeys.value) return\n e.preventDefault()\n create()\n }\n if (key === KEYS.MINUS) {\n if (disableHotkeys.value) return\n e.preventDefault()\n scaleCanvas('-')\n }\n if (key === KEYS.EQUAL) {\n if (disableHotkeys.value) return\n e.preventDefault()\n scaleCanvas('+')\n }\n if (key === KEYS.DIGIT_0) {\n if (disableHotkeys.value) return\n e.preventDefault()\n resetCanvas()\n }\n if (key === KEYS.TAB) {\n if (disableHotkeys.value) return\n e.preventDefault()\n tabActiveElement()\n }\n }\n \n const keyupListener = () => {\n if (ctrlKeyState.value) keyboardStore.setCtrlKeyState(false)\n if (shiftKeyState.value) keyboardStore.setShiftKeyState(false)\n if (spaceKeyState.value) keyboardStore.setSpaceKeyState(false)\n }\n\n onMounted(() => {\n document.addEventListener('keydown', keydownListener)\n document.addEventListener('keyup', keyupListener)\n window.addEventListener('blur', keyupListener)\n })\n onUnmounted(() => {\n document.removeEventListener('keydown', keydownListener)\n document.removeEventListener('keyup', keyupListener)\n window.removeEventListener('blur', keyupListener)\n })\n}","import { onMounted, onUnmounted } from 'vue'\nimport { storeToRefs } from 'pinia'\nimport { useMainStore } from '@/store'\nimport { getImageDataURL } from '@/utils/image'\nimport usePasteTextClipboardData from './usePasteTextClipboardData'\nimport useCreateElement from './useCreateElement'\n\nexport default () => {\n const { editorAreaFocus, thumbnailsFocus, disableHotkeys } = storeToRefs(useMainStore())\n\n const { pasteTextClipboardData } = usePasteTextClipboardData()\n const { createImageElement } = useCreateElement()\n\n // 粘贴图片到幻灯片元素\n const pasteImageFile = (imageFile: File) => {\n getImageDataURL(imageFile).then(dataURL => createImageElement(dataURL))\n }\n\n /**\n * 粘贴事件监听\n * @param e ClipboardEvent\n */\n const pasteListener = (e: ClipboardEvent) => {\n if (!editorAreaFocus.value && !thumbnailsFocus.value) return\n if (disableHotkeys.value) return\n\n if (!e.clipboardData) return\n\n const clipboardDataItems = e.clipboardData.items\n const clipboardDataFirstItem = clipboardDataItems[0]\n\n if (!clipboardDataFirstItem) return\n\n // 如果剪贴板内有图片,优先尝试读取图片\n for (const item of clipboardDataItems) {\n if (item.kind === 'file' && item.type.indexOf('image') !== -1) {\n const imageFile = item.getAsFile()\n if (imageFile) pasteImageFile(imageFile)\n return\n }\n }\n \n // 如果剪贴板内没有图片,但有文字内容,尝试解析文字内容\n if (clipboardDataFirstItem.kind === 'string' && clipboardDataFirstItem.type === 'text/plain') {\n clipboardDataFirstItem.getAsString(text => pasteTextClipboardData(text))\n }\n }\n\n onMounted(() => {\n document.addEventListener('paste', pasteListener)\n })\n onUnmounted(() => {\n document.removeEventListener('paste', pasteListener)\n })\n}","import { resolveComponent as _resolveComponent, createVNode as _createVNode, createElementVNode as _createElementVNode, createTextVNode as _createTextVNode, withCtx as _withCtx, toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-b81834c4\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = { class: \"editor-header\" }\nconst _hoisted_2 = { class: \"left\" }\nconst _hoisted_3 = { class: \"menu-item\" }\nconst _hoisted_4 = /*#__PURE__*/_createTextVNode()\nconst _hoisted_5 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(\"span\", { class: \"text\" }, \"文件\", -1))\nconst _hoisted_6 = /*#__PURE__*/_createTextVNode(\"导出 PPTX\")\nconst _hoisted_7 = /*#__PURE__*/_createTextVNode(\"导出图片\")\nconst _hoisted_8 = /*#__PURE__*/_createTextVNode(\"导出 JSON\")\nconst _hoisted_9 = /*#__PURE__*/_createTextVNode(\"打印 / 导出 PDF\")\nconst _hoisted_10 = { class: \"menu-item\" }\nconst _hoisted_11 = /*#__PURE__*/_createTextVNode()\nconst _hoisted_12 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(\"span\", { class: \"text\" }, \"编辑\", -1))\nconst _hoisted_13 = /*#__PURE__*/_createTextVNode(\"撤销\")\nconst _hoisted_14 = /*#__PURE__*/_createTextVNode(\"重做\")\nconst _hoisted_15 = /*#__PURE__*/_createTextVNode(\"添加页面\")\nconst _hoisted_16 = /*#__PURE__*/_createTextVNode(\"删除页面\")\nconst _hoisted_17 = /*#__PURE__*/_createTextVNode(\"重置幻灯片\")\nconst _hoisted_18 = { class: \"menu-item\" }\nconst _hoisted_19 = /*#__PURE__*/_createTextVNode()\nconst _hoisted_20 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(\"span\", { class: \"text\" }, \"演示\", -1))\nconst _hoisted_21 = /*#__PURE__*/_createTextVNode(\"从头开始\")\nconst _hoisted_22 = /*#__PURE__*/_createTextVNode(\"从当前页开始\")\nconst _hoisted_23 = { class: \"menu-item\" }\nconst _hoisted_24 = /*#__PURE__*/_createTextVNode()\nconst _hoisted_25 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(\"span\", { class: \"text\" }, \"帮助\", -1))\nconst _hoisted_26 = /*#__PURE__*/_createTextVNode(\"意见反馈\")\nconst _hoisted_27 = /*#__PURE__*/_createTextVNode(\"快捷键\")\nconst _hoisted_28 = { class: \"right\" }\nconst _hoisted_29 = {\n href: \"https://github.com/pipipi-pikachu/PPTist\",\n target: \"_blank\"\n}\nconst _hoisted_30 = { class: \"menu-item\" }\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _component_IconFolderClose = _resolveComponent(\"IconFolderClose\")!\n const _component_MenuItem = _resolveComponent(\"MenuItem\")!\n const _component_Menu = _resolveComponent(\"Menu\")!\n const _component_Dropdown = _resolveComponent(\"Dropdown\")!\n const _component_IconEdit = _resolveComponent(\"IconEdit\")!\n const _component_IconPpt = _resolveComponent(\"IconPpt\")!\n const _component_IconHelpcenter = _resolveComponent(\"IconHelpcenter\")!\n const _component_IconShare = _resolveComponent(\"IconShare\")!\n const _component_Tooltip = _resolveComponent(\"Tooltip\")!\n const _component_IconGithub = _resolveComponent(\"IconGithub\")!\n const _component_HotkeyDoc = _resolveComponent(\"HotkeyDoc\")!\n const _component_Drawer = _resolveComponent(\"Drawer\")!\n\n return (_openBlock(), _createElementBlock(\"div\", _hoisted_1, [\n _createElementVNode(\"div\", _hoisted_2, [\n _createVNode(_component_Dropdown, { trigger: ['click'] }, {\n overlay: _withCtx(() => [\n _createVNode(_component_Menu, null, {\n default: _withCtx(() => [\n _createVNode(_component_MenuItem, {\n onClick: _cache[0] || (_cache[0] = ($event: any) => (_ctx.setDialogForExport('pptx')))\n }, {\n default: _withCtx(() => [\n _hoisted_6\n ]),\n _: 1\n }),\n _createVNode(_component_MenuItem, {\n onClick: _cache[1] || (_cache[1] = ($event: any) => (_ctx.setDialogForExport('image')))\n }, {\n default: _withCtx(() => [\n _hoisted_7\n ]),\n _: 1\n }),\n _createVNode(_component_MenuItem, {\n onClick: _cache[2] || (_cache[2] = ($event: any) => (_ctx.setDialogForExport('json')))\n }, {\n default: _withCtx(() => [\n _hoisted_8\n ]),\n _: 1\n }),\n _createVNode(_component_MenuItem, {\n onClick: _cache[3] || (_cache[3] = ($event: any) => (_ctx.setDialogForExport('pdf')))\n }, {\n default: _withCtx(() => [\n _hoisted_9\n ]),\n _: 1\n })\n ]),\n _: 1\n })\n ]),\n default: _withCtx(() => [\n _createElementVNode(\"div\", _hoisted_3, [\n _createVNode(_component_IconFolderClose),\n _hoisted_4,\n _hoisted_5\n ])\n ]),\n _: 1\n }),\n _createVNode(_component_Dropdown, { trigger: ['click'] }, {\n overlay: _withCtx(() => [\n _createVNode(_component_Menu, null, {\n default: _withCtx(() => [\n _createVNode(_component_MenuItem, {\n onClick: _cache[4] || (_cache[4] = ($event: any) => (_ctx.undo()))\n }, {\n default: _withCtx(() => [\n _hoisted_13\n ]),\n _: 1\n }),\n _createVNode(_component_MenuItem, {\n onClick: _cache[5] || (_cache[5] = ($event: any) => (_ctx.redo()))\n }, {\n default: _withCtx(() => [\n _hoisted_14\n ]),\n _: 1\n }),\n _createVNode(_component_MenuItem, {\n onClick: _cache[6] || (_cache[6] = ($event: any) => (_ctx.createSlide()))\n }, {\n default: _withCtx(() => [\n _hoisted_15\n ]),\n _: 1\n }),\n _createVNode(_component_MenuItem, {\n onClick: _cache[7] || (_cache[7] = ($event: any) => (_ctx.deleteSlide()))\n }, {\n default: _withCtx(() => [\n _hoisted_16\n ]),\n _: 1\n }),\n _createVNode(_component_MenuItem, {\n onClick: _cache[8] || (_cache[8] = ($event: any) => (_ctx.toggleGridLines()))\n }, {\n default: _withCtx(() => [\n _createTextVNode(_toDisplayString(_ctx.showGridLines ? '关闭网格线' : '打开网格线'), 1)\n ]),\n _: 1\n }),\n _createVNode(_component_MenuItem, {\n onClick: _cache[9] || (_cache[9] = ($event: any) => (_ctx.toggleRuler()))\n }, {\n default: _withCtx(() => [\n _createTextVNode(_toDisplayString(_ctx.showRuler ? '关闭标尺' : '打开标尺'), 1)\n ]),\n _: 1\n }),\n _createVNode(_component_MenuItem, {\n onClick: _cache[10] || (_cache[10] = ($event: any) => (_ctx.resetSlides()))\n }, {\n default: _withCtx(() => [\n _hoisted_17\n ]),\n _: 1\n })\n ]),\n _: 1\n })\n ]),\n default: _withCtx(() => [\n _createElementVNode(\"div\", _hoisted_10, [\n _createVNode(_component_IconEdit),\n _hoisted_11,\n _hoisted_12\n ])\n ]),\n _: 1\n }),\n _createVNode(_component_Dropdown, { trigger: ['click'] }, {\n overlay: _withCtx(() => [\n _createVNode(_component_Menu, null, {\n default: _withCtx(() => [\n _createVNode(_component_MenuItem, {\n onClick: _cache[11] || (_cache[11] = ($event: any) => (_ctx.enterScreeningFromStart()))\n }, {\n default: _withCtx(() => [\n _hoisted_21\n ]),\n _: 1\n }),\n _createVNode(_component_MenuItem, {\n onClick: _cache[12] || (_cache[12] = ($event: any) => (_ctx.enterScreening()))\n }, {\n default: _withCtx(() => [\n _hoisted_22\n ]),\n _: 1\n })\n ]),\n _: 1\n })\n ]),\n default: _withCtx(() => [\n _createElementVNode(\"div\", _hoisted_18, [\n _createVNode(_component_IconPpt),\n _hoisted_19,\n _hoisted_20\n ])\n ]),\n _: 1\n }),\n _createVNode(_component_Dropdown, { trigger: ['click'] }, {\n overlay: _withCtx(() => [\n _createVNode(_component_Menu, null, {\n default: _withCtx(() => [\n _createVNode(_component_MenuItem, {\n onClick: _cache[13] || (_cache[13] = ($event: any) => (_ctx.goIssues()))\n }, {\n default: _withCtx(() => [\n _hoisted_26\n ]),\n _: 1\n }),\n _createVNode(_component_MenuItem, {\n onClick: _cache[14] || (_cache[14] = ($event: any) => (_ctx.hotkeyDrawerVisible = true))\n }, {\n default: _withCtx(() => [\n _hoisted_27\n ]),\n _: 1\n })\n ]),\n _: 1\n })\n ]),\n default: _withCtx(() => [\n _createElementVNode(\"div\", _hoisted_23, [\n _createVNode(_component_IconHelpcenter),\n _hoisted_24,\n _hoisted_25\n ])\n ]),\n _: 1\n })\n ]),\n _createElementVNode(\"div\", _hoisted_28, [\n _createVNode(_component_Tooltip, {\n mouseLeaveDelay: 0,\n title: \"导出\"\n }, {\n default: _withCtx(() => [\n _createElementVNode(\"div\", {\n class: \"menu-item\",\n onClick: _cache[15] || (_cache[15] = ($event: any) => (_ctx.setDialogForExport('pptx')))\n }, [\n _createVNode(_component_IconShare, {\n size: \"18\",\n fill: \"#666\"\n })\n ])\n ]),\n _: 1\n }),\n _createVNode(_component_Tooltip, {\n mouseLeaveDelay: 0,\n title: \"幻灯片放映\"\n }, {\n default: _withCtx(() => [\n _createElementVNode(\"div\", {\n class: \"menu-item\",\n onClick: _cache[16] || (_cache[16] = ($event: any) => (_ctx.enterScreening()))\n }, [\n _createVNode(_component_IconPpt, {\n size: \"19\",\n fill: \"#666\",\n style: {\"margin-top\":\"1px\"}\n })\n ])\n ]),\n _: 1\n }),\n _createElementVNode(\"a\", _hoisted_29, [\n _createElementVNode(\"div\", _hoisted_30, [\n _createVNode(_component_IconGithub, {\n size: \"18\",\n fill: \"#666\"\n })\n ])\n ])\n ]),\n _createVNode(_component_Drawer, {\n width: \"320\",\n placement: \"right\",\n visible: _ctx.hotkeyDrawerVisible,\n onClose: _cache[17] || (_cache[17] = ($event: any) => (_ctx.hotkeyDrawerVisible = false))\n }, {\n default: _withCtx(() => [\n _createVNode(_component_HotkeyDoc)\n ]),\n _: 1\n }, 8, [\"visible\"])\n ]))\n}","import { renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, toDisplayString as _toDisplayString, createElementVNode as _createElementVNode, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-b6d79f64\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = { class: \"hotkey-doc\" }\nconst _hoisted_2 = { class: \"title\" }\nconst _hoisted_3 = { class: \"label\" }\nconst _hoisted_4 = { class: \"value\" }\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n return (_openBlock(), _createElementBlock(\"div\", _hoisted_1, [\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.hotkeys, (item) => {\n return (_openBlock(), _createElementBlock(_Fragment, {\n key: item.type\n }, [\n _createElementVNode(\"div\", _hoisted_2, _toDisplayString(item.type), 1),\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(item.children, (hotkey) => {\n return (_openBlock(), _createElementBlock(\"div\", {\n class: \"hotkey-item\",\n key: hotkey.label\n }, [\n _createElementVNode(\"div\", _hoisted_3, _toDisplayString(hotkey.label), 1),\n _createElementVNode(\"div\", _hoisted_4, _toDisplayString(hotkey.value), 1)\n ]))\n }), 128))\n ], 64))\n }), 128))\n ]))\n}","\nimport { defineComponent } from 'vue'\nimport { HOTKEY_DOC } from '@/configs/hotkey'\n\nexport default defineComponent({\n name: 'hotkey-doc',\n setup() {\n return {\n hotkeys: HOTKEY_DOC,\n }\n },\n})\n","import { render } from \"./HotkeyDoc.vue?vue&type=template&id=b6d79f64&scoped=true&ts=true\"\nimport script from \"./HotkeyDoc.vue?vue&type=script&lang=ts\"\nexport * from \"./HotkeyDoc.vue?vue&type=script&lang=ts\"\n\nimport \"./HotkeyDoc.vue?vue&type=style&index=0&id=b6d79f64&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-b6d79f64\"]])\n\nexport default __exports__","\nimport { defineComponent, ref } from 'vue'\nimport { storeToRefs } from 'pinia'\nimport { useMainStore } from '@/store'\nimport useScreening from '@/hooks/useScreening'\nimport useSlideHandler from '@/hooks/useSlideHandler'\nimport useHistorySnapshot from '@/hooks/useHistorySnapshot'\n\nimport HotkeyDoc from './HotkeyDoc.vue'\n\nexport default defineComponent({\n name: 'editor-header',\n components: {\n HotkeyDoc,\n },\n setup() {\n const mainStore = useMainStore()\n const { showGridLines, showRuler } = storeToRefs(mainStore)\n\n const { enterScreening, enterScreeningFromStart } = useScreening()\n const { createSlide, deleteSlide, resetSlides } = useSlideHandler()\n const { redo, undo } = useHistorySnapshot()\n\n const setDialogForExport = mainStore.setDialogForExport\n\n const toggleGridLines = () => {\n mainStore.setGridLinesState(!showGridLines.value)\n }\n\n const toggleRuler = () => {\n mainStore.setRulerState(!showRuler.value)\n }\n\n const hotkeyDrawerVisible = ref(false)\n\n const goIssues = () => {\n window.open('https://github.com/pipipi-pikachu/PPTist/issues')\n }\n\n return {\n redo,\n undo,\n showGridLines,\n showRuler,\n hotkeyDrawerVisible,\n setDialogForExport,\n enterScreening,\n enterScreeningFromStart,\n createSlide,\n deleteSlide,\n toggleGridLines,\n toggleRuler,\n resetSlides,\n goIssues,\n }\n },\n})\n","import { render } from \"./index.vue?vue&type=template&id=b81834c4&scoped=true&ts=true\"\nimport script from \"./index.vue?vue&type=script&lang=ts\"\nexport * from \"./index.vue?vue&type=script&lang=ts\"\n\nimport \"./index.vue?vue&type=style&index=0&id=b81834c4&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-b81834c4\"]])\n\nexport default __exports__","import { resolveComponent as _resolveComponent, openBlock as _openBlock, createBlock as _createBlock, createCommentVNode as _createCommentVNode, renderList as _renderList, Fragment as _Fragment, createElementBlock as _createElementBlock, createVNode as _createVNode, createElementVNode as _createElementVNode, normalizeStyle as _normalizeStyle, withCtx as _withCtx, resolveDirective as _resolveDirective, withDirectives as _withDirectives, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-34ef1f93\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = { class: \"operates\" }\nconst _hoisted_2 = {\n key: 1,\n class: \"drag-mask\"\n}\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _component_ElementCreateSelection = _resolveComponent(\"ElementCreateSelection\")!\n const _component_AlignmentLine = _resolveComponent(\"AlignmentLine\")!\n const _component_MultiSelectOperate = _resolveComponent(\"MultiSelectOperate\")!\n const _component_Operate = _resolveComponent(\"Operate\")!\n const _component_ViewportBackground = _resolveComponent(\"ViewportBackground\")!\n const _component_MouseSelection = _resolveComponent(\"MouseSelection\")!\n const _component_EditableElement = _resolveComponent(\"EditableElement\")!\n const _component_Ruler = _resolveComponent(\"Ruler\")!\n const _component_LinkDialog = _resolveComponent(\"LinkDialog\")!\n const _component_Modal = _resolveComponent(\"Modal\")!\n const _directive_contextmenu = _resolveDirective(\"contextmenu\")!\n const _directive_click_outside = _resolveDirective(\"click-outside\")!\n\n return _withDirectives((_openBlock(), _createElementBlock(\"div\", {\n class: \"canvas\",\n ref: \"canvasRef\",\n onWheel: _cache[3] || (_cache[3] = $event => _ctx.handleMousewheelCanvas($event)),\n onMousedown: _cache[4] || (_cache[4] = $event => _ctx.handleClickBlankArea($event))\n }, [\n (_ctx.creatingElement)\n ? (_openBlock(), _createBlock(_component_ElementCreateSelection, {\n key: 0,\n onCreated: _cache[0] || (_cache[0] = data => _ctx.insertElementFromCreateSelection(data))\n }))\n : _createCommentVNode(\"\", true),\n _createElementVNode(\"div\", {\n class: \"viewport-wrapper\",\n style: _normalizeStyle({\n width: _ctx.viewportStyles.width * _ctx.canvasScale + 'px',\n height: _ctx.viewportStyles.height * _ctx.canvasScale + 'px',\n left: _ctx.viewportStyles.left + 'px',\n top: _ctx.viewportStyles.top + 'px',\n })\n }, [\n _createElementVNode(\"div\", _hoisted_1, [\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.alignmentLines, (line, index) => {\n return (_openBlock(), _createBlock(_component_AlignmentLine, {\n key: index,\n type: line.type,\n axis: line.axis,\n length: line.length\n }, null, 8, [\"type\", \"axis\", \"length\"]))\n }), 128)),\n (_ctx.activeElementIdList.length > 1)\n ? (_openBlock(), _createBlock(_component_MultiSelectOperate, {\n key: 0,\n elementList: _ctx.elementList,\n scaleMultiElement: _ctx.scaleMultiElement\n }, null, 8, [\"elementList\", \"scaleMultiElement\"]))\n : _createCommentVNode(\"\", true),\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.elementList, (element) => {\n return (_openBlock(), _createBlock(_component_Operate, {\n key: element.id,\n elementInfo: element,\n isSelected: _ctx.activeElementIdList.includes(element.id),\n isActive: _ctx.handleElementId === element.id,\n isActiveGroupElement: _ctx.activeGroupElementId === element.id,\n isMultiSelect: _ctx.activeElementIdList.length > 1,\n rotateElement: _ctx.rotateElement,\n scaleElement: _ctx.scaleElement,\n openLinkDialog: _ctx.openLinkDialog,\n dragLineElement: _ctx.dragLineElement\n }, null, 8, [\"elementInfo\", \"isSelected\", \"isActive\", \"isActiveGroupElement\", \"isMultiSelect\", \"rotateElement\", \"scaleElement\", \"openLinkDialog\", \"dragLineElement\"]))\n }), 128)),\n _createVNode(_component_ViewportBackground)\n ]),\n _createElementVNode(\"div\", {\n class: \"viewport\",\n ref: \"viewportRef\",\n style: _normalizeStyle({ transform: `scale(${_ctx.canvasScale})` })\n }, [\n (_ctx.mouseSelectionVisible)\n ? (_openBlock(), _createBlock(_component_MouseSelection, {\n key: 0,\n top: _ctx.mouseSelection.top,\n left: _ctx.mouseSelection.left,\n width: _ctx.mouseSelection.width,\n height: _ctx.mouseSelection.height,\n quadrant: _ctx.mouseSelectionQuadrant\n }, null, 8, [\"top\", \"left\", \"width\", \"height\", \"quadrant\"]))\n : _createCommentVNode(\"\", true),\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.elementList, (element, index) => {\n return (_openBlock(), _createBlock(_component_EditableElement, {\n key: element.id,\n elementInfo: element,\n elementIndex: index + 1,\n isMultiSelect: _ctx.activeElementIdList.length > 1,\n selectElement: _ctx.selectElement,\n openLinkDialog: _ctx.openLinkDialog\n }, null, 8, [\"elementInfo\", \"elementIndex\", \"isMultiSelect\", \"selectElement\", \"openLinkDialog\"]))\n }), 128))\n ], 4)\n ], 4),\n (_ctx.spaceKeyState)\n ? (_openBlock(), _createElementBlock(\"div\", _hoisted_2))\n : _createCommentVNode(\"\", true),\n (_ctx.showRuler)\n ? (_openBlock(), _createBlock(_component_Ruler, {\n key: 2,\n viewportStyles: _ctx.viewportStyles\n }, null, 8, [\"viewportStyles\"]))\n : _createCommentVNode(\"\", true),\n _createVNode(_component_Modal, {\n visible: _ctx.linkDialogVisible,\n \"onUpdate:visible\": _cache[2] || (_cache[2] = ($event: any) => ((_ctx.linkDialogVisible) = $event)),\n footer: null,\n centered: \"\",\n width: 540,\n destroyOnClose: \"\"\n }, {\n default: _withCtx(() => [\n _createVNode(_component_LinkDialog, {\n onClose: _cache[1] || (_cache[1] = ($event: any) => (_ctx.linkDialogVisible = false))\n })\n ]),\n _: 1\n }, 8, [\"visible\"])\n ], 32)), [\n [_directive_contextmenu, _ctx.contextmenus],\n [_directive_click_outside, _ctx.removeEditorAreaFocus]\n ])\n}","import { InjectionKey, Ref } from 'vue'\n\nexport type SlideScale = Ref\nexport type SlideId = Ref\n\nexport const injectKeySlideScale: InjectionKey = Symbol()\nexport const injectKeySlideId: InjectionKey = Symbol()","// 清除文字选区\nexport const removeAllRanges = () => {\n const selection = window.getSelection()\n selection && selection.removeAllRanges()\n}","import { ref, computed, onMounted, onUnmounted, Ref, watch } from 'vue'\nimport { storeToRefs } from 'pinia'\nimport { useMainStore, useSlidesStore } from '@/store'\nimport { VIEWPORT_SIZE } from '@/configs/canvas'\n\nexport default (canvasRef: Ref) => {\n const viewportLeft = ref(0)\n const viewportTop = ref(0)\n\n const mainStore = useMainStore()\n const { canvasPercentage, canvasDragged } = storeToRefs(mainStore)\n const { viewportRatio } = storeToRefs(useSlidesStore())\n\n // 计算画布可视区域的位置\n const setViewportPosition = () => {\n if (!canvasRef.value) return\n const canvasWidth = canvasRef.value.clientWidth\n const canvasHeight = canvasRef.value.clientHeight\n\n if (canvasHeight / canvasWidth > viewportRatio.value) {\n const viewportActualWidth = canvasWidth * (canvasPercentage.value / 100)\n mainStore.setCanvasScale(viewportActualWidth / VIEWPORT_SIZE)\n viewportLeft.value = (canvasWidth - viewportActualWidth) / 2\n viewportTop.value = (canvasHeight - viewportActualWidth * viewportRatio.value) / 2\n }\n else {\n const viewportActualHeight = canvasHeight * (canvasPercentage.value / 100)\n mainStore.setCanvasScale(viewportActualHeight / (VIEWPORT_SIZE * viewportRatio.value))\n viewportLeft.value = (canvasWidth - viewportActualHeight / viewportRatio.value) / 2\n viewportTop.value = (canvasHeight - viewportActualHeight) / 2\n }\n }\n\n // 可视区域缩放或比例变化时,更新可视区域的位置\n watch([canvasPercentage, viewportRatio], setViewportPosition)\n\n // 画布拖拽状态改变(复原)时,更新可视区域的位置\n watch(canvasDragged, () => {\n if (!canvasDragged.value) setViewportPosition()\n })\n\n // 画布可视区域位置和大小的样式\n const viewportStyles = computed(() => ({\n width: VIEWPORT_SIZE,\n height: VIEWPORT_SIZE * viewportRatio.value,\n left: viewportLeft.value,\n top: viewportTop.value,\n }))\n\n // 监听画布尺寸发生变化时,更新可视区域的位置\n const resizeObserver = new ResizeObserver(setViewportPosition)\n\n onMounted(() => {\n if (canvasRef.value) resizeObserver.observe(canvasRef.value)\n })\n onUnmounted(() => {\n if (canvasRef.value) resizeObserver.unobserve(canvasRef.value)\n })\n\n // 拖拽画布\n const dragViewport = (e: MouseEvent) => {\n let isMouseDown = true\n\n const startPageX = e.pageX\n const startPageY = e.pageY\n\n const originLeft = viewportLeft.value\n const originTop = viewportTop.value\n\n document.onmousemove = e => {\n if (!isMouseDown) return\n\n const currentPageX = e.pageX\n const currentPageY = e.pageY\n\n viewportLeft.value = originLeft + (currentPageX - startPageX)\n viewportTop.value = originTop + (currentPageY - startPageY)\n }\n\n document.onmouseup = () => {\n isMouseDown = false\n document.onmousemove = null\n document.onmouseup = null\n\n mainStore.setCanvasDragged(true)\n }\n }\n\n return {\n viewportStyles,\n dragViewport,\n }\n}","import { Ref, ref } from 'vue'\nimport { storeToRefs } from 'pinia'\nimport { useMainStore } from '@/store'\nimport { PPTElement } from '@/types/slides'\nimport { getElementRange } from '@/utils/element'\n\nexport default (elementList: Ref, viewportRef: Ref) => {\n const mainStore = useMainStore()\n const { canvasScale } = storeToRefs(mainStore)\n\n const mouseSelectionVisible = ref(false)\n const mouseSelectionQuadrant = ref(1)\n const mouseSelection = ref({\n top: 0,\n left: 0,\n width: 0,\n height: 0,\n })\n\n // 更新鼠标框选范围\n const updateMouseSelection = (e: MouseEvent) => {\n if (!viewportRef.value) return\n\n let isMouseDown = true\n const viewportRect = viewportRef.value.getBoundingClientRect()\n\n const minSelectionRange = 5\n \n const startPageX = e.pageX\n const startPageY = e.pageY\n\n const left = (startPageX - viewportRect.x) / canvasScale.value\n const top = (startPageY - viewportRect.y) / canvasScale.value\n\n // 确定框选的起始位置和其他默认值初始化\n mouseSelection.value = {\n top: top,\n left: left,\n width: 0,\n height: 0,\n }\n mouseSelectionVisible.value = false\n mouseSelectionQuadrant.value = 4\n\n document.onmousemove = e => {\n if (!isMouseDown) return\n\n const currentPageX = e.pageX\n const currentPageY = e.pageY\n\n const offsetWidth = (currentPageX - startPageX) / canvasScale.value\n const offsetHeight = (currentPageY - startPageY) / canvasScale.value\n\n const width = Math.abs(offsetWidth)\n const height = Math.abs(offsetHeight)\n\n if ( width < minSelectionRange || height < minSelectionRange ) return\n \n // 计算鼠标框选(移动)的方向\n // 按四个象限的位置区分,如右下角为第四象限\n let quadrant = 0\n if ( offsetWidth > 0 && offsetHeight > 0 ) quadrant = 4\n else if ( offsetWidth < 0 && offsetHeight < 0 ) quadrant = 1\n else if ( offsetWidth > 0 && offsetHeight < 0 ) quadrant = 2\n else if ( offsetWidth < 0 && offsetHeight > 0 ) quadrant = 3\n\n // 更新框选范围\n mouseSelection.value = {\n ...mouseSelection.value,\n width: width,\n height: height,\n }\n mouseSelectionVisible.value = true\n mouseSelectionQuadrant.value = quadrant\n }\n\n document.onmouseup = () => {\n document.onmousemove = null\n document.onmouseup = null\n isMouseDown = false\n\n // 计算画布中的元素是否处在鼠标选择范围中,处在范围中的元素设置为被选中状态\n let inRangeElementList: PPTElement[] = []\n for (let i = 0; i < elementList.value.length; i++) {\n const element = elementList.value[i]\n const mouseSelectionLeft = mouseSelection.value.left\n const mouseSelectionTop = mouseSelection.value.top\n const mouseSelectionWidth = mouseSelection.value.width\n const mouseSelectionHeight = mouseSelection.value.height\n\n const { minX, maxX, minY, maxY } = getElementRange(element)\n\n // 计算元素是否处在框选范围内时,四个框选方向的计算方式有差异\n let isInclude = false\n if (mouseSelectionQuadrant.value === 4) {\n isInclude = minX > mouseSelectionLeft && \n maxX < mouseSelectionLeft + mouseSelectionWidth && \n minY > mouseSelectionTop && \n maxY < mouseSelectionTop + mouseSelectionHeight\n }\n else if (mouseSelectionQuadrant.value === 1) {\n isInclude = minX > (mouseSelectionLeft - mouseSelectionWidth) && \n maxX < (mouseSelectionLeft - mouseSelectionWidth) + mouseSelectionWidth && \n minY > (mouseSelectionTop - mouseSelectionHeight) && \n maxY < (mouseSelectionTop - mouseSelectionHeight) + mouseSelectionHeight\n }\n else if (mouseSelectionQuadrant.value === 2) {\n isInclude = minX > mouseSelectionLeft && \n maxX < mouseSelectionLeft + mouseSelectionWidth && \n minY > (mouseSelectionTop - mouseSelectionHeight) && \n maxY < (mouseSelectionTop - mouseSelectionHeight) + mouseSelectionHeight\n }\n else if (mouseSelectionQuadrant.value === 3) {\n isInclude = minX > (mouseSelectionLeft - mouseSelectionWidth) && \n maxX < (mouseSelectionLeft - mouseSelectionWidth) + mouseSelectionWidth && \n minY > mouseSelectionTop && \n maxY < mouseSelectionTop + mouseSelectionHeight\n }\n\n // 被锁定的元素即使在范围内,也不需要设置为选中状态\n if (isInclude && !element.lock) inRangeElementList.push(element)\n }\n\n // 如果范围内有组合元素的成员,需要该组全部成员都处在范围内,才会被设置为选中状态\n inRangeElementList = inRangeElementList.filter(inRangeElement => {\n if (inRangeElement.groupId) {\n const inRangeElementIdList = inRangeElementList.map(inRangeElement => inRangeElement.id)\n const groupElementList = elementList.value.filter(element => element.groupId === inRangeElement.groupId)\n return groupElementList.every(groupElement => inRangeElementIdList.includes(groupElement.id))\n }\n return true\n })\n const inRangeElementIdList = inRangeElementList.map(inRangeElement => inRangeElement.id)\n mainStore.setActiveElementIdList(inRangeElementIdList)\n\n mouseSelectionVisible.value = false\n }\n }\n\n return {\n mouseSelection,\n mouseSelectionVisible,\n mouseSelectionQuadrant,\n updateMouseSelection,\n }\n}","import { onMounted, onUnmounted, Ref } from 'vue'\nimport { storeToRefs } from 'pinia'\nimport { useMainStore } from '@/store'\nimport { getImageDataURL } from '@/utils/image'\nimport { parseText2Paragraphs } from '@/utils/textParser'\nimport useCreateElement from '@/hooks/useCreateElement'\n\nexport default (elementRef: Ref) => {\n const { disableHotkeys } = storeToRefs(useMainStore())\n\n const { createImageElement, createTextElement } = useCreateElement()\n\n // 拖拽元素到画布中\n const handleDrop = (e: DragEvent) => {\n if (!e.dataTransfer || e.dataTransfer.items.length === 0) return\n const dataTransferItem = e.dataTransfer.items[0]\n\n // 检查事件对象中是否存在图片,存在则插入图片,否则继续检查是否存在文字,存在则插入文字\n if (dataTransferItem.kind === 'file' && dataTransferItem.type.indexOf('image') !== -1) {\n const imageFile = dataTransferItem.getAsFile()\n if (imageFile) {\n getImageDataURL(imageFile).then(dataURL => createImageElement(dataURL))\n }\n }\n else if (dataTransferItem.kind === 'string' && dataTransferItem.type === 'text/plain') {\n dataTransferItem.getAsString(text => {\n if (disableHotkeys.value) return\n const string = parseText2Paragraphs(text)\n createTextElement({\n left: 0,\n top: 0,\n width: 600,\n height: 50,\n }, string)\n })\n }\n }\n\n onMounted(() => {\n elementRef.value && elementRef.value.addEventListener('drop', handleDrop)\n\n document.ondragleave = e => e.preventDefault()\n document.ondrop = e => e.preventDefault()\n document.ondragenter = e => e.preventDefault()\n document.ondragover = e => e.preventDefault()\n })\n onUnmounted(() => {\n elementRef.value && elementRef.value.removeEventListener('drop', handleDrop)\n\n document.ondragleave = null\n document.ondrop = null\n document.ondragenter = null\n document.ondragover = null\n })\n}","import { Ref } from 'vue'\nimport { storeToRefs } from 'pinia'\nimport { useMainStore, useSlidesStore } from '@/store'\nimport { PPTElement, PPTLineElement, PPTVideoElement, PPTAudioElement } from '@/types/slides'\nimport useHistorySnapshot from '@/hooks/useHistorySnapshot'\n\n/**\n * 计算给定坐标到原点连线的弧度\n * @param x 坐标x\n * @param y 坐标y\n */\nconst getAngleFromCoordinate = (x: number, y: number) => {\n const radian = Math.atan2(x, y)\n const angle = 180 / Math.PI * radian\n return angle\n}\n\nexport default (elementList: Ref, viewportRef: Ref) => {\n const slidesStore = useSlidesStore()\n const { canvasScale } = storeToRefs(useMainStore())\n\n const { addHistorySnapshot } = useHistorySnapshot()\n\n // 旋转元素\n const rotateElement = (element: Exclude) => {\n let isMouseDown = true\n let angle = 0\n const elOriginRotate = element.rotate || 0\n\n const elLeft = element.left\n const elTop = element.top\n const elWidth = element.width\n const elHeight = element.height\n\n // 元素中心点(旋转中心点)\n const centerX = elLeft + elWidth / 2\n const centerY = elTop + elHeight / 2\n\n if (!viewportRef.value) return\n const viewportRect = viewportRef.value.getBoundingClientRect()\n\n document.onmousemove = e => {\n if (!isMouseDown) return\n \n // 计算当前鼠标位置相对元素中心点连线的角度(弧度)\n const mouseX = (e.pageX - viewportRect.left) / canvasScale.value\n const mouseY = (e.pageY - viewportRect.top) / canvasScale.value\n const x = mouseX - centerX\n const y = centerY - mouseY\n\n angle = getAngleFromCoordinate(x, y)\n\n // 靠近45倍数的角度时有吸附效果\n const sorptionRange = 5\n if ( Math.abs(angle) <= sorptionRange ) angle = 0\n else if ( angle > 0 && Math.abs(angle - 45) <= sorptionRange ) angle -= (angle - 45)\n else if ( angle < 0 && Math.abs(angle + 45) <= sorptionRange ) angle -= (angle + 45)\n else if ( angle > 0 && Math.abs(angle - 90) <= sorptionRange ) angle -= (angle - 90)\n else if ( angle < 0 && Math.abs(angle + 90) <= sorptionRange ) angle -= (angle + 90)\n else if ( angle > 0 && Math.abs(angle - 135) <= sorptionRange ) angle -= (angle - 135)\n else if ( angle < 0 && Math.abs(angle + 135) <= sorptionRange ) angle -= (angle + 135)\n else if ( angle > 0 && Math.abs(angle - 180) <= sorptionRange ) angle -= (angle - 180)\n else if ( angle < 0 && Math.abs(angle + 180) <= sorptionRange ) angle -= (angle + 180)\n\n elementList.value = elementList.value.map(el => element.id === el.id ? { ...el, rotate: angle } : el)\n }\n\n document.onmouseup = () => {\n isMouseDown = false\n document.onmousemove = null\n document.onmouseup = null\n\n if (elOriginRotate === angle) return\n\n slidesStore.updateSlide({ elements: elementList.value })\n addHistorySnapshot()\n }\n }\n\n return {\n rotateElement,\n }\n}","export const ELEMENT_TYPE_ZH = {\n text: '文本',\n image: '图片',\n shape: '形状',\n line: '线条',\n chart: '图表',\n table: '表格',\n video: '视频',\n audio: '音频',\n}\n\nexport const MIN_SIZE = {\n text: 20,\n image: 20,\n shape: 15,\n chart: 200,\n table: 20,\n video: 250,\n audio: 20,\n}","import { Ref } from 'vue'\nimport { storeToRefs } from 'pinia'\nimport { useMainStore, useSlidesStore, useKeyboardStore } from '@/store'\nimport { PPTElement, PPTImageElement, PPTLineElement, PPTShapeElement } from '@/types/slides'\nimport { OperateResizeHandlers, AlignmentLineProps, MultiSelectRange } from '@/types/edit'\nimport { VIEWPORT_SIZE } from '@/configs/canvas'\nimport { MIN_SIZE } from '@/configs/element'\nimport { SHAPE_PATH_FORMULAS } from '@/configs/shapes'\nimport { AlignLine, uniqAlignLines } from '@/utils/element'\nimport useHistorySnapshot from '@/hooks/useHistorySnapshot'\n\ninterface RotateElementData {\n left: number;\n top: number;\n width: number;\n height: number;\n}\n\n/**\n * 计算旋转后的元素八个缩放点的位置\n * @param element 元素原始位置大小信息\n * @param angle 旋转角度\n */\nconst getRotateElementPoints = (element: RotateElementData, angle: number) => {\n const { left, top, width, height } = element\n\n const radius = Math.sqrt( Math.pow(width, 2) + Math.pow(height, 2) ) / 2\n const auxiliaryAngle = Math.atan(height / width) * 180 / Math.PI\n\n const tlbraRadian = (180 - angle - auxiliaryAngle) * Math.PI / 180\n const trblaRadian = (auxiliaryAngle - angle) * Math.PI / 180\n const taRadian = (90 - angle) * Math.PI / 180\n const raRadian = angle * Math.PI / 180\n\n const halfWidth = width / 2\n const halfHeight = height / 2\n\n const middleLeft = left + halfWidth\n const middleTop = top + halfHeight\n\n const leftTopPoint = {\n left: middleLeft + radius * Math.cos(tlbraRadian),\n top: middleTop - radius * Math.sin(tlbraRadian),\n }\n const topPoint = {\n left: middleLeft + halfHeight * Math.cos(taRadian),\n top: middleTop - halfHeight * Math.sin(taRadian),\n }\n const rightTopPoint = {\n left: middleLeft + radius * Math.cos(trblaRadian),\n top: middleTop - radius * Math.sin(trblaRadian),\n }\n const rightPoint = {\n left: middleLeft + halfWidth * Math.cos(raRadian),\n top: middleTop + halfWidth * Math.sin(raRadian),\n }\n const rightBottomPoint = {\n left: middleLeft - radius * Math.cos(tlbraRadian),\n top: middleTop + radius * Math.sin(tlbraRadian),\n }\n const bottomPoint = {\n left: middleLeft - halfHeight * Math.sin(raRadian),\n top: middleTop + halfHeight * Math.cos(raRadian),\n }\n const leftBottomPoint = {\n left: middleLeft - radius * Math.cos(trblaRadian),\n top: middleTop + radius * Math.sin(trblaRadian),\n }\n const leftPoint = {\n left: middleLeft - halfWidth * Math.cos(raRadian),\n top: middleTop - halfWidth * Math.sin(raRadian),\n }\n\n return { leftTopPoint, topPoint, rightTopPoint, rightPoint, rightBottomPoint, bottomPoint, leftBottomPoint, leftPoint }\n}\n\n/**\n * 获取元素某缩放点相对的另一个点的位置,如:【上】对应【下】、【左上】对应【右下】\n * @param direction 当前操作的缩放点\n * @param points 旋转后的元素八个缩放点的位置\n */\nconst getOppositePoint = (direction: string, points: ReturnType): { left: number; top: number } => {\n const oppositeMap = {\n [OperateResizeHandlers.RIGHT_BOTTOM]: points.leftTopPoint,\n [OperateResizeHandlers.LEFT_BOTTOM]: points.rightTopPoint,\n [OperateResizeHandlers.LEFT_TOP]: points.rightBottomPoint,\n [OperateResizeHandlers.RIGHT_TOP]: points.leftBottomPoint,\n [OperateResizeHandlers.TOP]: points.bottomPoint,\n [OperateResizeHandlers.BOTTOM]: points.topPoint,\n [OperateResizeHandlers.LEFT]: points.rightPoint,\n [OperateResizeHandlers.RIGHT]: points.leftPoint,\n }\n return oppositeMap[direction]\n}\n\nexport default (\n elementList: Ref,\n alignmentLines: Ref,\n) => {\n const mainStore = useMainStore()\n const slidesStore = useSlidesStore()\n const { activeElementIdList, activeGroupElementId, canvasScale } = storeToRefs(mainStore)\n const { viewportRatio } = storeToRefs(slidesStore)\n const { ctrlOrShiftKeyActive } = storeToRefs(useKeyboardStore())\n\n const { addHistorySnapshot } = useHistorySnapshot()\n\n // 缩放元素\n const scaleElement = (e: MouseEvent, element: Exclude, command: OperateResizeHandlers) => {\n let isMouseDown = true\n mainStore.setScalingState(true)\n\n const elOriginLeft = element.left\n const elOriginTop = element.top\n const elOriginWidth = element.width\n const elOriginHeight = element.height\n \n const elRotate = ('rotate' in element && element.rotate) ? element.rotate : 0\n const rotateRadian = Math.PI * elRotate / 180\n\n const fixedRatio = ctrlOrShiftKeyActive.value || ('fixedRatio' in element && element.fixedRatio)\n const aspectRatio = elOriginWidth / elOriginHeight\n\n const startPageX = e.pageX\n const startPageY = e.pageY\n\n // 元素最小缩放限制\n const minSize = MIN_SIZE[element.type] || 20\n const getSizeWithinRange = (size: number) => size < minSize ? minSize : size\n\n let points: ReturnType\n let baseLeft = 0\n let baseTop = 0\n let horizontalLines: AlignLine[] = []\n let verticalLines: AlignLine[] = []\n\n // 旋转后的元素进行缩放时,引入基点的概念,以当前操作的缩放点相对的点为基点\n // 例如拖动右下角缩放时,左上角为基点,需要保持左上角不变然后修改其他的点的位置来达到所放的效果\n if ('rotate' in element && element.rotate) {\n const { left, top, width, height } = element\n points = getRotateElementPoints({ left, top, width, height }, elRotate)\n const oppositePoint = getOppositePoint(command, points)\n\n baseLeft = oppositePoint.left\n baseTop = oppositePoint.top\n }\n\n // 未旋转的元素具有缩放时的对齐吸附功能,在此处收集对齐对齐吸附线\n // 包括页面内除目标元素外的其他元素在画布中的各个可吸附对齐位置:上下左右四边\n // 其中线条和被旋转过的元素不参与吸附对齐\n else {\n const edgeWidth = VIEWPORT_SIZE\n const edgeHeight = VIEWPORT_SIZE * viewportRatio.value\n const isActiveGroupElement = element.id === activeGroupElementId.value\n \n for (const el of elementList.value) {\n if ('rotate' in el && el.rotate) continue\n if (el.type === 'line') continue\n if (isActiveGroupElement && el.id === element.id) continue\n if (!isActiveGroupElement && activeElementIdList.value.includes(el.id)) continue\n\n const left = el.left\n const top = el.top\n const width = el.width\n const height = el.height\n const right = left + width\n const bottom = top + height\n\n const topLine: AlignLine = { value: top, range: [left, right] }\n const bottomLine: AlignLine = { value: bottom, range: [left, right] }\n const leftLine: AlignLine = { value: left, range: [top, bottom] }\n const rightLine: AlignLine = { value: right, range: [top, bottom] }\n\n horizontalLines.push(topLine, bottomLine)\n verticalLines.push(leftLine, rightLine)\n }\n\n // 画布可视区域的四个边界、水平中心、垂直中心\n const edgeTopLine: AlignLine = { value: 0, range: [0, edgeWidth] }\n const edgeBottomLine: AlignLine = { value: edgeHeight, range: [0, edgeWidth] }\n const edgeHorizontalCenterLine: AlignLine = { value: edgeHeight / 2, range: [0, edgeWidth] }\n const edgeLeftLine: AlignLine = { value: 0, range: [0, edgeHeight] }\n const edgeRightLine: AlignLine = { value: edgeWidth, range: [0, edgeHeight] }\n const edgeVerticalCenterLine: AlignLine = { value: edgeWidth / 2, range: [0, edgeHeight] }\n\n horizontalLines.push(edgeTopLine, edgeBottomLine, edgeHorizontalCenterLine)\n verticalLines.push(edgeLeftLine, edgeRightLine, edgeVerticalCenterLine)\n \n horizontalLines = uniqAlignLines(horizontalLines)\n verticalLines = uniqAlignLines(verticalLines)\n }\n \n // 对齐吸附方法\n // 将收集到的对齐吸附线与计算的目标元素当前的位置大小相关数据做对比,差值小于设定的值时执行自动缩放校正\n // 水平和垂直两个方向需要分开计算\n const alignedAdsorption = (currentX: number | null, currentY: number | null) => {\n const sorptionRange = 5\n\n const _alignmentLines: AlignmentLineProps[] = []\n let isVerticalAdsorbed = false\n let isHorizontalAdsorbed = false\n const correctionVal = { offsetX: 0, offsetY: 0 }\n \n if (currentY || currentY === 0) {\n for (let i = 0; i < horizontalLines.length; i++) {\n const { value, range } = horizontalLines[i]\n const min = Math.min(...range, currentX || 0)\n const max = Math.max(...range, currentX || 0)\n \n if (Math.abs(currentY - value) < sorptionRange && !isHorizontalAdsorbed) {\n correctionVal.offsetY = currentY - value\n isHorizontalAdsorbed = true\n _alignmentLines.push({ type: 'horizontal', axis: {x: min - 50, y: value}, length: max - min + 100 })\n }\n }\n }\n if (currentX || currentX === 0) {\n for (let i = 0; i < verticalLines.length; i++) {\n const { value, range } = verticalLines[i]\n const min = Math.min(...range, (currentY || 0))\n const max = Math.max(...range, (currentY || 0))\n\n if (Math.abs(currentX - value) < sorptionRange && !isVerticalAdsorbed) {\n correctionVal.offsetX = currentX - value\n isVerticalAdsorbed = true\n _alignmentLines.push({ type: 'vertical', axis: {x: value, y: min - 50}, length: max - min + 100 })\n }\n }\n }\n alignmentLines.value = _alignmentLines\n return correctionVal\n }\n\n // 开始缩放\n document.onmousemove = e => {\n if (!isMouseDown) return\n\n const currentPageX = e.pageX\n const currentPageY = e.pageY\n\n const x = currentPageX - startPageX\n const y = currentPageY - startPageY\n\n let width = elOriginWidth\n let height = elOriginHeight\n let left = elOriginLeft\n let top = elOriginTop\n \n // 元素被旋转的情况下,需要根据元素旋转的角度,重新计算需要缩放的距离(鼠标按下后移动的距离)\n if (elRotate) {\n const revisedX = (Math.cos(rotateRadian) * x + Math.sin(rotateRadian) * y) / canvasScale.value\n let revisedY = (Math.cos(rotateRadian) * y - Math.sin(rotateRadian) * x) / canvasScale.value\n\n // 锁定宽高比例(仅四个角可能触发,四条边不会触发)\n // 以水平方向上缩放的距离为基础,计算垂直方向上的缩放距离,保持二者具有相同的缩放比例\n if (fixedRatio) {\n if (command === OperateResizeHandlers.RIGHT_BOTTOM || command === OperateResizeHandlers.LEFT_TOP) revisedY = revisedX / aspectRatio\n if (command === OperateResizeHandlers.LEFT_BOTTOM || command === OperateResizeHandlers.RIGHT_TOP) revisedY = -revisedX / aspectRatio\n }\n\n // 根据不同的操作点分别计算元素缩放后的大小和位置\n // 需要注意:\n // 此处计算的位置需要在后面重新进行校正,因为旋转后再缩放事实上会改变元素基点的位置(虽然视觉上基点保持不动,但这是【旋转】+【移动】共同作用的结果)\n // 但此处计算的大小不需要重新校正,因为前面已经重新计算需要缩放的距离,相当于大小已经经过了校正\n if (command === OperateResizeHandlers.RIGHT_BOTTOM) {\n width = getSizeWithinRange(elOriginWidth + revisedX)\n height = getSizeWithinRange(elOriginHeight + revisedY)\n }\n else if (command === OperateResizeHandlers.LEFT_BOTTOM) {\n width = getSizeWithinRange(elOriginWidth - revisedX)\n height = getSizeWithinRange(elOriginHeight + revisedY)\n left = elOriginLeft - (width - elOriginWidth)\n }\n else if (command === OperateResizeHandlers.LEFT_TOP) {\n width = getSizeWithinRange(elOriginWidth - revisedX)\n height = getSizeWithinRange(elOriginHeight - revisedY)\n left = elOriginLeft - (width - elOriginWidth)\n top = elOriginTop - (height - elOriginHeight)\n }\n else if (command === OperateResizeHandlers.RIGHT_TOP) {\n width = getSizeWithinRange(elOriginWidth + revisedX)\n height = getSizeWithinRange(elOriginHeight - revisedY)\n top = elOriginTop - (height - elOriginHeight)\n }\n else if (command === OperateResizeHandlers.TOP) {\n height = getSizeWithinRange(elOriginHeight - revisedY)\n top = elOriginTop - (height - elOriginHeight)\n }\n else if (command === OperateResizeHandlers.BOTTOM) {\n height = getSizeWithinRange(elOriginHeight + revisedY)\n }\n else if (command === OperateResizeHandlers.LEFT) {\n width = getSizeWithinRange(elOriginWidth - revisedX)\n left = elOriginLeft - (width - elOriginWidth)\n }\n else if (command === OperateResizeHandlers.RIGHT) {\n width = getSizeWithinRange(elOriginWidth + revisedX)\n }\n\n // 获取当前元素的基点坐标,与初始状态时的基点坐标进行对比,并计算差值进行元素位置的校正\n const currentPoints = getRotateElementPoints({ width, height, left, top }, elRotate)\n const currentOppositePoint = getOppositePoint(command, currentPoints)\n const currentBaseLeft = currentOppositePoint.left\n const currentBaseTop = currentOppositePoint.top\n\n const offsetX = currentBaseLeft - baseLeft\n const offsetY = currentBaseTop - baseTop\n\n left = left - offsetX\n top = top - offsetY\n }\n\n // 元素未被旋转的情况下,正常计算新的位置大小即可,无需复杂的校正等工作\n // 额外需要处理对齐吸附相关的操作\n // 锁定宽高比例相关的操作同上,不再赘述\n else {\n let moveX = x / canvasScale.value\n let moveY = y / canvasScale.value\n\n if (fixedRatio) {\n if (command === OperateResizeHandlers.RIGHT_BOTTOM || command === OperateResizeHandlers.LEFT_TOP) moveY = moveX / aspectRatio\n if (command === OperateResizeHandlers.LEFT_BOTTOM || command === OperateResizeHandlers.RIGHT_TOP) moveY = -moveX / aspectRatio\n }\n\n if (command === OperateResizeHandlers.RIGHT_BOTTOM) {\n const { offsetX, offsetY } = alignedAdsorption(elOriginLeft + elOriginWidth + moveX, elOriginTop + elOriginHeight + moveY)\n moveX = moveX - offsetX\n moveY = moveY - offsetY\n if (fixedRatio) {\n if (offsetY) moveX = moveY * aspectRatio\n else moveY = moveX / aspectRatio\n }\n width = getSizeWithinRange(elOriginWidth + moveX)\n height = getSizeWithinRange(elOriginHeight + moveY)\n }\n else if (command === OperateResizeHandlers.LEFT_BOTTOM) {\n const { offsetX, offsetY } = alignedAdsorption(elOriginLeft + moveX, elOriginTop + elOriginHeight + moveY)\n moveX = moveX - offsetX\n moveY = moveY - offsetY\n if (fixedRatio) {\n if (offsetY) moveX = -moveY * aspectRatio\n else moveY = -moveX / aspectRatio\n }\n width = getSizeWithinRange(elOriginWidth - moveX)\n height = getSizeWithinRange(elOriginHeight + moveY)\n left = elOriginLeft - (width - elOriginWidth)\n }\n else if (command === OperateResizeHandlers.LEFT_TOP) {\n const { offsetX, offsetY } = alignedAdsorption(elOriginLeft + moveX, elOriginTop + moveY)\n moveX = moveX - offsetX\n moveY = moveY - offsetY\n if (fixedRatio) {\n if (offsetY) moveX = moveY * aspectRatio\n else moveY = moveX / aspectRatio\n }\n width = getSizeWithinRange(elOriginWidth - moveX)\n height = getSizeWithinRange(elOriginHeight - moveY)\n left = elOriginLeft - (width - elOriginWidth)\n top = elOriginTop - (height - elOriginHeight)\n }\n else if (command === OperateResizeHandlers.RIGHT_TOP) {\n const { offsetX, offsetY } = alignedAdsorption(elOriginLeft + elOriginWidth + moveX, elOriginTop + moveY)\n moveX = moveX - offsetX\n moveY = moveY - offsetY\n if (fixedRatio) {\n if (offsetY) moveX = -moveY * aspectRatio\n else moveY = -moveX / aspectRatio\n }\n width = getSizeWithinRange(elOriginWidth + moveX)\n height = getSizeWithinRange(elOriginHeight - moveY)\n top = elOriginTop - (height - elOriginHeight)\n }\n else if (command === OperateResizeHandlers.LEFT) {\n const { offsetX } = alignedAdsorption(elOriginLeft + moveX, null)\n moveX = moveX - offsetX\n width = getSizeWithinRange(elOriginWidth - moveX)\n left = elOriginLeft - (width - elOriginWidth)\n }\n else if (command === OperateResizeHandlers.RIGHT) {\n const { offsetX } = alignedAdsorption(elOriginLeft + elOriginWidth + moveX, null)\n moveX = moveX - offsetX\n width = getSizeWithinRange(elOriginWidth + moveX)\n }\n else if (command === OperateResizeHandlers.TOP) {\n const { offsetY } = alignedAdsorption(null, elOriginTop + moveY)\n moveY = moveY - offsetY\n height = getSizeWithinRange(elOriginHeight - moveY)\n top = elOriginTop - (height - elOriginHeight)\n }\n else if (command === OperateResizeHandlers.BOTTOM) {\n const { offsetY } = alignedAdsorption(null, elOriginTop + elOriginHeight + moveY)\n moveY = moveY - offsetY\n height = getSizeWithinRange(elOriginHeight + moveY)\n }\n }\n \n elementList.value = elementList.value.map(el => {\n if (element.id !== el.id) return el\n if (el.type === 'shape' && 'pathFormula' in el && el.pathFormula) {\n return {\n ...el, left, top, width, height,\n viewBox: [width, height],\n path: SHAPE_PATH_FORMULAS[el.pathFormula](width, height),\n }\n }\n return { ...el, left, top, width, height }\n })\n }\n\n document.onmouseup = e => {\n isMouseDown = false\n document.onmousemove = null\n document.onmouseup = null\n alignmentLines.value = []\n \n if (startPageX === e.pageX && startPageY === e.pageY) return\n \n slidesStore.updateSlide({ elements: elementList.value })\n mainStore.setScalingState(false)\n \n addHistorySnapshot()\n }\n }\n\n // 多选元素缩放\n const scaleMultiElement = (e: MouseEvent, range: MultiSelectRange, command: OperateResizeHandlers) => {\n let isMouseDown = true\n \n const { minX, maxX, minY, maxY } = range\n const operateWidth = maxX - minX\n const operateHeight = maxY - minY\n const aspectRatio = operateWidth / operateHeight\n\n const startPageX = e.pageX\n const startPageY = e.pageY\n\n const originElementList: PPTElement[] = JSON.parse(JSON.stringify(elementList.value))\n\n document.onmousemove = e => {\n if (!isMouseDown) return\n \n const currentPageX = e.pageX\n const currentPageY = e.pageY\n\n const x = (currentPageX - startPageX) / canvasScale.value\n let y = (currentPageY - startPageY) / canvasScale.value\n\n // 锁定宽高比例,逻辑同上\n if (ctrlOrShiftKeyActive.value) {\n if (command === OperateResizeHandlers.RIGHT_BOTTOM || command === OperateResizeHandlers.LEFT_TOP) y = x / aspectRatio\n if (command === OperateResizeHandlers.LEFT_BOTTOM || command === OperateResizeHandlers.RIGHT_TOP) y = -x / aspectRatio\n }\n\n // 所有选中元素的整体范围\n let currentMinX = minX\n let currentMaxX = maxX\n let currentMinY = minY\n let currentMaxY = maxY\n\n if (command === OperateResizeHandlers.RIGHT_BOTTOM) {\n currentMaxX = maxX + x\n currentMaxY = maxY + y\n }\n else if (command === OperateResizeHandlers.LEFT_BOTTOM) {\n currentMinX = minX + x\n currentMaxY = maxY + y\n }\n else if (command === OperateResizeHandlers.LEFT_TOP) {\n currentMinX = minX + x\n currentMinY = minY + y\n }\n else if (command === OperateResizeHandlers.RIGHT_TOP) {\n currentMaxX = maxX + x\n currentMinY = minY + y\n }\n else if (command === OperateResizeHandlers.TOP) {\n currentMinY = minY + y\n }\n else if (command === OperateResizeHandlers.BOTTOM) {\n currentMaxY = maxY + y\n }\n else if (command === OperateResizeHandlers.LEFT) {\n currentMinX = minX + x\n }\n else if (command === OperateResizeHandlers.RIGHT) {\n currentMaxX = maxX + x\n }\n\n // 所有选中元素的整体宽高\n const currentOppositeWidth = currentMaxX - currentMinX\n const currentOppositeHeight = currentMaxY - currentMinY\n\n // 当前正在操作元素宽高占所有选中元素的整体宽高的比例\n let widthScale = currentOppositeWidth / operateWidth\n let heightScale = currentOppositeHeight / operateHeight\n\n if (widthScale <= 0) widthScale = 0\n if (heightScale <= 0) heightScale = 0\n \n // 根据前面计算的比例,计算并修改所有选中元素的位置大小\n elementList.value = elementList.value.map(el => {\n if ((el.type === 'image' || el.type === 'shape') && activeElementIdList.value.includes(el.id)) {\n const originElement = originElementList.find(originEl => originEl.id === el.id) as PPTImageElement | PPTShapeElement\n return {\n ...el,\n width: originElement.width * widthScale,\n height: originElement.height * heightScale,\n left: currentMinX + (originElement.left - minX) * widthScale,\n top: currentMinY + (originElement.top - minY) * heightScale,\n }\n }\n return el\n })\n }\n\n document.onmouseup = e => {\n isMouseDown = false\n document.onmousemove = null\n document.onmouseup = null\n\n if (startPageX === e.pageX && startPageY === e.pageY) return\n\n slidesStore.updateSlide({ elements: elementList.value })\n addHistorySnapshot()\n }\n }\n\n return {\n scaleElement,\n scaleMultiElement,\n }\n}","import { Ref } from 'vue'\nimport { uniq } from 'lodash'\nimport { storeToRefs } from 'pinia'\nimport { useMainStore, useKeyboardStore } from '@/store'\nimport { PPTElement } from '@/types/slides'\n\nexport default (\n elementList: Ref,\n moveElement: (e: MouseEvent, element: PPTElement) => void,\n) => {\n const mainStore = useMainStore()\n const { activeElementIdList, activeGroupElementId, handleElementId, editorAreaFocus } = storeToRefs(mainStore)\n const { ctrlOrShiftKeyActive } = storeToRefs(useKeyboardStore())\n\n // 选中元素\n // startMove 表示是否需要再选中操作后进入到开始移动的状态\n const selectElement = (e: MouseEvent, element: PPTElement, startMove = true) => {\n if (!editorAreaFocus.value) mainStore.setEditorareaFocus(true)\n\n // 如果目标元素当前未被选中,则将他设为选中状态\n // 此时如果按下Ctrl键或Shift键,则进入多选状态,将当前已选中的元素和目标元素一起设置为选中状态,否则仅将目标元素设置为选中状态\n // 如果目标元素是分组成员,需要将该组合的其他元素一起设置为选中状态\n if (!activeElementIdList.value.includes(element.id)) {\n let newActiveIdList: string[] = []\n\n if (ctrlOrShiftKeyActive.value) {\n newActiveIdList = [...activeElementIdList.value, element.id]\n }\n else newActiveIdList = [element.id]\n \n if (element.groupId) {\n const groupMembersId: string[] = []\n elementList.value.forEach((el: PPTElement) => {\n if (el.groupId === element.groupId) groupMembersId.push(el.id)\n })\n newActiveIdList = [...newActiveIdList, ...groupMembersId]\n }\n\n mainStore.setActiveElementIdList(uniq(newActiveIdList))\n mainStore.setHandleElementId(element.id)\n }\n\n // 如果目标元素已被选中,且按下了Ctrl键或Shift键,则取消其被选中状态\n // 除非目标元素是最后的一个被选中元素,或者目标元素所在的组合是最后一组选中组合\n // 如果目标元素是分组成员,需要将该组合的其他元素一起取消选中状态\n else if (ctrlOrShiftKeyActive.value) {\n let newActiveIdList: string[] = []\n\n if (element.groupId) {\n const groupMembersId: string[] = []\n elementList.value.forEach((el: PPTElement) => {\n if (el.groupId === element.groupId) groupMembersId.push(el.id)\n })\n newActiveIdList = activeElementIdList.value.filter(id => !groupMembersId.includes(id))\n }\n else {\n newActiveIdList = activeElementIdList.value.filter(id => id !== element.id)\n }\n\n if (newActiveIdList.length > 0) {\n mainStore.setActiveElementIdList(newActiveIdList)\n }\n }\n\n // 如果目标元素已被选中,同时目标元素不是当前操作元素,则将其设置为当前操作元素\n else if (handleElementId.value !== element.id) {\n mainStore.setHandleElementId(element.id)\n }\n\n // 如果目标元素已被选中,同时也是当前操作元素,那么当目标元素在该状态下再次被点击时,将被设置为多选元素中的激活成员\n else if (activeGroupElementId.value !== element.id) {\n const startPageX = e.pageX\n const startPageY = e.pageY\n\n ;(e.target as HTMLElement).onmouseup = (e: MouseEvent) => {\n const currentPageX = e.pageX\n const currentPageY = e.pageY\n\n if (startPageX === currentPageX && startPageY === currentPageY) {\n mainStore.setActiveGroupElementId(element.id)\n ;(e.target as HTMLElement).onmouseup = null\n }\n }\n }\n\n if (startMove) moveElement(e, element)\n }\n\n // 选中页面内的全部元素\n const selectAllElement = () => {\n const unlockedElements = elementList.value.filter(el => !el.lock)\n const newActiveElementIdList = unlockedElements.map(el => el.id)\n mainStore.setActiveElementIdList(newActiveElementIdList)\n }\n\n return {\n selectElement,\n selectAllElement,\n }\n}\n","import { Ref } from 'vue'\nimport { storeToRefs } from 'pinia'\nimport { useMainStore, useSlidesStore } from '@/store'\nimport { PPTElement } from '@/types/slides'\nimport { AlignmentLineProps } from '@/types/edit'\nimport { VIEWPORT_SIZE } from '@/configs/canvas'\nimport { getRectRotatedRange, AlignLine, uniqAlignLines } from '@/utils/element'\nimport useHistorySnapshot from '@/hooks/useHistorySnapshot'\n\nexport default (\n elementList: Ref,\n alignmentLines: Ref,\n) => {\n const slidesStore = useSlidesStore()\n const { activeElementIdList, activeGroupElementId, canvasScale } = storeToRefs(useMainStore())\n const { viewportRatio } = storeToRefs(slidesStore)\n\n const { addHistorySnapshot } = useHistorySnapshot()\n\n const dragElement = (e: MouseEvent, element: PPTElement) => {\n if (!activeElementIdList.value.includes(element.id)) return\n let isMouseDown = true\n\n const edgeWidth = VIEWPORT_SIZE\n const edgeHeight = VIEWPORT_SIZE * viewportRatio.value\n \n const sorptionRange = 5\n\n const originElementList: PPTElement[] = JSON.parse(JSON.stringify(elementList.value))\n const originActiveElementList = originElementList.filter(el => activeElementIdList.value.includes(el.id))\n \n const elOriginLeft = element.left\n const elOriginTop = element.top\n const elOriginWidth = element.width\n const elOriginHeight = ('height' in element && element.height) ? element.height : 0\n const elOriginRotate = ('rotate' in element && element.rotate) ? element.rotate : 0\n \n const startPageX = e.pageX\n const startPageY = e.pageY\n\n let isMisoperation: boolean | null = null\n\n const isActiveGroupElement = element.id === activeGroupElementId.value\n\n // 收集对齐对齐吸附线\n // 包括页面内除目标元素外的其他元素在画布中的各个可吸附对齐位置:上下左右四边,水平中心、垂直中心\n // 其中线条和被旋转过的元素需要重新计算他们在画布中的中心点位置的范围\n let horizontalLines: AlignLine[] = []\n let verticalLines: AlignLine[] = []\n\n for (const el of elementList.value) {\n if (el.type === 'line') continue\n if (isActiveGroupElement && el.id === element.id) continue\n if (!isActiveGroupElement && activeElementIdList.value.includes(el.id)) continue\n\n let left, top, width, height\n if ('rotate' in el && el.rotate) {\n const { xRange, yRange } = getRectRotatedRange({\n left: el.left,\n top: el.top,\n width: el.width,\n height: el.height,\n rotate: el.rotate,\n })\n left = xRange[0]\n top = yRange[0]\n width = xRange[1] - xRange[0]\n height = yRange[1] - yRange[0]\n }\n else {\n left = el.left\n top = el.top\n width = el.width\n height = el.height\n }\n \n const right = left + width\n const bottom = top + height\n const centerX = top + height / 2\n const centerY = left + width / 2\n\n const topLine: AlignLine = { value: top, range: [left, right] }\n const bottomLine: AlignLine = { value: bottom, range: [left, right] }\n const horizontalCenterLine: AlignLine = { value: centerX, range: [left, right] }\n const leftLine: AlignLine = { value: left, range: [top, bottom] }\n const rightLine: AlignLine = { value: right, range: [top, bottom] }\n const verticalCenterLine: AlignLine = { value: centerY, range: [top, bottom] }\n\n horizontalLines.push(topLine, bottomLine, horizontalCenterLine)\n verticalLines.push(leftLine, rightLine, verticalCenterLine)\n }\n\n // 画布可视区域的四个边界、水平中心、垂直中心\n const edgeTopLine: AlignLine = { value: 0, range: [0, edgeWidth] }\n const edgeBottomLine: AlignLine = { value: edgeHeight, range: [0, edgeWidth] }\n const edgeHorizontalCenterLine: AlignLine = { value: edgeHeight / 2, range: [0, edgeWidth] }\n const edgeLeftLine: AlignLine = { value: 0, range: [0, edgeHeight] }\n const edgeRightLine: AlignLine = { value: edgeWidth, range: [0, edgeHeight] }\n const edgeVerticalCenterLine: AlignLine = { value: edgeWidth / 2, range: [0, edgeHeight] }\n\n horizontalLines.push(edgeTopLine, edgeBottomLine, edgeHorizontalCenterLine)\n verticalLines.push(edgeLeftLine, edgeRightLine, edgeVerticalCenterLine)\n \n // 对齐吸附线去重\n horizontalLines = uniqAlignLines(horizontalLines)\n verticalLines = uniqAlignLines(verticalLines)\n\n // 开始移动\n document.onmousemove = e => {\n const currentPageX = e.pageX\n const currentPageY = e.pageY\n\n // 如果鼠标滑动距离过小,则将操作判定为误操作:\n // 如果误操作标记为null,表示是第一次触发移动,需要计算当前是否是误操作\n // 如果误操作标记为true,表示当前还处在误操作范围内,但仍然需要继续计算检查后续操作是否还处于误操作\n // 如果误操作标记为false,表示已经脱离了误操作范围,不需要再次计算\n if (isMisoperation !== false) {\n isMisoperation = Math.abs(startPageX - currentPageX) < sorptionRange && \n Math.abs(startPageY - currentPageY) < sorptionRange\n }\n if (!isMouseDown || isMisoperation) return\n\n const moveX = (currentPageX - startPageX) / canvasScale.value\n const moveY = (currentPageY - startPageY) / canvasScale.value\n\n // 基础目标位置\n let targetLeft = elOriginLeft + moveX\n let targetTop = elOriginTop + moveY\n\n // 计算目标元素在画布中的位置范围,用于吸附对齐\n // 需要区分单选和多选两种情况,其中多选状态下需要计算多选元素的整体范围;单选状态下需要继续区分线条、普通元素、旋转后的普通元素三种情况\n let targetMinX: number, targetMaxX: number, targetMinY: number, targetMaxY: number\n\n if (activeElementIdList.value.length === 1 || isActiveGroupElement) {\n if (elOriginRotate) {\n const { xRange, yRange } = getRectRotatedRange({\n left: targetLeft,\n top: targetTop,\n width: elOriginWidth,\n height: elOriginHeight,\n rotate: elOriginRotate,\n })\n targetMinX = xRange[0]\n targetMaxX = xRange[1]\n targetMinY = yRange[0]\n targetMaxY = yRange[1]\n }\n else if (element.type === 'line') {\n targetMinX = targetLeft\n targetMaxX = targetLeft + Math.max(element.start[0], element.end[0])\n targetMinY = targetTop\n targetMaxY = targetTop + Math.max(element.start[1], element.end[1])\n }\n else {\n targetMinX = targetLeft\n targetMaxX = targetLeft + elOriginWidth\n targetMinY = targetTop\n targetMaxY = targetTop + elOriginHeight\n }\n }\n else {\n const leftValues = []\n const topValues = []\n const rightValues = []\n const bottomValues = []\n \n for (let i = 0; i < originActiveElementList.length; i++) {\n const element = originActiveElementList[i]\n const left = element.left + moveX\n const top = element.top + moveY\n const width = element.width\n const height = ('height' in element && element.height) ? element.height : 0\n const rotate = ('rotate' in element && element.rotate) ? element.rotate : 0\n\n if ('rotate' in element && element.rotate) {\n const { xRange, yRange } = getRectRotatedRange({ left, top, width, height, rotate })\n leftValues.push(xRange[0])\n topValues.push(yRange[0])\n rightValues.push(xRange[1])\n bottomValues.push(yRange[1])\n }\n else if (element.type === 'line') {\n leftValues.push(left)\n topValues.push(top)\n rightValues.push(left + Math.max(element.start[0], element.end[0]))\n bottomValues.push(top + Math.max(element.start[1], element.end[1]))\n }\n else {\n leftValues.push(left)\n topValues.push(top)\n rightValues.push(left + width)\n bottomValues.push(top + height)\n }\n }\n\n targetMinX = Math.min(...leftValues)\n targetMaxX = Math.max(...rightValues)\n targetMinY = Math.min(...topValues)\n targetMaxY = Math.max(...bottomValues)\n }\n \n const targetCenterX = targetMinX + (targetMaxX - targetMinX) / 2\n const targetCenterY = targetMinY + (targetMaxY - targetMinY) / 2\n\n // 将收集到的对齐吸附线与计算的目标元素位置范围做对比,二者的差小于设定的值时执行自动对齐校正\n // 水平和垂直两个方向需要分开计算\n const _alignmentLines: AlignmentLineProps[] = []\n let isVerticalAdsorbed = false\n let isHorizontalAdsorbed = false\n for (let i = 0; i < horizontalLines.length; i++) {\n const { value, range } = horizontalLines[i]\n const min = Math.min(...range, targetMinX, targetMaxX)\n const max = Math.max(...range, targetMinX, targetMaxX)\n \n if (Math.abs(targetMinY - value) < sorptionRange && !isHorizontalAdsorbed) {\n targetTop = targetTop - (targetMinY - value)\n isHorizontalAdsorbed = true\n _alignmentLines.push({type: 'horizontal', axis: {x: min - 50, y: value}, length: max - min + 100})\n }\n if (Math.abs(targetMaxY - value) < sorptionRange && !isHorizontalAdsorbed) {\n targetTop = targetTop - (targetMaxY - value)\n isHorizontalAdsorbed = true\n _alignmentLines.push({type: 'horizontal', axis: {x: min - 50, y: value}, length: max - min + 100})\n }\n if (Math.abs(targetCenterY - value) < sorptionRange && !isHorizontalAdsorbed) {\n targetTop = targetTop - (targetCenterY - value)\n isHorizontalAdsorbed = true\n _alignmentLines.push({type: 'horizontal', axis: {x: min - 50, y: value}, length: max - min + 100})\n }\n }\n for (let i = 0; i < verticalLines.length; i++) {\n const { value, range } = verticalLines[i]\n const min = Math.min(...range, targetMinY, targetMaxY)\n const max = Math.max(...range, targetMinY, targetMaxY)\n\n if (Math.abs(targetMinX - value) < sorptionRange && !isVerticalAdsorbed) {\n targetLeft = targetLeft - (targetMinX - value)\n isVerticalAdsorbed = true\n _alignmentLines.push({type: 'vertical', axis: {x: value, y: min - 50}, length: max - min + 100})\n }\n if (Math.abs(targetMaxX - value) < sorptionRange && !isVerticalAdsorbed) {\n targetLeft = targetLeft - (targetMaxX - value)\n isVerticalAdsorbed = true\n _alignmentLines.push({type: 'vertical', axis: {x: value, y: min - 50}, length: max - min + 100})\n }\n if (Math.abs(targetCenterX - value) < sorptionRange && !isVerticalAdsorbed) {\n targetLeft = targetLeft - (targetCenterX - value)\n isVerticalAdsorbed = true\n _alignmentLines.push({type: 'vertical', axis: {x: value, y: min - 50}, length: max - min + 100})\n }\n }\n alignmentLines.value = _alignmentLines\n \n // 单选状态下,或者当前选中的多个元素中存在正在操作的元素时,仅修改正在操作的元素的位置\n if (activeElementIdList.value.length === 1 || isActiveGroupElement) {\n elementList.value = elementList.value.map(el => {\n return el.id === element.id ? { ...el, left: targetLeft, top: targetTop } : el\n })\n }\n\n // 多选状态下,除了修改正在操作的元素的位置,其他被选中的元素也需要修改位置信息\n // 其他被选中的元素的位置信息通过正在操作的元素的移动偏移量来进行计算\n else {\n const handleElement = elementList.value.find(el => el.id === element.id)\n if (!handleElement) return\n\n elementList.value = elementList.value.map(el => {\n if (activeElementIdList.value.includes(el.id)) {\n if (el.id === element.id) {\n return {\n ...el,\n left: targetLeft,\n top: targetTop,\n }\n }\n return {\n ...el,\n left: el.left + (targetLeft - handleElement.left),\n top: el.top + (targetTop - handleElement.top),\n }\n }\n return el\n })\n }\n }\n\n document.onmouseup = e => {\n isMouseDown = false\n document.onmousemove = null\n document.onmouseup = null\n alignmentLines.value = []\n\n const currentPageX = e.pageX\n const currentPageY = e.pageY\n\n if (startPageX === currentPageX && startPageY === currentPageY) return\n\n slidesStore.updateSlide({ elements: elementList.value })\n addHistorySnapshot()\n }\n }\n\n return {\n dragElement,\n }\n}","import { Ref } from 'vue'\nimport { storeToRefs } from 'pinia'\nimport { useMainStore, useSlidesStore } from '@/store'\nimport { PPTElement, PPTLineElement } from '@/types/slides'\nimport { OperateLineHandlers } from '@/types/edit'\nimport useHistorySnapshot from '@/hooks/useHistorySnapshot'\n\ninterface AdsorptionPoint {\n x: number;\n y: number;\n}\n\nexport default (elementList: Ref) => {\n const slidesStore = useSlidesStore()\n const { canvasScale } = storeToRefs(useMainStore())\n\n const { addHistorySnapshot } = useHistorySnapshot()\n\n // 拖拽线条端点\n const dragLineElement = (e: MouseEvent, element: PPTLineElement, command: OperateLineHandlers) => {\n let isMouseDown = true\n\n const sorptionRange = 8\n\n const startPageX = e.pageX\n const startPageY = e.pageY\n\n const adsorptionPoints: AdsorptionPoint[] = []\n\n // 获取所有线条以外的未旋转的元素的8个缩放点作为吸附位置\n for (let i = 0; i < elementList.value.length; i++) {\n const _element = elementList.value[i]\n if (_element.type === 'line' || _element.rotate) continue\n\n const left = _element.left\n const top = _element.top\n const width = _element.width\n const height = _element.height\n \n const right = left + width\n const bottom = top + height\n const centerX = top + height / 2\n const centerY = left + width / 2\n\n const topPoint = { x: centerY, y: top }\n const bottomPoint = { x: centerY, y: bottom }\n const leftPoint = { x: left, y: centerX }\n const rightPoint = { x: right, y: centerX }\n\n const leftTopPoint = { x: left, y: top }\n const rightTopPoint = { x: right, y: top }\n const leftBottomPoint = { x: left, y: bottom }\n const rightBottomPoint = { x: right, y: bottom }\n\n adsorptionPoints.push(\n topPoint,\n bottomPoint,\n leftPoint,\n rightPoint,\n leftTopPoint,\n rightTopPoint,\n leftBottomPoint,\n rightBottomPoint,\n )\n }\n\n document.onmousemove = e => {\n if (!isMouseDown) return\n\n const currentPageX = e.pageX\n const currentPageY = e.pageY\n\n const moveX = (currentPageX - startPageX) / canvasScale.value\n const moveY = (currentPageY - startPageY) / canvasScale.value\n \n // 线条起点和终点在编辑区域中的位置\n let startX = element.left + element.start[0]\n let startY = element.top + element.start[1]\n let endX = element.left + element.end[0]\n let endY = element.top + element.end[1]\n\n const mid = element.broken || element.curve || [0, 0]\n let midX = element.left + mid[0]\n let midY = element.top + mid[1]\n\n const [c1, c2] = element.cubic || [[0, 0], [0, 0]]\n let c1X = element.left + c1[0]\n let c1Y = element.top + c1[1]\n let c2X = element.left + c2[0]\n let c2Y = element.top + c2[1]\n\n // 拖拽起点或终点的位置\n // 水平和垂直方向上有吸附\n if (command === OperateLineHandlers.START) {\n startX = startX + moveX\n startY = startY + moveY\n\n if (Math.abs(startX - endX) < sorptionRange) startX = endX\n if (Math.abs(startY - endY) < sorptionRange) startY = endY\n\n for (const adsorptionPoint of adsorptionPoints) {\n const { x, y } = adsorptionPoint\n if (Math.abs(x - startX) < sorptionRange && Math.abs(y - startY) < sorptionRange) {\n startX = x\n startY = y\n break\n }\n }\n }\n else if (command === OperateLineHandlers.END) {\n endX = endX + moveX\n endY = endY + moveY\n\n if (Math.abs(startX - endX) < sorptionRange) endX = startX\n if (Math.abs(startY - endY) < sorptionRange) endY = startY\n\n for (const adsorptionPoint of adsorptionPoints) {\n const { x, y } = adsorptionPoint\n if (Math.abs(x - endX) < sorptionRange && Math.abs(y - endY) < sorptionRange) {\n endX = x\n endY = y\n break\n }\n }\n }\n else if (command === OperateLineHandlers.C) {\n midX = midX + moveX\n midY = midY + moveY\n\n if (Math.abs(midX - startX) < sorptionRange) midX = startX\n if (Math.abs(midY - startY) < sorptionRange) midY = startY\n if (Math.abs(midX - endX) < sorptionRange) midX = endX\n if (Math.abs(midY - endY) < sorptionRange) midY = endY\n if (Math.abs(midX - (startX + endX) / 2) < sorptionRange && Math.abs(midY - (startY + endY) / 2) < sorptionRange) {\n midX = (startX + endX) / 2\n midY = (startY + endY) / 2\n }\n }\n else if (command === OperateLineHandlers.C1) {\n c1X = c1X + moveX\n c1Y = c1Y + moveY\n\n if (Math.abs(c1X - startX) < sorptionRange) c1X = startX\n if (Math.abs(c1Y - startY) < sorptionRange) c1Y = startY\n if (Math.abs(c1X - endX) < sorptionRange) c1X = endX\n if (Math.abs(c1Y - endY) < sorptionRange) c1Y = endY\n }\n else if (command === OperateLineHandlers.C2) {\n c2X = c2X + moveX\n c2Y = c2Y + moveY\n\n if (Math.abs(c2X - startX) < sorptionRange) c2X = startX\n if (Math.abs(c2Y - startY) < sorptionRange) c2Y = startY\n if (Math.abs(c2X - endX) < sorptionRange) c2X = endX\n if (Math.abs(c2Y - endY) < sorptionRange) c2Y = endY\n }\n\n // 计算更新起点和终点基于自身元素位置的坐标\n const minX = Math.min(startX, endX)\n const minY = Math.min(startY, endY)\n const maxX = Math.max(startX, endX)\n const maxY = Math.max(startY, endY)\n\n const start: [number, number] = [0, 0]\n const end: [number, number] = [maxX - minX, maxY - minY]\n if (startX > endX) {\n start[0] = maxX - minX\n end[0] = 0\n }\n if (startY > endY) {\n start[1] = maxY - minY\n end[1] = 0\n }\n\n elementList.value = elementList.value.map(el => {\n if (el.id === element.id) {\n const newEl: PPTLineElement = {\n ...(el as PPTLineElement),\n left: minX,\n top: minY,\n start: start,\n end: end,\n }\n if (command === OperateLineHandlers.START || command === OperateLineHandlers.END) {\n if (element.broken) newEl.broken = [(start[0] + end[0]) / 2, (start[1] + end[1]) / 2]\n if (element.curve) newEl.curve = [(start[0] + end[0]) / 2, (start[1] + end[1]) / 2]\n if (element.cubic) newEl.cubic = [[(start[0] + end[0]) / 2, (start[1] + end[1]) / 2], [(start[0] + end[0]) / 2, (start[1] + end[1]) / 2]]\n }\n else if (command === OperateLineHandlers.C) {\n if (element.broken) newEl.broken = [midX - minX, midY - minY]\n if (element.curve) newEl.curve = [midX - minX, midY - minY]\n }\n else {\n if (element.cubic) newEl.cubic = [[c1X - minX, c1Y - minY], [c2X - minX, c2Y - minY]]\n }\n return newEl\n }\n return el\n })\n }\n\n document.onmouseup = e => {\n isMouseDown = false\n document.onmousemove = null\n document.onmouseup = null\n\n const currentPageX = e.pageX\n const currentPageY = e.pageY\n\n if (startPageX === currentPageX && startPageY === currentPageY) return\n\n slidesStore.updateSlide({ elements: elementList.value })\n addHistorySnapshot()\n }\n }\n\n return {\n dragLineElement,\n }\n}","import { Ref } from 'vue'\nimport { storeToRefs } from 'pinia'\nimport { useMainStore } from '@/store'\nimport { CreateElementSelectionData, CreatingLineElement, CreatingShapeElement } from '@/types/edit'\nimport useCreateElement from '@/hooks/useCreateElement'\n\nexport default (viewportRef: Ref) => {\n const mainStore = useMainStore()\n const { canvasScale, creatingElement } = storeToRefs(mainStore)\n\n // 通过鼠标框选时的起点和终点,计算选区的位置大小\n const formatCreateSelection = (selectionData: CreateElementSelectionData) => {\n const { start, end } = selectionData\n\n if (!viewportRef.value) return\n const viewportRect = viewportRef.value.getBoundingClientRect()\n\n const [startX, startY] = start\n const [endX, endY] = end\n const minX = Math.min(startX, endX)\n const maxX = Math.max(startX, endX)\n const minY = Math.min(startY, endY)\n const maxY = Math.max(startY, endY)\n\n const left = (minX - viewportRect.x) / canvasScale.value\n const top = (minY - viewportRect.y) / canvasScale.value\n const width = (maxX - minX) / canvasScale.value\n const height = (maxY - minY) / canvasScale.value\n\n return { left, top, width, height }\n }\n\n // 通过鼠标框选时的起点和终点,计算线条在画布中的位置和起点终点\n const formatCreateSelectionForLine = (selectionData: CreateElementSelectionData) => {\n const { start, end } = selectionData\n\n if (!viewportRef.value) return\n const viewportRect = viewportRef.value.getBoundingClientRect()\n\n const [startX, startY] = start\n const [endX, endY] = end\n const minX = Math.min(startX, endX)\n const maxX = Math.max(startX, endX)\n const minY = Math.min(startY, endY)\n const maxY = Math.max(startY, endY)\n\n const left = (minX - viewportRect.x) / canvasScale.value\n const top = (minY - viewportRect.y) / canvasScale.value\n const width = (maxX - minX) / canvasScale.value\n const height = (maxY - minY) / canvasScale.value\n\n const _start: [number, number] = [\n startX === minX ? 0 : width,\n startY === minY ? 0 : height,\n ]\n const _end: [number, number] = [\n endX === minX ? 0 : width,\n endY === minY ? 0 : height,\n ]\n\n return {\n left,\n top,\n start: _start,\n end: _end,\n }\n }\n\n const { createTextElement, createShapeElement, createLineElement } = useCreateElement()\n\n // 根据鼠标选区的位置大小插入元素\n const insertElementFromCreateSelection = (selectionData: CreateElementSelectionData) => {\n if (!creatingElement.value) return\n\n const type = creatingElement.value.type\n if (type === 'text') {\n const position = formatCreateSelection(selectionData)\n position && createTextElement(position)\n }\n else if (type === 'shape') {\n const position = formatCreateSelection(selectionData)\n position && createShapeElement(position, (creatingElement.value as CreatingShapeElement).data)\n }\n else if (type === 'line') {\n const position = formatCreateSelectionForLine(selectionData)\n position && createLineElement(position, (creatingElement.value as CreatingLineElement).data)\n }\n mainStore.setCreatingElement(null)\n }\n\n return {\n insertElementFromCreateSelection,\n }\n}","import { resolveDynamicComponent as _resolveDynamicComponent, openBlock as _openBlock, createBlock as _createBlock, normalizeStyle as _normalizeStyle, createElementBlock as _createElementBlock } from \"vue\"\n\nconst _hoisted_1 = [\"id\"]\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n return (_openBlock(), _createElementBlock(\"div\", {\n class: \"editable-element\",\n ref: \"elementRef\",\n id: `editable-element-${_ctx.elementInfo.id}`,\n style: _normalizeStyle({\n zIndex: _ctx.elementIndex,\n })\n }, [\n (_openBlock(), _createBlock(_resolveDynamicComponent(_ctx.currentElementComponent), {\n elementInfo: _ctx.elementInfo,\n selectElement: _ctx.selectElement,\n contextmenus: _ctx.contextmenus\n }, null, 8, [\"elementInfo\", \"selectElement\", \"contextmenus\"]))\n ], 12, _hoisted_1))\n}","import { storeToRefs } from 'pinia'\nimport { useMainStore, useSlidesStore } from '@/store'\nimport { PPTElement } from '@/types/slides'\nimport { ElementAlignCommands } from '@/types/edit'\nimport { getElementListRange } from '@/utils/element'\nimport { VIEWPORT_SIZE } from '@/configs/canvas'\nimport useHistorySnapshot from './useHistorySnapshot'\n\nexport default () => {\n const slidesStore = useSlidesStore()\n const { activeElementIdList, activeElementList } = storeToRefs(useMainStore())\n const { currentSlide, viewportRatio } = storeToRefs(slidesStore)\n\n const { addHistorySnapshot } = useHistorySnapshot()\n\n /**\n * 将所有选中的元素对齐到画布\n * @param command 对齐方向\n */\n const alignElementToCanvas = (command: ElementAlignCommands) => {\n const viewportWidth = VIEWPORT_SIZE\n const viewportHeight = VIEWPORT_SIZE * viewportRatio.value\n const { minX, maxX, minY, maxY } = getElementListRange(activeElementList.value)\n \n const newElementList: PPTElement[] = JSON.parse(JSON.stringify(currentSlide.value.elements))\n for (const element of newElementList) {\n if (!activeElementIdList.value.includes(element.id)) continue\n \n // 水平垂直居中\n if (command === ElementAlignCommands.CENTER) {\n const offsetY = minY + (maxY - minY) / 2 - viewportHeight / 2\n const offsetX = minX + (maxX - minX) / 2 - viewportWidth / 2\n element.top = element.top - offsetY \n element.left = element.left - offsetX \n }\n\n // 顶部对齐\n if (command === ElementAlignCommands.TOP) {\n const offsetY = minY - 0\n element.top = element.top - offsetY \n }\n\n // 垂直居中\n else if (command === ElementAlignCommands.VERTICAL) {\n const offsetY = minY + (maxY - minY) / 2 - viewportHeight / 2\n element.top = element.top - offsetY \n }\n\n // 底部对齐\n else if (command === ElementAlignCommands.BOTTOM) {\n const offsetY = maxY - viewportHeight\n element.top = element.top - offsetY \n }\n \n // 左侧对齐\n else if (command === ElementAlignCommands.LEFT) {\n const offsetX = minX - 0\n element.left = element.left - offsetX \n }\n\n // 水平居中\n else if (command === ElementAlignCommands.HORIZONTAL) {\n const offsetX = minX + (maxX - minX) / 2 - viewportWidth / 2\n element.left = element.left - offsetX \n }\n\n // 右侧对齐\n else if (command === ElementAlignCommands.RIGHT) {\n const offsetX = maxX - viewportWidth\n element.left = element.left - offsetX \n }\n }\n\n slidesStore.updateSlide({ elements: newElementList })\n addHistorySnapshot()\n }\n\n return {\n alignElementToCanvas,\n }\n}","import { resolveComponent as _resolveComponent, openBlock as _openBlock, createBlock as _createBlock, createCommentVNode as _createCommentVNode, createVNode as _createVNode, withModifiers as _withModifiers, normalizeStyle as _normalizeStyle, createElementVNode as _createElementVNode, resolveDirective as _resolveDirective, createElementBlock as _createElementBlock, withDirectives as _withDirectives, normalizeClass as _normalizeClass, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-288ee59d\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = [\"src\"]\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _component_ImageClipHandler = _resolveComponent(\"ImageClipHandler\")!\n const _component_ImageOutline = _resolveComponent(\"ImageOutline\")!\n const _directive_contextmenu = _resolveDirective(\"contextmenu\")!\n\n return (_openBlock(), _createElementBlock(\"div\", {\n class: _normalizeClass([\"editable-element-image\", { 'lock': _ctx.elementInfo.lock }]),\n style: _normalizeStyle({\n top: _ctx.elementInfo.top + 'px',\n left: _ctx.elementInfo.left + 'px',\n width: _ctx.elementInfo.width + 'px',\n height: _ctx.elementInfo.height + 'px',\n })\n }, [\n _createElementVNode(\"div\", {\n class: \"rotate-wrapper\",\n style: _normalizeStyle({ transform: `rotate(${_ctx.elementInfo.rotate}deg)` })\n }, [\n (_ctx.isCliping)\n ? (_openBlock(), _createBlock(_component_ImageClipHandler, {\n key: 0,\n src: _ctx.elementInfo.src,\n clipData: _ctx.elementInfo.clip,\n width: _ctx.elementInfo.width,\n height: _ctx.elementInfo.height,\n top: _ctx.elementInfo.top,\n left: _ctx.elementInfo.left,\n rotate: _ctx.elementInfo.rotate,\n clipPath: _ctx.clipShape.style,\n onClip: _cache[0] || (_cache[0] = range => _ctx.handleClip(range))\n }, null, 8, [\"src\", \"clipData\", \"width\", \"height\", \"top\", \"left\", \"rotate\", \"clipPath\"]))\n : _withDirectives((_openBlock(), _createElementBlock(\"div\", {\n key: 1,\n class: \"element-content\",\n style: _normalizeStyle({\n filter: _ctx.shadowStyle ? `drop-shadow(${_ctx.shadowStyle})` : '',\n transform: _ctx.flipStyle,\n }),\n onMousedown: _cache[2] || (_cache[2] = $event => _ctx.handleSelectElement($event))\n }, [\n _createVNode(_component_ImageOutline, { elementInfo: _ctx.elementInfo }, null, 8, [\"elementInfo\"]),\n _createElementVNode(\"div\", {\n class: \"image-content\",\n style: _normalizeStyle({ clipPath: _ctx.clipShape.style })\n }, [\n _createElementVNode(\"img\", {\n src: _ctx.elementInfo.src,\n draggable: false,\n style: _normalizeStyle({\n top: _ctx.imgPosition.top,\n left: _ctx.imgPosition.left,\n width: _ctx.imgPosition.width,\n height: _ctx.imgPosition.height,\n filter: _ctx.filter,\n }),\n onDragstart: _cache[1] || (_cache[1] = _withModifiers(() => {}, [\"prevent\"])),\n alt: \"\"\n }, null, 44, _hoisted_1)\n ], 4)\n ], 36)), [\n [_directive_contextmenu, _ctx.contextmenus]\n ])\n ], 4)\n ], 6))\n}","import { computed, Ref } from 'vue'\nimport { PPTElementShadow } from '@/types/slides'\n\n// 计算元素的阴影样式\nexport default (shadow: Ref) => {\n const shadowStyle = computed(() => {\n if (shadow.value) {\n const { h, v, blur, color } = shadow.value\n return `${h}px ${v}px ${blur}px ${color}`\n }\n return ''\n })\n\n return {\n shadowStyle,\n }\n}","export const enum ClipPathTypes {\n RECT = 'rect',\n ELLIPSE = 'ellipse',\n POLYGON = 'polygon',\n}\n\nexport const enum ClipPaths {\n RECT = 'rect',\n ROUNDRECT = 'roundRect',\n ELLIPSE = 'ellipse',\n TRIANGLE = 'triangle',\n PENTAGON = 'pentagon',\n RHOMBUS = 'rhombus',\n STAR = 'star',\n}\n\nexport const CLIPPATHS = {\n rect: {\n name: '矩形',\n type: ClipPathTypes.RECT,\n radius: '0',\n style: '',\n },\n rect2: {\n name: '矩形2',\n type: ClipPathTypes.POLYGON,\n style: 'polygon(0% 0%, 80% 0%, 100% 20%, 100% 100%, 0 100%)',\n createPath: (width: number, height: number) => {\n return `M 0 0 L ${width * 0.8} 0 L ${width} ${height * 0.2} L ${width} ${height} L 0 ${height} Z`\n },\n },\n rect3: {\n name: '矩形3',\n type: ClipPathTypes.POLYGON,\n style: 'polygon(0% 0%, 80% 0%, 100% 20%, 100% 100%, 20% 100%, 0% 80%)',\n createPath: (width: number, height: number) => {\n return `M 0 0 L ${width * 0.8} 0 L ${width} ${height * 0.2} L ${width} ${height} L ${width * 0.2} ${height} L 0 ${height * 0.8} Z`\n },\n },\n roundRect: {\n name: '圆角矩形',\n type: ClipPathTypes.RECT,\n radius: '10%',\n style: 'inset(0 0 0 0 round 10% 10% 10% 10%)',\n },\n ellipse: {\n name: '圆形',\n type: ClipPathTypes.ELLIPSE,\n style: 'ellipse(50% 50% at 50% 50%)',\n },\n triangle: {\n name: '三角形',\n type: ClipPathTypes.POLYGON,\n style: 'polygon(50% 0%, 0% 100%, 100% 100%)',\n createPath: (width: number, height: number) => {\n return `M ${width * 0.5} 0 L 0 ${height} L ${width} ${height} Z`\n },\n },\n triangle2: {\n name: '三角形2',\n type: ClipPathTypes.POLYGON,\n style: 'polygon(50% 100%, 0% 0%, 100% 0%)',\n createPath: (width: number, height: number) => {\n return `M ${width * 0.5} ${height} L 0 0 L ${width} 0 Z`\n },\n },\n triangle3: {\n name: '三角形3',\n type: ClipPathTypes.POLYGON,\n style: 'polygon(0% 0%, 0% 100%, 100% 100%)',\n createPath: (width: number, height: number) => {\n return `M 0 0 L 0 ${height} L ${width} ${height} Z`\n },\n },\n rhombus: {\n name: '菱形',\n type: ClipPathTypes.POLYGON,\n style: 'polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%)',\n createPath: (width: number, height: number) => {\n return `M ${width * 0.5} 0 L ${width} ${height * 0.5} L ${width * 0.5} ${height} L 0 ${height * 0.5} Z`\n },\n },\n hexagon: {\n name: '六边形',\n type: ClipPathTypes.POLYGON,\n style: 'polygon(20% 0%, 80% 0%, 100% 50%, 80% 100%, 20% 100%, 0% 50%)',\n createPath: (width: number, height: number) => {\n return `M ${width * 0.2} 0 L ${width * 0.8} 0 L ${width} ${height * 0.5} L ${width * 0.8} ${height} L ${width * 0.2} ${height} L 0 ${height * 0.5} Z`\n },\n },\n pentagon: {\n name: '五边形',\n type: ClipPathTypes.POLYGON,\n style: 'polygon(50% 0%, 100% 38%, 82% 100%, 18% 100%, 0% 38%)',\n createPath: (width: number, height: number) => {\n return `M ${width * 0.5} 0 L ${width} ${0.38 * height} L ${0.82 * width} ${height} L ${0.18 * width} ${height} L 0 ${0.38 * height} Z`\n },\n },\n parallelogram: {\n name: '平行四边形',\n type: ClipPathTypes.POLYGON,\n style: 'polygon(30% 0%, 100% 0%, 70% 100%, 0% 100%)',\n createPath: (width: number, height: number) => {\n return `M ${width * 0.3} 0 L ${width} 0 L ${width * 0.7} ${height} L 0 ${height} Z`\n },\n },\n parallelogram2: {\n name: '平行四边形2',\n type: ClipPathTypes.POLYGON,\n style: 'polygon(30% 100%, 100% 100%, 70% 0%, 0% 0%)',\n createPath: (width: number, height: number) => {\n return `M ${width * 0.3} ${height} L ${width} ${height} L ${width * 0.7} 0 L 0 0 Z`\n },\n },\n trapezoid: {\n name: '梯形',\n type: ClipPathTypes.POLYGON,\n style: 'polygon(25% 0%, 75% 0%, 100% 100%, 0% 100%)',\n createPath: (width: number, height: number) => {\n return `M ${width * 0.25} 0 L ${width * 0.75} 0 L ${width} ${height} L 0 ${height} Z`\n },\n },\n trapezoid2: {\n name: '梯形2',\n type: ClipPathTypes.POLYGON,\n style: 'polygon(0% 0%, 100% 0%, 75% 100%, 25% 100%)',\n createPath: (width: number, height: number) => {\n return `M 0 0 L ${width} 0 L ${width * 0.75} ${height} L ${width * 0.25} ${height} Z`\n },\n },\n}","import { computed, Ref } from 'vue'\n\n// 计算元素的翻转样式\nexport default (flipH: Ref, flipV: Ref) => {\n const flipStyle = computed(() => {\n let style = ''\n \n if (flipH.value && flipV.value) style = 'rotateX(180deg) rotateY(180deg)'\n else if (flipV.value) style = 'rotateX(180deg)'\n else if (flipH.value) style = 'rotateY(180deg)'\n\n return style\n })\n\n return {\n flipStyle,\n }\n}","import { computed, Ref } from 'vue'\nimport { CLIPPATHS, ClipPathTypes } from '@/configs/imageClip'\nimport { ImageElementClip } from '@/types/slides'\n\nexport default (clip: Ref) => {\n const clipShape = computed(() => {\n if (!clip.value) return CLIPPATHS.rect\n const shape = clip.value.shape || ClipPathTypes.RECT\n\n return CLIPPATHS[shape]\n })\n\n const imgPosition = computed(() => {\n if (!clip.value) {\n return {\n top: '0',\n left: '0',\n width: '100%',\n height: '100%',\n }\n }\n\n const [start, end] = clip.value.range\n\n const widthScale = (end[0] - start[0]) / 100\n const heightScale = (end[1] - start[1]) / 100\n const left = start[0] / widthScale\n const top = start[1] / heightScale\n\n return {\n left: -left + '%',\n top: -top + '%',\n width: 100 / widthScale + '%',\n height: 100 / heightScale + '%',\n }\n })\n\n return {\n clipShape,\n imgPosition,\n }\n}","import { computed, Ref } from 'vue'\nimport { ImageElementFilters } from '@/types/slides'\n\nexport default (filters: Ref) => {\n const filter = computed(() => {\n if (!filters.value) return ''\n let filter = ''\n for (const key of Object.keys(filters.value)) {\n filter += `${key}(${filters.value[key]}) `\n }\n return filter\n })\n\n return {\n filter,\n }\n}","import { resolveComponent as _resolveComponent, openBlock as _openBlock, createBlock as _createBlock, createCommentVNode as _createCommentVNode, createElementBlock as _createElementBlock } from \"vue\"\n\nconst _hoisted_1 = { class: \"image-outline\" }\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _component_ImageRectOutline = _resolveComponent(\"ImageRectOutline\")!\n const _component_ImageEllipseOutline = _resolveComponent(\"ImageEllipseOutline\")!\n const _component_ImagePolygonOutline = _resolveComponent(\"ImagePolygonOutline\")!\n\n return (_openBlock(), _createElementBlock(\"div\", _hoisted_1, [\n (_ctx.clipShape.type === 'rect')\n ? (_openBlock(), _createBlock(_component_ImageRectOutline, {\n key: 0,\n width: _ctx.elementInfo.width,\n height: _ctx.elementInfo.height,\n radius: _ctx.clipShape.radius,\n outline: _ctx.elementInfo.outline\n }, null, 8, [\"width\", \"height\", \"radius\", \"outline\"]))\n : (_ctx.clipShape.type === 'ellipse')\n ? (_openBlock(), _createBlock(_component_ImageEllipseOutline, {\n key: 1,\n width: _ctx.elementInfo.width,\n height: _ctx.elementInfo.height,\n outline: _ctx.elementInfo.outline\n }, null, 8, [\"width\", \"height\", \"outline\"]))\n : (_ctx.clipShape.type === 'polygon')\n ? (_openBlock(), _createBlock(_component_ImagePolygonOutline, {\n key: 2,\n width: _ctx.elementInfo.width,\n height: _ctx.elementInfo.height,\n outline: _ctx.elementInfo.outline,\n createPath: _ctx.clipShape.createPath\n }, null, 8, [\"width\", \"height\", \"outline\", \"createPath\"]))\n : _createCommentVNode(\"\", true)\n ]))\n}","import { createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock, createCommentVNode as _createCommentVNode, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-48a14431\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = [\"width\", \"height\"]\nconst _hoisted_2 = [\"rx\", \"ry\", \"width\", \"height\", \"stroke\", \"stroke-width\", \"stroke-dasharray\"]\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n return (_ctx.outline)\n ? (_openBlock(), _createElementBlock(\"svg\", {\n key: 0,\n class: \"image-rect-outline\",\n overflow: \"visible\",\n width: _ctx.width,\n height: _ctx.height\n }, [\n _createElementVNode(\"rect\", {\n \"vector-effect\": \"non-scaling-stroke\",\n \"stroke-linecap\": \"butt\",\n \"stroke-miterlimit\": \"8\",\n \"stroke-linejoin\": \"\",\n fill: \"transparent\",\n rx: _ctx.radius,\n ry: _ctx.radius,\n width: _ctx.width,\n height: _ctx.height,\n stroke: _ctx.outlineColor,\n \"stroke-width\": _ctx.outlineWidth,\n \"stroke-dasharray\": _ctx.outlineStyle === 'dashed' ? '10 6' : '0 0'\n }, null, 8, _hoisted_2)\n ], 8, _hoisted_1))\n : _createCommentVNode(\"\", true)\n}","import { computed, Ref } from 'vue'\nimport { PPTElementOutline } from '@/types/slides'\n\n// 计算边框相关属性值,主要是对默认值的处理\nexport default (outline: Ref) => {\n const outlineWidth = computed(() => outline.value?.width ?? 0)\n const outlineStyle = computed(() => outline.value?.style || 'solid')\n const outlineColor = computed(() => outline.value?.color || '#d14424')\n\n return {\n outlineWidth,\n outlineStyle,\n outlineColor,\n }\n}","\nimport { PropType, defineComponent, toRef } from 'vue'\nimport { PPTElementOutline } from '@/types/slides'\nimport useElementOutline from '@/views/components/element/hooks/useElementOutline'\n\nexport default defineComponent({\n name: 'image-rect-outline',\n props: {\n width: {\n type: Number,\n required: true,\n },\n height: {\n type: Number,\n required: true,\n },\n outline: {\n type: Object as PropType\n },\n radius: {\n type: String,\n default: '0',\n },\n },\n setup(props) {\n const {\n outlineWidth,\n outlineStyle,\n outlineColor,\n } = useElementOutline(toRef(props, 'outline'))\n\n return {\n outlineWidth,\n outlineStyle,\n outlineColor,\n }\n },\n})\n","import { render } from \"./ImageRectOutline.vue?vue&type=template&id=48a14431&scoped=true&ts=true\"\nimport script from \"./ImageRectOutline.vue?vue&type=script&lang=ts\"\nexport * from \"./ImageRectOutline.vue?vue&type=script&lang=ts\"\n\nimport \"./ImageRectOutline.vue?vue&type=style&index=0&id=48a14431&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-48a14431\"]])\n\nexport default __exports__","import { createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock, createCommentVNode as _createCommentVNode, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-692bbda6\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = [\"width\", \"height\"]\nconst _hoisted_2 = [\"cx\", \"cy\", \"rx\", \"ry\", \"stroke\", \"stroke-width\", \"stroke-dasharray\"]\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n return (_ctx.outline)\n ? (_openBlock(), _createElementBlock(\"svg\", {\n key: 0,\n class: \"image-ellipse-outline\",\n overflow: \"visible\",\n width: _ctx.width,\n height: _ctx.height\n }, [\n _createElementVNode(\"ellipse\", {\n \"vector-effect\": \"non-scaling-stroke\",\n \"stroke-linecap\": \"butt\",\n \"stroke-miterlimit\": \"8\",\n \"stroke-linejoin\": \"\",\n fill: \"transparent\",\n cx: _ctx.width / 2,\n cy: _ctx.height / 2,\n rx: _ctx.width / 2,\n ry: _ctx.height / 2,\n stroke: _ctx.outlineColor,\n \"stroke-width\": _ctx.outlineWidth,\n \"stroke-dasharray\": _ctx.outlineStyle === 'dashed' ? '10 6' : '0 0'\n }, null, 8, _hoisted_2)\n ], 8, _hoisted_1))\n : _createCommentVNode(\"\", true)\n}","\nimport { PropType, defineComponent, toRef } from 'vue'\nimport { PPTElementOutline } from '@/types/slides'\nimport useElementOutline from '@/views/components/element/hooks/useElementOutline'\n\nexport default defineComponent({\n name: 'image-ellipse-outline',\n props: {\n width: {\n type: Number,\n required: true,\n },\n height: {\n type: Number,\n required: true,\n },\n outline: {\n type: Object as PropType\n },\n },\n setup(props) {\n const {\n outlineWidth,\n outlineStyle,\n outlineColor,\n } = useElementOutline(toRef(props, 'outline'))\n\n return {\n outlineWidth,\n outlineStyle,\n outlineColor,\n }\n },\n})\n","import { render } from \"./ImageEllipseOutline.vue?vue&type=template&id=692bbda6&scoped=true&ts=true\"\nimport script from \"./ImageEllipseOutline.vue?vue&type=script&lang=ts\"\nexport * from \"./ImageEllipseOutline.vue?vue&type=script&lang=ts\"\n\nimport \"./ImageEllipseOutline.vue?vue&type=style&index=0&id=692bbda6&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-692bbda6\"]])\n\nexport default __exports__","import { createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock, createCommentVNode as _createCommentVNode, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-21ca95cc\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = [\"width\", \"height\"]\nconst _hoisted_2 = [\"d\", \"stroke\", \"stroke-width\", \"stroke-dasharray\"]\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n return (_ctx.outline)\n ? (_openBlock(), _createElementBlock(\"svg\", {\n key: 0,\n class: \"image-polygon-outline\",\n overflow: \"visible\",\n width: _ctx.width,\n height: _ctx.height\n }, [\n _createElementVNode(\"path\", {\n \"vector-effect\": \"non-scaling-stroke\",\n \"stroke-linecap\": \"butt\",\n \"stroke-miterlimit\": \"8\",\n \"stroke-linejoin\": \"\",\n fill: \"transparent\",\n d: _ctx.createPath(_ctx.width, _ctx.height),\n stroke: _ctx.outlineColor,\n \"stroke-width\": _ctx.outlineWidth,\n \"stroke-dasharray\": _ctx.outlineStyle === 'dashed' ? '10 6' : '0 0'\n }, null, 8, _hoisted_2)\n ], 8, _hoisted_1))\n : _createCommentVNode(\"\", true)\n}","\nimport { PropType, defineComponent, toRef } from 'vue'\nimport { PPTElementOutline } from '@/types/slides'\nimport useElementOutline from '@/views/components/element/hooks/useElementOutline'\n\nexport default defineComponent({\n name: 'image-polygon-outline',\n props: {\n width: {\n type: Number,\n required: true,\n },\n height: {\n type: Number,\n required: true,\n },\n outline: {\n type: Object as PropType\n },\n createPath: {\n type: Function,\n required: true,\n },\n },\n setup(props) {\n const {\n outlineWidth,\n outlineStyle,\n outlineColor,\n } = useElementOutline(toRef(props, 'outline'))\n\n return {\n outlineWidth,\n outlineStyle,\n outlineColor,\n }\n },\n})\n","import { render } from \"./ImagePolygonOutline.vue?vue&type=template&id=21ca95cc&scoped=true&ts=true\"\nimport script from \"./ImagePolygonOutline.vue?vue&type=script&lang=ts\"\nexport * from \"./ImagePolygonOutline.vue?vue&type=script&lang=ts\"\n\nimport \"./ImagePolygonOutline.vue?vue&type=style&index=0&id=21ca95cc&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-21ca95cc\"]])\n\nexport default __exports__","\nimport { computed, defineComponent, PropType } from 'vue'\nimport { PPTImageElement } from '@/types/slides'\nimport useClipImage from '../useClipImage'\n\nimport ImageRectOutline from './ImageRectOutline.vue'\nimport ImageEllipseOutline from './ImageEllipseOutline.vue'\nimport ImagePolygonOutline from './ImagePolygonOutline.vue'\n\nexport default defineComponent({\n name: 'image-outline',\n components: {\n ImageRectOutline,\n ImageEllipseOutline,\n ImagePolygonOutline,\n },\n props: {\n elementInfo: {\n type: Object as PropType,\n required: true,\n },\n },\n setup(props) {\n const clip = computed(() => props.elementInfo.clip)\n const { clipShape } = useClipImage(clip)\n\n return {\n clipShape,\n }\n },\n})\n","import { render } from \"./index.vue?vue&type=template&id=6b56be7d&ts=true\"\nimport script from \"./index.vue?vue&type=script&lang=ts\"\nexport * from \"./index.vue?vue&type=script&lang=ts\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render]])\n\nexport default __exports__","import { normalizeStyle as _normalizeStyle, createElementVNode as _createElementVNode, renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, withModifiers as _withModifiers, normalizeClass as _normalizeClass, resolveDirective as _resolveDirective, withDirectives as _withDirectives, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-e9da1d6a\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = [\"src\"]\nconst _hoisted_2 = [\"src\"]\nconst _hoisted_3 = [\"onMousedown\"]\nconst _hoisted_4 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(\"svg\", {\n width: \"16\",\n height: \"16\",\n fill: \"#fff\",\n stroke: \"#333\"\n}, [\n /*#__PURE__*/_createElementVNode(\"path\", {\n \"stroke-width\": \"0.3\",\n \"shape-rendering\": \"crispEdges\",\n d: \"M 16 0 L 0 0 L 0 16 L 4 16 L 4 4 L 16 4 L 16 0 Z\"\n })\n], -1))\nconst _hoisted_5 = [\n _hoisted_4\n]\nconst _hoisted_6 = [\"onMousedown\"]\nconst _hoisted_7 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(\"svg\", {\n width: \"16\",\n height: \"16\",\n fill: \"#fff\",\n stroke: \"#333\"\n}, [\n /*#__PURE__*/_createElementVNode(\"path\", {\n \"stroke-width\": \"0.3\",\n \"shape-rendering\": \"crispEdges\",\n d: \"M 16 0 L 0 0 L 0 4 L 16 4 Z\"\n })\n], -1))\nconst _hoisted_8 = [\n _hoisted_7\n]\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _directive_click_outside = _resolveDirective(\"click-outside\")!\n\n return _withDirectives((_openBlock(), _createElementBlock(\"div\", {\n class: \"image-clip-handler\",\n style: _normalizeStyle(_ctx.clipWrapperPositionStyle)\n }, [\n _createElementVNode(\"img\", {\n class: \"bottom-img\",\n src: _ctx.src,\n draggable: false,\n alt: \"\",\n style: _normalizeStyle(_ctx.bottomImgPositionStyle)\n }, null, 12, _hoisted_1),\n _createElementVNode(\"div\", {\n class: \"top-image-content\",\n style: _normalizeStyle({\n ..._ctx.topImgWrapperPositionStyle,\n clipPath: _ctx.clipPath,\n })\n }, [\n _createElementVNode(\"img\", {\n class: \"top-img\",\n src: _ctx.src,\n draggable: false,\n alt: \"\",\n style: _normalizeStyle(_ctx.topImgPositionStyle)\n }, null, 12, _hoisted_2)\n ], 4),\n _createElementVNode(\"div\", {\n class: \"operate\",\n style: _normalizeStyle(_ctx.topImgWrapperPositionStyle),\n onMousedown: _cache[0] || (_cache[0] = _withModifiers($event => _ctx.moveClipRange($event), [\"stop\"]))\n }, [\n (_openBlock(), _createElementBlock(_Fragment, null, _renderList(['left-top', 'right-top', 'left-bottom', 'right-bottom'], (point) => {\n return _createElementVNode(\"div\", {\n class: _normalizeClass(['clip-point', point, _ctx.rotateClassName]),\n key: point,\n onMousedown: _withModifiers($event => _ctx.scaleClipRange($event, point), [\"stop\"])\n }, _hoisted_5, 42, _hoisted_3)\n }), 64)),\n (_openBlock(), _createElementBlock(_Fragment, null, _renderList(['top', 'bottom', 'left', 'right'], (point) => {\n return _createElementVNode(\"div\", {\n class: _normalizeClass(['clip-point', point, _ctx.rotateClassName]),\n key: point,\n onMousedown: _withModifiers($event => _ctx.scaleClipRange($event, point), [\"stop\"])\n }, _hoisted_8, 42, _hoisted_6)\n }), 64))\n ], 36)\n ], 4)), [\n [_directive_click_outside, _ctx.handleClip]\n ])\n}","\nimport { computed, defineComponent, onMounted, onUnmounted, PropType, ref } from 'vue'\nimport { storeToRefs } from 'pinia'\nimport { useMainStore, useKeyboardStore } from '@/store'\nimport { KEYS } from '@/configs/hotkey'\nimport { ImageClipData, ImageClipDataRange, ImageClipedEmitData, OperateResizeHandlers } from '@/types/edit'\n\nexport default defineComponent({\n name: 'image-clip-handler',\n emits: ['clip'],\n props: {\n src: {\n type: String,\n required: true,\n },\n clipData: {\n type: Object as PropType,\n },\n clipPath: {\n type: String,\n required: true,\n },\n width: {\n type: Number,\n required: true,\n },\n height: {\n type: Number,\n required: true,\n },\n top: {\n type: Number,\n required: true,\n },\n left: {\n type: Number,\n required: true,\n },\n rotate: {\n type: Number,\n required: true,\n },\n },\n setup(props, { emit }) {\n const { canvasScale } = storeToRefs(useMainStore())\n const { ctrlOrShiftKeyActive } = storeToRefs(useKeyboardStore())\n\n const clipWrapperPositionStyle = ref({\n top: '0',\n left: '0',\n })\n const isSettingClipRange = ref(false)\n const currentRange = ref(null)\n\n // 获取裁剪区域信息(裁剪区域占原图的宽高比例,处在原图中的位置)\n const getClipDataTransformInfo = () => {\n const [start, end] = props.clipData ? props.clipData.range : [[0, 0], [100, 100]]\n\n const widthScale = (end[0] - start[0]) / 100\n const heightScale = (end[1] - start[1]) / 100\n const left = start[0] / widthScale\n const top = start[1] / heightScale\n\n return { widthScale, heightScale, left, top }\n }\n \n // 底层图片位置大小(遮罩区域图片)\n const imgPosition = computed(() => {\n const { widthScale, heightScale, left, top } = getClipDataTransformInfo()\n return {\n left: -left,\n top: -top,\n width: 100 / widthScale,\n height: 100 / heightScale,\n }\n })\n\n // 底层图片位置大小样式(遮罩区域图片)\n const bottomImgPositionStyle = computed(() => {\n return {\n top: imgPosition.value.top + '%',\n left: imgPosition.value.left + '%',\n width: imgPosition.value.width + '%',\n height: imgPosition.value.height + '%',\n }\n })\n\n // 顶层图片容器位置大小(裁剪高亮区域)\n const topImgWrapperPosition = ref({\n top: 0,\n left: 0,\n width: 0,\n height: 0,\n })\n\n // 顶层图片容器位置大小样式(裁剪高亮区域)\n const topImgWrapperPositionStyle = computed(() => {\n const { top, left, width, height } = topImgWrapperPosition.value\n return {\n top: top + '%',\n left: left + '%',\n width: width + '%',\n height: height + '%',\n }\n })\n\n // 顶层图片位置大小样式(裁剪区域图片)\n const topImgPositionStyle = computed(() => {\n const bottomWidth = imgPosition.value.width\n const bottomHeight = imgPosition.value.height\n \n const { top, left, width, height } = topImgWrapperPosition.value\n \n return {\n left: -left * (100 / width) + '%',\n top: -top * (100 / height) + '%',\n width: bottomWidth / width * 100 + '%',\n height: bottomHeight / height * 100 + '%',\n }\n })\n\n // 初始化裁剪位置信息\n const initClipPosition = () => {\n const { left, top } = getClipDataTransformInfo()\n topImgWrapperPosition.value = {\n left: left,\n top: top,\n width: 100,\n height: 100,\n }\n \n clipWrapperPositionStyle.value = {\n top: -top + '%',\n left: -left + '%',\n }\n }\n\n // 执行裁剪:计算裁剪后的图片位置大小和裁剪信息,并将数据同步出去\n const handleClip = () => {\n if (isSettingClipRange.value) return\n\n if (!currentRange.value) {\n emit('clip', null)\n return\n }\n\n const { left, top } = getClipDataTransformInfo()\n\n const position = {\n left: (topImgWrapperPosition.value.left - left) / 100 * props.width,\n top: (topImgWrapperPosition.value.top - top) / 100 * props.height,\n width: (topImgWrapperPosition.value.width - 100) / 100 * props.width,\n height: (topImgWrapperPosition.value.height - 100) / 100 * props.height,\n }\n\n const clipedEmitData: ImageClipedEmitData = {\n range: currentRange.value,\n position,\n }\n emit('clip', clipedEmitData)\n }\n\n // 快捷键监听:回车确认裁剪\n const keyboardListener = (e: KeyboardEvent) => {\n const key = e.key.toUpperCase()\n if (key === KEYS.ENTER) handleClip()\n }\n\n onMounted(() => {\n initClipPosition()\n document.addEventListener('keydown', keyboardListener)\n })\n onUnmounted(() => {\n document.removeEventListener('keydown', keyboardListener)\n })\n\n // 计算并更新裁剪区域范围数据\n const updateRange = () => {\n const retPosition = {\n left: parseInt(topImgPositionStyle.value.left),\n top: parseInt(topImgPositionStyle.value.top),\n width: parseInt(topImgPositionStyle.value.width),\n height: parseInt(topImgPositionStyle.value.height),\n }\n\n const widthScale = 100 / retPosition.width\n const heightScale = 100 / retPosition.height\n\n const start: [number, number] = [\n -retPosition.left * widthScale,\n -retPosition.top * heightScale,\n ]\n const end: [number, number] = [\n widthScale * 100 + start[0],\n heightScale * 100 + start[1],\n ]\n\n currentRange.value = [start, end]\n }\n\n // 移动裁剪区域\n const moveClipRange = (e: MouseEvent) => {\n isSettingClipRange.value = true\n let isMouseDown = true\n\n const startPageX = e.pageX\n const startPageY = e.pageY\n const bottomPosition = imgPosition.value\n const originPositopn = { ...topImgWrapperPosition.value }\n\n document.onmousemove = e => {\n if (!isMouseDown) return\n\n const currentPageX = e.pageX\n const currentPageY = e.pageY\n\n let moveX = (currentPageX - startPageX) / canvasScale.value / props.width * 100\n let moveY = (currentPageY - startPageY) / canvasScale.value / props.height * 100\n\n if (props.rotate > 45 && props.rotate < 135) {\n moveX = (currentPageY - startPageY) / canvasScale.value / props.width * 100\n moveY = -(currentPageX - startPageX) / canvasScale.value / props.height * 100\n }\n if ((props.rotate >= 135 && props.rotate <= 180) || (props.rotate >= -180 && props.rotate <= -135)) {\n moveX = -moveX\n moveY = -moveY\n }\n if (props.rotate > -135 && props.rotate < -45) {\n moveX = -(currentPageY - startPageY) / canvasScale.value / props.width * 100\n moveY = (currentPageX - startPageX) / canvasScale.value / props.height * 100\n }\n\n let targetLeft = originPositopn.left + moveX\n let targetTop = originPositopn.top + moveY\n\n if (targetLeft < 0) targetLeft = 0\n else if (targetLeft + originPositopn.width > bottomPosition.width) {\n targetLeft = bottomPosition.width - originPositopn.width\n }\n if (targetTop < 0) targetTop = 0\n else if (targetTop + originPositopn.height > bottomPosition.height) {\n targetTop = bottomPosition.height - originPositopn.height\n }\n \n topImgWrapperPosition.value = {\n ...topImgWrapperPosition.value,\n left: targetLeft,\n top: targetTop,\n }\n }\n\n document.onmouseup = () => {\n isMouseDown = false\n document.onmousemove = null\n document.onmouseup = null\n\n updateRange()\n\n setTimeout(() => {\n isSettingClipRange.value = false\n }, 0)\n }\n }\n\n // 缩放裁剪区域\n const scaleClipRange = (e: MouseEvent, type: OperateResizeHandlers) => {\n isSettingClipRange.value = true\n let isMouseDown = true\n\n const minWidth = 50 / props.width * 100\n const minHeight = 50 / props.height * 100\n \n const startPageX = e.pageX\n const startPageY = e.pageY\n const bottomPosition = imgPosition.value\n const originPositopn = { ...topImgWrapperPosition.value }\n\n const aspectRatio = topImgWrapperPosition.value.width / topImgWrapperPosition.value.height\n\n document.onmousemove = e => {\n if (!isMouseDown) return\n\n const currentPageX = e.pageX\n const currentPageY = e.pageY\n\n let moveX = (currentPageX - startPageX) / canvasScale.value / props.width * 100\n let moveY = (currentPageY - startPageY) / canvasScale.value / props.height * 100\n\n if (props.rotate > 45 && props.rotate < 135) {\n moveX = (currentPageY - startPageY) / canvasScale.value / props.width * 100\n moveY = -(currentPageX - startPageX) / canvasScale.value / props.height * 100\n }\n if ((props.rotate >= 135 && props.rotate <= 180) || (props.rotate >= -180 && props.rotate <= -135)) {\n moveX = -moveX\n moveY = -moveY\n }\n if (props.rotate > -135 && props.rotate < -45) {\n moveX = -(currentPageY - startPageY) / canvasScale.value / props.width * 100\n moveY = (currentPageX - startPageX) / canvasScale.value / props.height * 100\n }\n\n if (ctrlOrShiftKeyActive.value) {\n if (type === OperateResizeHandlers.RIGHT_BOTTOM || type === OperateResizeHandlers.LEFT_TOP) moveY = moveX / aspectRatio\n if (type === OperateResizeHandlers.LEFT_BOTTOM || type === OperateResizeHandlers.RIGHT_TOP) moveY = -moveX / aspectRatio\n }\n\n let targetLeft, targetTop, targetWidth, targetHeight\n\n if (type === OperateResizeHandlers.LEFT_TOP) {\n if (originPositopn.left + moveX < 0) {\n moveX = -originPositopn.left\n }\n if (originPositopn.top + moveY < 0) {\n moveY = -originPositopn.top\n }\n if (originPositopn.width - moveX < minWidth) {\n moveX = originPositopn.width - minWidth\n }\n if (originPositopn.height - moveY < minHeight) {\n moveY = originPositopn.height - minHeight\n }\n targetWidth = originPositopn.width - moveX\n targetHeight = originPositopn.height - moveY\n targetLeft = originPositopn.left + moveX\n targetTop = originPositopn.top + moveY\n }\n else if (type === OperateResizeHandlers.RIGHT_TOP) {\n if (originPositopn.left + originPositopn.width + moveX > bottomPosition.width) {\n moveX = bottomPosition.width - (originPositopn.left + originPositopn.width)\n }\n if (originPositopn.top + moveY < 0) {\n moveY = -originPositopn.top\n }\n if (originPositopn.width + moveX < minWidth) {\n moveX = minWidth - originPositopn.width\n }\n if (originPositopn.height - moveY < minHeight) {\n moveY = originPositopn.height - minHeight\n }\n targetWidth = originPositopn.width + moveX\n targetHeight = originPositopn.height - moveY\n targetLeft = originPositopn.left\n targetTop = originPositopn.top + moveY\n }\n else if (type === OperateResizeHandlers.LEFT_BOTTOM) {\n if (originPositopn.left + moveX < 0) {\n moveX = -originPositopn.left\n }\n if (originPositopn.top + originPositopn.height + moveY > bottomPosition.height) {\n moveY = bottomPosition.height - (originPositopn.top + originPositopn.height)\n }\n if (originPositopn.width - moveX < minWidth) {\n moveX = originPositopn.width - minWidth\n }\n if (originPositopn.height + moveY < minHeight) {\n moveY = minHeight - originPositopn.height\n }\n targetWidth = originPositopn.width - moveX\n targetHeight = originPositopn.height + moveY\n targetLeft = originPositopn.left + moveX\n targetTop = originPositopn.top\n }\n else if (type === OperateResizeHandlers.RIGHT_BOTTOM) {\n if (originPositopn.left + originPositopn.width + moveX > bottomPosition.width) {\n moveX = bottomPosition.width - (originPositopn.left + originPositopn.width)\n }\n if (originPositopn.top + originPositopn.height + moveY > bottomPosition.height) {\n moveY = bottomPosition.height - (originPositopn.top + originPositopn.height)\n }\n if (originPositopn.width + moveX < minWidth) {\n moveX = minWidth - originPositopn.width\n }\n if (originPositopn.height + moveY < minHeight) {\n moveY = minHeight - originPositopn.height\n }\n targetWidth = originPositopn.width + moveX\n targetHeight = originPositopn.height + moveY\n targetLeft = originPositopn.left\n targetTop = originPositopn.top\n }\n else if (type === OperateResizeHandlers.TOP) {\n if (originPositopn.top + moveY < 0) {\n moveY = -originPositopn.top\n }\n if (originPositopn.height - moveY < minHeight) {\n moveY = originPositopn.height - minHeight\n }\n targetWidth = originPositopn.width\n targetHeight = originPositopn.height - moveY\n targetLeft = originPositopn.left\n targetTop = originPositopn.top + moveY\n }\n else if (type === OperateResizeHandlers.BOTTOM) {\n if (originPositopn.top + originPositopn.height + moveY > bottomPosition.height) {\n moveY = bottomPosition.height - (originPositopn.top + originPositopn.height)\n }\n if (originPositopn.height + moveY < minHeight) {\n moveY = minHeight - originPositopn.height\n }\n targetWidth = originPositopn.width\n targetHeight = originPositopn.height + moveY\n targetLeft = originPositopn.left\n targetTop = originPositopn.top\n }\n else if (type === OperateResizeHandlers.LEFT) {\n if (originPositopn.left + moveX < 0) {\n moveX = -originPositopn.left\n }\n if (originPositopn.width - moveX < minWidth) {\n moveX = originPositopn.width - minWidth\n }\n targetWidth = originPositopn.width - moveX\n targetHeight = originPositopn.height\n targetLeft = originPositopn.left + moveX\n targetTop = originPositopn.top\n }\n else {\n if (originPositopn.left + originPositopn.width + moveX > bottomPosition.width) {\n moveX = bottomPosition.width - (originPositopn.left + originPositopn.width)\n }\n if (originPositopn.width + moveX < minWidth) {\n moveX = minWidth - originPositopn.width\n }\n targetHeight = originPositopn.height\n targetWidth = originPositopn.width + moveX\n targetLeft = originPositopn.left\n targetTop = originPositopn.top\n }\n\n topImgWrapperPosition.value = {\n left: targetLeft,\n top: targetTop,\n width: targetWidth,\n height: targetHeight,\n }\n }\n\n document.onmouseup = () => {\n isMouseDown = false\n document.onmousemove = null\n document.onmouseup = null\n\n updateRange()\n\n setTimeout(() => isSettingClipRange.value = false, 0)\n }\n }\n\n const rotateClassName = computed(() => {\n const prefix = 'rotate-'\n const rotate = props.rotate\n if (rotate > -22.5 && rotate <= 22.5) return prefix + 0\n else if (rotate > 22.5 && rotate <= 67.5) return prefix + 45\n else if (rotate > 67.5 && rotate <= 112.5) return prefix + 90\n else if (rotate > 112.5 && rotate <= 157.5) return prefix + 135\n else if (rotate > 157.5 || rotate <= -157.5) return prefix + 0\n else if (rotate > -157.5 && rotate <= -112.5) return prefix + 45\n else if (rotate > -112.5 && rotate <= -67.5) return prefix + 90\n else if (rotate > -67.5 && rotate <= -22.5) return prefix + 135\n return prefix + 0\n })\n\n return {\n clipWrapperPositionStyle,\n bottomImgPositionStyle,\n topImgWrapperPositionStyle,\n topImgPositionStyle,\n rotateClassName,\n handleClip,\n moveClipRange,\n scaleClipRange,\n }\n },\n})\n","import { render } from \"./ImageClipHandler.vue?vue&type=template&id=e9da1d6a&scoped=true&ts=true\"\nimport script from \"./ImageClipHandler.vue?vue&type=script&lang=ts\"\nexport * from \"./ImageClipHandler.vue?vue&type=script&lang=ts\"\n\nimport \"./ImageClipHandler.vue?vue&type=style&index=0&id=e9da1d6a&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-e9da1d6a\"]])\n\nexport default __exports__","\nimport { computed, defineComponent, PropType } from 'vue'\nimport { storeToRefs } from 'pinia'\nimport { useMainStore, useSlidesStore } from '@/store'\nimport { ImageElementClip, PPTImageElement } from '@/types/slides'\nimport { ImageClipedEmitData } from '@/types/edit'\nimport { ContextmenuItem } from '@/components/Contextmenu/types'\nimport useElementShadow from '@/views/components/element/hooks/useElementShadow'\nimport useElementFlip from '@/views/components/element/hooks/useElementFlip'\nimport useHistorySnapshot from '@/hooks/useHistorySnapshot'\nimport useClipImage from './useClipImage'\nimport useFilter from './useFilter'\n\nimport ImageOutline from './ImageOutline/index.vue'\nimport ImageClipHandler from './ImageClipHandler.vue'\n\nexport default defineComponent({\n name: 'editable-element-image',\n components: {\n ImageOutline,\n ImageClipHandler,\n },\n props: {\n elementInfo: {\n type: Object as PropType,\n required: true,\n },\n selectElement: {\n type: Function as PropType<(e: MouseEvent, element: PPTImageElement, canMove?: boolean) => void>,\n required: true,\n },\n contextmenus: {\n type: Function as PropType<() => ContextmenuItem[]>,\n },\n },\n setup(props) {\n const mainStore = useMainStore()\n const slidesStore = useSlidesStore()\n const { clipingImageElementId } = storeToRefs(mainStore)\n\n const isCliping = computed(() => clipingImageElementId.value === props.elementInfo.id)\n\n const { addHistorySnapshot } = useHistorySnapshot()\n\n const shadow = computed(() => props.elementInfo.shadow)\n const { shadowStyle } = useElementShadow(shadow)\n\n const flipH = computed(() => props.elementInfo.flipH)\n const flipV = computed(() => props.elementInfo.flipV)\n const { flipStyle } = useElementFlip(flipH, flipV)\n\n const clip = computed(() => props.elementInfo.clip)\n const { clipShape, imgPosition } = useClipImage(clip)\n\n const filters = computed(() => props.elementInfo.filters)\n const { filter } = useFilter(filters)\n\n const handleSelectElement = (e: MouseEvent) => {\n if (props.elementInfo.lock) return\n e.stopPropagation()\n props.selectElement(e, props.elementInfo)\n }\n\n const handleClip = (data: ImageClipedEmitData) => {\n mainStore.setClipingImageElementId('')\n \n if (!data) return\n\n const { range, position } = data\n const originClip: ImageElementClip = props.elementInfo.clip || { shape: 'rect', range: [[0, 0], [100, 100]] }\n\n const left = props.elementInfo.left + position.left\n const top = props.elementInfo.top + position.top\n const width = props.elementInfo.width + position.width\n const height = props.elementInfo.height + position.height\n\n let centerOffsetX = 0\n let centerOffsetY = 0\n\n if (props.elementInfo.rotate) {\n const centerX = (left + width / 2) - (props.elementInfo.left + props.elementInfo.width / 2)\n const centerY = -((top + height / 2) - (props.elementInfo.top + props.elementInfo.height / 2))\n\n const radian = -props.elementInfo.rotate * Math.PI / 180\n\n const rotatedCenterX = centerX * Math.cos(radian) - centerY * Math.sin(radian)\n const rotatedCenterY = centerX * Math.sin(radian) + centerY * Math.cos(radian)\n\n centerOffsetX = rotatedCenterX - centerX\n centerOffsetY = -(rotatedCenterY - centerY)\n }\n\n const _props = {\n clip: { ...originClip, range },\n left: left + centerOffsetX,\n top: top + centerOffsetY,\n width,\n height,\n }\n slidesStore.updateElement({ id: props.elementInfo.id, props: _props })\n \n addHistorySnapshot()\n }\n\n return {\n isCliping,\n handleClip,\n clipingImageElementId,\n shadowStyle,\n handleSelectElement,\n clipShape,\n imgPosition,\n filter,\n flipStyle,\n }\n },\n})\n","import { render } from \"./index.vue?vue&type=template&id=288ee59d&scoped=true&ts=true\"\nimport script from \"./index.vue?vue&type=script&lang=ts\"\nexport * from \"./index.vue?vue&type=script&lang=ts\"\n\nimport \"./index.vue?vue&type=style&index=0&id=288ee59d&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-288ee59d\"]])\n\nexport default __exports__","import { resolveComponent as _resolveComponent, createVNode as _createVNode, createElementVNode as _createElementVNode, normalizeStyle as _normalizeStyle, resolveDirective as _resolveDirective, openBlock as _openBlock, createElementBlock as _createElementBlock, withDirectives as _withDirectives, normalizeClass as _normalizeClass, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-67907c9d\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(\"div\", { class: \"drag-handler top\" }, null, -1))\nconst _hoisted_2 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(\"div\", { class: \"drag-handler bottom\" }, null, -1))\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _component_ElementOutline = _resolveComponent(\"ElementOutline\")!\n const _component_ProsemirrorEditor = _resolveComponent(\"ProsemirrorEditor\")!\n const _directive_contextmenu = _resolveDirective(\"contextmenu\")!\n\n return (_openBlock(), _createElementBlock(\"div\", {\n class: _normalizeClass([\"editable-element-text\", { 'lock': _ctx.elementInfo.lock }]),\n ref: \"elementRef\",\n style: _normalizeStyle({\n top: _ctx.elementInfo.top + 'px',\n left: _ctx.elementInfo.left + 'px',\n width: _ctx.elementInfo.width + 'px',\n })\n }, [\n _createElementVNode(\"div\", {\n class: \"rotate-wrapper\",\n style: _normalizeStyle({ transform: `rotate(${_ctx.elementInfo.rotate}deg)` })\n }, [\n _withDirectives((_openBlock(), _createElementBlock(\"div\", {\n class: \"element-content\",\n style: _normalizeStyle({\n backgroundColor: _ctx.elementInfo.fill,\n opacity: _ctx.elementInfo.opacity,\n textShadow: _ctx.shadowStyle,\n lineHeight: _ctx.elementInfo.lineHeight,\n letterSpacing: (_ctx.elementInfo.wordSpace || 0) + 'px',\n color: _ctx.elementInfo.defaultColor,\n fontFamily: _ctx.elementInfo.defaultFontName,\n }),\n onMousedown: _cache[3] || (_cache[3] = $event => _ctx.handleSelectElement($event))\n }, [\n _createVNode(_component_ElementOutline, {\n width: _ctx.elementInfo.width,\n height: _ctx.elementInfo.height,\n outline: _ctx.elementInfo.outline\n }, null, 8, [\"width\", \"height\", \"outline\"]),\n _createVNode(_component_ProsemirrorEditor, {\n class: \"text\",\n elementId: _ctx.elementInfo.id,\n defaultColor: _ctx.elementInfo.defaultColor,\n defaultFontName: _ctx.elementInfo.defaultFontName,\n editable: !_ctx.elementInfo.lock,\n value: _ctx.elementInfo.content,\n onUpdate: _cache[0] || (_cache[0] = value => _ctx.updateContent(value)),\n onBlur: _cache[1] || (_cache[1] = ($event: any) => (_ctx.checkEmptyText())),\n onMousedown: _cache[2] || (_cache[2] = $event => _ctx.handleSelectElement($event, false))\n }, null, 8, [\"elementId\", \"defaultColor\", \"defaultFontName\", \"editable\", \"value\"]),\n _hoisted_1,\n _hoisted_2\n ], 36)), [\n [_directive_contextmenu, _ctx.contextmenus]\n ])\n ], 4)\n ], 6))\n}","import { createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock, createCommentVNode as _createCommentVNode, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-34c0a0a8\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = [\"width\", \"height\"]\nconst _hoisted_2 = [\"d\", \"stroke\", \"stroke-width\", \"stroke-dasharray\"]\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n return (_ctx.outline)\n ? (_openBlock(), _createElementBlock(\"svg\", {\n key: 0,\n class: \"element-outline\",\n overflow: \"visible\",\n width: _ctx.width,\n height: _ctx.height\n }, [\n _createElementVNode(\"path\", {\n \"vector-effect\": \"non-scaling-stroke\",\n \"stroke-linecap\": \"butt\",\n \"stroke-miterlimit\": \"8\",\n fill: \"transparent\",\n d: `M0,0 L${_ctx.width},0 L${_ctx.width},${_ctx.height} L0,${_ctx.height} Z`,\n stroke: _ctx.outlineColor,\n \"stroke-width\": _ctx.outlineWidth,\n \"stroke-dasharray\": _ctx.outlineStyle === 'dashed' ? '10 6' : '0 0'\n }, null, 8, _hoisted_2)\n ], 8, _hoisted_1))\n : _createCommentVNode(\"\", true)\n}","\nimport { PropType, defineComponent, toRef } from 'vue'\nimport { PPTElementOutline } from '@/types/slides'\nimport useElementOutline from '@/views/components/element/hooks/useElementOutline'\n\nexport default defineComponent({\n name: 'element-outline',\n props: {\n width: {\n type: Number,\n required: true,\n },\n height: {\n type: Number,\n required: true,\n },\n outline: {\n type: Object as PropType\n },\n },\n setup(props) {\n const {\n outlineWidth,\n outlineStyle,\n outlineColor,\n } = useElementOutline(toRef(props, 'outline'))\n\n return {\n outlineWidth,\n outlineStyle,\n outlineColor,\n }\n },\n})\n","import { render } from \"./ElementOutline.vue?vue&type=template&id=34c0a0a8&scoped=true&ts=true\"\nimport script from \"./ElementOutline.vue?vue&type=script&lang=ts\"\nexport * from \"./ElementOutline.vue?vue&type=script&lang=ts\"\n\nimport \"./ElementOutline.vue?vue&type=style&index=0&id=34c0a0a8&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-34c0a0a8\"]])\n\nexport default __exports__","import { openBlock as _openBlock, createElementBlock as _createElementBlock, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-a1a33e98\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = {\n class: \"prosemirror-editor\",\n ref: \"editorViewRef\"\n}\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n return (_openBlock(), _createElementBlock(\"div\", _hoisted_1, null, 512))\n}","import mitt, { Emitter } from 'mitt'\n\nexport const enum EmitterEvents {\n RICH_TEXT_COMMAND = 'RICH_TEXT_COMMAND',\n OPEN_CHART_DATA_EDITOR = 'OPEN_CHART_DATA_EDITOR',\n OPEN_LATEX_EDITOR = 'OPEN_LATEX_EDITOR',\n}\n\nexport interface RichTextAction {\n command: string;\n value?: string;\n}\n\nexport interface RichTextCommand {\n target?: string;\n action: RichTextAction | RichTextAction[];\n}\n\ntype Events = {\n [EmitterEvents.RICH_TEXT_COMMAND]: RichTextCommand;\n [EmitterEvents.OPEN_CHART_DATA_EDITOR]: void;\n [EmitterEvents.OPEN_LATEX_EDITOR]: void;\n} \n\nconst emitter: Emitter = mitt()\n\nexport default emitter","import { splitListItem, liftListItem, sinkListItem } from 'prosemirror-schema-list'\nimport { Schema } from 'prosemirror-model'\nimport { undo, redo } from 'prosemirror-history'\nimport { undoInputRule } from 'prosemirror-inputrules'\nimport {\n toggleMark,\n selectParentNode,\n joinUp,\n joinDown,\n Command,\n} from 'prosemirror-commands'\n\nexport const buildKeymap = (schema: Schema) => {\n const keys = {}\n const bind = (key: string, cmd: Command) => keys[key] = cmd\n\n bind('Alt-ArrowUp', joinUp)\n bind('Alt-ArrowDown', joinDown)\n bind('Ctrl-z', undo)\n bind('Ctrl-y', redo)\n bind('Backspace', undoInputRule)\n bind('Escape', selectParentNode)\n bind('Ctrl-b', toggleMark(schema.marks.strong))\n bind('Ctrl-i', toggleMark(schema.marks.em))\n bind('Ctrl-u', toggleMark(schema.marks.underline))\n bind('Ctrl-d', toggleMark(schema.marks.strikethrough))\n\n bind('Enter', splitListItem(schema.nodes.list_item))\n bind('Mod-[', liftListItem(schema.nodes.list_item))\n bind('Mod-]', sinkListItem(schema.nodes.list_item))\n\n return keys\n}","import { NodeType, Schema } from 'prosemirror-model'\nimport {\n inputRules,\n wrappingInputRule,\n textblockTypeInputRule,\n smartQuotes,\n emDash,\n ellipsis,\n} from 'prosemirror-inputrules'\n\nconst blockQuoteRule = (nodeType: NodeType) => wrappingInputRule(/^\\s*>\\s$/, nodeType)\n\nconst orderedListRule = (nodeType: NodeType) => (\n wrappingInputRule(\n /^(\\d+)\\.\\s$/, \n nodeType, \n match => ({order: +match[1]}),\n (match, node) => node.childCount + node.attrs.order === +match[1],\n )\n)\n\nconst bulletListRule = (nodeType: NodeType) => wrappingInputRule(/^\\s*([-+*])\\s$/, nodeType)\n\nconst codeBlockRule = (nodeType: NodeType) => textblockTypeInputRule(/^```$/, nodeType)\n\nexport const buildInputRules = (schema: Schema) => {\n const rules = [\n ...smartQuotes,\n ellipsis,\n emDash,\n ]\n rules.push(blockQuoteRule(schema.nodes.blockquote))\n rules.push(orderedListRule(schema.nodes.ordered_list))\n rules.push(bulletListRule(schema.nodes.bullet_list))\n rules.push(codeBlockRule(schema.nodes.code_block))\n\n return inputRules({ rules })\n}","import { keymap } from 'prosemirror-keymap'\nimport { Schema } from 'prosemirror-model'\nimport { history } from 'prosemirror-history'\nimport { baseKeymap } from 'prosemirror-commands'\nimport { dropCursor } from 'prosemirror-dropcursor'\nimport { gapCursor } from 'prosemirror-gapcursor'\n\nimport { buildKeymap } from './keymap'\nimport { buildInputRules } from './inputrules'\n\nexport const buildPlugins = (schema: Schema) => {\n return [\n buildInputRules(schema),\n keymap(buildKeymap(schema)),\n keymap(baseKeymap),\n dropCursor(),\n gapCursor(),\n history(),\n ]\n}","import { nodes } from 'prosemirror-schema-basic'\nimport { Node, NodeSpec } from 'prosemirror-model'\nimport { orderedList, bulletList, listItem } from 'prosemirror-schema-list'\n\nconst _orderedList: NodeSpec = {\n ...orderedList,\n content: 'list_item+',\n group: 'block',\n}\n\nconst _bulletList: NodeSpec = {\n ...bulletList,\n content: 'list_item+',\n group: 'block',\n}\n\nconst _listItem: NodeSpec = {\n ...listItem,\n content: 'paragraph block*',\n group: 'block',\n}\n\nconst paragraph: NodeSpec = {\n attrs: {\n align: {\n default: '',\n },\n },\n content: 'inline*',\n group: 'block',\n parseDOM: [\n {\n tag: 'p',\n getAttrs: dom => {\n const { textAlign } = (dom as HTMLElement).style\n let align = (dom as HTMLElement).getAttribute('align') || textAlign || ''\n align = /(left|right|center|justify)/.test(align) ? align : ''\n \n return { align }\n }\n }\n ],\n toDOM: (node: Node) => {\n const { align } = node.attrs\n let style = ''\n if (align && align !== 'left') style += `text-align: ${align};`\n\n return ['p', { style }, 0]\n },\n}\n\nexport default {\n ...nodes,\n 'ordered_list': _orderedList,\n 'bullet_list': _bulletList,\n 'list_item': _listItem,\n paragraph,\n}\n","import { marks } from 'prosemirror-schema-basic'\nimport { MarkSpec } from 'prosemirror-model'\n\nconst subscript: MarkSpec = {\n excludes: 'subscript',\n parseDOM: [\n { tag: 'sub' },\n {\n style: 'vertical-align',\n getAttrs: value => value === 'sub' && null\n },\n ],\n toDOM: () => ['sub', 0],\n}\n\nconst superscript: MarkSpec = {\n excludes: 'superscript',\n parseDOM: [\n { tag: 'sup' },\n {\n style: 'vertical-align',\n getAttrs: value => value === 'super' && null\n },\n ],\n toDOM: () => ['sup', 0],\n}\n\nconst strikethrough: MarkSpec = {\n parseDOM: [\n { tag: 'strike' },\n {\n style: 'text-decoration',\n getAttrs: value => value === 'line-through' && null\n },\n {\n style: 'text-decoration-line',\n getAttrs: value => value === 'line-through' && null\n },\n ],\n toDOM: () => ['span', { style: 'text-decoration-line: line-through' }, 0],\n}\n\nconst underline: MarkSpec = {\n parseDOM: [\n { tag: 'u' },\n {\n style: 'text-decoration',\n getAttrs: value => value === 'underline' && null\n },\n {\n style: 'text-decoration-line',\n getAttrs: value => value === 'underline' && null\n },\n ],\n toDOM: () => ['span', { style: 'text-decoration: underline' }, 0],\n}\n\nconst forecolor: MarkSpec = {\n attrs: {\n color: {},\n },\n inline: true,\n group: 'inline',\n parseDOM: [\n {\n style: 'color',\n getAttrs: color => color ? { color } : {}\n },\n ],\n toDOM: mark => {\n const { color } = mark.attrs\n let style = ''\n if (color) style += `color: ${color};`\n return ['span', { style }, 0]\n },\n}\n\nconst backcolor: MarkSpec = {\n attrs: {\n backcolor: {},\n },\n inline: true,\n group: 'inline',\n parseDOM: [\n {\n style: 'background-color',\n getAttrs: backcolor => backcolor ? { backcolor } : {}\n },\n ],\n toDOM: mark => {\n const { backcolor } = mark.attrs\n let style = ''\n if (backcolor) style += `background-color: ${backcolor};`\n return ['span', { style }, 0]\n },\n}\n\nconst fontsize: MarkSpec = {\n attrs: {\n fontsize: {},\n },\n inline: true,\n group: 'inline',\n parseDOM: [\n {\n style: 'font-size',\n getAttrs: fontsize => fontsize ? { fontsize } : {}\n },\n ],\n toDOM: mark => {\n const { fontsize } = mark.attrs\n let style = ''\n if (fontsize) style += `font-size: ${fontsize}`\n return ['span', { style }, 0]\n },\n}\n\nconst fontname: MarkSpec = {\n attrs: {\n fontname: {},\n },\n inline: true,\n group: 'inline',\n parseDOM: [\n {\n style: 'font-family',\n getAttrs: fontname => {\n return { fontname: fontname && typeof fontname === 'string' ? fontname.replace(/[\\\"\\']/g, '') : '' }\n }\n },\n ],\n toDOM: mark => {\n const { fontname } = mark.attrs\n let style = ''\n if (fontname) style += `font-family: ${fontname}`\n return ['span', { style }, 0]\n },\n}\n\nconst link: MarkSpec = {\n attrs: {\n href: {},\n title: { default: null },\n target: { default: '_blank' },\n },\n inclusive: false,\n parseDOM: [\n {\n tag: 'a[href]',\n getAttrs: dom => {\n const href = (dom as HTMLElement).getAttribute('href')\n const title = (dom as HTMLElement).getAttribute('title')\n return { href, title }\n }\n },\n ],\n toDOM: node => ['a', node.attrs, 0],\n}\n\nexport default {\n ...marks,\n subscript,\n superscript,\n strikethrough,\n underline,\n forecolor,\n backcolor,\n fontsize,\n fontname,\n link,\n}","import nodes from './nodes'\nimport marks from './marks'\n\nexport const schemaNodes = nodes\nexport const schemaMarks = marks\n","import { EditorState } from 'prosemirror-state'\nimport { EditorView } from 'prosemirror-view'\nimport { Schema, DOMParser } from 'prosemirror-model'\n\nimport { buildPlugins } from './plugins/index'\nimport { schemaNodes, schemaMarks } from './schema/index'\n\nconst schema = new Schema({\n nodes: schemaNodes,\n marks: schemaMarks,\n})\n\nexport const createDocument = (content: string) => {\n const htmlString = `${content}
`\n const parser = new window.DOMParser()\n const element = parser.parseFromString(htmlString, 'text/html').body.firstElementChild\n return DOMParser.fromSchema(schema).parse(element as Element)\n}\n\nexport const initProsemirrorEditor = (dom: Element, content: string, props = {}) => {\n return new EditorView(dom, {\n state: EditorState.create({\n doc: createDocument(content),\n plugins: buildPlugins(schema),\n }),\n ...props,\n })\n}","import { Schema, Node, NodeType } from 'prosemirror-model'\nimport { Transaction } from 'prosemirror-state'\nimport { EditorView } from 'prosemirror-view'\n\nexport const setTextAlign = (tr: Transaction, schema: Schema, alignment: string) => {\n const { selection, doc } = tr\n if (!selection || !doc) return tr\n\n const { from, to } = selection\n const { nodes } = schema\n\n const blockquote = nodes.blockquote\n const listItem = nodes.list_item\n const paragraph = nodes.paragraph\n\n interface Task {\n node: Node;\n pos: number;\n nodeType: NodeType;\n }\n\n const tasks: Task[] = []\n alignment = alignment || ''\n\n const allowedNodeTypes = new Set([blockquote, listItem, paragraph])\n\n doc.nodesBetween(from, to, (node, pos) => {\n const nodeType = node.type\n const align = node.attrs.align || ''\n if (align !== alignment && allowedNodeTypes.has(nodeType)) {\n tasks.push({\n node,\n pos,\n nodeType,\n })\n }\n return true\n })\n\n if (!tasks.length) return tr\n\n tasks.forEach(task => {\n const { node, pos, nodeType } = task\n let { attrs } = node\n if (alignment) attrs = { ...attrs, align: alignment }\n else attrs = { ...attrs, align: null }\n tr = tr.setNodeMarkup(pos, nodeType, attrs, node.marks)\n })\n\n return tr\n}\n\nexport const alignmentCommand = (view: EditorView, alignment: string) => {\n const { state } = view\n const { schema, selection } = state\n const tr = setTextAlign(\n state.tr.setSelection(selection),\n schema,\n alignment,\n )\n view.dispatch(tr)\n}","import { wrapInList, liftListItem } from 'prosemirror-schema-list'\nimport { Schema, Node, NodeType } from 'prosemirror-model'\nimport { Transaction, EditorState } from 'prosemirror-state'\nimport { findParentNode } from '../utils'\n\nconst isList = (node: Node, schema: Schema) => {\n return (\n node.type === schema.nodes.bullet_list ||\n node.type === schema.nodes.ordered_list\n )\n}\n\nexport const toggleList = (listType: NodeType, itemType: NodeType) => {\n return (state: EditorState, dispatch: (tr: Transaction) => void) => {\n const { schema, selection } = state\n const { $from, $to } = selection\n const range = $from.blockRange($to)\n\n if (!range) return false\n\n const parentList = findParentNode((node: Node) => isList(node, schema))(selection)\n\n if (range.depth >= 1 && parentList && range.depth - parentList.depth <= 1) {\n if (parentList.node.type === listType) {\n return liftListItem(itemType)(state, dispatch)\n }\n\n if (isList(parentList.node, schema) && listType.validContent(parentList.node.content)) {\n const { tr } = state\n tr.setNodeMarkup(parentList.pos, listType)\n\n if (dispatch) dispatch(tr)\n\n return false\n }\n }\n\n return wrapInList(listType)(state, dispatch)\n }\n}\n","\nimport { computed, defineComponent, onMounted, onUnmounted, ref, watch } from 'vue'\nimport { debounce } from 'lodash'\nimport { storeToRefs } from 'pinia'\nimport { useMainStore } from '@/store'\nimport { EditorView } from 'prosemirror-view'\nimport { toggleMark, wrapIn } from 'prosemirror-commands'\nimport { initProsemirrorEditor, createDocument } from '@/utils/prosemirror'\nimport { findNodesWithSameMark, getTextAttrs, autoSelectAll, addMark, markActive, getFontsize } from '@/utils/prosemirror/utils'\nimport emitter, { EmitterEvents, RichTextCommand } from '@/utils/emitter'\nimport { alignmentCommand } from '@/utils/prosemirror/commands/setTextAlign'\nimport { toggleList } from '@/utils/prosemirror/commands/toggleList'\n\nexport default defineComponent({\n name: 'prosemirror-editor',\n emits: ['update', 'focus', 'blur'],\n props: {\n elementId: {\n type: String,\n required: true,\n },\n defaultColor: {\n type: String,\n required: true,\n },\n defaultFontName: {\n type: String,\n required: true,\n },\n editable: {\n type: Boolean,\n default: false,\n },\n value: {\n type: String,\n required: true,\n },\n autoFocus: {\n type: Boolean,\n default: false,\n },\n },\n setup(props, { emit }) {\n const mainStore = useMainStore()\n const { handleElementId } = storeToRefs(mainStore)\n\n const editorViewRef = ref()\n let editorView: EditorView\n\n // 富文本的各种交互事件监听:\n // 聚焦时取消全局快捷键事件\n // 输入文字时同步数据到vuex\n // 点击鼠标和键盘时同步富文本状态到工具栏\n const handleInput = debounce(function() {\n emit('update', editorView.dom.innerHTML)\n }, 300, { trailing: true })\n\n const handleFocus = () => {\n mainStore.setDisableHotkeysState(true)\n emit('focus')\n }\n\n const handleBlur = () => {\n mainStore.setDisableHotkeysState(false)\n emit('blur')\n }\n\n const handleClick = debounce(function() {\n const attrs = getTextAttrs(editorView, {\n color: props.defaultColor,\n fontname: props.defaultFontName,\n })\n mainStore.setRichtextAttrs(attrs)\n }, 30, { trailing: true })\n\n const handleKeydown = () => {\n handleInput()\n handleClick()\n }\n\n // 将富文本内容同步到DOM\n const textContent = computed(() => props.value)\n watch(textContent, () => {\n if (!editorView) return\n if (editorView.hasFocus()) return\n\n const { doc, tr } = editorView.state\n editorView.dispatch(tr.replaceRangeWith(0, doc.content.size, createDocument(textContent.value)))\n })\n\n // 打开/关闭编辑器的编辑模式\n watch(() => props.editable, () => {\n editorView.setProps({ editable: () => props.editable })\n })\n\n // Prosemirror编辑器的初始化和卸载\n onMounted(() => {\n editorView = initProsemirrorEditor((editorViewRef.value as Element), textContent.value, {\n handleDOMEvents: {\n focus: handleFocus,\n blur: handleBlur,\n keydown: handleKeydown,\n click: handleClick,\n },\n editable: () => props.editable,\n })\n if (props.autoFocus) editorView.focus()\n })\n onUnmounted(() => {\n editorView && editorView.destroy()\n })\n \n // 执行富文本命令(可以是一个或多个)\n // 部分命令在执行前先判断当前选区是否为空,如果选区为空先进行全选操作\n const execCommand = ({ target, action }: RichTextCommand) => {\n if (!target && handleElementId.value !== props.elementId) return\n if (target && target !== props.elementId) return\n\n const actions = ('command' in action) ? [action] : action\n\n for (const item of actions) {\n if (item.command === 'fontname' && item.value) {\n const mark = editorView.state.schema.marks.fontname.create({ fontname: item.value })\n autoSelectAll(editorView)\n addMark(editorView, mark)\n }\n else if (item.command === 'fontsize' && item.value) {\n const mark = editorView.state.schema.marks.fontsize.create({ fontsize: item.value })\n autoSelectAll(editorView)\n addMark(editorView, mark)\n }\n else if (item.command === 'fontsize-add') {\n const step = item.value ? +item.value : 2\n autoSelectAll(editorView)\n const fontsize = getFontsize(editorView) + step + 'px'\n const mark = editorView.state.schema.marks.fontsize.create({ fontsize })\n addMark(editorView, mark)\n }\n else if (item.command === 'fontsize-reduce') {\n const step = item.value ? +item.value : 2\n autoSelectAll(editorView)\n let fontsize = getFontsize(editorView) - step\n if (fontsize < 12) fontsize = 12\n const mark = editorView.state.schema.marks.fontsize.create({ fontsize: fontsize + 'px' })\n addMark(editorView, mark)\n }\n else if (item.command === 'color' && item.value) {\n const mark = editorView.state.schema.marks.forecolor.create({ color: item.value })\n autoSelectAll(editorView)\n addMark(editorView, mark)\n }\n else if (item.command === 'backcolor' && item.value) {\n const mark = editorView.state.schema.marks.backcolor.create({ backcolor: item.value })\n autoSelectAll(editorView)\n addMark(editorView, mark)\n }\n else if (item.command === 'bold') {\n autoSelectAll(editorView)\n toggleMark(editorView.state.schema.marks.strong)(editorView.state, editorView.dispatch)\n }\n else if (item.command === 'em') {\n autoSelectAll(editorView)\n toggleMark(editorView.state.schema.marks.em)(editorView.state, editorView.dispatch)\n }\n else if (item.command === 'underline') {\n autoSelectAll(editorView)\n toggleMark(editorView.state.schema.marks.underline)(editorView.state, editorView.dispatch)\n }\n else if (item.command === 'strikethrough') {\n autoSelectAll(editorView)\n toggleMark(editorView.state.schema.marks.strikethrough)(editorView.state, editorView.dispatch)\n }\n else if (item.command === 'subscript') {\n toggleMark(editorView.state.schema.marks.subscript)(editorView.state, editorView.dispatch)\n }\n else if (item.command === 'superscript') {\n toggleMark(editorView.state.schema.marks.superscript)(editorView.state, editorView.dispatch)\n }\n else if (item.command === 'blockquote') {\n wrapIn(editorView.state.schema.nodes.blockquote)(editorView.state, editorView.dispatch)\n }\n else if (item.command === 'code') {\n toggleMark(editorView.state.schema.marks.code)(editorView.state, editorView.dispatch)\n }\n else if (item.command === 'align' && item.value) {\n alignmentCommand(editorView, item.value)\n }\n else if (item.command === 'bulletList') {\n const { bullet_list: bulletList, list_item: listItem } = editorView.state.schema.nodes\n toggleList(bulletList, listItem)(editorView.state, editorView.dispatch)\n }\n else if (item.command === 'orderedList') {\n const { ordered_list: orderedList, list_item: listItem } = editorView.state.schema.nodes\n toggleList(orderedList, listItem)(editorView.state, editorView.dispatch)\n }\n else if (item.command === 'clear') {\n autoSelectAll(editorView)\n const { $from, $to } = editorView.state.selection\n editorView.dispatch(editorView.state.tr.removeMark($from.pos, $to.pos))\n }\n else if (item.command === 'link') {\n const markType = editorView.state.schema.marks.link\n const { from, to } = editorView.state.selection\n const result = findNodesWithSameMark(editorView.state.doc, from, to, markType)\n if (result) {\n if (item.value) {\n const mark = editorView.state.schema.marks.link.create({ href: item.value, title: item.value })\n addMark(editorView, mark, { from: result.from.pos, to: result.to.pos + 1 })\n }\n else editorView.dispatch(editorView.state.tr.removeMark(result.from.pos, result.to.pos + 1, markType))\n }\n else if (markActive(editorView.state, markType)) {\n if (item.value) {\n const mark = editorView.state.schema.marks.link.create({ href: item.value, title: item.value })\n addMark(editorView, mark)\n }\n else toggleMark(markType)(editorView.state, editorView.dispatch)\n }\n else if (item.value) {\n autoSelectAll(editorView)\n toggleMark(markType, { href: item.value, title: item.value })(editorView.state, editorView.dispatch)\n }\n }\n else if (item.command === 'insert' && item.value) {\n editorView.dispatch(editorView.state.tr.insertText(item.value))\n }\n }\n\n editorView.focus()\n handleInput()\n handleClick()\n }\n\n emitter.on(EmitterEvents.RICH_TEXT_COMMAND, execCommand)\n onUnmounted(() => {\n emitter.off(EmitterEvents.RICH_TEXT_COMMAND, execCommand)\n })\n\n return {\n editorViewRef,\n }\n },\n})\n","import { render } from \"./ProsemirrorEditor.vue?vue&type=template&id=a1a33e98&scoped=true&ts=true\"\nimport script from \"./ProsemirrorEditor.vue?vue&type=script&lang=ts\"\nexport * from \"./ProsemirrorEditor.vue?vue&type=script&lang=ts\"\n\nimport \"./ProsemirrorEditor.vue?vue&type=style&index=0&id=a1a33e98&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-a1a33e98\"]])\n\nexport default __exports__","\nimport { computed, defineComponent, onMounted, onUnmounted, PropType, ref, watch } from 'vue'\nimport { storeToRefs } from 'pinia'\nimport { useMainStore, useSlidesStore } from '@/store'\nimport { PPTTextElement } from '@/types/slides'\nimport { ContextmenuItem } from '@/components/Contextmenu/types'\nimport useElementShadow from '@/views/components/element/hooks/useElementShadow'\nimport useHistorySnapshot from '@/hooks/useHistorySnapshot'\n\nimport ElementOutline from '@/views/components/element/ElementOutline.vue'\nimport ProsemirrorEditor from '@/views/components/element/ProsemirrorEditor.vue'\n\nexport default defineComponent({\n name: 'editable-element-text',\n components: {\n ElementOutline,\n ProsemirrorEditor,\n },\n props: {\n elementInfo: {\n type: Object as PropType,\n required: true,\n },\n selectElement: {\n type: Function as PropType<(e: MouseEvent, element: PPTTextElement, canMove?: boolean) => void>,\n required: true,\n },\n contextmenus: {\n type: Function as PropType<() => ContextmenuItem[]>,\n },\n },\n setup(props) {\n const mainStore = useMainStore()\n const slidesStore = useSlidesStore()\n const { handleElementId, isScaling } = storeToRefs(mainStore)\n \n const { addHistorySnapshot } = useHistorySnapshot()\n\n const elementRef = ref()\n\n const shadow = computed(() => props.elementInfo.shadow)\n const { shadowStyle } = useElementShadow(shadow)\n\n const handleSelectElement = (e: MouseEvent, canMove = true) => {\n if (props.elementInfo.lock) return\n e.stopPropagation()\n\n props.selectElement(e, props.elementInfo, canMove)\n }\n\n // 监听文本元素的尺寸变化,当高度变化时,更新高度到vuex\n // 如果高度变化时正处在缩放操作中,则等待缩放操作结束后再更新\n const realHeightCache = ref(-1)\n\n watch(isScaling, () => {\n if (handleElementId.value !== props.elementInfo.id) return\n\n if (!isScaling.value && realHeightCache.value !== -1) {\n slidesStore.updateElement({\n id: props.elementInfo.id,\n props: { height: realHeightCache.value },\n })\n realHeightCache.value = -1\n }\n })\n\n const updateTextElementHeight = (entries: ResizeObserverEntry[]) => {\n const contentRect = entries[0].contentRect\n if (!elementRef.value) return\n\n const realHeight = contentRect.height\n\n if (props.elementInfo.height !== realHeight) {\n if (!isScaling.value) {\n slidesStore.updateElement({\n id: props.elementInfo.id,\n props: { height: realHeight },\n })\n }\n else realHeightCache.value = realHeight\n }\n }\n const resizeObserver = new ResizeObserver(updateTextElementHeight)\n\n onMounted(() => {\n if (elementRef.value) resizeObserver.observe(elementRef.value)\n })\n onUnmounted(() => {\n if (elementRef.value) resizeObserver.unobserve(elementRef.value)\n })\n\n const updateContent = (content: string) => {\n slidesStore.updateElement({\n id: props.elementInfo.id,\n props: { content },\n })\n \n addHistorySnapshot()\n }\n\n const checkEmptyText = () => {\n const pureText = props.elementInfo.content.replaceAll(/<[^>]+>/g, '')\n if (!pureText) slidesStore.deleteElement(props.elementInfo.id)\n }\n\n return {\n elementRef,\n shadowStyle,\n updateContent,\n handleSelectElement,\n checkEmptyText,\n }\n },\n})\n","import { render } from \"./index.vue?vue&type=template&id=67907c9d&scoped=true&ts=true\"\nimport script from \"./index.vue?vue&type=script&lang=ts\"\nexport * from \"./index.vue?vue&type=script&lang=ts\"\n\nimport \"./index.vue?vue&type=style&index=0&id=67907c9d&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-67907c9d\"]])\n\nexport default __exports__","import { resolveComponent as _resolveComponent, createVNode as _createVNode, openBlock as _openBlock, createElementBlock as _createElementBlock, createCommentVNode as _createCommentVNode, createElementVNode as _createElementVNode, createBlock as _createBlock, normalizeClass as _normalizeClass, normalizeStyle as _normalizeStyle, resolveDirective as _resolveDirective, withDirectives as _withDirectives, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-76089e77\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = [\"width\", \"height\"]\nconst _hoisted_2 = { key: 0 }\nconst _hoisted_3 = [\"transform\"]\nconst _hoisted_4 = [\"d\", \"fill\", \"stroke\", \"stroke-width\", \"stroke-dasharray\"]\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _component_GradientDefs = _resolveComponent(\"GradientDefs\")!\n const _component_ProsemirrorEditor = _resolveComponent(\"ProsemirrorEditor\")!\n const _directive_contextmenu = _resolveDirective(\"contextmenu\")!\n\n return (_openBlock(), _createElementBlock(\"div\", {\n class: _normalizeClass([\"editable-element-shape\", { 'lock': _ctx.elementInfo.lock }]),\n style: _normalizeStyle({\n top: _ctx.elementInfo.top + 'px',\n left: _ctx.elementInfo.left + 'px',\n width: _ctx.elementInfo.width + 'px',\n height: _ctx.elementInfo.height + 'px',\n })\n }, [\n _createElementVNode(\"div\", {\n class: \"rotate-wrapper\",\n style: _normalizeStyle({ transform: `rotate(${_ctx.elementInfo.rotate}deg)` })\n }, [\n _withDirectives((_openBlock(), _createElementBlock(\"div\", {\n class: \"element-content\",\n style: _normalizeStyle({\n opacity: _ctx.elementInfo.opacity,\n filter: _ctx.shadowStyle ? `drop-shadow(${_ctx.shadowStyle})` : '',\n transform: _ctx.flipStyle,\n color: _ctx.text.defaultColor,\n fontFamily: _ctx.text.defaultFontName,\n }),\n onMousedown: _cache[3] || (_cache[3] = $event => _ctx.handleSelectElement($event)),\n onDblclick: _cache[4] || (_cache[4] = ($event: any) => (_ctx.editable = true))\n }, [\n (_openBlock(), _createElementBlock(\"svg\", {\n overflow: \"visible\",\n width: _ctx.elementInfo.width,\n height: _ctx.elementInfo.height\n }, [\n (_ctx.elementInfo.gradient)\n ? (_openBlock(), _createElementBlock(\"defs\", _hoisted_2, [\n _createVNode(_component_GradientDefs, {\n id: `editabel-gradient-${_ctx.elementInfo.id}`,\n type: _ctx.elementInfo.gradient.type,\n color1: _ctx.elementInfo.gradient.color[0],\n color2: _ctx.elementInfo.gradient.color[1],\n rotate: _ctx.elementInfo.gradient.rotate\n }, null, 8, [\"id\", \"type\", \"color1\", \"color2\", \"rotate\"])\n ]))\n : _createCommentVNode(\"\", true),\n _createElementVNode(\"g\", {\n transform: `scale(${_ctx.elementInfo.width / _ctx.elementInfo.viewBox[0]}, ${_ctx.elementInfo.height / _ctx.elementInfo.viewBox[1]}) translate(0,0) matrix(1,0,0,1,0,0)`\n }, [\n _createElementVNode(\"path\", {\n class: \"shape-path\",\n \"vector-effect\": \"non-scaling-stroke\",\n \"stroke-linecap\": \"butt\",\n \"stroke-miterlimit\": \"8\",\n \"stroke-linejoin\": \"\",\n d: _ctx.elementInfo.path,\n fill: _ctx.elementInfo.gradient ? `url(#editabel-gradient-${_ctx.elementInfo.id})` : _ctx.elementInfo.fill,\n stroke: _ctx.outlineColor,\n \"stroke-width\": _ctx.outlineWidth,\n \"stroke-dasharray\": _ctx.outlineStyle === 'dashed' ? '10 6' : '0 0'\n }, null, 8, _hoisted_4)\n ], 8, _hoisted_3)\n ], 8, _hoisted_1)),\n _createElementVNode(\"div\", {\n class: _normalizeClass([\"shape-text\", [_ctx.text.align, { 'editable': _ctx.editable || _ctx.text.content }]])\n }, [\n (_ctx.editable || _ctx.text.content)\n ? (_openBlock(), _createBlock(_component_ProsemirrorEditor, {\n key: 0,\n elementId: _ctx.elementInfo.id,\n defaultColor: _ctx.text.defaultColor,\n defaultFontName: _ctx.text.defaultFontName,\n editable: !_ctx.elementInfo.lock,\n autoFocus: true,\n value: _ctx.text.content,\n onUpdate: _cache[0] || (_cache[0] = value => _ctx.updateText(value)),\n onBlur: _cache[1] || (_cache[1] = ($event: any) => (_ctx.checkEmptyText())),\n onMousedown: _cache[2] || (_cache[2] = $event => _ctx.handleSelectElement($event, false))\n }, null, 8, [\"elementId\", \"defaultColor\", \"defaultFontName\", \"editable\", \"value\"]))\n : _createCommentVNode(\"\", true)\n ], 2)\n ], 36)), [\n [_directive_contextmenu, _ctx.contextmenus]\n ])\n ], 4)\n ], 6))\n}","import { createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock, createCommentVNode as _createCommentVNode } from \"vue\"\n\nconst _hoisted_1 = [\"id\", \"gradientTransform\"]\nconst _hoisted_2 = [\"stop-color\"]\nconst _hoisted_3 = [\"stop-color\"]\nconst _hoisted_4 = [\"id\"]\nconst _hoisted_5 = [\"stop-color\"]\nconst _hoisted_6 = [\"stop-color\"]\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n return (_ctx.type === 'linear')\n ? (_openBlock(), _createElementBlock(\"linearGradient\", {\n key: 0,\n id: _ctx.id,\n x1: \"0%\",\n y1: \"0%\",\n x2: \"100%\",\n y2: \"0%\",\n gradientTransform: `rotate(${_ctx.rotate},0.5,0.5)`\n }, [\n _createElementVNode(\"stop\", {\n offset: \"0%\",\n \"stop-color\": _ctx.color1\n }, null, 8, _hoisted_2),\n _createElementVNode(\"stop\", {\n offset: \"100%\",\n \"stop-color\": _ctx.color2\n }, null, 8, _hoisted_3)\n ], 8, _hoisted_1))\n : (_openBlock(), _createElementBlock(\"radialGradient\", {\n key: 1,\n id: _ctx.id\n }, [\n _createElementVNode(\"stop\", {\n offset: \"0%\",\n \"stop-color\": _ctx.color1\n }, null, 8, _hoisted_5),\n _createElementVNode(\"stop\", {\n offset: \"100%\",\n \"stop-color\": _ctx.color2\n }, null, 8, _hoisted_6)\n ], 8, _hoisted_4))\n}","\r\nimport { defineComponent, PropType } from 'vue'\r\n\r\nexport default defineComponent({\r\n name: 'gradient-defs',\r\n props: {\r\n id: {\r\n type: String,\r\n required: true,\r\n },\r\n type: {\r\n type: String as PropType<'linear' | 'radial'>,\r\n },\r\n color1: {\r\n type: String,\r\n required: true,\r\n },\r\n color2: {\r\n type: String,\r\n required: true,\r\n },\r\n rotate: {\r\n type: Number,\r\n default: 0,\r\n },\r\n },\r\n})\r\n","import { render } from \"./GradientDefs.vue?vue&type=template&id=b787d5ea&ts=true\"\nimport script from \"./GradientDefs.vue?vue&type=script&lang=ts\"\nexport * from \"./GradientDefs.vue?vue&type=script&lang=ts\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render]])\n\nexport default __exports__","\nimport { computed, defineComponent, PropType, ref, watch } from 'vue'\nimport { storeToRefs } from 'pinia'\nimport { useMainStore, useSlidesStore } from '@/store'\nimport { PPTShapeElement, ShapeText } from '@/types/slides'\nimport { ContextmenuItem } from '@/components/Contextmenu/types'\nimport useElementOutline from '@/views/components/element/hooks/useElementOutline'\nimport useElementShadow from '@/views/components/element/hooks/useElementShadow'\nimport useElementFlip from '@/views/components/element/hooks/useElementFlip'\nimport useHistorySnapshot from '@/hooks/useHistorySnapshot'\n\nimport GradientDefs from './GradientDefs.vue'\nimport ProsemirrorEditor from '@/views/components/element/ProsemirrorEditor.vue'\n\nexport default defineComponent({\n name: 'editable-element-shape',\n components: {\n GradientDefs,\n ProsemirrorEditor,\n },\n props: {\n elementInfo: {\n type: Object as PropType,\n required: true,\n },\n selectElement: {\n type: Function as PropType<(e: MouseEvent, element: PPTShapeElement, canMove?: boolean) => void>,\n required: true,\n },\n contextmenus: {\n type: Function as PropType<() => ContextmenuItem[]>,\n },\n },\n setup(props) {\n const mainStore = useMainStore()\n const slidesStore = useSlidesStore()\n const { handleElementId } = storeToRefs(mainStore)\n\n const { addHistorySnapshot } = useHistorySnapshot()\n\n const handleSelectElement = (e: MouseEvent, canMove = true) => {\n if (props.elementInfo.lock) return\n e.stopPropagation()\n\n props.selectElement(e, props.elementInfo, canMove)\n }\n\n const outline = computed(() => props.elementInfo.outline)\n const { outlineWidth, outlineStyle, outlineColor } = useElementOutline(outline)\n \n const shadow = computed(() => props.elementInfo.shadow)\n const { shadowStyle } = useElementShadow(shadow)\n\n const flipH = computed(() => props.elementInfo.flipH)\n const flipV = computed(() => props.elementInfo.flipV)\n const { flipStyle } = useElementFlip(flipH, flipV)\n\n const editable = ref(false)\n \n watch(handleElementId, () => {\n if (handleElementId.value !== props.elementInfo.id) {\n if (editable.value) editable.value = false\n }\n })\n\n const text = computed(() => {\n const defaultText: ShapeText = {\n content: '',\n defaultFontName: '微软雅黑',\n defaultColor: '#000',\n align: 'middle',\n }\n if (!props.elementInfo.text) return defaultText\n\n return props.elementInfo.text\n })\n\n const updateText = (content: string) => {\n const _text = { ...text.value, content }\n slidesStore.updateElement({\n id: props.elementInfo.id, \n props: { text: _text },\n })\n \n addHistorySnapshot()\n }\n\n const checkEmptyText = () => {\n if (!props.elementInfo.text) return\n\n const pureText = props.elementInfo.text.content.replaceAll(/<[^>]+>/g, '')\n if (!pureText) {\n slidesStore.removeElementProps({ id: props.elementInfo.id, propName: 'text' })\n addHistorySnapshot()\n }\n }\n\n return {\n shadowStyle,\n outlineWidth,\n outlineStyle,\n outlineColor,\n flipStyle,\n editable,\n text,\n handleSelectElement,\n updateText,\n checkEmptyText,\n }\n },\n})\n","import { render } from \"./index.vue?vue&type=template&id=76089e77&scoped=true&ts=true\"\nimport script from \"./index.vue?vue&type=script&lang=ts\"\nexport * from \"./index.vue?vue&type=script&lang=ts\"\n\nimport \"./index.vue?vue&type=style&index=0&id=76089e77&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-76089e77\"]])\n\nexport default __exports__","import { resolveComponent as _resolveComponent, openBlock as _openBlock, createBlock as _createBlock, createCommentVNode as _createCommentVNode, createElementVNode as _createElementVNode, resolveDirective as _resolveDirective, withDirectives as _withDirectives, createElementBlock as _createElementBlock, normalizeStyle as _normalizeStyle, normalizeClass as _normalizeClass, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-603a705d\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = [\"width\", \"height\"]\nconst _hoisted_2 = [\"d\", \"stroke\", \"stroke-width\", \"stroke-dasharray\", \"marker-start\", \"marker-end\"]\nconst _hoisted_3 = [\"d\"]\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _component_LinePointMarker = _resolveComponent(\"LinePointMarker\")!\n const _directive_contextmenu = _resolveDirective(\"contextmenu\")!\n\n return (_openBlock(), _createElementBlock(\"div\", {\n class: _normalizeClass([\"editable-element-shape\", { 'lock': _ctx.elementInfo.lock }]),\n style: _normalizeStyle({\n top: _ctx.elementInfo.top + 'px',\n left: _ctx.elementInfo.left + 'px',\n })\n }, [\n _createElementVNode(\"div\", {\n class: \"element-content\",\n style: _normalizeStyle({ filter: _ctx.shadowStyle ? `drop-shadow(${_ctx.shadowStyle})` : '' }),\n onMousedown: _cache[0] || (_cache[0] = $event => _ctx.handleSelectElement($event))\n }, [\n (_openBlock(), _createElementBlock(\"svg\", {\n overflow: \"visible\",\n width: _ctx.svgWidth,\n height: _ctx.svgHeight\n }, [\n _createElementVNode(\"defs\", null, [\n (_ctx.elementInfo.points[0])\n ? (_openBlock(), _createBlock(_component_LinePointMarker, {\n key: 0,\n id: _ctx.elementInfo.id,\n position: \"start\",\n type: _ctx.elementInfo.points[0],\n color: _ctx.elementInfo.color,\n baseSize: _ctx.elementInfo.width\n }, null, 8, [\"id\", \"type\", \"color\", \"baseSize\"]))\n : _createCommentVNode(\"\", true),\n (_ctx.elementInfo.points[1])\n ? (_openBlock(), _createBlock(_component_LinePointMarker, {\n key: 1,\n id: _ctx.elementInfo.id,\n position: \"end\",\n type: _ctx.elementInfo.points[1],\n color: _ctx.elementInfo.color,\n baseSize: _ctx.elementInfo.width\n }, null, 8, [\"id\", \"type\", \"color\", \"baseSize\"]))\n : _createCommentVNode(\"\", true)\n ]),\n _createElementVNode(\"path\", {\n class: \"line-point\",\n d: _ctx.path,\n stroke: _ctx.elementInfo.color,\n \"stroke-width\": _ctx.elementInfo.width,\n \"stroke-dasharray\": _ctx.lineDashArray,\n fill: \"none\",\n \"stroke-linecap\": \"\",\n \"stroke-linejoin\": \"\",\n \"stroke-miterlimit\": \"\",\n \"marker-start\": _ctx.elementInfo.points[0] ? `url(#${_ctx.elementInfo.id}-${_ctx.elementInfo.points[0]}-start)` : '',\n \"marker-end\": _ctx.elementInfo.points[1] ? `url(#${_ctx.elementInfo.id}-${_ctx.elementInfo.points[1]}-end)` : ''\n }, null, 8, _hoisted_2),\n _withDirectives(_createElementVNode(\"path\", {\n class: \"line-path\",\n d: _ctx.path,\n stroke: \"transparent\",\n \"stroke-width\": \"20\",\n fill: \"none\"\n }, null, 8, _hoisted_3), [\n [_directive_contextmenu, _ctx.contextmenus]\n ])\n ], 8, _hoisted_1))\n ], 36)\n ], 6))\n}","import { createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from \"vue\"\n\nconst _hoisted_1 = [\"id\", \"markerWidth\", \"markerHeight\", \"refX\", \"refY\"]\nconst _hoisted_2 = [\"d\", \"fill\", \"transform\"]\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n return (_openBlock(), _createElementBlock(\"marker\", {\n id: `${_ctx.id}-${_ctx.type}-${_ctx.position}`,\n markerUnits: \"userSpaceOnUse\",\n orient: \"auto\",\n markerWidth: _ctx.size * 3,\n markerHeight: _ctx.size * 3,\n refX: _ctx.size * 1.5,\n refY: _ctx.size * 1.5\n }, [\n _createElementVNode(\"path\", {\n d: _ctx.path,\n fill: _ctx.color,\n transform: `scale(${_ctx.size * 0.3}, ${_ctx.size * 0.3}) rotate(${_ctx.rotate}, 5, 5)`\n }, null, 8, _hoisted_2)\n ], 8, _hoisted_1))\n}","\nimport { computed, defineComponent, PropType } from 'vue'\n\nconst pathMap = {\n dot: 'm0 5a5 5 0 1 0 10 0a5 5 0 1 0 -10 0z',\n arrow: 'M0,0 L10,5 0,10 Z',\n}\nconst rotateMap = {\n 'arrow-start': 180,\n 'arrow-end': 0,\n}\n\nexport default defineComponent({\n name: 'line-point-marker',\n props: {\n id: {\n type: String,\n required: true,\n },\n position: {\n type: String as PropType<'start' | 'end'>,\n required: true,\n },\n type: {\n type: String as PropType<'dot' | 'arrow'>,\n required: true,\n },\n color: {\n type: String,\n },\n baseSize: {\n type: Number,\n required: true,\n },\n },\n setup(props) {\n const path = computed(() => pathMap[props.type])\n const rotate = computed(() => rotateMap[`${props.type}-${props.position}`] || 0)\n const size = computed(() => props.baseSize < 2 ? 2 : props.baseSize)\n\n return {\n path,\n rotate,\n size,\n }\n },\n})\n","import { render } from \"./LinePointMarker.vue?vue&type=template&id=62085f5a&ts=true\"\nimport script from \"./LinePointMarker.vue?vue&type=script&lang=ts\"\nexport * from \"./LinePointMarker.vue?vue&type=script&lang=ts\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render]])\n\nexport default __exports__","\nimport { computed, defineComponent, PropType } from 'vue'\nimport { PPTLineElement } from '@/types/slides'\nimport { getLineElementPath } from '@/utils/element'\nimport { ContextmenuItem } from '@/components/Contextmenu/types'\nimport useElementShadow from '@/views/components/element/hooks/useElementShadow'\n\nimport LinePointMarker from './LinePointMarker.vue'\n\nexport default defineComponent({\n name: 'editable-element-shape',\n components: {\n LinePointMarker,\n },\n props: {\n elementInfo: {\n type: Object as PropType,\n required: true,\n },\n selectElement: {\n type: Function as PropType<(e: MouseEvent, element: PPTLineElement, canMove?: boolean) => void>,\n required: true,\n },\n contextmenus: {\n type: Function as PropType<() => ContextmenuItem[]>,\n },\n },\n setup(props) {\n const handleSelectElement = (e: MouseEvent) => {\n if (props.elementInfo.lock) return\n e.stopPropagation()\n\n props.selectElement(e, props.elementInfo)\n }\n \n const shadow = computed(() => props.elementInfo.shadow)\n const { shadowStyle } = useElementShadow(shadow)\n\n const svgWidth = computed(() => {\n const width = Math.abs(props.elementInfo.start[0] - props.elementInfo.end[0])\n return width < 24 ? 24 : width\n })\n const svgHeight = computed(() => {\n const height = Math.abs(props.elementInfo.start[1] - props.elementInfo.end[1])\n return height < 24 ? 24 : height\n })\n\n const lineDashArray = computed(() => props.elementInfo.style === 'dashed' ? '10 6' : '0 0')\n\n const path = computed(() => {\n return getLineElementPath(props.elementInfo)\n })\n\n return {\n handleSelectElement,\n shadowStyle,\n svgWidth,\n svgHeight,\n lineDashArray,\n path,\n }\n },\n})\n","import { render } from \"./index.vue?vue&type=template&id=603a705d&scoped=true&ts=true\"\nimport script from \"./index.vue?vue&type=script&lang=ts\"\nexport * from \"./index.vue?vue&type=script&lang=ts\"\n\nimport \"./index.vue?vue&type=style&index=0&id=603a705d&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-603a705d\"]])\n\nexport default __exports__","import { resolveComponent as _resolveComponent, createVNode as _createVNode, normalizeStyle as _normalizeStyle, resolveDirective as _resolveDirective, openBlock as _openBlock, createElementBlock as _createElementBlock, withDirectives as _withDirectives, createElementVNode as _createElementVNode, normalizeClass as _normalizeClass } from \"vue\"\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _component_ElementOutline = _resolveComponent(\"ElementOutline\")!\n const _component_Chart = _resolveComponent(\"Chart\")!\n const _directive_contextmenu = _resolveDirective(\"contextmenu\")!\n\n return (_openBlock(), _createElementBlock(\"div\", {\n class: _normalizeClass([\"editable-element-chart\", { 'lock': _ctx.elementInfo.lock }]),\n style: _normalizeStyle({\r\n top: _ctx.elementInfo.top + 'px',\r\n left: _ctx.elementInfo.left + 'px',\r\n width: _ctx.elementInfo.width + 'px',\r\n height: _ctx.elementInfo.height + 'px',\r\n })\n }, [\n _createElementVNode(\"div\", {\n class: \"rotate-wrapper\",\n style: _normalizeStyle({ transform: `rotate(${_ctx.elementInfo.rotate}deg)` })\n }, [\n _withDirectives((_openBlock(), _createElementBlock(\"div\", {\n class: \"element-content\",\n style: _normalizeStyle({\r\n backgroundColor: _ctx.elementInfo.fill,\r\n }),\n onMousedown: _cache[0] || (_cache[0] = $event => _ctx.handleSelectElement($event)),\n onDblclick: _cache[1] || (_cache[1] = ($event: any) => (_ctx.openDataEditor()))\n }, [\n _createVNode(_component_ElementOutline, {\n width: _ctx.elementInfo.width,\n height: _ctx.elementInfo.height,\n outline: _ctx.elementInfo.outline\n }, null, 8, [\"width\", \"height\", \"outline\"]),\n _createVNode(_component_Chart, {\n width: _ctx.elementInfo.width,\n height: _ctx.elementInfo.height,\n type: _ctx.elementInfo.chartType,\n data: _ctx.elementInfo.data,\n options: _ctx.elementInfo.options,\n themeColor: _ctx.elementInfo.themeColor,\n gridColor: _ctx.elementInfo.gridColor,\n legends: _ctx.elementInfo.data.legends,\n legend: _ctx.elementInfo.legend || ''\n }, null, 8, [\"width\", \"height\", \"type\", \"data\", \"options\", \"themeColor\", \"gridColor\", \"legends\", \"legend\"])\n ], 36)), [\n [_directive_contextmenu, _ctx.contextmenus]\n ])\n ], 4)\n ], 6))\n}","import { normalizeStyle as _normalizeStyle, createElementVNode as _createElementVNode, renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, toDisplayString as _toDisplayString, createTextVNode as _createTextVNode, createCommentVNode as _createCommentVNode } from \"vue\"\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n return (_openBlock(), _createElementBlock(\"div\", {\n class: \"chart\",\n style: _normalizeStyle({ flexDirection: _ctx.legend === 'top' ? 'column-reverse' : 'column' })\n }, [\n _createElementVNode(\"div\", {\n class: \"chart-content\",\n ref: \"chartRef\",\n style: _normalizeStyle({\n width: _ctx.width + 'px',\n height: _ctx.chartHeight + 'px',\n transform: `scale(${1 / _ctx.slideScale})`,\n })\n }, null, 4),\n (_ctx.legend)\n ? (_openBlock(), _createElementBlock(\"div\", {\n key: 0,\n class: \"legends\",\n style: _normalizeStyle({ transform: `scale(${1 / _ctx.slideScale})` })\n }, [\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.legends, (legend, index) => {\n return (_openBlock(), _createElementBlock(\"div\", {\n class: \"legend\",\n key: index,\n style: _normalizeStyle({ color: _ctx.gridColor })\n }, [\n _createElementVNode(\"div\", {\n class: \"block\",\n style: _normalizeStyle({ backgroundColor: _ctx.themeColors[index] })\n }, null, 4),\n _createTextVNode(\" \" + _toDisplayString(legend), 1)\n ], 4))\n }), 128))\n ], 4))\n : _createCommentVNode(\"\", true)\n ], 4))\n}","\nimport { computed, defineComponent, inject, onMounted, PropType, ref, watch } from 'vue'\nimport { upperFirst } from 'lodash'\nimport tinycolor from 'tinycolor2'\nimport Chartist, {\n IChartistLineChart,\n IChartistBarChart,\n IChartistPieChart,\n} from 'chartist'\nimport { ChartData, ChartOptions, ChartType } from '@/types/slides'\nimport { injectKeySlideScale } from '@/types/injectKey'\n\nimport 'chartist/dist/scss/chartist.scss'\n\nexport default defineComponent({\n name: 'chart',\n props: {\n width: {\n type: Number,\n required: true,\n },\n height: {\n type: Number,\n required: true,\n },\n type: {\n type: String as PropType,\n required: true,\n },\n data: {\n type: Object as PropType,\n required: true,\n },\n options: {\n type: Object as PropType,\n },\n themeColor: {\n type: Array as PropType,\n required: true,\n },\n legends: {\n type: Array as PropType,\n required: true,\n },\n gridColor: {\n type: String,\n },\n legend: {\n type: String as PropType<'' | 'top' | 'bottom'>,\n },\n },\n setup(props) {\n const chartRef = ref()\n const slideScale = inject(injectKeySlideScale) || ref(1)\n\n let chart: IChartistLineChart | IChartistBarChart | IChartistPieChart | undefined\n\n const chartHeight = computed(() => {\n if (props.legend) return props.height - 20\n return props.height\n })\n\n const getDataAndOptions = () => {\n const propsOptopns = props.options || {}\n const options = {\n ...propsOptopns,\n width: props.width * slideScale.value,\n height: chartHeight.value * slideScale.value,\n }\n const data = props.type === 'pie' ? { ...props.data, series: props.data.series[0] } : props.data\n return { data, options }\n }\n\n const renderChart = () => {\n if (!chartRef.value) return\n\n const type = upperFirst(props.type)\n const { data, options } = getDataAndOptions()\n chart = new Chartist[type](chartRef.value, data, options)\n }\n\n const updateChart = () => {\n if (!chart) {\n renderChart()\n return\n }\n const { data, options } = getDataAndOptions()\n chart.update(data, options)\n }\n\n watch([\n () => props.width,\n () => props.height,\n () => props.data,\n () => props.options,\n slideScale,\n ], updateChart)\n\n onMounted(renderChart)\n\n const themeColors = computed(() => {\n let colors: string[] = []\n if (props.themeColor.length >= 10) colors = props.themeColor\n else if (props.themeColor.length === 1) colors = tinycolor(props.themeColor[0]).analogous(10).map(color => color.toHexString())\n else {\n const len = props.themeColor.length\n const supplement = tinycolor(props.themeColor[len - 1]).analogous(10 + 1 - len).map(color => color.toHexString())\n colors = [...props.themeColor.slice(0, len - 1), ...supplement]\n }\n return colors\n })\n\n // 更新主题配色:\n // 如果当前所设置的主题色数小于10,剩余部分获取最后一个主题色的相近颜色作为配色\n const updateTheme = () => {\n if (!chartRef.value) return\n\n for (let i = 0; i < 10; i++) {\n chartRef.value.style.setProperty(`--theme-color-${i + 1}`, themeColors.value[i])\n }\n }\n\n watch(themeColors, updateTheme)\n onMounted(updateTheme)\n\n // 更新网格颜色,包括坐标的文字部分\n const updateGridColor = () => {\n if (!chartRef.value) return\n if (props.gridColor) chartRef.value.style.setProperty(`--grid-color`, props.gridColor)\n }\n\n watch(() => props.gridColor, updateGridColor)\n onMounted(updateGridColor)\n\n return {\n chartHeight,\n themeColors,\n slideScale,\n chartRef,\n }\n },\n})\n","import { render } from \"./Chart.vue?vue&type=template&id=79ce410e&scoped=true&ts=true\"\nimport script from \"./Chart.vue?vue&type=script&lang=ts\"\nexport * from \"./Chart.vue?vue&type=script&lang=ts\"\n\nimport \"./Chart.vue?vue&type=style&index=0&id=79ce410e&lang=scss&scoped=true\"\nimport \"./Chart.vue?vue&type=style&index=1&id=79ce410e&lang=scss\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-79ce410e\"]])\n\nexport default __exports__","\r\nimport { defineComponent, PropType } from 'vue'\r\nimport { PPTChartElement } from '@/types/slides'\r\nimport { ContextmenuItem } from '@/components/Contextmenu/types'\r\nimport emitter, { EmitterEvents } from '@/utils/emitter'\r\n\r\nimport ElementOutline from '@/views/components/element/ElementOutline.vue'\r\nimport Chart from './Chart.vue'\r\n\r\nexport default defineComponent({\r\n name: 'editable-element-chart',\r\n components: {\r\n ElementOutline,\r\n Chart,\r\n },\r\n props: {\r\n elementInfo: {\r\n type: Object as PropType,\r\n required: true,\r\n },\r\n selectElement: {\r\n type: Function as PropType<(e: MouseEvent, element: PPTChartElement, canMove?: boolean) => void>,\r\n required: true,\r\n },\r\n contextmenus: {\r\n type: Function as PropType<() => ContextmenuItem[]>,\r\n },\r\n },\r\n setup(props) {\r\n const handleSelectElement = (e: MouseEvent) => {\r\n if (props.elementInfo.lock) return\r\n e.stopPropagation()\r\n\r\n props.selectElement(e, props.elementInfo)\r\n }\r\n\r\n const openDataEditor = () => {\r\n emitter.emit(EmitterEvents.OPEN_CHART_DATA_EDITOR)\r\n }\r\n\r\n return {\r\n handleSelectElement,\r\n openDataEditor,\r\n }\r\n },\r\n})\r\n","import { render } from \"./index.vue?vue&type=template&id=c09631b6&scoped=true&ts=true\"\nimport script from \"./index.vue?vue&type=script&lang=ts\"\nexport * from \"./index.vue?vue&type=script&lang=ts\"\n\nimport \"./index.vue?vue&type=style&index=0&id=c09631b6&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-c09631b6\"]])\n\nexport default __exports__","import { resolveComponent as _resolveComponent, withModifiers as _withModifiers, createVNode as _createVNode, normalizeStyle as _normalizeStyle, createElementVNode as _createElementVNode, normalizeClass as _normalizeClass, openBlock as _openBlock, createElementBlock as _createElementBlock, createCommentVNode as _createCommentVNode, resolveDirective as _resolveDirective, withDirectives as _withDirectives, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-426704b7\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = { class: \"element-content\" }\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _component_EditableTable = _resolveComponent(\"EditableTable\")!\n const _directive_contextmenu = _resolveDirective(\"contextmenu\")!\n\n return (_openBlock(), _createElementBlock(\"div\", {\n class: _normalizeClass([\"editable-element-table\", { 'lock': _ctx.elementInfo.lock }]),\n ref: \"elementRef\",\n style: _normalizeStyle({\n top: _ctx.elementInfo.top + 'px',\n left: _ctx.elementInfo.left + 'px',\n width: _ctx.elementInfo.width + 'px',\n })\n }, [\n _createElementVNode(\"div\", {\n class: \"rotate-wrapper\",\n style: _normalizeStyle({ transform: `rotate(${_ctx.elementInfo.rotate}deg)` })\n }, [\n _withDirectives((_openBlock(), _createElementBlock(\"div\", _hoisted_1, [\n _createVNode(_component_EditableTable, {\n onMousedown: _cache[0] || (_cache[0] = _withModifiers(() => {}, [\"stop\"])),\n data: _ctx.elementInfo.data,\n width: _ctx.elementInfo.width,\n colWidths: _ctx.elementInfo.colWidths,\n outline: _ctx.elementInfo.outline,\n theme: _ctx.elementInfo.theme,\n editable: _ctx.editable,\n onChange: _cache[1] || (_cache[1] = data => _ctx.updateTableCells(data)),\n onChangeColWidths: _cache[2] || (_cache[2] = widths => _ctx.updateColWidths(widths)),\n onChangeSelectedCells: _cache[3] || (_cache[3] = cells => _ctx.updateSelectedCells(cells))\n }, null, 8, [\"data\", \"width\", \"colWidths\", \"outline\", \"theme\", \"editable\"]),\n (!_ctx.editable || _ctx.elementInfo.lock)\n ? (_openBlock(), _createElementBlock(\"div\", {\n key: 0,\n class: _normalizeClass([\"table-mask\", { 'lock': _ctx.elementInfo.lock }]),\n onDblclick: _cache[4] || (_cache[4] = ($event: any) => (_ctx.startEdit())),\n onMousedown: _cache[5] || (_cache[5] = $event => _ctx.handleSelectElement($event))\n }, [\n _createElementVNode(\"div\", {\n class: \"mask-tip\",\n style: _normalizeStyle({ transform: `scale(${ 1 / _ctx.canvasScale })` })\n }, \"双击编辑\", 4)\n ], 34))\n : _createCommentVNode(\"\", true)\n ])), [\n [_directive_contextmenu, _ctx.contextmenus]\n ])\n ], 4)\n ], 6))\n}","import { renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, normalizeStyle as _normalizeStyle, createCommentVNode as _createCommentVNode, createElementVNode as _createElementVNode, resolveComponent as _resolveComponent, normalizeClass as _normalizeClass, createBlock as _createBlock, vShow as _vShow, resolveDirective as _resolveDirective, withDirectives as _withDirectives, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-3ee7b416\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = {\n key: 0,\n class: \"handler\"\n}\nconst _hoisted_2 = [\"onMousedown\"]\nconst _hoisted_3 = [\"width\"]\nconst _hoisted_4 = [\"rowspan\", \"colspan\", \"data-cell-index\", \"onMousedown\", \"onMouseenter\"]\nconst _hoisted_5 = [\"innerHTML\"]\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _component_CustomTextarea = _resolveComponent(\"CustomTextarea\")!\n const _directive_contextmenu = _resolveDirective(\"contextmenu\")!\n\n return (_openBlock(), _createElementBlock(\"div\", {\n class: \"editable-table\",\n style: _normalizeStyle({ width: _ctx.totalWidth + 'px' })\n }, [\n (_ctx.editable)\n ? (_openBlock(), _createElementBlock(\"div\", _hoisted_1, [\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.dragLinePosition, (pos, index) => {\n return (_openBlock(), _createElementBlock(\"div\", {\n class: \"drag-line\",\n key: index,\n style: _normalizeStyle({ left: pos + 'px' }),\n onMousedown: $event => _ctx.handleMousedownColHandler($event, index)\n }, null, 44, _hoisted_2))\n }), 128))\n ]))\n : _createCommentVNode(\"\", true),\n _createElementVNode(\"table\", {\n class: _normalizeClass({\n 'theme': _ctx.theme,\n 'row-header': _ctx.theme?.rowHeader,\n 'row-footer': _ctx.theme?.rowFooter,\n 'col-header': _ctx.theme?.colHeader,\n 'col-footer': _ctx.theme?.colFooter,\n }),\n style: _normalizeStyle(`--themeColor: ${_ctx.theme?.color}; --subThemeColor1: ${_ctx.subThemeColor[0]}; --subThemeColor2: ${_ctx.subThemeColor[1]}`)\n }, [\n _createElementVNode(\"colgroup\", null, [\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.colSizeList, (width, index) => {\n return (_openBlock(), _createElementBlock(\"col\", {\n span: \"1\",\n key: index,\n width: width\n }, null, 8, _hoisted_3))\n }), 128))\n ]),\n _createElementVNode(\"tbody\", null, [\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.tableCells, (rowCells, rowIndex) => {\n return (_openBlock(), _createElementBlock(\"tr\", { key: rowIndex }, [\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(rowCells, (cell, colIndex) => {\n return _withDirectives((_openBlock(), _createElementBlock(\"td\", {\n class: _normalizeClass([\"cell\", {\n 'selected': _ctx.selectedCells.includes(`${rowIndex}_${colIndex}`) && _ctx.selectedCells.length > 1,\n 'active': _ctx.activedCell === `${rowIndex}_${colIndex}`,\n }]),\n style: _normalizeStyle({\n borderStyle: _ctx.outline.style,\n borderColor: _ctx.outline.color,\n borderWidth: _ctx.outline.width + 'px',\n ..._ctx.getTextStyle(cell.style),\n }),\n key: cell.id,\n rowspan: cell.rowspan,\n colspan: cell.colspan,\n \"data-cell-index\": `${rowIndex}_${colIndex}`,\n onMousedown: $event => _ctx.handleCellMousedown($event, rowIndex, colIndex),\n onMouseenter: ($event: any) => (_ctx.handleCellMouseenter(rowIndex, colIndex))\n }, [\n (_ctx.activedCell === `${rowIndex}_${colIndex}`)\n ? (_openBlock(), _createBlock(_component_CustomTextarea, {\n key: 0,\n class: _normalizeClass([\"cell-text\", { 'active': _ctx.activedCell === `${rowIndex}_${colIndex}` }]),\n contenteditable: \"plaintext-only\",\n value: cell.text,\n onUpdateValue: value => _ctx.handleInput(value, rowIndex, colIndex),\n onInsertExcelData: value => _ctx.insertExcelData(value, rowIndex, colIndex)\n }, null, 8, [\"class\", \"value\", \"onUpdateValue\", \"onInsertExcelData\"]))\n : (_openBlock(), _createElementBlock(\"div\", {\n key: 1,\n class: \"cell-text\",\n innerHTML: _ctx.formatText(cell.text)\n }, null, 8, _hoisted_5))\n ], 46, _hoisted_4)), [\n [_vShow, !_ctx.hideCells.includes(`${rowIndex}_${colIndex}`)],\n [_directive_contextmenu, el => _ctx.contextmenus(el)]\n ])\n }), 128))\n ]))\n }), 128))\n ])\n ], 6)\n ], 4))\n}","import { TableCellStyle } from '@/types/slides'\n\n/**\n * 计算单元格文本样式\n * @param style 单元格文本样式原数据\n */\nexport const getTextStyle = (style?: TableCellStyle) => {\n if (!style) return {}\n const {\n bold,\n em,\n underline,\n strikethrough,\n color,\n backcolor,\n fontsize,\n fontname,\n align,\n } = style\n\n let textDecoration = `${underline ? 'underline' : ''} ${strikethrough ? 'line-through' : ''}`\n if (textDecoration === ' ') textDecoration = 'none'\n \n return {\n fontWeight: bold ? 'bold' : 'normal',\n fontStyle: em ? 'italic' : 'normal',\n textDecoration,\n color: color || '#000',\n backgroundColor: backcolor || '',\n fontSize: fontsize || '14px',\n fontFamily: fontname || '微软雅黑',\n textAlign: align || 'left',\n }\n}\n\nexport const formatText = (text: string) => {\n return text.replace(/\\n/g, '').replace(/ /g, ' ')\n}","import { computed, Ref } from 'vue'\nimport { TableCell } from '@/types/slides'\n\n// 计算无效的单元格位置(被合并的单元格位置)集合\n\nexport default (cells: Ref) => {\n const hideCells = computed(() => {\n const hideCells = []\n \n for (let i = 0; i < cells.value.length; i++) {\n const rowCells = cells.value[i]\n\n for (let j = 0; j < rowCells.length; j++) {\n const cell = rowCells[j]\n \n if (cell.colspan > 1 || cell.rowspan > 1) {\n for (let row = i; row < i + cell.rowspan; row++) {\n for (let col = row === i ? j + 1 : j; col < j + cell.colspan; col++) {\n hideCells.push(`${row}_${col}`)\n }\n }\n }\n }\n }\n return hideCells\n })\n\n return {\n hideCells,\n }\n}","import { ref, Ref, watch } from 'vue'\nimport { TableTheme } from '@/types/slides'\nimport { getTableSubThemeColor } from '@/utils/element'\n\n// 通过表格的主题色计算辅助颜色\n\nexport default (theme: Ref) => {\n const subThemeColor = ref(['', ''])\n watch(() => theme.value, () => {\n if (theme.value) {\n subThemeColor.value = getTableSubThemeColor(theme.value.color)\n }\n }, { immediate: true })\n\n return {\n subThemeColor,\n }\n}","import { openBlock as _openBlock, createElementBlock as _createElementBlock, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-59f468e0\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = [\"contenteditable\", \"innerHTML\"]\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n return (_openBlock(), _createElementBlock(\"div\", {\n class: \"custom-textarea\",\n ref: \"textareaRef\",\n contenteditable: _ctx.contenteditable,\n onFocus: _cache[0] || (_cache[0] = \n//@ts-ignore\n(...args) => (_ctx.handleFocus && _ctx.handleFocus(...args))),\n onBlur: _cache[1] || (_cache[1] = \n//@ts-ignore\n(...args) => (_ctx.handleBlur && _ctx.handleBlur(...args))),\n onInput: _cache[2] || (_cache[2] = $event => _ctx.handleInput($event)),\n innerHTML: _ctx.text\n }, null, 40, _hoisted_1))\n}","\nimport { defineComponent, onUnmounted, ref, watch } from 'vue'\nimport { pasteCustomClipboardString, pasteExcelClipboardString } from '@/utils/clipboard'\n\nexport default defineComponent({\n name: 'custom-textarea',\n emits: ['updateValue', 'insertExcelData'],\n props: {\n value: {\n type: String,\n default: '',\n },\n contenteditable: {\n type: [Boolean, String],\n default: false,\n },\n },\n setup(props, { emit }) {\n const textareaRef = ref()\n const text = ref('')\n const isFocus = ref(false)\n\n // 自定义v-modal,同步数据\n // 当文本框聚焦时,不执行数据同步\n watch(() => props.value, () => {\n if (isFocus.value) return\n text.value = props.value\n if (textareaRef.value) textareaRef.value.innerHTML = props.value\n }, { immediate: true })\n\n const handleInput = () => {\n if (!textareaRef.value) return\n const text = textareaRef.value.innerHTML\n emit('updateValue', text)\n }\n\n // 聚焦时更新焦点标记,并监听粘贴事件\n const handleFocus = () => {\n isFocus.value = true\n\n if (!textareaRef.value) return\n textareaRef.value.onpaste = (e: ClipboardEvent) => {\n e.preventDefault()\n if (!e.clipboardData) return\n\n const clipboardDataFirstItem = e.clipboardData.items[0]\n\n if (clipboardDataFirstItem && clipboardDataFirstItem.kind === 'string' && clipboardDataFirstItem.type === 'text/plain') {\n clipboardDataFirstItem.getAsString(text => {\n const clipboardData = pasteCustomClipboardString(text)\n if (typeof clipboardData === 'object') return\n \n const excelData = pasteExcelClipboardString(text)\n if (excelData) {\n emit('insertExcelData', excelData)\n if (textareaRef.value) textareaRef.value.innerHTML = excelData[0][0]\n return\n }\n\n emit('updateValue', text)\n document.execCommand('insertText', false, text)\n })\n }\n }\n }\n\n // 失焦时更新焦点标记,清除粘贴事件监听\n const handleBlur = () => {\n isFocus.value = false\n if (textareaRef.value) textareaRef.value.onpaste = null\n }\n\n // 清除粘贴事件监听\n onUnmounted(() => {\n if (textareaRef.value) textareaRef.value.onpaste = null\n })\n\n return {\n textareaRef,\n handleFocus,\n handleInput,\n handleBlur,\n text,\n }\n },\n})\n","import { render } from \"./CustomTextarea.vue?vue&type=template&id=59f468e0&scoped=true&ts=true\"\nimport script from \"./CustomTextarea.vue?vue&type=script&lang=ts\"\nexport * from \"./CustomTextarea.vue?vue&type=script&lang=ts\"\n\nimport \"./CustomTextarea.vue?vue&type=style&index=0&id=59f468e0&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-59f468e0\"]])\n\nexport default __exports__","\nimport { computed, defineComponent, nextTick, onMounted, onUnmounted, PropType, ref, watch } from 'vue'\nimport { debounce, isEqual } from 'lodash'\nimport { storeToRefs } from 'pinia'\nimport { nanoid } from 'nanoid'\nimport { useMainStore } from '@/store'\nimport { PPTElementOutline, TableCell, TableTheme } from '@/types/slides'\nimport { ContextmenuItem } from '@/components/Contextmenu/types'\nimport { KEYS } from '@/configs/hotkey'\nimport { getTextStyle, formatText } from './utils'\nimport useHideCells from './useHideCells'\nimport useSubThemeColor from './useSubThemeColor'\n\nimport CustomTextarea from './CustomTextarea.vue'\n\nexport default defineComponent({\n name: 'editable-table',\n emits: ['change', 'changeColWidths', 'changeSelectedCells'],\n components: {\n CustomTextarea,\n },\n props: {\n data: {\n type: Array as PropType,\n required: true,\n },\n width: {\n type: Number,\n required: true,\n },\n colWidths: {\n type: Array as PropType,\n required: true,\n },\n outline: {\n type: Object as PropType,\n required: true,\n },\n theme: {\n type: Object as PropType,\n },\n editable: {\n type: Boolean,\n default: true,\n },\n },\n setup(props, { emit }) {\n const { canvasScale } = storeToRefs(useMainStore())\n \n const isStartSelect = ref(false)\n const startCell = ref([])\n const endCell = ref([])\n\n const tableCells = computed({\n get() {\n return props.data\n },\n set(newData) {\n emit('change', newData)\n },\n })\n\n // 主题辅助色\n const theme = computed(() => props.theme)\n const { subThemeColor } = useSubThemeColor(theme)\n\n // 计算表格每一列的列宽和总宽度\n const colSizeList = ref([])\n const totalWidth = computed(() => colSizeList.value.reduce((a, b) => a + b))\n watch([\n () => props.colWidths,\n () => props.width,\n ], () => {\n colSizeList.value = props.colWidths.map(item => item * props.width)\n }, { immediate: true })\n \n // 清除全部单元格的选中状态\n // 表格处于不可编辑状态时也需要清除\n const removeSelectedCells = () => {\n startCell.value = []\n endCell.value = []\n }\n\n watch(() => props.editable, () => {\n if (!props.editable) removeSelectedCells()\n })\n\n // 用于拖拽列宽的操作节点位置\n const dragLinePosition = computed(() => {\n const dragLinePosition: number[] = []\n for (let i = 1; i < colSizeList.value.length + 1; i++) {\n const pos = colSizeList.value.slice(0, i).reduce((a, b) => (a + b))\n dragLinePosition.push(pos)\n }\n return dragLinePosition\n })\n\n // 无效的单元格位置(被合并的单元格位置)集合\n const cells = computed(() => props.data)\n const { hideCells } = useHideCells(cells)\n\n // 当前选中的单元格集合\n const selectedCells = computed(() => {\n if (!startCell.value.length) return []\n const [startX, startY] = startCell.value\n\n if (!endCell.value.length) return [`${startX}_${startY}`]\n const [endX, endY] = endCell.value\n\n if (startX === endX && startY === endY) return [`${startX}_${startY}`]\n\n const selectedCells = []\n\n const minX = Math.min(startX, endX)\n const minY = Math.min(startY, endY)\n const maxX = Math.max(startX, endX)\n const maxY = Math.max(startY, endY)\n\n for (let i = 0; i < tableCells.value.length; i++) {\n const rowCells = tableCells.value[i]\n for (let j = 0; j < rowCells.length; j++) {\n if (i >= minX && i <= maxX && j >= minY && j <= maxY) selectedCells.push(`${i}_${j}`)\n }\n }\n return selectedCells\n })\n\n watch(selectedCells, (value, oldValue) => {\n if (isEqual(value, oldValue)) return\n emit('changeSelectedCells', selectedCells.value)\n })\n\n // 当前激活的单元格:当且仅当只有一个选中单元格时,该单元格为激活的单元格\n const activedCell = computed(() => {\n if (selectedCells.value.length > 1) return null\n return selectedCells.value[0]\n })\n\n // 当前选中的单元格位置范围\n const selectedRange = computed(() => {\n if (!startCell.value.length) return null\n const [startX, startY] = startCell.value\n\n if (!endCell.value.length) return { row: [startX, startX], col: [startY, startY] }\n const [endX, endY] = endCell.value\n\n if (startX === endX && startY === endY) return { row: [startX, startX], col: [startY, startY] }\n\n const minX = Math.min(startX, endX)\n const minY = Math.min(startY, endY)\n const maxX = Math.max(startX, endX)\n const maxY = Math.max(startY, endY)\n\n return {\n row: [minX, maxX],\n col: [minY, maxY],\n }\n })\n\n // 设置选中单元格状态(鼠标点击或拖选)\n const handleMouseup = () => isStartSelect.value = false\n\n const handleCellMousedown = (e: MouseEvent, rowIndex: number, colIndex: number) => {\n if (e.button === 0) {\n endCell.value = []\n isStartSelect.value = true\n startCell.value = [rowIndex, colIndex]\n }\n }\n\n const handleCellMouseenter = (rowIndex: number, colIndex: number) => {\n if (!isStartSelect.value) return\n endCell.value = [rowIndex, colIndex]\n }\n\n onMounted(() => {\n document.addEventListener('mouseup', handleMouseup)\n })\n onUnmounted(() => {\n document.removeEventListener('mouseup', handleMouseup)\n })\n\n // 判断某位置是否为无效单元格(被合并掉的位置)\n const isHideCell = (rowIndex: number, colIndex: number) => hideCells.value.includes(`${rowIndex}_${colIndex}`)\n\n // 选中指定的列\n const selectCol = (index: number) => {\n const maxRow = tableCells.value.length - 1\n startCell.value = [0, index]\n endCell.value = [maxRow, index]\n }\n\n // 选中指定的行\n const selectRow = (index: number) => {\n const maxCol = tableCells.value[index].length - 1\n startCell.value = [index, 0]\n endCell.value = [index, maxCol]\n }\n\n // 选中全部单元格\n const selectAll = () => {\n const maxRow = tableCells.value.length - 1\n const maxCol = tableCells.value[maxRow].length - 1\n startCell.value = [0, 0]\n endCell.value = [maxRow, maxCol]\n }\n\n // 删除一行\n const deleteRow = (rowIndex: number) => {\n const _tableCells: TableCell[][] = JSON.parse(JSON.stringify(tableCells.value))\n\n const targetCells = tableCells.value[rowIndex]\n const hideCellsPos = []\n for (let i = 0; i < targetCells.length; i++) {\n if (isHideCell(rowIndex, i)) hideCellsPos.push(i)\n }\n \n for (const pos of hideCellsPos) {\n for (let i = rowIndex; i >= 0; i--) {\n if (!isHideCell(i, pos)) {\n _tableCells[i][pos].rowspan = _tableCells[i][pos].rowspan - 1\n break\n }\n }\n }\n\n _tableCells.splice(rowIndex, 1)\n tableCells.value = _tableCells\n }\n\n // 删除一列\n const deleteCol = (colIndex: number) => {\n const _tableCells: TableCell[][] = JSON.parse(JSON.stringify(tableCells.value))\n\n const hideCellsPos = []\n for (let i = 0; i < tableCells.value.length; i++) {\n if (isHideCell(i, colIndex)) hideCellsPos.push(i)\n }\n\n for (const pos of hideCellsPos) {\n for (let i = colIndex; i >= 0; i--) {\n if (!isHideCell(pos, i)) {\n _tableCells[pos][i].colspan = _tableCells[pos][i].colspan - 1\n break\n }\n }\n }\n\n tableCells.value = _tableCells.map(item => {\n item.splice(colIndex, 1)\n return item\n })\n colSizeList.value.splice(colIndex, 1)\n emit('changeColWidths', colSizeList.value)\n }\n \n // 插入一行\n const insertRow = (rowIndex: number) => {\n const _tableCells: TableCell[][] = JSON.parse(JSON.stringify(tableCells.value))\n\n const rowCells: TableCell[] = []\n for (let i = 0; i < _tableCells[0].length; i++) {\n rowCells.push({\n colspan: 1,\n rowspan: 1,\n text: '',\n id: nanoid(10),\n })\n }\n\n _tableCells.splice(rowIndex, 0, rowCells)\n tableCells.value = _tableCells\n }\n\n // 插入一列\n const insertCol = (colIndex: number) => {\n tableCells.value = tableCells.value.map(item => {\n const cell = {\n colspan: 1,\n rowspan: 1,\n text: '',\n id: nanoid(10),\n }\n item.splice(colIndex, 0, cell)\n return item\n })\n colSizeList.value.splice(colIndex, 0, 100)\n emit('changeColWidths', colSizeList.value)\n }\n\n // 填充指定的行/列数\n const fillTable = (rowCount: number, colCount: number) => {\n let _tableCells: TableCell[][] = JSON.parse(JSON.stringify(tableCells.value))\n const defaultCell = { colspan: 1, rowspan: 1, text: '' }\n \n if (rowCount) {\n const newRows = []\n for (let i = 0; i < rowCount; i++) {\n const rowCells: TableCell[] = []\n for (let j = 0; j < _tableCells[0].length; j++) {\n rowCells.push({\n ...defaultCell,\n id: nanoid(10),\n })\n }\n newRows.push(rowCells)\n }\n _tableCells = [..._tableCells, ...newRows]\n }\n if (colCount) {\n _tableCells = _tableCells.map(item => {\n const cells: TableCell[] = []\n for (let i = 0; i < colCount; i++) {\n const cell = {\n ...defaultCell,\n id: nanoid(10),\n }\n cells.push(cell)\n }\n return [...item, ...cells]\n })\n colSizeList.value = [...colSizeList.value, ...new Array(colCount).fill(100)]\n emit('changeColWidths', colSizeList.value)\n }\n\n tableCells.value = _tableCells\n }\n \n // 合并单元格\n const mergeCells = () => {\n const [startX, startY] = startCell.value\n const [endX, endY] = endCell.value\n\n const minX = Math.min(startX, endX)\n const minY = Math.min(startY, endY)\n const maxX = Math.max(startX, endX)\n const maxY = Math.max(startY, endY)\n\n const _tableCells: TableCell[][] = JSON.parse(JSON.stringify(tableCells.value))\n \n _tableCells[minX][minY].rowspan = maxX - minX + 1\n _tableCells[minX][minY].colspan = maxY - minY + 1\n\n tableCells.value = _tableCells\n removeSelectedCells()\n }\n\n // 拆分单元格\n const splitCells = (rowIndex: number, colIndex: number) => {\n const _tableCells: TableCell[][] = JSON.parse(JSON.stringify(tableCells.value))\n _tableCells[rowIndex][colIndex].rowspan = 1\n _tableCells[rowIndex][colIndex].colspan = 1\n\n tableCells.value = _tableCells\n removeSelectedCells()\n }\n\n // 鼠标拖拽调整列宽\n const handleMousedownColHandler = (e: MouseEvent, colIndex: number) => {\n removeSelectedCells()\n let isMouseDown = true\n\n const originWidth = colSizeList.value[colIndex]\n const startPageX = e.pageX\n\n const minWidth = 50\n\n document.onmousemove = e => {\n if (!isMouseDown) return\n \n const moveX = (e.pageX - startPageX) / canvasScale.value\n const width = originWidth + moveX < minWidth ? minWidth : Math.round(originWidth + moveX)\n\n colSizeList.value[colIndex] = width\n }\n document.onmouseup = () => {\n isMouseDown = false\n document.onmousemove = null\n document.onmouseup = null\n\n emit('changeColWidths', colSizeList.value)\n }\n }\n\n // 清空选中单元格内的文字\n const clearSelectedCellText = () => {\n const _tableCells: TableCell[][] = JSON.parse(JSON.stringify(tableCells.value))\n\n for (let i = 0; i < _tableCells.length; i++) {\n for (let j = 0; j < _tableCells[i].length; j++) {\n if (selectedCells.value.includes(`${i}_${j}`)) {\n _tableCells[i][j].text = ''\n }\n }\n }\n tableCells.value = _tableCells\n }\n\n // 将焦点移动到下一个单元格\n // 当前行右边有单元格时,焦点右移\n // 当前行右边无单元格(已处在行末),且存在下一行时,焦点移动至下一行行首\n // 当前行右边无单元格(已处在行末),且不存在下一行(已处在最后一行)时,新建一行并将焦点移动至下一行行首\n const tabActiveCell = () => {\n const getNextCell = (i: number, j: number): [number, number] | null => {\n if (!tableCells.value[i]) return null\n if (!tableCells.value[i][j]) return getNextCell(i + 1, 0)\n if (isHideCell(i, j)) return getNextCell(i, j + 1)\n return [i, j]\n }\n\n endCell.value = []\n\n const nextRow = startCell.value[0]\n const nextCol = startCell.value[1] + 1\n\n const nextCell = getNextCell(nextRow, nextCol)\n if (!nextCell) {\n insertRow(nextRow + 1)\n startCell.value = [nextRow + 1, 0]\n }\n else startCell.value = nextCell\n\n // 移动焦点后自动聚焦文本\n nextTick(() => {\n const textRef = document.querySelector('.cell-text.active') as HTMLInputElement\n if (textRef) textRef.focus()\n })\n }\n\n // 表格快捷键监听\n const keydownListener = (e: KeyboardEvent) => {\n if (!props.editable || !selectedCells.value.length) return\n\n const key = e.key.toUpperCase()\n if (selectedCells.value.length < 2) {\n if (key === KEYS.TAB) {\n e.preventDefault()\n tabActiveCell()\n }\n if (e.ctrlKey && key === KEYS.UP) {\n e.preventDefault()\n const rowIndex = +selectedCells.value[0].split('_')[0]\n insertRow(rowIndex)\n }\n if (e.ctrlKey && key === KEYS.DOWN) {\n e.preventDefault()\n const rowIndex = +selectedCells.value[0].split('_')[0]\n insertRow(rowIndex + 1)\n }\n if (e.ctrlKey && key === KEYS.LEFT) {\n e.preventDefault()\n const colIndex = +selectedCells.value[0].split('_')[1]\n insertCol(colIndex)\n }\n if (e.ctrlKey && key === KEYS.RIGHT) {\n e.preventDefault()\n const colIndex = +selectedCells.value[0].split('_')[1]\n insertCol(colIndex + 1)\n }\n }\n else if (key === KEYS.DELETE) {\n clearSelectedCellText()\n }\n }\n\n onMounted(() => {\n document.addEventListener('keydown', keydownListener)\n })\n onUnmounted(() => {\n document.removeEventListener('keydown', keydownListener)\n })\n\n // 单元格文字输入时更新表格数据\n const handleInput = debounce(function(value, rowIndex, colIndex) {\n tableCells.value[rowIndex][colIndex].text = value\n emit('change', tableCells.value)\n }, 300, { trailing: true })\n\n // 插入来自Excel的数据,表格的行/列数不够时自动补足\n const insertExcelData = (data: string[][], rowIndex: number, colIndex: number) => {\n const maxRow = data.length\n const maxCol = data[0].length\n\n let fillRowCount = 0\n let fillColCount = 0\n if (rowIndex + maxRow > tableCells.value.length) fillRowCount = rowIndex + maxRow - tableCells.value.length\n if (colIndex + maxCol > tableCells.value[0].length) fillColCount = colIndex + maxCol - tableCells.value[0].length\n if (fillRowCount || fillColCount) fillTable(fillRowCount, fillColCount)\n\n nextTick(() => {\n for (let i = 0; i < maxRow; i++) {\n for (let j = 0; j < maxCol; j++) {\n if (tableCells.value[rowIndex + i][colIndex + j]) {\n tableCells.value[rowIndex + i][colIndex + j].text = data[i][j]\n }\n }\n }\n emit('change', tableCells.value)\n })\n }\n\n // 获取有效的单元格(排除掉被合并的单元格)\n const getEffectiveTableCells = () => {\n const effectiveTableCells = []\n\n for (let i = 0; i < tableCells.value.length; i++) {\n const rowCells = tableCells.value[i]\n const _rowCells = []\n for (let j = 0; j < rowCells.length; j++) {\n if (!isHideCell(i, j)) _rowCells.push(rowCells[j])\n }\n if (_rowCells.length) effectiveTableCells.push(_rowCells)\n }\n\n return effectiveTableCells\n }\n\n // 检查是否可以删除行和列:有效的行/列数大于1\n const checkCanDeleteRowOrCol = () => {\n const effectiveTableCells = getEffectiveTableCells()\n const canDeleteRow = effectiveTableCells.length > 1\n const canDeleteCol = effectiveTableCells[0].length > 1\n\n return { canDeleteRow, canDeleteCol }\n }\n\n // 检查是否可以合并或拆分\n // 必须多选才可以合并\n // 必须单选且所选单元格为合并单元格才可以拆分\n const checkCanMergeOrSplit = (rowIndex: number, colIndex: number) => {\n const isMultiSelected = selectedCells.value.length > 1\n const targetCell = tableCells.value[rowIndex][colIndex]\n\n const canMerge = isMultiSelected\n const canSplit = !isMultiSelected && (targetCell.rowspan > 1 || targetCell.colspan > 1)\n\n return { canMerge, canSplit }\n }\n\n const contextmenus = (el: HTMLElement): ContextmenuItem[] => {\n const cellIndex = el.dataset.cellIndex as string\n const rowIndex = +cellIndex.split('_')[0]\n const colIndex = +cellIndex.split('_')[1]\n\n if (!selectedCells.value.includes(`${rowIndex}_${colIndex}`)) {\n startCell.value = [rowIndex, colIndex]\n endCell.value = []\n }\n\n const { canMerge, canSplit } = checkCanMergeOrSplit(rowIndex, colIndex)\n const { canDeleteRow, canDeleteCol } = checkCanDeleteRowOrCol()\n\n return [\n {\n text: '插入列',\n children: [\n { text: '到左侧', handler: () => insertCol(colIndex) },\n { text: '到右侧', handler: () => insertCol(colIndex + 1) },\n ],\n },\n {\n text: '插入行',\n children: [\n { text: '到上方', handler: () => insertRow(rowIndex) },\n { text: '到下方', handler: () => insertRow(rowIndex + 1) },\n ],\n },\n {\n text: '删除列',\n disable: !canDeleteCol,\n handler: () => deleteCol(colIndex),\n },\n {\n text: '删除行',\n disable: !canDeleteRow,\n handler: () => deleteRow(rowIndex),\n },\n { divider: true },\n {\n text: '合并单元格',\n disable: !canMerge,\n handler: mergeCells,\n },\n {\n text: '取消合并单元格',\n disable: !canSplit,\n handler: () => splitCells(rowIndex, colIndex),\n },\n { divider: true },\n {\n text: '选中当前列',\n handler: () => selectCol(colIndex),\n },\n {\n text: '选中当前行',\n handler: () => selectRow(rowIndex),\n },\n {\n text: '选中全部单元格',\n handler: selectAll,\n },\n ]\n }\n\n return {\n getTextStyle,\n dragLinePosition,\n tableCells,\n colSizeList,\n totalWidth,\n hideCells,\n selectedCells,\n activedCell,\n selectedRange,\n handleCellMousedown,\n handleCellMouseenter,\n selectCol,\n selectRow,\n handleMousedownColHandler,\n contextmenus,\n handleInput,\n insertExcelData,\n subThemeColor,\n formatText,\n }\n },\n})\n","import { render } from \"./EditableTable.vue?vue&type=template&id=3ee7b416&scoped=true&ts=true\"\nimport script from \"./EditableTable.vue?vue&type=script&lang=ts\"\nexport * from \"./EditableTable.vue?vue&type=script&lang=ts\"\n\nimport \"./EditableTable.vue?vue&type=style&index=0&id=3ee7b416&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-3ee7b416\"]])\n\nexport default __exports__","\nimport { defineComponent, nextTick, onMounted, onUnmounted, PropType, ref, watch } from 'vue'\nimport { storeToRefs } from 'pinia'\nimport { useMainStore, useSlidesStore } from '@/store'\nimport { PPTTableElement, TableCell } from '@/types/slides'\nimport { ContextmenuItem } from '@/components/Contextmenu/types'\nimport useHistorySnapshot from '@/hooks/useHistorySnapshot'\n\nimport EditableTable from './EditableTable.vue'\n\nexport default defineComponent({\n name: 'editable-element-table',\n components: {\n EditableTable,\n },\n props: {\n elementInfo: {\n type: Object as PropType,\n required: true,\n },\n selectElement: {\n type: Function as PropType<(e: MouseEvent, element: PPTTableElement, canMove?: boolean) => void>,\n required: true,\n },\n contextmenus: {\n type: Function as PropType<() => ContextmenuItem[]>,\n },\n },\n setup(props) {\n const mainStore = useMainStore()\n const slidesStore = useSlidesStore()\n const { canvasScale, handleElementId, isScaling } = storeToRefs(mainStore)\n \n const elementRef = ref()\n\n const { addHistorySnapshot } = useHistorySnapshot()\n\n const handleSelectElement = (e: MouseEvent) => {\n if (props.elementInfo.lock) return\n e.stopPropagation()\n\n props.selectElement(e, props.elementInfo)\n }\n\n // 更新表格的可编辑状态,表格处于编辑状态时需要禁用全局快捷键\n const editable = ref(false)\n\n watch(handleElementId, () => {\n if (handleElementId.value !== props.elementInfo.id) editable.value = false\n })\n\n watch(editable, () => {\n mainStore.setDisableHotkeysState(editable.value)\n })\n\n const startEdit = () => {\n if (!props.elementInfo.lock) editable.value = true\n }\n\n // 监听表格元素的尺寸变化,当高度变化时,更新高度到vuex\n // 如果高度变化时正处在缩放操作中,则等待缩放操作结束后再更新\n const realHeightCache = ref(-1)\n\n watch(isScaling, () => {\n if (handleElementId.value !== props.elementInfo.id) return\n\n if (isScaling.value) editable.value = false\n\n if (!isScaling.value && realHeightCache.value !== -1) {\n slidesStore.updateElement({\n id: props.elementInfo.id,\n props: { height: realHeightCache.value },\n })\n realHeightCache.value = -1\n }\n })\n\n const updateTableElementHeight = (entries: ResizeObserverEntry[]) => {\n const contentRect = entries[0].contentRect\n if (!elementRef.value) return\n\n const realHeight = contentRect.height\n\n if (props.elementInfo.height !== realHeight) {\n if (!isScaling.value) {\n slidesStore.updateElement({\n id: props.elementInfo.id,\n props: { height: realHeight },\n })\n }\n else realHeightCache.value = realHeight\n }\n }\n\n const resizeObserver = new ResizeObserver(updateTableElementHeight)\n\n onMounted(() => {\n if (elementRef.value) resizeObserver.observe(elementRef.value)\n })\n onUnmounted(() => {\n if (elementRef.value) resizeObserver.unobserve(elementRef.value)\n })\n\n // 更新表格内容数据\n const updateTableCells = (data: TableCell[][]) => {\n slidesStore.updateElement({\n id: props.elementInfo.id, \n props: { data },\n })\n addHistorySnapshot()\n }\n\n // 更新表格的列宽数据\n const updateColWidths = (widths: number[]) => {\n const width = widths.reduce((a, b) => a + b)\n const colWidths = widths.map(item => item / width)\n\n slidesStore.updateElement({\n id: props.elementInfo.id, \n props: { width, colWidths },\n })\n addHistorySnapshot()\n }\n\n // 更新表格当前选中的单元格\n const updateSelectedCells = (cells: string[]) => {\n nextTick(() => mainStore.setSelectedTableCells(cells))\n }\n\n return {\n elementRef,\n canvasScale,\n handleSelectElement,\n updateTableCells,\n updateColWidths,\n editable,\n startEdit,\n updateSelectedCells,\n }\n },\n})\n","import { render } from \"./index.vue?vue&type=template&id=426704b7&scoped=true&ts=true\"\nimport script from \"./index.vue?vue&type=script&lang=ts\"\nexport * from \"./index.vue?vue&type=script&lang=ts\"\n\nimport \"./index.vue?vue&type=style&index=0&id=426704b7&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-426704b7\"]])\n\nexport default __exports__","import { createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock, resolveDirective as _resolveDirective, withDirectives as _withDirectives, normalizeStyle as _normalizeStyle, normalizeClass as _normalizeClass, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-3168526a\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = [\"width\", \"height\", \"stroke\", \"stroke-width\"]\nconst _hoisted_2 = [\"transform\"]\nconst _hoisted_3 = [\"d\"]\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _directive_contextmenu = _resolveDirective(\"contextmenu\")!\n\n return (_openBlock(), _createElementBlock(\"div\", {\n class: _normalizeClass([\"editable-element-latex\", { 'lock': _ctx.elementInfo.lock }]),\n style: _normalizeStyle({\n top: _ctx.elementInfo.top + 'px',\n left: _ctx.elementInfo.left + 'px',\n width: _ctx.elementInfo.width + 'px',\n height: _ctx.elementInfo.height + 'px',\n })\n }, [\n _createElementVNode(\"div\", {\n class: \"rotate-wrapper\",\n style: _normalizeStyle({ transform: `rotate(${_ctx.elementInfo.rotate}deg)` })\n }, [\n _withDirectives((_openBlock(), _createElementBlock(\"div\", {\n class: \"element-content\",\n onMousedown: _cache[0] || (_cache[0] = $event => _ctx.handleSelectElement($event)),\n onDblclick: _cache[1] || (_cache[1] = ($event: any) => (_ctx.openLatexEditor()))\n }, [\n (_openBlock(), _createElementBlock(\"svg\", {\n overflow: \"visible\",\n width: _ctx.elementInfo.width,\n height: _ctx.elementInfo.height,\n stroke: _ctx.elementInfo.color,\n \"stroke-width\": _ctx.elementInfo.strokeWidth,\n fill: \"none\",\n \"stroke-linecap\": \"round\",\n \"stroke-linejoin\": \"round\"\n }, [\n _createElementVNode(\"g\", {\n transform: `scale(${_ctx.elementInfo.width / _ctx.elementInfo.viewBox[0]}, ${_ctx.elementInfo.height / _ctx.elementInfo.viewBox[1]}) translate(0,0) matrix(1,0,0,1,0,0)`\n }, [\n _createElementVNode(\"path\", {\n d: _ctx.elementInfo.path\n }, null, 8, _hoisted_3)\n ], 8, _hoisted_2)\n ], 8, _hoisted_1))\n ], 32)), [\n [_directive_contextmenu, _ctx.contextmenus]\n ])\n ], 4)\n ], 6))\n}","\nimport { defineComponent, PropType } from 'vue'\nimport { PPTLatexElement } from '@/types/slides'\nimport { ContextmenuItem } from '@/components/Contextmenu/types'\nimport emitter, { EmitterEvents } from '@/utils/emitter'\n\nexport default defineComponent({\n name: 'editable-element-latex',\n props: {\n elementInfo: {\n type: Object as PropType,\n required: true,\n },\n selectElement: {\n type: Function as PropType<(e: MouseEvent, element: PPTLatexElement, canMove?: boolean) => void>,\n required: true,\n },\n contextmenus: {\n type: Function as PropType<() => ContextmenuItem[]>,\n },\n },\n setup(props) {\n const handleSelectElement = (e: MouseEvent) => {\n if (props.elementInfo.lock) return\n e.stopPropagation()\n\n props.selectElement(e, props.elementInfo)\n }\n\n const openLatexEditor = () => {\n emitter.emit(EmitterEvents.OPEN_LATEX_EDITOR)\n }\n\n return {\n handleSelectElement,\n openLatexEditor,\n }\n },\n})\n","import { render } from \"./index.vue?vue&type=template&id=3168526a&scoped=true&ts=true\"\nimport script from \"./index.vue?vue&type=script&lang=ts\"\nexport * from \"./index.vue?vue&type=script&lang=ts\"\n\nimport \"./index.vue?vue&type=style&index=0&id=3168526a&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-3168526a\"]])\n\nexport default __exports__","import { resolveComponent as _resolveComponent, createVNode as _createVNode, renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, normalizeClass as _normalizeClass, createElementVNode as _createElementVNode, resolveDirective as _resolveDirective, withDirectives as _withDirectives, normalizeStyle as _normalizeStyle } from \"vue\"\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _component_VideoPlayer = _resolveComponent(\"VideoPlayer\")!\n const _directive_contextmenu = _resolveDirective(\"contextmenu\")!\n\n return (_openBlock(), _createElementBlock(\"div\", {\n class: _normalizeClass([\"editable-element-video\", { 'lock': _ctx.elementInfo.lock }]),\n style: _normalizeStyle({\n top: _ctx.elementInfo.top + 'px',\n left: _ctx.elementInfo.left + 'px',\n width: _ctx.elementInfo.width + 'px',\n height: _ctx.elementInfo.height + 'px',\n })\n }, [\n _createElementVNode(\"div\", {\n class: \"rotate-wrapper\",\n style: _normalizeStyle({ transform: `rotate(${_ctx.elementInfo.rotate}deg)` })\n }, [\n _withDirectives((_openBlock(), _createElementBlock(\"div\", {\n class: \"element-content\",\n onMousedown: _cache[1] || (_cache[1] = $event => _ctx.handleSelectElement($event, false))\n }, [\n _createVNode(_component_VideoPlayer, {\n width: _ctx.elementInfo.width,\n height: _ctx.elementInfo.height,\n src: _ctx.elementInfo.src,\n poster: _ctx.elementInfo.poster,\n scale: _ctx.canvasScale\n }, null, 8, [\"width\", \"height\", \"src\", \"poster\", \"scale\"]),\n (_openBlock(), _createElementBlock(_Fragment, null, _renderList(['t', 'b', 'l', 'r'], (item) => {\n return _createElementVNode(\"div\", {\n class: _normalizeClass(['handler-border', item]),\n key: item,\n onMousedown: _cache[0] || (_cache[0] = $event => _ctx.handleSelectElement($event))\n }, null, 34)\n }), 64))\n ], 32)), [\n [_directive_contextmenu, _ctx.contextmenus]\n ])\n ], 4)\n ], 6))\n}","import { createElementVNode as _createElementVNode, resolveComponent as _resolveComponent, openBlock as _openBlock, createBlock as _createBlock, createCommentVNode as _createCommentVNode, normalizeClass as _normalizeClass, normalizeStyle as _normalizeStyle, toDisplayString as _toDisplayString, createTextVNode as _createTextVNode, renderList as _renderList, Fragment as _Fragment, createElementBlock as _createElementBlock, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-e716fb74\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = [\"src\", \"poster\"]\nconst _hoisted_2 = { class: \"bezel\" }\nconst _hoisted_3 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(\"div\", { class: \"controller-mask\" }, null, -1))\nconst _hoisted_4 = { class: \"controller\" }\nconst _hoisted_5 = { class: \"icons icons-left\" }\nconst _hoisted_6 = { class: \"icon-content\" }\nconst _hoisted_7 = { class: \"volume\" }\nconst _hoisted_8 = { class: \"icon-content\" }\nconst _hoisted_9 = {\n class: \"volume-bar\",\n ref: \"volumeBarRef\"\n}\nconst _hoisted_10 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(\"span\", { class: \"thumb\" }, null, -1))\nconst _hoisted_11 = [\n _hoisted_10\n]\nconst _hoisted_12 = { class: \"time\" }\nconst _hoisted_13 = { class: \"ptime\" }\nconst _hoisted_14 = /*#__PURE__*/_createTextVNode(\" / \")\nconst _hoisted_15 = { class: \"dtime\" }\nconst _hoisted_16 = { class: \"icons icons-right\" }\nconst _hoisted_17 = { class: \"speed\" }\nconst _hoisted_18 = { class: \"icon speed-icon\" }\nconst _hoisted_19 = [\"onClick\"]\nconst _hoisted_20 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(\"span\", { class: \"icon-content\" }, \"循环\", -1))\nconst _hoisted_21 = [\n _hoisted_20\n]\nconst _hoisted_22 = { class: \"bar\" }\nconst _hoisted_23 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(\"span\", { class: \"thumb\" }, null, -1))\nconst _hoisted_24 = [\n _hoisted_23\n]\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _component_IconPause = _resolveComponent(\"IconPause\")!\n const _component_IconPlayOne = _resolveComponent(\"IconPlayOne\")!\n const _component_IconVolumeMute = _resolveComponent(\"IconVolumeMute\")!\n const _component_IconVolumeNotice = _resolveComponent(\"IconVolumeNotice\")!\n const _component_IconVolumeSmall = _resolveComponent(\"IconVolumeSmall\")!\n\n return (_openBlock(), _createElementBlock(\"div\", {\n class: _normalizeClass([\"video-player\", { 'hide-controller': _ctx.hideController }]),\n style: _normalizeStyle({\n width: _ctx.width * _ctx.scale + 'px',\n height: _ctx.height * _ctx.scale + 'px',\n transform: `scale(${1 / _ctx.scale})`,\n }),\n onMousemove: _cache[22] || (_cache[22] = ($event: any) => (_ctx.autoHideController())),\n onClick: _cache[23] || (_cache[23] = ($event: any) => (_ctx.autoHideController()))\n }, [\n _createElementVNode(\"div\", {\n class: \"video-wrap\",\n onClick: _cache[8] || (_cache[8] = ($event: any) => (_ctx.toggle()))\n }, [\n _createElementVNode(\"video\", {\n class: \"video\",\n ref: \"videoRef\",\n src: _ctx.src,\n poster: _ctx.poster,\n \"webkit-playsinline\": \"\",\n playsinline: \"\",\n onDurationchange: _cache[0] || (_cache[0] = ($event: any) => (_ctx.handleDurationchange())),\n onTimeupdate: _cache[1] || (_cache[1] = ($event: any) => (_ctx.handleTimeupdate())),\n onEnded: _cache[2] || (_cache[2] = ($event: any) => (_ctx.handleEnded())),\n onProgress: _cache[3] || (_cache[3] = ($event: any) => (_ctx.handleProgress())),\n onPlay: _cache[4] || (_cache[4] = ($event: any) => (_ctx.autoHideController())),\n onPause: _cache[5] || (_cache[5] = ($event: any) => (_ctx.autoHideController())),\n onError: _cache[6] || (_cache[6] = ($event: any) => (_ctx.handleError()))\n }, null, 40, _hoisted_1),\n _createElementVNode(\"div\", _hoisted_2, [\n _createElementVNode(\"span\", {\n class: _normalizeClass([\"bezel-icon\", { 'bezel-transition': _ctx.bezelTransition }]),\n onAnimationend: _cache[7] || (_cache[7] = ($event: any) => (_ctx.bezelTransition = false))\n }, [\n (_ctx.paused)\n ? (_openBlock(), _createBlock(_component_IconPause, { key: 0 }))\n : (_openBlock(), _createBlock(_component_IconPlayOne, { key: 1 }))\n ], 34)\n ])\n ]),\n _hoisted_3,\n _createElementVNode(\"div\", _hoisted_4, [\n _createElementVNode(\"div\", _hoisted_5, [\n _createElementVNode(\"div\", {\n class: \"icon play-icon\",\n onClick: _cache[9] || (_cache[9] = ($event: any) => (_ctx.toggle()))\n }, [\n _createElementVNode(\"span\", _hoisted_6, [\n (_ctx.paused)\n ? (_openBlock(), _createBlock(_component_IconPlayOne, { key: 0 }))\n : (_openBlock(), _createBlock(_component_IconPause, { key: 1 }))\n ])\n ]),\n _createElementVNode(\"div\", _hoisted_7, [\n _createElementVNode(\"div\", {\n class: \"icon volume-icon\",\n onClick: _cache[10] || (_cache[10] = ($event: any) => (_ctx.toggleVolume()))\n }, [\n _createElementVNode(\"span\", _hoisted_8, [\n (_ctx.volume === 0)\n ? (_openBlock(), _createBlock(_component_IconVolumeMute, { key: 0 }))\n : (_ctx.volume === 1)\n ? (_openBlock(), _createBlock(_component_IconVolumeNotice, { key: 1 }))\n : (_openBlock(), _createBlock(_component_IconVolumeSmall, { key: 2 }))\n ])\n ]),\n _createElementVNode(\"div\", {\n class: \"volume-bar-wrap\",\n onMousedown: _cache[11] || (_cache[11] = $event => _ctx.handleMousedownVolumeBar($event)),\n onTouchstart: _cache[12] || (_cache[12] = $event => _ctx.handleMousedownVolumeBar($event)),\n onClick: _cache[13] || (_cache[13] = $event => _ctx.handleClickVolumeBar($event))\n }, [\n _createElementVNode(\"div\", _hoisted_9, [\n _createElementVNode(\"div\", {\n class: \"volume-bar-inner\",\n style: _normalizeStyle({ width: _ctx.volumeBarWidth })\n }, _hoisted_11, 4)\n ], 512)\n ], 32)\n ]),\n _createElementVNode(\"span\", _hoisted_12, [\n _createElementVNode(\"span\", _hoisted_13, _toDisplayString(_ctx.ptime), 1),\n _hoisted_14,\n _createElementVNode(\"span\", _hoisted_15, _toDisplayString(_ctx.dtime), 1)\n ])\n ]),\n _createElementVNode(\"div\", _hoisted_16, [\n _createElementVNode(\"div\", _hoisted_17, [\n _createElementVNode(\"div\", _hoisted_18, [\n _createElementVNode(\"span\", {\n class: \"icon-content\",\n onClick: _cache[14] || (_cache[14] = ($event: any) => (_ctx.speedMenuVisible = !_ctx.speedMenuVisible))\n }, \"倍速\"),\n (_ctx.speedMenuVisible)\n ? (_openBlock(), _createElementBlock(\"div\", {\n key: 0,\n class: \"speed-menu\",\n onMouseleave: _cache[15] || (_cache[15] = ($event: any) => (_ctx.speedMenuVisible = false))\n }, [\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.speedOptions, (item) => {\n return (_openBlock(), _createElementBlock(\"div\", {\n class: _normalizeClass([\"speed-menu-item\", { 'active': item.value === _ctx.playbackRate }]),\n key: item.label,\n onClick: ($event: any) => (_ctx.speed(item.value))\n }, _toDisplayString(item.label), 11, _hoisted_19))\n }), 128))\n ], 32))\n : _createCommentVNode(\"\", true)\n ])\n ]),\n _createElementVNode(\"div\", {\n class: \"loop\",\n onClick: _cache[16] || (_cache[16] = ($event: any) => (_ctx.toggleLoop()))\n }, [\n _createElementVNode(\"div\", {\n class: _normalizeClass([\"icon loop-icon\", { 'active': _ctx.loop }])\n }, _hoisted_21, 2)\n ])\n ]),\n _createElementVNode(\"div\", {\n class: \"bar-wrap\",\n ref: \"playBarWrap\",\n onMousedown: _cache[17] || (_cache[17] = $event => _ctx.handleMousedownPlayBar($event)),\n onTouchstart: _cache[18] || (_cache[18] = $event => _ctx.handleMousedownPlayBar($event)),\n onMousemove: _cache[19] || (_cache[19] = $event => _ctx.handleMousemovePlayBar($event)),\n onMouseenter: _cache[20] || (_cache[20] = ($event: any) => (_ctx.playBarTimeVisible = true)),\n onMouseleave: _cache[21] || (_cache[21] = ($event: any) => (_ctx.playBarTimeVisible = false))\n }, [\n _createElementVNode(\"div\", {\n class: _normalizeClass([\"bar-time\", { 'hidden': !_ctx.playBarTimeVisible }]),\n style: _normalizeStyle({ left: _ctx.playBarTimeLeft })\n }, _toDisplayString(_ctx.playBarTime), 7),\n _createElementVNode(\"div\", _hoisted_22, [\n _createElementVNode(\"div\", {\n class: \"loaded\",\n style: _normalizeStyle({ width: _ctx.loadedBarWidth })\n }, null, 4),\n _createElementVNode(\"div\", {\n class: \"played\",\n style: _normalizeStyle({ width: _ctx.playedBarWidth })\n }, _hoisted_24, 4)\n ])\n ], 544)\n ])\n ], 38))\n}","/* eslint-disable */\n\nimport { onMounted, Ref } from 'vue'\n\nexport default (\n src: string,\n videoRef: Ref,\n) => {\n onMounted(() => {\n if (!videoRef.value) return\n\n let type = 'normal'\n if (/m3u8(#|\\?|$)/i.exec(src)) type = 'hls'\n else if (/.flv(#|\\?|$)/i.exec(src)) type = 'flv'\n\n if (videoRef.value && type === 'hls' && (videoRef.value.canPlayType('application/x-mpegURL') || videoRef.value.canPlayType('application/vnd.apple.mpegURL'))) {\n type = 'normal'\n }\n\n if (type === 'hls') {\n const Hls = (window as any).Hls\n \n if (Hls && Hls.isSupported()) {\n const hls = new Hls()\n hls.loadSource(src)\n hls.attachMedia(videoRef.value)\n }\n }\n else if (type === 'flv') {\n const flvjs = (window as any).flvjs\n if (flvjs && flvjs.isSupported()) {\n const flvPlayer = flvjs.createPlayer({\n type: 'flv',\n url: src,\n })\n flvPlayer.attachMediaElement(videoRef.value)\n flvPlayer.load()\n }\n }\n })\n}","\nimport { computed, defineComponent, ref } from 'vue'\nimport useMSE from './useMSE'\nimport { message } from 'ant-design-vue'\n\nconst secondToTime = (second = 0) => {\n if (second === 0 || isNaN(second)) return '00:00'\n\n const add0 = (num: number) => (num < 10 ? '0' + num : '' + num)\n const hour = Math.floor(second / 3600)\n const min = Math.floor((second - hour * 3600) / 60)\n const sec = Math.floor(second - hour * 3600 - min * 60)\n return (hour > 0 ? [hour, min, sec] : [min, sec]).map(add0).join(':')\n}\n\nconst getBoundingClientRectViewLeft = (element: HTMLElement) => {\n return element.getBoundingClientRect().left\n}\n\nexport default defineComponent({\n name: 'video-player',\n props: {\n width: {\n type: Number,\n required: true,\n },\n height: {\n type: Number,\n required: true,\n },\n src: {\n type: String,\n required: true,\n },\n poster: {\n type: String,\n default: '',\n },\n scale: {\n type: Number,\n default: 1,\n },\n },\n setup(props) {\n const videoRef = ref()\n const playBarWrap = ref()\n const volumeBarRef = ref()\n\n const volume = ref(0.5)\n const paused = ref(true)\n const currentTime = ref(0)\n const duration = ref(0)\n const loaded = ref(0)\n const loop = ref(false)\n const bezelTransition = ref(false)\n const playbackRate = ref(1)\n\n const playBarTimeVisible = ref(false)\n const playBarTime = ref('00:00')\n const playBarTimeLeft = ref('0')\n\n const ptime = computed(() => secondToTime(currentTime.value))\n const dtime = computed(() => secondToTime(duration.value))\n const playedBarWidth = computed(() => currentTime.value / duration.value * 100 + '%')\n const loadedBarWidth = computed(() => loaded.value / duration.value * 100 + '%')\n const volumeBarWidth = computed(() => volume.value * 100 + '%')\n\n const speedMenuVisible = ref(false)\n const speedOptions = [\n { label: '2x', value: 2 },\n { label: '1.5x', value: 1.5 },\n { label: '1.25x', value: 1.25 },\n { label: '1x', value: 1 },\n { label: '0.75x', value: 0.75 },\n { label: '0.5x', value: 0.5 },\n ]\n\n const seek = (time: number) => {\n if (!videoRef.value) return\n\n time = Math.max(time, 0)\n time = Math.min(time, duration.value)\n\n videoRef.value.currentTime = time\n currentTime.value = time\n }\n\n const play = () => {\n if (!videoRef.value) return\n\n paused.value = false\n videoRef.value.play()\n bezelTransition.value = true\n }\n\n const pause = () => {\n if (!videoRef.value) return\n\n paused.value = true\n videoRef.value.pause()\n bezelTransition.value = true\n }\n\n const toggle = () => {\n if (paused.value) play() \n else pause()\n }\n\n const setVolume = (percentage: number) => {\n if (!videoRef.value) return\n\n percentage = Math.max(percentage, 0)\n percentage = Math.min(percentage, 1)\n\n videoRef.value.volume = percentage\n volume.value = percentage\n if (videoRef.value.muted && percentage !== 0) videoRef.value.muted = false\n }\n\n const speed = (rate: number) => {\n if (videoRef.value) videoRef.value.playbackRate = rate\n playbackRate.value = rate\n }\n\n const handleDurationchange = () => {\n duration.value = videoRef.value?.duration || 0\n }\n\n const handleTimeupdate = () => {\n currentTime.value = videoRef.value?.currentTime || 0\n }\n\n const handleEnded = () => {\n if (!loop.value) pause()\n else {\n seek(0)\n play()\n }\n }\n\n const handleProgress = () => {\n loaded.value = videoRef.value?.buffered.length ? videoRef.value.buffered.end(videoRef.value.buffered.length - 1) : 0\n }\n\n const handleError = () => message.error('视频加载失败')\n\n const thumbMove = (e: MouseEvent | TouchEvent) => {\n if (!videoRef.value || !playBarWrap.value) return\n const clientX = 'clientX' in e ? e.clientX : e.changedTouches[0].clientX\n let percentage = (clientX - getBoundingClientRectViewLeft(playBarWrap.value)) / playBarWrap.value.clientWidth\n percentage = Math.max(percentage, 0)\n percentage = Math.min(percentage, 1)\n const time = percentage * duration.value\n\n videoRef.value.currentTime = time\n currentTime.value = time\n }\n\n const thumbUp = (e: MouseEvent | TouchEvent) => {\n if (!videoRef.value || !playBarWrap.value) return\n\n const clientX = 'clientX' in e ? e.clientX : e.changedTouches[0].clientX\n let percentage = (clientX - getBoundingClientRectViewLeft(playBarWrap.value)) / playBarWrap.value.clientWidth\n percentage = Math.max(percentage, 0)\n percentage = Math.min(percentage, 1)\n const time = percentage * duration.value\n\n videoRef.value.currentTime = time\n currentTime.value = time\n\n document.removeEventListener('mousemove', thumbMove)\n document.removeEventListener('touchmove', thumbMove)\n document.removeEventListener('mouseup', thumbUp)\n document.removeEventListener('touchend', thumbUp)\n }\n\n const handleMousedownPlayBar = () => {\n document.addEventListener('mousemove', thumbMove)\n document.addEventListener('touchmove', thumbMove)\n document.addEventListener('mouseup', thumbUp)\n document.addEventListener('touchend', thumbUp)\n }\n\n const volumeMove = (e: MouseEvent | TouchEvent) => {\n if (!volumeBarRef.value) return\n const clientX = 'clientX' in e ? e.clientX : e.changedTouches[0].clientX\n const percentage = (clientX - getBoundingClientRectViewLeft(volumeBarRef.value) - 5.5) / 35\n setVolume(percentage)\n }\n\n const volumeUp = () => {\n document.removeEventListener('mousemove', volumeMove)\n document.removeEventListener('touchmove', volumeMove)\n document.removeEventListener('mouseup', volumeUp)\n document.removeEventListener('touchend', volumeUp)\n }\n\n const handleMousedownVolumeBar = () => {\n document.addEventListener('mousemove', volumeMove)\n document.addEventListener('touchmove', volumeMove)\n document.addEventListener('mouseup', volumeUp)\n document.addEventListener('touchend', volumeUp)\n }\n\n const handleClickVolumeBar = (e: MouseEvent) => {\n if (!volumeBarRef.value) return\n const percentage = (e.clientX - getBoundingClientRectViewLeft(volumeBarRef.value) - 5.5) / 35\n setVolume(percentage)\n }\n\n const handleMousemovePlayBar = (e: MouseEvent) => {\n if (duration.value && playBarWrap.value) {\n const px = playBarWrap.value.getBoundingClientRect().left\n const tx = e.clientX - px\n if (tx < 0 || tx > playBarWrap.value.offsetWidth) return\n\n const time = duration.value * (tx / playBarWrap.value.offsetWidth)\n playBarTimeLeft.value = `${tx - (time >= 3600 ? 25 : 20)}px`\n playBarTime.value = secondToTime(time)\n playBarTimeVisible.value = true\n }\n }\n\n const toggleVolume = () => {\n if (!videoRef.value) return\n\n if (videoRef.value.muted) {\n videoRef.value.muted = false\n setVolume(0.5)\n }\n else {\n videoRef.value.muted = true\n setVolume(0)\n }\n }\n\n const toggleLoop = () => {\n loop.value = !loop.value\n }\n\n const autoHideControllerTimer = ref(-1)\n const hideController = ref(false)\n const autoHideController = () => {\n hideController.value = false\n clearTimeout(autoHideControllerTimer.value)\n autoHideControllerTimer.value = setTimeout(() => {\n if (videoRef.value?.played.length) hideController.value = true\n }, 3000)\n }\n \n useMSE(props.src, videoRef)\n\n return {\n videoRef,\n playBarWrap,\n volumeBarRef,\n volume,\n loop,\n paused,\n ptime,\n dtime,\n playBarTime,\n playBarTimeVisible,\n playBarTimeLeft,\n playedBarWidth,\n loadedBarWidth,\n volumeBarWidth,\n hideController,\n bezelTransition,\n playbackRate,\n speedMenuVisible,\n speedOptions,\n seek,\n play,\n pause,\n toggle,\n setVolume,\n speed,\n handleDurationchange,\n handleTimeupdate,\n handleEnded,\n handleProgress,\n handleMousedownPlayBar,\n handleMousedownVolumeBar,\n handleClickVolumeBar,\n handleMousemovePlayBar,\n toggleVolume,\n toggleLoop,\n autoHideController,\n handleError,\n }\n },\n})\n","import { render } from \"./index.vue?vue&type=template&id=e716fb74&scoped=true&ts=true\"\nimport script from \"./index.vue?vue&type=script&lang=ts\"\nexport * from \"./index.vue?vue&type=script&lang=ts\"\n\nimport \"./index.vue?vue&type=style&index=0&id=e716fb74&scoped=true&lang=scss\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-e716fb74\"]])\n\nexport default __exports__","\nimport { defineComponent, PropType } from 'vue'\nimport { storeToRefs } from 'pinia'\nimport { useMainStore } from '@/store'\nimport { PPTVideoElement } from '@/types/slides'\nimport { ContextmenuItem } from '@/components/Contextmenu/types'\n\nimport VideoPlayer from './VideoPlayer/index.vue'\n\nexport default defineComponent({\n name: 'editable-element-video',\n components: {\n VideoPlayer,\n },\n props: {\n elementInfo: {\n type: Object as PropType,\n required: true,\n },\n selectElement: {\n type: Function as PropType<(e: MouseEvent, element: PPTVideoElement, canMove?: boolean) => void>,\n required: true,\n },\n contextmenus: {\n type: Function as PropType<() => ContextmenuItem[]>,\n },\n },\n setup(props) {\n const { canvasScale } = storeToRefs(useMainStore())\n\n const handleSelectElement = (e: MouseEvent, canMove = true) => {\n if (props.elementInfo.lock) return\n e.stopPropagation()\n\n props.selectElement(e, props.elementInfo, canMove)\n }\n\n return {\n canvasScale,\n handleSelectElement,\n }\n },\n})\n","import { render } from \"./index.vue?vue&type=template&id=7f80f8ee&scoped=true&ts=true\"\nimport script from \"./index.vue?vue&type=script&lang=ts\"\nexport * from \"./index.vue?vue&type=script&lang=ts\"\n\nimport \"./index.vue?vue&type=style&index=0&id=7f80f8ee&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-7f80f8ee\"]])\n\nexport default __exports__","import { resolveComponent as _resolveComponent, normalizeStyle as _normalizeStyle, createVNode as _createVNode, withModifiers as _withModifiers, openBlock as _openBlock, createBlock as _createBlock, createCommentVNode as _createCommentVNode, resolveDirective as _resolveDirective, createElementBlock as _createElementBlock, withDirectives as _withDirectives, createElementVNode as _createElementVNode, normalizeClass as _normalizeClass } from \"vue\"\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _component_IconVolumeNotice = _resolveComponent(\"IconVolumeNotice\")!\n const _component_AudioPlayer = _resolveComponent(\"AudioPlayer\")!\n const _directive_contextmenu = _resolveDirective(\"contextmenu\")!\n\n return (_openBlock(), _createElementBlock(\"div\", {\n class: _normalizeClass([\"editable-element-audio\", { 'lock': _ctx.elementInfo.lock }]),\n style: _normalizeStyle({\n top: _ctx.elementInfo.top + 'px',\n left: _ctx.elementInfo.left + 'px',\n width: _ctx.elementInfo.width + 'px',\n height: _ctx.elementInfo.height + 'px',\n })\n }, [\n _createElementVNode(\"div\", {\n class: \"rotate-wrapper\",\n style: _normalizeStyle({ transform: `rotate(${_ctx.elementInfo.rotate}deg)` })\n }, [\n _withDirectives((_openBlock(), _createElementBlock(\"div\", {\n class: \"element-content\",\n onMousedown: _cache[1] || (_cache[1] = $event => _ctx.handleSelectElement($event))\n }, [\n _createVNode(_component_IconVolumeNotice, {\n class: \"audio-icon\",\n style: _normalizeStyle({\n fontSize: _ctx.audioIconSize,\n color: _ctx.elementInfo.color,\n })\n }, null, 8, [\"style\"]),\n (_ctx.handleElementId === _ctx.elementInfo.id)\n ? (_openBlock(), _createBlock(_component_AudioPlayer, {\n key: 0,\n class: \"audio-player\",\n style: _normalizeStyle({ ..._ctx.audioPlayerPosition }),\n src: _ctx.elementInfo.src,\n loop: _ctx.elementInfo.loop,\n scale: _ctx.canvasScale,\n onMousedown: _cache[0] || (_cache[0] = _withModifiers(() => {}, [\"stop\"]))\n }, null, 8, [\"style\", \"src\", \"loop\", \"scale\"]))\n : _createCommentVNode(\"\", true)\n ], 32)), [\n [_directive_contextmenu, _ctx.contextmenus]\n ])\n ], 4)\n ], 6))\n}","import { createElementVNode as _createElementVNode, resolveComponent as _resolveComponent, openBlock as _openBlock, createBlock as _createBlock, createCommentVNode as _createCommentVNode, normalizeStyle as _normalizeStyle, toDisplayString as _toDisplayString, createTextVNode as _createTextVNode, normalizeClass as _normalizeClass, createElementBlock as _createElementBlock, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-5cabbb47\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = [\"src\", \"autoplay\"]\nconst _hoisted_2 = { class: \"controller\" }\nconst _hoisted_3 = { class: \"icons\" }\nconst _hoisted_4 = { class: \"icon-content\" }\nconst _hoisted_5 = { class: \"volume\" }\nconst _hoisted_6 = { class: \"icon-content\" }\nconst _hoisted_7 = {\n class: \"volume-bar\",\n ref: \"volumeBarRef\"\n}\nconst _hoisted_8 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(\"span\", { class: \"thumb\" }, null, -1))\nconst _hoisted_9 = [\n _hoisted_8\n]\nconst _hoisted_10 = { class: \"time\" }\nconst _hoisted_11 = { class: \"ptime\" }\nconst _hoisted_12 = /*#__PURE__*/_createTextVNode(\" / \")\nconst _hoisted_13 = { class: \"dtime\" }\nconst _hoisted_14 = { class: \"bar\" }\nconst _hoisted_15 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(\"span\", { class: \"thumb\" }, null, -1))\nconst _hoisted_16 = [\n _hoisted_15\n]\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _component_IconPlayOne = _resolveComponent(\"IconPlayOne\")!\n const _component_IconPause = _resolveComponent(\"IconPause\")!\n const _component_IconVolumeMute = _resolveComponent(\"IconVolumeMute\")!\n const _component_IconVolumeNotice = _resolveComponent(\"IconVolumeNotice\")!\n const _component_IconVolumeSmall = _resolveComponent(\"IconVolumeSmall\")!\n\n return (_openBlock(), _createElementBlock(\"div\", {\n class: \"audio-player\",\n style: _normalizeStyle({ transform: `scale(${1 / _ctx.scale})` })\n }, [\n _createElementVNode(\"audio\", {\n class: \"audio\",\n ref: \"audioRef\",\n src: _ctx.src,\n autoplay: _ctx.autoplay,\n onDurationchange: _cache[0] || (_cache[0] = ($event: any) => (_ctx.handleDurationchange())),\n onTimeupdate: _cache[1] || (_cache[1] = ($event: any) => (_ctx.handleTimeupdate())),\n onPlay: _cache[2] || (_cache[2] = ($event: any) => (_ctx.handlePlayed())),\n onEnded: _cache[3] || (_cache[3] = ($event: any) => (_ctx.handleEnded())),\n onProgress: _cache[4] || (_cache[4] = ($event: any) => (_ctx.handleProgress())),\n onError: _cache[5] || (_cache[5] = ($event: any) => (_ctx.handleError()))\n }, null, 40, _hoisted_1),\n _createElementVNode(\"div\", _hoisted_2, [\n _createElementVNode(\"div\", _hoisted_3, [\n _createElementVNode(\"div\", {\n class: \"icon play-icon\",\n onClick: _cache[6] || (_cache[6] = ($event: any) => (_ctx.toggle()))\n }, [\n _createElementVNode(\"span\", _hoisted_4, [\n (_ctx.paused)\n ? (_openBlock(), _createBlock(_component_IconPlayOne, { key: 0 }))\n : (_openBlock(), _createBlock(_component_IconPause, { key: 1 }))\n ])\n ]),\n _createElementVNode(\"div\", _hoisted_5, [\n _createElementVNode(\"div\", {\n class: \"icon volume-icon\",\n onClick: _cache[7] || (_cache[7] = ($event: any) => (_ctx.toggleVolume()))\n }, [\n _createElementVNode(\"span\", _hoisted_6, [\n (_ctx.volume === 0)\n ? (_openBlock(), _createBlock(_component_IconVolumeMute, { key: 0 }))\n : (_ctx.volume === 1)\n ? (_openBlock(), _createBlock(_component_IconVolumeNotice, { key: 1 }))\n : (_openBlock(), _createBlock(_component_IconVolumeSmall, { key: 2 }))\n ])\n ]),\n _createElementVNode(\"div\", {\n class: \"volume-bar-wrap\",\n onMousedown: _cache[8] || (_cache[8] = $event => _ctx.handleMousedownVolumeBar($event)),\n onTouchstart: _cache[9] || (_cache[9] = $event => _ctx.handleMousedownVolumeBar($event)),\n onClick: _cache[10] || (_cache[10] = $event => _ctx.handleClickVolumeBar($event))\n }, [\n _createElementVNode(\"div\", _hoisted_7, [\n _createElementVNode(\"div\", {\n class: \"volume-bar-inner\",\n style: _normalizeStyle({ width: _ctx.volumeBarWidth })\n }, _hoisted_9, 4)\n ], 512)\n ], 32)\n ])\n ]),\n _createElementVNode(\"span\", _hoisted_10, [\n _createElementVNode(\"span\", _hoisted_11, _toDisplayString(_ctx.ptime), 1),\n _hoisted_12,\n _createElementVNode(\"span\", _hoisted_13, _toDisplayString(_ctx.dtime), 1)\n ]),\n _createElementVNode(\"div\", {\n class: \"bar-wrap\",\n ref: \"playBarWrap\",\n onMousedown: _cache[11] || (_cache[11] = $event => _ctx.handleMousedownPlayBar($event)),\n onTouchstart: _cache[12] || (_cache[12] = $event => _ctx.handleMousedownPlayBar($event)),\n onMousemove: _cache[13] || (_cache[13] = $event => _ctx.handleMousemovePlayBar($event)),\n onMouseenter: _cache[14] || (_cache[14] = ($event: any) => (_ctx.playBarTimeVisible = true)),\n onMouseleave: _cache[15] || (_cache[15] = ($event: any) => (_ctx.playBarTimeVisible = false))\n }, [\n _createElementVNode(\"div\", {\n class: _normalizeClass([\"bar-time\", { 'hidden': !_ctx.playBarTimeVisible }]),\n style: _normalizeStyle({ left: _ctx.playBarTimeLeft })\n }, _toDisplayString(_ctx.playBarTime), 7),\n _createElementVNode(\"div\", _hoisted_14, [\n _createElementVNode(\"div\", {\n class: \"loaded\",\n style: _normalizeStyle({ width: _ctx.loadedBarWidth })\n }, null, 4),\n _createElementVNode(\"div\", {\n class: \"played\",\n style: _normalizeStyle({ width: _ctx.playedBarWidth })\n }, _hoisted_16, 4)\n ])\n ], 544)\n ])\n ], 4))\n}","\nimport { computed, defineComponent, ref } from 'vue'\nimport { message } from 'ant-design-vue'\n\nconst secondToTime = (second = 0) => {\n if (second === 0 || isNaN(second)) return '00:00'\n\n const add0 = (num: number) => (num < 10 ? '0' + num : '' + num)\n const hour = Math.floor(second / 3600)\n const min = Math.floor((second - hour * 3600) / 60)\n const sec = Math.floor(second - hour * 3600 - min * 60)\n return (hour > 0 ? [hour, min, sec] : [min, sec]).map(add0).join(':')\n}\n\nconst getBoundingClientRectViewLeft = (element: HTMLElement) => {\n return element.getBoundingClientRect().left\n}\n\nexport default defineComponent({\n name: 'audio-player',\n props: {\n src: {\n type: String,\n required: true,\n },\n loop: {\n type: Boolean,\n required: true,\n },\n autoplay: {\n type: Boolean,\n default: false,\n },\n scale: {\n type: Number,\n default: 1,\n },\n },\n setup(props) {\n const audioRef = ref()\n const playBarWrap = ref()\n const volumeBarRef = ref()\n\n const volume = ref(0.5)\n const paused = ref(true)\n const currentTime = ref(0)\n const duration = ref(0)\n const loaded = ref(0)\n\n const playBarTimeVisible = ref(false)\n const playBarTime = ref('00:00')\n const playBarTimeLeft = ref('0')\n\n const ptime = computed(() => secondToTime(currentTime.value))\n const dtime = computed(() => secondToTime(duration.value))\n const playedBarWidth = computed(() => currentTime.value / duration.value * 100 + '%')\n const loadedBarWidth = computed(() => loaded.value / duration.value * 100 + '%')\n const volumeBarWidth = computed(() => volume.value * 100 + '%')\n\n const seek = (time: number) => {\n if (!audioRef.value) return\n\n time = Math.max(time, 0)\n time = Math.min(time, duration.value)\n\n audioRef.value.currentTime = time\n currentTime.value = time\n }\n\n const play = () => {\n if (!audioRef.value) return\n\n paused.value = false\n audioRef.value.play()\n }\n\n const pause = () => {\n if (!audioRef.value) return\n\n paused.value = true\n audioRef.value.pause()\n }\n\n const toggle = () => {\n if (paused.value) play() \n else pause()\n }\n\n const setVolume = (percentage: number) => {\n if (!audioRef.value) return\n\n percentage = Math.max(percentage, 0)\n percentage = Math.min(percentage, 1)\n\n audioRef.value.volume = percentage\n volume.value = percentage\n if (audioRef.value.muted && percentage !== 0) audioRef.value.muted = false\n }\n\n const handleDurationchange = () => {\n duration.value = audioRef.value?.duration || 0\n }\n\n const handleTimeupdate = () => {\n currentTime.value = audioRef.value?.currentTime || 0\n }\n\n const handlePlayed = () => {\n paused.value = false\n }\n\n const handleEnded = () => {\n if (!props.loop) pause()\n else {\n seek(0)\n play()\n }\n }\n\n const handleProgress = () => {\n loaded.value = audioRef.value?.buffered.length ? audioRef.value.buffered.end(audioRef.value.buffered.length - 1) : 0\n }\n\n const handleError = () => message.error('视频加载失败')\n\n const thumbMove = (e: MouseEvent | TouchEvent) => {\n if (!audioRef.value || !playBarWrap.value) return\n const clientX = 'clientX' in e ? e.clientX : e.changedTouches[0].clientX\n let percentage = (clientX - getBoundingClientRectViewLeft(playBarWrap.value)) / playBarWrap.value.clientWidth\n percentage = Math.max(percentage, 0)\n percentage = Math.min(percentage, 1)\n const time = percentage * duration.value\n\n audioRef.value.currentTime = time\n currentTime.value = time\n }\n\n const thumbUp = (e: MouseEvent | TouchEvent) => {\n if (!audioRef.value || !playBarWrap.value) return\n\n const clientX = 'clientX' in e ? e.clientX : e.changedTouches[0].clientX\n let percentage = (clientX - getBoundingClientRectViewLeft(playBarWrap.value)) / playBarWrap.value.clientWidth\n percentage = Math.max(percentage, 0)\n percentage = Math.min(percentage, 1)\n const time = percentage * duration.value\n\n audioRef.value.currentTime = time\n currentTime.value = time\n\n document.removeEventListener('mousemove', thumbMove)\n document.removeEventListener('touchmove', thumbMove)\n document.removeEventListener('mouseup', thumbUp)\n document.removeEventListener('touchend', thumbUp)\n }\n\n const handleMousedownPlayBar = () => {\n document.addEventListener('mousemove', thumbMove)\n document.addEventListener('touchmove', thumbMove)\n document.addEventListener('mouseup', thumbUp)\n document.addEventListener('touchend', thumbUp)\n }\n\n const volumeMove = (e: MouseEvent | TouchEvent) => {\n if (!volumeBarRef.value) return\n const clientX = 'clientX' in e ? e.clientX : e.changedTouches[0].clientX\n const percentage = (clientX - getBoundingClientRectViewLeft(volumeBarRef.value) - 5.5) / 35\n setVolume(percentage)\n }\n\n const volumeUp = () => {\n document.removeEventListener('mousemove', volumeMove)\n document.removeEventListener('touchmove', volumeMove)\n document.removeEventListener('mouseup', volumeUp)\n document.removeEventListener('touchend', volumeUp)\n }\n\n const handleMousedownVolumeBar = () => {\n document.addEventListener('mousemove', volumeMove)\n document.addEventListener('touchmove', volumeMove)\n document.addEventListener('mouseup', volumeUp)\n document.addEventListener('touchend', volumeUp)\n }\n\n const handleClickVolumeBar = (e: MouseEvent) => {\n if (!volumeBarRef.value) return\n const percentage = (e.clientX - getBoundingClientRectViewLeft(volumeBarRef.value) - 5.5) / 35\n setVolume(percentage)\n }\n\n const handleMousemovePlayBar = (e: MouseEvent) => {\n if (duration.value && playBarWrap.value) {\n const px = playBarWrap.value.getBoundingClientRect().left\n const tx = e.clientX - px\n if (tx < 0 || tx > playBarWrap.value.offsetWidth) return\n\n const time = duration.value * (tx / playBarWrap.value.offsetWidth)\n playBarTimeLeft.value = `${tx - (time >= 3600 ? 25 : 20)}px`\n playBarTime.value = secondToTime(time)\n playBarTimeVisible.value = true\n }\n }\n\n const toggleVolume = () => {\n if (!audioRef.value) return\n\n if (audioRef.value.muted) {\n audioRef.value.muted = false\n setVolume(0.5)\n }\n else {\n audioRef.value.muted = true\n setVolume(0)\n }\n }\n\n return {\n audioRef,\n playBarWrap,\n volumeBarRef,\n volume,\n paused,\n ptime,\n dtime,\n playBarTime,\n playBarTimeVisible,\n playBarTimeLeft,\n playedBarWidth,\n loadedBarWidth,\n volumeBarWidth,\n play,\n pause,\n toggle,\n setVolume,\n handleDurationchange,\n handleTimeupdate,\n handlePlayed,\n handleEnded,\n handleProgress,\n handleError,\n handleMousedownPlayBar,\n handleMousedownVolumeBar,\n handleClickVolumeBar,\n handleMousemovePlayBar,\n toggleVolume,\n }\n },\n})\n","import { render } from \"./AudioPlayer.vue?vue&type=template&id=5cabbb47&scoped=true&ts=true\"\nimport script from \"./AudioPlayer.vue?vue&type=script&lang=ts\"\nexport * from \"./AudioPlayer.vue?vue&type=script&lang=ts\"\n\nimport \"./AudioPlayer.vue?vue&type=style&index=0&id=5cabbb47&scoped=true&lang=scss\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-5cabbb47\"]])\n\nexport default __exports__","\nimport { computed, defineComponent, PropType } from 'vue'\nimport { storeToRefs } from 'pinia'\nimport { useMainStore, useSlidesStore } from '@/store'\nimport { PPTAudioElement } from '@/types/slides'\nimport { ContextmenuItem } from '@/components/Contextmenu/types'\nimport { VIEWPORT_SIZE } from '@/configs/canvas'\n\nimport AudioPlayer from './AudioPlayer.vue'\n\nexport default defineComponent({\n name: 'editable-element-audio',\n components: {\n AudioPlayer,\n },\n props: {\n elementInfo: {\n type: Object as PropType,\n required: true,\n },\n selectElement: {\n type: Function as PropType<(e: MouseEvent, element: PPTAudioElement, canMove?: boolean) => void>,\n required: true,\n },\n contextmenus: {\n type: Function as PropType<() => ContextmenuItem[]>,\n },\n },\n setup(props) {\n const { canvasScale, handleElementId } = storeToRefs(useMainStore())\n const { viewportRatio } = storeToRefs(useSlidesStore())\n\n const audioIconSize = computed(() => {\n return Math.min(props.elementInfo.width, props.elementInfo.height) + 'px'\n })\n const audioPlayerPosition = computed(() => {\n const canvasWidth = VIEWPORT_SIZE\n const canvasHeight = VIEWPORT_SIZE * viewportRatio.value\n\n const audioWidth = 280 / canvasScale.value\n const audioHeight = 50 / canvasScale.value\n\n const elWidth = props.elementInfo.width\n const elHeight = props.elementInfo.height\n const elLeft = props.elementInfo.left\n const elTop = props.elementInfo.top\n\n let left = 0\n let top = elHeight\n \n if (elLeft + audioWidth >= canvasWidth) left = elWidth - audioWidth\n if (elTop + elHeight + audioHeight >= canvasHeight) top = -audioHeight\n\n return {\n left: left + 'px',\n top: top + 'px',\n }\n })\n\n const handleSelectElement = (e: MouseEvent) => {\n if (props.elementInfo.lock) return\n e.stopPropagation()\n\n props.selectElement(e, props.elementInfo)\n }\n\n return {\n canvasScale,\n handleElementId,\n audioIconSize,\n audioPlayerPosition,\n handleSelectElement,\n }\n },\n})\n","import { render } from \"./index.vue?vue&type=template&id=66dd4448&scoped=true&ts=true\"\nimport script from \"./index.vue?vue&type=script&lang=ts\"\nexport * from \"./index.vue?vue&type=script&lang=ts\"\n\nimport \"./index.vue?vue&type=style&index=0&id=66dd4448&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-66dd4448\"]])\n\nexport default __exports__","\nimport { computed, defineComponent, PropType } from 'vue'\nimport { ElementTypes, PPTElement } from '@/types/slides'\nimport { ContextmenuItem } from '@/components/Contextmenu/types'\n\nimport useLockElement from '@/hooks/useLockElement'\nimport useDeleteElement from '@/hooks/useDeleteElement'\nimport useCombineElement from '@/hooks/useCombineElement'\nimport useOrderElement from '@/hooks/useOrderElement'\nimport useAlignElementToCanvas from '@/hooks/useAlignElementToCanvas'\nimport useCopyAndPasteElement from '@/hooks/useCopyAndPasteElement'\nimport useSelectAllElement from '@/hooks/useSelectAllElement'\n\nimport { ElementOrderCommands, ElementAlignCommands } from '@/types/edit'\n\nimport ImageElement from '@/views/components/element/ImageElement/index.vue'\nimport TextElement from '@/views/components/element/TextElement/index.vue'\nimport ShapeElement from '@/views/components/element/ShapeElement/index.vue'\nimport LineElement from '@/views/components/element/LineElement/index.vue'\nimport ChartElement from '@/views/components/element/ChartElement/index.vue'\nimport TableElement from '@/views/components/element/TableElement/index.vue'\nimport LatexElement from '@/views/components/element/LatexElement/index.vue'\nimport VideoElement from '@/views/components/element/VideoElement/index.vue'\nimport AudioElement from '@/views/components/element/AudioElement/index.vue'\n\nexport default defineComponent({\n name: 'editable-element',\n props: {\n elementInfo: {\n type: Object as PropType,\n required: true,\n },\n elementIndex: {\n type: Number,\n required: true,\n },\n isMultiSelect: {\n type: Boolean,\n required: true,\n },\n selectElement: {\n type: Function as PropType<(e: MouseEvent, element: PPTElement, canMove?: boolean) => void>,\n required: true,\n },\n openLinkDialog: {\n type: Function as PropType<() => void>,\n required: true,\n },\n },\n setup(props) {\n const currentElementComponent = computed(() => {\n const elementTypeMap = {\n [ElementTypes.IMAGE]: ImageElement,\n [ElementTypes.TEXT]: TextElement,\n [ElementTypes.SHAPE]: ShapeElement,\n [ElementTypes.LINE]: LineElement,\n [ElementTypes.CHART]: ChartElement,\n [ElementTypes.TABLE]: TableElement,\n [ElementTypes.LATEX]: LatexElement,\n [ElementTypes.VIDEO]: VideoElement,\n [ElementTypes.AUDIO]: AudioElement,\n }\n return elementTypeMap[props.elementInfo.type] || null\n })\n\n const { orderElement } = useOrderElement()\n const { alignElementToCanvas } = useAlignElementToCanvas()\n const { combineElements, uncombineElements } = useCombineElement()\n const { deleteElement } = useDeleteElement()\n const { lockElement, unlockElement } = useLockElement()\n const { copyElement, pasteElement, cutElement } = useCopyAndPasteElement()\n const { selectAllElement } = useSelectAllElement()\n\n const contextmenus = (): ContextmenuItem[] => {\n if (props.elementInfo.lock) {\n return [{\n text: '解锁', \n handler: () => unlockElement(props.elementInfo),\n }]\n }\n\n return [\n {\n text: '剪切',\n subText: 'Ctrl + X',\n handler: cutElement,\n },\n {\n text: '复制',\n subText: 'Ctrl + C',\n handler: copyElement,\n },\n {\n text: '粘贴',\n subText: 'Ctrl + V',\n handler: pasteElement,\n },\n { divider: true },\n {\n text: '水平居中',\n handler: () => alignElementToCanvas(ElementAlignCommands.HORIZONTAL),\n children: [\n { text: '水平垂直居中', handler: () => alignElementToCanvas(ElementAlignCommands.CENTER), },\n { text: '水平居中', handler: () => alignElementToCanvas(ElementAlignCommands.HORIZONTAL) },\n { text: '左对齐', handler: () => alignElementToCanvas(ElementAlignCommands.LEFT) },\n { text: '右对齐', handler: () => alignElementToCanvas(ElementAlignCommands.RIGHT) },\n ],\n },\n {\n text: '垂直居中',\n handler: () => alignElementToCanvas(ElementAlignCommands.VERTICAL),\n children: [\n { text: '水平垂直居中', handler: () => alignElementToCanvas(ElementAlignCommands.CENTER) },\n { text: '垂直居中', handler: () => alignElementToCanvas(ElementAlignCommands.VERTICAL) },\n { text: '顶部对齐', handler: () => alignElementToCanvas(ElementAlignCommands.TOP) },\n { text: '底部对齐', handler: () => alignElementToCanvas(ElementAlignCommands.BOTTOM) },\n ],\n },\n { divider: true },\n {\n text: '置于顶层',\n disable: props.isMultiSelect && !props.elementInfo.groupId,\n handler: () => orderElement(props.elementInfo, ElementOrderCommands.TOP),\n children: [\n { text: '置于顶层', handler: () => orderElement(props.elementInfo, ElementOrderCommands.TOP) },\n { text: '上移一层', handler: () => orderElement(props.elementInfo, ElementOrderCommands.UP) },\n ],\n },\n {\n text: '置于底层',\n disable: props.isMultiSelect && !props.elementInfo.groupId,\n handler: () => orderElement(props.elementInfo, ElementOrderCommands.BOTTOM),\n children: [\n { text: '置于底层', handler: () => orderElement(props.elementInfo, ElementOrderCommands.BOTTOM) },\n { text: '下移一层', handler: () => orderElement(props.elementInfo, ElementOrderCommands.DOWN) },\n ],\n },\n { divider: true },\n {\n text: '设置链接',\n handler: props.openLinkDialog,\n },\n {\n text: props.elementInfo.groupId ? '取消组合' : '组合',\n subText: 'Ctrl + G',\n handler: props.elementInfo.groupId ? uncombineElements : combineElements,\n hide: !props.isMultiSelect,\n },\n {\n text: '全选',\n subText: 'Ctrl + A',\n handler: selectAllElement,\n },\n {\n text: '锁定',\n subText: 'Ctrl + L',\n handler: lockElement,\n },\n {\n text: '删除',\n subText: 'Delete',\n handler: deleteElement,\n },\n ]\n }\n\n return {\n currentElementComponent,\n contextmenus,\n }\n },\n})\n","import { render } from \"./EditableElement.vue?vue&type=template&id=19fcaa81&ts=true\"\nimport script from \"./EditableElement.vue?vue&type=script&lang=ts\"\nexport * from \"./EditableElement.vue?vue&type=script&lang=ts\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render]])\n\nexport default __exports__","import { normalizeClass as _normalizeClass, normalizeStyle as _normalizeStyle, openBlock as _openBlock, createElementBlock as _createElementBlock } from \"vue\"\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n return (_openBlock(), _createElementBlock(\"div\", {\n class: _normalizeClass(`mouse-selection quadrant-${_ctx.quadrant}`),\n style: _normalizeStyle({\n top: _ctx.top + 'px',\n left: _ctx.left + 'px',\n width: _ctx.width + 'px',\n height: _ctx.height + 'px',\n })\n }, null, 6))\n}","\nimport { defineComponent } from 'vue'\n\nexport default defineComponent({\n name: 'mouse-selection',\n props: {\n top: {\n type: Number,\n required: true,\n },\n left: {\n type: Number,\n required: true,\n },\n width: {\n type: Number,\n required: true,\n },\n height: {\n type: Number,\n required: true,\n },\n quadrant: {\n type: Number,\n required: true,\n validator(value: number) {\n return [1, 2, 3, 4].includes(value)\n },\n },\n }\n})\n","import { render } from \"./MouseSelection.vue?vue&type=template&id=25d4b5f8&scoped=true&ts=true\"\nimport script from \"./MouseSelection.vue?vue&type=script&lang=ts\"\nexport * from \"./MouseSelection.vue?vue&type=script&lang=ts\"\n\nimport \"./MouseSelection.vue?vue&type=style&index=0&id=25d4b5f8&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-25d4b5f8\"]])\n\nexport default __exports__","import { resolveComponent as _resolveComponent, openBlock as _openBlock, createBlock as _createBlock, createCommentVNode as _createCommentVNode, normalizeStyle as _normalizeStyle, createElementBlock as _createElementBlock } from \"vue\"\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _component_GridLines = _resolveComponent(\"GridLines\")!\n\n return (_openBlock(), _createElementBlock(\"div\", {\n class: \"viewport-background\",\n style: _normalizeStyle(_ctx.backgroundStyle)\n }, [\n (_ctx.showGridLines)\n ? (_openBlock(), _createBlock(_component_GridLines, { key: 0 }))\n : _createCommentVNode(\"\", true)\n ], 4))\n}","import { normalizeStyle as _normalizeStyle, createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-9b94cc34\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = { class: \"grid-lines\" }\nconst _hoisted_2 = [\"d\", \"stroke\"]\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n return (_openBlock(), _createElementBlock(\"svg\", _hoisted_1, [\n _createElementVNode(\"path\", {\n style: _normalizeStyle({\n transform: `scale(${_ctx.canvasScale})`,\n }),\n d: _ctx.path,\n fill: \"none\",\n stroke: _ctx.gridColor,\n \"stroke-width\": \"0.3\",\n \"stroke-dasharray\": \"5\"\n }, null, 12, _hoisted_2)\n ]))\n}","\nimport { defineComponent, computed } from 'vue'\nimport tinycolor from 'tinycolor2'\nimport { storeToRefs } from 'pinia'\nimport { useMainStore, useSlidesStore } from '@/store'\nimport { VIEWPORT_SIZE } from '@/configs/canvas'\nimport { SlideBackground } from '@/types/slides'\n\nexport default defineComponent({\n name: 'grid-lines',\n setup() {\n const { canvasScale } = storeToRefs(useMainStore())\n const { currentSlide, viewportRatio } = storeToRefs(useSlidesStore())\n\n const background = computed(() => currentSlide.value?.background)\n\n // 计算网格线的颜色,避免与背景的颜色太接近\n const gridColor = computed(() => {\n const bgColor = background.value?.color || '#fff'\n const colorList = ['#000', '#fff']\n return tinycolor.mostReadable(bgColor, colorList, { includeFallbackColors: true }).setAlpha(.5).toRgbString()\n })\n\n const gridSize = 50\n\n // 计算网格路径\n const getPath = () => {\n const maxX = VIEWPORT_SIZE\n const maxY = VIEWPORT_SIZE * viewportRatio.value\n\n let path = ''\n for (let i = 0; i <= Math.floor(maxY / gridSize); i++) {\n path += `M0 ${i * gridSize} L${maxX} ${i * gridSize} `\n }\n for (let i = 0; i <= Math.floor(maxX / gridSize); i++) {\n path += `M${i * gridSize} 0 L${i * gridSize} ${maxY} `\n }\n return path\n }\n\n return {\n canvasScale,\n gridColor,\n width: VIEWPORT_SIZE,\n height: VIEWPORT_SIZE * viewportRatio.value,\n path: getPath(),\n }\n },\n})\n","import { render } from \"./GridLines.vue?vue&type=template&id=9b94cc34&scoped=true&ts=true\"\nimport script from \"./GridLines.vue?vue&type=script&lang=ts\"\nexport * from \"./GridLines.vue?vue&type=script&lang=ts\"\n\nimport \"./GridLines.vue?vue&type=style&index=0&id=9b94cc34&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-9b94cc34\"]])\n\nexport default __exports__","import { Ref, computed } from 'vue'\nimport { SlideBackground } from '@/types/slides'\n\n// 将页面背景数据转换为css样式\nexport default (background: Ref) => {\n const backgroundStyle = computed(() => {\n if (!background.value) return { backgroundColor: '#fff' }\n\n const {\n type,\n color,\n image,\n imageSize,\n gradientColor,\n gradientRotate,\n gradientType,\n } = background.value\n\n // 纯色背景\n if (type === 'solid') return { backgroundColor: color }\n\n // 背景图模式\n // 包括:背景图、背景大小,是否重复\n else if (type === 'image') {\n if (!image) return { backgroundColor: '#fff' }\n if (imageSize === 'repeat') {\n return {\n backgroundImage: `url(${image}`,\n backgroundRepeat: 'repeat',\n backgroundSize: 'contain',\n }\n }\n return {\n backgroundImage: `url(${image}`,\n backgroundRepeat: 'no-repeat',\n backgroundSize: imageSize || 'cover',\n }\n }\n\n // 渐变色背景\n else if (type === 'gradient') {\n const rotate = gradientRotate || 0\n const color1 = gradientColor ? gradientColor[0] : '#fff'\n const color2 = gradientColor ? gradientColor[1] : '#fff'\n \n if (gradientType === 'radial') return { backgroundImage: `radial-gradient(${color1}, ${color2}` }\n return { backgroundImage: `linear-gradient(${rotate}deg, ${color1}, ${color2}` }\n }\n\n return { backgroundColor: '#fff' }\n })\n\n return {\n backgroundStyle,\n }\n}","\nimport { computed, defineComponent } from 'vue'\nimport { storeToRefs } from 'pinia'\nimport { useMainStore, useSlidesStore } from '@/store'\nimport { SlideBackground } from '@/types/slides'\nimport GridLines from './GridLines.vue'\nimport useSlideBackgroundStyle from '@/hooks/useSlideBackgroundStyle'\n\nexport default defineComponent({\n name: 'viewport-background',\n components: {\n GridLines,\n },\n setup() {\n const { showGridLines } = storeToRefs(useMainStore())\n const { currentSlide } = storeToRefs(useSlidesStore())\n const background = computed(() => currentSlide.value?.background)\n\n const { backgroundStyle } = useSlideBackgroundStyle(background)\n\n return {\n showGridLines,\n backgroundStyle,\n }\n },\n})\n","import { render } from \"./ViewportBackground.vue?vue&type=template&id=4d0018d8&scoped=true&ts=true\"\nimport script from \"./ViewportBackground.vue?vue&type=script&lang=ts\"\nexport * from \"./ViewportBackground.vue?vue&type=script&lang=ts\"\n\nimport \"./ViewportBackground.vue?vue&type=style&index=0&id=4d0018d8&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-4d0018d8\"]])\n\nexport default __exports__","import { normalizeClass as _normalizeClass, normalizeStyle as _normalizeStyle, createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from \"vue\"\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n return (_openBlock(), _createElementBlock(\"div\", {\n class: \"alignment-line\",\n style: _normalizeStyle({ left: _ctx.left, top: _ctx.top })\n }, [\n _createElementVNode(\"div\", {\n class: _normalizeClass(['line', _ctx.type]),\n style: _normalizeStyle(_ctx.sizeStyle)\n }, null, 6)\n ], 4))\n}","\nimport { computed, PropType, defineComponent } from 'vue'\nimport { storeToRefs } from 'pinia'\nimport { useMainStore } from '@/store'\nimport { AlignmentLineAxis } from '@/types/edit'\n\nexport default defineComponent({\n name: 'alignment-line',\n props: {\n type: {\n type: String as PropType<'vertical' | 'horizontal'>,\n required: true,\n },\n axis: {\n type: Object as PropType,\n required: true,\n },\n length: {\n type: Number,\n required: true,\n },\n },\n setup(props) {\n const { canvasScale } = storeToRefs(useMainStore())\n\n // 吸附对齐线的位置\n const left = computed(() => props.axis.x * canvasScale.value + 'px')\n const top = computed(() => props.axis.y * canvasScale.value + 'px')\n\n // 吸附对齐线的长度\n const sizeStyle = computed(() => {\n if (props.type === 'vertical') return { height: props.length * canvasScale.value + 'px' }\n return { width: props.length * canvasScale.value + 'px' }\n })\n\n return {\n left,\n top,\n sizeStyle,\n }\n },\n})\n","import { render } from \"./AlignmentLine.vue?vue&type=template&id=62c9018e&scoped=true&ts=true\"\nimport script from \"./AlignmentLine.vue?vue&type=script&lang=ts\"\nexport * from \"./AlignmentLine.vue?vue&type=script&lang=ts\"\n\nimport \"./AlignmentLine.vue?vue&type=style&index=0&id=62c9018e&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-62c9018e\"]])\n\nexport default __exports__","import { renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, toDisplayString as _toDisplayString, createElementVNode as _createElementVNode, normalizeClass as _normalizeClass, normalizeStyle as _normalizeStyle, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-1c631df3\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = { class: \"ruler\" }\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n return (_openBlock(), _createElementBlock(\"div\", _hoisted_1, [\n _createElementVNode(\"div\", {\n class: \"h\",\n style: _normalizeStyle({\n width: _ctx.viewportStyles.width * _ctx.canvasScale + 'px',\n left: _ctx.viewportStyles.left + 'px',\n })\n }, [\n (_openBlock(), _createElementBlock(_Fragment, null, _renderList(10, (marker) => {\n return _createElementVNode(\"div\", {\n class: _normalizeClass([\"ruler-marker-100\", { 'hide': _ctx.markerSize < 36, 'omit': _ctx.markerSize < 72 }]),\n key: `marker-100-${marker}`\n }, [\n _createElementVNode(\"span\", null, _toDisplayString(marker * 100), 1)\n ], 2)\n }), 64))\n ], 4),\n _createElementVNode(\"div\", {\n class: \"v\",\n style: _normalizeStyle({\n height: _ctx.viewportStyles.height * _ctx.canvasScale + 'px',\n top: _ctx.viewportStyles.top + 'px',\n })\n }, [\n (_openBlock(), _createElementBlock(_Fragment, null, _renderList(10, (marker) => {\n return _createElementVNode(\"div\", {\n class: _normalizeClass([\"ruler-marker-100\", { 'hide': _ctx.markerSize < 36, 'omit': _ctx.markerSize < 72 }]),\n key: marker,\n style: _normalizeStyle({ height: _ctx.markerSize + 'px' })\n }, [\n _createElementVNode(\"span\", null, _toDisplayString(marker * 100), 1)\n ], 6)\n }), 64))\n ], 4)\n ]))\n}","\nimport { computed, defineComponent, PropType } from 'vue'\nimport { storeToRefs } from 'pinia'\nimport { useMainStore } from '@/store'\n\ninterface ViewportStyles {\n top: number;\n left: number;\n width: number;\n height: number;\n}\n\nexport default defineComponent({\n props: {\n viewportStyles: {\n type: Object as PropType,\n required: true,\n },\n },\n setup(props) {\n const { canvasScale } = storeToRefs(useMainStore())\n\n const markerSize = computed(() => {\n return props.viewportStyles.width * canvasScale.value / 10\n })\n\n return {\n canvasScale,\n markerSize,\n }\n },\n})\n","import { render } from \"./Ruler.vue?vue&type=template&id=1c631df3&scoped=true&ts=true\"\nimport script from \"./Ruler.vue?vue&type=script&lang=ts\"\nexport * from \"./Ruler.vue?vue&type=script&lang=ts\"\n\nimport \"./Ruler.vue?vue&type=style&index=0&id=1c631df3&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-1c631df3\"]])\n\nexport default __exports__","import { createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock, createCommentVNode as _createCommentVNode, normalizeClass as _normalizeClass, normalizeStyle as _normalizeStyle, withModifiers as _withModifiers, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-15b60d90\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = [\"width\", \"height\"]\nconst _hoisted_2 = [\"d\"]\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n return (_openBlock(), _createElementBlock(\"div\", {\n class: \"element-create-selection\",\n ref: \"selectionRef\",\n onMousedown: _cache[0] || (_cache[0] = _withModifiers($event => _ctx.createSelection($event), [\"stop\"])),\n onContextmenu: _cache[1] || (_cache[1] = _withModifiers(() => {}, [\"stop\",\"prevent\"]))\n }, [\n (_ctx.start && _ctx.end)\n ? (_openBlock(), _createElementBlock(\"div\", {\n key: 0,\n class: _normalizeClass(['selection', _ctx.creatingElement?.type]),\n style: _normalizeStyle(_ctx.position)\n }, [\n (_ctx.creatingElement?.type === 'line' && _ctx.lineData)\n ? (_openBlock(), _createElementBlock(\"svg\", {\n key: 0,\n overflow: \"visible\",\n width: _ctx.lineData.svgWidth,\n height: _ctx.lineData.svgHeight\n }, [\n _createElementVNode(\"path\", {\n d: _ctx.lineData.path,\n stroke: \"#d14424\",\n fill: \"none\",\n \"stroke-width\": \"2\"\n }, null, 8, _hoisted_2)\n ], 8, _hoisted_1))\n : _createCommentVNode(\"\", true)\n ], 6))\n : _createCommentVNode(\"\", true)\n ], 544))\n}","\nimport { computed, defineComponent, onMounted, ref } from 'vue'\nimport { storeToRefs } from 'pinia'\nimport { useMainStore, useKeyboardStore } from '@/store'\n\nexport default defineComponent({\n name: 'element-create-selection',\n emits: ['created'],\n setup(props, { emit }) {\n const mainStore = useMainStore()\n const { creatingElement } = storeToRefs(mainStore)\n const { ctrlOrShiftKeyActive } = storeToRefs(useKeyboardStore())\n\n const start = ref<[number, number]>()\n const end = ref<[number, number]>()\n\n const selectionRef = ref()\n const offset = ref({\n x: 0,\n y: 0,\n })\n onMounted(() => {\n if (!selectionRef.value) return\n const { x, y } = selectionRef.value.getBoundingClientRect()\n offset.value = { x, y }\n })\n\n // 鼠标拖动创建元素生成位置大小\n // 获取范围的起始位置和终点位置\n const createSelection = (e: MouseEvent) => {\n let isMouseDown = true\n\n const startPageX = e.pageX\n const startPageY = e.pageY\n start.value = [startPageX, startPageY]\n\n document.onmousemove = e => {\n if (!creatingElement.value || !isMouseDown) return\n\n let currentPageX = e.pageX\n let currentPageY = e.pageY\n\n // 按住Ctrl键或者Shift键时:\n // 对于非线条元素需要锁定宽高比例,对于线条元素需要锁定水平或垂直方向\n if (ctrlOrShiftKeyActive.value) {\n const moveX = currentPageX - startPageX\n const moveY = currentPageY - startPageY\n\n // 水平和垂直方向的拖动距离,后面以拖动距离较大的方向为基础计算另一方向的数据\n const absX = Math.abs(moveX)\n const absY = Math.abs(moveY)\n\n if (creatingElement.value.type === 'shape') {\n\n // 判断是否为反向拖动:从左上到右下为正向操作,此外所有情况都是反向操作\n const isOpposite = (moveY > 0 && moveX < 0) || (moveY < 0 && moveX > 0)\n\n if (absX > absY) {\n currentPageY = isOpposite ? startPageY - moveX : startPageY + moveX\n }\n else {\n currentPageX = isOpposite ? startPageX - moveY : startPageX + moveY\n }\n }\n\n else if (creatingElement.value.type === 'line') {\n if (absX > absY) currentPageY = startPageY\n else currentPageX = startPageX\n }\n }\n\n end.value = [currentPageX, currentPageY]\n }\n\n document.onmouseup = e => {\n document.onmousemove = null\n document.onmouseup = null\n\n if (e.button === 2) {\n setTimeout(() => mainStore.setCreatingElement(null), 0)\n return\n }\n\n isMouseDown = false\n\n const endPageX = e.pageX\n const endPageY = e.pageY\n\n const minSize = 30\n\n if (\n creatingElement.value?.type === 'line' &&\n (Math.abs(endPageX - startPageX) >= minSize || Math.abs(endPageY - startPageY) >= minSize)\n ) {\n emit('created', {\n start: start.value,\n end: end.value,\n })\n }\n else if (\n creatingElement.value?.type !== 'line' &&\n (Math.abs(endPageX - startPageX) >= minSize && Math.abs(endPageY - startPageY) >= minSize)\n ) {\n emit('created', {\n start: start.value,\n end: end.value,\n })\n }\n else {\n const defaultSize = 200\n const minX = Math.min(endPageX, startPageX)\n const minY = Math.min(endPageY, startPageY)\n const maxX = Math.max(endPageX, startPageX)\n const maxY = Math.max(endPageY, startPageY)\n const offsetX = maxX - minX >= minSize ? maxX - minX : defaultSize\n const offsetY = maxY - minY >= minSize ? maxY - minY : defaultSize\n emit('created', {\n start: [minX, minY],\n end: [minX + offsetX, minY + offsetY],\n })\n }\n }\n }\n\n // 绘制线条的路径相关数据(仅当绘制元素类型为线条时使用)\n const lineData = computed(() => {\n if (!start.value || !end.value) return null\n if (!creatingElement.value || creatingElement.value.type !== 'line') return null\n\n const [_startX, _startY] = start.value\n const [_endX, _endY] = end.value\n const minX = Math.min(_startX, _endX)\n const maxX = Math.max(_startX, _endX)\n const minY = Math.min(_startY, _endY)\n const maxY = Math.max(_startY, _endY)\n\n const svgWidth = maxX - minX >= 24 ? maxX - minX : 24\n const svgHeight = maxY - minY >= 24 ? maxY - minY : 24\n\n const startX = _startX === minX ? 0 : maxX - minX\n const startY = _startY === minY ? 0 : maxY - minY\n const endX = _endX === minX ? 0 : maxX - minX\n const endY = _endY === minY ? 0 : maxY - minY\n\n const path = `M${startX}, ${startY} L${endX}, ${endY}`\n\n return {\n svgWidth,\n svgHeight,\n startX,\n startY,\n endX,\n endY,\n path,\n }\n })\n\n // 根据生成范围的起始位置和终点位置,计算元素创建时的位置和大小\n const position = computed(() => {\n if (!start.value || !end.value) return {}\n\n const [startX, startY] = start.value\n const [endX, endY] = end.value\n const minX = Math.min(startX, endX)\n const maxX = Math.max(startX, endX)\n const minY = Math.min(startY, endY)\n const maxY = Math.max(startY, endY)\n\n const width = maxX - minX\n const height = maxY - minY\n\n return {\n left: minX - offset.value.x + 'px',\n top: minY - offset.value.y + 'px',\n width: width + 'px',\n height: height + 'px',\n }\n })\n\n return {\n selectionRef,\n start,\n end,\n creatingElement,\n createSelection,\n lineData,\n position,\n }\n },\n})\n","import { render } from \"./ElementCreateSelection.vue?vue&type=template&id=15b60d90&scoped=true&ts=true\"\nimport script from \"./ElementCreateSelection.vue?vue&type=script&lang=ts\"\nexport * from \"./ElementCreateSelection.vue?vue&type=script&lang=ts\"\n\nimport \"./ElementCreateSelection.vue?vue&type=style&index=0&id=15b60d90&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-15b60d90\"]])\n\nexport default __exports__","import { renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, resolveComponent as _resolveComponent, normalizeStyle as _normalizeStyle, createBlock as _createBlock, withModifiers as _withModifiers, createCommentVNode as _createCommentVNode } from \"vue\"\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _component_BorderLine = _resolveComponent(\"BorderLine\")!\n const _component_ResizeHandler = _resolveComponent(\"ResizeHandler\")!\n\n return (_openBlock(), _createElementBlock(\"div\", {\n class: \"multi-select-operate\",\n style: _normalizeStyle({\n left: _ctx.range.minX * _ctx.canvasScale + 'px',\n top: _ctx.range.minY * _ctx.canvasScale + 'px',\n })\n }, [\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.borderLines, (line) => {\n return (_openBlock(), _createBlock(_component_BorderLine, {\n key: line.type,\n type: line.type,\n style: _normalizeStyle(line.style)\n }, null, 8, [\"type\", \"style\"]))\n }), 128)),\n (!_ctx.disableResize)\n ? (_openBlock(true), _createElementBlock(_Fragment, { key: 0 }, _renderList(_ctx.resizeHandlers, (point) => {\n return (_openBlock(), _createBlock(_component_ResizeHandler, {\n key: point.direction,\n type: point.direction,\n style: _normalizeStyle(point.style),\n onMousedown: _withModifiers(($event: any) => (_ctx.scaleMultiElement($event, _ctx.range, point.direction)), [\"stop\"])\n }, null, 8, [\"type\", \"style\", \"onMousedown\"]))\n }), 128))\n : _createCommentVNode(\"\", true)\n ], 4))\n}","import { computed, Ref } from 'vue'\nimport { OperateResizeHandlers, OperateBorderLines } from '@/types/edit'\n\nexport default (width: Ref, height: Ref) => {\n // 元素缩放点\n const resizeHandlers = computed(() => {\n return [\n { direction: OperateResizeHandlers.LEFT_TOP, style: {} },\n { direction: OperateResizeHandlers.TOP, style: {left: width.value / 2 + 'px'} },\n { direction: OperateResizeHandlers.RIGHT_TOP, style: {left: width.value + 'px'} },\n { direction: OperateResizeHandlers.LEFT, style: {top: height.value / 2 + 'px'} },\n { direction: OperateResizeHandlers.RIGHT, style: {left: width.value + 'px', top: height.value / 2 + 'px'} },\n { direction: OperateResizeHandlers.LEFT_BOTTOM, style: {top: height.value + 'px'} },\n { direction: OperateResizeHandlers.BOTTOM, style: {left: width.value / 2 + 'px', top: height.value + 'px'} },\n { direction: OperateResizeHandlers.RIGHT_BOTTOM, style: {left: width.value + 'px', top: height.value + 'px'} },\n ]\n })\n\n // 文本元素缩放点\n const textElementResizeHandlers = computed(() => {\n return [\n { direction: OperateResizeHandlers.LEFT, style: {top: height.value / 2 + 'px'} },\n { direction: OperateResizeHandlers.RIGHT, style: {left: width.value + 'px', top: height.value / 2 + 'px'} },\n ]\n })\n\n // 元素选中边框线\n const borderLines = computed(() => {\n return [\n { type: OperateBorderLines.T, style: {width: width.value + 'px'} },\n { type: OperateBorderLines.B, style: {top: height.value + 'px', width: width.value + 'px'} },\n { type: OperateBorderLines.L, style: {height: height.value + 'px'} },\n { type: OperateBorderLines.R, style: {left: width.value + 'px', height: height.value + 'px'} },\n ]\n })\n\n return {\n resizeHandlers,\n textElementResizeHandlers,\n borderLines,\n }\n}","import { normalizeClass as _normalizeClass, openBlock as _openBlock, createElementBlock as _createElementBlock } from \"vue\"\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n return (_openBlock(), _createElementBlock(\"div\", {\n class: _normalizeClass(['resize-handler', _ctx.rotateClassName, _ctx.type])\n }, null, 2))\n}","\nimport { computed, defineComponent, PropType } from 'vue'\nimport { OperateResizeHandlers } from '@/types/edit'\n\nexport default defineComponent({\n name: 'resize-handler',\n props: {\n type: {\n type: String as PropType,\n default: '',\n },\n rotate: {\n type: Number,\n default: 0,\n },\n },\n setup(props) {\n const rotateClassName = computed(() => {\n const prefix = 'rotate-'\n const rotate = props.rotate\n if (rotate > -22.5 && rotate <= 22.5) return prefix + 0\n else if (rotate > 22.5 && rotate <= 67.5) return prefix + 45\n else if (rotate > 67.5 && rotate <= 112.5) return prefix + 90\n else if (rotate > 112.5 && rotate <= 157.5) return prefix + 135\n else if (rotate > 157.5 || rotate <= -157.5) return prefix + 0\n else if (rotate > -157.5 && rotate <= -112.5) return prefix + 45\n else if (rotate > -112.5 && rotate <= -67.5) return prefix + 90\n else if (rotate > -67.5 && rotate <= -22.5) return prefix + 135\n return prefix + 0\n })\n\n return {\n rotateClassName,\n }\n },\n})\n","import { render } from \"./ResizeHandler.vue?vue&type=template&id=c9559332&scoped=true&ts=true\"\nimport script from \"./ResizeHandler.vue?vue&type=script&lang=ts\"\nexport * from \"./ResizeHandler.vue?vue&type=script&lang=ts\"\n\nimport \"./ResizeHandler.vue?vue&type=style&index=0&id=c9559332&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-c9559332\"]])\n\nexport default __exports__","import { normalizeClass as _normalizeClass, openBlock as _openBlock, createElementBlock as _createElementBlock } from \"vue\"\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n return (_openBlock(), _createElementBlock(\"div\", {\n class: _normalizeClass(['border-line', _ctx.type, { 'wide': _ctx.isWide }])\n }, null, 2))\n}","\nimport { defineComponent, PropType } from 'vue'\nimport { OperateBorderLines } from '@/types/edit'\n\nexport default defineComponent({\n name: 'border-line',\n props: {\n type: {\n type: String as PropType,\n required: true,\n },\n isWide: {\n type: Boolean,\n default: false,\n },\n },\n})\n","import { render } from \"./BorderLine.vue?vue&type=template&id=1430bedc&scoped=true&ts=true\"\nimport script from \"./BorderLine.vue?vue&type=script&lang=ts\"\nexport * from \"./BorderLine.vue?vue&type=script&lang=ts\"\n\nimport \"./BorderLine.vue?vue&type=style&index=0&id=1430bedc&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-1430bedc\"]])\n\nexport default __exports__","\nimport { computed, defineComponent, ref, PropType, watchEffect } from 'vue'\nimport { storeToRefs } from 'pinia'\nimport { useMainStore } from '@/store'\nimport { PPTElement } from '@/types/slides'\nimport { getElementListRange } from '@/utils/element'\nimport { OperateResizeHandlers, MultiSelectRange } from '@/types/edit'\nimport useCommonOperate from '../hooks/useCommonOperate'\n\nimport ResizeHandler from './ResizeHandler.vue'\nimport BorderLine from './BorderLine.vue'\n\nexport default defineComponent({\n name: 'multi-select-operate',\n components: {\n ResizeHandler,\n BorderLine,\n },\n props: {\n elementList: {\n type: Array as PropType,\n required: true,\n },\n scaleMultiElement: {\n type: Function as PropType<(e: MouseEvent, range: MultiSelectRange, command: OperateResizeHandlers) => void>,\n required: true,\n },\n },\n setup(props) {\n const { activeElementIdList, canvasScale } = storeToRefs(useMainStore())\n\n const localActiveElementList = computed(() => props.elementList.filter(el => activeElementIdList.value.includes(el.id)))\n\n const range = ref({\n minX: 0,\n maxX: 0,\n minY: 0,\n maxY: 0,\n })\n\n // 根据多选元素整体在画布中的范围,计算边框线和缩放点的位置信息\n const width = computed(() => (range.value.maxX - range.value.minX) * canvasScale.value)\n const height = computed(() => (range.value.maxY - range.value.minY) * canvasScale.value)\n const { resizeHandlers, borderLines } = useCommonOperate(width, height)\n\n // 计算多选元素整体在画布中的范围\n const setRange = () => {\n const { minX, maxX, minY, maxY } = getElementListRange(localActiveElementList.value)\n range.value = { minX, maxX, minY, maxY }\n }\n watchEffect(setRange)\n\n // 禁用多选状态下缩放:仅未旋转的图片和形状可以在多选状态下缩放\n const disableResize = computed(() => {\n return localActiveElementList.value.some(item => {\n if (\n (item.type === 'image' || item.type === 'shape') && \n !item.rotate\n ) return false\n return true\n })\n })\n\n return {\n range,\n canvasScale,\n borderLines,\n disableResize,\n resizeHandlers,\n }\n },\n})\n","import { render } from \"./MultiSelectOperate.vue?vue&type=template&id=55c8df22&scoped=true&ts=true\"\nimport script from \"./MultiSelectOperate.vue?vue&type=script&lang=ts\"\nexport * from \"./MultiSelectOperate.vue?vue&type=script&lang=ts\"\n\nimport \"./MultiSelectOperate.vue?vue&type=style&index=0&id=55c8df22&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-55c8df22\"]])\n\nexport default __exports__","import { resolveDynamicComponent as _resolveDynamicComponent, openBlock as _openBlock, createBlock as _createBlock, createCommentVNode as _createCommentVNode, renderList as _renderList, Fragment as _Fragment, createElementBlock as _createElementBlock, toDisplayString as _toDisplayString, resolveComponent as _resolveComponent, withModifiers as _withModifiers, normalizeClass as _normalizeClass, normalizeStyle as _normalizeStyle, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-10df1a8e\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = {\n key: 1,\n class: \"animation-index\"\n}\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _component_LinkHandler = _resolveComponent(\"LinkHandler\")!\n\n return (_openBlock(), _createElementBlock(\"div\", {\n class: _normalizeClass([\"operate\", { 'multi-select': _ctx.isMultiSelect && !_ctx.isActive }]),\n style: _normalizeStyle({\r\n top: _ctx.elementInfo.top * _ctx.canvasScale + 'px',\r\n left: _ctx.elementInfo.left * _ctx.canvasScale + 'px',\r\n transform: `rotate(${_ctx.rotate}deg)`,\r\n transformOrigin: `${_ctx.elementInfo.width * _ctx.canvasScale / 2}px ${_ctx.height * _ctx.canvasScale / 2}px`,\r\n })\n }, [\n (_ctx.isSelected)\n ? (_openBlock(), _createBlock(_resolveDynamicComponent(_ctx.currentOperateComponent), {\n key: 0,\n elementInfo: _ctx.elementInfo,\n handlerVisible: !_ctx.elementInfo.lock && (_ctx.isActiveGroupElement || !_ctx.isMultiSelect),\n rotateElement: _ctx.rotateElement,\n scaleElement: _ctx.scaleElement,\n dragLineElement: _ctx.dragLineElement\n }, null, 8, [\"elementInfo\", \"handlerVisible\", \"rotateElement\", \"scaleElement\", \"dragLineElement\"]))\n : _createCommentVNode(\"\", true),\n (_ctx.toolbarState === 'elAnimation' && _ctx.elementIndexListInAnimation.length)\n ? (_openBlock(), _createElementBlock(\"div\", _hoisted_1, [\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.elementIndexListInAnimation, (index) => {\n return (_openBlock(), _createElementBlock(\"div\", {\n class: \"index-item\",\n key: index\n }, _toDisplayString(index + 1), 1))\n }), 128))\n ]))\n : _createCommentVNode(\"\", true),\n (_ctx.isActive && _ctx.elementInfo.link)\n ? (_openBlock(), _createBlock(_component_LinkHandler, {\n key: 2,\n elementInfo: _ctx.elementInfo,\n link: _ctx.elementInfo.link,\n openLinkDialog: _ctx.openLinkDialog,\n onMousedown: _cache[0] || (_cache[0] = _withModifiers(() => {}, [\"stop\"]))\n }, null, 8, [\"elementInfo\", \"link\", \"openLinkDialog\"]))\n : _createCommentVNode(\"\", true)\n ], 6))\n}","import { renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, resolveComponent as _resolveComponent, normalizeStyle as _normalizeStyle, createBlock as _createBlock, withModifiers as _withModifiers, createVNode as _createVNode, createCommentVNode as _createCommentVNode, normalizeClass as _normalizeClass } from \"vue\"\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _component_BorderLine = _resolveComponent(\"BorderLine\")!\n const _component_ResizeHandler = _resolveComponent(\"ResizeHandler\")!\n const _component_RotateHandler = _resolveComponent(\"RotateHandler\")!\n\n return (_openBlock(), _createElementBlock(\"div\", {\n class: _normalizeClass([\"image-element-operate\", { 'cliping': _ctx.isCliping }])\n }, [\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.borderLines, (line) => {\n return (_openBlock(), _createBlock(_component_BorderLine, {\n class: \"operate-border-line\",\n key: line.type,\n type: line.type,\n style: _normalizeStyle(line.style)\n }, null, 8, [\"type\", \"style\"]))\n }), 128)),\n (_ctx.handlerVisible)\n ? (_openBlock(), _createElementBlock(_Fragment, { key: 0 }, [\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.resizeHandlers, (point) => {\n return (_openBlock(), _createBlock(_component_ResizeHandler, {\n class: \"operate-resize-handler\",\n key: point.direction,\n type: point.direction,\n rotate: _ctx.elementInfo.rotate,\n style: _normalizeStyle(point.style),\n onMousedown: _withModifiers($event => _ctx.scaleElement($event, _ctx.elementInfo, point.direction), [\"stop\"])\n }, null, 8, [\"type\", \"rotate\", \"style\", \"onMousedown\"]))\n }), 128)),\n _createVNode(_component_RotateHandler, {\n class: \"operate-rotate-handler\",\n style: _normalizeStyle({ left: _ctx.scaleWidth / 2 + 'px' }),\n onMousedown: _cache[0] || (_cache[0] = _withModifiers(($event: any) => (_ctx.rotateElement(_ctx.elementInfo)), [\"stop\"]))\n }, null, 8, [\"style\"])\n ], 64))\n : _createCommentVNode(\"\", true)\n ], 2))\n}","import { openBlock as _openBlock, createElementBlock as _createElementBlock, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-9a549510\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = { class: \"rotate-handler\" }\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n return (_openBlock(), _createElementBlock(\"div\", _hoisted_1))\n}","\nexport default {\n name: 'rotate-handler',\n}\n","import { render } from \"./RotateHandler.vue?vue&type=template&id=9a549510&scoped=true&ts=true\"\nimport script from \"./RotateHandler.vue?vue&type=script&lang=ts\"\nexport * from \"./RotateHandler.vue?vue&type=script&lang=ts\"\n\nimport \"./RotateHandler.vue?vue&type=style&index=0&id=9a549510&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-9a549510\"]])\n\nexport default __exports__","\r\nimport { computed, defineComponent, PropType } from 'vue'\r\nimport { storeToRefs } from 'pinia'\r\nimport { useMainStore } from '@/store'\r\nimport { PPTImageElement } from '@/types/slides'\r\nimport { OperateResizeHandlers } from '@/types/edit'\r\nimport useCommonOperate from '../hooks/useCommonOperate'\r\n\r\nimport RotateHandler from './RotateHandler.vue'\r\nimport ResizeHandler from './ResizeHandler.vue'\r\nimport BorderLine from './BorderLine.vue'\r\n\r\nexport default defineComponent({\r\n name: 'image-element-operate',\r\n inheritAttrs: false,\r\n components: {\r\n RotateHandler,\r\n ResizeHandler,\r\n BorderLine,\r\n },\r\n props: {\r\n elementInfo: {\r\n type: Object as PropType,\r\n required: true,\r\n },\r\n handlerVisible: {\r\n type: Boolean,\r\n required: true,\r\n },\r\n rotateElement: {\r\n type: Function as PropType<(element: PPTImageElement) => void>,\r\n required: true,\r\n },\r\n scaleElement: {\r\n type: Function as PropType<(e: MouseEvent, element: PPTImageElement, command: OperateResizeHandlers) => void>,\r\n required: true,\r\n },\r\n },\r\n setup(props) {\r\n const { canvasScale, clipingImageElementId } = storeToRefs(useMainStore())\r\n\r\n const isCliping = computed(() => clipingImageElementId.value === props.elementInfo.id)\r\n\r\n const scaleWidth = computed(() => props.elementInfo.width * canvasScale.value)\r\n const scaleHeight = computed(() => props.elementInfo.height * canvasScale.value)\r\n const { resizeHandlers, borderLines } = useCommonOperate(scaleWidth, scaleHeight)\r\n\r\n return {\r\n isCliping,\r\n scaleWidth,\r\n resizeHandlers,\r\n borderLines,\r\n }\r\n },\r\n})\r\n","import { render } from \"./ImageElementOperate.vue?vue&type=template&id=db6d5ba6&scoped=true&ts=true\"\nimport script from \"./ImageElementOperate.vue?vue&type=script&lang=ts\"\nexport * from \"./ImageElementOperate.vue?vue&type=script&lang=ts\"\n\nimport \"./ImageElementOperate.vue?vue&type=style&index=0&id=db6d5ba6&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-db6d5ba6\"]])\n\nexport default __exports__","import { renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, resolveComponent as _resolveComponent, normalizeStyle as _normalizeStyle, createBlock as _createBlock, withModifiers as _withModifiers, createVNode as _createVNode, createCommentVNode as _createCommentVNode } from \"vue\"\n\nconst _hoisted_1 = { class: \"text-element-operate\" }\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _component_BorderLine = _resolveComponent(\"BorderLine\")!\n const _component_ResizeHandler = _resolveComponent(\"ResizeHandler\")!\n const _component_RotateHandler = _resolveComponent(\"RotateHandler\")!\n\n return (_openBlock(), _createElementBlock(\"div\", _hoisted_1, [\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.borderLines, (line) => {\n return (_openBlock(), _createBlock(_component_BorderLine, {\n class: \"operate-border-line\",\n key: line.type,\n type: line.type,\n style: _normalizeStyle(line.style)\n }, null, 8, [\"type\", \"style\"]))\n }), 128)),\n (_ctx.handlerVisible)\n ? (_openBlock(), _createElementBlock(_Fragment, { key: 0 }, [\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.textElementResizeHandlers, (point) => {\n return (_openBlock(), _createBlock(_component_ResizeHandler, {\n class: \"operate-resize-handler\",\n key: point.direction,\n type: point.direction,\n rotate: _ctx.elementInfo.rotate,\n style: _normalizeStyle(point.style),\n onMousedown: _withModifiers($event => _ctx.scaleElement($event, _ctx.elementInfo, point.direction), [\"stop\"])\n }, null, 8, [\"type\", \"rotate\", \"style\", \"onMousedown\"]))\n }), 128)),\n _createVNode(_component_RotateHandler, {\n class: \"operate-rotate-handler\",\n style: _normalizeStyle({ left: _ctx.scaleWidth / 2 + 'px' }),\n onMousedown: _cache[0] || (_cache[0] = _withModifiers(($event: any) => (_ctx.rotateElement(_ctx.elementInfo)), [\"stop\"]))\n }, null, 8, [\"style\"])\n ], 64))\n : _createCommentVNode(\"\", true)\n ]))\n}","\r\nimport { computed, defineComponent, PropType } from 'vue'\r\nimport { storeToRefs } from 'pinia'\r\nimport { useMainStore } from '@/store'\r\nimport { PPTTextElement } from '@/types/slides'\r\nimport { OperateResizeHandlers } from '@/types/edit'\r\nimport useCommonOperate from '../hooks/useCommonOperate'\r\n\r\nimport RotateHandler from './RotateHandler.vue'\r\nimport ResizeHandler from './ResizeHandler.vue'\r\nimport BorderLine from './BorderLine.vue'\r\n\r\nexport default defineComponent({\r\n name: 'text-element-operate',\r\n inheritAttrs: false,\r\n components: {\r\n RotateHandler,\r\n ResizeHandler,\r\n BorderLine,\r\n },\r\n props: {\r\n elementInfo: {\r\n type: Object as PropType,\r\n required: true,\r\n },\r\n handlerVisible: {\r\n type: Boolean,\r\n required: true,\r\n },\r\n rotateElement: {\r\n type: Function as PropType<(element: PPTTextElement) => void>,\r\n required: true,\r\n },\r\n scaleElement: {\r\n type: Function as PropType<(e: MouseEvent, element: PPTTextElement, command: OperateResizeHandlers) => void>,\r\n required: true,\r\n },\r\n },\r\n setup(props) {\r\n const { canvasScale } = storeToRefs(useMainStore())\r\n\r\n const scaleWidth = computed(() => props.elementInfo.width * canvasScale.value)\r\n const scaleHeight = computed(() => props.elementInfo.height * canvasScale.value)\r\n\r\n const { textElementResizeHandlers, borderLines } = useCommonOperate(scaleWidth, scaleHeight)\r\n\r\n return {\r\n scaleWidth,\r\n textElementResizeHandlers,\r\n borderLines,\r\n }\r\n },\r\n})\r\n","import { render } from \"./TextElementOperate.vue?vue&type=template&id=115c48f0&ts=true\"\nimport script from \"./TextElementOperate.vue?vue&type=script&lang=ts\"\nexport * from \"./TextElementOperate.vue?vue&type=script&lang=ts\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render]])\n\nexport default __exports__","import { renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, resolveComponent as _resolveComponent, normalizeStyle as _normalizeStyle, createBlock as _createBlock, withModifiers as _withModifiers, createVNode as _createVNode, createCommentVNode as _createCommentVNode } from \"vue\"\n\nconst _hoisted_1 = { class: \"shape-element-operate\" }\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _component_BorderLine = _resolveComponent(\"BorderLine\")!\n const _component_ResizeHandler = _resolveComponent(\"ResizeHandler\")!\n const _component_RotateHandler = _resolveComponent(\"RotateHandler\")!\n\n return (_openBlock(), _createElementBlock(\"div\", _hoisted_1, [\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.borderLines, (line) => {\n return (_openBlock(), _createBlock(_component_BorderLine, {\n class: \"operate-border-line\",\n key: line.type,\n type: line.type,\n style: _normalizeStyle(line.style)\n }, null, 8, [\"type\", \"style\"]))\n }), 128)),\n (_ctx.handlerVisible)\n ? (_openBlock(), _createElementBlock(_Fragment, { key: 0 }, [\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.resizeHandlers, (point) => {\n return (_openBlock(), _createBlock(_component_ResizeHandler, {\n class: \"operate-resize-handler\",\n key: point.direction,\n type: point.direction,\n rotate: _ctx.elementInfo.rotate,\n style: _normalizeStyle(point.style),\n onMousedown: _withModifiers($event => _ctx.scaleElement($event, _ctx.elementInfo, point.direction), [\"stop\"])\n }, null, 8, [\"type\", \"rotate\", \"style\", \"onMousedown\"]))\n }), 128)),\n _createVNode(_component_RotateHandler, {\n class: \"operate-rotate-handler\",\n style: _normalizeStyle({ left: _ctx.scaleWidth / 2 + 'px' }),\n onMousedown: _cache[0] || (_cache[0] = _withModifiers(($event: any) => (_ctx.rotateElement(_ctx.elementInfo)), [\"stop\"]))\n }, null, 8, [\"style\"])\n ], 64))\n : _createCommentVNode(\"\", true)\n ]))\n}","\nimport { computed, defineComponent, PropType } from 'vue'\nimport { storeToRefs } from 'pinia'\nimport { useMainStore } from '@/store'\nimport { PPTShapeElement } from '@/types/slides'\nimport { OperateResizeHandlers } from '@/types/edit'\nimport useCommonOperate from '../hooks/useCommonOperate'\n\nimport RotateHandler from './RotateHandler.vue'\nimport ResizeHandler from './ResizeHandler.vue'\nimport BorderLine from './BorderLine.vue'\n\nexport default defineComponent({\n name: 'shape-element-operate',\n inheritAttrs: false,\n components: {\n RotateHandler,\n ResizeHandler,\n BorderLine,\n },\n props: {\n elementInfo: {\n type: Object as PropType,\n required: true,\n },\n handlerVisible: {\n type: Boolean,\n required: true,\n },\n rotateElement: {\n type: Function as PropType<(element: PPTShapeElement) => void>,\n required: true,\n },\n scaleElement: {\n type: Function as PropType<(e: MouseEvent, element: PPTShapeElement, command: OperateResizeHandlers) => void>,\n required: true,\n },\n },\n setup(props) {\n const { canvasScale } = storeToRefs(useMainStore())\n\n const scaleWidth = computed(() => props.elementInfo.width * canvasScale.value)\n const scaleHeight = computed(() => props.elementInfo.height * canvasScale.value)\n const { resizeHandlers, borderLines } = useCommonOperate(scaleWidth, scaleHeight)\n\n return {\n scaleWidth,\n resizeHandlers,\n borderLines,\n }\n },\n})\n","import { render } from \"./ShapeElementOperate.vue?vue&type=template&id=08e569b7&ts=true\"\nimport script from \"./ShapeElementOperate.vue?vue&type=script&lang=ts\"\nexport * from \"./ShapeElementOperate.vue?vue&type=script&lang=ts\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render]])\n\nexport default __exports__","import { renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, resolveComponent as _resolveComponent, withModifiers as _withModifiers, normalizeStyle as _normalizeStyle, createBlock as _createBlock, createElementVNode as _createElementVNode, createCommentVNode as _createCommentVNode, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-60f9f1f7\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = { class: \"line-element-operate\" }\nconst _hoisted_2 = [\"width\", \"height\", \"stroke\"]\nconst _hoisted_3 = { key: 0 }\nconst _hoisted_4 = [\"x1\", \"y1\", \"x2\", \"y2\"]\nconst _hoisted_5 = [\"x1\", \"y1\", \"x2\", \"y2\"]\nconst _hoisted_6 = [\"x1\", \"y1\", \"x2\", \"y2\"]\nconst _hoisted_7 = [\"x1\", \"y1\", \"x2\", \"y2\"]\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _component_ResizeHandler = _resolveComponent(\"ResizeHandler\")!\n\n return (_openBlock(), _createElementBlock(\"div\", _hoisted_1, [\n (_ctx.handlerVisible)\n ? (_openBlock(), _createElementBlock(_Fragment, { key: 0 }, [\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.resizeHandlers, (point) => {\n return (_openBlock(), _createBlock(_component_ResizeHandler, {\n class: \"operate-resize-handler\",\n key: point.handler,\n style: _normalizeStyle(point.style),\n onMousedown: _withModifiers($event => _ctx.dragLineElement($event, _ctx.elementInfo, point.handler), [\"stop\"])\n }, null, 8, [\"style\", \"onMousedown\"]))\n }), 128)),\n (_openBlock(), _createElementBlock(\"svg\", {\n width: _ctx.svgWidth || 1,\n height: _ctx.svgHeight || 1,\n stroke: _ctx.elementInfo.color,\n overflow: \"visible\",\n style: _normalizeStyle({ transform: `scale(${_ctx.canvasScale})` })\n }, [\n (_ctx.elementInfo.curve)\n ? (_openBlock(), _createElementBlock(\"g\", _hoisted_3, [\n _createElementVNode(\"line\", {\n class: \"anchor-line\",\n x1: _ctx.elementInfo.start[0],\n y1: _ctx.elementInfo.start[1],\n x2: _ctx.elementInfo.curve[0],\n y2: _ctx.elementInfo.curve[1]\n }, null, 8, _hoisted_4),\n _createElementVNode(\"line\", {\n class: \"anchor-line\",\n x1: _ctx.elementInfo.end[0],\n y1: _ctx.elementInfo.end[1],\n x2: _ctx.elementInfo.curve[0],\n y2: _ctx.elementInfo.curve[1]\n }, null, 8, _hoisted_5)\n ]))\n : _createCommentVNode(\"\", true),\n (_ctx.elementInfo.cubic)\n ? (_openBlock(true), _createElementBlock(_Fragment, { key: 1 }, _renderList(_ctx.elementInfo.cubic, (item, index) => {\n return (_openBlock(), _createElementBlock(\"g\", { key: index }, [\n (index === 0)\n ? (_openBlock(), _createElementBlock(\"line\", {\n key: 0,\n class: \"anchor-line\",\n x1: _ctx.elementInfo.start[0],\n y1: _ctx.elementInfo.start[1],\n x2: item[0],\n y2: item[1]\n }, null, 8, _hoisted_6))\n : _createCommentVNode(\"\", true),\n (index === 1)\n ? (_openBlock(), _createElementBlock(\"line\", {\n key: 1,\n class: \"anchor-line\",\n x1: _ctx.elementInfo.end[0],\n y1: _ctx.elementInfo.end[1],\n x2: item[0],\n y2: item[1]\n }, null, 8, _hoisted_7))\n : _createCommentVNode(\"\", true)\n ]))\n }), 128))\n : _createCommentVNode(\"\", true)\n ], 12, _hoisted_2))\n ], 64))\n : _createCommentVNode(\"\", true)\n ]))\n}","\nimport { computed, defineComponent, PropType } from 'vue'\nimport { storeToRefs } from 'pinia'\nimport { useMainStore } from '@/store'\nimport { PPTLineElement } from '@/types/slides'\nimport { OperateLineHandlers } from '@/types/edit'\n\nimport ResizeHandler from './ResizeHandler.vue'\n\nexport default defineComponent({\n name: 'line-element-operate',\n inheritAttrs: false,\n components: {\n ResizeHandler,\n },\n props: {\n elementInfo: {\n type: Object as PropType,\n required: true,\n },\n handlerVisible: {\n type: Boolean,\n required: true,\n },\n dragLineElement: {\n type: Function as PropType<(e: MouseEvent, element: PPTLineElement, command: OperateLineHandlers) => void>,\n required: true,\n },\n },\n setup(props) {\n const { canvasScale } = storeToRefs(useMainStore())\n\n const svgWidth = computed(() => Math.max(props.elementInfo.start[0], props.elementInfo.end[0]))\n const svgHeight = computed(() => Math.max(props.elementInfo.start[1], props.elementInfo.end[1]))\n\n const resizeHandlers = computed(() => {\n const handlers = [\n {\n handler: OperateLineHandlers.START,\n style: {\n left: props.elementInfo.start[0] * canvasScale.value + 'px',\n top: props.elementInfo.start[1] * canvasScale.value + 'px',\n }\n },\n {\n handler: OperateLineHandlers.END,\n style: {\n left: props.elementInfo.end[0] * canvasScale.value + 'px',\n top: props.elementInfo.end[1] * canvasScale.value + 'px',\n }\n },\n ]\n\n if (props.elementInfo.curve || props.elementInfo.broken) {\n const ctrlHandler = (props.elementInfo.curve || props.elementInfo.broken) as [number, number]\n\n handlers.push({\n handler: OperateLineHandlers.C,\n style: {\n left: ctrlHandler[0] * canvasScale.value + 'px',\n top: ctrlHandler[1] * canvasScale.value + 'px',\n }\n })\n }\n else if (props.elementInfo.cubic) {\n const [ctrlHandler1, ctrlHandler2] = props.elementInfo.cubic\n handlers.push({\n handler: OperateLineHandlers.C1,\n style: {\n left: ctrlHandler1[0] * canvasScale.value + 'px',\n top: ctrlHandler1[1] * canvasScale.value + 'px',\n }\n })\n handlers.push({\n handler: OperateLineHandlers.C2,\n style: {\n left: ctrlHandler2[0] * canvasScale.value + 'px',\n top: ctrlHandler2[1] * canvasScale.value + 'px',\n }\n })\n }\n\n return handlers\n })\n\n return {\n svgWidth,\n svgHeight,\n canvasScale,\n resizeHandlers,\n }\n },\n})\n","import { render } from \"./LineElementOperate.vue?vue&type=template&id=60f9f1f7&scoped=true&ts=true\"\nimport script from \"./LineElementOperate.vue?vue&type=script&lang=ts\"\nexport * from \"./LineElementOperate.vue?vue&type=script&lang=ts\"\n\nimport \"./LineElementOperate.vue?vue&type=style&index=0&id=60f9f1f7&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-60f9f1f7\"]])\n\nexport default __exports__","import { renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, resolveComponent as _resolveComponent, normalizeStyle as _normalizeStyle, createBlock as _createBlock, withModifiers as _withModifiers, createVNode as _createVNode, createCommentVNode as _createCommentVNode } from \"vue\"\n\nconst _hoisted_1 = { class: \"table-element-operate\" }\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _component_BorderLine = _resolveComponent(\"BorderLine\")!\n const _component_ResizeHandler = _resolveComponent(\"ResizeHandler\")!\n const _component_RotateHandler = _resolveComponent(\"RotateHandler\")!\n\n return (_openBlock(), _createElementBlock(\"div\", _hoisted_1, [\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.borderLines, (line) => {\n return (_openBlock(), _createBlock(_component_BorderLine, {\n class: \"operate-border-line\",\n key: line.type,\n type: line.type,\n style: _normalizeStyle(line.style)\n }, null, 8, [\"type\", \"style\"]))\n }), 128)),\n (_ctx.handlerVisible)\n ? (_openBlock(), _createElementBlock(_Fragment, { key: 0 }, [\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.textElementResizeHandlers, (point) => {\n return (_openBlock(), _createBlock(_component_ResizeHandler, {\n class: \"operate-resize-handler\",\n key: point.direction,\n type: point.direction,\n rotate: _ctx.elementInfo.rotate,\n style: _normalizeStyle(point.style),\n onMousedown: _withModifiers($event => _ctx.scaleElement($event, _ctx.elementInfo, point.direction), [\"stop\"])\n }, null, 8, [\"type\", \"rotate\", \"style\", \"onMousedown\"]))\n }), 128)),\n _createVNode(_component_RotateHandler, {\n class: \"operate-rotate-handler\",\n style: _normalizeStyle({ left: _ctx.scaleWidth / 2 + 'px' }),\n onMousedown: _cache[0] || (_cache[0] = _withModifiers(($event: any) => (_ctx.rotateElement(_ctx.elementInfo)), [\"stop\"]))\n }, null, 8, [\"style\"])\n ], 64))\n : _createCommentVNode(\"\", true)\n ]))\n}","\nimport { computed, defineComponent, PropType } from 'vue'\nimport { storeToRefs } from 'pinia'\nimport { useMainStore } from '@/store'\nimport { PPTTableElement } from '@/types/slides'\nimport { OperateResizeHandlers } from '@/types/edit'\nimport useCommonOperate from '../hooks/useCommonOperate'\n\nimport RotateHandler from './RotateHandler.vue'\nimport ResizeHandler from './ResizeHandler.vue'\nimport BorderLine from './BorderLine.vue'\n\nexport default defineComponent({\n name: 'table-element-operate',\n inheritAttrs: false,\n components: {\n RotateHandler,\n ResizeHandler,\n BorderLine,\n },\n props: {\n elementInfo: {\n type: Object as PropType,\n required: true,\n },\n handlerVisible: {\n type: Boolean,\n required: true,\n },\n rotateElement: {\n type: Function as PropType<(element: PPTTableElement) => void>,\n required: true,\n },\n scaleElement: {\n type: Function as PropType<(e: MouseEvent, element: PPTTableElement, command: OperateResizeHandlers) => void>,\n required: true,\n },\n },\n setup(props) {\n const { canvasScale } = storeToRefs(useMainStore())\n\n const outlineWidth = computed(() => props.elementInfo.outline.width || 1)\n\n const scaleWidth = computed(() => (props.elementInfo.width + outlineWidth.value) * canvasScale.value)\n const scaleHeight = computed(() => props.elementInfo.height * canvasScale.value)\n\n const { textElementResizeHandlers, borderLines } = useCommonOperate(scaleWidth, scaleHeight)\n\n return {\n scaleWidth,\n textElementResizeHandlers,\n borderLines,\n }\n },\n})\n","import { render } from \"./TableElementOperate.vue?vue&type=template&id=45c5e7da&ts=true\"\nimport script from \"./TableElementOperate.vue?vue&type=script&lang=ts\"\nexport * from \"./TableElementOperate.vue?vue&type=script&lang=ts\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render]])\n\nexport default __exports__","import { renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, resolveComponent as _resolveComponent, normalizeStyle as _normalizeStyle, createBlock as _createBlock, withModifiers as _withModifiers, createCommentVNode as _createCommentVNode } from \"vue\"\n\nconst _hoisted_1 = { class: \"common-element-operate\" }\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _component_BorderLine = _resolveComponent(\"BorderLine\")!\n const _component_ResizeHandler = _resolveComponent(\"ResizeHandler\")!\n const _component_RotateHandler = _resolveComponent(\"RotateHandler\")!\n\n return (_openBlock(), _createElementBlock(\"div\", _hoisted_1, [\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.borderLines, (line) => {\n return (_openBlock(), _createBlock(_component_BorderLine, {\n class: \"operate-border-line\",\n key: line.type,\n type: line.type,\n style: _normalizeStyle(line.style)\n }, null, 8, [\"type\", \"style\"]))\n }), 128)),\n (_ctx.handlerVisible)\n ? (_openBlock(), _createElementBlock(_Fragment, { key: 0 }, [\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.resizeHandlers, (point) => {\n return (_openBlock(), _createBlock(_component_ResizeHandler, {\n class: \"operate-resize-handler\",\n key: point.direction,\n type: point.direction,\n rotate: _ctx.elementInfo.rotate,\n style: _normalizeStyle(point.style),\n onMousedown: _withModifiers($event => _ctx.scaleElement($event, _ctx.elementInfo, point.direction), [\"stop\"])\n }, null, 8, [\"type\", \"rotate\", \"style\", \"onMousedown\"]))\n }), 128)),\n (!_ctx.cannotRotate)\n ? (_openBlock(), _createBlock(_component_RotateHandler, {\n key: 0,\n class: \"operate-rotate-handler\",\n style: _normalizeStyle({ left: _ctx.scaleWidth / 2 + 'px' }),\n onMousedown: _cache[0] || (_cache[0] = _withModifiers(($event: any) => (_ctx.rotateElement(_ctx.elementInfo)), [\"stop\"]))\n }, null, 8, [\"style\"]))\n : _createCommentVNode(\"\", true)\n ], 64))\n : _createCommentVNode(\"\", true)\n ]))\n}","\nimport { computed, defineComponent, PropType } from 'vue'\nimport { storeToRefs } from 'pinia'\nimport { useMainStore } from '@/store'\nimport { PPTShapeElement, PPTVideoElement, PPTLatexElement, PPTAudioElement } from '@/types/slides'\nimport { OperateResizeHandlers } from '@/types/edit'\nimport useCommonOperate from '../hooks/useCommonOperate'\n\nimport RotateHandler from './RotateHandler.vue'\nimport ResizeHandler from './ResizeHandler.vue'\nimport BorderLine from './BorderLine.vue'\n\ntype PPTElement = PPTShapeElement | PPTVideoElement | PPTLatexElement | PPTAudioElement\n\nexport default defineComponent({\n name: 'common-element-operate',\n inheritAttrs: false,\n components: {\n RotateHandler,\n ResizeHandler,\n BorderLine,\n },\n props: {\n elementInfo: {\n type: Object as PropType,\n required: true,\n },\n handlerVisible: {\n type: Boolean,\n required: true,\n },\n rotateElement: {\n type: Function as PropType<(element: PPTElement) => void>,\n required: true,\n },\n scaleElement: {\n type: Function as PropType<(e: MouseEvent, element: PPTElement, command: OperateResizeHandlers) => void>,\n required: true,\n },\n },\n setup(props) {\n const { canvasScale } = storeToRefs(useMainStore())\n\n const scaleWidth = computed(() => props.elementInfo.width * canvasScale.value)\n const scaleHeight = computed(() => props.elementInfo.height * canvasScale.value)\n const { resizeHandlers, borderLines } = useCommonOperate(scaleWidth, scaleHeight)\n\n const cannotRotate = computed(() => ['video', 'audio'].includes(props.elementInfo.type))\n\n return {\n scaleWidth,\n resizeHandlers,\n borderLines,\n cannotRotate,\n }\n },\n})\n","import { render } from \"./CommonElementOperate.vue?vue&type=template&id=474b1c2e&ts=true\"\nimport script from \"./CommonElementOperate.vue?vue&type=script&lang=ts\"\nexport * from \"./CommonElementOperate.vue?vue&type=script&lang=ts\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render]])\n\nexport default __exports__","import { toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock, createCommentVNode as _createCommentVNode, createElementVNode as _createElementVNode, resolveComponent as _resolveComponent, createVNode as _createVNode, normalizeStyle as _normalizeStyle, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-2b77dbb0\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = [\"href\"]\nconst _hoisted_2 = {\n key: 1,\n class: \"link\"\n}\nconst _hoisted_3 = { class: \"btns\" }\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _component_Divider = _resolveComponent(\"Divider\")!\n\n return (_openBlock(), _createElementBlock(\"div\", {\n class: \"link-handler\",\n style: _normalizeStyle({ top: _ctx.height * _ctx.canvasScale + 10 + 'px' })\n }, [\n (_ctx.link.type === 'web')\n ? (_openBlock(), _createElementBlock(\"a\", {\n key: 0,\n class: \"link\",\n href: _ctx.link.target,\n target: \"_blank\"\n }, _toDisplayString(_ctx.link.target), 9, _hoisted_1))\n : (_openBlock(), _createElementBlock(\"a\", _hoisted_2, \"幻灯片页面 \" + _toDisplayString(_ctx.link.target), 1)),\n _createElementVNode(\"div\", _hoisted_3, [\n _createElementVNode(\"div\", {\n class: \"btn\",\n onClick: _cache[0] || (_cache[0] = ($event: any) => (_ctx.openLinkDialog()))\n }, \"更换\"),\n _createVNode(_component_Divider, { type: \"vertical\" }),\n _createElementVNode(\"div\", {\n class: \"btn\",\n onClick: _cache[1] || (_cache[1] = ($event: any) => (_ctx.removeLink(_ctx.elementInfo)))\n }, \"移除\")\n ])\n ], 4))\n}","import { useSlidesStore } from '@/store'\nimport { PPTElement, PPTElementLink } from '@/types/slides'\nimport useHistorySnapshot from '@/hooks/useHistorySnapshot'\nimport { message } from 'ant-design-vue'\n\nexport default () => {\n const slidesStore = useSlidesStore()\n\n const { addHistorySnapshot } = useHistorySnapshot()\n\n const setLink = (handleElement: PPTElement, link: PPTElementLink) => {\n const linkRegExp = /^(https?):\\/\\/[\\w\\-]+(\\.[\\w\\-]+)+([\\w\\-.,@?^=%&:\\/~+#]*[\\w\\-@?^=%&\\/~+#])?$/\n if (link.type === 'web' && !linkRegExp.test(link.target)) {\n message.error('不是正确的网页链接地址')\n return false\n }\n const props = { link }\n slidesStore.updateElement({ id: handleElement.id, props })\n addHistorySnapshot()\n\n return true\n }\n\n const removeLink = (handleElement: PPTElement) => {\n slidesStore.removeElementProps({ id: handleElement.id, propName: 'link' })\n addHistorySnapshot()\n }\n\n return {\n setLink,\n removeLink,\n }\n}","\nimport { computed, defineComponent, PropType } from 'vue'\nimport { storeToRefs } from 'pinia'\nimport { useMainStore } from '@/store'\nimport { PPTElement, PPTElementLink } from '@/types/slides'\nimport useLink from '@/hooks/useLink'\n\nexport default defineComponent({\n name: 'link-handler',\n props: {\n elementInfo: {\n type: Object as PropType,\n required: true,\n },\n link: {\n type: Object as PropType,\n required: true,\n },\n openLinkDialog: {\n type: Function as PropType<() => void>,\n required: true,\n },\n },\n setup(props) {\n const { canvasScale } = storeToRefs(useMainStore())\n\n const { removeLink } = useLink()\n\n const height = computed(() => props.elementInfo.type === 'line' ? 0 : props.elementInfo.height)\n\n return {\n canvasScale,\n height,\n removeLink,\n }\n },\n})\n","import { render } from \"./LinkHandler.vue?vue&type=template&id=2b77dbb0&scoped=true&ts=true\"\nimport script from \"./LinkHandler.vue?vue&type=script&lang=ts\"\nexport * from \"./LinkHandler.vue?vue&type=script&lang=ts\"\n\nimport \"./LinkHandler.vue?vue&type=style&index=0&id=2b77dbb0&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-2b77dbb0\"]])\n\nexport default __exports__","\r\nimport { defineComponent, PropType, computed } from 'vue'\r\nimport { storeToRefs } from 'pinia'\r\nimport { useMainStore, useSlidesStore } from '@/store'\r\nimport { ElementTypes, PPTElement, PPTLineElement, PPTVideoElement, PPTAudioElement } from '@/types/slides'\r\nimport { OperateLineHandlers, OperateResizeHandlers } from '@/types/edit'\r\n\r\nimport ImageElementOperate from './ImageElementOperate.vue'\r\nimport TextElementOperate from './TextElementOperate.vue'\r\nimport ShapeElementOperate from './ShapeElementOperate.vue'\r\nimport LineElementOperate from './LineElementOperate.vue'\r\nimport TableElementOperate from './TableElementOperate.vue'\r\nimport CommonElementOperate from './CommonElementOperate.vue'\r\nimport LinkHandler from './LinkHandler.vue'\r\n\r\nexport default defineComponent({\r\n name: 'operate',\r\n components: {\r\n LinkHandler,\r\n },\r\n props: {\r\n elementInfo: {\r\n type: Object as PropType,\r\n required: true,\r\n },\r\n isSelected: {\r\n type: Boolean,\r\n required: true,\r\n },\r\n isActive: {\r\n type: Boolean,\r\n required: true,\r\n },\r\n isActiveGroupElement: {\r\n type: Boolean,\r\n required: true,\r\n },\r\n isMultiSelect: {\r\n type: Boolean,\r\n required: true,\r\n },\r\n rotateElement: {\r\n type: Function as PropType<(element: Exclude) => void>,\r\n required: true,\r\n },\r\n scaleElement: {\r\n type: Function as PropType<(e: MouseEvent, element: Exclude, command: OperateResizeHandlers) => void>,\r\n required: true,\r\n },\r\n dragLineElement: {\r\n type: Function as PropType<(e: MouseEvent, element: PPTLineElement, command: OperateLineHandlers) => void>,\r\n required: true,\r\n },\r\n openLinkDialog: {\r\n type: Function as PropType<() => void>,\r\n required: true,\r\n },\r\n },\r\n setup(props) {\r\n const { canvasScale, toolbarState } = storeToRefs(useMainStore())\r\n const { formatedAnimations } = storeToRefs(useSlidesStore())\r\n\r\n const currentOperateComponent = computed(() => {\r\n const elementTypeMap = {\r\n [ElementTypes.IMAGE]: ImageElementOperate,\r\n [ElementTypes.TEXT]: TextElementOperate,\r\n [ElementTypes.SHAPE]: ShapeElementOperate,\r\n [ElementTypes.LINE]: LineElementOperate,\r\n [ElementTypes.TABLE]: TableElementOperate,\r\n [ElementTypes.CHART]: CommonElementOperate,\r\n [ElementTypes.LATEX]: CommonElementOperate,\r\n [ElementTypes.VIDEO]: CommonElementOperate,\r\n [ElementTypes.AUDIO]: CommonElementOperate,\r\n }\r\n return elementTypeMap[props.elementInfo.type] || null\r\n })\r\n\r\n const elementIndexListInAnimation = computed(() => {\r\n const indexList = []\r\n for (let i = 0; i < formatedAnimations.value.length; i++) {\r\n const elIds = formatedAnimations.value[i].animations.map(item => item.elId)\r\n if (elIds.includes(props.elementInfo.id)) indexList.push(i)\r\n }\r\n return indexList\r\n })\r\n\r\n const rotate = computed(() => 'rotate' in props.elementInfo ? props.elementInfo.rotate : 0)\r\n const height = computed(() => 'height' in props.elementInfo ? props.elementInfo.height : 0)\r\n\r\n return {\r\n currentOperateComponent,\r\n canvasScale,\r\n toolbarState,\r\n elementIndexListInAnimation,\r\n rotate,\r\n height,\r\n }\r\n },\r\n})\r\n","import { render } from \"./index.vue?vue&type=template&id=10df1a8e&scoped=true&ts=true\"\nimport script from \"./index.vue?vue&type=script&lang=ts\"\nexport * from \"./index.vue?vue&type=script&lang=ts\"\n\nimport \"./index.vue?vue&type=style&index=0&id=10df1a8e&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-10df1a8e\"]])\n\nexport default __exports__","import { renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, toDisplayString as _toDisplayString, normalizeClass as _normalizeClass, createElementVNode as _createElementVNode, resolveComponent as _resolveComponent, createBlock as _createBlock, createCommentVNode as _createCommentVNode, createTextVNode as _createTextVNode, withCtx as _withCtx, createVNode as _createVNode, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-57530ef0\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = { class: \"link-dialog\" }\nconst _hoisted_2 = { class: \"tabs\" }\nconst _hoisted_3 = [\"onClick\"]\nconst _hoisted_4 = {\n key: 2,\n class: \"preview\"\n}\nconst _hoisted_5 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(\"div\", null, \"预览:\", -1))\nconst _hoisted_6 = { class: \"btns\" }\nconst _hoisted_7 = /*#__PURE__*/_createTextVNode(\"取消\")\nconst _hoisted_8 = /*#__PURE__*/_createTextVNode(\"确认\")\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _component_Input = _resolveComponent(\"Input\")!\n const _component_SelectOption = _resolveComponent(\"SelectOption\")!\n const _component_Select = _resolveComponent(\"Select\")!\n const _component_ThumbnailSlide = _resolveComponent(\"ThumbnailSlide\")!\n const _component_Button = _resolveComponent(\"Button\")!\n\n return (_openBlock(), _createElementBlock(\"div\", _hoisted_1, [\n _createElementVNode(\"div\", _hoisted_2, [\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.tabs, (tab) => {\n return (_openBlock(), _createElementBlock(\"div\", {\n class: _normalizeClass([\"tab\", { 'active': _ctx.type === tab.key }]),\n key: tab.key,\n onClick: ($event: any) => (_ctx.type = tab.key)\n }, _toDisplayString(tab.label), 11, _hoisted_3))\n }), 128))\n ]),\n (_ctx.type === 'web')\n ? (_openBlock(), _createBlock(_component_Input, {\n key: 0,\n class: \"input\",\n value: _ctx.address,\n \"onUpdate:value\": _cache[0] || (_cache[0] = ($event: any) => ((_ctx.address) = $event)),\n placeholder: \"请输入网页链接地址\"\n }, null, 8, [\"value\"]))\n : _createCommentVNode(\"\", true),\n (_ctx.type === 'slide')\n ? (_openBlock(), _createBlock(_component_Select, {\n key: 1,\n class: \"input\",\n value: _ctx.slideId,\n \"onUpdate:value\": _cache[1] || (_cache[1] = ($event: any) => ((_ctx.slideId) = $event))\n }, {\n default: _withCtx(() => [\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.slides, (slide, index) => {\n return (_openBlock(), _createBlock(_component_SelectOption, {\n key: slide.id,\n value: slide.id\n }, {\n default: _withCtx(() => [\n _createTextVNode(\"幻灯片 \" + _toDisplayString(index + 1), 1)\n ]),\n _: 2\n }, 1032, [\"value\"]))\n }), 128))\n ]),\n _: 1\n }, 8, [\"value\"]))\n : _createCommentVNode(\"\", true),\n (_ctx.type === 'slide' && _ctx.selectedSlide)\n ? (_openBlock(), _createElementBlock(\"div\", _hoisted_4, [\n _hoisted_5,\n _createVNode(_component_ThumbnailSlide, {\n class: \"thumbnail\",\n slide: _ctx.selectedSlide,\n size: 490\n }, null, 8, [\"slide\"])\n ]))\n : _createCommentVNode(\"\", true),\n _createElementVNode(\"div\", _hoisted_6, [\n _createVNode(_component_Button, {\n onClick: _cache[2] || (_cache[2] = ($event: any) => (_ctx.close())),\n style: {\"margin-right\":\"10px\"}\n }, {\n default: _withCtx(() => [\n _hoisted_7\n ]),\n _: 1\n }),\n _createVNode(_component_Button, {\n type: \"primary\",\n onClick: _cache[3] || (_cache[3] = ($event: any) => (_ctx.save()))\n }, {\n default: _withCtx(() => [\n _hoisted_8\n ]),\n _: 1\n })\n ])\n ]))\n}","import { normalizeStyle as _normalizeStyle, createElementVNode as _createElementVNode, renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, resolveComponent as _resolveComponent, createBlock as _createBlock, createCommentVNode as _createCommentVNode, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-025539cf\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = {\n key: 1,\n class: \"placeholder\"\n}\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _component_ThumbnailElement = _resolveComponent(\"ThumbnailElement\")!\n\n return (_openBlock(), _createElementBlock(\"div\", {\n class: \"thumbnail-slide\",\n style: _normalizeStyle({\n width: _ctx.size + 'px',\n height: _ctx.size * _ctx.viewportRatio + 'px',\n })\n }, [\n (_ctx.visible)\n ? (_openBlock(), _createElementBlock(\"div\", {\n key: 0,\n class: \"elements\",\n style: _normalizeStyle({\n width: _ctx.VIEWPORT_SIZE + 'px',\n height: _ctx.VIEWPORT_SIZE * _ctx.viewportRatio + 'px',\n transform: `scale(${_ctx.scale})`,\n })\n }, [\n _createElementVNode(\"div\", {\n class: \"background\",\n style: _normalizeStyle(_ctx.backgroundStyle)\n }, null, 4),\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.slide.elements, (element, index) => {\n return (_openBlock(), _createBlock(_component_ThumbnailElement, {\n key: element.id,\n elementInfo: element,\n elementIndex: index + 1\n }, null, 8, [\"elementInfo\", \"elementIndex\"]))\n }), 128))\n ], 4))\n : (_openBlock(), _createElementBlock(\"div\", _hoisted_1, \"加载中 ...\"))\n ], 4))\n}","import { resolveDynamicComponent as _resolveDynamicComponent, openBlock as _openBlock, createBlock as _createBlock, normalizeClass as _normalizeClass, normalizeStyle as _normalizeStyle, createElementBlock as _createElementBlock } from \"vue\"\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n return (_openBlock(), _createElementBlock(\"div\", {\n class: _normalizeClass([\"base-element\", `base-element-${_ctx.elementInfo.id}`]),\n style: _normalizeStyle({\n zIndex: _ctx.elementIndex,\n })\n }, [\n (_openBlock(), _createBlock(_resolveDynamicComponent(_ctx.currentElementComponent), {\n elementInfo: _ctx.elementInfo,\n target: \"thumbnail\"\n }, null, 8, [\"elementInfo\"]))\n ], 6))\n}","import { resolveComponent as _resolveComponent, createVNode as _createVNode, normalizeStyle as _normalizeStyle, createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-0b1f9460\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = [\"src\"]\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _component_ImageOutline = _resolveComponent(\"ImageOutline\")!\n\n return (_openBlock(), _createElementBlock(\"div\", {\n class: \"base-element-image\",\n style: _normalizeStyle({\n top: _ctx.elementInfo.top + 'px',\n left: _ctx.elementInfo.left + 'px',\n width: _ctx.elementInfo.width + 'px',\n height: _ctx.elementInfo.height + 'px',\n })\n }, [\n _createElementVNode(\"div\", {\n class: \"rotate-wrapper\",\n style: _normalizeStyle({ transform: `rotate(${_ctx.elementInfo.rotate}deg)` })\n }, [\n _createElementVNode(\"div\", {\n class: \"element-content\",\n style: _normalizeStyle({\n filter: _ctx.shadowStyle ? `drop-shadow(${_ctx.shadowStyle})` : '',\n transform: _ctx.flipStyle,\n })\n }, [\n _createVNode(_component_ImageOutline, { elementInfo: _ctx.elementInfo }, null, 8, [\"elementInfo\"]),\n _createElementVNode(\"div\", {\n class: \"image-content\",\n style: _normalizeStyle({ clipPath: _ctx.clipShape.style })\n }, [\n _createElementVNode(\"img\", {\n src: _ctx.elementInfo.src,\n draggable: false,\n style: _normalizeStyle({\n top: _ctx.imgPosition.top,\n left: _ctx.imgPosition.left,\n width: _ctx.imgPosition.width,\n height: _ctx.imgPosition.height,\n filter: _ctx.filter,\n }),\n alt: \"\"\n }, null, 12, _hoisted_1)\n ], 4)\n ], 4)\n ], 4)\n ], 4))\n}","\nimport { computed, defineComponent, PropType } from 'vue'\nimport { PPTImageElement } from '@/types/slides'\nimport useElementShadow from '@/views/components/element/hooks/useElementShadow'\nimport useElementFlip from '@/views/components/element/hooks/useElementFlip'\nimport useClipImage from './useClipImage'\nimport useFilter from './useFilter'\n\nimport ImageOutline from './ImageOutline/index.vue'\n\nexport default defineComponent({\n name: 'base-element-image',\n components: {\n ImageOutline,\n },\n props: {\n elementInfo: {\n type: Object as PropType,\n required: true,\n },\n },\n setup(props) {\n const shadow = computed(() => props.elementInfo.shadow)\n const { shadowStyle } = useElementShadow(shadow)\n\n const flipH = computed(() => props.elementInfo.flipH)\n const flipV = computed(() => props.elementInfo.flipV)\n const { flipStyle } = useElementFlip(flipH, flipV)\n \n const clip = computed(() => props.elementInfo.clip)\n const { clipShape, imgPosition } = useClipImage(clip)\n\n const filters = computed(() => props.elementInfo.filters)\n const { filter } = useFilter(filters)\n\n return {\n imgPosition,\n filter,\n flipStyle,\n shadowStyle,\n clipShape,\n }\n },\n})\n","import { render } from \"./BaseImageElement.vue?vue&type=template&id=0b1f9460&scoped=true&ts=true\"\nimport script from \"./BaseImageElement.vue?vue&type=script&lang=ts\"\nexport * from \"./BaseImageElement.vue?vue&type=script&lang=ts\"\n\nimport \"./BaseImageElement.vue?vue&type=style&index=0&id=0b1f9460&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-0b1f9460\"]])\n\nexport default __exports__","import { resolveComponent as _resolveComponent, createVNode as _createVNode, createElementVNode as _createElementVNode, normalizeStyle as _normalizeStyle, openBlock as _openBlock, createElementBlock as _createElementBlock, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-2ec235c4\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = [\"innerHTML\"]\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _component_ElementOutline = _resolveComponent(\"ElementOutline\")!\n\n return (_openBlock(), _createElementBlock(\"div\", {\n class: \"base-element-text\",\n style: _normalizeStyle({\n top: _ctx.elementInfo.top + 'px',\n left: _ctx.elementInfo.left + 'px',\n width: _ctx.elementInfo.width + 'px',\n })\n }, [\n _createElementVNode(\"div\", {\n class: \"rotate-wrapper\",\n style: _normalizeStyle({ transform: `rotate(${_ctx.elementInfo.rotate}deg)` })\n }, [\n _createElementVNode(\"div\", {\n class: \"element-content\",\n style: _normalizeStyle({\n backgroundColor: _ctx.elementInfo.fill,\n opacity: _ctx.elementInfo.opacity,\n textShadow: _ctx.shadowStyle,\n lineHeight: _ctx.elementInfo.lineHeight,\n letterSpacing: (_ctx.elementInfo.wordSpace || 0) + 'px',\n color: _ctx.elementInfo.defaultColor,\n fontFamily: _ctx.elementInfo.defaultFontName,\n })\n }, [\n _createVNode(_component_ElementOutline, {\n width: _ctx.elementInfo.width,\n height: _ctx.elementInfo.height,\n outline: _ctx.elementInfo.outline\n }, null, 8, [\"width\", \"height\", \"outline\"]),\n _createElementVNode(\"div\", {\n class: \"text ProseMirror-static\",\n innerHTML: _ctx.elementInfo.content\n }, null, 8, _hoisted_1)\n ], 4)\n ], 4)\n ], 4))\n}","\nimport { defineComponent, PropType, computed } from 'vue'\nimport { PPTTextElement } from '@/types/slides'\nimport ElementOutline from '@/views/components/element/ElementOutline.vue'\n\nimport useElementShadow from '@/views/components/element/hooks/useElementShadow'\n\nexport default defineComponent({\n name: 'base-element-text',\n components: {\n ElementOutline,\n },\n props: {\n elementInfo: {\n type: Object as PropType,\n required: true,\n },\n },\n setup(props) {\n const shadow = computed(() => props.elementInfo.shadow)\n const { shadowStyle } = useElementShadow(shadow)\n\n return {\n shadowStyle,\n }\n },\n})\n","import { render } from \"./BaseTextElement.vue?vue&type=template&id=2ec235c4&scoped=true&ts=true\"\nimport script from \"./BaseTextElement.vue?vue&type=script&lang=ts\"\nexport * from \"./BaseTextElement.vue?vue&type=script&lang=ts\"\n\nimport \"./BaseTextElement.vue?vue&type=style&index=0&id=2ec235c4&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-2ec235c4\"]])\n\nexport default __exports__","import { resolveComponent as _resolveComponent, createVNode as _createVNode, openBlock as _openBlock, createElementBlock as _createElementBlock, createCommentVNode as _createCommentVNode, createElementVNode as _createElementVNode, normalizeClass as _normalizeClass, normalizeStyle as _normalizeStyle, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-2bad8b7f\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = [\"width\", \"height\"]\nconst _hoisted_2 = { key: 0 }\nconst _hoisted_3 = [\"transform\"]\nconst _hoisted_4 = [\"d\", \"fill\", \"stroke\", \"stroke-width\", \"stroke-dasharray\"]\nconst _hoisted_5 = [\"innerHTML\"]\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _component_GradientDefs = _resolveComponent(\"GradientDefs\")!\n\n return (_openBlock(), _createElementBlock(\"div\", {\n class: \"base-element-shape\",\n style: _normalizeStyle({\n top: _ctx.elementInfo.top + 'px',\n left: _ctx.elementInfo.left + 'px',\n width: _ctx.elementInfo.width + 'px',\n height: _ctx.elementInfo.height + 'px',\n })\n }, [\n _createElementVNode(\"div\", {\n class: \"rotate-wrapper\",\n style: _normalizeStyle({ transform: `rotate(${_ctx.elementInfo.rotate}deg)` })\n }, [\n _createElementVNode(\"div\", {\n class: \"element-content\",\n style: _normalizeStyle({\n opacity: _ctx.elementInfo.opacity,\n filter: _ctx.shadowStyle ? `drop-shadow(${_ctx.shadowStyle})` : '',\n transform: _ctx.flipStyle,\n color: _ctx.text.defaultColor,\n fontFamily: _ctx.text.defaultFontName,\n })\n }, [\n (_openBlock(), _createElementBlock(\"svg\", {\n overflow: \"visible\",\n width: _ctx.elementInfo.width,\n height: _ctx.elementInfo.height\n }, [\n (_ctx.elementInfo.gradient)\n ? (_openBlock(), _createElementBlock(\"defs\", _hoisted_2, [\n _createVNode(_component_GradientDefs, {\n id: `base-gradient-${_ctx.elementInfo.id}`,\n type: _ctx.elementInfo.gradient.type,\n color1: _ctx.elementInfo.gradient.color[0],\n color2: _ctx.elementInfo.gradient.color[1],\n rotate: _ctx.elementInfo.gradient.rotate\n }, null, 8, [\"id\", \"type\", \"color1\", \"color2\", \"rotate\"])\n ]))\n : _createCommentVNode(\"\", true),\n _createElementVNode(\"g\", {\n transform: `scale(${_ctx.elementInfo.width / _ctx.elementInfo.viewBox[0]}, ${_ctx.elementInfo.height / _ctx.elementInfo.viewBox[1]}) translate(0,0) matrix(1,0,0,1,0,0)`\n }, [\n _createElementVNode(\"path\", {\n \"vector-effect\": \"non-scaling-stroke\",\n \"stroke-linecap\": \"butt\",\n \"stroke-miterlimit\": \"8\",\n \"stroke-linejoin\": \"\",\n d: _ctx.elementInfo.path,\n fill: _ctx.elementInfo.gradient ? `url(#base-gradient-${_ctx.elementInfo.id})` : _ctx.elementInfo.fill,\n stroke: _ctx.outlineColor,\n \"stroke-width\": _ctx.outlineWidth,\n \"stroke-dasharray\": _ctx.outlineStyle === 'dashed' ? '10 5' : '0 0'\n }, null, 8, _hoisted_4)\n ], 8, _hoisted_3)\n ], 8, _hoisted_1)),\n _createElementVNode(\"div\", {\n class: _normalizeClass([\"shape-text\", _ctx.text.align])\n }, [\n _createElementVNode(\"div\", {\n class: \"ProseMirror-static\",\n innerHTML: _ctx.text.content\n }, null, 8, _hoisted_5)\n ], 2)\n ], 4)\n ], 4)\n ], 4))\n}","\nimport { computed, defineComponent, PropType } from 'vue'\nimport { PPTShapeElement, ShapeText } from '@/types/slides'\nimport useElementOutline from '@/views/components/element/hooks/useElementOutline'\nimport useElementShadow from '@/views/components/element/hooks/useElementShadow'\nimport useElementFlip from '@/views/components/element/hooks/useElementFlip'\n\nimport GradientDefs from './GradientDefs.vue'\n\nexport default defineComponent({\n name: 'base-element-shape',\n components: {\n GradientDefs,\n },\n props: {\n elementInfo: {\n type: Object as PropType,\n required: true,\n },\n },\n setup(props) {\n const outline = computed(() => props.elementInfo.outline)\n const { outlineWidth, outlineStyle, outlineColor } = useElementOutline(outline)\n \n const shadow = computed(() => props.elementInfo.shadow)\n const { shadowStyle } = useElementShadow(shadow)\n\n const flipH = computed(() => props.elementInfo.flipH)\n const flipV = computed(() => props.elementInfo.flipV)\n const { flipStyle } = useElementFlip(flipH, flipV)\n\n const text = computed(() => {\n const defaultText: ShapeText = {\n content: '',\n defaultFontName: '微软雅黑',\n defaultColor: '#000',\n align: 'middle',\n }\n if (!props.elementInfo.text) return defaultText\n\n return props.elementInfo.text\n })\n\n return {\n shadowStyle,\n outlineWidth,\n outlineStyle,\n outlineColor,\n flipStyle,\n text,\n }\n },\n})\n","import { render } from \"./BaseShapeElement.vue?vue&type=template&id=2bad8b7f&scoped=true&ts=true\"\nimport script from \"./BaseShapeElement.vue?vue&type=script&lang=ts\"\nexport * from \"./BaseShapeElement.vue?vue&type=script&lang=ts\"\n\nimport \"./BaseShapeElement.vue?vue&type=style&index=0&id=2bad8b7f&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-2bad8b7f\"]])\n\nexport default __exports__","import { resolveComponent as _resolveComponent, openBlock as _openBlock, createBlock as _createBlock, createCommentVNode as _createCommentVNode, createElementVNode as _createElementVNode, createElementBlock as _createElementBlock, normalizeStyle as _normalizeStyle, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-1d54cbe6\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = [\"width\", \"height\"]\nconst _hoisted_2 = [\"d\", \"stroke\", \"stroke-width\", \"stroke-dasharray\", \"marker-start\", \"marker-end\"]\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _component_LinePointMarker = _resolveComponent(\"LinePointMarker\")!\n\n return (_openBlock(), _createElementBlock(\"div\", {\n class: \"base-element-line\",\n style: _normalizeStyle({\n top: _ctx.elementInfo.top + 'px',\n left: _ctx.elementInfo.left + 'px',\n })\n }, [\n _createElementVNode(\"div\", {\n class: \"element-content\",\n style: _normalizeStyle({ filter: _ctx.shadowStyle ? `drop-shadow(${_ctx.shadowStyle})` : '' })\n }, [\n (_openBlock(), _createElementBlock(\"svg\", {\n overflow: \"visible\",\n width: _ctx.svgWidth,\n height: _ctx.svgHeight\n }, [\n _createElementVNode(\"defs\", null, [\n (_ctx.elementInfo.points[0])\n ? (_openBlock(), _createBlock(_component_LinePointMarker, {\n key: 0,\n id: _ctx.elementInfo.id,\n position: \"start\",\n type: _ctx.elementInfo.points[0],\n color: _ctx.elementInfo.color,\n baseSize: _ctx.elementInfo.width\n }, null, 8, [\"id\", \"type\", \"color\", \"baseSize\"]))\n : _createCommentVNode(\"\", true),\n (_ctx.elementInfo.points[1])\n ? (_openBlock(), _createBlock(_component_LinePointMarker, {\n key: 1,\n id: _ctx.elementInfo.id,\n position: \"end\",\n type: _ctx.elementInfo.points[1],\n color: _ctx.elementInfo.color,\n baseSize: _ctx.elementInfo.width\n }, null, 8, [\"id\", \"type\", \"color\", \"baseSize\"]))\n : _createCommentVNode(\"\", true)\n ]),\n _createElementVNode(\"path\", {\n d: _ctx.path,\n stroke: _ctx.elementInfo.color,\n \"stroke-width\": _ctx.elementInfo.width,\n \"stroke-dasharray\": _ctx.lineDashArray,\n fill: \"none\",\n \"stroke-linecap\": \"\",\n \"stroke-linejoin\": \"\",\n \"stroke-miterlimit\": \"\",\n \"marker-start\": _ctx.elementInfo.points[0] ? `url(#${_ctx.elementInfo.id}-${_ctx.elementInfo.points[0]}-start)` : '',\n \"marker-end\": _ctx.elementInfo.points[1] ? `url(#${_ctx.elementInfo.id}-${_ctx.elementInfo.points[1]}-end)` : ''\n }, null, 8, _hoisted_2)\n ], 8, _hoisted_1))\n ], 4)\n ], 4))\n}","\nimport { computed, defineComponent, PropType } from 'vue'\nimport { PPTLineElement } from '@/types/slides'\nimport { getLineElementPath } from '@/utils/element'\nimport useElementShadow from '@/views/components/element/hooks/useElementShadow'\n\nimport LinePointMarker from './LinePointMarker.vue'\n\nexport default defineComponent({\n name: 'base-element-line',\n components: {\n LinePointMarker,\n },\n props: {\n elementInfo: {\n type: Object as PropType,\n required: true,\n },\n },\n setup(props) {\n const shadow = computed(() => props.elementInfo.shadow)\n const { shadowStyle } = useElementShadow(shadow)\n\n const svgWidth = computed(() => {\n const width = Math.abs(props.elementInfo.start[0] - props.elementInfo.end[0])\n return width < 24 ? 24 : width\n })\n const svgHeight = computed(() => {\n const height = Math.abs(props.elementInfo.start[1] - props.elementInfo.end[1])\n return height < 24 ? 24 : height\n })\n\n const lineDashArray = computed(() => props.elementInfo.style === 'dashed' ? '10, 5' : '0, 0')\n\n const path = computed(() => {\n return getLineElementPath(props.elementInfo)\n })\n\n return {\n shadowStyle,\n svgWidth,\n svgHeight,\n lineDashArray,\n path,\n }\n },\n})\n","import { render } from \"./BaseLineElement.vue?vue&type=template&id=1d54cbe6&scoped=true&ts=true\"\nimport script from \"./BaseLineElement.vue?vue&type=script&lang=ts\"\nexport * from \"./BaseLineElement.vue?vue&type=script&lang=ts\"\n\nimport \"./BaseLineElement.vue?vue&type=style&index=0&id=1d54cbe6&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-1d54cbe6\"]])\n\nexport default __exports__","import { resolveComponent as _resolveComponent, createVNode as _createVNode, normalizeStyle as _normalizeStyle, createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from \"vue\"\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _component_ElementOutline = _resolveComponent(\"ElementOutline\")!\n const _component_Chart = _resolveComponent(\"Chart\")!\n\n return (_openBlock(), _createElementBlock(\"div\", {\n class: \"base-element-chart\",\n style: _normalizeStyle({\r\n top: _ctx.elementInfo.top + 'px',\r\n left: _ctx.elementInfo.left + 'px',\r\n width: _ctx.elementInfo.width + 'px',\r\n height: _ctx.elementInfo.height + 'px',\r\n })\n }, [\n _createElementVNode(\"div\", {\n class: \"rotate-wrapper\",\n style: _normalizeStyle({ transform: `rotate(${_ctx.elementInfo.rotate}deg)` })\n }, [\n _createElementVNode(\"div\", {\n class: \"element-content\",\n style: _normalizeStyle({\r\n backgroundColor: _ctx.elementInfo.fill,\r\n })\n }, [\n _createVNode(_component_ElementOutline, {\n width: _ctx.elementInfo.width,\n height: _ctx.elementInfo.height,\n outline: _ctx.elementInfo.outline\n }, null, 8, [\"width\", \"height\", \"outline\"]),\n _createVNode(_component_Chart, {\n width: _ctx.elementInfo.width * _ctx.zoom,\n height: _ctx.elementInfo.height * _ctx.zoom,\n type: _ctx.elementInfo.chartType,\n data: _ctx.elementInfo.data,\n options: _ctx.elementInfo.options,\n themeColor: _ctx.elementInfo.themeColor,\n gridColor: _ctx.elementInfo.gridColor,\n legends: _ctx.elementInfo.data.legends,\n legend: _ctx.elementInfo.legend || '',\n style: _normalizeStyle({ zoom: 1 / _ctx.zoom })\n }, null, 8, [\"width\", \"height\", \"type\", \"data\", \"options\", \"themeColor\", \"gridColor\", \"legends\", \"legend\", \"style\"])\n ], 4)\n ], 4)\n ], 4))\n}","\r\nimport { computed, defineComponent, inject, PropType, ref } from 'vue'\r\nimport { PPTChartElement } from '@/types/slides'\r\nimport { injectKeySlideScale } from '@/types/injectKey'\r\n\r\nimport ElementOutline from '@/views/components/element/ElementOutline.vue'\r\nimport Chart from './Chart.vue'\r\n\r\nexport default defineComponent({\r\n name: 'base-element-chart',\r\n components: {\r\n ElementOutline,\r\n Chart,\r\n },\r\n props: {\r\n elementInfo: {\r\n type: Object as PropType,\r\n required: true,\r\n },\r\n },\r\n setup() {\r\n const slideScale = inject(injectKeySlideScale) || ref(1)\r\n\r\n const needScaleSize = computed(() => slideScale.value < 1)\r\n const zoom = computed(() => needScaleSize.value ? 1 / slideScale.value : 1)\r\n\r\n return {\r\n needScaleSize,\r\n zoom,\r\n }\r\n },\r\n})\r\n","import { render } from \"./BaseChartElement.vue?vue&type=template&id=342d4bac&scoped=true&ts=true\"\nimport script from \"./BaseChartElement.vue?vue&type=script&lang=ts\"\nexport * from \"./BaseChartElement.vue?vue&type=script&lang=ts\"\n\nimport \"./BaseChartElement.vue?vue&type=style&index=0&id=342d4bac&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-342d4bac\"]])\n\nexport default __exports__","import { resolveComponent as _resolveComponent, createVNode as _createVNode, createElementVNode as _createElementVNode, normalizeStyle as _normalizeStyle, openBlock as _openBlock, createElementBlock as _createElementBlock, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-3bbcc75c\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = { class: \"element-content\" }\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _component_StaticTable = _resolveComponent(\"StaticTable\")!\n\n return (_openBlock(), _createElementBlock(\"div\", {\n class: \"base-element-table\",\n style: _normalizeStyle({\n top: _ctx.elementInfo.top + 'px',\n left: _ctx.elementInfo.left + 'px',\n width: _ctx.elementInfo.width + 'px',\n })\n }, [\n _createElementVNode(\"div\", {\n class: \"rotate-wrapper\",\n style: _normalizeStyle({ transform: `rotate(${_ctx.elementInfo.rotate}deg)` })\n }, [\n _createElementVNode(\"div\", _hoisted_1, [\n _createVNode(_component_StaticTable, {\n data: _ctx.elementInfo.data,\n width: _ctx.elementInfo.width,\n colWidths: _ctx.elementInfo.colWidths,\n outline: _ctx.elementInfo.outline,\n theme: _ctx.elementInfo.theme\n }, null, 8, [\"data\", \"width\", \"colWidths\", \"outline\", \"theme\"])\n ])\n ], 4)\n ], 4))\n}","import { renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, createElementVNode as _createElementVNode, vShow as _vShow, normalizeStyle as _normalizeStyle, withDirectives as _withDirectives, normalizeClass as _normalizeClass, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-74b60291\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = [\"width\"]\nconst _hoisted_2 = [\"rowspan\", \"colspan\"]\nconst _hoisted_3 = [\"innerHTML\"]\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n return (_openBlock(), _createElementBlock(\"div\", {\n class: \"static-table\",\n style: _normalizeStyle({ width: _ctx.totalWidth + 'px' })\n }, [\n _createElementVNode(\"table\", {\n class: _normalizeClass({\n 'theme': _ctx.theme,\n 'row-header': _ctx.theme?.rowHeader,\n 'row-footer': _ctx.theme?.rowFooter,\n 'col-header': _ctx.theme?.colHeader,\n 'col-footer': _ctx.theme?.colFooter,\n }),\n style: _normalizeStyle(`--themeColor: ${_ctx.theme?.color}; --subThemeColor1: ${_ctx.subThemeColor[0]}; --subThemeColor2: ${_ctx.subThemeColor[1]}`)\n }, [\n _createElementVNode(\"colgroup\", null, [\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.colSizeList, (width, index) => {\n return (_openBlock(), _createElementBlock(\"col\", {\n span: \"1\",\n key: index,\n width: width\n }, null, 8, _hoisted_1))\n }), 128))\n ]),\n _createElementVNode(\"tbody\", null, [\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.data, (rowCells, rowIndex) => {\n return (_openBlock(), _createElementBlock(\"tr\", { key: rowIndex }, [\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(rowCells, (cell, colIndex) => {\n return _withDirectives((_openBlock(), _createElementBlock(\"td\", {\n class: \"cell\",\n style: _normalizeStyle({\n borderStyle: _ctx.outline.style,\n borderColor: _ctx.outline.color,\n borderWidth: _ctx.outline.width + 'px',\n ..._ctx.getTextStyle(cell.style),\n }),\n key: cell.id,\n rowspan: cell.rowspan,\n colspan: cell.colspan\n }, [\n _createElementVNode(\"div\", {\n class: \"cell-text\",\n innerHTML: _ctx.formatText(cell.text)\n }, null, 8, _hoisted_3)\n ], 12, _hoisted_2)), [\n [_vShow, !_ctx.hideCells.includes(`${rowIndex}_${colIndex}`)]\n ])\n }), 128))\n ]))\n }), 128))\n ])\n ], 6)\n ], 4))\n}","\nimport { computed, defineComponent, PropType, ref, watch } from 'vue'\nimport { PPTElementOutline, TableCell, TableTheme } from '@/types/slides'\nimport { getTextStyle, formatText } from './utils'\nimport useHideCells from './useHideCells'\nimport useSubThemeColor from './useSubThemeColor'\n\nexport default defineComponent({\n name: 'static-table',\n props: {\n data: {\n type: Array as PropType,\n required: true,\n },\n width: {\n type: Number,\n required: true,\n },\n colWidths: {\n type: Array as PropType,\n required: true,\n },\n outline: {\n type: Object as PropType,\n required: true,\n },\n theme: {\n type: Object as PropType,\n },\n editable: {\n type: Boolean,\n default: true,\n },\n },\n setup(props) {\n const colSizeList = ref([])\n const totalWidth = computed(() => colSizeList.value.reduce((a, b) => a + b))\n\n watch([\n () => props.colWidths,\n () => props.width,\n ], () => {\n colSizeList.value = props.colWidths.map(item => item * props.width)\n }, { immediate: true })\n\n const cells = computed(() => props.data)\n const { hideCells } = useHideCells(cells)\n\n const theme = computed(() => props.theme)\n const { subThemeColor } = useSubThemeColor(theme)\n\n return {\n colSizeList,\n totalWidth,\n hideCells,\n getTextStyle,\n formatText,\n subThemeColor,\n }\n },\n})\n","import { render } from \"./StaticTable.vue?vue&type=template&id=74b60291&scoped=true&ts=true\"\nimport script from \"./StaticTable.vue?vue&type=script&lang=ts\"\nexport * from \"./StaticTable.vue?vue&type=script&lang=ts\"\n\nimport \"./StaticTable.vue?vue&type=style&index=0&id=74b60291&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-74b60291\"]])\n\nexport default __exports__","\nimport { defineComponent, PropType } from 'vue'\nimport { PPTTableElement } from '@/types/slides'\n\nimport StaticTable from './StaticTable.vue'\n\nexport default defineComponent({\n name: 'base-element-table',\n components: {\n StaticTable,\n },\n props: {\n elementInfo: {\n type: Object as PropType,\n required: true,\n },\n },\n})\n","import { render } from \"./BaseTableElement.vue?vue&type=template&id=3bbcc75c&scoped=true&ts=true\"\nimport script from \"./BaseTableElement.vue?vue&type=script&lang=ts\"\nexport * from \"./BaseTableElement.vue?vue&type=script&lang=ts\"\n\nimport \"./BaseTableElement.vue?vue&type=style&index=0&id=3bbcc75c&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-3bbcc75c\"]])\n\nexport default __exports__","import { createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock, normalizeStyle as _normalizeStyle, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-4118f3dd\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = { class: \"element-content\" }\nconst _hoisted_2 = [\"width\", \"height\", \"stroke\", \"stroke-width\"]\nconst _hoisted_3 = [\"transform\"]\nconst _hoisted_4 = [\"d\"]\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n return (_openBlock(), _createElementBlock(\"div\", {\n class: \"base-element-latex\",\n style: _normalizeStyle({\n top: _ctx.elementInfo.top + 'px',\n left: _ctx.elementInfo.left + 'px',\n width: _ctx.elementInfo.width + 'px',\n height: _ctx.elementInfo.height + 'px',\n })\n }, [\n _createElementVNode(\"div\", {\n class: \"rotate-wrapper\",\n style: _normalizeStyle({ transform: `rotate(${_ctx.elementInfo.rotate}deg)` })\n }, [\n _createElementVNode(\"div\", _hoisted_1, [\n (_openBlock(), _createElementBlock(\"svg\", {\n overflow: \"visible\",\n width: _ctx.elementInfo.width,\n height: _ctx.elementInfo.height,\n stroke: _ctx.elementInfo.color,\n \"stroke-width\": _ctx.elementInfo.strokeWidth,\n fill: \"none\",\n \"stroke-linecap\": \"round\",\n \"stroke-linejoin\": \"round\"\n }, [\n _createElementVNode(\"g\", {\n transform: `scale(${_ctx.elementInfo.width / _ctx.elementInfo.viewBox[0]}, ${_ctx.elementInfo.height / _ctx.elementInfo.viewBox[1]}) translate(0,0) matrix(1,0,0,1,0,0)`\n }, [\n _createElementVNode(\"path\", {\n d: _ctx.elementInfo.path\n }, null, 8, _hoisted_4)\n ], 8, _hoisted_3)\n ], 8, _hoisted_2))\n ])\n ], 4)\n ], 4))\n}","\nimport { defineComponent, PropType } from 'vue'\nimport { PPTLatexElement } from '@/types/slides'\n\nexport default defineComponent({\n name: 'base-element-latex',\n props: {\n elementInfo: {\n type: Object as PropType,\n required: true,\n },\n },\n})\n","import { render } from \"./BaseLatexElement.vue?vue&type=template&id=4118f3dd&scoped=true&ts=true\"\nimport script from \"./BaseLatexElement.vue?vue&type=script&lang=ts\"\nexport * from \"./BaseLatexElement.vue?vue&type=script&lang=ts\"\n\nimport \"./BaseLatexElement.vue?vue&type=style&index=0&id=4118f3dd&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-4118f3dd\"]])\n\nexport default __exports__","import { resolveComponent as _resolveComponent, createVNode as _createVNode, normalizeStyle as _normalizeStyle, createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from \"vue\"\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _component_IconPlayOne = _resolveComponent(\"IconPlayOne\")!\n\n return (_openBlock(), _createElementBlock(\"div\", {\n class: \"base-element-video\",\n style: _normalizeStyle({\n top: _ctx.elementInfo.top + 'px',\n left: _ctx.elementInfo.left + 'px',\n width: _ctx.elementInfo.width + 'px',\n height: _ctx.elementInfo.height + 'px',\n })\n }, [\n _createElementVNode(\"div\", {\n class: \"rotate-wrapper\",\n style: _normalizeStyle({ transform: `rotate(${_ctx.elementInfo.rotate}deg)` })\n }, [\n _createElementVNode(\"div\", {\n class: \"element-content\",\n style: _normalizeStyle({ backgroundImage: `url(${_ctx.elementInfo.poster})` })\n }, [\n _createVNode(_component_IconPlayOne, { class: \"icon\" })\n ], 4)\n ], 4)\n ], 4))\n}","\nimport { defineComponent, PropType } from 'vue'\nimport { PPTVideoElement } from '@/types/slides'\n\nexport default defineComponent({\n name: 'base-element-video',\n props: {\n elementInfo: {\n type: Object as PropType,\n required: true,\n },\n },\n})\n","import { render } from \"./BaseVideoElement.vue?vue&type=template&id=1c831c75&scoped=true&ts=true\"\nimport script from \"./BaseVideoElement.vue?vue&type=script&lang=ts\"\nexport * from \"./BaseVideoElement.vue?vue&type=script&lang=ts\"\n\nimport \"./BaseVideoElement.vue?vue&type=style&index=0&id=1c831c75&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-1c831c75\"]])\n\nexport default __exports__","import { resolveComponent as _resolveComponent, normalizeStyle as _normalizeStyle, createVNode as _createVNode, createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-513e91c4\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = { class: \"element-content\" }\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _component_IconVolumeNotice = _resolveComponent(\"IconVolumeNotice\")!\n\n return (_openBlock(), _createElementBlock(\"div\", {\n class: \"base-element-audio\",\n style: _normalizeStyle({\n top: _ctx.elementInfo.top + 'px',\n left: _ctx.elementInfo.left + 'px',\n width: _ctx.elementInfo.width + 'px',\n height: _ctx.elementInfo.height + 'px',\n })\n }, [\n _createElementVNode(\"div\", {\n class: \"rotate-wrapper\",\n style: _normalizeStyle({ transform: `rotate(${_ctx.elementInfo.rotate}deg)` })\n }, [\n _createElementVNode(\"div\", _hoisted_1, [\n _createVNode(_component_IconVolumeNotice, {\n class: \"audio-icon\",\n style: _normalizeStyle({\n fontSize: _ctx.audioIconSize,\n color: _ctx.elementInfo.color,\n })\n }, null, 8, [\"style\"])\n ])\n ], 4)\n ], 4))\n}","\nimport { computed, defineComponent, PropType } from 'vue'\nimport { PPTAudioElement } from '@/types/slides'\n\nexport default defineComponent({\n name: 'base-element-audio',\n props: {\n elementInfo: {\n type: Object as PropType,\n required: true,\n },\n },\n setup(props) {\n const audioIconSize = computed(() => {\n return Math.min(props.elementInfo.width, props.elementInfo.height) + 'px'\n })\n\n return {\n audioIconSize,\n }\n },\n})\n","import { render } from \"./BaseAudioElement.vue?vue&type=template&id=513e91c4&scoped=true&ts=true\"\nimport script from \"./BaseAudioElement.vue?vue&type=script&lang=ts\"\nexport * from \"./BaseAudioElement.vue?vue&type=script&lang=ts\"\n\nimport \"./BaseAudioElement.vue?vue&type=style&index=0&id=513e91c4&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-513e91c4\"]])\n\nexport default __exports__","\nimport { computed, defineComponent, PropType } from 'vue'\nimport { ElementTypes, PPTElement } from '@/types/slides'\n\nimport BaseImageElement from '@/views/components/element/ImageElement/BaseImageElement.vue'\nimport BaseTextElement from '@/views/components/element/TextElement/BaseTextElement.vue'\nimport BaseShapeElement from '@/views/components/element/ShapeElement/BaseShapeElement.vue'\nimport BaseLineElement from '@/views/components/element/LineElement/BaseLineElement.vue'\nimport BaseChartElement from '@/views/components/element/ChartElement/BaseChartElement.vue'\nimport BaseTableElement from '@/views/components/element/TableElement/BaseTableElement.vue'\nimport BaseLatexElement from '@/views/components/element/LatexElement/BaseLatexElement.vue'\nimport BaseVideoElement from '@/views/components/element/VideoElement/BaseVideoElement.vue'\nimport BaseAudioElement from '@/views/components/element/AudioElement/BaseAudioElement.vue'\n\nexport default defineComponent({\n name: 'base-element',\n props: {\n elementInfo: {\n type: Object as PropType,\n required: true,\n },\n elementIndex: {\n type: Number,\n required: true,\n },\n },\n setup(props) {\n const currentElementComponent = computed(() => {\n const elementTypeMap = {\n [ElementTypes.IMAGE]: BaseImageElement,\n [ElementTypes.TEXT]: BaseTextElement,\n [ElementTypes.SHAPE]: BaseShapeElement,\n [ElementTypes.LINE]: BaseLineElement,\n [ElementTypes.CHART]: BaseChartElement,\n [ElementTypes.TABLE]: BaseTableElement,\n [ElementTypes.LATEX]: BaseLatexElement,\n [ElementTypes.VIDEO]: BaseVideoElement,\n [ElementTypes.AUDIO]: BaseAudioElement,\n }\n return elementTypeMap[props.elementInfo.type] || null\n })\n\n return {\n currentElementComponent,\n }\n },\n})\n","import { render } from \"./ThumbnailElement.vue?vue&type=template&id=36b099d8&ts=true\"\nimport script from \"./ThumbnailElement.vue?vue&type=script&lang=ts\"\nexport * from \"./ThumbnailElement.vue?vue&type=script&lang=ts\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render]])\n\nexport default __exports__","\nimport { computed, PropType, defineComponent, provide } from 'vue'\nimport { storeToRefs } from 'pinia'\nimport { useSlidesStore } from '@/store'\nimport { Slide } from '@/types/slides'\nimport { injectKeySlideScale } from '@/types/injectKey'\nimport { VIEWPORT_SIZE } from '@/configs/canvas'\nimport useSlideBackgroundStyle from '@/hooks/useSlideBackgroundStyle'\n\nimport ThumbnailElement from './ThumbnailElement.vue'\n\nexport default defineComponent({\n name: 'thumbnail-slide',\n components: {\n ThumbnailElement,\n },\n props: {\n slide: {\n type: Object as PropType,\n required: true,\n },\n size: {\n type: Number,\n required: true,\n },\n visible: {\n type: Boolean,\n default: true,\n },\n },\n setup(props) {\n const { viewportRatio } = storeToRefs(useSlidesStore())\n\n const background = computed(() => props.slide.background)\n const { backgroundStyle } = useSlideBackgroundStyle(background)\n\n const scale = computed(() => props.size / VIEWPORT_SIZE)\n provide(injectKeySlideScale, scale)\n\n return {\n scale,\n backgroundStyle,\n VIEWPORT_SIZE,\n viewportRatio,\n }\n },\n})\n","import { render } from \"./index.vue?vue&type=template&id=025539cf&scoped=true&ts=true\"\nimport script from \"./index.vue?vue&type=script&lang=ts\"\nexport * from \"./index.vue?vue&type=script&lang=ts\"\n\nimport \"./index.vue?vue&type=style&index=0&id=025539cf&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-025539cf\"]])\n\nexport default __exports__","\nimport { computed, defineComponent, onMounted, ref } from 'vue'\nimport { storeToRefs } from 'pinia'\nimport { useMainStore, useSlidesStore } from '@/store'\nimport { PPTElementLink } from '@/types/slides'\nimport useLink from '@/hooks/useLink'\n\nimport ThumbnailSlide from '@/views/components/ThumbnailSlide/index.vue'\n\ntype TypeKey = 'web' | 'slide'\ninterface TabItem {\n key: TypeKey;\n label: string;\n}\n\nexport default defineComponent({\n name: 'link-dialog',\n emits: ['close'],\n components: {\n ThumbnailSlide,\n },\n setup(props, { emit }) {\n const { handleElement } = storeToRefs(useMainStore())\n const { slides } = storeToRefs(useSlidesStore())\n\n const type = ref('web')\n const address = ref('')\n const slideId = ref('')\n\n slideId.value = slides.value[0].id\n\n const selectedSlide = computed(() => {\n if (!slideId.value) return null\n\n return slides.value.find(item => item.id === slideId.value) || null\n })\n\n const tabs: TabItem[] = [\n { key: 'web', label: '网页链接' },\n { key: 'slide', label: '幻灯片页面' },\n ]\n\n const { setLink } = useLink()\n\n onMounted(() => {\n if (handleElement.value?.link) {\n if (handleElement.value.link.type === 'web') address.value = handleElement.value.link.target\n else if (handleElement.value.link.type === 'slide') slideId.value = handleElement.value.link.target\n\n type.value = handleElement.value.link.type\n }\n })\n\n const close = () => emit('close')\n\n const save = () => {\n const link: PPTElementLink = {\n type: type.value,\n target: type.value === 'web' ? address.value : slideId.value,\n }\n if (handleElement.value) {\n const success = setLink(handleElement.value, link)\n if (success) close()\n else address.value = ''\n }\n }\n\n return {\n slides,\n tabs,\n type,\n address,\n slideId,\n selectedSlide,\n close,\n save,\n }\n },\n})\n","import { render } from \"./LinkDialog.vue?vue&type=template&id=57530ef0&scoped=true&ts=true\"\nimport script from \"./LinkDialog.vue?vue&type=script&lang=ts\"\nexport * from \"./LinkDialog.vue?vue&type=script&lang=ts\"\n\nimport \"./LinkDialog.vue?vue&type=style&index=0&id=57530ef0&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-57530ef0\"]])\n\nexport default __exports__","\nimport { defineComponent, onMounted, provide, ref, watch, watchEffect } from 'vue'\nimport { throttle } from 'lodash'\nimport { storeToRefs } from 'pinia'\nimport { useMainStore, useSlidesStore, useKeyboardStore } from '@/store'\nimport { ContextmenuItem } from '@/components/Contextmenu/types'\nimport { PPTElement } from '@/types/slides'\nimport { AlignmentLineProps } from '@/types/edit'\nimport { injectKeySlideScale } from '@/types/injectKey'\nimport { removeAllRanges } from '@/utils/selection'\nimport { KEYS } from '@/configs/hotkey'\n\nimport useViewportSize from './hooks/useViewportSize'\nimport useMouseSelection from './hooks/useMouseSelection'\nimport useDropImageOrText from './hooks/useDropImageOrText'\nimport useRotateElement from './hooks/useRotateElement'\nimport useScaleElement from './hooks/useScaleElement'\nimport useSelectElement from './hooks/useSelectElement'\nimport useDragElement from './hooks/useDragElement'\nimport useDragLineElement from './hooks/useDragLineElement'\nimport useInsertFromCreateSelection from './hooks/useInsertFromCreateSelection'\n\nimport useDeleteElement from '@/hooks/useDeleteElement'\nimport useCopyAndPasteElement from '@/hooks/useCopyAndPasteElement'\nimport useSelectAllElement from '@/hooks/useSelectAllElement'\nimport useScaleCanvas from '@/hooks/useScaleCanvas'\nimport useScreening from '@/hooks/useScreening'\nimport useSlideHandler from '@/hooks/useSlideHandler'\n\nimport EditableElement from './EditableElement.vue'\nimport MouseSelection from './MouseSelection.vue'\nimport ViewportBackground from './ViewportBackground.vue'\nimport AlignmentLine from './AlignmentLine.vue'\nimport Ruler from './Ruler.vue'\nimport ElementCreateSelection from './ElementCreateSelection.vue'\nimport MultiSelectOperate from './Operate/MultiSelectOperate.vue'\nimport Operate from './Operate/index.vue'\nimport LinkDialog from './LinkDialog.vue'\n\nexport default defineComponent({\n name: 'editor-canvas',\n components: {\n EditableElement,\n MouseSelection,\n ViewportBackground,\n AlignmentLine,\n Ruler,\n ElementCreateSelection,\n MultiSelectOperate,\n Operate,\n LinkDialog,\n },\n setup() {\n const mainStore = useMainStore()\n const {\n activeElementIdList,\n activeGroupElementId,\n handleElementId,\n editorAreaFocus,\n showGridLines,\n showRuler,\n creatingElement,\n canvasScale,\n } = storeToRefs(mainStore)\n const { currentSlide } = storeToRefs(useSlidesStore())\n const { ctrlKeyState, spaceKeyState } = storeToRefs(useKeyboardStore())\n\n const viewportRef = ref()\n const alignmentLines = ref([])\n\n const linkDialogVisible = ref(false)\n const openLinkDialog = () => linkDialogVisible.value = true\n\n watch(handleElementId, () => {\n mainStore.setActiveGroupElementId('')\n })\n\n const elementList = ref([])\n const setLocalElementList = () => {\n elementList.value = currentSlide.value ? JSON.parse(JSON.stringify(currentSlide.value.elements)) : []\n }\n watchEffect(setLocalElementList)\n\n const canvasRef = ref()\n const { dragViewport, viewportStyles } = useViewportSize(canvasRef)\n\n useDropImageOrText(canvasRef)\n\n const { mouseSelection, mouseSelectionVisible, mouseSelectionQuadrant, updateMouseSelection } = useMouseSelection(elementList, viewportRef)\n\n const { dragElement } = useDragElement(elementList, alignmentLines)\n const { dragLineElement } = useDragLineElement(elementList)\n const { selectElement } = useSelectElement(elementList, dragElement)\n const { scaleElement, scaleMultiElement } = useScaleElement(elementList, alignmentLines)\n const { rotateElement } = useRotateElement(elementList, viewportRef)\n\n const { selectAllElement } = useSelectAllElement()\n const { deleteAllElements } = useDeleteElement()\n const { pasteElement } = useCopyAndPasteElement()\n const { enterScreeningFromStart } = useScreening()\n const { updateSlideIndex } = useSlideHandler()\n\n // 组件渲染时,如果存在元素焦点,需要清除\n // 这种情况存在于:有焦点元素的情况下进入了放映模式,再退出时,需要清除原先的焦点(因为可能已经切换了页面)\n onMounted(() => {\n if (activeElementIdList.value.length) mainStore.setActiveElementIdList([])\n })\n\n // 点击画布的空白区域:清空焦点元素、设置画布焦点、清除文字选区\n const handleClickBlankArea = (e: MouseEvent) => {\n mainStore.setActiveElementIdList([])\n\n if (!spaceKeyState.value) updateMouseSelection(e)\n else dragViewport(e)\n\n if (!editorAreaFocus.value) mainStore.setEditorareaFocus(true)\n removeAllRanges()\n }\n\n // 移除画布编辑区域焦点\n const removeEditorAreaFocus = () => {\n if (editorAreaFocus.value) mainStore.setEditorareaFocus(false)\n }\n\n // 滚动鼠标\n const { scaleCanvas } = useScaleCanvas()\n const throttleScaleCanvas = throttle(scaleCanvas, 100, { leading: true, trailing: false })\n const throttleUpdateSlideIndex = throttle(updateSlideIndex, 300, { leading: true, trailing: false })\n\n const handleMousewheelCanvas = (e: WheelEvent) => {\n e.preventDefault()\n\n // 按住Ctrl键时:缩放画布\n if (ctrlKeyState.value) {\n if (e.deltaY > 0) throttleScaleCanvas('-')\n else if (e.deltaY < 0) throttleScaleCanvas('+')\n }\n // 上下翻页\n else {\n if (e.deltaY > 0) throttleUpdateSlideIndex(KEYS.DOWN)\n else if (e.deltaY < 0) throttleUpdateSlideIndex(KEYS.UP)\n }\n }\n\n // 开关网格线\n const toggleGridLines = () => {\n mainStore.setGridLinesState(!showGridLines.value)\n }\n\n // 开关标尺\n const toggleRuler = () => {\n mainStore.setRulerState(!showRuler.value)\n }\n\n // 在鼠标绘制的范围插入元素\n const { insertElementFromCreateSelection } = useInsertFromCreateSelection(viewportRef)\n\n const contextmenus = (): ContextmenuItem[] => {\n return [\n {\n text: '粘贴',\n subText: 'Ctrl + V',\n handler: pasteElement,\n },\n {\n text: '全选',\n subText: 'Ctrl + A',\n handler: selectAllElement,\n },\n {\n text: '网格线',\n subText: showGridLines.value ? '√' : '',\n handler: toggleGridLines,\n },\n {\n text: '标尺',\n subText: showRuler.value ? '√' : '',\n handler: toggleRuler,\n },\n {\n text: '重置当前页',\n handler: deleteAllElements,\n },\n { divider: true },\n {\n text: '幻灯片放映',\n subText: 'F5',\n handler: enterScreeningFromStart,\n },\n ]\n }\n\n provide(injectKeySlideScale, canvasScale)\n\n return {\n elementList,\n activeElementIdList,\n handleElementId,\n activeGroupElementId,\n canvasRef,\n viewportRef,\n viewportStyles,\n canvasScale,\n mouseSelection,\n mouseSelectionVisible,\n mouseSelectionQuadrant,\n creatingElement,\n alignmentLines,\n linkDialogVisible,\n spaceKeyState,\n showRuler,\n openLinkDialog,\n handleClickBlankArea,\n removeEditorAreaFocus,\n insertElementFromCreateSelection,\n selectElement,\n rotateElement,\n scaleElement,\n dragLineElement,\n scaleMultiElement,\n handleMousewheelCanvas,\n contextmenus,\n }\n },\n})\n","import { render } from \"./index.vue?vue&type=template&id=34ef1f93&scoped=true&ts=true\"\nimport script from \"./index.vue?vue&type=script&lang=ts\"\nexport * from \"./index.vue?vue&type=script&lang=ts\"\n\nimport \"./index.vue?vue&type=style&index=0&id=34ef1f93&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-34ef1f93\"]])\n\nexport default __exports__","import { resolveComponent as _resolveComponent, normalizeClass as _normalizeClass, createVNode as _createVNode, withCtx as _withCtx, createElementVNode as _createElementVNode, renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, toDisplayString as _toDisplayString, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-e9ddaac8\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = { class: \"canvas-tool\" }\nconst _hoisted_2 = { class: \"left-handler\" }\nconst _hoisted_3 = { class: \"add-element-handler\" }\nconst _hoisted_4 = { class: \"right-handler\" }\nconst _hoisted_5 = { class: \"viewport-size-preset\" }\nconst _hoisted_6 = [\"onClick\"]\nconst _hoisted_7 = { class: \"text\" }\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _component_IconBack = _resolveComponent(\"IconBack\")!\n const _component_Tooltip = _resolveComponent(\"Tooltip\")!\n const _component_IconNext = _resolveComponent(\"IconNext\")!\n const _component_IconFontSize = _resolveComponent(\"IconFontSize\")!\n const _component_IconPicture = _resolveComponent(\"IconPicture\")!\n const _component_FileInput = _resolveComponent(\"FileInput\")!\n const _component_ShapePool = _resolveComponent(\"ShapePool\")!\n const _component_IconGraphicDesign = _resolveComponent(\"IconGraphicDesign\")!\n const _component_Popover = _resolveComponent(\"Popover\")!\n const _component_LinePool = _resolveComponent(\"LinePool\")!\n const _component_IconConnection = _resolveComponent(\"IconConnection\")!\n const _component_ChartPool = _resolveComponent(\"ChartPool\")!\n const _component_IconChartProportion = _resolveComponent(\"IconChartProportion\")!\n const _component_TableGenerator = _resolveComponent(\"TableGenerator\")!\n const _component_IconInsertTable = _resolveComponent(\"IconInsertTable\")!\n const _component_IconFormula = _resolveComponent(\"IconFormula\")!\n const _component_MediaInput = _resolveComponent(\"MediaInput\")!\n const _component_IconVideoTwo = _resolveComponent(\"IconVideoTwo\")!\n const _component_IconMinus = _resolveComponent(\"IconMinus\")!\n const _component_IconPlus = _resolveComponent(\"IconPlus\")!\n const _component_IconFullScreen = _resolveComponent(\"IconFullScreen\")!\n const _component_LaTeXEditor = _resolveComponent(\"LaTeXEditor\")!\n const _component_Modal = _resolveComponent(\"Modal\")!\n\n return (_openBlock(), _createElementBlock(\"div\", _hoisted_1, [\n _createElementVNode(\"div\", _hoisted_2, [\n _createVNode(_component_Tooltip, {\n mouseLeaveDelay: 0,\n mouseEnterDelay: 0.5,\n title: \"撤销\"\n }, {\n default: _withCtx(() => [\n _createVNode(_component_IconBack, {\n class: _normalizeClass([\"handler-item\", { 'disable': !_ctx.canUndo }]),\n onClick: _cache[0] || (_cache[0] = ($event: any) => (_ctx.undo()))\n }, null, 8, [\"class\"])\n ]),\n _: 1\n }, 8, [\"mouseEnterDelay\"]),\n _createVNode(_component_Tooltip, {\n mouseLeaveDelay: 0,\n mouseEnterDelay: 0.5,\n title: \"重做\"\n }, {\n default: _withCtx(() => [\n _createVNode(_component_IconNext, {\n class: _normalizeClass([\"handler-item\", { 'disable': !_ctx.canRedo }]),\n onClick: _cache[1] || (_cache[1] = ($event: any) => (_ctx.redo()))\n }, null, 8, [\"class\"])\n ]),\n _: 1\n }, 8, [\"mouseEnterDelay\"])\n ]),\n _createElementVNode(\"div\", _hoisted_3, [\n _createVNode(_component_Tooltip, {\n mouseLeaveDelay: 0,\n mouseEnterDelay: 0.5,\n title: \"插入文字\"\n }, {\n default: _withCtx(() => [\n _createVNode(_component_IconFontSize, {\n class: _normalizeClass([\"handler-item\", { 'active': _ctx.creatingElement?.type === 'text' }]),\n onClick: _cache[2] || (_cache[2] = ($event: any) => (_ctx.drawText()))\n }, null, 8, [\"class\"])\n ]),\n _: 1\n }, 8, [\"mouseEnterDelay\"]),\n _createVNode(_component_FileInput, {\n onChange: _cache[3] || (_cache[3] = files => _ctx.insertImageElement(files))\n }, {\n default: _withCtx(() => [\n _createVNode(_component_Tooltip, {\n mouseLeaveDelay: 0,\n mouseEnterDelay: 0.5,\n title: \"插入图片\"\n }, {\n default: _withCtx(() => [\n _createVNode(_component_IconPicture, { class: \"handler-item\" })\n ]),\n _: 1\n }, 8, [\"mouseEnterDelay\"])\n ]),\n _: 1\n }),\n _createVNode(_component_Popover, {\n trigger: \"click\",\n visible: _ctx.shapePoolVisible,\n \"onUpdate:visible\": _cache[5] || (_cache[5] = ($event: any) => ((_ctx.shapePoolVisible) = $event))\n }, {\n content: _withCtx(() => [\n _createVNode(_component_ShapePool, {\n onSelect: _cache[4] || (_cache[4] = shape => _ctx.drawShape(shape))\n })\n ]),\n default: _withCtx(() => [\n _createVNode(_component_Tooltip, {\n mouseLeaveDelay: 0,\n mouseEnterDelay: 0.5,\n title: \"插入形状\"\n }, {\n default: _withCtx(() => [\n _createVNode(_component_IconGraphicDesign, {\n class: _normalizeClass([\"handler-item\", { 'active': _ctx.creatingElement?.type === 'shape' }])\n }, null, 8, [\"class\"])\n ]),\n _: 1\n }, 8, [\"mouseEnterDelay\"])\n ]),\n _: 1\n }, 8, [\"visible\"]),\n _createVNode(_component_Popover, {\n trigger: \"click\",\n visible: _ctx.linePoolVisible,\n \"onUpdate:visible\": _cache[7] || (_cache[7] = ($event: any) => ((_ctx.linePoolVisible) = $event))\n }, {\n content: _withCtx(() => [\n _createVNode(_component_LinePool, {\n onSelect: _cache[6] || (_cache[6] = line => _ctx.drawLine(line))\n })\n ]),\n default: _withCtx(() => [\n _createVNode(_component_Tooltip, {\n mouseLeaveDelay: 0,\n mouseEnterDelay: 0.5,\n title: \"插入线条\"\n }, {\n default: _withCtx(() => [\n _createVNode(_component_IconConnection, {\n class: _normalizeClass([\"handler-item\", { 'active': _ctx.creatingElement?.type === 'line' }])\n }, null, 8, [\"class\"])\n ]),\n _: 1\n }, 8, [\"mouseEnterDelay\"])\n ]),\n _: 1\n }, 8, [\"visible\"]),\n _createVNode(_component_Popover, {\n trigger: \"click\",\n visible: _ctx.chartPoolVisible,\n \"onUpdate:visible\": _cache[9] || (_cache[9] = ($event: any) => ((_ctx.chartPoolVisible) = $event))\n }, {\n content: _withCtx(() => [\n _createVNode(_component_ChartPool, {\n onSelect: _cache[8] || (_cache[8] = chart => { _ctx.createChartElement(chart); _ctx.chartPoolVisible = false })\n })\n ]),\n default: _withCtx(() => [\n _createVNode(_component_Tooltip, {\n mouseLeaveDelay: 0,\n mouseEnterDelay: 0.5,\n title: \"插入图表\"\n }, {\n default: _withCtx(() => [\n _createVNode(_component_IconChartProportion, { class: \"handler-item\" })\n ]),\n _: 1\n }, 8, [\"mouseEnterDelay\"])\n ]),\n _: 1\n }, 8, [\"visible\"]),\n _createVNode(_component_Popover, {\n trigger: \"click\",\n visible: _ctx.tableGeneratorVisible,\n \"onUpdate:visible\": _cache[12] || (_cache[12] = ($event: any) => ((_ctx.tableGeneratorVisible) = $event))\n }, {\n content: _withCtx(() => [\n _createVNode(_component_TableGenerator, {\n onClose: _cache[10] || (_cache[10] = ($event: any) => (_ctx.tableGeneratorVisible = false)),\n onInsert: _cache[11] || (_cache[11] = ({ row, col }) => { _ctx.createTableElement(row, col); _ctx.tableGeneratorVisible = false })\n })\n ]),\n default: _withCtx(() => [\n _createVNode(_component_Tooltip, {\n mouseLeaveDelay: 0,\n mouseEnterDelay: 0.5,\n title: \"插入表格\"\n }, {\n default: _withCtx(() => [\n _createVNode(_component_IconInsertTable, { class: \"handler-item\" })\n ]),\n _: 1\n }, 8, [\"mouseEnterDelay\"])\n ]),\n _: 1\n }, 8, [\"visible\"]),\n _createVNode(_component_Tooltip, {\n mouseLeaveDelay: 0,\n mouseEnterDelay: 0.5,\n title: \"插入公式\"\n }, {\n default: _withCtx(() => [\n _createVNode(_component_IconFormula, {\n class: \"handler-item\",\n onClick: _cache[13] || (_cache[13] = ($event: any) => (_ctx.latexEditorVisible = true))\n })\n ]),\n _: 1\n }, 8, [\"mouseEnterDelay\"]),\n _createVNode(_component_Popover, {\n trigger: \"click\",\n visible: _ctx.mediaInputVisible,\n \"onUpdate:visible\": _cache[17] || (_cache[17] = ($event: any) => ((_ctx.mediaInputVisible) = $event))\n }, {\n content: _withCtx(() => [\n _createVNode(_component_MediaInput, {\n onClose: _cache[14] || (_cache[14] = ($event: any) => (_ctx.mediaInputVisible = false)),\n onInsertVideo: _cache[15] || (_cache[15] = src => { _ctx.createVideoElement(src); _ctx.mediaInputVisible = false }),\n onInsertAudio: _cache[16] || (_cache[16] = src => { _ctx.createAudioElement(src); _ctx.mediaInputVisible = false })\n })\n ]),\n default: _withCtx(() => [\n _createVNode(_component_Tooltip, {\n mouseLeaveDelay: 0,\n mouseEnterDelay: 0.5,\n title: \"插入音视频\"\n }, {\n default: _withCtx(() => [\n _createVNode(_component_IconVideoTwo, { class: \"handler-item\" })\n ]),\n _: 1\n }, 8, [\"mouseEnterDelay\"])\n ]),\n _: 1\n }, 8, [\"visible\"])\n ]),\n _createElementVNode(\"div\", _hoisted_4, [\n _createVNode(_component_IconMinus, {\n class: \"handler-item viewport-size\",\n onClick: _cache[18] || (_cache[18] = ($event: any) => (_ctx.scaleCanvas('-')))\n }),\n _createVNode(_component_Popover, {\n trigger: \"click\",\n visible: _ctx.canvasScaleVisible,\n \"onUpdate:visible\": _cache[19] || (_cache[19] = ($event: any) => ((_ctx.canvasScaleVisible) = $event))\n }, {\n content: _withCtx(() => [\n _createElementVNode(\"div\", _hoisted_5, [\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.canvasScalePresetList, (item) => {\n return (_openBlock(), _createElementBlock(\"div\", {\n class: \"preset-item\",\n key: item,\n onClick: ($event: any) => (_ctx.applyCanvasPresetScale(item))\n }, _toDisplayString(item) + \"%\", 9, _hoisted_6))\n }), 128))\n ])\n ]),\n default: _withCtx(() => [\n _createElementVNode(\"span\", _hoisted_7, _toDisplayString(_ctx.canvasScalePercentage), 1)\n ]),\n _: 1\n }, 8, [\"visible\"]),\n _createVNode(_component_IconPlus, {\n class: \"handler-item viewport-size\",\n onClick: _cache[20] || (_cache[20] = ($event: any) => (_ctx.scaleCanvas('+')))\n }),\n _createVNode(_component_Tooltip, {\n mouseLeaveDelay: 0,\n mouseEnterDelay: 0.5,\n title: \"适应屏幕\"\n }, {\n default: _withCtx(() => [\n _createVNode(_component_IconFullScreen, {\n class: \"handler-item viewport-size-adaptation\",\n onClick: _cache[21] || (_cache[21] = ($event: any) => (_ctx.resetCanvas()))\n })\n ]),\n _: 1\n }, 8, [\"mouseEnterDelay\"])\n ]),\n _createVNode(_component_Modal, {\n visible: _ctx.latexEditorVisible,\n \"onUpdate:visible\": _cache[24] || (_cache[24] = ($event: any) => ((_ctx.latexEditorVisible) = $event)),\n footer: null,\n centered: \"\",\n width: 880,\n destroyOnClose: \"\"\n }, {\n default: _withCtx(() => [\n _createVNode(_component_LaTeXEditor, {\n onClose: _cache[22] || (_cache[22] = ($event: any) => (_ctx.latexEditorVisible = false)),\n onUpdate: _cache[23] || (_cache[23] = data => { _ctx.createLatexElement(data); _ctx.latexEditorVisible = false })\n })\n ]),\n _: 1\n }, 8, [\"visible\"])\n ]))\n}","import { renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, toDisplayString as _toDisplayString, createElementVNode as _createElementVNode, normalizeClass as _normalizeClass, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-f3479f82\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = { class: \"shape-pool\" }\nconst _hoisted_2 = { class: \"category-name\" }\nconst _hoisted_3 = { class: \"shape-list\" }\nconst _hoisted_4 = [\"onClick\"]\nconst _hoisted_5 = {\n overflow: \"visible\",\n width: \"18\",\n height: \"18\"\n}\nconst _hoisted_6 = [\"transform\"]\nconst _hoisted_7 = [\"fill\", \"stroke\", \"d\"]\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n return (_openBlock(), _createElementBlock(\"div\", _hoisted_1, [\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.shapeList, (item) => {\n return (_openBlock(), _createElementBlock(\"div\", {\n class: \"category\",\n key: item.type\n }, [\n _createElementVNode(\"div\", _hoisted_2, _toDisplayString(item.type), 1),\n _createElementVNode(\"div\", _hoisted_3, [\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(item.children, (shape, index) => {\n return (_openBlock(), _createElementBlock(\"div\", {\n class: \"shape-item\",\n key: index\n }, [\n _createElementVNode(\"div\", {\n class: \"shape-content\",\n onClick: ($event: any) => (_ctx.selectShape(shape))\n }, [\n (_openBlock(), _createElementBlock(\"svg\", _hoisted_5, [\n _createElementVNode(\"g\", {\n transform: `scale(${18 / shape.viewBox[0]}, ${18 / shape.viewBox[1]}) translate(0,0) matrix(1,0,0,1,0,0)`\n }, [\n _createElementVNode(\"path\", {\n class: _normalizeClass([\"shape-path\", { 'outlined': shape.outlined }]),\n \"vector-effect\": \"non-scaling-stroke\",\n \"stroke-linecap\": \"butt\",\n \"stroke-miterlimit\": \"8\",\n fill: shape.outlined ? '#999' : 'transparent',\n stroke: shape.outlined ? 'transparent' : '#999',\n \"stroke-width\": \"2\",\n d: shape.path\n }, null, 10, _hoisted_7)\n ], 8, _hoisted_6)\n ]))\n ], 8, _hoisted_4)\n ]))\n }), 128))\n ])\n ]))\n }), 128))\n ]))\n}","\nimport { defineComponent } from 'vue'\nimport { SHAPE_LIST, ShapePoolItem } from '@/configs/shapes'\n\nexport default defineComponent({\n name: 'shape-pool',\n emits: ['select'],\n setup(props, { emit }) {\n const shapeList = SHAPE_LIST\n\n const selectShape = (shape: ShapePoolItem) => {\n emit('select', shape)\n }\n\n return {\n shapeList,\n selectShape,\n }\n },\n})\n","import { render } from \"./ShapePool.vue?vue&type=template&id=f3479f82&scoped=true&ts=true\"\nimport script from \"./ShapePool.vue?vue&type=script&lang=ts\"\nexport * from \"./ShapePool.vue?vue&type=script&lang=ts\"\n\nimport \"./ShapePool.vue?vue&type=style&index=0&id=f3479f82&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-f3479f82\"]])\n\nexport default __exports__","import { renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, toDisplayString as _toDisplayString, createElementVNode as _createElementVNode, resolveComponent as _resolveComponent, createBlock as _createBlock, createCommentVNode as _createCommentVNode, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-7fa1d412\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = { class: \"line-pool\" }\nconst _hoisted_2 = { class: \"category-name\" }\nconst _hoisted_3 = { class: \"line-list\" }\nconst _hoisted_4 = [\"onClick\"]\nconst _hoisted_5 = {\n overflow: \"visible\",\n width: \"20\",\n height: \"20\"\n}\nconst _hoisted_6 = [\"d\", \"stroke-dasharray\", \"marker-start\", \"marker-end\"]\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _component_LinePointMarker = _resolveComponent(\"LinePointMarker\")!\n\n return (_openBlock(), _createElementBlock(\"div\", _hoisted_1, [\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.lineList, (item, i) => {\n return (_openBlock(), _createElementBlock(\"div\", {\n class: \"category\",\n key: item.type\n }, [\n _createElementVNode(\"div\", _hoisted_2, _toDisplayString(item.type), 1),\n _createElementVNode(\"div\", _hoisted_3, [\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(item.children, (line, j) => {\n return (_openBlock(), _createElementBlock(\"div\", {\n class: \"line-item\",\n key: j\n }, [\n _createElementVNode(\"div\", {\n class: \"line-content\",\n onClick: ($event: any) => (_ctx.selectLine(line))\n }, [\n (_openBlock(), _createElementBlock(\"svg\", _hoisted_5, [\n _createElementVNode(\"defs\", null, [\n (line.points[0])\n ? (_openBlock(), _createBlock(_component_LinePointMarker, {\n key: 0,\n class: \"line-marker\",\n id: `preset-line-${i}-${j}`,\n position: \"start\",\n type: line.points[0],\n color: \"currentColor\",\n baseSize: 2\n }, null, 8, [\"id\", \"type\"]))\n : _createCommentVNode(\"\", true),\n (line.points[1])\n ? (_openBlock(), _createBlock(_component_LinePointMarker, {\n key: 1,\n class: \"line-marker\",\n id: `preset-line-${i}-${j}`,\n position: \"end\",\n type: line.points[1],\n color: \"currentColor\",\n baseSize: 2\n }, null, 8, [\"id\", \"type\"]))\n : _createCommentVNode(\"\", true)\n ]),\n _createElementVNode(\"path\", {\n class: \"line-path\",\n d: line.path,\n stroke: \"currentColor\",\n fill: \"none\",\n \"stroke-width\": \"2\",\n \"stroke-dasharray\": line.style === 'solid' ? '0, 0' : '4, 1',\n \"marker-start\": line.points[0] ? `url(#${`preset-line-${i}-${j}`}-${line.points[0]}-start)` : '',\n \"marker-end\": line.points[1] ? `url(#${`preset-line-${i}-${j}`}-${line.points[1]}-end)` : ''\n }, null, 8, _hoisted_6)\n ]))\n ], 8, _hoisted_4)\n ]))\n }), 128))\n ])\n ]))\n }), 128))\n ]))\n}","import { LinePoint } from '@/types/slides'\n\n\nexport interface LinePoolItem {\n path: string;\n style: 'solid' | 'dashed';\n points: [LinePoint, LinePoint];\n isBroken?: boolean;\n isCurve?: boolean;\n isCubic?: boolean;\n}\n\ninterface PresetLine {\n type: string;\n children: LinePoolItem[];\n}\n\nexport const LINE_LIST: PresetLine[] = [\n {\n type: '直线',\n children: [\n { path: 'M 0 0 L 20 20', style: 'solid', points: ['', ''] },\n { path: 'M 0 0 L 20 20', style: 'dashed', points: ['', ''] },\n { path: 'M 0 0 L 20 20', style: 'solid', points: ['', 'arrow'] },\n { path: 'M 0 0 L 20 20', style: 'dashed', points: ['', 'arrow'] },\n { path: 'M 0 0 L 20 20', style: 'solid', points: ['', 'dot'] },\n ],\n },\n {\n type: '折线、曲线',\n children: [\n { path: 'M 0 0 L 0 20 L 20 20', style: 'solid', points: ['', 'arrow'], isBroken: true },\n { path: 'M 0 0 Q 0 20 20 20', style: 'solid', points: ['', 'arrow'], isCurve: true },\n { path: 'M 0 0 C 20 0 0 20 20 20', style: 'solid', points: ['', 'arrow'], isCubic: true },\n ],\n },\n]","\nimport { defineComponent } from 'vue'\nimport { LINE_LIST, LinePoolItem } from '@/configs/lines'\n\nimport LinePointMarker from '@/views/components/element/LineElement/LinePointMarker.vue'\n\nexport default defineComponent({\n name: 'line-pool',\n emits: ['select'],\n components: {\n LinePointMarker,\n },\n setup(props, { emit }) {\n const lineList = LINE_LIST\n\n const selectLine = (line: LinePoolItem) => {\n emit('select', line)\n }\n\n return {\n lineList,\n selectLine,\n }\n },\n})\n","import { render } from \"./LinePool.vue?vue&type=template&id=7fa1d412&scoped=true&ts=true\"\nimport script from \"./LinePool.vue?vue&type=script&lang=ts\"\nexport * from \"./LinePool.vue?vue&type=script&lang=ts\"\n\nimport \"./LinePool.vue?vue&type=style&index=0&id=7fa1d412&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-7fa1d412\"]])\n\nexport default __exports__","import { renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, resolveComponent as _resolveComponent, createBlock as _createBlock, createCommentVNode as _createCommentVNode, createElementVNode as _createElementVNode, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-64ca421e\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = { class: \"chart-pool\" }\nconst _hoisted_2 = [\"onClick\"]\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _component_IconChartLine = _resolveComponent(\"IconChartLine\")!\n const _component_IconChartHistogram = _resolveComponent(\"IconChartHistogram\")!\n const _component_IconChartPie = _resolveComponent(\"IconChartPie\")!\n const _component_IconChartHistogramOne = _resolveComponent(\"IconChartHistogramOne\")!\n const _component_IconChartLineArea = _resolveComponent(\"IconChartLineArea\")!\n const _component_IconChartRing = _resolveComponent(\"IconChartRing\")!\n const _component_IconChartScatter = _resolveComponent(\"IconChartScatter\")!\n\n return (_openBlock(), _createElementBlock(\"ul\", _hoisted_1, [\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.chartList, (chart, index) => {\n return (_openBlock(), _createElementBlock(\"li\", {\n class: \"chart-item\",\n key: index\n }, [\n _createElementVNode(\"div\", {\n class: \"chart-content\",\n onClick: ($event: any) => (_ctx.selectChart(chart))\n }, [\n (chart === 'line')\n ? (_openBlock(), _createBlock(_component_IconChartLine, {\n key: 0,\n size: \"24\"\n }))\n : (chart === 'bar')\n ? (_openBlock(), _createBlock(_component_IconChartHistogram, {\n key: 1,\n size: \"24\"\n }))\n : (chart === 'pie')\n ? (_openBlock(), _createBlock(_component_IconChartPie, {\n key: 2,\n size: \"24\"\n }))\n : (chart === 'horizontalBar')\n ? (_openBlock(), _createBlock(_component_IconChartHistogramOne, {\n key: 3,\n size: \"24\"\n }))\n : (chart === 'area')\n ? (_openBlock(), _createBlock(_component_IconChartLineArea, {\n key: 4,\n size: \"24\"\n }))\n : (chart === 'ring')\n ? (_openBlock(), _createBlock(_component_IconChartRing, {\n key: 5,\n size: \"24\"\n }))\n : (chart === 'scatter')\n ? (_openBlock(), _createBlock(_component_IconChartScatter, {\n key: 6,\n size: \"24\"\n }))\n : _createCommentVNode(\"\", true)\n ], 8, _hoisted_2)\n ]))\n }), 128))\n ]))\n}","\nimport { PresetChartType } from '@/types/slides'\nimport { defineComponent } from 'vue'\n\nexport default defineComponent({\n name: 'chart-pool',\n emits: ['select'],\n setup(props, { emit }) {\n const chartList: PresetChartType[] = ['bar', 'horizontalBar', 'line', 'area', 'scatter', 'pie', 'ring']\n\n const selectChart = (chart: PresetChartType) => {\n emit('select', chart)\n }\n\n return {\n chartList,\n selectChart,\n }\n },\n})\n","import { render } from \"./ChartPool.vue?vue&type=template&id=64ca421e&scoped=true&ts=true\"\nimport script from \"./ChartPool.vue?vue&type=script&lang=ts\"\nexport * from \"./ChartPool.vue?vue&type=script&lang=ts\"\n\nimport \"./ChartPool.vue?vue&type=style&index=0&id=64ca421e&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-64ca421e\"]])\n\nexport default __exports__","import { toDisplayString as _toDisplayString, createElementVNode as _createElementVNode, renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, normalizeClass as _normalizeClass, createCommentVNode as _createCommentVNode, resolveComponent as _resolveComponent, createVNode as _createVNode, createTextVNode as _createTextVNode, withCtx as _withCtx, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-e325ce98\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = { class: \"table-generator\" }\nconst _hoisted_2 = { class: \"title\" }\nconst _hoisted_3 = { class: \"lef\" }\nconst _hoisted_4 = [\"onMouseenter\"]\nconst _hoisted_5 = {\n key: 1,\n class: \"custom\"\n}\nconst _hoisted_6 = { class: \"row\" }\nconst _hoisted_7 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(\"div\", {\n class: \"label\",\n style: {\"flex\":\"1\"}\n}, \"行数:\", -1))\nconst _hoisted_8 = { class: \"row\" }\nconst _hoisted_9 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(\"div\", {\n class: \"label\",\n style: {\"flex\":\"1\"}\n}, \"列数:\", -1))\nconst _hoisted_10 = { class: \"btns\" }\nconst _hoisted_11 = /*#__PURE__*/_createTextVNode(\"取消\")\nconst _hoisted_12 = /*#__PURE__*/_createTextVNode(\"确认\")\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _component_InputNumber = _resolveComponent(\"InputNumber\")!\n const _component_Button = _resolveComponent(\"Button\")!\n\n return (_openBlock(), _createElementBlock(\"div\", _hoisted_1, [\n _createElementVNode(\"div\", _hoisted_2, [\n _createElementVNode(\"div\", _hoisted_3, \"表格 \" + _toDisplayString(_ctx.endCell.length ? `${_ctx.endCell[0]} x ${_ctx.endCell[1]}` : ''), 1),\n _createElementVNode(\"div\", {\n class: \"right\",\n onClick: _cache[0] || (_cache[0] = ($event: any) => (_ctx.isCustom = !_ctx.isCustom))\n }, _toDisplayString(_ctx.isCustom ? '返回' : '自定义'), 1)\n ]),\n (!_ctx.isCustom)\n ? (_openBlock(), _createElementBlock(\"table\", {\n key: 0,\n onMouseleave: _cache[1] || (_cache[1] = ($event: any) => (_ctx.endCell = [])),\n onClick: _cache[2] || (_cache[2] = ($event: any) => (_ctx.handleClickTable()))\n }, [\n _createElementVNode(\"tbody\", null, [\n (_openBlock(), _createElementBlock(_Fragment, null, _renderList(10, (row) => {\n return _createElementVNode(\"tr\", { key: row }, [\n (_openBlock(), _createElementBlock(_Fragment, null, _renderList(10, (col) => {\n return _createElementVNode(\"td\", {\n onMouseenter: ($event: any) => (_ctx.endCell = [row, col]),\n key: col\n }, [\n _createElementVNode(\"div\", {\n class: _normalizeClass([\"cell\", { 'active': _ctx.endCell.length && row <= _ctx.endCell[0] && col <= _ctx.endCell[1] }])\n }, null, 2)\n ], 40, _hoisted_4)\n }), 64))\n ])\n }), 64))\n ])\n ], 32))\n : (_openBlock(), _createElementBlock(\"div\", _hoisted_5, [\n _createElementVNode(\"div\", _hoisted_6, [\n _hoisted_7,\n _createVNode(_component_InputNumber, {\n min: 1,\n max: 20,\n value: _ctx.customRow,\n \"onUpdate:value\": _cache[3] || (_cache[3] = ($event: any) => ((_ctx.customRow) = $event)),\n style: {\"flex\":\"3\"}\n }, null, 8, [\"value\"])\n ]),\n _createElementVNode(\"div\", _hoisted_8, [\n _hoisted_9,\n _createVNode(_component_InputNumber, {\n min: 1,\n max: 20,\n value: _ctx.customCol,\n \"onUpdate:value\": _cache[4] || (_cache[4] = ($event: any) => ((_ctx.customCol) = $event)),\n style: {\"flex\":\"3\"}\n }, null, 8, [\"value\"])\n ]),\n _createElementVNode(\"div\", _hoisted_10, [\n _createVNode(_component_Button, {\n class: \"btn\",\n onClick: _cache[5] || (_cache[5] = ($event: any) => (_ctx.close()))\n }, {\n default: _withCtx(() => [\n _hoisted_11\n ]),\n _: 1\n }),\n _createVNode(_component_Button, {\n class: \"btn\",\n type: \"primary\",\n onClick: _cache[6] || (_cache[6] = ($event: any) => (_ctx.insertCustomTable()))\n }, {\n default: _withCtx(() => [\n _hoisted_12\n ]),\n _: 1\n })\n ])\n ]))\n ]))\n}","\nimport { defineComponent, ref } from 'vue'\n\nimport { message } from 'ant-design-vue'\n\nexport default defineComponent({\n name: 'table-generator',\n emits: ['insert', 'close'],\n setup(props, { emit }) {\n const endCell = ref([])\n const customRow = ref(3)\n const customCol = ref(3)\n const isCustom = ref(false)\n\n const handleClickTable = () => {\n if (!endCell.value.length) return\n const [row, col] = endCell.value\n emit('insert', { row, col })\n }\n\n const insertCustomTable = () => {\n if (customRow.value < 1 || customRow.value > 20) return message.warning('行数/列数必须在0~20之间!')\n if (customCol.value < 1 || customCol.value > 20) return message.warning('行数/列数必须在0~20之间!')\n emit('insert', { row: customRow.value, col: customCol.value })\n isCustom.value = false\n }\n\n const close = () => {\n emit('close')\n isCustom.value = false\n }\n\n return {\n endCell,\n customRow,\n customCol,\n handleClickTable,\n insertCustomTable,\n isCustom,\n close,\n }\n },\n})\n","import { render } from \"./TableGenerator.vue?vue&type=template&id=e325ce98&scoped=true&ts=true\"\nimport script from \"./TableGenerator.vue?vue&type=script&lang=ts\"\nexport * from \"./TableGenerator.vue?vue&type=script&lang=ts\"\n\nimport \"./TableGenerator.vue?vue&type=style&index=0&id=e325ce98&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-e325ce98\"]])\n\nexport default __exports__","import { renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, toDisplayString as _toDisplayString, normalizeClass as _normalizeClass, createElementVNode as _createElementVNode, resolveComponent as _resolveComponent, createVNode as _createVNode, createTextVNode as _createTextVNode, withCtx as _withCtx, createCommentVNode as _createCommentVNode, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-ec37f828\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = { class: \"media-input\" }\nconst _hoisted_2 = { class: \"tabs\" }\nconst _hoisted_3 = [\"onClick\"]\nconst _hoisted_4 = { class: \"btns\" }\nconst _hoisted_5 = /*#__PURE__*/_createTextVNode(\"取消\")\nconst _hoisted_6 = /*#__PURE__*/_createTextVNode(\"确认\")\nconst _hoisted_7 = { class: \"btns\" }\nconst _hoisted_8 = /*#__PURE__*/_createTextVNode(\"取消\")\nconst _hoisted_9 = /*#__PURE__*/_createTextVNode(\"确认\")\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _component_Input = _resolveComponent(\"Input\")!\n const _component_Button = _resolveComponent(\"Button\")!\n\n return (_openBlock(), _createElementBlock(\"div\", _hoisted_1, [\n _createElementVNode(\"div\", _hoisted_2, [\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.tabs, (tab) => {\n return (_openBlock(), _createElementBlock(\"div\", {\n class: _normalizeClass([\"tab\", { 'active': _ctx.type === tab.key }]),\n key: tab.key,\n onClick: ($event: any) => (_ctx.type = tab.key)\n }, _toDisplayString(tab.label), 11, _hoisted_3))\n }), 128))\n ]),\n (_ctx.type === 'video')\n ? (_openBlock(), _createElementBlock(_Fragment, { key: 0 }, [\n _createVNode(_component_Input, {\n value: _ctx.videoSrc,\n \"onUpdate:value\": _cache[0] || (_cache[0] = ($event: any) => ((_ctx.videoSrc) = $event)),\n placeholder: \"请输入视频地址,e.g. https://xxx.mp4\"\n }, null, 8, [\"value\"]),\n _createElementVNode(\"div\", _hoisted_4, [\n _createVNode(_component_Button, {\n onClick: _cache[1] || (_cache[1] = ($event: any) => (_ctx.close())),\n style: {\"margin-right\":\"10px\"}\n }, {\n default: _withCtx(() => [\n _hoisted_5\n ]),\n _: 1\n }),\n _createVNode(_component_Button, {\n type: \"primary\",\n onClick: _cache[2] || (_cache[2] = ($event: any) => (_ctx.insertVideo()))\n }, {\n default: _withCtx(() => [\n _hoisted_6\n ]),\n _: 1\n })\n ])\n ], 64))\n : _createCommentVNode(\"\", true),\n (_ctx.type === 'audio')\n ? (_openBlock(), _createElementBlock(_Fragment, { key: 1 }, [\n _createVNode(_component_Input, {\n value: _ctx.audioSrc,\n \"onUpdate:value\": _cache[3] || (_cache[3] = ($event: any) => ((_ctx.audioSrc) = $event)),\n placeholder: \"请输入音频地址,e.g. https://xxx.mp3\"\n }, null, 8, [\"value\"]),\n _createElementVNode(\"div\", _hoisted_7, [\n _createVNode(_component_Button, {\n onClick: _cache[4] || (_cache[4] = ($event: any) => (_ctx.close())),\n style: {\"margin-right\":\"10px\"}\n }, {\n default: _withCtx(() => [\n _hoisted_8\n ]),\n _: 1\n }),\n _createVNode(_component_Button, {\n type: \"primary\",\n onClick: _cache[5] || (_cache[5] = ($event: any) => (_ctx.insertAudio()))\n }, {\n default: _withCtx(() => [\n _hoisted_9\n ]),\n _: 1\n })\n ])\n ], 64))\n : _createCommentVNode(\"\", true)\n ]))\n}","\nimport { defineComponent, ref } from 'vue'\nimport { message } from 'ant-design-vue'\n\ntype TypeKey = 'video' | 'audio'\ninterface TabItem {\n key: TypeKey;\n label: string;\n}\n\nexport default defineComponent({\n name: 'media-input',\n emits: ['insertVideo', 'insertAudio', 'close'],\n setup(props, { emit }) {\n const type = ref('video')\n\n const videoSrc = ref('https://mazwai.com/videvo_files/video/free/2019-01/small_watermarked/181004_04_Dolphins-Whale_06_preview.webm')\n const audioSrc = ref('https://freesound.org/data/previews/614/614107_11861866-lq.mp3')\n\n const tabs: TabItem[] = [\n { key: 'video', label: '视频' },\n { key: 'audio', label: '音频' },\n ]\n\n const insertVideo = () => {\n if (!videoSrc.value) return message.error('请先输入正确的视频地址')\n emit('insertVideo', videoSrc.value)\n }\n\n const insertAudio = () => {\n if (!audioSrc.value) return message.error('请先输入正确的音频地址')\n emit('insertAudio', audioSrc.value)\n }\n\n const close = () => emit('close')\n\n return {\n type,\n videoSrc,\n audioSrc,\n tabs,\n insertVideo,\n insertAudio,\n close,\n }\n },\n})\n","import { render } from \"./MediaInput.vue?vue&type=template&id=ec37f828&scoped=true&ts=true\"\nimport script from \"./MediaInput.vue?vue&type=script&lang=ts\"\nexport * from \"./MediaInput.vue?vue&type=script&lang=ts\"\n\nimport \"./MediaInput.vue?vue&type=style&index=0&id=ec37f828&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-ec37f828\"]])\n\nexport default __exports__","import { resolveComponent as _resolveComponent, createVNode as _createVNode, createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock, createCommentVNode as _createCommentVNode, renderList as _renderList, Fragment as _Fragment, toDisplayString as _toDisplayString, normalizeClass as _normalizeClass, createTextVNode as _createTextVNode, withCtx as _withCtx, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-129e1afa\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = { class: \"latex-editor\" }\nconst _hoisted_2 = { class: \"container\" }\nconst _hoisted_3 = { class: \"left\" }\nconst _hoisted_4 = { class: \"input-area\" }\nconst _hoisted_5 = { class: \"preview\" }\nconst _hoisted_6 = {\n key: 0,\n class: \"placeholder\"\n}\nconst _hoisted_7 = {\n key: 1,\n class: \"preview-content\"\n}\nconst _hoisted_8 = { class: \"right\" }\nconst _hoisted_9 = { class: \"tabs\" }\nconst _hoisted_10 = [\"onClick\"]\nconst _hoisted_11 = { class: \"content\" }\nconst _hoisted_12 = {\n key: 0,\n class: \"symbol\"\n}\nconst _hoisted_13 = { class: \"symbol-tabs\" }\nconst _hoisted_14 = [\"onClick\"]\nconst _hoisted_15 = { class: \"symbol-pool\" }\nconst _hoisted_16 = [\"onClick\"]\nconst _hoisted_17 = {\n key: 1,\n class: \"formula\"\n}\nconst _hoisted_18 = { class: \"formula-title\" }\nconst _hoisted_19 = [\"onClick\"]\nconst _hoisted_20 = { class: \"footer\" }\nconst _hoisted_21 = /*#__PURE__*/_createTextVNode(\"取消\")\nconst _hoisted_22 = /*#__PURE__*/_createTextVNode(\"确定\")\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _component_TextArea = _resolveComponent(\"TextArea\")!\n const _component_FormulaContent = _resolveComponent(\"FormulaContent\")!\n const _component_SymbolContent = _resolveComponent(\"SymbolContent\")!\n const _component_Button = _resolveComponent(\"Button\")!\n\n return (_openBlock(), _createElementBlock(\"div\", _hoisted_1, [\n _createElementVNode(\"div\", _hoisted_2, [\n _createElementVNode(\"div\", _hoisted_3, [\n _createElementVNode(\"div\", _hoisted_4, [\n _createVNode(_component_TextArea, {\n value: _ctx.latex,\n \"onUpdate:value\": _cache[0] || (_cache[0] = ($event: any) => ((_ctx.latex) = $event)),\n placeholder: \"输入 LaTeX 公式\",\n ref: \"textAreaRef\"\n }, null, 8, [\"value\"])\n ]),\n _createElementVNode(\"div\", _hoisted_5, [\n (!_ctx.latex)\n ? (_openBlock(), _createElementBlock(\"div\", _hoisted_6, \"公式预览\"))\n : (_openBlock(), _createElementBlock(\"div\", _hoisted_7, [\n _createVNode(_component_FormulaContent, {\n width: 518,\n height: 138,\n latex: _ctx.latex\n }, null, 8, [\"latex\"])\n ]))\n ])\n ]),\n _createElementVNode(\"div\", _hoisted_8, [\n _createElementVNode(\"div\", _hoisted_9, [\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.tabs, (tab) => {\n return (_openBlock(), _createElementBlock(\"div\", {\n class: _normalizeClass([\"tab\", { 'active': tab.value === _ctx.toolbarState }]),\n key: tab.value,\n onClick: ($event: any) => (_ctx.toolbarState = tab.value)\n }, _toDisplayString(tab.label), 11, _hoisted_10))\n }), 128))\n ]),\n _createElementVNode(\"div\", _hoisted_11, [\n (_ctx.toolbarState === 'symbol')\n ? (_openBlock(), _createElementBlock(\"div\", _hoisted_12, [\n _createElementVNode(\"div\", _hoisted_13, [\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.symbolList, (group) => {\n return (_openBlock(), _createElementBlock(\"div\", {\n class: _normalizeClass([\"symbol-tab\", { 'active': _ctx.selectedSymbolKey === group.type }]),\n key: group.type,\n onClick: ($event: any) => (_ctx.selectedSymbolKey = group.type)\n }, _toDisplayString(group.label), 11, _hoisted_14))\n }), 128))\n ]),\n _createElementVNode(\"div\", _hoisted_15, [\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.symbolPool, (item) => {\n return (_openBlock(), _createElementBlock(\"div\", {\n class: \"symbol-item\",\n key: item.latex,\n onClick: ($event: any) => (_ctx.insertSymbol(item.latex))\n }, [\n _createVNode(_component_SymbolContent, {\n latex: item.latex\n }, null, 8, [\"latex\"])\n ], 8, _hoisted_16))\n }), 128))\n ])\n ]))\n : (_openBlock(), _createElementBlock(\"div\", _hoisted_17, [\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.formulaList, (item) => {\n return (_openBlock(), _createElementBlock(\"div\", {\n class: \"formula-item\",\n key: item.label\n }, [\n _createElementVNode(\"div\", _hoisted_18, _toDisplayString(item.label), 1),\n _createElementVNode(\"div\", {\n class: \"formula-item-content\",\n onClick: ($event: any) => (_ctx.latex =item.latex)\n }, [\n _createVNode(_component_FormulaContent, {\n width: 236,\n height: 60,\n latex: item.latex\n }, null, 8, [\"latex\"])\n ], 8, _hoisted_19)\n ]))\n }), 128))\n ]))\n ])\n ])\n ]),\n _createElementVNode(\"div\", _hoisted_20, [\n _createVNode(_component_Button, {\n class: \"btn\",\n onClick: _cache[1] || (_cache[1] = ($event: any) => (_ctx.close()))\n }, {\n default: _withCtx(() => [\n _hoisted_21\n ]),\n _: 1\n }),\n _createVNode(_component_Button, {\n class: \"btn\",\n type: \"primary\",\n onClick: _cache[2] || (_cache[2] = ($event: any) => (_ctx.update()))\n }, {\n default: _withCtx(() => [\n _hoisted_22\n ]),\n _: 1\n })\n ])\n ]))\n}","import { hfmath, CONFIG as hfmathConfig } from 'hfmath'\n\nhfmathConfig.SUB_SUP_SCALE = 0.5\n\nexport { hfmath }","export const FORMULA_LIST = [\n {\n label: '高斯公式',\n latex: `\\\\int\\\\int\\\\int _ { \\\\Omega } \\\\left( \\\\frac { \\\\partial {P} } { \\\\partial {x} } + \\\\frac { \\\\partial {Q} } { \\\\partial {y} } + \\\\frac { \\\\partial {R} }{ \\\\partial {z} } \\\\right) \\\\mathrm { d } V = \\\\oint _ { \\\\partial \\\\Omega } ( P \\\\cos \\\\alpha + Q \\\\cos \\\\beta + R \\\\cos \\\\gamma ) \\\\mathrm{ d} S`\n },\n {\n label: '傅里叶级数',\n latex: `f(x) = \\\\frac {a_0} 2 + \\\\sum_{n = 1}^\\\\infty {({a_n}\\\\cos {nx} + {b_n}\\\\sin {nx})}`,\n },\n {\n label: '泰勒展开式',\n latex: `e ^ { x } = 1 + \\\\frac { x } { 1 ! } + \\\\frac { x ^ { 2 } } { 2 ! } + \\\\frac { x ^ { 3 } } { 3 ! } + ... , \\\\quad - \\\\infty < x < \\\\infty`,\n },\n {\n label: '定积分',\n latex: `\\\\lim_ { n \\\\rightarrow + \\\\infty } \\\\sum _ { i = 1 } ^ { n } f \\\\left[ a + \\\\frac { i } { n } ( b - a ) \\\\right] \\\\frac { b - a } { n } = \\\\int _ { a } ^ { b } f ( x ) dx`,\n },\n {\n label: '三角恒等式1',\n latex: `\\\\sin \\\\alpha \\\\pm \\\\sin \\\\beta = 2 \\\\sin \\\\frac { 1 } { 2 } ( \\\\alpha \\\\pm \\\\beta ) \\\\cos \\\\frac { 1 } { 2 } ( \\\\alpha \\\\mp \\\\beta )`,\n },\n {\n label: '三角恒等式2',\n latex: `\\\\cos \\\\alpha + \\\\cos \\\\beta = 2 \\\\cos \\\\frac { 1 } { 2 } ( \\\\alpha + \\\\beta ) \\\\cos \\\\frac { 1 } { 2 } ( \\\\alpha - \\\\beta )`,\n },\n {\n label: '和的展开式',\n latex: `( 1 + x ) ^ { n } = 1 + \\\\frac { n x } { 1 ! } + \\\\frac { n ( n - 1 ) x ^ { 2 } } { 2 ! } + ...`,\n },\n {\n label: '欧拉公式',\n latex: ` e^{ix} = \\\\cos {x} + i\\\\sin {x}`,\n },\n {\n label: '贝努利方程',\n latex: `\\\\frac {dy} {dx} + P(x)y = Q(x) y^n ({n} \\\\not= {0,1})`,\n },\n {\n label: '全微分方程',\n latex: `du(x,y) = P(x,y)dx + Q(x,y)dy = 0`,\n },\n {\n label: '非齐次方程',\n latex: `y = (\\\\int Q(x) e^{\\\\int {P(x)dx}}dx + C)e^{-\\\\int {P(x)dx}}`,\n },\n {\n label: '柯西中值定理',\n latex: `\\\\frac{{f(b) - f(a)}}{{F(b) - F(a)}} = \\\\frac{{f'(\\\\xi )}}{{F'(\\\\xi )}}`,\n },\n {\n label: '拉格朗日中值定理',\n latex: `f(b) - f(a) = f'(\\\\xi )(b - a)`,\n },\n {\n label: '导数公式',\n latex: `(\\\\arcsin x)' = \\\\frac{1}{{\\\\sqrt {1 - x^2} }}`,\n },\n {\n label: '三角函数积分',\n latex: `\\\\int {tgxdx = - \\\\ln \\\\left| {\\\\cos x} \\\\right| + C}`,\n },\n {\n label: '二次曲面',\n latex: `\\\\frac{{{x^2}}}{{{a^2}}} + \\\\frac{{{y^2}}}{{{b^2}}} - \\\\frac{{{z^2}}}{{{c^2}}} = 1`,\n },\n {\n label: '二阶微分',\n latex: `\\\\frac {{d^2}y} {dx^2} + P(x) \\\\frac {dy} {dx} + Q(x)y = f(x)`,\n },\n {\n label: '方向导数',\n latex: `\\\\frac{{\\\\partial f}}{{\\\\partial l}} = \\\\frac{{\\\\partial f}}{{\\\\partial x}}\\\\cos \\\\phi + \\\\frac{{\\\\partial f}}{{\\\\partial y}}\\\\sin \\\\phi`,\n },\n]\n\nexport const SYMBOL_LIST = [\n {\n type: 'operators',\n label: '数学',\n children: [\n { latex: '\\\\cdot' },\n { latex: '\\\\pm' },\n { latex: '\\\\mp' },\n { latex: '+' },\n { latex: '-' },\n { latex: '\\\\times' },\n { latex: '\\\\div' },\n { latex: '<' },\n { latex: '>' },\n { latex: '=' },\n { latex: '\\\\neq\\\\ne' },\n { latex: '\\\\leqq' },\n { latex: '\\\\geqq' },\n { latex: '\\\\leq' },\n { latex: '\\\\geq' },\n { latex: '\\\\propto' },\n { latex: '\\\\sim' },\n { latex: '\\\\equiv' },\n { latex: '\\\\dagger' },\n { latex: '\\\\ddagger' },\n { latex: '\\\\ell' },\n { latex: '\\\\#' },\n { latex: '\\\\$' },\n { latex: '\\\\&' },\n { latex: '\\\\%' },\n { latex: '\\\\langle\\\\rangle' },\n { latex: '()' },\n { latex: '[]' },\n { latex: '\\\\{\\\\}' },\n { latex: '||' },\n { latex: '\\\\|' },\n { latex: '\\\\exists' },\n { latex: '\\\\in' },\n { latex: '\\\\subset' },\n { latex: '\\\\supset' },\n { latex: '\\\\cup' },\n { latex: '\\\\cap' },\n { latex: '\\\\infty' },\n { latex: '\\\\partial' },\n { latex: '\\\\nabla' },\n { latex: '\\\\aleph' },\n { latex: '\\\\wp' },\n { latex: '\\\\therefore' },\n { latex: '\\\\mid' },\n { latex: '\\\\sum' },\n { latex: '\\\\prod' },\n { latex: '\\\\bigoplus' },\n { latex: '\\\\bigodot' },\n { latex: '\\\\int' },\n { latex: '\\\\oint' },\n { latex: '\\\\oplus' },\n { latex: '\\\\odot' },\n { latex: '\\\\perp' },\n { latex: '\\\\angle' },\n { latex: '\\\\triangle' },\n { latex: '\\\\Box' },\n { latex: '\\\\rightarrow' },\n { latex: '\\\\to' },\n { latex: '\\\\leftarrow' },\n { latex: '\\\\gets' },\n { latex: '\\\\circ' },\n { latex: '\\\\bigcirc' },\n { latex: '\\\\bullet' },\n { latex: '\\\\star' },\n { latex: '\\\\diamond' },\n { latex: '\\\\ast' },\n { latex: ',' },\n { latex: '.' },\n { latex: ';' },\n { latex: '!' },\n ],\n },\n {\n type: 'group',\n label: '组合',\n children: [\n { latex: '\\\\frac{a}{b}' },\n { latex: '\\\\frac{dx}{dx}' },\n { latex: '\\\\frac{\\\\partial a}{\\\\partial b}' },\n { latex: '\\\\sqrt{x}' },\n { latex: '\\\\sqrt[n]{x}' },\n { latex: 'x^{n}' },\n { latex: 'x_{n}' },\n { latex: 'x_a^b' },\n { latex: '\\\\int_{a}^{b}' },\n { latex: '\\\\oint_a^b' },\n { latex: '\\\\lim_{a \\\\rightarrow b}' },\n { latex: '\\\\prod_a^b' },\n { latex: '\\\\sum_a^b' },\n { latex: '\\\\left(\\\\begin{array}a \\\\\\\\ b\\\\end{array}\\\\right)' },\n { latex: '\\\\begin{bmatrix}a & b \\\\\\\\ c & d \\\\end{bmatrix}' },\n { latex: '\\\\begin{cases}a & x = 0 \\\\\\\\ b & x > 0\\\\end{cases}' },\n { latex: '\\\\hat{a}' },\n { latex: '\\\\breve{a}' },\n { latex: '\\\\acute{a}' },\n { latex: '\\\\grave{a}' },\n { latex: '\\\\tilde{a}' },\n { latex: '\\\\bar{a}' },\n { latex: '\\\\vec{a}' },\n { latex: '\\\\underline{a}' },\n { latex: '\\\\overline{a}' },\n { latex: '\\\\widehat{ab}' },\n { latex: '\\\\overleftarrow{ab}' },\n { latex: '\\\\overrightarrow{ab}' },\n ],\n },\n {\n type: 'verbatim',\n label: '函数',\n children: [\n { latex: '\\\\log' },\n { latex: '\\\\ln' },\n { latex: '\\\\exp' },\n { latex: '\\\\mod' },\n { latex: '\\\\lim' },\n { latex: '\\\\sin' },\n { latex: '\\\\cos' },\n { latex: '\\\\tan' },\n { latex: '\\\\csc' },\n { latex: '\\\\sec' },\n { latex: '\\\\cot' },\n { latex: '\\\\sinh' },\n { latex: '\\\\cosh' },\n { latex: '\\\\tanh' },\n { latex: '\\\\csch' },\n { latex: '\\\\sech' },\n { latex: '\\\\coth' },\n { latex: '\\\\arcsin' },\n { latex: '\\\\arccos' },\n { latex: '\\\\arctan' },\n { latex: '\\\\arccsc' },\n { latex: '\\\\arcsec' },\n { latex: '\\\\arccot' },\n ],\n },\n {\n type: 'greek',\n label: '希腊字母',\n children: [\n { latex: '\\\\alpha' },\n { latex: '\\\\beta' },\n { latex: '\\\\gamma' },\n { latex: '\\\\delta' },\n { latex: '\\\\varepsilon' },\n { latex: '\\\\zeta' },\n { latex: '\\\\eta' },\n { latex: '\\\\vartheta' },\n { latex: '\\\\iota' },\n { latex: '\\\\kappa' },\n { latex: '\\\\lambda' },\n { latex: '\\\\mu' },\n { latex: '\\\\nu' },\n { latex: '\\\\xi' },\n { latex: '\\\\omicron' },\n { latex: '\\\\pi' },\n { latex: '\\\\rho' },\n { latex: '\\\\sigma' },\n { latex: '\\\\tau' },\n { latex: '\\\\upsilon' },\n { latex: '\\\\varphi' },\n { latex: '\\\\chi' },\n { latex: '\\\\psi' },\n { latex: '\\\\omega' },\n { latex: '\\\\epsilon' },\n { latex: '\\\\theta' },\n { latex: '\\\\phi' },\n { latex: '\\\\varsigma' },\n { latex: '\\\\Alpha' },\n { latex: '\\\\Beta' },\n { latex: '\\\\Gamma' },\n { latex: '\\\\Delta' },\n { latex: '\\\\Epsilon' },\n { latex: '\\\\Zeta' },\n { latex: '\\\\Eta' },\n { latex: '\\\\Theta' },\n { latex: '\\\\Iota' },\n { latex: '\\\\Kappa' },\n { latex: '\\\\Lambda' },\n { latex: '\\\\Mu' },\n { latex: '\\\\Nu' },\n { latex: '\\\\Xi' },\n { latex: '\\\\Omicron' },\n { latex: '\\\\Pi' },\n { latex: '\\\\Rho' },\n { latex: '\\\\Sigma' },\n { latex: '\\\\Tau' },\n { latex: '\\\\Upsilon' },\n { latex: '\\\\Phi' },\n { latex: '\\\\Chi' },\n { latex: '\\\\Psi' },\n { latex: '\\\\Omega' },\n ],\n },\n]","import { createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-cf0ca630\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = [\"width\", \"height\"]\nconst _hoisted_2 = [\"transform\"]\nconst _hoisted_3 = [\"d\"]\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n return (_openBlock(), _createElementBlock(\"svg\", {\n class: \"formula-content\",\n overflow: \"visible\",\n width: _ctx.box.w + 32,\n height: _ctx.box.h + 32,\n stroke: \"#000\",\n \"stroke-width\": \"1\",\n fill: \"none\",\n \"stroke-linecap\": \"round\",\n \"stroke-linejoin\": \"round\"\n }, [\n _createElementVNode(\"g\", {\n transform: `scale(${_ctx.scale}, ${_ctx.scale}) translate(0,0) matrix(1,0,0,1,0,0)`,\n \"transform-origin\": \"0 50%\"\n }, [\n _createElementVNode(\"path\", { d: _ctx.pathd }, null, 8, _hoisted_3)\n ], 8, _hoisted_2)\n ], 8, _hoisted_1))\n}","\nimport { computed, defineComponent, ref, watch } from 'vue'\nimport { hfmath } from './hfmath'\n\nexport default defineComponent({\n name: 'formula-content',\n props: {\n latex: {\n type: String,\n required: true,\n },\n width: {\n type: Number,\n required: true,\n },\n height: {\n type: Number,\n required: true,\n },\n },\n setup(props) {\n const box = ref({ x: 0, y: 0, w: 0, h: 0 })\n const pathd = ref('')\n\n watch(() => props.latex, () => {\n const eq = new hfmath(props.latex)\n pathd.value = eq.pathd({})\n box.value = eq.box({})\n }, { immediate: true })\n\n const scale = computed(() => {\n const boxW = box.value.w + 32\n const boxH = box.value.h + 32\n\n if (boxW > props.width || boxH > props.height) {\n if (boxW / boxH > props.width / props.height) return props.width / boxW\n return props.height / boxH\n }\n return 1\n })\n\n return {\n box,\n pathd,\n scale,\n }\n },\n})\n","import { render } from \"./FormulaContent.vue?vue&type=template&id=cf0ca630&scoped=true&ts=true\"\nimport script from \"./FormulaContent.vue?vue&type=script&lang=ts\"\nexport * from \"./FormulaContent.vue?vue&type=script&lang=ts\"\n\nimport \"./FormulaContent.vue?vue&type=style&index=0&id=cf0ca630&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-cf0ca630\"]])\n\nexport default __exports__","import { openBlock as _openBlock, createElementBlock as _createElementBlock } from \"vue\"\n\nconst _hoisted_1 = [\"innerHTML\"]\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n return (_openBlock(), _createElementBlock(\"div\", {\n class: \"symbol-content\",\n innerHTML: _ctx.svg\n }, null, 8, _hoisted_1))\n}","\nimport { computed, defineComponent } from 'vue'\nimport { hfmath } from './hfmath'\n\nexport default defineComponent({\n name: 'symbol-content',\n props: {\n latex: {\n type: String,\n required: true,\n },\n },\n setup(props) {\n const svg = computed(() => {\n const eq = new hfmath(props.latex)\n return eq.svg({\n SCALE_X: 10,\n SCALE_Y: 10,\n })\n })\n\n return {\n svg,\n }\n },\n})\n","import { render } from \"./SymbolContent.vue?vue&type=template&id=3a83adf0&ts=true\"\nimport script from \"./SymbolContent.vue?vue&type=script&lang=ts\"\nexport * from \"./SymbolContent.vue?vue&type=script&lang=ts\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render]])\n\nexport default __exports__","\nimport { computed, defineComponent, onMounted, ref } from 'vue'\nimport { hfmath } from './hfmath'\nimport { FORMULA_LIST, SYMBOL_LIST } from '@/configs/latex'\n\nimport FormulaContent from './FormulaContent.vue'\nimport SymbolContent from './SymbolContent.vue'\n\nconst tabs = [\n { label: '常用符号', value: 'symbol' },\n { label: '预置公式', value: 'formula' },\n]\n\nexport default defineComponent({\n name: 'latex-editor',\n emits: ['update', 'close'],\n components: {\n FormulaContent,\n SymbolContent,\n },\n props: {\n value: {\n type: String,\n default: '',\n },\n },\n setup(props, { emit }) {\n const latex = ref('')\n const toolbarState = ref('symbol')\n const textAreaRef = ref()\n\n const selectedSymbolKey = ref(SYMBOL_LIST[0].type)\n const symbolPool = computed(() => {\n const selectedSymbol = SYMBOL_LIST.find(item => item.type === selectedSymbolKey.value)\n return selectedSymbol?.children || []\n })\n\n onMounted(() => {\n if (props.value) latex.value = props.value\n })\n\n const update = () => {\n if (!latex.value) return\n\n const eq = new hfmath(latex.value)\n const pathd = eq.pathd({})\n const box = eq.box({})\n \n emit('update', {\n latex: latex.value,\n path: pathd,\n w: box.w + 32,\n h: box.h + 32,\n })\n }\n\n const close = () => emit('close')\n\n const insertSymbol = (latex: string) => {\n if (!textAreaRef.value) return\n textAreaRef.value.focus()\n document.execCommand('insertText', false, latex)\n }\n\n return {\n tabs,\n latex,\n toolbarState,\n selectedSymbolKey,\n formulaList: FORMULA_LIST,\n symbolList: SYMBOL_LIST,\n symbolPool,\n textAreaRef,\n update,\n close,\n insertSymbol,\n }\n },\n})\n","import { render } from \"./index.vue?vue&type=template&id=129e1afa&scoped=true&ts=true\"\nimport script from \"./index.vue?vue&type=script&lang=ts\"\nexport * from \"./index.vue?vue&type=script&lang=ts\"\n\nimport \"./index.vue?vue&type=style&index=0&id=129e1afa&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-129e1afa\"]])\n\nexport default __exports__","\nimport { defineComponent, ref } from 'vue'\nimport { storeToRefs } from 'pinia'\nimport { useMainStore, useSnapshotStore } from '@/store'\nimport { getImageDataURL } from '@/utils/image'\nimport { ShapePoolItem } from '@/configs/shapes'\nimport { LinePoolItem } from '@/configs/lines'\nimport useScaleCanvas from '@/hooks/useScaleCanvas'\nimport useHistorySnapshot from '@/hooks/useHistorySnapshot'\nimport useCreateElement from '@/hooks/useCreateElement'\n\nimport ShapePool from './ShapePool.vue'\nimport LinePool from './LinePool.vue'\nimport ChartPool from './ChartPool.vue'\nimport TableGenerator from './TableGenerator.vue'\nimport MediaInput from './MediaInput.vue'\nimport LaTeXEditor from '@/components/LaTeXEditor/index.vue'\n\nexport default defineComponent({\n name: 'canvas-tool',\n components: {\n ShapePool,\n LinePool,\n ChartPool,\n TableGenerator,\n MediaInput,\n LaTeXEditor,\n },\n setup() {\n const mainStore = useMainStore()\n const { creatingElement } = storeToRefs(mainStore)\n const { canUndo, canRedo } = storeToRefs(useSnapshotStore())\n\n const { redo, undo } = useHistorySnapshot()\n\n const {\n scaleCanvas,\n setCanvasScalePercentage,\n resetCanvas,\n canvasScalePercentage,\n } = useScaleCanvas()\n \n const canvasScalePresetList = [200, 150, 100, 80, 50]\n const canvasScaleVisible = ref(false)\n\n const applyCanvasPresetScale = (value: number) => {\n setCanvasScalePercentage(value)\n canvasScaleVisible.value = false\n }\n\n const {\n createImageElement,\n createChartElement,\n createTableElement,\n createLatexElement,\n createVideoElement,\n createAudioElement,\n } = useCreateElement()\n\n const insertImageElement = (files: File[]) => {\n const imageFile = files[0]\n if (!imageFile) return\n getImageDataURL(imageFile).then(dataURL => createImageElement(dataURL))\n }\n\n const shapePoolVisible = ref(false)\n const linePoolVisible = ref(false)\n const chartPoolVisible = ref(false)\n const tableGeneratorVisible = ref(false)\n const mediaInputVisible = ref(false)\n const latexEditorVisible = ref(false)\n\n // 绘制文字范围\n const drawText = () => {\n mainStore.setCreatingElement({\n type: 'text',\n })\n }\n\n // 绘制形状范围\n const drawShape = (shape: ShapePoolItem) => {\n mainStore.setCreatingElement({\n type: 'shape',\n data: shape,\n })\n shapePoolVisible.value = false\n }\n\n // 绘制线条路径\n const drawLine = (line: LinePoolItem) => {\n mainStore.setCreatingElement({\n type: 'line',\n data: line,\n })\n linePoolVisible.value = false\n }\n\n return {\n scaleCanvas,\n resetCanvas,\n canvasScalePercentage,\n canvasScaleVisible,\n canvasScalePresetList,\n applyCanvasPresetScale,\n canUndo,\n canRedo,\n redo,\n undo,\n insertImageElement,\n shapePoolVisible,\n linePoolVisible,\n chartPoolVisible,\n tableGeneratorVisible,\n mediaInputVisible,\n latexEditorVisible,\n creatingElement,\n drawText,\n drawShape,\n drawLine,\n createChartElement,\n createTableElement,\n createLatexElement,\n createVideoElement,\n createAudioElement,\n }\n },\n})\n","import { render } from \"./index.vue?vue&type=template&id=e9ddaac8&scoped=true&ts=true\"\nimport script from \"./index.vue?vue&type=script&lang=ts\"\nexport * from \"./index.vue?vue&type=script&lang=ts\"\n\nimport \"./index.vue?vue&type=style&index=0&id=e9ddaac8&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-e9ddaac8\"]])\n\nexport default __exports__","import { resolveComponent as _resolveComponent, createVNode as _createVNode, createTextVNode as _createTextVNode, createElementVNode as _createElementVNode, withCtx as _withCtx, toDisplayString as _toDisplayString, normalizeClass as _normalizeClass, resolveDirective as _resolveDirective, openBlock as _openBlock, createElementBlock as _createElementBlock, withDirectives as _withDirectives, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-17d8eefc\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = { class: \"add-slide\" }\nconst _hoisted_2 = /*#__PURE__*/_createTextVNode(\"添加幻灯片\")\nconst _hoisted_3 = { class: \"select-btn\" }\nconst _hoisted_4 = [\"onMousedown\"]\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _component_IconPlus = _resolveComponent(\"IconPlus\")!\n const _component_LayoutPool = _resolveComponent(\"LayoutPool\")!\n const _component_IconDown = _resolveComponent(\"IconDown\")!\n const _component_Popover = _resolveComponent(\"Popover\")!\n const _component_ThumbnailSlide = _resolveComponent(\"ThumbnailSlide\")!\n const _component_Draggable = _resolveComponent(\"Draggable\")!\n const _directive_contextmenu = _resolveDirective(\"contextmenu\")!\n const _directive_click_outside = _resolveDirective(\"click-outside\")!\n\n return _withDirectives((_openBlock(), _createElementBlock(\"div\", {\n class: \"thumbnails\",\n onMousedown: _cache[3] || (_cache[3] = () => _ctx.setThumbnailsFocus(true))\n }, [\n _createElementVNode(\"div\", _hoisted_1, [\n _createElementVNode(\"div\", {\n class: \"btn\",\n onClick: _cache[0] || (_cache[0] = ($event: any) => (_ctx.createSlide()))\n }, [\n _createVNode(_component_IconPlus, { class: \"icon\" }),\n _hoisted_2\n ]),\n _createVNode(_component_Popover, {\n trigger: \"click\",\n placement: \"bottomLeft\",\n visible: _ctx.presetLayoutPopoverVisible,\n \"onUpdate:visible\": _cache[2] || (_cache[2] = ($event: any) => ((_ctx.presetLayoutPopoverVisible) = $event))\n }, {\n content: _withCtx(() => [\n _createVNode(_component_LayoutPool, {\n onSelect: _cache[1] || (_cache[1] = slide => { _ctx.createSlideByTemplate(slide); _ctx.presetLayoutPopoverVisible = false })\n })\n ]),\n default: _withCtx(() => [\n _createElementVNode(\"div\", _hoisted_3, [\n _createVNode(_component_IconDown)\n ])\n ]),\n _: 1\n }, 8, [\"visible\"])\n ]),\n _createVNode(_component_Draggable, {\n class: \"thumbnail-list\",\n modelValue: _ctx.slides,\n animation: 300,\n scroll: true,\n scrollSensitivity: 50,\n setData: null,\n onEnd: _ctx.handleDragEnd,\n itemKey: \"id\"\n }, {\n item: _withCtx(({ element, index }) => [\n _withDirectives((_openBlock(), _createElementBlock(\"div\", {\n class: _normalizeClass([\"thumbnail-item\", {\n 'active': _ctx.slideIndex === index,\n 'selected': _ctx.selectedSlidesIndex.includes(index),\n }]),\n onMousedown: $event => _ctx.handleClickSlideThumbnail($event, index)\n }, [\n _createElementVNode(\"div\", {\n class: _normalizeClass([\"label\", { 'offset-left': index >= 99 }])\n }, _toDisplayString(_ctx.fillDigit(index + 1, 2)), 3),\n _createVNode(_component_ThumbnailSlide, {\n class: \"thumbnail\",\n slide: element,\n size: 120,\n visible: index < _ctx.slidesLoadLimit\n }, null, 8, [\"slide\", \"visible\"])\n ], 42, _hoisted_4)), [\n [_directive_contextmenu, _ctx.contextmenusThumbnailItem]\n ])\n ]),\n _: 1\n }, 8, [\"modelValue\", \"onEnd\"])\n ], 32)), [\n [_directive_click_outside, () => _ctx.setThumbnailsFocus(false)],\n [_directive_contextmenu, _ctx.contextmenusThumbnails]\n ])\n}","import { ref, onMounted, onUnmounted } from 'vue'\nimport { storeToRefs } from 'pinia'\nimport { useSlidesStore } from '@/store'\n\nexport default () => {\n const { slides } = storeToRefs(useSlidesStore())\n\n const timer = ref(null)\n const slidesLoadLimit = ref(50)\n\n const loadSlide = () => {\n if (slides.value.length > slidesLoadLimit.value) {\n timer.value = setTimeout(() => {\n slidesLoadLimit.value = slidesLoadLimit.value + 20\n loadSlide()\n }, 600)\n }\n else slidesLoadLimit.value = 9999\n }\n\n onMounted(loadSlide)\n\n onUnmounted(() => {\n if (timer.value) clearTimeout(timer.value)\n })\n\n return {\n slidesLoadLimit,\n }\n}","import { renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, resolveComponent as _resolveComponent, createVNode as _createVNode, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-741b366e\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = { class: \"layout-pool\" }\nconst _hoisted_2 = [\"onClick\"]\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _component_ThumbnailSlide = _resolveComponent(\"ThumbnailSlide\")!\n\n return (_openBlock(), _createElementBlock(\"div\", _hoisted_1, [\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.layouts, (slide) => {\n return (_openBlock(), _createElementBlock(\"div\", {\n class: \"layout-item\",\n key: slide.id,\n onClick: ($event: any) => (_ctx.selectSlideTemplate(slide))\n }, [\n _createVNode(_component_ThumbnailSlide, {\n class: \"thumbnail\",\n slide: slide,\n size: 180\n }, null, 8, [\"slide\"])\n ], 8, _hoisted_2))\n }), 128))\n ]))\n}","\nimport { defineComponent } from 'vue'\nimport { storeToRefs } from 'pinia'\nimport { useSlidesStore } from '@/store'\nimport { Slide } from '@/types/slides'\n\nimport ThumbnailSlide from '@/views/components/ThumbnailSlide/index.vue'\n\nexport default defineComponent({\n name: 'layout-pool',\n emits: ['select'],\n components: {\n ThumbnailSlide,\n },\n setup(props, { emit }) {\n const { layouts } = storeToRefs(useSlidesStore())\n\n const selectSlideTemplate = (slide: Slide) => {\n emit('select', slide)\n }\n\n return {\n layouts,\n selectSlideTemplate,\n }\n },\n})\n","import { render } from \"./LayoutPool.vue?vue&type=template&id=741b366e&scoped=true&ts=true\"\nimport script from \"./LayoutPool.vue?vue&type=script&lang=ts\"\nexport * from \"./LayoutPool.vue?vue&type=script&lang=ts\"\n\nimport \"./LayoutPool.vue?vue&type=style&index=0&id=741b366e&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-741b366e\"]])\n\nexport default __exports__","\nimport { computed, defineComponent, ref } from 'vue'\nimport { storeToRefs } from 'pinia'\nimport { useMainStore, useSlidesStore, useKeyboardStore } from '@/store'\nimport { fillDigit } from '@/utils/common'\nimport { ContextmenuItem } from '@/components/Contextmenu/types'\nimport useSlideHandler from '@/hooks/useSlideHandler'\nimport useScreening from '@/hooks/useScreening'\nimport useLoadSlides from '@/hooks/useLoadSlides'\n\nimport Draggable from 'vuedraggable'\nimport ThumbnailSlide from '@/views/components/ThumbnailSlide/index.vue'\nimport LayoutPool from './LayoutPool.vue'\n\nexport default defineComponent({\n name: 'thumbnails',\n components: {\n Draggable,\n ThumbnailSlide,\n LayoutPool,\n },\n setup() {\n const mainStore = useMainStore()\n const slidesStore = useSlidesStore()\n const keyboardStore = useKeyboardStore()\n const { selectedSlidesIndex: _selectedSlidesIndex, thumbnailsFocus } = storeToRefs(mainStore)\n const { slides, slideIndex } = storeToRefs(slidesStore)\n const { ctrlKeyState, shiftKeyState } = storeToRefs(keyboardStore)\n\n const { slidesLoadLimit } = useLoadSlides()\n\n const selectedSlidesIndex = computed(() => [..._selectedSlidesIndex.value, slideIndex.value])\n\n const presetLayoutPopoverVisible = ref(false)\n\n const {\n copySlide,\n pasteSlide,\n createSlide,\n createSlideByTemplate,\n copyAndPasteSlide,\n deleteSlide,\n cutSlide,\n selectAllSlide,\n } = useSlideHandler()\n\n // 切换页面\n const changSlideIndex = (index: number) => {\n mainStore.setActiveElementIdList([])\n\n if (slideIndex.value === index) return\n slidesStore.updateSlideIndex(index)\n }\n\n // 点击缩略图\n const handleClickSlideThumbnail = (e: MouseEvent, index: number) => {\n const isMultiSelected = selectedSlidesIndex.value.length > 1\n\n if (isMultiSelected && selectedSlidesIndex.value.includes(index) && e.button !== 0) return\n\n // 按住Ctrl键,点选幻灯片,再次点击已选中的页面则取消选中\n if (ctrlKeyState.value) {\n if (slideIndex.value === index) {\n if (!isMultiSelected) return\n\n const newSelectedSlidesIndex = selectedSlidesIndex.value.filter(item => item !== index)\n mainStore.updateSelectedSlidesIndex(newSelectedSlidesIndex)\n changSlideIndex(selectedSlidesIndex.value[0])\n }\n else {\n if (selectedSlidesIndex.value.includes(index)) {\n const newSelectedSlidesIndex = selectedSlidesIndex.value.filter(item => item !== index)\n mainStore.updateSelectedSlidesIndex(newSelectedSlidesIndex)\n }\n else {\n const newSelectedSlidesIndex = [...selectedSlidesIndex.value, index]\n mainStore.updateSelectedSlidesIndex(newSelectedSlidesIndex)\n changSlideIndex(index)\n }\n }\n }\n // 按住Shift键,选择范围内的全部幻灯片\n else if (shiftKeyState.value) {\n if (slideIndex.value === index && !isMultiSelected) return\n\n let minIndex = Math.min(...selectedSlidesIndex.value)\n let maxIndex = index\n\n if (index < minIndex) {\n maxIndex = Math.max(...selectedSlidesIndex.value)\n minIndex = index\n }\n\n const newSelectedSlidesIndex = []\n for (let i = minIndex; i <= maxIndex; i++) newSelectedSlidesIndex.push(i)\n mainStore.updateSelectedSlidesIndex(newSelectedSlidesIndex)\n changSlideIndex(index)\n }\n // 正常切换页面\n else {\n mainStore.updateSelectedSlidesIndex([])\n changSlideIndex(index)\n }\n }\n\n // 设置缩略图工具栏聚焦状态(只有聚焦状态下,该部分的快捷键才能生效)\n const setThumbnailsFocus = (focus: boolean) => {\n if (thumbnailsFocus.value === focus) return\n mainStore.setThumbnailsFocus(focus)\n\n if (!focus) mainStore.updateSelectedSlidesIndex([])\n }\n\n // 拖拽调整顺序后进行数据的同步\n const handleDragEnd = (eventData: { newIndex: number; oldIndex: number }) => {\n const { newIndex, oldIndex } = eventData\n if (oldIndex === newIndex) return\n\n const _slides = JSON.parse(JSON.stringify(slides.value))\n const _slide = _slides[oldIndex]\n _slides.splice(oldIndex, 1)\n _slides.splice(newIndex, 0, _slide)\n slidesStore.setSlides(_slides)\n slidesStore.updateSlideIndex(newIndex)\n }\n\n const { enterScreening, enterScreeningFromStart } = useScreening()\n\n const contextmenusThumbnails = (): ContextmenuItem[] => {\n return [\n {\n text: '粘贴',\n subText: 'Ctrl + V',\n handler: pasteSlide,\n },\n {\n text: '全选',\n subText: 'Ctrl + A',\n handler: selectAllSlide,\n },\n {\n text: '新建页面',\n subText: 'Enter',\n handler: createSlide,\n },\n {\n text: '幻灯片放映',\n subText: 'F5',\n handler: enterScreeningFromStart,\n },\n ]\n }\n\n const contextmenusThumbnailItem = (): ContextmenuItem[] => {\n return [\n {\n text: '剪切',\n subText: 'Ctrl + X',\n handler: cutSlide,\n },\n {\n text: '复制',\n subText: 'Ctrl + C',\n handler: copySlide,\n },\n {\n text: '粘贴',\n subText: 'Ctrl + V',\n handler: pasteSlide,\n },\n {\n text: '全选',\n subText: 'Ctrl + A',\n handler: selectAllSlide,\n },\n { divider: true },\n {\n text: '新建页面',\n subText: 'Enter',\n handler: createSlide,\n },\n {\n text: '复制页面',\n subText: 'Ctrl + D',\n handler: copyAndPasteSlide,\n },\n {\n text: '删除页面',\n subText: 'Delete',\n handler: () => deleteSlide(),\n },\n { divider: true },\n {\n text: '从当前放映',\n subText: 'Shift + F5',\n handler: enterScreening,\n },\n ]\n }\n\n return {\n slides,\n slideIndex,\n selectedSlidesIndex,\n presetLayoutPopoverVisible,\n slidesLoadLimit,\n createSlide,\n createSlideByTemplate,\n setThumbnailsFocus,\n handleClickSlideThumbnail,\n contextmenusThumbnails,\n contextmenusThumbnailItem,\n fillDigit,\n handleDragEnd,\n }\n },\n})\n","import { render } from \"./index.vue?vue&type=template&id=17d8eefc&scoped=true&ts=true\"\nimport script from \"./index.vue?vue&type=script&lang=ts\"\nexport * from \"./index.vue?vue&type=script&lang=ts\"\n\nimport \"./index.vue?vue&type=style&index=0&id=17d8eefc&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-17d8eefc\"]])\n\nexport default __exports__","import { renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, toDisplayString as _toDisplayString, normalizeClass as _normalizeClass, createElementVNode as _createElementVNode, resolveDynamicComponent as _resolveDynamicComponent, createBlock as _createBlock, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-6e28a17b\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = { class: \"toolbar\" }\nconst _hoisted_2 = { class: \"tabs\" }\nconst _hoisted_3 = [\"onClick\"]\nconst _hoisted_4 = { class: \"content\" }\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n return (_openBlock(), _createElementBlock(\"div\", _hoisted_1, [\n _createElementVNode(\"div\", _hoisted_2, [\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.currentTabs, (tab) => {\n return (_openBlock(), _createElementBlock(\"div\", {\n class: _normalizeClass([\"tab\", { 'active': tab.value === _ctx.toolbarState }]),\n key: tab.value,\n onClick: ($event: any) => (_ctx.setToolbarState(tab.value))\n }, _toDisplayString(tab.label), 11, _hoisted_3))\n }), 128))\n ]),\n _createElementVNode(\"div\", _hoisted_4, [\n (_openBlock(), _createBlock(_resolveDynamicComponent(_ctx.currentPanelComponent)))\n ])\n ]))\n}","import { resolveDynamicComponent as _resolveDynamicComponent, openBlock as _openBlock, createBlock as _createBlock, createElementBlock as _createElementBlock } from \"vue\"\n\nconst _hoisted_1 = { class: \"element-style-panel\" }\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n return (_openBlock(), _createElementBlock(\"div\", _hoisted_1, [\n (_openBlock(), _createBlock(_resolveDynamicComponent(_ctx.currentPanelComponent)))\n ]))\n}","import { renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, toDisplayString as _toDisplayString, normalizeStyle as _normalizeStyle, createElementVNode as _createElementVNode, resolveComponent as _resolveComponent, createVNode as _createVNode, withCtx as _withCtx, createBlock as _createBlock, createTextVNode as _createTextVNode, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-483299d4\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = { class: \"text-style-panel\" }\nconst _hoisted_2 = { class: \"preset-style\" }\nconst _hoisted_3 = [\"onClick\"]\nconst _hoisted_4 = /*#__PURE__*/_createTextVNode(\"+\")\nconst _hoisted_5 = /*#__PURE__*/_createTextVNode(\"-\")\nconst _hoisted_6 = { class: \"link-popover\" }\nconst _hoisted_7 = { class: \"btns\" }\nconst _hoisted_8 = /*#__PURE__*/_createTextVNode(\"移除\")\nconst _hoisted_9 = /*#__PURE__*/_createTextVNode(\"确认\")\nconst _hoisted_10 = { class: \"row\" }\nconst _hoisted_11 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(\"div\", { style: {\"flex\":\"2\"} }, \"行间距:\", -1))\nconst _hoisted_12 = { class: \"row\" }\nconst _hoisted_13 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(\"div\", { style: {\"flex\":\"2\"} }, \"字间距:\", -1))\nconst _hoisted_14 = { class: \"row\" }\nconst _hoisted_15 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(\"div\", { style: {\"flex\":\"2\"} }, \"文本框填充:\", -1))\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _component_Divider = _resolveComponent(\"Divider\")!\n const _component_IconFontSize = _resolveComponent(\"IconFontSize\")!\n const _component_SelectOption = _resolveComponent(\"SelectOption\")!\n const _component_SelectOptGroup = _resolveComponent(\"SelectOptGroup\")!\n const _component_Select = _resolveComponent(\"Select\")!\n const _component_IconAddText = _resolveComponent(\"IconAddText\")!\n const _component_InputGroup = _resolveComponent(\"InputGroup\")!\n const _component_ColorPicker = _resolveComponent(\"ColorPicker\")!\n const _component_IconText = _resolveComponent(\"IconText\")!\n const _component_Button = _resolveComponent(\"Button\")!\n const _component_Tooltip = _resolveComponent(\"Tooltip\")!\n const _component_Popover = _resolveComponent(\"Popover\")!\n const _component_IconHighLight = _resolveComponent(\"IconHighLight\")!\n const _component_ButtonGroup = _resolveComponent(\"ButtonGroup\")!\n const _component_IconTextBold = _resolveComponent(\"IconTextBold\")!\n const _component_CheckboxButton = _resolveComponent(\"CheckboxButton\")!\n const _component_IconTextItalic = _resolveComponent(\"IconTextItalic\")!\n const _component_IconTextUnderline = _resolveComponent(\"IconTextUnderline\")!\n const _component_IconStrikethrough = _resolveComponent(\"IconStrikethrough\")!\n const _component_IconFormat = _resolveComponent(\"IconFormat\")!\n const _component_CheckboxButtonGroup = _resolveComponent(\"CheckboxButtonGroup\")!\n const _component_IconUpOne = _resolveComponent(\"IconUpOne\")!\n const _component_IconDownOne = _resolveComponent(\"IconDownOne\")!\n const _component_IconCode = _resolveComponent(\"IconCode\")!\n const _component_IconQuote = _resolveComponent(\"IconQuote\")!\n const _component_Input = _resolveComponent(\"Input\")!\n const _component_IconLinkOne = _resolveComponent(\"IconLinkOne\")!\n const _component_IconAlignTextLeft = _resolveComponent(\"IconAlignTextLeft\")!\n const _component_RadioButton = _resolveComponent(\"RadioButton\")!\n const _component_IconAlignTextCenter = _resolveComponent(\"IconAlignTextCenter\")!\n const _component_IconAlignTextRight = _resolveComponent(\"IconAlignTextRight\")!\n const _component_RadioGroup = _resolveComponent(\"RadioGroup\")!\n const _component_IconList = _resolveComponent(\"IconList\")!\n const _component_IconOrderedList = _resolveComponent(\"IconOrderedList\")!\n const _component_IconRowHeight = _resolveComponent(\"IconRowHeight\")!\n const _component_IconFullwidth = _resolveComponent(\"IconFullwidth\")!\n const _component_ColorButton = _resolveComponent(\"ColorButton\")!\n const _component_ElementOutline = _resolveComponent(\"ElementOutline\")!\n const _component_ElementShadow = _resolveComponent(\"ElementShadow\")!\n const _component_ElementOpacity = _resolveComponent(\"ElementOpacity\")!\n\n return (_openBlock(), _createElementBlock(\"div\", _hoisted_1, [\n _createElementVNode(\"div\", _hoisted_2, [\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.presetStyles, (item) => {\n return (_openBlock(), _createElementBlock(\"div\", {\n class: \"preset-style-item\",\n key: item.label,\n style: _normalizeStyle(item.style),\n onClick: ($event: any) => (_ctx.emitBatchRichTextCommand(item.cmd))\n }, _toDisplayString(item.label), 13, _hoisted_3))\n }), 128))\n ]),\n _createVNode(_component_Divider),\n _createVNode(_component_InputGroup, {\n compact: \"\",\n class: \"row\"\n }, {\n default: _withCtx(() => [\n _createVNode(_component_Select, {\n style: {\"flex\":\"3\"},\n value: _ctx.richTextAttrs.fontname,\n onChange: _cache[0] || (_cache[0] = value => _ctx.emitRichTextCommand('fontname', value))\n }, {\n suffixIcon: _withCtx(() => [\n _createVNode(_component_IconFontSize)\n ]),\n default: _withCtx(() => [\n _createVNode(_component_SelectOptGroup, { label: \"系统字体\" }, {\n default: _withCtx(() => [\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.availableFonts, (font) => {\n return (_openBlock(), _createBlock(_component_SelectOption, {\n key: font.value,\n value: font.value\n }, {\n default: _withCtx(() => [\n _createElementVNode(\"span\", {\n style: _normalizeStyle({ fontFamily: font.value })\n }, _toDisplayString(font.label), 5)\n ]),\n _: 2\n }, 1032, [\"value\"]))\n }), 128))\n ]),\n _: 1\n }),\n _createVNode(_component_SelectOptGroup, { label: \"在线字体\" }, {\n default: _withCtx(() => [\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.webFonts, (font) => {\n return (_openBlock(), _createBlock(_component_SelectOption, {\n key: font.value,\n value: font.value\n }, {\n default: _withCtx(() => [\n _createElementVNode(\"span\", null, _toDisplayString(font.label), 1)\n ]),\n _: 2\n }, 1032, [\"value\"]))\n }), 128))\n ]),\n _: 1\n })\n ]),\n _: 1\n }, 8, [\"value\"]),\n _createVNode(_component_Select, {\n style: {\"flex\":\"2\"},\n value: _ctx.richTextAttrs.fontsize,\n onChange: _cache[1] || (_cache[1] = value => _ctx.emitRichTextCommand('fontsize', value))\n }, {\n suffixIcon: _withCtx(() => [\n _createVNode(_component_IconAddText)\n ]),\n default: _withCtx(() => [\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.fontSizeOptions, (fontsize) => {\n return (_openBlock(), _createBlock(_component_SelectOption, {\n key: fontsize,\n value: fontsize\n }, {\n default: _withCtx(() => [\n _createTextVNode(_toDisplayString(fontsize), 1)\n ]),\n _: 2\n }, 1032, [\"value\"]))\n }), 128))\n ]),\n _: 1\n }, 8, [\"value\"])\n ]),\n _: 1\n }),\n _createVNode(_component_ButtonGroup, { class: \"row\" }, {\n default: _withCtx(() => [\n _createVNode(_component_Popover, { trigger: \"click\" }, {\n content: _withCtx(() => [\n _createVNode(_component_ColorPicker, {\n modelValue: _ctx.richTextAttrs.color,\n \"onUpdate:modelValue\": _cache[2] || (_cache[2] = value => _ctx.emitRichTextCommand('color', value))\n }, null, 8, [\"modelValue\"])\n ]),\n default: _withCtx(() => [\n _createVNode(_component_Tooltip, {\n mouseLeaveDelay: 0,\n mouseEnterDelay: 0.5,\n title: \"文字颜色\"\n }, {\n default: _withCtx(() => [\n _createVNode(_component_Button, {\n class: \"text-color-btn\",\n style: {\"flex\":\"3\"}\n }, {\n default: _withCtx(() => [\n _createVNode(_component_IconText),\n _createElementVNode(\"div\", {\n class: \"text-color-block\",\n style: _normalizeStyle({ backgroundColor: _ctx.richTextAttrs.color })\n }, null, 4)\n ]),\n _: 1\n })\n ]),\n _: 1\n }, 8, [\"mouseEnterDelay\"])\n ]),\n _: 1\n }),\n _createVNode(_component_Popover, { trigger: \"click\" }, {\n content: _withCtx(() => [\n _createVNode(_component_ColorPicker, {\n modelValue: _ctx.richTextAttrs.backcolor,\n \"onUpdate:modelValue\": _cache[3] || (_cache[3] = value => _ctx.emitRichTextCommand('backcolor', value))\n }, null, 8, [\"modelValue\"])\n ]),\n default: _withCtx(() => [\n _createVNode(_component_Tooltip, {\n mouseLeaveDelay: 0,\n mouseEnterDelay: 0.5,\n title: \"文字高亮\"\n }, {\n default: _withCtx(() => [\n _createVNode(_component_Button, {\n class: \"text-color-btn\",\n style: {\"flex\":\"3\"}\n }, {\n default: _withCtx(() => [\n _createVNode(_component_IconHighLight),\n _createElementVNode(\"div\", {\n class: \"text-color-block\",\n style: _normalizeStyle({ backgroundColor: _ctx.richTextAttrs.backcolor })\n }, null, 4)\n ]),\n _: 1\n })\n ]),\n _: 1\n }, 8, [\"mouseEnterDelay\"])\n ]),\n _: 1\n }),\n _createVNode(_component_Tooltip, {\n mouseLeaveDelay: 0,\n mouseEnterDelay: 0.5,\n title: \"增大字号\"\n }, {\n default: _withCtx(() => [\n _createVNode(_component_Button, {\n class: \"font-size-btn\",\n style: {\"flex\":\"2\"},\n onClick: _cache[4] || (_cache[4] = ($event: any) => (_ctx.emitRichTextCommand('fontsize-add')))\n }, {\n default: _withCtx(() => [\n _createVNode(_component_IconFontSize),\n _hoisted_4\n ]),\n _: 1\n })\n ]),\n _: 1\n }, 8, [\"mouseEnterDelay\"]),\n _createVNode(_component_Tooltip, {\n mouseLeaveDelay: 0,\n mouseEnterDelay: 0.5,\n title: \"减小字号\"\n }, {\n default: _withCtx(() => [\n _createVNode(_component_Button, {\n class: \"font-size-btn\",\n style: {\"flex\":\"2\"},\n onClick: _cache[5] || (_cache[5] = ($event: any) => (_ctx.emitRichTextCommand('fontsize-reduce')))\n }, {\n default: _withCtx(() => [\n _createVNode(_component_IconFontSize),\n _hoisted_5\n ]),\n _: 1\n })\n ]),\n _: 1\n }, 8, [\"mouseEnterDelay\"])\n ]),\n _: 1\n }),\n _createVNode(_component_CheckboxButtonGroup, { class: \"row\" }, {\n default: _withCtx(() => [\n _createVNode(_component_Tooltip, {\n mouseLeaveDelay: 0,\n mouseEnterDelay: 0.5,\n title: \"加粗\"\n }, {\n default: _withCtx(() => [\n _createVNode(_component_CheckboxButton, {\n style: {\"flex\":\"1\"},\n checked: _ctx.richTextAttrs.bold,\n onClick: _cache[6] || (_cache[6] = ($event: any) => (_ctx.emitRichTextCommand('bold')))\n }, {\n default: _withCtx(() => [\n _createVNode(_component_IconTextBold)\n ]),\n _: 1\n }, 8, [\"checked\"])\n ]),\n _: 1\n }, 8, [\"mouseEnterDelay\"]),\n _createVNode(_component_Tooltip, {\n mouseLeaveDelay: 0,\n mouseEnterDelay: 0.5,\n title: \"斜体\"\n }, {\n default: _withCtx(() => [\n _createVNode(_component_CheckboxButton, {\n style: {\"flex\":\"1\"},\n checked: _ctx.richTextAttrs.em,\n onClick: _cache[7] || (_cache[7] = ($event: any) => (_ctx.emitRichTextCommand('em')))\n }, {\n default: _withCtx(() => [\n _createVNode(_component_IconTextItalic)\n ]),\n _: 1\n }, 8, [\"checked\"])\n ]),\n _: 1\n }, 8, [\"mouseEnterDelay\"]),\n _createVNode(_component_Tooltip, {\n mouseLeaveDelay: 0,\n mouseEnterDelay: 0.5,\n title: \"下划线\"\n }, {\n default: _withCtx(() => [\n _createVNode(_component_CheckboxButton, {\n style: {\"flex\":\"1\"},\n checked: _ctx.richTextAttrs.underline,\n onClick: _cache[8] || (_cache[8] = ($event: any) => (_ctx.emitRichTextCommand('underline')))\n }, {\n default: _withCtx(() => [\n _createVNode(_component_IconTextUnderline)\n ]),\n _: 1\n }, 8, [\"checked\"])\n ]),\n _: 1\n }, 8, [\"mouseEnterDelay\"]),\n _createVNode(_component_Tooltip, {\n mouseLeaveDelay: 0,\n mouseEnterDelay: 0.5,\n title: \"删除线\"\n }, {\n default: _withCtx(() => [\n _createVNode(_component_CheckboxButton, {\n style: {\"flex\":\"1\"},\n checked: _ctx.richTextAttrs.strikethrough,\n onClick: _cache[9] || (_cache[9] = ($event: any) => (_ctx.emitRichTextCommand('strikethrough')))\n }, {\n default: _withCtx(() => [\n _createVNode(_component_IconStrikethrough)\n ]),\n _: 1\n }, 8, [\"checked\"])\n ]),\n _: 1\n }, 8, [\"mouseEnterDelay\"]),\n _createVNode(_component_Tooltip, {\n mouseLeaveDelay: 0,\n mouseEnterDelay: 0.5,\n title: \"清除格式\"\n }, {\n default: _withCtx(() => [\n _createVNode(_component_CheckboxButton, {\n style: {\"flex\":\"1\"},\n onClick: _cache[10] || (_cache[10] = ($event: any) => (_ctx.emitRichTextCommand('clear')))\n }, {\n default: _withCtx(() => [\n _createVNode(_component_IconFormat)\n ]),\n _: 1\n })\n ]),\n _: 1\n }, 8, [\"mouseEnterDelay\"])\n ]),\n _: 1\n }),\n _createVNode(_component_CheckboxButtonGroup, { class: \"row\" }, {\n default: _withCtx(() => [\n _createVNode(_component_Tooltip, {\n mouseLeaveDelay: 0,\n mouseEnterDelay: 0.5,\n title: \"上标\"\n }, {\n default: _withCtx(() => [\n _createVNode(_component_CheckboxButton, {\n style: {\"flex\":\"1\"},\n checked: _ctx.richTextAttrs.superscript,\n onClick: _cache[11] || (_cache[11] = ($event: any) => (_ctx.emitRichTextCommand('superscript')))\n }, {\n default: _withCtx(() => [\n _createVNode(_component_IconUpOne)\n ]),\n _: 1\n }, 8, [\"checked\"])\n ]),\n _: 1\n }, 8, [\"mouseEnterDelay\"]),\n _createVNode(_component_Tooltip, {\n mouseLeaveDelay: 0,\n mouseEnterDelay: 0.5,\n title: \"下标\"\n }, {\n default: _withCtx(() => [\n _createVNode(_component_CheckboxButton, {\n style: {\"flex\":\"1\"},\n checked: _ctx.richTextAttrs.subscript,\n onClick: _cache[12] || (_cache[12] = ($event: any) => (_ctx.emitRichTextCommand('subscript')))\n }, {\n default: _withCtx(() => [\n _createVNode(_component_IconDownOne)\n ]),\n _: 1\n }, 8, [\"checked\"])\n ]),\n _: 1\n }, 8, [\"mouseEnterDelay\"]),\n _createVNode(_component_Tooltip, {\n mouseLeaveDelay: 0,\n mouseEnterDelay: 0.5,\n title: \"行内代码\"\n }, {\n default: _withCtx(() => [\n _createVNode(_component_CheckboxButton, {\n style: {\"flex\":\"1\"},\n checked: _ctx.richTextAttrs.code,\n onClick: _cache[13] || (_cache[13] = ($event: any) => (_ctx.emitRichTextCommand('code')))\n }, {\n default: _withCtx(() => [\n _createVNode(_component_IconCode)\n ]),\n _: 1\n }, 8, [\"checked\"])\n ]),\n _: 1\n }, 8, [\"mouseEnterDelay\"]),\n _createVNode(_component_Tooltip, {\n mouseLeaveDelay: 0,\n mouseEnterDelay: 0.5,\n title: \"引用\"\n }, {\n default: _withCtx(() => [\n _createVNode(_component_CheckboxButton, {\n style: {\"flex\":\"1\"},\n checked: _ctx.richTextAttrs.blockquote,\n onClick: _cache[14] || (_cache[14] = ($event: any) => (_ctx.emitRichTextCommand('blockquote')))\n }, {\n default: _withCtx(() => [\n _createVNode(_component_IconQuote)\n ]),\n _: 1\n }, 8, [\"checked\"])\n ]),\n _: 1\n }, 8, [\"mouseEnterDelay\"]),\n _createVNode(_component_Tooltip, {\n mouseLeaveDelay: 0,\n mouseEnterDelay: 0.5,\n title: \"超链接\"\n }, {\n default: _withCtx(() => [\n _createVNode(_component_Popover, {\n placement: \"bottomRight\",\n trigger: \"click\",\n visible: _ctx.linkPopoverVisible,\n \"onUpdate:visible\": _cache[19] || (_cache[19] = ($event: any) => ((_ctx.linkPopoverVisible) = $event))\n }, {\n content: _withCtx(() => [\n _createElementVNode(\"div\", _hoisted_6, [\n _createVNode(_component_Input, {\n value: _ctx.link,\n \"onUpdate:value\": _cache[15] || (_cache[15] = ($event: any) => ((_ctx.link) = $event)),\n placeholder: \"请输入超链接\"\n }, null, 8, [\"value\"]),\n _createElementVNode(\"div\", _hoisted_7, [\n _createVNode(_component_Button, {\n size: \"small\",\n disabled: !_ctx.richTextAttrs.link,\n onClick: _cache[16] || (_cache[16] = ($event: any) => (_ctx.updateLink())),\n style: {\"margin-right\":\"5px\"}\n }, {\n default: _withCtx(() => [\n _hoisted_8\n ]),\n _: 1\n }, 8, [\"disabled\"]),\n _createVNode(_component_Button, {\n size: \"small\",\n type: \"primary\",\n onClick: _cache[17] || (_cache[17] = ($event: any) => (_ctx.updateLink(_ctx.link)))\n }, {\n default: _withCtx(() => [\n _hoisted_9\n ]),\n _: 1\n })\n ])\n ])\n ]),\n default: _withCtx(() => [\n _createVNode(_component_CheckboxButton, {\n style: {\"flex\":\"1\"},\n checked: !!_ctx.richTextAttrs.link,\n onClick: _cache[18] || (_cache[18] = ($event: any) => (_ctx.openLinkPopover()))\n }, {\n default: _withCtx(() => [\n _createVNode(_component_IconLinkOne)\n ]),\n _: 1\n }, 8, [\"checked\"])\n ]),\n _: 1\n }, 8, [\"visible\"])\n ]),\n _: 1\n }, 8, [\"mouseEnterDelay\"])\n ]),\n _: 1\n }),\n _createVNode(_component_Divider),\n _createVNode(_component_RadioGroup, {\n class: \"row\",\n \"button-style\": \"solid\",\n value: _ctx.richTextAttrs.align,\n onChange: _cache[20] || (_cache[20] = e => _ctx.emitRichTextCommand('align', e.target.value))\n }, {\n default: _withCtx(() => [\n _createVNode(_component_Tooltip, {\n mouseLeaveDelay: 0,\n mouseEnterDelay: 0.5,\n title: \"左对齐\"\n }, {\n default: _withCtx(() => [\n _createVNode(_component_RadioButton, {\n value: \"left\",\n style: {\"flex\":\"1\"}\n }, {\n default: _withCtx(() => [\n _createVNode(_component_IconAlignTextLeft)\n ]),\n _: 1\n })\n ]),\n _: 1\n }, 8, [\"mouseEnterDelay\"]),\n _createVNode(_component_Tooltip, {\n mouseLeaveDelay: 0,\n mouseEnterDelay: 0.5,\n title: \"居中\"\n }, {\n default: _withCtx(() => [\n _createVNode(_component_RadioButton, {\n value: \"center\",\n style: {\"flex\":\"1\"}\n }, {\n default: _withCtx(() => [\n _createVNode(_component_IconAlignTextCenter)\n ]),\n _: 1\n })\n ]),\n _: 1\n }, 8, [\"mouseEnterDelay\"]),\n _createVNode(_component_Tooltip, {\n mouseLeaveDelay: 0,\n mouseEnterDelay: 0.5,\n title: \"右对齐\"\n }, {\n default: _withCtx(() => [\n _createVNode(_component_RadioButton, {\n value: \"right\",\n style: {\"flex\":\"1\"}\n }, {\n default: _withCtx(() => [\n _createVNode(_component_IconAlignTextRight)\n ]),\n _: 1\n })\n ]),\n _: 1\n }, 8, [\"mouseEnterDelay\"])\n ]),\n _: 1\n }, 8, [\"value\"]),\n _createVNode(_component_CheckboxButtonGroup, { class: \"row\" }, {\n default: _withCtx(() => [\n _createVNode(_component_Tooltip, {\n mouseLeaveDelay: 0,\n mouseEnterDelay: 0.5,\n title: \"项目符号\"\n }, {\n default: _withCtx(() => [\n _createVNode(_component_CheckboxButton, {\n style: {\"flex\":\"1\"},\n checked: _ctx.richTextAttrs.bulletList,\n onClick: _cache[21] || (_cache[21] = ($event: any) => (_ctx.emitRichTextCommand('bulletList')))\n }, {\n default: _withCtx(() => [\n _createVNode(_component_IconList)\n ]),\n _: 1\n }, 8, [\"checked\"])\n ]),\n _: 1\n }, 8, [\"mouseEnterDelay\"]),\n _createVNode(_component_Tooltip, {\n mouseLeaveDelay: 0,\n mouseEnterDelay: 0.5,\n title: \"编号\"\n }, {\n default: _withCtx(() => [\n _createVNode(_component_CheckboxButton, {\n style: {\"flex\":\"1\"},\n checked: _ctx.richTextAttrs.orderedList,\n onClick: _cache[22] || (_cache[22] = ($event: any) => (_ctx.emitRichTextCommand('orderedList')))\n }, {\n default: _withCtx(() => [\n _createVNode(_component_IconOrderedList)\n ]),\n _: 1\n }, 8, [\"checked\"])\n ]),\n _: 1\n }, 8, [\"mouseEnterDelay\"])\n ]),\n _: 1\n }),\n _createVNode(_component_Divider),\n _createElementVNode(\"div\", _hoisted_10, [\n _hoisted_11,\n _createVNode(_component_Select, {\n style: {\"flex\":\"3\"},\n value: _ctx.lineHeight,\n onChange: _cache[23] || (_cache[23] = value => _ctx.updateLineHeight(value))\n }, {\n suffixIcon: _withCtx(() => [\n _createVNode(_component_IconRowHeight)\n ]),\n default: _withCtx(() => [\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.lineHeightOptions, (item) => {\n return (_openBlock(), _createBlock(_component_SelectOption, {\n key: item,\n value: item\n }, {\n default: _withCtx(() => [\n _createTextVNode(_toDisplayString(item) + \"倍\", 1)\n ]),\n _: 2\n }, 1032, [\"value\"]))\n }), 128))\n ]),\n _: 1\n }, 8, [\"value\"])\n ]),\n _createElementVNode(\"div\", _hoisted_12, [\n _hoisted_13,\n _createVNode(_component_Select, {\n style: {\"flex\":\"3\"},\n value: _ctx.wordSpace,\n onChange: _cache[24] || (_cache[24] = value => _ctx.updateWordSpace(value))\n }, {\n suffixIcon: _withCtx(() => [\n _createVNode(_component_IconFullwidth)\n ]),\n default: _withCtx(() => [\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.wordSpaceOptions, (item) => {\n return (_openBlock(), _createBlock(_component_SelectOption, {\n key: item,\n value: item\n }, {\n default: _withCtx(() => [\n _createTextVNode(_toDisplayString(item) + \"px\", 1)\n ]),\n _: 2\n }, 1032, [\"value\"]))\n }), 128))\n ]),\n _: 1\n }, 8, [\"value\"])\n ]),\n _createElementVNode(\"div\", _hoisted_14, [\n _hoisted_15,\n _createVNode(_component_Popover, { trigger: \"click\" }, {\n content: _withCtx(() => [\n _createVNode(_component_ColorPicker, {\n modelValue: _ctx.fill,\n \"onUpdate:modelValue\": _cache[25] || (_cache[25] = value => _ctx.updateFill(value))\n }, null, 8, [\"modelValue\"])\n ]),\n default: _withCtx(() => [\n _createVNode(_component_ColorButton, {\n color: _ctx.fill,\n style: {\"flex\":\"3\"}\n }, null, 8, [\"color\"])\n ]),\n _: 1\n })\n ]),\n _createVNode(_component_Divider),\n _createVNode(_component_ElementOutline),\n _createVNode(_component_Divider),\n _createVNode(_component_ElementShadow),\n _createVNode(_component_Divider),\n _createVNode(_component_ElementOpacity)\n ]))\n}","import { createElementVNode as _createElementVNode, resolveComponent as _resolveComponent, createVNode as _createVNode, openBlock as _openBlock, createElementBlock as _createElementBlock, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-f62a02b6\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = { class: \"element-opacity\" }\nconst _hoisted_2 = { class: \"row\" }\nconst _hoisted_3 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(\"div\", { style: {\"flex\":\"2\"} }, \"不透明度:\", -1))\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _component_Slider = _resolveComponent(\"Slider\")!\n\n return (_openBlock(), _createElementBlock(\"div\", _hoisted_1, [\n _createElementVNode(\"div\", _hoisted_2, [\n _hoisted_3,\n _createVNode(_component_Slider, {\n class: \"slider\",\n min: 0,\n max: 1,\n step: 0.1,\n value: _ctx.opacity,\n onChange: _cache[0] || (_cache[0] = value => _ctx.updateOpacity(value))\n }, null, 8, [\"step\", \"value\"])\n ])\n ]))\n}","\nimport { defineComponent, ref, watch } from 'vue'\nimport { storeToRefs } from 'pinia'\nimport { useMainStore, useSlidesStore } from '@/store'\nimport useHistorySnapshot from '@/hooks/useHistorySnapshot'\n\nexport default defineComponent({\n name: 'element-opacity',\n setup() {\n const slidesStore = useSlidesStore()\n const { handleElement } = storeToRefs(useMainStore())\n\n const opacity = ref(1)\n\n watch(handleElement, () => {\n if (!handleElement.value) return\n opacity.value = 'opacity' in handleElement.value && handleElement.value.opacity !== undefined ? handleElement.value.opacity : 1\n }, { deep: true, immediate: true })\n\n const { addHistorySnapshot } = useHistorySnapshot()\n\n const updateOpacity = (value: number) => {\n if (!handleElement.value) return\n const props = { opacity: value }\n slidesStore.updateElement({ id: handleElement.value.id, props })\n addHistorySnapshot()\n }\n\n return {\n opacity,\n updateOpacity,\n }\n },\n})\n","import { render } from \"./ElementOpacity.vue?vue&type=template&id=f62a02b6&scoped=true&ts=true\"\nimport script from \"./ElementOpacity.vue?vue&type=script&lang=ts\"\nexport * from \"./ElementOpacity.vue?vue&type=script&lang=ts\"\n\nimport \"./ElementOpacity.vue?vue&type=style&index=0&id=f62a02b6&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-f62a02b6\"]])\n\nexport default __exports__","import { createElementVNode as _createElementVNode, resolveComponent as _resolveComponent, createVNode as _createVNode, openBlock as _openBlock, createElementBlock as _createElementBlock, createCommentVNode as _createCommentVNode, createTextVNode as _createTextVNode, withCtx as _withCtx, Fragment as _Fragment, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-35c4c4a3\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = { class: \"element-outline\" }\nconst _hoisted_2 = {\n key: 0,\n class: \"row\"\n}\nconst _hoisted_3 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(\"div\", { style: {\"flex\":\"2\"} }, \"启用边框:\", -1))\nconst _hoisted_4 = {\n class: \"switch-wrapper\",\n style: {\"flex\":\"3\"}\n}\nconst _hoisted_5 = { class: \"row\" }\nconst _hoisted_6 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(\"div\", { style: {\"flex\":\"2\"} }, \"边框样式:\", -1))\nconst _hoisted_7 = /*#__PURE__*/_createTextVNode(\"实线边框\")\nconst _hoisted_8 = /*#__PURE__*/_createTextVNode(\"虚线边框\")\nconst _hoisted_9 = { class: \"row\" }\nconst _hoisted_10 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(\"div\", { style: {\"flex\":\"2\"} }, \"边框颜色:\", -1))\nconst _hoisted_11 = { class: \"row\" }\nconst _hoisted_12 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(\"div\", { style: {\"flex\":\"2\"} }, \"边框粗细:\", -1))\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _component_Switch = _resolveComponent(\"Switch\")!\n const _component_SelectOption = _resolveComponent(\"SelectOption\")!\n const _component_Select = _resolveComponent(\"Select\")!\n const _component_ColorPicker = _resolveComponent(\"ColorPicker\")!\n const _component_ColorButton = _resolveComponent(\"ColorButton\")!\n const _component_Popover = _resolveComponent(\"Popover\")!\n const _component_InputNumber = _resolveComponent(\"InputNumber\")!\n\n return (_openBlock(), _createElementBlock(\"div\", _hoisted_1, [\n (!_ctx.fixed)\n ? (_openBlock(), _createElementBlock(\"div\", _hoisted_2, [\n _hoisted_3,\n _createElementVNode(\"div\", _hoisted_4, [\n _createVNode(_component_Switch, {\n checked: _ctx.hasOutline,\n onChange: _cache[0] || (_cache[0] = checked => _ctx.toggleOutline(checked))\n }, null, 8, [\"checked\"])\n ])\n ]))\n : _createCommentVNode(\"\", true),\n (_ctx.hasOutline)\n ? (_openBlock(), _createElementBlock(_Fragment, { key: 1 }, [\n _createElementVNode(\"div\", _hoisted_5, [\n _hoisted_6,\n _createVNode(_component_Select, {\n style: {\"flex\":\"3\"},\n value: _ctx.outline.style,\n onChange: _cache[1] || (_cache[1] = value => _ctx.updateOutline({ style: value }))\n }, {\n default: _withCtx(() => [\n _createVNode(_component_SelectOption, { value: \"solid\" }, {\n default: _withCtx(() => [\n _hoisted_7\n ]),\n _: 1\n }),\n _createVNode(_component_SelectOption, { value: \"dashed\" }, {\n default: _withCtx(() => [\n _hoisted_8\n ]),\n _: 1\n })\n ]),\n _: 1\n }, 8, [\"value\"])\n ]),\n _createElementVNode(\"div\", _hoisted_9, [\n _hoisted_10,\n _createVNode(_component_Popover, { trigger: \"click\" }, {\n content: _withCtx(() => [\n _createVNode(_component_ColorPicker, {\n modelValue: _ctx.outline.color,\n \"onUpdate:modelValue\": _cache[2] || (_cache[2] = value => _ctx.updateOutline({ color: value }))\n }, null, 8, [\"modelValue\"])\n ]),\n default: _withCtx(() => [\n _createVNode(_component_ColorButton, {\n color: _ctx.outline.color,\n style: {\"flex\":\"3\"}\n }, null, 8, [\"color\"])\n ]),\n _: 1\n })\n ]),\n _createElementVNode(\"div\", _hoisted_11, [\n _hoisted_12,\n _createVNode(_component_InputNumber, {\n value: _ctx.outline.width,\n onChange: _cache[3] || (_cache[3] = value => _ctx.updateOutline({ width: value })),\n style: {\"flex\":\"3\"}\n }, null, 8, [\"value\"])\n ])\n ], 64))\n : _createCommentVNode(\"\", true)\n ]))\n}","import { normalizeStyle as _normalizeStyle, createElementVNode as _createElementVNode, resolveComponent as _resolveComponent, createVNode as _createVNode, withCtx as _withCtx, openBlock as _openBlock, createBlock as _createBlock, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-204d666d\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = { class: \"color-block\" }\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _component_IconPlatte = _resolveComponent(\"IconPlatte\")!\n const _component_Button = _resolveComponent(\"Button\")!\n\n return (_openBlock(), _createBlock(_component_Button, { class: \"color-btn\" }, {\n default: _withCtx(() => [\n _createElementVNode(\"div\", _hoisted_1, [\n _createElementVNode(\"div\", {\n class: \"content\",\n style: _normalizeStyle({ backgroundColor: _ctx.color })\n }, null, 4)\n ]),\n _createVNode(_component_IconPlatte, { class: \"color-btn-icon\" })\n ]),\n _: 1\n }))\n}","\nimport { defineComponent } from 'vue'\n\nexport default defineComponent({\n name: 'color-button',\n props: {\n color: {\n type: String,\n required: true,\n },\n },\n})\n","import { render } from \"./ColorButton.vue?vue&type=template&id=204d666d&scoped=true&ts=true\"\nimport script from \"./ColorButton.vue?vue&type=script&lang=ts\"\nexport * from \"./ColorButton.vue?vue&type=script&lang=ts\"\n\nimport \"./ColorButton.vue?vue&type=style&index=0&id=204d666d&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-204d666d\"]])\n\nexport default __exports__","\nimport { defineComponent, ref, watch } from 'vue'\nimport { storeToRefs } from 'pinia'\nimport { useMainStore, useSlidesStore } from '@/store'\nimport { PPTElementOutline } from '@/types/slides'\nimport useHistorySnapshot from '@/hooks/useHistorySnapshot'\n\nimport ColorButton from './ColorButton.vue'\n\nexport default defineComponent({\n name: 'element-outline',\n components: {\n ColorButton,\n },\n props: {\n fixed: {\n type: Boolean,\n default: false,\n },\n },\n setup() {\n const slidesStore = useSlidesStore()\n const { handleElement } = storeToRefs(useMainStore())\n\n const outline = ref()\n const hasOutline = ref(false)\n\n watch(handleElement, () => {\n if (!handleElement.value) return\n outline.value = 'outline' in handleElement.value ? handleElement.value.outline : undefined\n hasOutline.value = !!outline.value\n }, { deep: true, immediate: true })\n\n const { addHistorySnapshot } = useHistorySnapshot()\n\n const updateOutline = (outlineProps: Partial) => {\n if (!handleElement.value) return\n const props = { outline: { ...outline.value, ...outlineProps } }\n slidesStore.updateElement({ id: handleElement.value.id, props })\n addHistorySnapshot()\n }\n\n const toggleOutline = (checked: boolean) => {\n if (!handleElement.value) return\n if (checked) {\n const _outline: PPTElementOutline = { width: 2, color: '#000', style: 'solid' }\n slidesStore.updateElement({ id: handleElement.value.id, props: { outline: _outline } })\n }\n else {\n slidesStore.removeElementProps({ id: handleElement.value.id, propName: 'outline' })\n }\n addHistorySnapshot()\n }\n\n return {\n outline,\n hasOutline,\n toggleOutline,\n updateOutline,\n }\n },\n})\n","import { render } from \"./ElementOutline.vue?vue&type=template&id=35c4c4a3&scoped=true&ts=true\"\nimport script from \"./ElementOutline.vue?vue&type=script&lang=ts\"\nexport * from \"./ElementOutline.vue?vue&type=script&lang=ts\"\n\nimport \"./ElementOutline.vue?vue&type=style&index=0&id=35c4c4a3&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-35c4c4a3\"]])\n\nexport default __exports__","import { createElementVNode as _createElementVNode, resolveComponent as _resolveComponent, createVNode as _createVNode, withCtx as _withCtx, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, createCommentVNode as _createCommentVNode, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-321cb564\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = { class: \"element-shadow\" }\nconst _hoisted_2 = { class: \"row\" }\nconst _hoisted_3 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(\"div\", { style: {\"flex\":\"2\"} }, \"启用阴影:\", -1))\nconst _hoisted_4 = {\n class: \"switch-wrapper\",\n style: {\"flex\":\"3\"}\n}\nconst _hoisted_5 = { class: \"row\" }\nconst _hoisted_6 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(\"div\", { style: {\"flex\":\"2\"} }, \"水平阴影:\", -1))\nconst _hoisted_7 = { class: \"row\" }\nconst _hoisted_8 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(\"div\", { style: {\"flex\":\"2\"} }, \"垂直阴影:\", -1))\nconst _hoisted_9 = { class: \"row\" }\nconst _hoisted_10 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(\"div\", { style: {\"flex\":\"2\"} }, \"模糊距离:\", -1))\nconst _hoisted_11 = { class: \"row\" }\nconst _hoisted_12 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(\"div\", { style: {\"flex\":\"2\"} }, \"阴影颜色:\", -1))\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _component_Switch = _resolveComponent(\"Switch\")!\n const _component_Slider = _resolveComponent(\"Slider\")!\n const _component_ColorPicker = _resolveComponent(\"ColorPicker\")!\n const _component_ColorButton = _resolveComponent(\"ColorButton\")!\n const _component_Popover = _resolveComponent(\"Popover\")!\n\n return (_openBlock(), _createElementBlock(\"div\", _hoisted_1, [\n _createElementVNode(\"div\", _hoisted_2, [\n _hoisted_3,\n _createElementVNode(\"div\", _hoisted_4, [\n _createVNode(_component_Switch, {\n checked: _ctx.hasShadow,\n onChange: _cache[0] || (_cache[0] = checked => _ctx.toggleShadow(checked))\n }, null, 8, [\"checked\"])\n ])\n ]),\n (_ctx.hasShadow && _ctx.shadow)\n ? (_openBlock(), _createElementBlock(_Fragment, { key: 0 }, [\n _createElementVNode(\"div\", _hoisted_5, [\n _hoisted_6,\n _createVNode(_component_Slider, {\n class: \"slider\",\n min: -10,\n max: 10,\n step: 1,\n value: _ctx.shadow.h,\n onChange: _cache[1] || (_cache[1] = value => _ctx.updateShadow({ h: value }))\n }, null, 8, [\"value\"])\n ]),\n _createElementVNode(\"div\", _hoisted_7, [\n _hoisted_8,\n _createVNode(_component_Slider, {\n class: \"slider\",\n min: -10,\n max: 10,\n step: 1,\n value: _ctx.shadow.v,\n onChange: _cache[2] || (_cache[2] = value => _ctx.updateShadow({ v: value }))\n }, null, 8, [\"value\"])\n ]),\n _createElementVNode(\"div\", _hoisted_9, [\n _hoisted_10,\n _createVNode(_component_Slider, {\n class: \"slider\",\n min: 1,\n max: 20,\n step: 1,\n value: _ctx.shadow.blur,\n onChange: _cache[3] || (_cache[3] = value => _ctx.updateShadow({ blur: value }))\n }, null, 8, [\"value\"])\n ]),\n _createElementVNode(\"div\", _hoisted_11, [\n _hoisted_12,\n _createVNode(_component_Popover, { trigger: \"click\" }, {\n content: _withCtx(() => [\n _createVNode(_component_ColorPicker, {\n modelValue: _ctx.shadow.color,\n \"onUpdate:modelValue\": _cache[4] || (_cache[4] = value => _ctx.updateShadow({ color: value }))\n }, null, 8, [\"modelValue\"])\n ]),\n default: _withCtx(() => [\n _createVNode(_component_ColorButton, {\n color: _ctx.shadow.color,\n style: {\"flex\":\"3\"}\n }, null, 8, [\"color\"])\n ]),\n _: 1\n })\n ])\n ], 64))\n : _createCommentVNode(\"\", true)\n ]))\n}","\nimport { defineComponent, ref, watch } from 'vue'\nimport { storeToRefs } from 'pinia'\nimport { useMainStore, useSlidesStore } from '@/store'\nimport { PPTElementShadow } from '@/types/slides'\nimport useHistorySnapshot from '@/hooks/useHistorySnapshot'\n\nimport ColorButton from './ColorButton.vue'\n\nexport default defineComponent({\n name: 'element-shadow',\n components: {\n ColorButton,\n },\n setup() {\n const slidesStore = useSlidesStore()\n const { handleElement } = storeToRefs(useMainStore())\n\n const shadow = ref()\n const hasShadow = ref(false)\n\n watch(handleElement, () => {\n if (!handleElement.value) return\n shadow.value = 'shadow' in handleElement.value ? handleElement.value.shadow : undefined\n hasShadow.value = !!shadow.value\n }, { deep: true, immediate: true })\n\n const { addHistorySnapshot } = useHistorySnapshot()\n\n const updateShadow = (shadowProps: Partial) => {\n if (!handleElement.value || !shadow.value) return\n const _shadow = { ...shadow.value, ...shadowProps }\n slidesStore.updateElement({ id: handleElement.value.id, props: { shadow: _shadow } })\n addHistorySnapshot()\n }\n\n const toggleShadow = (checked: boolean) => {\n if (!handleElement.value) return\n if (checked) {\n const _shadow: PPTElementShadow = { h: 1, v: 1, blur: 2, color: '#000' }\n slidesStore.updateElement({ id: handleElement.value.id, props: { shadow: _shadow } })\n }\n else {\n slidesStore.removeElementProps({ id: handleElement.value.id, propName: 'shadow' })\n }\n addHistorySnapshot()\n }\n\n return {\n shadow,\n hasShadow,\n toggleShadow,\n updateShadow,\n }\n },\n})\n","import { render } from \"./ElementShadow.vue?vue&type=template&id=321cb564&scoped=true&ts=true\"\nimport script from \"./ElementShadow.vue?vue&type=script&lang=ts\"\nexport * from \"./ElementShadow.vue?vue&type=script&lang=ts\"\n\nimport \"./ElementShadow.vue?vue&type=style&index=0&id=321cb564&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-321cb564\"]])\n\nexport default __exports__","\nimport { defineComponent, ref, watch } from 'vue'\nimport { storeToRefs } from 'pinia'\nimport { useMainStore, useSlidesStore } from '@/store'\nimport { PPTTextElement } from '@/types/slides'\nimport emitter, { EmitterEvents, RichTextAction } from '@/utils/emitter'\nimport { WEB_FONTS } from '@/configs/font'\nimport useHistorySnapshot from '@/hooks/useHistorySnapshot'\nimport { message } from 'ant-design-vue'\n\nimport ElementOpacity from '../common/ElementOpacity.vue'\nimport ElementOutline from '../common/ElementOutline.vue'\nimport ElementShadow from '../common/ElementShadow.vue'\nimport ColorButton from '../common/ColorButton.vue'\n\nconst presetStyles = [\n {\n label: '大标题',\n style: {\n fontSize: '26px',\n fontWeight: 700,\n },\n cmd: [\n { command: 'clear' },\n { command: 'fontsize', value: '48px' },\n { command: 'align', value: 'center' },\n { command: 'bold' },\n ],\n },\n {\n label: '小标题',\n style: {\n fontSize: '22px',\n fontWeight: 700,\n },\n cmd: [\n { command: 'clear' },\n { command: 'fontsize', value: '36px' },\n { command: 'align', value: 'center' },\n { command: 'bold' },\n ],\n },\n {\n label: '正文',\n style: {\n fontSize: '20px',\n },\n cmd: [\n { command: 'clear' },\n { command: 'fontsize', value: '20px' },\n ],\n },\n {\n label: '正文[小]',\n style: {\n fontSize: '18px',\n },\n cmd: [\n { command: 'clear' },\n { command: 'fontsize', value: '18px' },\n ],\n },\n {\n label: '注释 1',\n style: {\n fontSize: '16px',\n fontStyle: 'italic',\n },\n cmd: [\n { command: 'clear' },\n { command: 'fontsize', value: '16px' },\n { command: 'em' },\n ],\n },\n {\n label: '注释 2',\n style: {\n fontSize: '16px',\n textDecoration: 'underline',\n },\n cmd: [\n { command: 'clear' },\n { command: 'fontsize', value: '16px' },\n { command: 'underline' },\n ],\n },\n]\n\nconst webFonts = WEB_FONTS\n\nexport default defineComponent({\n name: 'text-style-panel',\n components: {\n ElementOpacity,\n ElementOutline,\n ElementShadow,\n ColorButton,\n },\n setup() {\n const slidesStore = useSlidesStore()\n const { handleElement, handleElementId, richTextAttrs, availableFonts } = storeToRefs(useMainStore())\n\n const { addHistorySnapshot } = useHistorySnapshot()\n\n const updateElement = (props: Partial) => {\n slidesStore.updateElement({ id: handleElementId.value, props })\n addHistorySnapshot()\n }\n\n const fill = ref()\n const lineHeight = ref()\n const wordSpace = ref()\n\n watch(handleElement, () => {\n if (!handleElement.value || handleElement.value.type !== 'text') return\n\n fill.value = handleElement.value.fill || '#fff'\n lineHeight.value = handleElement.value.lineHeight || 1.5\n wordSpace.value = handleElement.value.wordSpace || 0\n }, { deep: true, immediate: true })\n\n const fontSizeOptions = [\n '12px', '14px', '16px', '18px', '20px', '22px', '24px', '28px', '32px',\n '36px', '40px', '44px', '48px', '54px', '60px', '66px', '72px', '76px',\n '80px', '88px', '96px', '104px', '112px', '120px',\n ]\n const lineHeightOptions = [0.9, 1.0, 1.15, 1.2, 1.4, 1.5, 1.8, 2.0, 2.5, 3.0]\n const wordSpaceOptions = [0, 1, 2, 3, 4, 5, 6, 8, 10]\n\n // 设置行高\n const updateLineHeight = (value: number) => {\n updateElement({ lineHeight: value })\n }\n\n // 设置字间距\n const updateWordSpace = (value: number) => {\n updateElement({ wordSpace: value })\n }\n\n // 设置文本框填充\n const updateFill = (value: string) => {\n updateElement({ fill: value })\n }\n\n // 发射富文本设置命令\n const emitRichTextCommand = (command: string, value?: string) => {\n emitter.emit(EmitterEvents.RICH_TEXT_COMMAND, { action: { command, value } })\n }\n\n // 发射富文本设置命令(批量)\n const emitBatchRichTextCommand = (action: RichTextAction[]) => {\n emitter.emit(EmitterEvents.RICH_TEXT_COMMAND, { action })\n }\n\n // 设置富文本超链接\n const link = ref('')\n const linkPopoverVisible = ref(false)\n\n watch(richTextAttrs, () => linkPopoverVisible.value = false)\n\n const openLinkPopover = () => {\n link.value = richTextAttrs.value.link\n linkPopoverVisible.value = true\n }\n const updateLink = (link: string) => {\n if (link) {\n const linkRegExp = /^(https?):\\/\\/[\\w\\-]+(\\.[\\w\\-]+)+([\\w\\-.,@?^=%&:\\/~+#]*[\\w\\-@?^=%&\\/~+#])?$/\n if (!linkRegExp.test(link)) return message.error('不是正确的网页链接地址')\n }\n emitRichTextCommand('link', link)\n linkPopoverVisible.value = false\n }\n\n return {\n fill,\n lineHeight,\n wordSpace,\n richTextAttrs,\n availableFonts,\n webFonts,\n fontSizeOptions,\n lineHeightOptions,\n wordSpaceOptions,\n updateLineHeight,\n updateWordSpace,\n updateFill,\n emitRichTextCommand,\n emitBatchRichTextCommand,\n presetStyles,\n link,\n linkPopoverVisible,\n openLinkPopover,\n updateLink,\n }\n },\n})\n","import { render } from \"./TextStylePanel.vue?vue&type=template&id=483299d4&scoped=true&ts=true\"\nimport script from \"./TextStylePanel.vue?vue&type=script&lang=ts\"\nexport * from \"./TextStylePanel.vue?vue&type=script&lang=ts\"\n\nimport \"./TextStylePanel.vue?vue&type=style&index=0&id=483299d4&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-483299d4\"]])\n\nexport default __exports__","import { normalizeStyle as _normalizeStyle, createElementVNode as _createElementVNode, resolveComponent as _resolveComponent, createVNode as _createVNode, createTextVNode as _createTextVNode, withCtx as _withCtx, renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, toDisplayString as _toDisplayString, createCommentVNode as _createCommentVNode, createBlock as _createBlock, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-64e5a614\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = { class: \"image-style-panel\" }\nconst _hoisted_2 = /*#__PURE__*/_createTextVNode(\" 裁剪图片\")\nconst _hoisted_3 = { class: \"clip\" }\nconst _hoisted_4 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(\"div\", { class: \"title\" }, \"按形状:\", -1))\nconst _hoisted_5 = { class: \"shape-clip\" }\nconst _hoisted_6 = [\"onClick\"]\nconst _hoisted_7 = {\n key: 0,\n class: \"title\"\n}\nconst _hoisted_8 = /*#__PURE__*/_createTextVNode(\" 替换图片\")\nconst _hoisted_9 = /*#__PURE__*/_createTextVNode(\" 重置样式\")\nconst _hoisted_10 = /*#__PURE__*/_createTextVNode(\" 设为背景\")\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _component_ElementFlip = _resolveComponent(\"ElementFlip\")!\n const _component_IconTailoring = _resolveComponent(\"IconTailoring\")!\n const _component_Button = _resolveComponent(\"Button\")!\n const _component_ButtonGroup = _resolveComponent(\"ButtonGroup\")!\n const _component_IconDown = _resolveComponent(\"IconDown\")!\n const _component_Popover = _resolveComponent(\"Popover\")!\n const _component_Divider = _resolveComponent(\"Divider\")!\n const _component_ElementFilter = _resolveComponent(\"ElementFilter\")!\n const _component_ElementOutline = _resolveComponent(\"ElementOutline\")!\n const _component_ElementShadow = _resolveComponent(\"ElementShadow\")!\n const _component_IconTransform = _resolveComponent(\"IconTransform\")!\n const _component_FileInput = _resolveComponent(\"FileInput\")!\n const _component_IconUndo = _resolveComponent(\"IconUndo\")!\n const _component_IconTheme = _resolveComponent(\"IconTheme\")!\n\n return (_openBlock(), _createElementBlock(\"div\", _hoisted_1, [\n _createElementVNode(\"div\", {\n class: \"origin-image\",\n style: _normalizeStyle({ backgroundImage: `url(${_ctx.handleElement.src})` })\n }, null, 4),\n _createVNode(_component_ElementFlip),\n _createVNode(_component_ButtonGroup, { class: \"row\" }, {\n default: _withCtx(() => [\n _createVNode(_component_Button, {\n style: {\"flex\":\"5\"},\n onClick: _cache[0] || (_cache[0] = ($event: any) => (_ctx.clipImage()))\n }, {\n default: _withCtx(() => [\n _createVNode(_component_IconTailoring, { class: \"btn-icon\" }),\n _hoisted_2\n ]),\n _: 1\n }),\n _createVNode(_component_Popover, {\n trigger: \"click\",\n visible: _ctx.clipPanelVisible,\n \"onUpdate:visible\": _cache[1] || (_cache[1] = ($event: any) => ((_ctx.clipPanelVisible) = $event))\n }, {\n content: _withCtx(() => [\n _createElementVNode(\"div\", _hoisted_3, [\n _hoisted_4,\n _createElementVNode(\"div\", _hoisted_5, [\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.shapeClipPathOptions, (item, key) => {\n return (_openBlock(), _createElementBlock(\"div\", {\n class: \"shape-clip-item\",\n key: key,\n onClick: ($event: any) => (_ctx.presetImageClip(key))\n }, [\n _createElementVNode(\"div\", {\n class: \"shape\",\n style: _normalizeStyle({ clipPath: item.style })\n }, null, 4)\n ], 8, _hoisted_6))\n }), 128))\n ]),\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.ratioClipOptions, (type) => {\n return (_openBlock(), _createElementBlock(_Fragment, {\n key: type.label\n }, [\n (type.label)\n ? (_openBlock(), _createElementBlock(\"div\", _hoisted_7, \"按\" + _toDisplayString(type.label) + \":\", 1))\n : _createCommentVNode(\"\", true),\n _createVNode(_component_ButtonGroup, { class: \"row\" }, {\n default: _withCtx(() => [\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(type.children, (item) => {\n return (_openBlock(), _createBlock(_component_Button, {\n style: {\"flex\":\"1\"},\n key: item.key,\n onClick: ($event: any) => (_ctx.presetImageClip('rect', item.ratio))\n }, {\n default: _withCtx(() => [\n _createTextVNode(_toDisplayString(item.key), 1)\n ]),\n _: 2\n }, 1032, [\"onClick\"]))\n }), 128))\n ]),\n _: 2\n }, 1024)\n ], 64))\n }), 128))\n ])\n ]),\n default: _withCtx(() => [\n _createVNode(_component_Button, {\n class: \"no-padding\",\n style: {\"flex\":\"1\"}\n }, {\n default: _withCtx(() => [\n _createVNode(_component_IconDown)\n ]),\n _: 1\n })\n ]),\n _: 1\n }, 8, [\"visible\"])\n ]),\n _: 1\n }),\n _createVNode(_component_Divider),\n _createVNode(_component_ElementFilter),\n _createVNode(_component_Divider),\n _createVNode(_component_ElementOutline),\n _createVNode(_component_Divider),\n _createVNode(_component_ElementShadow),\n _createVNode(_component_Divider),\n _createVNode(_component_FileInput, {\n onChange: _cache[2] || (_cache[2] = files => _ctx.replaceImage(files))\n }, {\n default: _withCtx(() => [\n _createVNode(_component_Button, { class: \"full-width-btn\" }, {\n default: _withCtx(() => [\n _createVNode(_component_IconTransform, { class: \"btn-icon\" }),\n _hoisted_8\n ]),\n _: 1\n })\n ]),\n _: 1\n }),\n _createVNode(_component_Button, {\n class: \"full-width-btn\",\n onClick: _cache[3] || (_cache[3] = ($event: any) => (_ctx.resetImage()))\n }, {\n default: _withCtx(() => [\n _createVNode(_component_IconUndo, { class: \"btn-icon\" }),\n _hoisted_9\n ]),\n _: 1\n }),\n _createVNode(_component_Button, {\n class: \"full-width-btn\",\n onClick: _cache[4] || (_cache[4] = ($event: any) => (_ctx.setBackgroundImage()))\n }, {\n default: _withCtx(() => [\n _createVNode(_component_IconTheme, { class: \"btn-icon\" }),\n _hoisted_10\n ]),\n _: 1\n })\n ]))\n}","import { resolveComponent as _resolveComponent, createVNode as _createVNode, createTextVNode as _createTextVNode, withCtx as _withCtx, openBlock as _openBlock, createElementBlock as _createElementBlock, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-7bd50f1a\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = { class: \"element-flip\" }\nconst _hoisted_2 = /*#__PURE__*/_createTextVNode(\" 垂直翻转\")\nconst _hoisted_3 = /*#__PURE__*/_createTextVNode(\" 水平翻转\")\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _component_IconFlipVertically = _resolveComponent(\"IconFlipVertically\")!\n const _component_CheckboxButton = _resolveComponent(\"CheckboxButton\")!\n const _component_IconFlipHorizontally = _resolveComponent(\"IconFlipHorizontally\")!\n const _component_CheckboxButtonGroup = _resolveComponent(\"CheckboxButtonGroup\")!\n\n return (_openBlock(), _createElementBlock(\"div\", _hoisted_1, [\n _createVNode(_component_CheckboxButtonGroup, { class: \"row\" }, {\n default: _withCtx(() => [\n _createVNode(_component_CheckboxButton, {\n style: {\"flex\":\"1\"},\n checked: _ctx.flipV,\n onClick: _cache[0] || (_cache[0] = ($event: any) => (_ctx.updateFlip({ flipV: !_ctx.flipV })))\n }, {\n default: _withCtx(() => [\n _createVNode(_component_IconFlipVertically),\n _hoisted_2\n ]),\n _: 1\n }, 8, [\"checked\"]),\n _createVNode(_component_CheckboxButton, {\n style: {\"flex\":\"1\"},\n checked: _ctx.flipH,\n onClick: _cache[1] || (_cache[1] = ($event: any) => (_ctx.updateFlip({ flipH: !_ctx.flipH })))\n }, {\n default: _withCtx(() => [\n _createVNode(_component_IconFlipHorizontally),\n _hoisted_3\n ]),\n _: 1\n }, 8, [\"checked\"])\n ]),\n _: 1\n })\n ]))\n}","\nimport { defineComponent, ref, watch } from 'vue'\nimport { storeToRefs } from 'pinia'\nimport { useMainStore, useSlidesStore } from '@/store'\nimport { ImageOrShapeFlip } from '@/types/slides'\nimport useHistorySnapshot from '@/hooks/useHistorySnapshot'\n\nexport default defineComponent({\n name: 'element-flip',\n setup() {\n const slidesStore = useSlidesStore()\n const { handleElement } = storeToRefs(useMainStore())\n\n const flipH = ref(false)\n const flipV = ref(false)\n\n watch(handleElement, () => {\n if (handleElement.value && (handleElement.value.type === 'image' || handleElement.value.type === 'shape')) {\n flipH.value = !!handleElement.value.flipH\n flipV.value = !!handleElement.value.flipV\n }\n }, { deep: true, immediate: true })\n\n const { addHistorySnapshot } = useHistorySnapshot()\n\n const updateFlip = (flipProps: ImageOrShapeFlip) => {\n if (!handleElement.value) return\n slidesStore.updateElement({ id: handleElement.value.id, props: flipProps })\n addHistorySnapshot()\n }\n\n return {\n flipH,\n flipV,\n updateFlip,\n }\n },\n})\n","import { render } from \"./ElementFlip.vue?vue&type=template&id=7bd50f1a&scoped=true&ts=true\"\nimport script from \"./ElementFlip.vue?vue&type=script&lang=ts\"\nexport * from \"./ElementFlip.vue?vue&type=script&lang=ts\"\n\nimport \"./ElementFlip.vue?vue&type=style&index=0&id=7bd50f1a&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-7bd50f1a\"]])\n\nexport default __exports__","import { createElementVNode as _createElementVNode, resolveComponent as _resolveComponent, createVNode as _createVNode, renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, toDisplayString as _toDisplayString, createCommentVNode as _createCommentVNode, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-95955a74\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = { class: \"element-filter\" }\nconst _hoisted_2 = { class: \"row\" }\nconst _hoisted_3 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(\"div\", { style: {\"flex\":\"2\"} }, \"启用滤镜:\", -1))\nconst _hoisted_4 = {\n class: \"switch-wrapper\",\n style: {\"flex\":\"3\"}\n}\nconst _hoisted_5 = {\n key: 0,\n class: \"filter\"\n}\nconst _hoisted_6 = { class: \"name\" }\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _component_Switch = _resolveComponent(\"Switch\")!\n const _component_Slider = _resolveComponent(\"Slider\")!\n\n return (_openBlock(), _createElementBlock(\"div\", _hoisted_1, [\n _createElementVNode(\"div\", _hoisted_2, [\n _hoisted_3,\n _createElementVNode(\"div\", _hoisted_4, [\n _createVNode(_component_Switch, {\n checked: _ctx.hasFilters,\n onChange: _cache[0] || (_cache[0] = checked => _ctx.toggleFilters(checked))\n }, null, 8, [\"checked\"])\n ])\n ]),\n (_ctx.hasFilters)\n ? (_openBlock(), _createElementBlock(\"div\", _hoisted_5, [\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.filterOptions, (filter) => {\n return (_openBlock(), _createElementBlock(\"div\", {\n class: \"filter-item\",\n key: filter.key\n }, [\n _createElementVNode(\"div\", _hoisted_6, _toDisplayString(filter.label), 1),\n _createVNode(_component_Slider, {\n class: \"filter-slider\",\n max: filter.max,\n min: 0,\n step: filter.step,\n value: filter.value,\n onChange: value => _ctx.updateFilter(filter, value)\n }, null, 8, [\"max\", \"step\", \"value\", \"onChange\"])\n ]))\n }), 128))\n ]))\n : _createCommentVNode(\"\", true)\n ]))\n}","\nimport { defineComponent, ref, watch } from 'vue'\nimport { storeToRefs } from 'pinia'\nimport { useMainStore, useSlidesStore } from '@/store'\nimport { PPTImageElement } from '@/types/slides'\nimport useHistorySnapshot from '@/hooks/useHistorySnapshot'\n\ninterface FilterOption {\n label: string;\n key: string;\n default: number;\n value: number;\n unit: string;\n max: number;\n step: number;\n}\n\nconst defaultFilters: FilterOption[] = [\n { label: '模糊', key: 'blur', default: 0, value: 0, unit: 'px', max: 10, step: 1 },\n { label: '亮度', key: 'brightness', default: 100, value: 100, unit: '%', max: 200, step: 5 },\n { label: '对比度', key: 'contrast', default: 100, value: 100, unit: '%', max: 200, step: 5 },\n { label: '灰度', key: 'grayscale', default: 0, value: 0, unit: '%', max: 100, step: 5 },\n { label: '饱和度', key: 'saturate', default: 100, value: 100, unit: '%', max: 200, step: 5 },\n { label: '色相', key: 'hue-rotate', default: 0, value: 0, unit: 'deg', max: 360, step: 10 },\n { label: '不透明度', key: 'opacity', default: 100, value: 100, unit: '%', max: 100, step: 5 },\n]\n\nexport default defineComponent({\n name: 'element-filter',\n setup() {\n const slidesStore = useSlidesStore()\n const { handleElement, handleElementId } = storeToRefs(useMainStore())\n\n const filterOptions = ref(JSON.parse(JSON.stringify(defaultFilters)))\n const hasFilters = ref(false)\n\n const { addHistorySnapshot } = useHistorySnapshot()\n\n watch(handleElement, () => {\n if (!handleElement.value || handleElement.value.type !== 'image') return\n \n const filters = handleElement.value.filters\n if (filters) {\n filterOptions.value = defaultFilters.map(item => {\n if (filters[item.key] !== undefined) return { ...item, value: parseInt(filters[item.key]) }\n return item\n })\n hasFilters.value = true\n }\n else {\n filterOptions.value = JSON.parse(JSON.stringify(defaultFilters))\n hasFilters.value = false\n }\n }, { deep: true, immediate: true })\n\n // 设置滤镜\n const updateFilter = (filter: FilterOption, value: number) => {\n const _handleElement = handleElement.value as PPTImageElement\n \n const originFilters = _handleElement.filters || {}\n const filters = { ...originFilters, [filter.key]: `${value}${filter.unit}` }\n slidesStore.updateElement({ id: handleElementId.value, props: { filters } })\n addHistorySnapshot()\n }\n\n const toggleFilters = (checked: boolean) => {\n if (!handleElement.value) return\n if (checked) {\n slidesStore.updateElement({ id: handleElement.value.id, props: { filters: {} } })\n }\n else {\n slidesStore.removeElementProps({ id: handleElement.value.id, propName: 'filters' })\n }\n addHistorySnapshot()\n }\n\n return {\n filterOptions,\n hasFilters,\n toggleFilters,\n updateFilter,\n }\n },\n})\n","import { render } from \"./ElementFilter.vue?vue&type=template&id=95955a74&scoped=true&ts=true\"\nimport script from \"./ElementFilter.vue?vue&type=script&lang=ts\"\nexport * from \"./ElementFilter.vue?vue&type=script&lang=ts\"\n\nimport \"./ElementFilter.vue?vue&type=style&index=0&id=95955a74&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-95955a74\"]])\n\nexport default __exports__","\nimport { defineComponent, ref } from 'vue'\nimport { storeToRefs } from 'pinia'\nimport { useMainStore, useSlidesStore } from '@/store'\nimport { PPTImageElement, SlideBackground } from '@/types/slides'\nimport { CLIPPATHS } from '@/configs/imageClip'\nimport { getImageDataURL } from '@/utils/image'\nimport useHistorySnapshot from '@/hooks/useHistorySnapshot'\n\nimport ElementOutline from '../common/ElementOutline.vue'\nimport ElementShadow from '../common/ElementShadow.vue'\nimport ElementFlip from '../common/ElementFlip.vue'\nimport ElementFilter from '../common/ElementFilter.vue'\n\nconst shapeClipPathOptions = CLIPPATHS\nconst ratioClipOptions = [\n {\n label: '纵横比(方形)',\n children: [\n { key: '1:1', ratio: 1 / 1 },\n ],\n },\n {\n label: '纵横比(纵向)',\n children: [\n { key: '2:3', ratio: 3 / 2 },\n { key: '3:4', ratio: 4 / 3 },\n { key: '3:5', ratio: 5 / 3 },\n { key: '4:5', ratio: 5 / 4 },\n ],\n },\n {\n label: '纵横比(横向)',\n children: [\n { key: '3:2', ratio: 2 / 3 },\n { key: '4:3', ratio: 3 / 4 },\n { key: '5:3', ratio: 3 / 5 },\n { key: '5:4', ratio: 4 / 5 },\n ],\n },\n {\n children: [\n { key: '16:9', ratio: 9 / 16 },\n { key: '16:10', ratio: 10 / 16 },\n ],\n },\n]\n\nexport default defineComponent({\n name: 'image-style-panel',\n components: {\n ElementOutline,\n ElementShadow,\n ElementFlip,\n ElementFilter,\n },\n setup() {\n const mainStore = useMainStore()\n const slidesStore = useSlidesStore()\n const { handleElement, handleElementId } = storeToRefs(mainStore)\n const { currentSlide } = storeToRefs(slidesStore)\n\n const clipPanelVisible = ref(false)\n\n const { addHistorySnapshot } = useHistorySnapshot()\n\n // 打开自由裁剪\n const clipImage = () => {\n mainStore.setClipingImageElementId(handleElementId.value)\n clipPanelVisible.value = false\n }\n\n // 获取原始图片的位置大小\n const getImageElementDataBeforeClip = () => {\n const _handleElement = handleElement.value as PPTImageElement\n\n // 图片当前的位置大小和裁剪范围\n const imgWidth = _handleElement.width\n const imgHeight = _handleElement.height\n const imgLeft = _handleElement.left\n const imgTop = _handleElement.top\n const originClipRange: [[number, number], [number, number]] = _handleElement.clip ? _handleElement.clip.range : [[0, 0], [100, 100]]\n\n const originWidth = imgWidth / ((originClipRange[1][0] - originClipRange[0][0]) / 100)\n const originHeight = imgHeight / ((originClipRange[1][1] - originClipRange[0][1]) / 100)\n const originLeft = imgLeft - originWidth * (originClipRange[0][0] / 100)\n const originTop = imgTop - originHeight * (originClipRange[0][1] / 100)\n\n return {\n originClipRange,\n originWidth,\n originHeight,\n originLeft,\n originTop,\n }\n }\n\n // 预设裁剪\n const presetImageClip = (shape: string, ratio = 0) => {\n const _handleElement = handleElement.value as PPTImageElement\n\n const {\n originClipRange,\n originWidth,\n originHeight,\n originLeft,\n originTop,\n } = getImageElementDataBeforeClip()\n \n // 纵横比裁剪(形状固定为矩形)\n if (ratio) {\n const imageRatio = originHeight / originWidth\n\n const min = 0\n const max = 100\n let range: [[number, number], [number, number]]\n\n if (imageRatio > ratio) {\n const distance = ((1 - ratio / imageRatio) / 2) * 100\n range = [[min, distance], [max, max - distance]]\n }\n else {\n const distance = ((1 - imageRatio / ratio) / 2) * 100\n range = [[distance, min], [max - distance, max]]\n }\n slidesStore.updateElement({\n id: handleElementId.value,\n props: {\n clip: { ..._handleElement.clip, shape, range },\n left: originLeft + originWidth * (range[0][0] / 100),\n top: originTop + originHeight * (range[0][1] / 100),\n width: originWidth * (range[1][0] - range[0][0]) / 100,\n height: originHeight * (range[1][1] - range[0][1]) / 100,\n },\n })\n }\n // 形状裁剪(保持当前裁剪范围)\n else {\n slidesStore.updateElement({\n id: handleElementId.value,\n props: {\n clip: { ..._handleElement.clip, shape, range: originClipRange }\n },\n })\n }\n clipImage()\n addHistorySnapshot()\n }\n\n // 替换图片(保持当前的样式)\n const replaceImage = (files: File[]) => {\n const imageFile = files[0]\n if (!imageFile) return\n getImageDataURL(imageFile).then(dataURL => {\n const props = { src: dataURL }\n slidesStore.updateElement({ id: handleElementId.value, props })\n })\n addHistorySnapshot()\n }\n\n // 重置图片:清除全部样式\n const resetImage = () => {\n const _handleElement = handleElement.value as PPTImageElement\n\n if (_handleElement.clip) {\n const {\n originWidth,\n originHeight,\n originLeft,\n originTop,\n } = getImageElementDataBeforeClip()\n\n slidesStore.updateElement({\n id: handleElementId.value,\n props: {\n left: originLeft,\n top: originTop,\n width: originWidth,\n height: originHeight,\n },\n })\n }\n\n slidesStore.removeElementProps({\n id: handleElementId.value,\n propName: ['clip', 'outline', 'flip', 'shadow', 'filters'],\n })\n addHistorySnapshot()\n }\n\n // 将图片设置为背景\n const setBackgroundImage = () => {\n const _handleElement = handleElement.value as PPTImageElement\n\n const background: SlideBackground = {\n ...currentSlide.value.background,\n type: 'image',\n image: _handleElement.src,\n imageSize: 'cover',\n }\n slidesStore.updateSlide({ background })\n addHistorySnapshot()\n }\n\n return {\n clipPanelVisible,\n shapeClipPathOptions,\n ratioClipOptions,\n handleElement,\n clipImage,\n presetImageClip,\n replaceImage,\n resetImage,\n setBackgroundImage,\n }\n },\n})\n","import { render } from \"./ImageStylePanel.vue?vue&type=template&id=64e5a614&scoped=true&ts=true\"\nimport script from \"./ImageStylePanel.vue?vue&type=script&lang=ts\"\nexport * from \"./ImageStylePanel.vue?vue&type=script&lang=ts\"\n\nimport \"./ImageStylePanel.vue?vue&type=style&index=0&id=64e5a614&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-64e5a614\"]])\n\nexport default __exports__","import { createTextVNode as _createTextVNode, resolveComponent as _resolveComponent, withCtx as _withCtx, createVNode as _createVNode, createElementVNode as _createElementVNode, openBlock as _openBlock, createBlock as _createBlock, createCommentVNode as _createCommentVNode, createElementBlock as _createElementBlock, Fragment as _Fragment, renderList as _renderList, toDisplayString as _toDisplayString, normalizeStyle as _normalizeStyle, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-7a3cd02e\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = { class: \"shape-style-panel\" }\nconst _hoisted_2 = { class: \"row\" }\nconst _hoisted_3 = /*#__PURE__*/_createTextVNode(\"纯色填充\")\nconst _hoisted_4 = /*#__PURE__*/_createTextVNode(\"渐变填充\")\nconst _hoisted_5 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(\"div\", { style: {\"flex\":\"1\"} }, null, -1))\nconst _hoisted_6 = /*#__PURE__*/_createTextVNode(\"线性渐变\")\nconst _hoisted_7 = /*#__PURE__*/_createTextVNode(\"径向渐变\")\nconst _hoisted_8 = { class: \"row\" }\nconst _hoisted_9 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(\"div\", { style: {\"flex\":\"2\"} }, \"起点颜色:\", -1))\nconst _hoisted_10 = { class: \"row\" }\nconst _hoisted_11 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(\"div\", { style: {\"flex\":\"2\"} }, \"终点颜色:\", -1))\nconst _hoisted_12 = {\n key: 0,\n class: \"row\"\n}\nconst _hoisted_13 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(\"div\", { style: {\"flex\":\"2\"} }, \"渐变角度:\", -1))\nconst _hoisted_14 = /*#__PURE__*/_createTextVNode(\"+\")\nconst _hoisted_15 = /*#__PURE__*/_createTextVNode(\"-\")\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _component_SelectOption = _resolveComponent(\"SelectOption\")!\n const _component_Select = _resolveComponent(\"Select\")!\n const _component_ColorPicker = _resolveComponent(\"ColorPicker\")!\n const _component_ColorButton = _resolveComponent(\"ColorButton\")!\n const _component_Popover = _resolveComponent(\"Popover\")!\n const _component_Slider = _resolveComponent(\"Slider\")!\n const _component_ElementFlip = _resolveComponent(\"ElementFlip\")!\n const _component_Divider = _resolveComponent(\"Divider\")!\n const _component_IconFontSize = _resolveComponent(\"IconFontSize\")!\n const _component_SelectOptGroup = _resolveComponent(\"SelectOptGroup\")!\n const _component_IconAddText = _resolveComponent(\"IconAddText\")!\n const _component_InputGroup = _resolveComponent(\"InputGroup\")!\n const _component_IconText = _resolveComponent(\"IconText\")!\n const _component_Button = _resolveComponent(\"Button\")!\n const _component_Tooltip = _resolveComponent(\"Tooltip\")!\n const _component_IconHighLight = _resolveComponent(\"IconHighLight\")!\n const _component_ButtonGroup = _resolveComponent(\"ButtonGroup\")!\n const _component_IconTextBold = _resolveComponent(\"IconTextBold\")!\n const _component_CheckboxButton = _resolveComponent(\"CheckboxButton\")!\n const _component_IconTextItalic = _resolveComponent(\"IconTextItalic\")!\n const _component_IconTextUnderline = _resolveComponent(\"IconTextUnderline\")!\n const _component_IconFormat = _resolveComponent(\"IconFormat\")!\n const _component_CheckboxButtonGroup = _resolveComponent(\"CheckboxButtonGroup\")!\n const _component_IconAlignTextLeft = _resolveComponent(\"IconAlignTextLeft\")!\n const _component_RadioButton = _resolveComponent(\"RadioButton\")!\n const _component_IconAlignTextCenter = _resolveComponent(\"IconAlignTextCenter\")!\n const _component_IconAlignTextRight = _resolveComponent(\"IconAlignTextRight\")!\n const _component_RadioGroup = _resolveComponent(\"RadioGroup\")!\n const _component_IconAlignTextTopOne = _resolveComponent(\"IconAlignTextTopOne\")!\n const _component_IconAlignTextMiddleOne = _resolveComponent(\"IconAlignTextMiddleOne\")!\n const _component_IconAlignTextBottomOne = _resolveComponent(\"IconAlignTextBottomOne\")!\n const _component_ElementOutline = _resolveComponent(\"ElementOutline\")!\n const _component_ElementShadow = _resolveComponent(\"ElementShadow\")!\n const _component_ElementOpacity = _resolveComponent(\"ElementOpacity\")!\n\n return (_openBlock(), _createElementBlock(\"div\", _hoisted_1, [\n _createElementVNode(\"div\", _hoisted_2, [\n _createVNode(_component_Select, {\n style: {\"flex\":\"10\"},\n value: _ctx.fillType,\n onChange: _cache[0] || (_cache[0] = value => _ctx.updateFillType(value))\n }, {\n default: _withCtx(() => [\n _createVNode(_component_SelectOption, { value: \"fill\" }, {\n default: _withCtx(() => [\n _hoisted_3\n ]),\n _: 1\n }),\n _createVNode(_component_SelectOption, { value: \"gradient\" }, {\n default: _withCtx(() => [\n _hoisted_4\n ]),\n _: 1\n })\n ]),\n _: 1\n }, 8, [\"value\"]),\n _hoisted_5,\n (_ctx.fillType === 'fill')\n ? (_openBlock(), _createBlock(_component_Popover, {\n key: 0,\n trigger: \"click\"\n }, {\n content: _withCtx(() => [\n _createVNode(_component_ColorPicker, {\n modelValue: _ctx.fill,\n \"onUpdate:modelValue\": _cache[1] || (_cache[1] = value => _ctx.updateFill(value))\n }, null, 8, [\"modelValue\"])\n ]),\n default: _withCtx(() => [\n _createVNode(_component_ColorButton, {\n color: _ctx.fill,\n style: {\"flex\":\"10\"}\n }, null, 8, [\"color\"])\n ]),\n _: 1\n }))\n : (_openBlock(), _createBlock(_component_Select, {\n key: 1,\n style: {\"flex\":\"10\"},\n value: _ctx.gradient.type,\n onChange: _cache[2] || (_cache[2] = value => _ctx.updateGradient({ type: value }))\n }, {\n default: _withCtx(() => [\n _createVNode(_component_SelectOption, { value: \"linear\" }, {\n default: _withCtx(() => [\n _hoisted_6\n ]),\n _: 1\n }),\n _createVNode(_component_SelectOption, { value: \"radial\" }, {\n default: _withCtx(() => [\n _hoisted_7\n ]),\n _: 1\n })\n ]),\n _: 1\n }, 8, [\"value\"]))\n ]),\n (_ctx.fillType === 'gradient')\n ? (_openBlock(), _createElementBlock(_Fragment, { key: 0 }, [\n _createElementVNode(\"div\", _hoisted_8, [\n _hoisted_9,\n _createVNode(_component_Popover, { trigger: \"click\" }, {\n content: _withCtx(() => [\n _createVNode(_component_ColorPicker, {\n modelValue: _ctx.gradient.color[0],\n \"onUpdate:modelValue\": _cache[3] || (_cache[3] = value => _ctx.updateGradient({ color: [value, _ctx.gradient.color[1]] }))\n }, null, 8, [\"modelValue\"])\n ]),\n default: _withCtx(() => [\n _createVNode(_component_ColorButton, {\n color: _ctx.gradient.color[0],\n style: {\"flex\":\"3\"}\n }, null, 8, [\"color\"])\n ]),\n _: 1\n })\n ]),\n _createElementVNode(\"div\", _hoisted_10, [\n _hoisted_11,\n _createVNode(_component_Popover, { trigger: \"click\" }, {\n content: _withCtx(() => [\n _createVNode(_component_ColorPicker, {\n modelValue: _ctx.gradient.color[1],\n \"onUpdate:modelValue\": _cache[4] || (_cache[4] = value => _ctx.updateGradient({ color: [_ctx.gradient.color[0], value] }))\n }, null, 8, [\"modelValue\"])\n ]),\n default: _withCtx(() => [\n _createVNode(_component_ColorButton, {\n color: _ctx.gradient.color[1],\n style: {\"flex\":\"3\"}\n }, null, 8, [\"color\"])\n ]),\n _: 1\n })\n ]),\n (_ctx.gradient.type === 'linear')\n ? (_openBlock(), _createElementBlock(\"div\", _hoisted_12, [\n _hoisted_13,\n _createVNode(_component_Slider, {\n class: \"slider\",\n min: 0,\n max: 360,\n step: 15,\n value: _ctx.gradient.rotate,\n onChange: _cache[5] || (_cache[5] = value => _ctx.updateGradient({ rotate: value }))\n }, null, 8, [\"value\"])\n ]))\n : _createCommentVNode(\"\", true)\n ], 64))\n : _createCommentVNode(\"\", true),\n _createVNode(_component_ElementFlip),\n _createVNode(_component_Divider),\n (_ctx.handleElement?.text?.content)\n ? (_openBlock(), _createElementBlock(_Fragment, { key: 1 }, [\n _createVNode(_component_InputGroup, {\n compact: \"\",\n class: \"row\"\n }, {\n default: _withCtx(() => [\n _createVNode(_component_Select, {\n style: {\"flex\":\"3\"},\n value: _ctx.richTextAttrs.fontname,\n onChange: _cache[6] || (_cache[6] = value => _ctx.emitRichTextCommand('fontname', value))\n }, {\n suffixIcon: _withCtx(() => [\n _createVNode(_component_IconFontSize)\n ]),\n default: _withCtx(() => [\n _createVNode(_component_SelectOptGroup, { label: \"系统字体\" }, {\n default: _withCtx(() => [\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.availableFonts, (font) => {\n return (_openBlock(), _createBlock(_component_SelectOption, {\n key: font.value,\n value: font.value\n }, {\n default: _withCtx(() => [\n _createElementVNode(\"span\", {\n style: _normalizeStyle({ fontFamily: font.value })\n }, _toDisplayString(font.label), 5)\n ]),\n _: 2\n }, 1032, [\"value\"]))\n }), 128))\n ]),\n _: 1\n }),\n _createVNode(_component_SelectOptGroup, { label: \"在线字体\" }, {\n default: _withCtx(() => [\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.webFonts, (font) => {\n return (_openBlock(), _createBlock(_component_SelectOption, {\n key: font.value,\n value: font.value\n }, {\n default: _withCtx(() => [\n _createElementVNode(\"span\", null, _toDisplayString(font.label), 1)\n ]),\n _: 2\n }, 1032, [\"value\"]))\n }), 128))\n ]),\n _: 1\n })\n ]),\n _: 1\n }, 8, [\"value\"]),\n _createVNode(_component_Select, {\n style: {\"flex\":\"2\"},\n value: _ctx.richTextAttrs.fontsize,\n onChange: _cache[7] || (_cache[7] = value => _ctx.emitRichTextCommand('fontsize', value))\n }, {\n suffixIcon: _withCtx(() => [\n _createVNode(_component_IconAddText)\n ]),\n default: _withCtx(() => [\n (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.fontSizeOptions, (fontsize) => {\n return (_openBlock(), _createBlock(_component_SelectOption, {\n key: fontsize,\n value: fontsize\n }, {\n default: _withCtx(() => [\n _createTextVNode(_toDisplayString(fontsize), 1)\n ]),\n _: 2\n }, 1032, [\"value\"]))\n }), 128))\n ]),\n _: 1\n }, 8, [\"value\"])\n ]),\n _: 1\n }),\n _createVNode(_component_ButtonGroup, { class: \"row\" }, {\n default: _withCtx(() => [\n _createVNode(_component_Popover, { trigger: \"click\" }, {\n content: _withCtx(() => [\n _createVNode(_component_ColorPicker, {\n modelValue: _ctx.richTextAttrs.color,\n \"onUpdate:modelValue\": _cache[8] || (_cache[8] = value => _ctx.emitRichTextCommand('color', value))\n }, null, 8, [\"modelValue\"])\n ]),\n default: _withCtx(() => [\n _createVNode(_component_Tooltip, {\n mouseLeaveDelay: 0,\n mouseEnterDelay: 0.5,\n title: \"文字颜色\"\n }, {\n default: _withCtx(() => [\n _createVNode(_component_Button, {\n class: \"text-color-btn\",\n style: {\"flex\":\"3\"}\n }, {\n default: _withCtx(() => [\n _createVNode(_component_IconText),\n _createElementVNode(\"div\", {\n class: \"text-color-block\",\n style: _normalizeStyle({ backgroundColor: _ctx.richTextAttrs.color })\n }, null, 4)\n ]),\n _: 1\n })\n ]),\n _: 1\n }, 8, [\"mouseEnterDelay\"])\n ]),\n _: 1\n }),\n _createVNode(_component_Popover, { trigger: \"click\" }, {\n content: _withCtx(() => [\n _createVNode(_component_ColorPicker, {\n modelValue: _ctx.richTextAttrs.backcolor,\n \"onUpdate:modelValue\": _cache[9] || (_cache[9] = value => _ctx.emitRichTextCommand('backcolor', value))\n }, null, 8, [\"modelValue\"])\n ]),\n default: _withCtx(() => [\n _createVNode(_component_Tooltip, {\n mouseLeaveDelay: 0,\n mouseEnterDelay: 0.5,\n title: \"文字高亮\"\n }, {\n default: _withCtx(() => [\n _createVNode(_component_Button, {\n class: \"text-color-btn\",\n style: {\"flex\":\"3\"}\n }, {\n default: _withCtx(() => [\n _createVNode(_component_IconHighLight),\n _createElementVNode(\"div\", {\n class: \"text-color-block\",\n style: _normalizeStyle({ backgroundColor: _ctx.richTextAttrs.backcolor })\n }, null, 4)\n ]),\n _: 1\n })\n ]),\n _: 1\n }, 8, [\"mouseEnterDelay\"])\n ]),\n _: 1\n }),\n _createVNode(_component_Tooltip, {\n mouseLeaveDelay: 0,\n mouseEnterDelay: 0.5,\n title: \"增大字号\"\n }, {\n default: _withCtx(() => [\n _createVNode(_component_Button, {\n class: \"font-size-btn\",\n style: {\"flex\":\"2\"},\n onClick: _cache[10] || (_cache[10] = ($event: any) => (_ctx.emitRichTextCommand('fontsize-add')))\n }, {\n default: _withCtx(() => [\n _createVNode(_component_IconFontSize),\n _hoisted_14\n ]),\n _: 1\n })\n ]),\n _: 1\n }, 8, [\"mouseEnterDelay\"]),\n _createVNode(_component_Tooltip, {\n mouseLeaveDelay: 0,\n mouseEnterDelay: 0.5,\n title: \"减小字号\"\n }, {\n default: _withCtx(() => [\n _createVNode(_component_Button, {\n class: \"font-size-btn\",\n style: {\"flex\":\"2\"},\n onClick: _cache[11] || (_cache[11] = ($event: any) => (_ctx.emitRichTextCommand('fontsize-reduce')))\n }, {\n default: _withCtx(() => [\n _createVNode(_component_IconFontSize),\n _hoisted_15\n ]),\n _: 1\n })\n ]),\n _: 1\n }, 8, [\"mouseEnterDelay\"])\n ]),\n _: 1\n }),\n _createVNode(_component_CheckboxButtonGroup, { class: \"row\" }, {\n default: _withCtx(() => [\n _createVNode(_component_Tooltip, {\n mouseLeaveDelay: 0,\n mouseEnterDelay: 0.5,\n title: \"加粗\"\n }, {\n default: _withCtx(() => [\n _createVNode(_component_CheckboxButton, {\n style: {\"flex\":\"1\"},\n checked: _ctx.richTextAttrs.bold,\n onClick: _cache[12] || (_cache[12] = ($event: any) => (_ctx.emitRichTextCommand('bold')))\n }, {\n default: _withCtx(() => [\n _createVNode(_component_IconTextBold)\n ]),\n _: 1\n }, 8, [\"checked\"])\n ]),\n _: 1\n }, 8, [\"mouseEnterDelay\"]),\n _createVNode(_component_Tooltip, {\n mouseLeaveDelay: 0,\n mouseEnterDelay: 0.5,\n title: \"斜体\"\n }, {\n default: _withCtx(() => [\n _createVNode(_component_CheckboxButton, {\n style: {\"flex\":\"1\"},\n checked: _ctx.richTextAttrs.em,\n onClick: _cache[13] || (_cache[13] = ($event: any) => (_ctx.emitRichTextCommand('em')))\n }, {\n default: _withCtx(() => [\n _createVNode(_component_IconTextItalic)\n ]),\n _: 1\n }, 8, [\"checked\"])\n ]),\n _: 1\n }, 8, [\"mouseEnterDelay\"]),\n _createVNode(_component_Tooltip, {\n mouseLeaveDelay: 0,\n mouseEnterDelay: 0.5,\n title: \"下划线\"\n }, {\n default: _withCtx(() => [\n _createVNode(_component_CheckboxButton, {\n style: {\"flex\":\"1\"},\n checked: _ctx.richTextAttrs.underline,\n onClick: _cache[14] || (_cache[14] = ($event: any) => (_ctx.emitRichTextCommand('underline')))\n }, {\n default: _withCtx(() => [\n _createVNode(_component_IconTextUnderline)\n ]),\n _: 1\n }, 8, [\"checked\"])\n ]),\n _: 1\n }, 8, [\"mouseEnterDelay\"]),\n _createVNode(_component_Tooltip, {\n mouseLeaveDelay: 0,\n mouseEnterDelay: 0.5,\n title: \"清除格式\"\n }, {\n default: _withCtx(() => [\n _createVNode(_component_CheckboxButton, {\n style: {\"flex\":\"1\"},\n onClick: _cache[15] || (_cache[15] = ($event: any) => (_ctx.emitRichTextCommand('clear')))\n }, {\n default: _withCtx(() => [\n _createVNode(_component_IconFormat)\n ]),\n _: 1\n })\n ]),\n _: 1\n }, 8, [\"mouseEnterDelay\"])\n ]),\n _: 1\n }),\n _createVNode(_component_RadioGroup, {\n class: \"row\",\n \"button-style\": \"solid\",\n value: _ctx.richTextAttrs.align,\n onChange: _cache[16] || (_cache[16] = e => _ctx.emitRichTextCommand('align', e.target.value))\n }, {\n default: _withCtx(() => [\n _createVNode(_component_Tooltip, {\n mouseLeaveDelay: 0,\n mouseEnterDelay: 0.5,\n title: \"左对齐\"\n }, {\n default: _withCtx(() => [\n _createVNode(_component_RadioButton, {\n value: \"left\",\n style: {\"flex\":\"1\"}\n }, {\n default: _withCtx(() => [\n _createVNode(_component_IconAlignTextLeft)\n ]),\n _: 1\n })\n ]),\n _: 1\n }, 8, [\"mouseEnterDelay\"]),\n _createVNode(_component_Tooltip, {\n mouseLeaveDelay: 0,\n mouseEnterDelay: 0.5,\n title: \"居中\"\n }, {\n default: _withCtx(() => [\n _createVNode(_component_RadioButton, {\n value: \"center\",\n style: {\"flex\":\"1\"}\n }, {\n default: _withCtx(() => [\n _createVNode(_component_IconAlignTextCenter)\n ]),\n _: 1\n })\n ]),\n _: 1\n }, 8, [\"mouseEnterDelay\"]),\n _createVNode(_component_Tooltip, {\n mouseLeaveDelay: 0,\n mouseEnterDelay: 0.5,\n title: \"右对齐\"\n }, {\n default: _withCtx(() => [\n _createVNode(_component_RadioButton, {\n value: \"right\",\n style: {\"flex\":\"1\"}\n }, {\n default: _withCtx(() => [\n _createVNode(_component_IconAlignTextRight)\n ]),\n _: 1\n })\n ]),\n _: 1\n }, 8, [\"mouseEnterDelay\"])\n ]),\n _: 1\n }, 8, [\"value\"]),\n _createVNode(_component_RadioGroup, {\n class: \"row\",\n \"button-style\": \"solid\",\n value: _ctx.textAlign,\n onChange: _cache[17] || (_cache[17] = e => _ctx.updateTextAlign(e.target.value))\n }, {\n default: _withCtx(() => [\n _createVNode(_component_Tooltip, {\n mouseLeaveDelay: 0,\n mouseEnterDelay: 0.5,\n title: \"顶对齐\"\n }, {\n default: _withCtx(() => [\n _createVNode(_component_RadioButton, {\n value: \"top\",\n style: {\"flex\":\"1\"}\n }, {\n default: _withCtx(() => [\n _createVNode(_component_IconAlignTextTopOne)\n ]),\n _: 1\n })\n ]),\n _: 1\n }, 8, [\"mouseEnterDelay\"]),\n _createVNode(_component_Tooltip, {\n mouseLeaveDelay: 0,\n mouseEnterDelay: 0.5,\n title: \"居中\"\n }, {\n default: _withCtx(() => [\n _createVNode(_component_RadioButton, {\n value: \"middle\",\n style: {\"flex\":\"1\"}\n }, {\n default: _withCtx(() => [\n _createVNode(_component_IconAlignTextMiddleOne)\n ]),\n _: 1\n })\n ]),\n _: 1\n }, 8, [\"mouseEnterDelay\"]),\n _createVNode(_component_Tooltip, {\n mouseLeaveDelay: 0,\n mouseEnterDelay: 0.5,\n title: \"底对齐\"\n }, {\n default: _withCtx(() => [\n _createVNode(_component_RadioButton, {\n value: \"bottom\",\n style: {\"flex\":\"1\"}\n }, {\n default: _withCtx(() => [\n _createVNode(_component_IconAlignTextBottomOne)\n ]),\n _: 1\n })\n ]),\n _: 1\n }, 8, [\"mouseEnterDelay\"])\n ]),\n _: 1\n }, 8, [\"value\"]),\n _createVNode(_component_Divider)\n ], 64))\n : _createCommentVNode(\"\", true),\n _createVNode(_component_ElementOutline),\n _createVNode(_component_Divider),\n _createVNode(_component_ElementShadow),\n _createVNode(_component_Divider),\n _createVNode(_component_ElementOpacity)\n ]))\n}","\nimport { defineComponent, ref, watch } from 'vue'\nimport { storeToRefs } from 'pinia'\nimport { useMainStore, useSlidesStore } from '@/store'\nimport { PPTShapeElement, ShapeGradient, ShapeText } from '@/types/slides'\nimport { WEB_FONTS } from '@/configs/font'\nimport emitter, { EmitterEvents } from '@/utils/emitter'\nimport useHistorySnapshot from '@/hooks/useHistorySnapshot'\n\nimport ElementOpacity from '../common/ElementOpacity.vue'\nimport ElementOutline from '../common/ElementOutline.vue'\nimport ElementShadow from '../common/ElementShadow.vue'\nimport ElementFlip from '../common/ElementFlip.vue'\nimport ColorButton from '../common/ColorButton.vue'\n\nconst webFonts = WEB_FONTS\n\nexport default defineComponent({\n name: 'shape-style-panel',\n components: {\n ElementOpacity,\n ElementOutline,\n ElementShadow,\n ElementFlip,\n ColorButton,\n },\n setup() {\n const mainStore = useMainStore()\n const slidesStore = useSlidesStore()\n const { handleElement, handleElementId, richTextAttrs, availableFonts } = storeToRefs(mainStore)\n\n const fill = ref()\n const gradient = ref()\n const fillType = ref('fill')\n const textAlign = ref('middle')\n\n watch(handleElement, () => {\n if (!handleElement.value || handleElement.value.type !== 'shape') return\n\n fill.value = handleElement.value.fill || '#fff'\n gradient.value = handleElement.value.gradient || { type: 'linear', rotate: 0, color: [fill.value, '#fff'] }\n fillType.value = handleElement.value.gradient ? 'gradient' : 'fill'\n textAlign.value = handleElement.value?.text?.align || 'middle'\n }, { deep: true, immediate: true })\n\n const { addHistorySnapshot } = useHistorySnapshot()\n\n const updateElement = (props: Partial) => {\n slidesStore.updateElement({ id: handleElementId.value, props })\n addHistorySnapshot()\n }\n\n // 设置填充类型:渐变、纯色\n const updateFillType = (type: 'gradient' | 'fill') => {\n if (type === 'fill') {\n slidesStore.removeElementProps({ id: handleElementId.value, propName: 'gradient' })\n addHistorySnapshot()\n }\n else updateElement({ gradient: gradient.value })\n }\n\n // 设置渐变填充\n const updateGradient = (gradientProps: Partial) => {\n if (!gradient.value) return\n const _gradient: ShapeGradient = { ...gradient.value, ...gradientProps }\n updateElement({ gradient: _gradient })\n }\n\n // 设置填充色\n const updateFill = (value: string) => {\n updateElement({ fill: value })\n }\n\n const updateTextAlign = (align: 'top' | 'middle' | 'bottom') => {\n const _handleElement = handleElement.value as PPTShapeElement\n \n const defaultText: ShapeText = {\n content: '',\n defaultFontName: '微软雅黑',\n defaultColor: '#000',\n align: 'middle',\n }\n const _text = _handleElement.text || defaultText\n updateElement({ text: { ..._text, align } })\n }\n\n const fontSizeOptions = [\n '12px', '14px', '16px', '18px', '20px', '22px', '24px', '28px', '32px',\n '36px', '40px', '44px', '48px', '54px', '60px', '66px', '72px', '76px',\n '80px', '88px', '96px', '104px', '112px', '120px',\n ]\n\n const emitRichTextCommand = (command: string, value?: string) => {\n emitter.emit(EmitterEvents.RICH_TEXT_COMMAND, { action: { command, value } })\n }\n\n return {\n fill,\n gradient,\n fillType,\n textAlign,\n richTextAttrs,\n availableFonts,\n fontSizeOptions,\n webFonts,\n handleElement,\n emitRichTextCommand,\n updateFillType,\n updateFill,\n updateGradient,\n updateTextAlign,\n }\n },\n})\n","import { render } from \"./ShapeStylePanel.vue?vue&type=template&id=7a3cd02e&scoped=true&ts=true\"\nimport script from \"./ShapeStylePanel.vue?vue&type=script&lang=ts\"\nexport * from \"./ShapeStylePanel.vue?vue&type=script&lang=ts\"\n\nimport \"./ShapeStylePanel.vue?vue&type=style&index=0&id=7a3cd02e&lang=scss&scoped=true\"\n\nimport exportComponent from \"/home/runner/work/PPTist/PPTist/node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render],['__scopeId',\"data-v-7a3cd02e\"]])\n\nexport default __exports__","import { createElementVNode as _createElementVNode, createTextVNode as _createTextVNode, resolveComponent as _resolveComponent, withCtx as _withCtx, createVNode as _createVNode, openBlock as _openBlock, createElementBlock as _createElementBlock, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-cbc4d7fa\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = { class: \"line-style-panel\" }\nconst _hoisted_2 = { class: \"row\" }\nconst _hoisted_3 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(\"div\", { style: {\"flex\":\"2\"} }, \"线条样式:\", -1))\nconst _hoisted_4 = /*#__PURE__*/_createTextVNode(\"实线\")\nconst _hoisted_5 = /*#__PURE__*/_createTextVNode(\"虚线\")\nconst _hoisted_6 = { class: \"row\" }\nconst _hoisted_7 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(\"div\", { style: {\"flex\":\"2\"} }, \"线条颜色:\", -1))\nconst _hoisted_8 = { class: \"row\" }\nconst _hoisted_9 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(\"div\", { style: {\"flex\":\"2\"} }, \"线条宽度:\", -1))\nconst _hoisted_10 = { class: \"row\" }\nconst _hoisted_11 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(\"div\", { style: {\"flex\":\"2\"} }, \"起点样式:\", -1))\nconst _hoisted_12 = /*#__PURE__*/_createTextVNode(\"无\")\nconst _hoisted_13 = /*#__PURE__*/_createTextVNode(\"箭头\")\nconst _hoisted_14 = /*#__PURE__*/_createTextVNode(\"圆点\")\nconst _hoisted_15 = { class: \"row\" }\nconst _hoisted_16 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(\"div\", { style: {\"flex\":\"2\"} }, \"终点样式:\", -1))\nconst _hoisted_17 = /*#__PURE__*/_createTextVNode(\"无\")\nconst _hoisted_18 = /*#__PURE__*/_createTextVNode(\"箭头\")\nconst _hoisted_19 = /*#__PURE__*/_createTextVNode(\"圆点\")\n\nexport function render(_ctx: any,_cache: any,$props: any,$setup: any,$data: any,$options: any) {\n const _component_SelectOption = _resolveComponent(\"SelectOption\")!\n const _component_Select = _resolveComponent(\"Select\")!\n const _component_ColorPicker = _resolveComponent(\"ColorPicker\")!\n const _component_ColorButton = _resolveComponent(\"ColorButton\")!\n const _component_Popover = _resolveComponent(\"Popover\")!\n const _component_InputNumber = _resolveComponent(\"InputNumber\")!\n const _component_Divider = _resolveComponent(\"Divider\")!\n const _component_ElementShadow = _resolveComponent(\"ElementShadow\")!\n\n return (_openBlock(), _createElementBlock(\"div\", _hoisted_1, [\n _createElementVNode(\"div\", _hoisted_2, [\n _hoisted_3,\n _createVNode(_component_Select, {\n style: {\"flex\":\"3\"},\n value: _ctx.handleElement.style,\n onChange: _cache[0] || (_cache[0] = value => _ctx.updateLine({ style: value }))\n }, {\n default: _withCtx(() => [\n _createVNode(_component_SelectOption, { value: \"solid\" }, {\n default: _withCtx(() => [\n _hoisted_4\n ]),\n _: 1\n }),\n _createVNode(_component_SelectOption, { value: \"dashed\" }, {\n default: _withCtx(() => [\n _hoisted_5\n ]),\n _: 1\n })\n ]),\n _: 1\n }, 8, [\"value\"])\n ]),\n _createElementVNode(\"div\", _hoisted_6, [\n _hoisted_7,\n _createVNode(_component_Popover, { trigger: \"click\" }, {\n content: _withCtx(() => [\n _createVNode(_component_ColorPicker, {\n modelValue: _ctx.handleElement.color,\n \"onUpdate:modelValue\": _cache[1] || (_cache[1] = value => _ctx.updateLine({ color: value }))\n }, null, 8, [\"modelValue\"])\n ]),\n default: _withCtx(() => [\n _createVNode(_component_ColorButton, {\n color: _ctx.handleElement.color,\n style: {\"flex\":\"3\"}\n }, null, 8, [\"color\"])\n ]),\n _: 1\n })\n ]),\n _createElementVNode(\"div\", _hoisted_8, [\n _hoisted_9,\n _createVNode(_component_InputNumber, {\n value: _ctx.handleElement.width,\n onChange: _cache[2] || (_cache[2] = value => _ctx.updateLine({ width: value })),\n style: {\"flex\":\"3\"}\n }, null, 8, [\"value\"])\n ]),\n _createElementVNode(\"div\", _hoisted_10, [\n _hoisted_11,\n _createVNode(_component_Select, {\n style: {\"flex\":\"3\"},\n value: _ctx.handleElement.points[0],\n onChange: _cache[3] || (_cache[3] = value => _ctx.updateLine({ points: [value, _ctx.handleElement.points[1]] }))\n }, {\n default: _withCtx(() => [\n _createVNode(_component_SelectOption, { value: \"\" }, {\n default: _withCtx(() => [\n _hoisted_12\n ]),\n _: 1\n }),\n _createVNode(_component_SelectOption, { value: \"arrow\" }, {\n default: _withCtx(() => [\n _hoisted_13\n ]),\n _: 1\n }),\n _createVNode(_component_SelectOption, { value: \"dot\" }, {\n default: _withCtx(() => [\n _hoisted_14\n ]),\n _: 1\n })\n ]),\n _: 1\n }, 8, [\"value\"])\n ]),\n _createElementVNode(\"div\", _hoisted_15, [\n _hoisted_16,\n _createVNode(_component_Select, {\n style: {\"flex\":\"3\"},\n value: _ctx.handleElement.points[1],\n onChange: _cache[4] || (_cache[4] = value => _ctx.updateLine({ points: [_ctx.handleElement.points[0], value] }))\n }, {\n default: _withCtx(() => [\n _createVNode(_component_SelectOption, { value: \"\" }, {\n default: _withCtx(() => [\n _hoisted_17\n ]),\n _: 1\n }),\n _createVNode(_component_SelectOption, { value: \"arrow\" }, {\n default: _withCtx(() => [\n _hoisted_18\n ]),\n _: 1\n }),\n _createVNode(_component_SelectOption, { value: \"dot\" }, {\n default: _withCtx(() => [\n _hoisted_19\n ]),\n _: 1\n })\n ]),\n _: 1\n }, 8, [\"value\"])\n ]),\n _createVNode(_component_Divider),\n _createVNode(_component_ElementShadow)\n ]))\n}","\nimport { defineComponent } from 'vue'\nimport { storeToRefs } from 'pinia'\nimport { useMainStore, useSlidesStore } from '@/store'\nimport { PPTLineElement } from '@/types/slides'\nimport useHistorySnapshot from '@/hooks/useHistorySnapshot'\n\nimport ElementShadow from '../common/ElementShadow.vue'\nimport ColorButton from '../common/ColorButton.vue'\n\nexport default defineComponent({\n name: 'line-style-panel',\n components: {\n ElementShadow,\n ColorButton,\n },\n setup() {\n const slidesStore = useSlidesStore()\n const { handleElement } = storeToRefs(useMainStore())\n\n const { addHistorySnapshot } = useHistorySnapshot()\n\n const updateLine = (props: Partial