UNPKG

@finos/legend-application

Version:
180 lines 10.5 kB
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; /** * Copyright (c) 2020-present, Goldman Sachs * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { Backdrop, LegendStyleProvider, Portal } from '@finos/legend-art'; import { observer } from 'mobx-react-lite'; import { DndProvider } from 'react-dnd'; import { HTML5Backend } from 'react-dnd-html5-backend'; import { ActionAlert } from './ActionAlert.js'; import { useApplicationStore } from './ApplicationStoreProvider.js'; import { BlockingAlert } from './BlockingAlert.js'; import { NotificationManager } from './NotificationManager.js'; import { useEffect } from 'react'; import { createKeybindingsHandler, } from '@finos/legend-shared'; import { VirtualAssistant } from './VirtualAssistant.js'; import { LegendApplicationTelemetryHelper } from '../__lib__/LegendApplicationTelemetry.js'; var APPLICATION_COMPONENT_ELEMENT_ID; (function (APPLICATION_COMPONENT_ELEMENT_ID) { APPLICATION_COMPONENT_ELEMENT_ID["TOP_LEVEL_CONTAINER"] = "application.top-level-container"; APPLICATION_COMPONENT_ELEMENT_ID["BACKDROP_CONTAINER"] = "application.backdrop-container"; })(APPLICATION_COMPONENT_ELEMENT_ID || (APPLICATION_COMPONENT_ELEMENT_ID = {})); const PLATFORM_NATIVE_KEYBOARD_SHORTCUTS = [ 'Meta+KeyP', // Print 'Control+KeyP', 'Meta+KeyS', // Save 'Control+KeyS', 'F8', // Chrome: Developer Tools > Sources: Run or pause script 'F10', // Chrome: Developer Tools > Sources: Step over next function call 'F11', // Chrome: Developer Tools > Sources: Step into next function call 'Meta+Shift+KeyP', // Chrome: Developer Tools: Open Command Prompt inside developer tools 'Control+Backquote', // Chrome: Developer Tools: Open console 'Control+Shift+KeyN', // Chrome: Open Private Browsing (incognito) 'Control+Shift+KeyP', // Firefox: Open Private Browsing 'Meta+KeyB', // Firefox: Open bookmark sidebar 'Control+KeyB', 'F7', // Firefox: Caret browsing 'Alt+F7', // Firefox: Caret browsing (Mac) 'Control+Shift+KeyB', 'Alt+KeyZ', // Mac: special symbol ]; const buildHotkeysConfiguration = (commandKeyMap, handler) => { const keyMap = {}; commandKeyMap.forEach((keyCombinations, commandKey) => { if (keyCombinations.length) { keyMap[commandKey] = { combinations: keyCombinations, handler, }; } }); // Disable platform native keyboard shortcuts // NOTE: due to the order in which hotkey configuration is searched and applied, // we must place these after application hotkey configuration const PLATFORM_NATIVE_KEYBOARD_COMMAND = 'INTERNAL__PLATFORM_NATIVE_KEYBOARD_COMMAND'; keyMap[PLATFORM_NATIVE_KEYBOARD_COMMAND] = { combinations: PLATFORM_NATIVE_KEYBOARD_SHORTCUTS, handler: (keyCombination, event) => { // prevent default from potentially clashing key combinations event.preventDefault(); }, }; return keyMap; }; /** * Some elements (e.g. <canvas>) and components that we do not control the implementation * might have special logic to prevent `keydown` event bubbling naturally, this * method forces those event to surface to the top of the app and being handled * by keybinding service */ export const forceDispatchKeyboardEvent = (event, applicationStore) => { applicationStore.layoutService .getElementByID(APPLICATION_COMPONENT_ELEMENT_ID.TOP_LEVEL_CONTAINER) ?.dispatchEvent(new KeyboardEvent(event.type, event)); }; /** * Potential location to mount backdrop on * * NOTE: we usually want the backdrop container to be the first child of its immediate parent * so that it properly lies under the content that we pick to show on top of the backdrop */ export const BackdropContainer = (props) => (_jsx("div", { className: "backdrop__container", "data-elementid": props.elementId })); export const ApplicationComponentFrameworkProvider = observer((props) => { const { children, enableTransitions } = props; const applicationStore = useApplicationStore(); const disableContextMenu = (event) => { event.stopPropagation(); event.preventDefault(); }; const keyBindingMap = buildHotkeysConfiguration(applicationStore.keyboardShortcutsService.commandKeyMap, (keyCombination, event) => { // prevent default from potentially clashing key combinations with platform native keyboard shortcuts // NOTE: Though tempting since it's a good way to simplify and potentially avoid conflicts, // we should not call `preventDefault()` because if we have any hotkey sequence which is too short, // such as `r`, `a` - we risk blocking some very common interaction, i.e. user typing, or even // constructing longer key combinations if (PLATFORM_NATIVE_KEYBOARD_SHORTCUTS.includes(keyCombination)) { event.preventDefault(); } applicationStore.keyboardShortcutsService.dispatch(keyCombination); }); useEffect(() => { const onKeyEvent = createKeybindingsHandler(keyBindingMap); document.addEventListener('keydown', onKeyEvent); return () => { document.removeEventListener('keydown', onKeyEvent); }; }, [keyBindingMap]); /** * Keep track of when the application usage is interrupted (e.g. when the app window/tab is not in focus), * since for certain platform, background contexts are de-prioritized, given less resources, and hence, would * run less performantly; and might require particular handlings. * * See https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API#policies_in_place_to_aid_background_page_performance * See https://plumbr.io/blog/performance-blog/background-tabs-in-browser-load-20-times-slower */ useEffect(() => { const onVisibilityChange = () => { if (document.hidden) { LegendApplicationTelemetryHelper.logEvent_ApplicationUsageInterrupted(applicationStore.telemetryService); applicationStore.timeService.recordUsageInterruption(); } }; document.addEventListener('visibilitychange', onVisibilityChange); return () => { document.removeEventListener('visibilitychange', onVisibilityChange); }; }, [applicationStore]); return (_jsxs(LegendStyleProvider, { enableTransitions: enableTransitions, children: [_jsx(BlockingAlert, {}), _jsx(ActionAlert, {}), _jsx(NotificationManager, {}), _jsx(VirtualAssistant, {}), applicationStore.layoutService.showBackdrop && ( // We use <Portal> here to insert backdrop into different parts of the app // as backdrop relies heavily on z-index mechanism so its location in the DOM // really matters. // For example, the default location of the backdrop works fine for most cases // but if we want to use the backdrop for elements within modal dialogs, we would // need to mount the backdrop at a different location _jsx(Portal, { container: applicationStore.layoutService.getElementByID(applicationStore.layoutService.backdropContainerElementID ?? APPLICATION_COMPONENT_ELEMENT_ID.BACKDROP_CONTAINER) ?? null, children: _jsx(Backdrop, { className: "backdrop", open: applicationStore.layoutService.showBackdrop }) })), _jsx(DndProvider, { backend: HTML5Backend, children: _jsxs("div", { style: { height: '100%', width: '100%' }, "data-elementid": APPLICATION_COMPONENT_ELEMENT_ID.TOP_LEVEL_CONTAINER, // Disable global context menu so that only places in the app that supports context-menu will be effective onContextMenu: disableContextMenu, children: [_jsx(BackdropContainer, { elementId: APPLICATION_COMPONENT_ELEMENT_ID.BACKDROP_CONTAINER }), children] }) })] })); }); export const SimpleApplicationComponentFrameworkProvider = observer((props) => { const { children, enableTransitions } = props; const applicationStore = useApplicationStore(); const disableContextMenu = (event) => { event.stopPropagation(); event.preventDefault(); }; const keyBindingMap = buildHotkeysConfiguration(applicationStore.keyboardShortcutsService.commandKeyMap, (keyCombination, event) => { // prevent default from potentially clashing key combinations with platform native keyboard shortcuts // NOTE: Though tempting since it's a good way to simplify and potentially avoid conflicts, // we should not call `preventDefault()` because if we have any hotkey sequence which is too short, // such as `r`, `a` - we risk blocking some very common interaction, i.e. user typing, or even // constructing longer key combinations if (PLATFORM_NATIVE_KEYBOARD_SHORTCUTS.includes(keyCombination)) { event.preventDefault(); } applicationStore.keyboardShortcutsService.dispatch(keyCombination); }); useEffect(() => { const onKeyEvent = createKeybindingsHandler(keyBindingMap); document.addEventListener('keydown', onKeyEvent); return () => { document.removeEventListener('keydown', onKeyEvent); }; }, [keyBindingMap]); return (_jsx(LegendStyleProvider, { enableTransitions: enableTransitions, children: _jsx(DndProvider, { backend: HTML5Backend, children: _jsxs("div", { style: { height: '100%', width: '100%' }, "data-elementid": APPLICATION_COMPONENT_ELEMENT_ID.TOP_LEVEL_CONTAINER, // Disable global context menu so that only places in the app that supports context-menu will be effective onContextMenu: disableContextMenu, children: [_jsx(BackdropContainer, { elementId: APPLICATION_COMPONENT_ELEMENT_ID.BACKDROP_CONTAINER }), children] }) }) })); }); //# sourceMappingURL=ApplicationComponentFrameworkProvider.js.map