HintViewer.tsx 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. import { t } from "../i18n";
  2. import { NonDeletedExcalidrawElement } from "../element/types";
  3. import { getSelectedElements } from "../scene";
  4. import "./HintViewer.scss";
  5. import { AppState, Device } from "../types";
  6. import {
  7. isImageElement,
  8. isLinearElement,
  9. isTextBindableContainer,
  10. isTextElement,
  11. } from "../element/typeChecks";
  12. import { getShortcutKey } from "../utils";
  13. import { isEraserActive } from "../appState";
  14. interface HintViewerProps {
  15. appState: AppState;
  16. elements: readonly NonDeletedExcalidrawElement[];
  17. isMobile: boolean;
  18. device: Device;
  19. }
  20. const getHints = ({
  21. appState,
  22. elements,
  23. isMobile,
  24. device,
  25. }: HintViewerProps) => {
  26. const { activeTool, isResizing, isRotating, lastPointerDownWith } = appState;
  27. const multiMode = appState.multiElement !== null;
  28. if (appState.openSidebar === "library" && !device.canDeviceFitSidebar) {
  29. return null;
  30. }
  31. if (isEraserActive(appState)) {
  32. return t("hints.eraserRevert");
  33. }
  34. if (activeTool.type === "arrow" || activeTool.type === "line") {
  35. if (!multiMode) {
  36. return t("hints.linearElement");
  37. }
  38. return t("hints.linearElementMulti");
  39. }
  40. if (activeTool.type === "freedraw") {
  41. return t("hints.freeDraw");
  42. }
  43. if (activeTool.type === "text") {
  44. return t("hints.text");
  45. }
  46. if (appState.activeTool.type === "image" && appState.pendingImageElementId) {
  47. return t("hints.placeImage");
  48. }
  49. const selectedElements = getSelectedElements(elements, appState);
  50. if (
  51. isResizing &&
  52. lastPointerDownWith === "mouse" &&
  53. selectedElements.length === 1
  54. ) {
  55. const targetElement = selectedElements[0];
  56. if (isLinearElement(targetElement) && targetElement.points.length === 2) {
  57. return t("hints.lockAngle");
  58. }
  59. return isImageElement(targetElement)
  60. ? t("hints.resizeImage")
  61. : t("hints.resize");
  62. }
  63. if (isRotating && lastPointerDownWith === "mouse") {
  64. return t("hints.rotate");
  65. }
  66. if (selectedElements.length === 1 && isTextElement(selectedElements[0])) {
  67. return t("hints.text_selected");
  68. }
  69. if (appState.editingElement && isTextElement(appState.editingElement)) {
  70. return t("hints.text_editing");
  71. }
  72. if (activeTool.type === "selection") {
  73. if (
  74. appState.draggingElement?.type === "selection" &&
  75. !appState.editingElement &&
  76. !appState.editingLinearElement
  77. ) {
  78. return t("hints.deepBoxSelect");
  79. }
  80. if (!selectedElements.length && !isMobile) {
  81. return t("hints.canvasPanning");
  82. }
  83. }
  84. if (selectedElements.length === 1) {
  85. if (isLinearElement(selectedElements[0])) {
  86. if (appState.editingLinearElement) {
  87. return appState.editingLinearElement.selectedPointsIndices
  88. ? t("hints.lineEditor_pointSelected")
  89. : t("hints.lineEditor_nothingSelected");
  90. }
  91. return t("hints.lineEditor_info");
  92. }
  93. if (isTextBindableContainer(selectedElements[0])) {
  94. return t("hints.bindTextToElement");
  95. }
  96. }
  97. return null;
  98. };
  99. export const HintViewer = ({
  100. appState,
  101. elements,
  102. isMobile,
  103. device,
  104. }: HintViewerProps) => {
  105. let hint = getHints({
  106. appState,
  107. elements,
  108. isMobile,
  109. device,
  110. });
  111. if (!hint) {
  112. return null;
  113. }
  114. hint = getShortcutKey(hint);
  115. return (
  116. <div className="HintViewer">
  117. <span>{hint}</span>
  118. </div>
  119. );
  120. };