Browse Source

制作持续添加样式

bill 7 months ago
parent
commit
93ce00982d
36 changed files with 567 additions and 345 deletions
  1. 2 3
      src/core/components/arrow/index.ts
  2. 6 4
      src/core/components/arrow/temp-arrow.vue
  3. 5 3
      src/core/components/circle/temp-circle.vue
  4. 1 1
      src/core/components/icon/icon.vue
  5. 11 10
      src/core/components/icon/temp-icon.vue
  6. 1 1
      src/core/components/image/image.vue
  7. 7 6
      src/core/components/image/temp-image.vue
  8. 6 5
      src/core/components/line/temp-line.vue
  9. 4 3
      src/core/components/polygon/temp-polygon.vue
  10. 0 1
      src/core/components/rectangle/index.ts
  11. 3 3
      src/core/components/rectangle/temp-rectangle.vue
  12. 1 0
      src/core/components/text/index.ts
  13. 3 2
      src/core/components/text/temp-text.vue
  14. 4 3
      src/core/components/triangle/temp-triangle.vue
  15. 11 7
      src/core/helper/active-boxs.vue
  16. 1 1
      src/core/history.ts
  17. 36 4
      src/core/hook/use-animation.ts
  18. 16 10
      src/core/hook/use-component.ts
  19. 18 7
      src/core/hook/use-event.ts
  20. 17 4
      src/core/hook/use-expose.ts
  21. 2 3
      src/core/hook/use-global-vars.ts
  22. 2 2
      src/core/hook/use-layer.ts
  23. 225 208
      src/core/hook/use-mouse-status.ts
  24. 2 2
      src/core/hook/use-snap.ts
  25. 7 3
      src/core/hook/use-transformer.ts
  26. 2 2
      src/core/hook/use-viewer.ts
  27. 6 1
      src/core/propertys/mount.vue
  28. 1 0
      src/core/renderer/renderer.vue
  29. 33 16
      src/core/store/index.ts
  30. 10 1
      src/core/store/store.ts
  31. 14 5
      src/core/viewer.ts
  32. 51 5
      src/example/fuse/views/header/header.vue
  33. 44 13
      src/example/fuse/views/home.vue
  34. 4 3
      src/example/fuse/views/slide/menu.ts
  35. 0 1
      src/example/fuse/views/slide/slide.vue
  36. 11 2
      src/utils/shared.ts

+ 2 - 3
src/core/components/arrow/index.ts

@@ -30,12 +30,11 @@ export const getMouseStyle = (data: ArrowData) => {
 
   return {
     default: {
-      stroke: strokeStatus.pub,
       fill: strokeStatus.pub,
       strokeWidth,
     },
-    hover: { stroke: strokeStatus.hover, fill: strokeStatus.hover },
-    press: { stroke: strokeStatus.press, fill: strokeStatus.press },
+    hover: { fill: strokeStatus.hover },
+    press: { fill: strokeStatus.press },
   };
 };
 

+ 6 - 4
src/core/components/arrow/temp-arrow.vue

@@ -3,7 +3,7 @@
     :config="{
       ...data,
       zIndex: undefined,
-      pointerLength: data.pointerLength,
+      stroke: data.fill,
       pointerWidth: data.pointerLength,
       hitStrokeWidth: 20,
       closed: true,
@@ -18,17 +18,19 @@
 <script lang="ts" setup>
 import { ArrowData, defaultStyle, PointerPosition } from "./index.ts";
 import { DC } from "@/deconstruction.js";
-import { computed, ref, watchEffect } from "vue";
+import { computed, ref } from "vue";
 import { flatPositions } from "@/utils/shared.ts";
 import { Arrow } from "konva/lib/shapes/Arrow";
 
 const props = defineProps<{ data: ArrowData; addMode?: boolean }>();
 const shape = ref<DC<Arrow>>();
 
