diff --git a/src/hooks/useAIPPT.ts b/src/hooks/useAIPPT.ts index a66e33a0..a3ac4b7a 100644 --- a/src/hooks/useAIPPT.ts +++ b/src/hooks/useAIPPT.ts @@ -1,17 +1,21 @@ import api from '@/services' import { nanoid } from 'nanoid' -import type { PPTTextElement, Slide, TextType } from '@/types/slides' +import type { PPTElement, PPTShapeElement, PPTTextElement, Slide, TextType } from '@/types/slides' import type { AIPPTSlide } from '@/types/AIPPT' import { useSlidesStore } from '@/store' import useAddSlidesOrElements from './useAddSlidesOrElements' import useSlideHandler from './useSlideHandler' -const findClosestGreaterThanN = (templates: Slide[], n: number, type: TextType) => { +const checkTextType = (el: PPTElement, type: TextType) => { + return (el.type === 'text' && el.textType === type) || (el.type === 'shape' && el.text && el.text.type === type) +} + +const getUseableTemplates = (templates: Slide[], n: number, type: TextType) => { if (n === 1) { const list = templates.filter(slide => { - const items = slide.elements.filter(el => el.type === 'text' && el.textType === type) - const titles = slide.elements.filter(el => el.type === 'text' && el.textType === 'title') - const texts = slide.elements.filter(el => el.type === 'text' && el.textType === 'content') + const items = slide.elements.filter(el => checkTextType(el, type)) + const titles = slide.elements.filter(el => checkTextType(el, 'title')) + const texts = slide.elements.filter(el => checkTextType(el, 'content')) return !items.length && titles.length === 1 && texts.length === 1 }) @@ -22,33 +26,33 @@ const findClosestGreaterThanN = (templates: Slide[], n: number, type: TextType) let target: Slide | null = null const list = templates.filter(slide => { - const len = slide.elements.filter(el => el.type === 'text' && el.textType === type).length + const len = slide.elements.filter(el => checkTextType(el, type)).length return len >= n }) if (list.length === 0) { const sorted = templates.sort((a, b) => { - const aLen = a.elements.filter(el => el.type === 'text' && el.textType === type).length - const bLen = b.elements.filter(el => el.type === 'text' && el.textType === type).length + const aLen = a.elements.filter(el => checkTextType(el, type)).length + const bLen = b.elements.filter(el => checkTextType(el, type)).length return aLen - bLen }) target = sorted[sorted.length - 1] } else { target = list.reduce((closest, current) => { - const currentLen = current.elements.filter(el => el.type === 'text' && el.textType === type).length - const closestLen = closest.elements.filter(el => el.type === 'text' && el.textType === type).length + const currentLen = current.elements.filter(el => checkTextType(el, type)).length + const closestLen = closest.elements.filter(el => checkTextType(el, type)).length return (currentLen - n) <= (closestLen - n) ? current : closest }) } return templates.filter(slide => { - const len = slide.elements.filter(el => el.type === 'text' && el.textType === type).length - const targetLen = target!.elements.filter(el => el.type === 'text' && el.textType === type).length + const len = slide.elements.filter(el => checkTextType(el, type)).length + const targetLen = target!.elements.filter(el => checkTextType(el, type)).length return len === targetLen }) } -const getFontsizeInBox = ({ +const getAdaptedFontsize = ({ text, fontSize, fontFamily, @@ -99,22 +103,26 @@ const getFontInfo = (htmlString: string) => { } } -const getNewTextElData = ({ +const getNewTextElement = ({ el, text, maxLine, longestText, + digitPadding, }: { - el: PPTTextElement + el: PPTTextElement | PPTShapeElement text: string maxLine: number longestText?: string -}) => { + digitPadding?: boolean +}): PPTTextElement | PPTShapeElement => { const padding = 10 const width = el.width - padding * 2 - 10 - const fontInfo = getFontInfo(el.content) - const size = getFontsizeInBox({ + let content = el.type === 'text' ? el.content : el.text!.content + + const fontInfo = getFontInfo(content) + const size = getAdaptedFontsize({ text: longestText || text, fontSize: fontInfo.fontSize, fontFamily: fontInfo.fontFamily, @@ -122,15 +130,18 @@ const getNewTextElData = ({ maxLine, }) - let content = el.content - const parser = new DOMParser() const doc = parser.parseFromString(content, 'text/html') const treeWalker = document.createTreeWalker(doc.body, NodeFilter.SHOW_TEXT) const firstTextNode = treeWalker.nextNode() - if (firstTextNode) firstTextNode.textContent = text + if (firstTextNode) { + if (digitPadding && firstTextNode.textContent && firstTextNode.textContent.length === 2 && text.length === 1) { + firstTextNode.textContent = '0' + text + } + else firstTextNode.textContent = text + } if (doc.body.innerHTML.indexOf('font-size') === -1) { const p = doc.querySelector('p') @@ -139,7 +150,7 @@ const getNewTextElData = ({ content = doc.body.innerHTML.replace(/font-size:(.+?)px/g, `font-size: ${size}px`) - return { ...el, content } + return el.type === 'text' ? { ...el, content } : { ...el, text: { ...el.text!, content } } } export default () => { @@ -151,7 +162,7 @@ export default () => { const templateSlides: Slide[] = await api.getMockData('template').then(ret => ret.slides) const _AISlides: AIPPTSlide[] = await api.getMockData('AIPPT') - const AISlides = [] + const AISlides: AIPPTSlide[] = [] for (const template of _AISlides) { if (template.type === 'content') { const items = template.data.items @@ -159,29 +170,63 @@ export default () => { const items1 = items.slice(0, 3) const items2 = items.slice(3) AISlides.push({ ...template, data: { ...template.data, items: items1 } }) - AISlides.push({ ...template, data: { ...template.data, items: items2 } }) + AISlides.push({ ...template, data: { ...template.data, items: items2 }, offset: 3 }) } else if (items.length === 7 || items.length === 8) { const items1 = items.slice(0, 4) const items2 = items.slice(4) AISlides.push({ ...template, data: { ...template.data, items: items1 } }) - AISlides.push({ ...template, data: { ...template.data, items: items2 } }) + AISlides.push({ ...template, data: { ...template.data, items: items2 }, offset: 4 }) } else if (items.length === 9 || items.length === 10) { const items1 = items.slice(0, 3) const items2 = items.slice(3, 6) const items3 = items.slice(6) AISlides.push({ ...template, data: { ...template.data, items: items1 } }) - AISlides.push({ ...template, data: { ...template.data, items: items2 } }) - AISlides.push({ ...template, data: { ...template.data, items: items3 } }) + AISlides.push({ ...template, data: { ...template.data, items: items2 }, offset: 3 }) + AISlides.push({ ...template, data: { ...template.data, items: items3 }, offset: 6 }) } else if (items.length > 10) { const items1 = items.slice(0, 4) const items2 = items.slice(4, 8) const items3 = items.slice(8) AISlides.push({ ...template, data: { ...template.data, items: items1 } }) - AISlides.push({ ...template, data: { ...template.data, items: items2 } }) - AISlides.push({ ...template, data: { ...template.data, items: items3 } }) + AISlides.push({ ...template, data: { ...template.data, items: items2 }, offset: 4 }) + AISlides.push({ ...template, data: { ...template.data, items: items3 }, offset: 8 }) + } + else { + AISlides.push(template) + } + } + else if (template.type === 'contents') { + const items = template.data.items + if (items.length === 7) { + const items1 = items.slice(0, 5) + const items2 = items.slice(5) + AISlides.push({ ...template, data: { ...template.data, items: items1 } }) + AISlides.push({ ...template, data: { ...template.data, items: items2 }, offset: 5 }) + } + else if (items.length > 7 && items.length <= 12) { + const items1 = items.slice(0, 6) + const items2 = items.slice(6) + AISlides.push({ ...template, data: { ...template.data, items: items1 } }) + AISlides.push({ ...template, data: { ...template.data, items: items2 }, offset: 6 }) + } + else if (items.length === 13) { + const items1 = items.slice(0, 6) + const items2 = items.slice(6, 11) + const items3 = items.slice(11) + AISlides.push({ ...template, data: { ...template.data, items: items1 } }) + AISlides.push({ ...template, data: { ...template.data, items: items2 }, offset: 6 }) + AISlides.push({ ...template, data: { ...template.data, items: items3 }, offset: 11 }) + } + else if (items.length > 13) { + const items1 = items.slice(0, 6) + const items2 = items.slice(6, 12) + const items3 = items.slice(12) + AISlides.push({ ...template, data: { ...template.data, items: items1 } }) + AISlides.push({ ...template, data: { ...template.data, items: items2 }, offset: 6 }) + AISlides.push({ ...template, data: { ...template.data, items: items3 }, offset: 12 }) } else { AISlides.push(template) @@ -207,11 +252,12 @@ export default () => { for (const item of AISlides) { if (item.type === 'cover') { const elements = coverTemplate.elements.map(el => { - if (el.type === 'text' && el.textType === 'title' && item.data.title) { - return getNewTextElData({ el, text: item.data.title, maxLine: 1 }) + if (el.type !== 'text' && el.type !== 'shape') return el + if (checkTextType(el, 'title') && item.data.title) { + return getNewTextElement({ el, text: item.data.title, maxLine: 1 }) } - if (el.type === 'text' && el.textType === 'content' && item.data.text) { - return getNewTextElData({ el, text: item.data.text, maxLine: 3 }) + if (checkTextType(el, 'content') && item.data.text) { + return getNewTextElement({ el, text: item.data.text, maxLine: 3 }) } return el }) @@ -222,10 +268,16 @@ export default () => { }) } else if (item.type === 'contents') { - const _contentsTemplates = findClosestGreaterThanN(contentsTemplates, item.data.items.length, 'item') + const _contentsTemplates = getUseableTemplates(contentsTemplates, item.data.items.length, 'item') const contentsTemplate = _contentsTemplates[Math.floor(Math.random() * _contentsTemplates.length)] - const sortedItemIds = contentsTemplate.elements.filter(el => el.type === 'text' && el.textType === 'item').sort((a, b) => { + const sortedItemIds = contentsTemplate.elements.filter(el => checkTextType(el, 'item')).sort((a, b) => { + const aIndex = a.left + a.top * 2 + const bIndex = b.left + b.top * 2 + return aIndex - bIndex + }).map(el => el.id) + + const sortedNumberItemIds = contentsTemplate.elements.filter(el => checkTextType(el, 'itemNumber')).sort((a, b) => { const aIndex = a.left + a.top * 2 const bIndex = b.left + b.top * 2 return aIndex - bIndex @@ -234,10 +286,16 @@ export default () => { const longestText = item.data.items.reduce((longest, current) => current.length > longest.length ? current : longest, '') const elements = contentsTemplate.elements.map(el => { - if (el.type === 'text' && el.textType === 'item') { + if (el.type !== 'text' && el.type !== 'shape') return el + if (checkTextType(el, 'item')) { const index = sortedItemIds.findIndex(id => id === el.id) const itemTitle = item.data.items[index] - if (itemTitle) return getNewTextElData({ el, text: itemTitle, maxLine: 1, longestText }) + if (itemTitle) return getNewTextElement({ el, text: itemTitle, maxLine: 1, longestText }) + } + if (checkTextType(el, 'itemNumber')) { + const index = sortedNumberItemIds.findIndex(id => id === el.id) + const offset = item.offset || 0 + return getNewTextElement({ el, text: index + offset + 1 + '', maxLine: 1, digitPadding: true }) } return el }) @@ -250,14 +308,15 @@ export default () => { else if (item.type === 'transition') { transitionIndex++ const elements = transitionTemplate.elements.map(el => { - if (el.type === 'text' && el.textType === 'title' && item.data.title) { - return getNewTextElData({ el, text: item.data.title, maxLine: 1 }) + if (el.type !== 'text' && el.type !== 'shape') return el + if (checkTextType(el, 'title') && item.data.title) { + return getNewTextElement({ el, text: item.data.title, maxLine: 1 }) } - if (el.type === 'text' && el.textType === 'content' && item.data.text) { - return getNewTextElData({ el, text: item.data.text, maxLine: 3 }) + if (checkTextType(el, 'content') && item.data.text) { + return getNewTextElement({ el, text: item.data.text, maxLine: 3 }) } - if (el.type === 'text' && el.textType === 'partNumber') { - return getNewTextElData({ el, text: transitionIndex + '', maxLine: 1 }) + if (checkTextType(el, 'partNumber')) { + return getNewTextElement({ el, text: transitionIndex + '', maxLine: 1, digitPadding: true }) } return el }) @@ -268,16 +327,22 @@ export default () => { }) } else if (item.type === 'content') { - const _contentTemplates = findClosestGreaterThanN(contentTemplates, item.data.items.length, 'item') + const _contentTemplates = getUseableTemplates(contentTemplates, item.data.items.length, 'item') const contentTemplate = _contentTemplates[Math.floor(Math.random() * _contentTemplates.length)] - const sortedTitleItemIds = contentTemplate.elements.filter(el => el.type === 'text' && el.textType === 'itemTitle').sort((a, b) => { + const sortedTitleItemIds = contentTemplate.elements.filter(el => checkTextType(el, 'itemTitle')).sort((a, b) => { const aIndex = a.left + a.top * 2 const bIndex = b.left + b.top * 2 return aIndex - bIndex }).map(el => el.id) - const sortedTextItemIds = contentTemplate.elements.filter(el => el.type === 'text' && el.textType === 'item').sort((a, b) => { + const sortedTextItemIds = contentTemplate.elements.filter(el => checkTextType(el, 'item')).sort((a, b) => { + const aIndex = a.left + a.top * 2 + const bIndex = b.left + b.top * 2 + return aIndex - bIndex + }).map(el => el.id) + + const sortedNumberItemIds = contentTemplate.elements.filter(el => checkTextType(el, 'itemNumber')).sort((a, b) => { const aIndex = a.left + a.top * 2 const bIndex = b.left + b.top * 2 return aIndex - bIndex @@ -294,32 +359,36 @@ export default () => { const longestText = itemTexts.reduce((longest, current) => current.length > longest.length ? current : longest, '') const elements = contentTemplate.elements.map(el => { + if (el.type !== 'text' && el.type !== 'shape') return el if (item.data.items.length === 1) { const contentItem = item.data.items[0] - if (el.type === 'text' && el.textType === 'content' && contentItem.text) { - return getNewTextElData({ el, text: contentItem.text, maxLine: 6 }) + if (checkTextType(el, 'content') && contentItem.text) { + return getNewTextElement({ el, text: contentItem.text, maxLine: 6 }) } } else { - if (el.type === 'text' && el.textType === 'itemTitle') { + if (checkTextType(el, 'itemTitle')) { const index = sortedTitleItemIds.findIndex(id => id === el.id) const contentItem = item.data.items[index] if (contentItem && contentItem.title) { - return getNewTextElData({ el, text: contentItem.title, longestText: longestTitle, maxLine: 1 }) + return getNewTextElement({ el, text: contentItem.title, longestText: longestTitle, maxLine: 1 }) } - return { ...el, isInvalid: true } } - if (el.type === 'text' && el.textType === 'item') { + if (checkTextType(el, 'item')) { const index = sortedTextItemIds.findIndex(id => id === el.id) const contentItem = item.data.items[index] if (contentItem && contentItem.text) { - return getNewTextElData({ el, text: contentItem.text, longestText, maxLine: 4 }) + return getNewTextElement({ el, text: contentItem.text, longestText, maxLine: 4 }) } - return { ...el, isInvalid: true } + } + if (checkTextType(el, 'itemNumber')) { + const index = sortedNumberItemIds.findIndex(id => id === el.id) + const offset = item.offset || 0 + return getNewTextElement({ el, text: index + offset + 1 + '', maxLine: 1, digitPadding: true }) } } - if (el.type === 'text' && el.textType === 'title' && item.data.title) { - return getNewTextElData({ el, text: item.data.title, maxLine: 1 }) + if (checkTextType(el, 'title') && item.data.title) { + return getNewTextElement({ el, text: item.data.title, maxLine: 1 }) } return el }) diff --git a/src/types/AIPPT.ts b/src/types/AIPPT.ts index 092c66ab..c1f2950d 100644 --- a/src/types/AIPPT.ts +++ b/src/types/AIPPT.ts @@ -11,6 +11,7 @@ export interface AIPPTContents { data: { items: string[] } + offset?: number } export interface AIPPTTransition { @@ -29,7 +30,8 @@ export interface AIPPTContent { title: string text: string }[] - } + }, + offset?: number } export interface AIPPTEnd {