diff --git a/src/boot/app/app-loader-functions.ts b/src/boot/app/app-loader-functions.ts index 63c38f69..293d1a6a 100644 --- a/src/boot/app/app-loader-functions.ts +++ b/src/boot/app/app-loader-functions.ts @@ -37,7 +37,8 @@ import { useUserRight, useUserRights, getUserRight, - getUserRights + getUserRights, + useAuthenticated } from '../../store/account'; import { getApp, getAppContext, useApp, useAppContext } from '../../store/app'; import { @@ -122,6 +123,8 @@ export const getAppFunctions = (pkg: CarbonioModule): Record = getActionsFactory, useActionFactory, getActionFactory, + // AUTH + useAuthenticated, // ACCOUNTS useUserAccount, getUserAccount, diff --git a/src/boot/app/shared-libraries.ts b/src/boot/app/shared-libraries.ts index b1f94d49..fd5c988d 100644 --- a/src/boot/app/shared-libraries.ts +++ b/src/boot/app/shared-libraries.ts @@ -9,6 +9,7 @@ import React from 'react'; import * as ReduxJSToolkit from '@reduxjs/toolkit'; import * as ZappUI from '@zextras/carbonio-design-system'; import * as Preview from '@zextras/carbonio-ui-preview'; +import * as Darkreader from 'darkreader'; import * as Lodash from 'lodash'; import * as Moment from 'moment'; import * as ReactDOM from 'react-dom'; @@ -31,7 +32,8 @@ export function injectSharedLibraries(): void { '@reduxjs/toolkit': ReduxJSToolkit, '@zextras/carbonio-shell-ui': {}, '@zextras/carbonio-design-system': ZappUI, - '@zextras/carbonio-ui-preview': Preview + '@zextras/carbonio-ui-preview': Preview, + darkreader: Darkreader }; window.__ZAPP_HMR_EXPORT__ = {}; } diff --git a/src/boot/bootstrapper.tsx b/src/boot/bootstrapper.tsx index f6cd436c..455ee52e 100644 --- a/src/boot/bootstrapper.tsx +++ b/src/boot/bootstrapper.tsx @@ -16,15 +16,15 @@ import { ContextBridge } from './context-bridge'; import { Loader } from './loader'; import ShellI18nextProvider from './shell-i18n-provider'; import { ThemeProvider } from './theme-provider'; -import { BASENAME, IS_STANDALONE } from '../constants'; +import { BASENAME, IS_FOCUS_MODE } from '../constants'; import { NotificationPermissionChecker } from '../notification/NotificationPermissionChecker'; import ShellView from '../shell/shell-view'; import { useAppStore } from '../store/app'; -const StandaloneListener = (): null => { +const FocusModeListener = (): null => { const { route } = useParams<{ route?: string }>(); useEffect(() => { - if (route) useAppStore.setState({ standalone: route }); + if (route) useAppStore.setState({ focusMode: route }); }, [route]); return null; }; @@ -44,10 +44,10 @@ const Bootstrapper: FC = () => ( - {IS_STANDALONE && ( + {IS_FOCUS_MODE && ( - + )} diff --git a/src/boot/loader.tsx b/src/boot/loader.tsx index 3ad3cedb..19ffb938 100644 --- a/src/boot/loader.tsx +++ b/src/boot/loader.tsx @@ -11,6 +11,7 @@ import { find } from 'lodash'; import { useTranslation } from 'react-i18next'; import { loadApps, unloadAllApps } from './app/load-apps'; +import { IS_FOCUS_MODE } from '../constants'; import { getComponents } from '../network/get-components'; import { getInfo } from '../network/get-info'; import { loginConfig } from '../network/login-config'; @@ -80,7 +81,9 @@ export const Loader = (): React.JSX.Element => { } else if ('message' in promiseRejectedResult.reason) { console.error(promiseRejectedResult.reason.message); } - setOpen(true); + if (!IS_FOCUS_MODE) { + setOpen(true); + } } if (isPromiseFulfilledResult(getComponentsPromiseSettledResult)) { loadApps(Object.values(useAppStore.getState().apps)); diff --git a/src/constants/index.ts b/src/constants/index.ts index fe346faf..3ec657c0 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -78,10 +78,10 @@ export const darkReaderDynamicThemeFixes: DynamicThemeFix = { const base = '/carbonio/'; -const standaloneBase = `${base}standalone`; +const focusModeBase = `${base}focus-mode`; -export const IS_STANDALONE = window.location.pathname.startsWith(standaloneBase); -export const BASENAME = IS_STANDALONE ? standaloneBase : base; +export const IS_FOCUS_MODE = window.location.pathname.startsWith(focusModeBase); +export const BASENAME = IS_FOCUS_MODE ? focusModeBase : base; export const EMAIL_VALIDATION_REGEX = // eslint-disable-next-line @typescript-eslint/no-unused-vars, max-len, no-control-regex /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/; diff --git a/src/network/fetch.ts b/src/network/fetch.ts index d526ef77..d6a21ff4 100644 --- a/src/network/fetch.ts +++ b/src/network/fetch.ts @@ -15,7 +15,7 @@ import { SoapContext, SoapResponse } from '../../types'; -import { IS_STANDALONE, SHELL_APP_ID } from '../constants'; +import { IS_FOCUS_MODE, SHELL_APP_ID } from '../constants'; import { report } from '../reporting/functions'; import { useAccountStore } from '../store/account'; import { useNetworkStore } from '../store/network'; @@ -103,7 +103,7 @@ const handleResponse = (api: string, res: SoapResponse): R | ErrorSoapBody (code) => code === (res).Body.Fault.Detail?.Error?.Code ) ) { - if (IS_STANDALONE) { + if (IS_FOCUS_MODE) { useAccountStore.setState({ authenticated: false }); } else { goToLogin(); diff --git a/src/shell/app-view-container.tsx b/src/shell/app-view-container.tsx index 0c4e11dc..9ad0008c 100644 --- a/src/shell/app-view-container.tsx +++ b/src/shell/app-view-container.tsx @@ -12,13 +12,14 @@ import { Redirect, Route, Switch, useLocation } from 'react-router-dom'; import styled from 'styled-components'; import AppContextProvider from '../boot/app/app-context-provider'; +import { IS_FOCUS_MODE } from '../constants'; import { useAppList, useAppStore, useRoutes } from '../store/app'; const _BoardsRouterContainer = styled(Container)` flex-grow: 1; flex-basis: 0; min-width: 0.0625rem; - max-height: calc(100vh - 3.75rem); + max-height: ${IS_FOCUS_MODE ? '100vh' : 'calc(100vh - 3.75rem)'}; overflow-y: auto; `; diff --git a/src/shell/shell-primary-bar.tsx b/src/shell/shell-primary-bar.tsx index 46b8dcef..a63201ea 100644 --- a/src/shell/shell-primary-bar.tsx +++ b/src/shell/shell-primary-bar.tsx @@ -14,12 +14,7 @@ import styled from 'styled-components'; import BadgeWrap from './badge-wrap'; import { PrimaryAccessoryView, PrimaryBarView } from '../../types'; import AppContextProvider from '../boot/app/app-context-provider'; -import { - BOARD_CONTAINER_ZINDEX, - IS_STANDALONE, - PRIMARY_BAR_WIDTH, - SEARCH_APP_ID -} from '../constants'; +import { BOARD_CONTAINER_ZINDEX, PRIMARY_BAR_WIDTH, SEARCH_APP_ID } from '../constants'; import { useCurrentRoute } from '../history/hooks'; import { useAppStore } from '../store/app'; import { minimizeBoards, reopenBoards, useBoardStore } from '../store/boards'; @@ -155,10 +150,6 @@ const ShellPrimaryBar = (): React.JSX.Element | null => { [accessoryViews] ); - if (IS_STANDALONE && activeRoute?.standalone?.hidePrimaryBar) { - return null; - } - return ( { - const auth = useAccountStore((s) => s.authenticated); - useEffect(() => { - if (IS_STANDALONE && !auth && !allowUnauthenticated) { - goToLogin(); - } - }, [allowUnauthenticated, auth]); -}; - -interface ShellComponentProps { - allowUnauthenticated?: boolean; - hideShellHeader?: boolean; -} - -const ShellComponent = ({ - allowUnauthenticated, - hideShellHeader -}: ShellComponentProps): React.JSX.Element => { - useLoginRedirection(allowUnauthenticated); - return ( - - - {!(IS_STANDALONE && hideShellHeader) && ( - - - - )} - +const ShellComponent = (): React.JSX.Element => ( + + + {!IS_FOCUS_MODE && ( + + + + )} + + {!IS_FOCUS_MODE && ( - - - - - - ); -}; - -const MemoShell = React.memo(ShellComponent); + )} + + + + + +); -const ShellView = (): React.JSX.Element => { - const activeRoute = useCurrentRoute(); - const allowUnauthenticated = activeRoute?.standalone?.allowUnauthenticated; - const hideShellHeader = activeRoute?.standalone?.hideShellHeader; - return ( - - - - - - ); -}; +const ShellView = (): React.JSX.Element => ( + + + + + +); export default ShellView; diff --git a/src/store/account/hooks.ts b/src/store/account/hooks.ts index 550af1e4..4b202a89 100644 --- a/src/store/account/hooks.ts +++ b/src/store/account/hooks.ts @@ -17,6 +17,8 @@ import { AccountSettings } from '../../../types'; +export const useAuthenticated = (): boolean => useAccountStore((s) => s.authenticated); + export const useUserAccount = (): Account => useAccountStore((s) => s.account as Account); export const useUserAccounts = (): Array => { const acct = useAccountStore((s) => s.account); @@ -37,10 +39,15 @@ export const useUserSettings = (): AccountSettings => useAccountStore((s) => s.s export const useUserSetting = (...path: Array): string | T => useAccountStore((s) => get(s.settings, join(path, '.'))); -export const getUserAccount = (): Account => useAccountStore.getState().account as Account; -export const getUserAccounts = (): Array => [ - useAccountStore.getState().account as Account -]; +export const getUserAccount = (): Account | undefined => useAccountStore.getState().account; +export const getUserAccounts = (): Array => { + const { account } = useAccountStore.getState(); + const accounts: Account[] = []; + if (account) { + accounts.push(account); + } + return accounts; +}; export const getUserSettings = (): AccountSettings => useAccountStore.getState().settings; export const getUserSetting = (...path: Array): string | T => get(useAccountStore.getState().settings, join(path, '.')); diff --git a/src/store/app/store.ts b/src/store/app/store.ts index 61664ea7..c2f89eda 100644 --- a/src/store/app/store.ts +++ b/src/store/app/store.ts @@ -12,7 +12,7 @@ import { normalizeApp } from './utils'; import type { AppState, CarbonioModule } from '../../../types'; import { SHELL_APP_ID } from '../../constants'; -const STANDALONE_RESPONSE = 'standalone'; +const FOCUS_MODE_RESPONSE = 'focus-mode'; function addIfNotPresent( items: T[], @@ -42,7 +42,7 @@ function removeById(items: T[], id: unknown): void { // extra currying as suggested in https://github.com/pmndrs/zustand/blob/main/docs/guides/typescript.md#basic-usage export const useAppStore = create()((set, get) => ({ - standalone: false, + focusMode: false, apps: {}, appContexts: {}, shell: { @@ -114,14 +114,14 @@ export const useAppStore = create()((set, get) => ({ }, // add route (id route primaryBar secondaryBar app) addRoute: (routeData): string => { - const { standalone } = get(); - if (standalone && routeData.route !== standalone) { - return STANDALONE_RESPONSE; + const { focusMode } = get(); + if (focusMode && (routeData.route !== focusMode || !routeData.focusMode)) { + return FOCUS_MODE_RESPONSE; } set( produce((state) => { state.routes[routeData.id] = routeData; - if (routeData.primaryBar) { + if (routeData.primaryBar && !routeData.focusMode) { addAndSort(state.views.primaryBar, { app: routeData.app, id: routeData.id, @@ -199,9 +199,9 @@ export const useAppStore = create()((set, get) => ({ // add settings addSettingsView: (data): string => { - const { standalone } = get(); - if (standalone && data.route !== standalone) { - return STANDALONE_RESPONSE; + const { focusMode } = get(); + if (focusMode && data.route !== focusMode) { + return FOCUS_MODE_RESPONSE; } set( produce((state) => { @@ -222,9 +222,9 @@ export const useAppStore = create()((set, get) => ({ // // add search addSearchView: (data): string => { - const { standalone } = get(); - if (standalone && data.route !== standalone) { - return STANDALONE_RESPONSE; + const { focusMode } = get(); + if (focusMode && data.route !== focusMode) { + return FOCUS_MODE_RESPONSE; } set( produce((state) => { diff --git a/src/store/app/utils.tsx b/src/store/app/utils.tsx index 52b4e74a..488e9e08 100644 --- a/src/store/app/utils.tsx +++ b/src/store/app/utils.tsx @@ -60,11 +60,7 @@ export const normalizeRoute = ( primaryBar: data.primaryBar ?? app.icon ?? 'CubeOutline', secondaryBar: data.secondaryBar, appView: data.appView ?? FallbackView, - standalone: { - hidePrimaryBar: data?.standalone?.hidePrimaryBar, - hideShellHeader: data?.standalone?.hideShellHeader, - allowUnauthenticated: data?.standalone?.allowUnauthenticated - } + focusMode: data.focusMode }; }; diff --git a/src/utility-bar/utils.ts b/src/utility-bar/utils.ts index 19f821fe..00b29653 100644 --- a/src/utility-bar/utils.ts +++ b/src/utility-bar/utils.ts @@ -19,7 +19,7 @@ export const checkRoute = ( view: UtilityView | PrimaryAccessoryView | SecondaryAccessoryView, activeRoute?: AppRoute ): boolean => { - const activeRouteValues = Object.values(omit(activeRoute, 'standalone') ?? {}); + const activeRouteValues = Object.values(omit(activeRoute, 'focusMode') ?? {}); if (view.blacklistRoutes) return !checkList(activeRouteValues, view.blacklistRoutes); if (view.whitelistRoutes) return checkList(activeRouteValues, view.whitelistRoutes); return true; diff --git a/types/apps/index.d.ts b/types/apps/index.d.ts index 2c1c271c..155e8fb9 100644 --- a/types/apps/index.d.ts +++ b/types/apps/index.d.ts @@ -26,18 +26,11 @@ export type CarbonioModule = { sentryDsn?: string; }; -export type StandaloneFlags = { - hidePrimaryBar?: boolean; - hideShellHeader?: boolean; - allowUnauthenticated?: boolean; -}; - export type AppRoute = { - // persist?: boolean; id: string; route: string; app: string; - standalone?: StandaloneFlags; + focusMode?: boolean; }; export type AppRouteData = AppRoute & { @@ -138,7 +131,7 @@ export type AppRouteDescriptor = { label: string; secondaryBar?: ComponentType; appView: ComponentType; - standalone?: StandaloneFlags; + focusMode?: boolean; }; export type AppSetters = { setApps: (apps: Array>) => void; @@ -184,7 +177,7 @@ export type AppSetters = { setAppContext: (app: string) => (context: unknown) => void; }; export type AppState = { - standalone: false | string; + focusMode: false | string; apps: Record; appContexts: Record; entryPoints: Record; diff --git a/types/exports/index.d.ts b/types/exports/index.d.ts index 5aeb9817..ab4ee7fe 100644 --- a/types/exports/index.d.ts +++ b/types/exports/index.d.ts @@ -60,7 +60,7 @@ declare const ROOT_NAME: string; declare const SHELL_MODES: Record; declare const BASENAME: string; -declare const IS_STANDALONE: boolean; +declare const IS_FOCUS_MODE: boolean; // eslint-disable-next-line @typescript-eslint/ban-types declare const getIntegratedFunction: (id: string) => [Function, boolean]; @@ -87,10 +87,11 @@ declare const getApp: () => CarbonioModule; declare const useAppContext: () => T; declare const getAppContext: () => T; declare const useUserAccount: () => Account; +declare const useAuthenticated: () => boolean; declare const useUserAccounts: () => Array; declare const useUserRights: () => AccountRights; declare const useUserRight: (right: AccountRightName) => Array; -declare const getUserAccount: () => Account; +declare const getUserAccount: () => Account | undefined; declare const getUserAccounts: () => Array; declare const getUserRights: () => AccountRights; declare const getUserRight: (right: AccountRightName) => Array;