useImport.ts 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813
  1. import { ref } from 'vue'
  2. import { storeToRefs } from 'pinia'
  3. import { parse, type Shape, type Element, type ChartItem, type BaseElement } from 'pptxtojson'
  4. import { nanoid } from 'nanoid'
  5. import { useSlidesStore } from '@/store'
  6. import { decrypt } from '@/utils/crypto'
  7. import { type ShapePoolItem, SHAPE_LIST, SHAPE_PATH_FORMULAS } from '@/configs/shapes'
  8. import useAddSlidesOrElements from '@/hooks/useAddSlidesOrElements'
  9. import useSlideHandler from '@/hooks/useSlideHandler'
  10. import useHistorySnapshot from './useHistorySnapshot'
  11. import message from '@/utils/message'
  12. import { getSvgPathRange } from '@/utils/svgPathParser'
  13. import type {
  14. Slide,
  15. TableCellStyle,
  16. TableCell,
  17. ChartType,
  18. SlideBackground,
  19. PPTShapeElement,
  20. PPTLineElement,
  21. PPTImageElement,
  22. ShapeTextAlign,
  23. PPTTextElement,
  24. ChartOptions,
  25. Gradient,
  26. } from '@/types/slides'
  27. const convertFontSizePtToPx = (html: string, ratio: number) => {
  28. return html.replace(/font-size:\s*([\d.]+)pt/g, (match, p1) => {
  29. return `font-size: ${(parseFloat(p1) * ratio).toFixed(1)}px`
  30. })
  31. }
  32. export default () => {
  33. const slidesStore = useSlidesStore()
  34. const { theme } = storeToRefs(useSlidesStore())
  35. const { addHistorySnapshot } = useHistorySnapshot()
  36. const { addSlidesFromData } = useAddSlidesOrElements()
  37. const { isEmptySlide } = useSlideHandler()
  38. const exporting = ref(false)
  39. // 导入JSON文件
  40. const importJSON = (files: FileList, cover = false) => {
  41. const file = files[0]
  42. const reader = new FileReader()
  43. reader.addEventListener('load', () => {
  44. try {
  45. const { slides } = JSON.parse(reader.result as string)
  46. if (cover) {
  47. slidesStore.updateSlideIndex(0)
  48. slidesStore.setSlides(slides)
  49. addHistorySnapshot()
  50. }
  51. else if (isEmptySlide.value) {
  52. slidesStore.setSlides(slides)
  53. addHistorySnapshot()
  54. }
  55. else addSlidesFromData(slides)
  56. }
  57. catch {
  58. message.error('无法正确读取 / 解析该文件')
  59. }
  60. })
  61. reader.readAsText(file)
  62. }
  63. // 直接读取JSON功能,暴露到window.readJSON
  64. const readJSON = (jsonData: string | any, cover = false) => {
  65. try {
  66. let slides
  67. if (typeof jsonData === 'string') {
  68. const parsed = JSON.parse(jsonData)
  69. slides = parsed.slides || parsed
  70. }
  71. else {
  72. slides = jsonData.slides || jsonData
  73. }
  74. if (cover) {
  75. slidesStore.updateSlideIndex(0)
  76. slidesStore.setSlides(slides)
  77. addHistorySnapshot()
  78. }
  79. else if (isEmptySlide.value) {
  80. slidesStore.setSlides(slides)
  81. addHistorySnapshot()
  82. }
  83. else {
  84. addSlidesFromData(slides)
  85. }
  86. return { success: true, slides }
  87. }
  88. catch (error) {
  89. const errorMsg = '无法正确读取 / 解析该JSON数据'
  90. message.error(errorMsg)
  91. return { success: false, error: errorMsg, details: error }
  92. }
  93. }
  94. // 暴露到window对象
  95. if (typeof window !== 'undefined') {
  96. (window as any).readJSON = readJSON
  97. }
  98. // 导入pptist文件
  99. const importSpecificFile = (files: FileList, cover = false) => {
  100. const file = files[0]
  101. const reader = new FileReader()
  102. reader.addEventListener('load', () => {
  103. try {
  104. const { slides } = JSON.parse(decrypt(reader.result as string))
  105. if (cover) {
  106. slidesStore.updateSlideIndex(0)
  107. slidesStore.setSlides(slides)
  108. addHistorySnapshot()
  109. }
  110. else if (isEmptySlide.value) {
  111. slidesStore.setSlides(slides)
  112. addHistorySnapshot()
  113. }
  114. else addSlidesFromData(slides)
  115. }
  116. catch {
  117. message.error('无法正确读取 / 解析该文件')
  118. }
  119. })
  120. reader.readAsText(file)
  121. }
  122. const rotateLine = (line: PPTLineElement, angleDeg: number) => {
  123. const { start, end } = line
  124. const angleRad = angleDeg * Math.PI / 180
  125. const midX = (start[0] + end[0]) / 2
  126. const midY = (start[1] + end[1]) / 2
  127. const startTransX = start[0] - midX
  128. const startTransY = start[1] - midY
  129. const endTransX = end[0] - midX
  130. const endTransY = end[1] - midY
  131. const cosA = Math.cos(angleRad)
  132. const sinA = Math.sin(angleRad)
  133. const startRotX = startTransX * cosA - startTransY * sinA
  134. const startRotY = startTransX * sinA + startTransY * cosA
  135. const endRotX = endTransX * cosA - endTransY * sinA
  136. const endRotY = endTransX * sinA + endTransY * cosA
  137. const startNewX = startRotX + midX
  138. const startNewY = startRotY + midY
  139. const endNewX = endRotX + midX
  140. const endNewY = endRotY + midY
  141. const beforeMinX = Math.min(start[0], end[0])
  142. const beforeMinY = Math.min(start[1], end[1])
  143. const afterMinX = Math.min(startNewX, endNewX)
  144. const afterMinY = Math.min(startNewY, endNewY)
  145. const startAdjustedX = startNewX - afterMinX
  146. const startAdjustedY = startNewY - afterMinY
  147. const endAdjustedX = endNewX - afterMinX
  148. const endAdjustedY = endNewY - afterMinY
  149. const startAdjusted: [number, number] = [startAdjustedX, startAdjustedY]
  150. const endAdjusted: [number, number] = [endAdjustedX, endAdjustedY]
  151. const offset = [afterMinX - beforeMinX, afterMinY - beforeMinY]
  152. return {
  153. start: startAdjusted,
  154. end: endAdjusted,
  155. offset,
  156. }
  157. }
  158. const parseLineElement = (el: Shape, ratio: number) => {
  159. let start: [number, number] = [0, 0]
  160. let end: [number, number] = [0, 0]
  161. if (!el.isFlipV && !el.isFlipH) { // 右下
  162. start = [0, 0]
  163. end = [el.width, el.height]
  164. }
  165. else if (el.isFlipV && el.isFlipH) { // 左上
  166. start = [el.width, el.height]
  167. end = [0, 0]
  168. }
  169. else if (el.isFlipV && !el.isFlipH) { // 右上
  170. start = [0, el.height]
  171. end = [el.width, 0]
  172. }
  173. else { // 左下
  174. start = [el.width, 0]
  175. end = [0, el.height]
  176. }
  177. const data: PPTLineElement = {
  178. type: 'line',
  179. id: nanoid(10),
  180. width: +((el.borderWidth || 1) * ratio).toFixed(2),
  181. left: el.left,
  182. top: el.top,
  183. start,
  184. end,
  185. style: el.borderType,
  186. color: el.borderColor,
  187. points: ['', /straightConnector/.test(el.shapType) ? 'arrow' : '']
  188. }
  189. if (el.rotate) {
  190. const { start, end, offset } = rotateLine(data, el.rotate)
  191. data.start = start
  192. data.end = end
  193. data.left = data.left + offset[0]
  194. data.top = data.top + offset[1]
  195. }
  196. if (/bentConnector/.test(el.shapType)) {
  197. data.broken2 = [
  198. Math.abs(data.start[0] - data.end[0]) / 2,
  199. Math.abs(data.start[1] - data.end[1]) / 2,
  200. ]
  201. }
  202. if (/curvedConnector/.test(el.shapType)) {
  203. const cubic: [number, number] = [
  204. Math.abs(data.start[0] - data.end[0]) / 2,
  205. Math.abs(data.start[1] - data.end[1]) / 2,
  206. ]
  207. data.cubic = [cubic, cubic]
  208. }
  209. return data
  210. }
  211. const flipGroupElements = (elements: BaseElement[], axis: 'x' | 'y') => {
  212. const minX = Math.min(...elements.map(el => el.left))
  213. const maxX = Math.max(...elements.map(el => el.left + el.width))
  214. const minY = Math.min(...elements.map(el => el.top))
  215. const maxY = Math.max(...elements.map(el => el.top + el.height))
  216. const centerX = (minX + maxX) / 2
  217. const centerY = (minY + maxY) / 2
  218. return elements.map(element => {
  219. const newElement = { ...element }
  220. if (axis === 'y') newElement.left = 2 * centerX - element.left - element.width
  221. if (axis === 'x') newElement.top = 2 * centerY - element.top - element.height
  222. return newElement
  223. })
  224. }
  225. const calculateRotatedPosition = (
  226. x: number,
  227. y: number,
  228. w: number,
  229. h: number,
  230. ox: number,
  231. oy: number,
  232. k: number,
  233. ) => {
  234. const radians = k * (Math.PI / 180)
  235. const containerCenterX = x + w / 2
  236. const containerCenterY = y + h / 2
  237. const relativeX = ox - w / 2
  238. const relativeY = oy - h / 2
  239. const rotatedX = relativeX * Math.cos(radians) + relativeY * Math.sin(radians)
  240. const rotatedY = -relativeX * Math.sin(radians) + relativeY * Math.cos(radians)
  241. const graphicX = containerCenterX + rotatedX
  242. const graphicY = containerCenterY + rotatedY
  243. return { x: graphicX, y: graphicY }
  244. }
  245. // 导入PPTX文件
  246. const importPPTXFile = (files: FileList, options?: { cover?: boolean; fixedViewport?: boolean }) => {
  247. const defaultOptions = {
  248. cover: false,
  249. fixedViewport: false,
  250. }
  251. const { cover, fixedViewport } = { ...defaultOptions, ...options }
  252. const file = files[0]
  253. if (!file) return
  254. exporting.value = true
  255. const shapeList: ShapePoolItem[] = []
  256. for (const item of SHAPE_LIST) {
  257. shapeList.push(...item.children)
  258. }
  259. const reader = new FileReader()
  260. reader.onload = async e => {
  261. let json = null
  262. try {
  263. json = await parse(e.target!.result as ArrayBuffer)
  264. }
  265. catch {
  266. exporting.value = false
  267. message.error('无法正确读取 / 解析该文件')
  268. return
  269. }
  270. let ratio = 96 / 72
  271. const width = json.size.width
  272. if (fixedViewport) ratio = 1000 / width
  273. else slidesStore.setViewportSize(width * ratio)
  274. slidesStore.setTheme({ themeColors: json.themeColors })
  275. const slides: Slide[] = []
  276. for (const item of json.slides) {
  277. const { type, value } = item.fill
  278. let background: SlideBackground
  279. if (type === 'image') {
  280. background = {
  281. type: 'image',
  282. image: {
  283. src: value.picBase64,
  284. size: 'cover',
  285. },
  286. }
  287. }
  288. else if (type === 'gradient') {
  289. background = {
  290. type: 'gradient',
  291. gradient: {
  292. type: value.path === 'line' ? 'linear' : 'radial',
  293. colors: value.colors.map(item => ({
  294. ...item,
  295. pos: parseInt(item.pos),
  296. })),
  297. rotate: value.rot + 90,
  298. },
  299. }
  300. }
  301. else {
  302. background = {
  303. type: 'solid',
  304. color: value || '#fff',
  305. }
  306. }
  307. const slide: Slide = {
  308. id: nanoid(10),
  309. elements: [],
  310. background,
  311. remark: item.note || '',
  312. }
  313. const parseElements = (elements: Element[]) => {
  314. const sortedElements = elements.sort((a, b) => a.order - b.order)
  315. for (const el of sortedElements) {
  316. const originWidth = el.width || 1
  317. const originHeight = el.height || 1
  318. const originLeft = el.left
  319. const originTop = el.top
  320. el.width = el.width * ratio
  321. el.height = el.height * ratio
  322. el.left = el.left * ratio
  323. el.top = el.top * ratio
  324. if (el.type === 'text') {
  325. const textEl: PPTTextElement = {
  326. type: 'text',
  327. id: nanoid(10),
  328. width: el.width,
  329. height: el.height,
  330. left: el.left,
  331. top: el.top,
  332. rotate: el.rotate,
  333. defaultFontName: theme.value.fontName,
  334. defaultColor: theme.value.fontColor,
  335. content: convertFontSizePtToPx(el.content, ratio),
  336. lineHeight: 1,
  337. outline: {
  338. color: el.borderColor,
  339. width: +(el.borderWidth * ratio).toFixed(2),
  340. style: el.borderType,
  341. },
  342. fill: el.fill.type === 'color' ? el.fill.value : '',
  343. vertical: el.isVertical,
  344. }
  345. if (el.shadow) {
  346. textEl.shadow = {
  347. h: el.shadow.h * ratio,
  348. v: el.shadow.v * ratio,
  349. blur: el.shadow.blur * ratio,
  350. color: el.shadow.color,
  351. }
  352. }
  353. slide.elements.push(textEl)
  354. }
  355. else if (el.type === 'image') {
  356. const element: PPTImageElement = {
  357. type: 'image',
  358. id: nanoid(10),
  359. src: el.src,
  360. width: el.width,
  361. height: el.height,
  362. left: el.left,
  363. top: el.top,
  364. fixedRatio: true,
  365. rotate: el.rotate,
  366. flipH: el.isFlipH,
  367. flipV: el.isFlipV,
  368. }
  369. if (el.borderWidth) {
  370. element.outline = {
  371. color: el.borderColor,
  372. width: +(el.borderWidth * ratio).toFixed(2),
  373. style: el.borderType,
  374. }
  375. }
  376. const clipShapeTypes = ['roundRect', 'ellipse', 'triangle', 'rhombus', 'pentagon', 'hexagon', 'heptagon', 'octagon', 'parallelogram', 'trapezoid']
  377. if (el.rect) {
  378. element.clip = {
  379. shape: (el.geom && clipShapeTypes.includes(el.geom)) ? el.geom : 'rect',
  380. range: [
  381. [
  382. el.rect.l || 0,
  383. el.rect.t || 0,
  384. ],
  385. [
  386. 100 - (el.rect.r || 0),
  387. 100 - (el.rect.b || 0),
  388. ],
  389. ]
  390. }
  391. }
  392. else if (el.geom && clipShapeTypes.includes(el.geom)) {
  393. element.clip = {
  394. shape: el.geom,
  395. range: [[0, 0], [100, 100]]
  396. }
  397. }
  398. slide.elements.push(element)
  399. }
  400. else if (el.type === 'math') {
  401. slide.elements.push({
  402. type: 'image',
  403. id: nanoid(10),
  404. src: el.picBase64,
  405. width: el.width,
  406. height: el.height,
  407. left: el.left,
  408. top: el.top,
  409. fixedRatio: true,
  410. rotate: 0,
  411. })
  412. }
  413. else if (el.type === 'audio') {
  414. slide.elements.push({
  415. type: 'audio',
  416. id: nanoid(10),
  417. src: el.blob,
  418. width: el.width,
  419. height: el.height,
  420. left: el.left,
  421. top: el.top,
  422. rotate: 0,
  423. fixedRatio: false,
  424. color: theme.value.themeColors[0],
  425. loop: false,
  426. autoplay: false,
  427. })
  428. }
  429. else if (el.type === 'video') {
  430. slide.elements.push({
  431. type: 'video',
  432. id: nanoid(10),
  433. src: (el.blob || el.src)!,
  434. width: el.width,
  435. height: el.height,
  436. left: el.left,
  437. top: el.top,
  438. rotate: 0,
  439. autoplay: false,
  440. })
  441. }
  442. else if (el.type === 'shape') {
  443. if (el.shapType === 'line' || /Connector/.test(el.shapType)) {
  444. const lineElement = parseLineElement(el, ratio)
  445. slide.elements.push(lineElement)
  446. }
  447. else {
  448. const shape = shapeList.find(item => item.pptxShapeType === el.shapType)
  449. const vAlignMap: { [key: string]: ShapeTextAlign } = {
  450. 'mid': 'middle',
  451. 'down': 'bottom',
  452. 'up': 'top',
  453. }
  454. const gradient: Gradient | undefined = el.fill?.type === 'gradient' ? {
  455. type: el.fill.value.path === 'line' ? 'linear' : 'radial',
  456. colors: el.fill.value.colors.map(item => ({
  457. ...item,
  458. pos: parseInt(item.pos),
  459. })),
  460. rotate: el.fill.value.rot,
  461. } : undefined
  462. const pattern: string | undefined = el.fill?.type === 'image' ? el.fill.value.picBase64 : undefined
  463. const fill = el.fill?.type === 'color' ? el.fill.value : ''
  464. const element: PPTShapeElement = {
  465. type: 'shape',
  466. id: nanoid(10),
  467. width: el.width,
  468. height: el.height,
  469. left: el.left,
  470. top: el.top,
  471. viewBox: [200, 200],
  472. path: 'M 0 0 L 200 0 L 200 200 L 0 200 Z',
  473. fill,
  474. gradient,
  475. pattern,
  476. fixedRatio: false,
  477. rotate: el.rotate,
  478. outline: {
  479. color: el.borderColor,
  480. width: +(el.borderWidth * ratio).toFixed(2),
  481. style: el.borderType,
  482. },
  483. text: {
  484. content: convertFontSizePtToPx(el.content, ratio),
  485. defaultFontName: theme.value.fontName,
  486. defaultColor: theme.value.fontColor,
  487. align: vAlignMap[el.vAlign] || 'middle',
  488. },
  489. flipH: el.isFlipH,
  490. flipV: el.isFlipV,
  491. }
  492. if (el.shadow) {
  493. element.shadow = {
  494. h: el.shadow.h * ratio,
  495. v: el.shadow.v * ratio,
  496. blur: el.shadow.blur * ratio,
  497. color: el.shadow.color,
  498. }
  499. }
  500. if (shape) {
  501. element.path = shape.path
  502. element.viewBox = shape.viewBox
  503. if (shape.pathFormula) {
  504. element.pathFormula = shape.pathFormula
  505. element.viewBox = [el.width, el.height]
  506. const pathFormula = SHAPE_PATH_FORMULAS[shape.pathFormula]
  507. if ('editable' in pathFormula && pathFormula.editable) {
  508. element.path = pathFormula.formula(el.width, el.height, pathFormula.defaultValue)
  509. element.keypoints = pathFormula.defaultValue
  510. }
  511. else element.path = pathFormula.formula(el.width, el.height)
  512. }
  513. }
  514. else if (el.path && el.path.indexOf('NaN') === -1) {
  515. const { maxX, maxY } = getSvgPathRange(el.path)
  516. element.path = el.path
  517. element.viewBox = [maxX || originWidth, maxY || originHeight]
  518. }
  519. if (el.shapType === 'custom') {
  520. if (el.path!.indexOf('NaN') !== -1) {
  521. if (element.width === 0) element.width = 0.1
  522. if (element.height === 0) element.height = 0.1
  523. element.path = el.path!.replace(/NaN/g, '0')
  524. }
  525. else {
  526. element.special = true
  527. element.path = el.path!
  528. }
  529. const { maxX, maxY } = getSvgPathRange(element.path)
  530. element.viewBox = [maxX || originWidth, maxY || originHeight]
  531. }
  532. if (element.path) slide.elements.push(element)
  533. }
  534. }
  535. else if (el.type === 'table') {
  536. const row = el.data.length
  537. const col = el.data[0].length
  538. const style: TableCellStyle = {
  539. fontname: theme.value.fontName,
  540. color: theme.value.fontColor,
  541. }
  542. const data: TableCell[][] = []
  543. for (let i = 0; i < row; i++) {
  544. const rowCells: TableCell[] = []
  545. for (let j = 0; j < col; j++) {
  546. const cellData = el.data[i][j]
  547. let textDiv: HTMLDivElement | null = document.createElement('div')
  548. textDiv.innerHTML = cellData.text
  549. const p = textDiv.querySelector('p')
  550. const align = p?.style.textAlign || 'left'
  551. const span = textDiv.querySelector('span')
  552. const fontsize = span?.style.fontSize ? (parseInt(span?.style.fontSize) * ratio).toFixed(1) + 'px' : ''
  553. const fontname = span?.style.fontFamily || ''
  554. const color = span?.style.color || cellData.fontColor
  555. rowCells.push({
  556. id: nanoid(10),
  557. colspan: cellData.colSpan || 1,
  558. rowspan: cellData.rowSpan || 1,
  559. text: textDiv.innerText,
  560. style: {
  561. ...style,
  562. align: ['left', 'right', 'center'].includes(align) ? (align as 'left' | 'right' | 'center') : 'left',
  563. fontsize,
  564. fontname,
  565. color,
  566. bold: cellData.fontBold,
  567. backcolor: cellData.fillColor,
  568. },
  569. })
  570. textDiv = null
  571. }
  572. data.push(rowCells)
  573. }
  574. const allWidth = el.colWidths.reduce((a, b) => a + b, 0)
  575. const colWidths: number[] = el.colWidths.map(item => item / allWidth)
  576. const firstCell = el.data[0][0]
  577. const border = firstCell.borders.top ||
  578. firstCell.borders.bottom ||
  579. el.borders.top ||
  580. el.borders.bottom ||
  581. firstCell.borders.left ||
  582. firstCell.borders.right ||
  583. el.borders.left ||
  584. el.borders.right
  585. const borderWidth = border?.borderWidth || 0
  586. const borderStyle = border?.borderType || 'solid'
  587. const borderColor = border?.borderColor || '#eeece1'
  588. slide.elements.push({
  589. type: 'table',
  590. id: nanoid(10),
  591. width: el.width,
  592. height: el.height,
  593. left: el.left,
  594. top: el.top,
  595. colWidths,
  596. rotate: 0,
  597. data,
  598. outline: {
  599. width: +(borderWidth * ratio || 2).toFixed(2),
  600. style: borderStyle,
  601. color: borderColor,
  602. },
  603. cellMinHeight: el.rowHeights[0] ? el.rowHeights[0] * ratio : 36,
  604. })
  605. }
  606. else if (el.type === 'chart') {
  607. let labels: string[]
  608. let legends: string[]
  609. let series: number[][]
  610. if (el.chartType === 'scatterChart' || el.chartType === 'bubbleChart') {
  611. labels = el.data[0].map((item, index) => `坐标${index + 1}`)
  612. legends = ['X', 'Y']
  613. series = el.data
  614. }
  615. else {
  616. const data = el.data as ChartItem[]
  617. labels = Object.values(data[0].xlabels)
  618. legends = data.map(item => item.key)
  619. series = data.map(item => item.values.map(v => v.y))
  620. }
  621. const options: ChartOptions = {}
  622. let chartType: ChartType = 'bar'
  623. switch (el.chartType) {
  624. case 'barChart':
  625. case 'bar3DChart':
  626. chartType = 'bar'
  627. if (el.barDir === 'bar') chartType = 'column'
  628. if (el.grouping === 'stacked' || el.grouping === 'percentStacked') options.stack = true
  629. break
  630. case 'lineChart':
  631. case 'line3DChart':
  632. if (el.grouping === 'stacked' || el.grouping === 'percentStacked') options.stack = true
  633. chartType = 'line'
  634. break
  635. case 'areaChart':
  636. case 'area3DChart':
  637. if (el.grouping === 'stacked' || el.grouping === 'percentStacked') options.stack = true
  638. chartType = 'area'
  639. break
  640. case 'scatterChart':
  641. case 'bubbleChart':
  642. chartType = 'scatter'
  643. break
  644. case 'pieChart':
  645. case 'pie3DChart':
  646. chartType = 'pie'
  647. break
  648. case 'radarChart':
  649. chartType = 'radar'
  650. break
  651. case 'doughnutChart':
  652. chartType = 'ring'
  653. break
  654. default:
  655. }
  656. slide.elements.push({
  657. type: 'chart',
  658. id: nanoid(10),
  659. chartType: chartType,
  660. width: el.width,
  661. height: el.height,
  662. left: el.left,
  663. top: el.top,
  664. rotate: 0,
  665. themeColors: el.colors.length ? el.colors : theme.value.themeColors,
  666. textColor: theme.value.fontColor,
  667. data: {
  668. labels,
  669. legends,
  670. series,
  671. },
  672. options,
  673. })
  674. }
  675. else if (el.type === 'group') {
  676. let elements: BaseElement[] = el.elements.map(_el => {
  677. let left = _el.left + originLeft
  678. let top = _el.top + originTop
  679. if (el.rotate) {
  680. const { x, y } = calculateRotatedPosition(originLeft, originTop, originWidth, originHeight, _el.left, _el.top, el.rotate)
  681. left = x
  682. top = y
  683. }
  684. const element = {
  685. ..._el,
  686. left,
  687. top,
  688. }
  689. if (el.isFlipH && 'isFlipH' in element) element.isFlipH = true
  690. if (el.isFlipV && 'isFlipV' in element) element.isFlipV = true
  691. return element
  692. })
  693. if (el.isFlipH) elements = flipGroupElements(elements, 'y')
  694. if (el.isFlipV) elements = flipGroupElements(elements, 'x')
  695. parseElements(elements)
  696. }
  697. else if (el.type === 'diagram') {
  698. const elements = el.elements.map(_el => ({
  699. ..._el,
  700. left: _el.left + originLeft,
  701. top: _el.top + originTop,
  702. }))
  703. parseElements(elements)
  704. }
  705. }
  706. }
  707. parseElements([...item.elements, ...item.layoutElements])
  708. slides.push(slide)
  709. }
  710. if (cover) {
  711. slidesStore.updateSlideIndex(0)
  712. slidesStore.setSlides(slides)
  713. addHistorySnapshot()
  714. }
  715. else if (isEmptySlide.value) {
  716. slidesStore.setSlides(slides)
  717. addHistorySnapshot()
  718. }
  719. else addSlidesFromData(slides)
  720. exporting.value = false
  721. }
  722. reader.readAsArrayBuffer(file)
  723. }
  724. return {
  725. importSpecificFile,
  726. importJSON,
  727. importPPTXFile,
  728. readJSON,
  729. exporting,
  730. }
  731. }