mirror of
https://github.com/Kuingsmile/PicList.git
synced 2025-02-02 11:08:13 -05:00
✨ Feature: add remoteNotice
This commit is contained in:
parent
a355bc07f4
commit
9317871fdb
199
src/main/apis/app/remoteNotice/index.ts
Normal file
199
src/main/apis/app/remoteNotice/index.ts
Normal file
@ -0,0 +1,199 @@
|
||||
// get notice from remote
|
||||
// such as some notices for users; some updates for users
|
||||
import fs from 'fs-extra'
|
||||
import { app, clipboard, dialog, shell } from 'electron'
|
||||
import { IRemoteNoticeActionType, IRemoteNoticeTriggerCount, IRemoteNoticeTriggerHook } from '#/types/enum'
|
||||
import { lte, gte } from 'semver'
|
||||
import path from 'path'
|
||||
|
||||
import axios from 'axios'
|
||||
import windowManager from '../window/windowManager'
|
||||
import { showNotification } from '~/main/utils/common'
|
||||
import { isDev } from '~/universal/utils/common'
|
||||
|
||||
// for test
|
||||
const REMOTE_NOTICE_URL = isDev ? 'http://localhost:8181/remote-notice.json' : 'https://picgo-1251750343.cos.accelerate.myqcloud.com/remote-notice.yml'
|
||||
|
||||
const REMOTE_NOTICE_LOCAL_STORAGE_FILE = 'picgo-remote-notice.json'
|
||||
|
||||
const STORE_PATH = app.getPath('userData')
|
||||
|
||||
const REMOTE_NOTICE_LOCAL_STORAGE_PATH = path.join(STORE_PATH, REMOTE_NOTICE_LOCAL_STORAGE_FILE)
|
||||
|
||||
class RemoteNoticeHandler {
|
||||
private remoteNotice: IRemoteNotice | null = null
|
||||
private remoteNoticeLocalCountStorage: IRemoteNoticeLocalCountStorage | null = null
|
||||
|
||||
async init () {
|
||||
this.remoteNotice = await this.getRemoteNoticeInfo()
|
||||
this.initLocalCountStorage()
|
||||
}
|
||||
|
||||
private initLocalCountStorage () {
|
||||
const localCountStorage = {}
|
||||
if (!fs.existsSync(REMOTE_NOTICE_LOCAL_STORAGE_PATH)) {
|
||||
fs.writeFileSync(REMOTE_NOTICE_LOCAL_STORAGE_PATH, JSON.stringify({}))
|
||||
}
|
||||
try {
|
||||
const localCountStorage: IRemoteNoticeLocalCountStorage = fs.readJSONSync(REMOTE_NOTICE_LOCAL_STORAGE_PATH, 'utf8')
|
||||
this.remoteNoticeLocalCountStorage = localCountStorage
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
this.remoteNoticeLocalCountStorage = localCountStorage
|
||||
}
|
||||
}
|
||||
|
||||
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> {
|
||||
try {
|
||||
const noticeInfo = await axios({
|
||||
method: 'get',
|
||||
url: REMOTE_NOTICE_URL,
|
||||
responseType: 'json'
|
||||
}).then(res => res.data) as IRemoteNotice
|
||||
return noticeInfo
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* if the notice is not shown or is always shown, then show the notice
|
||||
* @param action
|
||||
*/
|
||||
private checkActionCount (action: IRemoteNoticeAction) {
|
||||
try {
|
||||
if (!this.remoteNoticeLocalCountStorage) {
|
||||
return true
|
||||
}
|
||||
const actionCount = this.remoteNoticeLocalCountStorage[action.id]
|
||||
if (actionCount === undefined) {
|
||||
if (action.triggerCount === IRemoteNoticeTriggerCount.ALWAYS) {
|
||||
this.remoteNoticeLocalCountStorage[action.id] = 1 // if always, count number
|
||||
} else {
|
||||
this.remoteNoticeLocalCountStorage[action.id] = true
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
// here is the count of action
|
||||
// if not always show, then can't show
|
||||
if (action.triggerCount !== IRemoteNoticeTriggerCount.ALWAYS) {
|
||||
return false
|
||||
} else {
|
||||
const preCount = this.remoteNoticeLocalCountStorage[action.id]
|
||||
if (typeof preCount !== 'number') {
|
||||
this.remoteNoticeLocalCountStorage[action.id] = true
|
||||
return true
|
||||
} else {
|
||||
this.remoteNoticeLocalCountStorage[action.id] = preCount + 1
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
this.saveLocalCountStorage()
|
||||
}
|
||||
}
|
||||
|
||||
private async doActions (actions: IRemoteNoticeAction[]) {
|
||||
for (const action of actions) {
|
||||
if (this.checkActionCount(action)) {
|
||||
switch (action.type) {
|
||||
case IRemoteNoticeActionType.SHOW_DIALOG: {
|
||||
// SHOW DIALOG
|
||||
const currentWindow = windowManager.getAvailableWindow()
|
||||
dialog.showOpenDialog(currentWindow, action.data?.options)
|
||||
break
|
||||
}
|
||||
case IRemoteNoticeActionType.SHOW_NOTICE:
|
||||
showNotification({
|
||||
title: action.data?.title || '',
|
||||
body: action.data?.content || '',
|
||||
clickToCopy: !!action.data?.copyToClipboard,
|
||||
copyContent: action.data?.copyToClipboard || '',
|
||||
clickFn () {
|
||||
if (action.data?.url) {
|
||||
shell.openExternal(action.data.url)
|
||||
}
|
||||
}
|
||||
})
|
||||
break
|
||||
case IRemoteNoticeActionType.OPEN_URL:
|
||||
// OPEN URL
|
||||
shell.openExternal(action.data?.url || '')
|
||||
break
|
||||
case IRemoteNoticeActionType.COMMON:
|
||||
// DO COMMON CASE
|
||||
if (action.data?.copyToClipboard) {
|
||||
clipboard.writeText(action.data.copyToClipboard)
|
||||
}
|
||||
if (action.data?.url) {
|
||||
shell.openExternal(action.data.url)
|
||||
}
|
||||
break
|
||||
case IRemoteNoticeActionType.SHOW_MESSAGE_BOX: {
|
||||
const currentWindow = windowManager.getAvailableWindow()
|
||||
dialog.showMessageBox(currentWindow, {
|
||||
title: action.data?.title || '',
|
||||
message: action.data?.content || '',
|
||||
type: 'info',
|
||||
buttons: action.data?.buttons?.map(item => item.label) || ['Yes']
|
||||
}).then(res => {
|
||||
const button = action.data?.buttons?.[res.response]
|
||||
if (button?.type === 'cancel') {
|
||||
// do nothing
|
||||
} else {
|
||||
if (button?.action) {
|
||||
this.doActions([button?.action])
|
||||
}
|
||||
}
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
triggerHook (hook: IRemoteNoticeTriggerHook) {
|
||||
if (!this.remoteNotice || !this.remoteNotice.list) {
|
||||
return
|
||||
}
|
||||
const actions = this.remoteNotice.list
|
||||
.filter(item => {
|
||||
if (item.versionMatch) {
|
||||
switch (item.versionMatch) {
|
||||
case 'exact':
|
||||
return item.versions.includes(app.getVersion())
|
||||
case 'gte':
|
||||
return item.versions.some(version => {
|
||||
// appVersion >= version
|
||||
return gte(app.getVersion(), version)
|
||||
})
|
||||
case 'lte':
|
||||
return item.versions.some(version => {
|
||||
// appVersion <= version
|
||||
return lte(app.getVersion(), version)
|
||||
})
|
||||
}
|
||||
}
|
||||
return item.versions.includes(app.getVersion())
|
||||
})
|
||||
.map(item => item.actions)
|
||||
.reduce((pre, cur) => pre.concat(cur), [])
|
||||
.filter(item => item.hooks.includes(hook))
|
||||
this.doActions(actions)
|
||||
}
|
||||
}
|
||||
|
||||
const remoteNoticeHandler = new RemoteNoticeHandler()
|
||||
|
||||
export {
|
||||
remoteNoticeHandler
|
||||
}
|
@ -4,12 +4,13 @@ import {
|
||||
MINI_WINDOW_URL,
|
||||
RENAME_WINDOW_URL
|
||||
} from './constants'
|
||||
import { IWindowList } from '#/types/enum'
|
||||
import { IRemoteNoticeTriggerHook, IWindowList } from '#/types/enum'
|
||||
import bus from '@core/bus'
|
||||
import { CREATE_APP_MENU } from '@core/bus/constants'
|
||||
import db from '~/main/apis/core/datastore'
|
||||
import { TOGGLE_SHORTKEY_MODIFIED_MODE } from '#/events/constants'
|
||||
import { app } from 'electron'
|
||||
import { remoteNoticeHandler } from '../remoteNotice'
|
||||
// import { i18n } from '~/main/i18n'
|
||||
// import { URLSearchParams } from 'url'
|
||||
|
||||
@ -88,6 +89,9 @@ windowList.set(IWindowList.SETTING_WINDOW, {
|
||||
return options
|
||||
},
|
||||
callback (window, windowManager) {
|
||||
window.once('show', () => {
|
||||
remoteNoticeHandler.triggerHook(IRemoteNoticeTriggerHook.SETTING_WINDOW_OPEN)
|
||||
})
|
||||
window.loadURL(handleWindowParams(SETTING_WINDOW_URL))
|
||||
window.on('closed', () => {
|
||||
bus.emit(TOGGLE_SHORTKEY_MODIFIED_MODE, false)
|
||||
|
@ -12,7 +12,7 @@ import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
|
||||
import beforeOpen from '~/main/utils/beforeOpen'
|
||||
import ipcList from '~/main/events/ipcList'
|
||||
import busEventList from '~/main/events/busEventList'
|
||||
import { IWindowList } from '#/types/enum'
|
||||
import { IRemoteNoticeTriggerHook, IWindowList } from '#/types/enum'
|
||||
import windowManager from 'apis/app/window/windowManager'
|
||||
import {
|
||||
updateShortKeyFromVersion212,
|
||||
@ -35,6 +35,7 @@ import logger from 'apis/core/picgo/logger'
|
||||
import picgo from 'apis/core/picgo'
|
||||
import fixPath from './fixPath'
|
||||
import { initI18n } from '~/main/utils/handleI18n'
|
||||
import { remoteNoticeHandler } from 'apis/app/remoteNotice'
|
||||
|
||||
const isDevelopment = process.env.NODE_ENV !== 'production'
|
||||
|
||||
@ -101,6 +102,8 @@ class LifeCycle {
|
||||
notice.show()
|
||||
}
|
||||
}
|
||||
await remoteNoticeHandler.init()
|
||||
remoteNoticeHandler.triggerHook(IRemoteNoticeTriggerHook.APP_START)
|
||||
}
|
||||
if (!app.isReady()) {
|
||||
app.on('ready', readyFunction)
|
||||
|
@ -15,7 +15,9 @@ export const handleCopyUrl = (str: string): void => {
|
||||
export const showNotification = (options: IPrivateShowNotificationOption = {
|
||||
title: '',
|
||||
body: '',
|
||||
clickToCopy: false
|
||||
clickToCopy: false,
|
||||
copyContent: '',
|
||||
clickFn: () => {}
|
||||
}) => {
|
||||
const notification = new Notification({
|
||||
title: options.title,
|
||||
@ -24,7 +26,10 @@ export const showNotification = (options: IPrivateShowNotificationOption = {
|
||||
})
|
||||
const handleClick = () => {
|
||||
if (options.clickToCopy) {
|
||||
clipboard.writeText(options.body)
|
||||
clipboard.writeText(options.copyContent || options.body)
|
||||
}
|
||||
if (options.clickFn) {
|
||||
options.clickFn()
|
||||
}
|
||||
}
|
||||
notification.once('click', handleClick)
|
||||
|
@ -27,3 +27,22 @@ export enum IWindowList {
|
||||
MINI_WINDOW = 'MINI_WINDOW',
|
||||
RENAME_WINDOW = 'RENAME_WINDOW'
|
||||
}
|
||||
|
||||
export enum 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 enum IRemoteNoticeTriggerHook {
|
||||
APP_START = 'APP_START',
|
||||
SETTING_WINDOW_OPEN = 'SETTING_WINDOW_OPEN',
|
||||
}
|
||||
|
||||
export enum IRemoteNoticeTriggerCount {
|
||||
ONCE = 'ONCE', // default
|
||||
ALWAYS = 'ALWAYS'
|
||||
}
|
||||
|
41
src/universal/types/types.d.ts
vendored
41
src/universal/types/types.d.ts
vendored
@ -219,6 +219,8 @@ interface IPrivateShowNotificationOption extends IShowNotificationOption{
|
||||
* click notification to copy the body
|
||||
*/
|
||||
clickToCopy?: boolean
|
||||
copyContent?: string // something to copy
|
||||
clickFn?: () => void
|
||||
}
|
||||
|
||||
interface IShowMessageBoxOption {
|
||||
@ -348,3 +350,42 @@ interface II18nItem {
|
||||
label: string
|
||||
value: string
|
||||
}
|
||||
|
||||
interface IRemoteNotice {
|
||||
version: number
|
||||
list: Array<{
|
||||
versions: string[] // matched picgo version
|
||||
actions: IRemoteNoticeAction[]
|
||||
versionMatch?: 'exact' | 'gte' | 'lte'
|
||||
}>
|
||||
}
|
||||
|
||||
interface IRemoteNoticeAction {
|
||||
type: import('#/types/enum').IRemoteNoticeActionType
|
||||
// trigger time
|
||||
hooks: import('#/types/enum').IRemoteNoticeTriggerHook[]
|
||||
id: string
|
||||
// trigger count: always or once; default: once
|
||||
triggerCount: import('#/types/enum').IRemoteNoticeTriggerCount
|
||||
|
||||
data?: {
|
||||
title?: string
|
||||
content?: string
|
||||
desc?: string // action desc
|
||||
buttons?: IRemoteNoticeButton[]
|
||||
url?: string
|
||||
copyToClipboard?: string
|
||||
options: any // for other case
|
||||
}
|
||||
}
|
||||
|
||||
interface IRemoteNoticeButton {
|
||||
label: string
|
||||
labelEN?: string
|
||||
type: 'confirm' | 'cancel' | 'other'
|
||||
action: IRemoteNoticeAction
|
||||
}
|
||||
|
||||
interface IRemoteNoticeLocalCountStorage {
|
||||
[id: string]: true | number
|
||||
}
|
||||
|
@ -41,3 +41,5 @@ export const simpleClone = (obj: any) => {
|
||||
export const enforceNumber = (num: number | string) => {
|
||||
return isNaN(Number(num)) ? 0 : Number(num)
|
||||
}
|
||||
|
||||
export const isDev = process.env.NODE_ENV === 'development'
|
||||
|
Loading…
Reference in New Issue
Block a user