use-expose.ts 9.5 KB

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