diff --git a/.scripts/configure.electron.ts b/.scripts/configure.electron.ts new file mode 100644 index 000000000..fb2f5256b --- /dev/null +++ b/.scripts/configure.electron.ts @@ -0,0 +1,77 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import { hideBin } from 'yargs/helpers'; +import yargs from 'yargs'; + +interface Arguments { + type: 'server' | 'constant' +} +const argv = yargs(hideBin(process.argv)) + .options({ + type: { + type: 'string', + choices: ['server', 'constant'], + demandOption: true, + description: 'Type of configuration to modify' + } + }) + .parseSync() as Arguments; + +function modifiedNextServer() { + const filePath = path.resolve(__dirname, '../apps/server-web/release/app/dist/standalone/apps/web/server.js'); + try { + let fileContent = fs.readFileSync(filePath, 'utf8'); + const searchString = 'process.env.__NEXT_PRIVATE_STANDALONE_CONFIG'; + const codeToInsert = ` + nextConfig.serverRuntimeConfig = { + "GAUZY_API_SERVER_URL": process.env.GAUZY_API_SERVER_URL, + "NEXT_PUBLIC_GAUZY_API_SERVER_URL": process.env.NEXT_PUBLIC_GAUZY_API_SERVER_URL + } + `; + + let lines = fileContent.split('\n'); + const index = lines.findIndex((line) => line.includes(searchString)); + + if (index !== -1) { + lines.splice(index - 1, 0, codeToInsert); + + fileContent = lines.join('\n'); + fs.writeFileSync(filePath, fileContent, 'utf8'); + console.log('Line of code successfully inserted.'); + } else { + console.log(`The string "${searchString}" was not found in the file.`); + } + } catch (error) { + console.error('Failed to change static server configuration'); + } +} + +function updateWebConstant(setDesktopApp) { + const filePath = path.resolve(__dirname, '../apps/web/app/constants.ts'); + try { + let fileContent = fs.readFileSync(filePath, 'utf8'); + const envCheck = `export const IS_DESKTOP_APP = process.env.IS_DESKTOP_APP === 'true';`; + const hardcoded = `export const IS_DESKTOP_APP = true;`; + + const [from, to] = setDesktopApp ? [envCheck, hardcoded] : [hardcoded, envCheck]; + + if (!fileContent.includes(from)) { + throw new Error(`Expected content not found in ${filePath}`); + } + + fileContent = fileContent.replace(from, to); + fs.writeFileSync(filePath, fileContent, 'utf8'); + console.log(`Successfully ${setDesktopApp ? 'set' : 'reverted'} IS_DESKTOP_APP`); + } catch (error) { + console.error('Failed to update constants:', error); + process.exit(1); + } +} + + +if (argv.type === 'server') { + modifiedNextServer(); + updateWebConstant(false); +} else if (argv.type === 'constant') { + updateWebConstant(true); +} diff --git a/.scripts/copy-web-build.ts b/.scripts/copy-web-build.ts new file mode 100644 index 000000000..d9291be66 --- /dev/null +++ b/.scripts/copy-web-build.ts @@ -0,0 +1,34 @@ +import { copy } from 'fs-extra'; +import { join } from 'path'; + +async function copyWebBuild() { + const webDir = join(process.cwd(), 'apps/web'); + const distDir = join(process.cwd(), 'apps/server-web/release/app/dist'); + + try { + // Copy standalone build + await copy( + join(webDir, '.next/standalone'), + join(distDir, 'standalone') + ); + + // Copy static files + await copy( + join(webDir, '.next/static'), + join(distDir, 'standalone/apps/web/.next/static') + ); + + // Copy public files + await copy( + join(webDir, 'public'), + join(distDir, 'standalone/apps/web/public') + ); + + console.log('Build files copied successfully'); + } catch (error) { + console.error('Failed to copy build files:', error); + process.exit(1); + } +} + +copyWebBuild(); diff --git a/apps/server-web/src/locales/i18n/bg/translation.json b/apps/server-web/src/locales/i18n/bg/translation.json index bcec1eff5..28ca92eeb 100644 --- a/apps/server-web/src/locales/i18n/bg/translation.json +++ b/apps/server-web/src/locales/i18n/bg/translation.json @@ -15,19 +15,19 @@ "SERVER_WINDOW": "Прозорец на сървъра" }, "MENU_APP": { - "ABOUT": "Относно", - "QUIT": "Изход", - "WINDOW": "Прозорец", - "SUBMENU": { - "SETTING": "Настройки", - "SERVER_WINDOW": "Сървърен прозорец", - "LEARN_MORE": "Научете повече", - "DOC": "Документация", - "SETTING_DEV": "Настройки за разработчици", - "SERVER_DEV": "Сървър за разработчици" + "APP_ABOUT": "Относно", + "APP_QUIT": "Изход", + "APP_WINDOW": "Прозорец", + "APP_SUBMENU": { + "APP_SETTING": "Настройки", + "APP_SERVER_WINDOW": "Сървърен прозорец", + "APP_LEARN_MORE": "Научете повече", + "APP_DOC": "Документация", + "APP_SETTING_DEV": "Настройки за разработчици", + "APP_SERVER_DEV": "Сървър за разработчици" }, - "DEV": "Разработчик", - "HELP": "Помощ" + "APP_DEV": "Разработчик", + "APP_HELP": "Помощ" }, "FORM": { "FIELDS": { diff --git a/apps/server-web/src/locales/i18n/en/translation.json b/apps/server-web/src/locales/i18n/en/translation.json index c18265dde..440e56bec 100644 --- a/apps/server-web/src/locales/i18n/en/translation.json +++ b/apps/server-web/src/locales/i18n/en/translation.json @@ -15,19 +15,19 @@ "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." + "APP_ABOUT": "About", + "APP_QUIT": "Quit", + "APP_WINDOW": "Window", + "APP_SUBMENU": { + "APP_SETTING": "Setting", + "APP_SERVER_WINDOW": "Server Window", + "APP_LEARN_MORE": "Learn More", + "APP_DOC": "Documentation", + "APP_SETTING_DEV": "Setting Dev.", + "APP_SERVER_DEV": "Server Dev." }, - "DEV": "Developer", - "HELP": "Help" + "APP_DEV": "Developer", + "APP_HELP": "Help" }, "FORM": { "FIELDS": { diff --git a/apps/server-web/src/main/helpers/constant.ts b/apps/server-web/src/main/helpers/constant.ts index b2ebfcb96..bcd77553f 100644 --- a/apps/server-web/src/main/helpers/constant.ts +++ b/apps/server-web/src/main/helpers/constant.ts @@ -17,7 +17,9 @@ export const EventLists = { SERVER_WINDOW: 'SERVER_WINDOW', RESTART_SERVER: 'RESTART_SERVER', CHANGE_THEME: 'CHANGE_THEME', - SETUP_WINDOW: 'SETUP_WINDOW' + SETUP_WINDOW: 'SETUP_WINDOW', + SETTING_WINDOW_DEV: 'SETTING_WINDOW_DEV', + SERVER_WINDOW_DEV: 'SERVER_WINDOW_DEV' } export const SettingPageTypeMessage = { diff --git a/apps/server-web/src/main/helpers/interfaces/i-constant.ts b/apps/server-web/src/main/helpers/interfaces/i-constant.ts index 421769ce3..e8e439af1 100644 --- a/apps/server-web/src/main/helpers/interfaces/i-constant.ts +++ b/apps/server-web/src/main/helpers/interfaces/i-constant.ts @@ -1 +1 @@ -export type Channels = 'setting-page' | 'ipc-renderer' | 'language-set' | 'updater-page' | 'server-page' | 'theme-change' | 'current-theme'; +export type Channels = 'setting-page' | 'ipc-renderer' | 'language-set' | 'updater-page' | 'server-page' | 'theme-change' | 'current-theme' | 'current-language'; diff --git a/apps/server-web/src/main/helpers/interfaces/i-menu.ts b/apps/server-web/src/main/helpers/interfaces/i-menu.ts new file mode 100644 index 000000000..158bc6011 --- /dev/null +++ b/apps/server-web/src/main/helpers/interfaces/i-menu.ts @@ -0,0 +1,15 @@ +import { MenuItemConstructorOptions } from 'electron'; +export interface AppMenu { + id?: string; + label: string; // Menu label + submenu?: (AppMenu | ElectronMenuItem)[]; // Nested menus or Electron menu items + role?: 'appMenu' | 'fileMenu' | 'editMenu' | 'viewMenu' | 'windowMenu' | 'help'; // Predefined menu roles + type?: 'normal' | 'separator' | 'submenu' | 'checkbox' | 'radio'; // Menu item type + click?: () => void; // Click handler for the menu item + accelerator?: string; // Keyboard shortcut + enabled?: boolean; // Whether the item is enabled + visible?: boolean; // Whether the item is visible + checked?: boolean; // For 'checkbox' or 'radio' type + } + + export type ElectronMenuItem = MenuItemConstructorOptions; diff --git a/apps/server-web/src/main/helpers/interfaces/i-server.ts b/apps/server-web/src/main/helpers/interfaces/i-server.ts index 43f188166..146e8ea31 100644 --- a/apps/server-web/src/main/helpers/interfaces/i-server.ts +++ b/apps/server-web/src/main/helpers/interfaces/i-server.ts @@ -1,13 +1,13 @@ -interface GeneralConfig { +export interface GeneralConfig { lang?: string autoUpdate?: boolean - updateCheckPeriode?: string + updateCheckPeriod?: string theme?: string setup?: boolean [key: string]: any } -interface ServerConfig { +export interface ServerConfig { PORT: number; NEXT_PUBLIC_GAUZY_API_SERVER_URL: string; GAUZY_API_SERVER_URL: string; diff --git a/apps/server-web/src/main/helpers/interfaces/index.ts b/apps/server-web/src/main/helpers/interfaces/index.ts index 8f45b8de0..de47665c2 100644 --- a/apps/server-web/src/main/helpers/interfaces/index.ts +++ b/apps/server-web/src/main/helpers/interfaces/index.ts @@ -1,3 +1,4 @@ export * from './i-server'; export * from './i-desktop-dialog'; export * from './i-constant'; +export * from './i-menu'; diff --git a/apps/server-web/src/main/helpers/replace-config.ts b/apps/server-web/src/main/helpers/replace-config.ts index 98e12f9af..5e80d8730 100644 --- a/apps/server-web/src/main/helpers/replace-config.ts +++ b/apps/server-web/src/main/helpers/replace-config.ts @@ -42,3 +42,27 @@ export const replaceConfig = async (folderPath: string, envOptions: EnvOptions) console.log('error on replacing file', error); } } + +export const clearDesktopConfig = async (folderPath: string): Promise => { + if (!folderPath || typeof folderPath !== 'string') { + throw new Error('Invalid folder path provided'); + } + const DESKTOP_CONFIG_FILES = ['desktop-server.body', 'desktop-server.meta'] as const; + try { + // remove cached desktop server config + await Promise.all( + DESKTOP_CONFIG_FILES.map(async (file) => { + const filePath = path.join(folderPath, file); + try { + await fs.promises.unlink(filePath) + } catch (error: any) { + console.log('error unlink static web file', error.message) + } + }) + ) + return true; + } catch (error) { + console.log('Failed to clear static config'); + return false; + } +} diff --git a/apps/server-web/src/main/helpers/services/desktop-server-factory.ts b/apps/server-web/src/main/helpers/services/desktop-server-factory.ts index 1a9502238..8206cc2a9 100644 --- a/apps/server-web/src/main/helpers/services/desktop-server-factory.ts +++ b/apps/server-web/src/main/helpers/services/desktop-server-factory.ts @@ -8,6 +8,7 @@ export class DesktopServerFactory { if (!this.apiInstance && !!env) { this.apiInstance = new WebService(path, env, win, signal, eventEmitter); } + this.apiInstance.updateEnv(env) return this.apiInstance; } } 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 01c708563..1875ee9c2 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,7 +1,7 @@ import Store from 'electron-store'; -import { WebServer } from '../../interfaces'; +import { WebServer, ServerConfig } from '../../interfaces'; const store = new Store(); -const DEFAULT_CONFIG:any = { +const DEFAULT_CONFIG:WebServer = { server: { PORT: 3002, GAUZY_API_SERVER_URL: 'http://localhost:3000', @@ -11,7 +11,7 @@ const DEFAULT_CONFIG:any = { general: { lang: 'en', autoUpdate: true, - updateCheckPeriode: '1140' + updateCheckPeriod: '1140' // Time in minutes } } export const LocalStore = { @@ -35,17 +35,41 @@ export const LocalStore = { }); }, + deepMerge(target: T, source: Partial): T { + const result: T = { ...target }; + Object.keys(source).forEach(key => { + const value = source[key as keyof T]; + if (value && typeof value === 'object') { + result[key as keyof T] = this.deepMerge( + target[key as keyof T], + value as any + ); + } else if (value !== undefined) { + result[key as keyof T] = value as any; + } + }); + return result; + }, - setDefaultServerConfig: () => { - 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 + validateConfig(config: WebServer): void { + const required = ['PORT', 'GAUZY_API_SERVER_URL']; + required.forEach(field => { + if (!config || !config.server || !config?.server[field as keyof ServerConfig]) { + throw new Error(`Missing required field: ${field}`); + } }); + }, + + setDefaultServerConfig() { + try { + const storedConfig = store.get('config') as Partial || {}; + const mergedConfig = this.deepMerge(DEFAULT_CONFIG, storedConfig) + this.validateConfig(mergedConfig || {}); + + store.set({ config: mergedConfig }); + } catch (error) { + console.error('Failed to set default configuration:', error); + store.set({ config: DEFAULT_CONFIG }); + } } }; diff --git a/apps/server-web/src/main/helpers/services/web-service.ts b/apps/server-web/src/main/helpers/services/web-service.ts index 80d02a4ff..ffb0b59b7 100644 --- a/apps/server-web/src/main/helpers/services/web-service.ts +++ b/apps/server-web/src/main/helpers/services/web-service.ts @@ -5,7 +5,7 @@ import { EventEmitter } from 'stream'; export class WebService extends ServerTask { constructor( readonly path: string, - readonly env: any, + private env: any, readonly window: BrowserWindow, readonly signal: AbortSignal, readonly eventEmitter: EventEmitter @@ -38,11 +38,11 @@ export class WebService extends ServerTask { } } - private setApiConfig(): void { - // Object.assign(this.args, { - // API_HOST: '0.0.0.0', - // API_PORT: this.config.setting.PORT, - // API_BASE_URL: this.config.apiUrl - // }); + public setApiConfig(): void { + Object.assign(this.args, {...this.env}); } -} + + public updateEnv(env: any): void { + this.env = {...env}; + } + } diff --git a/apps/server-web/src/main/main.ts b/apps/server-web/src/main/main.ts index cba85f718..09db991a2 100644 --- a/apps/server-web/src/main/main.ts +++ b/apps/server-web/src/main/main.ts @@ -10,11 +10,12 @@ import Updater from './updater'; import { mainBindings } from 'i18next-electron-fs-backend'; import i18nextMainBackend from '../configs/i18n.mainconfig'; import fs from 'fs'; -import { WebServer } from './helpers/interfaces'; -import { replaceConfig } from './helpers'; +import { WebServer, AppMenu, ServerConfig } from './helpers/interfaces'; +import { clearDesktopConfig } from './helpers'; import Log from 'electron-log'; import MenuBuilder from './menu'; import { config } from '../configs/config'; +import { debounce } from 'lodash'; console.log = Log.log; @@ -39,8 +40,6 @@ let tray: Tray; let settingWindow: BrowserWindow | null = null; 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) => { @@ -92,7 +91,7 @@ i18nextMainBackend.on('initialized', () => { }); let trayMenuItems: any = []; -let appMenuItems: any = []; +let appMenuItems: AppMenu[] = []; const RESOURCES_PATH = app.isPackaged ? path.join(process.resourcesPath, 'assets') @@ -180,7 +179,6 @@ const createWindow = async (type: 'SETTING_WINDOW' | 'LOG_WINDOW' | 'SETUP_WINDO mainBindings(ipcMain, settingWindow, fs); settingWindow.on('closed', () => { settingWindow = null; - SettingMenu = null }); Menu.setApplicationMenu(appMenu.buildDefaultTemplate(appMenuItems, i18nextMainBackend)) break; @@ -191,7 +189,6 @@ const createWindow = async (type: 'SETTING_WINDOW' | 'LOG_WINDOW' | 'SETUP_WINDO mainBindings(ipcMain, logWindow, fs); logWindow.on('closed', () => { logWindow = null; - ServerWindowMenu = null }) Menu.setApplicationMenu(appMenu.buildDefaultTemplate(appMenuItems, i18nextMainBackend)) break; @@ -200,6 +197,11 @@ const createWindow = async (type: 'SETTING_WINDOW' | 'LOG_WINDOW' | 'SETUP_WINDO url = resolveHtmlPath('index.html', 'setup'); setupWindow?.loadURL(url); mainBindings(ipcMain, setupWindow, fs); + if (process.platform === 'darwin') { + Menu.setApplicationMenu(Menu.buildFromTemplate([])); + } else { + setupWindow.removeMenu(); + } setupWindow.on('closed', () => { setupWindow = null; }) @@ -212,12 +214,17 @@ const createWindow = async (type: 'SETTING_WINDOW' | 'LOG_WINDOW' | 'SETUP_WINDO const runServer = async () => { console.log('Run the Server...'); try { - const envVal: any = getEnvApi(); + const envVal: ServerConfig | undefined = getEnvApi(); + const folderPath = getWebDirPath(); + await clearDesktopConfig(folderPath); // Instantiate API and UI servers await desktopServer.start( { api: serverPath }, - envVal, + { + ...(envVal || {}), + IS_DESKTOP_APP: true + }, undefined, signal ); @@ -243,7 +250,7 @@ const restartServer = async () => { }, 1000) } -const getEnvApi = () => { +const getEnvApi = (): ServerConfig | undefined => { const setting: WebServer = LocalStore.getStore('config') return setting?.server; }; @@ -257,24 +264,14 @@ const SendMessageToSettingWindow = (type: string, data: any) => { const onInitApplication = () => { // 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) { - + const storeConfig:WebServer = LocalStore.getStore('config'); + i18nextMainBackend.on('languageChanged', debounce((lng) => { + if (i18nextMainBackend.isInitialized && storeConfig.general?.setup) { trayMenuItems = trayMenuItems.length ? trayMenuItems : defaultTrayMenuItem(eventEmitter); updateTrayMenu('none', {}, eventEmitter, tray, trayMenuItems, i18nextMainBackend); Menu.setApplicationMenu(appMenu.buildDefaultTemplate(appMenuItems, i18nextMainBackend)) } - }); - eventEmitter.on(EventLists.webServerStart, async () => { - updateTrayMenu('SERVER_START', { enabled: false }, eventEmitter, tray, trayMenuItems, i18nextMainBackend); - isServerRun = true; - await runServer(); - }) + }, 250)); eventEmitter.on(EventLists.webServerStop, async () => { await stopServer(); @@ -400,6 +397,7 @@ const onInitApplication = () => { eventEmitter.on(EventLists.SERVER_WINDOW, async () => { if (!logWindow) { + initTrayMenu() await createWindow('LOG_WINDOW'); } const serverSetting = LocalStore.getStore('config'); @@ -419,7 +417,7 @@ const onInitApplication = () => { }) eventEmitter.on(EventLists.OPEN_WEB, () => { - const envConfig = getEnvApi(); + const envConfig: ServerConfig | undefined = getEnvApi(); const url = `http://127.0.0.1:${envConfig?.PORT}` shell.openExternal(url) }) @@ -431,21 +429,57 @@ const onInitApplication = () => { eventEmitter.on(EventLists.SERVER_WINDOW_DEV, () => { logWindow?.webContents.toggleDevTools(); }) +} - eventEmitter.emit(EventLists.SERVER_WINDOW); +const initTrayMenu = () => { + const MAX_RETRIES = 2; + const retryInit = async (attempts: number = 0) => { + try { + LocalStore.setDefaultServerConfig(); + createIntervalAutoUpdate() + trayMenuItems = trayMenuItems.length ? trayMenuItems : defaultTrayMenuItem(eventEmitter); + appMenuItems = appMenuItems.length ? appMenuItems : appMenu.defaultMenu(); + tray = _initTray(trayMenuItems, getAssetPath('icons/icon.png')); + + eventEmitter.on(EventLists.webServerStart, async () => { + updateTrayMenu('SERVER_START', { enabled: false }, eventEmitter, tray, trayMenuItems, i18nextMainBackend); + isServerRun = true; + await runServer(); + }) + + trayMenuItems = trayMenuItems.length ? trayMenuItems : defaultTrayMenuItem(eventEmitter); + updateTrayMenu('none', {}, eventEmitter, tray, trayMenuItems, i18nextMainBackend); + } catch (error) { + if (attempts < MAX_RETRIES) { + await new Promise((resolve) => setTimeout(resolve, 1000)) + retryInit(attempts + 1) + } + console.error('Failed to initialize application:', error); + dialog.showErrorBox('Initialization Error', 'Failed to initialize application'); + } + } + if (!tray) { + return retryInit(0) + } } (async () => { await app.whenReady() const storeConfig:WebServer = LocalStore.getStore('config'); + onInitApplication(); if (storeConfig?.general?.setup) { - onInitApplication(); + eventEmitter.emit(EventLists.SERVER_WINDOW); } else { if (!setupWindow) { await createWindow('SETUP_WINDOW'); } if (setupWindow) { - setupWindow?.show() + setupWindow?.show(); + setupWindow?.webContents.once('did-finish-load', () => { + setTimeout(() => { + setupWindow?.webContents.send('languageSignal', storeConfig.general?.lang); + }, 50) + }) } } })() @@ -459,47 +493,44 @@ ipcMain.on('message', async (event, arg) => { event.reply('message', `${arg} World!`) }) +const getWebDirPath = () => { + const dirFiles = 'standalone/apps/web/.next/server/app/api'; + const devDirFilesPath = path.join(__dirname, resourceDir.webServer, dirFiles); + const packDirFilesPath = path.join(process.resourcesPath, 'release', 'app', 'dist', dirFiles) + const diFilesPath = isPack ? packDirFilesPath : devDirFilesPath; + return diFilesPath; +} + ipcMain.on(IPC_TYPES.SETTING_PAGE, async (event, arg) => { switch (arg.type) { case SettingPageTypeMessage.saveSetting: - const existingConfig = getEnvApi(); - LocalStore.updateConfigSetting({ - server: arg.data - }); - const dirFiles = 'standalone/apps/web/.next'; - const devDirFilesPath = path.join(__dirname, resourceDir.webServer, dirFiles); - const packDirFilesPath = path.join(process.resourcesPath, 'release', 'app', 'dist', dirFiles) - const diFilesPath = isPack ? packDirFilesPath : devDirFilesPath; - await replaceConfig( - diFilesPath, - { - before: { - NEXT_PUBLIC_GAUZY_API_SERVER_URL: existingConfig?.NEXT_PUBLIC_GAUZY_API_SERVER_URL || config.NEXT_PUBLIC_GAUZY_API_SERVER_URL - }, - after: { - NEXT_PUBLIC_GAUZY_API_SERVER_URL: arg.data.NEXT_PUBLIC_GAUZY_API_SERVER_URL || config.NEXT_PUBLIC_GAUZY_API_SERVER_URL - } - } - ) - if (arg.isSetup) { + { LocalStore.updateConfigSetting({ - general: { - setup: true - } - }); - setupWindow?.close(); - onInitApplication(); - eventEmitter.emit(EventLists.SERVER_WINDOW); - } else { - event.sender.send(IPC_TYPES.SETTING_PAGE, { - type: SettingPageTypeMessage.mainResponse, data: { - status: true, - isServerRun: isServerRun - } + server: arg.data }); + const diFilesPath = getWebDirPath(); + await clearDesktopConfig( + diFilesPath + ) + if (arg.isSetup) { + LocalStore.updateConfigSetting({ + general: { + setup: true + } + }); + setupWindow?.close(); + eventEmitter.emit(EventLists.SERVER_WINDOW); + } else { + event.sender.send(IPC_TYPES.SETTING_PAGE, { + type: SettingPageTypeMessage.mainResponse, data: { + status: true, + isServerRun: isServerRun + } + }); + } + break; } - break; - case SettingPageTypeMessage.checkUpdate: + case SettingPageTypeMessage.checkUpdate: updater.checkUpdate(); break; case SettingPageTypeMessage.installUpdate: @@ -523,7 +554,7 @@ ipcMain.on(IPC_TYPES.SETTING_PAGE, async (event, arg) => { LocalStore.updateConfigSetting({ general: { autoUpdate: arg.data.autoUpdate, - updateCheckPeriode: arg.data.updateCheckPeriode + updateCheckPeriod: arg.data.updateCheckPeriod } }) createIntervalAutoUpdate() @@ -554,13 +585,18 @@ ipcMain.handle('current-theme', async () => { return setting?.general?.theme;; }) +ipcMain.handle('current-language', async (): Promise => { + const setting: WebServer = LocalStore.getStore('config'); + return setting?.general?.lang || 'en'; +}) + const createIntervalAutoUpdate = () => { if (intervalUpdate) { clearInterval(intervalUpdate) } const setting: WebServer = LocalStore.getStore('config'); - if (setting.general?.autoUpdate && setting.general.updateCheckPeriode) { - const checkIntervalSecond = parseInt(setting.general.updateCheckPeriode); + if (setting.general?.autoUpdate && setting.general.updateCheckPeriod) { + const checkIntervalSecond = parseInt(setting.general.updateCheckPeriod); if (!Number.isNaN(checkIntervalSecond)) { const intervalMS = checkIntervalSecond * 60 * 1000; intervalUpdate = setInterval(() => { diff --git a/apps/server-web/src/main/menu.ts b/apps/server-web/src/main/menu.ts index cec72f148..e95f8f8ba 100644 --- a/apps/server-web/src/main/menu.ts +++ b/apps/server-web/src/main/menu.ts @@ -7,6 +7,7 @@ import { config } from '../configs/config'; import { EventEmitter } from 'events'; import { EventLists } from './helpers/constant'; import i18n from 'i18next'; +import { AppMenu } from './helpers/interfaces'; export default class MenuBuilder { eventEmitter: EventEmitter @@ -15,7 +16,7 @@ export default class MenuBuilder { this.eventEmitter = eventEmitter } - defaultMenu() { + defaultMenu(): AppMenu[] { const isDarwin = process.platform === 'darwin'; return [ { @@ -24,8 +25,7 @@ export default class MenuBuilder { submenu: [ { id: 'MENU_APP_ABOUT', - label: `MENU_APP.ABOUT`, - selector: 'orderFrontStandardAboutPanel:', + label: `MENU_APP.APP_ABOUT`, click: () => { this.eventEmitter.emit(EventLists.gotoAbout) } @@ -33,7 +33,7 @@ export default class MenuBuilder { { type: 'separator' }, { id: 'MENU_APP_QUIT', - label: 'MENU_APP.QUIT', + label: 'MENU_APP.APP_QUIT', accelerator: isDarwin ? 'Command+Q' : 'Alt+F4', click: () => { app.quit(); @@ -43,18 +43,18 @@ export default class MenuBuilder { }, { id: 'MENU_APP_WINDOW', - label: 'MENU_APP.WINDOW', + label: 'MENU_APP.APP_WINDOW', submenu: [ { id: 'SUBMENU_SETTING', - label: 'MENU_APP.SUBMENU.SETTING', + label: 'MENU_APP.APP_SUBMENU.APP_SETTING', click: () => { this.eventEmitter.emit(EventLists.gotoSetting); } }, { id: 'SUBMENU_SERVER', - label: 'MENU_APP.SUBMENU.SERVER_WINDOW', + label: 'MENU_APP.APP_SUBMENU.APP_SERVER_WINDOW', click: () => { this.eventEmitter.emit(EventLists.SERVER_WINDOW); } @@ -63,18 +63,18 @@ export default class MenuBuilder { }, { id: 'MENU_APP_HELP', - label: 'MENU_APP.HELP', + label: 'MENU_APP.APP_HELP', submenu: [ { id: 'SUBMENU_LEARN_MORE', - label: 'MENU_APP.SUBMENU.LEARN_MORE', + label: 'MENU_APP.APP_SUBMENU.APP_LEARN_MORE', click() { shell.openExternal(config.COMPANY_SITE_LINK); }, }, { id: 'SUBMENU_DOC', - label: 'MENU_APP.SUBMENU.DOC', + label: 'MENU_APP.APP_SUBMENU.APP_DOC', click() { shell.openExternal( config.COMPANY_GITHUB_LINK @@ -85,18 +85,18 @@ export default class MenuBuilder { }, { id: 'MENU_APP_DEV', - label: 'MENU_APP.DEV', + label: 'MENU_APP.APP_DEV', submenu: [ { id: 'SUBMENU_SETTING_DEV', - label: 'MENU_APP.SUBMENU.SETTING_DEV', + label: 'MENU_APP.APP_SUBMENU.APP_SETTING_DEV', click: () => { this.eventEmitter.emit(EventLists.SETTING_WINDOW_DEV); }, }, { id: 'SUBMENU_SERVER_DEV', - label: 'MENU_APP.SUBMENU.SERVER_DEV', + label: 'MENU_APP.APP_SUBMENU.APP_SERVER_DEV', click: () => { this.eventEmitter.emit(EventLists.SERVER_WINDOW_DEV); }, diff --git a/apps/server-web/src/renderer/components/LanguageSelector.tsx b/apps/server-web/src/renderer/components/LanguageSelector.tsx new file mode 100644 index 000000000..0cebc1a71 --- /dev/null +++ b/apps/server-web/src/renderer/components/LanguageSelector.tsx @@ -0,0 +1,54 @@ +import { useTranslation } from 'react-i18next'; +import { SelectComponent } from './Select'; +import { useState } from 'react'; +import { ILanguages } from '../libs/interfaces'; +import { SettingPageTypeMessage } from '../../main/helpers/constant'; + +export const SUPPORTED_LANGUAGES: ILanguages[] = [ + { code: 'en', label: 'English' }, + { code: 'bg', label: 'Bulgarian' }, +]; + +type LanguageSelector = { + lang: string; +}; +const LanguageSelector = ({ lang }: LanguageSelector) => { + const [langs] = useState(SUPPORTED_LANGUAGES); + const { t } = useTranslation(); + + const changeLanguage = (data: ILanguages) => { + try { + window.electron.ipcRenderer.sendMessage('setting-page', { + type: SettingPageTypeMessage.langChange, + data: data.code, + }); + setLng(data.code); + } catch (error) { + console.error('Failed to change language:', error); + } + }; + + const [lng, setLng] = useState(lang); + + const language = langs.find((lg) => lg.code === lng) || { + code: 'en', + label: 'English', + }; + return ( + ({ + value: i.code, + label: `LANGUAGES.${i.code}`, + }))} + title={t('FORM.LABELS.LANGUAGES')} + defaultValue={language.code} + onValueChange={(val) => { + changeLanguage({ code: val }); + }} + disabled={false} + value={language.code} + /> + ); +}; + +export default LanguageSelector; diff --git a/apps/server-web/src/renderer/components/SideBar.tsx b/apps/server-web/src/renderer/components/SideBar.tsx index fd4c80ebe..f0b94c3d7 100644 --- a/apps/server-web/src/renderer/components/SideBar.tsx +++ b/apps/server-web/src/renderer/components/SideBar.tsx @@ -1,22 +1,16 @@ import { Link } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { ISidebarComponent } from '../libs/interfaces'; -import { SelectComponent } from './Select'; import { ThemeToggler } from './Toggler'; +import LanguageSelector from './LanguageSelector'; export function SideBar({ children, menus, menuChange, - langs, lang, - onLangChange, }: ISidebarComponent) { const { t } = useTranslation(); - const language = langs.find((lg) => lg.code === lang) || { - code: 'en', - label: 'English', - }; return (
@@ -43,19 +37,7 @@ export function SideBar({
- ({ - value: i.code, - label: `LANGUAGES.${i.code}`, - }))} - title={t('FORM.LABELS.LANGUAGES')} - defaultValue={language.code} - onValueChange={(lang) => { - onLangChange({ code: lang }); - }} - disabled={false} - value={language.code} - /> +
diff --git a/apps/server-web/src/renderer/components/Updater.tsx b/apps/server-web/src/renderer/components/Updater.tsx index eb70dd816..5e5d110b4 100644 --- a/apps/server-web/src/renderer/components/Updater.tsx +++ b/apps/server-web/src/renderer/components/Updater.tsx @@ -70,11 +70,11 @@ export const UpdaterComponent = (props: IUpdaterComponent) => { const onSelectPeriode = (value: string) => { props.changeAutoUpdate({ autoUpdate: props.data.autoUpdate, - updateCheckPeriode: value, + updateCheckPeriod: value, }); props.saveSettingUpdate({ autoUpdate: props.data.autoUpdate, - updateCheckPeriode: value, + updateCheckPeriod: value, }); }; @@ -123,11 +123,11 @@ export const UpdaterComponent = (props: IUpdaterComponent) => { setOpen(); props.changeAutoUpdate({ autoUpdate: value, - updateCheckPeriode: props.data.updateCheckPeriode, + updateCheckPeriod: props.data.updateCheckPeriod, }); props.saveSettingUpdate({ autoUpdate: value, - updateCheckPeriode: props.data.updateCheckPeriode, + updateCheckPeriod: props.data.updateCheckPeriod, }); }} checked={props.data.autoUpdate} @@ -149,8 +149,8 @@ export const UpdaterComponent = (props: IUpdaterComponent) => { ...i, label: `FORM.LABELS.UPDATE_OPTIONS.${i.label}`, }))} - value={props.data.updateCheckPeriode} - defaultValue={props.data.updateCheckPeriode} + value={props.data.updateCheckPeriod} + defaultValue={props.data.updateCheckPeriod} disabled={!props.data.autoUpdate} onValueChange={onSelectPeriode} /> diff --git a/apps/server-web/src/renderer/libs/interfaces/i-components.ts b/apps/server-web/src/renderer/libs/interfaces/i-components.ts index 607e7a8dd..fba435bbe 100644 --- a/apps/server-web/src/renderer/libs/interfaces/i-components.ts +++ b/apps/server-web/src/renderer/libs/interfaces/i-components.ts @@ -1,4 +1,4 @@ -import { IUpdaterStates, IUpdateSetting, ISideMenu, ISelectItems, IServerSetting, ILanguages } from './i-setting'; +import { IUpdaterStates, IUpdateSetting, ISideMenu, ISelectItems, IServerSetting } from './i-setting'; type IToastComponent = { title: string; @@ -51,8 +51,6 @@ type ISidebarComponent = { children: string | JSX.Element | JSX.Element[]; menus: ISideMenu[]; menuChange: (key: string) => void; - langs: ILanguages[]; - onLangChange: (lang: any) => void; lang: string; }; diff --git a/apps/server-web/src/renderer/libs/interfaces/i-setting.ts b/apps/server-web/src/renderer/libs/interfaces/i-setting.ts index f09118e9e..d061ed869 100644 --- a/apps/server-web/src/renderer/libs/interfaces/i-setting.ts +++ b/apps/server-web/src/renderer/libs/interfaces/i-setting.ts @@ -40,12 +40,12 @@ interface IPopup { interface ILanguages { code: string; - label: string; + label?: string; } type IUpdateSetting = { autoUpdate: boolean; - updateCheckPeriode: string; + updateCheckPeriod: string; }; type IAbout = { diff --git a/apps/server-web/src/renderer/pages/Setting.tsx b/apps/server-web/src/renderer/pages/Setting.tsx index 90b699ac7..c938f2e6d 100644 --- a/apps/server-web/src/renderer/pages/Setting.tsx +++ b/apps/server-web/src/renderer/pages/Setting.tsx @@ -6,14 +6,12 @@ import { ServerComponent, UpdaterComponent, AboutComponent, - GeneralComponent, } from '../components'; import { IUpdaterStates, IUpdateSetting, IServerSetting, IPopup, - ILanguages, ISideMenu, } from '../libs/interfaces'; import { useTranslation } from 'react-i18next'; @@ -40,20 +38,9 @@ export function Setting() { const [updateSetting, setUpdateSetting] = useState({ autoUpdate: false, - updateCheckPeriode: '180', + updateCheckPeriod: '180', }); - const [langs, setLangs] = useState([ - { - code: 'en', - label: 'English', - }, - { - code: 'bg', - label: 'Bulgarian', - }, - ]); - const [lng, setLng] = useState('en'); const [updateStates, setUpdateState] = useState({ @@ -91,11 +78,6 @@ export function Setting() { setMenu(newMenu); }; - const changeLanguage = (lang: ILanguages) => { - sendingMessageToMain(lang.code, SettingPageTypeMessage.langChange); - setLng(lang.code); - }; - const saveSettingUpdate = (data: IUpdateSetting) => { sendingMessageToMain(data, SettingPageTypeMessage.updateSetting); }; @@ -202,12 +184,6 @@ export function Setting() { sendingMessageToMain({}, SettingPageTypeMessage.showVersion); return ; } - - if (activeMenu() === 'general') { - return ( - - ); - } return ; }; @@ -268,7 +244,7 @@ export function Setting() { setLng(arg.data.general.lang); setUpdateSetting({ autoUpdate: arg.data.general.autoUpdate, - updateCheckPeriode: arg.data.general.updateCheckPeriode, + updateCheckPeriod: arg.data.general.updateCheckPeriod, }); break; case SettingPageTypeMessage.mainResponse: @@ -305,13 +281,7 @@ export function Setting() { }, []); return ( - + ); diff --git a/apps/server-web/src/renderer/pages/setup/Landing.tsx b/apps/server-web/src/renderer/pages/setup/Landing.tsx index e683418ae..072b6ee06 100644 --- a/apps/server-web/src/renderer/pages/setup/Landing.tsx +++ b/apps/server-web/src/renderer/pages/setup/Landing.tsx @@ -1,18 +1,32 @@ import { EverTeamsLogo } from '../../components/svgs/index'; import { config } from '../../../configs/config'; import { useTranslation } from 'react-i18next'; +import LanguageSelector from '../../components/LanguageSelector'; +import { useEffect, useState } from 'react'; type props = { nextAction: () => void; }; const Landing = (props: props) => { const { t } = useTranslation(); + const [defaultLang, setDefaultLang] = useState('en'); + + const getCurrentLanguage = async () => { + try { + const lang = await window.electron.ipcRenderer.invoke('current-language'); + setDefaultLang(lang); + } catch (error) { + console.error('Failed to get current language:', error); + setDefaultLang('en'); // Fallback to English + } + }; + + useEffect(() => { + getCurrentLanguage(); + }, []); return (
- +
diff --git a/apps/web/app/api/desktop-server/route.ts b/apps/web/app/api/desktop-server/route.ts new file mode 100644 index 000000000..c9405224c --- /dev/null +++ b/apps/web/app/api/desktop-server/route.ts @@ -0,0 +1,6 @@ +import { getDesktopConfig } from '@app/services/server/requests/desktop-source'; +import { NextResponse } from 'next/server'; + +export async function GET() { + return NextResponse.json(await getDesktopConfig()); +} diff --git a/apps/web/app/constants.ts b/apps/web/app/constants.ts index b7d0ad636..016df0a8b 100644 --- a/apps/web/app/constants.ts +++ b/apps/web/app/constants.ts @@ -1,6 +1,6 @@ import { JitsuOptions } from '@jitsu/jitsu-react/dist/useJitsu'; import { I_SMTPRequest } from './interfaces/ISmtp'; -import { getNextPublicEnv } from './env'; +import { getNextPublicEnv, getServerRuntimeConfig } from './env'; import enLanguage from '../locales/en.json'; import { BG, CN, DE, ES, FR, IS, IT, NL, PL, PT, RU, SA, US } from 'country-flag-icons/react/1x1'; import { ManualTimeReasons } from './interfaces/timer/IManualTimeReasons'; @@ -30,13 +30,20 @@ export const ACTIVE_TIMEZONE_COOKIE_NAME = 'auth-timezone'; export const NO_TEAM_POPUP_SHOW_COOKIE_NAME = 'no-team-popup-show'; export const ACTIVE_PROJECT_COOKIE_NAME = 'auth-active-project'; +export const IS_DESKTOP_APP = process.env.IS_DESKTOP_APP === 'true'; + // Recaptcha export const RECAPTCHA_SITE_KEY = getNextPublicEnv( 'NEXT_PUBLIC_CAPTCHA_SITE_KEY', process.env.NEXT_PUBLIC_CAPTCHA_SITE_KEY ); export const RECAPTCHA_SECRET_KEY = process.env.CAPTCHA_SECRET_KEY; -const basePath = process.env.GAUZY_API_SERVER_URL ? process.env.GAUZY_API_SERVER_URL : 'https://api.ever.team'; +let basePath = process.env.GAUZY_API_SERVER_URL ? process.env.GAUZY_API_SERVER_URL : 'https://api.ever.team'; +if (IS_DESKTOP_APP) { + const serverRuntimeConfig = getServerRuntimeConfig() + basePath = serverRuntimeConfig?.GAUZY_API_SERVER_URL || basePath; +} + export const GAUZY_API_SERVER_URL = basePath + '/api'; export const GAUZY_API_BASE_SERVER_URL = getNextPublicEnv( @@ -319,8 +326,6 @@ 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/app/env.ts b/apps/web/app/env.ts index 2de6fd64b..983803f66 100644 --- a/apps/web/app/env.ts +++ b/apps/web/app/env.ts @@ -1,3 +1,4 @@ +import getConfig from 'next/config'; const NEXT_PUBLIC_ENVS: { value: Env } = { value: {} }; type Env = Record; @@ -38,6 +39,17 @@ export function getNextPublicEnv>(name: string, optio }; } +export function getServerRuntimeConfig() { + try { + const { serverRuntimeConfig } = getConfig(); + return { + GAUZY_API_SERVER_URL: serverRuntimeConfig.GAUZY_API_SERVER_URL, + }; + } catch(e) { + console.log('skip get config on call from client'); + } +} + export function setNextPublicEnv(envs: Env) { if (envs) { NEXT_PUBLIC_ENVS.value = { diff --git a/apps/web/app/interfaces/IRuntimeServerConfig.ts b/apps/web/app/interfaces/IRuntimeServerConfig.ts new file mode 100644 index 000000000..cff07e845 --- /dev/null +++ b/apps/web/app/interfaces/IRuntimeServerConfig.ts @@ -0,0 +1,5 @@ +export interface ServerRuntimeConfig { + GAUZY_API_SERVER_URL?: string; + NEXT_PUBLIC_GAUZY_API_SERVER_URL?: string; + [key: string]: any; +} diff --git a/apps/web/app/services/client/axios.ts b/apps/web/app/services/client/axios.ts index eff23ac40..b1d4f20f2 100644 --- a/apps/web/app/services/client/axios.ts +++ b/apps/web/app/services/client/axios.ts @@ -1,5 +1,5 @@ /* eslint-disable no-mixed-spaces-and-tabs */ -import { API_BASE_URL, APPLICATION_LANGUAGES_CODE, DEFAULT_APP_PATH, GAUZY_API_BASE_SERVER_URL } from '@app/constants'; +import { API_BASE_URL, APPLICATION_LANGUAGES_CODE, DEFAULT_APP_PATH, GAUZY_API_BASE_SERVER_URL, IS_DESKTOP_APP } from '@app/constants'; import { getAccessTokenCookie, getActiveTeamIdCookie, @@ -87,11 +87,29 @@ apiDirect.interceptors.response.use( type APIConfig = AxiosRequestConfig & { tenantId?: string; directAPI?: boolean }; -function apiConfig(config?: APIConfig) { +async function desktopServerOverride() { + if (typeof window !== 'undefined') { + try { + const serverConfig = await api.get('/desktop-server'); + return serverConfig?.data?.NEXT_PUBLIC_GAUZY_API_SERVER_URL; + } catch (error) { + return GAUZY_API_BASE_SERVER_URL + } + } + return GAUZY_API_BASE_SERVER_URL; +} + +async function apiConfig(config?: APIConfig) { const tenantId = getTenantIdCookie(); const organizationId = getOrganizationIdCookie(); let baseURL: string | undefined = GAUZY_API_BASE_SERVER_URL.value; + + if (IS_DESKTOP_APP) { // dynamic api host while on desktop mode + const runtimeConfig = await desktopServerOverride(); + baseURL = runtimeConfig || GAUZY_API_BASE_SERVER_URL.value; + } + baseURL = baseURL ? `${baseURL}/api` : undefined; apiDirect.defaults.baseURL = baseURL; @@ -109,22 +127,22 @@ function apiConfig(config?: APIConfig) { }; } -function get(endpoint: string, config?: APIConfig) { - const { baseURL, headers } = apiConfig(config); +async function get(endpoint: string, config?: APIConfig) { + const { baseURL, headers } = await apiConfig(config); const { directAPI = true } = config || {}; return baseURL && directAPI ? apiDirect.get(endpoint, { ...config, headers }) : api.get(endpoint); } -function deleteApi(endpoint: string, config?: APIConfig) { - const { baseURL, headers } = apiConfig(config); +async function deleteApi(endpoint: string, config?: APIConfig) { + const { baseURL, headers } = await apiConfig(config); const { directAPI = true } = config || {}; return baseURL && directAPI ? apiDirect.delete(endpoint, { ...config, headers }) : api.delete(endpoint); } -function post(url: string, data?: Record | FormData, config?: APIConfig) { - const { baseURL, headers, tenantId, organizationId } = apiConfig(config); +async function post(url: string, data?: Record | FormData, config?: APIConfig) { + const { baseURL, headers, tenantId, organizationId } = await apiConfig(config); const { directAPI = true } = config || {}; if (baseURL && directAPI && data && !(data instanceof FormData)) { @@ -139,8 +157,8 @@ function post(url: string, data?: Record | FormData, config?: AP return baseURL && directAPI ? apiDirect.post(url, data, { ...config, headers }) : api.post(url, data); } -function put(url: string, data?: Record | FormData, config?: APIConfig) { - const { baseURL, headers, tenantId, organizationId } = apiConfig(config); +async function put(url: string, data?: Record | FormData, config?: APIConfig) { + const { baseURL, headers, tenantId, organizationId } = await apiConfig(config); const { directAPI = true } = config || {}; if (baseURL && directAPI && data && !(data instanceof FormData)) { @@ -155,8 +173,8 @@ function put(url: string, data?: Record | FormData, config?: API return baseURL && directAPI ? apiDirect.put(url, data, { ...config, headers }) : api.put(url, data); } -function patch(url: string, data?: Record | FormData, config?: APIConfig) { - const { baseURL, headers, tenantId, organizationId } = apiConfig(config); +async function patch(url: string, data?: Record | FormData, config?: APIConfig) { + const { baseURL, headers, tenantId, organizationId } = await apiConfig(config); const { directAPI = true } = config || {}; if (baseURL && directAPI && data && !(data instanceof FormData)) { diff --git a/apps/web/app/services/server/requests/desktop-source.ts b/apps/web/app/services/server/requests/desktop-source.ts new file mode 100644 index 000000000..92d5f2c09 --- /dev/null +++ b/apps/web/app/services/server/requests/desktop-source.ts @@ -0,0 +1,16 @@ +import getConfig from 'next/config'; +import { ServerRuntimeConfig } from '@app/interfaces/IRuntimeServerConfig'; +import { GAUZY_API_SERVER_URL, GAUZY_API_BASE_SERVER_URL } from '@app/constants'; + +export function getDesktopConfig(): Partial { + try { + const { serverRuntimeConfig } = getConfig(); + return serverRuntimeConfig; + } catch (error) { + console.log('skip get server runtime config'); + return { + GAUZY_API_SERVER_URL, + NEXT_PUBLIC_GAUZY_API_SERVER_URL: GAUZY_API_BASE_SERVER_URL.value + }; + } +} diff --git a/package.json b/package.json index 4948aeb7a..3048c8ad2 100644 --- a/package.json +++ b/package.json @@ -81,12 +81,13 @@ "spell": "cspell . --config .cspell.json", "spellcheck": "cspell .", "config:electron": "yarn ts-node ./.scripts/electron.env.ts", + "config:electron:web:build": "yarn ts-node ./.scripts/configure.electron.ts", "pack": "yarn ts-node .scripts/electron-package-utils/package-util.ts", "pack:server-web": "yarn run pack --desktop=server-web", "config:server-web": "yarn run config:electron -- --environment=prod --desktop=server-web", "dev:server-web": "cross-env NODE_ENV=production yarn prepare:config:server-web && yarn build:web:desktop && yarn run prepare:server-web && yarn copy:build:web && yarn run start:server-web", - "build:web:desktop": "cross-env NEXT_BUILD_OUTPUT_TYPE=standalone NEXT_SHARP_PATH=/temp/node_modules/sharp yarn build:web", - "copy:build:web": "pwd && cp -r apps/web/.next/standalone apps/server-web/release/app/dist && cp -r apps/web/.next/static apps/server-web/release/app/dist/standalone/apps/web/.next && cp -r apps/web/public apps/server-web/release/app/dist/standalone/apps/web/", + "build:web:desktop": "yarn config:electron:web:build -- --type=constant && cross-env NEXT_BUILD_OUTPUT_TYPE=standalone NEXT_SHARP_PATH=/tmp/node_modules/sharp yarn build:web", + "copy:build:web": "yarn ts-node .scripts/copy-web-build.ts && yarn config:electron:web:build -- --type=server", "start:server-web": "cd apps/server-web && yarn run start", "prepare:server-web": "cd apps/server-web && yarn run prepare:electron && yarn run build", "pack:server-web:mac": "cd apps/server-web && yarn run pack:mac",