actionClipboard.tsx 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. import { CODES, KEYS } from "../keys";
  2. import { register } from "./register";
  3. import {
  4. copyTextToSystemClipboard,
  5. copyToClipboard,
  6. probablySupportsClipboardBlob,
  7. probablySupportsClipboardWriteText,
  8. } from "../clipboard";
  9. import { actionDeleteSelected } from "./actionDeleteSelected";
  10. import { getSelectedElements } from "../scene/selection";
  11. import { exportCanvas } from "../data/index";
  12. import { getNonDeletedElements, isTextElement } from "../element";
  13. import { t } from "../i18n";
  14. export const actionCopy = register({
  15. name: "copy",
  16. trackEvent: { category: "element" },
  17. perform: (elements, appState, _, app) => {
  18. const selectedElements = getSelectedElements(elements, appState, true);
  19. copyToClipboard(selectedElements, appState, app.files);
  20. return {
  21. commitToHistory: false,
  22. };
  23. },
  24. predicate: (elements, appState, appProps, app) => {
  25. return app.device.isMobile && !!navigator.clipboard;
  26. },
  27. contextItemLabel: "labels.copy",
  28. // don't supply a shortcut since we handle this conditionally via onCopy event
  29. keyTest: undefined,
  30. });
  31. export const actionPaste = register({
  32. name: "paste",
  33. trackEvent: { category: "element" },
  34. perform: (elements: any, appStates: any, data, app) => {
  35. app.pasteFromClipboard(null);
  36. return {
  37. commitToHistory: false,
  38. };
  39. },
  40. predicate: (elements, appState, appProps, app) => {
  41. return app.device.isMobile && !!navigator.clipboard;
  42. },
  43. contextItemLabel: "labels.paste",
  44. // don't supply a shortcut since we handle this conditionally via onCopy event
  45. keyTest: undefined,
  46. });
  47. export const actionCut = register({
  48. name: "cut",
  49. trackEvent: { category: "element" },
  50. perform: (elements, appState, data, app) => {
  51. actionCopy.perform(elements, appState, data, app);
  52. return actionDeleteSelected.perform(elements, appState);
  53. },
  54. predicate: (elements, appState, appProps, app) => {
  55. return app.device.isMobile && !!navigator.clipboard;
  56. },
  57. contextItemLabel: "labels.cut",
  58. keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.key === KEYS.X,
  59. });
  60. export const actionCopyAsSvg = register({
  61. name: "copyAsSvg",
  62. trackEvent: { category: "element" },
  63. perform: async (elements, appState, _data, app) => {
  64. if (!app.canvas) {
  65. return {
  66. commitToHistory: false,
  67. };
  68. }
  69. const selectedElements = getSelectedElements(
  70. getNonDeletedElements(elements),
  71. appState,
  72. true,
  73. );
  74. try {
  75. await exportCanvas(
  76. "clipboard-svg",
  77. selectedElements.length
  78. ? selectedElements
  79. : getNonDeletedElements(elements),
  80. appState,
  81. app.files,
  82. appState,
  83. );
  84. return {
  85. commitToHistory: false,
  86. };
  87. } catch (error: any) {
  88. console.error(error);
  89. return {
  90. appState: {
  91. ...appState,
  92. errorMessage: error.message,
  93. },
  94. commitToHistory: false,
  95. };
  96. }
  97. },
  98. predicate: (elements) => {
  99. return probablySupportsClipboardWriteText && elements.length > 0;
  100. },
  101. contextItemLabel: "labels.copyAsSvg",
  102. });
  103. export const actionCopyAsPng = register({
  104. name: "copyAsPng",
  105. trackEvent: { category: "element" },
  106. perform: async (elements, appState, _data, app) => {
  107. if (!app.canvas) {
  108. return {
  109. commitToHistory: false,
  110. };
  111. }
  112. const selectedElements = getSelectedElements(
  113. getNonDeletedElements(elements),
  114. appState,
  115. true,
  116. );
  117. try {
  118. await exportCanvas(
  119. "clipboard",
  120. selectedElements.length
  121. ? selectedElements
  122. : getNonDeletedElements(elements),
  123. appState,
  124. app.files,
  125. appState,
  126. );
  127. return {
  128. appState: {
  129. ...appState,
  130. toast: {
  131. message: t("toast.copyToClipboardAsPng", {
  132. exportSelection: selectedElements.length
  133. ? t("toast.selection")
  134. : t("toast.canvas"),
  135. exportColorScheme: appState.exportWithDarkMode
  136. ? t("buttons.darkMode")
  137. : t("buttons.lightMode"),
  138. }),
  139. },
  140. },
  141. commitToHistory: false,
  142. };
  143. } catch (error: any) {
  144. console.error(error);
  145. return {
  146. appState: {
  147. ...appState,
  148. errorMessage: error.message,
  149. },
  150. commitToHistory: false,
  151. };
  152. }
  153. },
  154. predicate: (elements) => {
  155. return probablySupportsClipboardBlob && elements.length > 0;
  156. },
  157. contextItemLabel: "labels.copyAsPng",
  158. keyTest: (event) => event.code === CODES.C && event.altKey && event.shiftKey,
  159. });
  160. export const copyText = register({
  161. name: "copyText",
  162. trackEvent: { category: "element" },
  163. perform: (elements, appState) => {
  164. const selectedElements = getSelectedElements(
  165. getNonDeletedElements(elements),
  166. appState,
  167. true,
  168. );
  169. const text = selectedElements
  170. .reduce((acc: string[], element) => {
  171. if (isTextElement(element)) {
  172. acc.push(element.text);
  173. }
  174. return acc;
  175. }, [])
  176. .join("\n\n");
  177. copyTextToSystemClipboard(text);
  178. return {
  179. commitToHistory: false,
  180. };
  181. },
  182. predicate: (elements, appState) => {
  183. return (
  184. probablySupportsClipboardWriteText &&
  185. getSelectedElements(elements, appState, true).some(isTextElement)
  186. );
  187. },
  188. contextItemLabel: "labels.copyText",
  189. });