refactor: 类型补充、优化

This commit is contained in:
pipipi-pikachu 2023-07-13 21:19:59 +08:00
parent e666c30c19
commit 2c22196501
24 changed files with 88 additions and 35 deletions

View File

@ -20,7 +20,10 @@ const props = defineProps({
}, },
}) })
const checkboardCache = {} interface CheckboardCache {
[key: string]: string | null
}
const checkboardCache: CheckboardCache = {}
const renderCheckboard = (white: string, grey: string, size: number) => { const renderCheckboard = (white: string, grey: string, size: number) => {
const canvas = document.createElement('canvas') const canvas = document.createElement('canvas')

View File

@ -1,4 +1,4 @@
export const ELEMENT_TYPE_ZH = { export const ELEMENT_TYPE_ZH: { [key: string]: string } = {
text: '文本', text: '文本',
image: '图片', image: '图片',
shape: '形状', shape: '形状',
@ -10,7 +10,7 @@ export const ELEMENT_TYPE_ZH = {
latex: '公式', latex: '公式',
} }
export const MIN_SIZE = { export const MIN_SIZE: { [key: string]: number } = {
text: 20, text: 20,
image: 20, image: 20,
shape: 15, shape: 15,

View File

@ -14,7 +14,17 @@ export const enum ClipPaths {
STAR = 'star', STAR = 'star',
} }
export const CLIPPATHS = { interface ClipPath {
[key: string]: {
name: string
type: ClipPathTypes
style: string
radius?: string
createPath?: (width: number, height: number) => string
}
}
export const CLIPPATHS: ClipPath = {
rect: { rect: {
name: '矩形', name: '矩形',
type: ClipPathTypes.RECT, type: ClipPathTypes.RECT,

View File

@ -5,6 +5,10 @@ import { ElementAlignCommands } from '@/types/edit'
import { getElementListRange, getRectRotatedOffset } from '@/utils/element' import { getElementListRange, getRectRotatedOffset } from '@/utils/element'
import useHistorySnapshot from './useHistorySnapshot' import useHistorySnapshot from './useHistorySnapshot'
interface RangeMap {
[id: string]: ReturnType<typeof getElementListRange>
}
export default () => { export default () => {
const slidesStore = useSlidesStore() const slidesStore = useSlidesStore()
const { activeElementIdList, activeElementList } = storeToRefs(useMainStore()) const { activeElementIdList, activeElementList } = storeToRefs(useMainStore())
@ -21,7 +25,7 @@ export default () => {
const elementList: PPTElement[] = JSON.parse(JSON.stringify(currentSlide.value.elements)) const elementList: PPTElement[] = JSON.parse(JSON.stringify(currentSlide.value.elements))
// 如果所选择的元素为组合元素的成员,需要计算该组合的整体范围 // 如果所选择的元素为组合元素的成员,需要计算该组合的整体范围
const groupElementRangeMap = {} const groupElementRangeMap: RangeMap = {}
for (const activeElement of activeElementList.value) { for (const activeElement of activeElementList.value) {
if (activeElement.groupId && !groupElementRangeMap[activeElement.groupId]) { if (activeElement.groupId && !groupElementRangeMap[activeElement.groupId]) {
const groupElements = activeElementList.value.filter(item => item.groupId === activeElement.groupId) const groupElements = activeElementList.value.filter(item => item.groupId === activeElement.groupId)

View File

@ -88,7 +88,7 @@ export default () => {
let indent = 0 let indent = 0
const slices: pptxgen.TextProps[] = [] const slices: pptxgen.TextProps[] = []
const parse = (obj: AST[], baseStyleObj = {}) => { const parse = (obj: AST[], baseStyleObj: { [key: string]: string } = {}) => {
for (const item of obj) { for (const item of obj) {
const isBlockTag = 'tagName' in item && ['div', 'li', 'p'].includes(item.tagName) const isBlockTag = 'tagName' in item && ['div', 'li', 'p'].includes(item.tagName)
@ -186,7 +186,7 @@ export default () => {
if (styleObj['vertical-align'] === 'super') options.superscript = true if (styleObj['vertical-align'] === 'super') options.superscript = true
if (styleObj['vertical-align'] === 'sub') options.subscript = true if (styleObj['vertical-align'] === 'sub') options.subscript = true
} }
if (styleObj['text-align']) options.align = styleObj['text-align'] if (styleObj['text-align']) options.align = styleObj['text-align'] as pptxgen.HAlign
if (styleObj['font-weight']) options.bold = styleObj['font-weight'] === 'bold' if (styleObj['font-weight']) options.bold = styleObj['font-weight'] === 'bold'
if (styleObj['font-style']) options.italic = styleObj['font-style'] === 'italic' if (styleObj['font-style']) options.italic = styleObj['font-style'] === 'italic'
if (styleObj['font-family']) options.fontFace = styleObj['font-family'] if (styleObj['font-family']) options.fontFace = styleObj['font-family']

View File

@ -46,7 +46,7 @@ export default () => {
// 创建原颜色与新颜色的对应关系表 // 创建原颜色与新颜色的对应关系表
const createSlideThemeColorMap = (slide: Slide, newColors: string[]): { [key: string]: string } => { const createSlideThemeColorMap = (slide: Slide, newColors: string[]): { [key: string]: string } => {
const oldColors = getSlideAllColors(slide) const oldColors = getSlideAllColors(slide)
const themeColorMap = {} const themeColorMap: { [key: string]: string } = {}
if (oldColors.length > newColors.length) { if (oldColors.length > newColors.length) {
const analogous = tinycolor(newColors[0]).analogous(oldColors.length - newColors.length + 10) const analogous = tinycolor(newColors[0]).analogous(oldColors.length - newColors.length + 10)

View File

@ -2,6 +2,10 @@ import { Directive, DirectiveBinding } from 'vue'
const CTX_CLICK_OUTSIDE_HANDLER = 'CTX_CLICK_OUTSIDE_HANDLER' const CTX_CLICK_OUTSIDE_HANDLER = 'CTX_CLICK_OUTSIDE_HANDLER'
interface CustomHTMLElement extends HTMLElement {
[CTX_CLICK_OUTSIDE_HANDLER]?: (event: MouseEvent) => void
}
const clickListener = (el: HTMLElement, event: MouseEvent, binding: DirectiveBinding) => { const clickListener = (el: HTMLElement, event: MouseEvent, binding: DirectiveBinding) => {
const handler = binding.value const handler = binding.value
@ -13,14 +17,14 @@ const clickListener = (el: HTMLElement, event: MouseEvent, binding: DirectiveBin
} }
const ClickOutsideDirective: Directive = { const ClickOutsideDirective: Directive = {
mounted(el: HTMLElement, binding) { mounted(el: CustomHTMLElement, binding) {
el[CTX_CLICK_OUTSIDE_HANDLER] = (event: MouseEvent) => clickListener(el, event, binding) el[CTX_CLICK_OUTSIDE_HANDLER] = (event: MouseEvent) => clickListener(el, event, binding)
setTimeout(() => { setTimeout(() => {
document.addEventListener('click', el[CTX_CLICK_OUTSIDE_HANDLER]) document.addEventListener('click', el[CTX_CLICK_OUTSIDE_HANDLER]!)
}, 0) }, 0)
}, },
unmounted(el: HTMLElement) { unmounted(el: CustomHTMLElement) {
if (el[CTX_CLICK_OUTSIDE_HANDLER]) { if (el[CTX_CLICK_OUTSIDE_HANDLER]) {
document.removeEventListener('click', el[CTX_CLICK_OUTSIDE_HANDLER]) document.removeEventListener('click', el[CTX_CLICK_OUTSIDE_HANDLER])
delete el[CTX_CLICK_OUTSIDE_HANDLER] delete el[CTX_CLICK_OUTSIDE_HANDLER]

View File

@ -3,6 +3,10 @@ import ContextmenuComponent from '@/components/Contextmenu/index.vue'
const CTX_CONTEXTMENU_HANDLER = 'CTX_CONTEXTMENU_HANDLER' const CTX_CONTEXTMENU_HANDLER = 'CTX_CONTEXTMENU_HANDLER'
interface CustomHTMLElement extends HTMLElement {
[CTX_CONTEXTMENU_HANDLER]?: (event: MouseEvent) => void
}
const contextmenuListener = (el: HTMLElement, event: MouseEvent, binding: DirectiveBinding) => { const contextmenuListener = (el: HTMLElement, event: MouseEvent, binding: DirectiveBinding) => {
event.stopPropagation() event.stopPropagation()
event.preventDefault() event.preventDefault()
@ -44,12 +48,12 @@ const contextmenuListener = (el: HTMLElement, event: MouseEvent, binding: Direct
} }
const ContextmenuDirective: Directive = { const ContextmenuDirective: Directive = {
mounted(el: HTMLElement, binding) { mounted(el: CustomHTMLElement, binding) {
el[CTX_CONTEXTMENU_HANDLER] = (event: MouseEvent) => contextmenuListener(el, event, binding) el[CTX_CONTEXTMENU_HANDLER] = (event: MouseEvent) => contextmenuListener(el, event, binding)
el.addEventListener('contextmenu', el[CTX_CONTEXTMENU_HANDLER]) el.addEventListener('contextmenu', el[CTX_CONTEXTMENU_HANDLER])
}, },
unmounted(el: HTMLElement) { unmounted(el: CustomHTMLElement) {
if (el && el[CTX_CONTEXTMENU_HANDLER]) { if (el && el[CTX_CONTEXTMENU_HANDLER]) {
el.removeEventListener('contextmenu', el[CTX_CONTEXTMENU_HANDLER]) el.removeEventListener('contextmenu', el[CTX_CONTEXTMENU_HANDLER])
delete el[CTX_CONTEXTMENU_HANDLER] delete el[CTX_CONTEXTMENU_HANDLER]

View File

@ -114,7 +114,11 @@ import {
StopwatchStart, StopwatchStart,
} from '@icon-park/vue-next' } from '@icon-park/vue-next'
export const icons = { interface Icons {
[key: string]: typeof PlayOne
}
export const icons: Icons = {
IconPlayOne: PlayOne, IconPlayOne: PlayOne,
IconFullScreenPlay: FullScreenPlay, IconFullScreenPlay: FullScreenPlay,
IconLock: Lock, IconLock: Lock,

View File

@ -92,6 +92,8 @@ export interface CreatingLineElement {
} }
export type CreatingElement = CreatingTextElement | CreatingShapeElement | CreatingLineElement export type CreatingElement = CreatingTextElement | CreatingShapeElement | CreatingLineElement
export type TextFormatPainterKeys = 'bold' | 'em' | 'underline' | 'strikethrough' | 'color' | 'backcolor' | 'fontsize' | 'fontname' | 'align'
export interface TextFormatPainter { export interface TextFormatPainter {
bold?: boolean bold?: boolean
em?: boolean em?: boolean

View File

@ -194,6 +194,7 @@ export interface ImageOrShapeFlip {
* *
* 'opacity'?: 100% * 'opacity'?: 100%
*/ */
export type ImageElementFilterKeys = 'blur' | 'brightness' | 'contrast' | 'grayscale' | 'saturate' | 'hue-rotate' | 'opacity'
export interface ImageElementFilters { export interface ImageElementFilters {
'blur'?: string 'blur'?: string
'brightness'?: string 'brightness'?: string

View File

@ -10,6 +10,10 @@ interface RotatedElementData {
rotate: number rotate: number
} }
interface IdMap {
[id: string]: string
}
/** /**
* *
* @param element * @param element
@ -158,7 +162,7 @@ export const uniqAlignLines = (lines: AlignLine[]) => {
* @param slides * @param slides
*/ */
export const createSlideIdMap = (slides: Slide[]) => { export const createSlideIdMap = (slides: Slide[]) => {
const slideIdMap = {} const slideIdMap: IdMap = {}
for (const slide of slides) { for (const slide of slides) {
slideIdMap[slide.id] = nanoid(10) slideIdMap[slide.id] = nanoid(10)
} }
@ -172,8 +176,8 @@ export const createSlideIdMap = (slides: Slide[]) => {
* @param elements * @param elements
*/ */
export const createElementIdMap = (elements: PPTElement[]) => { export const createElementIdMap = (elements: PPTElement[]) => {
const groupIdMap = {} const groupIdMap: IdMap = {}
const elIdMap = {} const elIdMap: IdMap = {}
for (const element of elements) { for (const element of elements) {
const groupId = element.groupId const groupId = element.groupId
if (groupId && !groupIdMap[groupId]) { if (groupId && !groupIdMap[groupId]) {

View File

@ -26,7 +26,7 @@ export const hasTerminalParent = (tagName: string, stack: StackItem[]) => {
while (currentIndex >= 0) { while (currentIndex >= 0) {
const parentTagName = stack[currentIndex].tagName const parentTagName = stack[currentIndex].tagName
if (parentTagName === tagName) break if (parentTagName === tagName) break
if (tagParents.includes(parentTagName)) return true if (parentTagName && tagParents.includes(parentTagName)) return true
currentIndex-- currentIndex--
} }
} }

View File

@ -2,7 +2,11 @@ export const childlessTags = ['style', 'script', 'template']
export const closingTags = ['html', 'head', 'body', 'p', 'dt', 'dd', 'li', 'option', 'thead', 'th', 'tbody', 'tr', 'td', 'tfoot', 'colgroup'] export const closingTags = ['html', 'head', 'body', 'p', 'dt', 'dd', 'li', 'option', 'thead', 'th', 'tbody', 'tr', 'td', 'tfoot', 'colgroup']
export const closingTagAncestorBreakers = { interface ClosingTagAncestorBreakers {
[key: string]: string[]
}
export const closingTagAncestorBreakers: ClosingTagAncestorBreakers = {
li: ['ul', 'ol', 'menu'], li: ['ul', 'ol', 'menu'],
dt: ['dl'], dt: ['dl'],
dd: ['dl'], dd: ['dl'],

View File

@ -15,8 +15,12 @@ import {
splitBlockKeepMarks, splitBlockKeepMarks,
} from 'prosemirror-commands' } from 'prosemirror-commands'
interface Keys {
[key: string]: Command
}
export const buildKeymap = (schema: Schema) => { export const buildKeymap = (schema: Schema) => {
const keys = {} const keys: Keys = {}
const bind = (key: string, cmd: Command) => keys[key] = cmd const bind = (key: string, cmd: Command) => keys[key] = cmd
bind('Alt-ArrowUp', joinUp) bind('Alt-ArrowUp', joinUp)

View File

@ -2,6 +2,10 @@ import { nodes } from 'prosemirror-schema-basic'
import { Node, NodeSpec } from 'prosemirror-model' import { Node, NodeSpec } from 'prosemirror-model'
import { listItem as _listItem } from 'prosemirror-schema-list' import { listItem as _listItem } from 'prosemirror-schema-list'
interface Attr {
[key: string]: number | string
}
const orderedList: NodeSpec = { const orderedList: NodeSpec = {
attrs: { attrs: {
order: { order: {
@ -18,7 +22,7 @@ const orderedList: NodeSpec = {
tag: 'ol', tag: 'ol',
getAttrs: dom => { getAttrs: dom => {
const order = ((dom as HTMLElement).hasAttribute('start') ? (dom as HTMLElement).getAttribute('start') : 1) || 1 const order = ((dom as HTMLElement).hasAttribute('start') ? (dom as HTMLElement).getAttribute('start') : 1) || 1
const attr = { order: +order } const attr: Attr = { order: +order }
const { listStyleType } = (dom as HTMLElement).style const { listStyleType } = (dom as HTMLElement).style
if (listStyleType) attr['listStyleType'] = listStyleType if (listStyleType) attr['listStyleType'] = listStyleType
@ -32,7 +36,7 @@ const orderedList: NodeSpec = {
let style = '' let style = ''
if (listStyleType) style += `list-style-type: ${listStyleType};` if (listStyleType) style += `list-style-type: ${listStyleType};`
const attr = { style } const attr: Attr = { style }
if (order !== 1) attr['start'] = order if (order !== 1) attr['start'] = order
@ -111,7 +115,7 @@ const paragraph: NodeSpec = {
let style = '' let style = ''
if (align && align !== 'left') style += `text-align: ${align};` if (align && align !== 'left') style += `text-align: ${align};`
const attr = { style } const attr: Attr = { style }
if (indent) attr['data-indent'] = indent if (indent) attr['data-indent'] = indent
return ['p', attr, 0] return ['p', attr, 0]

View File

@ -79,7 +79,7 @@ const getRotateElementPoints = (element: RotateElementData, angle: number) => {
* @param direction * @param direction
* @param points * @param points
*/ */
const getOppositePoint = (direction: string, points: ReturnType<typeof getRotateElementPoints>): { left: number; top: number } => { const getOppositePoint = (direction: OperateResizeHandlers, points: ReturnType<typeof getRotateElementPoints>): { left: number; top: number } => {
const oppositeMap = { const oppositeMap = {
[OperateResizeHandlers.RIGHT_BOTTOM]: points.leftTopPoint, [OperateResizeHandlers.RIGHT_BOTTOM]: points.leftTopPoint,
[OperateResizeHandlers.LEFT_BOTTOM]: points.rightTopPoint, [OperateResizeHandlers.LEFT_BOTTOM]: points.rightTopPoint,

View File

@ -53,7 +53,8 @@ const currentDialogComponent = computed(() => {
'pptx': ExportPPTX, 'pptx': ExportPPTX,
'pptist': ExportSpecificFile, 'pptist': ExportSpecificFile,
} }
return dialogMap[dialogForExport.value] || null if (dialogForExport.value) return dialogMap[dialogForExport.value] || null
return null
}) })
</script> </script>

View File

@ -18,7 +18,7 @@
class="shape-clip-item" class="shape-clip-item"
v-for="(item, key) in shapeClipPathOptions" v-for="(item, key) in shapeClipPathOptions"
:key="key" :key="key"
@click="presetImageClip(key)" @click="presetImageClip(key as string)"
> >
<div class="shape" :style="{ clipPath: item.style }"></div> <div class="shape" :style="{ clipPath: item.style }"></div>
</div> </div>

View File

@ -29,14 +29,14 @@
import { ref, watch } from 'vue' import { ref, watch } from 'vue'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { useMainStore, useSlidesStore } from '@/store' import { useMainStore, useSlidesStore } from '@/store'
import { PPTImageElement } from '@/types/slides' import { ImageElementFilterKeys, PPTImageElement } from '@/types/slides'
import useHistorySnapshot from '@/hooks/useHistorySnapshot' import useHistorySnapshot from '@/hooks/useHistorySnapshot'
import { Slider, Switch } from 'ant-design-vue' import { Slider, Switch } from 'ant-design-vue'
interface FilterOption { interface FilterOption {
label: string label: string
key: string key: ImageElementFilterKeys
default: number default: number
value: number value: number
unit: string unit: string
@ -68,7 +68,8 @@ watch(handleElement, () => {
const filters = handleElement.value.filters const filters = handleElement.value.filters
if (filters) { if (filters) {
filterOptions.value = defaultFilters.map(item => { filterOptions.value = defaultFilters.map(item => {
if (filters[item.key] !== undefined) return { ...item, value: parseInt(filters[item.key]) } const filterItem = filters[item.key]
if (filterItem) return { ...item, value: parseInt(filterItem) }
return item return item
}) })
hasFilters.value = true hasFilters.value = true

View File

@ -18,7 +18,7 @@
:width="elementInfo.width" :width="elementInfo.width"
:height="elementInfo.height" :height="elementInfo.height"
:outline="elementInfo.outline" :outline="elementInfo.outline"
:createPath="clipShape.createPath" :createPath="clipShape.createPath!"
/> />
</div> </div>
</template> </template>

View File

@ -1,11 +1,12 @@
import { computed, Ref } from 'vue' import { computed, Ref } from 'vue'
import { ImageElementFilters } from '@/types/slides' import { ImageElementFilters, ImageElementFilterKeys } from '@/types/slides'
export default (filters: Ref<ImageElementFilters | undefined>) => { export default (filters: Ref<ImageElementFilters | undefined>) => {
const filter = computed(() => { const filter = computed(() => {
if (!filters.value) return '' if (!filters.value) return ''
let filter = '' let filter = ''
for (const key of Object.keys(filters.value)) { const keys = Object.keys(filters.value) as ImageElementFilterKeys[]
for (const key of keys) {
filter += `${key}(${filters.value[key]}) ` filter += `${key}(${filters.value[key]}) `
} }
return filter return filter

View File

@ -45,7 +45,7 @@ const pathMap = {
dot: 'm0 5a5 5 0 1 0 10 0a5 5 0 1 0 -10 0z', dot: 'm0 5a5 5 0 1 0 10 0a5 5 0 1 0 -10 0z',
arrow: 'M0,0 L10,5 0,10 Z', arrow: 'M0,0 L10,5 0,10 Z',
} }
const rotateMap = { const rotateMap: { [key: string]: number } = {
'arrow-start': 180, 'arrow-start': 180,
'arrow-end': 0, 'arrow-end': 0,
} }

View File

@ -20,6 +20,7 @@ import emitter, { EmitterEvents, RichTextAction, RichTextCommand } from '@/utils
import { alignmentCommand } from '@/utils/prosemirror/commands/setTextAlign' import { alignmentCommand } from '@/utils/prosemirror/commands/setTextAlign'
import { indentCommand } from '@/utils/prosemirror/commands/setTextIndent' import { indentCommand } from '@/utils/prosemirror/commands/setTextIndent'
import { toggleList } from '@/utils/prosemirror/commands/toggleList' import { toggleList } from '@/utils/prosemirror/commands/toggleList'
import { TextFormatPainterKeys } from '@/types/edit'
const props = defineProps({ const props = defineProps({
elementId: { elementId: {
@ -242,10 +243,11 @@ const handleMouseup = () => {
if (!textFormatPainter.value) return if (!textFormatPainter.value) return
const actions: RichTextAction[] = [{ command: 'clear' }] const actions: RichTextAction[] = [{ command: 'clear' }]
for (const key of Object.keys(textFormatPainter.value)) { for (const key of Object.keys(textFormatPainter.value) as TextFormatPainterKeys[]) {
const command = key const command = key
const value = textFormatPainter.value[key] const value = textFormatPainter.value[key]
if (value) actions.push({ command, value }) if (value === true) actions.push({ command })
else if (value) actions.push({ command, value })
} }
execCommand({ action: actions }) execCommand({ action: actions })
mainStore.setTextFormatPainter(null) mainStore.setTextFormatPainter(null)