edit-line.vue 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. <template>
  2. <v-line
  3. ref="line"
  4. :config="{
  5. id: id,
  6. strokeWidth: width,
  7. opacity: opacity || 0,
  8. stroke: data.stroke,
  9. zIndex: zIndex,
  10. points: flatPositions(points),
  11. hitStrokeWidth: hitWidth,
  12. }"
  13. />
  14. <v-circle
  15. v-if="!disablePoint"
  16. :config="{ ...pointStyle, ...center, opacity: isHover || isPointHover ? 1 : 0 }"
  17. ref="point"
  18. />
  19. </template>
  20. <script lang="ts" setup>
  21. import { copy, flatPositions, mergeFuns } from "@/utils/shared";
  22. import { computed, onUnmounted, ref, watch } from "vue";
  23. import { DC } from "@/deconstruction";
  24. import { Line } from "konva/lib/shapes/Line";
  25. import { useShapeDrag } from "@/core/hook/use-transformer";
  26. import { useShapeIsHover, useShapeClick } from "@/core/hook/use-mouse-status";
  27. import { useCursor } from "@/core/hook/use-global-vars";
  28. import { lineCenter, Pos } from "@/utils/math";
  29. import {
  30. useCustomSnapInfos,
  31. useGlobalSnapInfos,
  32. useSnap,
  33. useSnapResultInfo,
  34. } from "@/core/hook/use-snap";
  35. import { ComponentSnapInfo } from "..";
  36. import { generateSnapInfos } from "../util";
  37. import { getMouseColors } from "@/utils/colors";
  38. import { themeColor } from "@/constant";
  39. import { Circle } from "konva/lib/shapes/Circle";
  40. import {
  41. useFixedScale,
  42. useViewer,
  43. useViewerInvertTransformConfig,
  44. } from "@/core/hook/use-viewer";
  45. import { useMode } from "@/core/hook/use-status";
  46. import { Mode } from "@/constant/mode";
  47. import { LineDataLine } from "../line";
  48. type LData = Pick<LineDataLine, "strokeWidth" | "stroke"> & { fixed?: boolean };
  49. const scale = useFixedScale();
  50. const props = defineProps<{
  51. data: LData;
  52. points: Pos[];
  53. id: string;
  54. ndx: number;
  55. closed?: boolean;
  56. disablePoint?: boolean;
  57. opacity?: number;
  58. zIndex?: number;
  59. }>();
  60. const emit = defineEmits<{
  61. (e: "update:line", data: Pos[]): void;
  62. (e: "dragend"): void;
  63. (e: "dragstart"): void;
  64. (e: "addPoint", pos: Pos): void;
  65. }>();
  66. const line = ref<DC<Line>>();
  67. const offset = useShapeDrag(line);
  68. const viewer = useViewer();
  69. const [isHover] = useShapeIsHover(line);
  70. const mode = useMode();
  71. const drawing = computed(() => mode.include(Mode.draw));
  72. const cursor = useCursor();
  73. watch([isHover, drawing], ([hover, drawing], _, onCleanup) => {
  74. if (hover && !drawing) {
  75. onCleanup(cursor.push("./icons/m_move.png"));
  76. }
  77. });
  78. const points = computed(() => {
  79. return [props.points[props.ndx], props.points[(props.ndx + 1) % props.points.length]];
  80. });
  81. const infos = useCustomSnapInfos();
  82. const addedInfos = [] as ComponentSnapInfo[];
  83. const clearInfos = () => {
  84. addedInfos.forEach(infos.remove);
  85. };
  86. const dragStartHandler = () => {
  87. viewer.disabled.value = true;
  88. clearInfos();
  89. const ndx = props.ndx;
  90. const geos = [
  91. props.points.slice(Number(ndx === props.points.length - 1), ndx),
  92. props.points.slice(ndx + 2, props.points.length),
  93. ];
  94. if (ndx > 0 && ndx < props.points.length - 2) {
  95. geos.push([props.points[ndx - 1], props.points[ndx + 2]]);
  96. }
  97. geos.forEach((geo) => {
  98. const snapInfos = generateSnapInfos(geo, true, true, true);
  99. snapInfos.forEach((item) => {
  100. infos.add(item);
  101. addedInfos.push(item);
  102. });
  103. });
  104. };
  105. const snapInfos = useGlobalSnapInfos();
  106. const refSnapInfos = computed(() => {
  107. if (!props.id) {
  108. return snapInfos.value;
  109. } else {
  110. return snapInfos.value.filter((p) => !("id" in p) || p.id !== props.id);
  111. }
  112. });
  113. const snap = useSnap(refSnapInfos);
  114. let init: Pos[];
  115. let onUmHooks: (() => void)[] = [];
  116. const resultInfo = useSnapResultInfo();
  117. watch(offset, (offset, oldOffsert) => {
  118. snap.clear();
  119. if (!oldOffsert) {
  120. emit("dragstart");
  121. init = copy(points.value);
  122. dragStartHandler();
  123. onUmHooks.push(() => {
  124. clearInfos();
  125. emit("dragend");
  126. viewer.disabled.value = false;
  127. resultInfo.clear();
  128. });
  129. }
  130. if (offset) {
  131. const current = init.map((p) => ({
  132. x: p.x + offset.x,
  133. y: p.y + offset.y,
  134. }));
  135. const refSnapInfos = generateSnapInfos(current, true, true);
  136. const transform = snap.move(refSnapInfos);
  137. emit("update:line", transform ? current.map((p) => transform.point(p)) : current);
  138. } else {
  139. mergeFuns(onUmHooks)();
  140. onUmHooks.length = 0;
  141. }
  142. });
  143. onUnmounted(() => mergeFuns(onUmHooks)());
  144. const point = ref<DC<Circle>>();
  145. const [isPointHover] = useShapeIsHover(point);
  146. let addCursorPop: () => void;
  147. watch(isPointHover, (hover, _, onCleanup) => {
  148. if (hover) {
  149. let pop: (() => void) | null = cursor.push("./icons/m_add.png");
  150. addCursorPop = () => {
  151. pop && pop();
  152. pop = null;
  153. };
  154. onCleanup(addCursorPop);
  155. }
  156. });
  157. useShapeClick(point, () => {
  158. emit("addPoint", { ...center.value });
  159. addCursorPop();
  160. });
  161. const center = computed(() => lineCenter(points.value));
  162. const width = computed(() =>
  163. props.data.fixed ? props.data.strokeWidth * scale.value : props.data.strokeWidth
  164. );
  165. const hitWidth = computed(() => {
  166. const hitWidth = Math.max(props.data.strokeWidth || 0, 10);
  167. return props.data.fixed ? hitWidth * scale.value : hitWidth;
  168. });
  169. const pointStyle = computed(() => {
  170. const color = getMouseColors(props.data.stroke || themeColor);
  171. const size = (width.value || 1) + 6 || 5;
  172. return {
  173. radius: size / 2,
  174. fill: "#fff",
  175. strokeWidth: size / 4,
  176. stroke: color.pub,
  177. };
  178. });
  179. defineExpose({
  180. get shape() {
  181. return line.value;
  182. },
  183. });
  184. </script>