-
Notifications
You must be signed in to change notification settings - Fork 209
Support Command shortcuts in Crumble on macOS #1065
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
4aec90b
1e15490
b574415
0f35722
ff24cc8
1d7454d
4b02bb1
294959b
34ae69c
fb13e1b
a0bb7b9
9ab9bee
b83b194
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -197,7 +197,7 @@ editorState.canvas.addEventListener('mouseup', ev => { | |
| editorState.mouseDownY = undefined; | ||
| editorState.curMouseX = ev.offsetX + OFFSET_X; | ||
| editorState.curMouseY = ev.offsetY + OFFSET_Y; | ||
| editorState.changeFocus(highlightedArea, ev.shiftKey, ev.ctrlKey); | ||
| editorState.changeFocus(highlightedArea, ev.shiftKey, ev.ctrlKey || ev.metaKey); | ||
| if (ev.buttons === 1) { | ||
| isInScrubber = false; | ||
| } | ||
|
|
@@ -222,17 +222,8 @@ function makeChordHandlers() { | |
| res.set('ctrl+shift+z', preview => { if (!preview) editorState.redo() }); | ||
| res.set('ctrl+c', async preview => { await copyToClipboard(); }); | ||
| res.set('ctrl+v', pasteFromClipboard); | ||
| res.set('ctrl+x', async preview => { | ||
| await copyToClipboard(); | ||
| if (editorState.focusedSet.size === 0) { | ||
| let c = editorState.copyOfCurCircuit(); | ||
| c.layers[editorState.curLayer].id_ops.clear(); | ||
| c.layers[editorState.curLayer].markers.length = 0; | ||
| editorState.commit_or_preview(c, preview); | ||
| } else { | ||
| editorState.deleteAtFocus(preview); | ||
| } | ||
| }); | ||
| res.set('ctrl+x', cutToClipboard); | ||
|
|
||
| res.set('l', preview => { | ||
| if (!preview) { | ||
| editorState.timelineSet = new Map(editorState.focusedSet.entries()); | ||
|
|
@@ -360,6 +351,8 @@ function makeChordHandlers() { | |
| } | ||
|
|
||
| let fallbackEmulatedClipboard = undefined; | ||
| let pendingMetaPaste = false; | ||
| let pendingMetaPasteTimeout = undefined; | ||
| async function copyToClipboard() { | ||
| let c = editorState.copyOfCurCircuit(); | ||
| c.layers = [c.layers[editorState.curLayer]] | ||
|
|
@@ -397,6 +390,19 @@ async function pasteFromClipboard(preview) { | |
| return; | ||
| } | ||
|
|
||
| pasteTextAtFocus(text, preview); | ||
| } | ||
|
|
||
| /** | ||
| * Applies already-read clipboard text at the current focus. | ||
| * | ||
| * Text can come from navigator.clipboard for Ctrl+V, or from a browser paste | ||
| * event for Cmd+V. Keeping this shared avoids duplicating paste behavior. | ||
| * | ||
| * @param {!string} text | ||
| * @param {!boolean} preview | ||
| */ | ||
| function pasteTextAtFocus(text, preview) { | ||
| let pastedCircuit = Circuit.fromStimCircuit(text); | ||
| if (pastedCircuit.layers.length !== 1) { | ||
| throw new Error(text); | ||
|
|
@@ -442,12 +448,86 @@ async function pasteFromClipboard(preview) { | |
| editorState.commit_or_preview(newCircuit, preview); | ||
| } | ||
|
|
||
| function clearPendingMetaPaste() { | ||
| pendingMetaPaste = false; | ||
| if (pendingMetaPasteTimeout !== undefined) { | ||
| clearTimeout(pendingMetaPasteTimeout); | ||
| pendingMetaPasteTimeout = undefined; | ||
| } | ||
| } | ||
|
|
||
| async function cutToClipboard(preview) { | ||
| await copyToClipboard(); | ||
| if (editorState.focusedSet.size === 0) { | ||
| let c = editorState.copyOfCurCircuit(); | ||
| c.layers[editorState.curLayer].id_ops.clear(); | ||
| c.layers[editorState.curLayer].markers.length = 0; | ||
| editorState.commit_or_preview(c, preview); | ||
| } else { | ||
| editorState.deleteAtFocus(preview); | ||
| } | ||
| } | ||
|
|
||
| const CHORD_HANDLERS = makeChordHandlers(); | ||
| /** | ||
| * @param {!KeyboardEvent} ev | ||
| */ | ||
| function handleKeyboardEvent(ev) { | ||
| async function handleKeyboardEvent(ev) { | ||
| if (ev.type === 'keydown' && ev.metaKey) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wouldn't these better be right next to the other chord handlers (
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I had originally tried what you suggested: So I kept the cmd shortcut handling separate from cmd+V has one extra difference: it uses the browser paste event to get pasted text, instead of directly calling navigator.clipboard.readText(). This avoided the browser clipboard-read permission prompt I saw during testing. ctrl+V still gets text using the existing navigator.clipboard.readText() path. After the pasted text is obtained, cmd+V and ctrl+V now both call the same pasteTextAtFocus helper, so the actual Crumble editing behavior is shared (so the duplication is fixed). A short comment has been added above the helper explaining why it exists. I should note that I know very little about web development. I was asking chatGPT, testing, asking again and so on. So, probably there is a cleaner way to do this... |
||
| if (ev.repeat) { | ||
| ev.preventDefault(); | ||
| editorState.chorder.handleFocusChanged(); | ||
| return; | ||
| } | ||
|
|
||
| let key = ev.key.toLowerCase(); | ||
|
|
||
| if (key === 'z' && !ev.shiftKey) { | ||
| ev.preventDefault(); | ||
| editorState.chorder.handleFocusChanged(); | ||
| editorState.undo(); | ||
| return; | ||
| } | ||
| if ((key === 'z' && ev.shiftKey) || key === 'y') { | ||
| ev.preventDefault(); | ||
| editorState.chorder.handleFocusChanged(); | ||
| editorState.redo(); | ||
| return; | ||
| } | ||
| if (key === 'c') { | ||
| ev.preventDefault(); | ||
| editorState.chorder.handleFocusChanged(); | ||
| await copyToClipboard(); | ||
| return; | ||
| } | ||
| if (key === 'v') { | ||
| editorState.chorder.handleFocusChanged(); | ||
| pendingMetaPaste = true; | ||
| pendingMetaPasteTimeout = setTimeout(clearPendingMetaPaste, 1000); | ||
| return; | ||
| } | ||
| if (key === 'x') { | ||
| ev.preventDefault(); | ||
| editorState.chorder.handleFocusChanged(); | ||
| await cutToClipboard(false); | ||
| return; | ||
| } | ||
| if (key === 'backspace' || key === 'delete') { | ||
| ev.preventDefault(); | ||
| editorState.chorder.handleFocusChanged(); | ||
| editorState.deleteCurLayer(false); | ||
| return; | ||
| } | ||
| if (key === 'enter') { | ||
| ev.preventDefault(); | ||
| editorState.chorder.handleFocusChanged(); | ||
| editorState.insertLayer(false); | ||
| return; | ||
| } | ||
| } | ||
|
|
||
| editorState.chorder.handleKeyEvent(ev); | ||
|
|
||
| if (ev.type === 'keydown') { | ||
| if (ev.key.toLowerCase() === 'q') { | ||
| let d = ev.shiftKey ? 5 : 1; | ||
|
|
@@ -511,6 +591,20 @@ function handleKeyboardEvent(ev) { | |
| } | ||
| } | ||
|
|
||
| document.addEventListener('paste', ev => { | ||
| if (!pendingMetaPaste) { | ||
| return; | ||
| } | ||
| clearPendingMetaPaste(); | ||
|
|
||
| let text = ev.clipboardData.getData('text/plain'); | ||
| if (text === '') { | ||
| return; | ||
| } | ||
|
|
||
| ev.preventDefault(); | ||
| pasteTextAtFocus(text, false); | ||
| }); | ||
| document.addEventListener('keydown', handleKeyboardEvent); | ||
| document.addEventListener('keyup', handleKeyboardEvent); | ||
|
|
||
|
|
@@ -532,7 +626,7 @@ window.addEventListener('blur', () => { | |
| for (let anchor of document.getElementById('examples-div').querySelectorAll('a')) { | ||
| anchor.onclick = ev => { | ||
| // Don't stop the user from e.g. opening the example in a new tab using ctrl+click. | ||
| if (ev.shiftKey || ev.ctrlKey || ev.altKey || ev.button !== 0) { | ||
| if (ev.shiftKey || ev.ctrlKey || ev.metaKey || ev.altKey || ev.button !== 0) { | ||
| return undefined; | ||
| } | ||
| let circuitText = anchor.href.split('#circuit=')[1]; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is very similar to pasteFromClipboard. This code should be written once.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! This is fixed now. Please also see my reply to the next comment for further clarification.