diff --git a/package.json b/package.json index 381618e2e7..c3fe732f82 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,8 @@ }, "main": "app/main.js", "scripts": { - "postinstall": "run-s install-app-deps clean", + "postinstall": "run-s install-app-deps clean patch-package", + "patch-package": "patch-package", "start": "run-s build:watch", "clean": "rimraf app dist", "build": "rollup -c", @@ -38,7 +39,7 @@ "build-assets-win": "ts-node -O \"{\\\"module\\\":\\\"commonjs\\\"}\" src/buildAssets.ts", "release": "yarn electron-builder --publish onTagOrDraft --x64", "install-app-deps": "electron-builder install-app-deps", - "test": "xvfb-maybe jest", + "test": "xvfb-maybe jest --forceExit --detectOpenHandles --maxWorkers=1", "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s", "lint": "run-s .:lint:eslint .:lint:tsc", ".:lint:eslint": "eslint .", @@ -54,10 +55,10 @@ "@emotion/styled": "~11.11.0", "@ewsjs/xhr": "~2.0.2", "@rocket.chat/css-in-js": "~0.31.25", - "@rocket.chat/fuselage": "~0.54.2", + "@rocket.chat/fuselage": "0.58.0", "@rocket.chat/fuselage-hooks": "~0.33.1", "@rocket.chat/fuselage-polyfills": "~0.31.25", - "@rocket.chat/icons": "~0.36.0", + "@rocket.chat/icons": "0.37.0", "axios": "~1.6.4", "electron-dl": "3.5.2", "electron-store": "~8.1.0", @@ -88,7 +89,7 @@ "@babel/preset-typescript": "~7.23.3", "@fiahfy/icns-convert": "~0.0.12", "@fiahfy/ico-convert": "~0.0.12", - "@kayahr/jest-electron-runner": "~29.11.0", + "@kayahr/jest-electron-runner": "29.14.0", "@rocket.chat/eslint-config": "~0.7.0", "@rocket.chat/prettier-config": "~0.31.25", "@rollup/plugin-babel": "~6.0.4", @@ -108,7 +109,7 @@ "chokidar": "~3.5.3", "conventional-changelog-cli": "~4.1.0", "convert-svg-to-png": "~0.6.4", - "electron": "30.1.2", + "electron": "30.4.0", "electron-builder": "24.13.3", "electron-devtools-installer": "^3.2.0", "electron-notarize": "^1.2.2", @@ -123,6 +124,7 @@ "jest-environment-jsdom": "~29.7.0", "jimp": "~0.22.10", "npm-run-all": "~4.1.5", + "patch-package": "~8.0.0", "prettier": "~3.2.5", "puppeteer": "23.1.1", "rollup": "~4.9.6", diff --git a/patches/@kayahr+jest-electron-runner+29.14.0.patch b/patches/@kayahr+jest-electron-runner+29.14.0.patch new file mode 100644 index 0000000000..91b60700fa --- /dev/null +++ b/patches/@kayahr+jest-electron-runner+29.14.0.patch @@ -0,0 +1,30 @@ +diff --git a/node_modules/@kayahr/jest-electron-runner/lib/main/electron/TestRunner.js b/node_modules/@kayahr/jest-electron-runner/lib/main/electron/TestRunner.js +index 4485862..f34ba6a 100644 +--- a/node_modules/@kayahr/jest-electron-runner/lib/main/electron/TestRunner.js ++++ b/node_modules/@kayahr/jest-electron-runner/lib/main/electron/TestRunner.js +@@ -66,14 +66,19 @@ async function startWorker(rootDir, target, config) { + DISPOSABLES.add(() => { + if (child.pid != null) { + try { +- // Kill whole process group with negative PID (See `man kill`) +- process.kill(-child.pid, "SIGKILL"); +- } +- catch { +- // Ignored ++ if (process.platform === 'win32') { ++ // On Windows, use taskkill instead of process.kill ++ require('child_process').execSync(`taskkill /pid ${child.pid} /T /F`); ++ } else { ++ // Kill whole process group with negative PID (See `man kill`) ++ process.kill(-child.pid, 'SIGKILL'); ++ } ++ } catch (error) { ++ console.error('Failed to kill process:', error); + } + } +- child.kill("SIGKILL"); ++ // Fallback kill (this can remain as it works for most platforms) ++ child.kill('SIGKILL'); + }); + return child; + }); diff --git a/src/.jest/setup.ts b/src/.jest/setup.ts index fc69e49120..80474ce76e 100644 --- a/src/.jest/setup.ts +++ b/src/.jest/setup.ts @@ -12,13 +12,28 @@ expect.extend({ promise: this.promise, }; + const normalizedReceived = path.normalize(received); + const normalizedExpected = path.normalize(fullExpectedPath); + + console.log('Normalized Received:', normalizedReceived); + console.log('Normalized Expected:', normalizedExpected); + return { - pass: path.relative(received, fullExpectedPath) === '', + pass: normalizedReceived === normalizedExpected, message: () => - `${ this.utils.matcherHint('toMatchAppPath', undefined, undefined, options) - }\n\n` - + `Expected: ${ this.utils.printExpected(fullExpectedPath) }\n` - + `Received: ${ this.utils.printReceived(received) }`, + `${this.utils.matcherHint('toMatchAppPath', undefined, undefined, options)}\n\n` + + `Expected: ${this.utils.printExpected(normalizedExpected)}\n` + + `Received: ${this.utils.printReceived(normalizedReceived)}`, }; }, }); + +process.on('unhandledRejection', (reason, promise) => { + console.error('Unhandled Rejection at:', promise, 'reason:', reason); + process.exit(1); +}); + +process.on('uncaughtException', (error) => { + console.error('Uncaught Exception:', error); + process.exit(1); +}); diff --git a/src/app/PersistableValues.ts b/src/app/PersistableValues.ts index 7bd923fbef..42087b6c7b 100644 --- a/src/app/PersistableValues.ts +++ b/src/app/PersistableValues.ts @@ -66,6 +66,11 @@ type PersistableValues_3_9_6 = PersistableValues_3_8_12 & { isNTLMCredentialsEnabled: boolean; }; +type PersistableValues_4_1_0 = PersistableValues_3_9_6 & { + mainWindowTitle: string; + machineTheme: string; +}; + export type PersistableValues = Pick< PersistableValues_3_9_6, keyof PersistableValues_3_9_6 @@ -121,4 +126,9 @@ export const migrations = { allowedNTLMCredentialsDomains: null, lastSelectedServerUrl: '', }), + '>=4.1.0': (before: PersistableValues_3_9_6): PersistableValues_4_1_0 => ({ + ...before, + mainWindowTitle: 'Rocket.Chat', + machineTheme: 'light', + }), }; diff --git a/src/app/actions.ts b/src/app/actions.ts index 3b8024af59..7a9079f20f 100644 --- a/src/app/actions.ts +++ b/src/app/actions.ts @@ -6,6 +6,8 @@ export const APP_VERSION_SET = 'app/version-set'; export const APP_SETTINGS_LOADED = 'app/settings-loaded'; export const APP_ALLOWED_NTLM_CREDENTIALS_DOMAINS_SET = 'app/allowed-ntlm-credentials-domains-set'; +export const APP_MAIN_WINDOW_TITLE_SET = 'app/main-window-title-set'; +export const APP_MACHINE_THEME_SET = 'app/machine-theme-set'; export type AppActionTypeToPayloadMap = { [APP_ERROR_THROWN]: Error; @@ -13,4 +15,6 @@ export type AppActionTypeToPayloadMap = { [APP_VERSION_SET]: string; [APP_SETTINGS_LOADED]: Partial; [APP_ALLOWED_NTLM_CREDENTIALS_DOMAINS_SET]: string; + [APP_MAIN_WINDOW_TITLE_SET]: string; + [APP_MACHINE_THEME_SET]: string; }; diff --git a/src/app/main/app.ts b/src/app/main/app.ts index eb2fb236a0..569d4660b0 100644 --- a/src/app/main/app.ts +++ b/src/app/main/app.ts @@ -21,6 +21,7 @@ import { askForClearScreenCapturePermission } from '../../ui/main/dialogs'; import { getRootWindow } from '../../ui/main/rootWindow'; import { APP_ALLOWED_NTLM_CREDENTIALS_DOMAINS_SET, + APP_MAIN_WINDOW_TITLE_SET, APP_PATH_SET, APP_VERSION_SET, } from '../actions'; @@ -35,6 +36,19 @@ export const electronBuilderJsonInformation = { protocol: electronBuilderJson.protocols.schemes[0], }; +export const getPlatformName = (): string => { + switch (process.platform) { + case 'win32': + return 'Windows'; + case 'linux': + return 'Linux'; + case 'darwin': + return 'macOS'; + default: + return 'Unknown'; + } +}; + export const relaunchApp = (...args: string[]): void => { const command = process.argv.slice(1, app.isPackaged ? 1 : 2); app.relaunch({ args: [...command, ...args] }); @@ -151,4 +165,5 @@ export const setupApp = (): void => { dispatch({ type: APP_PATH_SET, payload: app.getAppPath() }); dispatch({ type: APP_VERSION_SET, payload: app.getVersion() }); + dispatch({ type: APP_MAIN_WINDOW_TITLE_SET, payload: 'Rocket.Chat' }); }; diff --git a/src/app/reducers/machineTheme.ts b/src/app/reducers/machineTheme.ts new file mode 100644 index 0000000000..20de0fbae0 --- /dev/null +++ b/src/app/reducers/machineTheme.ts @@ -0,0 +1,20 @@ +import type { Reducer } from 'redux'; + +import type { ActionOf } from '../../store/actions'; +import { APP_MACHINE_THEME_SET } from '../actions'; + +type MachineThemeAction = ActionOf; + +export const machineTheme: Reducer = ( + state = 'light', + action +) => { + switch (action.type) { + case APP_MACHINE_THEME_SET: { + return action.payload; + } + + default: + return state; + } +}; diff --git a/src/app/reducers/mainWindowTitle.ts b/src/app/reducers/mainWindowTitle.ts new file mode 100644 index 0000000000..462216b9dc --- /dev/null +++ b/src/app/reducers/mainWindowTitle.ts @@ -0,0 +1,19 @@ +import type { Reducer } from 'redux'; + +import type { ActionOf } from '../../store/actions'; +import { APP_MAIN_WINDOW_TITLE_SET } from '../actions'; + +type MainWindowTitleAction = ActionOf; + +export const mainWindowTitle: Reducer = ( + state = null, + action +) => { + switch (action.type) { + case APP_MAIN_WINDOW_TITLE_SET: + return action.payload; + + default: + return state; + } +}; diff --git a/src/app/selectors.ts b/src/app/selectors.ts index 5bd56e604d..9b7aa414e5 100644 --- a/src/app/selectors.ts +++ b/src/app/selectors.ts @@ -7,6 +7,7 @@ export const selectPersistableValues = createStructuredSelector({ doCheckForUpdatesOnStartup: ({ doCheckForUpdatesOnStartup }: RootState) => doCheckForUpdatesOnStartup, downloads: ({ downloads }: RootState) => downloads, + machineTheme: ({ machineTheme }: RootState) => machineTheme, isMenuBarEnabled: ({ isMenuBarEnabled }: RootState) => isMenuBarEnabled, isShowWindowOnUnreadChangedEnabled: ({ isShowWindowOnUnreadChangedEnabled, diff --git a/src/i18n/en.i18n.json b/src/i18n/en.i18n.json index 1230036aab..5efce037a5 100644 --- a/src/i18n/en.i18n.json +++ b/src/i18n/en.i18n.json @@ -325,14 +325,22 @@ "addNewServer": "Add new server", "downloads": "Downloads", "settings": "Settings", + "menuTitle": "Customize and control app", "item": { - "reload": "Reload server", - "remove": "Remove server", + "reload": "Reload", + "remove": "Remove", "openDevTools": "Open DevTools", "clearCache": "Clear Cache", "clearStorageData": "Clear Storage Data", "copyCurrentUrl": "Copy current URL", "reloadClearingCache": "Force reload" + }, + "tooltips": { + "unreadMessage": "{{- count}} unread message", + "unreadMessages": "{{- count}} unread messages", + "userNotLoggedIn": "Not logged in", + "addWorkspace": "Add workspace ({{shortcut}}+N)", + "settingsMenu": "Customize and control app" } }, "touchBar": { diff --git a/src/i18n/pt-BR.i18n.json b/src/i18n/pt-BR.i18n.json index c5eba4eb14..3c06e932e3 100644 --- a/src/i18n/pt-BR.i18n.json +++ b/src/i18n/pt-BR.i18n.json @@ -310,14 +310,22 @@ "addNewServer": "Adicionar novo servidor", "downloads": "Downloads", "settings": "Configurações", + "menuTitle": "Personalizar e controlar app", "item": { - "reload": "Recarregar servidor", - "remove": "Remover servidor", + "reload": "Recarregar", + "remove": "Remover", "openDevTools": "Abrir DevTools", "clearCache": "Limpar cache", "clearStorageData": "Limpar dados de armazenamento", - "copyCurrentUrl": "Copiar URL atual", + "copyCurrentUrl": "Copiar URL", "reloadClearingCache": "Forçar recarregamento" + }, + "tooltips": { + "unreadMessage": "{{- count}} mensagem não lida", + "unreadMessages": "{{- count}} mensagens não lidas", + "userNotLoggedIn": "Não conectado", + "addWorkspace": "Adicionar workspace ({{shortcut}}+N)", + "settingsMenu": "Personalizar e controlar app" } }, "touchBar": { diff --git a/src/main.ts b/src/main.ts index 60e7ecfe11..191e3d43d2 100644 --- a/src/main.ts +++ b/src/main.ts @@ -29,6 +29,7 @@ import { createRootWindow, showRootWindow, exportLocalStorage, + watchMachineTheme, } from './ui/main/rootWindow'; import { attachGuestWebContentsEvents } from './ui/main/serverView'; import touchBar from './ui/main/touchBar'; @@ -71,7 +72,7 @@ const start = async (): Promise => { // if (process.env.NODE_ENV === 'development') { // installDevTools(); // } - + watchMachineTheme(); setupNotifications(); setupScreenSharing(); startVideoCallWindowHandler(); diff --git a/src/servers/common.ts b/src/servers/common.ts index c7fd698e0b..624fc2fd92 100644 --- a/src/servers/common.ts +++ b/src/servers/common.ts @@ -4,6 +4,7 @@ import type { SupportedVersions } from './supportedVersions/types'; export type Server = { url: string; title?: string; + pageTitle?: string; badge?: '•' | number; favicon?: string | null; style?: { diff --git a/src/servers/reducers.ts b/src/servers/reducers.ts index 33b9f69906..f8680d6077 100644 --- a/src/servers/reducers.ts +++ b/src/servers/reducers.ts @@ -1,10 +1,13 @@ +/* eslint-disable complexity */ import type { Reducer } from 'redux'; import { APP_SETTINGS_LOADED } from '../app/actions'; import { DEEP_LINKS_SERVER_ADDED } from '../deepLinks/actions'; import { OUTLOOK_CALENDAR_SAVE_CREDENTIALS } from '../outlookCalendar/actions'; import type { ActionOf } from '../store/actions'; +import type { SIDE_BAR_SERVER_REMOVE } from '../ui/actions'; import { + WEBVIEW_PAGE_TITLE_CHANGED, ADD_SERVER_VIEW_SERVER_ADDED, SIDE_BAR_REMOVE_SERVER_CLICKED, SIDE_BAR_SERVERS_SORTED, @@ -66,7 +69,9 @@ type ServersActionTypes = | ActionOf | ActionOf | ActionOf - | ActionOf; + | ActionOf + | ActionOf + | ActionOf; const upsert = (state: Server[], server: Server): Server[] => { const index = state.findIndex(({ url }) => url === server.url); @@ -120,6 +125,11 @@ export const servers: Reducer = ( return upsert(state, { url, title }); } + case WEBVIEW_PAGE_TITLE_CHANGED: { + const { url, pageTitle } = action.payload; + return upsert(state, { url, pageTitle }); + } + case WEBVIEW_SERVER_SUPPORTED_VERSIONS_UPDATED: { const { url, supportedVersions, source } = action.payload; return upsert(state, { diff --git a/src/store/rootReducer.ts b/src/store/rootReducer.ts index 580df55ca0..4c421c43a3 100644 --- a/src/store/rootReducer.ts +++ b/src/store/rootReducer.ts @@ -3,6 +3,8 @@ import { combineReducers } from 'redux'; import { allowedNTLMCredentialsDomains } from '../app/reducers/allowedNTLMCredentialsDomains'; import { appPath } from '../app/reducers/appPath'; import { appVersion } from '../app/reducers/appVersion'; +import { machineTheme } from '../app/reducers/machineTheme'; +import { mainWindowTitle } from '../app/reducers/mainWindowTitle'; import { downloads } from '../downloads/reducers/downloads'; import { allowedJitsiServers } from '../jitsi/reducers'; import { @@ -60,6 +62,8 @@ export const rootReducer = combineReducers({ isMinimizeOnCloseEnabled, isUpdatingAllowed, isUpdatingEnabled, + mainWindowTitle, + machineTheme, newUpdateVersion, openDialog, rootWindowIcon, diff --git a/src/ui/actions.ts b/src/ui/actions.ts index 1bafa9f4ea..f470802f25 100644 --- a/src/ui/actions.ts +++ b/src/ui/actions.ts @@ -71,6 +71,7 @@ export const WEBVIEW_GIT_COMMIT_HASH_CHANGED = 'webview/git-commit-hash-changed'; export const WEBVIEW_GIT_COMMIT_HASH_CHECK = 'webview/git-commit-hash-check'; export const WEBVIEW_TITLE_CHANGED = 'webview/title-changed'; +export const WEBVIEW_PAGE_TITLE_CHANGED = 'webview/page-title-changed'; export const WEBVIEW_UNREAD_CHANGED = 'webview/unread-changed'; export const WEBVIEW_USER_LOGGED_IN = 'webview/user-loggedin'; export const WEBVIEW_USER_THEME_APPEARANCE_CHANGED = @@ -112,6 +113,11 @@ export const SUPPORTED_VERSION_DIALOG_DISMISS = 'supported-versions-dialog/dismiss'; export const WEBVIEW_SERVER_RELOADED = 'webview/server-reloaded'; export const WEBVIEW_PDF_VIEWER_ATTACHED = 'webview/pdf-viewer/attached'; +export const SIDE_BAR_SERVER_RELOAD = 'side-bar/server-reload'; +export const SIDE_BAR_SERVER_COPY_URL = 'side-bar/server-copy-url'; +export const SIDE_BAR_SERVER_OPEN_DEV_TOOLS = 'side-bar/server-open-dev-tools'; +export const SIDE_BAR_SERVER_FORCE_RELOAD = 'side-bar/server-force-reload'; +export const SIDE_BAR_SERVER_REMOVE = 'side-bar/server-remove'; export type UiActionTypeToPayloadMap = { [ABOUT_DIALOG_DISMISSED]: void; @@ -138,6 +144,11 @@ export type UiActionTypeToPayloadMap = { [SIDE_BAR_REMOVE_SERVER_CLICKED]: Server['url']; [SIDE_BAR_SERVER_SELECTED]: Server['url']; [SIDE_BAR_SERVERS_SORTED]: Server['url'][]; + [SIDE_BAR_SERVER_RELOAD]: Server['url']; + [SIDE_BAR_SERVER_COPY_URL]: Server['url']; + [SIDE_BAR_SERVER_OPEN_DEV_TOOLS]: Server['url']; + [SIDE_BAR_SERVER_FORCE_RELOAD]: Server['url']; + [SIDE_BAR_SERVER_REMOVE]: Server['url']; [TOUCH_BAR_FORMAT_BUTTON_TOUCHED]: | 'bold' | 'italic' @@ -169,6 +180,10 @@ export type UiActionTypeToPayloadMap = { customTheme: Server['customTheme']; }; [WEBVIEW_TITLE_CHANGED]: { url: Server['url']; title: Server['title'] }; + [WEBVIEW_PAGE_TITLE_CHANGED]: { + url: Server['url']; + pageTitle: Server['pageTitle']; + }; [WEBVIEW_UNREAD_CHANGED]: { url: Server['url']; badge: Server['badge'] }; [WEBVIEW_USER_LOGGED_IN]: { url: Server['url']; diff --git a/src/ui/components/DownloadsManagerView/index.tsx b/src/ui/components/DownloadsManagerView/index.tsx index 5593be8c07..a088842a1d 100644 --- a/src/ui/components/DownloadsManagerView/index.tsx +++ b/src/ui/components/DownloadsManagerView/index.tsx @@ -179,9 +179,12 @@ const DownloadsManagerView = () => { return ( { }; return ( - - - - - - - {t('loadingError.announcement')} + <> + {isFailed || + (isReloading && ( + + + + + + + {t('loadingError.announcement')} - {t('loadingError.title')} - - - + {t('loadingError.title')} + + + - - {isReloading && ( - - - - )} + + {isReloading && ( + + + + )} - {!isReloading && ( - - - - )} - - - + {!isReloading && ( + + + + )} + + + + ))} + ); }; diff --git a/src/ui/components/ServersView/UnsupportedServer.tsx b/src/ui/components/ServersView/UnsupportedServer.tsx index 56c986ab92..f02dc61a55 100644 --- a/src/ui/components/ServersView/UnsupportedServer.tsx +++ b/src/ui/components/ServersView/UnsupportedServer.tsx @@ -11,7 +11,6 @@ import { ipcRenderer } from 'electron'; import { useTranslation } from 'react-i18next'; import * as urls from '../../../urls'; -import { ErrorPane } from './styles'; type UnsupportedServerProps = { isSupported: boolean | undefined; @@ -32,39 +31,36 @@ const UnsupportedServer = ({ }; return ( - - - - - - {t('unsupportedServer.title', { - instanceDomain, - })} - - {t('unsupportedServer.announcement')} - - - - - - + <> + {isSupported === false && ( + + + + + {t('unsupportedServer.title', { + instanceDomain, + })} + + + {t('unsupportedServer.announcement')} + + + + + + + )} + ); }; diff --git a/src/ui/components/SettingsView/SettingsView.tsx b/src/ui/components/SettingsView/SettingsView.tsx index 541c349fab..5f80417997 100644 --- a/src/ui/components/SettingsView/SettingsView.tsx +++ b/src/ui/components/SettingsView/SettingsView.tsx @@ -19,9 +19,12 @@ export const SettingsView = () => { return ( { display='flex' flexDirection='row' flexWrap='nowrap' - color='default' fontScale='h1' + color='font-default' > {t('settings.title')} diff --git a/src/ui/components/Shell/index.tsx b/src/ui/components/Shell/index.tsx index e13689912d..105a82bd5a 100644 --- a/src/ui/components/Shell/index.tsx +++ b/src/ui/components/Shell/index.tsx @@ -1,6 +1,9 @@ -import { useLayoutEffect } from 'react'; +import { Box, PaletteStyleTag } from '@rocket.chat/fuselage'; +import type { Themes } from '@rocket.chat/fuselage/dist/components/PaletteStyleTag/types/themes'; +import { useEffect, useLayoutEffect, useState } from 'react'; import { useSelector } from 'react-redux'; +import { select } from '../../../store'; import type { RootState } from '../../../store/rootReducer'; import { AboutDialog } from '../AboutDialog'; import { AddServerView } from '../AddServerView'; @@ -13,11 +16,51 @@ import { ServersView } from '../ServersView'; import { SettingsView } from '../SettingsView'; import { SideBar } from '../SideBar'; import { SupportedVersionDialog } from '../SupportedVersionDialog'; +import { TopBar } from '../TopBar'; import { UpdateDialog } from '../UpdateDialog'; -import { GlobalStyles, Wrapper, WindowDragBar, ViewsWrapper } from './styles'; +import TooltipProvider from '../utils/TooltipProvider'; +import { GlobalStyles, WindowDragBar } from './styles'; export const Shell = () => { const appPath = useSelector(({ appPath }: RootState) => appPath); + const machineTheme = useSelector( + ({ machineTheme }: RootState) => machineTheme + ); + const currentView = useSelector(({ currentView }: RootState) => currentView); + + const currentServerUrl = select(({ currentView }) => + typeof currentView === 'object' ? currentView.url : null + ); + + const selectedServer = useSelector(({ servers }) => { + if (!currentServerUrl) return null; + + try { + return servers.find( + (s: { url: string | URL }) => + new URL(s.url).origin === new URL(currentServerUrl).origin + ); + } catch (e) { + return null; + } + }); + + const [currentTheme, setCurrentTheme] = useState( + machineTheme as Themes + ); + + useEffect(() => { + if (selectedServer) { + console.log(selectedServer.themeAppearance); + if (selectedServer.themeAppearance === 'auto') { + setCurrentTheme(machineTheme as Themes); + } else { + setCurrentTheme(selectedServer.themeAppearance as Themes); + } + } else { + setCurrentTheme(machineTheme as Themes); + } + }, [selectedServer, machineTheme, currentView]); useLayoutEffect(() => { if (!appPath) { @@ -35,18 +78,37 @@ export const Shell = () => { }, [appPath]); return ( - <> + + {process.platform === 'darwin' && } - - - - - - - - - + + {process.platform === 'darwin' && } + + + + + + + + + + @@ -54,6 +116,6 @@ export const Shell = () => { - + ); }; diff --git a/src/ui/components/SideBar/MenuItemWithOnClick.tsx b/src/ui/components/SideBar/MenuItemWithOnClick.tsx new file mode 100644 index 0000000000..1ea649f7a2 --- /dev/null +++ b/src/ui/components/SideBar/MenuItemWithOnClick.tsx @@ -0,0 +1,23 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +/* eslint-disable react/display-name */ +// MenuItemWithOnClick.tsx +import { MenuItem } from '@rocket.chat/fuselage'; // Adjust the import path as necessary +import type { MouseEvent } from 'react'; + +interface IWithOnClickProps { + onClick: (event: MouseEvent) => void; +} + +const withOnClick =

( + WrappedComponent: React.ComponentType

+): React.FC

=> { + return ({ onClick, ...props }) => ( +

+ +
+ ); +}; + +const MenuItemWithOnClick = withOnClick(MenuItem); + +export default MenuItemWithOnClick; diff --git a/src/ui/components/SideBar/ServerButton.tsx b/src/ui/components/SideBar/ServerButton.tsx index d1d2f8ad96..fcd17eef79 100644 --- a/src/ui/components/SideBar/ServerButton.tsx +++ b/src/ui/components/SideBar/ServerButton.tsx @@ -1,24 +1,33 @@ import { css } from '@rocket.chat/css-in-js'; -import { IconButton, Badge, Box } from '@rocket.chat/fuselage'; +import { + Avatar, + IconButton, + Badge, + Box, + Dropdown, + Option, + OptionIcon, + OptionContent, + OptionDivider, +} from '@rocket.chat/fuselage'; import type { DragEvent, MouseEvent } from 'react'; -import { useMemo } from 'react'; -import { useDispatch } from 'react-redux'; -import type { Dispatch } from 'redux'; +import { useMemo, useRef } from 'react'; +import { useTranslation } from 'react-i18next'; -import type { RootAction } from '../../../store/actions'; +import { dispatch } from '../../../store'; import { SIDE_BAR_SERVER_SELECTED, - SIDE_BAR_CONTEXT_MENU_TRIGGERED, + SIDE_BAR_SERVER_RELOAD, + SIDE_BAR_SERVER_COPY_URL, + SIDE_BAR_SERVER_OPEN_DEV_TOOLS, + SIDE_BAR_SERVER_FORCE_RELOAD, + SIDE_BAR_SERVER_REMOVE, } from '../../actions'; -import { - Avatar, - Favicon, - Initials, - KeyboardShortcut, - ServerButtonWrapper, -} from './styles'; +import { Initials, ServerButtonWrapper } from './styles'; +import { useDropdownVisibility } from './useDropdownVisibility'; type ServerButtonProps = { + className?: string; url: string; title: string; shortcutNumber: string | null; @@ -35,13 +44,20 @@ type ServerButtonProps = { onDrop: (event: DragEvent) => void; }; +type ServerActionType = + | typeof SIDE_BAR_SERVER_SELECTED + | typeof SIDE_BAR_SERVER_RELOAD + | typeof SIDE_BAR_SERVER_COPY_URL + | typeof SIDE_BAR_SERVER_OPEN_DEV_TOOLS + | typeof SIDE_BAR_SERVER_FORCE_RELOAD + | typeof SIDE_BAR_SERVER_REMOVE; + const ServerButton = ({ url, title, shortcutNumber, isSelected, favicon, - isShortcutVisible, hasUnreadMessages, mentionCount, userLoggedIn, @@ -50,13 +66,19 @@ const ServerButton = ({ onDragEnd, onDragEnter, onDrop, + className, }: ServerButtonProps) => { - const dispatch = useDispatch>(); - const handleServerClick = (): void => { dispatch({ type: SIDE_BAR_SERVER_SELECTED, payload: url }); }; + const reference = useRef(null); + const target = useRef(null); + + const { t } = useTranslation(); + + const { isVisible, toggle } = useDropdownVisibility({ reference, target }); + const initials = useMemo( () => title @@ -68,57 +90,141 @@ const ServerButton = ({ [title, url] ); + const handleActionDropdownClick = ( + action: ServerActionType, + serverUrl: string + ): void => { + if (action) dispatch({ type: action, payload: serverUrl }); + toggle(); + }; + const handleServerContextMenu = (event: MouseEvent): void => { event.preventDefault(); - dispatch({ type: SIDE_BAR_CONTEXT_MENU_TRIGGERED, payload: url }); + // dispatch({ type: SIDE_BAR_CONTEXT_MENU_TRIGGERED, payload: url }); + toggle(); }; + const tooltipContent = ` + ${title} (${process.platform === 'darwin' ? '⌘' : '^'}+${shortcutNumber}) + ${ + hasUnreadMessages + ? ` + ${ + mentionCount && mentionCount > 1 + ? t('sidebar.tooltips.unreadMessages', { count: mentionCount }) + : t('sidebar.tooltips.unreadMessage', { count: mentionCount }) + }` + : '' + } + ${!userLoggedIn ? t('sidebar.tooltips.userNotLoggedIn') : ''} +`.trim(); + return ( - event.preventDefault()} - onDragStart={onDragStart} - onDragEnd={onDragEnd} - onDragEnter={onDragEnter} - onDrop={onDrop} - > - - {initials} - - - } - /> - + event.preventDefault()} + onDragStart={onDragStart} + onDragEnd={onDragEnd} + onDragEnter={onDragEnter} + onDrop={onDrop} + className={className} + title={tooltipContent} > - {mentionCount && {mentionCount}} - {!userLoggedIn && !} - - {shortcutNumber && ( - - {process.platform === 'darwin' ? '⌘' : '^'} - {shortcutNumber} - + + {initials} + + {!!favicon && ( + + )} + +
+ } + > + + {mentionCount && {mentionCount}} + {!userLoggedIn && !} + + + + {isVisible && ( + + + Workspace + + + + + + + + )} - + ); }; diff --git a/src/ui/components/SideBar/TooltipComponent.tsx b/src/ui/components/SideBar/TooltipComponent.tsx new file mode 100644 index 0000000000..65710af3df --- /dev/null +++ b/src/ui/components/SideBar/TooltipComponent.tsx @@ -0,0 +1,30 @@ +import { + Tooltip, + PositionAnimated, + AnimatedVisibility, +} from '@rocket.chat/fuselage'; +import type { ReactElement, ReactNode } from 'react'; +import { useRef } from 'react'; + +type TooltipComponentProps = { + title: ReactNode; + anchor: Element; +}; + +export const TooltipComponent = ({ + title, + anchor, +}: TooltipComponentProps): ReactElement => { + const ref = useRef(anchor); + + return ( + + {title} + + ); +}; diff --git a/src/ui/components/SideBar/index.tsx b/src/ui/components/SideBar/index.tsx index 34e1029364..73592abcfb 100644 --- a/src/ui/components/SideBar/index.tsx +++ b/src/ui/components/SideBar/index.tsx @@ -1,28 +1,26 @@ -import { Icon } from '@rocket.chat/fuselage'; -import { useMemo } from 'react'; +import { + Box, + ButtonGroup, + IconButton, + MenuItem, + MenuSection, + MenuV2, + OptionContent, + OptionIcon, +} from '@rocket.chat/fuselage'; import { useTranslation } from 'react-i18next'; -import { useDispatch, useSelector } from 'react-redux'; -import type { Dispatch } from 'redux'; +import { useSelector } from 'react-redux'; -import type { RootAction } from '../../../store/actions'; +import { dispatch } from '../../../store'; import type { RootState } from '../../../store/rootReducer'; import { + SETTINGS_SET_IS_SIDE_BAR_ENABLED_CHANGED, SIDE_BAR_ADD_NEW_SERVER_CLICKED, SIDE_BAR_DOWNLOADS_BUTTON_CLICKED, SIDE_BAR_SETTINGS_BUTTON_CLICKED, } from '../../actions'; import { useServers } from '../hooks/useServers'; import ServerButton from './ServerButton'; -import CustomTheme from './customTheme'; -import { - Wrapper, - Content, - ServerList, - AddServerButton, - SidebarActionButton, - Button, - BottomButtons, -} from './styles'; import { useKeyboardShortcuts } from './useKeyboardShortcuts'; import { useSorting } from './useSorting'; @@ -37,15 +35,15 @@ export const SideBar = () => { ({ isAddNewServersEnabled }: RootState) => isAddNewServersEnabled ); const isVisible = servers.length > 0 && isSideBarEnabled; - const style = useMemo( - () => servers.find(({ selected }) => selected)?.style || {}, - [servers] - ); + // const style = useMemo( + // () => servers.find(({ selected }) => selected)?.style || {}, + // [servers] + // ); - const customTheme = useMemo( - () => servers.find(({ selected }) => selected)?.customTheme || '', - [servers] - ); + // const customTheme = useMemo( + // () => servers.find(({ selected }) => selected)?.customTheme || '', + // [servers] + // ); const isEachShortcutVisible = useKeyboardShortcuts(); const { sortedServers, @@ -55,29 +53,54 @@ export const SideBar = () => { handleDragEnter, handleDrop, } = useSorting(servers); - const dispatch = useDispatch>(); const handleAddServerButtonClicked = (): void => { dispatch({ type: SIDE_BAR_ADD_NEW_SERVER_CLICKED }); }; - const handelDownloadsButtonClicked = (): void => { + const handleDownloadsButtonClicked = (): void => { dispatch({ type: SIDE_BAR_DOWNLOADS_BUTTON_CLICKED }); }; - const handelSettingsButtonClicked = (): void => { + const handleSettingsButtonClicked = (): void => { dispatch({ type: SIDE_BAR_SETTINGS_BUTTON_CLICKED }); }; + + const handleHideWorkspaceBar = (): void => { + dispatch({ + type: SETTINGS_SET_IS_SIDE_BAR_ENABLED_CHANGED, + payload: false, + }); + }; + + const handleMenuClick = (key: React.Key) => { + switch (key) { + case 'hide_workspace_bar': + handleHideWorkspaceBar(); + break; + case 'downloads': + handleDownloadsButtonClicked(); + break; + case 'desktop_settings': + handleSettingsButtonClicked(); + break; + } + }; + const { t } = useTranslation(); - const currentView = useSelector(({ currentView }: RootState) => currentView); + // const currentView = useSelector(({ currentView }: RootState) => currentView); return ( - - - - + + + {sortedServers.map((server, order) => ( { onDrop={handleDrop(server.url)} /> ))} - - {isAddNewServersEnabled && ( - - - + - - - )} - - - - - - + title={t('sidebar.tooltips.addWorkspace', { + shortcut: process.platform === 'darwin' ? '⌘' : '^', + })} + > + )} + + + + + {/* + + Hide workspace bar + */} + + + {t('sidebar.downloads')} + + + + {t('sidebar.settings')} + + + + + ); }; diff --git a/src/ui/components/SideBar/styles.tsx b/src/ui/components/SideBar/styles.tsx index 0bd79f2cae..eea9c3b2c8 100644 --- a/src/ui/components/SideBar/styles.tsx +++ b/src/ui/components/SideBar/styles.tsx @@ -1,127 +1,52 @@ import { css } from '@emotion/react'; import styled from '@emotion/styled'; -import { withTooltip } from './withTolltip'; - -type WrapperProps = { - sideBarStyle: { - background?: string; - color?: string; - border?: string; - }; - isVisible: boolean; - customTheme?: string; -}; - -export const Wrapper = styled.div` - flex: 0 0 68px; - align-self: stretch; - - display: flex; - flex-direction: column; - align-items: stretch; - - user-select: none; - -webkit-app-region: drag; - - transition: - margin-inline-start 230ms ease-in-out, - visibility 230ms ease-in-out; - - ${({ sideBarStyle: { background } }) => css` - background: ${background ?? '#2f343d'}; - `} - ${({ sideBarStyle: { color } }) => css` - color: ${color ?? '#ffffff'}; - `} - - ${({ sideBarStyle: { border } }) => - border && - css` - border-right: ${border ?? 'none'}; - `} - ${({ isVisible }) => - !isVisible && - css` - margin-inline-start: -68px; - visibility: hidden; - `} - ${({ customTheme }) => customTheme} -`; - -type ContentProps = { - withWindowButtons: boolean; -}; - -export const Content = styled.div` - display: flex; - flex-direction: column; - flex: 1 1 0; - padding-top: 10px; - align-items: stretch; - - ${({ withWindowButtons }) => - withWindowButtons && - css` - padding-top: 28px; - `} -`; - -export const ServerList = styled.ol` - -webkit-app-region: no-drag; - display: flex; - flex-direction: column; - flex: 0 0 auto; - margin: 0; - padding: 0; - align-items: stretch; -`; - type ServerButtonWrapperProps = { isDragged: boolean; hasUnreadMessages: boolean; isSelected: boolean; - tooltip: string; + tooltip?: string; }; export const ServerButtonWrapper = styled.li` position: relative; - flex: 0 0 auto; - box-sizing: border-box; - margin: 4px 0; - font-size: 24px; - line-height: 1.25; display: flex; - cursor: pointer; - color: inherit; - align-items: center; - flex-flow: row wrap; - justify-content: space-between; - margin-left: 14px; - margin-right: 12px; + list-style-type: none; ${({ isDragged }) => isDragged && css` opacity: 0.5; `} - ${withTooltip} -`; - -type KeyboardShortcutProps = { - visible: boolean; -}; - -export const KeyboardShortcut = styled.div` - flex: 1 0 100%; - padding-top: 8px; - text-align: center; - font-size: 12px; - line-height: 1; - ${({ visible }) => css` - visibility: ${visible ? 'visible' : 'hidden'}; - `} + &::before { + position: absolute; + width: 4px; + height: 0; + left: -8px; + content: ''; + transition: + height var(--transitions-duration), + opacity var(--transitions-duration); + border-radius: 0 4px 4px 0; + + background-color: var( + --rcx-color-surface-selected, + var(--rcx-color-neutral-450, #d7dbe0) + ) !important; + + // background-color: var( + // --rcx-color-surface-dark, + // var(--rcx-color-neutral-800, #2f343d) + // ) !important; + + ${({ isSelected }) => + isSelected && + css` + height: 28px; + opacity: 1; + `} + } `; type InitialsProps = { @@ -153,118 +78,4 @@ type AvatarProps = { isSelected: boolean; }; -export const Avatar = styled.span` - flex: 1 1 auto; - display: flex; - flex-flow: row nowrap; - align-items: center; - justify-content: center; - height: 42px; - transition: opacity var(--transitions-duration); - - ${({ isSelected }) => css` - opacity: ${isSelected ? '1' : '0.6'}; - `} - - &:hover { - ${({ isSelected }) => css` - opacity: ${isSelected ? '1' : '0.8'}; - `} - } -`; - -export const AddServerButton = styled.button` - -webkit-app-region: no-drag; - font-family: inherit; - position: relative; - flex: 0 0 auto; - box-sizing: border-box; - margin: 4px 0; - font-size: 2.5rem; - line-height: 1.25; - display: flex; - flex-direction: row; - height: 40px; - padding: 0; - color: inherit; - border: none; - background: none; - align-items: center; - justify-content: center; -`; - -type AddServerButtonLabelProps = { - tooltip: string; -}; - -export const AddServerButtonLabel = styled.span` - display: block; - line-height: 30px; - width: 40px; - height: 40px; - transition: opacity var(--transitions-duration); - opacity: 0.6; - color: inherit; - background-color: rgba(0, 0, 0, 0.1); - cursor: pointer; - - &:hover { - opacity: 1; - } - - ${withTooltip} -`; - -type SidebarActionButtonProps = { - isSelected?: boolean; - tooltip: string; -}; - -export const SidebarActionButton = styled.span` - -webkit-app-region: no-drag; - display: flex; - justify-content: center; - align-items: center; - width: 40px; - height: 40px; - line-height: 30px; - transition: opacity var(--transitions-duration); - opacity: 0.6; - color: inherit; - background: rgba(0, 0, 0, 0); - cursor: pointer; - - ${({ isSelected }) => - isSelected && - css` - opacity: 1; - `} - - &:hover { - opacity: 1; - } - - ${withTooltip} -`; - -export const BottomButtons = styled.div` - display: flex; - flex: 1; - flex-direction: column; - justify-content: flex-end; - align-items: center; - padding-bottom: 16px; -`; - -export const Button = styled.button` - position: relative; - height: 40px; - border: none; - padding: 0; - margin-top: 14px; - font-size: 2.5rem; - line-height: 1.25; - background: rgba(0, 0, 0, 0); - color: inherit; - font-family: inherit; -`; +export const Avatar = styled.span``; diff --git a/src/ui/components/SideBar/useDropdownVisibility.tsx b/src/ui/components/SideBar/useDropdownVisibility.tsx new file mode 100644 index 0000000000..0230e44456 --- /dev/null +++ b/src/ui/components/SideBar/useDropdownVisibility.tsx @@ -0,0 +1,38 @@ +import { useToggle, useOutsideClick } from '@rocket.chat/fuselage-hooks'; +import type { RefObject } from 'react'; +import { useCallback } from 'react'; + +/** + * useDropdownVisibility + * is used to control the visibility of a dropdown + * also checks if the user clicked outside the dropdown, but ignores if the click was on the anchor + * @param {Object} props + * @param {Object} props.reference - The reference where the dropdown will be attached to + * @param {Object} props.target - The target, the dropdown itself + * @returns {Object} + * @returns {Boolean} isVisible - The visibility of the dropdown + * @returns {Function} toggle - The function to toggle the dropdown + */ + +export const useDropdownVisibility = ({ + reference, + target, +}: { + reference: RefObject; + target: RefObject; +}): { + isVisible: boolean; + toggle: (state?: boolean) => void; +} => { + const [isVisible, toggle] = useToggle(false); + + useOutsideClick( + [target, reference], + useCallback(() => toggle(false), [toggle]) + ); + + return { + isVisible, + toggle, + }; +}; diff --git a/src/ui/components/SideBar/withTolltip.tsx b/src/ui/components/SideBar/withTolltip.tsx index 486b86e45f..fa6d6e54bc 100644 --- a/src/ui/components/SideBar/withTolltip.tsx +++ b/src/ui/components/SideBar/withTolltip.tsx @@ -4,7 +4,7 @@ import { css } from '@emotion/react'; export const withTooltip = ({ tooltip, }: { - tooltip: string; + tooltip?: string; }): SerializedStyles => css` &::after { position: absolute; diff --git a/src/ui/components/TopBar/index.tsx b/src/ui/components/TopBar/index.tsx new file mode 100644 index 0000000000..b350ce7a1f --- /dev/null +++ b/src/ui/components/TopBar/index.tsx @@ -0,0 +1,25 @@ +import { Box } from '@rocket.chat/fuselage'; +import { useSelector } from 'react-redux'; + +import type { RootState } from '../../../store/rootReducer'; + +export const TopBar = () => { + const mainWindowTitle = useSelector( + ({ mainWindowTitle }: RootState) => mainWindowTitle + ); + return ( + + {mainWindowTitle} + + ); +}; diff --git a/src/ui/components/utils/TooltipComponent.tsx b/src/ui/components/utils/TooltipComponent.tsx new file mode 100644 index 0000000000..65710af3df --- /dev/null +++ b/src/ui/components/utils/TooltipComponent.tsx @@ -0,0 +1,30 @@ +import { + Tooltip, + PositionAnimated, + AnimatedVisibility, +} from '@rocket.chat/fuselage'; +import type { ReactElement, ReactNode } from 'react'; +import { useRef } from 'react'; + +type TooltipComponentProps = { + title: ReactNode; + anchor: Element; +}; + +export const TooltipComponent = ({ + title, + anchor, +}: TooltipComponentProps): ReactElement => { + const ref = useRef(anchor); + + return ( + + {title} + + ); +}; diff --git a/src/ui/components/utils/TooltipContext.tsx b/src/ui/components/utils/TooltipContext.tsx new file mode 100644 index 0000000000..20606f64fb --- /dev/null +++ b/src/ui/components/utils/TooltipContext.tsx @@ -0,0 +1,14 @@ +import type { ReactElement } from 'react'; +import { createContext } from 'react'; + +type TooltipPayload = ReactElement; + +export type TooltipContextValue = { + open: (payload: TooltipPayload, anchor: HTMLElement) => void; + close: () => void; +}; + +export const TooltipContext = createContext({ + open: () => undefined, + close: () => undefined, +}); diff --git a/src/ui/components/utils/TooltipPortal.tsx b/src/ui/components/utils/TooltipPortal.tsx new file mode 100644 index 0000000000..72d8d4737b --- /dev/null +++ b/src/ui/components/utils/TooltipPortal.tsx @@ -0,0 +1,18 @@ +import type { ReactNode } from 'react'; +import { memo, useEffect, useState } from 'react'; +import { createPortal } from 'react-dom'; + +import { createAnchor } from './createAnchor'; +import { deleteAnchor } from './deleteAnchor'; + +type TooltipPortalProps = { + children?: ReactNode; +}; + +const TooltipPortal = ({ children }: TooltipPortalProps) => { + const [tooltipRoot] = useState(() => createAnchor('tooltip-root')); + useEffect(() => (): void => deleteAnchor(tooltipRoot), [tooltipRoot]); + return <>{createPortal(children, tooltipRoot)}; +}; + +export default memo(TooltipPortal); diff --git a/src/ui/components/utils/TooltipProvider.tsx b/src/ui/components/utils/TooltipProvider.tsx new file mode 100644 index 0000000000..808e6f3c3b --- /dev/null +++ b/src/ui/components/utils/TooltipProvider.tsx @@ -0,0 +1,181 @@ +import { useDebouncedState, useMediaQuery } from '@rocket.chat/fuselage-hooks'; +import type { ReactNode } from 'react'; +import { useEffect, useMemo, useRef, memo, useCallback, useState } from 'react'; + +import { TooltipComponent } from './TooltipComponent'; +import { TooltipContext } from './TooltipContext'; +import TooltipPortal from './TooltipPortal'; + +type TooltipProviderProps = { + children?: ReactNode; +}; + +const TooltipProvider = ({ children }: TooltipProviderProps) => { + const lastAnchor = useRef(); + const hasHover = !useMediaQuery('(hover: none)'); + + const [tooltip, setTooltip] = useDebouncedState(null, 300); + + const restoreTitle = useCallback( + (previousAnchor: HTMLElement | undefined): void => { + setTimeout(() => { + if (previousAnchor && !previousAnchor.getAttribute('title')) { + previousAnchor.setAttribute( + 'title', + previousAnchor.getAttribute('data-title') ?? '' + ); + previousAnchor.removeAttribute('data-title'); + } + }, 100); + }, + [] + ); + + const contextValue = useMemo( + () => ({ + open: (tooltip: ReactNode, anchor: HTMLElement): void => { + const previousAnchor = lastAnchor.current; + + let formattedTooltip: ReactNode; + + if (typeof tooltip === 'string') { + const lines = tooltip + .split('\n') + .map((line, index) =>
{line}
); + formattedTooltip = <>{lines}; + } else { + formattedTooltip = tooltip; + } + + setTooltip( + + ); + lastAnchor.current = anchor; + previousAnchor && restoreTitle(previousAnchor); + }, + close: (): void => { + const previousAnchor = lastAnchor.current; + setTooltip(null); + setTooltip.flush(); + lastAnchor.current = undefined; + previousAnchor && restoreTitle(previousAnchor); + }, + dismiss: (): void => { + setTooltip(null); + setTooltip.flush(); + }, + }), + [setTooltip, restoreTitle] + ); + + useEffect(() => { + if (!hasHover) { + return; + } + + const handleMouseOver = (e: MouseEvent): void => { + const target = e.target as HTMLElement; + if (lastAnchor.current === target) { + return; + } + + const anchor = target.closest('[title], [data-tooltip]') as HTMLElement; + + if (lastAnchor.current === anchor) { + return; + } + + if (!anchor) { + contextValue.close(); + return; + } + + const title = + anchor.getAttribute('title') ?? + anchor.getAttribute('data-tooltip') ?? + ''; + if (!title) { + contextValue.close(); + return; + } + + // eslint-disable-next-line react/no-multi-comp + const Handler = () => { + const [state, setState] = useState(title.split('\n')); + useEffect(() => { + const close = (): void => contextValue.close(); + // store the title in a data attribute + anchor.setAttribute('data-title', title); + // Removes the title attribute to prevent the browser's tooltip from showing + anchor.setAttribute('title', ''); + + anchor.addEventListener('mouseleave', close); + + const observer = new MutationObserver(() => { + const updatedTitle = + anchor.getAttribute('title') ?? + anchor.getAttribute('data-tooltip') ?? + ''; + + if (updatedTitle === '') { + return; + } + + // store the title in a data attribute + anchor.setAttribute('data-title', updatedTitle); + // Removes the title attribute to prevent the browser's tooltip from showing + anchor.setAttribute('title', ''); + + setState(updatedTitle.split('\n')); + }); + + observer.observe(anchor, { + attributes: true, + attributeFilter: ['title', 'data-tooltip'], + }); + + return () => { + anchor.removeEventListener('mouseleave', close); + observer.disconnect(); + }; + }, []); + return ( + <> + {state.map((line, index) => ( +
{line}
+ ))} + + ); + }; + contextValue.open(, anchor); + }; + + const dismissOnClick = (): void => { + contextValue.dismiss(); + }; + + document.body.addEventListener('mouseover', handleMouseOver, { + passive: true, + }); + document.body.addEventListener('click', dismissOnClick, { capture: true }); + + return (): void => { + contextValue.close(); + document.body.removeEventListener('mouseover', handleMouseOver); + document.body.removeEventListener('click', dismissOnClick); + }; + }, [contextValue, setTooltip, hasHover]); + + return ( + + {children} + {tooltip && {tooltip}} + + ); +}; + +export default memo(TooltipProvider); diff --git a/src/ui/components/utils/createAnchor.ts b/src/ui/components/utils/createAnchor.ts new file mode 100644 index 0000000000..8ea37cb882 --- /dev/null +++ b/src/ui/components/utils/createAnchor.ts @@ -0,0 +1,23 @@ +import { registerAnchor } from './deleteAnchor'; + +type T = keyof HTMLElementTagNameMap; + +export const createAnchor: { + ( + id: string, + tag?: T + ): T extends undefined + ? HTMLElementTagNameMap['div'] + : HTMLElementTagNameMap[T]; +} = (id: string, tag = 'div') => { + const anchor = document.getElementById(id); + if (anchor && anchor.tagName.toLowerCase() === tag) { + return anchor as any; + } + const a = document.createElement(tag); + a.id = id; + document.body.appendChild(a); + + registerAnchor(a, () => document.body.removeChild(a)); + return a; +}; diff --git a/src/ui/components/utils/deleteAnchor.ts b/src/ui/components/utils/deleteAnchor.ts new file mode 100644 index 0000000000..8f455abc97 --- /dev/null +++ b/src/ui/components/utils/deleteAnchor.ts @@ -0,0 +1,11 @@ +const anchor = new WeakMap void>(); + +export const deleteAnchor = (element: HTMLElement): void => { + const fn = anchor.get(element); + if (fn) { + fn(); + } +}; +export const registerAnchor = (element: HTMLElement, fn: () => void): void => { + anchor.set(element, fn); +}; diff --git a/src/ui/main/rootWindow.ts b/src/ui/main/rootWindow.ts index 11ad6a4edb..7fcd4db9c7 100644 --- a/src/ui/main/rootWindow.ts +++ b/src/ui/main/rootWindow.ts @@ -1,12 +1,16 @@ import path from 'path'; import type { Rectangle, NativeImage, WebPreferences } from 'electron'; -import { app, BrowserWindow, nativeImage, screen } from 'electron'; +import { app, BrowserWindow, nativeImage, nativeTheme, screen } from 'electron'; import i18next from 'i18next'; import { createStructuredSelector } from 'reselect'; +import { + APP_MACHINE_THEME_SET, + APP_MAIN_WINDOW_TITLE_SET, +} from '../../app/actions'; import { setupRootWindowReload } from '../../app/main/dev'; -import { select, watch, listen, dispatchLocal } from '../../store'; +import { select, watch, listen, dispatchLocal, dispatch } from '../../store'; import type { RootState } from '../../store/rootReducer'; import { ROOT_WINDOW_STATE_CHANGED, WEBVIEW_FOCUS_REQUESTED } from '../actions'; import type { WindowState } from '../common'; @@ -208,11 +212,15 @@ export const setupRootWindow = (): void => { typeof currentView === 'object' ? servers.find(({ url }) => url === currentView.url) : null; - return currentServer?.title || app.name; + return currentServer?.pageTitle || currentServer?.title || app.name; }, async (windowTitle) => { const browserWindow = await getRootWindow(); browserWindow.setTitle(windowTitle); + dispatch({ + type: APP_MAIN_WINDOW_TITLE_SET, + payload: windowTitle, + }); } ), listen(WEBVIEW_FOCUS_REQUESTED, async () => { @@ -412,6 +420,21 @@ export const showRootWindow = async (): Promise => { }); }; +export const watchMachineTheme = (): void => { + dispatchMachineTheme(); + nativeTheme.on('updated', () => { + dispatchMachineTheme(); + }); +}; + +const dispatchMachineTheme = (): void => { + const isDarkMode = nativeTheme.shouldUseDarkColors; + dispatch({ + type: APP_MACHINE_THEME_SET, + payload: isDarkMode ? 'dark' : 'light', + }); +}; + export const exportLocalStorage = async (): Promise> => { try { tempWindow = new BrowserWindow({ diff --git a/src/ui/main/serverView/index.ts b/src/ui/main/serverView/index.ts index c916624dd8..cf1dd229ff 100644 --- a/src/ui/main/serverView/index.ts +++ b/src/ui/main/serverView/index.ts @@ -44,6 +44,12 @@ import { WEBVIEW_ATTACHED, WEBVIEW_SERVER_RELOADED, CLEAR_CACHE_TRIGGERED, + WEBVIEW_PAGE_TITLE_CHANGED, + SIDE_BAR_SERVER_RELOAD, + SIDE_BAR_SERVER_COPY_URL, + SIDE_BAR_SERVER_OPEN_DEV_TOOLS, + SIDE_BAR_SERVER_FORCE_RELOAD, + SIDE_BAR_SERVER_REMOVE, } from '../../actions'; import { getRootWindow } from '../rootWindow'; import { createPopupMenuForServerView } from './popupMenu'; @@ -70,6 +76,27 @@ const initializeServerWebContentsAfterReady = ( menu.popup({ window: rootWindow }); }; guestWebContents.addListener('context-menu', handleContextMenu); + + guestWebContents.on('page-title-updated', (_event, pageTitle) => { + dispatch({ + type: WEBVIEW_PAGE_TITLE_CHANGED, + payload: { url: _serverUrl, pageTitle }, + }); + }); +}; + +export const serverReloadView = async ( + serverUrl: Server['url'] +): Promise => { + const url = new URL(serverUrl).href; + const guestWebContents = getWebContentsByServerUrl(url); + await guestWebContents?.loadURL(url); + if (url) { + dispatch({ + type: WEBVIEW_SERVER_RELOADED, + payload: { url }, + }); + } }; const initializeServerWebContentsAfterAttach = ( @@ -388,6 +415,39 @@ export const attachGuestWebContentsEvents = async (): Promise => { guestWebContents?.loadURL(action.payload.url); }); + listen(SIDE_BAR_SERVER_RELOAD, (action) => { + serverReloadView(action.payload); + }); + + listen(SIDE_BAR_SERVER_COPY_URL, async (action) => { + const guestWebContents = getWebContentsByServerUrl(action.payload); + const currentUrl = await guestWebContents?.getURL(); + clipboard.writeText(currentUrl || ''); + }); + + listen(SIDE_BAR_SERVER_OPEN_DEV_TOOLS, (action) => { + const guestWebContents = getWebContentsByServerUrl(action.payload); + guestWebContents?.openDevTools(); + }); + + listen(SIDE_BAR_SERVER_FORCE_RELOAD, (action) => { + const guestWebContents = getWebContentsByServerUrl(action.payload); + if (!guestWebContents) { + return; + } + dispatch({ + type: CLEAR_CACHE_TRIGGERED, + payload: guestWebContents.id, + }); + }); + + listen(SIDE_BAR_SERVER_REMOVE, (action) => { + dispatch({ + type: SIDE_BAR_REMOVE_SERVER_CLICKED, + payload: action.payload, + }); + }); + listen(SIDE_BAR_CONTEXT_MENU_TRIGGERED, (action) => { const { payload: serverUrl } = action; diff --git a/src/ui/reducers/currentView.ts b/src/ui/reducers/currentView.ts index 2f243e6ff5..dd9f0f1ab2 100644 --- a/src/ui/reducers/currentView.ts +++ b/src/ui/reducers/currentView.ts @@ -5,6 +5,7 @@ import { } from '../../deepLinks/actions'; import { SERVERS_LOADED } from '../../servers/actions'; import type { ActionOf } from '../../store/actions'; +import type { SIDE_BAR_SERVER_REMOVE } from '../actions'; import { DOWNLOADS_BACK_BUTTON_CLICKED, ADD_SERVER_VIEW_SERVER_ADDED, @@ -34,7 +35,8 @@ type CurrentViewAction = | ActionOf | ActionOf | ActionOf - | ActionOf; + | ActionOf + | ActionOf; type CurrentViewState = | 'add-new-server' diff --git a/yarn.lock b/yarn.lock index 2f7cd05c33..cec403d00a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2258,12 +2258,12 @@ __metadata: languageName: node linkType: hard -"@electron/remote@npm:^2.1.1": - version: 2.1.1 - resolution: "@electron/remote@npm:2.1.1" +"@electron/remote@npm:^2.1.2": + version: 2.1.2 + resolution: "@electron/remote@npm:2.1.2" peerDependencies: electron: ">= 13.0.0" - checksum: 01ce6329d231e83dd79826c751bcd04afb991ddec381126217b5b28ca76318434e8a9a9ddf7f9f334bf6971b6c95a9349c25094ad79dccd7422f8b18ea0dcab6 + checksum: c0fa92fb30ca0dab600513b17f648a935b3e9718516d446d65590ac98a8765e0a8d20c5c6714069a164b52a32f0f07cee87a23ff8ee65197f69d0151679ac291 languageName: node linkType: hard @@ -3494,14 +3494,14 @@ __metadata: languageName: node linkType: hard -"@kayahr/jest-electron-runner@npm:~29.11.0": - version: 29.11.0 - resolution: "@kayahr/jest-electron-runner@npm:29.11.0" +"@kayahr/jest-electron-runner@npm:29.14.0": + version: 29.14.0 + resolution: "@kayahr/jest-electron-runner@npm:29.14.0" dependencies: - "@electron/remote": "npm:^2.1.1" + "@electron/remote": "npm:^2.1.2" "@jest/console": "npm:^29.7.0" "@jest/transform": "npm:^29.7.0" - electron: "npm:^28.1.0" + electron: "npm:^31.0.1" jest: "npm:^29.7.0" jest-docblock: "npm:^29.7.0" jest-haste-map: "npm:^29.7.0" @@ -3516,9 +3516,9 @@ __metadata: shell-quote: "npm:^1.8.1" source-map-support: "npm:^0.5.21" throat: "npm:^6.0.2" - tslib: "npm:^2.6.2" - uuid: "npm:^9.0.1" - checksum: 88ef97367b2a39459c4c8cbbf79e324d3b416634c8fd8270d7549f2b683aea2129cda7e476e4c94d49954c31818a4fd206d9dd7ddf96abf1e66aad44ba4fea04 + tslib: "npm:^2.6.3" + uuid: "npm:^10.0.0" + checksum: e851765f755e80a925f5d0a9cbf605510ccdc6f69d1b0d797ab5007f70652e3e96f7170c011d5d9b4b036fb2f873e388b13dc7c44071e272e7d4e6785dff2aa0 languageName: node linkType: hard @@ -5199,9 +5199,9 @@ __metadata: languageName: node linkType: hard -"@rocket.chat/fuselage@npm:~0.54.2": - version: 0.54.2 - resolution: "@rocket.chat/fuselage@npm:0.54.2" +"@rocket.chat/fuselage@npm:0.58.0": + version: 0.58.0 + resolution: "@rocket.chat/fuselage@npm:0.58.0" dependencies: "@rocket.chat/css-in-js": "npm:^0.31.25" "@rocket.chat/css-supports": "npm:^0.31.25" @@ -5219,14 +5219,14 @@ __metadata: react: ^17.0.2 react-dom: ^17.0.2 react-virtuoso: 1.2.4 - checksum: 3a15254269d3caecffef1d4f6ea177f60680ac85f61d2be88d2b5db4aa8accdd1bb30c2daae8d1eb52775ad163cd7a03686db75ab857fb9275cc9474d0ddb8c2 + checksum: 37c1d32b3787605953557e7e601af61b18389df856996add99eddbb3973b057edd0e8d6c1859f60f25d2bc9c8a898c7ece483c788bdca35ddf8b2725b6a351b7 languageName: node linkType: hard -"@rocket.chat/icons@npm:~0.36.0": - version: 0.36.0 - resolution: "@rocket.chat/icons@npm:0.36.0" - checksum: 5a351fc064f32679398460297a90408a612ee3b990937ba2c35ceb569ac5d3600468452550d28729983ee8be038012ff441a76fcd1a6b29454129e557a9660d9 +"@rocket.chat/icons@npm:0.37.0": + version: 0.37.0 + resolution: "@rocket.chat/icons@npm:0.37.0" + checksum: 92f42e930f4adba0bbdab92860ef51443399cb959395203d4905af483f47ef762d3a3a1ab419e6163961d61748c1bdc782beea0a55d7be33d6f15bfdea856522 languageName: node linkType: hard @@ -5831,15 +5831,6 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:^18.11.18": - version: 18.18.9 - resolution: "@types/node@npm:18.18.9" - dependencies: - undici-types: "npm:~5.26.4" - checksum: ebd98b117c868edc597807cd0dab214b6110b9cd5ee406632641d0cf5b8bd7cb199caaac657a046d9203c441dbcfb3c71154ffa2d615ec89f80e6972143e6ec8 - languageName: node - linkType: hard - "@types/node@npm:^20.9.0": version: 20.12.12 resolution: "@types/node@npm:20.12.12" @@ -6368,6 +6359,13 @@ __metadata: languageName: node linkType: hard +"@yarnpkg/lockfile@npm:^1.1.0": + version: 1.1.0 + resolution: "@yarnpkg/lockfile@npm:1.1.0" + checksum: cd19e1114aaf10a05126aeea8833ef4ca8af8a46e88e12884f8359d19333fd19711036dbc2698dbe937f81f037070cf9a8da45c2e8c6ca19cafd7d15659094ed + languageName: node + linkType: hard + "JSONStream@npm:^1.3.5": version: 1.3.5 resolution: "JSONStream@npm:1.3.5" @@ -7284,6 +7282,15 @@ __metadata: languageName: node linkType: hard +"braces@npm:^3.0.3": + version: 3.0.3 + resolution: "braces@npm:3.0.3" + dependencies: + fill-range: "npm:^7.1.1" + checksum: fad11a0d4697a27162840b02b1fad249c1683cbc510cd5bf1a471f2f8085c046d41094308c577a50a03a579dd99d5a6b3724c4b5e8b14df2c4443844cfcda2c6 + languageName: node + linkType: hard + "browserslist@npm:^4.21.3": version: 4.21.7 resolution: "browserslist@npm:4.21.7" @@ -7705,6 +7712,13 @@ __metadata: languageName: node linkType: hard +"ci-info@npm:^3.7.0": + version: 3.9.0 + resolution: "ci-info@npm:3.9.0" + checksum: 75bc67902b4d1c7b435497adeb91598f6d52a3389398e44294f6601b20cfef32cf2176f7be0eb961d9e085bb333a8a5cae121cb22f81cf238ae7f58eb80e9397 + languageName: node + linkType: hard + "cjs-module-lexer@npm:^1.0.0": version: 1.2.3 resolution: "cjs-module-lexer@npm:1.2.3" @@ -8994,29 +9008,29 @@ __metadata: languageName: node linkType: hard -"electron@npm:30.1.2": - version: 30.1.2 - resolution: "electron@npm:30.1.2" +"electron@npm:30.4.0": + version: 30.4.0 + resolution: "electron@npm:30.4.0" dependencies: "@electron/get": "npm:^2.0.0" "@types/node": "npm:^20.9.0" extract-zip: "npm:^2.0.1" bin: electron: cli.js - checksum: ab8a67ac4a318aa8a1751d561b070007001398c642f98ebf41ce98e37b4b38e0ae80c7cb5bda1603c889433235f2e0176136e4d3b0ce177ebd9d1ba79f12cdc4 + checksum: a79a43e7b074b1cc028140fadebe7fb10d27d388bc20210086533738b19a763e85cc91ffb5584e85e3ef3caa35f7293dd12363aebff2d8a28a0eac24998f4908 languageName: node linkType: hard -"electron@npm:^28.1.0": - version: 28.1.1 - resolution: "electron@npm:28.1.1" +"electron@npm:^31.0.1": + version: 31.4.0 + resolution: "electron@npm:31.4.0" dependencies: "@electron/get": "npm:^2.0.0" - "@types/node": "npm:^18.11.18" + "@types/node": "npm:^20.9.0" extract-zip: "npm:^2.0.1" bin: electron: cli.js - checksum: fa5489892ce7376aa50f0dc4c1c3225d791a301c4559ce458dab1949aabf3b036a909888202e4bd75fbf9691c8c110cae843f1bb105d9f64778f8c115f7f2015 + checksum: 445bf8b04f0f8fcc16da4832634507c01bbdd6733ef8236e2216f8f4667d070115d41b7bd28f73ca91f819a720a629e1cbea08b079fa7bde424fd24b6aef4499 languageName: node linkType: hard @@ -10144,6 +10158,15 @@ __metadata: languageName: node linkType: hard +"fill-range@npm:^7.1.1": + version: 7.1.1 + resolution: "fill-range@npm:7.1.1" + dependencies: + to-regex-range: "npm:^5.0.1" + checksum: a7095cb39e5bc32fada2aa7c7249d3f6b01bd1ce461a61b0adabacccabd9198500c6fb1f68a7c851a657e273fce2233ba869638897f3d7ed2e87a2d89b4436ea + languageName: node + linkType: hard + "find-root@npm:^1.1.0": version: 1.1.0 resolution: "find-root@npm:1.1.0" @@ -10190,6 +10213,15 @@ __metadata: languageName: node linkType: hard +"find-yarn-workspace-root@npm:^2.0.0": + version: 2.0.0 + resolution: "find-yarn-workspace-root@npm:2.0.0" + dependencies: + micromatch: "npm:^4.0.2" + checksum: 7fa7942849eef4d5385ee96a0a9a5a9afe885836fd72ed6a4280312a38690afea275e7d09b343fe97daf0412d833f8ac4b78c17fc756386d9ebebf0759d707a7 + languageName: node + linkType: hard + "flat-cache@npm:^3.0.4": version: 3.0.4 resolution: "flat-cache@npm:3.0.4" @@ -10783,7 +10815,7 @@ __metadata: languageName: node linkType: hard -"graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": +"graceful-fs@npm:^4.1.11, graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": version: 4.2.11 resolution: "graceful-fs@npm:4.2.11" checksum: bf152d0ed1dc159239db1ba1f74fdbc40cb02f626770dcd5815c427ce0688c2635a06ed69af364396da4636d0408fcf7d4afdf7881724c3307e46aff30ca49e2 @@ -11448,6 +11480,15 @@ __metadata: languageName: node linkType: hard +"is-docker@npm:^2.0.0": + version: 2.2.1 + resolution: "is-docker@npm:2.2.1" + bin: + is-docker: cli.js + checksum: 3fef7ddbf0be25958e8991ad941901bf5922ab2753c46980b60b05c1bf9c9c2402d35e6dc32e4380b980ef5e1970a5d9d5e5aa2e02d77727c3b6b5e918474c56 + languageName: node + linkType: hard + "is-extglob@npm:^2.1.1": version: 2.1.1 resolution: "is-extglob@npm:2.1.1" @@ -11724,6 +11765,15 @@ __metadata: languageName: node linkType: hard +"is-wsl@npm:^2.1.1": + version: 2.2.0 + resolution: "is-wsl@npm:2.2.0" + dependencies: + is-docker: "npm:^2.0.0" + checksum: 20849846ae414997d290b75e16868e5261e86ff5047f104027026fd61d8b5a9b0b3ade16239f35e1a067b3c7cc02f70183cb661010ed16f4b6c7c93dad1b19d8 + languageName: node + linkType: hard + "isarray@npm:0.0.1": version: 0.0.1 resolution: "isarray@npm:0.0.1" @@ -12577,6 +12627,18 @@ __metadata: languageName: node linkType: hard +"json-stable-stringify@npm:^1.0.2": + version: 1.1.1 + resolution: "json-stable-stringify@npm:1.1.1" + dependencies: + call-bind: "npm:^1.0.5" + isarray: "npm:^2.0.5" + jsonify: "npm:^0.0.1" + object-keys: "npm:^1.1.1" + checksum: 60853c1f63451319b5c7953465a555aa816cf84e60e3ca36b6c05225d8fdc4615127fb4ecb92f9f5ad880c552ab8cbae9a519f78b995e7788d6d89e57afafdeb + languageName: node + linkType: hard + "json-stringify-safe@npm:^5.0.1": version: 5.0.1 resolution: "json-stringify-safe@npm:5.0.1" @@ -12629,6 +12691,13 @@ __metadata: languageName: node linkType: hard +"jsonify@npm:^0.0.1": + version: 0.0.1 + resolution: "jsonify@npm:0.0.1" + checksum: 7b86b6f4518582ff1d8b7624ed6c6277affd5246445e864615dbdef843a4057ac58587684faf129ea111eeb80e01c15f0a4d9d03820eb3f3985fa67e81b12398 + languageName: node + linkType: hard + "jsonparse@npm:^1.2.0": version: 1.3.1 resolution: "jsonparse@npm:1.3.1" @@ -12715,6 +12784,15 @@ __metadata: languageName: node linkType: hard +"klaw-sync@npm:^6.0.0": + version: 6.0.0 + resolution: "klaw-sync@npm:6.0.0" + dependencies: + graceful-fs: "npm:^4.1.11" + checksum: 0da397f8961313c3ef8f79fb63af9002cde5a8fb2aeb1a37351feff0dd6006129c790400c3f5c3b4e757bedcabb13d21ec0a5eaef5a593d59515d4f2c291e475 + languageName: node + linkType: hard + "kleur@npm:^3.0.3": version: 3.0.3 resolution: "kleur@npm:3.0.3" @@ -13166,6 +13244,16 @@ __metadata: languageName: node linkType: hard +"micromatch@npm:^4.0.2": + version: 4.0.7 + resolution: "micromatch@npm:4.0.7" + dependencies: + braces: "npm:^3.0.3" + picomatch: "npm:^2.3.1" + checksum: a11ed1cb67dcbbe9a5fc02c4062cf8bb0157d73bf86956003af8dcfdf9b287f9e15ec0f6d6925ff6b8b5b496202335e497b01de4d95ef6cf06411bc5e5c474a0 + languageName: node + linkType: hard + "micromatch@npm:^4.0.4": version: 4.0.5 resolution: "micromatch@npm:4.0.5" @@ -13939,6 +14027,16 @@ __metadata: languageName: node linkType: hard +"open@npm:^7.4.2": + version: 7.4.2 + resolution: "open@npm:7.4.2" + dependencies: + is-docker: "npm:^2.0.0" + is-wsl: "npm:^2.1.1" + checksum: 4fc02ed3368dcd5d7247ad3566433ea2695b0713b041ebc0eeb2f0f9e5d4e29fc2068f5cdd500976b3464e77fe8b61662b1b059c73233ccc601fe8b16d6c1cd6 + languageName: node + linkType: hard + "optionator@npm:^0.9.3": version: 0.9.3 resolution: "optionator@npm:0.9.3" @@ -13953,6 +14051,13 @@ __metadata: languageName: node linkType: hard +"os-tmpdir@npm:~1.0.2": + version: 1.0.2 + resolution: "os-tmpdir@npm:1.0.2" + checksum: 5666560f7b9f10182548bf7013883265be33620b1c1b4a4d405c25be2636f970c5488ff3e6c48de75b55d02bde037249fe5dbfbb4c0fb7714953d56aed062e6d + languageName: node + linkType: hard + "p-cancelable@npm:^2.0.0": version: 2.1.1 resolution: "p-cancelable@npm:2.1.1" @@ -14167,6 +14272,31 @@ __metadata: languageName: node linkType: hard +"patch-package@npm:~8.0.0": + version: 8.0.0 + resolution: "patch-package@npm:8.0.0" + dependencies: + "@yarnpkg/lockfile": "npm:^1.1.0" + chalk: "npm:^4.1.2" + ci-info: "npm:^3.7.0" + cross-spawn: "npm:^7.0.3" + find-yarn-workspace-root: "npm:^2.0.0" + fs-extra: "npm:^9.0.0" + json-stable-stringify: "npm:^1.0.2" + klaw-sync: "npm:^6.0.0" + minimist: "npm:^1.2.6" + open: "npm:^7.4.2" + rimraf: "npm:^2.6.3" + semver: "npm:^7.5.3" + slash: "npm:^2.0.0" + tmp: "npm:^0.0.33" + yaml: "npm:^2.2.2" + bin: + patch-package: index.js + checksum: 8714322c35b29266e71c82d58443ce5322400a546a3327f1b8907b8eeb7e366dff33c4fdfbd25e3f0b3a9927189c26e9ac60636ca1e4140d6dbc11cca10f9b5d + languageName: node + linkType: hard + "path-exists@npm:^3.0.0": version: 3.0.0 resolution: "path-exists@npm:3.0.0" @@ -15334,6 +15464,17 @@ __metadata: languageName: node linkType: hard +"rimraf@npm:^2.6.3": + version: 2.7.1 + resolution: "rimraf@npm:2.7.1" + dependencies: + glob: "npm:^7.1.3" + bin: + rimraf: ./bin.js + checksum: 4586c296c736483e297da7cffd19475e4a3e41d07b1ae124aad5d687c79e4ffa716bdac8732ed1db942caf65271cee9dd39f8b639611de161a2753e2112ffe1d + languageName: node + linkType: hard + "rimraf@npm:~5.0.7": version: 5.0.7 resolution: "rimraf@npm:5.0.7" @@ -15377,13 +15518,13 @@ __metadata: "@ewsjs/xhr": "npm:~2.0.2" "@fiahfy/icns-convert": "npm:~0.0.12" "@fiahfy/ico-convert": "npm:~0.0.12" - "@kayahr/jest-electron-runner": "npm:~29.11.0" + "@kayahr/jest-electron-runner": "npm:29.14.0" "@rocket.chat/css-in-js": "npm:~0.31.25" "@rocket.chat/eslint-config": "npm:~0.7.0" - "@rocket.chat/fuselage": "npm:~0.54.2" + "@rocket.chat/fuselage": "npm:0.58.0" "@rocket.chat/fuselage-hooks": "npm:~0.33.1" "@rocket.chat/fuselage-polyfills": "npm:~0.31.25" - "@rocket.chat/icons": "npm:~0.36.0" + "@rocket.chat/icons": "npm:0.37.0" "@rocket.chat/prettier-config": "npm:~0.31.25" "@rollup/plugin-babel": "npm:~6.0.4" "@rollup/plugin-commonjs": "npm:~25.0.7" @@ -15403,7 +15544,7 @@ __metadata: chokidar: "npm:~3.5.3" conventional-changelog-cli: "npm:~4.1.0" convert-svg-to-png: "npm:~0.6.4" - electron: "npm:30.1.2" + electron: "npm:30.4.0" electron-builder: "npm:24.13.3" electron-devtools-installer: "npm:^3.2.0" electron-dl: "npm:3.5.2" @@ -15426,6 +15567,7 @@ __metadata: jsonwebtoken: "npm:~9.0.2" moment: "npm:~2.30.1" npm-run-all: "npm:~4.1.5" + patch-package: "npm:~8.0.0" prettier: "npm:~3.2.5" puppeteer: "npm:23.1.1" react: "npm:~18.2.0" @@ -15884,6 +16026,13 @@ __metadata: languageName: node linkType: hard +"slash@npm:^2.0.0": + version: 2.0.0 + resolution: "slash@npm:2.0.0" + checksum: 512d4350735375bd11647233cb0e2f93beca6f53441015eea241fe784d8068281c3987fbaa93e7ef1c38df68d9c60013045c92837423c69115297d6169aa85e6 + languageName: node + linkType: hard + "slash@npm:^3.0.0": version: 3.0.0 resolution: "slash@npm:3.0.0" @@ -16670,6 +16819,15 @@ __metadata: languageName: node linkType: hard +"tmp@npm:^0.0.33": + version: 0.0.33 + resolution: "tmp@npm:0.0.33" + dependencies: + os-tmpdir: "npm:~1.0.2" + checksum: 09c0abfd165cff29b32be42bc35e80b8c64727d97dedde6550022e88fa9fd39a084660415ed8e3ebaa2aca1ee142f86df8b31d4196d4f81c774a3a20fd4b6abf + languageName: node + linkType: hard + "tmp@npm:^0.2.0, tmp@npm:^0.2.1": version: 0.2.1 resolution: "tmp@npm:0.2.1" @@ -16891,6 +17049,13 @@ __metadata: languageName: node linkType: hard +"tslib@npm:^2.6.3": + version: 2.6.3 + resolution: "tslib@npm:2.6.3" + checksum: 52109bb681f8133a2e58142f11a50e05476de4f075ca906d13b596ae5f7f12d30c482feb0bff167ae01cfc84c5803e575a307d47938999246f5a49d174fc558c + languageName: node + linkType: hard + "tsutils@npm:^3.21.0": version: 3.21.0 resolution: "tsutils@npm:3.21.0" @@ -17400,21 +17565,21 @@ __metadata: languageName: node linkType: hard -"uuid@npm:^8.3.2": - version: 8.3.2 - resolution: "uuid@npm:8.3.2" +"uuid@npm:^10.0.0": + version: 10.0.0 + resolution: "uuid@npm:10.0.0" bin: uuid: dist/bin/uuid - checksum: 9a5f7aa1d6f56dd1e8d5f2478f855f25c645e64e26e347a98e98d95781d5ed20062d6cca2eecb58ba7c84bc3910be95c0451ef4161906abaab44f9cb68ffbdd1 + checksum: 35aa60614811a201ff90f8ca5e9ecb7076a75c3821e17f0f5ff72d44e36c2d35fcbc2ceee9c4ac7317f4cc41895da30e74f3885e30313bee48fda6338f250538 languageName: node linkType: hard -"uuid@npm:^9.0.1": - version: 9.0.1 - resolution: "uuid@npm:9.0.1" +"uuid@npm:^8.3.2": + version: 8.3.2 + resolution: "uuid@npm:8.3.2" bin: uuid: dist/bin/uuid - checksum: 9d0b6adb72b736e36f2b1b53da0d559125ba3e39d913b6072f6f033e0c87835b414f0836b45bcfaf2bdf698f92297fea1c3cc19b0b258bc182c9c43cc0fab9f2 + checksum: 9a5f7aa1d6f56dd1e8d5f2478f855f25c645e64e26e347a98e98d95781d5ed20062d6cca2eecb58ba7c84bc3910be95c0451ef4161906abaab44f9cb68ffbdd1 languageName: node linkType: hard @@ -17857,6 +18022,15 @@ __metadata: languageName: node linkType: hard +"yaml@npm:^2.2.2": + version: 2.5.0 + resolution: "yaml@npm:2.5.0" + bin: + yaml: bin.mjs + checksum: 72e903fdbe3742058885205db4a6c9ff38e5f497f4e05e631264f7756083c05e7d10dfb5e4ce9d7a95de95338f9b20d19dd0b91c60c65f7d7608b6b3929820ad + languageName: node + linkType: hard + "yargs-parser@npm:^20.2.2, yargs-parser@npm:^20.2.3, yargs-parser@npm:^20.2.9": version: 20.2.9 resolution: "yargs-parser@npm:20.2.9"