mirror of
https://github.com/pipipi-pikachu/PPTist.git
synced 2025-04-15 02:20:00 +08:00
refactor: 类型补充、优化
This commit is contained in:
parent
e666c30c19
commit
2c22196501
@ -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 canvas = document.createElement('canvas')
|
||||
|
@ -1,4 +1,4 @@
|
||||
export const ELEMENT_TYPE_ZH = {
|
||||
export const ELEMENT_TYPE_ZH: { [key: string]: string } = {
|
||||
text: '文本',
|
||||
image: '图片',
|
||||
shape: '形状',
|
||||
@ -10,7 +10,7 @@ export const ELEMENT_TYPE_ZH = {
|
||||
latex: '公式',
|
||||
}
|
||||
|
||||
export const MIN_SIZE = {
|
||||
export const MIN_SIZE: { [key: string]: number } = {
|
||||
text: 20,
|
||||
image: 20,
|
||||
shape: 15,
|
||||
|
@ -14,7 +14,17 @@ export const enum ClipPaths {
|
||||
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: {
|
||||
name: '矩形',
|
||||
type: ClipPathTypes.RECT,
|
||||
|
@ -5,6 +5,10 @@ import { ElementAlignCommands } from '@/types/edit'
|
||||
import { getElementListRange, getRectRotatedOffset } from '@/utils/element'
|
||||
import useHistorySnapshot from './useHistorySnapshot'
|
||||
|
||||
interface RangeMap {
|
||||
[id: string]: ReturnType<typeof getElementListRange>
|
||||
}
|
||||
|
||||
export default () => {
|
||||
const slidesStore = useSlidesStore()
|
||||
const { activeElementIdList, activeElementList } = storeToRefs(useMainStore())
|
||||
@ -21,7 +25,7 @@ export default () => {
|
||||
const elementList: PPTElement[] = JSON.parse(JSON.stringify(currentSlide.value.elements))
|
||||
|
||||
// 如果所选择的元素为组合元素的成员,需要计算该组合的整体范围
|
||||
const groupElementRangeMap = {}
|
||||
const groupElementRangeMap: RangeMap = {}
|
||||
for (const activeElement of activeElementList.value) {
|
||||
if (activeElement.groupId && !groupElementRangeMap[activeElement.groupId]) {
|
||||
const groupElements = activeElementList.value.filter(item => item.groupId === activeElement.groupId)
|
||||
|
@ -88,7 +88,7 @@ export default () => {
|
||||
let indent = 0
|
||||
|
||||
const slices: pptxgen.TextProps[] = []
|
||||
const parse = (obj: AST[], baseStyleObj = {}) => {
|
||||
const parse = (obj: AST[], baseStyleObj: { [key: string]: string } = {}) => {
|
||||
|
||||
for (const item of obj) {
|
||||
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'] === '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-style']) options.italic = styleObj['font-style'] === 'italic'
|
||||
if (styleObj['font-family']) options.fontFace = styleObj['font-family']
|
||||
|
@ -46,7 +46,7 @@ export default () => {
|
||||
// 创建原颜色与新颜色的对应关系表
|
||||
const createSlideThemeColorMap = (slide: Slide, newColors: string[]): { [key: string]: string } => {
|
||||
const oldColors = getSlideAllColors(slide)
|
||||
const themeColorMap = {}
|
||||
const themeColorMap: { [key: string]: string } = {}
|
||||
|
||||
if (oldColors.length > newColors.length) {
|
||||
const analogous = tinycolor(newColors[0]).analogous(oldColors.length - newColors.length + 10)
|
||||
|
@ -2,6 +2,10 @@ import { Directive, DirectiveBinding } from 'vue'
|
||||
|
||||
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 handler = binding.value
|
||||
|
||||
@ -13,14 +17,14 @@ const clickListener = (el: HTMLElement, event: MouseEvent, binding: DirectiveBin
|
||||
}
|
||||
|
||||
const ClickOutsideDirective: Directive = {
|
||||
mounted(el: HTMLElement, binding) {
|
||||
mounted(el: CustomHTMLElement, binding) {
|
||||
el[CTX_CLICK_OUTSIDE_HANDLER] = (event: MouseEvent) => clickListener(el, event, binding)
|
||||
setTimeout(() => {
|
||||
document.addEventListener('click', el[CTX_CLICK_OUTSIDE_HANDLER])
|
||||
document.addEventListener('click', el[CTX_CLICK_OUTSIDE_HANDLER]!)
|
||||
}, 0)
|
||||
},
|
||||
|
||||
unmounted(el: HTMLElement) {
|
||||
unmounted(el: CustomHTMLElement) {
|
||||
if (el[CTX_CLICK_OUTSIDE_HANDLER]) {
|
||||
document.removeEventListener('click', el[CTX_CLICK_OUTSIDE_HANDLER])
|
||||
delete el[CTX_CLICK_OUTSIDE_HANDLER]
|
||||
|
@ -3,6 +3,10 @@ import ContextmenuComponent from '@/components/Contextmenu/index.vue'
|
||||
|
||||
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) => {
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
@ -44,12 +48,12 @@ const contextmenuListener = (el: HTMLElement, event: MouseEvent, binding: Direct
|
||||
}
|
||||
|
||||
const ContextmenuDirective: Directive = {
|
||||
mounted(el: HTMLElement, binding) {
|
||||
mounted(el: CustomHTMLElement, binding) {
|
||||
el[CTX_CONTEXTMENU_HANDLER] = (event: MouseEvent) => contextmenuListener(el, event, binding)
|
||||
el.addEventListener('contextmenu', el[CTX_CONTEXTMENU_HANDLER])
|
||||
},
|
||||
|
||||
unmounted(el: HTMLElement) {
|
||||
unmounted(el: CustomHTMLElement) {
|
||||
if (el && el[CTX_CONTEXTMENU_HANDLER]) {
|
||||
el.removeEventListener('contextmenu', el[CTX_CONTEXTMENU_HANDLER])
|
||||
delete el[CTX_CONTEXTMENU_HANDLER]
|
||||
|
@ -114,7 +114,11 @@ import {
|
||||
StopwatchStart,
|
||||
} from '@icon-park/vue-next'
|
||||
|
||||
export const icons = {
|
||||
interface Icons {
|
||||
[key: string]: typeof PlayOne
|
||||
}
|
||||
|
||||
export const icons: Icons = {
|
||||
IconPlayOne: PlayOne,
|
||||
IconFullScreenPlay: FullScreenPlay,
|
||||
IconLock: Lock,
|
||||
|
@ -92,6 +92,8 @@ export interface CreatingLineElement {
|
||||
}
|
||||
export type CreatingElement = CreatingTextElement | CreatingShapeElement | CreatingLineElement
|
||||
|
||||
export type TextFormatPainterKeys = 'bold' | 'em' | 'underline' | 'strikethrough' | 'color' | 'backcolor' | 'fontsize' | 'fontname' | 'align'
|
||||
|
||||
export interface TextFormatPainter {
|
||||
bold?: boolean
|
||||
em?: boolean
|
||||
|
@ -194,6 +194,7 @@ export interface ImageOrShapeFlip {
|
||||
*
|
||||
* 'opacity'?: 不透明度,默认100(%)
|
||||
*/
|
||||
export type ImageElementFilterKeys = 'blur' | 'brightness' | 'contrast' | 'grayscale' | 'saturate' | 'hue-rotate' | 'opacity'
|
||||
export interface ImageElementFilters {
|
||||
'blur'?: string
|
||||
'brightness'?: string
|
||||
|
@ -10,6 +10,10 @@ interface RotatedElementData {
|
||||
rotate: number
|
||||
}
|
||||
|
||||
interface IdMap {
|
||||
[id: string]: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算元素在画布中的矩形范围旋转后的新位置范围
|
||||
* @param element 元素的位置大小和旋转角度信息
|
||||
@ -158,7 +162,7 @@ export const uniqAlignLines = (lines: AlignLine[]) => {
|
||||
* @param slides 页面列表
|
||||
*/
|
||||
export const createSlideIdMap = (slides: Slide[]) => {
|
||||
const slideIdMap = {}
|
||||
const slideIdMap: IdMap = {}
|
||||
for (const slide of slides) {
|
||||
slideIdMap[slide.id] = nanoid(10)
|
||||
}
|
||||
@ -172,8 +176,8 @@ export const createSlideIdMap = (slides: Slide[]) => {
|
||||
* @param elements 元素列表数据
|
||||
*/
|
||||
export const createElementIdMap = (elements: PPTElement[]) => {
|
||||
const groupIdMap = {}
|
||||
const elIdMap = {}
|
||||
const groupIdMap: IdMap = {}
|
||||
const elIdMap: IdMap = {}
|
||||
for (const element of elements) {
|
||||
const groupId = element.groupId
|
||||
if (groupId && !groupIdMap[groupId]) {
|
||||
|
@ -26,7 +26,7 @@ export const hasTerminalParent = (tagName: string, stack: StackItem[]) => {
|
||||
while (currentIndex >= 0) {
|
||||
const parentTagName = stack[currentIndex].tagName
|
||||
if (parentTagName === tagName) break
|
||||
if (tagParents.includes(parentTagName)) return true
|
||||
if (parentTagName && tagParents.includes(parentTagName)) return true
|
||||
currentIndex--
|
||||
}
|
||||
}
|
||||
|
@ -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 closingTagAncestorBreakers = {
|
||||
interface ClosingTagAncestorBreakers {
|
||||
[key: string]: string[]
|
||||
}
|
||||
|
||||
export const closingTagAncestorBreakers: ClosingTagAncestorBreakers = {
|
||||
li: ['ul', 'ol', 'menu'],
|
||||
dt: ['dl'],
|
||||
dd: ['dl'],
|
||||
|
@ -15,8 +15,12 @@ import {
|
||||
splitBlockKeepMarks,
|
||||
} from 'prosemirror-commands'
|
||||
|
||||
interface Keys {
|
||||
[key: string]: Command
|
||||
}
|
||||
|
||||
export const buildKeymap = (schema: Schema) => {
|
||||
const keys = {}
|
||||
const keys: Keys = {}
|
||||
const bind = (key: string, cmd: Command) => keys[key] = cmd
|
||||
|
||||
bind('Alt-ArrowUp', joinUp)
|
||||
|
@ -2,6 +2,10 @@ import { nodes } from 'prosemirror-schema-basic'
|
||||
import { Node, NodeSpec } from 'prosemirror-model'
|
||||
import { listItem as _listItem } from 'prosemirror-schema-list'
|
||||
|
||||
interface Attr {
|
||||
[key: string]: number | string
|
||||
}
|
||||
|
||||
const orderedList: NodeSpec = {
|
||||
attrs: {
|
||||
order: {
|
||||
@ -18,7 +22,7 @@ const orderedList: NodeSpec = {
|
||||
tag: 'ol',
|
||||
getAttrs: dom => {
|
||||
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
|
||||
if (listStyleType) attr['listStyleType'] = listStyleType
|
||||
@ -32,7 +36,7 @@ const orderedList: NodeSpec = {
|
||||
let style = ''
|
||||
if (listStyleType) style += `list-style-type: ${listStyleType};`
|
||||
|
||||
const attr = { style }
|
||||
const attr: Attr = { style }
|
||||
if (order !== 1) attr['start'] = order
|
||||
|
||||
|
||||
@ -111,7 +115,7 @@ const paragraph: NodeSpec = {
|
||||
let style = ''
|
||||
if (align && align !== 'left') style += `text-align: ${align};`
|
||||
|
||||
const attr = { style }
|
||||
const attr: Attr = { style }
|
||||
if (indent) attr['data-indent'] = indent
|
||||
|
||||
return ['p', attr, 0]
|
||||
|
@ -79,7 +79,7 @@ const getRotateElementPoints = (element: RotateElementData, angle: number) => {
|
||||
* @param direction 当前操作的缩放点
|
||||
* @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 = {
|
||||
[OperateResizeHandlers.RIGHT_BOTTOM]: points.leftTopPoint,
|
||||
[OperateResizeHandlers.LEFT_BOTTOM]: points.rightTopPoint,
|
||||
|
@ -53,7 +53,8 @@ const currentDialogComponent = computed(() => {
|
||||
'pptx': ExportPPTX,
|
||||
'pptist': ExportSpecificFile,
|
||||
}
|
||||
return dialogMap[dialogForExport.value] || null
|
||||
if (dialogForExport.value) return dialogMap[dialogForExport.value] || null
|
||||
return null
|
||||
})
|
||||
</script>
|
||||
|
||||
|
@ -18,7 +18,7 @@
|
||||
class="shape-clip-item"
|
||||
v-for="(item, key) in shapeClipPathOptions"
|
||||
:key="key"
|
||||
@click="presetImageClip(key)"
|
||||
@click="presetImageClip(key as string)"
|
||||
>
|
||||
<div class="shape" :style="{ clipPath: item.style }"></div>
|
||||
</div>
|
||||
|
@ -29,14 +29,14 @@
|
||||
import { ref, watch } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useMainStore, useSlidesStore } from '@/store'
|
||||
import { PPTImageElement } from '@/types/slides'
|
||||
import { ImageElementFilterKeys, PPTImageElement } from '@/types/slides'
|
||||
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
|
||||
|
||||
import { Slider, Switch } from 'ant-design-vue'
|
||||
|
||||
interface FilterOption {
|
||||
label: string
|
||||
key: string
|
||||
key: ImageElementFilterKeys
|
||||
default: number
|
||||
value: number
|
||||
unit: string
|
||||
@ -68,7 +68,8 @@ watch(handleElement, () => {
|
||||
const filters = handleElement.value.filters
|
||||
if (filters) {
|
||||
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
|
||||
})
|
||||
hasFilters.value = true
|
||||
|
@ -18,7 +18,7 @@
|
||||
:width="elementInfo.width"
|
||||
:height="elementInfo.height"
|
||||
:outline="elementInfo.outline"
|
||||
:createPath="clipShape.createPath"
|
||||
:createPath="clipShape.createPath!"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -1,11 +1,12 @@
|
||||
import { computed, Ref } from 'vue'
|
||||
import { ImageElementFilters } from '@/types/slides'
|
||||
import { ImageElementFilters, ImageElementFilterKeys } from '@/types/slides'
|
||||
|
||||
export default (filters: Ref<ImageElementFilters | undefined>) => {
|
||||
const filter = computed(() => {
|
||||
if (!filters.value) return ''
|
||||
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]}) `
|
||||
}
|
||||
return filter
|
||||
|
@ -45,7 +45,7 @@ const pathMap = {
|
||||
dot: 'm0 5a5 5 0 1 0 10 0a5 5 0 1 0 -10 0z',
|
||||
arrow: 'M0,0 L10,5 0,10 Z',
|
||||
}
|
||||
const rotateMap = {
|
||||
const rotateMap: { [key: string]: number } = {
|
||||
'arrow-start': 180,
|
||||
'arrow-end': 0,
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ import emitter, { EmitterEvents, RichTextAction, RichTextCommand } from '@/utils
|
||||
import { alignmentCommand } from '@/utils/prosemirror/commands/setTextAlign'
|
||||
import { indentCommand } from '@/utils/prosemirror/commands/setTextIndent'
|
||||
import { toggleList } from '@/utils/prosemirror/commands/toggleList'
|
||||
import { TextFormatPainterKeys } from '@/types/edit'
|
||||
|
||||
const props = defineProps({
|
||||
elementId: {
|
||||
@ -242,10 +243,11 @@ const handleMouseup = () => {
|
||||
if (!textFormatPainter.value) return
|
||||
|
||||
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 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 })
|
||||
mainStore.setTextFormatPainter(null)
|
||||
|
Loading…
x
Reference in New Issue
Block a user