mirror of
https://github.com/pipipi-pikachu/PPTist.git
synced 2025-04-15 02:20:00 +08:00
feat: 支持形状格式刷
This commit is contained in:
parent
697e9a2300
commit
60e4fbd4d0
@ -115,7 +115,8 @@ npm run serve
|
|||||||
- 阴影
|
- 阴影
|
||||||
- 透明度
|
- 透明度
|
||||||
- 翻转
|
- 翻转
|
||||||
- 编辑文字
|
- 形状格式刷
|
||||||
|
- 编辑文字(支持富文本,与文字元素的富文本编辑功能近似)
|
||||||
#### 线条
|
#### 线条
|
||||||
- 颜色
|
- 颜色
|
||||||
- 宽度
|
- 宽度
|
||||||
|
27
src/hooks/useShapeFormatPainter.ts
Normal file
27
src/hooks/useShapeFormatPainter.ts
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
},
|
},
|
||||||
|
@ -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
|
||||||
|
}
|
@ -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 })
|
||||||
|
@ -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 })
|
||||||
|
@ -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%;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user