-
Notifications
You must be signed in to change notification settings - Fork 206
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
9fe9158
deef92c
041d9db
d3440dc
888d6e8
08a942d
4d109be
41cf451
a4382dd
620ef88
2c610da
fca85a1
6d3ed8f
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.