Feature(custom): refactor all main ipc event

This commit is contained in:
Kuingsmile
2024-06-12 23:38:17 +08:00
parent 106290f868
commit 5ddc182bd1
91 changed files with 1924 additions and 1806 deletions

View File

@@ -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' }
])
}
}

View File

@@ -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<false | ImgInfo[]> => {
@@ -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<boolean[]> => {
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
}

View File

@@ -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)
}
}
}

View File

@@ -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<IWindowList, IWindowListItem>()
const handleWindowParams = (windowURL: string) => windowURL
const getDefaultWindowSizes = (): { width: number, height: number } => {
const mainWindowWidth = picgo.getConfig<any>(configPaths.settings.mainWindowWidth)
const mainWindowHeight = picgo.getConfig<any>(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,

View File

@@ -6,6 +6,7 @@ import { IWindowList } from '#/types/enum'
class WindowManager implements IWindowManager {
#windowMap: Map<IWindowList | string, BrowserWindow> = new Map()
#windowIdMap: Map<number, IWindowList | string> = new Map()
create (name: IWindowList) {
const windowConfig: IWindowListItem = windowList.get(name)!
if (windowConfig.isValid) {

View File

@@ -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<IConfig>): 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
}

View File

@@ -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)

View File

@@ -65,7 +65,7 @@ class GuiApi implements IGuiApi {
await this.showSettingWindow()
this.getWebcontentsByWindowId(this.settingWindowId)?.send(SHOW_INPUT_BOX, options)
return new Promise<string>((resolve) => {
ipcMain.once(SHOW_INPUT_BOX, (event: Event, value: string) => {
ipcMain.once(SHOW_INPUT_BOX, (_: Event, value: string) => {
resolve(value)
})
})

View File

@@ -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 () {}
}

View File

@@ -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<void>
}
// 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<IGuiMenuItem, 'handle'>[] = []
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<IPasteStyle>(configPaths.settings.pasteStyle) || IPasteStyle.MARKDOWN
const customLink = picgo.getConfig<string>(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
}

View File

@@ -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)

View File

@@ -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

View File

@@ -1,9 +1,22 @@
import { IRPCActionType } from '#/types/enum'
import { IRPCType, IRPCActionType } from '#/types/enum'
interface IBatchAddParams {
action: IRPCActionType
handler: IRPCHandler<any>
type?: IRPCType
}
export class RPCRouter implements IRPCRouter {
private routeMap: IRPCRoutes = new Map()
add = <T>(action: IRPCActionType, handler: IRPCHandler<T>) => {
this.routeMap.set(action, handler)
add = <T>(action: IRPCActionType, handler: IRPCHandler<T>, 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
}

View File

@@ -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
}

View File

@@ -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<IPasteStyle>(configPaths.settings.pasteStyle) || IPasteStyle.MARKDOWN
const customLink = picgo.getConfig<string>(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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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<IGuiMenuItem, 'handle'>[] = []
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])
}

View File

@@ -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()
}
}
]

View File

@@ -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
}
]

View File

@@ -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
}

View File

@@ -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]
})
}
}
]

View File

@@ -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)
}
}
]

View File

@@ -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
}

View File

@@ -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)
}
}
}
]

View File

@@ -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
}

View File

@@ -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()
}
}
]

View File

@@ -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)

View File

@@ -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<IToolboxFixMap<IToolboxItemType>> = {
}
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

View File

@@ -1,9 +1,11 @@
import { IpcMainEvent } from 'electron'
import { IRPCActionType, IToolboxItemType } from '#/types/enum'
export const sendToolboxResWithType = (type: IToolboxItemType) => (event: IpcMainEvent, res?: Omit<IToolboxCheckRes, 'type'>) => {
return event.sender.send(IRPCActionType.TOOLBOX_CHECK_RES, {
...res,
type
})
export function sendToolboxResWithType (type: IToolboxItemType) {
return (event: IpcMainEvent, res?: Omit<IToolboxCheckRes, 'type'>) => {
return event.sender.send(IRPCActionType.TOOLBOX_CHECK_RES, {
...res,
type
})
}
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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')
})
}

View File

@@ -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) {

View File

@@ -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)
})
}

View File

@@ -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'

View File

@@ -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<boolean[]> => {
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
}

View File

@@ -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
}
}

View File

@@ -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()
}
}