feat: display keyboard shortcuts on toolbar buttons (#788)
- Add shortcut hints to toolbar buttons via tooltip text - Integrate useKeyboardShortcutsHelp hook for consistent shortcut retrieval - Timeline toolbar: split, duplicate, delete, snapping, bookmark buttons - Preview toolbar: play/pause button - Format: tooltip text appended with '(Key)' or '(Key1 or Key2)' - Gracefully handle actions without defined shortcuts
This commit is contained in:
parent
fbe3db74d7
commit
6fdb1559aa
|
|
@ -4,6 +4,7 @@ import { useState, useEffect } from "react";
|
||||||
import { useEditor } from "@/editor/use-editor";
|
import { useEditor } from "@/editor/use-editor";
|
||||||
import { formatTimecode } from "opencut-wasm";
|
import { formatTimecode } from "opencut-wasm";
|
||||||
import { invokeAction } from "@/actions";
|
import { invokeAction } from "@/actions";
|
||||||
|
import { useKeyboardShortcutsHelp } from "@/actions/use-keyboard-shortcuts-help";
|
||||||
import { EditableTimecode } from "@/components/editable-timecode";
|
import { EditableTimecode } from "@/components/editable-timecode";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
|
|
@ -24,6 +25,11 @@ import { PREVIEW_ZOOM_PRESETS } from "@/preview/zoom";
|
||||||
import { usePreviewViewport } from "./preview-viewport";
|
import { usePreviewViewport } from "./preview-viewport";
|
||||||
import { GridPopover } from "./guide-popover";
|
import { GridPopover } from "./guide-popover";
|
||||||
import { usePreviewStore } from "@/preview/preview-store";
|
import { usePreviewStore } from "@/preview/preview-store";
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipTrigger,
|
||||||
|
TooltipContent,
|
||||||
|
} from "@/components/ui/tooltip";
|
||||||
import type { MediaTime } from "@/wasm";
|
import type { MediaTime } from "@/wasm";
|
||||||
|
|
||||||
export function PreviewToolbar({
|
export function PreviewToolbar({
|
||||||
|
|
@ -133,14 +139,24 @@ function ZoomSelect() {
|
||||||
|
|
||||||
function PlayPauseButton() {
|
function PlayPauseButton() {
|
||||||
const isPlaying = useEditor((e) => e.playback.getIsPlaying());
|
const isPlaying = useEditor((e) => e.playback.getIsPlaying());
|
||||||
|
const { shortcuts } = useKeyboardShortcutsHelp();
|
||||||
|
const shortcut = shortcuts.find((s) => s.action === "toggle-play");
|
||||||
|
const tooltipText = shortcut
|
||||||
|
? `Play/Pause (${shortcut.keys.join(" or ")})`
|
||||||
|
: "Play/Pause";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Tooltip delayDuration={200}>
|
||||||
variant="text"
|
<TooltipTrigger asChild>
|
||||||
size="icon"
|
<Button
|
||||||
onClick={() => invokeAction("toggle-play")}
|
variant="text"
|
||||||
>
|
size="icon"
|
||||||
<HugeiconsIcon icon={isPlaying ? PauseIcon : PlayIcon} />
|
onClick={() => invokeAction("toggle-play")}
|
||||||
</Button>
|
>
|
||||||
|
<HugeiconsIcon icon={isPlaying ? PauseIcon : PlayIcon} />
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>{tooltipText}</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import { TIMELINE_ZOOM_MAX } from "@/timeline/scale";
|
||||||
import { sliderToZoom, zoomToSlider } from "@/timeline/zoom-utils";
|
import { sliderToZoom, zoomToSlider } from "@/timeline/zoom-utils";
|
||||||
import { ScenesView } from "@/components/editor/scenes-view";
|
import { ScenesView } from "@/components/editor/scenes-view";
|
||||||
import { type TActionWithOptionalArgs, invokeAction } from "@/actions";
|
import { type TActionWithOptionalArgs, invokeAction } from "@/actions";
|
||||||
|
import { useKeyboardShortcutsHelp } from "@/actions/use-keyboard-shortcuts-help";
|
||||||
import {
|
import {
|
||||||
canToggleSourceAudio,
|
canToggleSourceAudio,
|
||||||
getSourceAudioActionLabel,
|
getSourceAudioActionLabel,
|
||||||
|
|
@ -143,18 +144,21 @@ function ToolbarLeftSection() {
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<TooltipProvider delayDuration={500}>
|
<TooltipProvider delayDuration={500}>
|
||||||
<ToolbarButton
|
<ToolbarButton
|
||||||
|
action="split"
|
||||||
icon={<HugeiconsIcon icon={ScissorIcon} />}
|
icon={<HugeiconsIcon icon={ScissorIcon} />}
|
||||||
tooltip="Split element"
|
tooltip="Split element"
|
||||||
onClick={({ event }) => handleAction({ action: "split", event })}
|
onClick={({ event }) => handleAction({ action: "split", event })}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ToolbarButton
|
<ToolbarButton
|
||||||
|
action="split-left"
|
||||||
icon={<HugeiconsIcon icon={AlignLeftIcon} />}
|
icon={<HugeiconsIcon icon={AlignLeftIcon} />}
|
||||||
tooltip="Split left"
|
tooltip="Split left"
|
||||||
onClick={({ event }) => handleAction({ action: "split-left", event })}
|
onClick={({ event }) => handleAction({ action: "split-left", event })}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ToolbarButton
|
<ToolbarButton
|
||||||
|
action="split-right"
|
||||||
icon={<HugeiconsIcon icon={AlignRightIcon} />}
|
icon={<HugeiconsIcon icon={AlignRightIcon} />}
|
||||||
tooltip="Split right"
|
tooltip="Split right"
|
||||||
onClick={({ event }) =>
|
onClick={({ event }) =>
|
||||||
|
|
@ -163,6 +167,7 @@ function ToolbarLeftSection() {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ToolbarButton
|
<ToolbarButton
|
||||||
|
action="toggle-source-audio"
|
||||||
icon={
|
icon={
|
||||||
<HugeiconsIcon
|
<HugeiconsIcon
|
||||||
icon={isSelectedSourceAudioSeparated ? Unlink02Icon : Link02Icon}
|
icon={isSelectedSourceAudioSeparated ? Unlink02Icon : Link02Icon}
|
||||||
|
|
@ -176,6 +181,7 @@ function ToolbarLeftSection() {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ToolbarButton
|
<ToolbarButton
|
||||||
|
action="duplicate-selected"
|
||||||
icon={<HugeiconsIcon icon={Copy01Icon} />}
|
icon={<HugeiconsIcon icon={Copy01Icon} />}
|
||||||
tooltip="Duplicate element"
|
tooltip="Duplicate element"
|
||||||
onClick={({ event }) =>
|
onClick={({ event }) =>
|
||||||
|
|
@ -191,6 +197,7 @@ function ToolbarLeftSection() {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ToolbarButton
|
<ToolbarButton
|
||||||
|
action="delete-selected"
|
||||||
icon={<HugeiconsIcon icon={Delete02Icon} />}
|
icon={<HugeiconsIcon icon={Delete02Icon} />}
|
||||||
tooltip="Delete element"
|
tooltip="Delete element"
|
||||||
onClick={({ event }) =>
|
onClick={({ event }) =>
|
||||||
|
|
@ -200,16 +207,15 @@ function ToolbarLeftSection() {
|
||||||
|
|
||||||
<div className="bg-border mx-1 h-6 w-px" />
|
<div className="bg-border mx-1 h-6 w-px" />
|
||||||
|
|
||||||
<Tooltip>
|
<ToolbarButton
|
||||||
<ToolbarButton
|
action="toggle-bookmark"
|
||||||
icon={<HugeiconsIcon icon={Bookmark02Icon} />}
|
icon={<HugeiconsIcon icon={Bookmark02Icon} />}
|
||||||
isActive={isCurrentlyBookmarked}
|
isActive={isCurrentlyBookmarked}
|
||||||
tooltip={isCurrentlyBookmarked ? "Remove bookmark" : "Add bookmark"}
|
tooltip={isCurrentlyBookmarked ? "Remove bookmark" : "Add bookmark"}
|
||||||
onClick={({ event }) =>
|
onClick={({ event }) =>
|
||||||
handleAction({ action: "toggle-bookmark", event })
|
handleAction({ action: "toggle-bookmark", event })
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
|
||||||
|
|
||||||
<GraphEditorPopover
|
<GraphEditorPopover
|
||||||
open={graphEditor.open}
|
open={graphEditor.open}
|
||||||
|
|
@ -284,6 +290,7 @@ function ToolbarRightSection({
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<TooltipProvider delayDuration={500}>
|
<TooltipProvider delayDuration={500}>
|
||||||
<ToolbarButton
|
<ToolbarButton
|
||||||
|
action="toggle-snapping"
|
||||||
icon={<HugeiconsIcon icon={MagnetIcon} />}
|
icon={<HugeiconsIcon icon={MagnetIcon} />}
|
||||||
isActive={snappingEnabled}
|
isActive={snappingEnabled}
|
||||||
tooltip="Auto snapping"
|
tooltip="Auto snapping"
|
||||||
|
|
@ -291,6 +298,7 @@ function ToolbarRightSection({
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ToolbarButton
|
<ToolbarButton
|
||||||
|
action="toggle-ripple-editing"
|
||||||
icon={<OcRippleIcon size={24} className="scale-110" />}
|
icon={<OcRippleIcon size={24} className="scale-110" />}
|
||||||
isActive={rippleEditingEnabled}
|
isActive={rippleEditingEnabled}
|
||||||
tooltip="Ripple editing"
|
tooltip="Ripple editing"
|
||||||
|
|
@ -337,6 +345,7 @@ function ToolbarButton({
|
||||||
disabled,
|
disabled,
|
||||||
isActive,
|
isActive,
|
||||||
buttonWrapper,
|
buttonWrapper,
|
||||||
|
action,
|
||||||
}: {
|
}: {
|
||||||
icon: React.ReactNode;
|
icon: React.ReactNode;
|
||||||
tooltip: string;
|
tooltip: string;
|
||||||
|
|
@ -344,7 +353,17 @@ function ToolbarButton({
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
buttonWrapper?: (button: React.ReactElement) => React.ReactElement;
|
buttonWrapper?: (button: React.ReactElement) => React.ReactElement;
|
||||||
|
action?: TActionWithOptionalArgs;
|
||||||
}) {
|
}) {
|
||||||
|
const { shortcuts } = useKeyboardShortcutsHelp();
|
||||||
|
const shortcut = action
|
||||||
|
? shortcuts.find((s) => s.action === action)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
const tooltipContent = shortcut
|
||||||
|
? `${tooltip} (${shortcut.keys.join(" or ")})`
|
||||||
|
: tooltip;
|
||||||
|
|
||||||
const button = (
|
const button = (
|
||||||
<Button
|
<Button
|
||||||
variant={isActive ? "secondary" : "text"}
|
variant={isActive ? "secondary" : "text"}
|
||||||
|
|
@ -370,7 +389,7 @@ function ToolbarButton({
|
||||||
return (
|
return (
|
||||||
<Tooltip delayDuration={200}>
|
<Tooltip delayDuration={200}>
|
||||||
<TooltipTrigger asChild>{trigger}</TooltipTrigger>
|
<TooltipTrigger asChild>{trigger}</TooltipTrigger>
|
||||||
<TooltipContent>{tooltip}</TooltipContent>
|
<TooltipContent>{tooltipContent}</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue