浏览代码

fix: command palette tweaks and fixes (#7876)

David Luzar 1 年之前
父节点
当前提交
f597bd3e01

+ 15 - 0
excalidraw-app/App.tsx

@@ -122,6 +122,7 @@ import {
   usersIcon,
   exportToPlus,
   share,
+  youtubeIcon,
 } from "../packages/excalidraw/components/icons";
 import { appThemeAtom, useHandleAppTheme } from "./useHandleAppTheme";
 
@@ -1053,6 +1054,20 @@ const ExcalidrawWrapper = () => {
                 );
               },
             },
+            {
+              label: "YouTube",
+              icon: youtubeIcon,
+              category: DEFAULT_CATEGORIES.links,
+              predicate: true,
+              keywords: ["features", "tutorials", "howto", "help", "community"],
+              perform: () => {
+                window.open(
+                  "https://youtube.com/@excalidraw",
+                  "_blank",
+                  "noopener noreferrer",
+                );
+              },
+            },
             ...(isExcalidrawPlusSignedUser
               ? [
                   {

+ 18 - 5
excalidraw-app/components/AppMainMenu.tsx

@@ -1,7 +1,11 @@
 import React from "react";
-import { PlusPromoIcon } from "../../packages/excalidraw/components/icons";
+import {
+  arrowBarToLeftIcon,
+  ExcalLogo,
+} from "../../packages/excalidraw/components/icons";
 import { Theme } from "../../packages/excalidraw/element/types";
 import { MainMenu } from "../../packages/excalidraw/index";
+import { isExcalidrawPlusSignedUser } from "../app_constants";
 import { LanguageList } from "./LanguageList";
 
 export const AppMainMenu: React.FC<{
@@ -23,20 +27,29 @@ export const AppMainMenu: React.FC<{
           onSelect={() => props.onCollabDialogOpen()}
         />
       )}
-      <MainMenu.DefaultItems.CommandPalette />
+      <MainMenu.DefaultItems.CommandPalette className="highlighted" />
       <MainMenu.DefaultItems.Help />
       <MainMenu.DefaultItems.ClearCanvas />
       <MainMenu.Separator />
       <MainMenu.ItemLink
-        icon={PlusPromoIcon}
+        icon={ExcalLogo}
         href={`${
-          import.meta.env.VITE_APP_PLUS_LP
+          import.meta.env.VITE_APP_PLUS_APP
         }/plus?utm_source=excalidraw&utm_medium=app&utm_content=hamburger`}
-        className="ExcalidrawPlus"
+        className=""
       >
         Excalidraw+
       </MainMenu.ItemLink>
       <MainMenu.DefaultItems.Socials />
+      <MainMenu.ItemLink
+        icon={arrowBarToLeftIcon}
+        href={`${import.meta.env.VITE_APP_PLUS_APP}${
+          isExcalidrawPlusSignedUser ? "" : "/sign-up"
+        }?utm_source=signin&utm_medium=app&utm_content=hamburger`}
+        className="highlighted"
+      >
+        {isExcalidrawPlusSignedUser ? "Sign in" : "Sign up"}
+      </MainMenu.ItemLink>
       <MainMenu.Separator />
       <MainMenu.DefaultItems.ToggleTheme
         allowSystemTheme

+ 3 - 3
excalidraw-app/components/AppWelcomeScreen.tsx

@@ -1,5 +1,5 @@
 import React from "react";
-import { PlusPromoIcon } from "../../packages/excalidraw/components/icons";
+import { arrowBarToLeftIcon } from "../../packages/excalidraw/components/icons";
 import { useI18n } from "../../packages/excalidraw/i18n";
 import { WelcomeScreen } from "../../packages/excalidraw/index";
 import { isExcalidrawPlusSignedUser } from "../app_constants";
@@ -61,9 +61,9 @@ export const AppWelcomeScreen: React.FC<{
                 import.meta.env.VITE_APP_PLUS_LP
               }/plus?utm_source=excalidraw&utm_medium=app&utm_content=welcomeScreenGuest`}
               shortcut={null}
-              icon={PlusPromoIcon}
+              icon={arrowBarToLeftIcon}
             >
-              Try Excalidraw Plus!
+              Sign up
             </WelcomeScreen.Center.MenuItemLink>
           )}
         </WelcomeScreen.Center.Menu>

+ 1 - 1
excalidraw-app/index.scss

@@ -38,7 +38,7 @@
         background-color: #ecfdf5;
         color: #064e3c;
       }
-      &.ExcalidrawPlus {
+      &.highlighted {
         color: var(--color-promo);
       }
     }

+ 8 - 17
excalidraw-app/tests/__snapshots__/MobileMenu.test.tsx.snap

@@ -216,32 +216,23 @@ exports[`Test MobileMenu > should initialize with welcome screen and hide once u
           stroke-width="2"
           viewBox="0 0 24 24"
         >
-          <g
-            stroke-width="1.5"
-          >
+          <g>
             <path
               d="M0 0h24v24H0z"
               fill="none"
               stroke="none"
             />
-            <rect
-              height="4"
-              rx="1"
-              width="18"
-              x="3"
-              y="8"
+            <path
+              d="M10 12l10 0"
             />
-            <line
-              x1="12"
-              x2="12"
-              y1="8"
-              y2="21"
+            <path
+              d="M10 12l4 4"
             />
             <path
-              d="M19 12v7a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2v-7"
+              d="M10 12l4 -4"
             />
             <path
-              d="M7.5 8a2.5 2.5 0 0 1 0 -5a4.8 8 0 0 1 4.5 5a4.8 8 0 0 1 4.5 -5a2.5 2.5 0 0 1 0 5"
+              d="M4 4l0 16"
             />
           </g>
         </svg>
@@ -249,7 +240,7 @@ exports[`Test MobileMenu > should initialize with welcome screen and hide once u
       <div
         class="welcome-screen-menu-item__text"
       >
-        Try Excalidraw Plus!
+        Sign up
       </div>
     </a>
   </div>

+ 1 - 1
packages/excalidraw/analytics.ts

@@ -1,6 +1,6 @@
 // place here categories that you want to track. We want to track just a
 // small subset of categories at a given time.
-const ALLOWED_CATEGORIES_TO_TRACK = ["ai"] as string[];
+const ALLOWED_CATEGORIES_TO_TRACK = ["ai", "command_palette"] as string[];
 
 export const trackEvent = (
   category: string,

+ 28 - 10
packages/excalidraw/components/CommandPalette/CommandPalette.tsx

@@ -49,6 +49,8 @@ import { jotaiStore } from "../../jotai";
 import { activeConfirmDialogAtom } from "../ActiveConfirmDialog";
 import { CommandPaletteItem } from "./types";
 import * as defaultItems from "./defaultCommandPaletteItems";
+import { trackEvent } from "../../analytics";
+import { useStable } from "../../hooks/useStable";
 
 import "./CommandPalette.scss";
 
@@ -130,12 +132,20 @@ export const CommandPalette = Object.assign(
         if (isCommandPaletteToggleShortcut(event)) {
           event.preventDefault();
           event.stopPropagation();
-          setAppState((appState) => ({
-            openDialog:
+          setAppState((appState) => {
+            const nextState =
               appState.openDialog?.name === "commandPalette"
                 ? null
-                : { name: "commandPalette" },
-          }));
+                : ({ name: "commandPalette" } as const);
+
+            if (nextState) {
+              trackEvent("command_palette", "open", "shortcut");
+            }
+
+            return {
+              openDialog: nextState,
+            };
+          });
         }
       };
       window.addEventListener(EVENT.KEYDOWN, commandPaletteShortcut, {
@@ -174,10 +184,20 @@ function CommandPaletteInner({
 
   const inputRef = useRef<HTMLInputElement>(null);
 
+  const stableDeps = useStable({
+    uiAppState,
+    customCommandPaletteItems,
+    appProps,
+  });
+
   useEffect(() => {
-    if (!uiAppState || !app.scene || !actionManager) {
-      return;
-    }
+    // these props change often and we don't want them to re-run the effect
+    // which would renew `allCommands`, cascading down and resetting state.
+    //
+    // This means that the commands won't update on appState/appProps changes
+    // while the command palette is open
+    const { uiAppState, customCommandPaletteItems, appProps } = stableDeps;
+
     const getActionLabel = (action: Action) => {
       let label = "";
       if (action.label) {
@@ -533,15 +553,13 @@ function CommandPaletteInner({
       );
     }
   }, [
+    stableDeps,
     app,
-    appProps,
-    uiAppState,
     actionManager,
     setAllCommands,
     lastUsed?.label,
     setLastUsed,
     setAppState,
-    customCommandPaletteItems,
   ]);
 
   const [commandSearch, setCommandSearch] = useState("");

+ 13 - 4
packages/excalidraw/components/HelpDialog.tsx

@@ -4,7 +4,7 @@ import { KEYS } from "../keys";
 import { Dialog } from "./Dialog";
 import { getShortcutKey } from "../utils";
 import "./HelpDialog.scss";
-import { ExternalLinkIcon } from "./icons";
+import { ExternalLinkIcon, GithubIcon, youtubeIcon } from "./icons";
 import { probablySupportsClipboardBlob } from "../clipboard";
 import { isDarwin, isFirefox, isWindows } from "../constants";
 import { getShortcutFromShortcutName } from "../actions/shortcuts";
@@ -17,8 +17,8 @@ const Header = () => (
       target="_blank"
       rel="noopener noreferrer"
     >
-      {t("helpDialog.documentation")}
       <div className="HelpDialog__link-icon">{ExternalLinkIcon}</div>
+      {t("helpDialog.documentation")}
     </a>
     <a
       className="HelpDialog__btn"
@@ -26,8 +26,8 @@ const Header = () => (
       target="_blank"
       rel="noopener noreferrer"
     >
-      {t("helpDialog.blog")}
       <div className="HelpDialog__link-icon">{ExternalLinkIcon}</div>
+      {t("helpDialog.blog")}
     </a>
     <a
       className="HelpDialog__btn"
@@ -35,8 +35,17 @@ const Header = () => (
       target="_blank"
       rel="noopener noreferrer"
     >
+      <div className="HelpDialog__link-icon">{GithubIcon}</div>
       {t("helpDialog.github")}
-      <div className="HelpDialog__link-icon">{ExternalLinkIcon}</div>
+    </a>
+    <a
+      className="HelpDialog__btn"
+      href="https://youtube.com/@excalidraw"
+      target="_blank"
+      rel="noopener noreferrer"
+    >
+      <div className="HelpDialog__link-icon">{youtubeIcon}</div>
+      YouTube
     </a>
   </div>
 );

+ 21 - 0
packages/excalidraw/components/icons.tsx

@@ -2095,3 +2095,24 @@ export const DeviceDesktopIcon = createIcon(
   </g>,
   { ...tablerIconProps, strokeWidth: 1.5 },
 );
+
+// arrow-bar-to-left
+export const arrowBarToLeftIcon = createIcon(
+  <g>
+    <path stroke="none" d="M0 0h24v24H0z" fill="none" />
+    <path d="M10 12l10 0" />
+    <path d="M10 12l4 4" />
+    <path d="M10 12l4 -4" />
+    <path d="M4 4l0 16" />
+  </g>,
+  tablerIconProps,
+);
+
+export const youtubeIcon = createIcon(
+  <g>
+    <path stroke="none" d="M0 0h24v24H0z" fill="none" />
+    <path d="M2 8a4 4 0 0 1 4 -4h12a4 4 0 0 1 4 4v8a4 4 0 0 1 -4 4h-12a4 4 0 0 1 -4 -4v-8z" />
+    <path d="M10 9l5 3l-5 3z" />
+  </g>,
+  tablerIconProps,
+);

+ 7 - 2
packages/excalidraw/components/main-menu/DefaultItems.tsx

@@ -39,6 +39,7 @@ import Trans from "../Trans";
 import DropdownMenuItemContentRadio from "../dropdownMenu/DropdownMenuItemContentRadio";
 import { THEME } from "../../constants";
 import type { Theme } from "../../element/types";
+import { trackEvent } from "../../analytics";
 
 import "./DefaultItems.scss";
 
@@ -122,7 +123,7 @@ export const SaveAsImage = () => {
 };
 SaveAsImage.displayName = "SaveAsImage";
 
-export const CommandPalette = () => {
+export const CommandPalette = (opts?: { className?: string }) => {
   const setAppState = useExcalidrawSetAppState();
   const { t } = useI18n();
 
@@ -130,9 +131,13 @@ export const CommandPalette = () => {
     <DropdownMenuItem
       icon={boltIcon}
       data-testid="command-palette-button"
-      onSelect={() => setAppState({ openDialog: { name: "commandPalette" } })}
+      onSelect={() => {
+        trackEvent("command_palette", "open", "menu");
+        setAppState({ openDialog: { name: "commandPalette" } });
+      }}
       shortcut={getShortcutFromShortcutName("commandPalette")}
       aria-label={t("commandPalette.title")}
+      className={opts?.className}
     >
       {t("commandPalette.title")}
     </DropdownMenuItem>