Add keyboard shortcuts to menus
This commit is contained in:
parent
237b8882cf
commit
455dd4a611
62
src/main.js
62
src/main.js
|
|
@ -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 = () => {
|
||||||
|
|
|
||||||
25
src/utils.js
25
src/utils.js
|
|
@ -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
|
||||||
};
|
};
|
||||||
Loading…
Reference in New Issue