你好 👋,Electron!

 现在 sql2 可以打包 electron 应用了!
 electron linux:在 gnome 环境下可选“自动跟随 GTK 主题”功能(实验性),程序将会尝试获取 GTK 主题的配色并应用
💄 一点点布局错误
This commit is contained in:
stapxs 2023-02-08 13:06:22 +08:00
parent b9ea89a3e5
commit bd07f9dc0f
14 changed files with 3559 additions and 124 deletions

3
.gitignore vendored
View File

@ -24,3 +24,6 @@ pnpm-debug.log*
# Others
src/pages/chat-view/ChatTest.vue
#Electron-builder output
/dist_electron

View File

@ -5,14 +5,21 @@
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
"lint": "vue-cli-service lint",
"electron:build": "vue-cli-service electron:build",
"electron:serve": "vue-cli-service electron:serve",
"postinstall": "electron-builder install-app-deps",
"postuninstall": "electron-builder install-app-deps"
},
"main": "background.js",
"dependencies": {
"@jakejarrett/gtk-theme": "^2.0.1",
"@types/semver-compare": "^1.0.1",
"char-dust": "^0.1.0",
"core-js": "^3.8.3",
"date-format": "^4.0.13",
"detect-browser": "^5.3.0",
"electron-window-state": "^5.0.3",
"jquery": "^3.6.0",
"js-file-downloader": "^1.1.24",
"layui": "^2.8.0-beta.2",
@ -21,6 +28,7 @@
"semver-compare": "^1.0.0",
"spacingjs": "^1.0.7",
"svg-sprite-loader": "^6.0.11",
"ts-loader": "~8.2.0",
"ts-md5": "^1.3.1",
"uuidv4": "^6.2.13",
"v-viewer": "^3.0.11",
@ -34,6 +42,8 @@
"xss": "^1.0.14"
},
"devDependencies": {
"@types/css": "^0.0.33",
"@types/electron-devtools-installer": "^2.2.0",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"@typescript-eslint/parser": "^5.4.0",
"@vue/cli-plugin-babel": "~5.0.0",
@ -42,8 +52,12 @@
"@vue/cli-plugin-typescript": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"@vue/eslint-config-typescript": "^9.1.0",
"css": "^3.0.0",
"electron": "^13.0.0",
"electron-devtools-installer": "^3.1.0",
"eslint": "^7.32.0",
"eslint-plugin-vue": "^8.0.3",
"typescript": "~4.5.5"
"typescript": "~4.5.5",
"vue-cli-plugin-electron-builder": "~2.1.1"
}
}

View File

