feat: 支持形状格式刷

This commit is contained in:
pipipi-pikachu 2023-10-11 22:43:27 +08:00
parent 697e9a2300
commit 60e4fbd4d0
8 changed files with 83 additions and 12 deletions

View File

@ -115,7 +115,8 @@ npm run serve
- 阴影 - 阴影
- 透明度 - 透明度
- 翻转 - 翻转
- 编辑文字 - 形状格式刷
- 编辑文字(支持富文本,与文字元素的富文本编辑功能近似)
#### 线条 #### 线条
- 颜色 - 颜色
- 宽度 - 宽度

View File

@ -0,0 +1,27 @@
import { storeToRefs } from 'pinia'
import { useMainStore } from '@/store'
import type { PPTShapeElement } from '@/types/slides'
export default () => {
const mainStore = useMainStore()
const { shapeFormatPainter, handleElement } = storeToRefs(mainStore)
const toggleShapeFormatPainter = () => {
const _handleElement = handleElement.value as PPTShapeElement
if (shapeFormatPainter.value) mainStore.setShapeFormatPainter(null)
else {
mainStore.setShapeFormatPainter({
fill: _handleElement.fill,
gradient: _handleElement.gradient,
outline: _handleElement.outline,
opacity: _handleElement.opacity,
shadow: _handleElement.shadow,
})
}
}
return {
toggleShapeFormatPainter,
}
}

View File

