diff --git a/apps/server-web/src/locales/i18n/bg/translation.json b/apps/server-web/src/locales/i18n/bg/translation.json index a9ba889cc..bcec1eff5 100644 --- a/apps/server-web/src/locales/i18n/bg/translation.json +++ b/apps/server-web/src/locales/i18n/bg/translation.json @@ -14,6 +14,21 @@ "OPEN_WEB": "Отворете уеб в браузъра", "SERVER_WINDOW": "Прозорец на сървъра" }, + "MENU_APP": { + "ABOUT": "Относно", + "QUIT": "Изход", + "WINDOW": "Прозорец", + "SUBMENU": { + "SETTING": "Настройки", + "SERVER_WINDOW": "Сървърен прозорец", + "LEARN_MORE": "Научете повече", + "DOC": "Документация", + "SETTING_DEV": "Настройки за разработчици", + "SERVER_DEV": "Сървър за разработчици" + }, + "DEV": "Разработчик", + "HELP": "Помощ" + }, "FORM": { "FIELDS": { "PORT": "ПРИСТАНИЩЕ", diff --git a/apps/server-web/src/locales/i18n/en/translation.json b/apps/server-web/src/locales/i18n/en/translation.json index 48320d87b..c18265dde 100644 --- a/apps/server-web/src/locales/i18n/en/translation.json +++ b/apps/server-web/src/locales/i18n/en/translation.json @@ -14,6 +14,21 @@ "OPEN_WEB": "Open Web In Browser", "SERVER_WINDOW": "Server Window" }, + "MENU_APP": { + "ABOUT": "About", + "QUIT": "Quit", + "WINDOW": "Window", + "SUBMENU": { + "SETTING": "Setting", + "SERVER_WINDOW": "Server Window", + "LEARN_MORE": "Learn More", + "DOC": "Documentation", + "SETTING_DEV": "Setting Dev.", + "SERVER_DEV": "Server Dev." + }, + "DEV": "Developer", + "HELP": "Help" + }, "FORM": { "FIELDS": { "PORT": "PORT", diff --git a/apps/server-web/src/main/helpers/services/libs/desktop-store.ts b/apps/server-web/src/main/helpers/services/libs/desktop-store.ts index 9db836cd9..01c708563 100644 --- a/apps/server-web/src/main/helpers/services/libs/desktop-store.ts +++ b/apps/server-web/src/main/helpers/services/libs/desktop-store.ts @@ -1,6 +1,19 @@ import Store from 'electron-store'; import { WebServer } from '../../interfaces'; const store = new Store(); +const DEFAULT_CONFIG:any = { + server: { + PORT: 3002, + GAUZY_API_SERVER_URL: 'http://localhost:3000', + NEXT_PUBLIC_GAUZY_API_SERVER_URL: 'http://localhost:3000', + DESKTOP_WEB_SERVER_HOSTNAME: '0.0.0.0' + }, + general: { + lang: 'en', + autoUpdate: true, + updateCheckPeriode: '1140' + } +} export const LocalStore = { getStore: (source: string | 'config'): WebServer | any => { return store.get(source); @@ -24,22 +37,15 @@ export const LocalStore = { setDefaultServerConfig: () => { - const defaultConfig: WebServer | any = store.get('config'); - if (!defaultConfig || !defaultConfig.server || !defaultConfig.general) { - const config: WebServer = { - server: { - PORT: 3002, - GAUZY_API_SERVER_URL: 'http://localhost:3000', - NEXT_PUBLIC_GAUZY_API_SERVER_URL: 'http://localhost:3000', - DESKTOP_WEB_SERVER_HOSTNAME: '0.0.0.0' - }, - general: { - lang: 'en', - autoUpdate: true, - updateCheckPeriode: '30' - } - } - store.set({ config }); - } + const defaultConfig: WebServer | any = store.get('config') || {}; + Object.keys(DEFAULT_CONFIG).forEach((key) => { + Object.keys(DEFAULT_CONFIG[key]).forEach((keySub) => { + defaultConfig[key] = defaultConfig[key] || {}; + defaultConfig[key][keySub] = defaultConfig[key][keySub] || DEFAULT_CONFIG[key][keySub]; + }) + }) + store.set({ + config: defaultConfig + }); } }; diff --git a/apps/server-web/src/main/main.ts b/apps/server-web/src/main/main.ts index f88b5cf7b..cba85f718 100644 --- a/apps/server-web/src/main/main.ts +++ b/apps/server-web/src/main/main.ts @@ -1,5 +1,5 @@ import path from 'path'; -import { app, ipcMain, Tray, dialog, BrowserWindow, shell } from 'electron'; +import { app, ipcMain, Tray, dialog, BrowserWindow, shell, Menu } from 'electron'; import { DesktopServer } from './helpers/desktop-server'; import { LocalStore } from './helpers/services/libs/desktop-store'; import { EventEmitter } from 'events'; @@ -23,8 +23,6 @@ Object.assign(console, Log.functions); app.name = config.DESCRIPTION; - - const eventEmitter = new EventEmitter(); const controller = new AbortController(); @@ -43,6 +41,7 @@ let logWindow: BrowserWindow | null = null; let setupWindow: BrowserWindow | any = null; let SettingMenu: any = null; let ServerWindowMenu: any = null; +const appMenu = new MenuBuilder(eventEmitter) Log.hooks.push((message: any, transport) => { if (transport !== Log.transports.file) { @@ -93,6 +92,7 @@ i18nextMainBackend.on('initialized', () => { }); let trayMenuItems: any = []; +let appMenuItems: any = []; const RESOURCES_PATH = app.isPackaged ? path.join(process.resourcesPath, 'assets') @@ -182,10 +182,7 @@ const createWindow = async (type: 'SETTING_WINDOW' | 'LOG_WINDOW' | 'SETUP_WINDO settingWindow = null; SettingMenu = null }); - if (!SettingMenu) { - SettingMenu = new MenuBuilder(settingWindow); - } - SettingMenu.buildMenu(); + Menu.setApplicationMenu(appMenu.buildDefaultTemplate(appMenuItems, i18nextMainBackend)) break; case 'LOG_WINDOW': logWindow = new BrowserWindow(defaultOptionWindow); @@ -196,10 +193,7 @@ const createWindow = async (type: 'SETTING_WINDOW' | 'LOG_WINDOW' | 'SETUP_WINDO logWindow = null; ServerWindowMenu = null }) - if (!ServerWindowMenu) { - ServerWindowMenu = new MenuBuilder(logWindow); - } - ServerWindowMenu.buildMenu(); + Menu.setApplicationMenu(appMenu.buildDefaultTemplate(appMenuItems, i18nextMainBackend)) break; case 'SETUP_WINDOW': setupWindow = new BrowserWindow(defaultOptionWindow); @@ -218,7 +212,7 @@ const createWindow = async (type: 'SETTING_WINDOW' | 'LOG_WINDOW' | 'SETUP_WINDO const runServer = async () => { console.log('Run the Server...'); try { - const envVal = getEnvApi(); + const envVal: any = getEnvApi(); // Instantiate API and UI servers await desktopServer.start( @@ -262,14 +256,18 @@ const SendMessageToSettingWindow = (type: string, data: any) => { } const onInitApplication = () => { - LocalStore.setDefaultServerConfig(); // check and set default config + // check and set default config + LocalStore.setDefaultServerConfig(); createIntervalAutoUpdate() trayMenuItems = trayMenuItems.length ? trayMenuItems : defaultTrayMenuItem(eventEmitter); + appMenuItems = appMenuItems.length ? appMenuItems : appMenu.defaultMenu(); tray = _initTray(trayMenuItems, getAssetPath('icons/icon.png')); i18nextMainBackend.on('languageChanged', (lng) => { if (i18nextMainBackend.isInitialized) { + trayMenuItems = trayMenuItems.length ? trayMenuItems : defaultTrayMenuItem(eventEmitter); updateTrayMenu('none', {}, eventEmitter, tray, trayMenuItems, i18nextMainBackend); + Menu.setApplicationMenu(appMenu.buildDefaultTemplate(appMenuItems, i18nextMainBackend)) } }); eventEmitter.on(EventLists.webServerStart, async () => { @@ -425,6 +423,16 @@ const onInitApplication = () => { const url = `http://127.0.0.1:${envConfig?.PORT}` shell.openExternal(url) }) + + eventEmitter.on(EventLists.SETTING_WINDOW_DEV, () => { + settingWindow?.webContents.toggleDevTools(); + }) + + eventEmitter.on(EventLists.SERVER_WINDOW_DEV, () => { + logWindow?.webContents.toggleDevTools(); + }) + + eventEmitter.emit(EventLists.SERVER_WINDOW); } (async () => { @@ -452,7 +460,6 @@ ipcMain.on('message', async (event, arg) => { }) ipcMain.on(IPC_TYPES.SETTING_PAGE, async (event, arg) => { - console.log('main setting page', arg); switch (arg.type) { case SettingPageTypeMessage.saveSetting: const existingConfig = getEnvApi(); @@ -512,13 +519,6 @@ ipcMain.on(IPC_TYPES.SETTING_PAGE, async (event, arg) => { case SettingPageTypeMessage.themeChange: eventEmitter.emit(EventLists.CHANGE_THEME, arg.data) break; - default: - break; - } -}) - -ipcMain.on(IPC_TYPES.UPDATER_PAGE, (event, arg) => { - switch (arg.type) { case SettingPageTypeMessage.updateSetting: LocalStore.updateConfigSetting({ general: { @@ -529,7 +529,6 @@ ipcMain.on(IPC_TYPES.UPDATER_PAGE, (event, arg) => { createIntervalAutoUpdate() event.sender.send(IPC_TYPES.UPDATER_PAGE, { type: SettingPageTypeMessage.updateSettingResponse, data: true }) break; - default: break; } diff --git a/apps/server-web/src/main/main_.ts b/apps/server-web/src/main/main_.ts deleted file mode 100644 index 10c8220ed..000000000 --- a/apps/server-web/src/main/main_.ts +++ /dev/null @@ -1,137 +0,0 @@ -/* eslint global-require: off, no-console: off, promise/always-return: off */ - -/** - * This module executes inside of electron's main process. You can start - * electron renderer process from here and communicate with the other processes - * through IPC. - * - * When running `npm run build` or `npm run build:main`, this file is compiled to - * `./src/main.js` using webpack. This gives us some performance wins. - */ -import path from 'path'; -import { app, BrowserWindow, shell, ipcMain } from 'electron'; -import { autoUpdater } from 'electron-updater'; -import log from 'electron-log'; -import MenuBuilder from './menu'; -import { resolveHtmlPath } from './util'; - -class AppUpdater { - constructor() { - log.transports.file.level = 'info'; - autoUpdater.logger = log; - autoUpdater.checkForUpdatesAndNotify(); - } -} - -let mainWindow: BrowserWindow | null = null; - -ipcMain.on('ipc-example', async (event, arg) => { - const msgTemplate = (pingPong: string) => `IPC test: ${pingPong}`; - console.log(msgTemplate(arg)); - event.reply('ipc-example', msgTemplate('pong')); -}); - -if (process.env.NODE_ENV === 'production') { - const sourceMapSupport = require('source-map-support'); - sourceMapSupport.install(); -} - -const isDebug = - process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD === 'true'; - -if (isDebug) { - require('electron-debug')(); -} - -const installExtensions = async () => { - const installer = require('electron-devtools-installer'); - const forceDownload = !!process.env.UPGRADE_EXTENSIONS; - const extensions = ['REACT_DEVELOPER_TOOLS']; - - return installer - .default( - extensions.map((name) => installer[name]), - forceDownload, - ) - .catch(console.log); -}; - -const createWindow = async () => { - if (isDebug) { - await installExtensions(); - } - - const RESOURCES_PATH = app.isPackaged - ? path.join(process.resourcesPath, 'assets') - : path.join(__dirname, '../../assets'); - - const getAssetPath = (...paths: string[]): string => { - return path.join(RESOURCES_PATH, ...paths); - }; - - mainWindow = new BrowserWindow({ - show: false, - width: 1024, - height: 728, - icon: getAssetPath('icon.png'), - webPreferences: { - preload: app.isPackaged - ? path.join(__dirname, 'preload.js') - : path.join(__dirname, '../../.erb/dll/preload.js'), - }, - }); - - mainWindow.loadURL(resolveHtmlPath('index.html')); - - mainWindow.on('ready-to-show', () => { - if (!mainWindow) { - throw new Error('"mainWindow" is not defined'); - } - if (process.env.START_MINIMIZED) { - mainWindow.minimize(); - } else { - mainWindow.show(); - } - }); - - mainWindow.on('closed', () => { - mainWindow = null; - }); - - const menuBuilder = new MenuBuilder(mainWindow); - menuBuilder.buildMenu(); - - // Open urls in the user's browser - mainWindow.webContents.setWindowOpenHandler((eData) => { - shell.openExternal(eData.url); - return { action: 'deny' }; - }); - - // Remove this if your app does not use auto updates - // eslint-disable-next-line - new AppUpdater(); -}; - -/** - * Add event listeners... - */ - -app.on('window-all-closed', () => { - // Respect the OSX convention of having the application in memory even - // after all windows have been closed - if (process.platform !== 'darwin') { - app.quit(); - } -}); - -app - .whenReady() - .then(() => { - createWindow(); - app.on('activate', () => { - // On macOS it's common to re-create a window in the app when the - // dock icon is clicked and there are no other windows open. - if (mainWindow === null) createWindow(); - }); - }) - .catch(console.log); diff --git a/apps/server-web/src/main/menu.ts b/apps/server-web/src/main/menu.ts index be4348ceb..cec72f148 100644 --- a/apps/server-web/src/main/menu.ts +++ b/apps/server-web/src/main/menu.ts @@ -2,216 +2,79 @@ import { app, Menu, shell, - BrowserWindow, - MenuItemConstructorOptions, } from 'electron'; import { config } from '../configs/config'; - -interface DarwinMenuItemConstructorOptions extends MenuItemConstructorOptions { - selector?: string; - submenu?: DarwinMenuItemConstructorOptions[] | Menu; -} +import { EventEmitter } from 'events'; +import { EventLists } from './helpers/constant'; +import i18n from 'i18next'; export default class MenuBuilder { - mainWindow: BrowserWindow; - - constructor(mainWindow: BrowserWindow) { - this.mainWindow = mainWindow; - } - - buildMenu(): Menu { - if ( - process.env.NODE_ENV === 'development' || - process.env.DEBUG_PROD === 'true' - ) { - this.setupDevelopmentEnvironment(); - } - - const template = - process.platform === 'darwin' - ? this.buildDarwinTemplate() - : this.buildDefaultTemplate(); - - const menu = Menu.buildFromTemplate(template); - Menu.setApplicationMenu(menu); - - return menu; - } - - setupDevelopmentEnvironment(): void { - this.mainWindow.webContents.on('context-menu', (_, props) => { - const { x, y } = props; - - Menu.buildFromTemplate([ - { - label: 'Inspect element', - click: () => { - this.mainWindow.webContents.inspectElement(x, y); - }, - }, - ]).popup({ window: this.mainWindow }); - }); - } + eventEmitter: EventEmitter - buildDarwinTemplate(): MenuItemConstructorOptions[] { - const subMenuAbout: DarwinMenuItemConstructorOptions = { - label: config.DESCRIPTION || app.getName(), - submenu: [ - { - label: `About ${config.DESCRIPTION || app.getName()}`, - selector: 'orderFrontStandardAboutPanel:', - }, - { type: 'separator' }, - { - label: 'Quit', - accelerator: 'Command+Q', - click: () => { - app.quit(); - }, - }, - ], - }; - const subMenuViewDev: MenuItemConstructorOptions = { - label: 'View', - submenu: [ - { - label: 'Reload', - accelerator: 'Command+R', - click: () => { - this.mainWindow.webContents.reload(); - }, - }, - { - label: 'Toggle Developer Tools', - accelerator: 'Alt+Command+I', - click: () => { - this.mainWindow.webContents.toggleDevTools(); - }, - }, - ], - }; - const subMenuViewProd: MenuItemConstructorOptions = { - label: 'View', - submenu: [ - { - label: 'Reload', - accelerator: 'Command+R', - click: () => { - this.mainWindow.webContents.reload(); - }, - }, - { - label: 'Toggle Developer Tools', - accelerator: 'Alt+Command+I', - click: () => { - this.mainWindow.webContents.toggleDevTools(); - }, - }, - ], - }; - const subMenuWindow: DarwinMenuItemConstructorOptions = { - label: 'Window', - submenu: [ - { - label: 'Minimize', - accelerator: 'Command+M', - selector: 'performMiniaturize:', - }, - { label: 'Close', accelerator: 'Command+W', selector: 'performClose:' }, - { type: 'separator' }, - ], - }; - const subMenuHelp: MenuItemConstructorOptions = { - label: 'Help', - submenu: [ - { - label: 'Learn More', - click() { - shell.openExternal('https://ever.team/'); - }, - }, - { - label: 'Documentation', - click() { - shell.openExternal( - 'https://github.com/ever-co/ever-teams/blob/develop/README.md', - ); - }, - }, - { - label: 'Search Issues', - click() { - shell.openExternal('https://github.com/ever-co/ever-teams/issues'); - }, - }, - ], - }; - - const subMenuView = - process.env.NODE_ENV === 'development' || - process.env.DEBUG_PROD === 'true' - ? subMenuViewDev - : subMenuViewProd; - - return [subMenuAbout, subMenuView, subMenuWindow, subMenuHelp]; + constructor(eventEmitter: EventEmitter) { + this.eventEmitter = eventEmitter } - buildDefaultTemplate() { - const templateDefault = [ + defaultMenu() { + const isDarwin = process.platform === 'darwin'; + return [ { - label: '&File', + id: 'MENU_APP', + label: config.DESCRIPTION || app.getName(), submenu: [ { - label: '&Close', - accelerator: 'Ctrl+W', + id: 'MENU_APP_ABOUT', + label: `MENU_APP.ABOUT`, + selector: 'orderFrontStandardAboutPanel:', + click: () => { + this.eventEmitter.emit(EventLists.gotoAbout) + } + }, + { type: 'separator' }, + { + id: 'MENU_APP_QUIT', + label: 'MENU_APP.QUIT', + accelerator: isDarwin ? 'Command+Q' : 'Alt+F4', click: () => { - this.mainWindow.close(); + app.quit(); }, }, ], }, { - label: '&View', - submenu: - process.env.NODE_ENV === 'development' || - process.env.DEBUG_PROD === 'true' - ? [ - { - label: '&Reload', - accelerator: 'Ctrl+R', - click: () => { - this.mainWindow.webContents.reload(); - }, - }, - { - label: 'Toggle &Developer Tools', - accelerator: 'Alt+Ctrl+I', - click: () => { - this.mainWindow.webContents.toggleDevTools(); - }, - }, - ] - : [ - { - label: 'Toggle &Developer Tools', - accelerator: 'Alt+Ctrl+I', - click: () => { - this.mainWindow.webContents.toggleDevTools(); - }, - }, - ], + id: 'MENU_APP_WINDOW', + label: 'MENU_APP.WINDOW', + submenu: [ + { + id: 'SUBMENU_SETTING', + label: 'MENU_APP.SUBMENU.SETTING', + click: () => { + this.eventEmitter.emit(EventLists.gotoSetting); + } + }, + { + id: 'SUBMENU_SERVER', + label: 'MENU_APP.SUBMENU.SERVER_WINDOW', + click: () => { + this.eventEmitter.emit(EventLists.SERVER_WINDOW); + } + } + ] }, { - label: 'Help', + id: 'MENU_APP_HELP', + label: 'MENU_APP.HELP', submenu: [ { - label: 'Learn More', + id: 'SUBMENU_LEARN_MORE', + label: 'MENU_APP.SUBMENU.LEARN_MORE', click() { shell.openExternal(config.COMPANY_SITE_LINK); }, }, { - label: 'Documentation', + id: 'SUBMENU_DOC', + label: 'MENU_APP.SUBMENU.DOC', click() { shell.openExternal( config.COMPANY_GITHUB_LINK @@ -220,8 +83,61 @@ export default class MenuBuilder { }, ], }, - ]; + { + id: 'MENU_APP_DEV', + label: 'MENU_APP.DEV', + submenu: [ + { + id: 'SUBMENU_SETTING_DEV', + label: 'MENU_APP.SUBMENU.SETTING_DEV', + click: () => { + this.eventEmitter.emit(EventLists.SETTING_WINDOW_DEV); + }, + }, + { + id: 'SUBMENU_SERVER_DEV', + label: 'MENU_APP.SUBMENU.SERVER_DEV', + click: () => { + this.eventEmitter.emit(EventLists.SERVER_WINDOW_DEV); + }, + }, + ] + }, + ] + } - return templateDefault; + buildDefaultTemplate(menuItems: any, i18nextMainBackend: typeof i18n) { + return Menu.buildFromTemplate(this.translateAppMenu(i18nextMainBackend, menuItems)); } + + updateAppMenu(menuItem: string, context: { label?: string, enabled?: boolean}, contextMenuItems: any, i18nextMainBackend: typeof i18n) { + const menuIdx:number = contextMenuItems.findIndex((item: any) => item.id === menuItem); + if (menuIdx > -1) { + contextMenuItems[menuIdx] = {...contextMenuItems[menuIdx], ...context}; + const newMenu = [...contextMenuItems]; + Menu.setApplicationMenu(Menu.buildFromTemplate(this.translateAppMenu(i18nextMainBackend, newMenu))); + } else { + const newMenu = [...contextMenuItems]; + Menu.setApplicationMenu(Menu.buildFromTemplate(this.translateAppMenu(i18nextMainBackend, newMenu))) + } +} + +translateAppMenu(i18nextMainBackend: typeof i18n, contextMenu: any) { + return contextMenu.map((menu: any) => { + const menuCopied = {...menu}; + if (menuCopied.label) { + menuCopied.label = i18nextMainBackend.t(menuCopied.label); + } + if (menuCopied.submenu && menuCopied.submenu.length) { + menuCopied.submenu = menuCopied.submenu.map((sm: any) => { + const submenu = {...sm}; + if (submenu.label) { + submenu.label = i18nextMainBackend.t(submenu.label) + } + return submenu; + }) + } + return menuCopied; + }) +} } diff --git a/apps/server-web/src/renderer/components/svgs/EverTeamsLogo.tsx b/apps/server-web/src/renderer/components/svgs/EverTeamsLogo.tsx index 2a123bf40..3df194cda 100644 --- a/apps/server-web/src/renderer/components/svgs/EverTeamsLogo.tsx +++ b/apps/server-web/src/renderer/components/svgs/EverTeamsLogo.tsx @@ -1,6 +1,12 @@ import Logo from '../../../resources/icons/platform-logo.png'; export const EverTeamsLogo = () => { return ( - EverTeams Logo + EverTeams Logo ); }; diff --git a/apps/web/app/constants.ts b/apps/web/app/constants.ts index db8321a56..e2a2e0564 100644 --- a/apps/web/app/constants.ts +++ b/apps/web/app/constants.ts @@ -317,6 +317,8 @@ export const SLACK_CLIENT_SECRET = process.env.SLACK_CLIENT_SECRET; export const TWITTER_CLIENT_ID = process.env.TWITTER_CLIENT_ID; export const TWITTER_CLIENT_SECRET = process.env.TWITTER_CLIENT_SECRET; +export const IS_DESKTOP_APP = process.env.IS_DESKTOP_APP === 'true'; + // Add manual timer reason export const manualTimeReasons: ManualTimeReasons[] = [ diff --git a/apps/web/auth.ts b/apps/web/auth.ts index 56e936665..36430b60a 100644 --- a/apps/web/auth.ts +++ b/apps/web/auth.ts @@ -2,9 +2,11 @@ import NextAuth from 'next-auth'; import { filteredProviders } from '@app/utils/check-provider-env-vars'; import { GauzyAdapter, jwtCallback, ProviderEnum, signInCallback } from '@app/services/server/requests/OAuth'; import { NextRequest } from 'next/server'; +import { IS_DESKTOP_APP } from '@app/constants'; export const { handlers, signIn, signOut, auth } = NextAuth((request) => ({ providers: filteredProviders, + trustHost: IS_DESKTOP_APP, adapter: GauzyAdapter(request as NextRequest), session: { strategy: 'jwt' }, callbacks: {