Skip to content

Commit

Permalink
Merge pull request #270 from XPoet/dev
Browse files Browse the repository at this point in the history
feat: local settings data can be saved to cloud repository (#227)
  • Loading branch information
XPoet committed Dec 8, 2023
2 parents de154a4 + 1d8826a commit 2bd77a9
Show file tree
Hide file tree
Showing 14 changed files with 306 additions and 11 deletions.
2 changes: 2 additions & 0 deletions src/auto-imports.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ declare global {
const ElMessageBox: typeof import('element-plus/es')['ElMessageBox']
const IEpArrowRight: typeof import('~icons/ep/arrow-right')['default']
const IEpChatDotRound: typeof import('~icons/ep/chat-dot-round')['default']
const IEpCheck: typeof import('~icons/ep/check')['default']
const IEpClose: typeof import('~icons/ep/close')['default']
const IEpEdit: typeof import('~icons/ep/edit')['default']
const IEpFiles: typeof import('~icons/ep/files')['default']
const IEpMagicStick: typeof import('~icons/ep/magic-stick')['default']
Expand Down
4 changes: 2 additions & 2 deletions src/common/api/repo.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { UserConfigInfoModel } from '@/common/model'
import { INIT_REPO_DESC, INIT_REPO_NAME } from '@/common/constant'
import { INIT_REPO_DESC, INIT_REPO_NAME, PICX_INIT_REPO_MSG } from '@/common/constant'
import request from '@/utils/request'

/**
Expand Down Expand Up @@ -89,7 +89,7 @@ If you like it, please give it a star on [GitHub](https://github.com/XPoet/picx)
url: `/repos/${owner}/${repo}/contents/README.md`,
method: 'PUT',
data: {
message: 'Init repo via PicX(https://github.com/XPoet/picx)',
message: PICX_INIT_REPO_MSG,
branch,
content: window.btoa(README)
},
Expand Down
11 changes: 7 additions & 4 deletions src/common/constant/init.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
export const PICX_REPO_NAME = 'XPoet/picx'
export const INIT_REPO_NAME = 'picx-images-hosting'
export const INIT_REPO_DESC = 'PicX images hosting repository'
export const INIT_REPO_DESC = 'PicX image hosting repository'
export const INIT_REPO_BARNCH = 'master'
export const GH_PAGES = 'gh-pages'
export const PICX_UPLOAD_IMG_DESC = 'Upload image via PicX(https://github.com/XPoet/picx)'
export const PICX_UPLOAD_IMGS_DESC = 'Upload images via PicX(https://github.com/XPoet/picx)'
export const PICX_DEL_IMG_DESC = 'Delete image via PicX(https://github.com/XPoet/picx)'
export const PICX_UPLOAD_IMG_DESC = 'Upload image via PicX (https://github.com/XPoet/picx)'
export const PICX_UPLOAD_IMGS_DESC = 'Upload images via PicX (https://github.com/XPoet/picx)'
export const PICX_DEL_IMG_DESC = 'Delete image via PicX (https://github.com/XPoet/picx)'
export const PICX_INIT_SETTINGS_MSG = 'Init settings via PicX (https://github.com/XPoet/picx)'
export const PICX_UPDATE_SETTINGS_MSG = 'Update settings via PicX (https://github.com/XPoet/picx)'
export const PICX_INIT_REPO_MSG = 'Init repo via PicX (https://github.com/XPoet/picx)'
3 changes: 3 additions & 0 deletions src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export {}
declare module '@vue/runtime-core' {
export interface GlobalComponents {
Base64Tool: typeof import('./components/tools/base64-tool/base64-tool.vue')['default']
CloudSettingsBar: typeof import('./components/cloud-settings-bar/cloud-settings-bar.vue')['default']
CompressConfigBox: typeof import('./components/compress-config-box/compress-config-box.vue')['default']
CompressTool: typeof import('./components/tools/compress-tool/compress-tool.vue')['default']
CopyImageLink: typeof import('./components/copy-image-link/copy-image-link.vue')['default']
Expand Down Expand Up @@ -42,10 +43,12 @@ declare module '@vue/runtime-core' {
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
ElRow: typeof import('element-plus/es')['ElRow']
ElSelect: typeof import('element-plus/es')['ElSelect']
ElSpace: typeof import('element-plus/es')['ElSpace']
ElSwitch: typeof import('element-plus/es')['ElSwitch']
ElTable: typeof import('element-plus/es')['ElTable']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
ElTag: typeof import('element-plus/es')['ElTag']
ElText: typeof import('element-plus/es')['ElText']
ElTooltip: typeof import('element-plus/es')['ElTooltip']
FolderCard: typeof import('./components/folder-card/folder-card.vue')['default']
GettingImages: typeof import('./components/getting-images/getting-images.vue')['default']
Expand Down
10 changes: 10 additions & 0 deletions src/components/cloud-settings-bar/cloud-settings-bar.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export enum CloudSettingsActions {
// eslint-disable-next-line no-unused-vars
save,
// eslint-disable-next-line no-unused-vars
use,
// eslint-disable-next-line no-unused-vars
update,
// eslint-disable-next-line no-unused-vars
equal
}
9 changes: 9 additions & 0 deletions src/components/cloud-settings-bar/cloud-settings-bar.styl
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.cloud-settings-data-box {
display flex
align-items center
justify-content space-between
padding 2rem 0 2rem 12rem
color var(--text-color-2)
border 1rem solid var(--text-color-4)
border-radius 6rem
}
49 changes: 49 additions & 0 deletions src/components/cloud-settings-bar/cloud-settings-bar.util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import request from '@/utils/request'
import { PICX_INIT_SETTINGS_MSG, PICX_UPDATE_SETTINGS_MSG } from '@/common/constant'
import { UserConfigInfoModel, UserSettingsModel } from '@/common/model'

export const getCloudSettings = async (userConfigInfo: UserConfigInfoModel) => {
const { owner, selectedRepo: repo } = userConfigInfo
const res = await request({
url: `/repos/${owner}/${repo}/contents/.settings`,
method: 'GET',
noShowErrorMsg: true,
cache: {
maxAge: 0
},
params: {
timestamp: Date.now() // 添加时间戳参数,防止获取缓存的数据
}
})

return Promise.resolve(res)
}

export const saveCloudSettings = async (
userSettings: UserSettingsModel,
userConfigInfo: UserConfigInfoModel
) => {
const { owner, selectedRepo: repo, selectedBranch: branch } = userConfigInfo

const res = await getCloudSettings(userConfigInfo)

const data: any = {
message: res ? PICX_UPDATE_SETTINGS_MSG : PICX_INIT_SETTINGS_MSG,
content: window.btoa(JSON.stringify(userSettings))
}

if (res) {
data.sha = res.sha
} else {
data.branch = branch
}

const res2 = await request({
url: `/repos/${owner}/${repo}/contents/.settings`,
method: 'PUT',
data,
noShowErrorMsg: true
})

return Promise.resolve(res2)
}
151 changes: 151 additions & 0 deletions src/components/cloud-settings-bar/cloud-settings-bar.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
<template>
<div class="cloud-settings-data-box border-box">
<div>{{ actionsTip }}</div>
<el-button
type="primary"
text
:icon="icon.IEpCheck"
:loading="saveLoading"
:disabled="saveDisabled"
@click="onOK"
>
{{ $t('confirm') }}
</el-button>
</div>
</template>

<script setup lang="ts">
import { computed, getCurrentInstance, onMounted, ref, shallowRef, watch } from 'vue'
import { store } from '@/stores'
import { UserSettingsModel } from '@/common/model'
import { deepAssignObject, deepObjectEqual } from '@/utils'
import { CloudSettingsActions } from './cloud-settings-bar.model'
import { getCloudSettings, saveCloudSettings } from './cloud-settings-bar.util'
const instance = getCurrentInstance()
const userSettings = computed(() => store.getters.getUserSettings).value
const userConfigInfo = computed(() => store.getters.getUserConfigInfo).value
const icon = shallowRef({ IEpCheck, IEpClose })
const cloudSettings = ref<null | UserSettingsModel>(null)
const saveLoading = ref(false)
const saveDisabled = ref(false)
const selectedAction = ref<CloudSettingsActions>(CloudSettingsActions.save)
const actionsTip = computed(() => {
switch (selectedAction.value) {
case CloudSettingsActions.save:
return instance?.proxy?.$t('settings.cloud_settings.tip_1')
case CloudSettingsActions.use:
return instance?.proxy?.$t('settings.cloud_settings.tip_2')
case CloudSettingsActions.update:
return instance?.proxy?.$t('settings.cloud_settings.tip_3')
case CloudSettingsActions.equal:
return instance?.proxy?.$t('settings.cloud_settings.tip_4')
default:
return instance?.proxy?.$t('settings.cloud_settings.tip_1')
}
})
// 保存(更新)到云端
const saveToCloud = async () => {
saveLoading.value = true
await saveCloudSettings(userSettings, userConfigInfo)
saveLoading.value = false
cloudSettings.value = JSON.parse(JSON.stringify(userSettings))
ElMessage.success(
cloudSettings.value === null
? instance?.proxy?.$t('settings.cloud_settings.success_msg_1')
: instance?.proxy?.$t('settings.cloud_settings.success_msg_2')
)
}
// 使用云端设置数据
const useCloudSettings = () => {
if (cloudSettings.value) {
deepAssignObject(userSettings, cloudSettings.value)
store.dispatch('USER_SETTINGS_PERSIST')
}
}
// 初始化云端设置数据
const initCloudSettings = async () => {
const res = await getCloudSettings(userConfigInfo)
cloudSettings.value = res ? JSON.parse(window.atob(res.content)) : null
}
// 确定操作
const onOK = () => {
// eslint-disable-next-line default-case
switch (selectedAction.value) {
case CloudSettingsActions.save:
case CloudSettingsActions.update:
saveToCloud()
break
case CloudSettingsActions.use:
useCloudSettings()
break
}
}
watch(
() => userSettings,
(settings) => {
// 本地设置发生变化时,判断和云端设置是否相等
if (deepObjectEqual(settings, cloudSettings.value!)) {
// 相等情况
selectedAction.value = CloudSettingsActions.equal
saveDisabled.value = true
} else {
// 不相等情况
selectedAction.value = CloudSettingsActions.update
saveDisabled.value = false
}
},
{
deep: true
}
)
watch(
() => cloudSettings.value,
(settings) => {
// 不存在云端设置数据,提示是否保存
if (settings === null) {
selectedAction.value = CloudSettingsActions.save
}
// 存在云端设置数据,提示是否使用
if (settings) {
selectedAction.value = CloudSettingsActions.use
}
// 判断 云端设置 和本地设置 是否相等,相等则禁止点击
if (settings && deepObjectEqual(settings, userSettings)) {
saveDisabled.value = true
selectedAction.value = CloudSettingsActions.equal
} else {
saveDisabled.value = false
selectedAction.value = CloudSettingsActions.update
}
},
{
deep: true
}
)
onMounted(() => {
initCloudSettings()
})
</script>

<style scoped lang="stylus">
@import "cloud-settings-bar.styl"
</style>
8 changes: 8 additions & 0 deletions src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,14 @@
"system": "System",
"dark": "Dark",
"light": "Light"
},
"cloud_settings": {
"tip_1": "It is detected that your settings data is not saved to the cloud repository. Do you want to save it?",
"tip_2": "It is detected that your cloud repository has a configuration file. Do you want to use it?",
"tip_3": "It is detected that your settings data has changed. Do you want to synchronize it to the cloud repository?",
"tip_4": "The local settings data is consistent with the cloud repository settings data",
"success_msg_1": "Save successfully",
"success_msg_2": "Update successful"
}
},
"config": {
Expand Down
8 changes: 8 additions & 0 deletions src/locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,14 @@
"system": "跟随系统",
"dark": "暗夜",
"light": "白昼"
},
"cloud_settings": {
"tip_1": "检测到你的设置数据未保存到云端仓库,是否保存?",
"tip_2": "检测到你的云端仓库有设置文件,是否使用?",
"tip_3": "检测到你的设置数据有变化,是否同步到云端仓库?",
"tip_4": "本地设置数据和云端仓库设置数据一致",
"success_msg_1": "保存成功",
"success_msg_2": "更新成功"
}
},
"config": {
Expand Down
8 changes: 8 additions & 0 deletions src/locales/zh-TW.json
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,14 @@
"system": "跟隨系統",
"dark": "暗夜",
"light": "白晝"
},
"cloud_settings": {
"tip_1": "偵測到你的設定資料未儲存到雲端倉庫,是否儲存?",
"tip_2": "偵測到你的雲端倉庫有設定文件,是否使用?",
"tip_3": "偵測到你的設定資料有變化,是否同步到雲端倉庫?",
"tip_4": "本地設定資料與雲端倉庫設定資料一致",
"success_msg_1": "儲存成功",
"success_msg_2": "更新成功"
}
},
"config": {
Expand Down
42 changes: 40 additions & 2 deletions src/utils/common-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import i18n from '@/plugins/vue/i18n'
* @param data
* @returns {string} array | string | number | boolean ...
*/
export const getType = (data: string) => {
export const getType = (data: string): string => {
const type = Object.prototype.toString.call(data).split(' ')[1]
return type.substring(0, type.length - 1).toLowerCase()
}
Expand Down Expand Up @@ -67,7 +67,7 @@ export const cleanObject = (object: any) => {
* @param obj1{Object}
* @param obj2{Object}
*/
export const deepAssignObject = (obj1: object, obj2: object): any => {
export const deepAssignObject = (obj1: object, obj2: object) => {
// eslint-disable-next-line no-restricted-syntax
for (const key in obj2) {
// @ts-ignore
Expand Down Expand Up @@ -157,3 +157,41 @@ export const setWindowTitle = (title: string) => {
;(<any>window).document.title = `${i18n.global.t(title)} | PicX`
}
}

/**
* 深度判断两个对象是否相等
* @param obj1 对象 1
* @param obj2 对象 2
* @return {boolean} true | false
*/
export const deepObjectEqual = (obj1: object, obj2: object): boolean => {
// 多维对象转换为一维对象
function flattenObject(obj: object) {
const result = {}

// eslint-disable-next-line no-restricted-syntax
for (const [key, value] of Object.entries(obj)) {
if (typeof value === 'object' && value !== null) {
// 递归处理嵌套对象
const nested = flattenObject(value)

// 使用 Object.entries() 处理嵌套对象的键
// eslint-disable-next-line no-restricted-syntax
for (const [nestedKey, nestedValue] of Object.entries(nested)) {
// @ts-ignore
result[`${key}.${nestedKey}`] = nestedValue
}
} else {
// @ts-ignore
result[key] = value
}
}

return result
}

return (
Object.entries(flattenObject(obj1)).toString() ===
Object.entries(flattenObject(obj2)).toString()
)
}

0 comments on commit 2bd77a9

Please sign in to comment.