edit-line.vue 4.8 KB

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