@ -202,7 +202,7 @@ import { Connector, login as loginInfo } from '@/function/connect'
import { Logger, popList, PopInfo } from '@/function/base'
import { runtimeData } from '@/function/msg'
import { BaseChatInfoElem } from '@/function/elements/information'
import { loadHistory, getTrueLang, gitmojiToEmoji, openLink } from '@/function/util'
import { loadHistory, getTrueLang, gitmojiToEmoji, openLink, loadSystemThemeColor } from '@/function/util'
import { DomainConfig, useState } from 'vue-gtag-next'
import Options from '@/pages/Options.vue'
@ -416,6 +416,9 @@ export default defineComponent({
if(runtimeData.sysConfig.auto_connect == true) {
this.connect()
}
//
runtimeData.tags.isElectron = (process.env.IS_ELECTRON as unknown) as boolean
Option.runAS('opt_auto_gtk', Option.get('opt_auto_gtk'))
//
logger.debug(this.$t('log_welcome'))
logger.debug(this.$t('log_runtime') + ': ' + process.env.NODE_ENV)

View File

@ -192,6 +192,9 @@
padding: 0;
flex: 1;
}
.opt-body::-webkit-scrollbar {
display: none;
}
.opt-body > div {
height: 100%;

View File

@ -262,5 +262,7 @@
"chat_msg_menu_remove": "移出群聊",
"sys_notice_new_group_nmember": "入群申请",
"sys_notice_new_group_nmember_add": "申请加入:",
"sys_notice_new_group_nmember_no_comment": "(没有个人介绍)"
"sys_notice_new_group_nmember_no_comment": "(没有个人介绍)",
"option_view_auto_gtk": "自动跟随 GTK 主题",
"option_view_auto_gtk_tip": "(实验性)自动从 GTK 配置获取主题配色"
}

109
src/background.ts Normal file
View File

@ -0,0 +1,109 @@
'use strict'
import windowStateKeeper from 'electron-window-state'
import os from 'os'
import { ipcMain, Menu } from 'electron'
import { app, protocol, BrowserWindow } from 'electron'
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib'
import installExtension, { VUEJS3_DEVTOOLS } from 'electron-devtools-installer'
import { GtkTheme, GtkData } from '@jakejarrett/gtk-theme'
const isDevelopment = process.env.NODE_ENV !== 'production'
protocol.registerSchemesAsPrivileged([
{ scheme: 'app', privileges: { secure: true, standard: true } }
])
let win = undefined as BrowserWindow | undefined
async function createWindow() {
ipcMain.handle('opt:getSystemInfo', () => {
const systemInfo = {} as { [key: string]: any }
systemInfo.electron = process.versions.electron
systemInfo.os = os.homedir()
return systemInfo
})
ipcMain.handle('sys:getGTKTheme', () => {
const gtkTheme = new GtkTheme({events: {
themeChange: (data: GtkData) => {
console.log('GTK 主题修改:' + data.name)
const info = {} as {[key:string]:any}
info.name = data.name
info.css = data.gtk.css
if(win) {
win.webContents.send('sys:updateGTKTheme', info)
}
}
}})
return gtkTheme.getTheme().gtk.css
})
ipcMain.on('win:openDevTools', () => {
if(win) {
win.webContents.openDevTools()
}
})
let mainWindowState = windowStateKeeper({
defaultWidth: 1200,
defaultHeight: 800
})
win = new BrowserWindow({
x: mainWindowState.x,
y: mainWindowState.y,
width: mainWindowState.width,
height: mainWindowState.height,
// frame: false,
webPreferences: {
nodeIntegration: true,
contextIsolation: false
}
})
if (process.env.WEBPACK_DEV_SERVER_URL) {
await win.loadURL(process.env.WEBPACK_DEV_SERVER_URL as string)
if (!process.env.IS_TEST) win.webContents.openDevTools()
} else {
createProtocol('app')
win.loadURL('app://./index.html')
}
mainWindowState.manage(win)
}
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
app.on('ready', async () => {
if (isDevelopment && !process.env.IS_TEST) {
try {
await installExtension(VUEJS3_DEVTOOLS)
} catch (e: any) {
console.error('Vue Devtools failed to install:', e.toString())
}
}
Menu.setApplicationMenu(null)
createWindow()
})
if (isDevelopment) {
if (process.platform === 'win32') {
process.on('message', (data) => {
if (data === 'graceful-exit') {
app.quit()
}
})
} else {
process.on('SIGTERM', () => {
app.quit()
})
}
}

View File

@ -24,7 +24,8 @@ export interface RunTimeDataElem {
show?: boolean,
index: number
},
loginWaveTimer?: any
loginWaveTimer?: any,
isElectron: boolean
},
messageList: any[]
mergeMessageList?: [],

View File

