mirror of
https://github.com/Kuingsmile/PicList.git
synced 2026-05-11 18:10:32 +08:00
🎨 Style(custom): lint code
This commit is contained in:
@@ -23,12 +23,12 @@ class RemoteNoticeHandler {
|
||||
private remoteNotice: IRemoteNotice | null = null
|
||||
private remoteNoticeLocalCountStorage: IRemoteNoticeLocalCountStorage | null = null
|
||||
|
||||
async init () {
|
||||
async init() {
|
||||
this.remoteNotice = await this.getRemoteNoticeInfo()
|
||||
this.initLocalCountStorage()
|
||||
}
|
||||
|
||||
private initLocalCountStorage () {
|
||||
private initLocalCountStorage() {
|
||||
const localCountStorage = {}
|
||||
if (!fs.existsSync(REMOTE_NOTICE_LOCAL_STORAGE_PATH)) {
|
||||
fs.writeFileSync(REMOTE_NOTICE_LOCAL_STORAGE_PATH, JSON.stringify({}))
|
||||
@@ -44,14 +44,14 @@ class RemoteNoticeHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private saveLocalCountStorage (newData?: IRemoteNoticeLocalCountStorage) {
|
||||
private saveLocalCountStorage(newData?: IRemoteNoticeLocalCountStorage) {
|
||||
if (newData) {
|
||||
this.remoteNoticeLocalCountStorage = newData
|
||||
}
|
||||
fs.writeFileSync(REMOTE_NOTICE_LOCAL_STORAGE_PATH, JSON.stringify(this.remoteNoticeLocalCountStorage))
|
||||
}
|
||||
|
||||
private async getRemoteNoticeInfo (): Promise<IRemoteNotice | null> {
|
||||
private async getRemoteNoticeInfo(): Promise<IRemoteNotice | null> {
|
||||
try {
|
||||
const noticeInfo = (await axios({
|
||||
method: 'get',
|
||||
@@ -68,7 +68,7 @@ class RemoteNoticeHandler {
|
||||
* if the notice is not shown or is always shown, then show the notice
|
||||
* @param action
|
||||
*/
|
||||
private checkActionCount (action: IRemoteNoticeAction) {
|
||||
private checkActionCount(action: IRemoteNoticeAction) {
|
||||
try {
|
||||
if (!this.remoteNoticeLocalCountStorage) {
|
||||
return true
|
||||
@@ -102,7 +102,7 @@ class RemoteNoticeHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private async doActions (actions: IRemoteNoticeAction[]) {
|
||||
private async doActions(actions: IRemoteNoticeAction[]) {
|
||||
for (const action of actions) {
|
||||
if (this.checkActionCount(action)) {
|
||||
switch (action.type) {
|
||||
@@ -117,7 +117,7 @@ class RemoteNoticeHandler {
|
||||
body: action.data?.content || '',
|
||||
clickToCopy: !!action.data?.copyToClipboard,
|
||||
copyContent: action.data?.copyToClipboard || '',
|
||||
clickFn () {
|
||||
clickFn() {
|
||||
if (action.data?.url) {
|
||||
shell.openExternal(action.data.url)
|
||||
}
|
||||
@@ -163,7 +163,7 @@ class RemoteNoticeHandler {
|
||||
}
|
||||
}
|
||||
|
||||
triggerHook (hook: string) {
|
||||
triggerHook(hook: string) {
|
||||
if (!this.remoteNotice || !this.remoteNotice.list) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -6,24 +6,30 @@ import shortKeyService from 'apis/app/shortKey/shortKeyService'
|
||||
import GuiApi from 'apis/gui'
|
||||
import { globalShortcut } from 'electron'
|
||||
|
||||
import type { IKeyCommandType, IPluginShortKeyConfig, IShortKeyConfig, IShortKeyConfigs, IShortKeyHandler } from '#/types/types'
|
||||
import type {
|
||||
IKeyCommandType,
|
||||
IPluginShortKeyConfig,
|
||||
IShortKeyConfig,
|
||||
IShortKeyConfigs,
|
||||
IShortKeyHandler
|
||||
} from '#/types/types'
|
||||
import { TOGGLE_SHORTKEY_MODIFIED_MODE } from '~/events/constant'
|
||||
import { configPaths } from '~/utils/configPaths'
|
||||
|
||||
class ShortKeyHandler {
|
||||
private isInModifiedMode: boolean = false
|
||||
constructor () {
|
||||
constructor() {
|
||||
bus.on(TOGGLE_SHORTKEY_MODIFIED_MODE, flag => {
|
||||
this.isInModifiedMode = flag
|
||||
})
|
||||
}
|
||||
|
||||
async init () {
|
||||
async init() {
|
||||
this.initBuiltInShortKey()
|
||||
await this.initPluginsShortKey()
|
||||
}
|
||||
|
||||
private initBuiltInShortKey () {
|
||||
private initBuiltInShortKey() {
|
||||
const commands = db.get(configPaths.settings.shortKey._path) as IShortKeyConfigs
|
||||
Object.keys(commands)
|
||||
.filter(item => item.includes('picgo:'))
|
||||
@@ -38,7 +44,7 @@ class ShortKeyHandler {
|
||||
})
|
||||
}
|
||||
|
||||
private async initPluginsShortKey () {
|
||||
private async initPluginsShortKey() {
|
||||
// get enabled plugin
|
||||
const pluginList = picgo.pluginLoader.getList()
|
||||
for (const item of pluginList) {
|
||||
@@ -68,7 +74,7 @@ class ShortKeyHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private registerShortKey (
|
||||
private registerShortKey(
|
||||
config: IShortKeyConfig | IPluginShortKeyConfig,
|
||||
command: string,
|
||||
handler: IShortKeyHandler,
|
||||
@@ -97,7 +103,7 @@ class ShortKeyHandler {
|
||||
}
|
||||
|
||||
// enable or disable shortKey
|
||||
bindOrUnbindShortKey (item: IShortKeyConfig, from: string): boolean {
|
||||
bindOrUnbindShortKey(item: IShortKeyConfig, from: string): boolean {
|
||||
const command = `${from}:${item.name}`
|
||||
if (item.enable === false) {
|
||||
globalShortcut.unregister(item.key)
|
||||
@@ -121,7 +127,7 @@ class ShortKeyHandler {
|
||||
}
|
||||
|
||||
// update shortKey bindings
|
||||
updateShortKey (item: IShortKeyConfig, oldKey: string, from: string): boolean {
|
||||
updateShortKey(item: IShortKeyConfig, oldKey: string, from: string): boolean {
|
||||
const command = `${from}:${item.name}`
|
||||
if (globalShortcut.isRegistered(item.key)) return false
|
||||
globalShortcut.unregister(oldKey)
|
||||
@@ -134,7 +140,7 @@ class ShortKeyHandler {
|
||||
return true
|
||||
}
|
||||
|
||||
private async handler (command: string) {
|
||||
private async handler(command: string) {
|
||||
if (this.isInModifiedMode) {
|
||||
return
|
||||
}
|
||||
@@ -150,7 +156,7 @@ class ShortKeyHandler {
|
||||
}
|
||||
}
|
||||
|
||||
async registerPluginShortKey (pluginName: string) {
|
||||
async registerPluginShortKey(pluginName: string) {
|
||||
const plugin = await picgo.pluginLoader.getPlugin(pluginName)
|
||||
if (plugin && plugin.commands) {
|
||||
if (typeof plugin.commands !== 'function') {
|
||||
@@ -170,7 +176,7 @@ class ShortKeyHandler {
|
||||
}
|
||||
}
|
||||
|
||||
unregisterPluginShortKey (pluginName: string) {
|
||||
unregisterPluginShortKey(pluginName: string) {
|
||||
const commands = db.get(configPaths.settings.shortKey._path) as IShortKeyConfigs
|
||||
const keyList = Object.keys(commands)
|
||||
.filter(command => command.includes(pluginName))
|
||||
|
||||
@@ -4,22 +4,22 @@ import type { IShortKeyHandler } from '#/types/types'
|
||||
|
||||
class ShortKeyService {
|
||||
private commandList: Map<string, IShortKeyHandler> = new Map()
|
||||
registerCommand (command: string, handler: IShortKeyHandler) {
|
||||
registerCommand(command: string, handler: IShortKeyHandler) {
|
||||
this.commandList.set(command, handler)
|
||||
}
|
||||
|
||||
unregisterCommand (command: string) {
|
||||
unregisterCommand(command: string) {
|
||||
this.commandList.delete(command)
|
||||
}
|
||||
|
||||
getShortKeyHandler (command: string): IShortKeyHandler | null {
|
||||
getShortKeyHandler(command: string): IShortKeyHandler | null {
|
||||
const handler = this.commandList.get(command)
|
||||
if (handler) return handler
|
||||
logger.warn(`cannot find command: ${command}`)
|
||||
return null
|
||||
}
|
||||
|
||||
getCommandList () {
|
||||
getCommandList() {
|
||||
return [...this.commandList.keys()]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ import uploadPng from '../../../../../resources/upload.png?asset&asarUnpack'
|
||||
import uploadDarkPng from '../../../../../resources/upload-dark.png?asset&asarUnpack'
|
||||
let contextMenu: Menu | null
|
||||
|
||||
export function setDockMenu () {
|
||||
export function setDockMenu() {
|
||||
const isListeningClipboard = db.get(configPaths.settings.isListeningClipboard) || false
|
||||
const dockMenu = Menu.buildFromTemplate([
|
||||
{
|
||||
@@ -45,7 +45,7 @@ export function setDockMenu () {
|
||||
},
|
||||
{
|
||||
label: $t('START_WATCH_CLIPBOARD'),
|
||||
click () {
|
||||
click() {
|
||||
db.set(configPaths.settings.isListeningClipboard, true)
|
||||
clipboardPoll.startListening()
|
||||
clipboardPoll.on('change', () => {
|
||||
@@ -58,7 +58,7 @@ export function setDockMenu () {
|
||||
},
|
||||
{
|
||||
label: $t('STOP_WATCH_CLIPBOARD'),
|
||||
click () {
|
||||
click() {
|
||||
db.set(configPaths.settings.isListeningClipboard, false)
|
||||
clipboardPoll.stopListening()
|
||||
clipboardPoll.removeAllListeners()
|
||||
@@ -70,7 +70,7 @@ export function setDockMenu () {
|
||||
app.dock?.setMenu(dockMenu)
|
||||
}
|
||||
|
||||
export function createMenu () {
|
||||
export function createMenu() {
|
||||
const submenu = buildPicBedListMenu()
|
||||
const appMenu = Menu.buildFromTemplate([
|
||||
{
|
||||
@@ -79,7 +79,7 @@ export function createMenu () {
|
||||
{ label: $t('OPEN_MAIN_WINDOW'), click: openMainWindow },
|
||||
{
|
||||
label: $t('RELOAD_APP'),
|
||||
click () {
|
||||
click() {
|
||||
app.relaunch()
|
||||
app.exit(0)
|
||||
}
|
||||
@@ -107,7 +107,7 @@ export function createMenu () {
|
||||
Menu.setApplicationMenu(appMenu)
|
||||
}
|
||||
|
||||
export function createContextMenu () {
|
||||
export function createContextMenu() {
|
||||
const ClipboardWatcher = clipboardPoll
|
||||
const isListeningClipboard = db.get(configPaths.settings.isListeningClipboard) || false
|
||||
const isMiniWindowVisible =
|
||||
@@ -147,7 +147,7 @@ export function createContextMenu () {
|
||||
},
|
||||
{
|
||||
label: $t('RELOAD_APP'),
|
||||
click () {
|
||||
click() {
|
||||
app.relaunch()
|
||||
app.exit(0)
|
||||
}
|
||||
@@ -160,7 +160,7 @@ export function createContextMenu () {
|
||||
0,
|
||||
{
|
||||
label: $t('OPEN_MINI_WINDOW'),
|
||||
click () {
|
||||
click() {
|
||||
openMiniWindow(false)
|
||||
},
|
||||
visible: !isMiniWindowVisible
|
||||
@@ -185,7 +185,7 @@ export function createContextMenu () {
|
||||
{ label: $t('OPEN_MAIN_WINDOW'), click: openMainWindow },
|
||||
{
|
||||
label: $t('OPEN_MINI_WINDOW'),
|
||||
click () {
|
||||
click() {
|
||||
openMiniWindow(false)
|
||||
},
|
||||
visible: !isMiniWindowVisible
|
||||
@@ -207,7 +207,7 @@ export function createContextMenu () {
|
||||
},
|
||||
{
|
||||
label: $t('ABOUT'),
|
||||
click () {
|
||||
click() {
|
||||
dialog.showMessageBox({
|
||||
title: 'PicList',
|
||||
message: 'PicList',
|
||||
@@ -230,7 +230,7 @@ const getTrayIcon = () => {
|
||||
}
|
||||
}
|
||||
|
||||
export function createTray (tooltip: string) {
|
||||
export function createTray(tooltip: string) {
|
||||
const menubarPic = getTrayIcon()
|
||||
setTray(new Tray(menubarPic))
|
||||
tray.setToolTip(tooltip)
|
||||
@@ -309,7 +309,7 @@ export function createTray (tooltip: string) {
|
||||
// drop-files only be supported in macOS
|
||||
// so the tray window must be available
|
||||
if (process.platform === 'darwin') {
|
||||
(tray as any).on('drop-files', async (_: Event, files: string[]) => {
|
||||
;(tray as any).on('drop-files', async (_: Event, files: string[]) => {
|
||||
const pasteStyle = db.get(configPaths.settings.pasteStyle) || IPasteStyle.MARKDOWN
|
||||
const rawInput = cloneDeep(files)
|
||||
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)!
|
||||
@@ -338,14 +338,14 @@ export function createTray (tooltip: string) {
|
||||
imgs[i].shortUrl = shortUrl
|
||||
pasteText.push(pasteTextItem)
|
||||
const isShowResultNotification =
|
||||
db.get(configPaths.settings.uploadResultNotification) === undefined
|
||||
? true
|
||||
: !!db.get(configPaths.settings.uploadResultNotification)
|
||||
db.get(configPaths.settings.uploadResultNotification) === undefined
|
||||
? true
|
||||
: !!db.get(configPaths.settings.uploadResultNotification)
|
||||
if (isShowResultNotification) {
|
||||
const notification = new Notification({
|
||||
title: $t('UPLOAD_SUCCEED'),
|
||||
body: shortUrl || imgs[i].imgUrl!
|
||||
// icon: files[i]
|
||||
// icon: files[i]
|
||||
})
|
||||
setTimeout(() => {
|
||||
notification.show()
|
||||
|
||||
@@ -1,236 +1,236 @@
|
||||
import db, { GalleryDB } from '@core/datastore'
|
||||
import picgo from '@core/picgo'
|
||||
import uploader from 'apis/app/uploader'
|
||||
import windowManager from 'apis/app/window/windowManager'
|
||||
import { Notification, WebContents } from 'electron'
|
||||
import fs from 'fs-extra'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import type { IPicGo } from 'piclist'
|
||||
|
||||
import type { IFileWithPath, ImgInfo, IStringKeyMap, IUploadOption } from '#/types/types'
|
||||
import { T as $t } from '~/i18n/index'
|
||||
import { handleCopyUrl, handleUrlEncodeWithSetting } from '~/utils/common'
|
||||
import { configPaths } from '~/utils/configPaths'
|
||||
import { IPasteStyle, IWindowList } from '~/utils/enum'
|
||||
import { changeCurrentUploader } from '~/utils/handleUploaderConfig'
|
||||
import pasteTemplate from '~/utils/pasteTemplate'
|
||||
|
||||
const handleClipboardUploading = async (): Promise<false | ImgInfo[]> => {
|
||||
const useBuiltinClipboard =
|
||||
db.get(configPaths.settings.useBuiltinClipboard) === undefined
|
||||
? true
|
||||
: !!db.get(configPaths.settings.useBuiltinClipboard)
|
||||
const win = windowManager.getAvailableWindow()
|
||||
if (useBuiltinClipboard) {
|
||||
return await uploader.setWebContents(win!.webContents).uploadWithBuildInClipboard()
|
||||
}
|
||||
return await uploader.setWebContents(win!.webContents).upload()
|
||||
}
|
||||
|
||||
const handleClipboardUploadingReturnCtx = async (img?: IUploadOption, skipProcess = false): Promise<false | IPicGo> => {
|
||||
const useBuiltinClipboard =
|
||||
db.get(configPaths.settings.useBuiltinClipboard) === undefined
|
||||
? true
|
||||
: !!db.get(configPaths.settings.useBuiltinClipboard)
|
||||
const win = windowManager.getAvailableWindow()
|
||||
if (useBuiltinClipboard) {
|
||||
return await uploader.setWebContents(win!.webContents).uploadWithBuildInClipboardReturnCtx(img, skipProcess)
|
||||
}
|
||||
return await uploader.setWebContents(win!.webContents).uploadReturnCtx(img, skipProcess)
|
||||
}
|
||||
|
||||
export const uploadClipboardFiles = async (): Promise<IStringKeyMap> => {
|
||||
const { needRestore, ctx } = await handleSecondaryUpload(undefined, undefined, 'clipboard')
|
||||
let img: ImgInfo[] | false = false
|
||||
if (needRestore) {
|
||||
const res = await handleClipboardUploadingReturnCtx(ctx ? ctx.processedInput : undefined, true)
|
||||
img = res ? res.output : false
|
||||
} else {
|
||||
img = await handleClipboardUploading()
|
||||
}
|
||||
if (img !== false) {
|
||||
if (img.length > 0) {
|
||||
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)
|
||||
const pasteStyle = db.get(configPaths.settings.pasteStyle) || IPasteStyle.MARKDOWN
|
||||
const [pastedText, shortUrl] = await pasteTemplate(pasteStyle, img[0], db.get(configPaths.settings.customLink))
|
||||
img[0].shortUrl = shortUrl
|
||||
handleCopyUrl(pastedText)
|
||||
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: shortUrl || img[0].imgUrl!
|
||||
// icon: img[0].imgUrl
|
||||
})
|
||||
setTimeout(() => {
|
||||
notification.show()
|
||||
}, 100)
|
||||
}
|
||||
const inserted = await GalleryDB.getInstance().insert(img[0])
|
||||
// trayWindow just be created in mac/windows, not in linux
|
||||
trayWindow?.webContents?.send('clipboardFiles', [])
|
||||
trayWindow?.webContents?.send('uploadFiles', img)
|
||||
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents?.send('updateGallery')
|
||||
}
|
||||
return {
|
||||
url: handleUrlEncodeWithSetting(inserted.imgUrl as string),
|
||||
fullResult: inserted
|
||||
}
|
||||
} else {
|
||||
const notification = new Notification({
|
||||
title: $t('UPLOAD_FAILED'),
|
||||
body: $t('TIPS_UPLOAD_NOT_PICTURES')
|
||||
})
|
||||
notification.show()
|
||||
return {
|
||||
url: '',
|
||||
fullResult: {}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
url: '',
|
||||
fullResult: {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const uploadChoosedFiles = async (
|
||||
webContents: WebContents,
|
||||
files: IFileWithPath[]
|
||||
): Promise<IStringKeyMap[]> => {
|
||||
const input = files.map(item => item.path)
|
||||
const rawInput = cloneDeep(input)
|
||||
const { needRestore, ctx } = await handleSecondaryUpload(webContents, input)
|
||||
let imgs: ImgInfo[] | false = false
|
||||
if (needRestore) {
|
||||
const res = await uploader.setWebContents(webContents).uploadReturnCtx(ctx ? ctx.processedInput : input, true)
|
||||
imgs = res ? res.output : false
|
||||
} else {
|
||||
imgs = await uploader.setWebContents(webContents).upload(input)
|
||||
}
|
||||
const result = []
|
||||
if (imgs !== false) {
|
||||
const pasteStyle = db.get(configPaths.settings.pasteStyle) || IPasteStyle.MARKDOWN
|
||||
const deleteLocalFile = db.get(configPaths.settings.deleteLocalFile) || false
|
||||
const pasteText: string[] = []
|
||||
for (let i = 0; i < imgs.length; i++) {
|
||||
if (deleteLocalFile) {
|
||||
fs.remove(rawInput[i])
|
||||
.then(() => {
|
||||
picgo.log.info(`delete local file: ${rawInput[i]}`)
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
picgo.log.error(err)
|
||||
})
|
||||
}
|
||||
const [pasteTextItem, shortUrl] = await pasteTemplate(
|
||||
pasteStyle,
|
||||
imgs[i],
|
||||
db.get(configPaths.settings.customLink)
|
||||
)
|
||||
imgs[i].shortUrl = shortUrl
|
||||
pasteText.push(pasteTextItem)
|
||||
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: shortUrl || imgs[i].imgUrl!
|
||||
// icon: files[i].path
|
||||
})
|
||||
setTimeout(() => {
|
||||
notification.show()
|
||||
}, i * 100)
|
||||
}
|
||||
const inserted = await GalleryDB.getInstance().insert(imgs[i])
|
||||
result.push({
|
||||
url: handleUrlEncodeWithSetting(inserted.imgUrl!),
|
||||
fullResult: inserted
|
||||
})
|
||||
}
|
||||
handleCopyUrl(pasteText.join('\n'))
|
||||
// trayWindow just be created in mac/windows, not in linux
|
||||
windowManager.get(IWindowList.TRAY_WINDOW)?.webContents?.send('uploadFiles', imgs)
|
||||
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents?.send('updateGallery')
|
||||
}
|
||||
return result
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
export const handleSecondaryUpload = async (
|
||||
webContents?: WebContents,
|
||||
input?: string[],
|
||||
uploadType: 'clipboard' | 'file' | 'tray' = 'file'
|
||||
): Promise<{ needRestore: boolean; ctx: IPicGo | false }> => {
|
||||
const enableSecondUploader = db.get(configPaths.settings.enableSecondUploader) || false
|
||||
let currentPicBedType = ''
|
||||
let currentPicBedConfig = {} as IStringKeyMap
|
||||
let currentPicBedConfigId = ''
|
||||
let needRestore = false
|
||||
let ctx: IPicGo | false = false
|
||||
if (enableSecondUploader) {
|
||||
const secondUploader = db.get(configPaths.picBed.secondUploader)
|
||||
const secondUploaderConfig = db.get(configPaths.picBed.secondUploaderConfig)
|
||||
const secondUploaderId = db.get(configPaths.picBed.secondUploaderId)
|
||||
const currentPicBed = db.get('picBed') || ({} as IStringKeyMap)
|
||||
currentPicBedType = currentPicBed.uploader || currentPicBed.current || 'smms'
|
||||
currentPicBedConfig = currentPicBed[currentPicBedType] || ({} as IStringKeyMap)
|
||||
currentPicBedConfigId = currentPicBedConfig._id
|
||||
if (
|
||||
secondUploader === currentPicBedType &&
|
||||
secondUploaderConfig._configName === currentPicBedConfig._configName &&
|
||||
secondUploaderId === currentPicBedConfigId
|
||||
) {
|
||||
picgo.log.info('second uploader is the same as current uploader')
|
||||
} else {
|
||||
needRestore = true
|
||||
let secondImgs: ImgInfo[] | false = false
|
||||
changeCurrentUploader(secondUploader, secondUploaderConfig, secondUploaderId)
|
||||
if (uploadType === 'clipboard') {
|
||||
ctx = await handleClipboardUploadingReturnCtx(undefined)
|
||||
} else {
|
||||
ctx = await uploader.setWebContents(webContents!).uploadReturnCtx(input)
|
||||
}
|
||||
secondImgs = ctx ? ctx.output : false
|
||||
if (secondImgs !== false) {
|
||||
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)
|
||||
if (uploadType === 'clipboard') {
|
||||
if (secondImgs.length > 0) {
|
||||
await GalleryDB.getInstance().insert(secondImgs[0])
|
||||
trayWindow?.webContents?.send('clipboardFiles', [])
|
||||
trayWindow?.webContents?.send('uploadFiles', secondImgs)
|
||||
}
|
||||
} else {
|
||||
for (const secondImgsItem of secondImgs) {
|
||||
await GalleryDB.getInstance().insert(secondImgsItem)
|
||||
}
|
||||
if (uploadType === 'tray') {
|
||||
trayWindow?.webContents?.send('dragFiles', secondImgs)
|
||||
} else {
|
||||
trayWindow?.webContents?.send('uploadFiles', secondImgs)
|
||||
}
|
||||
}
|
||||
if (windowManager.has(IWindowList.SETTING_WINDOW) && uploadType !== 'tray') {
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents?.send('updateGallery')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (needRestore) {
|
||||
changeCurrentUploader(currentPicBedType, currentPicBedConfig, currentPicBedConfigId)
|
||||
}
|
||||
return {
|
||||
needRestore,
|
||||
ctx
|
||||
}
|
||||
}
|
||||
import db, { GalleryDB } from '@core/datastore'
|
||||
import picgo from '@core/picgo'
|
||||
import uploader from 'apis/app/uploader'
|
||||
import windowManager from 'apis/app/window/windowManager'
|
||||
import { Notification, WebContents } from 'electron'
|
||||
import fs from 'fs-extra'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import type { IPicGo } from 'piclist'
|
||||
|
||||
import type { IFileWithPath, ImgInfo, IStringKeyMap, IUploadOption } from '#/types/types'
|
||||
import { T as $t } from '~/i18n/index'
|
||||
import { handleCopyUrl, handleUrlEncodeWithSetting } from '~/utils/common'
|
||||
import { configPaths } from '~/utils/configPaths'
|
||||
import { IPasteStyle, IWindowList } from '~/utils/enum'
|
||||
import { changeCurrentUploader } from '~/utils/handleUploaderConfig'
|
||||
import pasteTemplate from '~/utils/pasteTemplate'
|
||||
|
||||
const handleClipboardUploading = async (): Promise<false | ImgInfo[]> => {
|
||||
const useBuiltinClipboard =
|
||||
db.get(configPaths.settings.useBuiltinClipboard) === undefined
|
||||
? true
|
||||
: !!db.get(configPaths.settings.useBuiltinClipboard)
|
||||
const win = windowManager.getAvailableWindow()
|
||||
if (useBuiltinClipboard) {
|
||||
return await uploader.setWebContents(win!.webContents).uploadWithBuildInClipboard()
|
||||
}
|
||||
return await uploader.setWebContents(win!.webContents).upload()
|
||||
}
|
||||
|
||||
const handleClipboardUploadingReturnCtx = async (img?: IUploadOption, skipProcess = false): Promise<false | IPicGo> => {
|
||||
const useBuiltinClipboard =
|
||||
db.get(configPaths.settings.useBuiltinClipboard) === undefined
|
||||
? true
|
||||
: !!db.get(configPaths.settings.useBuiltinClipboard)
|
||||
const win = windowManager.getAvailableWindow()
|
||||
if (useBuiltinClipboard) {
|
||||
return await uploader.setWebContents(win!.webContents).uploadWithBuildInClipboardReturnCtx(img, skipProcess)
|
||||
}
|
||||
return await uploader.setWebContents(win!.webContents).uploadReturnCtx(img, skipProcess)
|
||||
}
|
||||
|
||||
export const uploadClipboardFiles = async (): Promise<IStringKeyMap> => {
|
||||
const { needRestore, ctx } = await handleSecondaryUpload(undefined, undefined, 'clipboard')
|
||||
let img: ImgInfo[] | false = false
|
||||
if (needRestore) {
|
||||
const res = await handleClipboardUploadingReturnCtx(ctx ? ctx.processedInput : undefined, true)
|
||||
img = res ? res.output : false
|
||||
} else {
|
||||
img = await handleClipboardUploading()
|
||||
}
|
||||
if (img !== false) {
|
||||
if (img.length > 0) {
|
||||
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)
|
||||
const pasteStyle = db.get(configPaths.settings.pasteStyle) || IPasteStyle.MARKDOWN
|
||||
const [pastedText, shortUrl] = await pasteTemplate(pasteStyle, img[0], db.get(configPaths.settings.customLink))
|
||||
img[0].shortUrl = shortUrl
|
||||
handleCopyUrl(pastedText)
|
||||
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: shortUrl || img[0].imgUrl!
|
||||
// icon: img[0].imgUrl
|
||||
})
|
||||
setTimeout(() => {
|
||||
notification.show()
|
||||
}, 100)
|
||||
}
|
||||
const inserted = await GalleryDB.getInstance().insert(img[0])
|
||||
// trayWindow just be created in mac/windows, not in linux
|
||||
trayWindow?.webContents?.send('clipboardFiles', [])
|
||||
trayWindow?.webContents?.send('uploadFiles', img)
|
||||
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents?.send('updateGallery')
|
||||
}
|
||||
return {
|
||||
url: handleUrlEncodeWithSetting(inserted.imgUrl as string),
|
||||
fullResult: inserted
|
||||
}
|
||||
} else {
|
||||
const notification = new Notification({
|
||||
title: $t('UPLOAD_FAILED'),
|
||||
body: $t('TIPS_UPLOAD_NOT_PICTURES')
|
||||
})
|
||||
notification.show()
|
||||
return {
|
||||
url: '',
|
||||
fullResult: {}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
url: '',
|
||||
fullResult: {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const uploadChoosedFiles = async (
|
||||
webContents: WebContents,
|
||||
files: IFileWithPath[]
|
||||
): Promise<IStringKeyMap[]> => {
|
||||
const input = files.map(item => item.path)
|
||||
const rawInput = cloneDeep(input)
|
||||
const { needRestore, ctx } = await handleSecondaryUpload(webContents, input)
|
||||
let imgs: ImgInfo[] | false = false
|
||||
if (needRestore) {
|
||||
const res = await uploader.setWebContents(webContents).uploadReturnCtx(ctx ? ctx.processedInput : input, true)
|
||||
imgs = res ? res.output : false
|
||||
} else {
|
||||
imgs = await uploader.setWebContents(webContents).upload(input)
|
||||
}
|
||||
const result = []
|
||||
if (imgs !== false) {
|
||||
const pasteStyle = db.get(configPaths.settings.pasteStyle) || IPasteStyle.MARKDOWN
|
||||
const deleteLocalFile = db.get(configPaths.settings.deleteLocalFile) || false
|
||||
const pasteText: string[] = []
|
||||
for (let i = 0; i < imgs.length; i++) {
|
||||
if (deleteLocalFile) {
|
||||
fs.remove(rawInput[i])
|
||||
.then(() => {
|
||||
picgo.log.info(`delete local file: ${rawInput[i]}`)
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
picgo.log.error(err)
|
||||
})
|
||||
}
|
||||
const [pasteTextItem, shortUrl] = await pasteTemplate(
|
||||
pasteStyle,
|
||||
imgs[i],
|
||||
db.get(configPaths.settings.customLink)
|
||||
)
|
||||
imgs[i].shortUrl = shortUrl
|
||||
pasteText.push(pasteTextItem)
|
||||
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: shortUrl || imgs[i].imgUrl!
|
||||
// icon: files[i].path
|
||||
})
|
||||
setTimeout(() => {
|
||||
notification.show()
|
||||
}, i * 100)
|
||||
}
|
||||
const inserted = await GalleryDB.getInstance().insert(imgs[i])
|
||||
result.push({
|
||||
url: handleUrlEncodeWithSetting(inserted.imgUrl!),
|
||||
fullResult: inserted
|
||||
})
|
||||
}
|
||||
handleCopyUrl(pasteText.join('\n'))
|
||||
// trayWindow just be created in mac/windows, not in linux
|
||||
windowManager.get(IWindowList.TRAY_WINDOW)?.webContents?.send('uploadFiles', imgs)
|
||||
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents?.send('updateGallery')
|
||||
}
|
||||
return result
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
export const handleSecondaryUpload = async (
|
||||
webContents?: WebContents,
|
||||
input?: string[],
|
||||
uploadType: 'clipboard' | 'file' | 'tray' = 'file'
|
||||
): Promise<{ needRestore: boolean; ctx: IPicGo | false }> => {
|
||||
const enableSecondUploader = db.get(configPaths.settings.enableSecondUploader) || false
|
||||
let currentPicBedType = ''
|
||||
let currentPicBedConfig = {} as IStringKeyMap
|
||||
let currentPicBedConfigId = ''
|
||||
let needRestore = false
|
||||
let ctx: IPicGo | false = false
|
||||
if (enableSecondUploader) {
|
||||
const secondUploader = db.get(configPaths.picBed.secondUploader)
|
||||
const secondUploaderConfig = db.get(configPaths.picBed.secondUploaderConfig)
|
||||
const secondUploaderId = db.get(configPaths.picBed.secondUploaderId)
|
||||
const currentPicBed = db.get('picBed') || ({} as IStringKeyMap)
|
||||
currentPicBedType = currentPicBed.uploader || currentPicBed.current || 'smms'
|
||||
currentPicBedConfig = currentPicBed[currentPicBedType] || ({} as IStringKeyMap)
|
||||
currentPicBedConfigId = currentPicBedConfig._id
|
||||
if (
|
||||
secondUploader === currentPicBedType &&
|
||||
secondUploaderConfig._configName === currentPicBedConfig._configName &&
|
||||
secondUploaderId === currentPicBedConfigId
|
||||
) {
|
||||
picgo.log.info('second uploader is the same as current uploader')
|
||||
} else {
|
||||
needRestore = true
|
||||
let secondImgs: ImgInfo[] | false = false
|
||||
changeCurrentUploader(secondUploader, secondUploaderConfig, secondUploaderId)
|
||||
if (uploadType === 'clipboard') {
|
||||
ctx = await handleClipboardUploadingReturnCtx(undefined)
|
||||
} else {
|
||||
ctx = await uploader.setWebContents(webContents!).uploadReturnCtx(input)
|
||||
}
|
||||
secondImgs = ctx ? ctx.output : false
|
||||
if (secondImgs !== false) {
|
||||
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)
|
||||
if (uploadType === 'clipboard') {
|
||||
if (secondImgs.length > 0) {
|
||||
await GalleryDB.getInstance().insert(secondImgs[0])
|
||||
trayWindow?.webContents?.send('clipboardFiles', [])
|
||||
trayWindow?.webContents?.send('uploadFiles', secondImgs)
|
||||
}
|
||||
} else {
|
||||
for (const secondImgsItem of secondImgs) {
|
||||
await GalleryDB.getInstance().insert(secondImgsItem)
|
||||
}
|
||||
if (uploadType === 'tray') {
|
||||
trayWindow?.webContents?.send('dragFiles', secondImgs)
|
||||
} else {
|
||||
trayWindow?.webContents?.send('uploadFiles', secondImgs)
|
||||
}
|
||||
}
|
||||
if (windowManager.has(IWindowList.SETTING_WINDOW) && uploadType !== 'tray') {
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents?.send('updateGallery')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (needRestore) {
|
||||
changeCurrentUploader(currentPicBedType, currentPicBedConfig, currentPicBedConfigId)
|
||||
}
|
||||
return {
|
||||
needRestore,
|
||||
ctx
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,11 +36,11 @@ const waitForRename = (window: BrowserWindow, id: number): Promise<string | null
|
||||
class Uploader {
|
||||
private webContents: WebContents | null = null
|
||||
|
||||
constructor () {
|
||||
constructor() {
|
||||
this.init()
|
||||
}
|
||||
|
||||
init () {
|
||||
init() {
|
||||
picgo.on(ICOREBuildInEvent.NOTIFICATION, (message: any) => {
|
||||
new Notification(message).show()
|
||||
})
|
||||
@@ -92,12 +92,12 @@ class Uploader {
|
||||
})
|
||||
}
|
||||
|
||||
setWebContents (webContents: WebContents) {
|
||||
setWebContents(webContents: WebContents) {
|
||||
this.webContents = webContents
|
||||
return this
|
||||
}
|
||||
|
||||
private async getClipboardImagePath (): Promise<string | false> {
|
||||
private async getClipboardImagePath(): Promise<string | false> {
|
||||
const imgPath = getClipboardFilePath()
|
||||
if (imgPath) return imgPath
|
||||
|
||||
@@ -115,7 +115,7 @@ class Uploader {
|
||||
/**
|
||||
* use electron's clipboard image to upload
|
||||
*/
|
||||
async uploadWithBuildInClipboard (): Promise<ImgInfo[] | false> {
|
||||
async uploadWithBuildInClipboard(): Promise<ImgInfo[] | false> {
|
||||
let imgPath: string | false = false
|
||||
try {
|
||||
imgPath = await this.getClipboardImagePath()
|
||||
@@ -131,7 +131,7 @@ class Uploader {
|
||||
}
|
||||
}
|
||||
|
||||
async uploadWithBuildInClipboardReturnCtx (img?: IUploadOption, skipProcess = false): Promise<IPicGo | false> {
|
||||
async uploadWithBuildInClipboardReturnCtx(img?: IUploadOption, skipProcess = false): Promise<IPicGo | false> {
|
||||
let imgPath: string | false = false
|
||||
try {
|
||||
imgPath = await this.getClipboardImagePath()
|
||||
@@ -147,7 +147,7 @@ class Uploader {
|
||||
}
|
||||
}
|
||||
|
||||
async uploadReturnCtx (img?: IUploadOption, skipProcess = false): Promise<IPicGo | false> {
|
||||
async uploadReturnCtx(img?: IUploadOption, skipProcess = false): Promise<IPicGo | false> {
|
||||
try {
|
||||
const ctx = await picgo.uploadReturnCtx(img, skipProcess)
|
||||
if (!Array.isArray(ctx.output) || !ctx.output.some((item: ImgInfo) => item.imgUrl)) return false
|
||||
@@ -172,7 +172,7 @@ class Uploader {
|
||||
}
|
||||
}
|
||||
|
||||
async upload (img?: IUploadOption): Promise<ImgInfo[] | false> {
|
||||
async upload(img?: IUploadOption): Promise<ImgInfo[] | false> {
|
||||
try {
|
||||
const output = await picgo.upload(img)
|
||||
if (!Array.isArray(output) || !output.some((item: ImgInfo) => item.imgUrl)) return false
|
||||
|
||||
@@ -1,277 +1,277 @@
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
import bus from '@core/bus'
|
||||
import { CREATE_APP_MENU } from '@core/bus/constants'
|
||||
import db from '@core/datastore'
|
||||
import { app, BrowserWindow, Rectangle } from 'electron'
|
||||
|
||||
import type { IWindowListItem } from '#/types/electron'
|
||||
import type { IBrowserWindowOptions } from '#/types/types'
|
||||
import { TOGGLE_SHORTKEY_MODIFIED_MODE } from '~/events/constant'
|
||||
import { T as $t } from '~/i18n'
|
||||
import { configPaths } from '~/utils/configPaths'
|
||||
import { IWindowList } from '~/utils/enum'
|
||||
|
||||
import logo from '../../../../../resources/logo.png?asset&asarUnpack'
|
||||
|
||||
const windowList = new Map<string, IWindowListItem>()
|
||||
|
||||
const getDefaultWindowSizes = (): { width: number; height: number } => {
|
||||
const [mainWindowWidth, mainWindowHeight] = db.get([
|
||||
configPaths.settings.mainWindowWidth,
|
||||
configPaths.settings.mainWindowHeight
|
||||
])
|
||||
return {
|
||||
width: mainWindowWidth || 1200,
|
||||
height: mainWindowHeight || 800
|
||||
}
|
||||
}
|
||||
|
||||
function setMiniWindowShape (win: BrowserWindow) {
|
||||
const radius = 32
|
||||
const shape: Rectangle[] = []
|
||||
|
||||
for (let y = -radius; y <= radius; y++) {
|
||||
for (let x = -radius; x <= radius; x++) {
|
||||
if (x * x + y * y <= radius * radius) {
|
||||
shape.push({ x: radius + x, y: radius + y, width: 1, height: 1 })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
win.setShape(shape)
|
||||
}
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const preloadPath = fileURLToPath(new URL('../preload/index.mjs', import.meta.url))
|
||||
|
||||
const { width: defaultWindowWidth, height: defaultWindowHeight } = getDefaultWindowSizes()
|
||||
|
||||
const trayWindowOptions = {
|
||||
height: 350,
|
||||
width: 196,
|
||||
show: false,
|
||||
frame: false,
|
||||
fullscreenable: false,
|
||||
resizable: false,
|
||||
transparent: true,
|
||||
vibrancy: 'ultra-dark',
|
||||
webPreferences: {
|
||||
sandbox: false,
|
||||
preload: preloadPath,
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
nodeIntegrationInWorker: false,
|
||||
backgroundThrottling: true,
|
||||
webSecurity: false
|
||||
}
|
||||
}
|
||||
|
||||
const settingWindowOptions = {
|
||||
height: defaultWindowHeight,
|
||||
width: defaultWindowWidth,
|
||||
show: false,
|
||||
frame: true,
|
||||
center: true,
|
||||
fullscreenable: true,
|
||||
resizable: true,
|
||||
title: 'PicList',
|
||||
transparent: false,
|
||||
backgroundColor: '#ebeef5',
|
||||
titleBarStyle: 'hidden',
|
||||
webPreferences: {
|
||||
sandbox: false,
|
||||
webviewTag: true,
|
||||
backgroundThrottling: true,
|
||||
preload: preloadPath,
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
nodeIntegrationInWorker: false,
|
||||
webSecurity: false
|
||||
}
|
||||
} as IBrowserWindowOptions
|
||||
|
||||
if (process.platform !== 'darwin') {
|
||||
settingWindowOptions.frame = false
|
||||
settingWindowOptions.icon = logo
|
||||
}
|
||||
|
||||
const miniWindowOptions = {
|
||||
height: 64,
|
||||
width: 64,
|
||||
show: process.platform === 'linux',
|
||||
frame: false,
|
||||
fullscreenable: false,
|
||||
skipTaskbar: true,
|
||||
resizable: false,
|
||||
transparent: process.platform !== 'linux',
|
||||
icon: logo,
|
||||
webPreferences: {
|
||||
sandbox: false,
|
||||
preload: preloadPath,
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
backgroundThrottling: true,
|
||||
nodeIntegrationInWorker: false
|
||||
}
|
||||
} as IBrowserWindowOptions
|
||||
|
||||
if (db.get(configPaths.settings.miniWindowOntop)) {
|
||||
miniWindowOptions.alwaysOnTop = true
|
||||
}
|
||||
|
||||
const renameWindowOptions = {
|
||||
height: 270,
|
||||
width: 350,
|
||||
show: true,
|
||||
fullscreenable: false,
|
||||
icon: logo,
|
||||
resizable: true,
|
||||
webPreferences: {
|
||||
sandbox: false,
|
||||
preload: preloadPath,
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
nodeIntegrationInWorker: false,
|
||||
backgroundThrottling: false
|
||||
}
|
||||
} as IBrowserWindowOptions
|
||||
|
||||
if (process.platform !== 'darwin') {
|
||||
renameWindowOptions.show = true
|
||||
renameWindowOptions.backgroundColor = '#3f3c37'
|
||||
renameWindowOptions.autoHideMenuBar = true
|
||||
renameWindowOptions.transparent = false
|
||||
}
|
||||
|
||||
const toolboxWindowOptions = {
|
||||
height: 450,
|
||||
width: 800,
|
||||
show: false,
|
||||
frame: true,
|
||||
center: true,
|
||||
fullscreenable: false,
|
||||
resizable: false,
|
||||
title: `PicList ${$t('TOOLBOX')}`,
|
||||
backgroundColor: '#ebeef5',
|
||||
icon: logo,
|
||||
webPreferences: {
|
||||
sandbox: false,
|
||||
backgroundThrottling: true,
|
||||
preload: preloadPath,
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
nodeIntegrationInWorker: false,
|
||||
webSecurity: false
|
||||
}
|
||||
} as IBrowserWindowOptions
|
||||
|
||||
if (process.platform !== 'darwin') {
|
||||
toolboxWindowOptions.backgroundColor = '#3f3c37'
|
||||
toolboxWindowOptions.autoHideMenuBar = true
|
||||
toolboxWindowOptions.transparent = false
|
||||
}
|
||||
|
||||
windowList.set(IWindowList.TRAY_WINDOW, {
|
||||
isValid: process.platform !== 'linux',
|
||||
multiple: false,
|
||||
options: () => trayWindowOptions,
|
||||
callback (window) {
|
||||
if (!app.isPackaged && process.env.ELECTRON_RENDERER_URL) {
|
||||
window.loadURL(process.env.ELECTRON_RENDERER_URL)
|
||||
} else {
|
||||
window.loadFile(path.join(__dirname, '../renderer/index.html'))
|
||||
}
|
||||
window.on('blur', () => {
|
||||
window.hide()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
windowList.set(IWindowList.SETTING_WINDOW, {
|
||||
isValid: true,
|
||||
multiple: false,
|
||||
options: () => settingWindowOptions,
|
||||
callback (window, windowManager) {
|
||||
if (!app.isPackaged && process.env.ELECTRON_RENDERER_URL) {
|
||||
window.loadURL(`${process.env.ELECTRON_RENDERER_URL}#main-page/upload`)
|
||||
} else {
|
||||
window.loadFile(path.join(__dirname, '../renderer/index.html'), {
|
||||
hash: 'main-page/upload'
|
||||
})
|
||||
}
|
||||
window.on('closed', () => {
|
||||
bus.emit(TOGGLE_SHORTKEY_MODIFIED_MODE, false)
|
||||
if (process.platform === 'linux') {
|
||||
process.nextTick(() => {
|
||||
app.quit()
|
||||
})
|
||||
}
|
||||
})
|
||||
bus.emit(CREATE_APP_MENU)
|
||||
windowManager.create(IWindowList.MINI_WINDOW)
|
||||
}
|
||||
})
|
||||
|
||||
windowList.set(IWindowList.MINI_WINDOW, {
|
||||
isValid: process.platform !== 'darwin',
|
||||
multiple: false,
|
||||
options: () => miniWindowOptions,
|
||||
callback (window) {
|
||||
if (!app.isPackaged && process.env.ELECTRON_RENDERER_URL) {
|
||||
window.loadURL(`${process.env.ELECTRON_RENDERER_URL}#mini-page`)
|
||||
} else {
|
||||
window.loadFile(path.join(__dirname, '../renderer/index.html'), {
|
||||
hash: 'mini-page'
|
||||
})
|
||||
}
|
||||
setMiniWindowShape(window)
|
||||
}
|
||||
})
|
||||
|
||||
windowList.set(IWindowList.RENAME_WINDOW, {
|
||||
isValid: true,
|
||||
multiple: true,
|
||||
options: () => renameWindowOptions,
|
||||
async callback (window, windowManager) {
|
||||
if (!app.isPackaged && process.env.ELECTRON_RENDERER_URL) {
|
||||
window.loadURL(`${process.env.ELECTRON_RENDERER_URL}#rename-page`)
|
||||
} else {
|
||||
window.loadFile(path.join(__dirname, '../renderer/index.html'), {
|
||||
hash: 'rename-page'
|
||||
})
|
||||
}
|
||||
const currentWindow = windowManager.getAvailableWindow(true)
|
||||
if (currentWindow && currentWindow.isVisible()) {
|
||||
const { x, y, width, height } = currentWindow.getBounds()
|
||||
const positionX = Math.floor(x + width / 2 - 150)
|
||||
const positionY = Math.floor(y + height / 2 - (height > 400 ? 88 : 0))
|
||||
window.setPosition(positionX, positionY, false)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
windowList.set(IWindowList.TOOLBOX_WINDOW, {
|
||||
isValid: true,
|
||||
multiple: false,
|
||||
options: () => toolboxWindowOptions,
|
||||
async callback (window, windowManager) {
|
||||
if (!app.isPackaged && process.env.ELECTRON_RENDERER_URL) {
|
||||
window.loadURL(`${process.env.ELECTRON_RENDERER_URL}#toolbox-page`)
|
||||
} else {
|
||||
window.loadFile(path.join(__dirname, '../renderer/index.html'), {
|
||||
hash: 'toolbox-page'
|
||||
})
|
||||
}
|
||||
const currentWindow = windowManager.getAvailableWindow(true)
|
||||
if (currentWindow && currentWindow.isVisible()) {
|
||||
const { x, y, width, height } = currentWindow.getBounds()
|
||||
const positionX = Math.floor(x + width / 2 - 400)
|
||||
const positionY = Math.floor(y + height / 2 - (height > 400 ? 225 : 0))
|
||||
window.setPosition(positionX, positionY, false)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export default windowList
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
import bus from '@core/bus'
|
||||
import { CREATE_APP_MENU } from '@core/bus/constants'
|
||||
import db from '@core/datastore'
|
||||
import { app, BrowserWindow, Rectangle } from 'electron'
|
||||
|
||||
import type { IWindowListItem } from '#/types/electron'
|
||||
import type { IBrowserWindowOptions } from '#/types/types'
|
||||
import { TOGGLE_SHORTKEY_MODIFIED_MODE } from '~/events/constant'
|
||||
import { T as $t } from '~/i18n'
|
||||
import { configPaths } from '~/utils/configPaths'
|
||||
import { IWindowList } from '~/utils/enum'
|
||||
|
||||
import logo from '../../../../../resources/logo.png?asset&asarUnpack'
|
||||
|
||||
const windowList = new Map<string, IWindowListItem>()
|
||||
|
||||
const getDefaultWindowSizes = (): { width: number; height: number } => {
|
||||
const [mainWindowWidth, mainWindowHeight] = db.get([
|
||||
configPaths.settings.mainWindowWidth,
|
||||
configPaths.settings.mainWindowHeight
|
||||
])
|
||||
return {
|
||||
width: mainWindowWidth || 1200,
|
||||
height: mainWindowHeight || 800
|
||||
}
|
||||
}
|
||||
|
||||
function setMiniWindowShape(win: BrowserWindow) {
|
||||
const radius = 32
|
||||
const shape: Rectangle[] = []
|
||||
|
||||
for (let y = -radius; y <= radius; y++) {
|
||||
for (let x = -radius; x <= radius; x++) {
|
||||
if (x * x + y * y <= radius * radius) {
|
||||
shape.push({ x: radius + x, y: radius + y, width: 1, height: 1 })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
win.setShape(shape)
|
||||
}
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const preloadPath = fileURLToPath(new URL('../preload/index.mjs', import.meta.url))
|
||||
|
||||
const { width: defaultWindowWidth, height: defaultWindowHeight } = getDefaultWindowSizes()
|
||||
|
||||
const trayWindowOptions = {
|
||||
height: 350,
|
||||
width: 196,
|
||||
show: false,
|
||||
frame: false,
|
||||
fullscreenable: false,
|
||||
resizable: false,
|
||||
transparent: true,
|
||||
vibrancy: 'ultra-dark',
|
||||
webPreferences: {
|
||||
sandbox: false,
|
||||
preload: preloadPath,
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
nodeIntegrationInWorker: false,
|
||||
backgroundThrottling: true,
|
||||
webSecurity: false
|
||||
}
|
||||
}
|
||||
|
||||
const settingWindowOptions = {
|
||||
height: defaultWindowHeight,
|
||||
width: defaultWindowWidth,
|
||||
show: false,
|
||||
frame: true,
|
||||
center: true,
|
||||
fullscreenable: true,
|
||||
resizable: true,
|
||||
title: 'PicList',
|
||||
transparent: false,
|
||||
backgroundColor: '#ebeef5',
|
||||
titleBarStyle: 'hidden',
|
||||
webPreferences: {
|
||||
sandbox: false,
|
||||
webviewTag: true,
|
||||
backgroundThrottling: true,
|
||||
preload: preloadPath,
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
nodeIntegrationInWorker: false,
|
||||
webSecurity: false
|
||||
}
|
||||
} as IBrowserWindowOptions
|
||||
|
||||
if (process.platform !== 'darwin') {
|
||||
settingWindowOptions.frame = false
|
||||
settingWindowOptions.icon = logo
|
||||
}
|
||||
|
||||
const miniWindowOptions = {
|
||||
height: 64,
|
||||
width: 64,
|
||||
show: process.platform === 'linux',
|
||||
frame: false,
|
||||
fullscreenable: false,
|
||||
skipTaskbar: true,
|
||||
resizable: false,
|
||||
transparent: process.platform !== 'linux',
|
||||
icon: logo,
|
||||
webPreferences: {
|
||||
sandbox: false,
|
||||
preload: preloadPath,
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
backgroundThrottling: true,
|
||||
nodeIntegrationInWorker: false
|
||||
}
|
||||
} as IBrowserWindowOptions
|
||||
|
||||
if (db.get(configPaths.settings.miniWindowOntop)) {
|
||||
miniWindowOptions.alwaysOnTop = true
|
||||
}
|
||||
|
||||
const renameWindowOptions = {
|
||||
height: 270,
|
||||
width: 350,
|
||||
show: true,
|
||||
fullscreenable: false,
|
||||
icon: logo,
|
||||
resizable: true,
|
||||
webPreferences: {
|
||||
sandbox: false,
|
||||
preload: preloadPath,
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
nodeIntegrationInWorker: false,
|
||||
backgroundThrottling: false
|
||||
}
|
||||
} as IBrowserWindowOptions
|
||||
|
||||
if (process.platform !== 'darwin') {
|
||||
renameWindowOptions.show = true
|
||||
renameWindowOptions.backgroundColor = '#3f3c37'
|
||||
renameWindowOptions.autoHideMenuBar = true
|
||||
renameWindowOptions.transparent = false
|
||||
}
|
||||
|
||||
const toolboxWindowOptions = {
|
||||
height: 450,
|
||||
width: 800,
|
||||
show: false,
|
||||
frame: true,
|
||||
center: true,
|
||||
fullscreenable: false,
|
||||
resizable: false,
|
||||
title: `PicList ${$t('TOOLBOX')}`,
|
||||
backgroundColor: '#ebeef5',
|
||||
icon: logo,
|
||||
webPreferences: {
|
||||
sandbox: false,
|
||||
backgroundThrottling: true,
|
||||
preload: preloadPath,
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
nodeIntegrationInWorker: false,
|
||||
webSecurity: false
|
||||
}
|
||||
} as IBrowserWindowOptions
|
||||
|
||||
if (process.platform !== 'darwin') {
|
||||
toolboxWindowOptions.backgroundColor = '#3f3c37'
|
||||
toolboxWindowOptions.autoHideMenuBar = true
|
||||
toolboxWindowOptions.transparent = false
|
||||
}
|
||||
|
||||
windowList.set(IWindowList.TRAY_WINDOW, {
|
||||
isValid: process.platform !== 'linux',
|
||||
multiple: false,
|
||||
options: () => trayWindowOptions,
|
||||
callback(window) {
|
||||
if (!app.isPackaged && process.env.ELECTRON_RENDERER_URL) {
|
||||
window.loadURL(process.env.ELECTRON_RENDERER_URL)
|
||||
} else {
|
||||
window.loadFile(path.join(__dirname, '../renderer/index.html'))
|
||||
}
|
||||
window.on('blur', () => {
|
||||
window.hide()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
windowList.set(IWindowList.SETTING_WINDOW, {
|
||||
isValid: true,
|
||||
multiple: false,
|
||||
options: () => settingWindowOptions,
|
||||
callback(window, windowManager) {
|
||||
if (!app.isPackaged && process.env.ELECTRON_RENDERER_URL) {
|
||||
window.loadURL(`${process.env.ELECTRON_RENDERER_URL}#main-page/upload`)
|
||||
} else {
|
||||
window.loadFile(path.join(__dirname, '../renderer/index.html'), {
|
||||
hash: 'main-page/upload'
|
||||
})
|
||||
}
|
||||
window.on('closed', () => {
|
||||
bus.emit(TOGGLE_SHORTKEY_MODIFIED_MODE, false)
|
||||
if (process.platform === 'linux') {
|
||||
process.nextTick(() => {
|
||||
app.quit()
|
||||
})
|
||||
}
|
||||
})
|
||||
bus.emit(CREATE_APP_MENU)
|
||||
windowManager.create(IWindowList.MINI_WINDOW)
|
||||
}
|
||||
})
|
||||
|
||||
windowList.set(IWindowList.MINI_WINDOW, {
|
||||
isValid: process.platform !== 'darwin',
|
||||
multiple: false,
|
||||
options: () => miniWindowOptions,
|
||||
callback(window) {
|
||||
if (!app.isPackaged && process.env.ELECTRON_RENDERER_URL) {
|
||||
window.loadURL(`${process.env.ELECTRON_RENDERER_URL}#mini-page`)
|
||||
} else {
|
||||
window.loadFile(path.join(__dirname, '../renderer/index.html'), {
|
||||
hash: 'mini-page'
|
||||
})
|
||||
}
|
||||
setMiniWindowShape(window)
|
||||
}
|
||||
})
|
||||
|
||||
windowList.set(IWindowList.RENAME_WINDOW, {
|
||||
isValid: true,
|
||||
multiple: true,
|
||||
options: () => renameWindowOptions,
|
||||
async callback(window, windowManager) {
|
||||
if (!app.isPackaged && process.env.ELECTRON_RENDERER_URL) {
|
||||
window.loadURL(`${process.env.ELECTRON_RENDERER_URL}#rename-page`)
|
||||
} else {
|
||||
window.loadFile(path.join(__dirname, '../renderer/index.html'), {
|
||||
hash: 'rename-page'
|
||||
})
|
||||
}
|
||||
const currentWindow = windowManager.getAvailableWindow(true)
|
||||
if (currentWindow && currentWindow.isVisible()) {
|
||||
const { x, y, width, height } = currentWindow.getBounds()
|
||||
const positionX = Math.floor(x + width / 2 - 150)
|
||||
const positionY = Math.floor(y + height / 2 - (height > 400 ? 88 : 0))
|
||||
window.setPosition(positionX, positionY, false)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
windowList.set(IWindowList.TOOLBOX_WINDOW, {
|
||||
isValid: true,
|
||||
multiple: false,
|
||||
options: () => toolboxWindowOptions,
|
||||
async callback(window, windowManager) {
|
||||
if (!app.isPackaged && process.env.ELECTRON_RENDERER_URL) {
|
||||
window.loadURL(`${process.env.ELECTRON_RENDERER_URL}#toolbox-page`)
|
||||
} else {
|
||||
window.loadFile(path.join(__dirname, '../renderer/index.html'), {
|
||||
hash: 'toolbox-page'
|
||||
})
|
||||
}
|
||||
const currentWindow = windowManager.getAvailableWindow(true)
|
||||
if (currentWindow && currentWindow.isVisible()) {
|
||||
const { x, y, width, height } = currentWindow.getBounds()
|
||||
const positionX = Math.floor(x + width / 2 - 400)
|
||||
const positionY = Math.floor(y + height / 2 - (height > 400 ? 225 : 0))
|
||||
window.setPosition(positionX, positionY, false)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export default windowList
|
||||
|
||||
@@ -5,10 +5,10 @@ import type { IWindowListItem, IWindowManager } from '#/types/electron'
|
||||
import { IWindowList } from '~/utils/enum'
|
||||
|
||||
class WindowManager implements IWindowManager {
|
||||
#windowMap: Map< string, BrowserWindow> = new Map()
|
||||
#windowMap: Map<string, BrowserWindow> = new Map()
|
||||
#windowIdMap: Map<number, string> = new Map()
|
||||
|
||||
create (name: string) {
|
||||
create(name: string) {
|
||||
const windowConfig: IWindowListItem = windowList.get(name)!
|
||||
if (!windowConfig.isValid) return null
|
||||
|
||||
@@ -30,14 +30,14 @@ class WindowManager implements IWindowManager {
|
||||
return window
|
||||
}
|
||||
|
||||
get (name: string) {
|
||||
get(name: string) {
|
||||
if (this.has(name)) {
|
||||
return this.#windowMap.get(name)!
|
||||
}
|
||||
return this.create(name)
|
||||
}
|
||||
|
||||
has (name: string) {
|
||||
has(name: string) {
|
||||
return this.#windowMap.has(name)
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ class WindowManager implements IWindowManager {
|
||||
}
|
||||
}
|
||||
|
||||
getAvailableWindow (isSkipMiniWindow = false) {
|
||||
getAvailableWindow(isSkipMiniWindow = false) {
|
||||
const miniWindow = this.#windowMap.get(IWindowList.MINI_WINDOW)
|
||||
if (miniWindow && miniWindow.isVisible() && !isSkipMiniWindow) {
|
||||
return miniWindow
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
import { EventEmitter } from 'node:events'
|
||||
|
||||
class OptimizedBus extends EventEmitter {
|
||||
constructor () {
|
||||
super()
|
||||
this.setMaxListeners(50)
|
||||
}
|
||||
|
||||
once (event: string | symbol, listener: (...args: any[]) => void): this {
|
||||
const wrappedListener = (...args: any[]) => {
|
||||
try {
|
||||
listener(...args)
|
||||
} finally {
|
||||
this.removeListener(event, wrappedListener)
|
||||
}
|
||||
}
|
||||
return super.once(event, wrappedListener)
|
||||
}
|
||||
|
||||
cleanupListeners () {
|
||||
const events = this.eventNames()
|
||||
events.forEach(event => {
|
||||
const listenerCount = this.listenerCount(event)
|
||||
console.log(` listener count (${listenerCount}) for event: ${String(event)}`)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const bus = new OptimizedBus()
|
||||
|
||||
export default bus
|
||||
import { EventEmitter } from 'node:events'
|
||||
|
||||
class OptimizedBus extends EventEmitter {
|
||||
constructor() {
|
||||
super()
|
||||
this.setMaxListeners(50)
|
||||
}
|
||||
|
||||
once(event: string | symbol, listener: (...args: any[]) => void): this {
|
||||
const wrappedListener = (...args: any[]) => {
|
||||
try {
|
||||
listener(...args)
|
||||
} finally {
|
||||
this.removeListener(event, wrappedListener)
|
||||
}
|
||||
}
|
||||
return super.once(event, wrappedListener)
|
||||
}
|
||||
|
||||
cleanupListeners() {
|
||||
const events = this.eventNames()
|
||||
events.forEach(event => {
|
||||
const listenerCount = this.listenerCount(event)
|
||||
console.log(` listener count (${listenerCount}) for event: ${String(event)}`)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const bus = new OptimizedBus()
|
||||
|
||||
export default bus
|
||||
|
||||
@@ -23,7 +23,7 @@ const errorMsg = {
|
||||
brokenButBackup: $t('TIPS_PICGO_CONFIG_FILE_BROKEN_WITH_BACKUP')
|
||||
}
|
||||
|
||||
function dbChecker () {
|
||||
function dbChecker() {
|
||||
if (process.type !== 'renderer') {
|
||||
// db save bak
|
||||
try {
|
||||
@@ -80,7 +80,7 @@ function dbChecker () {
|
||||
/**
|
||||
* Get config path
|
||||
*/
|
||||
function dbPathChecker (): string {
|
||||
function dbPathChecker(): string {
|
||||
if (_configFilePath) {
|
||||
return _configFilePath
|
||||
}
|
||||
@@ -120,11 +120,11 @@ function dbPathChecker (): string {
|
||||
}
|
||||
}
|
||||
|
||||
function dbPathDir () {
|
||||
function dbPathDir() {
|
||||
return path.dirname(dbPathChecker())
|
||||
}
|
||||
|
||||
function getGalleryDBPath (): {
|
||||
function getGalleryDBPath(): {
|
||||
dbPath: string
|
||||
dbBackupPath: string
|
||||
} {
|
||||
|
||||
@@ -19,7 +19,7 @@ export const DB_PATH: string = getGalleryDBPath().dbPath
|
||||
class ConfigStore {
|
||||
#db: JSONStore
|
||||
|
||||
constructor () {
|
||||
constructor() {
|
||||
this.#db = new JSONStore(CONFIG_PATH)
|
||||
|
||||
if (!this.#db.has('picBed')) {
|
||||
@@ -43,11 +43,11 @@ class ConfigStore {
|
||||
this.read()
|
||||
}
|
||||
|
||||
read (flush?: boolean): IJSON {
|
||||
read(flush?: boolean): IJSON {
|
||||
return this.#db.read(flush)
|
||||
}
|
||||
|
||||
getSingle (key = ''): any {
|
||||
getSingle(key = ''): any {
|
||||
if (key === '') {
|
||||
return this.#db.read(true)
|
||||
}
|
||||
@@ -55,43 +55,43 @@ class ConfigStore {
|
||||
return this.#db.get(key)
|
||||
}
|
||||
|
||||
get (key: string): any
|
||||
get (key: string[]): any[]
|
||||
get (key: string | string[] = ''): any {
|
||||
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 {
|
||||
set(key: string, value: any): void {
|
||||
this.read(true)
|
||||
return this.#db.set(key, value)
|
||||
}
|
||||
|
||||
has (key: string) {
|
||||
has(key: string) {
|
||||
this.read(true)
|
||||
return this.#db.has(key)
|
||||
}
|
||||
|
||||
unset (key: string, value: any): boolean {
|
||||
unset(key: string, value: any): boolean {
|
||||
this.read(true)
|
||||
return this.#db.unset(key, value)
|
||||
}
|
||||
|
||||
saveConfig (config: Partial<IConfig>): void {
|
||||
saveConfig(config: Partial<IConfig>): void {
|
||||
Object.keys(config).forEach((name: string) => {
|
||||
this.set(name, config[name])
|
||||
})
|
||||
}
|
||||
|
||||
removeConfig (config: IConfig): void {
|
||||
removeConfig(config: IConfig): void {
|
||||
Object.keys(config).forEach((name: string) => {
|
||||
this.unset(name, config[name])
|
||||
})
|
||||
}
|
||||
|
||||
getConfigPath () {
|
||||
getConfigPath() {
|
||||
return CONFIG_PATH
|
||||
}
|
||||
}
|
||||
@@ -103,11 +103,11 @@ export default db
|
||||
// v2.3.0 add gallery db
|
||||
class GalleryDB {
|
||||
static #instance: DBStore
|
||||
private constructor () {
|
||||
private constructor() {
|
||||
console.log('init gallery db')
|
||||
}
|
||||
|
||||
static getInstance (): DBStore {
|
||||
static getInstance(): DBStore {
|
||||
if (!GalleryDB.#instance) {
|
||||
GalleryDB.#instance = new DBStore(DB_PATH, 'gallery')
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ picgo.GUI_VERSION = pkg.version
|
||||
|
||||
const originPicGoSaveConfig = picgo.saveConfig.bind(picgo)
|
||||
|
||||
function flushDB () {
|
||||
function flushDB() {
|
||||
db.read(true)
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ interface IConfigMap {
|
||||
}
|
||||
|
||||
export default class AlistApi {
|
||||
static async delete (configMap: IConfigMap): Promise<boolean> {
|
||||
static async delete(configMap: IConfigMap): Promise<boolean> {
|
||||
const { fileName, config } = configMap
|
||||
try {
|
||||
const { version, url, uploadPath, token } = config
|
||||
|
||||
@@ -26,7 +26,7 @@ const getAListToken = async (url: string, username: string, password: string) =>
|
||||
}
|
||||
|
||||
export default class AListplistApi {
|
||||
static async delete (configMap: IConfigMap): Promise<boolean> {
|
||||
static async delete(configMap: IConfigMap): Promise<boolean> {
|
||||
const { fileName, config } = configMap
|
||||
try {
|
||||
const { url, username, password, uploadPath } = config
|
||||
|
||||
@@ -9,11 +9,11 @@ interface IConfigMap {
|
||||
}
|
||||
|
||||
export default class AliyunApi {
|
||||
static #getKey (fileName: string, path?: string): string {
|
||||
static #getKey(fileName: string, path?: string): string {
|
||||
return path && path !== '/' ? `${path.replace(/^\/+|\/+$/, '')}/${fileName}` : fileName
|
||||
}
|
||||
|
||||
static async delete (configMap: IConfigMap): Promise<boolean> {
|
||||
static async delete(configMap: IConfigMap): Promise<boolean> {
|
||||
const { fileName, config } = configMap
|
||||
try {
|
||||
const client = new OSS({ ...config, region: config.area })
|
||||
|
||||
@@ -39,7 +39,7 @@ const apiMap: IStringKeyMap = {
|
||||
}
|
||||
|
||||
export default class ALLApi {
|
||||
static async delete (configMap: IStringKeyMap): Promise<boolean> {
|
||||
static async delete(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const api = apiMap[configMap.type]
|
||||
return api ? await api.delete(configMap) : false
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { removeFileFromS3InMain } from '~/utils/deleteFunc'
|
||||
import { deleteFailedLog } from '~/utils/deleteLog'
|
||||
|
||||
export default class AwsS3Api {
|
||||
static async delete (configMap: IStringKeyMap): Promise<boolean> {
|
||||
static async delete(configMap: IStringKeyMap): Promise<boolean> {
|
||||
try {
|
||||
return await removeFileFromS3InMain(getRawData(configMap))
|
||||
} catch (error: any) {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { removeFileFromDogeInMain } from '~/utils/deleteFunc'
|
||||
import { deleteFailedLog } from '~/utils/deleteLog'
|
||||
|
||||
export default class AwsS3Api {
|
||||
static async delete (configMap: IStringKeyMap): Promise<boolean> {
|
||||
static async delete(configMap: IStringKeyMap): Promise<boolean> {
|
||||
try {
|
||||
return await removeFileFromDogeInMain(getRawData(configMap))
|
||||
} catch (error: any) {
|
||||
|
||||
@@ -10,18 +10,18 @@ interface IConfigMap {
|
||||
}
|
||||
|
||||
export default class GithubApi {
|
||||
static #createOctokit (token: string) {
|
||||
static #createOctokit(token: string) {
|
||||
return new Octokit({
|
||||
auth: token
|
||||
})
|
||||
}
|
||||
|
||||
static #createKey (path: string | undefined, fileName: string): string {
|
||||
static #createKey(path: string | undefined, fileName: string): string {
|
||||
const formatedFileName = fileName.replace(/%2F/g, '/')
|
||||
return path && path !== '/' ? `${path.replace(/^\/+|\/+$/, '')}/${formatedFileName}` : formatedFileName
|
||||
}
|
||||
|
||||
static async delete (configMap: IConfigMap): Promise<boolean> {
|
||||
static async delete(configMap: IConfigMap): Promise<boolean> {
|
||||
const {
|
||||
fileName,
|
||||
hash,
|
||||
|
||||
@@ -4,7 +4,7 @@ import { removeFileFromHuaweiInMain } from '~/utils/deleteFunc'
|
||||
import { deleteFailedLog } from '~/utils/deleteLog'
|
||||
|
||||
export default class HuaweicloudApi {
|
||||
static async delete (configMap: IStringKeyMap): Promise<boolean> {
|
||||
static async delete(configMap: IStringKeyMap): Promise<boolean> {
|
||||
try {
|
||||
return await removeFileFromHuaweiInMain(getRawData(configMap))
|
||||
} catch (error: any) {
|
||||
|
||||
@@ -11,7 +11,7 @@ interface IConfigMap {
|
||||
export default class ImgurApi {
|
||||
static #baseUrl = 'https://api.imgur.com/3'
|
||||
|
||||
static async delete (configMap: IConfigMap): Promise<boolean> {
|
||||
static async delete(configMap: IConfigMap): Promise<boolean> {
|
||||
const { config: { clientId = '', username = '', accessToken = '' } = {}, hash = '' } = configMap
|
||||
let Authorization: string, apiUrl: string
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ interface IConfigMap {
|
||||
}
|
||||
|
||||
export default class LocalApi {
|
||||
static async delete (configMap: IConfigMap): Promise<boolean> {
|
||||
static async delete(configMap: IConfigMap): Promise<boolean> {
|
||||
const { hash } = configMap
|
||||
if (!hash) {
|
||||
deleteLog(hash, 'Local', false, 'Local.delete: invalid params')
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { IStringKeyMap } from '#/types/types'
|
||||
import { deleteFailedLog, deleteLog } from '~/utils/deleteLog'
|
||||
|
||||
export default class LskyplistApi {
|
||||
static async delete (configMap: IStringKeyMap): Promise<boolean> {
|
||||
static async delete(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { hash, config } = configMap
|
||||
if (!hash || !config || !config.token) {
|
||||
deleteLog(hash, 'Lskyplist', false, 'LskyplistApi.delete: invalid params')
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { IStringKeyMap } from '#/types/types'
|
||||
import { deleteFailedLog, deleteLog } from '~/utils/deleteLog'
|
||||
|
||||
export default class PiclistApi {
|
||||
static async delete (configMap: IStringKeyMap): Promise<boolean> {
|
||||
static async delete(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { config, fullResult } = configMap
|
||||
const { host, port } = config
|
||||
if (!fullResult) return true
|
||||
|
||||
@@ -8,7 +8,7 @@ interface IConfigMap {
|
||||
}
|
||||
|
||||
export default class QiniuApi {
|
||||
static async delete (configMap: IConfigMap): Promise<boolean> {
|
||||
static async delete(configMap: IConfigMap): Promise<boolean> {
|
||||
const {
|
||||
fileName,
|
||||
config: { accessKey, secretKey, bucket, path }
|
||||
|
||||
@@ -4,7 +4,7 @@ import { removeFileFromSFTPInMain } from '~/utils/deleteFunc'
|
||||
import { deleteFailedLog } from '~/utils/deleteLog'
|
||||
|
||||
export default class SftpPlistApi {
|
||||
static async delete (configMap: IStringKeyMap): Promise<boolean> {
|
||||
static async delete(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { fileName, config } = configMap
|
||||
try {
|
||||
return await removeFileFromSFTPInMain(getRawData(config), fileName)
|
||||
|
||||
@@ -11,7 +11,7 @@ interface IConfigMap {
|
||||
export default class SmmsApi {
|
||||
static readonly #baseUrl = 'https://smms.app/api/v2'
|
||||
|
||||
static async delete (configMap: IConfigMap): Promise<boolean> {
|
||||
static async delete(configMap: IConfigMap): Promise<boolean> {
|
||||
const { hash, config } = configMap
|
||||
if (!hash || !config || !config.token) {
|
||||
deleteLog(hash, 'Smms', false, 'SmmsApi.delete: invalid params')
|
||||
|
||||
@@ -7,14 +7,14 @@ interface IConfigMap {
|
||||
config: PartialKeys<ITcYunConfig, 'path'>
|
||||
}
|
||||
export default class TcyunApi {
|
||||
static #createCOS (SecretId: string, SecretKey: string): COS {
|
||||
static #createCOS(SecretId: string, SecretKey: string): COS {
|
||||
return new COS({
|
||||
SecretId,
|
||||
SecretKey
|
||||
})
|
||||
}
|
||||
|
||||
static async delete (configMap: IConfigMap): Promise<boolean> {
|
||||
static async delete(configMap: IConfigMap): Promise<boolean> {
|
||||
const {
|
||||
fileName,
|
||||
config: { secretId, secretKey, bucket, area, path }
|
||||
|
||||
@@ -9,7 +9,7 @@ interface IConfigMap {
|
||||
}
|
||||
|
||||
export default class UpyunApi {
|
||||
static async delete (configMap: IConfigMap): Promise<boolean> {
|
||||
static async delete(configMap: IConfigMap): Promise<boolean> {
|
||||
const {
|
||||
fileName,
|
||||
config: { bucket, operator, password, path }
|
||||
|
||||
@@ -10,7 +10,7 @@ interface IConfigMap {
|
||||
}
|
||||
|
||||
export default class WebdavApi {
|
||||
static async delete (configMap: IConfigMap): Promise<boolean> {
|
||||
static async delete(configMap: IConfigMap): Promise<boolean> {
|
||||
const {
|
||||
fileName,
|
||||
config: { host, username, password, path, sslEnabled, authType }
|
||||
|
||||
@@ -8,7 +8,16 @@ import { BrowserWindow, dialog, ipcMain, IpcMainEvent, MessageBoxOptions, Notifi
|
||||
import fs from 'fs-extra'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
|
||||
import type { IGuiApi, ImgInfo, IShowFileExplorerOption, IShowInputBoxOption, IShowMessageBoxOption, IShowMessageBoxResult, IShowNotificationOption, IUploadOption } from '#/types/types'
|
||||
import type {
|
||||
IGuiApi,
|
||||
ImgInfo,
|
||||
IShowFileExplorerOption,
|
||||
IShowInputBoxOption,
|
||||
IShowMessageBoxOption,
|
||||
IShowMessageBoxResult,
|
||||
IShowNotificationOption,
|
||||
IUploadOption
|
||||
} from '#/types/types'
|
||||
import { SHOW_INPUT_BOX } from '~/events/constant'
|
||||
import { T as $t } from '~/i18n'
|
||||
import { handleCopyUrl } from '~/utils/common'
|
||||
@@ -21,18 +30,18 @@ class GuiApi implements IGuiApi {
|
||||
private static instance: GuiApi
|
||||
private windowId: number = -1
|
||||
private settingWindowId: number = -1
|
||||
private constructor () {
|
||||
private constructor() {
|
||||
console.log('init guiapi')
|
||||
}
|
||||
|
||||
static getInstance (): GuiApi {
|
||||
static getInstance(): GuiApi {
|
||||
if (!GuiApi.instance) {
|
||||
GuiApi.instance = new GuiApi()
|
||||
}
|
||||
return GuiApi.instance
|
||||
}
|
||||
|
||||
private async showSettingWindow () {
|
||||
private async showSettingWindow() {
|
||||
this.settingWindowId = await getSettingWindowId()
|
||||
const settingWindow = BrowserWindow.fromId(this.settingWindowId)
|
||||
if (settingWindow?.isVisible()) {
|
||||
@@ -46,11 +55,11 @@ class GuiApi implements IGuiApi {
|
||||
})
|
||||
}
|
||||
|
||||
private getWebcontentsByWindowId (id: number) {
|
||||
private getWebcontentsByWindowId(id: number) {
|
||||
return BrowserWindow.fromId(id)?.webContents
|
||||
}
|
||||
|
||||
async showInputBox (
|
||||
async showInputBox(
|
||||
options: IShowInputBoxOption = {
|
||||
title: '',
|
||||
placeholder: ''
|
||||
@@ -65,13 +74,13 @@ class GuiApi implements IGuiApi {
|
||||
})
|
||||
}
|
||||
|
||||
async showFileExplorer (options: IShowFileExplorerOption = {}) {
|
||||
async showFileExplorer(options: IShowFileExplorerOption = {}) {
|
||||
this.windowId = await getWindowId()
|
||||
const res = await dialog.showOpenDialog(BrowserWindow.fromId(this.windowId)!, options)
|
||||
return res.filePaths || []
|
||||
}
|
||||
|
||||
async upload (input: IUploadOption) {
|
||||
async upload(input: IUploadOption) {
|
||||
this.windowId = await getWindowId()
|
||||
const webContents = this.getWebcontentsByWindowId(this.windowId)
|
||||
const rawInput = cloneDeep(input)
|
||||
@@ -122,7 +131,7 @@ class GuiApi implements IGuiApi {
|
||||
return []
|
||||
}
|
||||
|
||||
showNotification (
|
||||
showNotification(
|
||||
options: IShowNotificationOption = {
|
||||
title: '',
|
||||
body: ''
|
||||
@@ -135,7 +144,7 @@ class GuiApi implements IGuiApi {
|
||||
notification.show()
|
||||
}
|
||||
|
||||
showMessageBox (
|
||||
showMessageBox(
|
||||
options: IShowMessageBoxOption = {
|
||||
title: '',
|
||||
message: '',
|
||||
@@ -159,7 +168,7 @@ class GuiApi implements IGuiApi {
|
||||
/**
|
||||
* get picgo config/data path
|
||||
*/
|
||||
async getConfigPath () {
|
||||
async getConfigPath() {
|
||||
const currentConfigPath = dbPathChecker()
|
||||
const galleryDBPath = getGalleryDBPath().dbPath
|
||||
return {
|
||||
@@ -169,12 +178,12 @@ class GuiApi implements IGuiApi {
|
||||
}
|
||||
}
|
||||
|
||||
get galleryDB (): DBStore {
|
||||
get galleryDB(): DBStore {
|
||||
return new Proxy<DBStore>(GalleryDB.getInstance(), {
|
||||
get (target, prop: keyof DBStore) {
|
||||
get(target, prop: keyof DBStore) {
|
||||
if (prop === 'overwrite') {
|
||||
return new Proxy(GalleryDB.getInstance().overwrite, {
|
||||
apply (target, ctx, args) {
|
||||
apply(target, ctx, args) {
|
||||
return new Promise(resolve => {
|
||||
const guiApi = GuiApi.getInstance()
|
||||
guiApi
|
||||
@@ -197,7 +206,7 @@ class GuiApi implements IGuiApi {
|
||||
}
|
||||
if (prop === 'removeById') {
|
||||
return new Proxy(GalleryDB.getInstance().removeById, {
|
||||
apply (target, ctx, args) {
|
||||
apply(target, ctx, args) {
|
||||
return new Promise(resolve => {
|
||||
const guiApi = GuiApi.getInstance()
|
||||
guiApi
|
||||
|
||||
@@ -17,7 +17,7 @@ import windowManager from 'apis/app/window/windowManager'
|
||||
import type { IFileWithPath } from '#/types/types'
|
||||
import { IWindowList } from '~/utils/enum'
|
||||
|
||||
function initEventCenter () {
|
||||
function initEventCenter() {
|
||||
const eventList: any = {
|
||||
'picgo:upload': uploadClipboardFiles,
|
||||
[UPLOAD_WITH_CLIPBOARD_FILES]: busCallUploadClipboardFiles,
|
||||
@@ -31,31 +31,31 @@ function initEventCenter () {
|
||||
}
|
||||
}
|
||||
|
||||
async function busCallUploadClipboardFiles () {
|
||||
async function busCallUploadClipboardFiles() {
|
||||
const result = await uploadClipboardFiles()
|
||||
const imgUrl = result.url
|
||||
bus.emit(UPLOAD_WITH_CLIPBOARD_FILES_RESPONSE, imgUrl)
|
||||
}
|
||||
|
||||
async function busCallUploadFiles (pathList: IFileWithPath[]) {
|
||||
async function busCallUploadFiles(pathList: IFileWithPath[]) {
|
||||
const win = windowManager.getAvailableWindow()
|
||||
const result = await uploadChoosedFiles(win.webContents, pathList)
|
||||
const urls = result.map((item: any) => item.url)
|
||||
bus.emit(UPLOAD_WITH_FILES_RESPONSE, urls)
|
||||
}
|
||||
|
||||
function busCallGetWindowId () {
|
||||
function busCallGetWindowId() {
|
||||
const win = windowManager.getAvailableWindow()
|
||||
bus.emit(GET_WINDOW_ID_REPONSE, win.id)
|
||||
}
|
||||
|
||||
function busCallGetSettingWindowId () {
|
||||
function busCallGetSettingWindowId() {
|
||||
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
bus.emit(GET_SETTING_WINDOW_ID_RESPONSE, settingWindow.id)
|
||||
}
|
||||
|
||||
export default {
|
||||
listen () {
|
||||
listen() {
|
||||
initEventCenter()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
export const SHOW_INPUT_BOX = 'SHOW_INPUT_BOX'
|
||||
export const SHOW_INPUT_BOX_RESPONSE = 'SHOW_INPUT_BOX_RESPONSE'
|
||||
export const TOGGLE_SHORTKEY_MODIFIED_MODE = 'TOGGLE_SHORTKEY_MODIFIED_MODE'
|
||||
// picgo plugin
|
||||
export const PICGO_CONFIG_PLUGIN = 'PICGO_CONFIG_PLUGIN'
|
||||
export const PICGO_HANDLE_PLUGIN_ING = 'PICGO_HANDLE_PLUGIN_ING'
|
||||
export const PICGO_HANDLE_PLUGIN_DONE = 'PICGO_HANDLE_PLUGIN_DONE'
|
||||
export const PICGO_TOGGLE_PLUGIN = 'PICGO_TOGGLE_PLUGIN'
|
||||
// picgo uploader
|
||||
export const RENAME_FILE_NAME = 'RENAME_FILE_NAME'
|
||||
export const GET_RENAME_FILE_NAME = 'GET_RENAME_FILE_NAME'
|
||||
export const SHOW_MAIN_PAGE_QRCODE = 'SHOW_MAIN_PAGE_QRCODE'
|
||||
// rpc
|
||||
export const RPC_ACTIONS = 'RPC_ACTIONS'
|
||||
export const RPC_ACTIONS_INVOKE = 'RPC_ACTIONS_INVOKE'
|
||||
export const SHOW_INPUT_BOX = 'SHOW_INPUT_BOX'
|
||||
export const SHOW_INPUT_BOX_RESPONSE = 'SHOW_INPUT_BOX_RESPONSE'
|
||||
export const TOGGLE_SHORTKEY_MODIFIED_MODE = 'TOGGLE_SHORTKEY_MODIFIED_MODE'
|
||||
// picgo plugin
|
||||
export const PICGO_CONFIG_PLUGIN = 'PICGO_CONFIG_PLUGIN'
|
||||
export const PICGO_HANDLE_PLUGIN_ING = 'PICGO_HANDLE_PLUGIN_ING'
|
||||
export const PICGO_HANDLE_PLUGIN_DONE = 'PICGO_HANDLE_PLUGIN_DONE'
|
||||
export const PICGO_TOGGLE_PLUGIN = 'PICGO_TOGGLE_PLUGIN'
|
||||
// picgo uploader
|
||||
export const RENAME_FILE_NAME = 'RENAME_FILE_NAME'
|
||||
export const GET_RENAME_FILE_NAME = 'GET_RENAME_FILE_NAME'
|
||||
export const SHOW_MAIN_PAGE_QRCODE = 'SHOW_MAIN_PAGE_QRCODE'
|
||||
// rpc
|
||||
export const RPC_ACTIONS = 'RPC_ACTIONS'
|
||||
export const RPC_ACTIONS_INVOKE = 'RPC_ACTIONS_INVOKE'
|
||||
|
||||
@@ -46,19 +46,19 @@ const buildMiniPageMenu = () => {
|
||||
},
|
||||
{
|
||||
label: $t('UPLOAD_BY_CLIPBOARD'),
|
||||
click () {
|
||||
click() {
|
||||
uploadClipboardFiles()
|
||||
}
|
||||
},
|
||||
{
|
||||
label: $t('HIDE_MINI_WINDOW'),
|
||||
click () {
|
||||
click() {
|
||||
BrowserWindow.getFocusedWindow()!.hide()
|
||||
}
|
||||
},
|
||||
{
|
||||
label: $t('START_WATCH_CLIPBOARD'),
|
||||
click () {
|
||||
click() {
|
||||
db.set(configPaths.settings.isListeningClipboard, true)
|
||||
ClipboardWatcher.startListening()
|
||||
ClipboardWatcher.on('change', () => {
|
||||
@@ -71,7 +71,7 @@ const buildMiniPageMenu = () => {
|
||||
},
|
||||
{
|
||||
label: $t('STOP_WATCH_CLIPBOARD'),
|
||||
click () {
|
||||
click() {
|
||||
db.set(configPaths.settings.isListeningClipboard, false)
|
||||
ClipboardWatcher.stopListening()
|
||||
ClipboardWatcher.removeAllListeners()
|
||||
@@ -81,7 +81,7 @@ const buildMiniPageMenu = () => {
|
||||
},
|
||||
{
|
||||
label: $t('RELOAD_APP'),
|
||||
click () {
|
||||
click() {
|
||||
app.relaunch()
|
||||
app.exit(0)
|
||||
}
|
||||
@@ -98,7 +98,7 @@ const buildMainPageMenu = (win: BrowserWindow) => {
|
||||
const template = [
|
||||
{
|
||||
label: $t('ABOUT'),
|
||||
click () {
|
||||
click() {
|
||||
dialog.showMessageBox({
|
||||
title: 'PicList',
|
||||
message: 'PicList',
|
||||
@@ -108,26 +108,26 @@ const buildMainPageMenu = (win: BrowserWindow) => {
|
||||
},
|
||||
{
|
||||
label: $t('SHOW_PICBED_QRCODE'),
|
||||
click () {
|
||||
click() {
|
||||
win?.webContents?.send(SHOW_MAIN_PAGE_QRCODE)
|
||||
}
|
||||
},
|
||||
{
|
||||
label: $t('OPEN_TOOLBOX'),
|
||||
click () {
|
||||
click() {
|
||||
const window = windowManager.create(IWindowList.TOOLBOX_WINDOW)
|
||||
window?.show()
|
||||
}
|
||||
},
|
||||
{
|
||||
label: $t('SHOW_DEVTOOLS'),
|
||||
click () {
|
||||
click() {
|
||||
win?.webContents?.openDevTools({ mode: 'detach' })
|
||||
}
|
||||
},
|
||||
{
|
||||
label: $t('FEEDBACK'),
|
||||
click () {
|
||||
click() {
|
||||
const url = 'https://github.com/Kuingsmile/PicList/issues'
|
||||
shell.openExternal(url)
|
||||
}
|
||||
@@ -176,10 +176,10 @@ const buildSecondPicBedMenu = () => {
|
||||
: undefined,
|
||||
click: !hasSubmenu
|
||||
? function () {
|
||||
picgo.saveConfig({
|
||||
[configPaths.picBed.secondUploader]: item.type
|
||||
})
|
||||
}
|
||||
picgo.saveConfig({
|
||||
[configPaths.picBed.secondUploader]: item.type
|
||||
})
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
})
|
||||
@@ -233,15 +233,15 @@ const buildPicBedListMenu = () => {
|
||||
: undefined,
|
||||
click: !hasSubmenu
|
||||
? function () {
|
||||
picgo.saveConfig({
|
||||
[configPaths.picBed.current]: item.type,
|
||||
[configPaths.picBed.uploader]: item.type
|
||||
})
|
||||
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('syncPicBed')
|
||||
picgo.saveConfig({
|
||||
[configPaths.picBed.current]: item.type,
|
||||
[configPaths.picBed.uploader]: item.type
|
||||
})
|
||||
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('syncPicBed')
|
||||
}
|
||||
setTrayToolTip(item.type)
|
||||
}
|
||||
setTrayToolTip(item.type)
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
})
|
||||
@@ -278,7 +278,7 @@ const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
|
||||
{
|
||||
label: $t('ENABLE_PLUGIN'),
|
||||
enabled: !plugin.enabled,
|
||||
click () {
|
||||
click() {
|
||||
picgo.saveConfig({
|
||||
[`picgoPlugins.${plugin.fullName}`]: true
|
||||
})
|
||||
@@ -289,7 +289,7 @@ const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
|
||||
{
|
||||
label: $t('DISABLE_PLUGIN'),
|
||||
enabled: plugin.enabled,
|
||||
click () {
|
||||
click() {
|
||||
picgo.saveConfig({
|
||||
[`picgoPlugins.${plugin.fullName}`]: false
|
||||
})
|
||||
@@ -307,7 +307,7 @@ const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
|
||||
},
|
||||
{
|
||||
label: $t('UNINSTALL_PLUGIN'),
|
||||
click () {
|
||||
click() {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
window.webContents.send(PICGO_HANDLE_PLUGIN_ING, plugin.fullName)
|
||||
handlePluginUninstall(plugin.fullName)
|
||||
@@ -315,7 +315,7 @@ const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
|
||||
},
|
||||
{
|
||||
label: $t('UPDATE_PLUGIN'),
|
||||
click () {
|
||||
click() {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
window.webContents.send(PICGO_HANDLE_PLUGIN_ING, plugin.fullName)
|
||||
handlePluginUpdate(plugin.fullName)
|
||||
@@ -328,7 +328,7 @@ const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
|
||||
label: $t('CONFIG_THING', {
|
||||
c: `${i} - ${plugin.config[i].fullName || plugin.config[i].name}`
|
||||
}),
|
||||
click () {
|
||||
click() {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
const currentType = i
|
||||
const configName = plugin.config[i].fullName || plugin.config[i].name
|
||||
@@ -346,7 +346,7 @@ const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
|
||||
const pluginTransformer = plugin.config.transformer.name
|
||||
const obj = {
|
||||
label: `${currentTransformer === pluginTransformer ? $t('DISABLE') : $t('ENABLE')}transformer - ${plugin.config.transformer.name}`,
|
||||
click () {
|
||||
click() {
|
||||
const transformer = plugin.config.transformer.name
|
||||
const currentTransformer = picgo.getConfig<string>(configPaths.picBed.transformer) || 'path'
|
||||
if (currentTransformer === transformer) {
|
||||
@@ -371,7 +371,7 @@ const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
|
||||
for (const i of plugin.guiMenu) {
|
||||
menu.push({
|
||||
label: i.label,
|
||||
async click () {
|
||||
async click() {
|
||||
const picgPlugin = await picgo.pluginLoader.getPlugin(plugin.fullName)
|
||||
if (picgPlugin?.guiMenu?.(picgo)?.length) {
|
||||
const menu: GuiMenuItem[] = picgPlugin.guiMenu(picgo)
|
||||
|
||||
@@ -37,12 +37,12 @@ class RPCServer implements IRPCServer {
|
||||
}
|
||||
}
|
||||
|
||||
start () {
|
||||
start() {
|
||||
ipcMain.on(RPC_ACTIONS, this.rpcEventHandler)
|
||||
ipcMain.handle(RPC_ACTIONS_INVOKE, this.rpcEventHandlerWithResponse)
|
||||
}
|
||||
|
||||
use (routes: IRPCRoutes) {
|
||||
use(routes: IRPCRoutes) {
|
||||
for (const [action, route] of routes) {
|
||||
if (route.type === IRPCType.SEND) {
|
||||
this.routes.set(action, route)
|
||||
@@ -52,7 +52,7 @@ class RPCServer implements IRPCServer {
|
||||
}
|
||||
}
|
||||
|
||||
stop () {
|
||||
stop() {
|
||||
ipcMain.off(RPC_ACTIONS, this.rpcEventHandler)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ export class RPCRouter implements IRPCRouter {
|
||||
return this
|
||||
}
|
||||
|
||||
routes () {
|
||||
routes() {
|
||||
return this.routeMap
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import ALLApi from 'apis/delete/allApi'
|
||||
|
||||
import type { IIPCEvent } from '#/types/rpc'
|
||||
import type { ImgInfo } from '#/types/types'
|
||||
import { IRPCActionType, IRPCType } from '~/utils/enum'
|
||||
|
||||
export default [
|
||||
{
|
||||
action: IRPCActionType.DELETE_ALL_API,
|
||||
handler: async (_: IIPCEvent, args:[item: ImgInfo]) => {
|
||||
return await ALLApi.delete(args[0])
|
||||
},
|
||||
type: IRPCType.INVOKE
|
||||
}
|
||||
]
|
||||
import ALLApi from 'apis/delete/allApi'
|
||||
|
||||
import type { IIPCEvent } from '#/types/rpc'
|
||||
import type { ImgInfo } from '#/types/types'
|
||||
import { IRPCActionType, IRPCType } from '~/utils/enum'
|
||||
|
||||
export default [
|
||||
{
|
||||
action: IRPCActionType.DELETE_ALL_API,
|
||||
handler: async (_: IIPCEvent, args: [item: ImgInfo]) => {
|
||||
return await ALLApi.delete(args[0])
|
||||
},
|
||||
type: IRPCType.INVOKE
|
||||
}
|
||||
]
|
||||
|
||||
@@ -85,11 +85,15 @@ const getPluginList = async (): Promise<IPicGoPlugin[]> => {
|
||||
},
|
||||
uploader: {
|
||||
name: uploaderName,
|
||||
config: handleConfigWithFunction(getConfig(uploaderName, IPicGoHelperType.uploader as keyof typeof IPicGoHelperType, picgo))
|
||||
config: handleConfigWithFunction(
|
||||
getConfig(uploaderName, IPicGoHelperType.uploader as keyof typeof IPicGoHelperType, picgo)
|
||||
)
|
||||
},
|
||||
transformer: {
|
||||
name: transformerName,
|
||||
config: handleConfigWithFunction(getConfig(uploaderName, IPicGoHelperType.transformer as keyof typeof IPicGoHelperType, picgo))
|
||||
config: handleConfigWithFunction(
|
||||
getConfig(uploaderName, IPicGoHelperType.transformer as keyof typeof IPicGoHelperType, picgo)
|
||||
)
|
||||
}
|
||||
},
|
||||
enabled: picgo.getConfig(`picgoPlugins.${pluginList[i]}`),
|
||||
|
||||
@@ -10,9 +10,7 @@ import { sendToolboxResWithType } from '~/events/rpc/routes/toolbox/utils'
|
||||
import { T as $t } from '~/i18n'
|
||||
import { IToolboxItemCheckStatus, IToolboxItemType } from '~/utils/enum'
|
||||
|
||||
export const checkFileMap: IToolboxCheckerMap<
|
||||
string
|
||||
> = {
|
||||
export const checkFileMap: IToolboxCheckerMap<string> = {
|
||||
[IToolboxItemType.IS_CONFIG_FILE_BROKEN]: async (event: IpcMainEvent) => {
|
||||
const sendToolboxRes = sendToolboxResWithType(IToolboxItemType.IS_CONFIG_FILE_BROKEN)
|
||||
sendToolboxRes(event, {
|
||||
@@ -62,9 +60,7 @@ export const checkFileMap: IToolboxCheckerMap<
|
||||
}
|
||||
}
|
||||
|
||||
export const fixFileMap: IToolboxFixMap<
|
||||
string
|
||||
> = {
|
||||
export const fixFileMap: IToolboxFixMap<string> = {
|
||||
[IToolboxItemType.IS_CONFIG_FILE_BROKEN]: async () => {
|
||||
try {
|
||||
fs.unlinkSync(dbPathChecker())
|
||||
|
||||
@@ -1,89 +1,89 @@
|
||||
import { dbPathChecker } from '@core/datastore/dbChecker'
|
||||
import axios, { AxiosRequestConfig } from 'axios'
|
||||
import fs from 'fs-extra'
|
||||
import { IConfig } from 'piclist'
|
||||
import tunnel from 'tunnel'
|
||||
|
||||
import type { IToolboxCheckerMap } from '#/types/rpc'
|
||||
import { sendToolboxResWithType } from '~/events/rpc/routes/toolbox/utils'
|
||||
import { T as $t } from '~/i18n'
|
||||
import { IToolboxItemCheckStatus, IToolboxItemType } from '~/utils/enum'
|
||||
|
||||
function getProxy (proxyStr: string): AxiosRequestConfig['proxy'] | null {
|
||||
if (proxyStr) {
|
||||
try {
|
||||
const proxyOptions = new URL(proxyStr)
|
||||
return {
|
||||
host: proxyOptions.hostname,
|
||||
port: parseInt(proxyOptions.port || '0', 10),
|
||||
protocol: proxyOptions.protocol
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const sendToolboxRes = sendToolboxResWithType(IToolboxItemType.HAS_PROBLEM_WITH_PROXY)
|
||||
|
||||
export const checkProxyMap: IToolboxCheckerMap<string> = {
|
||||
[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) {
|
||||
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')
|
||||
})
|
||||
}
|
||||
}
|
||||
import { dbPathChecker } from '@core/datastore/dbChecker'
|
||||
import axios, { AxiosRequestConfig } from 'axios'
|
||||
import fs from 'fs-extra'
|
||||
import { IConfig } from 'piclist'
|
||||
import tunnel from 'tunnel'
|
||||
|
||||
import type { IToolboxCheckerMap } from '#/types/rpc'
|
||||
import { sendToolboxResWithType } from '~/events/rpc/routes/toolbox/utils'
|
||||
import { T as $t } from '~/i18n'
|
||||
import { IToolboxItemCheckStatus, IToolboxItemType } from '~/utils/enum'
|
||||
|
||||
function getProxy(proxyStr: string): AxiosRequestConfig['proxy'] | null {
|
||||
if (proxyStr) {
|
||||
try {
|
||||
const proxyOptions = new URL(proxyStr)
|
||||
return {
|
||||
host: proxyOptions.hostname,
|
||||
port: parseInt(proxyOptions.port || '0', 10),
|
||||
protocol: proxyOptions.protocol
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const sendToolboxRes = sendToolboxResWithType(IToolboxItemType.HAS_PROBLEM_WITH_PROXY)
|
||||
|
||||
export const checkProxyMap: IToolboxCheckerMap<string> = {
|
||||
[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) {
|
||||
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')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { IpcMainEvent } from 'electron'
|
||||
import type { IToolboxCheckRes } from '#/types/rpc'
|
||||
import { IRPCActionType } from '~/utils/enum'
|
||||
|
||||
export function sendToolboxResWithType (type: string) {
|
||||
export function sendToolboxResWithType(type: string) {
|
||||
return (event: IpcMainEvent, res?: Omit<IToolboxCheckRes, 'type'>) => {
|
||||
return event.sender.send(IRPCActionType.TOOLBOX_CHECK_RES, {
|
||||
...res,
|
||||
|
||||
@@ -12,7 +12,7 @@ const serverPort = 36699
|
||||
|
||||
let server: http.Server
|
||||
|
||||
export function startFileServer () {
|
||||
export function startFileServer() {
|
||||
server = http.createServer((req, res) => {
|
||||
const requestPath = req.url?.split('?')[0]
|
||||
const filePath = path.join(imgFilePath, decodeURIComponent(requestPath as string))
|
||||
@@ -36,7 +36,7 @@ export function startFileServer () {
|
||||
})
|
||||
}
|
||||
|
||||
export function stopFileServer () {
|
||||
export function stopFileServer() {
|
||||
server.close(() => {
|
||||
logger.info('File server is stopped')
|
||||
})
|
||||
|
||||
@@ -33,18 +33,18 @@ class I18nManager {
|
||||
readonly defaultLanguage: string = 'zh-CN'
|
||||
private i18nFileList: II18nItem[] = builtinI18nList
|
||||
|
||||
setOutterI18nFolder (folder: string) {
|
||||
setOutterI18nFolder(folder: string) {
|
||||
this.outterI18nFolder = folder
|
||||
}
|
||||
|
||||
addI18nFile (file: string, label: string) {
|
||||
addI18nFile(file: string, label: string) {
|
||||
this.i18nFileList.push({
|
||||
label,
|
||||
value: file
|
||||
})
|
||||
}
|
||||
|
||||
private getLocales (lang: string): ILocales {
|
||||
private getLocales(lang: string): ILocales {
|
||||
if (this.localesMap.has(lang)) {
|
||||
return this.localesMap.get(lang)!
|
||||
}
|
||||
@@ -71,13 +71,13 @@ class I18nManager {
|
||||
}
|
||||
}
|
||||
|
||||
setCurrentLanguage (lang: string) {
|
||||
setCurrentLanguage(lang: string) {
|
||||
const locales = this.getLocales(lang)
|
||||
this.currentLanguage = lang
|
||||
this.initI18n(lang, locales)
|
||||
}
|
||||
|
||||
private initI18n (lang: string = this.defaultLanguage, locales: ILocales) {
|
||||
private initI18n(lang: string = this.defaultLanguage, locales: ILocales) {
|
||||
const objectAdapter = new ObjectAdapter({
|
||||
[lang]: locales
|
||||
})
|
||||
@@ -87,15 +87,15 @@ class I18nManager {
|
||||
})
|
||||
}
|
||||
|
||||
T (key: ILocalesKey, args: IStringKeyMap = {}): string {
|
||||
T(key: ILocalesKey, args: IStringKeyMap = {}): string {
|
||||
return this.i18n?.translate(key, args) || key
|
||||
}
|
||||
|
||||
get languageList () {
|
||||
get languageList() {
|
||||
return this.i18nFileList
|
||||
}
|
||||
|
||||
getCurrentLocales () {
|
||||
getCurrentLocales() {
|
||||
return {
|
||||
lang: this.currentLanguage,
|
||||
locales: this.getLocales(this.currentLanguage)
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import { lifeCycle } from '~/lifeCycle'
|
||||
|
||||
lifeCycle.launchApp()
|
||||
import { lifeCycle } from '~/lifeCycle'
|
||||
|
||||
lifeCycle.launchApp()
|
||||
|
||||
@@ -24,9 +24,9 @@ process.on('unhandledRejection', (error: any) => {
|
||||
})
|
||||
|
||||
// acconrding to https://github.com/Molunerfinn/PicGo/commit/7363be798cfef11e980934e542817ff1d6c04389#diff-896d0db4fbd446798fbffec14d456b4cd98d4c72c46856c770a585fa7ab0926f
|
||||
function bootstrapEPIPESuppression () {
|
||||
function bootstrapEPIPESuppression() {
|
||||
let suppressing = false
|
||||
function logEPIPEErrorOnce () {
|
||||
function logEPIPEErrorOnce() {
|
||||
if (suppressing) {
|
||||
return
|
||||
}
|
||||
@@ -41,11 +41,11 @@ function bootstrapEPIPESuppression () {
|
||||
|
||||
bootstrapEPIPESuppression()
|
||||
|
||||
function epipeBomb (stream: any, callback: any) {
|
||||
function epipeBomb(stream: any, callback: any) {
|
||||
if (stream == null) stream = process.stdout
|
||||
if (callback == null) callback = process.exit
|
||||
|
||||
function epipeFilter (err: any) {
|
||||
function epipeFilter(err: any) {
|
||||
if (err.code === 'EPIPE') return callback()
|
||||
|
||||
// If there's more than one error handler (ie, us),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { shellPathSync } from 'shell-path'
|
||||
|
||||
export default function fixPath () {
|
||||
export default function fixPath() {
|
||||
if (process.platform === 'win32') {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,354 +1,360 @@
|
||||
import '~/lifeCycle/errorHandler'
|
||||
|
||||
import path from 'node:path'
|
||||
|
||||
import bus from '@core/bus'
|
||||
import db from '@core/datastore'
|
||||
import picgo from '@core/picgo'
|
||||
import logger from '@core/picgo/logger'
|
||||
import { remoteNoticeHandler } from 'apis/app/remoteNotice'
|
||||
import shortKeyHandler from 'apis/app/shortKey/shortKeyHandler'
|
||||
import { createTray, setDockMenu } from 'apis/app/system'
|
||||
import { uploadChoosedFiles, uploadClipboardFiles } from 'apis/app/uploader/apis'
|
||||
import windowManager from 'apis/app/window/windowManager'
|
||||
import axios from 'axios'
|
||||
import { app, dialog, globalShortcut, Notification, protocol, screen, shell } from 'electron'
|
||||
import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
|
||||
import updater from 'electron-updater'
|
||||
import fs from 'fs-extra'
|
||||
|
||||
import busEventList from '~/events/busEventList'
|
||||
import { rpcServer } from '~/events/rpc'
|
||||
import { startFileServer, stopFileServer } from '~/fileServer'
|
||||
import { T as $t } from '~/i18n'
|
||||
import fixPath from '~/lifeCycle/fixPath'
|
||||
import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
|
||||
import getManageApi from '~/manage/Main'
|
||||
import { clearTempFolder } from '~/manage/utils/common'
|
||||
import server from '~/server/index'
|
||||
import webServer from '~/server/webServer'
|
||||
import beforeOpen from '~/utils/beforeOpen'
|
||||
import clipboardPoll from '~/utils/clipboardPoll'
|
||||
import { configPaths } from '~/utils/configPaths'
|
||||
import { II18nLanguage, IRemoteNoticeTriggerHook, ISartMode, IWindowList } from '~/utils/enum'
|
||||
import { getUploadFiles } from '~/utils/handleArgv'
|
||||
import { initI18n } from '~/utils/handleI18n'
|
||||
import { notificationList } from '~/utils/notification'
|
||||
import { MemoryMonitor } from '~/utils/performanceOptimizer'
|
||||
import { CLIPBOARD_IMAGE_FOLDER } from '~/utils/static'
|
||||
import updateChecker from '~/utils/updateChecker'
|
||||
|
||||
const isDevelopment = process.env.NODE_ENV !== 'production'
|
||||
|
||||
const handleStartUpFiles = (argv: string[], cwd: string) => {
|
||||
const files = getUploadFiles(argv, cwd, logger)
|
||||
|
||||
if (files === null) {
|
||||
logger.info('cli -> uploading file from clipboard')
|
||||
uploadClipboardFiles()
|
||||
return true
|
||||
}
|
||||
|
||||
if (files.length > 0) {
|
||||
logger.info('cli -> uploading files from cli', ...files.map(file => file.path))
|
||||
const win = windowManager.getAvailableWindow()
|
||||
uploadChoosedFiles(win.webContents, files)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
updater.autoUpdater.setFeedURL({
|
||||
provider: 'generic',
|
||||
url: 'https://release.piclist.cn/latest',
|
||||
channel: 'latest'
|
||||
})
|
||||
|
||||
updater.autoUpdater.autoDownload = false
|
||||
|
||||
updater.autoUpdater.on('update-available', async (info: updater.UpdateInfo) => {
|
||||
const lang = db.get(configPaths.settings.language) || II18nLanguage.ZH_CN
|
||||
let updateLog = ''
|
||||
try {
|
||||
const url =
|
||||
lang === II18nLanguage.ZH_CN
|
||||
? 'https://release.piclist.cn/currentVersion.md'
|
||||
: 'https://release.piclist.cn/currentVersion_en.md'
|
||||
const res = await axios.get(url)
|
||||
updateLog = res.data
|
||||
} catch (e: any) {
|
||||
logger.error(e)
|
||||
}
|
||||
|
||||
const maxLogLength = 800
|
||||
let displayLog = updateLog
|
||||
let truncatedNote = ''
|
||||
|
||||
if (updateLog.length > maxLogLength) {
|
||||
const truncatePoint = updateLog.lastIndexOf('\n', maxLogLength)
|
||||
displayLog = updateLog.substring(0, truncatePoint > 0 ? truncatePoint : maxLogLength)
|
||||
truncatedNote =
|
||||
lang === II18nLanguage.ZH_CN
|
||||
? '\n\n... (更多详情请查看完整更新日志)'
|
||||
: '\n\n... (See full changelog for more details)'
|
||||
}
|
||||
|
||||
dialog
|
||||
.showMessageBox({
|
||||
type: 'info',
|
||||
title: $t('FIND_NEW_VERSION'),
|
||||
buttons: ['Yes', 'Go to download page'],
|
||||
message:
|
||||
$t('TIPS_FIND_NEW_VERSION', {
|
||||
v: info.version
|
||||
}) +
|
||||
'\n\n' +
|
||||
displayLog +
|
||||
truncatedNote,
|
||||
checkboxLabel: $t('NO_MORE_NOTICE'),
|
||||
checkboxChecked: false
|
||||
})
|
||||
.then(result => {
|
||||
if (result.response === 0) {
|
||||
updater.autoUpdater.downloadUpdate()
|
||||
} else {
|
||||
shell.openExternal('https://github.com/Kuingsmile/PicList/releases/latest')
|
||||
}
|
||||
db.set(configPaths.settings.showUpdateTip, !result.checkboxChecked)
|
||||
})
|
||||
.catch(err => {
|
||||
logger.error(err)
|
||||
})
|
||||
})
|
||||
|
||||
updater.autoUpdater.on('download-progress', progressObj => {
|
||||
const percent = {
|
||||
progress: progressObj.percent
|
||||
}
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
window.webContents.send('updateProgress', percent)
|
||||
})
|
||||
|
||||
updater.autoUpdater.on('update-downloaded', () => {
|
||||
dialog
|
||||
.showMessageBox({
|
||||
type: 'info',
|
||||
title: $t('UPDATE_DOWNLOADED'),
|
||||
buttons: ['Yes', 'No'],
|
||||
message: $t('TIPS_UPDATE_DOWNLOADED')
|
||||
})
|
||||
.then(result => {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
window.webContents.send('updateProgress', { progress: 100 })
|
||||
if (result.response === 0) {
|
||||
updater.autoUpdater.quitAndInstall()
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
logger.error(err)
|
||||
})
|
||||
})
|
||||
|
||||
updater.autoUpdater.on('error', err => {
|
||||
logger.error(err)
|
||||
})
|
||||
|
||||
class LifeCycle {
|
||||
async #beforeReady () {
|
||||
protocol.registerSchemesAsPrivileged([{ scheme: 'picgo', privileges: { secure: true, standard: true } }])
|
||||
// fix the $PATH in macOS & linux
|
||||
fixPath()
|
||||
beforeOpen()
|
||||
getManageApi()
|
||||
UpDownTaskQueue.getInstance()
|
||||
initI18n()
|
||||
rpcServer.start()
|
||||
busEventList.listen()
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
MemoryMonitor.start(30000)
|
||||
}
|
||||
}
|
||||
|
||||
#onReady () {
|
||||
const readyFunction = async () => {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
installExtension(VUEJS_DEVTOOLS).catch(err => {
|
||||
logger.error('An error occurred: ', err)
|
||||
})
|
||||
}
|
||||
windowManager.create(IWindowList.TRAY_WINDOW)
|
||||
windowManager.create(IWindowList.SETTING_WINDOW)
|
||||
const isAutoListenClipboard = db.get(configPaths.settings.isAutoListenClipboard) || false
|
||||
const ClipboardWatcher = clipboardPoll
|
||||
if (isAutoListenClipboard) {
|
||||
db.set(configPaths.settings.isListeningClipboard, true)
|
||||
ClipboardWatcher.startListening()
|
||||
ClipboardWatcher.on('change', () => {
|
||||
picgo.log.info('clipboard changed')
|
||||
uploadClipboardFiles()
|
||||
})
|
||||
} else {
|
||||
db.set(configPaths.settings.isListeningClipboard, false)
|
||||
}
|
||||
const isHideDock = db.get(configPaths.settings.isHideDock) || false
|
||||
let startMode = db.get(configPaths.settings.startMode) || ISartMode.QUIET
|
||||
if (process.platform === 'darwin' && startMode === ISartMode.MINI) {
|
||||
startMode = ISartMode.QUIET
|
||||
}
|
||||
const currentPicBed = db.get(configPaths.picBed.uploader) || db.get(configPaths.picBed.current) || 'smms'
|
||||
const currentPicBedConfig = db.get(`picBed.${currentPicBed}`)?._configName || 'Default'
|
||||
const tooltip = `${currentPicBed} ${currentPicBedConfig}`
|
||||
if (process.platform === 'darwin') {
|
||||
isHideDock ? app.dock?.hide() : setDockMenu()
|
||||
startMode !== ISartMode.NO_TRAY && createTray(tooltip)
|
||||
} else {
|
||||
createTray(tooltip)
|
||||
}
|
||||
db.set(configPaths.needReload, false)
|
||||
updateChecker()
|
||||
// 不需要阻塞
|
||||
process.nextTick(() => {
|
||||
shortKeyHandler.init()
|
||||
})
|
||||
server.startup()
|
||||
webServer.start()
|
||||
startFileServer()
|
||||
if (process.env.NODE_ENV !== 'development') {
|
||||
handleStartUpFiles(process.argv, process.cwd())
|
||||
}
|
||||
|
||||
if (notificationList && notificationList.length > 0) {
|
||||
while (notificationList.length) {
|
||||
const option = notificationList.pop()
|
||||
const notice = new Notification(option!)
|
||||
notice.show()
|
||||
}
|
||||
}
|
||||
await remoteNoticeHandler.init()
|
||||
remoteNoticeHandler.triggerHook(IRemoteNoticeTriggerHook.APP_START)
|
||||
if (startMode === ISartMode.MINI && process.platform !== 'darwin') {
|
||||
windowManager.create(IWindowList.MINI_WINDOW)
|
||||
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) {
|
||||
if (lastPosition[0] < 0 || lastPosition[0] > width || lastPosition[1] < 0 || lastPosition[1] > height) {
|
||||
miniWindow.setPosition(width - 100, height - 100)
|
||||
db.set(configPaths.settings.miniWindowPosition, [width - 100, height - 100])
|
||||
} else if (lastPosition[0] + miniWindow.getSize()[0] > width || lastPosition[1] + miniWindow.getSize()[1] > height) {
|
||||
miniWindow.setPosition(width - miniWindow.getSize()[0], height - miniWindow.getSize()[1])
|
||||
db.set(configPaths.settings.miniWindowPosition, [width - miniWindow.getSize()[0], height - miniWindow.getSize()[1]])
|
||||
} else {
|
||||
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()
|
||||
} else if (startMode === ISartMode.MAIN) {
|
||||
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
settingWindow.show()
|
||||
settingWindow.focus()
|
||||
}
|
||||
const clipboardDir = path.join(picgo.baseDir, CLIPBOARD_IMAGE_FOLDER)
|
||||
fs.emptyDir(clipboardDir)
|
||||
}
|
||||
app.whenReady().then(readyFunction)
|
||||
}
|
||||
|
||||
#onRunning () {
|
||||
app.on('second-instance', (_, commandLine, workingDirectory) => {
|
||||
logger.info('detect second instance')
|
||||
const result = handleStartUpFiles(commandLine, workingDirectory)
|
||||
if (!result) {
|
||||
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
|
||||
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
if (settingWindow.isMinimized()) {
|
||||
settingWindow.restore()
|
||||
}
|
||||
settingWindow.focus()
|
||||
}
|
||||
}
|
||||
})
|
||||
app.on('activate', () => {
|
||||
if (!windowManager.has(IWindowList.TRAY_WINDOW)) {
|
||||
windowManager.create(IWindowList.TRAY_WINDOW)
|
||||
}
|
||||
if (!windowManager.has(IWindowList.SETTING_WINDOW)) {
|
||||
windowManager.create(IWindowList.SETTING_WINDOW)
|
||||
}
|
||||
})
|
||||
app.setLoginItemSettings({
|
||||
openAtLogin: db.get(configPaths.settings.autoStart) || false
|
||||
})
|
||||
if (process.platform === 'win32') {
|
||||
app.setAppUserModelId('com.kuingsmile.piclist')
|
||||
}
|
||||
|
||||
if (process.env.XDG_CURRENT_DESKTOP && process.env.XDG_CURRENT_DESKTOP.includes('Unity')) {
|
||||
process.env.XDG_CURRENT_DESKTOP = 'Unity'
|
||||
}
|
||||
}
|
||||
|
||||
#onQuit () {
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
|
||||
app.on('will-quit', () => {
|
||||
UpDownTaskQueue.getInstance().persist()
|
||||
clearTempFolder()
|
||||
globalShortcut.unregisterAll()
|
||||
bus.removeAllListeners()
|
||||
server.shutdown()
|
||||
webServer.stop()
|
||||
stopFileServer()
|
||||
MemoryMonitor.stop()
|
||||
})
|
||||
// Exit cleanly on request from parent process in development mode.
|
||||
if (isDevelopment) {
|
||||
if (process.platform === 'win32') {
|
||||
process.on('message', data => {
|
||||
if (data === 'graceful-exit') {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
process.on('SIGTERM', () => {
|
||||
app.quit()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async launchApp () {
|
||||
const gotTheLock = app.requestSingleInstanceLock()
|
||||
if (!gotTheLock) {
|
||||
app.quit()
|
||||
} else {
|
||||
await this.#beforeReady()
|
||||
this.#onReady()
|
||||
this.#onRunning()
|
||||
this.#onQuit()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const lifeCycle = new LifeCycle()
|
||||
|
||||
export { lifeCycle }
|
||||
import '~/lifeCycle/errorHandler'
|
||||
|
||||
import path from 'node:path'
|
||||
|
||||
import bus from '@core/bus'
|
||||
import db from '@core/datastore'
|
||||
import picgo from '@core/picgo'
|
||||
import logger from '@core/picgo/logger'
|
||||
import { remoteNoticeHandler } from 'apis/app/remoteNotice'
|
||||
import shortKeyHandler from 'apis/app/shortKey/shortKeyHandler'
|
||||
import { createTray, setDockMenu } from 'apis/app/system'
|
||||
import { uploadChoosedFiles, uploadClipboardFiles } from 'apis/app/uploader/apis'
|
||||
import windowManager from 'apis/app/window/windowManager'
|
||||
import axios from 'axios'
|
||||
import { app, dialog, globalShortcut, Notification, protocol, screen, shell } from 'electron'
|
||||
import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
|
||||
import updater from 'electron-updater'
|
||||
import fs from 'fs-extra'
|
||||
|
||||
import busEventList from '~/events/busEventList'
|
||||
import { rpcServer } from '~/events/rpc'
|
||||
import { startFileServer, stopFileServer } from '~/fileServer'
|
||||
import { T as $t } from '~/i18n'
|
||||
import fixPath from '~/lifeCycle/fixPath'
|
||||
import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
|
||||
import getManageApi from '~/manage/Main'
|
||||
import { clearTempFolder } from '~/manage/utils/common'
|
||||
import server from '~/server/index'
|
||||
import webServer from '~/server/webServer'
|
||||
import beforeOpen from '~/utils/beforeOpen'
|
||||
import clipboardPoll from '~/utils/clipboardPoll'
|
||||
import { configPaths } from '~/utils/configPaths'
|
||||
import { II18nLanguage, IRemoteNoticeTriggerHook, ISartMode, IWindowList } from '~/utils/enum'
|
||||
import { getUploadFiles } from '~/utils/handleArgv'
|
||||
import { initI18n } from '~/utils/handleI18n'
|
||||
import { notificationList } from '~/utils/notification'
|
||||
import { MemoryMonitor } from '~/utils/performanceOptimizer'
|
||||
import { CLIPBOARD_IMAGE_FOLDER } from '~/utils/static'
|
||||
import updateChecker from '~/utils/updateChecker'
|
||||
|
||||
const isDevelopment = process.env.NODE_ENV !== 'production'
|
||||
|
||||
const handleStartUpFiles = (argv: string[], cwd: string) => {
|
||||
const files = getUploadFiles(argv, cwd, logger)
|
||||
|
||||
if (files === null) {
|
||||
logger.info('cli -> uploading file from clipboard')
|
||||
uploadClipboardFiles()
|
||||
return true
|
||||
}
|
||||
|
||||
if (files.length > 0) {
|
||||
logger.info('cli -> uploading files from cli', ...files.map(file => file.path))
|
||||
const win = windowManager.getAvailableWindow()
|
||||
uploadChoosedFiles(win.webContents, files)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
updater.autoUpdater.setFeedURL({
|
||||
provider: 'generic',
|
||||
url: 'https://release.piclist.cn/latest',
|
||||
channel: 'latest'
|
||||
})
|
||||
|
||||
updater.autoUpdater.autoDownload = false
|
||||
|
||||
updater.autoUpdater.on('update-available', async (info: updater.UpdateInfo) => {
|
||||
const lang = db.get(configPaths.settings.language) || II18nLanguage.ZH_CN
|
||||
let updateLog = ''
|
||||
try {
|
||||
const url =
|
||||
lang === II18nLanguage.ZH_CN
|
||||
? 'https://release.piclist.cn/currentVersion.md'
|
||||
: 'https://release.piclist.cn/currentVersion_en.md'
|
||||
const res = await axios.get(url)
|
||||
updateLog = res.data
|
||||
} catch (e: any) {
|
||||
logger.error(e)
|
||||
}
|
||||
|
||||
const maxLogLength = 800
|
||||
let displayLog = updateLog
|
||||
let truncatedNote = ''
|
||||
|
||||
if (updateLog.length > maxLogLength) {
|
||||
const truncatePoint = updateLog.lastIndexOf('\n', maxLogLength)
|
||||
displayLog = updateLog.substring(0, truncatePoint > 0 ? truncatePoint : maxLogLength)
|
||||
truncatedNote =
|
||||
lang === II18nLanguage.ZH_CN
|
||||
? '\n\n... (更多详情请查看完整更新日志)'
|
||||
: '\n\n... (See full changelog for more details)'
|
||||
}
|
||||
|
||||
dialog
|
||||
.showMessageBox({
|
||||
type: 'info',
|
||||
title: $t('FIND_NEW_VERSION'),
|
||||
buttons: ['Yes', 'Go to download page'],
|
||||
message:
|
||||
$t('TIPS_FIND_NEW_VERSION', {
|
||||
v: info.version
|
||||
}) +
|
||||
'\n\n' +
|
||||
displayLog +
|
||||
truncatedNote,
|
||||
checkboxLabel: $t('NO_MORE_NOTICE'),
|
||||
checkboxChecked: false
|
||||
})
|
||||
.then(result => {
|
||||
if (result.response === 0) {
|
||||
updater.autoUpdater.downloadUpdate()
|
||||
} else {
|
||||
shell.openExternal('https://github.com/Kuingsmile/PicList/releases/latest')
|
||||
}
|
||||
db.set(configPaths.settings.showUpdateTip, !result.checkboxChecked)
|
||||
})
|
||||
.catch(err => {
|
||||
logger.error(err)
|
||||
})
|
||||
})
|
||||
|
||||
updater.autoUpdater.on('download-progress', progressObj => {
|
||||
const percent = {
|
||||
progress: progressObj.percent
|
||||
}
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
window.webContents.send('updateProgress', percent)
|
||||
})
|
||||
|
||||
updater.autoUpdater.on('update-downloaded', () => {
|
||||
dialog
|
||||
.showMessageBox({
|
||||
type: 'info',
|
||||
title: $t('UPDATE_DOWNLOADED'),
|
||||
buttons: ['Yes', 'No'],
|
||||
message: $t('TIPS_UPDATE_DOWNLOADED')
|
||||
})
|
||||
.then(result => {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
window.webContents.send('updateProgress', { progress: 100 })
|
||||
if (result.response === 0) {
|
||||
updater.autoUpdater.quitAndInstall()
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
logger.error(err)
|
||||
})
|
||||
})
|
||||
|
||||
updater.autoUpdater.on('error', err => {
|
||||
logger.error(err)
|
||||
})
|
||||
|
||||
class LifeCycle {
|
||||
async #beforeReady() {
|
||||
protocol.registerSchemesAsPrivileged([{ scheme: 'picgo', privileges: { secure: true, standard: true } }])
|
||||
// fix the $PATH in macOS & linux
|
||||
fixPath()
|
||||
beforeOpen()
|
||||
getManageApi()
|
||||
UpDownTaskQueue.getInstance()
|
||||
initI18n()
|
||||
rpcServer.start()
|
||||
busEventList.listen()
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
MemoryMonitor.start(30000)
|
||||
}
|
||||
}
|
||||
|
||||
#onReady() {
|
||||
const readyFunction = async () => {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
installExtension(VUEJS_DEVTOOLS).catch(err => {
|
||||
logger.error('An error occurred: ', err)
|
||||
})
|
||||
}
|
||||
windowManager.create(IWindowList.TRAY_WINDOW)
|
||||
windowManager.create(IWindowList.SETTING_WINDOW)
|
||||
const isAutoListenClipboard = db.get(configPaths.settings.isAutoListenClipboard) || false
|
||||
const ClipboardWatcher = clipboardPoll
|
||||
if (isAutoListenClipboard) {
|
||||
db.set(configPaths.settings.isListeningClipboard, true)
|
||||
ClipboardWatcher.startListening()
|
||||
ClipboardWatcher.on('change', () => {
|
||||
picgo.log.info('clipboard changed')
|
||||
uploadClipboardFiles()
|
||||
})
|
||||
} else {
|
||||
db.set(configPaths.settings.isListeningClipboard, false)
|
||||
}
|
||||
const isHideDock = db.get(configPaths.settings.isHideDock) || false
|
||||
let startMode = db.get(configPaths.settings.startMode) || ISartMode.QUIET
|
||||
if (process.platform === 'darwin' && startMode === ISartMode.MINI) {
|
||||
startMode = ISartMode.QUIET
|
||||
}
|
||||
const currentPicBed = db.get(configPaths.picBed.uploader) || db.get(configPaths.picBed.current) || 'smms'
|
||||
const currentPicBedConfig = db.get(`picBed.${currentPicBed}`)?._configName || 'Default'
|
||||
const tooltip = `${currentPicBed} ${currentPicBedConfig}`
|
||||
if (process.platform === 'darwin') {
|
||||
isHideDock ? app.dock?.hide() : setDockMenu()
|
||||
startMode !== ISartMode.NO_TRAY && createTray(tooltip)
|
||||
} else {
|
||||
createTray(tooltip)
|
||||
}
|
||||
db.set(configPaths.needReload, false)
|
||||
updateChecker()
|
||||
// 不需要阻塞
|
||||
process.nextTick(() => {
|
||||
shortKeyHandler.init()
|
||||
})
|
||||
server.startup()
|
||||
webServer.start()
|
||||
startFileServer()
|
||||
if (process.env.NODE_ENV !== 'development') {
|
||||
handleStartUpFiles(process.argv, process.cwd())
|
||||
}
|
||||
|
||||
if (notificationList && notificationList.length > 0) {
|
||||
while (notificationList.length) {
|
||||
const option = notificationList.pop()
|
||||
const notice = new Notification(option!)
|
||||
notice.show()
|
||||
}
|
||||
}
|
||||
await remoteNoticeHandler.init()
|
||||
remoteNoticeHandler.triggerHook(IRemoteNoticeTriggerHook.APP_START)
|
||||
if (startMode === ISartMode.MINI && process.platform !== 'darwin') {
|
||||
windowManager.create(IWindowList.MINI_WINDOW)
|
||||
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) {
|
||||
if (lastPosition[0] < 0 || lastPosition[0] > width || lastPosition[1] < 0 || lastPosition[1] > height) {
|
||||
miniWindow.setPosition(width - 100, height - 100)
|
||||
db.set(configPaths.settings.miniWindowPosition, [width - 100, height - 100])
|
||||
} else if (
|
||||
lastPosition[0] + miniWindow.getSize()[0] > width ||
|
||||
lastPosition[1] + miniWindow.getSize()[1] > height
|
||||
) {
|
||||
miniWindow.setPosition(width - miniWindow.getSize()[0], height - miniWindow.getSize()[1])
|
||||
db.set(configPaths.settings.miniWindowPosition, [
|
||||
width - miniWindow.getSize()[0],
|
||||
height - miniWindow.getSize()[1]
|
||||
])
|
||||
} else {
|
||||
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()
|
||||
} else if (startMode === ISartMode.MAIN) {
|
||||
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
settingWindow.show()
|
||||
settingWindow.focus()
|
||||
}
|
||||
const clipboardDir = path.join(picgo.baseDir, CLIPBOARD_IMAGE_FOLDER)
|
||||
fs.emptyDir(clipboardDir)
|
||||
}
|
||||
app.whenReady().then(readyFunction)
|
||||
}
|
||||
|
||||
#onRunning() {
|
||||
app.on('second-instance', (_, commandLine, workingDirectory) => {
|
||||
logger.info('detect second instance')
|
||||
const result = handleStartUpFiles(commandLine, workingDirectory)
|
||||
if (!result) {
|
||||
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
|
||||
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
if (settingWindow.isMinimized()) {
|
||||
settingWindow.restore()
|
||||
}
|
||||
settingWindow.focus()
|
||||
}
|
||||
}
|
||||
})
|
||||
app.on('activate', () => {
|
||||
if (!windowManager.has(IWindowList.TRAY_WINDOW)) {
|
||||
windowManager.create(IWindowList.TRAY_WINDOW)
|
||||
}
|
||||
if (!windowManager.has(IWindowList.SETTING_WINDOW)) {
|
||||
windowManager.create(IWindowList.SETTING_WINDOW)
|
||||
}
|
||||
})
|
||||
app.setLoginItemSettings({
|
||||
openAtLogin: db.get(configPaths.settings.autoStart) || false
|
||||
})
|
||||
if (process.platform === 'win32') {
|
||||
app.setAppUserModelId('com.kuingsmile.piclist')
|
||||
}
|
||||
|
||||
if (process.env.XDG_CURRENT_DESKTOP && process.env.XDG_CURRENT_DESKTOP.includes('Unity')) {
|
||||
process.env.XDG_CURRENT_DESKTOP = 'Unity'
|
||||
}
|
||||
}
|
||||
|
||||
#onQuit() {
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
|
||||
app.on('will-quit', () => {
|
||||
UpDownTaskQueue.getInstance().persist()
|
||||
clearTempFolder()
|
||||
globalShortcut.unregisterAll()
|
||||
bus.removeAllListeners()
|
||||
server.shutdown()
|
||||
webServer.stop()
|
||||
stopFileServer()
|
||||
MemoryMonitor.stop()
|
||||
})
|
||||
// Exit cleanly on request from parent process in development mode.
|
||||
if (isDevelopment) {
|
||||
if (process.platform === 'win32') {
|
||||
process.on('message', data => {
|
||||
if (data === 'graceful-exit') {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
process.on('SIGTERM', () => {
|
||||
app.quit()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async launchApp() {
|
||||
const gotTheLock = app.requestSingleInstanceLock()
|
||||
if (!gotTheLock) {
|
||||
app.quit()
|
||||
} else {
|
||||
await this.#beforeReady()
|
||||
this.#onReady()
|
||||
this.#onRunning()
|
||||
this.#onQuit()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const lifeCycle = new LifeCycle()
|
||||
|
||||
export { lifeCycle }
|
||||
|
||||
@@ -28,7 +28,7 @@ class AliyunApi {
|
||||
timeOut = 30000
|
||||
logger: ManageLogger
|
||||
|
||||
constructor (accessKeyId: string, accessKeySecret: string, logger: ManageLogger) {
|
||||
constructor(accessKeyId: string, accessKeySecret: string, logger: ManageLogger) {
|
||||
this.ctx = new OSS({
|
||||
accessKeyId,
|
||||
accessKeySecret,
|
||||
@@ -39,7 +39,7 @@ class AliyunApi {
|
||||
this.logger = logger
|
||||
}
|
||||
|
||||
formatFolder (item: string, slicedPrefix: string, urlPrefix: string): any {
|
||||
formatFolder(item: string, slicedPrefix: string, urlPrefix: string): any {
|
||||
return {
|
||||
key: item,
|
||||
url: `${urlPrefix}/${item}`,
|
||||
@@ -54,7 +54,7 @@ class AliyunApi {
|
||||
}
|
||||
}
|
||||
|
||||
formatFile (item: OSS.ObjectMeta, slicedPrefix: string, urlPrefix: string): any {
|
||||
formatFile(item: OSS.ObjectMeta, slicedPrefix: string, urlPrefix: string): any {
|
||||
const fileName = item.name.replace(slicedPrefix, '')
|
||||
return {
|
||||
...item,
|
||||
@@ -71,7 +71,7 @@ class AliyunApi {
|
||||
}
|
||||
}
|
||||
|
||||
getCanonicalizedOSSHeaders (headers: IStringKeyMap) {
|
||||
getCanonicalizedOSSHeaders(headers: IStringKeyMap) {
|
||||
const lowerCaseHeaders = Object.keys(headers).reduce((acc, key) => {
|
||||
acc[key.toLowerCase()] = headers[key]
|
||||
return acc
|
||||
@@ -84,7 +84,7 @@ class AliyunApi {
|
||||
return canonicalizedOSSHeaders
|
||||
}
|
||||
|
||||
authorization (
|
||||
authorization(
|
||||
method: string,
|
||||
canonicalizedResource: string,
|
||||
headers: IStringKeyMap,
|
||||
@@ -96,7 +96,7 @@ class AliyunApi {
|
||||
return `OSS ${this.accessKeyId}:${hmacSha1Base64(this.accessKeySecret, stringToSign)}`
|
||||
}
|
||||
|
||||
getNewCtx (region: string, bucket: string) {
|
||||
getNewCtx(region: string, bucket: string) {
|
||||
return new OSS({
|
||||
accessKeyId: this.accessKeyId,
|
||||
accessKeySecret: this.accessKeySecret,
|
||||
@@ -109,7 +109,7 @@ class AliyunApi {
|
||||
/**
|
||||
* 获取存储桶列表
|
||||
*/
|
||||
async getBucketList (): Promise<any> {
|
||||
async getBucketList(): Promise<any> {
|
||||
const getBuckets = async (marker?: string) => {
|
||||
const res = (await this.ctx.listBuckets({
|
||||
marker,
|
||||
@@ -143,7 +143,7 @@ class AliyunApi {
|
||||
/**
|
||||
* 获取自定义域名
|
||||
*/
|
||||
async getBucketDomain (param: IStringKeyMap): Promise<any> {
|
||||
async getBucketDomain(param: IStringKeyMap): Promise<any> {
|
||||
const headers = {
|
||||
Date: new Date().toUTCString()
|
||||
}
|
||||
@@ -186,7 +186,7 @@ class AliyunApi {
|
||||
* @description
|
||||
* acl: private | publicRead | publicReadWrite
|
||||
*/
|
||||
async createBucket (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async createBucket(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const client = new OSS({
|
||||
accessKeyId: this.accessKeyId,
|
||||
accessKeySecret: this.accessKeySecret,
|
||||
@@ -207,7 +207,7 @@ class AliyunApi {
|
||||
return res?.res?.status === 200
|
||||
}
|
||||
|
||||
async getBucketListRecursively (configMap: IStringKeyMap): Promise<any> {
|
||||
async getBucketListRecursively(configMap: IStringKeyMap): Promise<any> {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
const {
|
||||
bucketName: bucket,
|
||||
@@ -262,7 +262,7 @@ class AliyunApi {
|
||||
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
|
||||
}
|
||||
|
||||
async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> {
|
||||
async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
const {
|
||||
bucketName: bucket,
|
||||
@@ -336,7 +336,7 @@ class AliyunApi {
|
||||
* customUrl: string
|
||||
* }
|
||||
*/
|
||||
async getBucketFileList (configMap: IStringKeyMap): Promise<any> {
|
||||
async getBucketFileList(configMap: IStringKeyMap): Promise<any> {
|
||||
const {
|
||||
bucketName: bucket,
|
||||
bucketConfig: { Location: region },
|
||||
@@ -393,7 +393,7 @@ class AliyunApi {
|
||||
* newKey: string
|
||||
* }
|
||||
*/
|
||||
async renameBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async renameBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { bucketName, region, oldKey, newKey } = configMap
|
||||
const client = this.getNewCtx(region, bucketName)
|
||||
const copyRes = (await client.copy(newKey, oldKey)) as any
|
||||
@@ -413,7 +413,7 @@ class AliyunApi {
|
||||
* key: string
|
||||
* }
|
||||
*/
|
||||
async deleteBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async deleteBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { bucketName, region, key } = configMap
|
||||
const client = this.getNewCtx(region, bucketName)
|
||||
const res = (await client.delete(key)) as any
|
||||
@@ -424,7 +424,7 @@ class AliyunApi {
|
||||
* 删除文件夹
|
||||
* @param configMap
|
||||
*/
|
||||
async deleteBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async deleteBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { bucketName, region, key } = configMap
|
||||
const client = this.getNewCtx(region, bucketName)
|
||||
let marker
|
||||
@@ -486,7 +486,7 @@ class AliyunApi {
|
||||
* customUrl: string
|
||||
* }
|
||||
*/
|
||||
async getPreSignedUrl (configMap: IStringKeyMap): Promise<string> {
|
||||
async getPreSignedUrl(configMap: IStringKeyMap): Promise<string> {
|
||||
const { bucketName, region, key, expires, customUrl } = configMap
|
||||
const client = this.getNewCtx(region, bucketName)
|
||||
const res = client.signatureUrl(key, {
|
||||
@@ -499,7 +499,7 @@ class AliyunApi {
|
||||
* 上传文件
|
||||
* @param configMap
|
||||
*/
|
||||
async uploadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async uploadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { fileArray } = configMap
|
||||
// fileArray = [{
|
||||
// bucketName: string,
|
||||
@@ -586,7 +586,7 @@ class AliyunApi {
|
||||
* 新建文件夹
|
||||
* @param configMap
|
||||
*/
|
||||
async createBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async createBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { bucketName, region, key } = configMap
|
||||
const client = this.getNewCtx(region, bucketName)
|
||||
const res = (await client.put(key, Buffer.from(''))) as any
|
||||
@@ -597,7 +597,7 @@ class AliyunApi {
|
||||
* 下载文件
|
||||
* @param configMap
|
||||
*/
|
||||
async downloadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async downloadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { downloadPath, fileArray, maxDownloadFileCount } = configMap
|
||||
const instance = UpDownTaskQueue.getInstance()
|
||||
const promises = [] as any
|
||||
|
||||
@@ -29,7 +29,7 @@ class GithubApi {
|
||||
baseUrl = 'https://api.github.com'
|
||||
commonHeaders: IStringKeyMap
|
||||
|
||||
constructor (token: string, username: string, proxy: string | undefined, logger: ManageLogger) {
|
||||
constructor(token: string, username: string, proxy: string | undefined, logger: ManageLogger) {
|
||||
this.logger = logger
|
||||
this.token = token.startsWith('Bearer ') ? token : `Bearer ${token}`.trim()
|
||||
this.username = username
|
||||
@@ -41,7 +41,7 @@ class GithubApi {
|
||||
}
|
||||
}
|
||||
|
||||
formatFolder (item: any, slicedPrefix: string, branch: string, repo: string, cdnUrl: string | undefined) {
|
||||
formatFolder(item: any, slicedPrefix: string, branch: string, repo: string, cdnUrl: string | undefined) {
|
||||
const key = `${slicedPrefix ? `${slicedPrefix}/` : ''}${item.path}/`
|
||||
let rawUrl = ''
|
||||
const placeholders = ['{username}', '{repo}', '{branch}', '{path}']
|
||||
@@ -78,7 +78,7 @@ class GithubApi {
|
||||
}
|
||||
}
|
||||
|
||||
formatFile (item: any, slicedPrefix: string, branch: string, repo: string, cdnUrl: string | undefined) {
|
||||
formatFile(item: any, slicedPrefix: string, branch: string, repo: string, cdnUrl: string | undefined) {
|
||||
let rawUrl = ''
|
||||
const placeholders = ['{username}', '{repo}', '{branch}', '{path}']
|
||||
const key = slicedPrefix === '' ? item.path : `${slicedPrefix}/${item.path}`
|
||||
@@ -119,7 +119,7 @@ class GithubApi {
|
||||
/**
|
||||
* get repo list
|
||||
*/
|
||||
async getBucketList (): Promise<any> {
|
||||
async getBucketList(): Promise<any> {
|
||||
let initPage = 1
|
||||
let res
|
||||
const result = [] as any[]
|
||||
@@ -156,7 +156,7 @@ class GithubApi {
|
||||
/**
|
||||
* 获取branch列表
|
||||
*/
|
||||
async getBucketDomain (param: IStringKeyMap): Promise<any> {
|
||||
async getBucketDomain(param: IStringKeyMap): Promise<any> {
|
||||
const { bucketName: repo } = param
|
||||
let initPage = 1
|
||||
let res
|
||||
@@ -184,7 +184,7 @@ class GithubApi {
|
||||
return result
|
||||
}
|
||||
|
||||
async getBucketListRecursively (configMap: IStringKeyMap): Promise<any> {
|
||||
async getBucketListRecursively(configMap: IStringKeyMap): Promise<any> {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
const { bucketName: repo, customUrl: branch, prefix, cancelToken, cdnUrl } = configMap
|
||||
const slicedPrefix = prefix.replace(/(^\/+|\/+$)/g, '')
|
||||
@@ -235,7 +235,7 @@ class GithubApi {
|
||||
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
|
||||
}
|
||||
|
||||
async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> {
|
||||
async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
const { bucketName: repo, customUrl: branch, prefix, cancelToken, cdnUrl } = configMap
|
||||
const slicedPrefix = prefix.replace(/(^\/+|\/+$)/g, '')
|
||||
@@ -285,7 +285,7 @@ class GithubApi {
|
||||
* key: string
|
||||
* }
|
||||
*/
|
||||
async deleteBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async deleteBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { bucketName: repo, githubBranch: branch, key, DeleteHash: sha } = configMap
|
||||
const body = {
|
||||
message: 'deleted by PicList',
|
||||
@@ -303,7 +303,7 @@ class GithubApi {
|
||||
* create a new tree to delete a folder
|
||||
* @param configMap
|
||||
*/
|
||||
async deleteBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async deleteBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { bucketName: repo, githubBranch: branch, key } = configMap
|
||||
// get sha of the branch
|
||||
const refRes = (await got(
|
||||
@@ -412,7 +412,7 @@ class GithubApi {
|
||||
* customUrl: string
|
||||
* }
|
||||
*/
|
||||
async getPreSignedUrl (configMap: IStringKeyMap): Promise<string> {
|
||||
async getPreSignedUrl(configMap: IStringKeyMap): Promise<string> {
|
||||
const { bucketName: repo, customUrl: branch, key, rawUrl, githubPrivate: isPrivate } = configMap
|
||||
if (!isPrivate) return rawUrl
|
||||
const res = (await got(
|
||||
@@ -436,7 +436,7 @@ class GithubApi {
|
||||
* 新建文件夹
|
||||
* @param configMap
|
||||
*/
|
||||
async createBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async createBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { bucketName: repo, githubBranch: branch, key } = configMap
|
||||
const newFileKey = `${trimPath(key)}/.gitkeep`
|
||||
const base64Content = Buffer.from('created by PicList').toString('base64')
|
||||
@@ -456,7 +456,7 @@ class GithubApi {
|
||||
* 上传文件
|
||||
* @param configMap
|
||||
*/
|
||||
async uploadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async uploadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { fileArray } = configMap
|
||||
const instance = UpDownTaskQueue.getInstance()
|
||||
fileArray.forEach((item: any) => {
|
||||
@@ -505,7 +505,7 @@ class GithubApi {
|
||||
* 下载文件
|
||||
* @param configMap
|
||||
*/
|
||||
async downloadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async downloadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { downloadPath, fileArray, maxDownloadFileCount } = configMap
|
||||
const instance = UpDownTaskQueue.getInstance()
|
||||
const promises = [] as any
|
||||
|
||||
@@ -31,7 +31,7 @@ class ImgurApi {
|
||||
idHeaders: any
|
||||
baseUrl = 'https://api.imgur.com/3'
|
||||
|
||||
constructor (userName: string, accessToken: string, proxy: any, logger: ManageLogger) {
|
||||
constructor(userName: string, accessToken: string, proxy: any, logger: ManageLogger) {
|
||||
this.userName = userName
|
||||
this.accessToken = accessToken.startsWith('Bearer ') ? accessToken : `Bearer ${accessToken}`
|
||||
this.proxy = proxy
|
||||
@@ -42,7 +42,7 @@ class ImgurApi {
|
||||
}
|
||||
}
|
||||
|
||||
formatFile (item: any) {
|
||||
formatFile(item: any) {
|
||||
const fileName = path.basename(item.link)
|
||||
const isImg = isImage(fileName)
|
||||
return {
|
||||
@@ -64,7 +64,7 @@ class ImgurApi {
|
||||
/**
|
||||
* get repo list
|
||||
*/
|
||||
async getBucketList (): Promise<any> {
|
||||
async getBucketList(): Promise<any> {
|
||||
let initPage = 0
|
||||
let res
|
||||
const result = [] as any[]
|
||||
@@ -93,7 +93,7 @@ class ImgurApi {
|
||||
return finalResult
|
||||
}
|
||||
|
||||
async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> {
|
||||
async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
const {
|
||||
bucketConfig: { Location: albumHash },
|
||||
@@ -153,7 +153,7 @@ class ImgurApi {
|
||||
ipcMain.removeAllListeners('cancelLoadingFileList')
|
||||
}
|
||||
|
||||
async deleteBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async deleteBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { DeleteHash: deleteHash } = configMap
|
||||
const res = (await got(
|
||||
`${this.baseUrl}/account/${this.userName}/image/${deleteHash}`,
|
||||
@@ -166,7 +166,7 @@ class ImgurApi {
|
||||
* 上传文件
|
||||
* @param configMap
|
||||
*/
|
||||
async uploadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async uploadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { fileArray } = configMap
|
||||
const instance = UpDownTaskQueue.getInstance()
|
||||
fileArray.forEach((item: any) => {
|
||||
@@ -226,7 +226,7 @@ class ImgurApi {
|
||||
* 下载文件
|
||||
* @param configMap
|
||||
*/
|
||||
async downloadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async downloadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { downloadPath, fileArray, maxDownloadFileCount } = configMap
|
||||
const instance = UpDownTaskQueue.getInstance()
|
||||
const promises = [] as any
|
||||
|
||||
@@ -17,7 +17,7 @@ class LocalApi {
|
||||
logger: ManageLogger
|
||||
isWindows: boolean
|
||||
|
||||
constructor (logger: ManageLogger) {
|
||||
constructor(logger: ManageLogger) {
|
||||
this.logger = logger
|
||||
this.isWindows = process.platform === 'win32'
|
||||
}
|
||||
@@ -25,12 +25,12 @@ class LocalApi {
|
||||
logParam = (error: any, method: string) => this.logger.error(formatError(error, { class: 'LocalApi', method }))
|
||||
|
||||
// windows 系统下将路径转换为 unix 风格
|
||||
transPathToUnix (filePath: string | undefined) {
|
||||
transPathToUnix(filePath: string | undefined) {
|
||||
if (!filePath) return ''
|
||||
return this.isWindows ? filePath.split(path.sep).join(path.posix.sep) : filePath.replace(/^\/+/, '')
|
||||
}
|
||||
|
||||
transBack (filePath: string | undefined) {
|
||||
transBack(filePath: string | undefined) {
|
||||
if (!filePath) return ''
|
||||
return this.isWindows
|
||||
? filePath
|
||||
@@ -40,7 +40,7 @@ class LocalApi {
|
||||
: `/${filePath.replace(/^\/+|\/+$/g, '')}`
|
||||
}
|
||||
|
||||
formatFolder (item: fs.Stats, urlPrefix: string, fileName: string, filePath: string) {
|
||||
formatFolder(item: fs.Stats, urlPrefix: string, fileName: string, filePath: string) {
|
||||
const key = `${this.transPathToUnix(filePath)}/`.replace(/\/+$/, '/')
|
||||
return {
|
||||
...item,
|
||||
@@ -57,7 +57,7 @@ class LocalApi {
|
||||
}
|
||||
}
|
||||
|
||||
formatFile (item: fs.Stats, urlPrefix: string, fileName: string, filePath: string, isDownload = false) {
|
||||
formatFile(item: fs.Stats, urlPrefix: string, fileName: string, filePath: string, isDownload = false) {
|
||||
const key = isDownload ? filePath : this.transPathToUnix(filePath)
|
||||
return {
|
||||
...item,
|
||||
@@ -74,7 +74,7 @@ class LocalApi {
|
||||
}
|
||||
}
|
||||
|
||||
async getBucketListRecursively (configMap: IStringKeyMap): Promise<any> {
|
||||
async getBucketListRecursively(configMap: IStringKeyMap): Promise<any> {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
const { prefix, customUrl = '', cancelToken } = configMap
|
||||
const urlPrefix = customUrl.replace(/\/+$/, '')
|
||||
@@ -114,7 +114,7 @@ class LocalApi {
|
||||
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
|
||||
}
|
||||
|
||||
async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> {
|
||||
async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
const { customUrl = '', cancelToken, baseDir } = configMap
|
||||
let prefix = configMap.prefix
|
||||
@@ -170,7 +170,7 @@ class LocalApi {
|
||||
ipcMain.removeAllListeners('cancelLoadingFileList')
|
||||
}
|
||||
|
||||
async renameBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async renameBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { oldKey, newKey } = configMap
|
||||
let result = false
|
||||
try {
|
||||
@@ -182,7 +182,7 @@ class LocalApi {
|
||||
return result
|
||||
}
|
||||
|
||||
async deleteBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async deleteBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { key } = configMap
|
||||
let result = false
|
||||
try {
|
||||
@@ -194,7 +194,7 @@ class LocalApi {
|
||||
return result
|
||||
}
|
||||
|
||||
async deleteBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async deleteBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { key } = configMap
|
||||
let result = false
|
||||
try {
|
||||
@@ -208,7 +208,7 @@ class LocalApi {
|
||||
return result
|
||||
}
|
||||
|
||||
async uploadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async uploadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { fileArray } = configMap
|
||||
const instance = UpDownTaskQueue.getInstance()
|
||||
for (const item of fileArray) {
|
||||
@@ -250,7 +250,7 @@ class LocalApi {
|
||||
return true
|
||||
}
|
||||
|
||||
async createBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async createBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { key } = configMap
|
||||
let result = false
|
||||
try {
|
||||
@@ -264,7 +264,7 @@ class LocalApi {
|
||||
return result
|
||||
}
|
||||
|
||||
async downloadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async downloadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { downloadPath, fileArray } = configMap
|
||||
const instance = UpDownTaskQueue.getInstance()
|
||||
for (const item of fileArray) {
|
||||
|
||||
@@ -33,14 +33,14 @@ class QiniuApi {
|
||||
getBucketDomain: 'https://uc.qiniuapi.com/v2/domains'
|
||||
}
|
||||
|
||||
constructor (accessKey: string, secretKey: string, logger: ManageLogger) {
|
||||
constructor(accessKey: string, secretKey: string, logger: ManageLogger) {
|
||||
this.mac = new qiniu.auth.digest.Mac(accessKey, secretKey)
|
||||
this.accessKey = accessKey
|
||||
this.secretKey = secretKey
|
||||
this.logger = logger
|
||||
}
|
||||
|
||||
formatFolder (item: string, slicedPrefix: string, urlPrefix: string) {
|
||||
formatFolder(item: string, slicedPrefix: string, urlPrefix: string) {
|
||||
return {
|
||||
Key: item,
|
||||
key: item,
|
||||
@@ -54,7 +54,7 @@ class QiniuApi {
|
||||
}
|
||||
}
|
||||
|
||||
formatFile (item: any, slicedPrefix: string, urlPrefix: string) {
|
||||
formatFile(item: any, slicedPrefix: string, urlPrefix: string) {
|
||||
const fileName = item.key.replace(slicedPrefix, '')
|
||||
return {
|
||||
...item,
|
||||
@@ -69,7 +69,7 @@ class QiniuApi {
|
||||
}
|
||||
}
|
||||
|
||||
authorization (
|
||||
authorization(
|
||||
method: string,
|
||||
urlPath: string,
|
||||
host: string,
|
||||
@@ -98,7 +98,7 @@ class QiniuApi {
|
||||
/**
|
||||
* 获取存储桶列表
|
||||
*/
|
||||
async getBucketList (): Promise<any> {
|
||||
async getBucketList(): Promise<any> {
|
||||
const host = this.hostList.getBucketList
|
||||
const authorization = qiniu.util.generateAccessToken(this.mac, host, undefined)
|
||||
const res = await axios.get(host, {
|
||||
@@ -128,7 +128,7 @@ class QiniuApi {
|
||||
/**
|
||||
* 获取存储桶详细信息
|
||||
*/
|
||||
async getBucketInfo (param: IStringKeyMap): Promise<any> {
|
||||
async getBucketInfo(param: IStringKeyMap): Promise<any> {
|
||||
const { bucketName } = param
|
||||
const urlPath = `/v2/bucketInfo?bucket=${bucketName}&fs=true`
|
||||
const authorization = this.authorization('POST', urlPath, this.host, '', '', 'application/json')
|
||||
@@ -160,7 +160,7 @@ class QiniuApi {
|
||||
/**
|
||||
* 获取自定义域名
|
||||
*/
|
||||
async getBucketDomain (param: IStringKeyMap): Promise<any> {
|
||||
async getBucketDomain(param: IStringKeyMap): Promise<any> {
|
||||
const { bucketName } = param
|
||||
const host = this.hostList.getBucketDomain
|
||||
const authorization = qiniu.util.generateAccessToken(this.mac, `${host}?tbl=${bucketName}`, undefined)
|
||||
@@ -180,7 +180,7 @@ class QiniuApi {
|
||||
/**
|
||||
* 修改存储桶权限
|
||||
*/
|
||||
async setBucketAclPolicy (param: IStringKeyMap): Promise<boolean> {
|
||||
async setBucketAclPolicy(param: IStringKeyMap): Promise<boolean> {
|
||||
// 0: 公开访问 1: 私有访问
|
||||
const { bucketName } = param
|
||||
let { isPrivate } = param
|
||||
@@ -213,7 +213,7 @@ class QiniuApi {
|
||||
* acl: boolean // 是否公开访问
|
||||
* }
|
||||
*/
|
||||
async createBucket (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async createBucket(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { BucketName, region, acl } = configMap
|
||||
const urlPath = `/mkbucketv3/${BucketName}/region/${region}`
|
||||
const authorization = this.authorization('POST', urlPath, this.host, '', '', 'application/json')
|
||||
@@ -235,7 +235,7 @@ class QiniuApi {
|
||||
: false
|
||||
}
|
||||
|
||||
async getBucketListRecursively (configMap: IStringKeyMap): Promise<any> {
|
||||
async getBucketListRecursively(configMap: IStringKeyMap): Promise<any> {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
const { bucketName: bucket, prefix, cancelToken, customUrl: urlPrefix } = configMap
|
||||
let marker = undefined as any
|
||||
@@ -297,7 +297,7 @@ class QiniuApi {
|
||||
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
|
||||
}
|
||||
|
||||
async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> {
|
||||
async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
const { bucketName: bucket, prefix, cancelToken, customUrl: urlPrefix } = configMap
|
||||
let marker = undefined as any
|
||||
@@ -380,7 +380,7 @@ class QiniuApi {
|
||||
* customUrl: string
|
||||
* }
|
||||
*/
|
||||
async getBucketFileList (configMap: IStringKeyMap): Promise<any> {
|
||||
async getBucketFileList(configMap: IStringKeyMap): Promise<any> {
|
||||
const { bucketName: bucket, prefix, marker, itemsPerPage, customUrl: urlPrefix } = configMap
|
||||
const slicedPrefix = prefix.slice(1)
|
||||
const config = new qiniu.conf.Config()
|
||||
@@ -440,7 +440,7 @@ class QiniuApi {
|
||||
* key: string
|
||||
* }
|
||||
*/
|
||||
async deleteBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async deleteBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { bucketName, key } = configMap
|
||||
const config = new qiniu.conf.Config()
|
||||
const bucketManager = new qiniu.rs.BucketManager(this.mac, config)
|
||||
@@ -463,7 +463,7 @@ class QiniuApi {
|
||||
* 删除文件夹
|
||||
* @param configMap
|
||||
*/
|
||||
async deleteBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async deleteBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { bucketName, key } = configMap
|
||||
const config = new qiniu.conf.Config()
|
||||
const bucketManager = new qiniu.rs.BucketManager(this.mac, config)
|
||||
@@ -535,7 +535,7 @@ class QiniuApi {
|
||||
* newKey: string
|
||||
* }
|
||||
*/
|
||||
async renameBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async renameBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { bucketName, oldKey, newKey } = configMap
|
||||
const config = new qiniu.conf.Config()
|
||||
const bucketManager = new qiniu.rs.BucketManager(this.mac, config)
|
||||
@@ -574,7 +574,7 @@ class QiniuApi {
|
||||
* customUrl: string
|
||||
* }
|
||||
*/
|
||||
async getPreSignedUrl (configMap: IStringKeyMap): Promise<string> {
|
||||
async getPreSignedUrl(configMap: IStringKeyMap): Promise<string> {
|
||||
const { key, expires, customUrl } = configMap
|
||||
const config = new qiniu.conf.Config()
|
||||
const bucketManager = new qiniu.rs.BucketManager(this.mac, config)
|
||||
@@ -588,7 +588,7 @@ class QiniuApi {
|
||||
* 上传文件
|
||||
* @param configMap
|
||||
*/
|
||||
async uploadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async uploadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { fileArray } = configMap
|
||||
const instance = UpDownTaskQueue.getInstance()
|
||||
fileArray.forEach((item: any) => {
|
||||
@@ -667,7 +667,7 @@ class QiniuApi {
|
||||
* 新建文件夹
|
||||
* @param configMap
|
||||
*/
|
||||
async createBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async createBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { bucketName, key } = configMap
|
||||
const putPolicy = new qiniu.rs.PutPolicy({
|
||||
scope: `${bucketName}:${key}`
|
||||
@@ -694,7 +694,7 @@ class QiniuApi {
|
||||
* 下载文件
|
||||
* @param configMap
|
||||
*/
|
||||
async downloadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async downloadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { downloadPath, fileArray, maxDownloadFileCount } = configMap
|
||||
const instance = UpDownTaskQueue.getInstance()
|
||||
const promises = [] as any
|
||||
|
||||
@@ -46,7 +46,7 @@ class S3plistApi {
|
||||
secretAccessKey: string
|
||||
bucketName: string
|
||||
|
||||
constructor (
|
||||
constructor(
|
||||
accessKeyId: string,
|
||||
secretAccessKey: string,
|
||||
endpoint: string | undefined,
|
||||
@@ -75,7 +75,7 @@ class S3plistApi {
|
||||
this.proxy = formatHttpProxy(proxy, 'string') as string | undefined
|
||||
}
|
||||
|
||||
async getDogeCloudToken () {
|
||||
async getDogeCloudToken() {
|
||||
if (!this.dogeCloudSupport) return
|
||||
const token = (await getTempToken(this.accessKeyId, this.secretAccessKey)) as DogecloudToken
|
||||
if (Object.keys(token).length === 0) {
|
||||
@@ -88,7 +88,7 @@ class S3plistApi {
|
||||
}
|
||||
}
|
||||
|
||||
setAgent (proxy: string | undefined, sslEnabled: boolean): NodeHttpHandler {
|
||||
setAgent(proxy: string | undefined, sslEnabled: boolean): NodeHttpHandler {
|
||||
const agent = getAgent(proxy, sslEnabled)
|
||||
const commonOptions: AgentOptions = {
|
||||
keepAlive: true,
|
||||
@@ -98,26 +98,26 @@ class S3plistApi {
|
||||
const extraOptions = sslEnabled ? { rejectUnauthorized: false } : {}
|
||||
return sslEnabled
|
||||
? new NodeHttpHandler({
|
||||
httpsAgent: agent.https
|
||||
? agent.https
|
||||
: new https.Agent({
|
||||
...commonOptions,
|
||||
...extraOptions
|
||||
})
|
||||
})
|
||||
httpsAgent: agent.https
|
||||
? agent.https
|
||||
: new https.Agent({
|
||||
...commonOptions,
|
||||
...extraOptions
|
||||
})
|
||||
})
|
||||
: new NodeHttpHandler({
|
||||
httpAgent: agent.http
|
||||
? agent.http
|
||||
: new http.Agent({
|
||||
...commonOptions,
|
||||
...extraOptions
|
||||
})
|
||||
})
|
||||
httpAgent: agent.http
|
||||
? agent.http
|
||||
: new http.Agent({
|
||||
...commonOptions,
|
||||
...extraOptions
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
logParam = (error: any, method: string) => this.logger.error(formatError(error, { class: 'S3plistApi', method }))
|
||||
|
||||
formatFolder (item: CommonPrefix, slicedPrefix: string, urlPrefix: string): any {
|
||||
formatFolder(item: CommonPrefix, slicedPrefix: string, urlPrefix: string): any {
|
||||
return {
|
||||
Key: item.Prefix,
|
||||
url: `${urlPrefix}/${item.Prefix}`,
|
||||
@@ -132,7 +132,7 @@ class S3plistApi {
|
||||
}
|
||||
}
|
||||
|
||||
formatFile (item: _Object, slicedPrefix: string, urlPrefix: string): any {
|
||||
formatFile(item: _Object, slicedPrefix: string, urlPrefix: string): any {
|
||||
const fileName = item.Key?.replace(slicedPrefix, '')
|
||||
return {
|
||||
...item,
|
||||
@@ -148,7 +148,7 @@ class S3plistApi {
|
||||
}
|
||||
}
|
||||
|
||||
async putPublicAccess (bucketName: string, client: S3Client) {
|
||||
async putPublicAccess(bucketName: string, client: S3Client) {
|
||||
const input = {
|
||||
Bucket: bucketName,
|
||||
PublicAccessBlockConfiguration: {
|
||||
@@ -175,11 +175,11 @@ class S3plistApi {
|
||||
* acl: string
|
||||
* }
|
||||
*/
|
||||
async createBucket (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async createBucket(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { BucketName, region, acl, endpoint } = configMap
|
||||
try {
|
||||
await this.getDogeCloudToken()
|
||||
const options = ({ ...this.baseOptions }) as S3ClientConfig
|
||||
const options = { ...this.baseOptions } as S3ClientConfig
|
||||
options.region = String(region) || 'us-east-1'
|
||||
const client = new S3Client(options)
|
||||
const command = new ListBucketsCommand({})
|
||||
@@ -234,7 +234,7 @@ class S3plistApi {
|
||||
/**
|
||||
* 获取存储桶列表
|
||||
*/
|
||||
async getBucketList (): Promise<any> {
|
||||
async getBucketList(): Promise<any> {
|
||||
if (this.dogeCloudSupport) {
|
||||
try {
|
||||
const res = await dogecloudApi('/oss/bucket/list.json', {}, false, this.accessKeyId, this.secretAccessKey)
|
||||
@@ -255,7 +255,7 @@ class S3plistApi {
|
||||
}
|
||||
return []
|
||||
}
|
||||
const options = ({ ...this.baseOptions }) as S3ClientConfig
|
||||
const options = { ...this.baseOptions } as S3ClientConfig
|
||||
const result: IStringKeyMap[] = []
|
||||
const endpoint = (options.endpoint as string) || ''
|
||||
options.region = endpoint.includes('cloudflarestorage') ? 'auto' : 'us-east-1'
|
||||
@@ -305,7 +305,7 @@ class S3plistApi {
|
||||
return result
|
||||
}
|
||||
|
||||
async getBucketListRecursively (configMap: IStringKeyMap): Promise<any> {
|
||||
async getBucketListRecursively(configMap: IStringKeyMap): Promise<any> {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
const {
|
||||
bucketName: bucket,
|
||||
@@ -331,7 +331,7 @@ class S3plistApi {
|
||||
}
|
||||
try {
|
||||
do {
|
||||
const options = ({ ...this.baseOptions }) as S3ClientConfig
|
||||
const options = { ...this.baseOptions } as S3ClientConfig
|
||||
options.region = String(region) || 'us-east-1'
|
||||
const client = new S3Client(options)
|
||||
const command = new ListObjectsV2Command({
|
||||
@@ -369,7 +369,7 @@ class S3plistApi {
|
||||
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
|
||||
}
|
||||
|
||||
async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> {
|
||||
async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
const {
|
||||
bucketName: bucket,
|
||||
@@ -396,7 +396,7 @@ class S3plistApi {
|
||||
try {
|
||||
await this.getDogeCloudToken()
|
||||
do {
|
||||
const options = ({ ...this.baseOptions }) as S3ClientConfig
|
||||
const options = { ...this.baseOptions } as S3ClientConfig
|
||||
options.region = String(region) || 'us-east-1'
|
||||
const client = new S3Client(options)
|
||||
const command = new ListObjectsV2Command({
|
||||
@@ -439,7 +439,7 @@ class S3plistApi {
|
||||
ipcMain.removeAllListeners('cancelLoadingFileList')
|
||||
}
|
||||
|
||||
async getBucketFileList (configMap: IStringKeyMap): Promise<any> {
|
||||
async getBucketFileList(configMap: IStringKeyMap): Promise<any> {
|
||||
const {
|
||||
bucketName: bucket,
|
||||
bucketConfig: { Location: region },
|
||||
@@ -457,10 +457,10 @@ class S3plistApi {
|
||||
}
|
||||
try {
|
||||
await this.getDogeCloudToken()
|
||||
const options = ({
|
||||
|
||||
...this.baseOptions, region: String(region) || 'us-east-1'
|
||||
}) as S3ClientConfig
|
||||
const options = {
|
||||
...this.baseOptions,
|
||||
region: String(region) || 'us-east-1'
|
||||
} as S3ClientConfig
|
||||
const client = new S3Client(options)
|
||||
const command = new ListObjectsV2Command({
|
||||
Bucket: bucket,
|
||||
@@ -495,15 +495,15 @@ class S3plistApi {
|
||||
* newKey: string
|
||||
* }
|
||||
*/
|
||||
async renameBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async renameBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { bucketName, region, oldKey, newKey } = configMap
|
||||
let result = false
|
||||
try {
|
||||
await this.getDogeCloudToken()
|
||||
const options = ({
|
||||
|
||||
...this.baseOptions, region: String(region) || 'us-east-1'
|
||||
}) as S3ClientConfig
|
||||
const options = {
|
||||
...this.baseOptions,
|
||||
region: String(region) || 'us-east-1'
|
||||
} as S3ClientConfig
|
||||
const client = new S3Client(options)
|
||||
const command = new CopyObjectCommand({
|
||||
Bucket: bucketName,
|
||||
@@ -540,12 +540,12 @@ class S3plistApi {
|
||||
* key: string
|
||||
* }
|
||||
*/
|
||||
async deleteBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async deleteBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { bucketName, region, key } = configMap
|
||||
let result = false
|
||||
try {
|
||||
await this.getDogeCloudToken()
|
||||
const options = ({ ...this.baseOptions }) as S3ClientConfig
|
||||
const options = { ...this.baseOptions } as S3ClientConfig
|
||||
options.region = String(region) || 'us-east-1'
|
||||
const client = new S3Client(options)
|
||||
const command = new DeleteObjectCommand({
|
||||
@@ -568,7 +568,7 @@ class S3plistApi {
|
||||
* 删除文件夹
|
||||
* @param configMap
|
||||
*/
|
||||
async deleteBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async deleteBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { bucketName, region, key } = configMap
|
||||
let marker
|
||||
let result = false
|
||||
@@ -581,7 +581,7 @@ class S3plistApi {
|
||||
try {
|
||||
await this.getDogeCloudToken()
|
||||
do {
|
||||
const options = ({ ...this.baseOptions }) as S3ClientConfig
|
||||
const options = { ...this.baseOptions } as S3ClientConfig
|
||||
options.region = String(region) || 'us-east-1'
|
||||
const client = new S3Client(options)
|
||||
const command = new ListObjectsV2Command({
|
||||
@@ -616,7 +616,7 @@ class S3plistApi {
|
||||
}
|
||||
if (allFileList.Contents.length > 0) {
|
||||
const cycle = Math.ceil(allFileList.Contents.length / 1000)
|
||||
const options = ({ ...this.baseOptions }) as S3ClientConfig
|
||||
const options = { ...this.baseOptions } as S3ClientConfig
|
||||
options.region = String(region) || 'us-east-1'
|
||||
const client = new S3Client(options)
|
||||
for (let i = 0; i < cycle; i++) {
|
||||
@@ -657,11 +657,11 @@ class S3plistApi {
|
||||
* customUrl: string
|
||||
* }
|
||||
*/
|
||||
async getPreSignedUrl (configMap: IStringKeyMap): Promise<string> {
|
||||
async getPreSignedUrl(configMap: IStringKeyMap): Promise<string> {
|
||||
const { bucketName, region, key, expires } = configMap
|
||||
try {
|
||||
await this.getDogeCloudToken()
|
||||
const options = ({ ...this.baseOptions }) as S3ClientConfig
|
||||
const options = { ...this.baseOptions } as S3ClientConfig
|
||||
options.region = String(region) || 'us-east-1'
|
||||
const client = new S3Client(options)
|
||||
const signedUrl = await getSignedUrl(
|
||||
@@ -685,12 +685,12 @@ class S3plistApi {
|
||||
* 新建文件夹
|
||||
* @param configMap
|
||||
*/
|
||||
async createBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async createBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { bucketName, region, key } = configMap
|
||||
let result = false
|
||||
try {
|
||||
await this.getDogeCloudToken()
|
||||
const options = ({ ...this.baseOptions }) as S3ClientConfig
|
||||
const options = { ...this.baseOptions } as S3ClientConfig
|
||||
options.region = String(region) || 'us-east-1'
|
||||
const client = new S3Client(options)
|
||||
const command = new PutObjectCommand({
|
||||
@@ -713,7 +713,7 @@ class S3plistApi {
|
||||
* upload file
|
||||
* @param configMap
|
||||
*/
|
||||
async uploadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async uploadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { fileArray } = configMap
|
||||
// fileArray = [{
|
||||
// bucketName: string,
|
||||
@@ -764,7 +764,7 @@ class S3plistApi {
|
||||
})
|
||||
continue
|
||||
}
|
||||
const options = ({ ...this.baseOptions }) as S3ClientConfig
|
||||
const options = { ...this.baseOptions } as S3ClientConfig
|
||||
options.region = String(region) || 'us-east-1'
|
||||
const client = new S3Client(options)
|
||||
const fileStream = fs.createReadStream(filePath)
|
||||
@@ -825,7 +825,7 @@ class S3plistApi {
|
||||
* 下载文件
|
||||
* @param configMap
|
||||
*/
|
||||
async downloadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async downloadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { downloadPath, fileArray, maxDownloadFileCount } = configMap
|
||||
const instance = UpDownTaskQueue.getInstance()
|
||||
const promises = [] as any
|
||||
|
||||
@@ -44,7 +44,7 @@ class SftpApi {
|
||||
passphrase: string
|
||||
}
|
||||
|
||||
constructor (
|
||||
constructor(
|
||||
host: string,
|
||||
port: Undefinable<number>,
|
||||
username: Undefinable<string>,
|
||||
@@ -94,7 +94,7 @@ class SftpApi {
|
||||
return `0${result}`
|
||||
}
|
||||
|
||||
formatFolder (item: listDirResult, urlPrefix: string, isWebPath = false) {
|
||||
formatFolder(item: listDirResult, urlPrefix: string, isWebPath = false) {
|
||||
const key = item.key
|
||||
let url: string
|
||||
if (isWebPath) {
|
||||
@@ -121,7 +121,7 @@ class SftpApi {
|
||||
}
|
||||
}
|
||||
|
||||
formatFile (item: listDirResult, urlPrefix: string, isWebPath = false) {
|
||||
formatFile(item: listDirResult, urlPrefix: string, isWebPath = false) {
|
||||
const key = item.key
|
||||
return {
|
||||
...item,
|
||||
@@ -151,7 +151,7 @@ class SftpApi {
|
||||
}
|
||||
}
|
||||
|
||||
async getBucketListRecursively (configMap: IStringKeyMap): Promise<any> {
|
||||
async getBucketListRecursively(configMap: IStringKeyMap): Promise<any> {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
const { prefix, customUrl, cancelToken } = configMap
|
||||
const urlPrefix = customUrl || `${this.host}:${this.port}`
|
||||
@@ -191,7 +191,7 @@ class SftpApi {
|
||||
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
|
||||
}
|
||||
|
||||
formatLSResult (res: string, cwd: string): listDirResult[] {
|
||||
formatLSResult(res: string, cwd: string): listDirResult[] {
|
||||
const result = [] as listDirResult[]
|
||||
const resArray = res.trim().split('\n')
|
||||
resArray.slice(resArray[0].startsWith('total') ? 1 : 0).forEach((item: string) => {
|
||||
@@ -217,7 +217,7 @@ class SftpApi {
|
||||
return result
|
||||
}
|
||||
|
||||
async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> {
|
||||
async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
const { prefix, customUrl, cancelToken, baseDir } = configMap
|
||||
let urlPrefix = customUrl || `${this.host}:${this.port}`
|
||||
@@ -276,7 +276,7 @@ class SftpApi {
|
||||
ipcMain.removeAllListeners('cancelLoadingFileList')
|
||||
}
|
||||
|
||||
async renameBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async renameBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { oldKey, newKey } = configMap
|
||||
let result = false
|
||||
try {
|
||||
@@ -290,7 +290,7 @@ class SftpApi {
|
||||
return result
|
||||
}
|
||||
|
||||
async deleteBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async deleteBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { key } = configMap
|
||||
let result = false
|
||||
try {
|
||||
@@ -304,7 +304,7 @@ class SftpApi {
|
||||
return result
|
||||
}
|
||||
|
||||
async deleteBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async deleteBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { key } = configMap
|
||||
let result = false
|
||||
try {
|
||||
@@ -321,7 +321,7 @@ class SftpApi {
|
||||
return result
|
||||
}
|
||||
|
||||
async uploadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async uploadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { fileArray } = configMap
|
||||
const instance = UpDownTaskQueue.getInstance()
|
||||
for (const item of fileArray) {
|
||||
@@ -376,7 +376,7 @@ class SftpApi {
|
||||
return true
|
||||
}
|
||||
|
||||
async createBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async createBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { key } = configMap
|
||||
let result = false
|
||||
try {
|
||||
@@ -390,7 +390,7 @@ class SftpApi {
|
||||
return result
|
||||
}
|
||||
|
||||
async downloadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async downloadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { downloadPath, fileArray } = configMap
|
||||
const instance = UpDownTaskQueue.getInstance()
|
||||
for (const item of fileArray) {
|
||||
|
||||
@@ -21,7 +21,7 @@ class SmmsApi {
|
||||
logger: ManageLogger
|
||||
timeout = 30000
|
||||
|
||||
constructor (token: string, logger: ManageLogger) {
|
||||
constructor(token: string, logger: ManageLogger) {
|
||||
this.token = token
|
||||
this.axiosInstance = axios.create({
|
||||
baseURL: this.baseUrl,
|
||||
@@ -37,7 +37,7 @@ class SmmsApi {
|
||||
this.logger = logger
|
||||
}
|
||||
|
||||
formatFile (item: any) {
|
||||
formatFile(item: any) {
|
||||
return {
|
||||
...item,
|
||||
Key: item.path,
|
||||
@@ -54,7 +54,7 @@ class SmmsApi {
|
||||
}
|
||||
}
|
||||
|
||||
async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> {
|
||||
async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
const { cancelToken } = configMap
|
||||
let marker = 1
|
||||
@@ -123,7 +123,7 @@ class SmmsApi {
|
||||
* customUrl: string
|
||||
* }
|
||||
*/
|
||||
async getBucketFileList ({ currentPage }: IStringKeyMap): Promise<any> {
|
||||
async getBucketFileList({ currentPage }: IStringKeyMap): Promise<any> {
|
||||
const result = {
|
||||
fullList: [] as any,
|
||||
isTruncated: false,
|
||||
@@ -162,7 +162,7 @@ class SmmsApi {
|
||||
* DeleteHash: string
|
||||
* }
|
||||
*/
|
||||
async deleteBucketFile ({ DeleteHash }: IStringKeyMap): Promise<boolean> {
|
||||
async deleteBucketFile({ DeleteHash }: IStringKeyMap): Promise<boolean> {
|
||||
const res = await this.axiosInstance(`/delete/${DeleteHash}`, {
|
||||
method: 'GET',
|
||||
params: {
|
||||
@@ -177,7 +177,7 @@ class SmmsApi {
|
||||
* 上传文件
|
||||
* @param configMap
|
||||
*/
|
||||
async uploadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async uploadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { fileArray } = configMap
|
||||
const instance = UpDownTaskQueue.getInstance()
|
||||
for (const item of fileArray) {
|
||||
@@ -214,7 +214,7 @@ class SmmsApi {
|
||||
* 下载文件
|
||||
* @param configMap
|
||||
*/
|
||||
async downloadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async downloadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { downloadPath, fileArray, maxDownloadFileCount } = configMap
|
||||
const instance = UpDownTaskQueue.getInstance()
|
||||
const promises = [] as any
|
||||
|
||||
@@ -17,7 +17,7 @@ class TcyunApi {
|
||||
ctx: COS
|
||||
logger: ManageLogger
|
||||
|
||||
constructor (secretId: string, secretKey: string, logger: ManageLogger) {
|
||||
constructor(secretId: string, secretKey: string, logger: ManageLogger) {
|
||||
this.ctx = new COS({
|
||||
SecretId: secretId,
|
||||
SecretKey: secretKey
|
||||
@@ -25,7 +25,7 @@ class TcyunApi {
|
||||
this.logger = logger
|
||||
}
|
||||
|
||||
formatFolder (item: { Prefix: string }, slicedPrefix: string, urlPrefix: string) {
|
||||
formatFolder(item: { Prefix: string }, slicedPrefix: string, urlPrefix: string) {
|
||||
return {
|
||||
...item,
|
||||
key: item.Prefix,
|
||||
@@ -40,7 +40,7 @@ class TcyunApi {
|
||||
}
|
||||
}
|
||||
|
||||
formatFile (item: COS.CosObject, slicedPrefix: string, urlPrefix: string): any {
|
||||
formatFile(item: COS.CosObject, slicedPrefix: string, urlPrefix: string): any {
|
||||
return {
|
||||
...item,
|
||||
key: item.Key,
|
||||
@@ -58,7 +58,7 @@ class TcyunApi {
|
||||
/**
|
||||
* 获取存储桶列表
|
||||
*/
|
||||
async getBucketList (): Promise<any> {
|
||||
async getBucketList(): Promise<any> {
|
||||
const res = await this.ctx.getService({})
|
||||
return res?.Buckets || []
|
||||
}
|
||||
@@ -66,7 +66,7 @@ class TcyunApi {
|
||||
/**
|
||||
* 获取自定义域名
|
||||
*/
|
||||
async getBucketDomain (param: IStringKeyMap): Promise<any> {
|
||||
async getBucketDomain(param: IStringKeyMap): Promise<any> {
|
||||
const { bucketName, region } = param
|
||||
const res = await this.ctx.getBucketDomain({
|
||||
Bucket: bucketName,
|
||||
@@ -87,7 +87,7 @@ class TcyunApi {
|
||||
* @description
|
||||
* acl: private | publicRead | publicReadWrite
|
||||
*/
|
||||
async createBucket (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async createBucket(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const res = await this.ctx.putBucket({
|
||||
ACL: configMap.acl,
|
||||
Bucket: configMap.BucketName,
|
||||
@@ -96,7 +96,7 @@ class TcyunApi {
|
||||
return res?.statusCode === 200
|
||||
}
|
||||
|
||||
async getBucketListRecursively (configMap: IStringKeyMap): Promise<any> {
|
||||
async getBucketListRecursively(configMap: IStringKeyMap): Promise<any> {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
const {
|
||||
bucketName: bucket,
|
||||
@@ -150,7 +150,7 @@ class TcyunApi {
|
||||
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
|
||||
}
|
||||
|
||||
async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> {
|
||||
async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
const {
|
||||
bucketName: bucket,
|
||||
@@ -221,7 +221,7 @@ class TcyunApi {
|
||||
* customUrl: string
|
||||
* }
|
||||
*/
|
||||
async getBucketFileList (configMap: IStringKeyMap): Promise<any> {
|
||||
async getBucketFileList(configMap: IStringKeyMap): Promise<any> {
|
||||
const {
|
||||
bucketName: bucket,
|
||||
bucketConfig: { Location: region },
|
||||
@@ -272,7 +272,7 @@ class TcyunApi {
|
||||
* newKey: string
|
||||
* }
|
||||
*/
|
||||
async renameBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async renameBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { bucketName, region, oldKey, newKey } = configMap
|
||||
const copyRes = await this.ctx.putObjectCopy({
|
||||
Bucket: bucketName,
|
||||
@@ -301,7 +301,7 @@ class TcyunApi {
|
||||
* key: string
|
||||
* }
|
||||
*/
|
||||
async deleteBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async deleteBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { bucketName, region, key } = configMap
|
||||
const res = await this.ctx.deleteObject({
|
||||
Bucket: bucketName,
|
||||
@@ -315,7 +315,7 @@ class TcyunApi {
|
||||
* 删除文件夹
|
||||
* @param configMap
|
||||
*/
|
||||
async deleteBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async deleteBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { bucketName, region, key } = configMap
|
||||
let marker
|
||||
let res: any
|
||||
@@ -346,7 +346,9 @@ class TcyunApi {
|
||||
region,
|
||||
key: item.Prefix
|
||||
}))
|
||||
) { return false }
|
||||
) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
const cycles = Math.ceil(allFileList.Contents.length / 1000)
|
||||
for (let i = 0; i < cycles; i++) {
|
||||
@@ -371,7 +373,7 @@ class TcyunApi {
|
||||
* customUrl: string
|
||||
* }
|
||||
*/
|
||||
async getPreSignedUrl (configMap: IStringKeyMap): Promise<string> {
|
||||
async getPreSignedUrl(configMap: IStringKeyMap): Promise<string> {
|
||||
const { bucketName, region, key, expires, customUrl } = configMap
|
||||
const res = this.ctx.getObjectUrl(
|
||||
{
|
||||
@@ -390,7 +392,7 @@ class TcyunApi {
|
||||
* 高级上传文件
|
||||
* @param configMap
|
||||
*/
|
||||
async uploadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async uploadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { fileArray } = configMap
|
||||
// fileArray = [{
|
||||
// bucketName: string,
|
||||
@@ -470,7 +472,7 @@ class TcyunApi {
|
||||
* 新建文件夹
|
||||
* @param configMap
|
||||
*/
|
||||
async createBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async createBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { bucketName, region, key } = configMap
|
||||
const res = await this.ctx.putObject({
|
||||
Bucket: bucketName,
|
||||
@@ -485,7 +487,7 @@ class TcyunApi {
|
||||
* 下载文件
|
||||
* @param configMap
|
||||
*/
|
||||
async downloadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async downloadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { downloadPath, fileArray } = configMap
|
||||
// fileArray = [{
|
||||
// bucketName: string,
|
||||
|
||||
@@ -34,7 +34,7 @@ class UpyunApi {
|
||||
stopMarker = 'g2gCZAAEbmV4dGQAA2VvZg'
|
||||
logger: ManageLogger
|
||||
|
||||
constructor (
|
||||
constructor(
|
||||
bucket: string,
|
||||
operator: string,
|
||||
password: string,
|
||||
@@ -52,7 +52,7 @@ class UpyunApi {
|
||||
this.expireTime = expireTime || 24 * 60 * 60
|
||||
}
|
||||
|
||||
getAntiLeechParam (key: string): string {
|
||||
getAntiLeechParam(key: string): string {
|
||||
const uri = `/${key}`.replace(/%2F/g, '/').replace(/^\/+/g, '/')
|
||||
const now = Math.round(new Date().getTime() / 1000)
|
||||
const expire = this.expireTime ? now + parseInt(this.expireTime.toString(), 10) : now + 1800
|
||||
@@ -61,7 +61,7 @@ class UpyunApi {
|
||||
return `_upt=${upt}`
|
||||
}
|
||||
|
||||
formatFolder (item: any, slicedPrefix: string, urlPrefix: string) {
|
||||
formatFolder(item: any, slicedPrefix: string, urlPrefix: string) {
|
||||
const key = `${slicedPrefix}${item.name}/`
|
||||
let url = `${urlPrefix}/${key}`
|
||||
if (this.antiLeechToken) {
|
||||
@@ -82,7 +82,7 @@ class UpyunApi {
|
||||
}
|
||||
}
|
||||
|
||||
formatFile (item: any, slicedPrefix: string, urlPrefix: string) {
|
||||
formatFile(item: any, slicedPrefix: string, urlPrefix: string) {
|
||||
const key = `${slicedPrefix}${item.name}`
|
||||
let url = `${urlPrefix}/${key}`
|
||||
if (this.antiLeechToken) {
|
||||
@@ -102,7 +102,7 @@ class UpyunApi {
|
||||
}
|
||||
}
|
||||
|
||||
authorization (method: string, uri: string, contentMd5: string, operator: string, password: string) {
|
||||
authorization(method: string, uri: string, contentMd5: string, operator: string, password: string) {
|
||||
return `UPYUN ${operator}:${hmacSha1Base64(
|
||||
md5(password, 'hex'),
|
||||
`${method.toUpperCase()}&${encodeURI(uri)}&${new Date().toUTCString()}${contentMd5 ? `&${contentMd5}` : ''}`
|
||||
@@ -112,11 +112,11 @@ class UpyunApi {
|
||||
/**
|
||||
* 获取空间列表
|
||||
*/
|
||||
async getBucketList (): Promise<any> {
|
||||
async getBucketList(): Promise<any> {
|
||||
return this.bucket
|
||||
}
|
||||
|
||||
async getBucketListRecursively (configMap: IStringKeyMap): Promise<any> {
|
||||
async getBucketListRecursively(configMap: IStringKeyMap): Promise<any> {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
const { bucketName: bucket, prefix, cancelToken } = configMap
|
||||
const slicedPrefix = prefix.slice(1)
|
||||
@@ -168,7 +168,7 @@ class UpyunApi {
|
||||
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
|
||||
}
|
||||
|
||||
async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> {
|
||||
async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
const { bucketName: bucket, prefix, cancelToken } = configMap
|
||||
const slicedPrefix = prefix.slice(1)
|
||||
@@ -227,7 +227,7 @@ class UpyunApi {
|
||||
* customUrl: string
|
||||
* }
|
||||
*/
|
||||
async getBucketFileList (configMap: IStringKeyMap): Promise<any> {
|
||||
async getBucketFileList(configMap: IStringKeyMap): Promise<any> {
|
||||
const { bucketName: bucket, prefix, marker, itemsPerPage } = configMap
|
||||
const slicedPrefix = prefix.slice(1)
|
||||
const urlPrefix = configMap.customUrl || `http://${bucket}.test.upcdn.net`
|
||||
@@ -264,7 +264,7 @@ class UpyunApi {
|
||||
* newKey: string
|
||||
* }
|
||||
*/
|
||||
async renameBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async renameBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const oldKey = configMap.oldKey
|
||||
let newKey = configMap.newKey
|
||||
const method = 'PUT'
|
||||
@@ -297,7 +297,7 @@ class UpyunApi {
|
||||
* key: string
|
||||
* }
|
||||
*/
|
||||
async deleteBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async deleteBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { key } = configMap
|
||||
const res = await this.cli.deleteFile(key)
|
||||
return res
|
||||
@@ -307,7 +307,7 @@ class UpyunApi {
|
||||
* delete bucket folder
|
||||
* @param configMap
|
||||
*/
|
||||
async deleteBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async deleteBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { key } = configMap
|
||||
let marker = ''
|
||||
let isTruncated
|
||||
@@ -370,7 +370,7 @@ class UpyunApi {
|
||||
* axiso:onUploadProgress not work in nodejs , use got instead
|
||||
* @param configMap
|
||||
*/
|
||||
async uploadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async uploadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { fileArray } = configMap
|
||||
const instance = UpDownTaskQueue.getInstance()
|
||||
fileArray.forEach((item: any) => {
|
||||
@@ -426,7 +426,7 @@ class UpyunApi {
|
||||
* 新建文件夹
|
||||
* @param configMap
|
||||
*/
|
||||
async createBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async createBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { key } = configMap
|
||||
const res = await this.cli.makeDir(`/${key}`)
|
||||
return res
|
||||
@@ -436,7 +436,7 @@ class UpyunApi {
|
||||
* 下载文件
|
||||
* @param configMap
|
||||
*/
|
||||
async downloadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async downloadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { downloadPath, fileArray, maxDownloadFileCount } = configMap
|
||||
const instance = UpDownTaskQueue.getInstance()
|
||||
const promises = [] as any
|
||||
|
||||
@@ -28,7 +28,7 @@ class WebdavplistApi {
|
||||
agent: https.Agent | http.Agent
|
||||
ctx: WebDAVClient
|
||||
|
||||
constructor (
|
||||
constructor(
|
||||
endpoint: string,
|
||||
username: string,
|
||||
password: string,
|
||||
@@ -62,7 +62,7 @@ class WebdavplistApi {
|
||||
|
||||
logParam = (error: any, method: string) => this.logger.error(formatError(error, { class: 'WebdavplistApi', method }))
|
||||
|
||||
formatFolder (item: FileStat, urlPrefix: string, isWebPath = false) {
|
||||
formatFolder(item: FileStat, urlPrefix: string, isWebPath = false) {
|
||||
const key = item.filename.replace(/^\/+/, '')
|
||||
return {
|
||||
...item,
|
||||
@@ -79,7 +79,7 @@ class WebdavplistApi {
|
||||
}
|
||||
}
|
||||
|
||||
formatFile (item: FileStat, urlPrefix: string, isWebPath = false) {
|
||||
formatFile(item: FileStat, urlPrefix: string, isWebPath = false) {
|
||||
const key = item.filename.replace(/^\/+/, '')
|
||||
return {
|
||||
...item,
|
||||
@@ -98,7 +98,7 @@ class WebdavplistApi {
|
||||
|
||||
isRequestSuccess = (code: number) => code >= 200 && code < 300
|
||||
|
||||
async getBucketListRecursively (configMap: IStringKeyMap): Promise<any> {
|
||||
async getBucketListRecursively(configMap: IStringKeyMap): Promise<any> {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
const { prefix, customUrl, cancelToken } = configMap
|
||||
const urlPrefix = customUrl || this.endpoint
|
||||
@@ -138,7 +138,7 @@ class WebdavplistApi {
|
||||
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
|
||||
}
|
||||
|
||||
async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> {
|
||||
async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
const { prefix, customUrl, cancelToken, baseDir } = configMap
|
||||
let urlPrefix = customUrl || this.endpoint
|
||||
@@ -197,7 +197,7 @@ class WebdavplistApi {
|
||||
ipcMain.removeAllListeners('cancelLoadingFileList')
|
||||
}
|
||||
|
||||
async renameBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async renameBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { oldKey, newKey } = configMap
|
||||
let result = false
|
||||
try {
|
||||
@@ -209,7 +209,7 @@ class WebdavplistApi {
|
||||
return result
|
||||
}
|
||||
|
||||
async deleteBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async deleteBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { key } = configMap
|
||||
let result = false
|
||||
try {
|
||||
@@ -221,7 +221,7 @@ class WebdavplistApi {
|
||||
return result
|
||||
}
|
||||
|
||||
async deleteBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async deleteBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { key } = configMap
|
||||
let result = false
|
||||
try {
|
||||
@@ -233,7 +233,7 @@ class WebdavplistApi {
|
||||
return result
|
||||
}
|
||||
|
||||
async getPreSignedUrl (configMap: IStringKeyMap): Promise<string> {
|
||||
async getPreSignedUrl(configMap: IStringKeyMap): Promise<string> {
|
||||
const { key } = configMap
|
||||
let result = ''
|
||||
try {
|
||||
@@ -245,7 +245,7 @@ class WebdavplistApi {
|
||||
return result
|
||||
}
|
||||
|
||||
async uploadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async uploadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { fileArray } = configMap
|
||||
const instance = UpDownTaskQueue.getInstance()
|
||||
for (const item of fileArray) {
|
||||
@@ -306,7 +306,7 @@ class WebdavplistApi {
|
||||
return true
|
||||
}
|
||||
|
||||
async createBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async createBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { key } = configMap
|
||||
let result = false
|
||||
try {
|
||||
@@ -320,7 +320,7 @@ class WebdavplistApi {
|
||||
return result
|
||||
}
|
||||
|
||||
async downloadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async downloadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { downloadPath, fileArray, maxDownloadFileCount } = configMap
|
||||
const instance = UpDownTaskQueue.getInstance()
|
||||
const promises = [] as any
|
||||
|
||||
@@ -9,7 +9,7 @@ interface IJSON {
|
||||
class ManageDB {
|
||||
readonly #ctx: IManageApiType
|
||||
readonly #db: JSONStore
|
||||
constructor (ctx: IManageApiType) {
|
||||
constructor(ctx: IManageApiType) {
|
||||
this.#ctx = ctx
|
||||
this.#db = new JSONStore(this.#ctx.configPath)
|
||||
const initParams: IStringKeyMap = {
|
||||
@@ -28,37 +28,37 @@ class ManageDB {
|
||||
}
|
||||
}
|
||||
|
||||
read (flush?: boolean): IJSON {
|
||||
read(flush?: boolean): IJSON {
|
||||
return this.#db.read(flush)
|
||||
}
|
||||
|
||||
get (key: string = ''): any {
|
||||
get(key: string = ''): any {
|
||||
this.read(true)
|
||||
return this.#db.get(key)
|
||||
}
|
||||
|
||||
set (key: string, value: any): void {
|
||||
set(key: string, value: any): void {
|
||||
this.read(true)
|
||||
return this.#db.set(key, value)
|
||||
}
|
||||
|
||||
has (key: string): boolean {
|
||||
has(key: string): boolean {
|
||||
this.read(true)
|
||||
return this.#db.has(key)
|
||||
}
|
||||
|
||||
unset (key: string, value: any): boolean {
|
||||
unset(key: string, value: any): boolean {
|
||||
this.read(true)
|
||||
return this.#db.unset(key, value)
|
||||
}
|
||||
|
||||
saveConfig (config: Partial<IManageConfigType>): void {
|
||||
saveConfig(config: Partial<IManageConfigType>): void {
|
||||
Object.keys(config).forEach((name: string) => {
|
||||
this.set(name, config[name])
|
||||
})
|
||||
}
|
||||
|
||||
removeConfig (config: IManageConfigType): void {
|
||||
removeConfig(config: IManageConfigType): void {
|
||||
Object.keys(config).forEach((name: string) => {
|
||||
this.unset(name, config[name])
|
||||
})
|
||||
|
||||
@@ -21,7 +21,7 @@ const errorMsg = {
|
||||
brokenButBackup: $t('TIPS_PICGO_CONFIG_FILE_BROKEN_WITH_BACKUP')
|
||||
}
|
||||
|
||||
function manageDbChecker () {
|
||||
function manageDbChecker() {
|
||||
if (process.type !== 'renderer') {
|
||||
const manageConfigFilePath = managePathChecker()
|
||||
if (!fs.existsSync(manageConfigFilePath)) {
|
||||
@@ -72,7 +72,7 @@ function manageDbChecker () {
|
||||
/**
|
||||
* Get manage config path
|
||||
*/
|
||||
function managePathChecker (): string {
|
||||
function managePathChecker(): string {
|
||||
if (_configFilePath) {
|
||||
return _configFilePath
|
||||
}
|
||||
@@ -113,7 +113,7 @@ function managePathChecker (): string {
|
||||
}
|
||||
}
|
||||
|
||||
function managePathDir () {
|
||||
function managePathDir() {
|
||||
return path.dirname(managePathChecker())
|
||||
}
|
||||
|
||||
|
||||
@@ -18,38 +18,38 @@ class UpDownTaskQueue {
|
||||
|
||||
private persistPath = path.join(app.getPath('userData'), 'UpDownTaskQueue.json')
|
||||
|
||||
private constructor () {
|
||||
private constructor() {
|
||||
this.restore()
|
||||
}
|
||||
|
||||
static getInstance () {
|
||||
static getInstance() {
|
||||
if (!UpDownTaskQueue.instance) {
|
||||
UpDownTaskQueue.instance = new UpDownTaskQueue()
|
||||
}
|
||||
return UpDownTaskQueue.instance
|
||||
}
|
||||
|
||||
getUploadTaskQueue () {
|
||||
getUploadTaskQueue() {
|
||||
return UpDownTaskQueue.getInstance().uploadTaskQueue
|
||||
}
|
||||
|
||||
getDownloadTaskQueue () {
|
||||
getDownloadTaskQueue() {
|
||||
return UpDownTaskQueue.getInstance().downloadTaskQueue
|
||||
}
|
||||
|
||||
getUploadTask (taskId: string) {
|
||||
getUploadTask(taskId: string) {
|
||||
return UpDownTaskQueue.getInstance().uploadTaskQueue.find(item => item.id === taskId)
|
||||
}
|
||||
|
||||
getAllUploadTask () {
|
||||
getAllUploadTask() {
|
||||
return UpDownTaskQueue.getInstance().uploadTaskQueue
|
||||
}
|
||||
|
||||
addUploadTask (task: IUploadTask) {
|
||||
addUploadTask(task: IUploadTask) {
|
||||
UpDownTaskQueue.getInstance().uploadTaskQueue.push(task)
|
||||
}
|
||||
|
||||
updateUploadTask (task: Partial<IUploadTask>) {
|
||||
updateUploadTask(task: Partial<IUploadTask>) {
|
||||
const taskIndex = UpDownTaskQueue.getInstance().uploadTaskQueue.findIndex(item => item.id === task.id)
|
||||
if (taskIndex !== -1) {
|
||||
const taskKeys = Object.keys(task)
|
||||
@@ -61,33 +61,33 @@ class UpDownTaskQueue {
|
||||
}
|
||||
}
|
||||
|
||||
removeUploadTask (taskId: string) {
|
||||
removeUploadTask(taskId: string) {
|
||||
const taskIndex = UpDownTaskQueue.getInstance().uploadTaskQueue.findIndex(item => item.id === taskId)
|
||||
if (taskIndex !== -1) {
|
||||
UpDownTaskQueue.getInstance().uploadTaskQueue.splice(taskIndex, 1)
|
||||
}
|
||||
}
|
||||
|
||||
removeDownloadTask (taskId: string) {
|
||||
removeDownloadTask(taskId: string) {
|
||||
const taskIndex = UpDownTaskQueue.getInstance().downloadTaskQueue.findIndex(item => item.id === taskId)
|
||||
if (taskIndex !== -1) {
|
||||
UpDownTaskQueue.getInstance().downloadTaskQueue.splice(taskIndex, 1)
|
||||
}
|
||||
}
|
||||
|
||||
getDownloadTask (taskId: string) {
|
||||
getDownloadTask(taskId: string) {
|
||||
return UpDownTaskQueue.getInstance().downloadTaskQueue.find(item => item.id === taskId)
|
||||
}
|
||||
|
||||
getAllDownloadTask () {
|
||||
getAllDownloadTask() {
|
||||
return UpDownTaskQueue.getInstance().downloadTaskQueue
|
||||
}
|
||||
|
||||
addDownloadTask (task: IDownloadTask) {
|
||||
addDownloadTask(task: IDownloadTask) {
|
||||
UpDownTaskQueue.getInstance().downloadTaskQueue.push(task)
|
||||
}
|
||||
|
||||
updateDownloadTask (task: Partial<IDownloadTask>) {
|
||||
updateDownloadTask(task: Partial<IDownloadTask>) {
|
||||
const taskIndex = UpDownTaskQueue.getInstance().downloadTaskQueue.findIndex(item => item.id === task.id)
|
||||
if (taskIndex !== -1) {
|
||||
const taskKeys = Object.keys(task)
|
||||
@@ -99,11 +99,11 @@ class UpDownTaskQueue {
|
||||
}
|
||||
}
|
||||
|
||||
clearUploadTaskQueue () {
|
||||
clearUploadTaskQueue() {
|
||||
UpDownTaskQueue.getInstance().uploadTaskQueue = []
|
||||
}
|
||||
|
||||
removeUploadedTask () {
|
||||
removeUploadedTask() {
|
||||
UpDownTaskQueue.getInstance().uploadTaskQueue = UpDownTaskQueue.getInstance().uploadTaskQueue.filter(
|
||||
item =>
|
||||
item.status !== uploadTaskSpecialStatus.uploaded &&
|
||||
@@ -112,7 +112,7 @@ class UpDownTaskQueue {
|
||||
)
|
||||
}
|
||||
|
||||
removeDownloadedTask () {
|
||||
removeDownloadedTask() {
|
||||
UpDownTaskQueue.getInstance().downloadTaskQueue = UpDownTaskQueue.getInstance().downloadTaskQueue.filter(
|
||||
item =>
|
||||
item.status !== downloadTaskSpecialStatus.downloaded &&
|
||||
@@ -121,16 +121,16 @@ class UpDownTaskQueue {
|
||||
)
|
||||
}
|
||||
|
||||
clearDownloadTaskQueue () {
|
||||
clearDownloadTaskQueue() {
|
||||
UpDownTaskQueue.getInstance().downloadTaskQueue = []
|
||||
}
|
||||
|
||||
clearAllTaskQueue () {
|
||||
clearAllTaskQueue() {
|
||||
this.clearUploadTaskQueue()
|
||||
this.clearDownloadTaskQueue()
|
||||
}
|
||||
|
||||
persist () {
|
||||
persist() {
|
||||
try {
|
||||
this.checkPersistPath()
|
||||
fs.writeFileSync(
|
||||
@@ -145,7 +145,7 @@ class UpDownTaskQueue {
|
||||
}
|
||||
}
|
||||
|
||||
private restore () {
|
||||
private restore() {
|
||||
try {
|
||||
this.checkPersistPath()
|
||||
const persistData = JSON.parse(fs.readFileSync(this.persistPath, { encoding: 'utf-8' }))
|
||||
@@ -157,7 +157,7 @@ class UpDownTaskQueue {
|
||||
}
|
||||
}
|
||||
|
||||
private checkPersistPath () {
|
||||
private checkPersistPath() {
|
||||
if (!fs.existsSync(this.persistPath)) {
|
||||
fs.writeFileSync(
|
||||
this.persistPath,
|
||||
|
||||
@@ -26,7 +26,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
|
||||
logger: ManageLogger
|
||||
currentPicBedConfig: IPicBedMangeConfig
|
||||
|
||||
constructor (currentPicBed: string = '') {
|
||||
constructor(currentPicBed: string = '') {
|
||||
super()
|
||||
this.currentPicBed = currentPicBed || 'placeholder'
|
||||
this.configPath = managePathChecker()
|
||||
@@ -36,7 +36,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
|
||||
this.currentPicBedConfig = this.getPicBedConfig(this.currentPicBed)
|
||||
}
|
||||
|
||||
getMsgParam (method: string) {
|
||||
getMsgParam(method: string) {
|
||||
return {
|
||||
class: 'ManageApi',
|
||||
method,
|
||||
@@ -44,11 +44,11 @@ export class ManageApi extends EventEmitter implements IManageApiType {
|
||||
}
|
||||
}
|
||||
|
||||
errorMsg (err: any, param: IStringKeyMap) {
|
||||
errorMsg(err: any, param: IStringKeyMap) {
|
||||
this.logger.error(formatError(err, param))
|
||||
}
|
||||
|
||||
createClient () {
|
||||
createClient() {
|
||||
const name = this.currentPicBedConfig.picBedName
|
||||
switch (name) {
|
||||
case 'aliyun':
|
||||
@@ -127,11 +127,11 @@ export class ManageApi extends EventEmitter implements IManageApiType {
|
||||
}
|
||||
}
|
||||
|
||||
private getPicBedConfig (picBedName: string): IPicBedMangeConfig {
|
||||
private getPicBedConfig(picBedName: string): IPicBedMangeConfig {
|
||||
return this.getConfig<IPicBedMangeConfig>(`picBed.${picBedName}`)
|
||||
}
|
||||
|
||||
private initConfigPath (): void {
|
||||
private initConfigPath(): void {
|
||||
if (this.configPath === '') {
|
||||
this.configPath = `${homedir()}/.piclist/manage.json`
|
||||
}
|
||||
@@ -146,7 +146,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
|
||||
}
|
||||
}
|
||||
|
||||
private initconfig (): void {
|
||||
private initconfig(): void {
|
||||
this.db = new ManageDB(this)
|
||||
this._config = this.db.read(true) as IManageConfigType
|
||||
}
|
||||
@@ -158,7 +158,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
|
||||
return get(this._config, name)
|
||||
}
|
||||
|
||||
saveConfig (config: IStringKeyMap): void {
|
||||
saveConfig(config: IStringKeyMap): void {
|
||||
if (!isInputConfigValid(config)) {
|
||||
this.logger.warn('the format of config is invalid, please provide object')
|
||||
return
|
||||
@@ -167,7 +167,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
|
||||
this.db.saveConfig(config)
|
||||
}
|
||||
|
||||
removeConfig (key: string, propName: string): void {
|
||||
removeConfig(key: string, propName: string): void {
|
||||
if (!key || !propName) {
|
||||
return
|
||||
}
|
||||
@@ -175,7 +175,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
|
||||
this.db.unset(key, propName)
|
||||
}
|
||||
|
||||
setConfig (config: IStringKeyMap): void {
|
||||
setConfig(config: IStringKeyMap): void {
|
||||
if (!isInputConfigValid(config)) {
|
||||
this.logger.warn('the format of config is invalid, please provide object')
|
||||
return
|
||||
@@ -185,12 +185,12 @@ export class ManageApi extends EventEmitter implements IManageApiType {
|
||||
})
|
||||
}
|
||||
|
||||
unsetConfig (key: string, propName: string): void {
|
||||
unsetConfig(key: string, propName: string): void {
|
||||
if (!key || !propName) return
|
||||
unset(this.getConfig(key), propName)
|
||||
}
|
||||
|
||||
async getBucketList (param?: IStringKeyMap | undefined): Promise<any> {
|
||||
async getBucketList(param?: IStringKeyMap | undefined): Promise<any> {
|
||||
let client
|
||||
const name = this.currentPicBedConfig.picBedName.replace('plist', '')
|
||||
switch (this.currentPicBedConfig.picBedName) {
|
||||
@@ -232,12 +232,12 @@ export class ManageApi extends EventEmitter implements IManageApiType {
|
||||
}
|
||||
}
|
||||
|
||||
async getBucketInfo (param?: IStringKeyMap | undefined): Promise<IStringKeyMap | IManageError> {
|
||||
async getBucketInfo(param?: IStringKeyMap | undefined): Promise<IStringKeyMap | IManageError> {
|
||||
console.log(param)
|
||||
return {}
|
||||
}
|
||||
|
||||
async getBucketDomain (param: IStringKeyMap): Promise<IStringKeyMap | IManageError> {
|
||||
async getBucketDomain(param: IStringKeyMap): Promise<IStringKeyMap | IManageError> {
|
||||
let client
|
||||
switch (this.currentPicBedConfig.picBedName) {
|
||||
case 'tcyun':
|
||||
@@ -262,7 +262,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
|
||||
}
|
||||
}
|
||||
|
||||
async createBucket (param?: IStringKeyMap): Promise<boolean> {
|
||||
async createBucket(param?: IStringKeyMap): Promise<boolean> {
|
||||
let client
|
||||
switch (this.currentPicBedConfig.picBedName) {
|
||||
case 'tcyun':
|
||||
@@ -281,32 +281,32 @@ export class ManageApi extends EventEmitter implements IManageApiType {
|
||||
}
|
||||
}
|
||||
|
||||
async deleteBucket (param?: IStringKeyMap): Promise<boolean> {
|
||||
async deleteBucket(param?: IStringKeyMap): Promise<boolean> {
|
||||
console.log(param)
|
||||
return false
|
||||
}
|
||||
|
||||
async getOperatorList (param?: IStringKeyMap): Promise<string[] | IManageError> {
|
||||
async getOperatorList(param?: IStringKeyMap): Promise<string[] | IManageError> {
|
||||
console.log(param)
|
||||
return []
|
||||
}
|
||||
|
||||
async addOperator (param?: IStringKeyMap): Promise<boolean> {
|
||||
async addOperator(param?: IStringKeyMap): Promise<boolean> {
|
||||
console.log(param)
|
||||
return false
|
||||
}
|
||||
|
||||
async deleteOperator (param?: IStringKeyMap): Promise<boolean> {
|
||||
async deleteOperator(param?: IStringKeyMap): Promise<boolean> {
|
||||
console.log(param)
|
||||
return false
|
||||
}
|
||||
|
||||
async getBucketAclPolicy (param?: IStringKeyMap): Promise<IStringKeyMap | IManageError> {
|
||||
async getBucketAclPolicy(param?: IStringKeyMap): Promise<IStringKeyMap | IManageError> {
|
||||
console.log(param)
|
||||
return {}
|
||||
}
|
||||
|
||||
async setBucketAclPolicy (param?: IStringKeyMap): Promise<boolean> {
|
||||
async setBucketAclPolicy(param?: IStringKeyMap): Promise<boolean> {
|
||||
let client
|
||||
switch (this.currentPicBedConfig.picBedName) {
|
||||
case 'qiniu':
|
||||
@@ -322,7 +322,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
|
||||
}
|
||||
}
|
||||
|
||||
async getBucketListRecursively (param?: IStringKeyMap): Promise<IStringKeyMap | IManageError> {
|
||||
async getBucketListRecursively(param?: IStringKeyMap): Promise<IStringKeyMap | IManageError> {
|
||||
let client
|
||||
let window
|
||||
const defaultResult = {
|
||||
@@ -365,7 +365,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
|
||||
* @param param
|
||||
* @returns
|
||||
*/
|
||||
async getBucketListBackstage (param?: IStringKeyMap): Promise<IStringKeyMap | IManageError> {
|
||||
async getBucketListBackstage(param?: IStringKeyMap): Promise<IStringKeyMap | IManageError> {
|
||||
let client
|
||||
let window
|
||||
const defaultResult = {
|
||||
@@ -412,7 +412,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
|
||||
* isDir: 是否是文件夹
|
||||
* fileSize: 文件大小
|
||||
**/
|
||||
async getBucketFileList (param?: IStringKeyMap): Promise<IStringKeyMap | IManageError> {
|
||||
async getBucketFileList(param?: IStringKeyMap): Promise<IStringKeyMap | IManageError> {
|
||||
const defaultResponse = {
|
||||
fullList: [] as any,
|
||||
isTruncated: false,
|
||||
@@ -439,7 +439,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
|
||||
}
|
||||
}
|
||||
|
||||
async deleteBucketFile (param?: IStringKeyMap): Promise<boolean> {
|
||||
async deleteBucketFile(param?: IStringKeyMap): Promise<boolean> {
|
||||
let client
|
||||
switch (this.currentPicBedConfig.picBedName) {
|
||||
case 'tcyun':
|
||||
@@ -466,7 +466,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
|
||||
}
|
||||
}
|
||||
|
||||
async deleteBucketFolder (param?: IStringKeyMap): Promise<boolean> {
|
||||
async deleteBucketFolder(param?: IStringKeyMap): Promise<boolean> {
|
||||
let client
|
||||
switch (this.currentPicBedConfig.picBedName) {
|
||||
case 'tcyun':
|
||||
@@ -490,7 +490,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
|
||||
}
|
||||
}
|
||||
|
||||
async renameBucketFile (param?: IStringKeyMap): Promise<boolean> {
|
||||
async renameBucketFile(param?: IStringKeyMap): Promise<boolean> {
|
||||
let client
|
||||
switch (this.currentPicBedConfig.picBedName) {
|
||||
case 'tcyun':
|
||||
@@ -513,7 +513,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
|
||||
}
|
||||
}
|
||||
|
||||
async downloadBucketFile (param?: IStringKeyMap): Promise<boolean> {
|
||||
async downloadBucketFile(param?: IStringKeyMap): Promise<boolean> {
|
||||
let client
|
||||
switch (this.currentPicBedConfig.picBedName) {
|
||||
case 'tcyun':
|
||||
@@ -540,12 +540,12 @@ export class ManageApi extends EventEmitter implements IManageApiType {
|
||||
}
|
||||
}
|
||||
|
||||
async copyMoveBucketFile (param?: IStringKeyMap): Promise<boolean> {
|
||||
async copyMoveBucketFile(param?: IStringKeyMap): Promise<boolean> {
|
||||
console.log(param)
|
||||
return false
|
||||
}
|
||||
|
||||
async createBucketFolder (param?: IStringKeyMap): Promise<boolean> {
|
||||
async createBucketFolder(param?: IStringKeyMap): Promise<boolean> {
|
||||
let client
|
||||
switch (this.currentPicBedConfig.picBedName) {
|
||||
case 'tcyun':
|
||||
@@ -569,7 +569,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
|
||||
}
|
||||
}
|
||||
|
||||
async uploadBucketFile (param?: IStringKeyMap): Promise<boolean> {
|
||||
async uploadBucketFile(param?: IStringKeyMap): Promise<boolean> {
|
||||
let client
|
||||
switch (this.currentPicBedConfig.picBedName) {
|
||||
case 'tcyun':
|
||||
@@ -595,7 +595,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
|
||||
}
|
||||
}
|
||||
|
||||
async getPreSignedUrl (param?: IStringKeyMap): Promise<string> {
|
||||
async getPreSignedUrl(param?: IStringKeyMap): Promise<string> {
|
||||
let client
|
||||
switch (this.currentPicBedConfig.picBedName) {
|
||||
case 'tcyun':
|
||||
|
||||
@@ -34,7 +34,7 @@ export const getFSFile = async (filePath: string, stream: boolean = false): Prom
|
||||
}
|
||||
}
|
||||
|
||||
export function isInputConfigValid (config: any): boolean {
|
||||
export function isInputConfigValid(config: any): boolean {
|
||||
return typeof config === 'object' && !Array.isArray(config) && Object.keys(config).length > 0
|
||||
}
|
||||
|
||||
@@ -280,7 +280,7 @@ export const getInnerAgent = (proxy: any, sslEnabled: boolean = true) => {
|
||||
}
|
||||
}
|
||||
|
||||
export function getOptions (
|
||||
export function getOptions(
|
||||
method?: string,
|
||||
headers?: IStringKeyMap,
|
||||
searchParams?: IStringKeyMap,
|
||||
@@ -309,14 +309,14 @@ export class ConcurrencyPromisePool {
|
||||
runningNum: number
|
||||
results: any[]
|
||||
|
||||
constructor (limit: number) {
|
||||
constructor(limit: number) {
|
||||
this.limit = limit
|
||||
this.queue = []
|
||||
this.runningNum = 0
|
||||
this.results = []
|
||||
}
|
||||
|
||||
all (promises: any[] = []) {
|
||||
all(promises: any[] = []) {
|
||||
return new Promise((resolve, reject) => {
|
||||
for (const promise of promises) {
|
||||
this._run(promise, resolve, reject)
|
||||
@@ -324,7 +324,7 @@ export class ConcurrencyPromisePool {
|
||||
})
|
||||
}
|
||||
|
||||
_run (promise: any, resolve: any, reject: any) {
|
||||
_run(promise: any, resolve: any, reject: any) {
|
||||
if (this.runningNum >= this.limit) {
|
||||
this.queue.push(promise)
|
||||
return
|
||||
|
||||
@@ -12,7 +12,7 @@ export interface DogecloudToken {
|
||||
sessionToken: string
|
||||
}
|
||||
|
||||
export async function dogecloudApi (
|
||||
export async function dogecloudApi(
|
||||
apiPath: string,
|
||||
data = {},
|
||||
jsonMode: boolean = false,
|
||||
@@ -45,7 +45,7 @@ export async function dogecloudApi (
|
||||
}
|
||||
}
|
||||
|
||||
export async function getTempToken (accessKey: string, secretKey: string): Promise<IObj | DogecloudToken> {
|
||||
export async function getTempToken(accessKey: string, secretKey: string): Promise<IObj | DogecloudToken> {
|
||||
const dogeTempToken = (await picgo.getConfig('Credentials.doge-token')) || ({} as any)
|
||||
if (dogeTempToken.token && dogeTempToken.expires > Date.now() + 7200000) {
|
||||
return dogeTempToken.token
|
||||
|
||||
@@ -24,11 +24,11 @@ export class ManageLogger implements ILogger {
|
||||
#logLevel!: string
|
||||
#logPath!: string
|
||||
|
||||
constructor (ctx: IManageApiType) {
|
||||
constructor(ctx: IManageApiType) {
|
||||
this.#ctx = ctx
|
||||
}
|
||||
|
||||
#handleLog (type: string, ...msg: ILogArgvTypeWithError[]): void {
|
||||
#handleLog(type: string, ...msg: ILogArgvTypeWithError[]): void {
|
||||
const logHeader = chalk[this.#level[type] as ILogColor](`[PicList ${type.toUpperCase()}]`)
|
||||
console.log(logHeader, ...msg)
|
||||
this.#logLevel = this.#ctx.getConfig(configPaths.settings.logLevel)
|
||||
@@ -53,7 +53,7 @@ export class ManageLogger implements ILogger {
|
||||
}, 0)
|
||||
}
|
||||
|
||||
#checkLogFileIsLarge (logPath: string): {
|
||||
#checkLogFileIsLarge(logPath: string): {
|
||||
isLarge: boolean
|
||||
logFileSize?: number
|
||||
logFileSizeLimit?: number
|
||||
@@ -76,14 +76,14 @@ export class ManageLogger implements ILogger {
|
||||
}
|
||||
}
|
||||
|
||||
#recreateLogFile (logPath: string): void {
|
||||
#recreateLogFile(logPath: string): void {
|
||||
if (fs.existsSync(logPath)) {
|
||||
fs.unlinkSync(logPath)
|
||||
fs.createFileSync(logPath)
|
||||
}
|
||||
}
|
||||
|
||||
#handleWriteLog (logPath: string, type: string, ...msg: ILogArgvTypeWithError[]): void {
|
||||
#handleWriteLog(logPath: string, type: string, ...msg: ILogArgvTypeWithError[]): void {
|
||||
try {
|
||||
if (this.#checkLogLevel(type, this.#logLevel)) {
|
||||
let log = `${dayjs().format('YYYY-MM-DD HH:mm:ss')} [PicList ${type.toUpperCase()}] `
|
||||
@@ -98,7 +98,7 @@ export class ManageLogger implements ILogger {
|
||||
}
|
||||
}
|
||||
|
||||
#formatLogItem (item: ILogArgvTypeWithError, type: string): string {
|
||||
#formatLogItem(item: ILogArgvTypeWithError, type: string): string {
|
||||
let result = ''
|
||||
if (item instanceof Error && type === 'error') {
|
||||
result += `\n------Error Stack Begin------\n${util.format(item?.stack)}\n-------Error Stack End------- `
|
||||
@@ -114,7 +114,7 @@ export class ManageLogger implements ILogger {
|
||||
return result
|
||||
}
|
||||
|
||||
#checkLogLevel (type: string, level: undefined | string | string[]): boolean {
|
||||
#checkLogLevel(type: string, level: undefined | string | string[]): boolean {
|
||||
if (level === undefined || level === 'all') {
|
||||
return true
|
||||
}
|
||||
@@ -124,23 +124,23 @@ export class ManageLogger implements ILogger {
|
||||
return type === level
|
||||
}
|
||||
|
||||
success (...msq: ILogArgvType[]): void {
|
||||
success(...msq: ILogArgvType[]): void {
|
||||
return this.#handleLog(ILogType.success, ...msq)
|
||||
}
|
||||
|
||||
info (...msq: ILogArgvType[]): void {
|
||||
info(...msq: ILogArgvType[]): void {
|
||||
return this.#handleLog(ILogType.info, ...msq)
|
||||
}
|
||||
|
||||
error (...msq: ILogArgvTypeWithError[]): void {
|
||||
error(...msq: ILogArgvTypeWithError[]): void {
|
||||
return this.#handleLog(ILogType.error, ...msq)
|
||||
}
|
||||
|
||||
warn (...msq: ILogArgvType[]): void {
|
||||
warn(...msq: ILogArgvType[]): void {
|
||||
return this.#handleLog(ILogType.warn, ...msq)
|
||||
}
|
||||
|
||||
debug (...msq: ILogArgvType[]): void {
|
||||
debug(...msq: ILogArgvType[]): void {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
this.#handleLog(ILogType.info, ...msq)
|
||||
}
|
||||
|
||||
@@ -41,12 +41,12 @@ class Server {
|
||||
#httpServer: http.Server
|
||||
#config: IServerConfig
|
||||
|
||||
constructor () {
|
||||
constructor() {
|
||||
this.#config = this.getConfigWithDefaults()
|
||||
this.#httpServer = http.createServer(this.#handleRequest)
|
||||
}
|
||||
|
||||
getConfigWithDefaults () {
|
||||
getConfigWithDefaults() {
|
||||
let config = picgo.getConfig<IServerConfig>(configPaths.settings.server)
|
||||
if (!this.#isValidConfig(config)) {
|
||||
config = { port: DEFAULT_PORT, host: DEFAULT_HOST, enable: true }
|
||||
@@ -55,7 +55,7 @@ class Server {
|
||||
return config
|
||||
}
|
||||
|
||||
#isValidConfig (config: IObj | undefined) {
|
||||
#isValidConfig(config: IObj | undefined) {
|
||||
return config && config.port && config.host && config.enable !== undefined
|
||||
}
|
||||
|
||||
@@ -198,20 +198,20 @@ class Server {
|
||||
})
|
||||
}
|
||||
|
||||
startup () {
|
||||
startup() {
|
||||
if (this.#config.enable) {
|
||||
this.#listen(this.#config.port)
|
||||
}
|
||||
}
|
||||
|
||||
shutdown (hasStarted?: boolean) {
|
||||
shutdown(hasStarted?: boolean) {
|
||||
this.#httpServer.close()
|
||||
if (!hasStarted) {
|
||||
logger.info('[PicList Server] shutdown')
|
||||
}
|
||||
}
|
||||
|
||||
restart () {
|
||||
restart() {
|
||||
this.shutdown()
|
||||
this.#config = this.getConfigWithDefaults()
|
||||
this.startup()
|
||||
|
||||
@@ -5,27 +5,27 @@ type HttpMethod = 'GET' | 'POST'
|
||||
class Router {
|
||||
#router = new Map<string, Map<HttpMethod, { handler: routeHandler; urlparams?: URLSearchParams }>>()
|
||||
|
||||
#addRoute (method: HttpMethod, url: string, callback: routeHandler, urlparams?: URLSearchParams): void {
|
||||
#addRoute(method: HttpMethod, url: string, callback: routeHandler, urlparams?: URLSearchParams): void {
|
||||
if (!this.#router.has(url)) {
|
||||
this.#router.set(url, new Map())
|
||||
}
|
||||
this.#router.get(url)!.set(method, { handler: callback, urlparams })
|
||||
}
|
||||
|
||||
get (url: string, callback: routeHandler, urlparams?: URLSearchParams): void {
|
||||
get(url: string, callback: routeHandler, urlparams?: URLSearchParams): void {
|
||||
this.#addRoute('GET', url, callback, urlparams)
|
||||
}
|
||||
|
||||
post (url: string, callback: routeHandler, urlparams?: URLSearchParams): void {
|
||||
post(url: string, callback: routeHandler, urlparams?: URLSearchParams): void {
|
||||
this.#addRoute('POST', url, callback, urlparams)
|
||||
}
|
||||
|
||||
any (url: string, callback: routeHandler, urlparams?: URLSearchParams): void {
|
||||
any(url: string, callback: routeHandler, urlparams?: URLSearchParams): void {
|
||||
this.#addRoute('GET', url, callback, urlparams)
|
||||
this.#addRoute('POST', url, callback, urlparams)
|
||||
}
|
||||
|
||||
getHandler (url: string, method: HttpMethod) {
|
||||
getHandler(url: string, method: HttpMethod) {
|
||||
if (this.#router.has(url)) {
|
||||
const methods = this.#router.get(url)!
|
||||
if (methods.has(method)) {
|
||||
|
||||
@@ -1,234 +1,234 @@
|
||||
import http from 'node:http'
|
||||
import path from 'node:path'
|
||||
|
||||
import { dbPathDir } from '@core/datastore/dbChecker'
|
||||
import picgo from '@core/picgo'
|
||||
import logger from '@core/picgo/logger'
|
||||
import { uploadChoosedFiles, uploadClipboardFiles } from 'apis/app/uploader/apis'
|
||||
import windowManager from 'apis/app/window/windowManager'
|
||||
import { app } from 'electron'
|
||||
import fs from 'fs-extra'
|
||||
import { marked } from 'marked'
|
||||
|
||||
import type { IHttpResponse, IStringKeyMap } from '#/types/types'
|
||||
import { markdownContent } from '~/server/apiDoc'
|
||||
import router from '~/server/router'
|
||||
import { deleteChoosedFiles, handleResponse } from '~/server/utils'
|
||||
import { AESHelper } from '~/utils/aesHelper'
|
||||
import { configPaths } from '~/utils/configPaths'
|
||||
import { changeCurrentUploader } from '~/utils/handleUploaderConfig'
|
||||
|
||||
const appPath = app.getPath('userData')
|
||||
const serverTempDir = path.join(appPath, 'serverTemp')
|
||||
|
||||
const STORE_PATH = dbPathDir()
|
||||
const LOG_PATH = path.join(STORE_PATH, 'piclist.log')
|
||||
|
||||
const errorMessage = `upload error. see ${LOG_PATH} for more detail.`
|
||||
const deleteErrorMessage = `delete error. see ${LOG_PATH} for more detail.`
|
||||
|
||||
async function responseForGet ({ response }: { response: http.ServerResponse }) {
|
||||
response.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' })
|
||||
const htmlContent = marked(markdownContent)
|
||||
response.write(htmlContent)
|
||||
response.end()
|
||||
}
|
||||
|
||||
router.get('/', responseForGet)
|
||||
router.get('/upload', responseForGet)
|
||||
|
||||
router.post(
|
||||
'/upload',
|
||||
async ({
|
||||
response,
|
||||
list = [],
|
||||
urlparams
|
||||
}: {
|
||||
response: IHttpResponse
|
||||
list?: string[]
|
||||
urlparams?: URLSearchParams
|
||||
}): Promise<void> => {
|
||||
try {
|
||||
const picbed = urlparams?.get('picbed')
|
||||
const passedKey = urlparams?.get('key')
|
||||
const serverKey = picgo.getConfig<string>(configPaths.settings.serverKey) || ''
|
||||
const useShortUrl = picgo.getConfig<boolean>(configPaths.settings.useShortUrl)
|
||||
if (serverKey && passedKey !== serverKey) {
|
||||
handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: false,
|
||||
message: 'server key is uncorrect'
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
let currentPicBedType = ''
|
||||
let currentPicBedConfig = {} as IStringKeyMap
|
||||
let currentPicBedConfigId = ''
|
||||
let needRestore = false
|
||||
if (picbed) {
|
||||
const currentPicBed = picgo.getConfig<IStringKeyMap>('picBed') || ({} as IStringKeyMap)
|
||||
currentPicBedType = currentPicBed.uploader || currentPicBed.current || 'smms'
|
||||
currentPicBedConfig = currentPicBed[currentPicBedType] || ({} as IStringKeyMap)
|
||||
currentPicBedConfigId = currentPicBedConfig._id
|
||||
const configName = urlparams?.get('configName') || currentPicBed[picbed]?._configName
|
||||
if (picbed === currentPicBedType && configName === currentPicBedConfig._configName) {
|
||||
// do nothing
|
||||
} else {
|
||||
needRestore = true
|
||||
const picBeds = picgo.getConfig<IStringKeyMap>('uploader')
|
||||
const currentPicBedList = picBeds?.[picbed]?.configList
|
||||
if (currentPicBedList) {
|
||||
const currentConfig = currentPicBedList?.find((item: any) => item._configName === configName)
|
||||
if (currentConfig) {
|
||||
changeCurrentUploader(picbed, currentConfig, currentConfig._id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (list.length === 0) {
|
||||
// upload with clipboard
|
||||
logger.info('[PicList Server] upload clipboard file')
|
||||
const result = await uploadClipboardFiles()
|
||||
const res = useShortUrl ? result.fullResult.shortUrl || result.url : result.url
|
||||
const fullResult = result.fullResult
|
||||
fullResult.imgUrl = useShortUrl ? fullResult.shortUrl || fullResult.imgUrl : fullResult.imgUrl
|
||||
logger.info('[PicList Server] upload result:', res)
|
||||
if (res) {
|
||||
const treatedFullResult = {
|
||||
isEncrypted: 1,
|
||||
EncryptedData: new AESHelper().encrypt(JSON.stringify(fullResult)),
|
||||
...fullResult
|
||||
}
|
||||
delete treatedFullResult.config
|
||||
handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: true,
|
||||
result: [res],
|
||||
fullResult: [treatedFullResult]
|
||||
}
|
||||
})
|
||||
} else {
|
||||
handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: false,
|
||||
message: errorMessage
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
logger.info('[PicList Server] upload files in list')
|
||||
// upload with files
|
||||
const pathList = list.map(item => {
|
||||
return {
|
||||
path: item
|
||||
}
|
||||
})
|
||||
const win = windowManager.getAvailableWindow()
|
||||
const result = await uploadChoosedFiles(win.webContents, pathList)
|
||||
const res = result.map(item => {
|
||||
return useShortUrl ? item.fullResult.shortUrl || item.url : item.url
|
||||
})
|
||||
const fullResult = result.map((item: any) => {
|
||||
const treatedItem = {
|
||||
isEncrypted: 1,
|
||||
EncryptedData: new AESHelper().encrypt(JSON.stringify(item.fullResult)),
|
||||
...item.fullResult
|
||||
}
|
||||
delete treatedItem.config
|
||||
treatedItem.imgUrl = useShortUrl ? treatedItem.shortUrl || treatedItem.imgUrl : treatedItem.imgUrl
|
||||
return treatedItem
|
||||
})
|
||||
logger.info('[PicList Server] upload result', res.join(' ; '))
|
||||
if (res.length) {
|
||||
handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: true,
|
||||
result: res,
|
||||
fullResult
|
||||
}
|
||||
})
|
||||
} else {
|
||||
handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: false,
|
||||
message: errorMessage
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
fs.emptyDirSync(serverTempDir)
|
||||
if (needRestore) {
|
||||
changeCurrentUploader(currentPicBedType, currentPicBedConfig, currentPicBedConfigId)
|
||||
}
|
||||
} catch (err: any) {
|
||||
logger.error(err)
|
||||
handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: false,
|
||||
message: errorMessage
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/delete',
|
||||
async ({ response, list = [] }: { response: IHttpResponse; list?: IStringKeyMap[] }): Promise<void> => {
|
||||
if (list.length === 0) {
|
||||
handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: false,
|
||||
message: 'no file to delete'
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
try {
|
||||
const aesHelper = new AESHelper()
|
||||
const treatList = list.map(item => {
|
||||
if (!item.isEncrypted) return item
|
||||
return JSON.parse(aesHelper.decrypt(item.EncryptedData))
|
||||
})
|
||||
const result = await deleteChoosedFiles(treatList)
|
||||
const successCount = result.filter(item => item).length
|
||||
const failCount = result.length - successCount
|
||||
handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: !!successCount,
|
||||
message: successCount ? `delete success: ${successCount}, fail: ${failCount}` : deleteErrorMessage
|
||||
}
|
||||
})
|
||||
} catch (err: any) {
|
||||
logger.error(err)
|
||||
handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: false,
|
||||
message: deleteErrorMessage
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
router.any('/heartbeat', async ({ response }: { response: IHttpResponse }) => {
|
||||
handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: true,
|
||||
result: 'alive'
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
export default router
|
||||
import http from 'node:http'
|
||||
import path from 'node:path'
|
||||
|
||||
import { dbPathDir } from '@core/datastore/dbChecker'
|
||||
import picgo from '@core/picgo'
|
||||
import logger from '@core/picgo/logger'
|
||||
import { uploadChoosedFiles, uploadClipboardFiles } from 'apis/app/uploader/apis'
|
||||
import windowManager from 'apis/app/window/windowManager'
|
||||
import { app } from 'electron'
|
||||
import fs from 'fs-extra'
|
||||
import { marked } from 'marked'
|
||||
|
||||
import type { IHttpResponse, IStringKeyMap } from '#/types/types'
|
||||
import { markdownContent } from '~/server/apiDoc'
|
||||
import router from '~/server/router'
|
||||
import { deleteChoosedFiles, handleResponse } from '~/server/utils'
|
||||
import { AESHelper } from '~/utils/aesHelper'
|
||||
import { configPaths } from '~/utils/configPaths'
|
||||
import { changeCurrentUploader } from '~/utils/handleUploaderConfig'
|
||||
|
||||
const appPath = app.getPath('userData')
|
||||
const serverTempDir = path.join(appPath, 'serverTemp')
|
||||
|
||||
const STORE_PATH = dbPathDir()
|
||||
const LOG_PATH = path.join(STORE_PATH, 'piclist.log')
|
||||
|
||||
const errorMessage = `upload error. see ${LOG_PATH} for more detail.`
|
||||
const deleteErrorMessage = `delete error. see ${LOG_PATH} for more detail.`
|
||||
|
||||
async function responseForGet({ response }: { response: http.ServerResponse }) {
|
||||
response.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' })
|
||||
const htmlContent = marked(markdownContent)
|
||||
response.write(htmlContent)
|
||||
response.end()
|
||||
}
|
||||
|
||||
router.get('/', responseForGet)
|
||||
router.get('/upload', responseForGet)
|
||||
|
||||
router.post(
|
||||
'/upload',
|
||||
async ({
|
||||
response,
|
||||
list = [],
|
||||
urlparams
|
||||
}: {
|
||||
response: IHttpResponse
|
||||
list?: string[]
|
||||
urlparams?: URLSearchParams
|
||||
}): Promise<void> => {
|
||||
try {
|
||||
const picbed = urlparams?.get('picbed')
|
||||
const passedKey = urlparams?.get('key')
|
||||
const serverKey = picgo.getConfig<string>(configPaths.settings.serverKey) || ''
|
||||
const useShortUrl = picgo.getConfig<boolean>(configPaths.settings.useShortUrl)
|
||||
if (serverKey && passedKey !== serverKey) {
|
||||
handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: false,
|
||||
message: 'server key is uncorrect'
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
let currentPicBedType = ''
|
||||
let currentPicBedConfig = {} as IStringKeyMap
|
||||
let currentPicBedConfigId = ''
|
||||
let needRestore = false
|
||||
if (picbed) {
|
||||
const currentPicBed = picgo.getConfig<IStringKeyMap>('picBed') || ({} as IStringKeyMap)
|
||||
currentPicBedType = currentPicBed.uploader || currentPicBed.current || 'smms'
|
||||
currentPicBedConfig = currentPicBed[currentPicBedType] || ({} as IStringKeyMap)
|
||||
currentPicBedConfigId = currentPicBedConfig._id
|
||||
const configName = urlparams?.get('configName') || currentPicBed[picbed]?._configName
|
||||
if (picbed === currentPicBedType && configName === currentPicBedConfig._configName) {
|
||||
// do nothing
|
||||
} else {
|
||||
needRestore = true
|
||||
const picBeds = picgo.getConfig<IStringKeyMap>('uploader')
|
||||
const currentPicBedList = picBeds?.[picbed]?.configList
|
||||
if (currentPicBedList) {
|
||||
const currentConfig = currentPicBedList?.find((item: any) => item._configName === configName)
|
||||
if (currentConfig) {
|
||||
changeCurrentUploader(picbed, currentConfig, currentConfig._id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (list.length === 0) {
|
||||
// upload with clipboard
|
||||
logger.info('[PicList Server] upload clipboard file')
|
||||
const result = await uploadClipboardFiles()
|
||||
const res = useShortUrl ? result.fullResult.shortUrl || result.url : result.url
|
||||
const fullResult = result.fullResult
|
||||
fullResult.imgUrl = useShortUrl ? fullResult.shortUrl || fullResult.imgUrl : fullResult.imgUrl
|
||||
logger.info('[PicList Server] upload result:', res)
|
||||
if (res) {
|
||||
const treatedFullResult = {
|
||||
isEncrypted: 1,
|
||||
EncryptedData: new AESHelper().encrypt(JSON.stringify(fullResult)),
|
||||
...fullResult
|
||||
}
|
||||
delete treatedFullResult.config
|
||||
handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: true,
|
||||
result: [res],
|
||||
fullResult: [treatedFullResult]
|
||||
}
|
||||
})
|
||||
} else {
|
||||
handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: false,
|
||||
message: errorMessage
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
logger.info('[PicList Server] upload files in list')
|
||||
// upload with files
|
||||
const pathList = list.map(item => {
|
||||
return {
|
||||
path: item
|
||||
}
|
||||
})
|
||||
const win = windowManager.getAvailableWindow()
|
||||
const result = await uploadChoosedFiles(win.webContents, pathList)
|
||||
const res = result.map(item => {
|
||||
return useShortUrl ? item.fullResult.shortUrl || item.url : item.url
|
||||
})
|
||||
const fullResult = result.map((item: any) => {
|
||||
const treatedItem = {
|
||||
isEncrypted: 1,
|
||||
EncryptedData: new AESHelper().encrypt(JSON.stringify(item.fullResult)),
|
||||
...item.fullResult
|
||||
}
|
||||
delete treatedItem.config
|
||||
treatedItem.imgUrl = useShortUrl ? treatedItem.shortUrl || treatedItem.imgUrl : treatedItem.imgUrl
|
||||
return treatedItem
|
||||
})
|
||||
logger.info('[PicList Server] upload result', res.join(' ; '))
|
||||
if (res.length) {
|
||||
handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: true,
|
||||
result: res,
|
||||
fullResult
|
||||
}
|
||||
})
|
||||
} else {
|
||||
handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: false,
|
||||
message: errorMessage
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
fs.emptyDirSync(serverTempDir)
|
||||
if (needRestore) {
|
||||
changeCurrentUploader(currentPicBedType, currentPicBedConfig, currentPicBedConfigId)
|
||||
}
|
||||
} catch (err: any) {
|
||||
logger.error(err)
|
||||
handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: false,
|
||||
message: errorMessage
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/delete',
|
||||
async ({ response, list = [] }: { response: IHttpResponse; list?: IStringKeyMap[] }): Promise<void> => {
|
||||
if (list.length === 0) {
|
||||
handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: false,
|
||||
message: 'no file to delete'
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
try {
|
||||
const aesHelper = new AESHelper()
|
||||
const treatList = list.map(item => {
|
||||
if (!item.isEncrypted) return item
|
||||
return JSON.parse(aesHelper.decrypt(item.EncryptedData))
|
||||
})
|
||||
const result = await deleteChoosedFiles(treatList)
|
||||
const successCount = result.filter(item => item).length
|
||||
const failCount = result.length - successCount
|
||||
handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: !!successCount,
|
||||
message: successCount ? `delete success: ${successCount}, fail: ${failCount}` : deleteErrorMessage
|
||||
}
|
||||
})
|
||||
} catch (err: any) {
|
||||
logger.error(err)
|
||||
handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: false,
|
||||
message: deleteErrorMessage
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
router.any('/heartbeat', async ({ response }: { response: IHttpResponse }) => {
|
||||
handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: true,
|
||||
result: 'alive'
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
export default router
|
||||
|
||||
@@ -11,7 +11,7 @@ import { configPaths } from '~/utils/configPaths'
|
||||
|
||||
const defaultPath = process.platform === 'win32' ? 'C:\\Users' : '/'
|
||||
|
||||
function generateDirectoryListingHtml (files: any[], requestPath: any) {
|
||||
function generateDirectoryListingHtml(files: any[], requestPath: any) {
|
||||
let html = '<!DOCTYPE html><html><head><meta charset="UTF-8"></head><body><h1>Directory Listing</h1><ul>'
|
||||
files.forEach((file: string) => {
|
||||
const filePath = path.join(requestPath, file)
|
||||
@@ -21,7 +21,7 @@ function generateDirectoryListingHtml (files: any[], requestPath: any) {
|
||||
return html
|
||||
}
|
||||
|
||||
function serveDirectory (res: http.ServerResponse, filePath: fs.PathLike, requestPath: any) {
|
||||
function serveDirectory(res: http.ServerResponse, filePath: fs.PathLike, requestPath: any) {
|
||||
fs.readdir(filePath, (err, files) => {
|
||||
if (err) {
|
||||
res.writeHead(500)
|
||||
@@ -33,7 +33,7 @@ function serveDirectory (res: http.ServerResponse, filePath: fs.PathLike, reques
|
||||
})
|
||||
}
|
||||
|
||||
function serveFile (res: http.ServerResponse, filePath: fs.PathLike) {
|
||||
function serveFile(res: http.ServerResponse, filePath: fs.PathLike) {
|
||||
const readStream = fs.createReadStream(filePath)
|
||||
readStream.pipe(res)
|
||||
readStream.on('error', () => {
|
||||
@@ -46,12 +46,12 @@ class WebServer {
|
||||
#server!: http.Server
|
||||
#config!: IStringKeyMap
|
||||
|
||||
constructor () {
|
||||
constructor() {
|
||||
this.loadConfig()
|
||||
this.initServer()
|
||||
}
|
||||
|
||||
loadConfig (): void {
|
||||
loadConfig(): void {
|
||||
this.#config = {
|
||||
enableWebServer: picgo.getConfig<boolean>(configPaths.settings.enableWebServer) || false,
|
||||
webServerHost: picgo.getConfig<string>(configPaths.settings.webServerHost) || '0.0.0.0',
|
||||
@@ -60,7 +60,7 @@ class WebServer {
|
||||
}
|
||||
}
|
||||
|
||||
initServer (): void {
|
||||
initServer(): void {
|
||||
this.#server = http.createServer((req, res) => {
|
||||
const requestPath = req.url?.split('?')[0]
|
||||
const filePath = path.join(this.#config.webServerPath, decodeURIComponent(requestPath || ''))
|
||||
@@ -79,7 +79,7 @@ class WebServer {
|
||||
})
|
||||
}
|
||||
|
||||
start () {
|
||||
start() {
|
||||
if (this.#config.enableWebServer) {
|
||||
this.#server
|
||||
.listen(
|
||||
@@ -99,13 +99,13 @@ class WebServer {
|
||||
}
|
||||
}
|
||||
|
||||
stop () {
|
||||
stop() {
|
||||
this.#server.close(() => {
|
||||
logger.info('Web server is stopped')
|
||||
})
|
||||
}
|
||||
|
||||
restart () {
|
||||
restart() {
|
||||
this.stop()
|
||||
this.loadConfig()
|
||||
this.initServer()
|
||||
|
||||
@@ -1,75 +1,60 @@
|
||||
import crypto from 'node:crypto'
|
||||
|
||||
import picgo from '@core/picgo'
|
||||
|
||||
import { configPaths } from '~/utils/configPaths'
|
||||
|
||||
export class AESHelper {
|
||||
static readonly #SALT = Buffer.from('a8b3c4d2e4f5098712345678feedc0de', 'hex')
|
||||
static readonly #ITERATIONS = 100_000
|
||||
static readonly #KEYLEN = 32
|
||||
static readonly #DIGEST = 'sha512' as const
|
||||
static readonly #ALGO = 'aes-256-cbc'
|
||||
static readonly #IV_LENGTH = 16
|
||||
static readonly #SEP = ':'
|
||||
|
||||
static #keyCache = new Map<string, Buffer>()
|
||||
|
||||
readonly key: Buffer
|
||||
|
||||
constructor (password?: string) {
|
||||
const pwd =
|
||||
password ??
|
||||
picgo.getConfig<string>(configPaths.settings.aesPassword) ??
|
||||
'aesPassword'
|
||||
this.key = AESHelper.#deriveKey(pwd)
|
||||
}
|
||||
|
||||
static #deriveKey (password: string): Buffer {
|
||||
const cached = this.#keyCache.get(password)
|
||||
if (cached) return cached
|
||||
const key = crypto.pbkdf2Sync(
|
||||
password,
|
||||
this.#SALT,
|
||||
this.#ITERATIONS,
|
||||
this.#KEYLEN,
|
||||
this.#DIGEST
|
||||
)
|
||||
this.#keyCache.set(password, key)
|
||||
return key
|
||||
}
|
||||
|
||||
encrypt (plainText: string): string {
|
||||
const iv = crypto.randomBytes(AESHelper.#IV_LENGTH)
|
||||
const cipher = crypto.createCipheriv(AESHelper.#ALGO, this.key, iv)
|
||||
const encrypted = Buffer.concat([
|
||||
cipher.update(plainText, 'utf8'),
|
||||
cipher.final()
|
||||
])
|
||||
return `${iv.toString('hex')}${AESHelper.#SEP}${encrypted.toString('hex')}`
|
||||
}
|
||||
|
||||
decrypt (encryptedData: string): string {
|
||||
if (!encryptedData) return '{}'
|
||||
|
||||
const sepIndex = encryptedData.indexOf(AESHelper.#SEP)
|
||||
if (sepIndex <= 0) return '{}'
|
||||
|
||||
const ivHex = encryptedData.slice(0, sepIndex)
|
||||
const encryptedHex = encryptedData.slice(sepIndex + 1)
|
||||
|
||||
try {
|
||||
const iv = Buffer.from(ivHex, 'hex')
|
||||
if (iv.length !== AESHelper.#IV_LENGTH) return '{}'
|
||||
|
||||
const decipher = crypto.createDecipheriv(AESHelper.#ALGO, this.key, iv)
|
||||
const decrypted = Buffer.concat([
|
||||
decipher.update(Buffer.from(encryptedHex, 'hex')),
|
||||
decipher.final()
|
||||
])
|
||||
return decrypted.toString('utf8')
|
||||
} catch {
|
||||
return '{}'
|
||||
}
|
||||
}
|
||||
}
|
||||
import crypto from 'node:crypto'
|
||||
|
||||
import picgo from '@core/picgo'
|
||||
|
||||
import { configPaths } from '~/utils/configPaths'
|
||||
|
||||
export class AESHelper {
|
||||
static readonly #SALT = Buffer.from('a8b3c4d2e4f5098712345678feedc0de', 'hex')
|
||||
static readonly #ITERATIONS = 100_000
|
||||
static readonly #KEYLEN = 32
|
||||
static readonly #DIGEST = 'sha512' as const
|
||||
static readonly #ALGO = 'aes-256-cbc'
|
||||
static readonly #IV_LENGTH = 16
|
||||
static readonly #SEP = ':'
|
||||
|
||||
static #keyCache = new Map<string, Buffer>()
|
||||
|
||||
readonly key: Buffer
|
||||
|
||||
constructor(password?: string) {
|
||||
const pwd = password ?? picgo.getConfig<string>(configPaths.settings.aesPassword) ?? 'aesPassword'
|
||||
this.key = AESHelper.#deriveKey(pwd)
|
||||
}
|
||||
|
||||
static #deriveKey(password: string): Buffer {
|
||||
const cached = this.#keyCache.get(password)
|
||||
if (cached) return cached
|
||||
const key = crypto.pbkdf2Sync(password, this.#SALT, this.#ITERATIONS, this.#KEYLEN, this.#DIGEST)
|
||||
this.#keyCache.set(password, key)
|
||||
return key
|
||||
}
|
||||
|
||||
encrypt(plainText: string): string {
|
||||
const iv = crypto.randomBytes(AESHelper.#IV_LENGTH)
|
||||
const cipher = crypto.createCipheriv(AESHelper.#ALGO, this.key, iv)
|
||||
const encrypted = Buffer.concat([cipher.update(plainText, 'utf8'), cipher.final()])
|
||||
return `${iv.toString('hex')}${AESHelper.#SEP}${encrypted.toString('hex')}`
|
||||
}
|
||||
|
||||
decrypt(encryptedData: string): string {
|
||||
if (!encryptedData) return '{}'
|
||||
|
||||
const sepIndex = encryptedData.indexOf(AESHelper.#SEP)
|
||||
if (sepIndex <= 0) return '{}'
|
||||
|
||||
const ivHex = encryptedData.slice(0, sepIndex)
|
||||
const encryptedHex = encryptedData.slice(sepIndex + 1)
|
||||
|
||||
try {
|
||||
const iv = Buffer.from(ivHex, 'hex')
|
||||
if (iv.length !== AESHelper.#IV_LENGTH) return '{}'
|
||||
|
||||
const decipher = crypto.createDecipheriv(AESHelper.#ALGO, this.key, iv)
|
||||
const decrypted = Buffer.concat([decipher.update(Buffer.from(encryptedHex, 'hex')), decipher.final()])
|
||||
return decrypted.toString('utf8')
|
||||
} catch {
|
||||
return '{}'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ const configPath = dbPathChecker()
|
||||
const CONFIG_DIR = path.dirname(configPath)
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
function beforeOpen () {
|
||||
function beforeOpen() {
|
||||
if (process.platform === 'darwin') {
|
||||
resolveMacWorkFlow()
|
||||
}
|
||||
@@ -21,7 +21,7 @@ function beforeOpen () {
|
||||
resolveOtherI18nFiles()
|
||||
}
|
||||
|
||||
function copyFileOutsideOfElectronAsar (sourceInAsarArchive: string, destOutsideAsarArchive: string) {
|
||||
function copyFileOutsideOfElectronAsar(sourceInAsarArchive: string, destOutsideAsarArchive: string) {
|
||||
if (fs.existsSync(sourceInAsarArchive)) {
|
||||
// file will be copied
|
||||
if (fs.statSync(sourceInAsarArchive).isFile()) {
|
||||
@@ -45,16 +45,21 @@ function copyFileOutsideOfElectronAsar (sourceInAsarArchive: string, destOutside
|
||||
/**
|
||||
* macOS 右键菜单
|
||||
*/
|
||||
function resolveMacWorkFlow () {
|
||||
function resolveMacWorkFlow() {
|
||||
const dest = `${os.homedir()}/Library/Services/Upload pictures with PicList.workflow`
|
||||
try {
|
||||
copyFileOutsideOfElectronAsar(path.join(__dirname, '../../resources', 'Upload pictures with PicList.workflow').replace('app.asar', 'app.asar.unpacked'), dest)
|
||||
copyFileOutsideOfElectronAsar(
|
||||
path
|
||||
.join(__dirname, '../../resources', 'Upload pictures with PicList.workflow')
|
||||
.replace('app.asar', 'app.asar.unpacked'),
|
||||
dest
|
||||
)
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
|
||||
function diffFilesAndUpdate (filePath1: string, filePath2: string) {
|
||||
function diffFilesAndUpdate(filePath1: string, filePath2: string) {
|
||||
try {
|
||||
const file1 = fs.existsSync(filePath1) && fs.readFileSync(filePath1)
|
||||
const file2 = fs.existsSync(filePath1) && fs.readFileSync(filePath2)
|
||||
@@ -71,7 +76,7 @@ function diffFilesAndUpdate (filePath1: string, filePath2: string) {
|
||||
/**
|
||||
* 初始化剪贴板生成图片的脚本
|
||||
*/
|
||||
function resolveClipboardImageGenerator () {
|
||||
function resolveClipboardImageGenerator() {
|
||||
const clipboardFiles = getClipboardFiles()
|
||||
if (!fs.pathExistsSync(path.join(CONFIG_DIR, 'windows10.ps1'))) {
|
||||
clipboardFiles.forEach(item => {
|
||||
@@ -84,7 +89,7 @@ function resolveClipboardImageGenerator () {
|
||||
})
|
||||
}
|
||||
|
||||
function getClipboardFiles () {
|
||||
function getClipboardFiles() {
|
||||
const files = ['linux.sh', 'mac.applescript', 'windows.ps1', 'windows10.ps1', 'wsl.sh']
|
||||
|
||||
return files.map(item => {
|
||||
@@ -99,7 +104,7 @@ function resolveClipboardImageGenerator () {
|
||||
/**
|
||||
* 初始化其他语言文件
|
||||
*/
|
||||
function resolveOtherI18nFiles () {
|
||||
function resolveOtherI18nFiles() {
|
||||
const i18nFolder = path.join(CONFIG_DIR, 'i18n')
|
||||
if (!fs.pathExistsSync(i18nFolder)) {
|
||||
fs.mkdirSync(i18nFolder)
|
||||
|
||||
@@ -9,13 +9,13 @@ class ClipboardWatcher extends EventEmitter {
|
||||
timer: NodeJS.Timeout | null
|
||||
lastImageHash: string | null
|
||||
|
||||
constructor () {
|
||||
constructor() {
|
||||
super()
|
||||
this.lastImageHash = null
|
||||
this.timer = null
|
||||
}
|
||||
|
||||
startListening (watchDelay = 1000) {
|
||||
startListening(watchDelay = 1000) {
|
||||
this.stopListening(false)
|
||||
|
||||
this.timer = setInterval(() => {
|
||||
@@ -34,7 +34,7 @@ class ClipboardWatcher extends EventEmitter {
|
||||
logger.info('Start to watch clipboard')
|
||||
}
|
||||
|
||||
stopListening (isLog = true) {
|
||||
stopListening(isLog = true) {
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer)
|
||||
this.timer = null
|
||||
@@ -43,7 +43,7 @@ class ClipboardWatcher extends EventEmitter {
|
||||
isLog && logger.info('Stop to watch clipboard')
|
||||
}
|
||||
|
||||
getImageHash (image: NativeImage): string {
|
||||
getImageHash(image: NativeImage): string {
|
||||
const buffer = image.toBitmap()
|
||||
return crypto.createHash('md5').update(buffer).digest('hex')
|
||||
}
|
||||
|
||||
@@ -1,323 +1,323 @@
|
||||
import path from 'node:path'
|
||||
|
||||
import db from '@core/datastore'
|
||||
import logger from '@core/picgo/logger'
|
||||
import axios from 'axios'
|
||||
import { clipboard, Notification, Tray } from 'electron'
|
||||
import FormData from 'form-data'
|
||||
import fs from 'fs-extra'
|
||||
import { isReactive, isRef, toRaw, unref } from 'vue'
|
||||
|
||||
import type { IHTTPProxy, IPrivateShowNotificationOption, IStringKeyMap } from '#/types/types'
|
||||
import { configPaths } from '~/utils/configPaths'
|
||||
import { IShortUrlServer } from '~/utils/enum'
|
||||
|
||||
/**
|
||||
* get raw data from reactive or ref
|
||||
*/
|
||||
export const getRawData = (args: any): any => {
|
||||
if (isRef(args)) return unref(args)
|
||||
if (isReactive(args)) return toRaw(args)
|
||||
if (Array.isArray(args)) return args.map(getRawData)
|
||||
if (typeof args === 'object' && args !== null) {
|
||||
const data = {} as Record<string, any>
|
||||
for (const key in args) {
|
||||
data[key] = getRawData(args[key])
|
||||
}
|
||||
return data
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
const getExtension = (fileName: string) => path.extname(fileName).slice(1)
|
||||
|
||||
export const isImage = (fileName: string) =>
|
||||
['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'ico', 'svg', 'avif'].includes(getExtension(fileName))
|
||||
|
||||
export let tray: Tray
|
||||
|
||||
export const setTray = (t: Tray) => {
|
||||
tray = t
|
||||
}
|
||||
|
||||
export const getTray = () => tray
|
||||
|
||||
export function setTrayToolTip (title: string): void {
|
||||
if (tray) {
|
||||
tray.setToolTip(title)
|
||||
}
|
||||
}
|
||||
|
||||
export const handleCopyUrl = (str: string): void => {
|
||||
if (db.get(configPaths.settings.autoCopy) !== false) {
|
||||
clipboard.writeText(str)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* show notification
|
||||
* @param options
|
||||
*/
|
||||
export const showNotification = (
|
||||
options: IPrivateShowNotificationOption = {
|
||||
title: '',
|
||||
body: '',
|
||||
clickToCopy: false,
|
||||
copyContent: '',
|
||||
clickFn: () => {}
|
||||
}
|
||||
) => {
|
||||
const notification = new Notification({
|
||||
title: options.title,
|
||||
body: options.body
|
||||
})
|
||||
const handleClick = () => {
|
||||
if (options.clickToCopy) {
|
||||
clipboard.writeText(options.copyContent || options.body)
|
||||
}
|
||||
if (options.clickFn) {
|
||||
options.clickFn()
|
||||
}
|
||||
}
|
||||
notification.once('click', handleClick)
|
||||
notification.once('close', () => {
|
||||
notification.removeListener('click', handleClick)
|
||||
})
|
||||
notification.show()
|
||||
}
|
||||
|
||||
/**
|
||||
* macOS public.file-url will get encoded file path,
|
||||
* so we need to decode it
|
||||
*/
|
||||
export const ensureFilePath = (filePath: string, prefix = 'file://'): string => {
|
||||
filePath = filePath.replace(prefix, '')
|
||||
if (fs.existsSync(filePath)) {
|
||||
return `${prefix}${filePath}`
|
||||
}
|
||||
filePath = decodeURIComponent(filePath)
|
||||
if (fs.existsSync(filePath)) {
|
||||
return `${prefix}${filePath}`
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
/**
|
||||
* for builtin clipboard to get image path from clipboard
|
||||
* @returns
|
||||
*/
|
||||
export const getClipboardFilePath = (): string => {
|
||||
// TODO: linux support
|
||||
const img = clipboard.readImage()
|
||||
const platform = process.platform
|
||||
|
||||
if (!img.isEmpty() && platform === 'darwin') {
|
||||
let imgPath = clipboard.read('public.file-url') // will get file://xxx/xxx
|
||||
imgPath = ensureFilePath(imgPath)
|
||||
return imgPath ? imgPath.replace('file://', '') : ''
|
||||
}
|
||||
|
||||
if (img.isEmpty() && platform === 'win32') {
|
||||
const imgPath = clipboard
|
||||
.readBuffer('FileNameW')
|
||||
?.toString('ucs2')
|
||||
?.replace(RegExp(String.fromCharCode(0), 'g'), '')
|
||||
return imgPath || ''
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
|
||||
const c1nApi = 'https://c1n.cn/link/short'
|
||||
|
||||
const createC1NShortUrl = async (url: string) => {
|
||||
const c1nToken = db.get(configPaths.settings.c1nToken) || ''
|
||||
if (!c1nToken) {
|
||||
logger.warn('c1n token is not set')
|
||||
return url
|
||||
}
|
||||
try {
|
||||
const form = new FormData()
|
||||
form.append('url', url)
|
||||
const res = await axios.post(c1nApi, form, {
|
||||
headers: {
|
||||
token: c1nToken
|
||||
}
|
||||
})
|
||||
if (res.status >= 200 && res.status < 300 && res.data?.code === 0) {
|
||||
return res.data.data
|
||||
}
|
||||
} catch (e: any) {
|
||||
logger.error(e)
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
const createYOURLSShortLink = async (url: string) => {
|
||||
let domain = db.get(configPaths.settings.yourlsDomain) || ''
|
||||
const signature = db.get(configPaths.settings.yourlsSignature) || ''
|
||||
|
||||
if (!domain || !signature) {
|
||||
logger.warn('Yourls server or signature is not set')
|
||||
return url
|
||||
}
|
||||
if (!/^https?:\/\//.test(domain)) {
|
||||
domain = `http://${domain}`
|
||||
}
|
||||
const params = new URLSearchParams({
|
||||
signature,
|
||||
action: 'shorturl',
|
||||
format: 'json',
|
||||
url
|
||||
})
|
||||
try {
|
||||
const res = await axios.get(`${domain}/yourls-api.php?${params.toString()}`)
|
||||
if (res.data?.shorturl) {
|
||||
return res.data.shorturl
|
||||
}
|
||||
} catch (e: any) {
|
||||
if (e.response?.data?.message?.includes('already exists in database')) {
|
||||
return e.response.data.shorturl
|
||||
}
|
||||
logger.error(e)
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
const createShortUrlForCFWorker = async (url: string) => {
|
||||
let cfWorkerHost = db.get(configPaths.settings.cfWorkerHost) || ''
|
||||
cfWorkerHost = cfWorkerHost.replace(/\/$/, '')
|
||||
if (!cfWorkerHost) {
|
||||
logger.warn('CF Worker host is not set')
|
||||
return url
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await axios.post(cfWorkerHost, { url })
|
||||
if (res.data?.status === 200 && res.data?.key?.startsWith('/')) {
|
||||
return `${cfWorkerHost}${res.data.key}`
|
||||
}
|
||||
} catch (e: any) {
|
||||
logger.error(e)
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
const createShortUrlFromSink = async (url: string) => {
|
||||
let sinkDomain = db.get(configPaths.settings.sinkDomain) || ''
|
||||
const sinkToken = db.get(configPaths.settings.sinkToken) || ''
|
||||
if (!sinkDomain || !sinkToken) {
|
||||
logger.warn('Sink domain or token is not set')
|
||||
return url
|
||||
}
|
||||
if (!/^https?:\/\//.test(sinkDomain)) {
|
||||
sinkDomain = `http://${sinkDomain}`
|
||||
}
|
||||
if (sinkDomain.endsWith('/')) {
|
||||
sinkDomain = sinkDomain.slice(0, -1)
|
||||
}
|
||||
try {
|
||||
const res = await axios.post(
|
||||
`${sinkDomain}/api/link/create`,
|
||||
{ url },
|
||||
{ headers: { Authorization: `Bearer ${sinkToken}` } }
|
||||
)
|
||||
if (res.data?.link?.slug) {
|
||||
return `${sinkDomain}/${res.data.link.slug}`
|
||||
}
|
||||
} catch (e: any) {
|
||||
logger.error(e)
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
export const generateShortUrl = async (url: string) => {
|
||||
const server = db.get(configPaths.settings.shortUrlServer) || IShortUrlServer.C1N
|
||||
switch (server) {
|
||||
case IShortUrlServer.C1N:
|
||||
return createC1NShortUrl(url)
|
||||
case IShortUrlServer.YOURLS:
|
||||
return createYOURLSShortLink(url)
|
||||
case IShortUrlServer.CFWORKER:
|
||||
return createShortUrlForCFWorker(url)
|
||||
case IShortUrlServer.SINK:
|
||||
return createShortUrlFromSink(url)
|
||||
default:
|
||||
return url
|
||||
}
|
||||
}
|
||||
|
||||
export const isUrl = (url: string): boolean => {
|
||||
try {
|
||||
return Boolean(new URL(url))
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export const isUrlEncode = (url: string): boolean => {
|
||||
url = url || ''
|
||||
try {
|
||||
return url !== decodeURI(url)
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export const handleUrlEncode = (url: string): string => (isUrlEncode(url) ? url : encodeURI(url))
|
||||
|
||||
export const handleUrlEncodeWithSetting = (url: string) =>
|
||||
db.get(configPaths.settings.encodeOutputURL) ? handleUrlEncode(url) : url
|
||||
|
||||
export const handleStreamlinePluginName = (name: string) => name.replace(/(@[^/]+\/)?picgo-plugin-/, '')
|
||||
export const simpleClone = (obj: any) => JSON.parse(JSON.stringify(obj))
|
||||
export const enforceNumber = (num: number | string) => (isNaN(+num) ? 0 : +num)
|
||||
|
||||
export const trimValues = <T extends IStringKeyMap>(
|
||||
obj: T
|
||||
): { [K in keyof T]: T[K] extends string ? string : T[K] } => {
|
||||
return Object.fromEntries(
|
||||
Object.entries(obj).map(([key, value]) => [key, typeof value === 'string' ? value.trim() : value])
|
||||
) as { [K in keyof T]: T[K] extends string ? string : T[K] }
|
||||
}
|
||||
|
||||
export const formatEndpoint = (endpoint: string, sslEnabled: boolean): string => {
|
||||
const hasProtocol = /^https?:\/\//.test(endpoint)
|
||||
if (!hasProtocol) {
|
||||
return `${sslEnabled ? 'https' : 'http'}://${endpoint}`
|
||||
}
|
||||
return sslEnabled ? endpoint.replace(/^http:\/\//, 'https://') : endpoint.replace(/^https:\/\//, 'http://')
|
||||
}
|
||||
|
||||
export const formatHttpProxy = (
|
||||
proxy: string | undefined,
|
||||
type: 'object' | 'string'
|
||||
): IHTTPProxy | undefined | string => {
|
||||
if (!proxy) return undefined
|
||||
if (/^https?:\/\//.test(proxy)) {
|
||||
const { protocol, hostname, port } = new URL(proxy)
|
||||
return type === 'string'
|
||||
? `${protocol}//${hostname}:${port}`
|
||||
: {
|
||||
host: hostname,
|
||||
port: Number(port),
|
||||
protocol: protocol.slice(0, -1)
|
||||
}
|
||||
}
|
||||
const [host, port] = proxy.split(':')
|
||||
return type === 'string'
|
||||
? `http://${host}:${port}`
|
||||
: {
|
||||
host,
|
||||
port: port ? Number(port) : 80,
|
||||
protocol: 'http'
|
||||
}
|
||||
}
|
||||
|
||||
export function encodeFilePath (filePath: string) {
|
||||
return filePath.replace(/\\/g, '/').split('/').map(encodeURIComponent).join('/')
|
||||
}
|
||||
|
||||
export const trimPath = (path: string) => path.replace(/^\/+|\/+$/g, '').replace(/\/+/g, '/')
|
||||
import path from 'node:path'
|
||||
|
||||
import db from '@core/datastore'
|
||||
import logger from '@core/picgo/logger'
|
||||
import axios from 'axios'
|
||||
import { clipboard, Notification, Tray } from 'electron'
|
||||
import FormData from 'form-data'
|
||||
import fs from 'fs-extra'
|
||||
import { isReactive, isRef, toRaw, unref } from 'vue'
|
||||
|
||||
import type { IHTTPProxy, IPrivateShowNotificationOption, IStringKeyMap } from '#/types/types'
|
||||
import { configPaths } from '~/utils/configPaths'
|
||||
import { IShortUrlServer } from '~/utils/enum'
|
||||
|
||||
/**
|
||||
* get raw data from reactive or ref
|
||||
*/
|
||||
export const getRawData = (args: any): any => {
|
||||
if (isRef(args)) return unref(args)
|
||||
if (isReactive(args)) return toRaw(args)
|
||||
if (Array.isArray(args)) return args.map(getRawData)
|
||||
if (typeof args === 'object' && args !== null) {
|
||||
const data = {} as Record<string, any>
|
||||
for (const key in args) {
|
||||
data[key] = getRawData(args[key])
|
||||
}
|
||||
return data
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
const getExtension = (fileName: string) => path.extname(fileName).slice(1)
|
||||
|
||||
export const isImage = (fileName: string) =>
|
||||
['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'ico', 'svg', 'avif'].includes(getExtension(fileName))
|
||||
|
||||
export let tray: Tray
|
||||
|
||||
export const setTray = (t: Tray) => {
|
||||
tray = t
|
||||
}
|
||||
|
||||
export const getTray = () => tray
|
||||
|
||||
export function setTrayToolTip(title: string): void {
|
||||
if (tray) {
|
||||
tray.setToolTip(title)
|
||||
}
|
||||
}
|
||||
|
||||
export const handleCopyUrl = (str: string): void => {
|
||||
if (db.get(configPaths.settings.autoCopy) !== false) {
|
||||
clipboard.writeText(str)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* show notification
|
||||
* @param options
|
||||
*/
|
||||
export const showNotification = (
|
||||
options: IPrivateShowNotificationOption = {
|
||||
title: '',
|
||||
body: '',
|
||||
clickToCopy: false,
|
||||
copyContent: '',
|
||||
clickFn: () => {}
|
||||
}
|
||||
) => {
|
||||
const notification = new Notification({
|
||||
title: options.title,
|
||||
body: options.body
|
||||
})
|
||||
const handleClick = () => {
|
||||
if (options.clickToCopy) {
|
||||
clipboard.writeText(options.copyContent || options.body)
|
||||
}
|
||||
if (options.clickFn) {
|
||||
options.clickFn()
|
||||
}
|
||||
}
|
||||
notification.once('click', handleClick)
|
||||
notification.once('close', () => {
|
||||
notification.removeListener('click', handleClick)
|
||||
})
|
||||
notification.show()
|
||||
}
|
||||
|
||||
/**
|
||||
* macOS public.file-url will get encoded file path,
|
||||
* so we need to decode it
|
||||
*/
|
||||
export const ensureFilePath = (filePath: string, prefix = 'file://'): string => {
|
||||
filePath = filePath.replace(prefix, '')
|
||||
if (fs.existsSync(filePath)) {
|
||||
return `${prefix}${filePath}`
|
||||
}
|
||||
filePath = decodeURIComponent(filePath)
|
||||
if (fs.existsSync(filePath)) {
|
||||
return `${prefix}${filePath}`
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
/**
|
||||
* for builtin clipboard to get image path from clipboard
|
||||
* @returns
|
||||
*/
|
||||
export const getClipboardFilePath = (): string => {
|
||||
// TODO: linux support
|
||||
const img = clipboard.readImage()
|
||||
const platform = process.platform
|
||||
|
||||
if (!img.isEmpty() && platform === 'darwin') {
|
||||
let imgPath = clipboard.read('public.file-url') // will get file://xxx/xxx
|
||||
imgPath = ensureFilePath(imgPath)
|
||||
return imgPath ? imgPath.replace('file://', '') : ''
|
||||
}
|
||||
|
||||
if (img.isEmpty() && platform === 'win32') {
|
||||
const imgPath = clipboard
|
||||
.readBuffer('FileNameW')
|
||||
?.toString('ucs2')
|
||||
?.replace(RegExp(String.fromCharCode(0), 'g'), '')
|
||||
return imgPath || ''
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
|
||||
const c1nApi = 'https://c1n.cn/link/short'
|
||||
|
||||
const createC1NShortUrl = async (url: string) => {
|
||||
const c1nToken = db.get(configPaths.settings.c1nToken) || ''
|
||||
if (!c1nToken) {
|
||||
logger.warn('c1n token is not set')
|
||||
return url
|
||||
}
|
||||
try {
|
||||
const form = new FormData()
|
||||
form.append('url', url)
|
||||
const res = await axios.post(c1nApi, form, {
|
||||
headers: {
|
||||
token: c1nToken
|
||||
}
|
||||
})
|
||||
if (res.status >= 200 && res.status < 300 && res.data?.code === 0) {
|
||||
return res.data.data
|
||||
}
|
||||
} catch (e: any) {
|
||||
logger.error(e)
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
const createYOURLSShortLink = async (url: string) => {
|
||||
let domain = db.get(configPaths.settings.yourlsDomain) || ''
|
||||
const signature = db.get(configPaths.settings.yourlsSignature) || ''
|
||||
|
||||
if (!domain || !signature) {
|
||||
logger.warn('Yourls server or signature is not set')
|
||||
return url
|
||||
}
|
||||
if (!/^https?:\/\//.test(domain)) {
|
||||
domain = `http://${domain}`
|
||||
}
|
||||
const params = new URLSearchParams({
|
||||
signature,
|
||||
action: 'shorturl',
|
||||
format: 'json',
|
||||
url
|
||||
})
|
||||
try {
|
||||
const res = await axios.get(`${domain}/yourls-api.php?${params.toString()}`)
|
||||
if (res.data?.shorturl) {
|
||||
return res.data.shorturl
|
||||
}
|
||||
} catch (e: any) {
|
||||
if (e.response?.data?.message?.includes('already exists in database')) {
|
||||
return e.response.data.shorturl
|
||||
}
|
||||
logger.error(e)
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
const createShortUrlForCFWorker = async (url: string) => {
|
||||
let cfWorkerHost = db.get(configPaths.settings.cfWorkerHost) || ''
|
||||
cfWorkerHost = cfWorkerHost.replace(/\/$/, '')
|
||||
if (!cfWorkerHost) {
|
||||
logger.warn('CF Worker host is not set')
|
||||
return url
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await axios.post(cfWorkerHost, { url })
|
||||
if (res.data?.status === 200 && res.data?.key?.startsWith('/')) {
|
||||
return `${cfWorkerHost}${res.data.key}`
|
||||
}
|
||||
} catch (e: any) {
|
||||
logger.error(e)
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
const createShortUrlFromSink = async (url: string) => {
|
||||
let sinkDomain = db.get(configPaths.settings.sinkDomain) || ''
|
||||
const sinkToken = db.get(configPaths.settings.sinkToken) || ''
|
||||
if (!sinkDomain || !sinkToken) {
|
||||
logger.warn('Sink domain or token is not set')
|
||||
return url
|
||||
}
|
||||
if (!/^https?:\/\//.test(sinkDomain)) {
|
||||
sinkDomain = `http://${sinkDomain}`
|
||||
}
|
||||
if (sinkDomain.endsWith('/')) {
|
||||
sinkDomain = sinkDomain.slice(0, -1)
|
||||
}
|
||||
try {
|
||||
const res = await axios.post(
|
||||
`${sinkDomain}/api/link/create`,
|
||||
{ url },
|
||||
{ headers: { Authorization: `Bearer ${sinkToken}` } }
|
||||
)
|
||||
if (res.data?.link?.slug) {
|
||||
return `${sinkDomain}/${res.data.link.slug}`
|
||||
}
|
||||
} catch (e: any) {
|
||||
logger.error(e)
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
export const generateShortUrl = async (url: string) => {
|
||||
const server = db.get(configPaths.settings.shortUrlServer) || IShortUrlServer.C1N
|
||||
switch (server) {
|
||||
case IShortUrlServer.C1N:
|
||||
return createC1NShortUrl(url)
|
||||
case IShortUrlServer.YOURLS:
|
||||
return createYOURLSShortLink(url)
|
||||
case IShortUrlServer.CFWORKER:
|
||||
return createShortUrlForCFWorker(url)
|
||||
case IShortUrlServer.SINK:
|
||||
return createShortUrlFromSink(url)
|
||||
default:
|
||||
return url
|
||||
}
|
||||
}
|
||||
|
||||
export const isUrl = (url: string): boolean => {
|
||||
try {
|
||||
return Boolean(new URL(url))
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export const isUrlEncode = (url: string): boolean => {
|
||||
url = url || ''
|
||||
try {
|
||||
return url !== decodeURI(url)
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export const handleUrlEncode = (url: string): string => (isUrlEncode(url) ? url : encodeURI(url))
|
||||
|
||||
export const handleUrlEncodeWithSetting = (url: string) =>
|
||||
db.get(configPaths.settings.encodeOutputURL) ? handleUrlEncode(url) : url
|
||||
|
||||
export const handleStreamlinePluginName = (name: string) => name.replace(/(@[^/]+\/)?picgo-plugin-/, '')
|
||||
export const simpleClone = (obj: any) => JSON.parse(JSON.stringify(obj))
|
||||
export const enforceNumber = (num: number | string) => (isNaN(+num) ? 0 : +num)
|
||||
|
||||
export const trimValues = <T extends IStringKeyMap>(
|
||||
obj: T
|
||||
): { [K in keyof T]: T[K] extends string ? string : T[K] } => {
|
||||
return Object.fromEntries(
|
||||
Object.entries(obj).map(([key, value]) => [key, typeof value === 'string' ? value.trim() : value])
|
||||
) as { [K in keyof T]: T[K] extends string ? string : T[K] }
|
||||
}
|
||||
|
||||
export const formatEndpoint = (endpoint: string, sslEnabled: boolean): string => {
|
||||
const hasProtocol = /^https?:\/\//.test(endpoint)
|
||||
if (!hasProtocol) {
|
||||
return `${sslEnabled ? 'https' : 'http'}://${endpoint}`
|
||||
}
|
||||
return sslEnabled ? endpoint.replace(/^http:\/\//, 'https://') : endpoint.replace(/^https:\/\//, 'http://')
|
||||
}
|
||||
|
||||
export const formatHttpProxy = (
|
||||
proxy: string | undefined,
|
||||
type: 'object' | 'string'
|
||||
): IHTTPProxy | undefined | string => {
|
||||
if (!proxy) return undefined
|
||||
if (/^https?:\/\//.test(proxy)) {
|
||||
const { protocol, hostname, port } = new URL(proxy)
|
||||
return type === 'string'
|
||||
? `${protocol}//${hostname}:${port}`
|
||||
: {
|
||||
host: hostname,
|
||||
port: Number(port),
|
||||
protocol: protocol.slice(0, -1)
|
||||
}
|
||||
}
|
||||
const [host, port] = proxy.split(':')
|
||||
return type === 'string'
|
||||
? `http://${host}:${port}`
|
||||
: {
|
||||
host,
|
||||
port: port ? Number(port) : 80,
|
||||
protocol: 'http'
|
||||
}
|
||||
}
|
||||
|
||||
export function encodeFilePath(filePath: string) {
|
||||
return filePath.replace(/\\/g, '/').split('/').map(encodeURIComponent).join('/')
|
||||
}
|
||||
|
||||
export const trimPath = (path: string) => path.replace(/^\/+|\/+$/g, '').replace(/\/+/g, '/')
|
||||
|
||||
@@ -1,188 +1,206 @@
|
||||
import type { IBuildInCompressOptions, IBuildInWaterMarkOptions } from 'piclist'
|
||||
|
||||
import type { IAliYunConfig, IAwsS3PListUserConfig, IGitHubConfig, IImgurConfig, ILocalConfig, ILskyConfig, IPicBedType, IQiniuConfig, IServerConfig, ISftpPlistConfig, IShortKeyConfig, ISMMSConfig, ISyncConfig, ITcYunConfig, IUploaderConfig, IUpYunConfig, IWebdavPlistConfig } from '#/types/types'
|
||||
|
||||
export type manualPageOpenType = 'window' | 'browser'
|
||||
|
||||
interface IPicGoPlugins {
|
||||
[key: `picgo-plugin-${string}`]: boolean
|
||||
}
|
||||
|
||||
export interface IConfigStruct {
|
||||
picBed: {
|
||||
uploader: string
|
||||
current?: string
|
||||
smms?: ISMMSConfig
|
||||
qiniu?: IQiniuConfig
|
||||
upyun?: IUpYunConfig
|
||||
tcyun?: ITcYunConfig
|
||||
github?: IGitHubConfig
|
||||
aliyun?: IAliYunConfig
|
||||
imgur?: IImgurConfig
|
||||
webdavplist?: IWebdavPlistConfig
|
||||
local?: ILocalConfig
|
||||
sftpplist?: ISftpPlistConfig
|
||||
lskyplist?: ILskyConfig
|
||||
'aws-s3-plist': IAwsS3PListUserConfig
|
||||
proxy?: string
|
||||
transformer?: string
|
||||
list: IPicBedType[]
|
||||
[others: string]: any
|
||||
}
|
||||
settings: {
|
||||
shortKey: {
|
||||
[key: string]: IShortKeyConfig
|
||||
}
|
||||
logLevel: string[]
|
||||
logPath: string
|
||||
logFileSizeLimit: number
|
||||
isAutoListenClipboard: boolean
|
||||
isListeningClipboard: boolean
|
||||
showUpdateTip: boolean
|
||||
miniWindowPosition: [number, number]
|
||||
miniWindowOntop: boolean
|
||||
mainWindowWidth: number
|
||||
mainWindowHeight: number
|
||||
isHideDock: boolean
|
||||
autoCloseMiniWindow: boolean
|
||||
autoCloseMainWindow: boolean
|
||||
isCustomMiniIcon: boolean
|
||||
customMiniIcon: string
|
||||
startMode: string
|
||||
autoRename: boolean
|
||||
deleteCloudFile: boolean
|
||||
server: IServerConfig
|
||||
serverKey: string
|
||||
pasteStyle: string
|
||||
aesPassword: string
|
||||
rename: boolean
|
||||
sync: ISyncConfig
|
||||
tempDirPath: string
|
||||
language: string
|
||||
customLink: string
|
||||
manualPageOpen: manualPageOpenType
|
||||
encodeOutputURL: boolean
|
||||
useShortUrl: boolean
|
||||
shortUrlServer: string
|
||||
c1nToken: string
|
||||
cfWorkerHost: string
|
||||
yourlsDomain: string
|
||||
yourlsSignature: string
|
||||
sinkDomain: string
|
||||
sinkToken: string
|
||||
isSilentNotice: boolean
|
||||
proxy: string
|
||||
registry: string
|
||||
autoCopy: boolean
|
||||
enableWebServer: boolean
|
||||
webServerHost: string
|
||||
webServerPort: number
|
||||
webServerPath: string
|
||||
deleteLocalFile: boolean
|
||||
uploadResultNotification: boolean
|
||||
uploadNotification: boolean
|
||||
useBuiltinClipboard: boolean
|
||||
autoStart: boolean
|
||||
autoImport: boolean
|
||||
autoImportPicBed: string[]
|
||||
}
|
||||
needReload: boolean
|
||||
picgoPlugins: IPicGoPlugins
|
||||
uploader: IUploaderConfig
|
||||
buildIn: {
|
||||
compress: IBuildInCompressOptions
|
||||
watermark: IBuildInWaterMarkOptions
|
||||
rename: {
|
||||
enable: boolean
|
||||
format: string
|
||||
}
|
||||
skipProcess: {
|
||||
skipProcessExtList: string
|
||||
}
|
||||
}
|
||||
debug: boolean
|
||||
PICGO_ENV: string
|
||||
}
|
||||
|
||||
export const configPaths = {
|
||||
picBed: {
|
||||
current: 'picBed.current',
|
||||
uploader: 'picBed.uploader',
|
||||
secondUploader: 'picBed.secondUploader',
|
||||
secondUploaderId: 'picBed.secondUploaderId',
|
||||
secondUploaderConfig: 'picBed.secondUploaderConfig',
|
||||
proxy: 'picBed.proxy',
|
||||
transformer: 'picBed.transformer',
|
||||
list: 'picBed.list'
|
||||
},
|
||||
settings: {
|
||||
shortKey: {
|
||||
_path: 'settings.shortKey',
|
||||
'picgo:upload': 'settings.shortKey[picgo:upload]'
|
||||
},
|
||||
logLevel: 'settings.logLevel',
|
||||
logPath: 'settings.logPath',
|
||||
logFileSizeLimit: 'settings.logFileSizeLimit',
|
||||
isAutoListenClipboard: 'settings.isAutoListenClipboard',
|
||||
isListeningClipboard: 'settings.isListeningClipboard',
|
||||
showUpdateTip: 'settings.showUpdateTip',
|
||||
miniWindowPosition: 'settings.miniWindowPosition',
|
||||
miniWindowOntop: 'settings.miniWindowOntop',
|
||||
isHideDock: 'settings.isHideDock',
|
||||
mainWindowWidth: 'settings.mainWindowWidth',
|
||||
mainWindowHeight: 'settings.mainWindowHeight',
|
||||
autoCloseMiniWindow: 'settings.autoCloseMiniWindow',
|
||||
autoCloseMainWindow: 'settings.autoCloseMainWindow',
|
||||
isCustomMiniIcon: 'settings.isCustomMiniIcon',
|
||||
customMiniIcon: 'settings.customMiniIcon',
|
||||
startMode: 'settings.startMode',
|
||||
autoRename: 'settings.autoRename',
|
||||
deleteCloudFile: 'settings.deleteCloudFile',
|
||||
server: 'settings.server',
|
||||
serverKey: 'settings.serverKey',
|
||||
pasteStyle: 'settings.pasteStyle',
|
||||
aesPassword: 'settings.aesPassword',
|
||||
rename: 'settings.rename',
|
||||
sync: 'settings.sync',
|
||||
tempDirPath: 'settings.tempDirPath',
|
||||
language: 'settings.language',
|
||||
customLink: 'settings.customLink',
|
||||
manualPageOpen: 'settings.manualPageOpen',
|
||||
encodeOutputURL: 'settings.encodeOutputURL',
|
||||
useShortUrl: 'settings.useShortUrl',
|
||||
shortUrlServer: 'settings.shortUrlServer',
|
||||
c1nToken: 'settings.c1nToken',
|
||||
cfWorkerHost: 'settings.cfWorkerHost',
|
||||
yourlsDomain: 'settings.yourlsDomain',
|
||||
yourlsSignature: 'settings.yourlsSignature',
|
||||
sinkDomain: 'settings.sinkDomain',
|
||||
sinkToken: 'settings.sinkToken',
|
||||
isSilentNotice: 'settings.isSilentNotice',
|
||||
proxy: 'settings.proxy',
|
||||
registry: 'settings.registry',
|
||||
autoCopy: 'settings.autoCopy',
|
||||
enableWebServer: 'settings.enableWebServer',
|
||||
webServerHost: 'settings.webServerHost',
|
||||
webServerPort: 'settings.webServerPort',
|
||||
webServerPath: 'settings.webServerPath',
|
||||
deleteLocalFile: 'settings.deleteLocalFile',
|
||||
uploadResultNotification: 'settings.uploadResultNotification',
|
||||
uploadNotification: 'settings.uploadNotification',
|
||||
useBuiltinClipboard: 'settings.useBuiltinClipboard',
|
||||
autoStart: 'settings.autoStart',
|
||||
autoImport: 'settings.autoImport',
|
||||
autoImportPicBed: 'settings.autoImportPicBed',
|
||||
enableSecondUploader: 'settings.enableSecondUploader'
|
||||
},
|
||||
needReload: 'needReload',
|
||||
picgoPlugins: 'picgoPlugins',
|
||||
uploader: 'uploader',
|
||||
buildIn: {
|
||||
compress: 'buildIn.compress',
|
||||
watermark: 'buildIn.watermark',
|
||||
rename: 'buildIn.rename',
|
||||
skipProcess: 'buildIn.skipProcess'
|
||||
},
|
||||
debug: 'debug',
|
||||
PICGO_ENV: 'PICGO_ENV'
|
||||
}
|
||||
import type { IBuildInCompressOptions, IBuildInWaterMarkOptions } from 'piclist'
|
||||
|
||||
import type {
|
||||
IAliYunConfig,
|
||||
IAwsS3PListUserConfig,
|
||||
IGitHubConfig,
|
||||
IImgurConfig,
|
||||
ILocalConfig,
|
||||
ILskyConfig,
|
||||
IPicBedType,
|
||||
IQiniuConfig,
|
||||
IServerConfig,
|
||||
ISftpPlistConfig,
|
||||
IShortKeyConfig,
|
||||
ISMMSConfig,
|
||||
ISyncConfig,
|
||||
ITcYunConfig,
|
||||
IUploaderConfig,
|
||||
IUpYunConfig,
|
||||
IWebdavPlistConfig
|
||||
} from '#/types/types'
|
||||
|
||||
export type manualPageOpenType = 'window' | 'browser'
|
||||
|
||||
interface IPicGoPlugins {
|
||||
[key: `picgo-plugin-${string}`]: boolean
|
||||
}
|
||||
|
||||
export interface IConfigStruct {
|
||||
picBed: {
|
||||
uploader: string
|
||||
current?: string
|
||||
smms?: ISMMSConfig
|
||||
qiniu?: IQiniuConfig
|
||||
upyun?: IUpYunConfig
|
||||
tcyun?: ITcYunConfig
|
||||
github?: IGitHubConfig
|
||||
aliyun?: IAliYunConfig
|
||||
imgur?: IImgurConfig
|
||||
webdavplist?: IWebdavPlistConfig
|
||||
local?: ILocalConfig
|
||||
sftpplist?: ISftpPlistConfig
|
||||
lskyplist?: ILskyConfig
|
||||
'aws-s3-plist': IAwsS3PListUserConfig
|
||||
proxy?: string
|
||||
transformer?: string
|
||||
list: IPicBedType[]
|
||||
[others: string]: any
|
||||
}
|
||||
settings: {
|
||||
shortKey: {
|
||||
[key: string]: IShortKeyConfig
|
||||
}
|
||||
logLevel: string[]
|
||||
logPath: string
|
||||
logFileSizeLimit: number
|
||||
isAutoListenClipboard: boolean
|
||||
isListeningClipboard: boolean
|
||||
showUpdateTip: boolean
|
||||
miniWindowPosition: [number, number]
|
||||
miniWindowOntop: boolean
|
||||
mainWindowWidth: number
|
||||
mainWindowHeight: number
|
||||
isHideDock: boolean
|
||||
autoCloseMiniWindow: boolean
|
||||
autoCloseMainWindow: boolean
|
||||
isCustomMiniIcon: boolean
|
||||
customMiniIcon: string
|
||||
startMode: string
|
||||
autoRename: boolean
|
||||
deleteCloudFile: boolean
|
||||
server: IServerConfig
|
||||
serverKey: string
|
||||
pasteStyle: string
|
||||
aesPassword: string
|
||||
rename: boolean
|
||||
sync: ISyncConfig
|
||||
tempDirPath: string
|
||||
language: string
|
||||
customLink: string
|
||||
manualPageOpen: manualPageOpenType
|
||||
encodeOutputURL: boolean
|
||||
useShortUrl: boolean
|
||||
shortUrlServer: string
|
||||
c1nToken: string
|
||||
cfWorkerHost: string
|
||||
yourlsDomain: string
|
||||
yourlsSignature: string
|
||||
sinkDomain: string
|
||||
sinkToken: string
|
||||
isSilentNotice: boolean
|
||||
proxy: string
|
||||
registry: string
|
||||
autoCopy: boolean
|
||||
enableWebServer: boolean
|
||||
webServerHost: string
|
||||
webServerPort: number
|
||||
webServerPath: string
|
||||
deleteLocalFile: boolean
|
||||
uploadResultNotification: boolean
|
||||
uploadNotification: boolean
|
||||
useBuiltinClipboard: boolean
|
||||
autoStart: boolean
|
||||
autoImport: boolean
|
||||
autoImportPicBed: string[]
|
||||
}
|
||||
needReload: boolean
|
||||
picgoPlugins: IPicGoPlugins
|
||||
uploader: IUploaderConfig
|
||||
buildIn: {
|
||||
compress: IBuildInCompressOptions
|
||||
watermark: IBuildInWaterMarkOptions
|
||||
rename: {
|
||||
enable: boolean
|
||||
format: string
|
||||
}
|
||||
skipProcess: {
|
||||
skipProcessExtList: string
|
||||
}
|
||||
}
|
||||
debug: boolean
|
||||
PICGO_ENV: string
|
||||
}
|
||||
|
||||
export const configPaths = {
|
||||
picBed: {
|
||||
current: 'picBed.current',
|
||||
uploader: 'picBed.uploader',
|
||||
secondUploader: 'picBed.secondUploader',
|
||||
secondUploaderId: 'picBed.secondUploaderId',
|
||||
secondUploaderConfig: 'picBed.secondUploaderConfig',
|
||||
proxy: 'picBed.proxy',
|
||||
transformer: 'picBed.transformer',
|
||||
list: 'picBed.list'
|
||||
},
|
||||
settings: {
|
||||
shortKey: {
|
||||
_path: 'settings.shortKey',
|
||||
'picgo:upload': 'settings.shortKey[picgo:upload]'
|
||||
},
|
||||
logLevel: 'settings.logLevel',
|
||||
logPath: 'settings.logPath',
|
||||
logFileSizeLimit: 'settings.logFileSizeLimit',
|
||||
isAutoListenClipboard: 'settings.isAutoListenClipboard',
|
||||
isListeningClipboard: 'settings.isListeningClipboard',
|
||||
showUpdateTip: 'settings.showUpdateTip',
|
||||
miniWindowPosition: 'settings.miniWindowPosition',
|
||||
miniWindowOntop: 'settings.miniWindowOntop',
|
||||
isHideDock: 'settings.isHideDock',
|
||||
mainWindowWidth: 'settings.mainWindowWidth',
|
||||
mainWindowHeight: 'settings.mainWindowHeight',
|
||||
autoCloseMiniWindow: 'settings.autoCloseMiniWindow',
|
||||
autoCloseMainWindow: 'settings.autoCloseMainWindow',
|
||||
isCustomMiniIcon: 'settings.isCustomMiniIcon',
|
||||
customMiniIcon: 'settings.customMiniIcon',
|
||||
startMode: 'settings.startMode',
|
||||
autoRename: 'settings.autoRename',
|
||||
deleteCloudFile: 'settings.deleteCloudFile',
|
||||
server: 'settings.server',
|
||||
serverKey: 'settings.serverKey',
|
||||
pasteStyle: 'settings.pasteStyle',
|
||||
aesPassword: 'settings.aesPassword',
|
||||
rename: 'settings.rename',
|
||||
sync: 'settings.sync',
|
||||
tempDirPath: 'settings.tempDirPath',
|
||||
language: 'settings.language',
|
||||
customLink: 'settings.customLink',
|
||||
manualPageOpen: 'settings.manualPageOpen',
|
||||
encodeOutputURL: 'settings.encodeOutputURL',
|
||||
useShortUrl: 'settings.useShortUrl',
|
||||
shortUrlServer: 'settings.shortUrlServer',
|
||||
c1nToken: 'settings.c1nToken',
|
||||
cfWorkerHost: 'settings.cfWorkerHost',
|
||||
yourlsDomain: 'settings.yourlsDomain',
|
||||
yourlsSignature: 'settings.yourlsSignature',
|
||||
sinkDomain: 'settings.sinkDomain',
|
||||
sinkToken: 'settings.sinkToken',
|
||||
isSilentNotice: 'settings.isSilentNotice',
|
||||
proxy: 'settings.proxy',
|
||||
registry: 'settings.registry',
|
||||
autoCopy: 'settings.autoCopy',
|
||||
enableWebServer: 'settings.enableWebServer',
|
||||
webServerHost: 'settings.webServerHost',
|
||||
webServerPort: 'settings.webServerPort',
|
||||
webServerPath: 'settings.webServerPath',
|
||||
deleteLocalFile: 'settings.deleteLocalFile',
|
||||
uploadResultNotification: 'settings.uploadResultNotification',
|
||||
uploadNotification: 'settings.uploadNotification',
|
||||
useBuiltinClipboard: 'settings.useBuiltinClipboard',
|
||||
autoStart: 'settings.autoStart',
|
||||
autoImport: 'settings.autoImport',
|
||||
autoImportPicBed: 'settings.autoImportPicBed',
|
||||
enableSecondUploader: 'settings.enableSecondUploader'
|
||||
},
|
||||
needReload: 'needReload',
|
||||
picgoPlugins: 'picgoPlugins',
|
||||
uploader: 'uploader',
|
||||
buildIn: {
|
||||
compress: 'buildIn.compress',
|
||||
watermark: 'buildIn.watermark',
|
||||
rename: 'buildIn.rename',
|
||||
skipProcess: 'buildIn.skipProcess'
|
||||
},
|
||||
debug: 'debug',
|
||||
PICGO_ENV: 'PICGO_ENV'
|
||||
}
|
||||
|
||||
@@ -1,98 +1,98 @@
|
||||
import crypto from 'node:crypto'
|
||||
import http, { AgentOptions } from 'node:http'
|
||||
import https from 'node:https'
|
||||
import path from 'node:path'
|
||||
import querystring from 'node:querystring'
|
||||
|
||||
import type { S3ClientConfig } from '@aws-sdk/client-s3'
|
||||
import { DeleteObjectCommand, S3Client } from '@aws-sdk/client-s3'
|
||||
import logger from '@core/picgo/logger'
|
||||
import { NodeHttpHandler } from '@smithy/node-http-handler'
|
||||
import axios from 'axios'
|
||||
import type { ISftpPlistConfig } from 'piclist'
|
||||
|
||||
import type { IObj, IStringKeyMap } from '#/types/types'
|
||||
import { getAgent } from '~/manage/utils/common'
|
||||
import SSHClient from '~/utils/sshClient'
|
||||
|
||||
interface DogecloudTokenFull {
|
||||
Credentials: {
|
||||
accessKeyId: string
|
||||
secretAccessKey: string
|
||||
sessionToken: string
|
||||
}
|
||||
ExpiredAt: number
|
||||
Buckets: {
|
||||
name: string
|
||||
s3Bucket: string
|
||||
s3Endpoint: string
|
||||
}[]
|
||||
}
|
||||
|
||||
const dogeRegionMap: IStringKeyMap = {
|
||||
'ap-shanghai': '0',
|
||||
'ap-beijing': '1',
|
||||
'ap-guangzhou': '2',
|
||||
'ap-chengdu': '3'
|
||||
}
|
||||
|
||||
async function dogecloudApi (
|
||||
apiPath: string,
|
||||
data = {},
|
||||
jsonMode: boolean = false,
|
||||
accessKey: string,
|
||||
secretKey: string
|
||||
) {
|
||||
const body = jsonMode ? JSON.stringify(data) : querystring.encode(data)
|
||||
const sign = crypto
|
||||
.createHmac('sha1', secretKey)
|
||||
.update(Buffer.from(apiPath + '\n' + body, 'utf8'))
|
||||
.digest('hex')
|
||||
const authorization = `TOKEN ${accessKey}:${sign}`
|
||||
try {
|
||||
const res = await axios.request({
|
||||
url: `https://api.dogecloud.com${apiPath}`,
|
||||
method: 'POST',
|
||||
data: body,
|
||||
responseType: 'json',
|
||||
headers: {
|
||||
'Content-Type': jsonMode ? 'application/json' : 'application/x-www-form-urlencoded',
|
||||
Authorization: authorization
|
||||
}
|
||||
})
|
||||
if (res.data.code !== 200) {
|
||||
throw new Error('API Error')
|
||||
}
|
||||
return res.data.data
|
||||
} catch (err: any) {
|
||||
throw new Error('API Error')
|
||||
}
|
||||
}
|
||||
|
||||
async function getDogeToken (accessKey: string, secretKey: string): Promise<IObj | DogecloudTokenFull> {
|
||||
try {
|
||||
const data = await dogecloudApi(
|
||||
'/auth/tmp_token.json',
|
||||
{
|
||||
channel: 'OSS_FULL',
|
||||
scopes: ['*']
|
||||
},
|
||||
true,
|
||||
accessKey,
|
||||
secretKey
|
||||
)
|
||||
return data
|
||||
} catch (err: any) {
|
||||
logger.error(err)
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
export async function removeFileFromS3InMain (configMap: IStringKeyMap, dogeMode: boolean = false) {
|
||||
try {
|
||||
const {
|
||||
url: rawUrl,
|
||||
type,
|
||||
import crypto from 'node:crypto'
|
||||
import http, { AgentOptions } from 'node:http'
|
||||
import https from 'node:https'
|
||||
import path from 'node:path'
|
||||
import querystring from 'node:querystring'
|
||||
|
||||
import type { S3ClientConfig } from '@aws-sdk/client-s3'
|
||||
import { DeleteObjectCommand, S3Client } from '@aws-sdk/client-s3'
|
||||
import logger from '@core/picgo/logger'
|
||||
import { NodeHttpHandler } from '@smithy/node-http-handler'
|
||||
import axios from 'axios'
|
||||
import type { ISftpPlistConfig } from 'piclist'
|
||||
|
||||
import type { IObj, IStringKeyMap } from '#/types/types'
|
||||
import { getAgent } from '~/manage/utils/common'
|
||||
import SSHClient from '~/utils/sshClient'
|
||||
|
||||
interface DogecloudTokenFull {
|
||||
Credentials: {
|
||||
accessKeyId: string
|
||||
secretAccessKey: string
|
||||
sessionToken: string
|
||||
}
|
||||
ExpiredAt: number
|
||||
Buckets: {
|
||||
name: string
|
||||
s3Bucket: string
|
||||
s3Endpoint: string
|
||||
}[]
|
||||
}
|
||||
|
||||
const dogeRegionMap: IStringKeyMap = {
|
||||
'ap-shanghai': '0',
|
||||
'ap-beijing': '1',
|
||||
'ap-guangzhou': '2',
|
||||
'ap-chengdu': '3'
|
||||
}
|
||||
|
||||
async function dogecloudApi(
|
||||
apiPath: string,
|
||||
data = {},
|
||||
jsonMode: boolean = false,
|
||||
accessKey: string,
|
||||
secretKey: string
|
||||
) {
|
||||
const body = jsonMode ? JSON.stringify(data) : querystring.encode(data)
|
||||
const sign = crypto
|
||||
.createHmac('sha1', secretKey)
|
||||
.update(Buffer.from(apiPath + '\n' + body, 'utf8'))
|
||||
.digest('hex')
|
||||
const authorization = `TOKEN ${accessKey}:${sign}`
|
||||
try {
|
||||
const res = await axios.request({
|
||||
url: `https://api.dogecloud.com${apiPath}`,
|
||||
method: 'POST',
|
||||
data: body,
|
||||
responseType: 'json',
|
||||
headers: {
|
||||
'Content-Type': jsonMode ? 'application/json' : 'application/x-www-form-urlencoded',
|
||||
Authorization: authorization
|
||||
}
|
||||
})
|
||||
if (res.data.code !== 200) {
|
||||
throw new Error('API Error')
|
||||
}
|
||||
return res.data.data
|
||||
} catch (err: any) {
|
||||
throw new Error('API Error')
|
||||
}
|
||||
}
|
||||
|
||||
async function getDogeToken(accessKey: string, secretKey: string): Promise<IObj | DogecloudTokenFull> {
|
||||
try {
|
||||
const data = await dogecloudApi(
|
||||
'/auth/tmp_token.json',
|
||||
{
|
||||
channel: 'OSS_FULL',
|
||||
scopes: ['*']
|
||||
},
|
||||
true,
|
||||
accessKey,
|
||||
secretKey
|
||||
)
|
||||
return data
|
||||
} catch (err: any) {
|
||||
logger.error(err)
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
export async function removeFileFromS3InMain(configMap: IStringKeyMap, dogeMode: boolean = false) {
|
||||
try {
|
||||
const {
|
||||
url: rawUrl,
|
||||
type,
|
||||
config: {
|
||||
accessKeyID,
|
||||
secretAccessKey,
|
||||
@@ -103,14 +103,14 @@ export async function removeFileFromS3InMain (configMap: IStringKeyMap, dogeMode
|
||||
proxy,
|
||||
urlPrefix
|
||||
}
|
||||
} = configMap
|
||||
let {
|
||||
imgUrl,
|
||||
config: { region }
|
||||
} = configMap
|
||||
if (type === 'aws-s3' || type === 'aws-s3-plist') {
|
||||
imgUrl = rawUrl || imgUrl || ''
|
||||
}
|
||||
} = configMap
|
||||
let {
|
||||
imgUrl,
|
||||
config: { region }
|
||||
} = configMap
|
||||
if (type === 'aws-s3' || type === 'aws-s3-plist') {
|
||||
imgUrl = rawUrl || imgUrl || ''
|
||||
}
|
||||
let fileKey
|
||||
if (urlPrefix && imgUrl.startsWith(urlPrefix)) {
|
||||
const urlPrefixObj = new URL(urlPrefix)
|
||||
@@ -126,159 +126,159 @@ export async function removeFileFromS3InMain (configMap: IStringKeyMap, dogeMode
|
||||
if (pathStyleAccess) {
|
||||
fileKey = fileKey.replace(/^[^/]+\//, '')
|
||||
}
|
||||
}
|
||||
const endpointUrl: string | undefined = endpoint
|
||||
? /^https?:\/\//.test(endpoint)
|
||||
? endpoint
|
||||
: `http://${endpoint}`
|
||||
: undefined
|
||||
if (endpointUrl && endpointUrl.includes('cloudflarestorage')) {
|
||||
region = region || 'auto'
|
||||
}
|
||||
const sslEnabled = endpointUrl ? endpointUrl.startsWith('https') : true
|
||||
const agent = getAgent(proxy, sslEnabled)
|
||||
const commonOptions: AgentOptions = {
|
||||
keepAlive: true,
|
||||
keepAliveMsecs: 1000,
|
||||
scheduling: 'lifo' as 'lifo' | 'fifo' | undefined
|
||||
}
|
||||
const extraOptions = sslEnabled ? { rejectUnauthorized: !!rejectUnauthorized } : {}
|
||||
const handler = sslEnabled
|
||||
? new NodeHttpHandler({
|
||||
httpsAgent: agent.https
|
||||
? agent.https
|
||||
: new https.Agent({
|
||||
...commonOptions,
|
||||
...extraOptions
|
||||
})
|
||||
})
|
||||
: new NodeHttpHandler({
|
||||
httpAgent: agent.http
|
||||
? agent.http
|
||||
: new http.Agent({
|
||||
...commonOptions,
|
||||
...extraOptions
|
||||
})
|
||||
})
|
||||
const s3Options: S3ClientConfig = {
|
||||
credentials: {
|
||||
accessKeyId: accessKeyID,
|
||||
secretAccessKey
|
||||
},
|
||||
endpoint: endpointUrl,
|
||||
tls: sslEnabled,
|
||||
forcePathStyle: pathStyleAccess,
|
||||
region,
|
||||
requestHandler: handler
|
||||
}
|
||||
if (dogeMode) {
|
||||
s3Options.credentials = {
|
||||
accessKeyId: configMap.config.accessKeyID,
|
||||
secretAccessKey: configMap.config.secretAccessKey,
|
||||
sessionToken: configMap.config.sessionToken
|
||||
}
|
||||
}
|
||||
let result: any
|
||||
try {
|
||||
fileKey = decodeURIComponent(fileKey)
|
||||
} catch (err: any) {}
|
||||
try {
|
||||
const client = new S3Client(s3Options)
|
||||
const command = new DeleteObjectCommand({
|
||||
Bucket: bucketName,
|
||||
Key: fileKey
|
||||
})
|
||||
result = await client.send(command)
|
||||
} catch (err: any) {
|
||||
s3Options.region = 'us-east-1'
|
||||
const client = new S3Client(s3Options)
|
||||
const command = new DeleteObjectCommand({
|
||||
Bucket: bucketName,
|
||||
Key: fileKey
|
||||
})
|
||||
result = await client.send(command)
|
||||
}
|
||||
return result.$metadata.httpStatusCode === 204
|
||||
} catch (err: any) {
|
||||
logger.error(err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export async function removeFileFromDogeInMain (configMap: IStringKeyMap) {
|
||||
try {
|
||||
const {
|
||||
config: { bucketName, AccessKey, SecretKey }
|
||||
} = configMap
|
||||
const token = (await getDogeToken(AccessKey, SecretKey)) as DogecloudTokenFull
|
||||
const bucket = token.Buckets?.find(item => item.name === bucketName || item.s3Bucket === bucketName)
|
||||
const newConfigMap = { ...configMap }
|
||||
newConfigMap.config = {
|
||||
...newConfigMap.config,
|
||||
accessKeyID: token.Credentials?.accessKeyId,
|
||||
secretAccessKey: token.Credentials?.secretAccessKey,
|
||||
sessionToken: token.Credentials?.sessionToken,
|
||||
endpoint: bucket?.s3Endpoint,
|
||||
region: dogeRegionMap[bucket?.s3Endpoint?.split('.')[1] || 'ap-shanghai'],
|
||||
bucketName: bucket?.s3Bucket
|
||||
}
|
||||
return await removeFileFromS3InMain(newConfigMap, true)
|
||||
} catch (err: any) {
|
||||
logger.error(err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function createHuaweiAuthorization (
|
||||
bucketName: string,
|
||||
path: string,
|
||||
fileName: string,
|
||||
accessKey: string,
|
||||
secretKey: string,
|
||||
date: string = new Date().toUTCString()
|
||||
) {
|
||||
const strToSign = `DELETE\n\n\n${date}\n/${bucketName}${path}/${fileName}`
|
||||
const singature = crypto.createHmac('sha1', secretKey).update(strToSign).digest('base64')
|
||||
return `OBS ${accessKey}:${singature}`
|
||||
}
|
||||
|
||||
export async function removeFileFromHuaweiInMain (configMap: IStringKeyMap) {
|
||||
const { fileName, config } = configMap
|
||||
const { accessKeyId, accessKeySecret, bucketName, endpoint } = config
|
||||
let path = config.path || '/'
|
||||
path = `/${path.replace(/^\/+|\/+$/, '')}`
|
||||
path = path === '/' ? '' : path
|
||||
const date = new Date().toUTCString()
|
||||
const authorization = createHuaweiAuthorization(bucketName, path, fileName, accessKeyId, accessKeySecret, date)
|
||||
try {
|
||||
const res = await axios.request({
|
||||
url: `https://${bucketName}.${endpoint}${encodeURI(path)}/${encodeURIComponent(fileName)}`,
|
||||
method: 'DELETE',
|
||||
responseType: 'json',
|
||||
headers: {
|
||||
Host: `${bucketName}.${endpoint}`,
|
||||
Date: date,
|
||||
Authorization: authorization
|
||||
}
|
||||
})
|
||||
return res.status === 204
|
||||
} catch (error: any) {
|
||||
logger.error(error)
|
||||
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) {
|
||||
logger.error(err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
const endpointUrl: string | undefined = endpoint
|
||||
? /^https?:\/\//.test(endpoint)
|
||||
? endpoint
|
||||
: `http://${endpoint}`
|
||||
: undefined
|
||||
if (endpointUrl && endpointUrl.includes('cloudflarestorage')) {
|
||||
region = region || 'auto'
|
||||
}
|
||||
const sslEnabled = endpointUrl ? endpointUrl.startsWith('https') : true
|
||||
const agent = getAgent(proxy, sslEnabled)
|
||||
const commonOptions: AgentOptions = {
|
||||
keepAlive: true,
|
||||
keepAliveMsecs: 1000,
|
||||
scheduling: 'lifo' as 'lifo' | 'fifo' | undefined
|
||||
}
|
||||
const extraOptions = sslEnabled ? { rejectUnauthorized: !!rejectUnauthorized } : {}
|
||||
const handler = sslEnabled
|
||||
? new NodeHttpHandler({
|
||||
httpsAgent: agent.https
|
||||
? agent.https
|
||||
: new https.Agent({
|
||||
...commonOptions,
|
||||
...extraOptions
|
||||
})
|
||||
})
|
||||
: new NodeHttpHandler({
|
||||
httpAgent: agent.http
|
||||
? agent.http
|
||||
: new http.Agent({
|
||||
...commonOptions,
|
||||
...extraOptions
|
||||
})
|
||||
})
|
||||
const s3Options: S3ClientConfig = {
|
||||
credentials: {
|
||||
accessKeyId: accessKeyID,
|
||||
secretAccessKey
|
||||
},
|
||||
endpoint: endpointUrl,
|
||||
tls: sslEnabled,
|
||||
forcePathStyle: pathStyleAccess,
|
||||
region,
|
||||
requestHandler: handler
|
||||
}
|
||||
if (dogeMode) {
|
||||
s3Options.credentials = {
|
||||
accessKeyId: configMap.config.accessKeyID,
|
||||
secretAccessKey: configMap.config.secretAccessKey,
|
||||
sessionToken: configMap.config.sessionToken
|
||||
}
|
||||
}
|
||||
let result: any
|
||||
try {
|
||||
fileKey = decodeURIComponent(fileKey)
|
||||
} catch (err: any) {}
|
||||
try {
|
||||
const client = new S3Client(s3Options)
|
||||
const command = new DeleteObjectCommand({
|
||||
Bucket: bucketName,
|
||||
Key: fileKey
|
||||
})
|
||||
result = await client.send(command)
|
||||
} catch (err: any) {
|
||||
s3Options.region = 'us-east-1'
|
||||
const client = new S3Client(s3Options)
|
||||
const command = new DeleteObjectCommand({
|
||||
Bucket: bucketName,
|
||||
Key: fileKey
|
||||
})
|
||||
result = await client.send(command)
|
||||
}
|
||||
return result.$metadata.httpStatusCode === 204
|
||||
} catch (err: any) {
|
||||
logger.error(err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export async function removeFileFromDogeInMain(configMap: IStringKeyMap) {
|
||||
try {
|
||||
const {
|
||||
config: { bucketName, AccessKey, SecretKey }
|
||||
} = configMap
|
||||
const token = (await getDogeToken(AccessKey, SecretKey)) as DogecloudTokenFull
|
||||
const bucket = token.Buckets?.find(item => item.name === bucketName || item.s3Bucket === bucketName)
|
||||
const newConfigMap = { ...configMap }
|
||||
newConfigMap.config = {
|
||||
...newConfigMap.config,
|
||||
accessKeyID: token.Credentials?.accessKeyId,
|
||||
secretAccessKey: token.Credentials?.secretAccessKey,
|
||||
sessionToken: token.Credentials?.sessionToken,
|
||||
endpoint: bucket?.s3Endpoint,
|
||||
region: dogeRegionMap[bucket?.s3Endpoint?.split('.')[1] || 'ap-shanghai'],
|
||||
bucketName: bucket?.s3Bucket
|
||||
}
|
||||
return await removeFileFromS3InMain(newConfigMap, true)
|
||||
} catch (err: any) {
|
||||
logger.error(err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function createHuaweiAuthorization(
|
||||
bucketName: string,
|
||||
path: string,
|
||||
fileName: string,
|
||||
accessKey: string,
|
||||
secretKey: string,
|
||||
date: string = new Date().toUTCString()
|
||||
) {
|
||||
const strToSign = `DELETE\n\n\n${date}\n/${bucketName}${path}/${fileName}`
|
||||
const singature = crypto.createHmac('sha1', secretKey).update(strToSign).digest('base64')
|
||||
return `OBS ${accessKey}:${singature}`
|
||||
}
|
||||
|
||||
export async function removeFileFromHuaweiInMain(configMap: IStringKeyMap) {
|
||||
const { fileName, config } = configMap
|
||||
const { accessKeyId, accessKeySecret, bucketName, endpoint } = config
|
||||
let path = config.path || '/'
|
||||
path = `/${path.replace(/^\/+|\/+$/, '')}`
|
||||
path = path === '/' ? '' : path
|
||||
const date = new Date().toUTCString()
|
||||
const authorization = createHuaweiAuthorization(bucketName, path, fileName, accessKeyId, accessKeySecret, date)
|
||||
try {
|
||||
const res = await axios.request({
|
||||
url: `https://${bucketName}.${endpoint}${encodeURI(path)}/${encodeURIComponent(fileName)}`,
|
||||
method: 'DELETE',
|
||||
responseType: 'json',
|
||||
headers: {
|
||||
Host: `${bucketName}.${endpoint}`,
|
||||
Date: date,
|
||||
Authorization: authorization
|
||||
}
|
||||
})
|
||||
return res.status === 204
|
||||
} catch (error: any) {
|
||||
logger.error(error)
|
||||
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) {
|
||||
logger.error(err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
export const deleteLog = (fileName?: string, type?: string, isSuccess = true, msg?: string) => {
|
||||
console.log(`Delete ${fileName} on ${type} ${isSuccess ? 'success' : 'failed'}, message: ${msg || ''}`)
|
||||
}
|
||||
|
||||
export const deleteFailedLog = (fileName: string, type: string, error: any) => {
|
||||
deleteLog(fileName, type, false)
|
||||
console.error(error)
|
||||
}
|
||||
export const deleteLog = (fileName?: string, type?: string, isSuccess = true, msg?: string) => {
|
||||
console.log(`Delete ${fileName} on ${type} ${isSuccess ? 'success' : 'failed'}, message: ${msg || ''}`)
|
||||
}
|
||||
|
||||
export const deleteFailedLog = (fileName: string, type: string, error: any) => {
|
||||
deleteLog(fileName, type, false)
|
||||
console.error(error)
|
||||
}
|
||||
|
||||
@@ -8,11 +8,11 @@ const AUTH_KEY_VALUE_RE = /(\w+)=["']?([^'"]{1,10000})["']?/
|
||||
let NC = 0
|
||||
const NC_PAD = '00000000'
|
||||
|
||||
function md5 (text: crypto.BinaryLike) {
|
||||
function md5(text: crypto.BinaryLike) {
|
||||
return crypto.createHash('md5').update(text).digest('hex')
|
||||
}
|
||||
|
||||
export function digestAuthHeader (
|
||||
export function digestAuthHeader(
|
||||
method: string,
|
||||
uri: string,
|
||||
wwwAuthenticate: string,
|
||||
@@ -70,7 +70,7 @@ export function digestAuthHeader (
|
||||
return authstring
|
||||
}
|
||||
|
||||
export async function getAuthHeader (method: string, host: string, uri: string, username: string, password: string) {
|
||||
export async function getAuthHeader(method: string, host: string, uri: string, username: string, password: string) {
|
||||
try {
|
||||
await axios.get(`${host}${uri}`)
|
||||
} catch (error: any) {
|
||||
|
||||
@@ -1,242 +1,242 @@
|
||||
export const ILogType = {
|
||||
success: 'success',
|
||||
info: 'info',
|
||||
warn: 'warn',
|
||||
error: 'error'
|
||||
}
|
||||
|
||||
export const ICOREBuildInEvent = {
|
||||
UPLOAD_PROGRESS: 'uploadProgress',
|
||||
FAILED: 'failed',
|
||||
BEFORE_TRANSFORM: 'beforeTransform',
|
||||
BEFORE_UPLOAD: 'beforeUpload',
|
||||
AFTER_UPLOAD: 'afterUpload',
|
||||
FINISHED: 'finished',
|
||||
INSTALL: 'install',
|
||||
UNINSTALL: 'uninstall',
|
||||
UPDATE: 'update',
|
||||
NOTIFICATION: 'notification',
|
||||
REMOVE: 'remove'
|
||||
}
|
||||
|
||||
export const IPicGoHelperType = {
|
||||
afterUploadPlugins: 'afterUploadPlugins',
|
||||
beforeTransformPlugins: 'beforeTransformPlugins',
|
||||
beforeUploadPlugins: 'beforeUploadPlugins',
|
||||
uploader: 'uploader',
|
||||
transformer: 'transformer'
|
||||
}
|
||||
|
||||
export const IPasteStyle = {
|
||||
MARKDOWN: 'markdown',
|
||||
HTML: 'HTML',
|
||||
URL: 'URL',
|
||||
UBB: 'UBB',
|
||||
CUSTOM: 'Custom'
|
||||
}
|
||||
|
||||
export const IWindowList = {
|
||||
SETTING_WINDOW: 'SETTING_WINDOW',
|
||||
TRAY_WINDOW: 'TRAY_WINDOW',
|
||||
MINI_WINDOW: 'MINI_WINDOW',
|
||||
RENAME_WINDOW: 'RENAME_WINDOW',
|
||||
TOOLBOX_WINDOW: 'TOOLBOX_WINDOW'
|
||||
}
|
||||
|
||||
export const IRemoteNoticeActionType = {
|
||||
OPEN_URL: 'OPEN_URL',
|
||||
SHOW_NOTICE: 'SHOW_NOTICE', // notification
|
||||
SHOW_DIALOG: 'SHOW_DIALOG', // dialog notice
|
||||
COMMON: 'COMMON',
|
||||
VOID: 'VOID', // do nothing
|
||||
SHOW_MESSAGE_BOX: 'SHOW_MESSAGE_BOX'
|
||||
}
|
||||
|
||||
export const IRemoteNoticeTriggerHook = {
|
||||
APP_START: 'APP_START',
|
||||
SETTING_WINDOW_OPEN: 'SETTING_WINDOW_OPEN'
|
||||
}
|
||||
|
||||
export const IRemoteNoticeTriggerCount = {
|
||||
ONCE: 'ONCE', // default
|
||||
ALWAYS: 'ALWAYS'
|
||||
}
|
||||
|
||||
export const IRPCType = {
|
||||
INVOKE: 'INVOKE',
|
||||
SEND: 'SEND'
|
||||
}
|
||||
|
||||
export const IRPCActionType = {
|
||||
// system rpc
|
||||
RELOAD_APP: 'RELOAD_APP',
|
||||
OPEN_URL: 'OPEN_URL',
|
||||
OPEN_FILE: 'OPEN_FILE',
|
||||
HIDE_DOCK: 'HIDE_DOCK',
|
||||
SET_CURRENT_LANGUAGE: 'SET_CURRENT_LANGUAGE',
|
||||
OPEN_WINDOW: 'OPEN_WINDOW',
|
||||
OPEN_MINI_WINDOW: 'OPEN_MINI_WINDOW',
|
||||
CLOSE_WINDOW: 'CLOSE_WINDOW',
|
||||
MINIMIZE_WINDOW: 'MINIMIZE_WINDOW',
|
||||
SHOW_MINI_PAGE_MENU: 'SHOW_MINI_PAGE_MENU',
|
||||
SHOW_MAIN_PAGE_MENU: 'SHOW_MAIN_PAGE_MENU',
|
||||
SHOW_UPLOAD_PAGE_MENU: 'SHOW_UPLOAD_PAGE_MENU',
|
||||
SHOW_SECOND_UPLOADER_MENU: 'SHOW_SECOND_UPLOADER_MENU',
|
||||
SHOW_PLUGIN_PAGE_MENU: 'SHOW_PLUGIN_PAGE_MENU',
|
||||
SET_MINI_WINDOW_POS: 'SET_MINI_WINDOW_POS',
|
||||
MINI_WINDOW_ON_TOP: 'MINI_WINDOW_ON_TOP',
|
||||
MAIN_WINDOW_ON_TOP: 'MAIN_WINDOW_ON_TOP',
|
||||
UPDATE_MINI_WINDOW_ICON: 'UPDATE_MINI_WINDOW_ICON',
|
||||
REFRESH_SETTING_WINDOW: 'REFRESH_SETTING_WINDOW',
|
||||
// picbed RPC
|
||||
PICBED_GET_PICBED_CONFIG: 'PICBED_GET_PICBED_CONFIG',
|
||||
PICBED_GET_CONFIG_LIST: 'PICBED_GET_CONFIG_LIST',
|
||||
PICBED_DELETE_CONFIG: 'PICBED_DELETE_CONFIG',
|
||||
UPLOADER_CHANGE_CURRENT: 'UPLOADER_CHANGE_CURRENT',
|
||||
UPLOADER_SELECT: 'UPLOADER_SELECT',
|
||||
UPLOADER_UPDATE_CONFIG: 'UPLOADER_UPDATE_CONFIG',
|
||||
UPLOADER_RESET_CONFIG: 'UPLOADER_RESET_CONFIG',
|
||||
DELETE_ALL_API: 'DELETE_ALL_API',
|
||||
|
||||
// toolbox rpc
|
||||
TOOLBOX_CHECK: 'TOOLBOX_CHECK',
|
||||
TOOLBOX_CHECK_RES: 'TOOLBOX_CHECK_RES',
|
||||
TOOLBOX_CHECK_FIX: 'TOOLBOX_CHECK_FIX',
|
||||
|
||||
// main app setting rpc
|
||||
PICLIST_GET_CONFIG: 'PICLIST_GET_CONFIG',
|
||||
PICLIST_GET_CONFIG_SYNC: 'PICLIST_GET_CONFIG_SYNC',
|
||||
PICLIST_SAVE_CONFIG: 'PICLIST_SAVE_CONFIG',
|
||||
PICLIST_OPEN_FILE: 'PICLIST_OPEN_FILE',
|
||||
PICLIST_OPEN_DIRECTORY: 'PICLIST_OPEN_DIRECTORY',
|
||||
PICLIST_AUTO_START: 'PICLIST_AUTO_START',
|
||||
|
||||
// shortkey setting rpc
|
||||
SHORTKEY_UPDATE: 'SHORTKEY_UPDATE',
|
||||
SHORTKEY_BIND_OR_UNBIND: 'SHORTKEY_BIND_OR_UNBIND',
|
||||
SHORTKEY_TOGGLE_SHORTKEY_MODIFIED_MODE: 'SHORTKEY_TOGGLE_SHORTKEY_MODIFIED_MODE',
|
||||
|
||||
// configuration setting rpc
|
||||
CONFIGURE_MIGRATE_FROM_PICGO: 'CONFIGURE_MIGRATE_FROM_PICGO',
|
||||
CONFIGURE_UPLOAD_COMMON_CONFIG: 'CONFIGURE_UPLOAD_COMMON_CONFIG',
|
||||
CONFIGURE_UPLOAD_MANAGE_CONFIG: 'CONFIGURE_UPLOAD_MANAGE_CONFIG',
|
||||
CONFIGURE_UPLOAD_ALL_CONFIG: 'CONFIGURE_UPLOAD_ALL_CONFIG',
|
||||
CONFIGURE_DOWNLOAD_COMMON_CONFIG: 'CONFIGURE_DOWNLOAD_COMMON_CONFIG',
|
||||
CONFIGURE_DOWNLOAD_MANAGE_CONFIG: 'CONFIGURE_DOWNLOAD_MANAGE_CONFIG',
|
||||
CONFIGURE_DOWNLOAD_ALL_CONFIG: 'CONFIGURE_DOWNLOAD_ALL_CONFIG',
|
||||
|
||||
// advanced setting rpc
|
||||
ADVANCED_UPDATE_SERVER: 'ADVANCED_UPDATE_SERVER',
|
||||
ADVANCED_STOP_WEB_SERVER: 'ADVANCED_STOP_WEB_SERVER',
|
||||
ADVANCED_RESTART_WEB_SERVER: 'ADVANCED_RESTART_WEB_SERVER',
|
||||
|
||||
// upload and main page rpc
|
||||
MAIN_GET_PICBED: 'MAIN_GET_PICBED',
|
||||
UPLOAD_CLIPBOARD_FILES_FROM_UPLOAD_PAGE: 'UPLOAD_CLIPBOARD_FILES_FROM_UPLOAD_PAGE',
|
||||
UPLOAD_CHOOSED_FILES: 'UPLOAD_CHOOSED_FILES',
|
||||
|
||||
// gallery rpc
|
||||
GALLERY_PASTE_TEXT: 'GALLERY_PASTE_TEXT',
|
||||
GALLERY_REMOVE_FILES: 'GALLERY_REMOVE_FILES',
|
||||
GALLERY_GET_DB: 'GALLERY_GET_DB',
|
||||
GALLERY_GET_BY_ID_DB: 'GALLERY_GET_BY_ID_DB',
|
||||
GALLERY_UPDATE_BY_ID_DB: 'GALLERY_UPDATE_BY_ID_DB',
|
||||
GALLERY_REMOVE_BY_ID_DB: 'GALLERY_REMOVE_BY_ID_DB',
|
||||
GALLERY_INSERT_DB: 'GALLERY_INSERT_DB',
|
||||
GALLERY_INSERT_DB_BATCH: 'GALLERY_INSERT_DB_BATCH',
|
||||
// plugin rpc
|
||||
PLUGIN_GET_LIST: 'PLUGIN_GET_LIST',
|
||||
PLUGIN_INSTALL: 'PLUGIN_INSTALL',
|
||||
PLUGIN_IMPORT_LOCAL: 'PLUGIN_IMPORT_LOCAL',
|
||||
PLUGIN_UPDATE_ALL: 'PLUGIN_UPDATE_ALL',
|
||||
|
||||
// tray rpc
|
||||
TRAY_SET_TOOL_TIP: 'TRAY_SET_TOOL_TIP',
|
||||
TRAY_GET_SHORT_URL: 'TRAY_GET_SHORT_URL',
|
||||
TRAY_UPLOAD_CLIPBOARD_FILES: 'TRAY_UPLOAD_CLIPBOARD_FILES',
|
||||
|
||||
// manage rpc
|
||||
MANAGE_GET_CONFIG: 'MANAGE_GET_CONFIG',
|
||||
MANAGE_SAVE_CONFIG: 'MANAGE_SAVE_CONFIG',
|
||||
MANAGE_REMOVE_CONFIG: 'MANAGE_REMOVE_CONFIG',
|
||||
MANAGE_GET_BUCKET_LIST: 'MANAGE_GET_BUCKET_LIST',
|
||||
MANAGE_GET_BUCKET_LIST_BACKSTAGE: 'MANAGE_GET_BUCKET_LIST_BACKSTAGE',
|
||||
MANAGE_GET_BUCKET_LIST_RECURSIVELY: 'MANAGE_GET_BUCKET_LIST_RECURSIVELY',
|
||||
MANAGE_CREATE_BUCKET: 'MANAGE_CREATE_BUCKET',
|
||||
MANAGE_GET_BUCKET_FILE_LIST: 'MANAGE_GET_BUCKET_FILE_LIST',
|
||||
MANAGE_GET_BUCKET_DOMAIN: 'MANAGE_GET_BUCKET_DOMAIN',
|
||||
MANAGE_SET_BUCKET_ACL_POLICY: 'MANAGE_SET_BUCKET_ACL_POLICY',
|
||||
MANAGE_RENAME_BUCKET_FILE: 'MANAGE_RENAME_BUCKET_FILE',
|
||||
MANAGE_DELETE_BUCKET_FILE: 'MANAGE_DELETE_BUCKET_FILE',
|
||||
MANAGE_DELETE_BUCKET_FOLDER: 'MANAGE_DELETE_BUCKET_FOLDER',
|
||||
MANAGE_GET_PRE_SIGNED_URL: 'MANAGE_GET_PRE_SIGNED_URL',
|
||||
MANAGE_UPLOAD_BUCKET_FILE: 'MANAGE_UPLOAD_BUCKET_FILE',
|
||||
MANAGE_DOWNLOAD_BUCKET_FILE: 'MANAGE_DOWNLOAD_BUCKET_FILE',
|
||||
MANAGE_CREATE_BUCKET_FOLDER: 'MANAGE_CREATE_BUCKET_FOLDER',
|
||||
MANAGE_OPEN_FILE_SELECT_DIALOG: 'MANAGE_OPEN_FILE_SELECT_DIALOG',
|
||||
MANAGE_GET_UPLOAD_TASK_LIST: 'MANAGE_GET_UPLOAD_TASK_LIST',
|
||||
MANAGE_GET_DOWNLOAD_TASK_LIST: 'MANAGE_GET_DOWNLOAD_TASK_LIST',
|
||||
MANAGE_DELETE_UPLOADED_TASK: 'MANAGE_DELETE_UPLOADED_TASK',
|
||||
MANAGE_DELETE_ALL_UPLOADED_TASK: 'MANAGE_DELETE_ALL_UPLOADED_TASK',
|
||||
MANAGE_DELETE_DOWNLOADED_TASK: 'MANAGE_DELETE_DOWNLOADED_TASK',
|
||||
MANAGE_DELETE_ALL_DOWNLOADED_TASK: 'MANAGE_DELETE_ALL_DOWNLOADED_TASK',
|
||||
MANAGE_SELECT_DOWNLOAD_FOLDER: 'MANAGE_SELECT_DOWNLOAD_FOLDER',
|
||||
MANAGE_GET_DEFAULT_DOWNLOAD_FOLDER: 'MANAGE_GET_DEFAULT_DOWNLOAD_FOLDER',
|
||||
MANAGE_OPEN_DOWNLOADED_FOLDER: 'MANAGE_OPEN_DOWNLOADED_FOLDER',
|
||||
MANAGE_OPEN_LOCAL_FILE: 'MANAGE_OPEN_LOCAL_FILE',
|
||||
MANAGE_DOWNLOAD_FILE_FROM_URL: 'MANAGE_DOWNLOAD_FILE_FROM_URL',
|
||||
MANAGE_CONVERT_PATH_TO_BASE64: 'MANAGE_CONVERT_PATH_TO_BASE64'
|
||||
}
|
||||
|
||||
export const IToolboxItemType = {
|
||||
IS_CONFIG_FILE_BROKEN: 'IS_CONFIG_FILE_BROKEN',
|
||||
IS_GALLERY_FILE_BROKEN: 'IS_GALLERY_FILE_BROKEN',
|
||||
HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD: 'HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD',
|
||||
HAS_PROBLEM_WITH_PROXY: 'HAS_PROBLEM_WITH_PROXY'
|
||||
}
|
||||
|
||||
export const IToolboxItemCheckStatus = {
|
||||
INIT: 'init',
|
||||
LOADING: 'loading',
|
||||
SUCCESS: 'success',
|
||||
ERROR: 'error'
|
||||
}
|
||||
|
||||
export const ISartMode = {
|
||||
QUIET: 'quiet',
|
||||
MINI: 'mini',
|
||||
MAIN: 'main',
|
||||
NO_TRAY: 'no-tray'
|
||||
}
|
||||
|
||||
export const II18nLanguage = {
|
||||
ZH_CN: 'zh-CN',
|
||||
ZH_TW: 'zh-TW',
|
||||
EN: 'en'
|
||||
}
|
||||
|
||||
export const IShortUrlServer = {
|
||||
C1N: 'c1n',
|
||||
YOURLS: 'yourls',
|
||||
CFWORKER: 'cf_worker',
|
||||
SINK: 'sink'
|
||||
}
|
||||
|
||||
export const commonTaskStatus = {
|
||||
queuing: 'queuing',
|
||||
failed: 'failed',
|
||||
canceled: 'canceled',
|
||||
paused: 'paused'
|
||||
}
|
||||
|
||||
// manage task status
|
||||
|
||||
export const uploadTaskSpecialStatus = {
|
||||
uploading: 'uploading',
|
||||
uploaded: 'uploaded'
|
||||
}
|
||||
|
||||
export const downloadTaskSpecialStatus = {
|
||||
downloading: 'downloading',
|
||||
downloaded: 'downloaded'
|
||||
}
|
||||
export const ILogType = {
|
||||
success: 'success',
|
||||
info: 'info',
|
||||
warn: 'warn',
|
||||
error: 'error'
|
||||
}
|
||||
|
||||
export const ICOREBuildInEvent = {
|
||||
UPLOAD_PROGRESS: 'uploadProgress',
|
||||
FAILED: 'failed',
|
||||
BEFORE_TRANSFORM: 'beforeTransform',
|
||||
BEFORE_UPLOAD: 'beforeUpload',
|
||||
AFTER_UPLOAD: 'afterUpload',
|
||||
FINISHED: 'finished',
|
||||
INSTALL: 'install',
|
||||
UNINSTALL: 'uninstall',
|
||||
UPDATE: 'update',
|
||||
NOTIFICATION: 'notification',
|
||||
REMOVE: 'remove'
|
||||
}
|
||||
|
||||
export const IPicGoHelperType = {
|
||||
afterUploadPlugins: 'afterUploadPlugins',
|
||||
beforeTransformPlugins: 'beforeTransformPlugins',
|
||||
beforeUploadPlugins: 'beforeUploadPlugins',
|
||||
uploader: 'uploader',
|
||||
transformer: 'transformer'
|
||||
}
|
||||
|
||||
export const IPasteStyle = {
|
||||
MARKDOWN: 'markdown',
|
||||
HTML: 'HTML',
|
||||
URL: 'URL',
|
||||
UBB: 'UBB',
|
||||
CUSTOM: 'Custom'
|
||||
}
|
||||
|
||||
export const IWindowList = {
|
||||
SETTING_WINDOW: 'SETTING_WINDOW',
|
||||
TRAY_WINDOW: 'TRAY_WINDOW',
|
||||
MINI_WINDOW: 'MINI_WINDOW',
|
||||
RENAME_WINDOW: 'RENAME_WINDOW',
|
||||
TOOLBOX_WINDOW: 'TOOLBOX_WINDOW'
|
||||
}
|
||||
|
||||
export const IRemoteNoticeActionType = {
|
||||
OPEN_URL: 'OPEN_URL',
|
||||
SHOW_NOTICE: 'SHOW_NOTICE', // notification
|
||||
SHOW_DIALOG: 'SHOW_DIALOG', // dialog notice
|
||||
COMMON: 'COMMON',
|
||||
VOID: 'VOID', // do nothing
|
||||
SHOW_MESSAGE_BOX: 'SHOW_MESSAGE_BOX'
|
||||
}
|
||||
|
||||
export const IRemoteNoticeTriggerHook = {
|
||||
APP_START: 'APP_START',
|
||||
SETTING_WINDOW_OPEN: 'SETTING_WINDOW_OPEN'
|
||||
}
|
||||
|
||||
export const IRemoteNoticeTriggerCount = {
|
||||
ONCE: 'ONCE', // default
|
||||
ALWAYS: 'ALWAYS'
|
||||
}
|
||||
|
||||
export const IRPCType = {
|
||||
INVOKE: 'INVOKE',
|
||||
SEND: 'SEND'
|
||||
}
|
||||
|
||||
export const IRPCActionType = {
|
||||
// system rpc
|
||||
RELOAD_APP: 'RELOAD_APP',
|
||||
OPEN_URL: 'OPEN_URL',
|
||||
OPEN_FILE: 'OPEN_FILE',
|
||||
HIDE_DOCK: 'HIDE_DOCK',
|
||||
SET_CURRENT_LANGUAGE: 'SET_CURRENT_LANGUAGE',
|
||||
OPEN_WINDOW: 'OPEN_WINDOW',
|
||||
OPEN_MINI_WINDOW: 'OPEN_MINI_WINDOW',
|
||||
CLOSE_WINDOW: 'CLOSE_WINDOW',
|
||||
MINIMIZE_WINDOW: 'MINIMIZE_WINDOW',
|
||||
SHOW_MINI_PAGE_MENU: 'SHOW_MINI_PAGE_MENU',
|
||||
SHOW_MAIN_PAGE_MENU: 'SHOW_MAIN_PAGE_MENU',
|
||||
SHOW_UPLOAD_PAGE_MENU: 'SHOW_UPLOAD_PAGE_MENU',
|
||||
SHOW_SECOND_UPLOADER_MENU: 'SHOW_SECOND_UPLOADER_MENU',
|
||||
SHOW_PLUGIN_PAGE_MENU: 'SHOW_PLUGIN_PAGE_MENU',
|
||||
SET_MINI_WINDOW_POS: 'SET_MINI_WINDOW_POS',
|
||||
MINI_WINDOW_ON_TOP: 'MINI_WINDOW_ON_TOP',
|
||||
MAIN_WINDOW_ON_TOP: 'MAIN_WINDOW_ON_TOP',
|
||||
UPDATE_MINI_WINDOW_ICON: 'UPDATE_MINI_WINDOW_ICON',
|
||||
REFRESH_SETTING_WINDOW: 'REFRESH_SETTING_WINDOW',
|
||||
// picbed RPC
|
||||
PICBED_GET_PICBED_CONFIG: 'PICBED_GET_PICBED_CONFIG',
|
||||
PICBED_GET_CONFIG_LIST: 'PICBED_GET_CONFIG_LIST',
|
||||
PICBED_DELETE_CONFIG: 'PICBED_DELETE_CONFIG',
|
||||
UPLOADER_CHANGE_CURRENT: 'UPLOADER_CHANGE_CURRENT',
|
||||
UPLOADER_SELECT: 'UPLOADER_SELECT',
|
||||
UPLOADER_UPDATE_CONFIG: 'UPLOADER_UPDATE_CONFIG',
|
||||
UPLOADER_RESET_CONFIG: 'UPLOADER_RESET_CONFIG',
|
||||
DELETE_ALL_API: 'DELETE_ALL_API',
|
||||
|
||||
// toolbox rpc
|
||||
TOOLBOX_CHECK: 'TOOLBOX_CHECK',
|
||||
TOOLBOX_CHECK_RES: 'TOOLBOX_CHECK_RES',
|
||||
TOOLBOX_CHECK_FIX: 'TOOLBOX_CHECK_FIX',
|
||||
|
||||
// main app setting rpc
|
||||
PICLIST_GET_CONFIG: 'PICLIST_GET_CONFIG',
|
||||
PICLIST_GET_CONFIG_SYNC: 'PICLIST_GET_CONFIG_SYNC',
|
||||
PICLIST_SAVE_CONFIG: 'PICLIST_SAVE_CONFIG',
|
||||
PICLIST_OPEN_FILE: 'PICLIST_OPEN_FILE',
|
||||
PICLIST_OPEN_DIRECTORY: 'PICLIST_OPEN_DIRECTORY',
|
||||
PICLIST_AUTO_START: 'PICLIST_AUTO_START',
|
||||
|
||||
// shortkey setting rpc
|
||||
SHORTKEY_UPDATE: 'SHORTKEY_UPDATE',
|
||||
SHORTKEY_BIND_OR_UNBIND: 'SHORTKEY_BIND_OR_UNBIND',
|
||||
SHORTKEY_TOGGLE_SHORTKEY_MODIFIED_MODE: 'SHORTKEY_TOGGLE_SHORTKEY_MODIFIED_MODE',
|
||||
|
||||
// configuration setting rpc
|
||||
CONFIGURE_MIGRATE_FROM_PICGO: 'CONFIGURE_MIGRATE_FROM_PICGO',
|
||||
CONFIGURE_UPLOAD_COMMON_CONFIG: 'CONFIGURE_UPLOAD_COMMON_CONFIG',
|
||||
CONFIGURE_UPLOAD_MANAGE_CONFIG: 'CONFIGURE_UPLOAD_MANAGE_CONFIG',
|
||||
CONFIGURE_UPLOAD_ALL_CONFIG: 'CONFIGURE_UPLOAD_ALL_CONFIG',
|
||||
CONFIGURE_DOWNLOAD_COMMON_CONFIG: 'CONFIGURE_DOWNLOAD_COMMON_CONFIG',
|
||||
CONFIGURE_DOWNLOAD_MANAGE_CONFIG: 'CONFIGURE_DOWNLOAD_MANAGE_CONFIG',
|
||||
CONFIGURE_DOWNLOAD_ALL_CONFIG: 'CONFIGURE_DOWNLOAD_ALL_CONFIG',
|
||||
|
||||
// advanced setting rpc
|
||||
ADVANCED_UPDATE_SERVER: 'ADVANCED_UPDATE_SERVER',
|
||||
ADVANCED_STOP_WEB_SERVER: 'ADVANCED_STOP_WEB_SERVER',
|
||||
ADVANCED_RESTART_WEB_SERVER: 'ADVANCED_RESTART_WEB_SERVER',
|
||||
|
||||
// upload and main page rpc
|
||||
MAIN_GET_PICBED: 'MAIN_GET_PICBED',
|
||||
UPLOAD_CLIPBOARD_FILES_FROM_UPLOAD_PAGE: 'UPLOAD_CLIPBOARD_FILES_FROM_UPLOAD_PAGE',
|
||||
UPLOAD_CHOOSED_FILES: 'UPLOAD_CHOOSED_FILES',
|
||||
|
||||
// gallery rpc
|
||||
GALLERY_PASTE_TEXT: 'GALLERY_PASTE_TEXT',
|
||||
GALLERY_REMOVE_FILES: 'GALLERY_REMOVE_FILES',
|
||||
GALLERY_GET_DB: 'GALLERY_GET_DB',
|
||||
GALLERY_GET_BY_ID_DB: 'GALLERY_GET_BY_ID_DB',
|
||||
GALLERY_UPDATE_BY_ID_DB: 'GALLERY_UPDATE_BY_ID_DB',
|
||||
GALLERY_REMOVE_BY_ID_DB: 'GALLERY_REMOVE_BY_ID_DB',
|
||||
GALLERY_INSERT_DB: 'GALLERY_INSERT_DB',
|
||||
GALLERY_INSERT_DB_BATCH: 'GALLERY_INSERT_DB_BATCH',
|
||||
// plugin rpc
|
||||
PLUGIN_GET_LIST: 'PLUGIN_GET_LIST',
|
||||
PLUGIN_INSTALL: 'PLUGIN_INSTALL',
|
||||
PLUGIN_IMPORT_LOCAL: 'PLUGIN_IMPORT_LOCAL',
|
||||
PLUGIN_UPDATE_ALL: 'PLUGIN_UPDATE_ALL',
|
||||
|
||||
// tray rpc
|
||||
TRAY_SET_TOOL_TIP: 'TRAY_SET_TOOL_TIP',
|
||||
TRAY_GET_SHORT_URL: 'TRAY_GET_SHORT_URL',
|
||||
TRAY_UPLOAD_CLIPBOARD_FILES: 'TRAY_UPLOAD_CLIPBOARD_FILES',
|
||||
|
||||
// manage rpc
|
||||
MANAGE_GET_CONFIG: 'MANAGE_GET_CONFIG',
|
||||
MANAGE_SAVE_CONFIG: 'MANAGE_SAVE_CONFIG',
|
||||
MANAGE_REMOVE_CONFIG: 'MANAGE_REMOVE_CONFIG',
|
||||
MANAGE_GET_BUCKET_LIST: 'MANAGE_GET_BUCKET_LIST',
|
||||
MANAGE_GET_BUCKET_LIST_BACKSTAGE: 'MANAGE_GET_BUCKET_LIST_BACKSTAGE',
|
||||
MANAGE_GET_BUCKET_LIST_RECURSIVELY: 'MANAGE_GET_BUCKET_LIST_RECURSIVELY',
|
||||
MANAGE_CREATE_BUCKET: 'MANAGE_CREATE_BUCKET',
|
||||
MANAGE_GET_BUCKET_FILE_LIST: 'MANAGE_GET_BUCKET_FILE_LIST',
|
||||
MANAGE_GET_BUCKET_DOMAIN: 'MANAGE_GET_BUCKET_DOMAIN',
|
||||
MANAGE_SET_BUCKET_ACL_POLICY: 'MANAGE_SET_BUCKET_ACL_POLICY',
|
||||
MANAGE_RENAME_BUCKET_FILE: 'MANAGE_RENAME_BUCKET_FILE',
|
||||
MANAGE_DELETE_BUCKET_FILE: 'MANAGE_DELETE_BUCKET_FILE',
|
||||
MANAGE_DELETE_BUCKET_FOLDER: 'MANAGE_DELETE_BUCKET_FOLDER',
|
||||
MANAGE_GET_PRE_SIGNED_URL: 'MANAGE_GET_PRE_SIGNED_URL',
|
||||
MANAGE_UPLOAD_BUCKET_FILE: 'MANAGE_UPLOAD_BUCKET_FILE',
|
||||
MANAGE_DOWNLOAD_BUCKET_FILE: 'MANAGE_DOWNLOAD_BUCKET_FILE',
|
||||
MANAGE_CREATE_BUCKET_FOLDER: 'MANAGE_CREATE_BUCKET_FOLDER',
|
||||
MANAGE_OPEN_FILE_SELECT_DIALOG: 'MANAGE_OPEN_FILE_SELECT_DIALOG',
|
||||
MANAGE_GET_UPLOAD_TASK_LIST: 'MANAGE_GET_UPLOAD_TASK_LIST',
|
||||
MANAGE_GET_DOWNLOAD_TASK_LIST: 'MANAGE_GET_DOWNLOAD_TASK_LIST',
|
||||
MANAGE_DELETE_UPLOADED_TASK: 'MANAGE_DELETE_UPLOADED_TASK',
|
||||
MANAGE_DELETE_ALL_UPLOADED_TASK: 'MANAGE_DELETE_ALL_UPLOADED_TASK',
|
||||
MANAGE_DELETE_DOWNLOADED_TASK: 'MANAGE_DELETE_DOWNLOADED_TASK',
|
||||
MANAGE_DELETE_ALL_DOWNLOADED_TASK: 'MANAGE_DELETE_ALL_DOWNLOADED_TASK',
|
||||
MANAGE_SELECT_DOWNLOAD_FOLDER: 'MANAGE_SELECT_DOWNLOAD_FOLDER',
|
||||
MANAGE_GET_DEFAULT_DOWNLOAD_FOLDER: 'MANAGE_GET_DEFAULT_DOWNLOAD_FOLDER',
|
||||
MANAGE_OPEN_DOWNLOADED_FOLDER: 'MANAGE_OPEN_DOWNLOADED_FOLDER',
|
||||
MANAGE_OPEN_LOCAL_FILE: 'MANAGE_OPEN_LOCAL_FILE',
|
||||
MANAGE_DOWNLOAD_FILE_FROM_URL: 'MANAGE_DOWNLOAD_FILE_FROM_URL',
|
||||
MANAGE_CONVERT_PATH_TO_BASE64: 'MANAGE_CONVERT_PATH_TO_BASE64'
|
||||
}
|
||||
|
||||
export const IToolboxItemType = {
|
||||
IS_CONFIG_FILE_BROKEN: 'IS_CONFIG_FILE_BROKEN',
|
||||
IS_GALLERY_FILE_BROKEN: 'IS_GALLERY_FILE_BROKEN',
|
||||
HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD: 'HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD',
|
||||
HAS_PROBLEM_WITH_PROXY: 'HAS_PROBLEM_WITH_PROXY'
|
||||
}
|
||||
|
||||
export const IToolboxItemCheckStatus = {
|
||||
INIT: 'init',
|
||||
LOADING: 'loading',
|
||||
SUCCESS: 'success',
|
||||
ERROR: 'error'
|
||||
}
|
||||
|
||||
export const ISartMode = {
|
||||
QUIET: 'quiet',
|
||||
MINI: 'mini',
|
||||
MAIN: 'main',
|
||||
NO_TRAY: 'no-tray'
|
||||
}
|
||||
|
||||
export const II18nLanguage = {
|
||||
ZH_CN: 'zh-CN',
|
||||
ZH_TW: 'zh-TW',
|
||||
EN: 'en'
|
||||
}
|
||||
|
||||
export const IShortUrlServer = {
|
||||
C1N: 'c1n',
|
||||
YOURLS: 'yourls',
|
||||
CFWORKER: 'cf_worker',
|
||||
SINK: 'sink'
|
||||
}
|
||||
|
||||
export const commonTaskStatus = {
|
||||
queuing: 'queuing',
|
||||
failed: 'failed',
|
||||
canceled: 'canceled',
|
||||
paused: 'paused'
|
||||
}
|
||||
|
||||
// manage task status
|
||||
|
||||
export const uploadTaskSpecialStatus = {
|
||||
uploading: 'uploading',
|
||||
uploaded: 'uploaded'
|
||||
}
|
||||
|
||||
export const downloadTaskSpecialStatus = {
|
||||
downloading: 'downloading',
|
||||
downloaded: 'downloaded'
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ const parseVersion = (plist: string) => {
|
||||
return matches[1].replace('10.16', '11')
|
||||
}
|
||||
|
||||
export function macOSVersion (): string {
|
||||
export function macOSVersion(): string {
|
||||
if (!isMacOS) return ''
|
||||
|
||||
if (!version) {
|
||||
@@ -43,7 +43,7 @@ if (process.env.NODE_ENV === 'test') {
|
||||
macOSVersion._parseVersion = parseVersion
|
||||
}
|
||||
|
||||
export function isMacOSVersion (semverRange: string) {
|
||||
export function isMacOSVersion(semverRange: string) {
|
||||
if (!isMacOS) {
|
||||
return false
|
||||
}
|
||||
@@ -53,7 +53,7 @@ export function isMacOSVersion (semverRange: string) {
|
||||
return semver.satisfies(macOSVersion(), clean(semverRange))
|
||||
}
|
||||
|
||||
export function isMacOSVersionGreaterThanOrEqualTo (version: string) {
|
||||
export function isMacOSVersionGreaterThanOrEqualTo(version: string) {
|
||||
if (!isMacOS) {
|
||||
return false
|
||||
}
|
||||
@@ -63,7 +63,7 @@ export function isMacOSVersionGreaterThanOrEqualTo (version: string) {
|
||||
return semver.gte(macOSVersion(), clean(version))
|
||||
}
|
||||
|
||||
export function assertMacOSVersion (semverRange: string) {
|
||||
export function assertMacOSVersion(semverRange: string) {
|
||||
semverRange = semverRange.replace('10.16', '11')
|
||||
|
||||
if (!isMacOSVersion(semverRange)) {
|
||||
@@ -71,7 +71,7 @@ export function assertMacOSVersion (semverRange: string) {
|
||||
}
|
||||
}
|
||||
|
||||
export function assertMacOSVersionGreaterThanOrEqualTo (version: string) {
|
||||
export function assertMacOSVersionGreaterThanOrEqualTo(version: string) {
|
||||
version = version.replace('10.16', '11')
|
||||
|
||||
if (!isMacOSVersionGreaterThanOrEqualTo(version)) {
|
||||
@@ -79,7 +79,7 @@ export function assertMacOSVersionGreaterThanOrEqualTo (version: string) {
|
||||
}
|
||||
}
|
||||
|
||||
export function assertMacOS () {
|
||||
export function assertMacOS() {
|
||||
if (!isMacOS) {
|
||||
throw new Error('Requires macOS')
|
||||
}
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import picgo from '@core/picgo'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
|
||||
import type { IPicGoPluginConfig, IPicGoPluginOriginConfig, IStringKeyMap, IUploaderConfigItem, IUploaderConfigListItem } from '#/types/types'
|
||||
import type {
|
||||
IPicGoPluginConfig,
|
||||
IPicGoPluginOriginConfig,
|
||||
IStringKeyMap,
|
||||
IUploaderConfigItem,
|
||||
IUploaderConfigListItem
|
||||
} from '#/types/types'
|
||||
import { setTrayToolTip, trimValues } from '~/utils/common'
|
||||
import { configPaths } from '~/utils/configPaths'
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import type { IAppNotification } from '#/types/types'
|
||||
|
||||
export const notificationList: IAppNotification[] = []
|
||||
import type { IAppNotification } from '#/types/types'
|
||||
|
||||
export const notificationList: IAppNotification[] = []
|
||||
|
||||
@@ -1,31 +1,33 @@
|
||||
export class MemoryMonitor {
|
||||
// eslint-disable-next-line no-undef
|
||||
private static interval: NodeJS.Timeout | null = null
|
||||
|
||||
static start (intervalMs: number = 30000) {
|
||||
if (this.interval) return
|
||||
|
||||
this.interval = setInterval(() => {
|
||||
const memUsage = process.memoryUsage()
|
||||
const mbUsage = {
|
||||
rss: Math.round(memUsage.rss / 1024 / 1024),
|
||||
heapTotal: Math.round(memUsage.heapTotal / 1024 / 1024),
|
||||
heapUsed: Math.round(memUsage.heapUsed / 1024 / 1024),
|
||||
external: Math.round(memUsage.external / 1024 / 1024)
|
||||
}
|
||||
console.log(`[Memory] RSS: ${mbUsage.rss}MB, Heap: ${mbUsage.heapUsed}/${mbUsage.heapTotal}MB, External: ${mbUsage.external}MB`)
|
||||
|
||||
if (mbUsage.heapUsed / mbUsage.heapTotal > 0.8 && global.gc) {
|
||||
console.log('[Memory] Triggering garbage collection')
|
||||
global.gc()
|
||||
}
|
||||
}, intervalMs)
|
||||
}
|
||||
|
||||
static stop () {
|
||||
if (this.interval) {
|
||||
clearInterval(this.interval)
|
||||
this.interval = null
|
||||
}
|
||||
}
|
||||
}
|
||||
export class MemoryMonitor {
|
||||
// eslint-disable-next-line no-undef
|
||||
private static interval: NodeJS.Timeout | null = null
|
||||
|
||||
static start(intervalMs: number = 30000) {
|
||||
if (this.interval) return
|
||||
|
||||
this.interval = setInterval(() => {
|
||||
const memUsage = process.memoryUsage()
|
||||
const mbUsage = {
|
||||
rss: Math.round(memUsage.rss / 1024 / 1024),
|
||||
heapTotal: Math.round(memUsage.heapTotal / 1024 / 1024),
|
||||
heapUsed: Math.round(memUsage.heapUsed / 1024 / 1024),
|
||||
external: Math.round(memUsage.external / 1024 / 1024)
|
||||
}
|
||||
console.log(
|
||||
`[Memory] RSS: ${mbUsage.rss}MB, Heap: ${mbUsage.heapUsed}/${mbUsage.heapTotal}MB, External: ${mbUsage.external}MB`
|
||||
)
|
||||
|
||||
if (mbUsage.heapUsed / mbUsage.heapTotal > 0.8 && global.gc) {
|
||||
console.log('[Memory] Triggering garbage collection')
|
||||
global.gc()
|
||||
}
|
||||
}, intervalMs)
|
||||
}
|
||||
|
||||
static stop() {
|
||||
if (this.interval) {
|
||||
clearInterval(this.interval)
|
||||
this.interval = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,194 +1,194 @@
|
||||
import path from 'node:path'
|
||||
|
||||
import logger from '@core/picgo/logger'
|
||||
import fs from 'fs-extra'
|
||||
import { Config, NodeSSH, SSHExecCommandResponse } from 'node-ssh-no-cpu-features'
|
||||
import { ISftpPlistConfig } from 'piclist/dist/types'
|
||||
import { Client } from 'ssh2-no-cpu-features'
|
||||
|
||||
class SSHClient {
|
||||
private static _instance: SSHClient
|
||||
private static _client: NodeSSH
|
||||
private _isConnected = false
|
||||
|
||||
static get instance (): SSHClient {
|
||||
return this._instance || (this._instance = new this())
|
||||
}
|
||||
|
||||
static get client (): NodeSSH {
|
||||
return this._client || (this._client = new NodeSSH())
|
||||
}
|
||||
|
||||
private changeWinStylePathToUnix (path: string): string {
|
||||
return path.replace(/\\/g, '/')
|
||||
}
|
||||
|
||||
async connect (config: ISftpPlistConfig): Promise<boolean> {
|
||||
const { username, password, privateKey, passphrase } = config
|
||||
const loginInfo: Config = privateKey
|
||||
? {
|
||||
username,
|
||||
privateKeyPath: privateKey,
|
||||
passphrase: passphrase || undefined
|
||||
}
|
||||
: { username, password }
|
||||
try {
|
||||
await SSHClient.client.connect({
|
||||
host: config.host,
|
||||
port: Number(config.port) || 22,
|
||||
...loginInfo
|
||||
})
|
||||
this._isConnected = true
|
||||
return true
|
||||
} catch (err: any) {
|
||||
throw new Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
async deleteFileSFTP (config: ISftpPlistConfig, remote: string): Promise<boolean> {
|
||||
try {
|
||||
const client = new Client()
|
||||
const { username, password, privateKey, passphrase } = config
|
||||
const loginInfo: Config = privateKey
|
||||
? {
|
||||
username,
|
||||
privateKey: fs.readFileSync(privateKey),
|
||||
passphrase: passphrase || undefined
|
||||
}
|
||||
: { username, password }
|
||||
remote = this.changeWinStylePathToUnix(remote)
|
||||
if (remote === '/' || remote.includes('*')) return false
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
client
|
||||
.on('ready', () => {
|
||||
client.sftp(
|
||||
(
|
||||
err: any,
|
||||
sftp: {
|
||||
unlink: (arg0: string, arg1: (err: any) => void) => void
|
||||
}
|
||||
) => {
|
||||
// eslint-disable-next-line prefer-promise-reject-errors
|
||||
if (err) reject(false)
|
||||
sftp.unlink(remote, (err: any) => {
|
||||
// eslint-disable-next-line prefer-promise-reject-errors
|
||||
if (err) reject(false)
|
||||
client.end()
|
||||
resolve(true)
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
.connect({
|
||||
host: config.host,
|
||||
port: Number(config.port) || 22,
|
||||
...loginInfo
|
||||
})
|
||||
})
|
||||
return (await promise) as boolean
|
||||
} catch (err: any) {
|
||||
logger.error(err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private async exec (script: string): Promise<boolean> {
|
||||
const execResult = await SSHClient.client.execCommand(script)
|
||||
return execResult.code === 0
|
||||
}
|
||||
|
||||
async execCommand (script: string): Promise<SSHExecCommandResponse> {
|
||||
const execResult = await SSHClient.client.execCommand(script)
|
||||
return execResult || { code: 1, stdout: '', stderr: '' }
|
||||
}
|
||||
|
||||
async getFile (local: string, remote: string): Promise<boolean> {
|
||||
if (!this._isConnected) {
|
||||
throw new Error('SSH 未连接')
|
||||
}
|
||||
try {
|
||||
remote = this.changeWinStylePathToUnix(remote)
|
||||
local = this.changeWinStylePathToUnix(local)
|
||||
await SSHClient.client.getFile(local, remote, undefined, {
|
||||
concurrency: 1
|
||||
})
|
||||
return true
|
||||
} catch (err: any) {
|
||||
logger.error(err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async putFile (
|
||||
local: string,
|
||||
remote: string,
|
||||
config: {
|
||||
fileMode?: string
|
||||
dirMode?: string
|
||||
} = {}
|
||||
): Promise<boolean> {
|
||||
if (!this._isConnected) {
|
||||
throw new Error('SSH 未连接')
|
||||
}
|
||||
try {
|
||||
remote = this.changeWinStylePathToUnix(remote)
|
||||
await this.mkdir(path.dirname(remote).replace(/^\/+|\/+$/g, ''), config)
|
||||
await SSHClient.client.putFile(local, remote)
|
||||
const fileMode = config.fileMode || '0644'
|
||||
if (fileMode !== '0644') {
|
||||
const script = `chmod ${fileMode} "${remote}"`
|
||||
return await this.exec(script)
|
||||
}
|
||||
return true
|
||||
} catch (err: any) {
|
||||
logger.error(err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async mkdir (
|
||||
dirPath: string,
|
||||
config: {
|
||||
dirMode?: string
|
||||
} = {}
|
||||
): Promise<boolean> {
|
||||
if (!this._isConnected) {
|
||||
throw new Error('SSH 未连接')
|
||||
}
|
||||
try {
|
||||
const directoryMode = config.dirMode || '0755'
|
||||
if (directoryMode === '0755') {
|
||||
const script = `mkdir -p "${dirPath}"`
|
||||
return await this.exec(script)
|
||||
} else {
|
||||
const dirs = dirPath.split('/')
|
||||
let currentPath = ''
|
||||
for (const dir of dirs) {
|
||||
if (dir) {
|
||||
currentPath += `/${dir}`
|
||||
const script = `mkdir "${currentPath}" && chmod ${directoryMode} "${currentPath}"`
|
||||
const result = await this.exec(script)
|
||||
if (!result) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
} catch (err: any) {
|
||||
logger.error(err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
get isConnected (): boolean {
|
||||
return SSHClient.client.isConnected()
|
||||
}
|
||||
|
||||
close (): void {
|
||||
SSHClient.client.dispose()
|
||||
this._isConnected = false
|
||||
}
|
||||
}
|
||||
|
||||
export default SSHClient
|
||||
import path from 'node:path'
|
||||
|
||||
import logger from '@core/picgo/logger'
|
||||
import fs from 'fs-extra'
|
||||
import { Config, NodeSSH, SSHExecCommandResponse } from 'node-ssh-no-cpu-features'
|
||||
import { ISftpPlistConfig } from 'piclist/dist/types'
|
||||
import { Client } from 'ssh2-no-cpu-features'
|
||||
|
||||
class SSHClient {
|
||||
private static _instance: SSHClient
|
||||
private static _client: NodeSSH
|
||||
private _isConnected = false
|
||||
|
||||
static get instance(): SSHClient {
|
||||
return this._instance || (this._instance = new this())
|
||||
}
|
||||
|
||||
static get client(): NodeSSH {
|
||||
return this._client || (this._client = new NodeSSH())
|
||||
}
|
||||
|
||||
private changeWinStylePathToUnix(path: string): string {
|
||||
return path.replace(/\\/g, '/')
|
||||
}
|
||||
|
||||
async connect(config: ISftpPlistConfig): Promise<boolean> {
|
||||
const { username, password, privateKey, passphrase } = config
|
||||
const loginInfo: Config = privateKey
|
||||
? {
|
||||
username,
|
||||
privateKeyPath: privateKey,
|
||||
passphrase: passphrase || undefined
|
||||
}
|
||||
: { username, password }
|
||||
try {
|
||||
await SSHClient.client.connect({
|
||||
host: config.host,
|
||||
port: Number(config.port) || 22,
|
||||
...loginInfo
|
||||
})
|
||||
this._isConnected = true
|
||||
return true
|
||||
} catch (err: any) {
|
||||
throw new Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
async deleteFileSFTP(config: ISftpPlistConfig, remote: string): Promise<boolean> {
|
||||
try {
|
||||
const client = new Client()
|
||||
const { username, password, privateKey, passphrase } = config
|
||||
const loginInfo: Config = privateKey
|
||||
? {
|
||||
username,
|
||||
privateKey: fs.readFileSync(privateKey),
|
||||
passphrase: passphrase || undefined
|
||||
}
|
||||
: { username, password }
|
||||
remote = this.changeWinStylePathToUnix(remote)
|
||||
if (remote === '/' || remote.includes('*')) return false
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
client
|
||||
.on('ready', () => {
|
||||
client.sftp(
|
||||
(
|
||||
err: any,
|
||||
sftp: {
|
||||
unlink: (arg0: string, arg1: (err: any) => void) => void
|
||||
}
|
||||
) => {
|
||||
// eslint-disable-next-line prefer-promise-reject-errors
|
||||
if (err) reject(false)
|
||||
sftp.unlink(remote, (err: any) => {
|
||||
// eslint-disable-next-line prefer-promise-reject-errors
|
||||
if (err) reject(false)
|
||||
client.end()
|
||||
resolve(true)
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
.connect({
|
||||
host: config.host,
|
||||
port: Number(config.port) || 22,
|
||||
...loginInfo
|
||||
})
|
||||
})
|
||||
return (await promise) as boolean
|
||||
} catch (err: any) {
|
||||
logger.error(err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private async exec(script: string): Promise<boolean> {
|
||||
const execResult = await SSHClient.client.execCommand(script)
|
||||
return execResult.code === 0
|
||||
}
|
||||
|
||||
async execCommand(script: string): Promise<SSHExecCommandResponse> {
|
||||
const execResult = await SSHClient.client.execCommand(script)
|
||||
return execResult || { code: 1, stdout: '', stderr: '' }
|
||||
}
|
||||
|
||||
async getFile(local: string, remote: string): Promise<boolean> {
|
||||
if (!this._isConnected) {
|
||||
throw new Error('SSH 未连接')
|
||||
}
|
||||
try {
|
||||
remote = this.changeWinStylePathToUnix(remote)
|
||||
local = this.changeWinStylePathToUnix(local)
|
||||
await SSHClient.client.getFile(local, remote, undefined, {
|
||||
concurrency: 1
|
||||
})
|
||||
return true
|
||||
} catch (err: any) {
|
||||
logger.error(err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async putFile(
|
||||
local: string,
|
||||
remote: string,
|
||||
config: {
|
||||
fileMode?: string
|
||||
dirMode?: string
|
||||
} = {}
|
||||
): Promise<boolean> {
|
||||
if (!this._isConnected) {
|
||||
throw new Error('SSH 未连接')
|
||||
}
|
||||
try {
|
||||
remote = this.changeWinStylePathToUnix(remote)
|
||||
await this.mkdir(path.dirname(remote).replace(/^\/+|\/+$/g, ''), config)
|
||||
await SSHClient.client.putFile(local, remote)
|
||||
const fileMode = config.fileMode || '0644'
|
||||
if (fileMode !== '0644') {
|
||||
const script = `chmod ${fileMode} "${remote}"`
|
||||
return await this.exec(script)
|
||||
}
|
||||
return true
|
||||
} catch (err: any) {
|
||||
logger.error(err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async mkdir(
|
||||
dirPath: string,
|
||||
config: {
|
||||
dirMode?: string
|
||||
} = {}
|
||||
): Promise<boolean> {
|
||||
if (!this._isConnected) {
|
||||
throw new Error('SSH 未连接')
|
||||
}
|
||||
try {
|
||||
const directoryMode = config.dirMode || '0755'
|
||||
if (directoryMode === '0755') {
|
||||
const script = `mkdir -p "${dirPath}"`
|
||||
return await this.exec(script)
|
||||
} else {
|
||||
const dirs = dirPath.split('/')
|
||||
let currentPath = ''
|
||||
for (const dir of dirs) {
|
||||
if (dir) {
|
||||
currentPath += `/${dir}`
|
||||
const script = `mkdir "${currentPath}" && chmod ${directoryMode} "${currentPath}"`
|
||||
const result = await this.exec(script)
|
||||
if (!result) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
} catch (err: any) {
|
||||
logger.error(err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
get isConnected(): boolean {
|
||||
return SSHClient.client.isConnected()
|
||||
}
|
||||
|
||||
close(): void {
|
||||
SSHClient.client.dispose()
|
||||
this._isConnected = false
|
||||
}
|
||||
}
|
||||
|
||||
export default SSHClient
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
export const CLIPBOARD_IMAGE_FOLDER = 'piclist-clipboard-images'
|
||||
|
||||
export const cancelDownloadLoadingFileList = 'cancelDownloadLoadingFileList'
|
||||
export const refreshDownloadFileTransferList = 'refreshDownloadFileTransferList'
|
||||
|
||||
export const picBedsCanbeDeleted = [
|
||||
'aliyun',
|
||||
'alist',
|
||||
'alistplist',
|
||||
'aws-s3',
|
||||
'aws-s3-plist',
|
||||
'dogecloud',
|
||||
'github',
|
||||
'huaweicloud-uploader',
|
||||
'imgur',
|
||||
'local',
|
||||
'lskyplist',
|
||||
'piclist',
|
||||
'qiniu',
|
||||
'sftpplist',
|
||||
'smms',
|
||||
'tcyun',
|
||||
'upyun',
|
||||
'webdavplist'
|
||||
]
|
||||
export const CLIPBOARD_IMAGE_FOLDER = 'piclist-clipboard-images'
|
||||
|
||||
export const cancelDownloadLoadingFileList = 'cancelDownloadLoadingFileList'
|
||||
export const refreshDownloadFileTransferList = 'refreshDownloadFileTransferList'
|
||||
|
||||
export const picBedsCanbeDeleted = [
|
||||
'aliyun',
|
||||
'alist',
|
||||
'alistplist',
|
||||
'aws-s3',
|
||||
'aws-s3-plist',
|
||||
'dogecloud',
|
||||
'github',
|
||||
'huaweicloud-uploader',
|
||||
'imgur',
|
||||
'local',
|
||||
'lskyplist',
|
||||
'piclist',
|
||||
'qiniu',
|
||||
'sftpplist',
|
||||
'smms',
|
||||
'tcyun',
|
||||
'upyun',
|
||||
'webdavplist'
|
||||
]
|
||||
|
||||
@@ -37,16 +37,16 @@ const getSyncConfig = () => {
|
||||
const getProxyagent = (proxy: string | undefined) => {
|
||||
return proxy
|
||||
? new HttpsProxyAgent({
|
||||
keepAlive: true,
|
||||
keepAliveMsecs: 1000,
|
||||
rejectUnauthorized: false,
|
||||
proxy: proxy.replace('127.0.0.1', 'localhost'),
|
||||
scheduling: 'lifo'
|
||||
})
|
||||
keepAlive: true,
|
||||
keepAliveMsecs: 1000,
|
||||
rejectUnauthorized: false,
|
||||
proxy: proxy.replace('127.0.0.1', 'localhost'),
|
||||
scheduling: 'lifo'
|
||||
})
|
||||
: undefined
|
||||
}
|
||||
|
||||
function getOctokit (syncConfig: ISyncConfig) {
|
||||
function getOctokit(syncConfig: ISyncConfig) {
|
||||
const { token, proxy } = syncConfig
|
||||
return new Octokit({
|
||||
auth: token,
|
||||
@@ -83,7 +83,7 @@ const isSyncConfigValidate = ({
|
||||
return type && username && repo && branch && token
|
||||
}
|
||||
|
||||
async function uploadLocalToRemote (syncConfig: ISyncConfig, fileName: string) {
|
||||
async function uploadLocalToRemote(syncConfig: ISyncConfig, fileName: string) {
|
||||
const localFilePath = path.join(STORE_PATH, fileName)
|
||||
if (!fs.existsSync(localFilePath)) {
|
||||
return false
|
||||
@@ -161,7 +161,7 @@ async function uploadLocalToRemote (syncConfig: ISyncConfig, fileName: string) {
|
||||
}
|
||||
}
|
||||
|
||||
async function updateLocalToRemote (syncConfig: ISyncConfig, fileName: string) {
|
||||
async function updateLocalToRemote(syncConfig: ISyncConfig, fileName: string) {
|
||||
const localFilePath = path.join(STORE_PATH, fileName)
|
||||
if (!fs.existsSync(localFilePath)) {
|
||||
return false
|
||||
@@ -277,7 +277,7 @@ async function updateLocalToRemote (syncConfig: ISyncConfig, fileName: string) {
|
||||
}
|
||||
}
|
||||
|
||||
async function uploadFile (fileName: string[]): Promise<number> {
|
||||
async function uploadFile(fileName: string[]): Promise<number> {
|
||||
const syncConfig = getSyncConfig()
|
||||
if (!isSyncConfigValidate(syncConfig)) {
|
||||
logger.error('sync config is invalid')
|
||||
@@ -302,7 +302,7 @@ async function uploadFile (fileName: string[]): Promise<number> {
|
||||
return count
|
||||
}
|
||||
|
||||
async function downloadAndWriteFile (url: string, localFilePath: string, config: any, isWriteJson = false) {
|
||||
async function downloadAndWriteFile(url: string, localFilePath: string, config: any, isWriteJson = false) {
|
||||
const res = await axios.get(url, config)
|
||||
if (isHttpResSuccess(res)) {
|
||||
await fs.writeFile(
|
||||
@@ -314,7 +314,7 @@ async function downloadAndWriteFile (url: string, localFilePath: string, config:
|
||||
return false
|
||||
}
|
||||
|
||||
async function downloadRemoteToLocal (syncConfig: ISyncConfig, fileName: string) {
|
||||
async function downloadRemoteToLocal(syncConfig: ISyncConfig, fileName: string) {
|
||||
const localFilePath = path.join(STORE_PATH, fileName)
|
||||
const { username, repo, branch, token, proxy, type } = syncConfig
|
||||
try {
|
||||
@@ -394,7 +394,7 @@ async function downloadRemoteToLocal (syncConfig: ISyncConfig, fileName: string)
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadFile (fileName: string[]): Promise<number> {
|
||||
async function downloadFile(fileName: string[]): Promise<number> {
|
||||
const syncConfig = getSyncConfig()
|
||||
if (!isSyncConfigValidate(syncConfig)) {
|
||||
logger.error('sync config is invalid')
|
||||
|
||||
@@ -1,66 +1,72 @@
|
||||
import db from '@core/datastore'
|
||||
import windowManager from 'apis/app/window/windowManager'
|
||||
import { screen } from 'electron'
|
||||
|
||||
import { configPaths } from '~/utils/configPaths'
|
||||
import { IWindowList } from '~/utils/enum'
|
||||
|
||||
export function openMiniWindow (hideSettingWindow: boolean = true) {
|
||||
const miniWindow = windowManager.get(IWindowList.MINI_WINDOW)!
|
||||
|
||||
miniWindow.removeAllListeners('close')
|
||||
miniWindow.removeAllListeners('move')
|
||||
|
||||
if (db.get(configPaths.settings.miniWindowOntop)) {
|
||||
miniWindow.setAlwaysOnTop(true)
|
||||
}
|
||||
const { width, height } = screen.getPrimaryDisplay().workAreaSize
|
||||
const lastPosition = db.get(configPaths.settings.miniWindowPosition)
|
||||
const setPositionFunc = () => {
|
||||
const position = miniWindow.getPosition()
|
||||
db.set(configPaths.settings.miniWindowPosition, position)
|
||||
}
|
||||
if (lastPosition) {
|
||||
if (lastPosition[0] < 0 || lastPosition[0] > width || lastPosition[1] < 0 || lastPosition[1] > height) {
|
||||
miniWindow.setPosition(width - 100, height - 100)
|
||||
db.set(configPaths.settings.miniWindowPosition, [width - 100, height - 100])
|
||||
} else if (lastPosition[0] + miniWindow.getSize()[0] > width || lastPosition[1] + miniWindow.getSize()[1] > height) {
|
||||
miniWindow.setPosition(width - miniWindow.getSize()[0], height - miniWindow.getSize()[1])
|
||||
db.set(configPaths.settings.miniWindowPosition, [width - miniWindow.getSize()[0], height - miniWindow.getSize()[1]])
|
||||
} else {
|
||||
miniWindow.setPosition(lastPosition[0], lastPosition[1])
|
||||
}
|
||||
} else {
|
||||
miniWindow.setPosition(width - 100, height - 100)
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
import db from '@core/datastore'
|
||||
import windowManager from 'apis/app/window/windowManager'
|
||||
import { screen } from 'electron'
|
||||
|
||||
import { configPaths } from '~/utils/configPaths'
|
||||
import { IWindowList } from '~/utils/enum'
|
||||
|
||||
export function openMiniWindow(hideSettingWindow: boolean = true) {
|
||||
const miniWindow = windowManager.get(IWindowList.MINI_WINDOW)!
|
||||
|
||||
miniWindow.removeAllListeners('close')
|
||||
miniWindow.removeAllListeners('move')
|
||||
|
||||
if (db.get(configPaths.settings.miniWindowOntop)) {
|
||||
miniWindow.setAlwaysOnTop(true)
|
||||
}
|
||||
const { width, height } = screen.getPrimaryDisplay().workAreaSize
|
||||
const lastPosition = db.get(configPaths.settings.miniWindowPosition)
|
||||
const setPositionFunc = () => {
|
||||
const position = miniWindow.getPosition()
|
||||
db.set(configPaths.settings.miniWindowPosition, position)
|
||||
}
|
||||
if (lastPosition) {
|
||||
if (lastPosition[0] < 0 || lastPosition[0] > width || lastPosition[1] < 0 || lastPosition[1] > height) {
|
||||
miniWindow.setPosition(width - 100, height - 100)
|
||||
db.set(configPaths.settings.miniWindowPosition, [width - 100, height - 100])
|
||||
} else if (
|
||||
lastPosition[0] + miniWindow.getSize()[0] > width ||
|
||||
lastPosition[1] + miniWindow.getSize()[1] > height
|
||||
) {
|
||||
miniWindow.setPosition(width - miniWindow.getSize()[0], height - miniWindow.getSize()[1])
|
||||
db.set(configPaths.settings.miniWindowPosition, [
|
||||
width - miniWindow.getSize()[0],
|
||||
height - miniWindow.getSize()[1]
|
||||
])
|
||||
} else {
|
||||
miniWindow.setPosition(lastPosition[0], lastPosition[1])
|
||||
}
|
||||
} else {
|
||||
miniWindow.setPosition(width - 100, height - 100)
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,101 +1,101 @@
|
||||
import crypto from 'node:crypto'
|
||||
import path from 'node:path'
|
||||
|
||||
import { clipboard, contextBridge, ipcRenderer, IpcRendererEvent, webFrame, webUtils } from 'electron'
|
||||
import fs from 'fs-extra'
|
||||
import yaml from 'js-yaml'
|
||||
import mime from 'mime-types'
|
||||
import { isReactive, isRef, toRaw, unref } from 'vue'
|
||||
|
||||
export const getRawData = (args: any): any => {
|
||||
if (isRef(args)) return unref(args)
|
||||
if (isReactive(args)) return toRaw(args)
|
||||
if (Array.isArray(args)) return args.map(getRawData)
|
||||
if (typeof args === 'object' && args !== null) {
|
||||
const data = {} as Record<string, any>
|
||||
for (const key in args) {
|
||||
data[key] = getRawData(args[key])
|
||||
}
|
||||
return data
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
function sendToMain (channel: string, ...args: any[]) {
|
||||
ipcRenderer.send(channel, ...getRawData(args))
|
||||
}
|
||||
|
||||
function sendRPC (action: string, ...args: any[]): void {
|
||||
ipcRenderer.send('RPC_ACTIONS', action, getRawData(args))
|
||||
}
|
||||
|
||||
async function triggerRPC<T> (action: string, ...args: any[]): Promise<T | undefined> {
|
||||
return await ipcRenderer.invoke('RPC_ACTIONS_INVOKE', action, getRawData(args))
|
||||
}
|
||||
|
||||
function sendRpcSync (action: string, ...args: any[]): any {
|
||||
return ipcRenderer.sendSync('RPC_ACTIONS', action, getRawData(args))
|
||||
}
|
||||
|
||||
try {
|
||||
contextBridge.exposeInMainWorld('electron', {
|
||||
setVisualZoomLevelLimits: (min: number, max: number) => {
|
||||
webFrame.setVisualZoomLevelLimits(min, max)
|
||||
},
|
||||
clipboard: {
|
||||
writeText: clipboard.writeText
|
||||
},
|
||||
platform: process.platform,
|
||||
sendRpcSync,
|
||||
triggerRPC,
|
||||
sendToMain,
|
||||
sendRPC,
|
||||
ipcRendererOn: (channel: string, listener: (...args: any[]) => void) => {
|
||||
const subscription = (_: IpcRendererEvent, ...args: any[]) => listener(...args)
|
||||
ipcRenderer.on(channel, subscription)
|
||||
return () => {
|
||||
ipcRenderer.removeListener(channel, subscription)
|
||||
}
|
||||
},
|
||||
ipcRendererCountListeners: (channel: string): number => {
|
||||
return ipcRenderer.listenerCount(channel)
|
||||
},
|
||||
ipcRendererRemoveAllListeners: (channel: string) => {
|
||||
ipcRenderer.removeAllListeners(channel)
|
||||
},
|
||||
showFilePath (file: File) {
|
||||
return webUtils.getPathForFile(file)
|
||||
}
|
||||
})
|
||||
|
||||
contextBridge.exposeInMainWorld('node', {
|
||||
path: {
|
||||
join: path.join,
|
||||
dirname: path.dirname,
|
||||
basename: path.basename,
|
||||
normalize: path.normalize,
|
||||
extname: path.extname,
|
||||
sep: path.sep,
|
||||
posix: {
|
||||
sep: path.posix.sep
|
||||
}
|
||||
},
|
||||
fs: {
|
||||
remove: fs.remove,
|
||||
readFile: fs.readFile,
|
||||
statSync: fs.statSync
|
||||
},
|
||||
crypto: {
|
||||
randomBytes: crypto.randomBytes,
|
||||
createHash: crypto.createHash
|
||||
},
|
||||
yaml: {
|
||||
load: yaml.load
|
||||
},
|
||||
mime: {
|
||||
lookup: mime.lookup
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
import crypto from 'node:crypto'
|
||||
import path from 'node:path'
|
||||
|
||||
import { clipboard, contextBridge, ipcRenderer, IpcRendererEvent, webFrame, webUtils } from 'electron'
|
||||
import fs from 'fs-extra'
|
||||
import yaml from 'js-yaml'
|
||||
import mime from 'mime-types'
|
||||
import { isReactive, isRef, toRaw, unref } from 'vue'
|
||||
|
||||
export const getRawData = (args: any): any => {
|
||||
if (isRef(args)) return unref(args)
|
||||
if (isReactive(args)) return toRaw(args)
|
||||
if (Array.isArray(args)) return args.map(getRawData)
|
||||
if (typeof args === 'object' && args !== null) {
|
||||
const data = {} as Record<string, any>
|
||||
for (const key in args) {
|
||||
data[key] = getRawData(args[key])
|
||||
}
|
||||
return data
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
function sendToMain(channel: string, ...args: any[]) {
|
||||
ipcRenderer.send(channel, ...getRawData(args))
|
||||
}
|
||||
|
||||
function sendRPC(action: string, ...args: any[]): void {
|
||||
ipcRenderer.send('RPC_ACTIONS', action, getRawData(args))
|
||||
}
|
||||
|
||||
async function triggerRPC<T>(action: string, ...args: any[]): Promise<T | undefined> {
|
||||
return await ipcRenderer.invoke('RPC_ACTIONS_INVOKE', action, getRawData(args))
|
||||
}
|
||||
|
||||
function sendRpcSync(action: string, ...args: any[]): any {
|
||||
return ipcRenderer.sendSync('RPC_ACTIONS', action, getRawData(args))
|
||||
}
|
||||
|
||||
try {
|
||||
contextBridge.exposeInMainWorld('electron', {
|
||||
setVisualZoomLevelLimits: (min: number, max: number) => {
|
||||
webFrame.setVisualZoomLevelLimits(min, max)
|
||||
},
|
||||
clipboard: {
|
||||
writeText: clipboard.writeText
|
||||
},
|
||||
platform: process.platform,
|
||||
sendRpcSync,
|
||||
triggerRPC,
|
||||
sendToMain,
|
||||
sendRPC,
|
||||
ipcRendererOn: (channel: string, listener: (...args: any[]) => void) => {
|
||||
const subscription = (_: IpcRendererEvent, ...args: any[]) => listener(...args)
|
||||
ipcRenderer.on(channel, subscription)
|
||||
return () => {
|
||||
ipcRenderer.removeListener(channel, subscription)
|
||||
}
|
||||
},
|
||||
ipcRendererCountListeners: (channel: string): number => {
|
||||
return ipcRenderer.listenerCount(channel)
|
||||
},
|
||||
ipcRendererRemoveAllListeners: (channel: string) => {
|
||||
ipcRenderer.removeAllListeners(channel)
|
||||
},
|
||||
showFilePath(file: File) {
|
||||
return webUtils.getPathForFile(file)
|
||||
}
|
||||
})
|
||||
|
||||
contextBridge.exposeInMainWorld('node', {
|
||||
path: {
|
||||
join: path.join,
|
||||
dirname: path.dirname,
|
||||
basename: path.basename,
|
||||
normalize: path.normalize,
|
||||
extname: path.extname,
|
||||
sep: path.sep,
|
||||
posix: {
|
||||
sep: path.posix.sep
|
||||
}
|
||||
},
|
||||
fs: {
|
||||
remove: fs.remove,
|
||||
readFile: fs.readFile,
|
||||
statSync: fs.statSync
|
||||
},
|
||||
crypto: {
|
||||
randomBytes: crypto.randomBytes,
|
||||
createHash: crypto.createHash
|
||||
},
|
||||
yaml: {
|
||||
load: yaml.load
|
||||
},
|
||||
mime: {
|
||||
lookup: mime.lookup
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
|
||||
@@ -1,60 +1,56 @@
|
||||
<template>
|
||||
<div
|
||||
id="app"
|
||||
:key="pageReloadCount"
|
||||
>
|
||||
<router-view />
|
||||
<UIServiceProvider />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { IConfig } from 'piclist'
|
||||
import { onBeforeMount, onMounted } from 'vue'
|
||||
|
||||
import UIServiceProvider from '@/components/ui/UIServiceProvider.vue'
|
||||
import { useATagClick } from '@/hooks/useATagClick'
|
||||
import { useStore } from '@/hooks/useStore'
|
||||
import { getConfig } from '@/utils/dataSender'
|
||||
import { pageReloadCount } from '@/utils/global'
|
||||
|
||||
import { useAppStore } from './hooks/useAppStore'
|
||||
|
||||
useATagClick()
|
||||
|
||||
const store = useStore()
|
||||
const appStore = useAppStore()
|
||||
|
||||
onBeforeMount(async () => {
|
||||
const config = await getConfig<IConfig>()
|
||||
if (config) {
|
||||
store?.setDefaultPicBed(config?.picBed?.uploader || config?.picBed?.current || 'smms')
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
appStore.init()
|
||||
} catch (error) {
|
||||
console.error('Failed to load settings:', error)
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'PicGoApp'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
body,
|
||||
html
|
||||
padding 0
|
||||
margin 0
|
||||
height 100%
|
||||
#app
|
||||
height 100%
|
||||
user-select none
|
||||
</style>
|
||||
<template>
|
||||
<div id="app" :key="pageReloadCount">
|
||||
<router-view />
|
||||
<UIServiceProvider />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { IConfig } from 'piclist'
|
||||
import { onBeforeMount, onMounted } from 'vue'
|
||||
|
||||
import UIServiceProvider from '@/components/ui/UIServiceProvider.vue'
|
||||
import { useATagClick } from '@/hooks/useATagClick'
|
||||
import { useStore } from '@/hooks/useStore'
|
||||
import { getConfig } from '@/utils/dataSender'
|
||||
import { pageReloadCount } from '@/utils/global'
|
||||
|
||||
import { useAppStore } from './hooks/useAppStore'
|
||||
|
||||
useATagClick()
|
||||
|
||||
const store = useStore()
|
||||
const appStore = useAppStore()
|
||||
|
||||
onBeforeMount(async () => {
|
||||
const config = await getConfig<IConfig>()
|
||||
if (config) {
|
||||
store?.setDefaultPicBed(config?.picBed?.uploader || config?.picBed?.current || 'smms')
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
appStore.init()
|
||||
} catch (error) {
|
||||
console.error('Failed to load settings:', error)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'PicGoApp'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
body,
|
||||
html
|
||||
padding 0
|
||||
margin 0
|
||||
height 100%
|
||||
#app
|
||||
height 100%
|
||||
user-select none
|
||||
</style>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { IRPCActionType } from '@/utils/enum'
|
||||
import type { IStringKeyMap } from '#/types/types'
|
||||
|
||||
export default class ALLApi {
|
||||
static async delete (configMap: IStringKeyMap): Promise<boolean> {
|
||||
static async delete(configMap: IStringKeyMap): Promise<boolean> {
|
||||
return (await window.electron.triggerRPC(IRPCActionType.DELETE_ALL_API, getRawData(configMap))) || false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,115 +1,107 @@
|
||||
<template>
|
||||
<div class="image-container">
|
||||
<div
|
||||
v-if="isLoading"
|
||||
class="loading-placeholder"
|
||||
>
|
||||
<div class="loading-spinner" />
|
||||
</div>
|
||||
<img
|
||||
v-else-if="!hasError"
|
||||
:src="
|
||||
isShowThumbnail && item.isImage
|
||||
? base64Image
|
||||
: `./assets/icons/${getFileIconPath(item.fileName ?? '')}`
|
||||
"
|
||||
alt=""
|
||||
class="image"
|
||||
@load="handleImageLoad"
|
||||
@error="handleImageError"
|
||||
>
|
||||
<img
|
||||
v-else
|
||||
:src="`./assets/icons/${getFileIconPath(item.fileName ?? '')}`"
|
||||
alt=""
|
||||
class="image"
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onBeforeMount, ref } from 'vue'
|
||||
|
||||
import { getFileIconPath } from '@/manage/utils/common'
|
||||
|
||||
const base64Image = ref('')
|
||||
const isLoading = ref(true)
|
||||
const hasError = ref(false)
|
||||
|
||||
const props = defineProps<{
|
||||
isShowThumbnail: boolean
|
||||
item: {
|
||||
isImage: boolean
|
||||
fileName: string
|
||||
}
|
||||
localPath: string
|
||||
}>()
|
||||
|
||||
const createBase64Image = async () => {
|
||||
try {
|
||||
const filePath = window.node.path.normalize(props.localPath)
|
||||
const base64 = await window.node.fs.readFile(filePath, 'base64')
|
||||
base64Image.value = `data:${window.node.mime.lookup(filePath) || 'image/png'};base64,${base64}`
|
||||
isLoading.value = false
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
hasError.value = true
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleImageLoad = () => {
|
||||
isLoading.value = false
|
||||
hasError.value = false
|
||||
}
|
||||
|
||||
const handleImageError = () => {
|
||||
isLoading.value = false
|
||||
hasError.value = true
|
||||
}
|
||||
|
||||
onBeforeMount(async () => {
|
||||
await createBase64Image()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.image-container {
|
||||
height: 100px;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.image {
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.loading-placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: 2px solid #e4e7ed;
|
||||
border-top: 2px solid #409eff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div class="image-container">
|
||||
<div v-if="isLoading" class="loading-placeholder">
|
||||
<div class="loading-spinner" />
|
||||
</div>
|
||||
<img
|
||||
v-else-if="!hasError"
|
||||
:src="isShowThumbnail && item.isImage ? base64Image : `./assets/icons/${getFileIconPath(item.fileName ?? '')}`"
|
||||
alt=""
|
||||
class="image"
|
||||
@load="handleImageLoad"
|
||||
@error="handleImageError"
|
||||
/>
|
||||
<img v-else :src="`./assets/icons/${getFileIconPath(item.fileName ?? '')}`" alt="" class="image" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onBeforeMount, ref } from 'vue'
|
||||
|
||||
import { getFileIconPath } from '@/manage/utils/common'
|
||||
|
||||
const base64Image = ref('')
|
||||
const isLoading = ref(true)
|
||||
const hasError = ref(false)
|
||||
|
||||
const props = defineProps<{
|
||||
isShowThumbnail: boolean
|
||||
item: {
|
||||
isImage: boolean
|
||||
fileName: string
|
||||
}
|
||||
localPath: string
|
||||
}>()
|
||||
|
||||
const createBase64Image = async () => {
|
||||
try {
|
||||
const filePath = window.node.path.normalize(props.localPath)
|
||||
const base64 = await window.node.fs.readFile(filePath, 'base64')
|
||||
base64Image.value = `data:${window.node.mime.lookup(filePath) || 'image/png'};base64,${base64}`
|
||||
isLoading.value = false
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
hasError.value = true
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleImageLoad = () => {
|
||||
isLoading.value = false
|
||||
hasError.value = false
|
||||
}
|
||||
|
||||
const handleImageError = () => {
|
||||
isLoading.value = false
|
||||
hasError.value = true
|
||||
}
|
||||
|
||||
onBeforeMount(async () => {
|
||||
await createBase64Image()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.image-container {
|
||||
height: 100px;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.image {
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.loading-placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: 2px solid #e4e7ed;
|
||||
border-top: 2px solid #409eff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,123 +1,123 @@
|
||||
<template>
|
||||
<div class="image-container">
|
||||
<div
|
||||
v-if="isLoading"
|
||||
class="loading-placeholder"
|
||||
>
|
||||
<div class="loading-spinner" />
|
||||
</div>
|
||||
<img
|
||||
v-else-if="!hasError"
|
||||
:src="imageSource"
|
||||
alt=""
|
||||
class="image"
|
||||
@load="handleImageLoad"
|
||||
@error="handleImageError"
|
||||
>
|
||||
<img
|
||||
v-else
|
||||
:src="iconPath"
|
||||
alt=""
|
||||
class="image"
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
|
||||
import { getFileIconPath } from '@/manage/utils/common'
|
||||
import { IRPCActionType } from '@/utils/enum'
|
||||
|
||||
const preSignedUrl = ref('')
|
||||
const isLoading = ref(true)
|
||||
const hasError = ref(false)
|
||||
|
||||
const props = defineProps<{
|
||||
item: {
|
||||
key: string
|
||||
isImage: boolean
|
||||
fileName: string | null | undefined
|
||||
}
|
||||
alias: string
|
||||
url: string
|
||||
config: any
|
||||
isShowThumbnail: boolean
|
||||
}>()
|
||||
|
||||
const imageSource = computed(() => {
|
||||
return props.isShowThumbnail && props.item.isImage
|
||||
? preSignedUrl.value
|
||||
: `./assets/icons/${getFileIconPath(props.item.fileName ?? '')}`
|
||||
})
|
||||
|
||||
const iconPath = computed(() => `./assets/icons/${getFileIconPath(props.item.fileName ?? '')}`)
|
||||
|
||||
async function getUrl () {
|
||||
try {
|
||||
isLoading.value = true
|
||||
hasError.value = false
|
||||
preSignedUrl.value = await window.electron.triggerRPC<any>(IRPCActionType.MANAGE_GET_PRE_SIGNED_URL, props.alias, props.config)
|
||||
isLoading.value = false
|
||||
} catch (error) {
|
||||
console.error('Failed to get pre-signed URL:', error)
|
||||
hasError.value = true
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleImageLoad = () => {
|
||||
isLoading.value = false
|
||||
hasError.value = false
|
||||
}
|
||||
|
||||
const handleImageError = () => {
|
||||
isLoading.value = false
|
||||
hasError.value = true
|
||||
}
|
||||
|
||||
watch(() => [props.url, props.item], getUrl, { deep: true })
|
||||
|
||||
onMounted(getUrl)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.image-container {
|
||||
height: 100px;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.image {
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.loading-placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: 2px solid #e4e7ed;
|
||||
border-top: 2px solid #409eff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div class="image-container">
|
||||
<div v-if="isLoading" class="loading-placeholder">
|
||||
<div class="loading-spinner" />
|
||||
</div>
|
||||
<img
|
||||
v-else-if="!hasError"
|
||||
:src="imageSource"
|
||||
alt=""
|
||||
class="image"
|
||||
@load="handleImageLoad"
|
||||
@error="handleImageError"
|
||||
/>
|
||||
<img v-else :src="iconPath" alt="" class="image" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
|
||||
import { getFileIconPath } from '@/manage/utils/common'
|
||||
import { IRPCActionType } from '@/utils/enum'
|
||||
|
||||
const preSignedUrl = ref('')
|
||||
const isLoading = ref(true)
|
||||
const hasError = ref(false)
|
||||
|
||||
const props = defineProps<{
|
||||
item: {
|
||||
key: string
|
||||
isImage: boolean
|
||||
fileName: string | null | undefined
|
||||
}
|
||||
alias: string
|
||||
url: string
|
||||
config: any
|
||||
isShowThumbnail: boolean
|
||||
}>()
|
||||
|
||||
const imageSource = computed(() => {
|
||||
return props.isShowThumbnail && props.item.isImage
|
||||
? preSignedUrl.value
|
||||
: `./assets/icons/${getFileIconPath(props.item.fileName ?? '')}`
|
||||
})
|
||||
|
||||
const iconPath = computed(() => `./assets/icons/${getFileIconPath(props.item.fileName ?? '')}`)
|
||||
|
||||
async function getUrl() {
|
||||
try {
|
||||
isLoading.value = true
|
||||
hasError.value = false
|
||||
preSignedUrl.value = await window.electron.triggerRPC<any>(
|
||||
IRPCActionType.MANAGE_GET_PRE_SIGNED_URL,
|
||||
props.alias,
|
||||
props.config
|
||||
)
|
||||
isLoading.value = false
|
||||
} catch (error) {
|
||||
console.error('Failed to get pre-signed URL:', error)
|
||||
hasError.value = true
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleImageLoad = () => {
|
||||
isLoading.value = false
|
||||
hasError.value = false
|
||||
}
|
||||
|
||||
const handleImageError = () => {
|
||||
isLoading.value = false
|
||||
hasError.value = true
|
||||
}
|
||||
|
||||
watch(() => [props.url, props.item], getUrl, { deep: true })
|
||||
|
||||
onMounted(getUrl)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.image-container {
|
||||
height: 100px;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.image {
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.loading-placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: 2px solid #e4e7ed;
|
||||
border-top: 2px solid #409eff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,154 +1,150 @@
|
||||
<template>
|
||||
<div class="image-container">
|
||||
<div
|
||||
v-if="isLoading"
|
||||
class="loading-placeholder"
|
||||
>
|
||||
<div class="loading-spinner" />
|
||||
</div>
|
||||
<img
|
||||
v-else-if="!hasError"
|
||||
:src="imageSource"
|
||||
alt=""
|
||||
class="image"
|
||||
@load="handleImageLoad"
|
||||
@error="handleImageError"
|
||||
>
|
||||
<img
|
||||
v-else
|
||||
:src="iconPath"
|
||||
alt=""
|
||||
class="image"
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
|
||||
import { getFileIconPath } from '@/manage/utils/common'
|
||||
import { getAuthHeader } from '@/manage/utils/digestAuth'
|
||||
import { formatEndpoint } from '@/utils/common'
|
||||
|
||||
const base64Url = ref('')
|
||||
const success = ref(false)
|
||||
const isLoading = ref(true)
|
||||
const hasError = ref(false)
|
||||
|
||||
const props = defineProps<{
|
||||
item: {
|
||||
key: string
|
||||
isImage: boolean
|
||||
fileName: string | null | undefined
|
||||
}
|
||||
url: string
|
||||
config: any
|
||||
isShowThumbnail: boolean
|
||||
}>()
|
||||
|
||||
const imageSource = computed(() => {
|
||||
return props.isShowThumbnail && props.item.isImage && success.value
|
||||
? base64Url.value
|
||||
: `./assets/icons/${getFileIconPath(props.item.fileName ?? '')}`
|
||||
})
|
||||
|
||||
const iconPath = computed(() => `./assets/icons/${getFileIconPath(props.item.fileName ?? '')}`)
|
||||
|
||||
async function getWebdavHeader (key: string) {
|
||||
let headers = {} as any
|
||||
if (props.config.authType === 'digest') {
|
||||
const authHeader = await getAuthHeader(
|
||||
'GET',
|
||||
formatEndpoint(props.config.endpoint, props.config.sslEnabled || false),
|
||||
`/${key.replace(/^\//, '')}`,
|
||||
props.config.username,
|
||||
props.config.password
|
||||
)
|
||||
headers = {
|
||||
Authorization: authHeader
|
||||
}
|
||||
} else {
|
||||
headers = {
|
||||
Authorization: 'Basic ' + Buffer.from(`${props.config.username}:${props.config.password}`).toString('base64')
|
||||
}
|
||||
}
|
||||
return headers
|
||||
}
|
||||
|
||||
const fetchImage = async () => {
|
||||
try {
|
||||
isLoading.value = true
|
||||
hasError.value = false
|
||||
const headers = await getWebdavHeader(props.item.key)
|
||||
const res = await fetch(props.url, { method: 'GET', headers })
|
||||
if (res.status >= 200 && res.status < 300) {
|
||||
const blob = await res.blob()
|
||||
success.value = true
|
||||
base64Url.value = URL.createObjectURL(blob)
|
||||
isLoading.value = false
|
||||
} else {
|
||||
throw new Error('Network response was not ok.')
|
||||
}
|
||||
} catch (err) {
|
||||
success.value = false
|
||||
hasError.value = true
|
||||
isLoading.value = false
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
|
||||
const handleImageLoad = () => {
|
||||
isLoading.value = false
|
||||
hasError.value = false
|
||||
}
|
||||
|
||||
const handleImageError = () => {
|
||||
isLoading.value = false
|
||||
hasError.value = true
|
||||
}
|
||||
|
||||
watch(() => [props.url, props.item], fetchImage, { deep: true })
|
||||
|
||||
onMounted(fetchImage)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.image-container {
|
||||
height: 100px;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.image {
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.loading-placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: 2px solid #e4e7ed;
|
||||
border-top: 2px solid #409eff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div class="image-container">
|
||||
<div v-if="isLoading" class="loading-placeholder">
|
||||
<div class="loading-spinner" />
|
||||
</div>
|
||||
<img
|
||||
v-else-if="!hasError"
|
||||
:src="imageSource"
|
||||
alt=""
|
||||
class="image"
|
||||
@load="handleImageLoad"
|
||||
@error="handleImageError"
|
||||
/>
|
||||
<img v-else :src="iconPath" alt="" class="image" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
|
||||
import { getFileIconPath } from '@/manage/utils/common'
|
||||
import { getAuthHeader } from '@/manage/utils/digestAuth'
|
||||
import { formatEndpoint } from '@/utils/common'
|
||||
|
||||
const base64Url = ref('')
|
||||
const success = ref(false)
|
||||
const isLoading = ref(true)
|
||||
const hasError = ref(false)
|
||||
|
||||
const props = defineProps<{
|
||||
item: {
|
||||
key: string
|
||||
isImage: boolean
|
||||
fileName: string | null | undefined
|
||||
}
|
||||
url: string
|
||||
config: any
|
||||
isShowThumbnail: boolean
|
||||
}>()
|
||||
|
||||
const imageSource = computed(() => {
|
||||
return props.isShowThumbnail && props.item.isImage && success.value
|
||||
? base64Url.value
|
||||
: `./assets/icons/${getFileIconPath(props.item.fileName ?? '')}`
|
||||
})
|
||||
|
||||
const iconPath = computed(() => `./assets/icons/${getFileIconPath(props.item.fileName ?? '')}`)
|
||||
|
||||
async function getWebdavHeader(key: string) {
|
||||
let headers = {} as any
|
||||
if (props.config.authType === 'digest') {
|
||||
const authHeader = await getAuthHeader(
|
||||
'GET',
|
||||
formatEndpoint(props.config.endpoint, props.config.sslEnabled || false),
|
||||
`/${key.replace(/^\//, '')}`,
|
||||
props.config.username,
|
||||
props.config.password
|
||||
)
|
||||
headers = {
|
||||
Authorization: authHeader
|
||||
}
|
||||
} else {
|
||||
headers = {
|
||||
Authorization: 'Basic ' + Buffer.from(`${props.config.username}:${props.config.password}`).toString('base64')
|
||||
}
|
||||
}
|
||||
return headers
|
||||
}
|
||||
|
||||
const fetchImage = async () => {
|
||||
try {
|
||||
isLoading.value = true
|
||||
hasError.value = false
|
||||
const headers = await getWebdavHeader(props.item.key)
|
||||
const res = await fetch(props.url, { method: 'GET', headers })
|
||||
if (res.status >= 200 && res.status < 300) {
|
||||
const blob = await res.blob()
|
||||
success.value = true
|
||||
base64Url.value = URL.createObjectURL(blob)
|
||||
isLoading.value = false
|
||||
} else {
|
||||
throw new Error('Network response was not ok.')
|
||||
}
|
||||
} catch (err) {
|
||||
success.value = false
|
||||
hasError.value = true
|
||||
isLoading.value = false
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
|
||||
const handleImageLoad = () => {
|
||||
isLoading.value = false
|
||||
hasError.value = false
|
||||
}
|
||||
|
||||
const handleImageError = () => {
|
||||
isLoading.value = false
|
||||
hasError.value = true
|
||||
}
|
||||
|
||||
watch(() => [props.url, props.item], fetchImage, { deep: true })
|
||||
|
||||
onMounted(fetchImage)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.image-container {
|
||||
height: 100px;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.image {
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.loading-placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: 2px solid #e4e7ed;
|
||||
border-top: 2px solid #409eff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,22 +1,12 @@
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<div
|
||||
v-if="showInputBoxVisible"
|
||||
class="inputbox-overlay"
|
||||
@click="handleInputBoxCancel"
|
||||
>
|
||||
<div
|
||||
class="inputbox-container"
|
||||
@click.stop
|
||||
>
|
||||
<div v-if="showInputBoxVisible" class="inputbox-overlay" @click="handleInputBoxCancel">
|
||||
<div class="inputbox-container" @click.stop>
|
||||
<div class="inputbox-header">
|
||||
<h3 class="inputbox-title">
|
||||
{{ inputBoxOptions.title || t('pages.inputBox.title') }}
|
||||
</h3>
|
||||
<button
|
||||
class="inputbox-close"
|
||||
@click="handleInputBoxCancel"
|
||||
>
|
||||
<button class="inputbox-close" @click="handleInputBoxCancel">
|
||||
<X :size="20" />
|
||||
</button>
|
||||
</div>
|
||||
@@ -28,19 +18,13 @@
|
||||
type="text"
|
||||
@keyup.enter="handleInputBoxConfirm"
|
||||
@keyup.escape="handleInputBoxCancel"
|
||||
>
|
||||
/>
|
||||
</div>
|
||||
<div class="inputbox-actions">
|
||||
<button
|
||||
class="inputbox-btn cancel-btn"
|
||||
@click="handleInputBoxCancel"
|
||||
>
|
||||
<button class="inputbox-btn cancel-btn" @click="handleInputBoxCancel">
|
||||
{{ t('common.cancel') }}
|
||||
</button>
|
||||
<button
|
||||
class="inputbox-btn confirm-btn primary"
|
||||
@click="handleInputBoxConfirm"
|
||||
>
|
||||
<button class="inputbox-btn confirm-btn primary" @click="handleInputBoxConfirm">
|
||||
{{ t('common.confirm') }}
|
||||
</button>
|
||||
</div>
|
||||
@@ -66,27 +50,27 @@ const inputBoxOptions = reactive({
|
||||
placeholder: ''
|
||||
})
|
||||
|
||||
let removeInputBoxListenerCallback: (() => void) = () => {}
|
||||
let removeInputBoxListenerCallback: () => void = () => {}
|
||||
|
||||
function handleIpcInputBoxEvent (options: IShowInputBoxOption) {
|
||||
function handleIpcInputBoxEvent(options: IShowInputBoxOption) {
|
||||
initInputBoxValue(options)
|
||||
}
|
||||
|
||||
function initInputBoxValue (options: IShowInputBoxOption) {
|
||||
function initInputBoxValue(options: IShowInputBoxOption) {
|
||||
inputBoxValue.value = options.value || ''
|
||||
inputBoxOptions.title = options.title || ''
|
||||
inputBoxOptions.placeholder = options.placeholder || ''
|
||||
showInputBoxVisible.value = true
|
||||
}
|
||||
|
||||
function handleInputBoxCancel () {
|
||||
function handleInputBoxCancel() {
|
||||
// TODO: RPCServer
|
||||
showInputBoxVisible.value = false
|
||||
window.electron.sendToMain(SHOW_INPUT_BOX, '')
|
||||
$bus.emit(SHOW_INPUT_BOX_RESPONSE, '')
|
||||
}
|
||||
|
||||
function handleInputBoxConfirm () {
|
||||
function handleInputBoxConfirm() {
|
||||
showInputBoxVisible.value = false
|
||||
window.electron.sendToMain(SHOW_INPUT_BOX, inputBoxValue.value)
|
||||
$bus.emit(SHOW_INPUT_BOX_RESPONSE, inputBoxValue.value)
|
||||
@@ -126,7 +110,9 @@ export default {
|
||||
.inputbox-container {
|
||||
background: white;
|
||||
border-radius: 0.75rem;
|
||||
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
||||
box-shadow:
|
||||
0 20px 25px -5px rgba(0, 0, 0, 0.1),
|
||||
0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
||||
max-width: 32rem;
|
||||
width: 90%;
|
||||
max-height: 80vh;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,61 +1,57 @@
|
||||
<template>
|
||||
<div class="toolbox-handler">
|
||||
<button
|
||||
class="handler-button"
|
||||
@click="() => props.handler(value)"
|
||||
>
|
||||
{{ props.handlerText }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
||||
interface IProps {
|
||||
status: string
|
||||
value: any
|
||||
handlerText: string
|
||||
handler: (value: any) => void | Promise<void>
|
||||
}
|
||||
|
||||
const props = defineProps<IProps>()
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'ToolboxHandler'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
.toolbox-handler {
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
.handler-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem 1rem;
|
||||
background: var(--color-accent);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: var(--radius-md);
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: var(--transition-fast);
|
||||
font-family: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.handler-button:hover {
|
||||
background: var(--color-accent-hover);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.handler-button:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div class="toolbox-handler">
|
||||
<button class="handler-button" @click="() => props.handler(value)">
|
||||
{{ props.handlerText }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
interface IProps {
|
||||
status: string
|
||||
value: any
|
||||
handlerText: string
|
||||
handler: (value: any) => void | Promise<void>
|
||||
}
|
||||
|
||||
const props = defineProps<IProps>()
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'ToolboxHandler'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
.toolbox-handler {
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
.handler-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem 1rem;
|
||||
background: var(--color-accent);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: var(--radius-md);
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: var(--transition-fast);
|
||||
font-family: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.handler-button:hover {
|
||||
background: var(--color-accent-hover);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.handler-button:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
<template>
|
||||
<component
|
||||
:is="icon"
|
||||
class="toolbox-status-icon"
|
||||
:style="{ color }"
|
||||
/>
|
||||
<component :is="icon" class="toolbox-status-icon" :style="{ color }" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,14 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
ref="containerRef"
|
||||
class="virtual-scroller"
|
||||
:style="{ height: `${containerHeight}px` }"
|
||||
@scroll="handleScroll"
|
||||
>
|
||||
<div
|
||||
class="virtual-scroller-content"
|
||||
:style="contentStyles"
|
||||
>
|
||||
<div ref="containerRef" class="virtual-scroller" :style="{ height: `${containerHeight}px` }" @scroll="handleScroll">
|
||||
<div class="virtual-scroller-content" :style="contentStyles">
|
||||
<div
|
||||
class="virtual-scroller-viewport"
|
||||
:class="{ 'is-grid': isGridMode, 'is-list': !isGridMode }"
|
||||
@@ -16,14 +8,15 @@
|
||||
>
|
||||
<div
|
||||
v-for="realIndex in visibleIndexes"
|
||||
:key="itemsRef[realIndex] && itemsRef[realIndex][props.keyField || 'id'] ? itemsRef[realIndex][props.keyField || 'id'] : realIndex"
|
||||
:key="
|
||||
itemsRef[realIndex] && itemsRef[realIndex][props.keyField || 'id']
|
||||
? itemsRef[realIndex][props.keyField || 'id']
|
||||
: realIndex
|
||||
"
|
||||
class="virtual-scroller-item"
|
||||
:style="itemStyle"
|
||||
>
|
||||
<slot
|
||||
:item="itemsRef[realIndex]"
|
||||
:index="realIndex"
|
||||
/>
|
||||
<slot :item="itemsRef[realIndex]" :index="realIndex" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -36,29 +29,35 @@ import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
import { useVirtualGrid } from '@/hooks/useVirtualGrid'
|
||||
|
||||
type Item = any
|
||||
interface Breakpoint { min: number; cols: number }
|
||||
interface Breakpoint {
|
||||
min: number
|
||||
cols: number
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
items: Item[]
|
||||
itemHeight: number
|
||||
height?: number
|
||||
gridItems?: number
|
||||
gridBreakpoints?: Breakpoint[]
|
||||
bufferFactor?: number
|
||||
pageMode?: boolean
|
||||
keyField?: string
|
||||
itemPadding?: number
|
||||
viewMode?: 'list' | 'grid'
|
||||
}>(), {
|
||||
height: 400,
|
||||
gridItems: 1,
|
||||
gridBreakpoints: () => [],
|
||||
bufferFactor: 0.5,
|
||||
pageMode: false,
|
||||
keyField: 'id',
|
||||
itemPadding: 0,
|
||||
viewMode: 'grid'
|
||||
})
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
items: Item[]
|
||||
itemHeight: number
|
||||
height?: number
|
||||
gridItems?: number
|
||||
gridBreakpoints?: Breakpoint[]
|
||||
bufferFactor?: number
|
||||
pageMode?: boolean
|
||||
keyField?: string
|
||||
itemPadding?: number
|
||||
viewMode?: 'list' | 'grid'
|
||||
}>(),
|
||||
{
|
||||
height: 400,
|
||||
gridItems: 1,
|
||||
gridBreakpoints: () => [],
|
||||
bufferFactor: 0.5,
|
||||
pageMode: false,
|
||||
keyField: 'id',
|
||||
itemPadding: 0,
|
||||
viewMode: 'grid'
|
||||
}
|
||||
)
|
||||
|
||||
const containerRef = ref<HTMLElement | null>(null)
|
||||
const containerHeight = ref<number>(props.pageMode ? 0 : props.height)
|
||||
@@ -66,15 +65,23 @@ const containerWidth = ref<number>(0)
|
||||
const parentScrollListeners = ref<HTMLElement[]>([])
|
||||
|
||||
const itemsRef = ref<Item[]>(props.items)
|
||||
watch(() => props.items, v => { itemsRef.value = v })
|
||||
|
||||
const localViewMode = ref< 'list' | 'grid'>(props.viewMode)
|
||||
watch(() => props.viewMode, v => { localViewMode.value = v })
|
||||
|
||||
const sortedBreakpoints = computed<Breakpoint[]>(() =>
|
||||
[...props.gridBreakpoints].sort((a, b) => a.min - b.min)
|
||||
watch(
|
||||
() => props.items,
|
||||
v => {
|
||||
itemsRef.value = v
|
||||
}
|
||||
)
|
||||
|
||||
const localViewMode = ref<'list' | 'grid'>(props.viewMode)
|
||||
watch(
|
||||
() => props.viewMode,
|
||||
v => {
|
||||
localViewMode.value = v
|
||||
}
|
||||
)
|
||||
|
||||
const sortedBreakpoints = computed<Breakpoint[]>(() => [...props.gridBreakpoints].sort((a, b) => a.min - b.min))
|
||||
|
||||
const isForcedList = computed(() => localViewMode.value === 'list')
|
||||
|
||||
const effectiveCols = computed<number>(() => {
|
||||
@@ -92,19 +99,14 @@ const effectiveCols = computed<number>(() => {
|
||||
|
||||
const isGridMode = computed(() => effectiveCols.value > 1)
|
||||
|
||||
const {
|
||||
gridCalculations,
|
||||
visibleIndexes,
|
||||
viewportOffset,
|
||||
updateScrollTop,
|
||||
scrollToItem, scrollToTop, scrollToBottom
|
||||
} = useVirtualGrid({
|
||||
items: itemsRef,
|
||||
itemHeight: props.itemHeight,
|
||||
containerHeight,
|
||||
gridItems: effectiveCols,
|
||||
bufferFactor: props.bufferFactor
|
||||
})
|
||||
const { gridCalculations, visibleIndexes, viewportOffset, updateScrollTop, scrollToItem, scrollToTop, scrollToBottom } =
|
||||
useVirtualGrid({
|
||||
items: itemsRef,
|
||||
itemHeight: props.itemHeight,
|
||||
containerHeight,
|
||||
gridItems: effectiveCols,
|
||||
bufferFactor: props.bufferFactor
|
||||
})
|
||||
|
||||
const contentStyles = computed(() => ({
|
||||
height: `${gridCalculations.value.totalHeight}px`
|
||||
@@ -122,19 +124,15 @@ const viewportStyle = computed(() => {
|
||||
return base
|
||||
})
|
||||
|
||||
const itemStyle = computed(() =>
|
||||
isGridMode.value
|
||||
? {}
|
||||
: { height: `${props.itemHeight}px` }
|
||||
)
|
||||
const itemStyle = computed(() => (isGridMode.value ? {} : { height: `${props.itemHeight}px` }))
|
||||
|
||||
function handleScroll () {
|
||||
function handleScroll() {
|
||||
const c = containerRef.value
|
||||
if (!c) return
|
||||
updateScrollTop(c.scrollTop)
|
||||
}
|
||||
|
||||
function handlePageScroll () {
|
||||
function handlePageScroll() {
|
||||
if (!props.pageMode) return
|
||||
const now = Date.now()
|
||||
if (now - lastScrollTime.value < 16) return
|
||||
@@ -159,7 +157,7 @@ function handlePageScroll () {
|
||||
let ro: ResizeObserver | null = null
|
||||
const lastScrollTime = ref(0)
|
||||
|
||||
function updateContainerMetrics () {
|
||||
function updateContainerMetrics() {
|
||||
const el = containerRef.value
|
||||
if (!el) return
|
||||
const rect = el.getBoundingClientRect()
|
||||
@@ -206,16 +204,18 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
})
|
||||
|
||||
function scrollTo (index: number) { scrollToItem(index) }
|
||||
function scrollTo(index: number) {
|
||||
scrollToItem(index)
|
||||
}
|
||||
|
||||
function setViewMode (mode: 'list' | 'grid') {
|
||||
function setViewMode(mode: 'list' | 'grid') {
|
||||
localViewMode.value = mode
|
||||
}
|
||||
function toggleViewMode () {
|
||||
function toggleViewMode() {
|
||||
setViewMode(isGridMode.value ? 'list' : 'grid')
|
||||
}
|
||||
|
||||
function refresh () {
|
||||
function refresh() {
|
||||
updateContainerMetrics()
|
||||
if (containerRef.value) {
|
||||
updateScrollTop(containerRef.value.scrollTop)
|
||||
@@ -266,5 +266,4 @@ defineExpose({ scrollTo, scrollToTop, scrollToBottom, setViewMode, toggleViewMod
|
||||
.virtual-scroller-viewport.is-grid .virtual-scroller-item {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,342 +1,305 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="isOpen"
|
||||
class="messagebox-overlay"
|
||||
@click="onCancel"
|
||||
>
|
||||
<div
|
||||
class="messagebox-container"
|
||||
@click.stop
|
||||
>
|
||||
<div class="messagebox-header">
|
||||
<h3 class="messagebox-title">
|
||||
{{ title }}
|
||||
</h3>
|
||||
<button
|
||||
v-if="showClose"
|
||||
class="messagebox-close"
|
||||
@click="onCancel"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
<div class="messagebox-content">
|
||||
<div
|
||||
v-if="type"
|
||||
class="messagebox-icon"
|
||||
>
|
||||
<component
|
||||
:is="iconComponent"
|
||||
:size="48"
|
||||
/>
|
||||
</div>
|
||||
<div class="messagebox-message">
|
||||
<p>{{ message }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="center"
|
||||
class="messagebox-actions center"
|
||||
>
|
||||
<button
|
||||
class="messagebox-btn cancel-btn"
|
||||
@click="onCancel"
|
||||
>
|
||||
{{ cancelButtonText }}
|
||||
</button>
|
||||
<button
|
||||
class="messagebox-btn confirm-btn"
|
||||
:class="confirmButtonClass"
|
||||
@click="onConfirm"
|
||||
>
|
||||
{{ confirmButtonText }}
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="messagebox-actions"
|
||||
>
|
||||
<button
|
||||
class="messagebox-btn confirm-btn"
|
||||
:class="confirmButtonClass"
|
||||
@click="onConfirm"
|
||||
>
|
||||
{{ confirmButtonText }}
|
||||
</button>
|
||||
<button
|
||||
class="messagebox-btn cancel-btn"
|
||||
@click="onCancel"
|
||||
>
|
||||
{{ cancelButtonText }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { AlertTriangle, CheckCircle, Info, XCircle } from 'lucide-vue-next'
|
||||
import { computed } from 'vue'
|
||||
|
||||
interface Props {
|
||||
isOpen: boolean
|
||||
title?: string
|
||||
message: string
|
||||
type?: 'info' | 'success' | 'warning' | 'error'
|
||||
confirmButtonText?: string
|
||||
cancelButtonText?: string
|
||||
showClose?: boolean
|
||||
center?: boolean
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'confirm'): void
|
||||
(e: 'cancel'): void
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
title: 'Confirm',
|
||||
confirmButtonText: 'Confirm',
|
||||
cancelButtonText: 'Cancel',
|
||||
showClose: true,
|
||||
center: false,
|
||||
type: undefined
|
||||
})
|
||||
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
const iconComponent = computed(() => {
|
||||
switch (props.type) {
|
||||
case 'warning':
|
||||
return AlertTriangle
|
||||
case 'info':
|
||||
return Info
|
||||
case 'success':
|
||||
return CheckCircle
|
||||
case 'error':
|
||||
return XCircle
|
||||
default:
|
||||
return Info
|
||||
}
|
||||
})
|
||||
|
||||
const confirmButtonClass = computed(() => {
|
||||
switch (props.type) {
|
||||
case 'warning':
|
||||
case 'error':
|
||||
return 'danger'
|
||||
case 'success':
|
||||
return 'success'
|
||||
default:
|
||||
return 'primary'
|
||||
}
|
||||
})
|
||||
|
||||
const onConfirm = () => {
|
||||
emit('confirm')
|
||||
}
|
||||
|
||||
const onCancel = () => {
|
||||
emit('cancel')
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'ConfirmMessageBox'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.messagebox-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 2000;
|
||||
}
|
||||
|
||||
.messagebox-container {
|
||||
background: white;
|
||||
border-radius: 0.75rem;
|
||||
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
||||
max-width: 32rem;
|
||||
width: 90%;
|
||||
max-height: 80vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:root.dark .messagebox-container,
|
||||
:root.auto.dark .messagebox-container {
|
||||
background: rgb(31 41 55);
|
||||
border: 1px solid rgb(55 65 81);
|
||||
}
|
||||
|
||||
.messagebox-header {
|
||||
padding: 1.5rem 1.5rem 0 1.5rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.messagebox-title {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
color: rgb(17 24 39);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
:root.dark .messagebox-title,
|
||||
:root.auto.dark .messagebox-title {
|
||||
color: rgb(243 244 246);
|
||||
}
|
||||
|
||||
.messagebox-close {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 1.5rem;
|
||||
color: rgb(107 114 128);
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
.messagebox-close:hover {
|
||||
background: rgb(243 244 246);
|
||||
color: rgb(17 24 39);
|
||||
}
|
||||
|
||||
:root.dark .messagebox-close,
|
||||
:root.auto.dark .messagebox-close {
|
||||
color: rgb(156 163 175);
|
||||
}
|
||||
|
||||
:root.dark .messagebox-close:hover,
|
||||
:root.auto.dark .messagebox-close:hover {
|
||||
background: rgb(55 65 81);
|
||||
color: rgb(243 244 246);
|
||||
}
|
||||
|
||||
.messagebox-content {
|
||||
padding: 1rem 1.5rem;
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.messagebox-icon {
|
||||
flex-shrink: 0;
|
||||
color: rgb(107 114 128);
|
||||
}
|
||||
|
||||
.messagebox-icon svg[data-lucide="alert-triangle"] {
|
||||
color: rgb(245 158 11);
|
||||
}
|
||||
|
||||
.messagebox-icon svg[data-lucide="info"] {
|
||||
color: rgb(59 130 246);
|
||||
}
|
||||
|
||||
.messagebox-icon svg[data-lucide="check-circle"] {
|
||||
color: rgb(34 197 94);
|
||||
}
|
||||
|
||||
.messagebox-icon svg[data-lucide="x-circle"] {
|
||||
color: rgb(239 68 68);
|
||||
}
|
||||
|
||||
.messagebox-message {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.messagebox-message p {
|
||||
color: rgb(107 114 128);
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
:root.dark .messagebox-message p,
|
||||
:root.auto.dark .messagebox-message p {
|
||||
color: rgb(156 163 175);
|
||||
}
|
||||
|
||||
.messagebox-actions {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
padding: 0 1.5rem 1.5rem 1.5rem;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.messagebox-actions.center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.messagebox-btn {
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 0.375rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
min-width: 4rem;
|
||||
}
|
||||
|
||||
.cancel-btn {
|
||||
background: rgb(243 244 246);
|
||||
color: rgb(75 85 99);
|
||||
border: 1px solid rgb(209 213 219);
|
||||
}
|
||||
|
||||
.cancel-btn:hover {
|
||||
background: rgb(229 231 235);
|
||||
}
|
||||
|
||||
:root.dark .cancel-btn,
|
||||
:root.auto.dark .cancel-btn {
|
||||
background: rgb(55 65 81);
|
||||
color: rgb(209 213 219);
|
||||
border-color: rgb(75 85 99);
|
||||
}
|
||||
|
||||
:root.dark .cancel-btn:hover,
|
||||
:root.auto.dark .cancel-btn:hover {
|
||||
background: rgb(75 85 99);
|
||||
}
|
||||
|
||||
.confirm-btn.primary {
|
||||
background: rgb(59 130 246);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.confirm-btn.primary:hover {
|
||||
background: rgb(37 99 235);
|
||||
}
|
||||
|
||||
.confirm-btn.danger {
|
||||
background: rgb(239 68 68);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.confirm-btn.danger:hover {
|
||||
background: rgb(220 38 38);
|
||||
}
|
||||
|
||||
.confirm-btn.success {
|
||||
background: rgb(34 197 94);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.confirm-btn.success:hover {
|
||||
background: rgb(22 163 74);
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div v-if="isOpen" class="messagebox-overlay" @click="onCancel">
|
||||
<div class="messagebox-container" @click.stop>
|
||||
<div class="messagebox-header">
|
||||
<h3 class="messagebox-title">
|
||||
{{ title }}
|
||||
</h3>
|
||||
<button v-if="showClose" class="messagebox-close" @click="onCancel">×</button>
|
||||
</div>
|
||||
<div class="messagebox-content">
|
||||
<div v-if="type" class="messagebox-icon">
|
||||
<component :is="iconComponent" :size="48" />
|
||||
</div>
|
||||
<div class="messagebox-message">
|
||||
<p>{{ message }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="center" class="messagebox-actions center">
|
||||
<button class="messagebox-btn cancel-btn" @click="onCancel">
|
||||
{{ cancelButtonText }}
|
||||
</button>
|
||||
<button class="messagebox-btn confirm-btn" :class="confirmButtonClass" @click="onConfirm">
|
||||
{{ confirmButtonText }}
|
||||
</button>
|
||||
</div>
|
||||
<div v-else class="messagebox-actions">
|
||||
<button class="messagebox-btn confirm-btn" :class="confirmButtonClass" @click="onConfirm">
|
||||
{{ confirmButtonText }}
|
||||
</button>
|
||||
<button class="messagebox-btn cancel-btn" @click="onCancel">
|
||||
{{ cancelButtonText }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { AlertTriangle, CheckCircle, Info, XCircle } from 'lucide-vue-next'
|
||||
import { computed } from 'vue'
|
||||
|
||||
interface Props {
|
||||
isOpen: boolean
|
||||
title?: string
|
||||
message: string
|
||||
type?: 'info' | 'success' | 'warning' | 'error'
|
||||
confirmButtonText?: string
|
||||
cancelButtonText?: string
|
||||
showClose?: boolean
|
||||
center?: boolean
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'confirm'): void
|
||||
(e: 'cancel'): void
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
title: 'Confirm',
|
||||
confirmButtonText: 'Confirm',
|
||||
cancelButtonText: 'Cancel',
|
||||
showClose: true,
|
||||
center: false,
|
||||
type: undefined
|
||||
})
|
||||
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
const iconComponent = computed(() => {
|
||||
switch (props.type) {
|
||||
case 'warning':
|
||||
return AlertTriangle
|
||||
case 'info':
|
||||
return Info
|
||||
case 'success':
|
||||
return CheckCircle
|
||||
case 'error':
|
||||
return XCircle
|
||||
default:
|
||||
return Info
|
||||
}
|
||||
})
|
||||
|
||||
const confirmButtonClass = computed(() => {
|
||||
switch (props.type) {
|
||||
case 'warning':
|
||||
case 'error':
|
||||
return 'danger'
|
||||
case 'success':
|
||||
return 'success'
|
||||
default:
|
||||
return 'primary'
|
||||
}
|
||||
})
|
||||
|
||||
const onConfirm = () => {
|
||||
emit('confirm')
|
||||
}
|
||||
|
||||
const onCancel = () => {
|
||||
emit('cancel')
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'ConfirmMessageBox'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.messagebox-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 2000;
|
||||
}
|
||||
|
||||
.messagebox-container {
|
||||
background: white;
|
||||
border-radius: 0.75rem;
|
||||
box-shadow:
|
||||
0 20px 25px -5px rgba(0, 0, 0, 0.1),
|
||||
0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
||||
max-width: 32rem;
|
||||
width: 90%;
|
||||
max-height: 80vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:root.dark .messagebox-container,
|
||||
:root.auto.dark .messagebox-container {
|
||||
background: rgb(31 41 55);
|
||||
border: 1px solid rgb(55 65 81);
|
||||
}
|
||||
|
||||
.messagebox-header {
|
||||
padding: 1.5rem 1.5rem 0 1.5rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.messagebox-title {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
color: rgb(17 24 39);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
:root.dark .messagebox-title,
|
||||
:root.auto.dark .messagebox-title {
|
||||
color: rgb(243 244 246);
|
||||
}
|
||||
|
||||
.messagebox-close {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 1.5rem;
|
||||
color: rgb(107 114 128);
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
.messagebox-close:hover {
|
||||
background: rgb(243 244 246);
|
||||
color: rgb(17 24 39);
|
||||
}
|
||||
|
||||
:root.dark .messagebox-close,
|
||||
:root.auto.dark .messagebox-close {
|
||||
color: rgb(156 163 175);
|
||||
}
|
||||
|
||||
:root.dark .messagebox-close:hover,
|
||||
:root.auto.dark .messagebox-close:hover {
|
||||
background: rgb(55 65 81);
|
||||
color: rgb(243 244 246);
|
||||
}
|
||||
|
||||
.messagebox-content {
|
||||
padding: 1rem 1.5rem;
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.messagebox-icon {
|
||||
flex-shrink: 0;
|
||||
color: rgb(107 114 128);
|
||||
}
|
||||
|
||||
.messagebox-icon svg[data-lucide='alert-triangle'] {
|
||||
color: rgb(245 158 11);
|
||||
}
|
||||
|
||||
.messagebox-icon svg[data-lucide='info'] {
|
||||
color: rgb(59 130 246);
|
||||
}
|
||||
|
||||
.messagebox-icon svg[data-lucide='check-circle'] {
|
||||
color: rgb(34 197 94);
|
||||
}
|
||||
|
||||
.messagebox-icon svg[data-lucide='x-circle'] {
|
||||
color: rgb(239 68 68);
|
||||
}
|
||||
|
||||
.messagebox-message {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.messagebox-message p {
|
||||
color: rgb(107 114 128);
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
:root.dark .messagebox-message p,
|
||||
:root.auto.dark .messagebox-message p {
|
||||
color: rgb(156 163 175);
|
||||
}
|
||||
|
||||
.messagebox-actions {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
padding: 0 1.5rem 1.5rem 1.5rem;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.messagebox-actions.center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.messagebox-btn {
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 0.375rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
min-width: 4rem;
|
||||
}
|
||||
|
||||
.cancel-btn {
|
||||
background: rgb(243 244 246);
|
||||
color: rgb(75 85 99);
|
||||
border: 1px solid rgb(209 213 219);
|
||||
}
|
||||
|
||||
.cancel-btn:hover {
|
||||
background: rgb(229 231 235);
|
||||
}
|
||||
|
||||
:root.dark .cancel-btn,
|
||||
:root.auto.dark .cancel-btn {
|
||||
background: rgb(55 65 81);
|
||||
color: rgb(209 213 219);
|
||||
border-color: rgb(75 85 99);
|
||||
}
|
||||
|
||||
:root.dark .cancel-btn:hover,
|
||||
:root.auto.dark .cancel-btn:hover {
|
||||
background: rgb(75 85 99);
|
||||
}
|
||||
|
||||
.confirm-btn.primary {
|
||||
background: rgb(59 130 246);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.confirm-btn.primary:hover {
|
||||
background: rgb(37 99 235);
|
||||
}
|
||||
|
||||
.confirm-btn.danger {
|
||||
background: rgb(239 68 68);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.confirm-btn.danger:hover {
|
||||
background: rgb(220 38 38);
|
||||
}
|
||||
|
||||
.confirm-btn.success {
|
||||
background: rgb(34 197 94);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.confirm-btn.success:hover {
|
||||
background: rgb(22 163 74);
|
||||
}
|
||||
</style>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user