All checks were successful
CI/CD Pipeline / VM Test - backend-integration (push) Successful in 8s
CI/CD Pipeline / VM Test - full-stack (push) Successful in 7s
CI/CD Pipeline / VM Test - performance (push) Successful in 7s
CI/CD Pipeline / VM Test - security (push) Successful in 7s
CI/CD Pipeline / Backend Linting (push) Successful in 2s
CI/CD Pipeline / Frontend Linting (push) Successful in 16s
CI/CD Pipeline / Nix Flake Check (push) Successful in 38s
CI/CD Pipeline / CI Summary (push) Successful in 0s
226 lines
4.7 KiB
TypeScript
226 lines
4.7 KiB
TypeScript
/**
|
|
* Keyboard shortcuts for canvas operations
|
|
* Handles Ctrl+A (select all), Escape (deselect), and other shortcuts
|
|
*/
|
|
|
|
import { selection } from '$lib/stores/selection';
|
|
|
|
export interface KeyboardShortcutHandlers {
|
|
onSelectAll?: (allImageIds: string[]) => void;
|
|
onDeselectAll?: () => void;
|
|
onDelete?: () => void;
|
|
onCopy?: () => void;
|
|
onCut?: () => void;
|
|
onPaste?: () => void;
|
|
onUndo?: () => void;
|
|
onRedo?: () => void;
|
|
onBringToFront?: () => void;
|
|
onSendToBack?: () => void;
|
|
onBringForward?: () => void;
|
|
onSendBackward?: () => void;
|
|
}
|
|
|
|
/**
|
|
* Setup keyboard shortcuts for canvas
|
|
*/
|
|
export function setupKeyboardShortcuts(
|
|
getAllImageIds: () => string[],
|
|
handlers: KeyboardShortcutHandlers = {}
|
|
): () => void {
|
|
/**
|
|
* Handle keyboard shortcuts
|
|
*/
|
|
function handleKeyDown(e: KeyboardEvent) {
|
|
// Ignore if typing in input/textarea
|
|
if (
|
|
document.activeElement?.tagName === 'INPUT' ||
|
|
document.activeElement?.tagName === 'TEXTAREA'
|
|
) {
|
|
return;
|
|
}
|
|
|
|
const isCtrlOrCmd = e.ctrlKey || e.metaKey;
|
|
|
|
// Ctrl+A / Cmd+A - Select all
|
|
if (isCtrlOrCmd && e.key === 'a') {
|
|
e.preventDefault();
|
|
const allIds = getAllImageIds();
|
|
selection.selectAll(allIds);
|
|
|
|
if (handlers.onSelectAll) {
|
|
handlers.onSelectAll(allIds);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Escape - Deselect all
|
|
if (e.key === 'Escape') {
|
|
e.preventDefault();
|
|
selection.clearSelection();
|
|
|
|
if (handlers.onDeselectAll) {
|
|
handlers.onDeselectAll();
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Delete / Backspace - Delete selected
|
|
if (e.key === 'Delete' || e.key === 'Backspace') {
|
|
e.preventDefault();
|
|
|
|
if (handlers.onDelete) {
|
|
handlers.onDelete();
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Ctrl+C / Cmd+C - Copy
|
|
if (isCtrlOrCmd && e.key === 'c') {
|
|
e.preventDefault();
|
|
|
|
if (handlers.onCopy) {
|
|
handlers.onCopy();
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Ctrl+X / Cmd+X - Cut
|
|
if (isCtrlOrCmd && e.key === 'x') {
|
|
e.preventDefault();
|
|
|
|
if (handlers.onCut) {
|
|
handlers.onCut();
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Ctrl+V / Cmd+V - Paste
|
|
if (isCtrlOrCmd && e.key === 'v') {
|
|
e.preventDefault();
|
|
|
|
if (handlers.onPaste) {
|
|
handlers.onPaste();
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Ctrl+Z / Cmd+Z - Undo
|
|
if (isCtrlOrCmd && e.key === 'z' && !e.shiftKey) {
|
|
e.preventDefault();
|
|
|
|
if (handlers.onUndo) {
|
|
handlers.onUndo();
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Ctrl+Shift+Z / Cmd+Shift+Z - Redo
|
|
if (isCtrlOrCmd && e.key === 'z' && e.shiftKey) {
|
|
e.preventDefault();
|
|
|
|
if (handlers.onRedo) {
|
|
handlers.onRedo();
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Ctrl+Y / Cmd+Y - Alternative Redo
|
|
if (isCtrlOrCmd && e.key === 'y') {
|
|
e.preventDefault();
|
|
|
|
if (handlers.onRedo) {
|
|
handlers.onRedo();
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Ctrl+] - Bring to front
|
|
if (isCtrlOrCmd && e.key === ']') {
|
|
e.preventDefault();
|
|
|
|
if (handlers.onBringToFront) {
|
|
handlers.onBringToFront();
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Ctrl+[ - Send to back
|
|
if (isCtrlOrCmd && e.key === '[') {
|
|
e.preventDefault();
|
|
|
|
if (handlers.onSendToBack) {
|
|
handlers.onSendToBack();
|
|
}
|
|
return;
|
|
}
|
|
|
|
// PageUp - Bring forward
|
|
if (e.key === 'PageUp') {
|
|
e.preventDefault();
|
|
|
|
if (handlers.onBringForward) {
|
|
handlers.onBringForward();
|
|
}
|
|
return;
|
|
}
|
|
|
|
// PageDown - Send backward
|
|
if (e.key === 'PageDown') {
|
|
e.preventDefault();
|
|
|
|
if (handlers.onSendBackward) {
|
|
handlers.onSendBackward();
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Attach event listener
|
|
window.addEventListener('keydown', handleKeyDown);
|
|
|
|
// Return cleanup function
|
|
return () => {
|
|
window.removeEventListener('keydown', handleKeyDown);
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Select all images programmatically
|
|
*/
|
|
export function selectAllImages(allImageIds: string[]): void {
|
|
selection.selectAll(allImageIds);
|
|
}
|
|
|
|
/**
|
|
* Deselect all images programmatically
|
|
*/
|
|
export function deselectAllImages(): void {
|
|
selection.clearSelection();
|
|
}
|
|
|
|
/**
|
|
* Check if modifier key is pressed
|
|
*/
|
|
export function isModifierPressed(e: KeyboardEvent): boolean {
|
|
return e.ctrlKey || e.metaKey;
|
|
}
|
|
|
|
/**
|
|
* Check if shift key is pressed
|
|
*/
|
|
export function isShiftPressed(e: KeyboardEvent): boolean {
|
|
return e.shiftKey;
|
|
}
|
|
|
|
/**
|
|
* Get keyboard shortcut display string
|
|
*/
|
|
export function getShortcutDisplay(shortcut: string): string {
|
|
const isMac = typeof navigator !== 'undefined' && /Mac/.test(navigator.platform);
|
|
|
|
return shortcut
|
|
.replace('Ctrl', isMac ? '⌘' : 'Ctrl')
|
|
.replace('Alt', isMac ? '⌥' : 'Alt')
|
|
.replace('Shift', isMac ? '⇧' : 'Shift');
|
|
}
|