From 308a128ff24da69927bd9bcc7111ac9ec5f1e4ed Mon Sep 17 00:00:00 2001 From: Mario Nebl Date: Wed, 25 Jul 2018 22:39:32 +0200 Subject: [PATCH] fix: reenable default edit operations on input and textarea --- .../page-list/page-tile-container.tsx | 2 +- src/electron/create-edit-message-handler.ts | 9 ++- .../create-main-menu/create-edit-menu.ts | 38 ++++++--- src/model/alva-app.ts | 14 ++++ src/model/project.ts | 80 ++++++++----------- src/renderer/create-edit-message-handler.ts | 3 +- src/renderer/create-init-message-handler.ts | 2 +- src/renderer/register-global-listeners.ts | 19 +++++ src/store/view-store.ts | 2 +- src/types/types.ts | 2 +- 10 files changed, 110 insertions(+), 61 deletions(-) diff --git a/src/container/page-list/page-tile-container.tsx b/src/container/page-list/page-tile-container.tsx index 35078bb41..b1270829f 100644 --- a/src/container/page-list/page-tile-container.tsx +++ b/src/container/page-list/page-tile-container.tsx @@ -34,7 +34,7 @@ export class PageTileContainer extends React.Component { if (rootElement) { store.setSelectedElement(rootElement); - store.getProject().setFocusedItem(Types.ItemType.Page, this.props.page); + store.getProject().setFocusedItem(this.props.page); } } diff --git a/src/electron/create-edit-message-handler.ts b/src/electron/create-edit-message-handler.ts index 081d9d1ed..40f314672 100644 --- a/src/electron/create-edit-message-handler.ts +++ b/src/electron/create-edit-message-handler.ts @@ -1,5 +1,6 @@ import * as Clipboard from './clipboard'; import * as Message from '../message'; +import { requestApp } from './request-app'; import { requestProject } from './request-project'; import * as Types from '../types'; import * as uuid from 'uuid'; @@ -17,11 +18,17 @@ export async function createEditMessageHandler( switch (message.type) { case Message.MessageType.Cut: case Message.MessageType.Copy: { + const app = await requestApp(injection.sender); + + if (app.getHasFocusedInput()) { + return; + } + const project = await requestProject(injection.sender); const focusedItemType = project.getFocusedItemType(); const focusedItem = project.getFocusedItem(); - if (!focusedItem || focusedItemType === Types.ItemType.None) { + if (!focusedItem) { return; } diff --git a/src/electron/create-main-menu/create-edit-menu.ts b/src/electron/create-main-menu/create-edit-menu.ts index a0195a066..235702777 100644 --- a/src/electron/create-main-menu/create-edit-menu.ts +++ b/src/electron/create-main-menu/create-edit-menu.ts @@ -24,23 +24,33 @@ export function createEditMenu( label: '&Undo', accelerator: 'CmdOrCtrl+Z', enabled: typeof ctx.project !== 'undefined', - click: () => + click: () => { + if (ctx.app.hasFocusedInput) { + return Electron.Menu.sendActionToFirstResponder('redo:'); + } + injection.sender.send({ id: uuid.v4(), type: MessageType.Undo, payload: undefined - }) + }); + } }, { label: '&Redo', accelerator: 'Shift+CmdOrCtrl+Z', enabled: typeof ctx.project !== 'undefined', - click: () => + click: () => { + if (ctx.app.hasFocusedInput) { + return Electron.Menu.sendActionToFirstResponder('redo:'); + } + injection.sender.send({ id: uuid.v4(), payload: undefined, type: MessageType.Redo - }) + }); + } }, { type: 'separator' @@ -50,12 +60,15 @@ export function createEditMenu( enabled: typeof ctx.project !== 'undefined', accelerator: 'CmdOrCtrl+X', click: () => { + if (ctx.app.hasFocusedInput) { + return Electron.Menu.sendActionToFirstResponder('cut:'); + } + injection.sender.send({ id: uuid.v4(), payload: undefined, type: MessageType.Cut }); - Electron.Menu.sendActionToFirstResponder('cut:'); } }, { @@ -63,12 +76,15 @@ export function createEditMenu( enabled: typeof ctx.project !== 'undefined', accelerator: 'CmdOrCtrl+C', click: () => { + if (ctx.app.hasFocusedInput) { + return Electron.Menu.sendActionToFirstResponder('copy:'); + } + injection.sender.send({ id: uuid.v4(), payload: undefined, type: MessageType.Copy }); - Electron.Menu.sendActionToFirstResponder('copy:'); } }, { @@ -76,12 +92,15 @@ export function createEditMenu( enabled: typeof ctx.project !== 'undefined', accelerator: 'CmdOrCtrl+V', click: () => { + if (ctx.app.hasFocusedInput) { + return Electron.Menu.sendActionToFirstResponder('paste:'); + } + injection.sender.send({ id: uuid.v4(), payload: undefined, type: MessageType.Paste }); - Electron.Menu.sendActionToFirstResponder('paste:'); } }, { @@ -104,7 +123,6 @@ export function createEditMenu( }, type: MessageType.Paste }); - Electron.Menu.sendActionToFirstResponder('paste:'); } }, { @@ -138,12 +156,14 @@ export function createEditMenu( enabled: typeof ctx.project !== 'undefined', accelerator: process.platform === 'darwin' ? 'Backspace' : 'Delete', click: () => { + if (ctx.app.hasFocusedInput) { + return Electron.Menu.sendActionToFirstResponder('delete:'); + } injection.sender.send({ id: uuid.v4(), payload: undefined, type: MessageType.Delete }); - Electron.Menu.sendActionToFirstResponder('delete:'); } } ] diff --git a/src/model/alva-app.ts b/src/model/alva-app.ts index 341f9b34a..8a699da99 100644 --- a/src/model/alva-app.ts +++ b/src/model/alva-app.ts @@ -3,6 +3,7 @@ import * as Types from '../types'; export interface AlvaAppInit { activeView: Types.AlvaView; + hasFocusedInput: boolean; panes: Set; paneSizes: Types.PaneSize[]; rightSidebarTab: Types.RightSidebarTab; @@ -30,6 +31,8 @@ export class AlvaApp { @Mobx.observable private paneSizes: Map = new Map(); + @Mobx.observable private hasFocusedInput: boolean = false; + public constructor(init?: AlvaAppInit) { if (init) { this.activeView = init.activeView; @@ -43,6 +46,7 @@ export class AlvaApp { public static from(serialized: Types.SerializedAlvaApp): AlvaApp { return new AlvaApp({ activeView: deserializeView(serialized.activeView), + hasFocusedInput: serialized.hasFocusedInput, panes: new Set(serialized.panes.map(deserializePane)), paneSizes: serialized.paneSizes.map(p => ({ width: p.width, @@ -59,6 +63,10 @@ export class AlvaApp { return this.activeView; } + public getHasFocusedInput(): boolean { + return this.hasFocusedInput; + } + public getHoverArea(): Types.HoverArea { return this.hoverArea; } @@ -96,6 +104,11 @@ export class AlvaApp { this.activeView = view; } + @Mobx.action + public setHasFocusedInput(hasFocusedInput: boolean): void { + this.hasFocusedInput = hasFocusedInput; + } + @Mobx.action public setPaneSelectOpen(paneSelectOpen: boolean): void { this.paneSelectOpen = paneSelectOpen; @@ -146,6 +159,7 @@ export class AlvaApp { public toJSON(): Types.SerializedAlvaApp { return { activeView: serializeView(this.activeView), + hasFocusedInput: this.hasFocusedInput, panes: [...this.panes.values()].map(serializePane), paneSizes: [...this.paneSizes.values()].map(paneSize => ({ width: paneSize.width, diff --git a/src/model/project.ts b/src/model/project.ts index 4f95abb07..10f529557 100644 --- a/src/model/project.ts +++ b/src/model/project.ts @@ -15,14 +15,11 @@ import * as uuid from 'uuid'; export interface ProjectProperties { id?: string; - lastChangedAuthor?: string; - lastChangedDate?: Date; name: string; pages: Page[]; path: string; patternLibraries: PatternLibrary[]; userStore: UserStore; - focusedItemType?: Types.ItemType; } export interface ProjectCreateInit { @@ -37,8 +34,6 @@ export class Project { @Mobx.observable private elementContents: Map = new Map(); - @Mobx.observable private focusedItemType: Types.ItemType = Types.ItemType.None; - @Mobx.observable private id: string; @Mobx.observable private name: string; @@ -110,6 +105,36 @@ export class Project { return elementProperties; } + @Mobx.computed + private get focusedItem(): Element | Page | undefined { + const element = this.getElements().find(e => e.getFocused()); + + if (element) { + return element; + } + + const page = this.getPages().find(p => p.getFocused()); + + if (page) { + return page; + } + + return; + } + + @Mobx.computed + private get focusedItemType(): Types.ItemType { + if (this.focusedItem instanceof Page) { + return Types.ItemType.Page; + } + + if (this.focusedItem instanceof Element) { + return Types.ItemType.Element; + } + + return Types.ItemType.None; + } + public constructor(init: ProjectProperties) { this.name = init.name; this.id = init.id ? init.id : uuid.v4(); @@ -117,10 +142,6 @@ export class Project { this.path = init.path; this.userStore = init.userStore; - if (typeof init.focusedItemType !== 'undefined') { - this.focusedItemType = init.focusedItemType; - } - init.patternLibraries.forEach(patternLibrary => { if (patternLibrary.getOrigin() === Types.PatternLibraryOrigin.BuiltIn) { const updatedLibrary = Project.createBuiltinPatternLibrary({ @@ -198,8 +219,7 @@ export class Project { path: serialized.path, pages: [], patternLibraries: serialized.patternLibraries.map(p => PatternLibrary.from(p)), - userStore, - focusedItemType: deserializeItemType(serialized.focusedItemType) + userStore }); serialized.pages.forEach(page => project.addPage(Page.from(page, { project }))); @@ -289,14 +309,7 @@ export class Project { } public getFocusedItem(): Element | Page | undefined { - switch (this.focusedItemType) { - case Types.ItemType.Element: - return this.getElements().find(element => element.getFocused()); - case Types.ItemType.Page: - return this.getPages().find(page => page.getFocused()); - default: - return; - } + return this.focusedItem; } public getFocusedItemType(): Types.ItemType { @@ -483,7 +496,7 @@ export class Project { this.unsetSelectedElement(); page.setActive(true); - this.setFocusedItem(Types.ItemType.Page, page); + this.setFocusedItem(page); } @Mobx.action @@ -513,13 +526,13 @@ export class Project { } @Mobx.action - public setFocusedItem(type: Types.ItemType, payload: Element | Page | undefined): void { + public setFocusedItem(payload: Element | Page | undefined): void { const previousFocusItem = this.getFocusedItem(); if (previousFocusItem) { previousFocusItem.setFocused(false); } - this.focusedItemType = type; + if (payload) { payload.setFocused(true); } @@ -568,7 +581,6 @@ export class Project { elements: this.getElements().map(e => e.toJSON()), elementActions: this.getElementActions().map(e => e.toJSON()), elementContents: this.getElementContents().map(e => e.toJSON()), - focusedItemType: serializeItemType(this.focusedItemType), id: this.id, name: this.name, pages: this.pages.map(p => p.toJSON()), @@ -622,25 +634,3 @@ export class Project { .forEach(element => element.setPlaceholderHighlighted(false)); } } - -function serializeItemType(type: Types.ItemType): Types.SerializedItemType { - switch (type) { - case Types.ItemType.None: - return 'none'; - case Types.ItemType.Element: - return 'element'; - case Types.ItemType.Page: - return 'page'; - } -} - -function deserializeItemType(type: Types.SerializedItemType): Types.ItemType { - switch (type) { - case 'none': - return Types.ItemType.None; - case 'element': - return Types.ItemType.Element; - case 'page': - return Types.ItemType.Page; - } -} diff --git a/src/renderer/create-edit-message-handler.ts b/src/renderer/create-edit-message-handler.ts index 7c471e91d..7b72474e6 100644 --- a/src/renderer/create-edit-message-handler.ts +++ b/src/renderer/create-edit-message-handler.ts @@ -14,8 +14,7 @@ export function createEditMessageHandler({ }): EditMessageHandler { // tslint:disable-next-line:cyclomatic-complexity return function editMessageHandler(message: Message.Message): void { - // Do not perform custom operations when an input/textarea is selected - if (['input', 'textarea'].includes(document.activeElement.tagName.toLowerCase())) { + if (store.getApp().getHasFocusedInput()) { return; } diff --git a/src/renderer/create-init-message-handler.ts b/src/renderer/create-init-message-handler.ts index 9484aa6c7..24ec3d22b 100644 --- a/src/renderer/create-init-message-handler.ts +++ b/src/renderer/create-init-message-handler.ts @@ -64,7 +64,7 @@ export function createInitMessageHandler({ store.setProject(projectResult.project); app.setActiveView(Types.AlvaView.PageDetail); - store.getProject().setFocusedItem(Types.ItemType.Page, store.getActivePage()); + store.getProject().setFocusedItem(store.getActivePage()); store.commit(); break; diff --git a/src/renderer/register-global-listeners.ts b/src/renderer/register-global-listeners.ts index 0f767f30f..c3d7ded6a 100644 --- a/src/renderer/register-global-listeners.ts +++ b/src/renderer/register-global-listeners.ts @@ -13,6 +13,25 @@ export function registerGlobalListeners({ store }: { store: ViewStore }): void { } }); + window.addEventListener( + 'focus', + () => + store + .getApp() + .setHasFocusedInput( + ['input', 'textarea'].includes(document.activeElement.tagName.toLowerCase()) + ), + true + ); + + window.addEventListener( + 'blur', + () => { + store.getApp().setHasFocusedInput(false); + }, + true + ); + // Disable drag and drop from outside the application document.addEventListener( 'dragenter', diff --git a/src/store/view-store.ts b/src/store/view-store.ts index 552825c5b..79ecaef1a 100644 --- a/src/store/view-store.ts +++ b/src/store/view-store.ts @@ -850,7 +850,7 @@ export class ViewStore { selectedElement.setSelected(true); this.app.setRightSidebarTab(Types.RightSidebarTab.Properties); - this.project.setFocusedItem(Types.ItemType.Element, selectedElement); + this.project.setFocusedItem(selectedElement); selectedElement.getAncestors().forEach(ancestor => { ancestor.setForcedOpen(true); diff --git a/src/types/types.ts b/src/types/types.ts index 37e6b82a5..91a4da0ef 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -17,7 +17,6 @@ export interface SavedProject { pages: SerializedPage[]; patternLibraries: SerializedPatternLibrary[]; userStore: UserStore.SerializedUserStore; - focusedItemType: SerializedItemType; } export interface SerializedProject extends SavedProject { @@ -333,6 +332,7 @@ export interface SerializedPaneSize { export interface SerializedAlvaApp { activeView: SerializedAlvaView; + hasFocusedInput: boolean; panes: SerializedAppPane[]; paneSizes: SerializedPaneSize[]; rightSidebarTab: SerializedRightSidebarTab;