@ -798,7 +798,8 @@ const baseRuntime = {
canLoadHistory: true,
openSideBar: false,
viewer: { index: 0 },
msgType: BotMsgType.JSON
msgType: BotMsgType.JSON,
isElectron: false
},
chatInfo: {
show: { type: '', id: 0, name: '', avatar: '' },

View File

@ -17,7 +17,7 @@ import { i18n } from '@/main'
import { markRaw, defineAsyncComponent } from 'vue'
import { Logger, LogType } from './base'
import { runtimeData } from './msg'
import { initUITest, getTrueLang } from './util'
import { initUITest, getTrueLang, loadSystemThemeColor } from './util'
let cacheConfigs: { [key: string]: any }
@ -41,7 +41,14 @@ const configFunction: { [key: string]: (value: any) => void } = {
ui_test: changeUiTest,
chatview_name: changeChatView,
initial_scale: changeInitialScale,
msg_type: setMsgType
msg_type: setMsgType,
opt_auto_gtk: updateGTKColor
}
function updateGTKColor(value: boolean) {
if(value == true) {
loadSystemThemeColor()
}
}
function setMsgType(value: any) {

View File

@ -15,12 +15,13 @@ import l10nConfig from '@/assets/l10n/_l10nconfig.json'
import zh from '@/assets/l10n/zh-CN.json'
import FileDownloader from 'js-file-downloader'
import { Rule, Stylesheet, Declaration } from 'css'
import { Logger, PopInfo, PopType } from './base'
import { MsgIdInfoElem } from './elements/system'
import { runtimeData } from './msg'
import { BaseChatInfoElem } from './elements/information'
import { Connector } from './connect'
import option from './option'
const logger = new Logger()
const popInfo = new PopInfo()
@ -498,6 +499,95 @@ export function downloadFile (url: string, name: string, onprocess: (event: Prog
})
}
/**
* 使 gtk CSS Border Card UI
* @param cssStr css
*/
function updateGTKTheme(cssStr: string) {
if(option.get('log_level') == 'debug') {
console.log(cssStr)
}
const css = window.require('css')
let cssObj = undefined
let color = '#000'
// color-main
color = cssStr.substring(cssStr.indexOf('@define-color theme_fg_color') + 29)
color = color.substring(0, color.indexOf(';'))
document.documentElement.style.setProperty('--color-main', color)
// color-bg
color = cssStr.substring(cssStr.indexOf('@define-color theme_bg_color') + 29)
color = color.substring(0, color.indexOf(';'))
document.documentElement.style.setProperty('--color-bg', color)
// color-card
color = cssStr.substring(cssStr.indexOf('.context-menu {'))
color = color.substring(0, color.indexOf('}') + 1)
cssObj = css.parse(color, {silent: true}) as Stylesheet
if(cssObj.stylesheet) {
const colorGet = ((cssObj.stylesheet.rules[0] as Rule).declarations?.filter((item: Declaration) => {
return item.property == 'background-color'
})[0] as Declaration).value
if(colorGet) {
document.documentElement.style
.setProperty('--color-card', colorGet)
}
}
// color-card-1
color = cssStr.substring(cssStr.indexOf('.context-menu .view:selected {'))
color = color.substring(0, color.indexOf('}') + 1)
cssObj = css.parse(color, {silent: true}) as Stylesheet
if(cssObj.stylesheet) {
const colorGet = ((cssObj.stylesheet.rules[0] as Rule).declarations?.filter((item: Declaration) => {
return item.property == 'background-color'
})[0] as Declaration).value
if(colorGet) {
document.documentElement.style
.setProperty('--color-card-1', colorGet)
}
}
// color-card-2
color = cssStr.substring(cssStr.indexOf('.context-menu menuitem:hover {'))
color = color.substring(0, color.indexOf('}') + 1)
cssObj = css.parse(color, {silent: true}) as Stylesheet
if(cssObj.stylesheet) {
const colorGet = ((cssObj.stylesheet.rules[0] as Rule).declarations?.filter((item: Declaration) => {
return item.property == 'background-color'
})[0] as Declaration).value
if(colorGet) {
document.documentElement.style
.setProperty('--color-card-2', colorGet)
}
}
// color-font
color = cssStr.substring(cssStr.indexOf('@define-color theme_text_color') + 31)
color = color.substring(0, color.indexOf(';'))
document.documentElement.style.setProperty('--color-font', color)
// color-font-1
color = cssStr.substring(cssStr.indexOf('@define-color theme_unfocused_text_color') + 41)
color = color.substring(0, color.indexOf(';'))
document.documentElement.style.setProperty('--color-font-1', color)
document.documentElement.style.setProperty('--color-font-2', color)
}
/**
* electronu主题适配
*/
export async function loadSystemThemeColor() {
// 加载 GTK 主题适配(以及主题更新回调监听)
const electron = (process.env.IS_ELECTRON as any) === true ? window.require('electron') : null
const reader = electron ? electron.ipcRenderer : null
if (reader) {
// 主题更新回调
reader.on('sys:updateGTKTheme', (event, params) => {
if(option.get('opt_auto_gtk') == true) {
console.log('GTK 主题已更新:' + params.name)
updateGTKTheme(params.css)
}
})
updateGTKTheme(await reader.invoke('sys:getGTKTheme'))
}
}
export default {
openLink,
getTrueLang,
@ -512,5 +602,6 @@ export default {
gitmojiToEmoji,
randomNum,
downloadFile,
getSizeFromBytes
getSizeFromBytes,
loadSystemThemeColor
}

View File

@ -259,8 +259,22 @@ export default defineComponent({
console.log('=========================')
console.log(runtimeData)
console.log('=========================')
const electron = (process.env.IS_ELECTRON as any) === true ? window.require('electron') : null
const reader = electron ? electron.ipcRenderer : null
if (reader) {
reader.send('win:openDevTools')
}
},
printVersionInfo() {
async printVersionInfo() {
// electron electron
let addInfo = undefined
const electron = (process.env.IS_ELECTRON as any) === true ? window.require('electron') : null
const reader = electron ? electron.ipcRenderer : null
if(reader) {
addInfo = await reader.invoke('opt:getSystemInfo')
}
const browser = detect() as BrowserInfo
let html = '<div class="debug-info">'
@ -273,6 +287,13 @@ export default defineComponent({
html += `<span>7 - ${runtimeData.botInfo.app_name}</span>`
html += `<span>8 - ${runtimeData.botInfo.app_version !== undefined ? runtimeData.botInfo.app_version : runtimeData.botInfo.version}</span>`
html += `<span>9 - ${document.getElementById('app')?.offsetWidth} px</span>`
const lastIndex = 9
if(addInfo) {
const info = addInfo as {[key: string]: any}
Object.keys(info).forEach((name: string, index) => {
html += `<span>${lastIndex + index + 1} - ${info[name]}</span>`
})
}
html += '</div>'
// popBox
const popInfo = {

View File

@ -38,58 +38,79 @@
</div>
<div class="ss-card">
<header>{{ $t('option_view_theme') }}</header>
<div class="opt-item" id="opt_view_dark">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512">
<path
d="M223.5 32C100 32 0 132.3 0 256S100 480 223.5 480c60.6 0 115.5-24.2 155.8-63.4c5-4.9 6.3-12.5 3.1-18.7s-10.1-9.7-17-8.5c-9.8 1.7-19.8 2.6-30.1 2.6c-96.9 0-175.5-78.8-175.5-176c0-65.8 36-123.1 89.3-153.3c6.1-3.5 9.2-10.5 7.7-17.3s-7.3-11.9-14.3-12.5c-6.3-.5-12.6-.8-19-.8z" />
</svg>
<div>
<span>{{ $t('option_view_dark_mode') }}</span>
<span>{{ $t('option_view_dark_mode_tip') }}</span>
</div>
<label class="ss-switch">
<input type="checkbox" @change="save" name="opt_dark" v-model="runtimeData.sysConfig.opt_dark">
<template v-if="!runtimeData.sysConfig.opt_auto_gtk">
<div class="opt-item" id="opt_view_dark">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512">
<path
d="M223.5 32C100 32 0 132.3 0 256S100 480 223.5 480c60.6 0 115.5-24.2 155.8-63.4c5-4.9 6.3-12.5 3.1-18.7s-10.1-9.7-17-8.5c-9.8 1.7-19.8 2.6-30.1 2.6c-96.9 0-175.5-78.8-175.5-176c0-65.8 36-123.1 89.3-153.3c6.1-3.5 9.2-10.5 7.7-17.3s-7.3-11.9-14.3-12.5c-6.3-.5-12.6-.8-19-.8z" />
</svg>
<div>
<div></div>
<span>{{ $t('option_view_dark_mode') }}</span>
<span>{{ $t('option_view_dark_mode_tip') }}</span>
</div>
</label>
</div>
<div class="opt-item">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512">
<path
d="M192 64C86 64 0 150 0 256S86 448 192 448H384c106 0 192-86 192-192s-86-192-192-192H192zM384 352c-53 0-96-43-96-96s43-96 96-96s96 43 96 96s-43 96-96 96z" />
</svg>
<div>
<span>{{ $t('option_view_auto_dark') }}</span>
<span>{{ $t('option_view_auto_dark_tip') }}</span>
</div>
<label class="ss-switch">
<input type="checkbox" @change="save" name="opt_auto_dark"
v-model="runtimeData.sysConfig.opt_auto_dark">
<div>
<div></div>
</div>
</label>
</div>
<div class="opt-item">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512">
<path
d="M192 64L160 0H128L96 64 64 0H48C21.5 0 0 21.5 0 48V256H384V48c0-26.5-21.5-48-48-48H224L192 64zM0 288v32c0 35.3 28.7 64 64 64h64v64c0 35.3 28.7 64 64 64s64-28.7 64-64V384h64c35.3 0 64-28.7 64-64V288H0zM192 464c-8.8 0-16-7.2-16-16s7.2-16 16-16s16 7.2 16 16s-7.2 16-16 16z" />
</svg>
<div>
<span>{{ $t('option_view_theme_color') }}</span>
<span>{{ $t('option_view_theme_color_tip') }}</span>
</div>
<div class="theme-color-col">
<label v-for="(name, index) in colors" :title="name" :key="'color_id_' + index" class="ss-radio">
<input type="radio" name="theme_color" @change="save" :data-id="index"
:checked="runtimeData.sysConfig.theme_color === undefined ? index === 0 : Number(runtimeData.sysConfig.theme_color) === index">
<div :style="'background: var(--color-main-' + index + ');'">
<label class="ss-switch">
<input type="checkbox" @change="save" name="opt_dark" v-model="runtimeData.sysConfig.opt_dark">
<div>
<div></div>
</div>
</label>
</div>
</div>
<div class="opt-item">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512">
<path
d="M192 64C86 64 0 150 0 256S86 448 192 448H384c106 0 192-86 192-192s-86-192-192-192H192zM384 352c-53 0-96-43-96-96s43-96 96-96s96 43 96 96s-43 96-96 96z" />
</svg>
<div>
<span>{{ $t('option_view_auto_dark') }}</span>
<span>{{ $t('option_view_auto_dark_tip') }}</span>
</div>
<label class="ss-switch">
<input type="checkbox" @change="save" name="opt_auto_dark"
v-model="runtimeData.sysConfig.opt_auto_dark">
<div>
<div></div>
</div>
</label>
</div>
<div class="opt-item">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512">
<path
d="M192 64L160 0H128L96 64 64 0H48C21.5 0 0 21.5 0 48V256H384V48c0-26.5-21.5-48-48-48H224L192 64zM0 288v32c0 35.3 28.7 64 64 64h64v64c0 35.3 28.7 64 64 64s64-28.7 64-64V384h64c35.3 0 64-28.7 64-64V288H0zM192 464c-8.8 0-16-7.2-16-16s7.2-16 16-16s16 7.2 16 16s-7.2 16-16 16z" />
</svg>
<div>
<span>{{ $t('option_view_theme_color') }}</span>
<span>{{ $t('option_view_theme_color_tip') }}</span>
</div>
<div class="theme-color-col">
<label v-for="(name, index) in colors" :title="name" :key="'color_id_' + index" class="ss-radio">
<input type="radio" name="theme_color" @change="save" :data-id="index"
:checked="runtimeData.sysConfig.theme_color === undefined ? index === 0 : Number(runtimeData.sysConfig.theme_color) === index">
<div :style="'background: var(--color-main-' + index + ');'">
<div></div>
</div>
</label>
</div>
</div>
</template>
<template v-if="process.env.IS_ELECTRON">
<div class="opt-item">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512">
<path
d="M192 64C86 64 0 150 0 256S86 448 192 448H384c106 0 192-86 192-192s-86-192-192-192H192zM384 352c-53 0-96-43-96-96s43-96 96-96s96 43 96 96s-43 96-96 96z" />
</svg>
<div>
<span>{{ $t('option_view_auto_gtk') }}</span>
<span>{{ $t('option_view_auto_gtk_tip') }}</span>
</div>
<label class="ss-switch">
<input type="checkbox" @change="save" name="opt_auto_gtk"
v-model="runtimeData.sysConfig.opt_auto_gtk">
<div>
<div></div>
</div>
</label>
</div>
</template>
</div>
<div class="ss-card">
<header>{{ $t('option_view_view') }}</header>
@ -122,6 +143,7 @@ export default defineComponent({
name: 'ViewOptTheme',
data() {
return {
process: process,
get: get,
runtimeData: runtimeData,
save: save,

3292
yarn.lock

File diff suppressed because it is too large Load Diff