use-expose.ts 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. import {
  2. installGlobalVar,
  3. useCursor,
  4. useInstanceProps,
  5. useLayers,
  6. useMountMenusFilter,
  7. useMouseMenusFilter,
  8. useRunHook,
  9. useStage,
  10. useTempStatus,
  11. } from "./use-global-vars.ts";
  12. import { useMode, useOperMode } from "./use-status";
  13. import { Stage } from "konva/lib/Stage";
  14. import { useInteractiveProps } from "./use-interactive.ts";
  15. import { useStore } from "../store/index.ts";
  16. import {
  17. useGetViewBoxPositionPixel,
  18. useSetViewport,
  19. useViewer,
  20. } from "./use-viewer.ts";
  21. import { useGlobalResize, useListener } from "./use-event.ts";
  22. import { useInteractiveDrawShapeAPI } from "./use-draw.ts";
  23. import { useHistory } from "./use-history.ts";
  24. import { watchEffect } from "vue";
  25. import { usePaste } from "./use-paste.ts";
  26. import { useMouseShapesStatus } from "./use-mouse-status.ts";
  27. import { Mode } from "@/constant/mode.ts";
  28. import { ElMessageBox } from "element-plus";
  29. import { mergeFuns } from "@/utils/shared.ts";
  30. import { getImage, isSvgString } from "@/utils/resource.ts";
  31. import { useResourceHandler } from "./use-fetch.ts";
  32. import { useConfig } from "./use-config.ts";
  33. import { useSelectionRevise } from "./use-selection.ts";
  34. import { useFormalLayer, useGetFormalChildren } from "./use-layer.ts";
  35. import { components } from "../components/index.ts";
  36. import { useProportion } from "./use-proportion.ts";
  37. import { useGetDXF } from "./use-dxf.ts";
  38. import { getIconStyle } from "../components/icon/index.ts";
  39. import { useGetShapeBelong } from "./use-component.ts";
  40. // 自动粘贴服务
  41. export const useAutoPaste = () => {
  42. const paste = usePaste();
  43. const drawAPI = useInteractiveDrawShapeAPI();
  44. const resourceHandler = useResourceHandler();
  45. paste.push({
  46. ["text/plain"]: {
  47. async handler(pos, val) {
  48. if (isSvgString(val)) {
  49. const url = await resourceHandler(val, "svg");
  50. const style = await getIconStyle(url, 100, 100);
  51. drawAPI.addShape(
  52. "icon",
  53. { ...style, fill: undefined, stroke: undefined },
  54. pos,
  55. true
  56. );
  57. } else {
  58. drawAPI.addShape("text", { content: val }, pos, true);
  59. }
  60. },
  61. type: "string",
  62. },
  63. ["image"]: {
  64. async handler(pos, val, type) {
  65. const url = await resourceHandler(val, type);
  66. if (type.includes("svg")) {
  67. const style = await getIconStyle(url, 100, 100);
  68. drawAPI.addShape(
  69. "icon",
  70. { ...style, fill: undefined, stroke: undefined },
  71. pos,
  72. true
  73. );
  74. } else {
  75. const image = await getImage(url);
  76. drawAPI.addShape(
  77. "image",
  78. { url, width: image.width, height: image.height },
  79. pos,
  80. true
  81. );
  82. }
  83. },
  84. type: "file",
  85. },
  86. });
  87. };
  88. // 快捷键服务
  89. export const useShortcutKey = () => {
  90. // 自动退出添加模式
  91. const { quitDrawShape, enterDrawShape } = useInteractiveDrawShapeAPI();
  92. const interactiveProps = useInteractiveProps();
  93. const store = useStore();
  94. useListener(
  95. "contextmenu",
  96. (ev) => {
  97. const iProps = interactiveProps.value;
  98. if (!iProps?.type || ev.button !== 2) return;
  99. const addCount = quitDrawShape();
  100. // 钢笔工具需要右键两次才退出,右键一次相当于完成
  101. const isDots = ["single-dots"].includes(components[iProps.type].addMode);
  102. if (isDots && addCount > 0) {
  103. setTimeout(() => {
  104. enterDrawShape(iProps.type, iProps.preset, iProps.operate?.single);
  105. }, 10);
  106. }
  107. },
  108. document.documentElement
  109. );
  110. const history = useHistory();
  111. const status = useMouseShapesStatus();
  112. const getChildren = useGetFormalChildren();
  113. const operMode = useOperMode();
  114. const getShapeBelong = useGetShapeBelong();
  115. useListener(
  116. "keydown",
  117. (ev) => {
  118. if (ev.target !== document.body) return;
  119. if (ev.key === "z" && ev.ctrlKey) {
  120. ev.preventDefault();
  121. history.hasUndo.value && history.undo();
  122. } else if (ev.key === "y" && ev.ctrlKey) {
  123. ev.preventDefault();
  124. history.hasRedo.value && history.redo();
  125. } else if (ev.key === "s" && ev.ctrlKey) {
  126. ev.preventDefault();
  127. // 保存
  128. // history.saveLocal();
  129. } else if (ev.key === "Delete" || ev.key === "Backspace") {
  130. // 删除
  131. ev.preventDefault();
  132. const isSelect = status.selects.length;
  133. const shapes = isSelect ? status.selects : status.actives;
  134. const delItems = shapes
  135. .map(getShapeBelong)
  136. .filter((item) => !!item);
  137. history.onceTrack(() => {
  138. delItems.forEach((belong) => {
  139. const compDelItem = components[belong.type].delItem
  140. if (compDelItem) {
  141. compDelItem(store, belong.item as any, belong.isSelf ? undefined : belong.curId)
  142. } else if (belong.isSelf) {
  143. store.delItem(belong.type, belong.id);
  144. }
  145. });
  146. });
  147. if (delItems.length) {
  148. if (isSelect) {
  149. status.selects = [];
  150. } else {
  151. status.actives = [];
  152. }
  153. }
  154. } else if (operMode.value.mulSelection && ev.key === "A") {
  155. ev.preventDefault();
  156. if (status.selects.length) {
  157. status.selects = [];
  158. } else {
  159. status.selects = getChildren();
  160. }
  161. }
  162. },
  163. window
  164. );
  165. };
  166. export const useAutoService = () => {
  167. useAutoPaste();
  168. useShortcutKey();
  169. // 鼠标自动变化服务
  170. const status = useMouseShapesStatus();
  171. const operMode = useOperMode();
  172. const mode = useMode();
  173. const cursor = useCursor();
  174. const { set: setCursor } = cursor.push("initial");
  175. watchEffect(() => {
  176. let style: string | null = null;
  177. if (operMode.value.freeView) {
  178. style = "pointer";
  179. } else if (mode.include(Mode.update)) {
  180. style = "./icons/m_move.png";
  181. } else if (status.hovers.length) {
  182. style = "pointer";
  183. } else {
  184. style = "initial";
  185. }
  186. setCursor(style);
  187. });
  188. // 自动保存历史及恢复服务
  189. const history = useHistory();
  190. const instanceProps = useInstanceProps();
  191. const init = (id: any) => {
  192. const quitHooks: (() => void)[] = [];
  193. if (!id) return quitHooks;
  194. const unloadHandler = () => {
  195. if (history.hasRedo.value || history.hasUndo.value) {
  196. // history.saveLocal();
  197. }
  198. };
  199. history.setLocalId(id);
  200. window.addEventListener("beforeunload", unloadHandler);
  201. quitHooks.push(() =>
  202. window.removeEventListener("beforeunload", unloadHandler)
  203. );
  204. if (!history.hasLocal()) return quitHooks;
  205. if (!import.meta.env.DEV) {
  206. let isOpen = true;
  207. ElMessageBox.confirm("检测到有历史数据,是否要恢复?", {
  208. type: "info",
  209. confirmButtonText: "恢复",
  210. cancelButtonText: "取消",
  211. })
  212. .then(() => {
  213. history.loadLocalStorage();
  214. })
  215. .catch(() => {
  216. history.clearLocal();
  217. })
  218. .finally(() => {
  219. isOpen = false;
  220. });
  221. quitHooks.push(() => isOpen && ElMessageBox.close());
  222. }
  223. return quitHooks;
  224. };
  225. watchEffect((onCleanup) => {
  226. onCleanup(mergeFuns(init(instanceProps.get().id)));
  227. });
  228. useSelectionRevise();
  229. };
  230. export type DrawExpose = ReturnType<typeof useExpose>;
  231. type PickParams<K extends keyof Stage, O extends string> = Stage[K] extends (
  232. ...args: any
  233. ) => any
  234. ? Omit<Required<Parameters<Stage[K]>>[0], O>
  235. : never;
  236. export const useExpose = installGlobalVar(() => {
  237. const mode = useMode();
  238. const interactiveProps = useInteractiveProps();
  239. const stage = useStage();
  240. const layers = useLayers();
  241. const store = useStore();
  242. const history = useHistory();
  243. const viewer = useViewer().viewer;
  244. const { updateSize } = useGlobalResize();
  245. const exposeBlob = (config?: PickParams<"toBlob", "callback">) => {
  246. const $stage = stage.value!.getStage();
  247. return new Promise<Blob>((resolve) => {
  248. $stage.toBlob({ ...config, resolve } as any);
  249. });
  250. };
  251. const toggleHit = () => {
  252. if (!layers.value) return;
  253. layers.value.forEach((layer) => {
  254. layer.toggleHitCanvas();
  255. });
  256. };
  257. return {
  258. ...useInteractiveDrawShapeAPI(),
  259. get stage() {
  260. const $store = stage.value?.getStage();
  261. return $store;
  262. },
  263. ...useTempStatus(),
  264. exposeBlob,
  265. toggleHit,
  266. formalLayer: useFormalLayer(),
  267. updateSize,
  268. history,
  269. store,
  270. ...useSetViewport(),
  271. mode,
  272. getData() {
  273. return store.data;
  274. },
  275. getViewBoxPositionPixel: useGetViewBoxPositionPixel(),
  276. viewer,
  277. runHook: useRunHook(),
  278. presetAdd: interactiveProps,
  279. config: useConfig(),
  280. mountFilter: useMountMenusFilter(),
  281. menusFilter: useMouseMenusFilter(),
  282. proportion: useProportion(),
  283. getDXF: useGetDXF(),
  284. };
  285. });