diff --git a/package.json b/package.json index 50ffb7c3..da0cd34e 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,6 @@ "got": "^12.6.0", "highlight.js": "^11.9.0", "hpagent": "^1.2.0", - "keycode": "^2.2.0", "lowdb": "^1.0.0", "marked": "^9.1.5", "mime-types": "^2.1.35", diff --git a/public/i18n/en.yml b/public/i18n/en.yml index 45e5f579..e7ba04d2 100644 --- a/public/i18n/en.yml +++ b/public/i18n/en.yml @@ -2,6 +2,7 @@ LANG_DISPLAY_LABEL: 'English' ABOUT: About OPEN_MAIN_WINDOW: Open Main Window OPEN_MINI_WINDOW: Open Mini Window +HIDE_MINI_WINDOW: Hide Mini Window CHOOSE_DEFAULT_PICBED: Choose Default Picbed OPEN_UPDATE_HELPER: Open Update Helper RELOAD_APP: Reload App @@ -311,7 +312,7 @@ SETTINGS_SYNC_MANAGE_CONFIG: Manage configuration SETTINGS_AUTO_IMPORT: Auto import config in manage page SETTINGS_AUTO_IMPORT_SELECT_PICBED: Select picbed SETTINGS_TAB_SYSTEM: System -SETTINGS_TAB_SYNC_CONFIG: Sync and Configuration +SETTINGS_TAB_SYNC_CONFIG: Configuration SETTINGS_TAB_UPLOAD: Upload SETTINGS_TAB_ADVANCED: Advanced SETTINGS_TAB_UPDATE: Update diff --git a/public/i18n/zh-CN.yml b/public/i18n/zh-CN.yml index 9e7d199d..0ca38feb 100644 --- a/public/i18n/zh-CN.yml +++ b/public/i18n/zh-CN.yml @@ -2,6 +2,7 @@ LANG_DISPLAY_LABEL: 中文 ABOUT: 关于 OPEN_MAIN_WINDOW: 打开主窗口 OPEN_MINI_WINDOW: 打开mini窗口 +HIDE_MINI_WINDOW: 隐藏mini窗口 CHOOSE_DEFAULT_PICBED: 选择默认图床 OPEN_UPDATE_HELPER: 打开更新助手 RELOAD_APP: 重启应用 diff --git a/public/i18n/zh-TW.yml b/public/i18n/zh-TW.yml index 1adf2790..83793d0a 100644 --- a/public/i18n/zh-TW.yml +++ b/public/i18n/zh-TW.yml @@ -2,6 +2,7 @@ LANG_DISPLAY_LABEL: 繁體中文 ABOUT: 關於 OPEN_MAIN_WINDOW: 打開主視窗 OPEN_MINI_WINDOW: 打開mini視窗 +HIDE_MINI_WINDOW: 隱藏mini視窗 CHOOSE_DEFAULT_PICBED: 選擇預設圖床 OPEN_UPDATE_HELPER: 開啟更新助手 RELOAD_APP: 重啟程式 diff --git a/src/main.ts b/src/main.ts index 1ec9f80f..4fc3204f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -19,10 +19,9 @@ import db from '@/utils/db' import { T } from '@/i18n/index' import { store } from '@/store' import { initTalkingData } from '@/utils/analytic' -import { getConfig, saveConfig, triggerRPC } from '@/utils/dataSender' import { mainMixin } from '@/utils/mainMixin' import { dragMixin } from '@/utils/mixin' -import { sendToMain } from '@/utils/common' +import { sendRPC, sendToMain, triggerRPC } from '@/utils/common' webFrame.setVisualZoomLevelLimits(1, 1) @@ -30,16 +29,14 @@ const app = createApp(App) app.config.globalProperties.$$db = db app.config.globalProperties.$T = T -app.config.globalProperties.getConfig = getConfig app.config.globalProperties.triggerRPC = triggerRPC -app.config.globalProperties.saveConfig = saveConfig +app.config.globalProperties.sendRPC = sendRPC app.config.globalProperties.sendToMain = sendToMain app.mixin(mainMixin) app.mixin(dragMixin) const pinia = createPinia() pinia.use(piniaPluginPersistedstate) - app.use(VueLazyLoad, { error: `file://${__static.replace(/\\/g, '/')}/unknown-file-type.svg` }) @@ -52,5 +49,4 @@ console.log(hljsCommon.highlightAuto('

Highlight.js has been registered succe app.use(hljsVuePlugin) app.use(VueVideoPlayer) app.mount('#app') - initTalkingData() diff --git a/src/main/apis/app/system/index.ts b/src/main/apis/app/system/index.ts index 31a564b1..f315bac6 100644 --- a/src/main/apis/app/system/index.ts +++ b/src/main/apis/app/system/index.ts @@ -3,9 +3,10 @@ import { clipboard, dialog, Menu, + MenuItem, + MenuItemConstructorOptions, nativeTheme, Notification, - screen, Tray } from 'electron' import fs from 'fs-extra' @@ -29,23 +30,16 @@ import { configPaths } from '#/utils/configPaths' import { IPasteStyle, IWindowList } from '#/types/enum' import pkg from 'root/package.json' +import { hideMiniWindow, openMainWindow, openMiniWindow } from '~/utils/windowHelper' let contextMenu: Menu | null export function setDockMenu () { const isListeningClipboard = db.get(configPaths.settings.isListeningClipboard) || false - const autoCloseMiniWindow = db.get(configPaths.settings.autoCloseMiniWindow) || false const dockMenu = Menu.buildFromTemplate([ { label: T('OPEN_MAIN_WINDOW'), - click () { - const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW) - settingWindow!.show() - settingWindow!.focus() - if (windowManager.has(IWindowList.MINI_WINDOW) && autoCloseMiniWindow) { - windowManager.get(IWindowList.MINI_WINDOW)!.hide() - } - } + click: openMainWindow }, { label: T('START_WATCH_CLIPBOARD'), @@ -58,7 +52,7 @@ export function setDockMenu () { }) setDockMenu() }, - enabled: !isListeningClipboard + visible: !isListeningClipboard }, { label: T('STOP_WATCH_CLIPBOARD'), @@ -68,7 +62,7 @@ export function setDockMenu () { clipboardPoll.removeAllListeners() setDockMenu() }, - enabled: isListeningClipboard + visible: isListeningClipboard } ]) app.dock.setMenu(dockMenu) @@ -80,59 +74,27 @@ export function createMenu () { { label: 'PicList', submenu: [ - { - label: T('OPEN_MAIN_WINDOW'), - click () { - const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW) - const autoCloseMiniWindow = db.get(configPaths.settings.autoCloseMiniWindow) || false - settingWindow!.show() - settingWindow!.focus() - if (windowManager.has(IWindowList.MINI_WINDOW) && autoCloseMiniWindow) { - windowManager.get(IWindowList.MINI_WINDOW)!.hide() - } - } - }, - { - label: T('RELOAD_APP'), - click () { - app.relaunch() - app.exit(0) - } - } + { label: T('OPEN_MAIN_WINDOW'), click: openMainWindow }, + { label: T('RELOAD_APP'), click () { app.relaunch(); app.exit(0) } } ] }, - { - label: T('CHOOSE_DEFAULT_PICBED'), - type: 'submenu', - // @ts-ignore - submenu - }, + { label: T('CHOOSE_DEFAULT_PICBED'), type: 'submenu', submenu }, { label: 'Edit', - // @ts-ignore submenu: [ - // @ts-ignore - { label: 'Undo', accelerator: 'CmdOrCtrl+Z', selector: 'undo:' }, - // @ts-ignore - { label: 'Redo', accelerator: 'Shift+CmdOrCtrl+Z', selector: 'redo:' }, + { label: 'Undo', accelerator: 'CmdOrCtrl+Z', role: 'undo' }, + { label: 'Redo', accelerator: 'Shift+CmdOrCtrl+Z', role: 'redo' }, { type: 'separator' }, - // @ts-ignore - { label: 'Cut', accelerator: 'CmdOrCtrl+X', selector: 'cut:' }, - // @ts-ignore - { label: 'Copy', accelerator: 'CmdOrCtrl+C', selector: 'copy:' }, - // @ts-ignore - { label: 'Paste', accelerator: 'CmdOrCtrl+V', selector: 'paste:' }, - // @ts-ignore - { label: 'Select All', accelerator: 'CmdOrCtrl+A', selector: 'selectAll:' } + { label: 'Cut', accelerator: 'CmdOrCtrl+X', role: 'cut' }, + { label: 'Copy', accelerator: 'CmdOrCtrl+C', role: 'copy' }, + { label: 'Paste', accelerator: 'CmdOrCtrl+V', role: 'paste' }, + { label: 'Select All', accelerator: 'CmdOrCtrl+A', role: 'selectAll' } ] }, { label: T('QUIT'), submenu: [ - { - label: T('QUIT'), - role: 'quit' - } + { label: T('QUIT'), role: 'quit' } ] } ]) @@ -142,94 +104,39 @@ export function createMenu () { export function createContextMenu () { const ClipboardWatcher = clipboardPoll const isListeningClipboard = db.get(configPaths.settings.isListeningClipboard) || false + const isMiniWindowVisible = windowManager.has(IWindowList.MINI_WINDOW) && windowManager.get(IWindowList.MINI_WINDOW)!.isVisible() + + const startWatchClipboard = () => { + db.set(configPaths.settings.isListeningClipboard, true) + ClipboardWatcher.startListening() + ClipboardWatcher.on('change', () => { + picgo.log.info('clipboard changed') + uploadClipboardFiles() + }) + createContextMenu() + } + + const stopWatchClipboard = () => { + db.set(configPaths.settings.isListeningClipboard, false) + ClipboardWatcher.stopListening() + ClipboardWatcher.removeAllListeners() + createContextMenu() + } + if (process.platform === 'darwin' || process.platform === 'win32') { const submenu = buildPicBedListMenu() - const template = [ - { - label: T('OPEN_MAIN_WINDOW'), - click () { - const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW) - const autoCloseMiniWindow = db.get(configPaths.settings.autoCloseMiniWindow) || false - settingWindow!.show() - settingWindow!.focus() - if (windowManager.has(IWindowList.MINI_WINDOW) && autoCloseMiniWindow) { - windowManager.get(IWindowList.MINI_WINDOW)!.hide() - } - } - }, - { - label: T('CHOOSE_DEFAULT_PICBED'), - type: 'submenu', - // @ts-ignore - submenu - }, - { - label: T('START_WATCH_CLIPBOARD'), - click () { - db.set(configPaths.settings.isListeningClipboard, true) - ClipboardWatcher.startListening() - ClipboardWatcher.on('change', () => { - picgo.log.info('clipboard changed') - uploadClipboardFiles() - }) - createContextMenu() - }, - enabled: !isListeningClipboard - }, - { - label: T('STOP_WATCH_CLIPBOARD'), - click () { - db.set(configPaths.settings.isListeningClipboard, false) - ClipboardWatcher.stopListening() - ClipboardWatcher.removeAllListeners() - createContextMenu() - }, - enabled: isListeningClipboard - }, - { - label: T('RELOAD_APP'), - click () { - app.relaunch() - app.exit(0) - } - }, - // @ts-ignore - { - role: 'quit', - label: T('QUIT') - } - ] as any + const template: Array<(MenuItemConstructorOptions) | (MenuItem)> = [ + { label: T('OPEN_MAIN_WINDOW'), click: openMainWindow }, + { label: T('CHOOSE_DEFAULT_PICBED'), type: 'submenu', submenu }, + { label: T('START_WATCH_CLIPBOARD'), click: startWatchClipboard, visible: !isListeningClipboard }, + { label: T('STOP_WATCH_CLIPBOARD'), click: stopWatchClipboard, visible: isListeningClipboard }, + { label: T('RELOAD_APP'), click () { app.relaunch(); app.exit(0) } }, + { label: T('QUIT'), role: 'quit' } + ] if (process.platform === 'win32') { template.splice(2, 0, - { - label: T('OPEN_MINI_WINDOW'), - click () { - const miniWindow = windowManager.get(IWindowList.MINI_WINDOW)! - miniWindow.removeAllListeners() - if (db.get(configPaths.settings.miniWindowOntop)) { - miniWindow.setAlwaysOnTop(true) - } - const { width, height } = screen.getPrimaryDisplay().workAreaSize - const lastPosition = db.get(configPaths.settings.miniWindowPosition) - if (lastPosition) { - miniWindow.setPosition(lastPosition[0], lastPosition[1]) - } else { - miniWindow.setPosition(width - 100, height - 100) - } - const setPositionFunc = () => { - const position = miniWindow.getPosition() - db.set(configPaths.settings.miniWindowPosition, position) - } - miniWindow.on('close', setPositionFunc) - miniWindow.on('move', setPositionFunc) - miniWindow.show() - miniWindow.focus() - const autoCloseMainWindow = db.get(configPaths.settings.autoCloseMainWindow) || false - if (windowManager.has(IWindowList.SETTING_WINDOW) && autoCloseMainWindow) { - windowManager.get(IWindowList.SETTING_WINDOW)!.hide() - } - } - } + { label: T('OPEN_MINI_WINDOW'), click () { openMiniWindow(false) }, visible: !isMiniWindowVisible }, + { label: T('HIDE_MINI_WINDOW'), click: hideMiniWindow, visible: isMiniWindowVisible } ) } contextMenu = Menu.buildFromTemplate(template) @@ -242,70 +149,11 @@ export function createContextMenu () { // 目前的实现无法正常工作 contextMenu = Menu.buildFromTemplate([ - { - label: T('OPEN_MAIN_WINDOW'), - click () { - const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW) - const autoCloseMiniWindow = db.get(configPaths.settings.autoCloseMiniWindow) || false - settingWindow!.show() - settingWindow!.focus() - if (windowManager.has(IWindowList.MINI_WINDOW) && autoCloseMiniWindow) { - windowManager.get(IWindowList.MINI_WINDOW)!.hide() - } - } - }, - { - label: T('OPEN_MINI_WINDOW'), - click () { - const miniWindow = windowManager.get(IWindowList.MINI_WINDOW)! - miniWindow.removeAllListeners() - if (db.get(configPaths.settings.miniWindowOntop)) { - miniWindow.setAlwaysOnTop(true) - } - const { width, height } = screen.getPrimaryDisplay().workAreaSize - const lastPosition = db.get(configPaths.settings.miniWindowPosition) - if (lastPosition) { - miniWindow.setPosition(lastPosition[0], lastPosition[1]) - } else { - miniWindow.setPosition(width - 100, height - 100) - } - const setPositionFunc = () => { - const position = miniWindow.getPosition() - db.set(configPaths.settings.miniWindowPosition, position) - } - miniWindow.on('close', setPositionFunc) - miniWindow.on('move', setPositionFunc) - miniWindow.show() - miniWindow.focus() - const autoCloseMainWindow = db.get(configPaths.settings.autoCloseMainWindow) || false - if (windowManager.has(IWindowList.SETTING_WINDOW) && autoCloseMainWindow) { - windowManager.get(IWindowList.SETTING_WINDOW)!.hide() - } - } - }, - { - label: T('START_WATCH_CLIPBOARD'), - click () { - db.set(configPaths.settings.isListeningClipboard, true) - ClipboardWatcher.startListening() - ClipboardWatcher.on('change', () => { - picgo.log.info('clipboard changed') - uploadClipboardFiles() - }) - createContextMenu() - }, - enabled: !isListeningClipboard - }, - { - label: T('STOP_WATCH_CLIPBOARD'), - click () { - db.set(configPaths.settings.isListeningClipboard, false) - ClipboardWatcher.stopListening() - ClipboardWatcher.removeAllListeners() - createContextMenu() - }, - enabled: isListeningClipboard - }, + { label: T('OPEN_MAIN_WINDOW'), click: openMainWindow }, + { label: T('OPEN_MINI_WINDOW'), click () { openMiniWindow(false) }, visible: !isMiniWindowVisible }, + { label: T('HIDE_MINI_WINDOW'), click: hideMiniWindow, visible: isMiniWindowVisible }, + { label: T('START_WATCH_CLIPBOARD'), click: startWatchClipboard, visible: !isListeningClipboard }, + { label: T('STOP_WATCH_CLIPBOARD'), click: stopWatchClipboard, visible: isListeningClipboard }, { label: T('ABOUT'), click () { @@ -317,11 +165,7 @@ export function createContextMenu () { }) } }, - // @ts-ignore - { - role: 'quit', - label: T('QUIT') - } + { label: T('QUIT'), role: 'quit' } ]) } } diff --git a/src/main/apis/app/uploader/apis.ts b/src/main/apis/app/uploader/apis.ts index ee94f63c..d24f3cbc 100644 --- a/src/main/apis/app/uploader/apis.ts +++ b/src/main/apis/app/uploader/apis.ts @@ -4,26 +4,18 @@ import { } from 'electron' import fs from 'fs-extra' import { cloneDeep } from 'lodash' -import path from 'path' -import { ISftpPlistConfig } from 'piclist' import picgo from '@core/picgo' import db, { GalleryDB } from '@core/datastore' -import GuiApi from 'apis/gui' import uploader from 'apis/app/uploader' import windowManager from 'apis/app/window/windowManager' import { T } from '~/i18n/index' import { handleCopyUrl, handleUrlEncodeWithSetting } from '~/utils/common' import pasteTemplate from '~/utils/pasteTemplate' -import SSHClient from '~/utils/sshClient' - -import ALLApi from '@/apis/allApi' -import { getRawData } from '@/utils/common' import { IPasteStyle, IWindowList } from '#/types/enum' -import { picBedsCanbeDeleted } from '#/utils/static' import { configPaths } from '#/utils/configPaths' const handleClipboardUploading = async (): Promise => { @@ -129,62 +121,3 @@ export const uploadChoosedFiles = async (webContents: WebContents, files: IFileW return [] } } - -async function deleteSFTPFile (config: ISftpPlistConfig, fileName: string) { - try { - const client = SSHClient.instance - await client.connect(config) - const uploadPath = `/${(config.uploadPath || '')}/`.replace(/\/+/g, '/') - const remote = path.join(uploadPath, fileName) - const deleteResult = await client.deleteFileSFTP(config, remote) - client.close() - return deleteResult - } catch (err: any) { - picgo.log.error(err) - return false - } -} - -export const deleteChoosedFiles = async (list: ImgInfo[]): Promise => { - const result = [] - for (const item of list) { - if (item.id) { - try { - const dbStore = GalleryDB.getInstance() - const file = await dbStore.getById(item.id) - await dbStore.removeById(item.id) - if (await picgo.getConfig(configPaths.settings.deleteCloudFile)) { - if (item.type !== undefined && picBedsCanbeDeleted.includes(item.type)) { - const noteFunc = (value: boolean) => { - const notification = new Notification({ - title: T('MANAGE_BUCKET_BATCH_DELETE_ERROR_MSG_MSG2'), - body: T(value ? 'GALLERY_SYNC_DELETE_NOTICE_SUCCEED' : 'GALLERY_SYNC_DELETE_NOTICE_FAILED') - }) - notification.show() - } - if (item.type === 'sftpplist') { - const { fileName, config } = item - setTimeout(() => { - deleteSFTPFile(getRawData(config), fileName || '').then(noteFunc) - }, 0) - } else { - setTimeout(() => { - ALLApi.delete(item).then(noteFunc) - }, 0) - } - } - } - setTimeout(() => { - picgo.emit('remove', [file], GuiApi.getInstance()) - }, 500) - result.push(true) - } catch (e) { - result.push(false) - } - } - } - if (windowManager.has(IWindowList.SETTING_WINDOW)) { - windowManager.get(IWindowList.SETTING_WINDOW)!.webContents?.send('updateGallery') - } - return result -} diff --git a/src/main/apis/app/uploader/index.ts b/src/main/apis/app/uploader/index.ts index 65f8ec8e..845c8448 100644 --- a/src/main/apis/app/uploader/index.ts +++ b/src/main/apis/app/uploader/index.ts @@ -6,7 +6,7 @@ import { Notification, WebContents } from 'electron' -import fse from 'fs-extra' +import fs from 'fs-extra' import util from 'util' import path from 'path' import { IPicGo } from 'piclist' @@ -26,7 +26,7 @@ import { RENAME_FILE_NAME, TALKING_DATA_EVENT } from '#/events/constants' -import { IWindowList } from '#/types/enum' +import { ICOREBuildInEvent, IWindowList } from '#/types/enum' import { configPaths } from '#/utils/configPaths' import { CLIPBOARD_IMAGE_FOLDER } from '#/utils/static' @@ -61,21 +61,22 @@ const handleTalkingData = (webContents: WebContents, options: IAnalyticsData) => class Uploader { private webContents: WebContents | null = null - // private uploading: boolean = false + constructor () { this.init() } init () { - picgo.on('notification', (message: Electron.NotificationConstructorOptions | undefined) => { + picgo.on(ICOREBuildInEvent.NOTIFICATION, (message: Electron.NotificationConstructorOptions | undefined) => { const notification = new Notification(message) notification.show() }) - picgo.on('uploadProgress', (progress: any) => { + picgo.on(ICOREBuildInEvent.UPLOAD_PROGRESS, (progress: any) => { this.webContents?.send('uploadProgress', progress) }) - picgo.on('beforeTransform', () => { + + picgo.on(ICOREBuildInEvent.BEFORE_TRANSFORM, () => { if (db.get(configPaths.settings.uploadNotification)) { const notification = new Notification({ title: T('UPLOAD_PROGRESS'), @@ -84,6 +85,7 @@ class Uploader { notification.show() } }) + picgo.helper.beforeUploadPlugins.register('renameFn', { handle: async (ctx: IPicGo) => { const rename = db.get(configPaths.settings.rename) @@ -150,7 +152,7 @@ class Uploader { return false } finally { if (filePath) { - fse.remove(filePath) + fs.remove(filePath) } } } diff --git a/src/main/apis/app/window/windowList.ts b/src/main/apis/app/window/windowList.ts index 225d76e1..20a15b86 100644 --- a/src/main/apis/app/window/windowList.ts +++ b/src/main/apis/app/window/windowList.ts @@ -12,7 +12,6 @@ import { import bus from '@core/bus' import { CREATE_APP_MENU } from '@core/bus/constants' import db from '@core/datastore' -import picgo from '@core/picgo' import { T } from '~/i18n' @@ -25,16 +24,14 @@ const windowList = new Map() const handleWindowParams = (windowURL: string) => windowURL const getDefaultWindowSizes = (): { width: number, height: number } => { - const mainWindowWidth = picgo.getConfig(configPaths.settings.mainWindowWidth) - const mainWindowHeight = picgo.getConfig(configPaths.settings.mainWindowHeight) + const [mainWindowWidth, mainWindowHeight] = db.get([configPaths.settings.mainWindowWidth, configPaths.settings.mainWindowHeight]) return { width: mainWindowWidth || 1200, height: mainWindowHeight || 800 } } -const defaultWindowWidth = getDefaultWindowSizes().width -const defaultWindowHeight = getDefaultWindowSizes().height +const { width: defaultWindowWidth, height: defaultWindowHeight } = getDefaultWindowSizes() const trayWindowOptions = { height: 350, diff --git a/src/main/apis/app/window/windowManager.ts b/src/main/apis/app/window/windowManager.ts index ea652f19..496ee50d 100644 --- a/src/main/apis/app/window/windowManager.ts +++ b/src/main/apis/app/window/windowManager.ts @@ -6,6 +6,7 @@ import { IWindowList } from '#/types/enum' class WindowManager implements IWindowManager { #windowMap: Map = new Map() #windowIdMap: Map = new Map() + create (name: IWindowList) { const windowConfig: IWindowListItem = windowList.get(name)! if (windowConfig.isValid) { diff --git a/src/main/apis/core/datastore/index.ts b/src/main/apis/core/datastore/index.ts index 18bb6609..78888dfe 100644 --- a/src/main/apis/core/datastore/index.ts +++ b/src/main/apis/core/datastore/index.ts @@ -5,6 +5,8 @@ import { dbPathChecker, dbPathDir, getGalleryDBPath } from '@core/datastore/dbCh import { T } from '~/i18n' import { configPaths } from '#/utils/configPaths' +import { IJSON } from '@picgo/store/dist/types' +import { IConfig } from 'piclist' const STORE_PATH = dbPathDir() @@ -16,6 +18,7 @@ export const DB_PATH: string = getGalleryDBPath().dbPath class ConfigStore { #db: JSONStore + constructor () { this.#db = new JSONStore(CONFIG_PATH) @@ -40,34 +43,54 @@ class ConfigStore { this.read() } - flush () { - this.#db = new JSONStore(CONFIG_PATH) + read (flush?: boolean): IJSON { + return this.#db.read(flush) } - read () { - this.#db.read() - return this.#db - } - - get (key = ''): any { + getSingle (key = ''): any { if (key === '') { - return this.#db.read() + return this.#db.read(true) } + this.read(true) return this.#db.get(key) } + get (key: string): any + get (key: string[]): any[] + get (key: string | string[] = ''): any { + if (Array.isArray(key)) { + return key.map(k => this.getSingle(k)) + } + return this.getSingle(key) + } + set (key: string, value: any): void { + this.read(true) return this.#db.set(key, value) } has (key: string) { + this.read(true) return this.#db.has(key) } unset (key: string, value: any): boolean { + this.read(true) return this.#db.unset(key, value) } + saveConfig (config: Partial): void { + Object.keys(config).forEach((name: string) => { + this.set(name, config[name]) + }) + } + + removeConfig (config: IConfig): void { + Object.keys(config).forEach((name: string) => { + this.unset(name, config[name]) + }) + } + getConfigPath () { return CONFIG_PATH } diff --git a/src/main/apis/core/picgo/index.ts b/src/main/apis/core/picgo/index.ts index 8cda033c..92d61f5c 100644 --- a/src/main/apis/core/picgo/index.ts +++ b/src/main/apis/core/picgo/index.ts @@ -22,7 +22,7 @@ picgo.GUI_VERSION = global.PICGO_GUI_VERSION const originPicGoSaveConfig = picgo.saveConfig.bind(picgo) function flushDB () { - db.flush() + db.read(true) } const debounced = debounce(flushDB, 1000) diff --git a/src/main/apis/gui/index.ts b/src/main/apis/gui/index.ts index f4a0c55e..4e995f0e 100644 --- a/src/main/apis/gui/index.ts +++ b/src/main/apis/gui/index.ts @@ -65,7 +65,7 @@ class GuiApi implements IGuiApi { await this.showSettingWindow() this.getWebcontentsByWindowId(this.settingWindowId)?.send(SHOW_INPUT_BOX, options) return new Promise((resolve) => { - ipcMain.once(SHOW_INPUT_BOX, (event: Event, value: string) => { + ipcMain.once(SHOW_INPUT_BOX, (_: Event, value: string) => { resolve(value) }) }) diff --git a/src/main/events/ipcList.ts b/src/main/events/ipcList.ts deleted file mode 100644 index b7f11293..00000000 --- a/src/main/events/ipcList.ts +++ /dev/null @@ -1,388 +0,0 @@ -import { - app, - ipcMain, - shell, - Notification, - IpcMainEvent, - BrowserWindow, - screen, - IpcMainInvokeEvent -} from 'electron' -import fs from 'fs-extra' -import path from 'path' -import { ISftpPlistConfig } from 'piclist' - -import bus from '@core/bus' -import logger from '@core/picgo/logger' -import db, { GalleryDB } from '@core/datastore' - -import shortKeyHandler from 'apis/app/shortKey/shortKeyHandler' -import uploader from 'apis/app/uploader' -import { - uploadClipboardFiles, - uploadChoosedFiles -} from 'apis/app/uploader/apis' -import windowManager from 'apis/app/window/windowManager' - -import { T } from '~/i18n' -import server from '~/server' -import picgoCoreIPC from '~/events/picgoCoreIPC' -import { buildMainPageMenu, buildMiniPageMenu, buildPluginPageMenu, buildPicBedListMenu } from '~/events/remotes/menu' -import { handleCopyUrl, generateShortUrl, setTrayToolTip } from '~/utils/common' -import { removeFileFromS3InMain, removeFileFromDogeInMain, removeFileFromHuaweiInMain } from '~/utils/deleteFunc' -import getPicBeds from '~/utils/getPicBeds' -import pasteTemplate from '~/utils/pasteTemplate' -import SSHClient from '~/utils/sshClient' -import { uploadFile, downloadFile } from '~/utils/syncSettings' -import webServer from '~/server/webServer' - -import { - TOGGLE_SHORTKEY_MODIFIED_MODE, - OPEN_DEVTOOLS, - SHOW_MINI_PAGE_MENU, - MINIMIZE_WINDOW, - CLOSE_WINDOW, - SHOW_MAIN_PAGE_MENU, - SHOW_UPLOAD_PAGE_MENU, - OPEN_USER_STORE_FILE, - OPEN_URL, - RELOAD_APP, - SHOW_PLUGIN_PAGE_MENU, - SET_MINI_WINDOW_POS, - GET_PICBEDS, - HIDE_DOCK -} from '#/events/constants' -import { configPaths } from '#/utils/configPaths' -import { ILogType, IPasteStyle, IWindowList } from '#/types/enum' - -const STORE_PATH = app.getPath('userData') -const commonConfigList = ['data.json', 'data.bak.json'] -const manageConfigList = ['manage.json', 'manage.bak.json'] - -export default { - listen () { - picgoCoreIPC.listen() - // Upload Related IPC - // from macOS tray - ipcMain.on('uploadClipboardFiles', async () => { - const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)! - // macOS use builtin clipboard is OK - const img = await uploader.setWebContents(trayWindow.webContents).uploadWithBuildInClipboard() - if (img !== false) { - const pasteStyle = db.get(configPaths.settings.pasteStyle) || IPasteStyle.MARKDOWN - handleCopyUrl(await (pasteTemplate(pasteStyle, img[0], db.get(configPaths.settings.customLink)))) - const isShowResultNotification = db.get(configPaths.settings.uploadResultNotification) === undefined ? true : !!db.get(configPaths.settings.uploadResultNotification) - if (isShowResultNotification) { - const notification = new Notification({ - title: T('UPLOAD_SUCCEED'), - body: img[0].imgUrl! - // icon: file[0] - // icon: img[0].imgUrl - }) - notification.show() - } - await GalleryDB.getInstance().insert(img[0]) - trayWindow.webContents.send('clipboardFiles', []) - if (windowManager.has(IWindowList.SETTING_WINDOW)) { - windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('updateGallery') - } - } - trayWindow.webContents.send('uploadFiles') - }) - - ipcMain.on('uploadClipboardFilesFromUploadPage', () => { - uploadClipboardFiles() - }) - - ipcMain.on('uploadChoosedFiles', async (evt: IpcMainEvent, files: IFileWithPath[]) => { - return uploadChoosedFiles(evt.sender, files) - }) - - ipcMain.on('setTrayToolTip', (_: IpcMainEvent, title: string) => { - setTrayToolTip(title) - }) - - // ShortKey Related IPC - ipcMain.on('updateShortKey', (evt: IpcMainEvent, item: IShortKeyConfig, oldKey: string, from: string) => { - const result = shortKeyHandler.updateShortKey(item, oldKey, from) - evt.sender.send('updateShortKeyResponse', result) - if (result) { - const notification = new Notification({ - title: T('OPERATION_SUCCEED'), - body: T('TIPS_SHORTCUT_MODIFIED_SUCCEED') - }) - notification.show() - } else { - const notification = new Notification({ - title: T('OPERATION_FAILED'), - body: T('TIPS_SHORTCUT_MODIFIED_CONFLICT') - }) - notification.show() - } - }) - - ipcMain.on('bindOrUnbindShortKey', (_: IpcMainEvent, item: IShortKeyConfig, from: string) => { - const result = shortKeyHandler.bindOrUnbindShortKey(item, from) - if (result) { - const notification = new Notification({ - title: T('OPERATION_SUCCEED'), - body: T('TIPS_SHORTCUT_MODIFIED_SUCCEED') - }) - notification.show() - } else { - const notification = new Notification({ - title: T('OPERATION_FAILED'), - body: T('TIPS_SHORTCUT_MODIFIED_CONFLICT') - }) - notification.show() - } - }) - - // Gallery image cloud delete IPC - - ipcMain.on('logDeleteMsg', async (_: IpcMainEvent, msg: string, logLevel: ILogType) => { - logger[logLevel](msg) - }) - - ipcMain.handle('delete-sftp-file', async (_: IpcMainInvokeEvent, config: ISftpPlistConfig, fileName: string) => { - try { - const client = SSHClient.instance - await client.connect(config) - const uploadPath = `/${(config.uploadPath || '')}/`.replace(/\/+/g, '/') - const remote = path.join(uploadPath, fileName) - const deleteResult = await client.deleteFileSFTP(config, remote) - client.close() - return deleteResult - } catch (err: any) { - logger.error(err) - return false - } - }) - - ipcMain.handle('delete-aws-s3-file', async (_: IpcMainInvokeEvent, configMap: IStringKeyMap) => { - const result = await removeFileFromS3InMain(configMap) - return result - }) - - ipcMain.handle('delete-doge-file', async (_: IpcMainInvokeEvent, configMap: IStringKeyMap) => { - const result = await removeFileFromDogeInMain(configMap) - return result - }) - - ipcMain.handle('delete-huaweicloud-file', async (_: IpcMainInvokeEvent, configMap: IStringKeyMap) => { - const result = await removeFileFromHuaweiInMain(configMap) - return result - }) - - // migrate from PicGo - - ipcMain.handle('migrateFromPicGo', async () => { - const picGoConfigPath = STORE_PATH.replace('piclist', 'picgo') - const files = [ - 'data.json', - 'data.bak.json', - 'picgo.db', - 'picgo.bak.db' - ] - try { - await Promise.all(files.map(async file => { - const sourcePath = path.join(picGoConfigPath, file) - const targetPath = path.join(STORE_PATH, file.replace('picgo', 'piclist')) - await fs.remove(targetPath) - await fs.copy(sourcePath, targetPath, { overwrite: true }) - } - )) - return true - } catch (err: any) { - logger.error(err) - return false - } - }) - - // PicList Setting page IPC - - ipcMain.on('autoStart', (_: IpcMainEvent, val: boolean) => { - app.setLoginItemSettings({ - openAtLogin: val - }) - }) - - ipcMain.handle('getShortUrl', async (_: IpcMainInvokeEvent, url: string) => { - const shortUrl = await generateShortUrl(url) - return shortUrl - }) - - ipcMain.handle('uploadCommonConfig', async () => { - return await uploadFile(commonConfigList) - }) - - ipcMain.handle('downloadCommonConfig', async () => { - return await downloadFile(commonConfigList) - }) - - ipcMain.handle('uploadManageConfig', async () => { - return await uploadFile(manageConfigList) - }) - - ipcMain.handle('downloadManageConfig', async () => { - return await downloadFile(manageConfigList) - }) - - ipcMain.handle('uploadAllConfig', async () => { - return await uploadFile([...commonConfigList, ...manageConfigList]) - }) - - ipcMain.handle('downloadAllConfig', async () => { - return await downloadFile([...commonConfigList, ...manageConfigList]) - }) - - ipcMain.on('toggleMainWindowAlwaysOnTop', () => { - const mainWindow = windowManager.get(IWindowList.SETTING_WINDOW)! - const isAlwaysOnTop = mainWindow.isAlwaysOnTop() - mainWindow.setAlwaysOnTop(!isAlwaysOnTop) - }) - - // Window operation API - - ipcMain.on('openSettingWindow', () => { - windowManager.get(IWindowList.SETTING_WINDOW)!.show() - const autoCloseMiniWindow = db.get(configPaths.settings.autoCloseMiniWindow) || false - if (autoCloseMiniWindow) { - if (windowManager.has(IWindowList.MINI_WINDOW)) { - windowManager.get(IWindowList.MINI_WINDOW)!.hide() - } - } - }) - - ipcMain.on('openManualWindow', () => { - windowManager.get(IWindowList.MANUAL_WINDOW)!.show() - }) - - ipcMain.on('openMiniWindow', () => { - const miniWindow = windowManager.get(IWindowList.MINI_WINDOW)! - const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)! - miniWindow.removeAllListeners() - if (db.get(configPaths.settings.miniWindowOntop)) { - miniWindow.setAlwaysOnTop(true) - } - const { width, height } = screen.getPrimaryDisplay().workAreaSize - const lastPosition = db.get(configPaths.settings.miniWindowPosition) - if (lastPosition) { - miniWindow.setPosition(lastPosition[0], lastPosition[1]) - } else { - miniWindow.setPosition(width - 100, height - 100) - } - const setPositionFunc = () => { - const position = miniWindow.getPosition() - db.set(configPaths.settings.miniWindowPosition, position) - } - miniWindow.on('close', setPositionFunc) - miniWindow.on('move', setPositionFunc) - miniWindow.show() - miniWindow.focus() - settingWindow.hide() - }) - - ipcMain.on('updateMiniIcon', (_: IpcMainEvent, iconPath: string) => { - const miniWindow = windowManager.get(IWindowList.MINI_WINDOW)! - miniWindow.webContents.send('updateMiniIcon', iconPath) - }) - - ipcMain.on('miniWindowOntop', (_: IpcMainEvent, val: boolean) => { - const miniWindow = windowManager.get(IWindowList.MINI_WINDOW)! - miniWindow.setAlwaysOnTop(val) - }) - - ipcMain.on('refreshSettingWindow', () => { - const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)! - settingWindow.webContents.reloadIgnoringCache() - }) - - ipcMain.on(GET_PICBEDS, (evt: IpcMainEvent) => { - const picBeds = getPicBeds() - evt.sender.send(GET_PICBEDS, picBeds) - // evt.returnValue = picBeds - }) - - ipcMain.on(TOGGLE_SHORTKEY_MODIFIED_MODE, (_: IpcMainEvent, val: boolean) => { - bus.emit(TOGGLE_SHORTKEY_MODIFIED_MODE, val) - }) - - ipcMain.on('updateServer', () => { - server.restart() - }) - ipcMain.on('stopWebServer', () => { - webServer.stop() - }) - ipcMain.on('restartWebServer', () => { - webServer.restart() - }) - ipcMain.on(OPEN_DEVTOOLS, (event: IpcMainEvent) => { - event.sender.openDevTools() - }) - // menu & window methods - ipcMain.on(SHOW_MINI_PAGE_MENU, () => { - const window = windowManager.get(IWindowList.MINI_WINDOW)! - const menu = buildMiniPageMenu() - menu.popup({ - window - }) - }) - ipcMain.on(SHOW_MAIN_PAGE_MENU, () => { - const window = windowManager.get(IWindowList.SETTING_WINDOW)! - const menu = buildMainPageMenu(window) - menu.popup({ - window - }) - }) - ipcMain.on(SHOW_UPLOAD_PAGE_MENU, () => { - const window = windowManager.get(IWindowList.SETTING_WINDOW)! - const menu = buildPicBedListMenu() - menu.popup({ - window - }) - }) - ipcMain.on(SHOW_PLUGIN_PAGE_MENU, (_: IpcMainEvent, plugin: IPicGoPlugin) => { - const window = windowManager.get(IWindowList.SETTING_WINDOW)! - const menu = buildPluginPageMenu(plugin) - menu.popup({ - window - }) - }) - ipcMain.on(MINIMIZE_WINDOW, () => { - const window = BrowserWindow.getFocusedWindow() - window?.minimize() - }) - ipcMain.on(CLOSE_WINDOW, () => { - const window = BrowserWindow.getFocusedWindow() - if (process.platform === 'linux') { - window?.hide() - } else { - window?.close() - } - }) - ipcMain.on(OPEN_USER_STORE_FILE, (_: IpcMainEvent, filePath: string) => { - const abFilePath = path.join(STORE_PATH, filePath) - shell.openPath(abFilePath) - }) - ipcMain.on(OPEN_URL, (_: IpcMainEvent, url: string) => { - shell.openExternal(url) - }) - ipcMain.on(RELOAD_APP, () => { - app.relaunch() - app.exit(0) - }) - ipcMain.on(SET_MINI_WINDOW_POS, (_: IpcMainEvent, pos: IMiniWindowPos) => { - const window = BrowserWindow.getFocusedWindow() - window?.setBounds(pos) - }) - ipcMain.on(HIDE_DOCK, (_: IpcMainEvent, val: boolean) => { - if (val) { - app.dock.hide() - } else { - app.dock.show() - } - }) - }, - dispose () {} -} diff --git a/src/main/events/picgoCoreIPC.ts b/src/main/events/picgoCoreIPC.ts deleted file mode 100644 index 9d22abe8..00000000 --- a/src/main/events/picgoCoreIPC.ts +++ /dev/null @@ -1,459 +0,0 @@ -import { - dialog, - shell, - IpcMainEvent, - ipcMain, - clipboard -} from 'electron' -import fs from 'fs-extra' -import path from 'path' -import { IGuiMenuItem, PicGo as PicGoCore } from 'piclist' -import { IObject, IFilter } from '@picgo/store/dist/types' - -import picgo from '@core/picgo' -import { GalleryDB } from '@core/datastore' -import { dbPathChecker } from '@core/datastore/dbChecker' - -import shortKeyHandler from 'apis/app/shortKey/shortKeyHandler' -import windowManager from 'apis/app/window/windowManager' -import GuiApi from 'apis/gui' - -import { showNotification } from '~/utils/common' -import pasteTemplate from '~/utils/pasteTemplate' -import { i18nManager, T } from '~/i18n' -import { rpcServer } from '~/events/rpc' - -import { - PICGO_SAVE_CONFIG, - PICGO_GET_CONFIG, - PICGO_GET_DB, - PICGO_INSERT_DB, - PICGO_INSERT_MANY_DB, - PICGO_UPDATE_BY_ID_DB, - PICGO_GET_BY_ID_DB, - PICGO_REMOVE_BY_ID_DB, - PICGO_OPEN_FILE, - PICGO_OPEN_DIRECTORY, - PASTE_TEXT, - OPEN_WINDOW, - GET_LANGUAGE_LIST, - SET_CURRENT_LANGUAGE, - GET_CURRENT_LANGUAGE, - PICGO_GET_CONFIG_SYNC -} from '#/events/constants' -import { configPaths } from '#/utils/configPaths' -import { IPasteStyle, IPicGoHelperType, IWindowList } from '#/types/enum' -import { handleStreamlinePluginName, simpleClone } from '#/utils/common' - -// eslint-disable-next-line -const requireFunc = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require -// const PluginHandler = requireFunc('picgo/lib/PluginHandler').default -const STORE_PATH = path.dirname(dbPathChecker()) -// const CONFIG_PATH = path.join(STORE_PATH, '/data.json') - -interface GuiMenuItem { - label: string - handle: (arg0: PicGoCore, arg1: GuiApi) => Promise -} - -// get uploader or transformer config -const getConfig = (name: string, type: IPicGoHelperType, ctx: PicGoCore) => { - let config: any[] = [] - if (name === '') { - return config - } else { - const handler = ctx.helper[type].get(name) - if (handler) { - if (handler.config) { - config = handler.config(ctx) - } - } - return config - } -} - -const handleConfigWithFunction = (config: any[]) => { - for (const i in config) { - if (typeof config[i].default === 'function') { - config[i].default = config[i].default() - } - if (typeof config[i].choices === 'function') { - config[i].choices = config[i].choices() - } - } - return config -} - -const getPluginList = (): IPicGoPlugin[] => { - const pluginList = picgo.pluginLoader.getFullList() - const list = [] - for (const i in pluginList) { - const plugin = picgo.pluginLoader.getPlugin(pluginList[i])! - const pluginPath = path.join(STORE_PATH, `/node_modules/${pluginList[i]}`) - const pluginPKG = requireFunc(path.join(pluginPath, 'package.json')) - const uploaderName = plugin.uploader || '' - const transformerName = plugin.transformer || '' - let menu: Omit[] = [] - if (plugin.guiMenu) { - menu = plugin.guiMenu(picgo).map(item => ({ - label: item.label - })) - } - let gui = false - if (pluginPKG.keywords && pluginPKG.keywords.length > 0) { - if (pluginPKG.keywords.includes('picgo-gui-plugin')) { - gui = true - } - } - const obj: IPicGoPlugin = { - name: handleStreamlinePluginName(pluginList[i]), - fullName: pluginList[i], - author: pluginPKG.author.name || pluginPKG.author, - description: pluginPKG.description, - logo: 'file://' + path.join(pluginPath, 'logo.png').split(path.sep).join('/'), - version: pluginPKG.version, - gui, - config: { - plugin: { - fullName: pluginList[i], - name: handleStreamlinePluginName(pluginList[i]), - config: plugin.config ? handleConfigWithFunction(plugin.config(picgo)) : [] - }, - uploader: { - name: uploaderName, - config: handleConfigWithFunction(getConfig(uploaderName, IPicGoHelperType.uploader, picgo)) - }, - transformer: { - name: transformerName, - config: handleConfigWithFunction(getConfig(uploaderName, IPicGoHelperType.transformer, picgo)) - } - }, - enabled: picgo.getConfig(`picgoPlugins.${pluginList[i]}`), - homepage: pluginPKG.homepage ? pluginPKG.homepage : '', - guiMenu: menu, - ing: false - } - list.push(obj) - } - return list -} - -const handleGetPluginList = () => { - ipcMain.on('getPluginList', (event: IpcMainEvent) => { - try { - const list = simpleClone(getPluginList()) - // here can just send JS Object not function - // or will cause [Failed to serialize arguments] error - event.sender.send('pluginList', list) - } catch (e: any) { - event.sender.send('pluginList', []) - showNotification({ - title: T('TIPS_GET_PLUGIN_LIST_FAILED'), - body: e.message - }) - picgo.log.error(e) - } - }) -} - -const handlePluginInstall = () => { - ipcMain.on('installPlugin', async (event: IpcMainEvent, fullName: string) => { - const dispose = handleNPMError() - const res = await picgo.pluginHandler.install([fullName]) - event.sender.send('installPlugin', { - success: res.success, - body: fullName, - errMsg: res.success ? '' : res.body - }) - if (res.success) { - shortKeyHandler.registerPluginShortKey(res.body[0]) - } else { - showNotification({ - title: T('PLUGIN_INSTALL_FAILED'), - body: res.body as string - }) - } - event.sender.send('hideLoading') - dispose() - }) -} - -const handlePluginUninstall = async (fullName: string) => { - const window = windowManager.get(IWindowList.SETTING_WINDOW)! - const dispose = handleNPMError() - const res = await picgo.pluginHandler.uninstall([fullName]) - if (res.success) { - window.webContents.send('uninstallSuccess', res.body[0]) - shortKeyHandler.unregisterPluginShortKey(res.body[0]) - } else { - showNotification({ - title: T('PLUGIN_UNINSTALL_FAILED'), - body: res.body as string - }) - } - window.webContents.send('hideLoading') - dispose() -} - -const handlePluginUpdate = async (fullName: string | string[]) => { - const window = windowManager.get(IWindowList.SETTING_WINDOW)! - const dispose = handleNPMError() - const res = await picgo.pluginHandler.update(typeof fullName === 'string' ? [fullName] : fullName) - if (res.success) { - window.webContents.send('updateSuccess', res.body[0]) - } else { - showNotification({ - title: T('PLUGIN_UPDATE_FAILED'), - body: res.body as string - }) - } - window.webContents.send('hideLoading') - dispose() -} - -const handleUpdateAllPlugin = () => { - ipcMain.on('updateAllPlugin', async (_: IpcMainEvent, list: string[]) => { - handlePluginUpdate(list) - }) -} - -const handleNPMError = (): IDispose => { - const handler = (msg: string) => { - if (msg === 'NPM is not installed') { - dialog.showMessageBox({ - title: T('TIPS_ERROR'), - message: T('TIPS_INSTALL_NODE_AND_RELOAD_PICGO'), - buttons: ['Yes'] - }).then((res) => { - if (res.response === 0) { - shell.openExternal('https://nodejs.org/') - } - }) - } - } - picgo.once('failed', handler) - return () => picgo.off('failed', handler) -} - -const handleGetPicBedConfig = () => { - ipcMain.on('getPicBedConfig', (event: IpcMainEvent, type: string) => { - const name = picgo.helper.uploader.get(type)?.name || type - if (picgo.helper.uploader.get(type)?.config) { - const _config = picgo.helper.uploader.get(type)!.config!(picgo) - const config = handleConfigWithFunction(_config) - event.sender.send('getPicBedConfig', config, name) - } else { - event.sender.send('getPicBedConfig', [], name) - } - }) -} - -// TODO: remove it -const handlePluginActions = () => { - ipcMain.on('pluginActions', (_: IpcMainEvent, name: string, label: string) => { - const plugin = picgo.pluginLoader.getPlugin(name) - if (plugin?.guiMenu?.(picgo)?.length) { - const menu: GuiMenuItem[] = plugin.guiMenu(picgo) - menu.forEach(item => { - if (item.label === label) { - item.handle(picgo, GuiApi.getInstance()) - } - }) - } - }) -} - -const handleRemoveFiles = () => { - ipcMain.on('removeFiles', (_: IpcMainEvent, files: ImgInfo[]) => { - setTimeout(() => { - picgo.emit('remove', files, GuiApi.getInstance()) - }, 500) - }) -} - -const handlePicGoSaveConfig = () => { - ipcMain.on(PICGO_SAVE_CONFIG, (_: IpcMainEvent, data: IObj) => { - picgo.saveConfig(data) - }) -} - -const handlePicGoGetConfig = () => { - ipcMain.handle(PICGO_GET_CONFIG, (_, key: string | undefined) => { - return picgo.getConfig(key) - }) -} - -const handlePicGoGetConfigSync = () => { - ipcMain.on(PICGO_GET_CONFIG_SYNC, (event: IpcMainEvent, key: string | undefined) => { - const result = picgo.getConfig(key) - event.returnValue = result - }) -} - -const handleImportLocalPlugin = () => { - ipcMain.on('importLocalPlugin', async (event: IpcMainEvent) => { - const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)! - const res = await dialog.showOpenDialog(settingWindow, { - properties: ['openDirectory'] - }) - const filePaths = res.filePaths - if (filePaths.length > 0) { - const res = await picgo.pluginHandler.install(filePaths) - if (res.success) { - try { - const list = simpleClone(getPluginList()) - event.sender.send('pluginList', list) - } catch (e: any) { - event.sender.send('pluginList', []) - showNotification({ - title: T('TIPS_GET_PLUGIN_LIST_FAILED'), - body: e.message - }) - } - showNotification({ - title: T('PLUGIN_IMPORT_SUCCEED'), - body: '' - }) - } else { - showNotification({ - title: T('PLUGIN_IMPORT_FAILED'), - body: res.body as string - }) - } - } - event.sender.send('hideLoading') - }) -} - -const handlePicGoGalleryDB = () => { - ipcMain.on(PICGO_GET_DB, async (event: IpcMainEvent, filter: IFilter, callbackId: string) => { - const dbStore = GalleryDB.getInstance() - const res = await dbStore.get(filter) - event.sender.send(PICGO_GET_DB, res, callbackId) - }) - - ipcMain.on(PICGO_INSERT_DB, async (event: IpcMainEvent, value: IObject, callbackId: string) => { - const dbStore = GalleryDB.getInstance() - const res = await dbStore.insert(value) - event.sender.send(PICGO_INSERT_DB, res, callbackId) - }) - - ipcMain.on(PICGO_INSERT_MANY_DB, async (event: IpcMainEvent, value: IObject[], callbackId: string) => { - const dbStore = GalleryDB.getInstance() - const res = await dbStore.insertMany(value) - event.sender.send(PICGO_INSERT_MANY_DB, res, callbackId) - }) - - ipcMain.on(PICGO_UPDATE_BY_ID_DB, async (event: IpcMainEvent, id: string, value: IObject[], callbackId: string) => { - const dbStore = GalleryDB.getInstance() - const res = await dbStore.updateById(id, value) - event.sender.send(PICGO_UPDATE_BY_ID_DB, res, callbackId) - }) - - ipcMain.on(PICGO_GET_BY_ID_DB, async (event: IpcMainEvent, id: string, callbackId: string) => { - const dbStore = GalleryDB.getInstance() - const res = await dbStore.getById(id) - event.sender.send(PICGO_GET_BY_ID_DB, res, callbackId) - }) - - ipcMain.on(PICGO_REMOVE_BY_ID_DB, async (event: IpcMainEvent, id: string, callbackId: string) => { - const dbStore = GalleryDB.getInstance() - const res = await dbStore.removeById(id) - event.sender.send(PICGO_REMOVE_BY_ID_DB, res, callbackId) - }) - - ipcMain.handle(PASTE_TEXT, async (_, item: ImgInfo, copy = true) => { - const pasteStyle = picgo.getConfig(configPaths.settings.pasteStyle) || IPasteStyle.MARKDOWN - const customLink = picgo.getConfig(configPaths.settings.customLink) - const txt = await pasteTemplate(pasteStyle, item, customLink) - if (copy) { - clipboard.writeText(txt) - } - return txt - }) -} - -const handleOpenFile = () => { - ipcMain.on(PICGO_OPEN_FILE, (event: IpcMainEvent, fileName: string) => { - const abFilePath = path.join(STORE_PATH, fileName) - if (!fs.existsSync(abFilePath)) { - fs.writeFileSync(abFilePath, '') - } - shell.openPath(abFilePath) - }) -} - -const handleOpenDirectory = () => { - ipcMain.on(PICGO_OPEN_DIRECTORY, (event: IpcMainEvent, dirPath?: string, inStorePath: boolean = true) => { - if (inStorePath) { - dirPath = path.join(STORE_PATH, dirPath || '') - } - if (!dirPath || !fs.existsSync(dirPath)) { - return - } - shell.openPath(dirPath) - }) -} - -const handleOpenWindow = () => { - ipcMain.on(OPEN_WINDOW, (event: IpcMainEvent, windowName: IWindowList) => { - const window = windowManager.get(windowName) - if (window) { - window.show() - } - }) -} - -const handleI18n = () => { - ipcMain.on(GET_LANGUAGE_LIST, (event: IpcMainEvent) => { - event.sender.send(GET_LANGUAGE_LIST, i18nManager.languageList) - }) - ipcMain.on(SET_CURRENT_LANGUAGE, (event: IpcMainEvent, language: string) => { - i18nManager.setCurrentLanguage(language) - const { lang, locales } = i18nManager.getCurrentLocales() - picgo.i18n.setLanguage(lang) - if (process.platform === 'darwin') { - const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW) - trayWindow?.webContents.send(SET_CURRENT_LANGUAGE, lang, locales) - } - const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW) - settingWindow?.webContents.send(SET_CURRENT_LANGUAGE, lang, locales) - if (windowManager.has(IWindowList.MINI_WINDOW)) { - const miniWindow = windowManager.get(IWindowList.MINI_WINDOW) - miniWindow?.webContents.send(SET_CURRENT_LANGUAGE, lang, locales) - } - // event.sender.send(SET_CURRENT_LANGUAGE, lang, locales) - }) - ipcMain.on(GET_CURRENT_LANGUAGE, (event: IpcMainEvent) => { - const { lang, locales } = i18nManager.getCurrentLocales() - event.sender.send(GET_CURRENT_LANGUAGE, lang, locales) - }) -} - -const handleRPCActions = () => { - rpcServer.start() -} - -export default { - listen () { - handleGetPluginList() - handlePluginInstall() - handleGetPicBedConfig() - handlePluginActions() - handleRemoveFiles() - handlePicGoSaveConfig() - handlePicGoGetConfig() - handlePicGoGetConfigSync() - handlePicGoGalleryDB() - handleImportLocalPlugin() - handleUpdateAllPlugin() - handleOpenFile() - handleOpenDirectory() - handleOpenWindow() - handleI18n() - handleRPCActions() - }, - // TODO: separate to single file - handlePluginUninstall, - handlePluginUpdate -} diff --git a/src/main/events/remotes/menu.ts b/src/main/events/remotes/menu.ts index 17314701..96669a54 100644 --- a/src/main/events/remotes/menu.ts +++ b/src/main/events/remotes/menu.ts @@ -3,7 +3,9 @@ import { dialog, BrowserWindow, Menu, - shell + shell, + MenuItemConstructorOptions, + MenuItem } from 'electron' import { PicGo as PicGoCore } from 'piclist' @@ -16,7 +18,7 @@ import { import windowManager from 'apis/app/window/windowManager' import GuiApi from 'apis/gui' -import picgoCoreIPC from '~/events/picgoCoreIPC' +import { handlePluginUninstall, handlePluginUpdate } from '~/events/rpc/routes/plugin/utils' import { T } from '~/i18n' import clipboardPoll from '~/utils/clipboardPoll' import { setTrayToolTip } from '~/utils/common' @@ -34,6 +36,7 @@ import { IWindowList } from '#/types/enum' import { configPaths } from '#/utils/configPaths' import pkg from 'root/package.json' +import { openMainWindow } from '~/utils/windowHelper' interface GuiMenuItem { label: string @@ -44,18 +47,10 @@ const buildMiniPageMenu = () => { const isListeningClipboard = db.get(configPaths.settings.isListeningClipboard) || false const ClipboardWatcher = clipboardPoll const submenu = buildPicBedListMenu() - const template = [ + const template: Array<(MenuItemConstructorOptions) | (MenuItem)> = [ { label: T('OPEN_MAIN_WINDOW'), - click () { - windowManager.get(IWindowList.SETTING_WINDOW)!.show() - const autoCloseMiniWindow = db.get(configPaths.settings.autoCloseMiniWindow) || false - if (autoCloseMiniWindow) { - if (windowManager.has(IWindowList.MINI_WINDOW)) { - windowManager.get(IWindowList.MINI_WINDOW)!.hide() - } - } - } + click: openMainWindow }, { label: T('CHOOSE_DEFAULT_PICBED'), @@ -69,7 +64,7 @@ const buildMiniPageMenu = () => { } }, { - label: T('HIDE_WINDOW'), + label: T('HIDE_MINI_WINDOW'), click () { BrowserWindow.getFocusedWindow()!.hide() } @@ -85,7 +80,7 @@ const buildMiniPageMenu = () => { }) buildMiniPageMenu() }, - enabled: !isListeningClipboard + visible: !isListeningClipboard }, { label: T('STOP_WATCH_CLIPBOARD'), @@ -95,7 +90,7 @@ const buildMiniPageMenu = () => { ClipboardWatcher.removeAllListeners() buildMiniPageMenu() }, - enabled: isListeningClipboard + visible: isListeningClipboard }, { label: T('RELOAD_APP'), @@ -109,7 +104,6 @@ const buildMiniPageMenu = () => { label: T('QUIT') } ] - // @ts-ignore return Menu.buildFromTemplate(template) } @@ -269,14 +263,14 @@ const buildPluginPageMenu = (plugin: IPicGoPlugin) => { click () { const window = windowManager.get(IWindowList.SETTING_WINDOW)! window.webContents.send(PICGO_HANDLE_PLUGIN_ING, plugin.fullName) - picgoCoreIPC.handlePluginUninstall(plugin.fullName) + handlePluginUninstall(plugin.fullName) } }, { label: T('UPDATE_PLUGIN'), click () { const window = windowManager.get(IWindowList.SETTING_WINDOW)! window.webContents.send(PICGO_HANDLE_PLUGIN_ING, plugin.fullName) - picgoCoreIPC.handlePluginUpdate(plugin.fullName) + handlePluginUpdate(plugin.fullName) } }] for (const i in plugin.config) { @@ -330,7 +324,6 @@ const buildPluginPageMenu = (plugin: IPicGoPlugin) => { menu.push({ label: i.label, click () { - // ipcRenderer.send('pluginActions', plugin.fullName, i.label) const picgPlugin = picgo.pluginLoader.getPlugin(plugin.fullName) if (picgPlugin?.guiMenu?.(picgo)?.length) { const menu: GuiMenuItem[] = picgPlugin.guiMenu(picgo) diff --git a/src/main/events/rpc/index.ts b/src/main/events/rpc/index.ts index 9d7bdb20..8b6a8892 100644 --- a/src/main/events/rpc/index.ts +++ b/src/main/events/rpc/index.ts @@ -1,45 +1,62 @@ -import { ipcMain, IpcMainEvent } from 'electron' +import { ipcMain, IpcMainEvent, IpcMainInvokeEvent } from 'electron' -import { configRouter } from '~/events/rpc/routes/config' -import { toolboxRouter } from '~/events/rpc/routes/toolbox' +import logger from '@core/picgo/logger' + +import { galleryRouter } from '~/events/rpc/routes/gallery' +import { picbedRouter } from '~/events/rpc/routes/picbed' +import { pluginRouter } from '~/events/rpc/routes/plugin' +import { settingRouter } from '~/events/rpc/routes/setting' import { systemRouter } from '~/events/rpc/routes/system' +import { toolboxRouter } from '~/events/rpc/routes/toolbox' +import { trayRouter } from '~/events/rpc/routes/tray' +import { uploadRouter } from '~/events/rpc/routes/upload' -import { IRPCActionType } from '#/types/enum' -import { RPC_ACTIONS } from '#/events/constants' +import { IRPCActionType, IRPCType } from '#/types/enum' +import { RPC_ACTIONS, RPC_ACTIONS_INVOKE } from '#/events/constants' + +const isDevelopment = process.env.NODE_ENV !== 'production' class RPCServer implements IRPCServer { private routes: IRPCRoutes = new Map() + private routesWithResponse: IRPCRoutes = new Map() - private rpcEventHandler = async (event: IpcMainEvent, action: IRPCActionType, args: any[], callbackId: string) => { + private rpcEventHandler = async (event: IpcMainEvent, action: IRPCActionType, args: any[]) => { try { - const handler = this.routes.get(action) - if (!handler) { - return this.sendBack(event, action, null, callbackId) + if (isDevelopment) { + console.log(`action: ${action} args: ${JSON.stringify(args)}`) } - const res = await handler?.(args, event) - this.sendBack(event, action, res, callbackId) - } catch (e) { - this.sendBack(event, action, null, callbackId) + const route = this.routes.get(action) + await route?.handler?.(event, args) + } catch (e: any) { + logger.error(e) } } - /** - * if sendback data is null, then it means that the action is not supported or error occurs - * if there is no callbackId, then do not send back - */ - private sendBack (event: IpcMainEvent, action: IRPCActionType, data: any, callbackId: string) { - if (callbackId) { - event.sender.send(RPC_ACTIONS, data, action, callbackId) + private rpcEventHandlerWithResponse = async (event: IpcMainInvokeEvent, action: IRPCActionType, args: any[]) => { + try { + if (isDevelopment) { + console.log(`action: ${action} args: ${JSON.stringify(args)}`) + } + const route = this.routesWithResponse.get(action) + return await route?.handler?.(event, args) + } catch (e: any) { + logger.error(e) + return undefined } } start () { ipcMain.on(RPC_ACTIONS, this.rpcEventHandler) + ipcMain.handle(RPC_ACTIONS_INVOKE, this.rpcEventHandlerWithResponse) } use (routes: IRPCRoutes) { - for (const [action, handler] of routes) { - this.routes.set(action, handler) + for (const [action, route] of routes) { + if (route.type === IRPCType.SEND) { + this.routes.set(action, route) + } else { + this.routesWithResponse.set(action, route) + } } } @@ -50,9 +67,20 @@ class RPCServer implements IRPCServer { const rpcServer = new RPCServer() -rpcServer.use(configRouter.routes()) -rpcServer.use(toolboxRouter.routes()) -rpcServer.use(systemRouter.routes()) +const routes = [ + galleryRouter.routes(), + picbedRouter.routes(), + pluginRouter.routes(), + settingRouter.routes(), + systemRouter.routes(), + toolboxRouter.routes(), + trayRouter.routes(), + uploadRouter.routes() +] + +for (const route of routes) { + rpcServer.use(route) +} export { rpcServer diff --git a/src/main/events/rpc/router.ts b/src/main/events/rpc/router.ts index abda51e5..8117e21b 100644 --- a/src/main/events/rpc/router.ts +++ b/src/main/events/rpc/router.ts @@ -1,9 +1,22 @@ -import { IRPCActionType } from '#/types/enum' +import { IRPCType, IRPCActionType } from '#/types/enum' + +interface IBatchAddParams { + action: IRPCActionType + handler: IRPCHandler + type?: IRPCType +} export class RPCRouter implements IRPCRouter { private routeMap: IRPCRoutes = new Map() - add = (action: IRPCActionType, handler: IRPCHandler) => { - this.routeMap.set(action, handler) + add = (action: IRPCActionType, handler: IRPCHandler, type: IRPCType = IRPCType.SEND): this => { + this.routeMap.set(action, { handler, type }) + return this + } + + addBatch = (params: IBatchAddParams[]): this => { + for (const { action, handler, type = IRPCType.SEND } of params) { + this.routeMap.set(action, { handler, type }) + } return this } diff --git a/src/main/events/rpc/routes/config.ts b/src/main/events/rpc/routes/config.ts deleted file mode 100644 index b8b0ee1e..00000000 --- a/src/main/events/rpc/routes/config.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { RPCRouter } from '~/events/rpc/router' -import { - deleteUploaderConfig, - getUploaderConfigList, - resetUploaderConfig, - selectUploaderConfig, - updateUploaderConfig -} from '~/utils/handleUploaderConfig' -import { IRPCActionType } from '#/types/enum' - -const configRouter = new RPCRouter() - -configRouter - .add(IRPCActionType.GET_PICBED_CONFIG_LIST, async (args) => { - const [type] = args as IGetUploaderConfigListArgs - const config = getUploaderConfigList(type) - return config - }) - .add(IRPCActionType.DELETE_PICBED_CONFIG, async (args) => { - const [type, id] = args as IDeleteUploaderConfigArgs - const config = deleteUploaderConfig(type, id) - return config - }) - .add(IRPCActionType.SELECT_UPLOADER, async (args) => { - const [type, id] = args as ISelectUploaderConfigArgs - selectUploaderConfig(type, id) - return true - }) - .add(IRPCActionType.UPDATE_UPLOADER_CONFIG, async (args) => { - const [type, id, config] = args as IUpdateUploaderConfigArgs - updateUploaderConfig(type, id, config) - return true - }) - .add(IRPCActionType.RESET_UPLOADER_CONFIG, async (args) => { - const [type, id] = args as IUpdateUploaderConfigArgs - resetUploaderConfig(type, id) - return true - }) - -export { - configRouter -} diff --git a/src/main/events/rpc/routes/gallery/index.ts b/src/main/events/rpc/routes/gallery/index.ts new file mode 100644 index 00000000..5ae1e388 --- /dev/null +++ b/src/main/events/rpc/routes/gallery/index.ts @@ -0,0 +1,133 @@ + +import { clipboard } from 'electron' + +import { GalleryDB } from '@core/datastore' +import picgo from '@core/picgo' +import logger from '@core/picgo/logger' +import { IFilter, IObject } from '@picgo/store/dist/types' +import GuiApi from 'apis/gui' + +import { RPCRouter } from '~/events/rpc/router' +import { removeFileFromDogeInMain, removeFileFromHuaweiInMain, removeFileFromS3InMain, removeFileFromSFTPInMain } from '~/utils/deleteFunc' +import pasteTemplate from '~/utils/pasteTemplate' + +import { ICOREBuildInEvent, IPasteStyle, IRPCActionType, IRPCType } from '#/types/enum' +import { configPaths } from '#/utils/configPaths' + +const galleryRouter = new RPCRouter() + +const galleryRoutes = [ + { + action: IRPCActionType.GALLERY_PASTE_TEXT, + handler: async (_: IIPCEvent, args: [ item: ImgInfo, copy?: boolean]) => { + const [item, copy = true] = args + const pasteStyle = picgo.getConfig(configPaths.settings.pasteStyle) || IPasteStyle.MARKDOWN + const customLink = picgo.getConfig(configPaths.settings.customLink) + const txt = await pasteTemplate(pasteStyle, item, customLink) + if (copy) { + clipboard.writeText(txt) + } + return txt + }, + type: IRPCType.INVOKE + }, + { + action: IRPCActionType.GALLERY_REMOVE_FILES, + handler: async (_: IIPCEvent, args: [files: ImgInfo[]]) => { + setTimeout(() => { + picgo.emit(ICOREBuildInEvent.REMOVE, args[0], GuiApi.getInstance()) + }, 500) + } + }, + { + action: IRPCActionType.GALLERY_GET_DB, + handler: async (_: IIPCEvent, args: [filter: IFilter]) => { + const dbStore = GalleryDB.getInstance() + return await dbStore.get(args[0]) + }, + type: IRPCType.INVOKE + }, + { + action: IRPCActionType.GALLERY_GET_BY_ID_DB, + handler: async (_: IIPCEvent, args: [id: string]) => { + const dbStore = GalleryDB.getInstance() + return await dbStore.getById(args[0]) + }, + type: IRPCType.INVOKE + }, + { + action: IRPCActionType.GALLERY_UPDATE_BY_ID_DB, + handler: async (_: IIPCEvent, args: [id: string, value: IObject]) => { + const dbStore = GalleryDB.getInstance() + return await dbStore.updateById(args[0], args[1]) + }, + type: IRPCType.INVOKE + }, + { + action: IRPCActionType.GALLERY_REMOVE_BY_ID_DB, + handler: async (_: IIPCEvent, args: [id: string]) => { + const dbStore = GalleryDB.getInstance() + return await dbStore.removeById(args[0]) + }, + type: IRPCType.INVOKE + }, + { + action: IRPCActionType.GALLERY_INSERT_DB, + handler: async (_: IIPCEvent, args: [value: IObject]) => { + const dbStore = GalleryDB.getInstance() + return await dbStore.insert(args[0]) + }, + type: IRPCType.INVOKE + }, + { + action: IRPCActionType.GALLERY_INSERT_DB_BATCH, + handler: async (_: IIPCEvent, args: [value: IObject[]]) => { + const dbStore = GalleryDB.getInstance() + return await dbStore.insertMany(args[0]) + }, + type: IRPCType.INVOKE + }, + { + action: IRPCActionType.GALLERY_LOG_DELETE_MSG, + handler: async (_: IIPCEvent, args: [msg: string, logLevel: ILogType]) => { + const [msg, logLevel] = args + console.log(msg, logLevel) + logger[logLevel](msg) + } + }, + { + action: IRPCActionType.GALLERY_DELETE_SFTP_FILE, + handler: async (_: IIPCEvent, args: [config: ISftpPlistConfig, fileName: string]) => { + const [config, fileName] = args + return await removeFileFromSFTPInMain(config, fileName) + }, + type: IRPCType.INVOKE + }, + { + action: IRPCActionType.GALLERY_DELETE_AWS_S3_FILE, + handler: async (_: IIPCEvent, args: [configMap: IStringKeyMap]) => { + return await removeFileFromS3InMain(args[0]) + }, + type: IRPCType.INVOKE + }, + { + action: IRPCActionType.GALLERY_DELETE_DOGE_FILE, + handler: async (_: IIPCEvent, args: [configMap: IStringKeyMap]) => { + return await removeFileFromDogeInMain(args[0]) + }, + type: IRPCType.INVOKE + }, + { + action: IRPCActionType.GALLERY_DELETE_HUAWEI_OSS_FILE, + handler: async (_: IIPCEvent, args: [configMap: IStringKeyMap]) => { + return await removeFileFromHuaweiInMain(args[0]) + }, + type: IRPCType.INVOKE + } +] + +galleryRouter.addBatch(galleryRoutes) + +export { + galleryRouter +} diff --git a/src/main/events/rpc/routes/picbed/index.ts b/src/main/events/rpc/routes/picbed/index.ts new file mode 100644 index 00000000..5936a863 --- /dev/null +++ b/src/main/events/rpc/routes/picbed/index.ts @@ -0,0 +1,99 @@ +import picgo from '@core/picgo' + +import { RPCRouter } from '~/events/rpc/router' +import { + deleteUploaderConfig, + getUploaderConfigList, + resetUploaderConfig, + selectUploaderConfig, + updateUploaderConfig +} from '~/utils/handleUploaderConfig' +import { IRPCActionType, IRPCType } from '#/types/enum' + +const picbedRouter = new RPCRouter() + +const handleConfigWithFunction = (config: any[]) => { + for (const i in config) { + if (typeof config[i].default === 'function') { + config[i].default = config[i].default() + } + if (typeof config[i].choices === 'function') { + config[i].choices = config[i].choices() + } + } + return config +} + +const picbedRoutes = [ + { + action: IRPCActionType.PICBED_GET_CONFIG_LIST, + handler: async (_: IIPCEvent, args: [type: string]) => { + const config = getUploaderConfigList(args[0]) + return config + }, + type: IRPCType.INVOKE + }, + { + action: IRPCActionType.PICBED_DELETE_CONFIG, + handler: async (_: IIPCEvent, args: [type: string, id: string]) => { + const [type, id] = args + const config = deleteUploaderConfig(type, id) + return config + }, + type: IRPCType.INVOKE + }, + { + action: IRPCActionType.UPLOADER_SELECT, + handler: async (_: IIPCEvent, args: [type: string, id: string]) => { + const [type, id] = args + selectUploaderConfig(type, id) + return true + }, + type: IRPCType.INVOKE + }, + { + action: IRPCActionType.UPLOADER_UPDATE_CONFIG, + handler: async (_: IIPCEvent, args: [type: string, id: string, config: IStringKeyMap]) => { + const [type, id, config] = args + updateUploaderConfig(type, id, config) + return true + }, + type: IRPCType.INVOKE + }, + { + action: IRPCActionType.UPLOADER_RESET_CONFIG, + handler: async (_: IIPCEvent, args: [type: string, id: string]) => { + const [type, id] = args + resetUploaderConfig(type, id) + return true + }, + type: IRPCType.INVOKE + }, + { + action: IRPCActionType.PICBED_GET_PICBED_CONFIG, + handler: async (_: IIPCEvent, args: [type: string]) => { + const type = args[0] + const name = picgo.helper.uploader.get(type)?.name || type + if (picgo.helper.uploader.get(type)?.config) { + const _config = picgo.helper.uploader.get(type)!.config!(picgo) + const config = handleConfigWithFunction(_config) + return { + config, + name + } + } else { + return { + config: [], + name + } + } + }, + type: IRPCType.INVOKE + } +] + +picbedRouter.addBatch(picbedRoutes) + +export { + picbedRouter +} diff --git a/src/main/events/rpc/routes/plugin/index.ts b/src/main/events/rpc/routes/plugin/index.ts new file mode 100644 index 00000000..ea91bc78 --- /dev/null +++ b/src/main/events/rpc/routes/plugin/index.ts @@ -0,0 +1,37 @@ + +import { RPCRouter } from '~/events/rpc/router' +import { + pluginImportLocalFunc, + pluginInstallFunc, + pluginGetListFunc, + pluginUpdateAllFunc +} from '~/events/rpc/routes/plugin/utils' + +import { IRPCActionType } from '#/types/enum' + +const pluginRouter = new RPCRouter() + +const pluginRoutes = [ + { + action: IRPCActionType.PLUGIN_GET_LIST, + handler: pluginGetListFunc + }, + { + action: IRPCActionType.PLUGIN_INSTALL, + handler: pluginInstallFunc + }, + { + action: IRPCActionType.PLUGIN_IMPORT_LOCAL, + handler: pluginImportLocalFunc + }, + { + action: IRPCActionType.PLUGIN_UPDATE_ALL, + handler: pluginUpdateAllFunc + } +] + +pluginRouter.addBatch(pluginRoutes) + +export { + pluginRouter +} diff --git a/src/main/events/rpc/routes/plugin/utils.ts b/src/main/events/rpc/routes/plugin/utils.ts new file mode 100644 index 00000000..37711074 --- /dev/null +++ b/src/main/events/rpc/routes/plugin/utils.ts @@ -0,0 +1,227 @@ +import { dialog, shell } from 'electron' +import { IGuiMenuItem, PicGo as PicGoCore } from 'piclist' +import path from 'path' + +import { dbPathDir } from '@core/datastore/dbChecker' +import picgo from '@core/picgo' + +import shortKeyHandler from 'apis/app/shortKey/shortKeyHandler' +import windowManager from 'apis/app/window/windowManager' + +import { T } from '~/i18n' +import { showNotification } from '~/utils/common' + +import { handleStreamlinePluginName, simpleClone } from '#/utils/common' +import { ICOREBuildInEvent, IPicGoHelperType, IWindowList } from '#/types/enum' + +const STORE_PATH = dbPathDir() + +// eslint-disable-next-line +const requireFunc = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require + +// get uploader or transformer config +const getConfig = (name: string, type: IPicGoHelperType, ctx: PicGoCore) => { + let config: any[] = [] + if (name === '') { + return config + } else { + const handler = ctx.helper[type].get(name) + if (handler) { + if (handler.config) { + config = handler.config(ctx) + } + } + return config + } +} + +const handleConfigWithFunction = (config: any[]) => { + for (const i in config) { + if (typeof config[i].default === 'function') { + config[i].default = config[i].default() + } + if (typeof config[i].choices === 'function') { + config[i].choices = config[i].choices() + } + } + return config +} + +const getPluginList = (): IPicGoPlugin[] => { + const pluginList = picgo.pluginLoader.getFullList() + const list = [] + for (const i in pluginList) { + const plugin = picgo.pluginLoader.getPlugin(pluginList[i])! + const pluginPath = path.join(STORE_PATH, `/node_modules/${pluginList[i]}`) + const pluginPKG = requireFunc(path.join(pluginPath, 'package.json')) + const uploaderName = plugin.uploader || '' + const transformerName = plugin.transformer || '' + let menu: Omit[] = [] + if (plugin.guiMenu) { + menu = plugin.guiMenu(picgo).map(item => ({ + label: item.label + })) + } + let gui = false + if (pluginPKG.keywords && pluginPKG.keywords.length > 0) { + if (pluginPKG.keywords.includes('picgo-gui-plugin')) { + gui = true + } + } + const obj: IPicGoPlugin = { + name: handleStreamlinePluginName(pluginList[i]), + fullName: pluginList[i], + author: pluginPKG.author.name || pluginPKG.author, + description: pluginPKG.description, + logo: 'file://' + path.join(pluginPath, 'logo.png').split(path.sep).join('/'), + version: pluginPKG.version, + gui, + config: { + plugin: { + fullName: pluginList[i], + name: handleStreamlinePluginName(pluginList[i]), + config: plugin.config ? handleConfigWithFunction(plugin.config(picgo)) : [] + }, + uploader: { + name: uploaderName, + config: handleConfigWithFunction(getConfig(uploaderName, IPicGoHelperType.uploader, picgo)) + }, + transformer: { + name: transformerName, + config: handleConfigWithFunction(getConfig(uploaderName, IPicGoHelperType.transformer, picgo)) + } + }, + enabled: picgo.getConfig(`picgoPlugins.${pluginList[i]}`), + homepage: pluginPKG.homepage ? pluginPKG.homepage : '', + guiMenu: menu, + ing: false + } + list.push(obj) + } + return list +} + +const handleNPMError = (): IDispose => { + const handler = (msg: string) => { + if (msg === 'NPM is not installed') { + dialog.showMessageBox({ + title: T('TIPS_ERROR'), + message: T('TIPS_INSTALL_NODE_AND_RELOAD_PICGO'), + buttons: ['Yes'] + }).then((res) => { + if (res.response === 0) { + shell.openExternal('https://nodejs.org/') + } + }) + } + } + picgo.once(ICOREBuildInEvent.FAILED, handler) + return () => picgo.off(ICOREBuildInEvent.FAILED, handler) +} + +export const handlePluginUpdate = async (fullName: string | string[]) => { + const window = windowManager.get(IWindowList.SETTING_WINDOW)! + const dispose = handleNPMError() + const res = await picgo.pluginHandler.update(typeof fullName === 'string' ? [fullName] : fullName) + if (res.success) { + window.webContents.send('updateSuccess', res.body[0]) + } else { + showNotification({ + title: T('PLUGIN_UPDATE_FAILED'), + body: res.body as string + }) + } + window.webContents.send('hideLoading') + dispose() +} + +export const handlePluginUninstall = async (fullName: string) => { + const window = windowManager.get(IWindowList.SETTING_WINDOW)! + const dispose = handleNPMError() + const res = await picgo.pluginHandler.uninstall([fullName]) + if (res.success) { + window.webContents.send('uninstallSuccess', res.body[0]) + shortKeyHandler.unregisterPluginShortKey(res.body[0]) + } else { + showNotification({ + title: T('PLUGIN_UNINSTALL_FAILED'), + body: res.body as string + }) + } + window.webContents.send('hideLoading') + dispose() +} + +export const pluginGetListFunc = async (event: IIPCEvent) => { + try { + const list = simpleClone(getPluginList()) + // here can just send JS Object not function + // or will cause [Failed to serialize arguments] error + event.sender.send('pluginList', list) + } catch (e: any) { + event.sender.send('pluginList', []) + showNotification({ + title: T('TIPS_GET_PLUGIN_LIST_FAILED'), + body: e.message + }) + picgo.log.error(e) + } +} + +export const pluginInstallFunc = async (event: IIPCEvent, args: [fullName: string]) => { + const fullName = args[0] + const dispose = handleNPMError() + const res = await picgo.pluginHandler.install([fullName]) + event.sender.send('installPlugin', { + success: res.success, + body: fullName, + errMsg: res.success ? '' : res.body + }) + if (res.success) { + shortKeyHandler.registerPluginShortKey(res.body[0]) + } else { + showNotification({ + title: T('PLUGIN_INSTALL_FAILED'), + body: res.body as string + }) + } + event.sender.send('hideLoading') + dispose() +} + +export const pluginImportLocalFunc = async (event: IIPCEvent) => { + const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)! + const res = await dialog.showOpenDialog(settingWindow, { + properties: ['openDirectory'] + }) + const filePaths = res.filePaths + if (filePaths.length > 0) { + const res = await picgo.pluginHandler.install(filePaths) + if (res.success) { + try { + const list = simpleClone(getPluginList()) + event.sender.send('pluginList', list) + } catch (e: any) { + event.sender.send('pluginList', []) + showNotification({ + title: T('TIPS_GET_PLUGIN_LIST_FAILED'), + body: e.message + }) + } + showNotification({ + title: T('PLUGIN_IMPORT_SUCCEED'), + body: '' + }) + } else { + showNotification({ + title: T('PLUGIN_IMPORT_FAILED'), + body: res.body as string + }) + } + } + event.sender.send('hideLoading') +} + +export const pluginUpdateAllFunc = async (_: IIPCEvent, args: [list: string[]]) => { + handlePluginUpdate(args[0]) +} diff --git a/src/main/events/rpc/routes/setting/advanced.ts b/src/main/events/rpc/routes/setting/advanced.ts new file mode 100644 index 00000000..9edef769 --- /dev/null +++ b/src/main/events/rpc/routes/setting/advanced.ts @@ -0,0 +1,25 @@ + +import { IRPCActionType } from '#/types/enum' +import server from '~/server' +import webServer from '~/server/webServer' + +export default [ + { + action: IRPCActionType.ADVANCED_UPDATE_SERVER, + handler: async () => { + server.restart() + } + }, + { + action: IRPCActionType.ADVANCED_STOP_WEB_SERVER, + handler: async () => { + webServer.stop() + } + }, + { + action: IRPCActionType.ADVANCED_RESTART_WEB_SERVER, + handler: async () => { + webServer.restart() + } + } +] diff --git a/src/main/events/rpc/routes/setting/configure.ts b/src/main/events/rpc/routes/setting/configure.ts new file mode 100644 index 00000000..0987cad6 --- /dev/null +++ b/src/main/events/rpc/routes/setting/configure.ts @@ -0,0 +1,82 @@ +import { app } from 'electron' +import fs from 'fs-extra' +import path from 'path' + +import logger from '@core/picgo/logger' +import { downloadFile, uploadFile } from '~/utils/syncSettings' +import { IRPCActionType, IRPCType } from '#/types/enum' + +const STORE_PATH = app.getPath('userData') + +const commonConfigList = ['data.json', 'data.bak.json'] +const manageConfigList = ['manage.json', 'manage.bak.json'] + +export default [ + { + action: IRPCActionType.CONFIGURE_MIGRATE_FROM_PICGO, + handler: async () => { + const picGoConfigPath = STORE_PATH.replace('piclist', 'picgo') + const files = [ + 'data.json', + 'data.bak.json', + 'picgo.db', + 'picgo.bak.db' + ] + try { + await Promise.all(files.map(async file => { + const sourcePath = path.join(picGoConfigPath, file) + const targetPath = path.join(STORE_PATH, file.replace('picgo', 'piclist')) + await fs.copy(sourcePath, targetPath, { overwrite: true }) + } + )) + return true + } catch (err: any) { + logger.error(err) + throw new Error('Migrate failed') + } + }, + type: IRPCType.INVOKE + }, + { + action: IRPCActionType.CONFIGURE_UPLOAD_COMMON_CONFIG, + handler: async () => { + return await uploadFile(commonConfigList) + }, + type: IRPCType.INVOKE + }, + { + action: IRPCActionType.CONFIGURE_UPLOAD_MANAGE_CONFIG, + handler: async () => { + return await uploadFile(manageConfigList) + }, + type: IRPCType.INVOKE + }, + { + action: IRPCActionType.CONFIGURE_UPLOAD_ALL_CONFIG, + handler: async () => { + return await uploadFile([...commonConfigList, ...manageConfigList]) + }, + type: IRPCType.INVOKE + }, + { + action: IRPCActionType.CONFIGURE_DOWNLOAD_COMMON_CONFIG, + handler: async () => { + return await downloadFile(commonConfigList) + }, + type: IRPCType.INVOKE + }, + { + action: IRPCActionType.CONFIGURE_DOWNLOAD_MANAGE_CONFIG, + handler: async () => { + return await downloadFile(manageConfigList) + }, + type: IRPCType.INVOKE + }, + { + action: IRPCActionType.CONFIGURE_DOWNLOAD_ALL_CONFIG, + handler: async () => { + return await downloadFile([...commonConfigList, ...manageConfigList]) + }, + type: IRPCType.INVOKE + } +] diff --git a/src/main/events/rpc/routes/setting/index.ts b/src/main/events/rpc/routes/setting/index.ts new file mode 100644 index 00000000..20ecdbe9 --- /dev/null +++ b/src/main/events/rpc/routes/setting/index.ts @@ -0,0 +1,21 @@ +import { RPCRouter } from '~/events/rpc/router' + +import advancedRoutes from '~/events/rpc/routes/setting/advanced' +import configureRoutes from '~/events/rpc/routes/setting/configure' +import mainAppRoutes from '~/events/rpc/routes/setting/mainApp' +import shortKeyRoutes from '~/events/rpc/routes/setting/shortKey' + +const settingRouter = new RPCRouter() + +const settingRoutes = [ + ...advancedRoutes, + ...configureRoutes, + ...mainAppRoutes, + ...shortKeyRoutes +] + +settingRouter.addBatch(settingRoutes) + +export { + settingRouter +} diff --git a/src/main/events/rpc/routes/setting/mainApp.ts b/src/main/events/rpc/routes/setting/mainApp.ts new file mode 100644 index 00000000..4851e9e4 --- /dev/null +++ b/src/main/events/rpc/routes/setting/mainApp.ts @@ -0,0 +1,65 @@ +import { app, IpcMainEvent, shell } from 'electron' +import fs from 'fs-extra' +import path from 'path' + +import picgo from '@core/picgo' +import { dbPathDir } from '@core/datastore/dbChecker' + +import { IRPCActionType, IRPCType } from '#/types/enum' + +const STORE_PATH = dbPathDir() + +export default [ + { + action: IRPCActionType.PICLIST_GET_CONFIG, + handler: async (_: IIPCEvent, args: [key?: string]) => { + return picgo.getConfig(args[0]) + }, + type: IRPCType.INVOKE + }, + { + action: IRPCActionType.PICLIST_GET_CONFIG_SYNC, + handler: async (event: IIPCEvent, args: [key?: string]) => { + const result = picgo.getConfig(args[0]) + const eventInstance = event as IpcMainEvent + eventInstance.returnValue = result + } + }, + { + action: IRPCActionType.PICLIST_SAVE_CONFIG, + handler: async (_: IIPCEvent, args: [data: IObj]) => { + picgo.saveConfig(args[0]) + } + }, + { + action: IRPCActionType.PICLIST_OPEN_FILE, + handler: async (_: IIPCEvent, args: [fileName: string]) => { + const abFilePath = path.join(STORE_PATH, args[0]) + if (!fs.existsSync(abFilePath)) { + fs.writeFileSync(abFilePath, '') + } + shell.openPath(abFilePath) + } + }, + { + action: IRPCActionType.PICLIST_OPEN_DIRECTORY, + handler: async (_: IIPCEvent, args: [dirPath?: string, inStorePath?: boolean]) => { + let [dirPath, inStorePath = true] = args + if (inStorePath) { + dirPath = path.join(STORE_PATH, dirPath || '') + } + if (!dirPath || !fs.existsSync(dirPath)) { + return + } + shell.openPath(dirPath) + } + }, + { + action: IRPCActionType.PICLIST_AUTO_START, + handler: async (_: IIPCEvent, args: [val: boolean]) => { + app.setLoginItemSettings({ + openAtLogin: args[0] + }) + } + } +] diff --git a/src/main/events/rpc/routes/setting/shortKey.ts b/src/main/events/rpc/routes/setting/shortKey.ts new file mode 100644 index 00000000..9cf4443c --- /dev/null +++ b/src/main/events/rpc/routes/setting/shortKey.ts @@ -0,0 +1,46 @@ +import { + Notification +} from 'electron' + +import bus from '@core/bus' +import shortKeyHandler from 'apis/app/shortKey/shortKeyHandler' +import { T } from '~/i18n' + +import { IRPCActionType, IRPCType } from '#/types/enum' +import { TOGGLE_SHORTKEY_MODIFIED_MODE } from '#/events/constants' + +const notificationFunc = (result: boolean) => { + const notification = new Notification({ + title: T(`OPERATION_${result ? 'SUCCEED' : 'FAILED'}`), + body: T(`TIPS_SHORTCUT_MODIFIED_${result ? 'SUCCEED' : 'CONFLICT'}`) + }) + notification.show() +} + +export default [ + { + action: IRPCActionType.SHORTKEY_UPDATE, + handler: async (_: IIPCEvent, args: [item: IShortKeyConfig, oldKey: string, from: string]) => { + const [item, oldKey, from] = args + const result = shortKeyHandler.updateShortKey(item, oldKey, from) + notificationFunc(result) + return result + }, + type: IRPCType.INVOKE + }, + { + action: IRPCActionType.SHORTKEY_BIND_OR_UNBIND, + handler: async (_: IIPCEvent, args: [item: IShortKeyConfig, from: string]) => { + const [item, from] = args + const result = shortKeyHandler.bindOrUnbindShortKey(item, from) + notificationFunc(result) + } + }, + { + action: IRPCActionType.SHORTKEY_TOGGLE_SHORTKEY_MODIFIED_MODE, + handler: async (_: IIPCEvent, args: [status: boolean]) => { + const [status] = args + bus.emit(TOGGLE_SHORTKEY_MODIFIED_MODE, status) + } + } +] diff --git a/src/main/events/rpc/routes/system.ts b/src/main/events/rpc/routes/system.ts deleted file mode 100644 index e7180f6d..00000000 --- a/src/main/events/rpc/routes/system.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { app, clipboard, shell } from 'electron' - -import { RPCRouter } from '~/events/rpc/router' -import { IRPCActionType } from '#/types/enum' - -const systemRouter = new RPCRouter() - -systemRouter - .add(IRPCActionType.RELOAD_APP, async () => { - app.relaunch() - app.exit(0) - }) - .add(IRPCActionType.OPEN_FILE, async (args) => { - const [filePath] = args as IOpenFileArgs - shell.openPath(filePath) - }) - .add(IRPCActionType.COPY_TEXT, async (args) => { - const [text] = args as ICopyTextArgs - return clipboard.writeText(text) - }) - -export { - systemRouter -} diff --git a/src/main/events/rpc/routes/system/app.ts b/src/main/events/rpc/routes/system/app.ts new file mode 100644 index 00000000..6a9b76fd --- /dev/null +++ b/src/main/events/rpc/routes/system/app.ts @@ -0,0 +1,62 @@ +import { app, shell } from 'electron' + +import picgo from '@core/picgo' + +import windowManager from 'apis/app/window/windowManager' +import { i18nManager } from '~/i18n' + +import { IRPCActionType, IWindowList } from '#/types/enum' +import { GET_CURRENT_LANGUAGE, GET_LANGUAGE_LIST, SET_CURRENT_LANGUAGE } from '#/events/constants' + +export default [ + { + action: IRPCActionType.RELOAD_APP, + handler: async () => { + app.relaunch() + app.exit(0) + } + }, + { + action: IRPCActionType.OPEN_FILE, + handler: async (_: IIPCEvent, args: [filePath: string]) => { + shell.openPath(args[0]) + } + }, + { + action: IRPCActionType.OPEN_URL, + handler: async (_: IIPCEvent, args: [url: string]) => { + shell.openExternal(args[0]) + } + }, + { + action: IRPCActionType.GET_LANGUAGE_LIST, + handler: async (event: IIPCEvent) => { + event.sender.send(GET_LANGUAGE_LIST, i18nManager.languageList) + } + }, + { + action: IRPCActionType.GET_CURRENT_LANGUAGE, + handler: async (event: IIPCEvent) => { + const { lang, locales } = i18nManager.getCurrentLocales() + event.sender.send(GET_CURRENT_LANGUAGE, lang, locales) + } + }, + { + action: IRPCActionType.SET_CURRENT_LANGUAGE, + handler: async (_: IIPCEvent, args: [language: string]) => { + i18nManager.setCurrentLanguage(args[0]) + const { lang, locales } = i18nManager.getCurrentLocales() + picgo.i18n.setLanguage(lang) + if (process.platform === 'darwin') { + const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW) + trayWindow?.webContents.send(SET_CURRENT_LANGUAGE, lang, locales) + } + const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW) + settingWindow?.webContents.send(SET_CURRENT_LANGUAGE, lang, locales) + if (windowManager.has(IWindowList.MINI_WINDOW)) { + const miniWindow = windowManager.get(IWindowList.MINI_WINDOW) + miniWindow?.webContents.send(SET_CURRENT_LANGUAGE, lang, locales) + } + } + } +] diff --git a/src/main/events/rpc/routes/system/index.ts b/src/main/events/rpc/routes/system/index.ts new file mode 100644 index 00000000..9b5a6df4 --- /dev/null +++ b/src/main/events/rpc/routes/system/index.ts @@ -0,0 +1,16 @@ +import { RPCRouter } from '~/events/rpc/router' +import appRoutes from '~/events/rpc/routes/system/app' +import windowRoutes from '~/events/rpc/routes/system/window' + +const systemRouter = new RPCRouter() + +const systemRoutes = [ + ...appRoutes, + ...windowRoutes +] + +systemRouter.addBatch(systemRoutes) + +export { + systemRouter +} diff --git a/src/main/events/rpc/routes/system/window.ts b/src/main/events/rpc/routes/system/window.ts new file mode 100644 index 00000000..e2a70cd1 --- /dev/null +++ b/src/main/events/rpc/routes/system/window.ts @@ -0,0 +1,137 @@ +import { app, BrowserWindow } from 'electron' + +import windowManager from 'apis/app/window/windowManager' + +import { + buildMainPageMenu, + buildMiniPageMenu, + buildPicBedListMenu, + buildPluginPageMenu +} from '~/events/remotes/menu' +import { openMiniWindow } from '~/utils/windowHelper' + +import { IRPCActionType, IWindowList } from '#/types/enum' + +export default [ + { + action: IRPCActionType.HIDE_DOCK, + handler: async (_: IIPCEvent, args: [value: boolean]) => { + args[0] ? app.dock.hide() : app.dock.show() + } + }, + { + action: IRPCActionType.OPEN_WINDOW, + handler: async (_: IIPCEvent, args: [windowName: IWindowList]) => { + const window = windowManager.get(args[0]) + if (window) { + window.show() + } + } + }, + { + action: IRPCActionType.OPEN_MANUAL_WINDOW, + handler: async () => { + windowManager.get(IWindowList.MANUAL_WINDOW)!.show() + } + }, + { + action: IRPCActionType.OPEN_MINI_WINDOW, + handler: async () => { + openMiniWindow() + } + }, + { + action: IRPCActionType.CLOSE_WINDOW, + handler: async () => { + const window = BrowserWindow.getFocusedWindow() + if (process.platform === 'linux') { + window?.hide() + } else { + window?.close() + } + } + }, + { + action: IRPCActionType.MINIMIZE_WINDOW, + handler: async () => { + const window = BrowserWindow.getFocusedWindow() + window?.minimize() + } + }, + { + action: IRPCActionType.SHOW_MINI_PAGE_MENU, + handler: async () => { + const window = windowManager.get(IWindowList.MINI_WINDOW)! + const menu = buildMiniPageMenu() + menu.popup({ + window + }) + } + }, + { + action: IRPCActionType.SHOW_MAIN_PAGE_MENU, + handler: async () => { + const window = windowManager.get(IWindowList.SETTING_WINDOW)! + const menu = buildMainPageMenu(window) + menu.popup({ + window + }) + } + }, + { + action: IRPCActionType.SHOW_UPLOAD_PAGE_MENU, + handler: async () => { + const window = windowManager.get(IWindowList.SETTING_WINDOW)! + const menu = buildPicBedListMenu() + menu.popup({ + window + }) + } + }, + { + action: IRPCActionType.SHOW_PLUGIN_PAGE_MENU, + handler: async (_: IIPCEvent, args: [plugin: IPicGoPlugin]) => { + const window = windowManager.get(IWindowList.SETTING_WINDOW)! + const menu = buildPluginPageMenu(args[0]) + menu.popup({ + window + }) + } + }, + { + action: IRPCActionType.SET_MINI_WINDOW_POS, + handler: async (_: IIPCEvent, args: [pos: IMiniWindowPos]) => { + const window = BrowserWindow.getFocusedWindow() + window?.setBounds(args[0]) + } + }, + { + action: IRPCActionType.MINI_WINDOW_ON_TOP, + handler: async (_: IIPCEvent, args: [isOnTop: boolean]) => { + const miniWindow = windowManager.get(IWindowList.MINI_WINDOW)! + miniWindow.setAlwaysOnTop(args[0]) + } + }, + { + action: IRPCActionType.MAIN_WINDOW_ON_TOP, + handler: async () => { + const mainWindow = windowManager.get(IWindowList.SETTING_WINDOW)! + const isAlwaysOnTop = mainWindow.isAlwaysOnTop() + mainWindow.setAlwaysOnTop(!isAlwaysOnTop) + } + }, + { + action: IRPCActionType.UPDATE_MINI_WINDOW_ICON, + handler: async (_: IIPCEvent, args: [iconPath: string]) => { + const miniWindow = windowManager.get(IWindowList.MINI_WINDOW)! + miniWindow.webContents.send('updateMiniIcon', args[0]) + } + }, + { + action: IRPCActionType.REFRESH_SETTING_WINDOW, + handler: async () => { + const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)! + settingWindow.webContents.reloadIgnoringCache() + } + } +] diff --git a/src/main/events/rpc/routes/toolbox/checkProxy.ts b/src/main/events/rpc/routes/toolbox/checkProxy.ts index b2a0f6b5..5e7b680d 100644 --- a/src/main/events/rpc/routes/toolbox/checkProxy.ts +++ b/src/main/events/rpc/routes/toolbox/checkProxy.ts @@ -11,7 +11,7 @@ import { T } from '~/i18n' import { IToolboxItemCheckStatus, IToolboxItemType } from '#/types/enum' -const getProxy = (proxyStr: string): AxiosRequestConfig['proxy'] | false => { +function getProxy (proxyStr: string): AxiosRequestConfig['proxy'] | null { if (proxyStr) { try { const proxyOptions = new URL(proxyStr) @@ -20,10 +20,9 @@ const getProxy = (proxyStr: string): AxiosRequestConfig['proxy'] | false => { port: parseInt(proxyOptions.port || '0', 10), protocol: proxyOptions.protocol } - } catch (e) { - } + } catch (e) {} } - return false + return null } const sendToolboxRes = sendToolboxResWithType(IToolboxItemType.HAS_PROBLEM_WITH_PROXY) diff --git a/src/main/events/rpc/routes/toolbox/index.ts b/src/main/events/rpc/routes/toolbox/index.ts index 934b8155..dfc7db1d 100644 --- a/src/main/events/rpc/routes/toolbox/index.ts +++ b/src/main/events/rpc/routes/toolbox/index.ts @@ -3,7 +3,8 @@ import { checkFileMap, fixFileMap } from '~/events/rpc/routes/toolbox/checkFile' import { checkProxyMap } from '~/events/rpc/routes/toolbox/checkProxy' import { RPCRouter } from '~/events/rpc/router' -import { IRPCActionType, IToolboxItemType } from '#/types/enum' +import { IRPCActionType, IRPCType, IToolboxItemType } from '#/types/enum' +import { IpcMainEvent } from 'electron' const toolboxRouter = new RPCRouter() @@ -19,30 +20,37 @@ const toolboxFixMap: Partial> = { } toolboxRouter - .add(IRPCActionType.TOOLBOX_CHECK, async (args, event) => { - const [type] = args as IToolboxCheckArgs - if (type) { - const handler = toolboxCheckMap[type] - if (handler) { - handler(event) - } - } else { - // do check all - for (const key in toolboxCheckMap) { - const handler = toolboxCheckMap[key as IToolboxItemType] + .add( + IRPCActionType.TOOLBOX_CHECK, + async (event, args) => { + const [type] = args as IToolboxCheckArgs + if (type) { + const handler = toolboxCheckMap[type] if (handler) { - handler(event) + handler(event as IpcMainEvent) + } + } else { + // do check all + for (const key in toolboxCheckMap) { + const handler = toolboxCheckMap[key as IToolboxItemType] + if (handler) { + handler(event as IpcMainEvent) + } } } - } - }) - .add(IRPCActionType.TOOLBOX_CHECK_FIX, async (args, event) => { - const [type] = args as IToolboxCheckArgs - const handler = toolboxFixMap[type] - if (handler) { - return await handler(event) - } - }) + }, + IRPCType.SEND + ) + .add( + IRPCActionType.TOOLBOX_CHECK_FIX, async (event, args) => { + const [type] = args as IToolboxCheckArgs + const handler = toolboxFixMap[type] + if (handler) { + return await handler(event as IpcMainEvent) + } + }, + IRPCType.INVOKE + ) export { toolboxRouter diff --git a/src/main/events/rpc/routes/toolbox/utils.ts b/src/main/events/rpc/routes/toolbox/utils.ts index 7b8a18b1..4cb34285 100644 --- a/src/main/events/rpc/routes/toolbox/utils.ts +++ b/src/main/events/rpc/routes/toolbox/utils.ts @@ -1,9 +1,11 @@ import { IpcMainEvent } from 'electron' import { IRPCActionType, IToolboxItemType } from '#/types/enum' -export const sendToolboxResWithType = (type: IToolboxItemType) => (event: IpcMainEvent, res?: Omit) => { - return event.sender.send(IRPCActionType.TOOLBOX_CHECK_RES, { - ...res, - type - }) +export function sendToolboxResWithType (type: IToolboxItemType) { + return (event: IpcMainEvent, res?: Omit) => { + return event.sender.send(IRPCActionType.TOOLBOX_CHECK_RES, { + ...res, + type + }) + } } diff --git a/src/main/events/rpc/routes/tray/index.ts b/src/main/events/rpc/routes/tray/index.ts new file mode 100644 index 00000000..40906dc9 --- /dev/null +++ b/src/main/events/rpc/routes/tray/index.ts @@ -0,0 +1,71 @@ +import { + Notification +} from 'electron' + +import { RPCRouter } from '~/events/rpc/router' +import { generateShortUrl, setTrayToolTip, handleCopyUrl } from '~/utils/common' + +import { IRPCActionType, IRPCType, IPasteStyle, IWindowList } from '#/types/enum' + +import db, { GalleryDB } from '@core/datastore' + +import uploader from 'apis/app/uploader' +import windowManager from 'apis/app/window/windowManager' + +import { T } from '~/i18n' + +import pasteTemplate from '~/utils/pasteTemplate' + +import { configPaths } from '#/utils/configPaths' + +const trayRouter = new RPCRouter() + +const trayRoutes = [ + { + action: IRPCActionType.TRAY_SET_TOOL_TIP, + handler: async (_: IIPCEvent, args: [text: string]) => { + setTrayToolTip(args[0]) + } + }, + { + action: IRPCActionType.TRAY_GET_SHORT_URL, + handler: async (_: IIPCEvent, args: [url: string]) => { + return await generateShortUrl(args[0]) + }, + type: IRPCType.INVOKE + }, + { + action: IRPCActionType.TRAY_UPLOAD_CLIPBOARD_FILES, + handler: async () => { + const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)! + // macOS use builtin clipboard is OK + const img = await uploader.setWebContents(trayWindow.webContents).uploadWithBuildInClipboard() + if (img !== false) { + const pasteStyle = db.get(configPaths.settings.pasteStyle) || IPasteStyle.MARKDOWN + handleCopyUrl(await (pasteTemplate(pasteStyle, img[0], db.get(configPaths.settings.customLink)))) + const isShowResultNotification = db.get(configPaths.settings.uploadResultNotification) === undefined ? true : !!db.get(configPaths.settings.uploadResultNotification) + if (isShowResultNotification) { + const notification = new Notification({ + title: T('UPLOAD_SUCCEED'), + body: img[0].imgUrl! + // icon: file[0] + // icon: img[0].imgUrl + }) + notification.show() + } + await GalleryDB.getInstance().insert(img[0]) + trayWindow.webContents.send('clipboardFiles', []) + if (windowManager.has(IWindowList.SETTING_WINDOW)) { + windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('updateGallery') + } + } + trayWindow.webContents.send('uploadFiles') + } + } +] + +trayRouter.addBatch(trayRoutes) + +export { + trayRouter +} diff --git a/src/main/events/rpc/routes/upload/index.ts b/src/main/events/rpc/routes/upload/index.ts new file mode 100644 index 00000000..740f7cb3 --- /dev/null +++ b/src/main/events/rpc/routes/upload/index.ts @@ -0,0 +1,35 @@ +import { RPCRouter } from '~/events/rpc/router' +import getPicBeds from '~/utils/getPicBeds' +import { uploadChoosedFiles, uploadClipboardFiles } from '~/apis/app/uploader/apis' + +import { IRPCActionType, IRPCType } from '#/types/enum' + +const uploadRouter = new RPCRouter() + +const uploadRoutes = [ + { + action: IRPCActionType.MAIN_GET_PICBED, + handler: async () => { + return getPicBeds() + }, + type: IRPCType.INVOKE + }, + { + action: IRPCActionType.UPLOAD_CLIPBOARD_FILES_FROM_UPLOAD_PAGE, + handler: async () => { + uploadClipboardFiles() + } + }, + { + action: IRPCActionType.UPLOAD_CHOOSED_FILES, + handler: async (evt: IIPCEvent, args: [files: IFileWithPath[]]) => { + return uploadChoosedFiles(evt.sender, args[0]) + } + } +] + +uploadRouter.addBatch(uploadRoutes) + +export { + uploadRouter +} diff --git a/src/main/fileServer/index.ts b/src/main/fileServer/index.ts index b6701986..06ec7ca1 100644 --- a/src/main/fileServer/index.ts +++ b/src/main/fileServer/index.ts @@ -10,8 +10,10 @@ fs.ensureDirSync(imgFilePath) const serverPort = 36699 +let server: http.Server + export function startFileServer () { - const server = http.createServer((req, res) => { + server = http.createServer((req, res) => { const requestPath = req.url?.split('?')[0] const filePath = path.join(imgFilePath, decodeURIComponent(requestPath as string)) @@ -31,3 +33,9 @@ export function startFileServer () { logger.error(err) }) } + +export function stopFileServer () { + server.close(() => { + logger.info('File server is stopped') + }) +} diff --git a/src/main/lifeCycle/index.ts b/src/main/lifeCycle/index.ts index 169c7bd3..ccba9f7d 100644 --- a/src/main/lifeCycle/index.ts +++ b/src/main/lifeCycle/index.ts @@ -32,8 +32,7 @@ import { import windowManager from 'apis/app/window/windowManager' import busEventList from '~/events/busEventList' -import ipcList from '~/events/ipcList' -import { startFileServer } from '~/fileServer' +import { startFileServer, stopFileServer } from '~/fileServer' import { T } from '~/i18n' import '~/lifeCycle/errorHandler' import fixPath from '~/lifeCycle/fixPath' @@ -52,6 +51,7 @@ import updateChecker from '~/utils/updateChecker' import { II18nLanguage, IRemoteNoticeTriggerHook, ISartMode, IWindowList } from '#/types/enum' import { configPaths } from '#/utils/configPaths' import { CLIPBOARD_IMAGE_FOLDER } from '#/utils/static' +import { rpcServer } from '~/events/rpc' const isDevelopment = process.env.NODE_ENV !== 'production' @@ -146,7 +146,7 @@ class LifeCycle { fixPath() beforeOpen() initI18n() - ipcList.listen() + rpcServer.start() getManageApi() UpDownTaskQueue.getInstance() manageIpcList.listen() @@ -285,6 +285,8 @@ class LifeCycle { globalShortcut.unregisterAll() bus.removeAllListeners() server.shutdown() + webServer.stop() + stopFileServer() }) // Exit cleanly on request from parent process in development mode. if (isDevelopment) { diff --git a/src/main/manage/events/manageCoreIPC.ts b/src/main/manage/events/manageCoreIPC.ts index 9ed27b09..04b48f56 100644 --- a/src/main/manage/events/manageCoreIPC.ts +++ b/src/main/manage/events/manageCoreIPC.ts @@ -1,5 +1,6 @@ import { IpcMainEvent, + IpcMainInvokeEvent, ipcMain } from 'electron' @@ -9,20 +10,19 @@ import { PICLIST_MANAGE_GET_CONFIG, PICLIST_MANAGE_SAVE_CONFIG, PICLIST_MANAGE_R const manageApi = getManageApi() const handleManageGetConfig = () => { - ipcMain.on(PICLIST_MANAGE_GET_CONFIG, (event: IpcMainEvent, key: string | undefined, callbackId: string) => { - const result = manageApi.getConfig(key) - event.sender.send(PICLIST_MANAGE_GET_CONFIG, result, callbackId) + ipcMain.handle(PICLIST_MANAGE_GET_CONFIG, (_: IpcMainInvokeEvent, key: string | undefined) => { + return manageApi.getConfig(key) }) } const handleManageSaveConfig = () => { - ipcMain.on(PICLIST_MANAGE_SAVE_CONFIG, (_event: IpcMainEvent, data: any) => { + ipcMain.on(PICLIST_MANAGE_SAVE_CONFIG, (_: IpcMainEvent, data: any) => { manageApi.saveConfig(data) }) } const handleManageRemoveConfig = () => { - ipcMain.on(PICLIST_MANAGE_REMOVE_CONFIG, (_event: IpcMainEvent, key: string, propName: string) => { + ipcMain.on(PICLIST_MANAGE_REMOVE_CONFIG, (_: IpcMainEvent, key: string, propName: string) => { manageApi.removeConfig(key, propName) }) } diff --git a/src/main/server/routerManager.ts b/src/main/server/routerManager.ts index 0a912273..34b7495f 100644 --- a/src/main/server/routerManager.ts +++ b/src/main/server/routerManager.ts @@ -11,12 +11,13 @@ import logger from '@core/picgo/logger' import { AESHelper } from '~/utils/aesHelper' import { changeCurrentUploader } from '~/utils/handleUploaderConfig' -import { uploadChoosedFiles, uploadClipboardFiles, deleteChoosedFiles } from 'apis/app/uploader/apis' +import { uploadChoosedFiles, uploadClipboardFiles } from 'apis/app/uploader/apis' import windowManager from 'apis/app/window/windowManager' import { markdownContent } from '~/server/apiDoc' import router from '~/server/router' import { + deleteChoosedFiles, handleResponse } from '~/server/utils' diff --git a/src/main/server/utils.ts b/src/main/server/utils.ts index ba6c4e7c..b1f6de89 100644 --- a/src/main/server/utils.ts +++ b/src/main/server/utils.ts @@ -1,4 +1,20 @@ +import { + Notification +} from 'electron' + +import picgo from '@core/picgo' import logger from '@core/picgo/logger' +import db, { GalleryDB } from '@core/datastore' + +import windowManager from 'apis/app/window/windowManager' + +import GuiApi from '~/apis/gui' +import { T } from '~/i18n/index' +import { configPaths } from '#/utils/configPaths' +import { picBedsCanbeDeleted } from '#/utils/static' +import { ICOREBuildInEvent, IWindowList } from '#/types/enum' + +import ALLApi from '@/apis/allApi' export const handleResponse = ({ response, @@ -31,3 +47,43 @@ export const ensureHTTPLink = (url: string): string => { ? url : `http://${url}` } + +export const deleteChoosedFiles = async (list: ImgInfo[]): Promise => { + const result = [] + for (const item of list) { + if (item.id) { + try { + const dbStore = GalleryDB.getInstance() + const file = await dbStore.getById(item.id) + await dbStore.removeById(item.id) + if (await db.get(configPaths.settings.deleteCloudFile)) { + if (item.type !== undefined && picBedsCanbeDeleted.includes(item.type)) { + const noteFunc = (value: boolean) => { + const notification = new Notification({ + title: T('MANAGE_BUCKET_BATCH_DELETE_ERROR_MSG_MSG2'), + body: T(value + ? 'GALLERY_SYNC_DELETE_NOTICE_SUCCEED' + : 'GALLERY_SYNC_DELETE_NOTICE_FAILED' + ) + }) + notification.show() + } + setTimeout(() => { + ALLApi.delete(item).then(noteFunc) + }, 0) + } + } + setTimeout(() => { + picgo.emit(ICOREBuildInEvent.REMOVE, [file], GuiApi.getInstance()) + }, 500) + result.push(true) + } catch (e) { + result.push(false) + } + } + } + if (windowManager.has(IWindowList.SETTING_WINDOW)) { + windowManager.get(IWindowList.SETTING_WINDOW)!.webContents?.send('updateGallery') + } + return result +} diff --git a/src/main/utils/deleteFunc.ts b/src/main/utils/deleteFunc.ts index efd3b1be..078abe43 100644 --- a/src/main/utils/deleteFunc.ts +++ b/src/main/utils/deleteFunc.ts @@ -2,10 +2,13 @@ import axios from 'axios' import crypto from 'crypto' import http, { AgentOptions } from 'http' import https from 'https' +import path from 'path' +import { ISftpPlistConfig } from 'piclist' import querystring from 'querystring' import { S3Client, DeleteObjectCommand, S3ClientConfig } from '@aws-sdk/client-s3' import { NodeHttpHandler } from '@smithy/node-http-handler' +import SSHClient from '~/utils/sshClient' import { getAgent } from '~/manage/utils/common' interface DogecloudTokenFull { @@ -219,3 +222,18 @@ export async function removeFileFromHuaweiInMain (configMap: IStringKeyMap) { return false } } + +export async function removeFileFromSFTPInMain (config: ISftpPlistConfig, fileName: string) { + try { + const client = SSHClient.instance + await client.connect(config) + const uploadPath = `/${(config.uploadPath || '')}/`.replace(/\/+/g, '/') + const remote = path.join(uploadPath, fileName) + const deleteResult = await client.deleteFileSFTP(config, remote) + client.close() + return deleteResult + } catch (err: any) { + console.log(err) + return false + } +} diff --git a/src/main/utils/windowHelper.ts b/src/main/utils/windowHelper.ts new file mode 100644 index 00000000..37175c47 --- /dev/null +++ b/src/main/utils/windowHelper.ts @@ -0,0 +1,56 @@ +import { screen } from 'electron' + +import db from '@core/datastore' + +import windowManager from 'apis/app/window/windowManager' + +import { IWindowList } from '#/types/enum' +import { configPaths } from '#/utils/configPaths' + +export function openMiniWindow (hideSettingWindow:boolean = true) { + const miniWindow = windowManager.get(IWindowList.MINI_WINDOW)! + miniWindow.removeAllListeners() + if (db.get(configPaths.settings.miniWindowOntop)) { + miniWindow.setAlwaysOnTop(true) + } + const { width, height } = screen.getPrimaryDisplay().workAreaSize + const lastPosition = db.get(configPaths.settings.miniWindowPosition) + if (lastPosition) { + miniWindow.setPosition(lastPosition[0], lastPosition[1]) + } else { + miniWindow.setPosition(width - 100, height - 100) + } + const setPositionFunc = () => { + const position = miniWindow.getPosition() + db.set(configPaths.settings.miniWindowPosition, position) + } + miniWindow.on('close', setPositionFunc) + miniWindow.on('move', setPositionFunc) + miniWindow.show() + miniWindow.focus() + if (hideSettingWindow) { + const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)! + settingWindow.hide() + } else { + const autoCloseMainWindow = db.get(configPaths.settings.autoCloseMainWindow) || false + if (windowManager.has(IWindowList.SETTING_WINDOW) && autoCloseMainWindow) { + windowManager.get(IWindowList.SETTING_WINDOW)!.hide() + } + } +} + +export const openMainWindow = () => { + const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW) + const autoCloseMiniWindow = db.get(configPaths.settings.autoCloseMiniWindow) || false + settingWindow!.show() + settingWindow!.focus() + if (windowManager.has(IWindowList.MINI_WINDOW) && autoCloseMiniWindow) { + windowManager.get(IWindowList.MINI_WINDOW)!.hide() + } +} + +export const hideMiniWindow = () => { + if (windowManager.has(IWindowList.MINI_WINDOW)) { + windowManager.get(IWindowList.MINI_WINDOW)!.hide() + } +} diff --git a/src/renderer/App.vue b/src/renderer/App.vue index de9c3739..5cb84e4f 100644 --- a/src/renderer/App.vue +++ b/src/renderer/App.vue @@ -17,6 +17,7 @@ import { FORCE_UPDATE } from '#/events/constants' useATagClick() const store = useStore() + onBeforeMount(async () => { const config = await getConfig() if (config) { diff --git a/src/renderer/apis/alist.ts b/src/renderer/apis/alist.ts index 99426e72..e70be769 100644 --- a/src/renderer/apis/alist.ts +++ b/src/renderer/apis/alist.ts @@ -1,7 +1,7 @@ import axios from 'axios' import path from 'path' -import { deleteFailedLog, deleteLog } from '@/utils/common' +import { deleteFailedLog, deleteLog } from '#/utils/deleteLog' interface IConfigMap { fileName: string diff --git a/src/renderer/apis/aliyun.ts b/src/renderer/apis/aliyun.ts index 495c6420..c02dcbb5 100644 --- a/src/renderer/apis/aliyun.ts +++ b/src/renderer/apis/aliyun.ts @@ -1,6 +1,6 @@ import OSS from 'ali-oss' -import { deleteFailedLog, deleteLog } from '@/utils/common' +import { deleteFailedLog, deleteLog } from '#/utils/deleteLog' interface IConfigMap { fileName: string diff --git a/src/renderer/apis/awss3.ts b/src/renderer/apis/awss3.ts index aaa4563f..5b4a3a5f 100644 --- a/src/renderer/apis/awss3.ts +++ b/src/renderer/apis/awss3.ts @@ -1,15 +1,15 @@ import { ipcRenderer } from 'electron' -import { deleteFailedLog, getRawData } from '@/utils/common' +import { getRawData, triggerRPC } from '@/utils/common' import { removeFileFromS3InMain } from '~/utils/deleteFunc' +import { deleteFailedLog } from '#/utils/deleteLog' +import { IRPCActionType } from '#/types/enum' export default class AwsS3Api { static async delete (configMap: IStringKeyMap): Promise { try { return ipcRenderer - ? await ipcRenderer.invoke('delete-aws-s3-file', - getRawData(configMap) - ) + ? await triggerRPC(IRPCActionType.GALLERY_DELETE_AWS_S3_FILE, getRawData(configMap)) || false : await removeFileFromS3InMain(getRawData(configMap)) } catch (error: any) { deleteFailedLog(configMap.fileName, 'AWS S3', error) diff --git a/src/renderer/apis/dogecloud.ts b/src/renderer/apis/dogecloud.ts index 796303c1..2e4796f3 100644 --- a/src/renderer/apis/dogecloud.ts +++ b/src/renderer/apis/dogecloud.ts @@ -1,15 +1,16 @@ import { ipcRenderer } from 'electron' -import { deleteFailedLog, getRawData } from '@/utils/common' import { removeFileFromDogeInMain } from '~/utils/deleteFunc' +import { getRawData, triggerRPC } from '@/utils/common' +import { deleteFailedLog } from '#/utils/deleteLog' +import { IRPCActionType } from '#/types/enum' + export default class AwsS3Api { static async delete (configMap: IStringKeyMap): Promise { try { return ipcRenderer - ? await ipcRenderer.invoke('delete-doge-file', - getRawData(configMap) - ) + ? await triggerRPC(IRPCActionType.GALLERY_DELETE_DOGE_FILE, getRawData(configMap)) || false : await removeFileFromDogeInMain(getRawData(configMap)) } catch (error: any) { deleteFailedLog(configMap.fileName, 'DogeCloud', error) diff --git a/src/renderer/apis/github.ts b/src/renderer/apis/github.ts index 6c848110..70091dc4 100644 --- a/src/renderer/apis/github.ts +++ b/src/renderer/apis/github.ts @@ -1,6 +1,6 @@ import { Octokit } from '@octokit/rest' -import { deleteFailedLog, deleteLog } from '@/utils/common' +import { deleteFailedLog, deleteLog } from '#/utils/deleteLog' interface IConfigMap { fileName: string diff --git a/src/renderer/apis/huaweiyun.ts b/src/renderer/apis/huaweiyun.ts index c840c119..5499e7a0 100644 --- a/src/renderer/apis/huaweiyun.ts +++ b/src/renderer/apis/huaweiyun.ts @@ -1,15 +1,16 @@ import { ipcRenderer } from 'electron' -import { deleteFailedLog, getRawData } from '@/utils/common' import { removeFileFromHuaweiInMain } from '~/utils/deleteFunc' +import { getRawData, triggerRPC } from '@/utils/common' +import { deleteFailedLog } from '#/utils/deleteLog' +import { IRPCActionType } from '#/types/enum' + export default class HuaweicloudApi { static async delete (configMap: IStringKeyMap): Promise { try { return ipcRenderer - ? await ipcRenderer.invoke('delete-huaweicloud-file', - getRawData(configMap) - ) + ? await triggerRPC(IRPCActionType.GALLERY_DELETE_HUAWEI_OSS_FILE, getRawData(configMap)) || false : await removeFileFromHuaweiInMain(getRawData(configMap)) } catch (error: any) { deleteFailedLog(configMap.fileName, 'HuaweiCloud', error) diff --git a/src/renderer/apis/imgur.ts b/src/renderer/apis/imgur.ts index 1167e246..4c980775 100644 --- a/src/renderer/apis/imgur.ts +++ b/src/renderer/apis/imgur.ts @@ -1,6 +1,6 @@ import axios, { AxiosResponse } from 'axios' -import { deleteFailedLog, deleteLog } from '@/utils/common' +import { deleteFailedLog, deleteLog } from '#/utils/deleteLog' interface IConfigMap { config?: Partial diff --git a/src/renderer/apis/local.ts b/src/renderer/apis/local.ts index 6951044a..1f08a5c3 100644 --- a/src/renderer/apis/local.ts +++ b/src/renderer/apis/local.ts @@ -1,6 +1,6 @@ import fs from 'fs-extra' -import { deleteFailedLog, deleteLog } from '@/utils/common' +import { deleteFailedLog, deleteLog } from '#/utils/deleteLog' interface IConfigMap { hash: string diff --git a/src/renderer/apis/lskyplist.ts b/src/renderer/apis/lskyplist.ts index a2e65432..e0404e33 100644 --- a/src/renderer/apis/lskyplist.ts +++ b/src/renderer/apis/lskyplist.ts @@ -1,7 +1,7 @@ import axios, { AxiosResponse } from 'axios' import https from 'https' -import { deleteFailedLog, deleteLog } from '@/utils/common' +import { deleteFailedLog, deleteLog } from '#/utils/deleteLog' export default class LskyplistApi { static async delete (configMap: IStringKeyMap): Promise { diff --git a/src/renderer/apis/piclist.ts b/src/renderer/apis/piclist.ts index eb9e93db..26a5607c 100644 --- a/src/renderer/apis/piclist.ts +++ b/src/renderer/apis/piclist.ts @@ -1,6 +1,6 @@ import axios, { AxiosResponse } from 'axios' -import { deleteFailedLog, deleteLog } from '@/utils/common' +import { deleteFailedLog, deleteLog } from '#/utils/deleteLog' export default class PiclistApi { static async delete (configMap: IStringKeyMap): Promise { diff --git a/src/renderer/apis/qiniu.ts b/src/renderer/apis/qiniu.ts index 4e619da3..12fa90e1 100644 --- a/src/renderer/apis/qiniu.ts +++ b/src/renderer/apis/qiniu.ts @@ -1,6 +1,6 @@ import Qiniu from 'qiniu' -import { deleteFailedLog, deleteLog } from '@/utils/common' +import { deleteFailedLog, deleteLog } from '#/utils/deleteLog' interface IConfigMap { fileName: string diff --git a/src/renderer/apis/sftpplist.ts b/src/renderer/apis/sftpplist.ts index 381a4b99..60a17d66 100644 --- a/src/renderer/apis/sftpplist.ts +++ b/src/renderer/apis/sftpplist.ts @@ -1,16 +1,19 @@ import { ipcRenderer } from 'electron' -import { deleteFailedLog, getRawData } from '@/utils/common' +import { removeFileFromSFTPInMain } from '~/utils/deleteFunc' + +import { getRawData, triggerRPC } from '@/utils/common' +import { deleteFailedLog } from '#/utils/deleteLog' +import { IRPCActionType } from '#/types/enum' export default class SftpPlistApi { static async delete (configMap: IStringKeyMap): Promise { const { fileName, config } = configMap try { - const deleteResult = await ipcRenderer.invoke('delete-sftp-file', - getRawData(config), - fileName - ) - return deleteResult + return ipcRenderer + ? await triggerRPC(IRPCActionType.GALLERY_DELETE_SFTP_FILE, getRawData(config), + fileName) || false + : await removeFileFromSFTPInMain(getRawData(config), fileName) } catch (error: any) { deleteFailedLog(fileName, 'SFTP', error) return false diff --git a/src/renderer/apis/smms.ts b/src/renderer/apis/smms.ts index b99a578a..bd21fb4a 100644 --- a/src/renderer/apis/smms.ts +++ b/src/renderer/apis/smms.ts @@ -1,6 +1,6 @@ import axios, { AxiosResponse } from 'axios' -import { deleteFailedLog, deleteLog } from '@/utils/common' +import { deleteFailedLog, deleteLog } from '#/utils/deleteLog' interface IConfigMap { hash?: string diff --git a/src/renderer/apis/tcyun.ts b/src/renderer/apis/tcyun.ts index bb348618..1026e20e 100644 --- a/src/renderer/apis/tcyun.ts +++ b/src/renderer/apis/tcyun.ts @@ -1,6 +1,6 @@ import COS from 'cos-nodejs-sdk-v5' -import { deleteFailedLog, deleteLog } from '@/utils/common' +import { deleteFailedLog, deleteLog } from '#/utils/deleteLog' interface IConfigMap { fileName: string diff --git a/src/renderer/apis/upyun.ts b/src/renderer/apis/upyun.ts index 9858aabe..a2283bfc 100644 --- a/src/renderer/apis/upyun.ts +++ b/src/renderer/apis/upyun.ts @@ -1,6 +1,6 @@ import Upyun from 'upyun' -import { deleteFailedLog, deleteLog } from '@/utils/common' +import { deleteFailedLog, deleteLog } from '#/utils/deleteLog' interface IConfigMap { fileName: string diff --git a/src/renderer/apis/webdav.ts b/src/renderer/apis/webdav.ts index 1afbd4e1..c7ef4803 100644 --- a/src/renderer/apis/webdav.ts +++ b/src/renderer/apis/webdav.ts @@ -1,7 +1,6 @@ import { AuthType, WebDAVClientOptions, createClient } from 'webdav' -import { deleteFailedLog, deleteLog } from '@/utils/common' - +import { deleteFailedLog, deleteLog } from '#/utils/deleteLog' import { formatEndpoint } from '#/utils/common' interface IConfigMap { diff --git a/src/renderer/hooks/useATagClick.ts b/src/renderer/hooks/useATagClick.ts index b5d195dc..8b6f42b7 100644 --- a/src/renderer/hooks/useATagClick.ts +++ b/src/renderer/hooks/useATagClick.ts @@ -1,13 +1,14 @@ import { onMounted, onUnmounted } from 'vue' -import { openURL } from '@/utils/common' +import { sendRPC } from '@/utils/common' +import { IRPCActionType } from 'root/src/universal/types/enum' export function useATagClick () { const handleATagClick = (e: MouseEvent) => { if (e.target instanceof HTMLAnchorElement) { if (e.target.href) { e.preventDefault() - openURL(e.target.href) + sendRPC(IRPCActionType.OPEN_URL, e.target.href) } } } diff --git a/src/renderer/i18n/index.ts b/src/renderer/i18n/index.ts index 6c6338b1..1cc80e7b 100644 --- a/src/renderer/i18n/index.ts +++ b/src/renderer/i18n/index.ts @@ -2,23 +2,34 @@ import { ipcRenderer } from 'electron' import { ObjectAdapter, I18n } from '@picgo/i18n' import bus from '@/utils/bus' +import { sendRPC } from '@/utils/common' import { GET_CURRENT_LANGUAGE, SET_CURRENT_LANGUAGE, FORCE_UPDATE, GET_LANGUAGE_LIST } from '#/events/constants' import { builtinI18nList } from '#/i18n' +import { IRPCActionType } from '#/types/enum' export class I18nManager { #i18n: I18n | null = null #i18nFileList: II18nItem[] = builtinI18nList + constructor () { + this.#getCurrentLanguage() + this.#getLanguageList() + ipcRenderer.on(SET_CURRENT_LANGUAGE, (_, lang: string, locales: ILocales) => { + this.#setLocales(lang, locales) + bus.emit(FORCE_UPDATE) + }) + } + #getLanguageList () { - ipcRenderer.send(GET_LANGUAGE_LIST) + sendRPC(IRPCActionType.GET_LANGUAGE_LIST) ipcRenderer.once(GET_LANGUAGE_LIST, (_, list: II18nItem[]) => { this.#i18nFileList = list }) } #getCurrentLanguage () { - ipcRenderer.send(GET_CURRENT_LANGUAGE) + sendRPC(IRPCActionType.GET_CURRENT_LANGUAGE) ipcRenderer.once(GET_CURRENT_LANGUAGE, (_, lang: string, locales: ILocales) => { this.#setLocales(lang, locales) bus.emit(FORCE_UPDATE) @@ -35,21 +46,12 @@ export class I18nManager { }) } - constructor () { - this.#getCurrentLanguage() - this.#getLanguageList() - ipcRenderer.on(SET_CURRENT_LANGUAGE, (_, lang: string, locales: ILocales) => { - this.#setLocales(lang, locales) - bus.emit(FORCE_UPDATE) - }) - } - T (key: ILocalesKey, args: IStringKeyMap = {}): string { return this.#i18n?.translate(key, args) || key } setCurrentLanguage (lang: string) { - ipcRenderer.send(SET_CURRENT_LANGUAGE, lang) + sendRPC(IRPCActionType.SET_CURRENT_LANGUAGE, lang) } get languageList () { diff --git a/src/renderer/layouts/Main.vue b/src/renderer/layouts/Main.vue index 857f5b45..ba72bf86 100644 --- a/src/renderer/layouts/Main.vue +++ b/src/renderer/layouts/Main.vue @@ -7,7 +7,7 @@ PicList - {{ version }}
@@ -101,7 +100,7 @@ {{ $T('PICBEDS_SETTINGS') }}