@ -5,7 +5,7 @@ export default () => {
const mainStore = useMainStore() const mainStore = useMainStore()
const { richTextAttrs, textFormatPainter } = storeToRefs(mainStore) const { richTextAttrs, textFormatPainter } = storeToRefs(mainStore)
const toggleFormatPainter = () => { const toggleTextFormatPainter = () => {
if (textFormatPainter.value) mainStore.setTextFormatPainter(null) if (textFormatPainter.value) mainStore.setTextFormatPainter(null)
else { else {
mainStore.setTextFormatPainter({ mainStore.setTextFormatPainter({
@ -23,6 +23,6 @@ export default () => {
} }
return { return {
toggleFormatPainter, toggleTextFormatPainter,
} }
} }

View File

@ -1,7 +1,7 @@
import { customAlphabet } from 'nanoid' import { customAlphabet } from 'nanoid'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { ToolbarStates } from '@/types/toolbar' import { ToolbarStates } from '@/types/toolbar'
import type { CreatingElement, TextFormatPainter } from '@/types/edit' import type { CreatingElement, ShapeFormatPainter, TextFormatPainter } from '@/types/edit'
import type { DialogForExportTypes } from '@/types/export' import type { DialogForExportTypes } from '@/types/export'
import { type TextAttrs, defaultRichTextAttrs } from '@/utils/prosemirror/utils' import { type TextAttrs, defaultRichTextAttrs } from '@/utils/prosemirror/utils'
import { SYS_FONTS } from '@/configs/font' import { SYS_FONTS } from '@/configs/font'
@ -34,6 +34,7 @@ export interface MainState {
dialogForExport: DialogForExportTypes dialogForExport: DialogForExportTypes
databaseId: string databaseId: string
textFormatPainter: TextFormatPainter | null textFormatPainter: TextFormatPainter | null
shapeFormatPainter: ShapeFormatPainter | null
showSelectPanel: boolean showSelectPanel: boolean
showSearchPanel: boolean showSearchPanel: boolean
} }
@ -67,6 +68,7 @@ export const useMainStore = defineStore('main', {
dialogForExport: '', // 导出面板 dialogForExport: '', // 导出面板
databaseId, // 标识当前应用的indexedDB数据库ID databaseId, // 标识当前应用的indexedDB数据库ID
textFormatPainter: null, // 文字格式刷 textFormatPainter: null, // 文字格式刷
shapeFormatPainter: null, // 形状格式刷
showSelectPanel: false, // 打开选择面板 showSelectPanel: false, // 打开选择面板
showSearchPanel: false, // 打开查找替换面板 showSearchPanel: false, // 打开查找替换面板
}), }),
@ -183,6 +185,10 @@ export const useMainStore = defineStore('main', {
this.textFormatPainter = textFormatPainter this.textFormatPainter = textFormatPainter
}, },
setShapeFormatPainter(shapeFormatPainter: ShapeFormatPainter | null) {
this.shapeFormatPainter = shapeFormatPainter
},
setSelectPanelState(show: boolean) { setSelectPanelState(show: boolean) {
this.showSelectPanel = show this.showSelectPanel = show
}, },

View File

@ -1,6 +1,6 @@
import type { ShapePoolItem } from '@/configs/shapes' import type { ShapePoolItem } from '@/configs/shapes'
import type { LinePoolItem } from '@/configs/lines' import type { LinePoolItem } from '@/configs/lines'
import type { ImageClipDataRange } from './slides' import type { ImageClipDataRange, PPTElementOutline, PPTElementShadow, ShapeGradient } from './slides'
export enum ElementOrderCommands { export enum ElementOrderCommands {
UP = 'up', UP = 'up',
@ -112,3 +112,11 @@ export interface TextFormatPainter {
fontname?: string fontname?: string
align?: 'left' | 'right' | 'center' align?: 'left' | 'right' | 'center'
} }
export interface ShapeFormatPainter {
fill?: string
gradient?: ShapeGradient
outline?: PPTElementOutline
opacity?: number
shadow?: PPTElementShadow
}

View File

@ -198,7 +198,7 @@
style="flex: 1;" style="flex: 1;"
:checked="!!textFormatPainter" :checked="!!textFormatPainter"
v-tooltip="'格式刷'" v-tooltip="'格式刷'"
@click="toggleFormatPainter()" @click="toggleTextFormatPainter()"
><IconFormatBrush /></CheckboxButton> ><IconFormatBrush /></CheckboxButton>
</ButtonGroup> </ButtonGroup>
@ -234,6 +234,15 @@
<ElementShadow /> <ElementShadow />
<Divider /> <Divider />
<ElementOpacity /> <ElementOpacity />
<Divider />
<div class="row">
<CheckboxButton
style="flex: 1;"
:checked="!!shapeFormatPainter"
@click="toggleShapeFormatPainter()"
><IconFormatBrush /> 形状格式刷</CheckboxButton>
</div>
</div> </div>
</template> </template>
@ -247,6 +256,7 @@ import { type ShapePoolItem, SHAPE_LIST, SHAPE_PATH_FORMULAS } from '@/configs/s
import emitter, { EmitterEvents } from '@/utils/emitter' import emitter, { EmitterEvents } from '@/utils/emitter'
import useHistorySnapshot from '@/hooks/useHistorySnapshot' import useHistorySnapshot from '@/hooks/useHistorySnapshot'
import useTextFormatPainter from '@/hooks/useTextFormatPainter' import useTextFormatPainter from '@/hooks/useTextFormatPainter'
import useShapeFormatPainter from '@/hooks/useShapeFormatPainter'
import ElementOpacity from '../common/ElementOpacity.vue' import ElementOpacity from '../common/ElementOpacity.vue'
import ElementOutline from '../common/ElementOutline.vue' import ElementOutline from '../common/ElementOutline.vue'
@ -269,7 +279,7 @@ import Popover from '@/components/Popover.vue'
const mainStore = useMainStore() const mainStore = useMainStore()
const slidesStore = useSlidesStore() const slidesStore = useSlidesStore()
const { handleElement, handleElementId, richTextAttrs, availableFonts, textFormatPainter } = storeToRefs(mainStore) const { handleElement, handleElementId, richTextAttrs, availableFonts, textFormatPainter, shapeFormatPainter } = storeToRefs(mainStore)
const handleShapeElement = handleElement as Ref<PPTShapeElement> const handleShapeElement = handleElement as Ref<PPTShapeElement>
@ -292,7 +302,8 @@ watch(handleElement, () => {
}, { deep: true, immediate: true }) }, { deep: true, immediate: true })
const { addHistorySnapshot } = useHistorySnapshot() const { addHistorySnapshot } = useHistorySnapshot()
const { toggleFormatPainter } = useTextFormatPainter() const { toggleTextFormatPainter } = useTextFormatPainter()
const { toggleShapeFormatPainter } = useShapeFormatPainter()
const updateElement = (props: Partial<PPTShapeElement>) => { const updateElement = (props: Partial<PPTShapeElement>) => {
slidesStore.updateElement({ id: handleElementId.value, props }) slidesStore.updateElement({ id: handleElementId.value, props })

View File

@ -142,7 +142,7 @@
style="flex: 1;" style="flex: 1;"
:checked="!!textFormatPainter" :checked="!!textFormatPainter"
v-tooltip="'格式刷'" v-tooltip="'格式刷'"
@click="toggleFormatPainter()" @click="toggleTextFormatPainter()"
><IconFormatBrush /></CheckboxButton> ><IconFormatBrush /></CheckboxButton>
<Popover placement="bottom-end" trigger="click" v-model:value="linkPopoverVisible" style="width: 33.33%;"> <Popover placement="bottom-end" trigger="click" v-model:value="linkPopoverVisible" style="width: 33.33%;">
<template #content> <template #content>
@ -424,7 +424,7 @@ const slidesStore = useSlidesStore()
const { handleElement, handleElementId, richTextAttrs, availableFonts, textFormatPainter } = storeToRefs(mainStore) const { handleElement, handleElementId, richTextAttrs, availableFonts, textFormatPainter } = storeToRefs(mainStore)
const { addHistorySnapshot } = useHistorySnapshot() const { addHistorySnapshot } = useHistorySnapshot()
const { toggleFormatPainter } = useTextFormatPainter() const { toggleTextFormatPainter } = useTextFormatPainter()
const updateElement = (props: Partial<PPTTextElement>) => { const updateElement = (props: Partial<PPTTextElement>) => {
slidesStore.updateElement({ id: handleElementId.value, props }) slidesStore.updateElement({ id: handleElementId.value, props })

View File

@ -1,7 +1,10 @@
<template> <template>
<div <div
class="editable-element-shape" class="editable-element-shape"
:class="{ 'lock': elementInfo.lock }" :class="{
'lock': elementInfo.lock,
'format-painter': shapeFormatPainter,
}"
:style="{ :style="{
top: elementInfo.top + 'px', top: elementInfo.top + 'px',
left: elementInfo.left + 'px', left: elementInfo.left + 'px',
@ -24,6 +27,7 @@
}" }"
v-contextmenu="contextmenus" v-contextmenu="contextmenus"
@mousedown="$event => handleSelectElement($event)" @mousedown="$event => handleSelectElement($event)"
@mouseup="execFormatPainter()"
@touchstart="$event => handleSelectElement($event)" @touchstart="$event => handleSelectElement($event)"
@dblclick="startEdit()" @dblclick="startEdit()"
> >
@ -99,7 +103,7 @@ const props = defineProps<{
const mainStore = useMainStore() const mainStore = useMainStore()
const slidesStore = useSlidesStore() const slidesStore = useSlidesStore()
const { handleElementId } = storeToRefs(mainStore) const { handleElementId, shapeFormatPainter } = storeToRefs(mainStore)
const { addHistorySnapshot } = useHistorySnapshot() const { addHistorySnapshot } = useHistorySnapshot()
@ -110,6 +114,17 @@ const handleSelectElement = (e: MouseEvent | TouchEvent, canMove = true) => {
props.selectElement(e, props.elementInfo, canMove) props.selectElement(e, props.elementInfo, canMove)
} }
const execFormatPainter = () => {
if (!shapeFormatPainter.value) return
slidesStore.updateElement({
id: props.elementInfo.id,
props: shapeFormatPainter.value,
})
addHistorySnapshot()
mainStore.setShapeFormatPainter(null)
}
const outline = computed(() => props.elementInfo.outline) const outline = computed(() => props.elementInfo.outline)
const { outlineWidth, outlineColor, strokeDashArray } = useElementOutline(outline) const { outlineWidth, outlineColor, strokeDashArray } = useElementOutline(outline)
@ -175,6 +190,9 @@ const startEdit = () => {
&.lock .element-content { &.lock .element-content {
cursor: default; cursor: default;
} }
&.format-painter .element-content {
cursor: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAVCAYAAACzK0UYAAAABHNCSVQICAgIfAhkiAAAAVRJREFUSInt1DFuwjAYBeCXUrFavgBN9yB6AKR6Bi7AVLrlBpFYgAUpp2i37AysVDIXcCIuwJRMEEYk9LrQDlVQ7EiVOvSt/v1/tmUbeZ7TGMPL5WLgEJLzNE2ptabWmsfjkTeLjTGUUvJ8Pjsjo9GIUkpKKam1voncuTRumn/EKfd1BSQnAF4qhvyK2k1VD88YQ6UUiqJI2+12r2LiPI7j2Xa7rV9yRZbLpRWiAKhGwjW1x3XN828jD9PpVK3X60bAarWy20lZltjv940QwO4KPzbu7oCgLMu/g3Q6ncZI73Q6WSFhGDZGnrIss0LG4zGEEG4ISZUkiW8DDAYDCCEQBIEbAmAWx7GNgSiKAOB1OBzaIyQnSZIom/cRRRG63e7C87z3MAw/fu7Gy/OcRVEgCIK01Wp9/10k37Ism9TdLCHEFzC/zvMPh8Nmt9v5ANDv9/EJD8ykxYswZDkAAAAASUVORK5CYII=) 1 10, default !important;
}
} }
.rotate-wrapper { .rotate-wrapper {
width: 100%; width: 100%;