| |
| |
| |
| |
|
|
| import { defineStore } from 'pinia' |
| import { ref, computed } from 'vue' |
| import type { Toast, ToastType, PublicSettings } from '@/types' |
| import { |
| checkUpdates as checkUpdatesAPI, |
| type VersionInfo, |
| type ReleaseInfo |
| } from '@/api/admin/system' |
| import { getPublicSettings as fetchPublicSettingsAPI } from '@/api/auth' |
|
|
| export const useAppStore = defineStore('app', () => { |
| |
|
|
| const sidebarCollapsed = ref<boolean>(false) |
| const mobileOpen = ref<boolean>(false) |
| const loading = ref<boolean>(false) |
| const toasts = ref<Toast[]>([]) |
|
|
| |
| const publicSettingsLoaded = ref<boolean>(false) |
| const publicSettingsLoading = ref<boolean>(false) |
| const siteName = ref<string>('Sub2API') |
| const siteLogo = ref<string>('') |
| const siteVersion = ref<string>('') |
| const contactInfo = ref<string>('') |
| const apiBaseUrl = ref<string>('') |
| const docUrl = ref<string>('') |
| const cachedPublicSettings = ref<PublicSettings | null>(null) |
|
|
| |
| const versionLoaded = ref<boolean>(false) |
| const versionLoading = ref<boolean>(false) |
| const currentVersion = ref<string>('') |
| const latestVersion = ref<string>('') |
| const hasUpdate = ref<boolean>(false) |
| const buildType = ref<string>('source') |
| const releaseInfo = ref<ReleaseInfo | null>(null) |
|
|
| |
| let toastIdCounter = 0 |
|
|
| |
|
|
| const hasActiveToasts = computed(() => toasts.value.length > 0) |
| const backendModeEnabled = computed(() => cachedPublicSettings.value?.backend_mode_enabled ?? false) |
|
|
| const loadingCount = ref<number>(0) |
|
|
| |
|
|
| |
| |
| |
| function toggleSidebar(): void { |
| sidebarCollapsed.value = !sidebarCollapsed.value |
| } |
|
|
| |
| |
| |
| |
| function setSidebarCollapsed(collapsed: boolean): void { |
| sidebarCollapsed.value = collapsed |
| } |
|
|
| |
| |
| |
| function toggleMobileSidebar(): void { |
| mobileOpen.value = !mobileOpen.value |
| } |
|
|
| |
| |
| |
| |
| function setMobileOpen(open: boolean): void { |
| mobileOpen.value = open |
| } |
|
|
| |
| |
| |
| |
| function setLoading(isLoading: boolean): void { |
| if (isLoading) { |
| loadingCount.value++ |
| } else { |
| loadingCount.value = Math.max(0, loadingCount.value - 1) |
| } |
| loading.value = loadingCount.value > 0 |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| function showToast(type: ToastType, message: string, duration?: number): string { |
| const id = `toast-${++toastIdCounter}` |
| const toast: Toast = { |
| id, |
| type, |
| message, |
| duration, |
| startTime: duration !== undefined ? Date.now() : undefined |
| } |
|
|
| toasts.value.push(toast) |
|
|
| |
| if (duration !== undefined) { |
| setTimeout(() => { |
| hideToast(id) |
| }, duration) |
| } |
|
|
| return id |
| } |
|
|
| |
| |
| |
| |
| |
| function showSuccess(message: string, duration: number = 3000): string { |
| return showToast('success', message, duration) |
| } |
|
|
| |
| |
| |
| |
| |
| function showError(message: string, duration: number = 5000): string { |
| return showToast('error', message, duration) |
| } |
|
|
| |
| |
| |
| |
| |
| function showInfo(message: string, duration: number = 3000): string { |
| return showToast('info', message, duration) |
| } |
|
|
| |
| |
| |
| |
| |
| function showWarning(message: string, duration: number = 4000): string { |
| return showToast('warning', message, duration) |
| } |
|
|
| |
| |
| |
| |
| function hideToast(id: string): void { |
| const index = toasts.value.findIndex((t) => t.id === id) |
| if (index !== -1) { |
| toasts.value.splice(index, 1) |
| } |
| } |
|
|
| |
| |
| |
| function clearAllToasts(): void { |
| toasts.value = [] |
| } |
|
|
| |
| |
| |
| |
| |
| |
| async function withLoading<T>(operation: () => Promise<T>): Promise<T> { |
| setLoading(true) |
| try { |
| return await operation() |
| } finally { |
| setLoading(false) |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| async function withLoadingAndError<T>( |
| operation: () => Promise<T>, |
| errorMessage?: string |
| ): Promise<T | null> { |
| setLoading(true) |
| try { |
| return await operation() |
| } catch (error) { |
| const message = errorMessage || (error as { message?: string }).message || 'An error occurred' |
| showError(message) |
| return null |
| } finally { |
| setLoading(false) |
| } |
| } |
|
|
| |
| |
| |
| |
| function reset(): void { |
| sidebarCollapsed.value = false |
| loading.value = false |
| loadingCount.value = 0 |
| toasts.value = [] |
| } |
|
|
| |
|
|
| |
| |
| |
| |
| async function fetchVersion(force = false): Promise<VersionInfo | null> { |
| |
| if (versionLoaded.value && !force) { |
| return { |
| current_version: currentVersion.value, |
| latest_version: latestVersion.value, |
| has_update: hasUpdate.value, |
| build_type: buildType.value, |
| release_info: releaseInfo.value || undefined, |
| cached: true |
| } |
| } |
|
|
| |
| if (versionLoading.value) { |
| return null |
| } |
|
|
| versionLoading.value = true |
| try { |
| const data = await checkUpdatesAPI(force) |
| currentVersion.value = data.current_version |
| latestVersion.value = data.latest_version |
| hasUpdate.value = data.has_update |
| buildType.value = data.build_type || 'source' |
| releaseInfo.value = data.release_info || null |
| versionLoaded.value = true |
| return data |
| } catch (error) { |
| console.error('Failed to fetch version:', error) |
| return null |
| } finally { |
| versionLoading.value = false |
| } |
| } |
|
|
| |
| |
| |
| function clearVersionCache(): void { |
| versionLoaded.value = false |
| hasUpdate.value = false |
| } |
|
|
| |
|
|
| |
| |
| |
| function applySettings(config: PublicSettings): void { |
| cachedPublicSettings.value = config |
| siteName.value = config.site_name || 'Sub2API' |
| siteLogo.value = config.site_logo || '' |
| siteVersion.value = config.version || '' |
| contactInfo.value = config.contact_info || '' |
| apiBaseUrl.value = config.api_base_url || '' |
| docUrl.value = config.doc_url || '' |
| publicSettingsLoaded.value = true |
| } |
|
|
| |
| |
| |
| |
| async function fetchPublicSettings(force = false): Promise<PublicSettings | null> { |
| |
| if (!publicSettingsLoaded.value && !force && window.__APP_CONFIG__) { |
| applySettings(window.__APP_CONFIG__) |
| return window.__APP_CONFIG__ |
| } |
|
|
| |
| if (publicSettingsLoaded.value && !force) { |
| if (cachedPublicSettings.value) { |
| return { ...cachedPublicSettings.value } |
| } |
| return { |
| registration_enabled: false, |
| email_verify_enabled: false, |
| registration_email_suffix_whitelist: [], |
| promo_code_enabled: true, |
| password_reset_enabled: false, |
| invitation_code_enabled: false, |
| turnstile_enabled: false, |
| turnstile_site_key: '', |
| site_name: siteName.value, |
| site_logo: siteLogo.value, |
| site_subtitle: '', |
| api_base_url: apiBaseUrl.value, |
| contact_info: contactInfo.value, |
| doc_url: docUrl.value, |
| home_content: '', |
| hide_ccs_import_button: false, |
| purchase_subscription_enabled: false, |
| purchase_subscription_url: '', |
| custom_menu_items: [], |
| linuxdo_oauth_enabled: false, |
| sora_client_enabled: false, |
| backend_mode_enabled: false, |
| version: siteVersion.value |
| } |
| } |
|
|
| |
| if (publicSettingsLoading.value) { |
| return null |
| } |
|
|
| publicSettingsLoading.value = true |
| try { |
| const data = await fetchPublicSettingsAPI() |
| applySettings(data) |
| return data |
| } catch (error) { |
| console.error('Failed to fetch public settings:', error) |
| return null |
| } finally { |
| publicSettingsLoading.value = false |
| } |
| } |
|
|
| |
| |
| |
| function clearPublicSettingsCache(): void { |
| publicSettingsLoaded.value = false |
| cachedPublicSettings.value = null |
| } |
|
|
| |
| |
| |
| |
| |
| function initFromInjectedConfig(): boolean { |
| if (window.__APP_CONFIG__) { |
| applySettings(window.__APP_CONFIG__) |
| return true |
| } |
| return false |
| } |
|
|
| |
|
|
| return { |
| |
| sidebarCollapsed, |
| mobileOpen, |
| loading, |
| toasts, |
|
|
| |
| publicSettingsLoaded, |
| siteName, |
| siteLogo, |
| siteVersion, |
| contactInfo, |
| apiBaseUrl, |
| docUrl, |
| cachedPublicSettings, |
|
|
| |
| versionLoaded, |
| versionLoading, |
| currentVersion, |
| latestVersion, |
| hasUpdate, |
| buildType, |
| releaseInfo, |
|
|
| |
| hasActiveToasts, |
| backendModeEnabled, |
|
|
| |
| toggleSidebar, |
| setSidebarCollapsed, |
| toggleMobileSidebar, |
| setMobileOpen, |
| setLoading, |
| showToast, |
| showSuccess, |
| showError, |
| showInfo, |
| showWarning, |
| hideToast, |
| clearAllToasts, |
| withLoading, |
| withLoadingAndError, |
| reset, |
|
|
| |
| fetchVersion, |
| clearVersionCache, |
|
|
| |
| fetchPublicSettings, |
| clearPublicSettingsCache, |
| initFromInjectedConfig |
| } |
| }) |
|
|