Sfoglia il codice sorgente

feat: 添加形状翻转

pipipi-pikachu 5 anni fa
parent
commit
2838426ca9

+ 1 - 0
README.md

@@ -70,6 +70,7 @@ npm run serve
 - 边框
 - 阴影
 - 透明度
+- 翻转
 ### 线条
 - 颜色
 - 宽度

+ 6 - 1
src/types/slides.ts

@@ -41,6 +41,10 @@ export interface PPTTextElement {
   shadow?: PPTElementShadow;
 }
 
+export interface ImageOrShapeFlip {
+  x?: number;
+  y?: number;
+}
 export interface ImageElementFilters {
   'blur'?: string;
   'brightness'?: string;
@@ -68,7 +72,7 @@ export interface PPTImageElement {
     range: [[number, number], [number, number]];
     shape: 'rect' | 'roundRect' | 'ellipse' | 'triangle' | 'pentagon' | 'rhombus' | 'star';
   };
-  flip?: { x?: number; y?: number };
+  flip?: ImageOrShapeFlip;
   shadow?: PPTElementShadow;
 }
 
@@ -94,6 +98,7 @@ export interface PPTShapeElement {
   rotate?: number;
   outline?: PPTElementOutline;
   opacity?: number;
+  flip?: ImageOrShapeFlip;
   shadow?: PPTElementShadow;
 }
 

+ 5 - 35
src/views/Editor/Toolbar/ElementStylePanel/ImageStylePanel.vue

@@ -58,20 +58,8 @@
       </template>
       <Button class="full-width-btn"><IconColorFilter class="btn-icon" /> 设置滤镜</Button>
     </Popover>
-    
-    <CheckboxButtonGroup class="row">
-      <CheckboxButton 
-        style="flex: 1;"
-        :checked="flip.x === 180"
-        @click="updateImage({ flip: { x: flip.x === 180 ? 0 : 180, y: flip.y } })"
-      ><IconFlipVertically /> 水平翻转</CheckboxButton>
-      <CheckboxButton 
-        style="flex: 1;"
-        :checked="flip.y === 180"
-        @click="updateImage({ flip: { x: flip.x, y: flip.y === 180 ? 0 : 180 } })"
-      ><IconFlipHorizontally /> 垂直翻转</CheckboxButton>
-    </CheckboxButtonGroup>
-
+  
+    <ElementFlip />
     <Divider />
     <ElementOutline />
     <Divider />
@@ -96,6 +84,7 @@ import useHistorySnapshot from '@/hooks/useHistorySnapshot'
 
 import ElementOutline from '../common/ElementOutline.vue'
 import ElementShadow from '../common/ElementShadow.vue'
