mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-04-14 16:40:58 -04:00
fix: command palette tweaks and fixes (#7876)
This commit is contained in:
parent
4987cc53d0
commit
f597bd3e01
10 changed files with 116 additions and 44 deletions
|
@ -122,6 +122,7 @@ import {
|
||||||
usersIcon,
|
usersIcon,
|
||||||
exportToPlus,
|
exportToPlus,
|
||||||
share,
|
share,
|
||||||
|
youtubeIcon,
|
||||||
} from "../packages/excalidraw/components/icons";
|
} from "../packages/excalidraw/components/icons";
|
||||||
import { appThemeAtom, useHandleAppTheme } from "./useHandleAppTheme";
|
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
|
...(isExcalidrawPlusSignedUser
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
import React from "react";
|
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 { Theme } from "../../packages/excalidraw/element/types";
|
||||||
import { MainMenu } from "../../packages/excalidraw/index";
|
import { MainMenu } from "../../packages/excalidraw/index";
|
||||||
|
import { isExcalidrawPlusSignedUser } from "../app_constants";
|
||||||
import { LanguageList } from "./LanguageList";
|
import { LanguageList } from "./LanguageList";
|
||||||
|
|
||||||
export const AppMainMenu: React.FC<{
|
export const AppMainMenu: React.FC<{
|
||||||
|
@ -23,20 +27,29 @@ export const AppMainMenu: React.FC<{
|
||||||
onSelect={() => props.onCollabDialogOpen()}
|
onSelect={() => props.onCollabDialogOpen()}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<MainMenu.DefaultItems.CommandPalette />
|
<MainMenu.DefaultItems.CommandPalette className="highlighted" />
|
||||||
<MainMenu.DefaultItems.Help />
|
<MainMenu.DefaultItems.Help />
|
||||||
<MainMenu.DefaultItems.ClearCanvas />
|
<MainMenu.DefaultItems.ClearCanvas />
|
||||||
<MainMenu.Separator />
|
<MainMenu.Separator />
|
||||||
<MainMenu.ItemLink
|
<MainMenu.ItemLink
|
||||||
icon={PlusPromoIcon}
|
icon={ExcalLogo}
|
||||||
href={`${
|
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`}
|
}/plus?utm_source=excalidraw&utm_medium=app&utm_content=hamburger`}
|
||||||
className="ExcalidrawPlus"
|
className=""
|
||||||
>
|
>
|
||||||
Excalidraw+
|
Excalidraw+
|
||||||
</MainMenu.ItemLink>
|
</MainMenu.ItemLink>
|
||||||
<MainMenu.DefaultItems.Socials />
|
<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.Separator />
|
||||||
<MainMenu.DefaultItems.ToggleTheme
|
<MainMenu.DefaultItems.ToggleTheme
|
||||||
allowSystemTheme
|
allowSystemTheme
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React from "react";
|
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 { useI18n } from "../../packages/excalidraw/i18n";
|
||||||
import { WelcomeScreen } from "../../packages/excalidraw/index";
|
import { WelcomeScreen } from "../../packages/excalidraw/index";
|
||||||
import { isExcalidrawPlusSignedUser } from "../app_constants";
|
import { isExcalidrawPlusSignedUser } from "../app_constants";
|
||||||
|
@ -61,9 +61,9 @@ export const AppWelcomeScreen: React.FC<{
|
||||||
import.meta.env.VITE_APP_PLUS_LP
|
import.meta.env.VITE_APP_PLUS_LP
|
||||||
}/plus?utm_source=excalidraw&utm_medium=app&utm_content=welcomeScreenGuest`}
|
}/plus?utm_source=excalidraw&utm_medium=app&utm_content=welcomeScreenGuest`}
|
||||||
shortcut={null}
|
shortcut={null}
|
||||||
icon={PlusPromoIcon}
|
icon={arrowBarToLeftIcon}
|
||||||
>
|
>
|
||||||
Try Excalidraw Plus!
|
Sign up
|
||||||
</WelcomeScreen.Center.MenuItemLink>
|
</WelcomeScreen.Center.MenuItemLink>
|
||||||
)}
|
)}
|
||||||
</WelcomeScreen.Center.Menu>
|
</WelcomeScreen.Center.Menu>
|
||||||
|
|
|
@ -38,7 +38,7 @@
|
||||||
background-color: #ecfdf5;
|
background-color: #ecfdf5;
|
||||||
color: #064e3c;
|
color: #064e3c;
|
||||||
}
|
}
|
||||||
&.ExcalidrawPlus {
|
&.highlighted {
|
||||||
color: var(--color-promo);
|
color: var(--color-promo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -216,32 +216,23 @@ exports[`Test MobileMenu > should initialize with welcome screen and hide once u
|
||||||
stroke-width="2"
|
stroke-width="2"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g>
|
||||||
stroke-width="1.5"
|
|
||||||
>
|
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="none"
|
stroke="none"
|
||||||
/>
|
/>
|
||||||
<rect
|
<path
|
||||||
height="4"
|
d="M10 12l10 0"
|
||||||
rx="1"
|
|
||||||
width="18"
|
|
||||||
x="3"
|
|
||||||
y="8"
|
|
||||||
/>
|
|
||||||
<line
|
|
||||||
x1="12"
|
|
||||||
x2="12"
|
|
||||||
y1="8"
|
|
||||||
y2="21"
|
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M19 12v7a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2v-7"
|
d="M10 12l4 4"
|
||||||
/>
|
/>
|
||||||
<path
|
<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="M10 12l4 -4"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M4 4l0 16"
|
||||||
/>
|
/>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
@ -249,7 +240,7 @@ exports[`Test MobileMenu > should initialize with welcome screen and hide once u
|
||||||
<div
|
<div
|
||||||
class="welcome-screen-menu-item__text"
|
class="welcome-screen-menu-item__text"
|
||||||
>
|
>
|
||||||
Try Excalidraw Plus!
|
Sign up
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// place here categories that you want to track. We want to track just a
|
// place here categories that you want to track. We want to track just a
|
||||||
// small subset of categories at a given time.
|
// 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 = (
|
export const trackEvent = (
|
||||||
category: string,
|
category: string,
|
||||||
|
|
|
@ -49,6 +49,8 @@ import { jotaiStore } from "../../jotai";
|
||||||
import { activeConfirmDialogAtom } from "../ActiveConfirmDialog";
|
import { activeConfirmDialogAtom } from "../ActiveConfirmDialog";
|
||||||
import { CommandPaletteItem } from "./types";
|
import { CommandPaletteItem } from "./types";
|
||||||
import * as defaultItems from "./defaultCommandPaletteItems";
|
import * as defaultItems from "./defaultCommandPaletteItems";
|
||||||
|
import { trackEvent } from "../../analytics";
|
||||||
|
import { useStable } from "../../hooks/useStable";
|
||||||
|
|
||||||
import "./CommandPalette.scss";
|
import "./CommandPalette.scss";
|
||||||
|
|
||||||
|
@ -130,12 +132,20 @@ export const CommandPalette = Object.assign(
|
||||||
if (isCommandPaletteToggleShortcut(event)) {
|
if (isCommandPaletteToggleShortcut(event)) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
setAppState((appState) => ({
|
setAppState((appState) => {
|
||||||
openDialog:
|
const nextState =
|
||||||
appState.openDialog?.name === "commandPalette"
|
appState.openDialog?.name === "commandPalette"
|
||||||
? null
|
? null
|
||||||
: { name: "commandPalette" },
|
: ({ name: "commandPalette" } as const);
|
||||||
}));
|
|
||||||
|
if (nextState) {
|
||||||
|
trackEvent("command_palette", "open", "shortcut");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
openDialog: nextState,
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
window.addEventListener(EVENT.KEYDOWN, commandPaletteShortcut, {
|
window.addEventListener(EVENT.KEYDOWN, commandPaletteShortcut, {
|
||||||
|
@ -174,10 +184,20 @@ function CommandPaletteInner({
|
||||||
|
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
const stableDeps = useStable({
|
||||||
|
uiAppState,
|
||||||
|
customCommandPaletteItems,
|
||||||
|
appProps,
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!uiAppState || !app.scene || !actionManager) {
|
// these props change often and we don't want them to re-run the effect
|
||||||
return;
|
// 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) => {
|
const getActionLabel = (action: Action) => {
|
||||||
let label = "";
|
let label = "";
|
||||||
if (action.label) {
|
if (action.label) {
|
||||||
|
@ -533,15 +553,13 @@ function CommandPaletteInner({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
|
stableDeps,
|
||||||
app,
|
app,
|
||||||
appProps,
|
|
||||||
uiAppState,
|
|
||||||
actionManager,
|
actionManager,
|
||||||
setAllCommands,
|
setAllCommands,
|
||||||
lastUsed?.label,
|
lastUsed?.label,
|
||||||
setLastUsed,
|
setLastUsed,
|
||||||
setAppState,
|
setAppState,
|
||||||
customCommandPaletteItems,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const [commandSearch, setCommandSearch] = useState("");
|
const [commandSearch, setCommandSearch] = useState("");
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { KEYS } from "../keys";
|
||||||
import { Dialog } from "./Dialog";
|
import { Dialog } from "./Dialog";
|
||||||
import { getShortcutKey } from "../utils";
|
import { getShortcutKey } from "../utils";
|
||||||
import "./HelpDialog.scss";
|
import "./HelpDialog.scss";
|
||||||
import { ExternalLinkIcon } from "./icons";
|
import { ExternalLinkIcon, GithubIcon, youtubeIcon } from "./icons";
|
||||||
import { probablySupportsClipboardBlob } from "../clipboard";
|
import { probablySupportsClipboardBlob } from "../clipboard";
|
||||||
import { isDarwin, isFirefox, isWindows } from "../constants";
|
import { isDarwin, isFirefox, isWindows } from "../constants";
|
||||||
import { getShortcutFromShortcutName } from "../actions/shortcuts";
|
import { getShortcutFromShortcutName } from "../actions/shortcuts";
|
||||||
|
@ -17,8 +17,8 @@ const Header = () => (
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
{t("helpDialog.documentation")}
|
|
||||||
<div className="HelpDialog__link-icon">{ExternalLinkIcon}</div>
|
<div className="HelpDialog__link-icon">{ExternalLinkIcon}</div>
|
||||||
|
{t("helpDialog.documentation")}
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
className="HelpDialog__btn"
|
className="HelpDialog__btn"
|
||||||
|
@ -26,8 +26,8 @@ const Header = () => (
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
{t("helpDialog.blog")}
|
|
||||||
<div className="HelpDialog__link-icon">{ExternalLinkIcon}</div>
|
<div className="HelpDialog__link-icon">{ExternalLinkIcon}</div>
|
||||||
|
{t("helpDialog.blog")}
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
className="HelpDialog__btn"
|
className="HelpDialog__btn"
|
||||||
|
@ -35,8 +35,17 @@ const Header = () => (
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
|
<div className="HelpDialog__link-icon">{GithubIcon}</div>
|
||||||
{t("helpDialog.github")}
|
{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>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -2095,3 +2095,24 @@ export const DeviceDesktopIcon = createIcon(
|
||||||
</g>,
|
</g>,
|
||||||
{ ...tablerIconProps, strokeWidth: 1.5 },
|
{ ...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,
|
||||||
|
);
|
||||||
|
|
|
@ -39,6 +39,7 @@ import Trans from "../Trans";
|
||||||
import DropdownMenuItemContentRadio from "../dropdownMenu/DropdownMenuItemContentRadio";
|
import DropdownMenuItemContentRadio from "../dropdownMenu/DropdownMenuItemContentRadio";
|
||||||
import { THEME } from "../../constants";
|
import { THEME } from "../../constants";
|
||||||
import type { Theme } from "../../element/types";
|
import type { Theme } from "../../element/types";
|
||||||
|
import { trackEvent } from "../../analytics";
|
||||||
|
|
||||||
import "./DefaultItems.scss";
|
import "./DefaultItems.scss";
|
||||||
|
|
||||||
|
@ -122,7 +123,7 @@ export const SaveAsImage = () => {
|
||||||
};
|
};
|
||||||
SaveAsImage.displayName = "SaveAsImage";
|
SaveAsImage.displayName = "SaveAsImage";
|
||||||
|
|
||||||
export const CommandPalette = () => {
|
export const CommandPalette = (opts?: { className?: string }) => {
|
||||||
const setAppState = useExcalidrawSetAppState();
|
const setAppState = useExcalidrawSetAppState();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
@ -130,9 +131,13 @@ export const CommandPalette = () => {
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
icon={boltIcon}
|
icon={boltIcon}
|
||||||
data-testid="command-palette-button"
|
data-testid="command-palette-button"
|
||||||
onSelect={() => setAppState({ openDialog: { name: "commandPalette" } })}
|
onSelect={() => {
|
||||||
|
trackEvent("command_palette", "open", "menu");
|
||||||
|
setAppState({ openDialog: { name: "commandPalette" } });
|
||||||
|
}}
|
||||||
shortcut={getShortcutFromShortcutName("commandPalette")}
|
shortcut={getShortcutFromShortcutName("commandPalette")}
|
||||||
aria-label={t("commandPalette.title")}
|
aria-label={t("commandPalette.title")}
|
||||||
|
className={opts?.className}
|
||||||
>
|
>
|
||||||
{t("commandPalette.title")}
|
{t("commandPalette.title")}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
|
Loading…
Add table
Reference in a new issue