Add keyboard shortcuts to menus

This commit is contained in:
Skyler Lehmkuhl 2024-12-21 05:23:17 -05:00
parent 237b8882cf
commit 455dd4a611
2 changed files with 71 additions and 16 deletions

View File

@ -3,7 +3,7 @@ import * as fitCurve from '/fit-curve.js';
import { Bezier } from "/bezier.js"; import { Bezier } from "/bezier.js";
import { Quadtree } from './quadtree.js'; import { Quadtree } from './quadtree.js';
import { createNewFileDialog, showNewFileDialog, closeDialog } from './newfile.js'; import { createNewFileDialog, showNewFileDialog, closeDialog } from './newfile.js';
import { titleCase, getMousePositionFraction, getKeyframesSurrounding, invertPixels, lerpColor, lerp, camelToWords, generateWaveform, floodFillRegion, getShapeAtPoint, hslToRgb, drawCheckerboardBackground, hexToHsl, hsvToRgb, hexToHsv, rgbToHex, clamp, drawBorderedRect, drawCenteredText, drawHorizontallyCenteredText } from './utils.js'; import { titleCase, getMousePositionFraction, getKeyframesSurrounding, invertPixels, lerpColor, lerp, camelToWords, generateWaveform, floodFillRegion, getShapeAtPoint, hslToRgb, drawCheckerboardBackground, hexToHsl, hsvToRgb, hexToHsv, rgbToHex, clamp, drawBorderedRect, drawCenteredText, drawHorizontallyCenteredText, deepMerge } from './utils.js';
import { backgroundColor, darkMode, foregroundColor, frameWidth, gutterHeight, highlight, iconSize, labelColor, layerHeight, layerWidth, scrubberColor, shade, shadow } from './styles.js'; import { backgroundColor, darkMode, foregroundColor, frameWidth, gutterHeight, highlight, iconSize, labelColor, layerHeight, layerWidth, scrubberColor, shade, shadow } from './styles.js';
import { Icon } from './icon.js'; import { Icon } from './icon.js';
const { writeTextFile: writeTextFile, readTextFile: readTextFile, writeFile: writeFile, readFile: readFile }= window.__TAURI__.fs; const { writeTextFile: writeTextFile, readTextFile: readTextFile, writeFile: writeFile, readFile: readFile }= window.__TAURI__.fs;
@ -246,6 +246,7 @@ let config = {
saveAs: "<mod>S", saveAs: "<mod>S",
open: "<mod>o", open: "<mod>o",
import: "<mod>i", import: "<mod>i",
export: "<mod>e",
quit: "<mod>q", quit: "<mod>q",
copy: "<mod>c", copy: "<mod>c",
paste: "<mod>v", paste: "<mod>v",
@ -254,6 +255,7 @@ let config = {
group: "<mod>g", group: "<mod>g",
zoomIn: "<mod>+", zoomIn: "<mod>+",
zoomOut: "<mod>-", zoomOut: "<mod>-",
resetZoom: "<mod>0"
}, },
fileWidth: 800, fileWidth: 800,
fileHeight: 600, fileHeight: 600,
@ -261,14 +263,25 @@ let config = {
recentFiles: [] recentFiles: []
} }
function getShortcut(shortcut) {
if (!(shortcut in config.shortcuts)) return undefined;
let shortcutValue = config.shortcuts[shortcut].replace("<mod>", "CmdOrCtrl+");
const key = shortcutValue.slice(-1);
// If the last character is uppercase, prepend "Shift+" to it
return key === key.toUpperCase() && key !== key.toLowerCase()
? shortcutValue.replace(key, `Shift+${key}`)
: shortcutValue.replace("++", "+Shift+="); // Hardcode uppercase from = to +
}
// Load the configuration from the file system // Load the configuration from the file system
async function loadConfig() { async function loadConfig() {
try { try {
const configPath = await join(await appLocalDataDir(), CONFIG_FILE_PATH); const configPath = await join(await appLocalDataDir(), CONFIG_FILE_PATH);
const configData = await readTextFile(configPath); const configData = await readTextFile(configPath);
config = JSON.parse(configData); config = deepMerge({...config}, JSON.parse(configData));
updateUI() updateUI()
console.log(config)
} catch (error) { } catch (error) {
console.log('Error loading config, returning default config:', error); console.log('Error loading config, returning default config:', error);
} }
@ -4460,21 +4473,25 @@ async function updateMenu() {
text: 'New file...', text: 'New file...',
enabled: true, enabled: true,
action: newFile, action: newFile,
accelerator: getShortcut("new")
}, },
{ {
text: 'Save', text: 'Save',
enabled: true, enabled: true,
action: save, action: save,
accelerator: getShortcut("save")
}, },
{ {
text: 'Save As...', text: 'Save As...',
enabled: true, enabled: true,
action: saveAs, action: saveAs,
accelerator: getShortcut("saveAs")
}, },
{ {
text: 'Open File...', text: 'Open File...',
enabled: true, enabled: true,
action: open, action: open,
accelerator: getShortcut("open")
}, },
{ {
text: 'Revert', text: 'Revert',
@ -4485,16 +4502,19 @@ async function updateMenu() {
text: 'Import...', text: 'Import...',
enabled: true, enabled: true,
action: importFile, action: importFile,
accelerator: getShortcut("import")
}, },
{ {
text: "Export...", text: "Export...",
enabled: true, enabled: true,
action: render action: render,
accelerator: getShortcut("export")
}, },
{ {
text: 'Quit', text: 'Quit',
enabled: true, enabled: true,
action: quit, action: quit,
accelerator: getShortcut("quit")
}, },
] ]
}) })
@ -4505,12 +4525,14 @@ async function updateMenu() {
{ {
text: "Undo " + ((undoStack.length>0) ? camelToWords(undoStack[undoStack.length-1].name) : ""), text: "Undo " + ((undoStack.length>0) ? camelToWords(undoStack[undoStack.length-1].name) : ""),
enabled: undoStack.length > 0, enabled: undoStack.length > 0,
action: undo action: undo,
accelerator: getShortcut("undo")
}, },
{ {
text: "Redo " + ((redoStack.length>0) ? camelToWords(redoStack[redoStack.length-1].name) : ""), text: "Redo " + ((redoStack.length>0) ? camelToWords(redoStack[redoStack.length-1].name) : ""),
enabled: redoStack.length > 0, enabled: redoStack.length > 0,
action: redo action: redo,
accelerator: getShortcut("redo")
}, },
{ {
text: "Cut", text: "Cut",
@ -4520,22 +4542,26 @@ async function updateMenu() {
{ {
text: "Copy", text: "Copy",
enabled: (context.selection.length > 0 || context.shapeselection.length > 0), enabled: (context.selection.length > 0 || context.shapeselection.length > 0),
action: copy action: copy,
accelerator: getShortcut("copy")
}, },
{ {
text: "Paste", text: "Paste",
enabled: true, enabled: true,
action: paste action: paste,
accelerator: getShortcut("paste")
}, },
{ {
text: "Delete", text: "Delete",
enabled: (context.selection.length > 0 || context.shapeselection.length > 0), enabled: (context.selection.length > 0 || context.shapeselection.length > 0),
action: delete_action action: delete_action,
accelerator: getShortcut("delete")
}, },
{ {
text: "Select All", text: "Select All",
enabled: true, enabled: true,
action: selectAll action: selectAll,
accelerator: getShortcut("selectAll")
}, },
] ]
}); });
@ -4546,7 +4572,8 @@ async function updateMenu() {
{ {
text: "Group", text: "Group",
enabled: context.selection.length != 0 || context.shapeselection.length != 0, enabled: context.selection.length != 0 || context.shapeselection.length != 0,
action: actions.group.create action: actions.group.create,
accelerator: getShortcut("group")
}, },
{ {
text: "Send to back", text: "Send to back",
@ -4622,7 +4649,8 @@ async function updateMenu() {
{ {
text: "Play", text: "Play",
enabled: !playing, enabled: !playing,
action: playPause action: playPause,
accelerator: getShortcut("playAnimation")
}, },
] ]
}); });
@ -4632,17 +4660,20 @@ async function updateMenu() {
{ {
text: "Zoom In", text: "Zoom In",
enabled: true, enabled: true,
action: zoomIn action: zoomIn,
accelerator: getShortcut("zoomIn")
}, },
{ {
text: "Zoom Out", text: "Zoom Out",
enabled: true, enabled: true,
action: zoomOut action: zoomOut,
accelerator: getShortcut("zoomOut")
}, },
{ {
text: "Actual Size", text: "Actual Size",
enabled: context.zoomLevel != 1, enabled: context.zoomLevel != 1,
action: resetZoom action: resetZoom,
accelerator: getShortcut("resetZoom")
}, },
] ]
}); });
@ -4742,6 +4773,7 @@ function getMimeType(filePath) {
} }
} }
function startToneOnUserInteraction() { function startToneOnUserInteraction() {
// Function to handle the first interaction (click or key press) // Function to handle the first interaction (click or key press)
const startTone = () => { const startTone = () => {

View File

@ -600,6 +600,28 @@ function drawHorizontallyCenteredText(ctx, text, x, y, height) {
ctx.fillText(text, x, centerY); ctx.fillText(text, x, centerY);
} }
function deepMerge(target, source) {
// If either target or source is not an object, return source (base case)
if (typeof target !== 'object' || target === null) {
return source;
}
// If target is an object, recursively merge
if (typeof source === 'object' && source !== null) {
for (let key in source) {
// If the key exists in both objects, and both are objects, recursively merge
if (target.hasOwnProperty(key) && typeof target[key] === 'object' && typeof source[key] === 'object') {
target[key] = deepMerge(target[key], source[key]);
} else {
// Otherwise, just assign the source value to target
target[key] = source[key];
}
}
}
return target;
}
export { export {
titleCase, titleCase,
getMousePositionFraction, getMousePositionFraction,
@ -620,5 +642,6 @@ export {
clamp, clamp,
drawBorderedRect, drawBorderedRect,
drawCenteredText, drawCenteredText,
drawHorizontallyCenteredText drawHorizontallyCenteredText,
deepMerge
}; };