From 11045cd4ded3a241eaefb70c4fe2f7ea85248abb Mon Sep 17 00:00:00 2001 From: ModStart Date: Wed, 25 Dec 2024 16:10:43 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E6=96=B0=E5=A2=9E=EF=BC=9A=E5=B7=A5?= =?UTF-8?q?=E5=8D=95=E5=8F=8D=E9=A6=88=E5=8A=9F=E8=83=BD=EF=BC=8C=E4=BE=BF?= =?UTF-8?q?=E4=BA=8E=E8=A7=A3=E5=86=B3=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- electron/config/window.ts | 2 + electron/mapi/app/main.ts | 15 +++- electron/mapi/app/render.ts | 13 ++++ electron/mapi/file/index.ts | 42 ++++++++++- electron/mapi/log/index.ts | 71 ++++++++++++++++++- electron/mapi/log/render.ts | 1 + electron/page/feedback.ts | 34 +++++++++ electron/page/index.ts | 2 + page/feedback.html | 14 ++++ src/components/Setting/SettingAbout.vue | 2 +- .../common/FeedbackTicketButton.vue | 18 +++++ src/config.ts | 2 +- src/declarations/type.d.ts | 7 ++ src/entry/feedback.ts | 34 +++++++++ src/pages/PageAbout.vue | 10 ++- src/pages/PageFeedback.vue | 67 +++++++++++++++++ vite.config.ts | 1 + 17 files changed, 323 insertions(+), 12 deletions(-) create mode 100644 electron/page/feedback.ts create mode 100644 page/feedback.html create mode 100644 src/components/common/FeedbackTicketButton.vue create mode 100644 src/entry/feedback.ts create mode 100644 src/pages/PageFeedback.vue diff --git a/electron/config/window.ts b/electron/config/window.ts index ba701905..cf3ae036 100644 --- a/electron/config/window.ts +++ b/electron/config/window.ts @@ -8,6 +8,8 @@ export const WindowConfig = { guideHeight:540, aboutWidth: 500, aboutHeight: 400, + feedbackWidth: 600, + feedbackHeight: 600, setupWidth: 800, setupHeight: 540, } diff --git a/electron/mapi/app/main.ts b/electron/mapi/app/main.ts index 7d1eb4a4..c735b099 100644 --- a/electron/mapi/app/main.ts +++ b/electron/mapi/app/main.ts @@ -1,7 +1,7 @@ -import {app, BrowserWindow, ipcMain, screen, shell, clipboard, nativeImage, nativeTheme} from "electron"; +import {app, BrowserWindow, clipboard, ipcMain, nativeImage, nativeTheme, screen, shell} from "electron"; import {WindowConfig} from "../../config/window"; import {AppRuntime} from "../env"; -import {isDev, isMac} from "../../lib/env"; +import {isDev, isMac, platformArch, platformName} from "../../lib/env"; import {AppPosition} from "./lib/position"; import {Events} from "../event/main"; import {ConfigMain} from "../config/main"; @@ -315,6 +315,17 @@ ipcMain.handle('app:getBuildInfo', async () => { return getBuildInfo() }) +const collect = async (options?: {}) => { + return { + platformName: platformName(), + platformArch: platformArch(), + } +} + +ipcMain.handle('app:collect', async (event, options?: {}) => { + return collect(options) +}) + export default { quit } diff --git a/electron/mapi/app/render.ts b/electron/mapi/app/render.ts index 276df24d..458e9d9e 100644 --- a/electron/mapi/app/render.ts +++ b/electron/mapi/app/render.ts @@ -76,6 +76,13 @@ const appEnv = async () => { return AppEnv } +const setRenderAppEnv = (env: any) => { + AppEnv.isInit = true + AppEnv.appRoot = env.appRoot + AppEnv.appData = env.appData + AppEnv.userData = env.userData +} + const getClipboardText = () => { return ipcRenderer.invoke('app:getClipboardText') } @@ -112,6 +119,10 @@ const getBuildInfo = async () => { return ipcRenderer.invoke('app:getBuildInfo') } +const collect = async (options?: {}) => { + return ipcRenderer.invoke('app:collect', options) +} + export const AppsRender = { isDarkMode, resourcePathResolve, @@ -131,6 +142,7 @@ export const AppsRender = { openExternalWeb, getPreload, appEnv, + setRenderAppEnv, getClipboardText, setClipboardText, getClipboardImage, @@ -140,6 +152,7 @@ export const AppsRender = { setupOpen, setupIsOk, getBuildInfo, + collect, shell: appIndex.shell, spawnShell: appIndex.spawnShell, availablePort: appIndex.availablePort, diff --git a/electron/mapi/file/index.ts b/electron/mapi/file/index.ts index 2c8b31d1..088bfcc9 100644 --- a/electron/mapi/file/index.ts +++ b/electron/mapi/file/index.ts @@ -214,6 +214,43 @@ const readBuffer = async (path: string, option?: { isFullPath?: boolean, }): Pro }) } +const readLine = async (path: string, callback: (line: string) => void, option?: { + isFullPath?: boolean, +}) => { + option = Object.assign({ + isFullPath: false, + }, option) + let fp = path + if (!option.isFullPath) { + fp = await fullPath(path) + } + if (!fs.existsSync(fp)) { + return + } + return new Promise((resolve, reject) => { + const f = fs.createReadStream(fp) + let remaining = '' + f.on('data', (chunk) => { + remaining += chunk + let index = remaining.indexOf('\n') + let last = 0 + while (index > -1) { + let line = remaining.substring(last, index) + last = index + 1 + callback(line) + index = remaining.indexOf('\n', last) + } + remaining = remaining.substring(last) + }) + f.on('end', () => { + if (remaining.length > 0) { + callback(remaining) + } + resolve(undefined) + }) + }) +} + const deletes = async (path: string, option?: { isFullPath?: boolean, }) => { option = Object.assign({ isFullPath: false, @@ -516,7 +553,7 @@ const download = async (url: string, path: string, option?: { }) } -export default { +export const FileIndex = { fullPath, absolutePath, exists, @@ -528,6 +565,7 @@ export default { writeBuffer, read, readBuffer, + readLine, deletes, rename, copy, @@ -537,3 +575,5 @@ export default { appendText, download, } + +export default FileIndex diff --git a/electron/mapi/log/index.ts b/electron/mapi/log/index.ts index 4e9157e0..c369b518 100644 --- a/electron/mapi/log/index.ts +++ b/electron/mapi/log/index.ts @@ -3,6 +3,8 @@ import date from "date-and-time"; import path from "node:path"; import {AppEnv} from "../env"; import fs from "node:fs"; +import dayjs from "dayjs"; +import FileIndex from "../file"; let fileName = null let fileStream = null @@ -43,7 +45,7 @@ const cleanOldLogs = (keepDays: number) => { for (let file of files) { const filePath = path.join(logDir, file) let date = null - for (let s of file.split('_')) { + for (let s of file.split(/[_\\.]/)) { // 匹配 YYYYMMDD if (s.match(/^\d{8}$/)) { date = s @@ -117,6 +119,72 @@ const errorRenderOrMain = (label: string, data: any = null) => { } } +const collectRenderOrMain = async (option?: { + startTime?: string, + endTime?: string, + limit?: number, +}) => { + option = Object.assign({ + startTime: dayjs().subtract(1, 'day').format('YYYY-MM-DD HH:mm:ss'), + endTime: dayjs().format('YYYY-MM-DD HH:mm:ss'), + limit: 10 * 10000, + }, option) + let startMs = dayjs(option.startTime).valueOf() + let endMs = dayjs(option.endTime).valueOf() + let startDayMs = dayjs(option.startTime).startOf('day').valueOf() + let endDayMs = dayjs(option.endTime).endOf('day').valueOf() + let resultLines = [] + let logFiles = [] + logFiles = logFiles.concat(await FileIndex.list(logsDir(), {isFullPath: true})) + logFiles = logFiles.concat(await FileIndex.list(appLogsDir(), {isFullPath: true})) + // console.log('logFiles', logFiles) + logFiles = logFiles.filter((logFile) => { + if (logFile.isDirectory) { + return false + } + let date = null + for (let s of logFile.name.split(/[_\\.]/)) { + // 匹配 YYYYMMDD + if (s.match(/^\d{8}$/)) { + date = s + break + } + } + if (!date) { + return false + } + const fileDate = new Date( + parseInt(date.substring(0, 4)), + parseInt(date.substring(4, 6)) - 1, + parseInt(date.substring(6, 8)) + ) + if (fileDate.getTime() < startDayMs || fileDate.getTime() > endDayMs) { + return false + } + return true + }) + // console.log('collectRenderOrMain', { + // ...option, + // logFiles, startMs, endMs, startDayMs, endDayMs + // }) + for (const logFile of logFiles) { + await FileIndex.readLine(logFile.pathname, (line) => { + const lineParts = line.split(' - ') + const lineTime = dayjs(lineParts[0]) + // console.log('lineTime', lineParts[0], lineTime.isBefore(startMs) || lineTime.isAfter(endMs)) + if (lineTime.isBefore(startMs) || lineTime.isAfter(endMs)) { + return + } + resultLines.push(line) + }, {isFullPath: true}) + } + return { + startTime: option.startTime, + endTime: option.endTime, + logs: resultLines.join("\n"), + } +} + export default { root, @@ -124,6 +192,7 @@ export default { error, infoRenderOrMain, errorRenderOrMain, + collectRenderOrMain, } export const Log = { diff --git a/electron/mapi/log/render.ts b/electron/mapi/log/render.ts index c2d84ad2..63ef64f1 100644 --- a/electron/mapi/log/render.ts +++ b/electron/mapi/log/render.ts @@ -4,4 +4,5 @@ export default { root: logIndex.root, info: logIndex.infoRenderOrMain, error: logIndex.errorRenderOrMain, + collect: logIndex.collectRenderOrMain, } diff --git a/electron/page/feedback.ts b/electron/page/feedback.ts new file mode 100644 index 00000000..901b6f6f --- /dev/null +++ b/electron/page/feedback.ts @@ -0,0 +1,34 @@ +import {BrowserWindow} from "electron"; +import {preloadDefault} from "../lib/env-main"; +import {AppRuntime} from "../mapi/env"; +import {t} from "../config/lang"; +import {Page} from "./index"; +import {WindowConfig} from "../config/window"; + +export const PageFeedback = { + NAME: 'feedback', + open: async (option: any) => { + const win = new BrowserWindow({ + title: t('工单反馈'), + parent: AppRuntime.mainWindow, + minWidth: WindowConfig.feedbackWidth, + minHeight: WindowConfig.feedbackHeight, + width: WindowConfig.feedbackWidth, + height: WindowConfig.feedbackHeight, + webPreferences: { + preload: preloadDefault, + // Warning: Enable nodeIntegration and disable contextIsolation is not secure in production + nodeIntegration: true, + webSecurity: false, + webviewTag: true, + // Consider using contextBridge.exposeInMainWorld + // Read more on https://www.electronjs.org/docs/latest/tutorial/context-isolation + contextIsolation: false, + }, + show: true, + frame: false, + transparent: false, + }); + return Page.openWindow(PageFeedback.NAME, win, "page/feedback.html"); + } +} diff --git a/electron/page/index.ts b/electron/page/index.ts index 99967c53..b3fe1fc1 100644 --- a/electron/page/index.ts +++ b/electron/page/index.ts @@ -7,12 +7,14 @@ import {rendererLoadPath} from "../lib/env-main"; import {PageGuide} from "./guide"; import {PageSetup} from "./setup"; import {DevToolsManager} from "../lib/devtools"; +import {PageFeedback} from "./feedback"; const Pages = { 'thirdPartyImageBeautifier': PageThirdPartyImageBeautifier, 'user': PageUser, 'guide': PageGuide, 'setup': PageSetup, + 'feedback': PageFeedback, } export const Page = { diff --git a/page/feedback.html b/page/feedback.html new file mode 100644 index 00000000..9b141da9 --- /dev/null +++ b/page/feedback.html @@ -0,0 +1,14 @@ + + + + + + + %name% + + + +
+ + + diff --git a/src/components/Setting/SettingAbout.vue b/src/components/Setting/SettingAbout.vue index e1554e51..5c248295 100644 --- a/src/components/Setting/SettingAbout.vue +++ b/src/components/Setting/SettingAbout.vue @@ -38,7 +38,7 @@ const doOpenLog = async () => { target="_blank" class="align-top arco-btn arco-btn-secondary arco-btn-shape-square arco-btn-size-mini arco-btn-status-normal ml-3"> - {{ t('使用反馈') }} + {{ t('工单反馈') }} +import {AppConfig} from "../../config"; + +const doShow = () => { + window.$mapi.app.windowOpen('feedback') +} + + + diff --git a/src/config.ts b/src/config.ts index 3366c7ef..77524690 100644 --- a/src/config.ts +++ b/src/config.ts @@ -13,7 +13,7 @@ export const AppConfig = { apiBaseUrl: `${BASE_URL}/api`, updaterUrl: `${BASE_URL}/app_manager/updater`, downloadUrl: `${BASE_URL}/app_manager/download`, - feedbackUrl: `${BASE_URL}/feedback`, + feedbackUrl: `${BASE_URL}/feedback_ticket`, statisticsUrl: `${BASE_URL}/app_manager/collect`, guideUrl: `${BASE_URL}/app_manager/guide`, helpUrl: `${BASE_URL}/app_manager/help`, diff --git a/src/declarations/type.d.ts b/src/declarations/type.d.ts index ce1e4167..40e9a3f5 100644 --- a/src/declarations/type.d.ts +++ b/src/declarations/type.d.ts @@ -40,6 +40,7 @@ declare interface Window { windowClose: (name?: string) => Promise, openExternalWeb: (url: string) => Promise, appEnv: () => Promise, + setRenderAppEnv: (env: any) => Promise, isDarkMode: () => Promise, shell: (command: string, option?: { cwd?: string, @@ -84,6 +85,7 @@ declare interface Window { getBuildInfo: () => Promise<{ buildTime: string, }>, + collect: (options?: {}) => Promise, }, config: { get: (key: string, defaultValue: any = null) => Promise, @@ -97,6 +99,11 @@ declare interface Window { root: () => string, info: (msg: string, data: any = null) => Promise, error: (msg: string, data: any = null) => Promise, + collect: (option?: { + startTime?: string, + endTime?: string, + limit?: number, + }) => Promise, }, storage: { all: () => Promise, diff --git a/src/entry/feedback.ts b/src/entry/feedback.ts new file mode 100644 index 00000000..c020fba2 --- /dev/null +++ b/src/entry/feedback.ts @@ -0,0 +1,34 @@ +import {createApp} from 'vue' +import store from "../store"; + +import ArcoVue, {Message} from '@arco-design/web-vue' +import ArcoVueIcon from '@arco-design/web-vue/es/icon' +import '@arco-design/web-vue/dist/arco.css' + +import {i18n, t} from "../lang"; + +import '../style.less' +import {Dialog} from "../lib/dialog"; + +import {CommonComponents} from "../components/common"; +import Page from "./Page.vue"; +import PageFeedback from "../pages/PageFeedback.vue"; + +const app = createApp(Page, { + name: 'feedback', + title: t('工单反馈'), + page: PageFeedback +}) +app.use(ArcoVue) +app.use(ArcoVueIcon) +app.use(CommonComponents) +app.use(i18n) +app.use(store) +Message._context = app._context +app.config.globalProperties.$mapi = window.$mapi +app.config.globalProperties.$dialog = Dialog +app.config.globalProperties.$t = t as any +app.mount('#app') + .$nextTick(() => { + postMessage({payload: 'removeLoading'}, '*') + }) diff --git a/src/pages/PageAbout.vue b/src/pages/PageAbout.vue index df6cd7c2..af7a1bda 100644 --- a/src/pages/PageAbout.vue +++ b/src/pages/PageAbout.vue @@ -4,6 +4,7 @@ import {AppConfig} from "../config"; import {t} from "../lang"; import UpdaterButton from "../components/common/UpdaterButton.vue"; import {useSettingStore} from "../store/modules/setting"; +import FeedbackTicketButton from "../components/common/FeedbackTicketButton.vue"; const setting = useSettingStore() const licenseYear = new Date().getFullYear() @@ -66,12 +67,9 @@ const doDevSettingTriggerClick = () => {
- - - {{ t('使用反馈') }} - +
+ +
diff --git a/src/pages/PageFeedback.vue b/src/pages/PageFeedback.vue new file mode 100644 index 00000000..c9f3cad4 --- /dev/null +++ b/src/pages/PageFeedback.vue @@ -0,0 +1,67 @@ + + + + + diff --git a/vite.config.ts b/vite.config.ts index da9a2771..c982f0b7 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -133,6 +133,7 @@ export default defineConfig(({command}) => { input: { main: path.resolve(__dirname, 'index.html'), about: path.resolve(__dirname, 'page/about.html'), + feedback: path.resolve(__dirname, 'page/feedback.html'), user: path.resolve(__dirname, 'page/user.html'), guide: path.resolve(__dirname, 'page/guide.html'), setup: path.resolve(__dirname, 'page/setup.html'), From f8a720ba7c6210c442fe91349fe0d3d9c751a283 Mon Sep 17 00:00:00 2001 From: ModStart Date: Wed, 25 Dec 2024 16:37:53 +0800 Subject: [PATCH 2/4] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=EF=BC=9A=E5=8F=8D?= =?UTF-8?q?=E9=A6=88=E9=A1=B5=E9=9D=A2=E8=B0=83=E8=AF=95=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=E5=85=B3=E9=97=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- changelog.md | 4 ++++ package.json | 2 +- src/pages/PageFeedback.vue | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/changelog.md b/changelog.md index 0ea48def..2d5ca924 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,7 @@ +## v0.4.0 + +- 新增:工单反馈功能,便于解决问题 + ## v0.3.0 - 新增:支持设置投屏视频比特率和刷新率 diff --git a/package.json b/package.json index 39ef4d36..5aa67181 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "linkandroid", - "version": "0.3.0", + "version": "0.4.0-beta", "main": "dist-electron/main/index.js", "description": "Link android to PC easily", "author": "ModStartLib", diff --git a/src/pages/PageFeedback.vue b/src/pages/PageFeedback.vue index c9f3cad4..c8eb9001 100644 --- a/src/pages/PageFeedback.vue +++ b/src/pages/PageFeedback.vue @@ -26,7 +26,7 @@ onMounted(async () => { web.value.addEventListener('dom-ready', async (e) => { const appEnv = await window.$mapi.app.appEnv() web.value.executeJavaScript(`window.$mapi.app.setRenderAppEnv(${JSON.stringify(appEnv)})`) - web.value.openDevTools() + // web.value.openDevTools() window.$mapi.user.refresh() web.value.executeJavaScript(` document.addEventListener('click', (event) => { From 87a480aa864280b5dfb53a8c056008814885f635 Mon Sep 17 00:00:00 2001 From: ModStart Date: Fri, 27 Dec 2024 10:59:50 +0800 Subject: [PATCH 3/4] =?UTF-8?q?=E4=BC=98=E5=8C=96=EF=BC=9A=E8=AF=B7?= =?UTF-8?q?=E6=B1=82=E5=BC=82=E5=B8=B8=E6=97=B6=E5=A2=9E=E5=8A=A0=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- changelog.md | 1 + electron/lib/api.ts | 4 ++-- electron/mapi/user/main.ts | 6 ++++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/changelog.md b/changelog.md index 2d5ca924..4d411571 100644 --- a/changelog.md +++ b/changelog.md @@ -1,6 +1,7 @@ ## v0.4.0 - 新增:工单反馈功能,便于解决问题 +- 优化:请求异常时增加错误码 ## v0.3.0 diff --git a/electron/lib/api.ts b/electron/lib/api.ts index c22869a4..35775a79 100644 --- a/electron/lib/api.ts +++ b/electron/lib/api.ts @@ -2,9 +2,9 @@ import {AppConfig} from "../../src/config"; import Apps from "../mapi/app"; export type ResultType = { - code: boolean, + code: number, msg: string, - data: T + data?: T } export const post = async (url: string, data: any) => { diff --git a/electron/mapi/user/main.ts b/electron/mapi/user/main.ts index 9fcec853..a83f0a0d 100644 --- a/electron/mapi/user/main.ts +++ b/electron/mapi/user/main.ts @@ -157,6 +157,12 @@ const post = async (api: string, data: Record): Promise Date: Mon, 30 Dec 2024 13:50:39 +0800 Subject: [PATCH 4/4] =?UTF-8?q?=E6=96=B0=E5=A2=9E=EF=BC=9A=E5=BA=94?= =?UTF-8?q?=E7=94=A8=20loading=20=E7=AA=97=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- changelog.md | 1 + electron/mapi/app/icons.ts | 20 ++++ electron/mapi/app/loading.ts | 164 +++++++++++++++++++++++++++++++++ electron/mapi/app/main.ts | 17 ++++ electron/mapi/app/toast.ts | 8 +- electron/mapi/protocol/main.ts | 77 ++++++++++++++++ 6 files changed, 281 insertions(+), 6 deletions(-) create mode 100644 electron/mapi/app/icons.ts create mode 100644 electron/mapi/app/loading.ts create mode 100644 electron/mapi/protocol/main.ts diff --git a/changelog.md b/changelog.md index 4d411571..d01b52dc 100644 --- a/changelog.md +++ b/changelog.md @@ -1,6 +1,7 @@ ## v0.4.0 - 新增:工单反馈功能,便于解决问题 +- 新增:应用 loading 窗口 - 优化:请求异常时增加错误码 ## v0.3.0 diff --git a/electron/mapi/app/icons.ts b/electron/mapi/app/icons.ts new file mode 100644 index 00000000..c3f9ec5d --- /dev/null +++ b/electron/mapi/app/icons.ts @@ -0,0 +1,20 @@ +export const icons = { + success: '', + error: '', + info: '', + loading: ` + + + + + + + + + + + + + + `, +} diff --git a/electron/mapi/app/loading.ts b/electron/mapi/app/loading.ts new file mode 100644 index 00000000..4eff5f43 --- /dev/null +++ b/electron/mapi/app/loading.ts @@ -0,0 +1,164 @@ +import {BrowserWindow} from "electron"; +import {AppsMain} from "./main"; +import {icons} from "./icons"; + +export const makeLoading = (msg: string, options?: { + timeout?: number, + percentAuto?: boolean, + percentTotalSeconds?: number, +}): { + close: () => void, + percent: (value: number) => void +} => { + + options = Object.assign({ + percentAuto: false, + percentTotalSeconds: 30, + timeout: 0 + }, options) + + if (options.timeout === 0) { + options.timeout = 60 * 10 * 1000 + } + // console.log('options', options) + + const display = AppsMain.getCurrentScreenDisplay() + // console.log('xxxx', primaryDisplay); + const width = display.workArea.width + const height = 60 + const icon = icons.loading + + const win = new BrowserWindow({ + height, + width, + x: 0, + y: 0, + modal: false, + frame: false, + alwaysOnTop: true, + center: false, + transparent: true, + hasShadow: false, + show: false, + focusable: false, + skipTaskbar: true, + }) + const htmlContent = ` + + + + + + +
+
${icon}${msg}
+
+
+
+
+ + +`; + + const encodedHTML = encodeURIComponent(htmlContent); + let percentAutoTimer = null + win.loadURL(`data:text/html;charset=UTF-8,${encodedHTML}`); + win.on('ready-to-show', async () => { + const width = Math.ceil(await win.webContents.executeJavaScript(`(()=>{ + const message = document.getElementById('message'); + const width = message.scrollWidth; + return width; + })()`)) + win.setSize(width + 20, height) + const x = display.workArea.x + (display.workArea.width / 2) - ((width + 20) / 2) + const y = display.workArea.y + (display.workArea.height * 2 / 3) + win.setPosition(Math.floor(x), Math.floor(y)) + win.show() + if (options.percentAuto) { + let percent = 0 + percentAutoTimer = setInterval(() => { + percent += 0.01 + if (percent >= 1) { + clearInterval(percentAutoTimer) + return + } + controller.percent(percent) + }, options.percentTotalSeconds * 1000 / 100) + } + // win.webContents.openDevTools({ + // mode: 'detach' + // }) + }) + const winCloseTimer = setTimeout(() => { + win.close() + clearTimeout(winCloseTimer) + }, options.timeout) + const controller = { + close: () => { + win.close() + clearTimeout(winCloseTimer) + if (percentAutoTimer) { + clearInterval(percentAutoTimer) + } + }, + percent: (value: number) => { + const percent = 100 * value + win.webContents.executeJavaScript(`(()=>{ + const percent = document.querySelector('#percent'); + const percentValue = document.querySelector('#percent .value'); + percent.style.display = 'block'; + percentValue.style.width = '${percent}%'; + })()`) + } + } + return controller +} diff --git a/electron/mapi/app/main.ts b/electron/mapi/app/main.ts index c735b099..52feb2a9 100644 --- a/electron/mapi/app/main.ts +++ b/electron/mapi/app/main.ts @@ -11,6 +11,7 @@ import {Page} from "../../page"; import {makeToast} from "./toast"; import {SetupMain} from "./setup"; import {Files} from "../file/main"; +import {makeLoading} from "./loading"; const getWindowByName = (name?: string) => { @@ -282,6 +283,21 @@ ipcMain.handle('app:toast', (event, msg: string, option?: any) => { return toast(msg, option) }) +const loading = (msg: string, options?: { + timeout?: number, + percentAuto?: boolean, + percentTotalSeconds?: number, +}): { + close: () => void, + percent: (value: number) => void +} => { + return makeLoading(msg, options) +} + +ipcMain.handle('app:loading', (event, msg: string, option?: any) => { + return loading(msg, option) +}) + ipcMain.handle('app:setupList', async () => { return SetupMain.list() }) @@ -341,6 +357,7 @@ export const AppsMain = { getCurrentScreenDisplay, calcPositionInCurrentDisplay, toast, + loading, setupIsOk, windowOpen, } diff --git a/electron/mapi/app/toast.ts b/electron/mapi/app/toast.ts index 5a6adf04..c0f445ef 100644 --- a/electron/mapi/app/toast.ts +++ b/electron/mapi/app/toast.ts @@ -1,11 +1,7 @@ -import {BrowserWindow, screen} from "electron"; +import {BrowserWindow} from "electron"; import {AppsMain} from "./main"; +import {icons} from "./icons"; -const icons = { - success: '', - error: '', - info:'', -} let win = null let winCloseTimer = null diff --git a/electron/mapi/protocol/main.ts b/electron/mapi/protocol/main.ts new file mode 100644 index 00000000..f75d3bb6 --- /dev/null +++ b/electron/mapi/protocol/main.ts @@ -0,0 +1,77 @@ +import {Log} from "../log/main"; + +export const ProtocolMain = { + isReady: false, + ready() { + this.isReady = true + }, + url: null, + async queue(url: string) { + this.url = url + await this.runProtocol() + }, + async runProtocol() { + return new Promise(async (resolve) => { + const run = async () => { + if (!this.isReady) { + setTimeout(run, 100) + return + } + if (!this.url) { + Log.info('ProtocolMain.runProtocol.url.Empty', this.filePath) + return + } + const url = this.url + const urlInfo = new URL(url) + const command = urlInfo.hostname + const param = urlInfo.searchParams + Log.info('ProtocolMain.runProtocol', {command, param, url, urlInfo}) + if (!command) { + Log.info('ProtocolMain.runProtocol.command.Empty', url) + return + } + if (!this.commandListeners[command]) { + Log.info('ProtocolMain.runProtocol.command.NotFound', command) + return + } + for (const callback of this.commandListeners[command]) { + callback(Object.fromEntries(param.entries())) + } + resolve(undefined) + } + run().then() + }); + }, + commandListeners: {} as { + [command: string]: Array<(params: { + [key: string]: string + }) => void> + }, + register( + command: string, + callback: (params: { + [key: string]: string + }) => void, + ) { + if (!this.commandListeners[command]) { + this.commandListeners[command] = [] + } + this.commandListeners[command].push(callback) + }, + unregister( + command: string, + callback: (params: { + [key: string]: string + }) => void, + ) { + if (!this.commandListeners[command]) { + return + } + const index = this.commandListeners[command].indexOf(callback) + if (index >= 0) { + this.commandListeners[command].splice(index, 1) + } + }, +} + +export default ProtocolMain