mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
refactor(desktop): simplify app shell shortcuts
Extract global navigation shortcuts into a focused hook and remove the unused threadActivityItems context field. Co-authored-by: Amp <amp@ampcode.com> Amp-Thread-ID: https://ampcode.com/threads/T-019f8626-f5ed-731e-94dc-37ce0fd51b93
This commit is contained in:
@@ -18,6 +18,7 @@ import { useAppShellLifecycleEffects } from "@/app/useAppShellLifecycleEffects";
|
||||
import { useThreadActivityFeedItems } from "@/app/useThreadActivityFeedItems";
|
||||
import { useTauriWindowDrag } from "@/app/useTauriWindowDrag";
|
||||
import { useWebviewZoomShortcuts } from "@/app/useWebviewZoomShortcuts";
|
||||
import { useGlobalShortcuts } from "@/app/useGlobalShortcuts";
|
||||
import {
|
||||
channelsQueryKey,
|
||||
useChannelsQuery,
|
||||
@@ -84,7 +85,6 @@ import { ChannelNavigationProvider } from "@/shared/context/ChannelNavigationCon
|
||||
import { MainInsetProvider } from "@/shared/layout/MainInsetContext";
|
||||
import { chromeCssVarDefaults } from "@/shared/layout/chromeLayout";
|
||||
import { cn } from "@/shared/lib/cn";
|
||||
import { hasPrimaryShortcutModifier } from "@/shared/lib/platform";
|
||||
import { useMessageDeepLinks } from "@/shared/useMessageDeepLinks";
|
||||
import { SidebarInset, SidebarProvider } from "@/shared/ui/sidebar";
|
||||
import { RelayConnectionOverlay } from "@/app/RelayConnectionOverlay";
|
||||
@@ -584,69 +584,15 @@ export function AppShell() {
|
||||
() => setIsCreateChannelOpen(true),
|
||||
[],
|
||||
);
|
||||
React.useLayoutEffect(() => {
|
||||
if (settingsOpen) {
|
||||
return;
|
||||
}
|
||||
|
||||
function handleKeyDown(event: KeyboardEvent) {
|
||||
if (!hasPrimaryShortcutModifier(event) || event.altKey || event.repeat) {
|
||||
return;
|
||||
}
|
||||
|
||||
// A focused surface may claim the shortcut first — e.g. the composer
|
||||
// consumes ⌘K to open the link editor when text is selected. Its
|
||||
// element-level handler runs before this window-level bubble listener
|
||||
// and calls `preventDefault()`; respect that instead of also opening
|
||||
// the global dialog.
|
||||
if (event.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
|
||||
const key = event.key.toLowerCase();
|
||||
if (key === "k" && !event.shiftKey) {
|
||||
event.preventDefault();
|
||||
handleOpenSearch();
|
||||
return;
|
||||
}
|
||||
|
||||
if (key === "k" && event.shiftKey) {
|
||||
event.preventDefault();
|
||||
handleOpenNewDm();
|
||||
return;
|
||||
}
|
||||
|
||||
if (key === "n" && event.shiftKey) {
|
||||
event.preventDefault();
|
||||
handleOpenCreateChannel();
|
||||
return;
|
||||
}
|
||||
|
||||
if (key === "o" && event.shiftKey) {
|
||||
event.preventDefault();
|
||||
handleOpenBrowseChannels();
|
||||
return;
|
||||
}
|
||||
|
||||
if (key === "a" && event.shiftKey) {
|
||||
event.preventDefault();
|
||||
void goHome();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("keydown", handleKeyDown);
|
||||
return () => {
|
||||
window.removeEventListener("keydown", handleKeyDown);
|
||||
};
|
||||
}, [
|
||||
handleOpenBrowseChannels,
|
||||
handleOpenNewDm,
|
||||
handleOpenCreateChannel,
|
||||
handleOpenSearch,
|
||||
goHome,
|
||||
settingsOpen,
|
||||
]);
|
||||
const handleGoHome = React.useCallback(() => void goHome(), [goHome]);
|
||||
useGlobalShortcuts({
|
||||
disabled: settingsOpen,
|
||||
onOpenSearch: handleOpenSearch,
|
||||
onOpenNewDm: handleOpenNewDm,
|
||||
onOpenCreateChannel: handleOpenCreateChannel,
|
||||
onBrowseChannels: handleOpenBrowseChannels,
|
||||
onGoHome: handleGoHome,
|
||||
});
|
||||
useSettingsShortcuts({
|
||||
onClose: handleCloseSettings,
|
||||
onOpenSettings: handleOpenSettings,
|
||||
@@ -688,7 +634,6 @@ export function AppShell() {
|
||||
isFollowingThread,
|
||||
isNotifiedForThread,
|
||||
isThreadMuted: (rootId) => mutedRootIds.has(rootId),
|
||||
threadActivityItems,
|
||||
threadActivityFeedItems,
|
||||
feedItemState,
|
||||
onOpenSettings: handleOpenSettings,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import * as React from "react";
|
||||
import type { ContextParentResolver } from "@/features/channels/readState/readStateManager";
|
||||
import type { ThreadActivityItem } from "@/features/channels/useUnreadChannels";
|
||||
import type { FeedItemState } from "@/features/home/useFeedItemState";
|
||||
import type { FeedItem } from "@/shared/api/types";
|
||||
import type { SettingsSection } from "@/features/settings/ui/SettingsPanels";
|
||||
@@ -44,7 +43,6 @@ type AppShellContextValue = {
|
||||
isFollowingThread: (rootId: string) => boolean;
|
||||
isNotifiedForThread: (rootId: string) => boolean;
|
||||
isThreadMuted: (rootId: string) => boolean;
|
||||
threadActivityItems: ThreadActivityItem[];
|
||||
threadActivityFeedItems: FeedItem[];
|
||||
feedItemState: FeedItemState;
|
||||
// Open the Settings panel at the given section. Available on all surfaces
|
||||
@@ -72,7 +70,6 @@ const AppShellContext = React.createContext<AppShellContextValue>({
|
||||
isFollowingThread: () => false,
|
||||
isNotifiedForThread: () => false,
|
||||
isThreadMuted: () => false,
|
||||
threadActivityItems: [],
|
||||
threadActivityFeedItems: [],
|
||||
feedItemState: {
|
||||
doneSet: EMPTY_SET,
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { hasPrimaryShortcutModifier } from "@/shared/lib/platform";
|
||||
|
||||
type UseGlobalShortcutsOptions = {
|
||||
/** Whether global navigation shortcuts are temporarily disabled. */
|
||||
disabled: boolean;
|
||||
/** ⌘K — open the search / quick-switch dialog. */
|
||||
onOpenSearch: () => void;
|
||||
/** ⇧⌘K — open the new direct-message composer. */
|
||||
onOpenNewDm: () => void;
|
||||
/** ⇧⌘N — open the create-channel dialog. */
|
||||
onOpenCreateChannel: () => void;
|
||||
/** ⇧⌘O — open the channel browser. */
|
||||
onBrowseChannels: () => void;
|
||||
/** ⇧⌘A — navigate to the home feed. */
|
||||
onGoHome: () => void;
|
||||
};
|
||||
|
||||
/**
|
||||
* Window-level global navigation shortcuts. Dormant while `disabled` (e.g. when
|
||||
* settings is open). Respects `event.defaultPrevented` so a focused surface —
|
||||
* e.g. the composer consuming ⌘K for its link editor — can claim the key first
|
||||
* via an element-level handler that runs before this bubble listener.
|
||||
*/
|
||||
export function useGlobalShortcuts({
|
||||
disabled,
|
||||
onOpenSearch,
|
||||
onOpenNewDm,
|
||||
onOpenCreateChannel,
|
||||
onBrowseChannels,
|
||||
onGoHome,
|
||||
}: UseGlobalShortcutsOptions): void {
|
||||
React.useLayoutEffect(() => {
|
||||
if (disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
function handleKeyDown(event: KeyboardEvent) {
|
||||
if (!hasPrimaryShortcutModifier(event) || event.altKey || event.repeat) {
|
||||
return;
|
||||
}
|
||||
if (event.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
|
||||
const key = event.key.toLowerCase();
|
||||
if (key === "k" && !event.shiftKey) {
|
||||
event.preventDefault();
|
||||
onOpenSearch();
|
||||
} else if (key === "k" && event.shiftKey) {
|
||||
event.preventDefault();
|
||||
onOpenNewDm();
|
||||
} else if (key === "n" && event.shiftKey) {
|
||||
event.preventDefault();
|
||||
onOpenCreateChannel();
|
||||
} else if (key === "o" && event.shiftKey) {
|
||||
event.preventDefault();
|
||||
onBrowseChannels();
|
||||
} else if (key === "a" && event.shiftKey) {
|
||||
event.preventDefault();
|
||||
onGoHome();
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("keydown", handleKeyDown);
|
||||
return () => {
|
||||
window.removeEventListener("keydown", handleKeyDown);
|
||||
};
|
||||
}, [
|
||||
disabled,
|
||||
onBrowseChannels,
|
||||
onGoHome,
|
||||
onOpenCreateChannel,
|
||||
onOpenNewDm,
|
||||
onOpenSearch,
|
||||
]);
|
||||
}
|
||||
Reference in New Issue
Block a user