diff options
Diffstat (limited to 'src/client/views/GlobalKeyHandler.ts')
-rw-r--r-- | src/client/views/GlobalKeyHandler.ts | 196 |
1 files changed, 109 insertions, 87 deletions
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index 2f64ea28c..c73da35bc 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -8,11 +8,10 @@ import { Cast, PromiseValue } from '../../fields/Types'; import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager'; import { DragManager } from '../util/DragManager'; import { GroupManager } from '../util/GroupManager'; -import { SelectionManager } from '../util/SelectionManager'; import { SettingsManager } from '../util/SettingsManager'; import { SharingManager } from '../util/SharingManager'; import { SnappingManager } from '../util/SnappingManager'; -import { UndoManager } from '../util/UndoManager'; +import { UndoManager, undoable } from '../util/UndoManager'; import { ContextMenu } from './ContextMenu'; import { DocumentDecorations } from './DocumentDecorations'; import { InkStrokeProperties } from './InkStrokeProperties'; @@ -21,24 +20,26 @@ import { MainView } from './MainView'; import { CollectionDockingView } from './collections/CollectionDockingView'; import { CollectionStackedTimeline } from './collections/CollectionStackedTimeline'; import { CollectionFreeFormView } from './collections/collectionFreeForm'; +import { CollectionFreeFormDocumentView } from './nodes/CollectionFreeFormDocumentView'; import { DocumentLinksButton } from './nodes/DocumentLinksButton'; -import { OpenWhereMod } from './nodes/DocumentView'; +import { DocumentView } from './nodes/DocumentView'; +import { OpenWhereMod } from './nodes/OpenWhere'; import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; import { AnchorMenu } from './pdf/AnchorMenu'; const modifiers = ['control', 'meta', 'shift', 'alt']; -type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo; type KeyControlInfo = { preventDefault: boolean; stopPropagation: boolean; }; - -export let CtrlKey = false; +type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo; export class KeyManager { - public static Instance: KeyManager = new KeyManager(); + // eslint-disable-next-line no-use-before-define + public static Instance: KeyManager; private router = new Map<string, KeyHandler>(); constructor() { + KeyManager.Instance = this; const isMac = navigator.platform.toLowerCase().indexOf('mac') >= 0; // SHIFT CONTROL ALT META @@ -47,10 +48,20 @@ export class KeyManager { this.router.set(isMac ? '0100' : '0010', this.alt); this.router.set(isMac ? '1001' : '1100', this.ctrl_shift); this.router.set('1000', this.shift); + + window.removeEventListener('keydown', KeyManager.Instance.handleModifiers, true); + window.addEventListener('keydown', KeyManager.Instance.handleModifiers, true); + window.removeEventListener('keyup', KeyManager.Instance.unhandleModifiers); + window.addEventListener('keyup', KeyManager.Instance.unhandleModifiers); + window.removeEventListener('keydown', KeyManager.Instance.handle); + window.addEventListener('keydown', KeyManager.Instance.handle); + window.removeEventListener('keyup', KeyManager.Instance.unhandle); + window.addEventListener('keyup', KeyManager.Instance.unhandle); + window.addEventListener('paste', KeyManager.Instance.paste as any); } - public unhandle = action((e: KeyboardEvent) => { - e.key === 'Control' && (CtrlKey = false); + public unhandle = action((/* e: KeyboardEvent */) => { + /* empty */ }); public handleModifiers = action((e: KeyboardEvent) => { if (e.shiftKey) SnappingManager.SetShiftKey(true); @@ -65,11 +76,9 @@ export class KeyManager { public handle = action((e: KeyboardEvent) => { // accumulate buffer of characters to insert in a new text note. once the note is created, it will stop keyboard events from reaching this function. - if (FormattedTextBox.SelectOnLoadChar) FormattedTextBox.SelectOnLoadChar = FormattedTextBox.SelectOnLoadChar + (e.key === 'Enter' ? '\n' : e.key); - e.key === 'Control' && (CtrlKey = true); - //if (!Doc.noviceMode && e.key.toLocaleLowerCase() === "shift") DocServer.UPDATE_SERVER_CACHE(true); + if (FormattedTextBox.SelectOnLoadChar) FormattedTextBox.SelectOnLoadChar += e.key === 'Enter' ? '\n' : e.key; const keyname = e.key && e.key.toLowerCase(); - this.handleGreedy(keyname); + this.handleGreedy(/* keyname */); if (modifiers.includes(keyname)) { return; @@ -89,14 +98,11 @@ export class KeyManager { control.preventDefault && e.preventDefault(); }); - private handleGreedy = action((keyname: string) => { - switch (keyname) { - } - }); + private handleGreedy = action((/* keyname: string */) => {}); nudge = (x: number, y: number, label: string) => { - const nudgeable = SelectionManager.Views.some(dv => dv.CollectionFreeFormDocumentView?.nudge); - nudgeable && UndoManager.RunInBatch(() => SelectionManager.Views.map(dv => dv.CollectionFreeFormDocumentView?.nudge(x, y)), label); + const nudgeable = DocumentView.Selected().some(dv => CollectionFreeFormDocumentView.from(dv)?.nudge); + nudgeable && UndoManager.RunInBatch(() => DocumentView.Selected().map(dv => CollectionFreeFormDocumentView.from(dv)?.nudge(x, y)), label); return { stopPropagation: nudgeable, preventDefault: nudgeable }; }; @@ -104,67 +110,62 @@ export class KeyManager { switch (keyname) { case 'u': if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') { - const ungroupings = SelectionManager.Views; - UndoManager.RunInBatch(() => ungroupings.map(dv => (dv.layoutDoc.group = undefined)), 'ungroup'); - SelectionManager.DeselectAll(); + const ungroupings = DocumentView.Selected(); + undoable(() => () => ungroupings.forEach(dv => { dv.layoutDoc.group = undefined; }), 'ungroup'); + DocumentView.DeselectAll(); } break; case 'g': if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') { - const selected = SelectionManager.Views; - const collectionView = selected.reduce((col, dv) => (col === null || dv.CollectionFreeFormView === col ? dv.CollectionFreeFormView : undefined), null as null | undefined | CollectionFreeFormView); - if (collectionView) { - UndoManager.RunInBatch(() => - collectionView._marqueeViewRef.current?.collection(e, true, SelectionManager.Docs) - , 'grouping'); - break; - } + const selected = DocumentView.Selected(); + const cv = selected.reduce((col, dv) => (!col || CollectionFreeFormView.from(dv) === col ? CollectionFreeFormView.from(dv) : undefined), undefined as undefined | CollectionFreeFormView); + cv && undoable(() => cv._marqueeViewRef.current?.collection(e, true, DocumentView.SelectedDocs()), 'grouping'); } break; case ' ': // MarqueeView.DragMarquee = !MarqueeView.DragMarquee; // bcz: this needs a better disclosure UI break; - case 'escape': - DocumentLinksButton.StartLink = undefined; - DocumentLinksButton.StartLinkView = undefined; - InkStrokeProperties.Instance._controlButton = false; - Doc.ActiveTool = InkTool.None; - DragManager.CompleteWindowDrag?.(true); - var doDeselect = true; - if (SnappingManager.IsDragging) { - DragManager.AbortDrag(); - } - if (CollectionDockingView.Instance?.HasFullScreen) { - CollectionDockingView.Instance?.CloseFullScreen(); - } - if (CollectionStackedTimeline.SelectingRegions.size) { - CollectionStackedTimeline.StopSelecting(); - doDeselect = false; - } else { - doDeselect = !ContextMenu.Instance.closeMenu(); - } - if (doDeselect) { - SelectionManager.DeselectAll(); - LightboxView.Instance.SetLightboxDoc(undefined); + case 'escape': { + DocumentLinksButton.StartLink = undefined; + DocumentLinksButton.StartLinkView = undefined; + InkStrokeProperties.Instance._controlButton = false; + Doc.ActiveTool = InkTool.None; + DragManager.CompleteWindowDrag?.(true); + let doDeselect = true; + if (SnappingManager.IsDragging) { + DragManager.AbortDrag(); + } + if (CollectionDockingView.Instance?.HasFullScreen) { + CollectionDockingView.Instance?.CloseFullScreen(); + } + if (CollectionStackedTimeline.SelectingRegions.size) { + CollectionStackedTimeline.StopSelecting(); + doDeselect = false; + } else { + doDeselect = !ContextMenu.Instance.closeMenu(); + } + if (doDeselect) { + DocumentView.DeselectAll(); + LightboxView.Instance.SetLightboxDoc(undefined); + } + // DictationManager.Controls.stop(); + GoogleAuthenticationManager.Instance.cancel(); + SharingManager.Instance.close(); + if (!GroupManager.Instance.isOpen) SettingsManager.Instance.closeMgr(); + GroupManager.Instance.close(); + window.getSelection()?.empty(); + document.body.focus(); } - // DictationManager.Controls.stop(); - GoogleAuthenticationManager.Instance.cancel(); - SharingManager.Instance.close(); - if (!GroupManager.Instance.isOpen) SettingsManager.Instance.close(); - GroupManager.Instance.close(); - window.getSelection()?.empty(); - document.body.focus(); break; - case 'enter': { - //DocumentDecorations.Instance.onCloseClick(false); + case 'enter': + // DocumentDecorations.Instance.onCloseClick(false); break; - } case 'delete': case 'backspace': if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') { if (LightboxView.LightboxDoc) { LightboxView.Instance.SetLightboxDoc(undefined); - SelectionManager.DeselectAll(); + DocumentView.DeselectAll(); } else if (!window.getSelection()?.toString()) DocumentDecorations.Instance.onCloseClick(true); return { stopPropagation: true, preventDefault: true }; } @@ -173,6 +174,7 @@ export class KeyManager { case 'arrowright': return this.nudge(1,0, 'nudge right'); case 'arrowup': return this.nudge(0, -1, 'nudge up'); case 'arrowdown': return this.nudge(0, 1, 'nudge down'); + default: } // prettier-ignore return { @@ -189,17 +191,18 @@ export class KeyManager { case 'arrowdown': return this.nudge(0, 10, 'nudge down'); case 'u' : if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') { - UndoManager.RunInBatch(() => SelectionManager.Docs.forEach(doc => (doc.group = undefined)), 'unggroup'); - SelectionManager.DeselectAll(); + UndoManager.RunInBatch(() => DocumentView.Selected().map(dv => dv.Document).forEach(doc => {doc.group = undefined}), 'unggroup'); + DocumentView.DeselectAll(); } break; case 'g': if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') { const randomGroup = random(0, 1000); - UndoManager.RunInBatch(() => SelectionManager.Docs.forEach(doc => (doc.group = randomGroup)), 'group'); - SelectionManager.DeselectAll(); + UndoManager.RunInBatch(() => DocumentView.Selected().forEach(dv => {dv.Document.group = randomGroup}), 'group'); + DocumentView.DeselectAll(); } break; + default: } // prettier-ignore return { @@ -215,8 +218,9 @@ export class KeyManager { switch (keyname) { case 'ƒ': case 'f': - const dv = SelectionManager.Views?.[0]; - UndoManager.RunInBatch(() => dv.CollectionFreeFormDocumentView?.float(), 'float'); + UndoManager.RunInBatch(() => CollectionFreeFormDocumentView.from(DocumentView.Selected()?.[0])?.float(), 'float'); + break; + default: } return { @@ -251,20 +255,24 @@ export class KeyManager { PromiseValue(Cast(Doc.UserDoc()['tabs-button-tools'], Doc)).then(pv => pv && (pv.onClick as ScriptField).script.run({ this: pv })); break; case 'i': - const importBtn = DocListCast(Doc.MyLeftSidebarMenu.data).find(d => d.target === Doc.MyImports); - if (importBtn) { - MainView.Instance.selectMenu(importBtn); + { + const importBtn = DocListCast(Doc.MyLeftSidebarMenu.data).find(d => d.target === Doc.MyImports); + if (importBtn) { + MainView.Instance.selectMenu(importBtn); + } } break; case 's': - const trailsBtn = DocListCast(Doc.MyLeftSidebarMenu.data).find(d => d.target === Doc.MyTrails); - if (trailsBtn) { - MainView.Instance.selectMenu(trailsBtn); + { + const trailsBtn = DocListCast(Doc.MyLeftSidebarMenu.data).find(d => d.target === Doc.MyTrails); + if (trailsBtn) { + MainView.Instance.selectMenu(trailsBtn); + } } break; case 'f': - if (SelectionManager.Views.length === 1 && SelectionManager.Views[0].ComponentView?.search) { - SelectionManager.Views[0].ComponentView?.search?.('', false, false); + if (DocumentView.Selected().length === 1 && DocumentView.Selected()[0].ComponentView?.search) { + DocumentView.Selected()[0].ComponentView?.search?.('', false, false); } else { const searchBtn = DocListCast(Doc.MyLeftSidebarMenu.data).find(d => d.target === Doc.MySearcher); if (searchBtn) { @@ -283,14 +291,14 @@ export class KeyManager { break; case 'y': if (Doc.ActivePage !== 'home') { - SelectionManager.DeselectAll(); + DocumentView.DeselectAll(); UndoManager.Redo(); } stopPropagation = false; break; case 'z': if (Doc.ActivePage !== 'home') { - SelectionManager.DeselectAll(); + DocumentView.DeselectAll(); UndoManager.Undo(); } stopPropagation = false; @@ -306,11 +314,17 @@ export class KeyManager { preventDefault = false; break; case 'x': - if (SelectionManager.Views.length) { + if (DocumentView.Selected().length) { const bds = DocumentDecorations.Instance.Bounds; - const pt = SelectionManager.Views[0].screenToViewTransform().transformPoint(bds.x + (bds.r - bds.x) / 2, bds.y + (bds.b - bds.y) / 2); - const text = `__DashDocId(${pt?.[0] || 0},${pt?.[1] || 0}):` + SelectionManager.Views.map(dv => dv.Document[Id]).join(':'); - SelectionManager.Views.length && navigator.clipboard.writeText(text); + const pt = DocumentView.Selected()[0] // + .screenToViewTransform() + .transformPoint(bds.x + (bds.r - bds.x) / 2, bds.y + (bds.b - bds.y) / 2); + const text = + `__DashDocId(${pt?.[0] || 0},${pt?.[1] || 0}):` + // + DocumentView.Selected() + .map(dv => dv.Document[Id]) + .join(':'); // prettier-ignore + DocumentView.Selected().length && navigator.clipboard.writeText(text); DocumentDecorations.Instance.onCloseClick(true); stopPropagation = false; preventDefault = false; @@ -319,13 +333,20 @@ export class KeyManager { case 'c': if ((document.activeElement as any)?.type !== 'text' && !AnchorMenu.Instance.Active && DocumentDecorations.Instance.Bounds.r - DocumentDecorations.Instance.Bounds.x > 2) { const bds = DocumentDecorations.Instance.Bounds; - const pt = SelectionManager.Views[0].screenToViewTransform().transformPoint(bds.x + (bds.r - bds.x) / 2, bds.y + (bds.b - bds.y) / 2); - const text = `__DashCloneId(${pt?.[0] || 0},${pt?.[1] || 0}):` + SelectionManager.Views.map(dv => dv.Document[Id]).join(':'); - SelectionManager.Views.length && navigator.clipboard.writeText(text); + const pt = DocumentView.Selected()[0] + .screenToViewTransform() + .transformPoint(bds.x + (bds.r - bds.x) / 2, bds.y + (bds.b - bds.y) / 2); // prettier-ignore + const text = + `__DashCloneId(${pt?.[0] || 0},${pt?.[1] || 0}):` + + DocumentView.SelectedDocs() + .map(doc => doc[Id]) + .join(':'); // prettier-ignore + DocumentView.Selected().length && navigator.clipboard.writeText(text); stopPropagation = false; } preventDefault = false; break; + default: } return { @@ -339,7 +360,7 @@ export class KeyManager { if (!plain) return; const clone = plain.startsWith('__DashCloneId('); const docids = plain.split(':'); // hack! docids[0] is the top left of the selection rectangle - const addDocument = SelectionManager.Views.lastElement()?.ComponentView?.addDocument; + const addDocument = DocumentView.Selected().lastElement()?.ComponentView?.addDocument; if (addDocument && (plain.startsWith('__DashDocId(') || clone)) { Doc.Paste(docids.slice(1), clone, addDocument); } @@ -360,6 +381,7 @@ export class KeyManager { case 'p': Doc.ActiveTool = InkTool.Write; break; + default: } return { |