* feat: desktop revamp * feat: refactor system monitor * fix linter CI * remove unused import component * added responsive and resizeable component * responsive and resizeable local server page * finalize responsive and resizeable component * fix scroll custom ui * remove react scroll to bottom from modal troubleshoot * fix modal troubleshoot ui * fix setting gpu list * text area custom scroll bar * fix padding message input * cleanup classname * update inference engine model dropdown * update loader style * update quick ask ui * prepare theme provider * update dark theme * remove update hotkey list model and navigation * fix: cleanup hardcode classname * fix: update feedback * Set native theme electron * update destop ui revamp from feedback * update button icon component insider icon chat input message * update model dropdown ui * update tranaparent baclground * update logo model provider * fix: set background material acrylic support to blur background windows * fix: update tranparent left and right panel * fix: linter CI * update app using frameless window * styling custom style minimize, maximize and close app * temporary hidden maximize window * fix: responsive left and right panel * fix: enable click outside when leftpanel responsive * fix: remove unused import * update transparent variable css windows * fix: ui import model * feat: Support Theme system (#2946) * feat: update support theme system * update select component * feat: add theme folder in root project * fix: padding left and right center panel * fix: update padding left and right * chore: migrate themes * fix: rmdirsync error * chore: update gitignore * fix: cp recursive * fix: files electron package json * fix: migration * fix: update fgit ignore --------- Co-authored-by: Louis <louis@jan.ai> * fix: update feedback missing state when refrash app * fix: error test CI * chore: refactor useLoadThemes * chore: cleanup unused vars * fix: revert back menubar windows * fix minor ui * fix: minor ui --------- Co-authored-by: Louis <louis@jan.ai>
193 lines
5.0 KiB
TypeScript
193 lines
5.0 KiB
TypeScript
import { BrowserWindow, app, shell } from 'electron'
|
|
import { quickAskWindowConfig } from './quickAskWindowConfig'
|
|
import { mainWindowConfig } from './mainWindowConfig'
|
|
import { getAppConfigurations, AppEvent } from '@janhq/core/node'
|
|
import { getBounds, saveBounds } from '../utils/setup'
|
|
|
|
/**
|
|
* Manages the current window instance.
|
|
*/
|
|
// TODO: refactor this
|
|
let isAppQuitting = false
|
|
|
|
class WindowManager {
|
|
public mainWindow?: BrowserWindow
|
|
private _quickAskWindow: BrowserWindow | undefined = undefined
|
|
private _quickAskWindowVisible = false
|
|
private _mainWindowVisible = false
|
|
|
|
private deeplink: string | undefined
|
|
/**
|
|
* Creates a new window instance.
|
|
* @returns The created window instance.
|
|
*/
|
|
async createMainWindow(preloadPath: string, startUrl: string) {
|
|
const bounds = await getBounds()
|
|
|
|
this.mainWindow = new BrowserWindow({
|
|
...mainWindowConfig,
|
|
width: bounds.width,
|
|
height: bounds.height,
|
|
x: bounds.x,
|
|
y: bounds.y,
|
|
webPreferences: {
|
|
nodeIntegration: true,
|
|
preload: preloadPath,
|
|
webSecurity: false,
|
|
},
|
|
})
|
|
|
|
if (process.platform === 'win32' || process.platform === 'linux') {
|
|
/// This is work around for windows deeplink.
|
|
/// second-instance event is not fired when app is not open, so the app
|
|
/// does not received the deeplink.
|
|
const commandLine = process.argv.slice(1)
|
|
if (commandLine.length > 0) {
|
|
const url = commandLine[0]
|
|
this.sendMainAppDeepLink(url)
|
|
}
|
|
}
|
|
|
|
this.mainWindow.on('resized', () => {
|
|
saveBounds(this.mainWindow?.getBounds())
|
|
})
|
|
|
|
this.mainWindow.on('moved', () => {
|
|
saveBounds(this.mainWindow?.getBounds())
|
|
})
|
|
|
|
/* Load frontend app to the window */
|
|
this.mainWindow.loadURL(startUrl)
|
|
|
|
/* Open external links in the default browser */
|
|
this.mainWindow.webContents.setWindowOpenHandler(({ url }) => {
|
|
shell.openExternal(url)
|
|
return { action: 'deny' }
|
|
})
|
|
|
|
app.on('before-quit', function () {
|
|
isAppQuitting = true
|
|
})
|
|
|
|
windowManager.mainWindow?.on('close', function (evt) {
|
|
// Feature Toggle for Quick Ask
|
|
if (!getAppConfigurations().quick_ask) return
|
|
|
|
if (!isAppQuitting) {
|
|
evt.preventDefault()
|
|
windowManager.hideMainWindow()
|
|
}
|
|
})
|
|
}
|
|
|
|
createQuickAskWindow(preloadPath: string, startUrl: string): void {
|
|
this._quickAskWindow = new BrowserWindow({
|
|
...quickAskWindowConfig,
|
|
webPreferences: {
|
|
nodeIntegration: true,
|
|
preload: preloadPath,
|
|
webSecurity: false,
|
|
},
|
|
})
|
|
|
|
this._quickAskWindow.loadURL(startUrl)
|
|
this._quickAskWindow.on('blur', () => {
|
|
this.hideQuickAskWindow()
|
|
})
|
|
}
|
|
|
|
isMainWindowVisible(): boolean {
|
|
return this._mainWindowVisible
|
|
}
|
|
|
|
hideMainWindow(): void {
|
|
this.mainWindow?.hide()
|
|
this._mainWindowVisible = false
|
|
}
|
|
|
|
showMainWindow(): void {
|
|
this.mainWindow?.show()
|
|
this._mainWindowVisible = true
|
|
}
|
|
|
|
hideQuickAskWindow(): void {
|
|
this._quickAskWindow?.hide()
|
|
this._quickAskWindowVisible = false
|
|
}
|
|
|
|
showQuickAskWindow(): void {
|
|
this._quickAskWindow?.show()
|
|
this._quickAskWindowVisible = true
|
|
}
|
|
|
|
closeQuickAskWindow(): void {
|
|
if (this._quickAskWindow?.isDestroyed()) return
|
|
this._quickAskWindow?.close()
|
|
this._quickAskWindow?.destroy()
|
|
this._quickAskWindow = undefined
|
|
this._quickAskWindowVisible = false
|
|
}
|
|
|
|
isQuickAskWindowVisible(): boolean {
|
|
return this._quickAskWindowVisible
|
|
}
|
|
|
|
isQuickAskWindowDestroyed(): boolean {
|
|
return this._quickAskWindow?.isDestroyed() ?? true
|
|
}
|
|
|
|
expandQuickAskWindow(heightOffset: number): void {
|
|
const width = quickAskWindowConfig.width!
|
|
const height = quickAskWindowConfig.height! + heightOffset
|
|
this._quickAskWindow?.setMinimumSize(width, height)
|
|
this._quickAskWindow?.setSize(width, height, true)
|
|
}
|
|
|
|
sendQuickAskSelectedText(selectedText: string): void {
|
|
this._quickAskWindow?.webContents.send(
|
|
AppEvent.onSelectedText,
|
|
selectedText
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Try to send the deep link to the main app.
|
|
*/
|
|
sendMainAppDeepLink(url: string): void {
|
|
this.deeplink = url
|
|
const interval = setInterval(() => {
|
|
if (!this.deeplink) clearInterval(interval)
|
|
const mainWindow = this.mainWindow
|
|
if (mainWindow) {
|
|
mainWindow.webContents.send(AppEvent.onDeepLink, this.deeplink)
|
|
if (mainWindow.isMinimized()) mainWindow.restore()
|
|
mainWindow.focus()
|
|
}
|
|
}, 500)
|
|
}
|
|
|
|
cleanUp(): void {
|
|
if (!this.mainWindow?.isDestroyed()) {
|
|
this.mainWindow?.close()
|
|
this.mainWindow?.destroy()
|
|
this.mainWindow = undefined
|
|
this._mainWindowVisible = false
|
|
}
|
|
if (!this._quickAskWindow?.isDestroyed()) {
|
|
this._quickAskWindow?.close()
|
|
this._quickAskWindow?.destroy()
|
|
this._quickAskWindow = undefined
|
|
this._quickAskWindowVisible = false
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Acknowledges that the window has received a deep link. We can remove it.
|
|
*/
|
|
ackDeepLink() {
|
|
this.deeplink = undefined
|
|
}
|
|
}
|
|
|
|
export const windowManager = new WindowManager()
|