actionClipboard.tsx 5.6 KB

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