+const data = computed(() => ({ ...defaultStyle, ...props.data }));
+
 const eConfig = computed(() => {
   const position =
-    "pointerPosition" in props.data
-      ? props.data.pointerPosition!
+    "pointerPosition" in data.value
+      ? data.value.pointerPosition!
       : defaultStyle.pointerPosition;
   const eStart = [PointerPosition.all, PointerPosition.start].includes(position);
   const eEnd = [PointerPosition.all, PointerPosition.end].includes(position);

+ 5 - 3
src/core/components/circle/temp-circle.vue

@@ -11,11 +11,13 @@
 </template>
 
 <script lang="ts" setup>
-import { CircleData } from "./index.ts";
-import { ref } from "vue";
+import { CircleData, defaultStyle } from "./index.ts";
+import { computed, ref } from "vue";
 import { DC } from "@/deconstruction.js";
 import { Circle } from "konva/lib/shapes/Circle";
-defineProps<{ data: CircleData; addMode?: boolean }>();
+
+const props = defineProps<{ data: CircleData; addMode?: boolean }>();
+const data = computed(() => ({ ...defaultStyle, ...props.data }));
 
 const shape = ref<DC<Circle>>();
 defineExpose({

+ 1 - 1
src/core/components/icon/icon.vue

@@ -1,5 +1,5 @@
 <template>
-  <TempIcon :data="tData" :ref="(e: any) => shape = e.shape" />
+  <TempIcon :data="tData" :ref="(e: any) => shape = e?.shape" />
   <PropertyUpdate
     :describes="describes"
     :data="data"

+ 11 - 10
src/core/components/icon/temp-icon.vue

@@ -8,7 +8,7 @@
 </template>
 
 <script lang="ts" setup>
-import { IconData } from "./index.ts";
+import { defaultStyle, IconData } from "./index.ts";
 import { computed, ref, watch } from "vue";
 import { getSvgContent, parseSvgContent, SVGParseResult } from "@/utils/resource.ts";
 import { Group } from "konva/lib/Group";
@@ -18,6 +18,7 @@ import { Transform } from "konva/lib/Util";
 const props = defineProps<{ data: IconData; addMode?: boolean }>();
 const svg = ref<SVGParseResult | null>(null);
 const shape = ref<DC<Group>>();
+const data = computed(() => ({ ...defaultStyle, ...props.data }));
 
 defineExpose({
   get shape() {
@@ -26,7 +27,7 @@ defineExpose({
 });
 
 watch(
-  () => props.data.url,
+  () => data.value.url,
   async (url) => {
     svg.value = null;
     const svgContent = await getSvgContent(url);
@@ -39,7 +40,7 @@ const pathConfigs = computed(() => {
   if (!svg.value) return [];
   return svg.value.paths.map((path) => ({
     ...path,
-    ...props.data,
+    ...data.value,
     zIndex: undefined,
     offset: { x: svg.value!.x, y: svg.value!.y },
   }));
@@ -47,8 +48,8 @@ const pathConfigs = computed(() => {
 
 const initDecMat = computed(() => {
   if (!svg.value) return null;
-  let w = props.data.width;
-  let h = props.data.height;
+  let w = data.value.width;
+  let h = data.value.height;
   w = w || svg.value.width || 0;
   h = h || svg.value.height || 0;
 
@@ -65,7 +66,7 @@ const initDecMat = computed(() => {
 
 const groupConfig = computed(() => {
   return {
-    ...new Transform(props.data.mat).decompose(),
+    ...new Transform(data.value.mat).decompose(),
     opacity: props.addMode ? 0.3 : 1,
   };
 });
@@ -73,11 +74,11 @@ const groupConfig = computed(() => {
 const rectConfig = computed(() => {
   if (!svg.value) return null;
   return {
-    fill: props.data.coverFill,
+    fill: data.value.coverFill,
     id: "rep",
-    stroke: props.data.coverStroke,
-    opacity: props.data.coverOpcatiy,
-    strokeWidth: props.data.coverStrokeWidth,
+    stroke: data.value.coverStroke,
+    opacity: data.value.coverOpcatiy,
+    strokeWidth: data.value.coverStrokeWidth,
     width: svg.value.width,
     height: svg.value.height,
   };

+ 1 - 1
src/core/components/image/image.vue

@@ -1,5 +1,5 @@
 <template>
-  <TempImage :data="tData" :ref="(e: any) => shape = e.shape" />
+  <TempImage :data="tData" :ref="(e: any) => shape = e?.shape" />
   <PropertyUpdate
     :describes="describes"
     :data="data"

+ 7 - 6
src/core/components/image/temp-image.vue

@@ -12,7 +12,7 @@
 </template>
 
 <script lang="ts" setup>
-import { ImageData } from "./index.ts";
+import { defaultStyle, ImageData } from "./index.ts";
 import { computed, ref, watch } from "vue";
 import { getImage } from "@/utils/resource.ts";
 import { useResize } from "@/core/hook/use-event.ts";
@@ -21,6 +21,7 @@ import { Group } from "konva/lib/Group";
 import { DC } from "@/deconstruction.js";
 
 const props = defineProps<{ data: ImageData; addMode?: boolean }>();
+const data = computed(() => ({ ...defaultStyle, ...props.data }));
 const image = ref<HTMLImageElement | null>(null);
 const shape = ref<DC<Group>>();
 
@@ -31,7 +32,7 @@ defineExpose({
 });
 
 watch(
-  () => props.data.url,
+  () => data.value.url,
   async (url) => {
     image.value = null;
     image.value = await getImage(url);
@@ -41,8 +42,8 @@ watch(
 
 const size = useResize();
 const config = computed(() => {
-  let w = props.data.width;
-  let h = props.data.height;
+  let w = data.value.width;
+  let h = data.value.height;
 
   // 认为是百分比
   if (image.value && size.value && (w <= 1 || h <= 1)) {
@@ -56,7 +57,7 @@ const config = computed(() => {
 
   return {
     image: image.value,
-    opacity: props.addMode ? 0.3 : props.data.opacity,
+    opacity: props.addMode ? 0.3 : data.value.opacity,
     width: w,
     height: h,
     offset: {
@@ -68,7 +69,7 @@ const config = computed(() => {
 
 const groupConfig = computed(() => {
   return {
-    ...new Transform(props.data.mat).decompose(),
+    ...new Transform(data.value.mat).decompose(),
   };
 });
 </script>

+ 6 - 5
src/core/components/line/temp-line.vue

@@ -12,18 +12,19 @@
 </template>
 
 <script lang="ts" setup>
-import { LineData } from "./index.ts";
+import { defaultStyle, LineData } from "./index.ts";
 import { flatPositions } from "@/utils/shared.ts";
-import { ref } from "vue";
+import { computed, ref } from "vue";
 import { DC } from "@/deconstruction.js";
 import { Line, LineConfig } from "konva/lib/shapes/Line";
 const props = defineProps<{ data: LineData; addMode?: boolean }>();
+const data = computed(() => ({ ...defaultStyle, ...props.data }));
 
 const hitFunc: LineConfig["hitFunc"] = (con, shape) => {
   con.beginPath();
-  con.moveTo(props.data.points[0].x, props.data.points[0].y);
-  for (let i = 1; i < props.data.points.length; i++) {
-    con.lineTo(props.data.points[i].x, props.data.points[i].y);
+  con.moveTo(data.value.points[0].x, data.value.points[0].y);
+  for (let i = 1; i < data.value.points.length; i++) {
+    con.lineTo(data.value.points[i].x, data.value.points[i].y);
   }
   con.closePath();
   con.fillStrokeShape(shape);

+ 4 - 3
src/core/components/polygon/temp-polygon.vue

@@ -12,12 +12,13 @@
 </template>
 
 <script lang="ts" setup>
-import { PolygonData } from "./index.ts";
+import { defaultStyle, PolygonData } from "./index.ts";
 import { flatPositions } from "@/utils/shared.ts";
-import { ref } from "vue";
+import { computed, ref } from "vue";
 import { DC } from "@/deconstruction.js";
 import { Line } from "konva/lib/shapes/Line";
-defineProps<{ data: PolygonData; addMode?: boolean }>();
+const props = defineProps<{ data: PolygonData; addMode?: boolean }>();
+const data = computed(() => ({ ...defaultStyle, ...props.data }));
 
 const shape = ref<DC<Line>>();
 defineExpose({

+ 0 - 1
src/core/components/rectangle/index.ts

@@ -20,7 +20,6 @@ export const getMouseStyle = (data: RectangleData) => {
   const strokeStatus = getMouseColors(data.stroke || defaultStyle.stroke);
   const strokeWidth = data.strokeWidth || defaultStyle.strokeWidth;
 
-  console.log(fillStatus)
   return {
     default: { fill: fillStatus && fillStatus.pub, stroke: strokeStatus.pub, strokeWidth },
     hover: { fill: fillStatus && fillStatus.hover, stroke: strokeStatus.hover },

+ 3 - 3
src/core/components/rectangle/temp-rectangle.vue

@@ -13,12 +13,12 @@
 
 <script lang="ts" setup>
 import { flatPositions } from "@/utils/shared.ts";
-import { RectangleData } from "./index.ts";
-import { ref, watchEffect } from "vue";
+import { defaultStyle, RectangleData } from "./index.ts";
+import { computed, ref } from "vue";
 import { DC } from "@/deconstruction.js";
 import { Line } from "konva/lib/shapes/Line";
 const props = defineProps<{ data: RectangleData; addMode?: boolean }>();
-
+const data = computed(() => ({ ...defaultStyle, ...props.data }));
 
 const shape = ref<DC<Line>>();
 defineExpose({

+ 1 - 0
src/core/components/text/index.ts

@@ -7,6 +7,7 @@ import { getMouseColors } from "@/utils/colors.ts";
 import { shallowReactive } from "vue";
 
 export { default as Component } from "./text.vue";
+export { default as TempComponent } from "./temp-text.vue";
 
 export const shapeName = "文字";
 export const defaultStyle = {

+ 3 - 2
src/core/components/text/temp-text.vue

@@ -15,13 +15,14 @@
 </template>
 
 <script lang="ts" setup>
-import { TextData } from "./index.ts";
+import { defaultStyle, TextData } from "./index.ts";
 import { computed, ref } from "vue";
 import { DC } from "@/deconstruction.js";
 import { Transform } from "konva/lib/Util";
 import { Text } from "konva/lib/shapes/Text";
 
 const props = defineProps<{ data: TextData; addMode?: boolean }>();
+const data = computed(() => ({ ...defaultStyle, ...props.data }));
 const shape = ref<DC<Text>>();
 defineExpose({
   get shape() {
@@ -29,6 +30,6 @@ defineExpose({
   },
 });
 const matConfig = computed(() => {
-  return new Transform(props.data.mat).decompose();
+  return new Transform(data.value.mat).decompose();
 });
 </script>

+ 4 - 3
src/core/components/triangle/temp-triangle.vue

@@ -13,11 +13,12 @@
 
 <script lang="ts" setup>
 import { DC } from "@/deconstruction.js";
-import { TriangleData } from "./index.ts";
+import { defaultStyle, TriangleData } from "./index.ts";
 import { Line } from "konva/lib/shapes/Line";
-import { ref } from "vue";
+import { computed, ref } from "vue";
 import { flatPositions } from "@/utils/shared.ts";
-defineProps<{ data: TriangleData; addMode?: boolean }>();
+const props = defineProps<{ data: TriangleData; addMode?: boolean }>();
+const data = computed(() => ({ ...defaultStyle, ...props.data }));
 
 const shape = ref<DC<Line>>();
 defineExpose({

+ 11 - 7
src/core/helper/active-boxs.vue

@@ -23,6 +23,7 @@ import { themeColor } from "@/constant/help-style";
 import { useTransformer } from "../hook/use-transformer";
 import { Rect } from "konva/lib/shapes/Rect";
 import { useDashAnimation } from "../hook/use-animation";
+import { useGetComponentData } from "../hook/use-component";
 
 const status = useMouseShapesStatus();
 const boxs = reactive(new WeakMap<EntityShape, IRect>());
@@ -40,18 +41,21 @@ const updateBox = ($shape: EntityShape) => {
 
 type OnCleanup = (cleanupFn: () => void) => void;
 
+const getComponentData = useGetComponentData();
 const transformer = useTransformer();
 const shapeListener = (shape: EntityShape, onCleanup: OnCleanup) => {
+  const update = async () => {
+    await nextTick();
+    updateBox(shape);
+  };
+
   watchEffect((onCleanup) => {
-    transformer.on("transform.helper-box", async () => {
-      await nextTick();
-      updateBox(shape);
-    });
+    transformer.on("transform.helper-box", update);
     onCleanup(() => transformer.off("transform.helper-box"));
   });
-  const handler = () => updateBox(shape);
-  shape.on("bound-change", handler);
-  onCleanup(() => shape.off("bound-change", handler));
+  shape.on("bound-change", update);
+  watch(() => getComponentData(shape).value, update);
+  onCleanup(() => shape.off("bound-change", update));
 };
 
 watchEffect(

+ 1 - 1
src/core/history.ts

@@ -18,9 +18,9 @@ export class SingleHistory<T = any> {
 	}
 
 	private syncState() {
-		if (!this.history) return;
 		this.hasRedo.value = this.history.hasRedo;
 		this.hasUndo.value = this.history.hasUndo;
+		console.log(this.history.hasRedo, this.history.hasUndo)
 	}
 
 	get data() {

+ 36 - 4
src/core/hook/use-animation.ts

@@ -1,10 +1,40 @@
-import { onUnmounted, reactive, ref, Ref, watch, watchEffect } from "vue"
+import { onUnmounted, ref, Ref, watch } from "vue"
 import { Tween, Easing } from '@tweenjs/tween.js'
 import { inRevise, startAnimation } from "@/utils/shared.ts"
 import { Color, RGB } from "three"
-import { DC, EntityShape } from "@/deconstruction"
+import { DC } from "@/deconstruction"
 import { Shape } from "konva/lib/Shape"
 
+export const animation = <T extends object>(origin: T, target: T, update?: (data: T) => void) => {
+  let isStop = false
+  const stop = () => {
+    tw.stop()
+    isStop = true
+  }
+
+  const tw = new Tween(origin)
+  .to(target, 300)
+  .easing(easing)
+  .start()
+  .onComplete(() => {
+    stop()
+  })
+  if (update) {
+    tw.onUpdate(() => {
+      update && update(origin)
+    })
+  }
+
+  const start = () => {
+    requestAnimationFrame(() => {
+      tw.update()
+      isStop || start()
+    })
+  }
+  start()
+  return stop
+}
+
 const pickColors = <T extends object>(origin: T): Record<string, RGB>  => {
   const originColors = {} as any
   for (const [key, val] of Object.entries(origin)) {
@@ -23,7 +53,7 @@ const resumeColors = <T extends object>(origin: T, colors: Record<string, RGB>)
 }
 
 const easing = Easing.Quadratic.InOut
-const animation = <T extends object>(origin: T, target: T) => {
+const animationProperty = <T extends object>(origin: T, target: T, update?: (data: T) => void) => {
   let isStop = false
   const stop = () => {
     numTw.stop()
@@ -68,11 +98,13 @@ const animation = <T extends object>(origin: T, target: T) => {
   const numTw = tw(tOrigin, tTarget)
     .onUpdate(() => {
       Object.assign(origin, tOrigin)
+      update && update(origin)
     })
   
   const colorTw = tw(oColors, tColors)
     .onUpdate(() => {
       resumeColors(origin, oColors)
+      update && update(origin)
     })
 
   const start = () => {
@@ -96,7 +128,7 @@ export const useAniamtion = <T extends object>(data: Ref<T>) => {
     if (isPause) {
       atData.value = newData
     } else {
-      animationStop = animation(atData.value, newData)
+      animationStop = animationProperty(atData.value, newData)
     }
   })
 

+ 16 - 10
src/core/hook/use-component.ts

@@ -1,5 +1,5 @@
 import { DC, EntityShape } from "@/deconstruction";
-import { computed, EmitFn, Ref, ref } from "vue";
+import { computed, EmitFn, isRef, Ref, ref } from "vue";
 import { useAutomaticData } from "./use-automatic-data";
 import { useMouseMigrateTempLayer, useZIndex } from "./use-layer";
 import { useAnimationMouseStyle } from "./use-mouse-status";
@@ -11,7 +11,7 @@ import { onlyId } from "@/utils/shared";
 import { Shape } from "konva/lib/Shape";
 import { Transform } from "konva/lib/Util";
 import { mergeDescribes, PropertyKeys } from "../propertys";
-
+import { useStore } from "../store";
 
 type Emit<T> = EmitFn<{
   updateShape: (value: T) => void;
@@ -27,7 +27,7 @@ export type UseComponentStatusProps<
   props: { data: T };
   getMouseStyle: any;
   defaultStyle: any;
-  propertys: PropertyKeys,
+  propertys: PropertyKeys;
   transformType?: "line" | "mat" | "custom";
   customTransform?: (
     callback: () => void,
@@ -73,7 +73,6 @@ export const useComponentStatus = <S extends EntityShape, T extends DrawItem>(
       emit("updateShape", nData as any)
     );
   } else if (transformType === "custom" && customTransform) {
-    console.log("????");
     customTransform(() => emit("updateShape", data.value as any), shape, data);
   }
 
@@ -103,12 +102,8 @@ export const useComponentStatus = <S extends EntityShape, T extends DrawItem>(
       },
     },
   ];
-  
-  const describes = mergeDescribes(
-    data,
-    defaultStyle,
-    propertys || []
-  );
+
+  const describes = mergeDescribes(data, defaultStyle, propertys || []);
 
   return {
     data,
@@ -119,3 +114,14 @@ export const useComponentStatus = <S extends EntityShape, T extends DrawItem>(
     describes,
   };
 };
+
+export const useGetComponentData = <D extends DrawItem>() => {
+  const store = useStore();
+
+  return (shape: Ref<EntityShape | undefined> | EntityShape | undefined) =>
+    computed(() => {
+      shape = isRef(shape) ? shape.value : shape;
+      if (!shape?.id()) return;
+      return store.getItemById(shape.id()) as D;
+    });
+};

+ 18 - 7
src/core/hook/use-event.ts

@@ -1,5 +1,5 @@
 import { listener } from "../../utils/event.ts";
-import { useStage } from "./use-global-vars.ts";
+import { installGlobalVar, useStage } from "./use-global-vars.ts";
 import { nextTick, ref, watchEffect } from "vue";
 
 export const useListener = <
@@ -28,25 +28,36 @@ export const useListener = <
   });
 };
 
-export const useResize = () => {
-  const size = ref<{ width: number; height: number }>();
+export const useGlobalResize = installGlobalVar(() => {
   const stage = useStage();
+  const size = ref<{ width: number; height: number }>();
   const setSize = () => {
+    const container = stage.value?.getStage().container()
+    if (container) {
+      container.style.setProperty('display', 'none')
+    }
+
     const dom = stage.value!.getNode().container().parentElement!;
     size.value = {
 			width: dom.offsetWidth,
 			height: dom.offsetHeight
 		}
+    if (container) {
+      container.style.removeProperty('display')
+    }
   };
-
   const stopWatch = watchEffect(() => {
     if (stage.value) {
       setSize();
       nextTick(() => stopWatch());
     }
   });
-
   useListener("resize", setSize, window);
 
-  return size;
-};
+  return {
+    updateSize: setSize,
+    size,
+  }
+}, Symbol('resize'))
+
+export const useResize = () => useGlobalResize().size;

+ 17 - 4
src/core/hook/use-expose.ts

@@ -1,7 +1,10 @@
 import { useLayers, useMode, useStage } from "./use-global-vars.ts";
 import { Stage } from "konva/lib/Stage";
-import { reactive } from "vue";
+import { computed, reactive } from "vue";
 import { useInteractiveProps, useInteractiveShapeAPI } from "./use-interactive.ts";
+import { useHistory, useStore } from "../store/index.ts";
+import { useViewer } from "./use-viewer.ts";
+import { useGlobalResize } from "./use-event.ts";
 
 type PickParams<K extends keyof Stage, O extends string> = Stage[K]  extends (...args: any) => any ?  Omit<Required<Parameters<Stage[K]>>[0], O> : never
 
@@ -12,7 +15,10 @@ export const useExpose = () => {
 	const interactiveProps = useInteractiveProps()
 	const stage = useStage()
 	const layers = useLayers()
-	const store = useStage()
+	const store = useStore()
+	const history = useHistory()
+	const viewer = useViewer().viewer
+	const { updateSize } = useGlobalResize()
 
 	const exposeBlob = (config?: PickParams<'toBlob', 'callback'>) => {
 		const $stage = stage.value!.getStage()
@@ -28,13 +34,20 @@ export const useExpose = () => {
 		})
 	}
 
-	return reactive({
+	return {
 		...useInteractiveShapeAPI(),
+		get stage()  {
+			const $store = stage.value!.getStage()
+			return $store
+		},
 		exposeBlob,
 		toggleHit,
+		updateSize,
+		history,
 		store,
 		mode,
+		viewer,
 		presetAdd: interactiveProps,
-	})
+	}
 }
 

+ 2 - 3
src/core/hook/use-global-vars.ts

@@ -122,12 +122,11 @@ export const usePointerPos = installGlobalVar(() => {
 		}))
 	})
 	return pos
-})
+}, Symbol("pointerPos"))
 
 export const useLayers = () => {
 	const stage = useStage()
 	return computed(() => stage.value?.getNode().children as Layer[]) 
 }
 
-export const useTransformIngShapes = installGlobalVar(() => ref<EntityShape[]>([]))
-export const useSetAttrisIngShapes = installGlobalVar(() => ref<EntityShape[]>([]))
+export const useTransformIngShapes = installGlobalVar(() => ref<EntityShape[]>([]), Symbol("transformIngShapes"))

+ 2 - 2
src/core/hook/use-layer.ts

@@ -98,7 +98,7 @@ export const useMouseMigrateTempLayer = (
   );
 };
 
-const useCurrentStaticZIndex = installGlobalVar(() => ref(0));
+const useCurrentStaticZIndex = installGlobalVar(() => ref(0), Symbol('currentStaticZIndex'));
 
 export const useStaticZIndex = (refNum = 1) => {
   const current = useCurrentStaticZIndex();
@@ -152,7 +152,7 @@ const useZIndexsManage = installGlobalVar(() => {
     },
     refresh: setZIndexs,
   };
-});
+}, Symbol('zIndexsManage'));
 
 export const useZIndex = (
   shape: Ref<DC<EntityShape> | undefined>,

+ 225 - 208
src/core/hook/use-mouse-status.ts

@@ -1,223 +1,240 @@
 import { computed, reactive, ref, Ref, watch, watchEffect } from "vue";
 import { DC, EntityShape } from "../../deconstruction";
 import { Shape } from "konva/lib/Shape";
-import { globalWatch, installGlobalVar, useMode, useStage, useTransformIngShapes } from "./use-global-vars.ts";
+import {
+  globalWatch,
+  installGlobalVar,
+  useMode,
+  useStage,
+  useTransformIngShapes,
+} from "./use-global-vars.ts";
 import { Stage } from "konva/lib/Stage";
 import { listener } from "../../utils/event.ts";
 import { inRevise, mergeFuns } from "../../utils/shared.ts";
 import { ComponentValue, DrawItem, ShapeType } from "../components";
 import { shapeTreeContain } from "../../utils/shape.ts";
-import { usePointerIsTransformerInner, useTransformer } from "./use-transformer.ts";
+import {
+  usePointerIsTransformerInner,
+  useTransformer,
+} from "./use-transformer.ts";
 import { Mode } from "@/constant/mode.ts";
 import { useAniamtion } from "./use-animation.ts";
 
-export const useMouseShapesStatus = installGlobalVar(
-	() => {
-		const mode = useMode()
-		const stage = useStage()
-		const listeners = ref([]) as Ref<EntityShape[]>
-		const hovers = ref([]) as Ref<EntityShape[]>
-		const press = ref([]) as Ref<EntityShape[]>
-		const selects = ref([]) as Ref<EntityShape[]>
-		const actives = ref([]) as Ref<EntityShape[]>
-		const transformer = useTransformer()
-		const pointerIsTransformerInner = usePointerIsTransformerInner()
-
-	
-		const init = (stage: Stage) => {
-			console.log('init?')
-			let downTime: number
-			let downTarget: EntityShape | null
-			const inner = new WeakMap<EntityShape, boolean>()
-			
-
-			stage.on('pointerenter.mouse-status', async (ev) => {
-				const target = shapeTreeContain(listeners.value, ev.target)
-				if (!target) return;
-				inner.set(target, true)
-				if (hovers.value.includes(target)) return;
-
-				hovers.value.push(target)
-				const targetLeave = () => {
-					target.off('pointerleave.mouse-status')
-					stage.off('pointermove.mouse-status')
-					stopIncludeWatch! && stopIncludeWatch()
-					const ndx = hovers.value.indexOf(target)
-					if (~ndx) {
-						hovers.value.splice(ndx, 1)
-					}	
-				}
-
-				let stopIncludeWatch: () => void
-				target.on('pointerleave.mouse-status', ev => {
-					const target = shapeTreeContain(listeners.value, ev.target)
-					if (!target) return;
-					inner.set(target, false)
-
-					// TODO: 有可能在transformer上,需要额外检测
-					stopIncludeWatch! && stopIncludeWatch()
-					stopIncludeWatch = watch(transformer.queueShapes, (queueShapes, _, onCleanup) => {
-						if (inner.get(target)) return;
-
-						if (!queueShapes.includes(target) || !pointerIsTransformerInner()) {
-							targetLeave()
-						} else {
-							stage.on('pointermove.mouse-status', () => {
-								if (!inner.get(target) && !pointerIsTransformerInner()) {
-									targetLeave()
-								}
-							})
-							onCleanup(() => stage.off('pointermove.mouse-status'))
-						}
-					}, {immediate: true})
-				})
-			})
-			stage.on('pointerdown.mouse-status', (ev) => {
-				downTime = Date.now()
-				const target = shapeTreeContain(listeners.value, ev.target)
-				if (target && !press.value.includes(target)) {
-					press.value.push(target)
-				}
-				downTarget = target
-			})
-	
-			return mergeFuns(
-				listener(stage.container(), 'pointerup', () => {
-					press.value = []
-					if ( Date.now() - downTime >= 300) return;
-					if (downTarget) {
-						const ndx = selects.value.indexOf(downTarget)
-						if (~ndx) {
-							selects.value.splice(ndx, 1)
-						} else {
-							selects.value.push(downTarget)
-						}
-						actives.value = [downTarget]
-					} else{
-						actives.value = []
-					}
-				}),
-				() => {
-					listeners.value.forEach((shape) => {
-						shape.off('pointerleave.mouse-status')
-					})
-					stage.off('pointerenter.mouse-status pointerdown.mouse-status pointereup.mouse-status pointermove.mouse-status')
-					hovers.value = []
-					actives.value = []
-					press.value = []
-					selects.value = []
-				}
-			)
-		}
-
-		let cleanup: () => void
-		const stopStatusWatch = globalWatch(
-			() => [stage.value?.getStage(), [Mode.update, Mode.viewer].includes(mode.value)] as const, 
-			(current, prev) => {
-				if (inRevise(prev, current)) {
-					cleanup! && cleanup()
-					if (current[0] && current[1]) {
-						cleanup = init(current[0])
-					}
-				}
-			},
-			{ immediate: true }
-		)
-
-		return {
-			var: reactive({hovers, actives, selects, press, listeners}),
-			onDestroy: () => {
-				stopStatusWatch()
-				cleanup && cleanup()
-			}
-		}
-	},
-	Symbol('mouseStatus')
-)
-
-
-export const useMouseShapeStatus = (shape: Ref<DC<EntityShape> | undefined>) => {
-	const status = useMouseShapesStatus()
-	watch(
-		() => shape.value?.getStage(), 
-		(shape, _, onCleanup) => {
-		if (shape) {
-			if (status.listeners.includes(shape)) return;
-			status.listeners.push(shape)
-			onCleanup(() => {
-				for (const key in status) {
-					const k = key as keyof typeof status
-					const ndx = status[k].indexOf(shape)
-					~ndx && status[k].splice(ndx, 1)
-				}
-			})
-		}
-	})
-
-	return computed(() => {
-		const $shape = shape.value?.getStage() as Shape
-		return {
-			hover: status.hovers.includes($shape),
-			active: status.actives.includes($shape),
-			press:  status.press.includes($shape),
-			select: status.selects.includes($shape),
-		}
-	})
-}
-
+export const useMouseShapesStatus = installGlobalVar(() => {
+  const mode = useMode();
+  const stage = useStage();
+  const listeners = ref([]) as Ref<EntityShape[]>;
+  const hovers = ref([]) as Ref<EntityShape[]>;
+  const press = ref([]) as Ref<EntityShape[]>;
+  const selects = ref([]) as Ref<EntityShape[]>;
+  const actives = ref([]) as Ref<EntityShape[]>;
+  const transformer = useTransformer();
+  const pointerIsTransformerInner = usePointerIsTransformerInner();
+
+  const init = (stage: Stage) => {
+    console.log("init?");
+    let downTime: number;
+    let downTarget: EntityShape | null;
+    const inner = new WeakMap<EntityShape, boolean>();
+
+    stage.on("pointerenter.mouse-status", async (ev) => {
+      const target = shapeTreeContain(listeners.value, ev.target);
+      if (!target) return;
+      inner.set(target, true);
+      if (hovers.value.includes(target)) return;
+
+      hovers.value.push(target);
+      const targetLeave = () => {
+        target.off("pointerleave.mouse-status");
+        stage.off("pointermove.mouse-status");
+        stopIncludeWatch! && stopIncludeWatch();
+        const ndx = hovers.value.indexOf(target);
+        if (~ndx) {
+          hovers.value.splice(ndx, 1);
+        }
+      };
+
+      let stopIncludeWatch: () => void;
+      target.on("pointerleave.mouse-status", (ev) => {
+        const target = shapeTreeContain(listeners.value, ev.target);
+        if (!target) return;
+        inner.set(target, false);
+
+        // TODO: 有可能在transformer上,需要额外检测
+        stopIncludeWatch! && stopIncludeWatch();
+        stopIncludeWatch = watch(
+          transformer.queueShapes,
+          (queueShapes, _, onCleanup) => {
+            if (inner.get(target)) return;
+
+            if (!queueShapes.includes(target) || !pointerIsTransformerInner()) {
+              targetLeave();
+            } else {
+              stage.on("pointermove.mouse-status", () => {
+                if (!inner.get(target) && !pointerIsTransformerInner()) {
+                  targetLeave();
+                }
+              });
+              onCleanup(() => stage.off("pointermove.mouse-status"));
+            }
+          },
+          { immediate: true }
+        );
+      });
+    });
+    stage.on("pointerdown.mouse-status", (ev) => {
+      downTime = Date.now();
+      const target = shapeTreeContain(listeners.value, ev.target);
+      if (target && !press.value.includes(target)) {
+        press.value.push(target);
+      }
+      downTarget = target;
+    });
+
+    return mergeFuns(
+      listener(stage.container(), "pointerup", () => {
+        press.value = [];
+        if (Date.now() - downTime >= 300) return;
+        if (downTarget) {
+          const ndx = selects.value.indexOf(downTarget);
+          if (~ndx) {
+            selects.value.splice(ndx, 1);
+          } else {
+            selects.value.push(downTarget);
+          }
+          actives.value = [downTarget];
+        } else {
+          actives.value = [];
+        }
+      }),
+      () => {
+        listeners.value.forEach((shape) => {
+          shape.off("pointerleave.mouse-status");
+        });
+        stage.off(
+          "pointerenter.mouse-status pointerdown.mouse-status pointereup.mouse-status pointermove.mouse-status"
+        );
+        hovers.value = [];
+        actives.value = [];
+        press.value = [];
+        selects.value = [];
+      }
+    );
+  };
+
+  let cleanup: () => void;
+  const stopStatusWatch = globalWatch(
+    () =>
+      [
+        stage.value?.getStage(),
+        [Mode.update, Mode.viewer].includes(mode.value),
+      ] as const,
+    (current, prev) => {
+      if (inRevise(prev, current)) {
+        cleanup! && cleanup();
+        if (current[0] && current[1]) {
+          cleanup = init(current[0]);
+        }
+      }
+    },
+    { immediate: true }
+  );
+
+  return {
+    var: reactive({ hovers, actives, selects, press, listeners }),
+    onDestroy: () => {
+      stopStatusWatch();
+      cleanup && cleanup();
+    },
+  };
+}, Symbol("mouseStatus"));
+
+export const useMouseShapeStatus = (
+  shape: Ref<DC<EntityShape> | undefined>
+) => {
+  const status = useMouseShapesStatus();
+  watch(
+    () => shape.value?.getStage(),
+    (shape, _, onCleanup) => {
+      if (shape) {
+        if (status.listeners.includes(shape)) return;
+        status.listeners.push(shape);
+        onCleanup(() => {
+          for (const key in status) {
+            const k = key as keyof typeof status;
+            const ndx = status[k].indexOf(shape);
+            ~ndx && status[k].splice(ndx, 1);
+          }
+        });
+      }
+    }
+  );
+
+  return computed(() => {
+    const $shape = shape.value?.getStage() as Shape;
+    return {
+      hover: status.hovers.includes($shape),
+      active: status.actives.includes($shape),
+      press: status.press.includes($shape),
+      select: status.selects.includes($shape),
+    };
+  });
+};
 
 type MouseStyleProps<T extends ShapeType> = {
-	shape?: Ref<DC<EntityShape> | undefined>
-	// getMouseStyle: ComponentValue<T, 'getMouseStyle'>
-	data: Ref<DrawItem<T>>
-	getMouseStyle: any
-}
-type ValueOf<T> = T[keyof T]
+  shape?: Ref<DC<EntityShape> | undefined>;
+  getMouseStyle: ComponentValue<T, "getMouseStyle">;
+  data: Ref<DrawItem<T>>;
+};
 export const useMouseStyle = <T extends ShapeType>(
-	props: MouseStyleProps<T>, 
+  props: MouseStyleProps<T>
 ) => {
-	const shape = props.shape || ref()
-	const status = useMouseShapeStatus(shape)
-	const transformIngShapes = useTransformIngShapes()
-	const mouseStyle = computed(() => {
-		return props.getMouseStyle(props.data.value)
-	})
-	const getStyle = () => {
-		const styleMap = new Map([[mouseStyle.value.default as any, true]])
-		if ('hover' in mouseStyle.value) {
-			styleMap.set(mouseStyle.value.hover, status.value.hover)
-		}
-		if ('press' in mouseStyle.value) {
-			styleMap.set(mouseStyle.value.press, status.value.press)
-		}
-		if ('focus' in mouseStyle.value) {
-			styleMap.set(mouseStyle.value.focus, status.value.active)
-		}
-		if ( 'drag' in mouseStyle.value && transformIngShapes.value.includes(shape.value?.getNode()!)) {
-			styleMap.set(mouseStyle.value.drag, true)
-		}
-		// if ('select' in props.style) {
-		// 	styleMap.set(props.style.select, status.value.select)
-		// }
-
-		const finalStyle = {} as ValueOf<ComponentValue<T, 'style'>>
-		for (const [style, use] of styleMap.entries()) {
-			use && Object.assign(finalStyle as any, style)
-		}
-		return finalStyle
-	}
-	const style = ref<ValueOf<ComponentValue<T, 'style'>>>()
-	watchEffect(() => {
-		const newStyle = getStyle()
-		if (inRevise(newStyle, style.value)) {
-			style.value = newStyle
-		}
-	})
-	return { currentStyle: style, status, shape }
-}
-
-export const useAnimationMouseStyle = <T extends ShapeType>(props: MouseStyleProps<T>) => {
-	const { currentStyle } = useMouseStyle(props);
-	return useAniamtion(currentStyle as any);
-	// return [currentStyle]
-}
+  const shape = props.shape || ref();
+  const status = useMouseShapeStatus(shape);
+  const transformIngShapes = useTransformIngShapes();
+  const mouseStyle = computed(() => {
+    return props.getMouseStyle(props.data.value as any) as any;
+  });
+  const getStyle = () => {
+    const styleMap = new Map([[mouseStyle.value.default, true]]);
+    if ("hover" in mouseStyle.value) {
+      styleMap.set(mouseStyle.value.hover, status.value.hover);
+    }
+    if ("press" in mouseStyle.value) {
+      styleMap.set(mouseStyle.value.press, status.value.press);
+    }
+    if ("focus" in mouseStyle.value) {
+      styleMap.set(mouseStyle.value.focus, status.value.active);
+    }
+    if (
+      "drag" in mouseStyle.value &&
+      transformIngShapes.value.includes(shape.value?.getNode()!)
+    ) {
+      styleMap.set(mouseStyle.value.drag, true);
+    }
+    // if ('select' in props.style) {
+    // 	styleMap.set(props.style.select, status.value.select)
+    // }
+
+    const finalStyle = {};
+    for (const [style, use] of styleMap.entries()) {
+      use && Object.assign(finalStyle as any, style);
+    }
+    return finalStyle;
+  };
+  const style = ref();
+  watchEffect(() => {
+    const newStyle = getStyle();
+    if (inRevise(newStyle, style.value)) {
+      style.value = newStyle;
+    }
+  });
+  return { currentStyle: style, status, shape };
+};
+
+export const useAnimationMouseStyle = <T extends ShapeType>(
+  props: MouseStyleProps<T>
+) => {
+  const { currentStyle } = useMouseStyle(props);
+  return useAniamtion(currentStyle as any);
+};

+ 2 - 2
src/core/hook/use-snap.ts

@@ -77,7 +77,7 @@ export const useGlobalSnapInfos = installGlobalVar(() => {
   }
 
   return computed(() => Array.from(infos.values()));
-});
+}, Symbol('snapInfos'));
 
 export const useSnapConfig = () => {
   const unitTransform = useCacheUnitTransform();
@@ -100,7 +100,7 @@ export const useSnapResultInfo = installGlobalVar(() => {
     },
   }) as SnapResultInfo;
   return snapInfo;
-});
+}, Symbol('snapResultInfo'));
 
 export type AttractSnapInfo = {
   ref: ComponentSnapInfo;

+ 7 - 3
src/core/hook/use-transformer.ts

@@ -233,7 +233,7 @@ export const useShapeDrag = (shape: Ref<DC<EntityShape> | undefined>) => {
   return offset;
 };
 
-type Rep<T> = { tempShape: T; update?: () => void; destory: () => void };
+type Rep<T> = { tempShape: T; init?: () => void; update?: () => void; destory: () => void };
 const emptyFn = () => {};
 export const useShapeTransformer = <T extends EntityShape>(
   shape: Ref<DC<T> | undefined>,
@@ -379,13 +379,16 @@ export const useShapeTransformer = <T extends EntityShape>(
 
 export const cloneRepShape = <T extends EntityShape>(
   shape: T
-): { shape: T } => {
+): ReturnType<GetRepShape<T, any>> => {
   return {
     shape: shape.clone({
       fill: "rgb(0, 255, 0)",
       visible: false,
       strokeWidth: 0,
     }),
+    update: (_, rep) => {
+      setShapeTransform(rep, shape.getTransform())
+    }
   };
 };
 
@@ -531,7 +534,8 @@ export const useLineTransformer = <T extends LineTransformerData>(
         const attitude = new Transform(data.attitude);
         const inverMat = attitude.copy().invert();
         setShapeTransform(repShape, attitude);
-        const initVs = stableVs.map((v) => inverMat.point(v));
+        const initVs = data.points.map((v) => inverMat.point(v));
+        stableVs = tempVs = data.points
         repShape.points(flatPositions(initVs));
         repShape.closed(true);
         inverAttitude = inverMat;

+ 2 - 2
src/core/hook/use-viewer.ts

@@ -88,7 +88,7 @@ export const useUnitTransform = installGlobalVar(() => {
       );
     },
   };
-});
+}, Symbol('unitTransform'));
 
 export const useCacheUnitTransform = installGlobalVar(() => {
 	const unitTransform = useUnitTransform()
@@ -119,4 +119,4 @@ export const useCacheUnitTransform = installGlobalVar(() => {
       }
     },
   };
-});
+}, Symbol('cacheUnitTransform'));

+ 6 - 1
src/core/propertys/mount.vue

@@ -11,7 +11,7 @@
                 'value' in describes[key] ? describes[key].value : data && data[key]
               "
               @update:value="(val: any) => updateValue(key, val)"
-              @change="emit('change')"
+              @change="changeHandler"
               :is="propertyComponents[val.type]"
               :key="key"
             />
@@ -66,6 +66,11 @@ watch(hidden, (nHidden, oHidden) => {
     emit("change");
   }
 });
+
+const changeHandler = () => {
+  isUpdate = false;
+  emit("change");
+};
 </script>
 
 <style lang="scss" scoped>

+ 1 - 0
src/core/renderer/renderer.vue

@@ -70,6 +70,7 @@ defineExpose(useExpose());
   width: 100%;
   height: 100%;
   overflow: hidden;
+  position: relative;
 }
 .mount-mask {
   position: absolute;

+ 33 - 16
src/core/store/index.ts

@@ -3,8 +3,7 @@ import { SingleHistory } from "../history";
 import { installGlobalVar } from "../hook/use-global-vars";
 import { useStoreRaw } from "./store";
 
-type Store = ReturnType<typeof useStoreRaw>
-
+const emptyData = JSON.stringify({})
 class DrawHistory extends SingleHistory<string> {
 	preventFlag = false;
 	onceFlag = false;
@@ -17,8 +16,10 @@ class DrawHistory extends SingleHistory<string> {
 		this.renderer = renderer
 	}
 
-	init(data: string) {
+	setInit(data: string) {
 		this.initData = data
+		super.clear()
+		this.push(data)
 	}
 
 	preventTrack(fn: () => void) {
@@ -28,7 +29,7 @@ class DrawHistory extends SingleHistory<string> {
 	}
 
 	push(data: string) {
-		if (!this.preventFlag) return;
+		if (this.preventFlag) return;
 		if (this.onceFlag) {
 			this.onceHistory = data
 		} else {
@@ -59,32 +60,48 @@ class DrawHistory extends SingleHistory<string> {
 	}
 
 	clear(): void {
-		super.clear()
-		this.initData && this.push(this.initData)
+		this.push(emptyData)
+		this.renderer(emptyData)
+	}
+
+	init() {
+		if (this.initData) {
+			this.push(this.initData)
+			this.renderer(emptyData)
+		}
 	}
 }
 
 
-export const useStore = installGlobalVar(() => {
-  const store = useStoreRaw() as Store & { history: DrawHistory; };
-	const history = new DrawHistory((data) => {
-		store.data = JSON.parse(data) as DrawData
+
+const useStoreAndHistory = installGlobalVar(() => {
+  const store = useStoreRaw();
+	const history = new DrawHistory((dataStr) => {
+		const data = JSON.parse(dataStr) as DrawData
+		store.data = data
 	})
-	store.history = history
 	
   const trackActions = ["setStore", "repStore", "addItem", "delItem", "setItem"];
   store.$onAction(({ args, name, after, store }) => {
     if (!trackActions.includes(name)) return;
 		const isInit = name === "setStore"
-		const current = isInit ? null : JSON.stringify(store.data)
 		after(() => {
 			if (isInit) {
-				history.init(JSON.stringify(store.data))
+				history.setInit(JSON.stringify(store.data))
 			} else {
-				history.push(current!)
+				history.push(JSON.stringify(store.data)!)
 			}
 		})
   });
 
-  return store;
-});
+  return { store, history };
+}, Symbol('storeAndHistory'));
+
+export const useStore = () => {
+	return useStoreAndHistory().store
+}
+
+export const useHistory = () => {
+	return useStoreAndHistory().history
+}
+

+ 10 - 1
src/core/store/store.ts

@@ -40,7 +40,7 @@ export const useStoreRaw = defineStore('draw-data', {
 		},
 		addItem<T extends ShapeType>(type: T, item: DrawItem<T>) {
 			this.$patch((state) => {
-				if (!(type in state)) {
+				if (!(type in state.data)) {
 					state.data[type] = []
 				}
 				state.data[type]!.push(item as any);
@@ -76,6 +76,15 @@ export const useStoreRaw = defineStore('draw-data', {
 					return type
 				}
 			}
+		},
+		getItemById(id: string) {
+			const types = Object.keys(this.data) as ShapeType[]
+			for (const type of types) {
+				const item = this.data[type]?.find(item=> item.id === id)
+				if (item) {
+					return item;
+				}
+			}
 		}
 	}
 })

+ 14 - 5
src/core/viewer.ts

@@ -69,11 +69,16 @@ export class Viewer {
 	}
 
 	movePixel(position: Pos, initMat = this.viewMat) {
-		const info = initMat.decompose()
-		const tf = new Transform()
-		tf.rotate(info.rotation)
-		tf.scale(info.scaleX, info.scaleY)
-		this.move(tf.invert().point(position), this.viewMat)
+		const mat = initMat.copy().invert()
+		const p1 = mat.point({x: 0, y: 0})
+		const p2 = mat.point(position)
+		this.move({x: p2.x - p1.x, y: p2.y - p1.y})
+
+		// const info = initMat.decompose()
+		// const tf = new Transform()
+		// tf.rotate(info.rotation)
+		// tf.scale(info.scaleX, info.scaleY)
+		// this.move(tf.invert().point(position), this.viewMat)
 	}
 
 
@@ -114,6 +119,7 @@ export class Viewer {
 	}
 
 	mutMat(mat: Transform, initMat = this.viewMat) {
+		// this.setViewMat(mat.copy().multiply(initMat))
 		this.setViewMat(initMat.copy().multiply(mat))
 	}
 
@@ -129,4 +135,7 @@ export class Viewer {
 	get transform() {
 		return this.partMat.copy().multiply(this.viewMat);
 	}
+	get current() {
+		return this.viewMat.decompose()
+	}
 }

+ 51 - 5
src/example/fuse/views/header/header.vue

@@ -5,21 +5,32 @@
     </div>
     <div class="draw-operate">
       <div>
-        <span class="operate">
+        <span
+          class="operate"
+          @click="draw.history.undo()"
+          :class="{ disabled: !draw.history.hasUndo.value }"
+        >
           撤销<el-icon><Plus /></el-icon>
         </span>
-        <span class="operate">
+        <span
+          class="operate"
+          @click="draw.history.redo()"
+          :class="{ disabled: !draw.history.hasRedo.value }"
+        >
           重做<el-icon><Plus /></el-icon>
         </span>
       </div>
       <div>
-        <span class="operate">
+        <span class="operate" @click="draw.history.clear()">
           清除<el-icon><Plus /></el-icon>
         </span>
-        <span class="operate">
+        <span class="operate" @click="rotateView">
           旋转<el-icon><Plus /></el-icon>
         </span>
-        <span class="operate">
+        <span class="operate" @click="initView">
+          初始<el-icon><Plus /></el-icon>
+        </span>
+        <span class="operate" @click="emit('full')">
           全屏<el-icon><Plus /></el-icon>
         </span>
       </div>
@@ -59,11 +70,15 @@ import { ElButton, ElIcon } from "element-plus";
 import { useDraw } from "../use-draw.ts";
 import { ref } from "vue";
 import { Plus } from "@element-plus/icons-vue";
+import { Transform } from "konva/lib/Util";
+import { animation } from "@/core/hook/use-animation.ts";
 
 const draw = useDraw();
 const bgFileInput = ref<HTMLInputElement | null>(null);
 const dev = import.meta.env.DEV;
 
+const emit = defineEmits<{ (e: "full"): void }>();
+
 const setBGImage = (file: File) => {
   draw.addShape(
     "image",
@@ -71,11 +86,29 @@ const setBGImage = (file: File) => {
       width: 1000,
       height: 1000,
       url: URL.createObjectURL(file),
+      zIndex: -1,
     },
     { x: window.innerWidth / 2, y: window.innerHeight / 2 },
     true
   );
 };
+
+const rotateView = () => {
+  const dom = draw.stage.container();
+  let rotated = 0;
+  animation({ rotation: 0 }, { rotation: Math.PI / 2 }, ({ rotation }) => {
+    draw.viewer.rotatePixel(
+      { x: dom.offsetWidth / 2, y: dom.offsetHeight / 2 },
+      rotation - rotated
+    );
+    rotated = rotation;
+  });
+};
+const initView = () => {
+  animation(draw.viewer.viewMat.m, new Transform().m, (dec) => {
+    draw.viewer.setViewMat(dec);
+  });
+};
 </script>
 
 <style lang="scss" scoped>
@@ -103,7 +136,20 @@ const setBGImage = (file: File) => {
 
   i {
     width: auto;
+  }
+
+  .operate {
     margin: 0 5px;
+    display: inline-flex;
+    align-items: center;
+    font-size: 14px;
+    flex-direction: row-reverse;
+    justify-content: center;
+
+    &.disabled {
+      pointer-events: none;
+      opacity: 0.7;
+    }
   }
 }
 

+ 44 - 13
src/example/fuse/views/home.vue

@@ -1,6 +1,6 @@
 <template>
-  <div class="layout">
-    <Header class="header" v-if="draw" />
+  <div class="layout" :class="{ full }">
+    <Header class="header" v-if="draw" @full="fullHandler" />
     <div class="container">
       <Slide class="slide" v-if="draw" />
       <div class="content" ref="drawEle">
@@ -17,14 +17,37 @@
 <script lang="ts" setup>
 import Header from "./header/header.vue";
 import Slide from "./slide/slide.vue";
-import { ref } from "vue";
+import { onUnmounted, ref, watch, watchEffect } from "vue";
 import { DrawExpose, DrawBoard } from "@/index";
 import { initData } from "./init.ts";
 import { installDraw } from "./use-draw.ts";
+import { listener } from "@/utils/event.ts";
+import { ElMessage } from "element-plus";
+import { startAnimation } from "@/utils/shared.ts";
 
 const drawEle = ref<HTMLDivElement | null>(null);
 const draw = ref<DrawExpose>();
 installDraw(draw);
+
+const full = ref(false);
+watch(full, (_f1, _f2, onCleanup) => {
+  onCleanup(
+    startAnimation(() => {
+      draw.value?.updateSize();
+    }, 400)
+  );
+});
+const fullHandler = () => {
+  full.value = true;
+  ElMessage.warning({ message: "按ESC键可退出全屏模式", duration: 3000 });
+};
+onUnmounted(
+  listener(document.documentElement, "keyup", (ev) => {
+    if (ev.key === "Escape") {
+      full.value = false;
+    }
+  })
+);
 </script>
 
 <style lang="scss" scoped>
@@ -36,31 +59,39 @@ installDraw(draw);
   align-items: stretch;
   height: 100vh;
   background: #f0f2f5;
+  --top: 0px;
+  --left: 0px;
+  overflow: hidden;
 
   .header {
+    margin-top: var(--top);
+    transition: margin-top 0.3s ease;
     height: global.$headerSize;
+    flex: 0 0 auto;
   }
 
   .container {
-    position: relative;
-    width: 100%;
-    height: calc(100% - #{global.$headerSize});
+    flex: 1;
+    display: flex;
+    align-items: stretch;
   }
 
   .slide {
-    position: absolute;
-    left: 0;
+    flex: 0 0 auto;
     width: global.$slideSize;
+    margin-left: var(--left);
     overflow-y: auto;
-    height: 100%;
-    top: 0;
     background: #fff;
-    z-index: 1;
+    transition: margin-left 0.3s ease;
   }
 
   .content {
-    position: absolute;
-    inset: 0;
+    flex: 1;
+  }
+
+  &.full {
+    --top: calc(-1 * #{global.$headerSize});
+    --left: calc(-1 * #{global.$slideSize});
   }
 }
 </style>

+ 4 - 3
src/example/fuse/views/slide/menu.ts

@@ -1,3 +1,4 @@
+import { themeColor } from "@/constant/help-style";
 import { DrawItem, ShapeType, shapeNames } from "@/index.ts";
 import { v4 as uuid } from "uuid";
 import { toRaw } from "vue";
@@ -98,15 +99,15 @@ export const menus: MenuItem[] = [
     children: [
       {
         icon: "",
-        ...genItem("icon", { url: '/icons/BedsideCupboard.svg', width: 500, height: 500 }),
+        ...genItem("icon", { url: '/icons/BedsideCupboard.svg', width: 100, height: 100, fill: themeColor }),
         name: "vue",
       },
       {
         icon: "",
         ...genItem("icon", {
           url: '/icons/vue.svg',
-          width: 300,
-          height: 300,
+          width: 100,
+          height: 100,
           stroke: "red",
           strokeWidth: 1,
           strokeScaleEnabled: false,

+ 0 - 1
src/example/fuse/views/slide/slide.vue

@@ -32,7 +32,6 @@ const selectHandler = (value: string) => {
 @use '../../styles/global';
 
 .slide {
-  transition: transform 0.3s ease;
   margin-left: 0;
 
   &.hide {

+ 11 - 2
src/utils/shared.ts

@@ -164,7 +164,7 @@ export const flatToPositions = (coords: number[]) => {
 
 export const onlyId = () => uuid();
 
-export const startAnimation = (update: () => void) => {
+export const startAnimation = (update: () => void, dur = -1) => {
   let isStop = false;
   const animation = () => {
     requestAnimationFrame(() => {
@@ -175,7 +175,16 @@ export const startAnimation = (update: () => void) => {
     });
   };
   animation();
-  return () => (isStop = true);
+
+  let timeout: any
+  if (dur >= 0) {
+    setTimeout(() => isStop = true, dur)
+  }
+
+  return () => {
+    clearTimeout(timeout)
+    isStop = true
+  }
 };
 
 export const arrayInsert = <T>(