Feature: sync with picgo 2.4.0 beta 1

This commit is contained in:
萌萌哒赫萝
2023-05-05 17:51:49 +08:00
parent bda1421aa5
commit e8d54fac4c
33 changed files with 1154 additions and 81 deletions

View File

@@ -15,3 +15,7 @@ export const MINI_WINDOW_URL = isDevelopment
export const RENAME_WINDOW_URL = process.env.NODE_ENV === 'development'
? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#rename-page`
: 'picgo://./index.html#rename-page'
export const TOOLBOX_WINDOW_URL = process.env.NODE_ENV === 'development'
? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#toolbox-page`
: 'picgo://./index.html#toolbox-page'

View File

@@ -2,7 +2,8 @@ import {
SETTING_WINDOW_URL,
TRAY_WINDOW_URL,
MINI_WINDOW_URL,
RENAME_WINDOW_URL
RENAME_WINDOW_URL,
TOOLBOX_WINDOW_URL
} from './constants'
import { IRemoteNoticeTriggerHook, IWindowList } from '#/types/enum'
import bus from '@core/bus'
@@ -12,6 +13,7 @@ import { TOGGLE_SHORTKEY_MODIFIED_MODE } from '#/events/constants'
import { app } from 'electron'
import { remoteNoticeHandler } from '../remoteNotice'
import picgo from '~/main/apis/core/picgo'
import { T } from '~/main/i18n'
const windowList = new Map<IWindowList, IWindowListItem>()
@@ -189,4 +191,51 @@ windowList.set(IWindowList.RENAME_WINDOW, {
}
})
windowList.set(IWindowList.TOOLBOX_WINDOW, {
isValid: true,
multiple: false,
options () {
const options: IBrowserWindowOptions = {
height: 450,
width: 800,
show: false,
frame: true,
center: true,
fullscreenable: false,
resizable: false,
title: `PicList ${T('TOOLBOX')}`,
vibrancy: 'ultra-dark',
icon: `${__static}/logo.png`,
webPreferences: {
backgroundThrottling: false,
nodeIntegration: !!process.env.ELECTRON_NODE_INTEGRATION,
contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION,
nodeIntegrationInWorker: true,
webSecurity: false
}
}
if (process.platform !== 'darwin') {
options.backgroundColor = '#3f3c37'
options.autoHideMenuBar = true
options.transparent = false
}
return options
},
async callback (window, windowManager) {
window.loadURL(TOOLBOX_WINDOW_URL)
const currentWindow = windowManager.getAvailableWindow()
if (currentWindow && currentWindow.isVisible()) {
const bounds = currentWindow.getBounds()
const positionX = bounds.x + bounds.width / 2 - 400
let positionY
if (bounds.height > 400) {
positionY = bounds.y + bounds.height / 2 - 225
} else {
positionY = bounds.y + bounds.height / 2
}
window.setPosition(positionX, positionY, false)
}
}
})
export default windowList

View File