+import ElementFlip from '../common/ElementFlip.vue'
 
 interface FilterOption {
   label: string;
@@ -156,6 +145,7 @@ export default defineComponent({
   components: {
     ElementOutline,
     ElementShadow,
+    ElementFlip,
   },
   setup() {
     const store = useStore()
@@ -164,24 +154,11 @@ export default defineComponent({
 
     const clipPanelVisible = ref(false)
 
-    const flip = ref({
-      x: 0,
-      y: 0,
-    })
-
     const filterOptions = ref<FilterOption[]>(JSON.parse(JSON.stringify(defaultFilters)))
 
     watch(handleElement, () => {
       if (!handleElement.value || handleElement.value.type !== 'image') return
-
-      if (handleElement.value.flip) {
-        flip.value = {
-          x: handleElement.value.flip.x || 0,
-          y: handleElement.value.flip.y || 0,
-        }
-      }
-      else flip.value = { x: 0, y: 0 }
-
+      
       const filters = handleElement.value.filters
       if (filters) {
         filterOptions.value = defaultFilters.map(item => {
@@ -194,11 +171,6 @@ export default defineComponent({
 
     const { addHistorySnapshot } = useHistorySnapshot()
 
-    const updateImage = (props: Partial<PPTImageElement>) => {
-      store.commit(MutationTypes.UPDATE_ELEMENT, { id: handleElement.value.id, props })
-      addHistorySnapshot()
-    }
-
     const updateFilter = (filter: FilterOption, value: number) => {
       const originFilters = handleElement.value.filters || {}
       const filters = { ...originFilters, [filter.key]: `${value}${filter.unit}` }
@@ -337,9 +309,7 @@ export default defineComponent({
       shapeClipPathOptions,
       ratioClipOptions,
       filterOptions,
-      flip,
       handleElement,
-      updateImage,
       updateFilter,
       clipImage,
       presetImageClip,

+ 3 - 0
src/views/Editor/Toolbar/ElementStylePanel/ShapeStylePanel.vue

@@ -68,6 +68,7 @@
       </div>
     </template>
 
+    <ElementFlip />
     <Divider />
     <ElementOutline />
     <Divider />
@@ -86,6 +87,7 @@ import useHistorySnapshot from '@/hooks/useHistorySnapshot'
 import ElementOpacity from '../common/ElementOpacity.vue'
 import ElementOutline from '../common/ElementOutline.vue'
 import ElementShadow from '../common/ElementShadow.vue'
+import ElementFlip from '../common/ElementFlip.vue'
 import ColorButton from '../common/ColorButton.vue'
 
 export default defineComponent({
@@ -94,6 +96,7 @@ export default defineComponent({
     ElementOpacity,
     ElementOutline,
     ElementShadow,
+    ElementFlip,
     ColorButton,
   },
   setup() {

+ 70 - 0
src/views/Editor/Toolbar/common/ElementFlip.vue

@@ -0,0 +1,70 @@
+<template>
+  <div class="element-flip">
+    <CheckboxButtonGroup class="row">
+      <CheckboxButton 
+        style="flex: 1;"
+        :checked="flip.x === 180"
+        @click="updateFlip({ x: flip.x === 180 ? 0 : 180, y: flip.y })"
+      ><IconFlipVertically /> 水平翻转</CheckboxButton>
+      <CheckboxButton 
+        style="flex: 1;"
+        :checked="flip.y === 180"
+        @click="updateFlip({ x: flip.x, y: flip.y === 180 ? 0 : 180 })"
+      ><IconFlipHorizontally /> 垂直翻转</CheckboxButton>
+    </CheckboxButtonGroup>
+  </div>
+</template>
+
+<script lang="ts">
+import { computed, defineComponent, ref, watch } from 'vue'
+import { MutationTypes, useStore } from '@/store'
+import { PPTImageElement, PPTShapeElement } from '@/types/slides'
+import useHistorySnapshot from '@/hooks/useHistorySnapshot'
+
+export default defineComponent({
+  name: 'element-flip',
+  setup() {
+    const store = useStore()
+    const handleElement = computed<PPTImageElement | PPTShapeElement>(() => store.getters.handleElement)
+
+    const flip = ref({
+      x: 0,
+      y: 0,
+    })
+
+    watch(handleElement, () => {
+      if (!handleElement.value || !['image', 'shape'].includes(handleElement.value.type)) return
+
+      if (handleElement.value.flip) {
+        flip.value = {
+          x: handleElement.value.flip.x || 0,
+          y: handleElement.value.flip.y || 0,
+        }
+      }
+      else flip.value = { x: 0, y: 0 }
+    }, { deep: true, immediate: true })
+
+    const { addHistorySnapshot } = useHistorySnapshot()
+
+    const updateFlip = (value: number) => {
+      const props = { flip: value }
+      store.commit(MutationTypes.UPDATE_ELEMENT, { id: handleElement.value.id, props })
+      addHistorySnapshot()
+    }
+
+    return {
+      flip,
+      updateFlip,
+    }
+  },
+})
+</script>
+
+<style lang="scss" scoped>
+.row {
+  width: 100%;
+  display: flex;
+  align-items: center;
+  margin-bottom: 10px;
+}
+</style>

+ 6 - 11
src/views/components/element/ImageElement/BaseImageElement.vue

@@ -13,7 +13,7 @@
       class="element-content"
       :style="{
         filter: shadowStyle ? `drop-shadow(${shadowStyle})` : '',
-        transform: flip,
+        transform: flipStyle,
       }"
     >
       <ImageRectOutline
@@ -66,6 +66,7 @@ import ImageEllipseOutline from './ImageEllipseOutline.vue'
 import ImagePolygonOutline from './ImagePolygonOutline.vue'
 
 import useElementShadow from '@/views/components/element/hooks/useElementShadow'
+import useElementFlip from '@/views/components/element/hooks/useElementFlip'
 
 export default defineComponent({
   name: 'base-element-image',
@@ -122,23 +123,17 @@ export default defineComponent({
       return filter
     })
 
-    const flip = computed(() => {
-      if (!props.elementInfo.flip) return ''
-      const { x, y } = props.elementInfo.flip
-      if (x && y) return `rotateX(${x}deg) rotateY(${y}deg)`
-      else if (x) return `rotateX(${x}deg)`
-      else if (y) return `rotateY(${y}deg)`
-      return ''
-    })
-
     const shadow = computed(() => props.elementInfo.shadow)
     const { shadowStyle } = useElementShadow(shadow)
 
+    const flip = computed(() => props.elementInfo.flip)
+    const { flipStyle } = useElementFlip(flip)
+
     return {
       imgPosition,
       clipShape,
       filter,
-      flip,
+      flipStyle,
       shadowStyle,
     }
   },

+ 6 - 11
src/views/components/element/ImageElement/index.vue

@@ -28,7 +28,7 @@
       v-contextmenu="contextmenus"
       :style="{
         filter: shadowStyle ? `drop-shadow(${shadowStyle})` : '',
-        transform: flip,
+        transform: flipStyle,
       }"
     >
       <ImageRectOutline
@@ -77,6 +77,7 @@ import { PPTImageElement } from '@/types/slides'
 import { ContextmenuItem } from '@/components/Contextmenu/types'
 import { CLIPPATHS, ClipPathTypes } from '@/configs/imageClip'
 import useElementShadow from '@/views/components/element/hooks/useElementShadow'
+import useElementFlip from '@/views/components/element/hooks/useElementFlip'
 
 import ImageRectOutline from './ImageRectOutline.vue'
 import ImageEllipseOutline from './ImageEllipseOutline.vue'
@@ -113,6 +114,9 @@ export default defineComponent({
     const shadow = computed(() => props.elementInfo.shadow)
     const { shadowStyle } = useElementShadow(shadow)
 
+    const flip = computed(() => props.elementInfo.flip)
+    const { flipStyle } = useElementFlip(flip)
+
     const handleSelectElement = (e: MouseEvent) => {
       if (props.elementInfo.lock) return
       e.stopPropagation()
@@ -159,15 +163,6 @@ export default defineComponent({
       return filter
     })
 
-    const flip = computed(() => {
-      if (!props.elementInfo.flip) return ''
-      const { x, y } = props.elementInfo.flip
-      if (x && y) return `rotateX(${x}deg) rotateY(${y}deg)`
-      else if (x) return `rotateX(${x}deg)`
-      else if (y) return `rotateY(${y}deg)`
-      return ''
-    })
-
     const clip = (data: ImageClipedEmitData) => {
       store.commit(MutationTypes.SET_CLIPING_IMAGE_ELEMENT_ID, '')
       
@@ -195,7 +190,7 @@ export default defineComponent({
       clipShape,
       imgPosition,
       filter,
-      flip,
+      flipStyle,
     }
   },
 })

+ 6 - 0
src/views/components/element/ShapeElement/BaseShapeElement.vue

@@ -14,6 +14,7 @@
       :style="{
         opacity: elementInfo.opacity,
         filter: shadowStyle ? `drop-shadow(${shadowStyle})` : '',
+        transform: flipStyle,
       }"
     >
       <SvgWrapper 
@@ -55,6 +56,7 @@ import { computed, defineComponent, PropType } from 'vue'
 import { PPTShapeElement } from '@/types/slides'
 import useElementOutline from '@/views/components/element/hooks/useElementOutline'
 import useElementShadow from '@/views/components/element/hooks/useElementShadow'
+import useElementFlip from '@/views/components/element/hooks/useElementFlip'
 
 import GradientDefs from './GradientDefs.vue'
 
@@ -76,11 +78,15 @@ export default defineComponent({
     const shadow = computed(() => props.elementInfo.shadow)
     const { shadowStyle } = useElementShadow(shadow)
 
+    const flip = computed(() => props.elementInfo.flip)
+    const { flipStyle } = useElementFlip(flip)
+
     return {
       shadowStyle,
       outlineWidth,
       outlineStyle,
       outlineColor,
+      flipStyle,
     }
   },
 })

+ 6 - 0
src/views/components/element/ShapeElement/index.vue

@@ -17,6 +17,7 @@
       :style="{
         opacity: elementInfo.opacity,
         filter: shadowStyle ? `drop-shadow(${shadowStyle})` : '',
+        transform: flipStyle,
       }"
     >
       <SvgWrapper 
@@ -59,6 +60,7 @@ import { PPTShapeElement } from '@/types/slides'
 import { ContextmenuItem } from '@/components/Contextmenu/types'
 import useElementOutline from '@/views/components/element/hooks/useElementOutline'
 import useElementShadow from '@/views/components/element/hooks/useElementShadow'
+import useElementFlip from '@/views/components/element/hooks/useElementFlip'
 
 import GradientDefs from './GradientDefs.vue'
 
@@ -94,12 +96,16 @@ export default defineComponent({
     const shadow = computed(() => props.elementInfo.shadow)
     const { shadowStyle } = useElementShadow(shadow)
 
+    const flip = computed(() => props.elementInfo.flip)
+    const { flipStyle } = useElementFlip(flip)
+
     return {
       handleSelectElement,
       shadowStyle,
       outlineWidth,
       outlineStyle,
       outlineColor,
+      flipStyle,
     }
   },
 })

+ 21 - 0
src/views/components/element/hooks/useElementFlip.ts

@@ -0,0 +1,21 @@
+import { ref, Ref, watchEffect } from 'vue'
+import { ImageOrShapeFlip } from '@/types/slides'
+
+export default (flip: Ref<ImageOrShapeFlip | undefined>) => {
+  const flipStyle = ref('')
+
+  watchEffect(() => {
+    if (flip.value) {
+      const { x, y } = flip.value
+      if (x && y) flipStyle.value = `rotateX(${x}deg) rotateY(${y}deg)`
+      else if (x) flipStyle.value = `rotateX(${x}deg)`
+      else if (y) flipStyle.value = `rotateY(${y}deg)`
+      else flipStyle.value = ''
+    }
+    else flipStyle.value = ''
+  })
+
+  return {
+    flipStyle,
+  }
+}