mirror of
https://github.com/nexu-io/open-design.git
synced 2026-06-01 03:14:35 +07:00
feat(web): add Cmd+, shortcut to open settings with platform shortcut badge (#1173)
Register a capture-phase Cmd+, (mac) / Ctrl+, (win/linux) listener in App.tsx that opens Settings, and show a shortcut badge on the Settings menu item in both AvatarMenu and EntryView. Extract the duplicated isMac platform check into a shared isMacPlatform() utility in utils/platform.ts, replacing inline copies in FileWorkspace and ProjectView as well.
This commit is contained in:
parent
2838a28585
commit
979733d39b
6 changed files with 28 additions and 6 deletions
|
|
@ -34,6 +34,7 @@ import {
|
|||
syncMediaProvidersToDaemon,
|
||||
} from './state/config';
|
||||
import { applyAppearanceToDocument } from './state/appearance';
|
||||
import { isMacPlatform } from './utils/platform';
|
||||
import {
|
||||
createProject,
|
||||
deleteProject as deleteProjectApi,
|
||||
|
|
@ -717,6 +718,22 @@ export function App() {
|
|||
setSettingsOpen(true);
|
||||
}, []);
|
||||
|
||||
// Cmd+, (mac) / Ctrl+, (win/linux) opens Settings. Capture phase so we
|
||||
// beat the browser's default Preferences dialog. Platform-gated so
|
||||
// meta/ctrl don't conflict across OS.
|
||||
useEffect(() => {
|
||||
const onKeyDown = (e: KeyboardEvent) => {
|
||||
const primary = isMacPlatform() ? e.metaKey && !e.ctrlKey : e.ctrlKey && !e.metaKey;
|
||||
if (primary && !e.shiftKey && !e.altKey && e.key === ',') {
|
||||
if (e.isComposing) return;
|
||||
e.preventDefault();
|
||||
openSettings();
|
||||
}
|
||||
};
|
||||
window.addEventListener('keydown', onKeyDown, { capture: true });
|
||||
return () => window.removeEventListener('keydown', onKeyDown, { capture: true });
|
||||
}, [openSettings]);
|
||||
|
||||
// Explicit enabled toggle — true = wake, false = tuck. Persists to
|
||||
// localStorage so the overlay state survives across reloads. We keep
|
||||
// `adopted` untouched so the entry-view CTA does not regress to
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { Icon } from './Icon';
|
|||
import { renderModelOptions } from './modelOptions';
|
||||
import type { AgentInfo, AppConfig, ExecMode } from '../types';
|
||||
import { apiProtocolLabel } from '../utils/apiProtocol';
|
||||
import { isMacPlatform } from '../utils/platform';
|
||||
|
||||
interface Props {
|
||||
config: AppConfig;
|
||||
|
|
@ -265,6 +266,7 @@ export function AvatarMenu({
|
|||
<Icon name="settings" size={14} />
|
||||
</span>
|
||||
<span>{t('avatar.settings')}</span>
|
||||
<span className="avatar-item-meta">{isMacPlatform() ? '⌘,' : 'Ctrl+,'}</span>
|
||||
</button>
|
||||
{onBack ? (
|
||||
<button
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import { PetRail } from './pet/PetRail';
|
|||
import { PromptTemplatePreviewModal } from './PromptTemplatePreviewModal';
|
||||
import { PromptTemplatesTab } from './PromptTemplatesTab';
|
||||
import { apiProtocolLabel } from '../utils/apiProtocol';
|
||||
import { isMacPlatform } from '../utils/platform';
|
||||
|
||||
type TopTab = 'designs' | 'examples' | 'design-systems' | 'image-templates' | 'video-templates';
|
||||
|
||||
|
|
@ -451,6 +452,7 @@ export function EntryView({
|
|||
<Icon name="settings" size={14} />
|
||||
</span>
|
||||
<span>{t('avatar.settings')}</span>
|
||||
<span className="avatar-item-meta">{isMacPlatform() ? '⌘,' : 'Ctrl+,'}</span>
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import {
|
|||
type DragEvent as ReactDragEvent,
|
||||
} from 'react';
|
||||
import { useT } from '../i18n';
|
||||
import { isMacPlatform } from '../utils/platform';
|
||||
import {
|
||||
deleteProjectFile,
|
||||
fetchProjectFileText,
|
||||
|
|
@ -332,10 +333,8 @@ export function FileWorkspace({
|
|||
// text fields, and on win/linux we don't steal Cmd+P (rare but possible
|
||||
// on remapped keyboards).
|
||||
useEffect(() => {
|
||||
const isMac =
|
||||
typeof navigator !== 'undefined' && /Mac|iPod|iPhone|iPad/.test(navigator.platform);
|
||||
const onKeyDown = (e: KeyboardEvent) => {
|
||||
const primary = isMac ? e.metaKey && !e.ctrlKey : e.ctrlKey && !e.metaKey;
|
||||
const primary = isMacPlatform() ? e.metaKey && !e.ctrlKey : e.ctrlKey && !e.metaKey;
|
||||
if (primary && !e.shiftKey && !e.altKey && e.key.toLowerCase() === 'p') {
|
||||
if (e.isComposing) return;
|
||||
e.preventDefault();
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import { useProjectFileEvents, type ProjectEvent } from '../providers/project-ev
|
|||
import { composeSystemPrompt, type ResearchOptions } from '@open-design/contracts';
|
||||
import { navigate } from '../router';
|
||||
import { agentDisplayName, agentModelDisplayName } from '../utils/agentLabels';
|
||||
import { isMacPlatform } from '../utils/platform';
|
||||
import {
|
||||
apiProtocolAgentId,
|
||||
apiProtocolModelLabel,
|
||||
|
|
@ -1956,10 +1957,8 @@ export function ProjectView({
|
|||
// Quick Switcher shortcut. ⌘+Shift+K is free (⌘+P is the only
|
||||
// existing primary-modifier shortcut on this surface).
|
||||
useEffect(() => {
|
||||
const isMac =
|
||||
typeof navigator !== 'undefined' && /Mac|iPod|iPhone|iPad/.test(navigator.platform);
|
||||
const onKeyDown = (e: KeyboardEvent) => {
|
||||
const primary = isMac ? e.metaKey && !e.ctrlKey : e.ctrlKey && !e.metaKey;
|
||||
const primary = isMacPlatform() ? e.metaKey && !e.ctrlKey : e.ctrlKey && !e.metaKey;
|
||||
if (primary && e.shiftKey && !e.altKey && e.key.toLowerCase() === 'k') {
|
||||
if (e.isComposing) return;
|
||||
if (!designMdState.exists) return;
|
||||
|
|
|
|||
3
apps/web/src/utils/platform.ts
Normal file
3
apps/web/src/utils/platform.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export function isMacPlatform(): boolean {
|
||||
return typeof navigator !== 'undefined' && /Mac|iPod|iPhone|iPad/.test(navigator.platform);
|
||||
}
|
||||
Loading…
Reference in a new issue