/* * YukiHookAPI Project Builder - A Xposed Project Builder by YukiHookAPI. * Copyright (C) 2019-2022 HighCapable * https://github.com/fankes/YukiHookAPI-ProjectBuilder * * This software is non-free but opensource software: you can redistribute it * and/or modify it under the terms of the GNU Affero General Public License * as published by the Free Software Foundation; either * version 3 of the License, or any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * and eula along with this software. If not, see * * * This file is Created by fankes on 2022/7/21. */ const {app, ipcMain, dialog, BrowserWindow, Menu, MenuItem} = require('electron'); const {locale} = require('./libs/locale'); const {fileSystem} = require('./libs/file-system'); const {store} = require('./libs/store'); const {system} = require("./libs/system"); /** 主窗口 */ let mainWindow = null; /** * 应用程序初始化命名空间 */ const appSpace = { /** 开始装载窗口 */ loadFrame: () => { appSpace.createWindow(); appSpace.createMenu(); }, /** 创建窗口 */ createWindow: () => { mainWindow = new BrowserWindow({ title: locale.i18n.windowTitle, width: 1000, height: 1050, resizable: false, maximizable: false, show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } }); mainWindow.loadFile('src/content/index.html').then(() => { appWindow.webContents.methods.page.init(localeConfig.currentLocale(), app.getVersion()); }); mainWindow.once('ready-to-show', () => { mainWindow.show(); }); }, /** 创建菜单 */ createMenu: () => { const menu = new Menu(); const separatorItem = new MenuItem({type: 'separator'}); const aboutChildItem = new MenuItem({ label: locale.i18n.about, accelerator: system.isMacOS ? 'command+a' : 'ctrl+a', click: () => { appWindow.webContents.methods.page.showAbout(app.getName(), app.getVersion()); } }); const checkUpdateChildItem = new MenuItem({ label: locale.i18n.checkUpdate, click: () => { appWindow.webContents.methods.page.checkForUpdates(); } }); const projectAddressChildItem = new MenuItem({ label: locale.i18n.projectAddress, accelerator: system.isMacOS ? 'command+w' : 'ctrl+w', click: () => { system.openBrowser('https://github.com/fankes/YukiHookAPI'); } }); const helpDocumentationChildItem = new MenuItem({ label: locale.i18n.helpDocumentation, accelerator: system.isMacOS ? 'command+h' : 'ctrl+h', click: () => { system.openBrowser('https://fankes.github.io/YukiHookAPI'); } }); const openSourceChildItem = new MenuItem({ label: locale.i18n.openSource, accelerator: system.isMacOS ? 'command+o' : 'ctrl+o', click: () => { appWindow.webContents.methods.page.showOpenSource(); } }); const laguageSubChildItems = []; laguageSubChildItems.push(new MenuItem({ type: 'radio', label: locale.i18n.followSystem, checked: localeConfig.isFollowSystem, click: () => { localeConfig.changeLocale(); } })); for (const la in locale.languageSupports) laguageSubChildItems.push(new MenuItem({ type: 'radio', label: locale.languageSupports[la], checked: la === locale.name && !localeConfig.isFollowSystem, click: () => { localeConfig.changeLocale(la); } })); const languageChildItem = new MenuItem({ label: locale.i18n.language + ' (Language)', submenu: laguageSubChildItems }); const quitChildItem = new MenuItem({ label: locale.i18n.quit, accelerator: system.isMacOS ? 'command+q' : 'ctrl+q', click: () => { app.quit(); } }); const runBuildChildItem = new MenuItem({ label: locale.i18n.runBuild, accelerator: system.isMacOS ? 'command+b' : 'ctrl+b', click: () => { appWindow.webContents.methods.build.checking(); } }); const newTemplateChildItem = new MenuItem({ label: locale.i18n.newTemplate, accelerator: system.isMacOS ? 'command+n' : 'ctrl+n', click: () => { appWindow.webContents.methods.configTemplate.createNew(); } }); const templateSubChildItems = []; const clearAllTemplateChildItem = new MenuItem({ label: locale.i18n.clearAllTemplate, accelerator: system.isMacOS ? 'command+d' : 'ctrl+d', click: () => { appWindow.webContents.methods.configTemplate.clearAll(); } }); const templates = store.get('config-templates', []); if (templates.length > 0) { templates.forEach((value) => { templateSubChildItems.push(new MenuItem({ label: value.name, click: () => { appWindow.webContents.methods.configTemplate.load(value); } })); }); templateSubChildItems.push(separatorItem); templateSubChildItems.push(clearAllTemplateChildItem); } else templateSubChildItems.push(new MenuItem({ label: locale.i18n.nothing, enabled: false })); const savedTemplateChildItem = new MenuItem({ label: locale.i18n.savedTemplate, submenu: templateSubChildItems }); const configTemplateChildItem = new MenuItem({ label: locale.i18n.configTemplate, submenu: [newTemplateChildItem, savedTemplateChildItem] }); const resetCurrentConfigChildItem = new MenuItem({ label: locale.i18n.resetCurrentConfig, accelerator: system.isMacOS ? 'command+r' : 'ctrl+r', click: () => { appWindow.webContents.methods.page.restore(); } }); const fileMainItem = new MenuItem({ label: system.isMacOS ? app.name : locale.i18n.file, submenu: system.isMacOS ? [aboutChildItem, separatorItem, checkUpdateChildItem, separatorItem, projectAddressChildItem, helpDocumentationChildItem, separatorItem, openSourceChildItem, separatorItem, languageChildItem, separatorItem, quitChildItem] : [languageChildItem, separatorItem, quitChildItem] }); const projectMainItem = new MenuItem({ label: locale.i18n.project, submenu: [runBuildChildItem, separatorItem, configTemplateChildItem, resetCurrentConfigChildItem] }); const helpMainItem = new MenuItem({ label: (locale.i18n.help), submenu: [aboutChildItem, separatorItem, checkUpdateChildItem, separatorItem, projectAddressChildItem, helpDocumentationChildItem, separatorItem, openSourceChildItem] }); menu.append(fileMainItem); menu.append(projectMainItem); if (!system.isMacOS) menu.append(helpMainItem); Menu.setApplicationMenu(menu); }, /** * 注册 ipc 监听 * @param option 回调选项 */ registerIpcListener: (option) => { ipcMain.on('run-packaging-project', (_, configs) => { option['run-packaging-project'](configs); }); ipcMain.on('open-complete-project', () => { option['open-complete-project'](); }); ipcMain.on('save-config-template', (_, configs) => { const templates = store.get('config-templates', []); templates.push(configs); store.set('config-templates', templates); appSpace.createMenu(); }); ipcMain.on('clear-all-config-template', () => { store.set('config-templates', []); appSpace.createMenu(); }); ipcMain.on('reload-current-page', () => { setTimeout(() => { mainWindow.reload(); appWindow.webContents.methods.page.init(localeConfig.currentLocale(), app.getVersion()); }, 150); }); ipcMain.on('relaunch-app', () => { mainWindow.close(); app.relaunch(); }); ipcMain.on('open-system-browser', (_, url) => { system.openBrowser(url); }); } }; /** * 应用程序配置命名空间 */ const appConfig = { /** * 项目根路径 * @return string */ sourcePath: app.isPackaged ? app.getAppPath() : '.', /** * 项目外部路径 * @return string */ dataPath: fileSystem.path(app.getPath('appData'), 'YukiHookAPI'), /** 创建应用数据目录 */ createDataDir: () => { const dataPath = appConfig.dataPath; if (!fileSystem.exists(dataPath)) fileSystem.mkdir(dataPath); } }; /** * 语言区域配置命名空间 */ const localeConfig = { /** * 语言是否跟随系统 * @return boolean */ isFollowSystem: store.get('locale-follow-system', true), /** * 当前存储的语言名称 * @return string */ storeLocaleName: store.get('store-locale-name', locale.default), /** * 获取当前语言区域名称 * @return string */ currentLocale: () => { return localeConfig.isFollowSystem ? app.getLocale() : localeConfig.storeLocaleName; }, /** * 修改语言 * @param name 语言名称 */ changeLocale: (name = 'default') => { if (name === 'default') { store.set('locale-follow-system', true); store.set('store-locale-name', locale.default); } else { store.set('locale-follow-system', false); store.set('store-locale-name', name); } appWindow.webContents.methods.page.relaunch(); } }; /** * 应用程序窗口命名空间 */ const appWindow = { /** * 打开目录 * @param option 选项 * @param callback 回调结果 * @param onError 回调错误 */ showOpenDirectory: (option, callback, onError) => { dialog.showOpenDialog(mainWindow, { properties: ['openDirectory'], title: option['title'], message: option['message'], buttonLabel: option['buttonLabel'] }).then(result => { callback(result); }).catch((e) => { onError(e); }); }, /** 窗口内网页交互调用桥 */ webContents: { /** * 执行内部 JS * @param code 执行代码 */ execJs: (code) => { mainWindow.webContents.executeJavaScript(code).then(() => null); }, methods: { configTemplate: { load: (configs) => { appWindow.webContents.execJs('configTemplate.load(\'' + encodeURI(JSON.stringify(configs)) + '\')'); }, createNew: () => { appWindow.webContents.execJs('configTemplate.createNew()'); }, clearAll: () => { appWindow.webContents.execJs('configTemplate.clearAll()'); } }, page: { init: (localeName, appVersion) => { appWindow.webContents.execJs('page.init(\'' + localeName + '\',\'' + appVersion + '\')'); }, snack: (message) => { appWindow.webContents.execJs('page.snack(\'' + message + '\')'); }, showAbout: (name, version) => { appWindow.webContents.execJs('page.showAbout(\'' + name + '\',\'' + version + '\')'); }, showOpenSource: () => { appWindow.webContents.execJs('page.showOpenSource()'); }, checkForUpdates: () => { appWindow.webContents.execJs('page.checkForUpdates()'); }, restore: () => { appWindow.webContents.execJs('page.restore()'); }, relaunch: () => { appWindow.webContents.execJs('page.relaunch()'); } }, build: { checking: () => { appWindow.webContents.execJs('build.checking()'); }, changeStatus: (msg) => { appWindow.webContents.execJs('build.changeStatus(\'' + msg + '\')'); }, complete: (msg) => { appWindow.webContents.execJs('build.complete(\'' + msg + '\')'); }, failure: (msg) => { appWindow.webContents.execJs('build.failure(\'' + msg + '\')'); }, cancel: () => { appWindow.webContents.execJs('build.cancel()'); } } } } }; module.exports = {appSpace, appWindow, appConfig, localeConfig};