@@ -9,7 +9,7 @@ if (!fs.pathExistsSync(STORE_PATH)) {
fs.mkdirpSync(STORE_PATH)
}
const CONFIG_PATH: string = dbPathChecker()
const DB_PATH: string = getGalleryDBPath().dbPath
export const DB_PATH: string = getGalleryDBPath().dbPath
class ConfigStore {
private db: JSONStore

View File

@@ -116,6 +116,13 @@ const buildMainPageMenu = (win: BrowserWindow) => {
win?.webContents?.send(SHOW_MAIN_PAGE_QRCODE)
}
},
{
label: T('OPEN_TOOLBOX'),
click () {
const window = windowManager.create(IWindowList.TOOLBOX_WINDOW)
window?.show()
}
},
{
label: T('SHOW_DEVTOOLS'),
click () {

View File

@@ -1,95 +1,57 @@
import { ipcMain, IpcMainEvent } from 'electron'
import { IRPCActionType } from '~/universal/types/enum'
import { RPC_ACTIONS } from '#/events/constants'
import {
deleteUploaderConfig,
getUploaderConfigList,
resetUploaderConfig,
selectUploaderConfig,
updateUploaderConfig
} from '~/main/utils/handleUploaderConfig'
import { configRouter } from './routes/config'
import { toolboxRouter } from './routes/toolbox'
import { systemRouter } from './routes/system'
class RPCServer {
start () {
ipcMain.on(RPC_ACTIONS, (event: IpcMainEvent, action: IRPCActionType, args: any[], callbackId: string) => {
try {
switch (action) {
case IRPCActionType.GET_PICBED_CONFIG_LIST: {
const configList = this.getPicBedConfigList(args as IGetUploaderConfigListArgs)
this.sendBack(event, action, configList, callbackId)
break
}
case IRPCActionType.DELETE_PICBED_CONFIG: {
const res = this.deleteUploaderConfig(args as IDeleteUploaderConfigArgs)
this.sendBack(event, action, res, callbackId)
break
}
case IRPCActionType.SELECT_UPLOADER: {
this.selectUploaderConfig(args as ISelectUploaderConfigArgs)
this.sendBack(event, action, true, callbackId)
break
}
case IRPCActionType.UPDATE_UPLOADER_CONFIG: {
this.updateUploaderConfig(args as IUpdateUploaderConfigArgs)
this.sendBack(event, action, true, callbackId)
break
}
case IRPCActionType.RESET_UPLOADER_CONFIG: {
this.resetUploaderConfig(args as IResetUploaderConfigArgs)
this.sendBack(event, action, true, callbackId)
break
}
default: {
this.sendBack(event, action, null, callbackId)
break
}
}
} catch (e) {
this.sendBack(event, action, null, callbackId)
class RPCServer implements IRPCServer {
private routes: IRPCRoutes = new Map()
private rpcEventHandler = async (event: IpcMainEvent, action: IRPCActionType, args: any[], callbackId: string) => {
try {
const handler = this.routes.get(action)
if (!handler) {
return this.sendBack(event, action, null, callbackId)
}
})
const res = await handler?.(args, event)
this.sendBack(event, action, res, callbackId)
} catch (e) {
this.sendBack(event, action, null, callbackId)
}
}
/**
* 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) {
event.sender.send(RPC_ACTIONS, data, action, callbackId)
if (callbackId) {
event.sender.send(RPC_ACTIONS, data, action, callbackId)
}
}
private getPicBedConfigList (args: IGetUploaderConfigListArgs) {
const [type] = args
const config = getUploaderConfigList(type)
return config
start () {
ipcMain.on(RPC_ACTIONS, this.rpcEventHandler)
}
private deleteUploaderConfig (args: IDeleteUploaderConfigArgs) {
const [type, id] = args
const config = deleteUploaderConfig(type, id)
return config
use (routes: IRPCRoutes) {
for (const [action, handler] of routes) {
this.routes.set(action, handler)
}
}
private selectUploaderConfig (args: ISelectUploaderConfigArgs) {
const [type, id] = args
const config = selectUploaderConfig(type, id)
return config
}
private updateUploaderConfig (args: IUpdateUploaderConfigArgs) {
const [type, id, config] = args
const res = updateUploaderConfig(type, id, config)
return res
}
private resetUploaderConfig (args: IResetUploaderConfigArgs) {
const [type, id] = args
const res = resetUploaderConfig(type, id)
return res
stop () {
ipcMain.off(RPC_ACTIONS, this.rpcEventHandler)
}
}
const rpcServer = new RPCServer()
rpcServer.use(configRouter.routes())
rpcServer.use(toolboxRouter.routes())
rpcServer.use(systemRouter.routes())
export {
rpcServer
}

View File

@@ -0,0 +1,13 @@
import { IRPCActionType } from '~/universal/types/enum'
export class RPCRouter implements IRPCRouter {
private routeMap: IRPCRoutes = new Map()
add = <T>(action: IRPCActionType, handler: IRPCHandler<T>) => {
this.routeMap.set(action, handler)
return this
}
routes () {
return this.routeMap
}
}

View File

@@ -0,0 +1,36 @@
import { IRPCActionType } from '~/universal/types/enum'
import { RPCRouter } from '../router'
import { deleteUploaderConfig, getUploaderConfigList, selectUploaderConfig, updateUploaderConfig, resetUploaderConfig } from '~/main/utils/handleUploaderConfig'
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,23 @@
import { IRPCActionType } from '~/universal/types/enum'
import { RPCRouter } from '../router'
import { app, clipboard, shell } from 'electron'
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,77 @@
import fs from 'fs-extra'
import path from 'path'
import { dbPathChecker, defaultConfigPath } from '~/main/apis/core/datastore/dbChecker'
import { IToolboxItemCheckStatus, IToolboxItemType } from '~/universal/types/enum'
import { CLIPBOARD_IMAGE_FOLDER } from '~/universal/utils/static'
import { sendToolboxResWithType } from './utils'
import { T } from '~/main/i18n'
const sendToolboxRes = sendToolboxResWithType(IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD)
const defaultClipboardImagePath = path.join(defaultConfigPath, CLIPBOARD_IMAGE_FOLDER)
export const checkClipboardUploadMap: IToolboxCheckerMap<
IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD
> = {
[IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD]: async (event) => {
sendToolboxRes(event, {
status: IToolboxItemCheckStatus.LOADING
})
const configFilePath = dbPathChecker()
if (fs.existsSync(configFilePath)) {
const dirPath = path.dirname(configFilePath)
const clipboardImagePath = path.join(dirPath, CLIPBOARD_IMAGE_FOLDER)
if (fs.existsSync(clipboardImagePath)) {
sendToolboxRes(event, {
status: IToolboxItemCheckStatus.SUCCESS,
msg: T('TOOLBOX_CHECK_CLIPBOARD_FILE_PATH_TIPS', {
path: clipboardImagePath
}),
value: clipboardImagePath
})
} else {
sendToolboxRes(event, {
status: IToolboxItemCheckStatus.ERROR,
msg: T('TOOLBOX_CHECK_CLIPBOARD_FILE_PATH_NOT_EXIST_TIPS', {
path: clipboardImagePath
}),
value: path.dirname(clipboardImagePath)
})
}
} else {
sendToolboxRes(event, {
status: IToolboxItemCheckStatus.ERROR,
msg: T('TOOLBOX_CHECK_CLIPBOARD_FILE_PATH_NOT_EXIST_TIPS', {
path: defaultClipboardImagePath
}),
value: path.dirname(defaultClipboardImagePath)
})
}
}
}
export const fixClipboardUploadMap: IToolboxFixMap<
IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD
> = {
[IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD]: async () => {
const configFilePath = dbPathChecker()
const dirPath = path.dirname(configFilePath)
const clipboardImagePath = path.join(dirPath, CLIPBOARD_IMAGE_FOLDER)
try {
fs.mkdirsSync(clipboardImagePath)
return {
type: IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD,
status: IToolboxItemCheckStatus.SUCCESS
}
} catch (e) {
return {
type: IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD,
status: IToolboxItemCheckStatus.ERROR,
msg: T('TOOLBOX_CHECK_CLIPBOARD_FILE_PATH_ERROR_TIPS', {
path: clipboardImagePath
}),
value: path.dirname(clipboardImagePath)
}
}
}
}

View File

@@ -0,0 +1,87 @@
import fs from 'fs-extra'
import { IpcMainEvent } from 'electron'
import { IToolboxItemCheckStatus, IToolboxItemType } from '~/universal/types/enum'
import { sendToolboxResWithType } from './utils'
import { dbPathChecker } from '~/main/apis/core/datastore/dbChecker'
import { GalleryDB, DB_PATH } from '~/main/apis/core/datastore'
import path from 'path'
import { T } from '~/main/i18n'
export const checkFileMap: IToolboxCheckerMap<
IToolboxItemType.IS_CONFIG_FILE_BROKEN | IToolboxItemType.IS_GALLERY_FILE_BROKEN
> = {
[IToolboxItemType.IS_CONFIG_FILE_BROKEN]: async (event: IpcMainEvent) => {
const sendToolboxRes = sendToolboxResWithType(IToolboxItemType.IS_CONFIG_FILE_BROKEN)
sendToolboxRes(event, {
status: IToolboxItemCheckStatus.LOADING
})
const configFilePath = dbPathChecker()
try {
if (fs.existsSync(configFilePath)) {
await fs.readJSON(configFilePath)
sendToolboxRes(event, {
status: IToolboxItemCheckStatus.SUCCESS,
msg: T('TOOLBOX_CHECK_CONFIG_FILE_PATH_TIPS', {
path: configFilePath
}),
value: configFilePath
})
}
} catch (e) {
sendToolboxRes(event, {
status: IToolboxItemCheckStatus.ERROR,
msg: T('TOOLBOX_CHECK_CONFIG_FILE_BROKEN_TIPS'),
value: path.dirname(configFilePath)
})
}
},
[IToolboxItemType.IS_GALLERY_FILE_BROKEN]: async (event) => {
const sendToolboxRes = sendToolboxResWithType(IToolboxItemType.IS_GALLERY_FILE_BROKEN)
sendToolboxRes(event, {
status: IToolboxItemCheckStatus.LOADING
})
const galleryDB = GalleryDB.getInstance()
if (galleryDB.errorList.length === 0) {
sendToolboxRes(event, {
status: IToolboxItemCheckStatus.SUCCESS,
msg: T('TOOLBOX_CHECK_GALLERY_FILE_PATH_TIPS', {
path: DB_PATH
}),
value: path.dirname(DB_PATH)
})
} else {
sendToolboxRes(event, {
status: IToolboxItemCheckStatus.ERROR,
msg: T('TOOLBOX_CHECK_GALLERY_FILE_BROKEN_TIPS'),
value: path.dirname(DB_PATH)
})
}
}
}
export const fixFileMap: IToolboxFixMap<
IToolboxItemType.IS_CONFIG_FILE_BROKEN | IToolboxItemType.IS_GALLERY_FILE_BROKEN
> = {
[IToolboxItemType.IS_CONFIG_FILE_BROKEN]: async () => {
try {
fs.unlinkSync(dbPathChecker())
} catch (e) {
// do nothing
}
return {
type: IToolboxItemType.IS_CONFIG_FILE_BROKEN,
status: IToolboxItemCheckStatus.SUCCESS
}
},
[IToolboxItemType.IS_GALLERY_FILE_BROKEN]: async () => {
try {
fs.unlinkSync(DB_PATH)
} catch (e) {
// do nothing
}
return {
type: IToolboxItemType.IS_GALLERY_FILE_BROKEN,
status: IToolboxItemCheckStatus.SUCCESS
}
}
}

View File

@@ -0,0 +1,92 @@
import fs from 'fs-extra'
import { IToolboxItemCheckStatus, IToolboxItemType } from '~/universal/types/enum'
import { sendToolboxResWithType } from './utils'
import tunnel from 'tunnel'
import { dbPathChecker } from '~/main/apis/core/datastore/dbChecker'
import { IConfig } from 'piclist'
import axios, { AxiosRequestConfig } from 'axios'
import { T } from '~/main/i18n'
const getProxy = (proxyStr: string): AxiosRequestConfig['proxy'] | false => {
if (proxyStr) {
try {
const proxyOptions = new URL(proxyStr)
return {
host: proxyOptions.hostname,
port: parseInt(proxyOptions.port || '0', 10),
protocol: proxyOptions.protocol
}
} catch (e) {
}
}
return false
}
const sendToolboxRes = sendToolboxResWithType(IToolboxItemType.HAS_PROBLEM_WITH_PROXY)
export const checkProxyMap: IToolboxCheckerMap<
IToolboxItemType.HAS_PROBLEM_WITH_PROXY
> = {
[IToolboxItemType.HAS_PROBLEM_WITH_PROXY]: async (event) => {
sendToolboxRes(event, {
status: IToolboxItemCheckStatus.LOADING
})
const configFilePath = dbPathChecker()
if (fs.existsSync(configFilePath)) {
let config: IConfig | undefined
try {
config = await fs.readJSON(configFilePath) as IConfig
} catch (e) {
}
if (!config) {
return sendToolboxRes(event, {
status: IToolboxItemCheckStatus.SUCCESS,
msg: T('TOOLBOX_CHECK_PROXY_NO_PROXY_TIPS')
})
}
const proxy = config.picBed?.proxy
if (!proxy) {
return sendToolboxRes(event, {
status: IToolboxItemCheckStatus.SUCCESS,
msg: T('TOOLBOX_CHECK_PROXY_NO_PROXY_TIPS')
})
} else {
const proxyOptions = getProxy(proxy)
if (!proxyOptions) {
return sendToolboxRes(event, {
status: IToolboxItemCheckStatus.ERROR,
msg: T('TOOLBOX_CHECK_PROXY_PROXY_IS_NOT_CORRECT')
})
} else {
const httpsAgent = tunnel.httpsOverHttp({
proxy: {
host: proxyOptions.host,
port: proxyOptions.port
}
})
try {
await axios.get('https://www.google.com', {
httpsAgent
})
return sendToolboxRes(event, {
status: IToolboxItemCheckStatus.SUCCESS,
msg: T('TOOLBOX_CHECK_PROXY_SUCCESS_TIPS')
})
} catch (e) {
console.log(e)
return sendToolboxRes(event, {
status: IToolboxItemCheckStatus.ERROR,
msg: T('TOOLBOX_CHECK_PROXY_PROXY_IS_NOT_WORKING')
})
}
}
}
}
sendToolboxRes(event, {
status: IToolboxItemCheckStatus.SUCCESS,
msg: T('TOOLBOX_CHECK_PROXY_NO_PROXY_TIPS')
})
}
}

View File

@@ -0,0 +1,48 @@
import { IRPCActionType, IToolboxItemType } from '~/universal/types/enum'
import { RPCRouter } from '../../router'
import { checkFileMap, fixFileMap } from './checkFile'
import { checkClipboardUploadMap, fixClipboardUploadMap } from './checkClipboardUpload'
import { checkProxyMap } from './checkProxy'
const toolboxRouter = new RPCRouter()
const toolboxCheckMap: Partial<IToolboxCheckerMap<IToolboxItemType>> = {
...checkFileMap,
...checkClipboardUploadMap,
...checkProxyMap
}
const toolboxFixMap: Partial<IToolboxFixMap<IToolboxItemType>> = {
...fixFileMap,
...fixClipboardUploadMap
}
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]
if (handler) {
handler(event)
}
}
}
})
.add(IRPCActionType.TOOLBOX_CHECK_FIX, async (args, event) => {
const [type] = args as IToolboxCheckArgs
const handler = toolboxFixMap[type]
if (handler) {
return await handler(event)
}
})
export {
toolboxRouter
}

View File

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