mirror of
https://github.com/Kuingsmile/PicList.git
synced 2025-03-15 17:28:13 -04: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,
|
MINI_WINDOW_URL,
|
||||||
RENAME_WINDOW_URL
|
RENAME_WINDOW_URL
|
||||||
} from './constants'
|
} from './constants'
|
||||||
import { IWindowList } from '#/types/enum'
|
import { IRemoteNoticeTriggerHook, IWindowList } from '#/types/enum'
|
||||||
import bus from '@core/bus'
|
import bus from '@core/bus'
|
||||||
import { CREATE_APP_MENU } from '@core/bus/constants'
|
import { CREATE_APP_MENU } from '@core/bus/constants'
|
||||||
import db from '~/main/apis/core/datastore'
|
import db from '~/main/apis/core/datastore'
|
||||||
import { TOGGLE_SHORTKEY_MODIFIED_MODE } from '#/events/constants'
|
import { TOGGLE_SHORTKEY_MODIFIED_MODE } from '#/events/constants'
|
||||||
import { app } from 'electron'
|
import { app } from 'electron'
|
||||||
|
import { remoteNoticeHandler } from '../remoteNotice'
|
||||||
// import { i18n } from '~/main/i18n'
|
// import { i18n } from '~/main/i18n'
|
||||||
// import { URLSearchParams } from 'url'
|
// import { URLSearchParams } from 'url'
|
||||||
|
|
||||||
@ -88,6 +89,9 @@ windowList.set(IWindowList.SETTING_WINDOW, {
|
|||||||
return options
|
return options
|
||||||
},
|
},
|
||||||
callback (window, windowManager) {
|
callback (window, windowManager) {
|
||||||
|
window.once('show', () => {
|
||||||
|
remoteNoticeHandler.triggerHook(IRemoteNoticeTriggerHook.SETTING_WINDOW_OPEN)
|
||||||
|
})
|
||||||
window.loadURL(handleWindowParams(SETTING_WINDOW_URL))
|
window.loadURL(handleWindowParams(SETTING_WINDOW_URL))
|
||||||
window.on('closed', () => {
|
window.on('closed', () => {
|
||||||
bus.emit(TOGGLE_SHORTKEY_MODIFIED_MODE, false)
|
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 beforeOpen from '~/main/utils/beforeOpen'
|
||||||
import ipcList from '~/main/events/ipcList'
|
import ipcList from '~/main/events/ipcList'
|
||||||
import busEventList from '~/main/events/busEventList'
|
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 windowManager from 'apis/app/window/windowManager'
|
||||||
import {
|
import {
|
||||||
updateShortKeyFromVersion212,
|
updateShortKeyFromVersion212,
|
||||||
@ -35,6 +35,7 @@ import logger from 'apis/core/picgo/logger'
|
|||||||
import picgo from 'apis/core/picgo'
|
import picgo from 'apis/core/picgo'
|
||||||
import fixPath from './fixPath'
|
import fixPath from './fixPath'
|
||||||
import { initI18n } from '~/main/utils/handleI18n'
|
import { initI18n } from '~/main/utils/handleI18n'
|
||||||
|
import { remoteNoticeHandler } from 'apis/app/remoteNotice'
|
||||||
|
|
||||||
const isDevelopment = process.env.NODE_ENV !== 'production'
|
const isDevelopment = process.env.NODE_ENV !== 'production'
|
||||||
|
|
||||||
@ -101,6 +102,8 @@ class LifeCycle {
|
|||||||
notice.show()
|
notice.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
await remoteNoticeHandler.init()
|
||||||
|
remoteNoticeHandler.triggerHook(IRemoteNoticeTriggerHook.APP_START)
|
||||||
}
|
}
|
||||||
if (!app.isReady()) {
|
if (!app.isReady()) {
|
||||||
app.on('ready', readyFunction)
|
app.on('ready', readyFunction)
|
||||||
|
@ -15,7 +15,9 @@ export const handleCopyUrl = (str: string): void => {
|
|||||||
export const showNotification = (options: IPrivateShowNotificationOption = {
|
export const showNotification = (options: IPrivateShowNotificationOption = {
|
||||||
title: '',
|
title: '',
|
||||||
body: '',
|
body: '',
|
||||||
clickToCopy: false
|
clickToCopy: false,
|
||||||
|
copyContent: '',
|
||||||
|
clickFn: () => {}
|
||||||
}) => {
|
}) => {
|
||||||
const notification = new Notification({
|
const notification = new Notification({
|
||||||
title: options.title,
|
title: options.title,
|
||||||
@ -24,7 +26,10 @@ export const showNotification = (options: IPrivateShowNotificationOption = {
|
|||||||
})
|
})
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
if (options.clickToCopy) {
|
if (options.clickToCopy) {
|
||||||
clipboard.writeText(options.body)
|
clipboard.writeText(options.copyContent || options.body)
|
||||||
|
}
|
||||||
|
if (options.clickFn) {
|
||||||
|
options.clickFn()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
notification.once('click', handleClick)
|
notification.once('click', handleClick)
|
||||||
|
@ -27,3 +27,22 @@ export enum IWindowList {
|
|||||||
MINI_WINDOW = 'MINI_WINDOW',
|
MINI_WINDOW = 'MINI_WINDOW',
|
||||||
RENAME_WINDOW = 'RENAME_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
|
* click notification to copy the body
|
||||||
*/
|
*/
|
||||||
clickToCopy?: boolean
|
clickToCopy?: boolean
|
||||||
|
copyContent?: string // something to copy
|
||||||
|
clickFn?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IShowMessageBoxOption {
|
interface IShowMessageBoxOption {
|
||||||
@ -348,3 +350,42 @@ interface II18nItem {
|
|||||||
label: string
|
label: string
|
||||||
value: 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) => {
|
export const enforceNumber = (num: number | string) => {
|
||||||
return isNaN(Number(num)) ? 0 : Number(num)
|
return isNaN(Number(num)) ? 0 : Number(num)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const isDev = process.env.NODE_ENV === 'development'
|
||||||
|
Loading…
Reference in New Issue
Block a user