Compare commits
16 Commits
fa8bd3d2af
...
db881da671
Author | SHA1 | Date | |
---|---|---|---|
db881da671 | |||
c0e26c0c80 | |||
195f3eced9 | |||
c262149d51 | |||
38f37bba08 | |||
fdd45f2c85 | |||
33ed04bb35 | |||
a3d82f12ec | |||
6f7e161c0f | |||
aaf58f03e1 | |||
ba93f7db0b | |||
49cc0676fc | |||
3a1443db9c | |||
592758ec6d | |||
c098e0c37f | |||
e0533ce2e1 |
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"name": "MSR Mod",
|
"name": "MSR Mod",
|
||||||
"version": "0.0.5",
|
"version": "0.0.6",
|
||||||
"description": "塞壬唱片(Monster Siren Records)官网的替代前端。",
|
"description": "塞壬唱片(Monster Siren Records)官网的替代前端。",
|
||||||
"content_scripts": [
|
"content_scripts": [
|
||||||
{
|
{
|
||||||
|
|
|
@ -9,6 +9,8 @@ import LeftArrowIcon from './assets/icons/leftarrow.vue'
|
||||||
import CorgIcon from './assets/icons/corg.vue'
|
import CorgIcon from './assets/icons/corg.vue'
|
||||||
import { watch } from 'vue'
|
import { watch } from 'vue'
|
||||||
|
|
||||||
|
import UpdatePopup from './components/UpdatePopup.vue'
|
||||||
|
|
||||||
const presentPreferencePanel = ref(false)
|
const presentPreferencePanel = ref(false)
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
@ -21,6 +23,8 @@ watch(() => presentPreferencePanel, (value) => {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<UpdatePopup />
|
||||||
|
|
||||||
<div class="w-screen h-screen overflow-hidden bg-[#191919]">
|
<div class="w-screen h-screen overflow-hidden bg-[#191919]">
|
||||||
<div class="flex flex-col w-full h-full overflow-y-auto">
|
<div class="flex flex-col w-full h-full overflow-y-auto">
|
||||||
<div class="py-8 px-4 md:px-8 w-screen bg-gradient-to-b from-[#00000080] to-transparent z-20 absolute top-0">
|
<div class="py-8 px-4 md:px-8 w-screen bg-gradient-to-b from-[#00000080] to-transparent z-20 absolute top-0">
|
||||||
|
@ -77,4 +81,4 @@ watch(() => presentPreferencePanel, (value) => {
|
||||||
</div>
|
</div>
|
||||||
<PreferencePanel :present="presentPreferencePanel" @dismiss="presentPreferencePanel = false" />
|
<PreferencePanel :present="presentPreferencePanel" @dismiss="presentPreferencePanel = false" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
54
src/components/UpdatePopup.vue
Normal file
54
src/components/UpdatePopup.vue
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import XIcon from '../assets/icons/x.vue'
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import { useUpdatePopup } from '../stores/useUpdatePopup'
|
||||||
|
|
||||||
|
const updatePopupStore = useUpdatePopup()
|
||||||
|
const showPopup = ref(false)
|
||||||
|
|
||||||
|
const version = updatePopupStore.getCurrentVersion()
|
||||||
|
|
||||||
|
// 关闭弹窗的处理函数
|
||||||
|
const handleDismiss = async () => {
|
||||||
|
showPopup.value = false
|
||||||
|
// 标记弹窗已显示,避免再次显示
|
||||||
|
await updatePopupStore.markUpdatePopupShown()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 组件挂载时检查是否需要显示弹窗
|
||||||
|
onMounted(async () => {
|
||||||
|
// 等待 store 初始化完成
|
||||||
|
if (!updatePopupStore.isLoaded) {
|
||||||
|
await updatePopupStore.initializeUpdatePopup()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否需要显示更新弹窗
|
||||||
|
const shouldShow = await updatePopupStore.shouldShowUpdatePopup()
|
||||||
|
showPopup.value = shouldShow
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-if="showPopup" class="absolute top-0 left-0 w-screen h-screen bg-neutral-700/30 flex justify-center items-center select-none z-50">
|
||||||
|
<div class="bg-neutral-900/80 shadow-[0_0_16px_0_rgba(0,0,0,0.5)] backdrop-blur-2xl border border-[#ffffff39] rounded-lg w-[60rem] h-3/4 relative overflow-y-auto text-white">
|
||||||
|
<div
|
||||||
|
class="flex justify-between items-center p-8 sticky top-0 bg-gradient-to-b from-neutral-900 to-neutral-900/0 z-10">
|
||||||
|
<div class="text-white text-2xl font-semibold">MSR Mod 已更新至 {{version}}</div>
|
||||||
|
<button
|
||||||
|
class="text-white w-9 h-9 bg-neutral-800/80 border border-[#ffffff39] rounded-full text-center backdrop-blur-3xl flex justify-center items-center transition-all duration-200 hover:bg-neutral-700/80"
|
||||||
|
@click="handleDismiss">
|
||||||
|
<XIcon :size="4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-4 mb-8 px-8 text-lg">
|
||||||
|
<p>MSR Mod 现在有两种渠道接收错误及意见反馈。如果你对 MSR Mod 有任何的意见建议,或是想要回报错误及体验困惑之处,欢迎前往 <a href="https://github.com/Astrian/msr-mod/issues" target="_blank" class="underline">GitHub Issue</a> 或 <a href="https://discord.gg/QQUfeb2gzH" target="_blank" class="underline">Discord 社群</a> 向我们反馈。如果你的意见或错误回报被接受,我们会将其放入 <a href="https://trello.com/b/Ju1TRXla" target="_blank" class="underline">Trello 看板</a> 中进行跟踪,敬请留意。</p>
|
||||||
|
|
||||||
|
<ul class="list-disc list-inside">
|
||||||
|
<li>新增版本更新提示对话框,将在 MSR Mod 更新后首次启动显示。</li>
|
||||||
|
<li>增强对 Apple Safari 浏览器的兼容性支持。</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
204
src/stores/useUpdatePopup.ts
Normal file
204
src/stores/useUpdatePopup.ts
Normal file
|
@ -0,0 +1,204 @@
|
||||||
|
import { defineStore } from "pinia"
|
||||||
|
import { ref } from "vue"
|
||||||
|
|
||||||
|
// 声明全局类型
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
browser?: any
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useUpdatePopup = defineStore('updatePopup', () => {
|
||||||
|
const isLoaded = ref(false)
|
||||||
|
const storageType = ref<'chrome' | 'localStorage' | 'memory'>('chrome')
|
||||||
|
|
||||||
|
// 获取当前版本号
|
||||||
|
const getCurrentVersion = (): string => {
|
||||||
|
try {
|
||||||
|
// 尝试从 Chrome 扩展 API 获取版本号
|
||||||
|
return chrome?.runtime?.getManifest?.()?.version || 'unknown'
|
||||||
|
} catch (error) {
|
||||||
|
return 'unknown'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检测可用的 API
|
||||||
|
const detectAvailableAPIs = () => {
|
||||||
|
// 检查原生 chrome API
|
||||||
|
try {
|
||||||
|
if (typeof chrome !== 'undefined' && chrome.storage && chrome.storage.sync) {
|
||||||
|
storageType.value = 'chrome'
|
||||||
|
return 'chrome'
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Silent fail
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查 window.chrome
|
||||||
|
try {
|
||||||
|
if (window.chrome && window.chrome.storage && window.chrome.storage.sync) {
|
||||||
|
storageType.value = 'chrome'
|
||||||
|
return 'chrome'
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Silent fail
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查 localStorage
|
||||||
|
try {
|
||||||
|
if (typeof localStorage !== 'undefined') {
|
||||||
|
localStorage.setItem('msr_test', 'test')
|
||||||
|
localStorage.removeItem('msr_test')
|
||||||
|
storageType.value = 'localStorage'
|
||||||
|
return 'localStorage'
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Silent fail
|
||||||
|
}
|
||||||
|
|
||||||
|
// 都不可用,使用内存存储
|
||||||
|
storageType.value = 'memory'
|
||||||
|
return 'memory'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通用的获取存储值函数
|
||||||
|
const getStoredValue = async (key: string, defaultValue: any) => {
|
||||||
|
const type = detectAvailableAPIs()
|
||||||
|
|
||||||
|
try {
|
||||||
|
switch (type) {
|
||||||
|
case 'chrome':
|
||||||
|
return await new Promise((resolve) => {
|
||||||
|
const api = chrome?.storage?.sync || window.chrome?.storage?.sync
|
||||||
|
if (api) {
|
||||||
|
api.get({ [key]: defaultValue }, (result) => {
|
||||||
|
if (chrome.runtime.lastError) {
|
||||||
|
resolve(defaultValue)
|
||||||
|
} else {
|
||||||
|
resolve(result[key])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
resolve(defaultValue)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
case 'localStorage':
|
||||||
|
const stored = localStorage.getItem(`msr_${key}`)
|
||||||
|
const value = stored ? JSON.parse(stored) : defaultValue
|
||||||
|
return value
|
||||||
|
|
||||||
|
case 'memory':
|
||||||
|
default:
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通用的设置存储值函数
|
||||||
|
const setStoredValue = async (key: string, value: any) => {
|
||||||
|
const type = storageType.value
|
||||||
|
|
||||||
|
try {
|
||||||
|
switch (type) {
|
||||||
|
case 'chrome':
|
||||||
|
return await new Promise<void>((resolve, reject) => {
|
||||||
|
const api = chrome?.storage?.sync || window.chrome?.storage?.sync
|
||||||
|
if (api) {
|
||||||
|
api.set({ [key]: value }, () => {
|
||||||
|
if (chrome.runtime.lastError) {
|
||||||
|
reject(new Error(chrome.runtime.lastError.message))
|
||||||
|
} else {
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
reject(new Error('Chrome storage API 不可用'))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
case 'localStorage':
|
||||||
|
localStorage.setItem(`msr_${key}`, JSON.stringify(value))
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'memory':
|
||||||
|
// 内存存储(不持久化)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否需要显示更新弹窗
|
||||||
|
const shouldShowUpdatePopup = async (): Promise<boolean> => {
|
||||||
|
try {
|
||||||
|
const currentVersion = getCurrentVersion()
|
||||||
|
|
||||||
|
// 如果无法获取当前版本,不显示弹窗
|
||||||
|
if (currentVersion === 'unknown') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取上次显示弹窗的版本号
|
||||||
|
const lastShownVersion = await getStoredValue('lastUpdatePopupVersion', '')
|
||||||
|
|
||||||
|
// 如果版本号不同,需要显示弹窗并更新存储的版本号
|
||||||
|
if (lastShownVersion !== currentVersion) {
|
||||||
|
await setStoredValue('lastUpdatePopupVersion', currentVersion)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
} catch (error) {
|
||||||
|
console.error('检查更新弹窗状态失败:', error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标记已显示过更新弹窗(手动关闭时调用)
|
||||||
|
const markUpdatePopupShown = async () => {
|
||||||
|
try {
|
||||||
|
const currentVersion = getCurrentVersion()
|
||||||
|
if (currentVersion !== 'unknown') {
|
||||||
|
await setStoredValue('lastUpdatePopupVersion', currentVersion)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('标记更新弹窗已显示失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前存储的版本号
|
||||||
|
const getLastShownVersion = async (): Promise<string> => {
|
||||||
|
return await getStoredValue('lastUpdatePopupVersion', '')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 异步初始化函数
|
||||||
|
const initializeUpdatePopup = async () => {
|
||||||
|
try {
|
||||||
|
// 初始化存储类型检测
|
||||||
|
detectAvailableAPIs()
|
||||||
|
isLoaded.value = true
|
||||||
|
} catch (error) {
|
||||||
|
console.error('初始化更新弹窗 store 失败:', error)
|
||||||
|
isLoaded.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 立即初始化
|
||||||
|
initializeUpdatePopup()
|
||||||
|
|
||||||
|
return {
|
||||||
|
isLoaded,
|
||||||
|
storageType,
|
||||||
|
getCurrentVersion,
|
||||||
|
shouldShowUpdatePopup,
|
||||||
|
markUpdatePopupShown,
|
||||||
|
getLastShownVersion,
|
||||||
|
initializeUpdatePopup,
|
||||||
|
getStoredValue,
|
||||||
|
setStoredValue
|
||||||
|
}
|
||||||
|
})
|
Loading…
Reference in New Issue
Block a user