forked from Mirrors/elk
refactor: unify user settings (#821)
Co-authored-by: 三咲智子 <sxzz@sxzz.moe>
This commit is contained in:
parent
35c9a871be
commit
1aa118283e
17 changed files with 105 additions and 61 deletions
|
@ -22,9 +22,9 @@ function toggleDark() {
|
|||
<button
|
||||
flex
|
||||
text-lg
|
||||
:class="isZenMode ? 'i-ri:layout-right-2-line' : 'i-ri:layout-right-line'"
|
||||
:class="userSettings.zenMode ? 'i-ri:layout-right-2-line' : 'i-ri:layout-right-line'"
|
||||
:aria-label="$t('nav.zen_mode')"
|
||||
@click="toggleZenMode()"
|
||||
@click="userSettings.zenMode = !userSettings.zenMode"
|
||||
/>
|
||||
</CommonTooltip>
|
||||
</div>
|
||||
|
|
|
@ -126,7 +126,7 @@ async function editStatus() {
|
|||
|
||||
<template #popper>
|
||||
<div flex="~ col">
|
||||
<template v-if="isZenMode">
|
||||
<template v-if="userSettings.zenMode">
|
||||
<CommonDropdownItem
|
||||
:text="$t('action.reply')"
|
||||
icon="i-ri:chat-3-line"
|
||||
|
|
|
@ -138,7 +138,7 @@ const isDM = $computed(() => status.visibility === 'direct')
|
|||
<StatusReplyingTo :collapsed="true" :status="status" :class="faded ? 'text-secondary-light' : ''" />
|
||||
</div>
|
||||
<div flex-auto />
|
||||
<div v-if="!isZenMode" text-sm text-secondary flex="~ row nowrap" hover:underline>
|
||||
<div v-if="!userSettings.zenMode" text-sm text-secondary flex="~ row nowrap" hover:underline>
|
||||
<AccountBotIndicator v-if="status.account.bot" me-2 />
|
||||
<div flex>
|
||||
<CommonTooltip :content="createdAt">
|
||||
|
@ -155,7 +155,7 @@ const isDM = $computed(() => status.visibility === 'direct')
|
|||
</div>
|
||||
<StatusContent :status="status" :context="context" mb2 :class="{ 'mt-2 mb1': isDM }" />
|
||||
<div>
|
||||
<StatusActions v-if="(actions !== false && !isZenMode)" :status="status" />
|
||||
<StatusActions v-if="(actions !== false && !userSettings.zenMode)" :status="status" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -21,7 +21,7 @@ const isSquare = $computed(() => (
|
|||
))
|
||||
const providerName = $computed(() => props.card.providerName ? props.card.providerName : new URL(props.card.url).hostname)
|
||||
|
||||
const gitHubCards = $(computedEager(() => useFeatureFlags().experimentalGitHubCards))
|
||||
const gitHubCards = $(useFeatureFlag('experimentalGitHubCards'))
|
||||
|
||||
// TODO: handle card.type: 'photo' | 'video' | 'rich';
|
||||
const cardTypeIconMap: Record<CardType, string> = {
|
||||
|
|
|
@ -12,7 +12,7 @@ const { paginator, stream } = defineProps<{
|
|||
}>()
|
||||
|
||||
const { formatNumber } = useHumanReadableNumber()
|
||||
const virtualScroller = $(computedEager(() => useFeatureFlags().experimentalVirtualScroll))
|
||||
const virtualScroller = $(useFeatureFlag('experimentalVirtualScroll'))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -271,10 +271,10 @@ export const provideGlobalCommands = () => {
|
|||
scope: 'Preferences',
|
||||
|
||||
name: () => t('command.toggle_zen_mode'),
|
||||
icon: () => isZenMode.value ? 'i-ri:layout-right-2-line' : 'i-ri:layout-right-line',
|
||||
icon: () => userSettings.value.zenMode ? 'i-ri:layout-right-2-line' : 'i-ri:layout-right-line',
|
||||
|
||||
onActivate() {
|
||||
toggleZenMode()
|
||||
userSettings.value.zenMode = !userSettings.value.zenMode
|
||||
},
|
||||
})
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import type { Attachment, Status, StatusEdit } from 'masto'
|
||||
import type { Draft } from '~/types'
|
||||
import { STORAGE_KEY_FIRST_VISIT, STORAGE_KEY_ZEN_MODE } from '~/constants'
|
||||
import { STORAGE_KEY_FIRST_VISIT } from '~/constants'
|
||||
|
||||
export const mediaPreviewList = ref<Attachment[]>([])
|
||||
export const mediaPreviewIndex = ref(0)
|
||||
|
@ -11,7 +11,6 @@ export const dialogDraftKey = ref<string>()
|
|||
export const commandPanelInput = ref('')
|
||||
|
||||
export const isFirstVisit = useLocalStorage(STORAGE_KEY_FIRST_VISIT, !process.mock)
|
||||
export const isZenMode = useLocalStorage(STORAGE_KEY_ZEN_MODE, false)
|
||||
|
||||
export const isSigninDialogOpen = ref(false)
|
||||
export const isPublishDialogOpen = ref(false)
|
||||
|
@ -22,8 +21,6 @@ export const isCommandPanelOpen = ref(false)
|
|||
|
||||
export const lastPublishDialogStatus = ref<Status | null>(null)
|
||||
|
||||
export const toggleZenMode = useToggle(isZenMode)
|
||||
|
||||
export function openSigninDialog() {
|
||||
isSigninDialogOpen.value = true
|
||||
}
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
import { STORAGE_KEY_FEATURE_FLAGS } from '~/constants'
|
||||
|
||||
export interface FeatureFlags {
|
||||
experimentalVirtualScroll: boolean
|
||||
experimentalGitHubCards: boolean
|
||||
experimentalUserPicker: boolean
|
||||
}
|
||||
export type FeatureFlagsMap = Record<string, FeatureFlags>
|
||||
|
||||
export function getDefaultFeatureFlags(): FeatureFlags {
|
||||
return {
|
||||
experimentalVirtualScroll: false,
|
||||
experimentalGitHubCards: true,
|
||||
experimentalUserPicker: true,
|
||||
}
|
||||
}
|
||||
|
||||
export const currentUserFeatureFlags = process.server
|
||||
? computed(getDefaultFeatureFlags)
|
||||
: useUserLocalStorage(STORAGE_KEY_FEATURE_FLAGS, getDefaultFeatureFlags)
|
||||
|
||||
export function useFeatureFlags() {
|
||||
const featureFlags = currentUserFeatureFlags.value
|
||||
|
||||
return featureFlags
|
||||
}
|
||||
|
||||
export function toggleFeatureFlag(key: keyof FeatureFlags) {
|
||||
const featureFlags = currentUserFeatureFlags.value
|
||||
|
||||
if (featureFlags[key])
|
||||
featureFlags[key] = !featureFlags[key]
|
||||
else
|
||||
featureFlags[key] = true
|
||||
}
|
||||
|
||||
const userPicker = eagerComputed(() => useFeatureFlags().experimentalUserPicker)
|
||||
export const showUserPicker = computed(() => useUsers().value.length > 1 && userPicker.value)
|
36
composables/settings/featureFlags.ts
Normal file
36
composables/settings/featureFlags.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
import type { Ref } from 'vue'
|
||||
import { userSettings } from '.'
|
||||
|
||||
export interface FeatureFlags {
|
||||
experimentalVirtualScroll: boolean
|
||||
experimentalGitHubCards: boolean
|
||||
experimentalUserPicker: boolean
|
||||
}
|
||||
export type FeatureFlagsMap = Record<string, FeatureFlags>
|
||||
|
||||
const DEFAULT_FEATURE_FLAGS: FeatureFlags = {
|
||||
experimentalVirtualScroll: false,
|
||||
experimentalGitHubCards: true,
|
||||
experimentalUserPicker: true,
|
||||
}
|
||||
|
||||
export function useFeatureFlag<T extends keyof FeatureFlags>(name: T): Ref<FeatureFlags[T]> {
|
||||
return computed({
|
||||
get() {
|
||||
return getFeatureFlag(name)
|
||||
},
|
||||
set(value) {
|
||||
if (userSettings.value)
|
||||
userSettings.value.featureFlags[name] = value
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function getFeatureFlag<T extends keyof FeatureFlags>(name: T): FeatureFlags[T] {
|
||||
return userSettings.value?.featureFlags?.[name] ?? DEFAULT_FEATURE_FLAGS[name]
|
||||
}
|
||||
|
||||
export function toggleFeatureFlag(key: keyof FeatureFlags) {
|
||||
const flag = useFeatureFlag(key)
|
||||
flag.value = !flag.value
|
||||
}
|
21
composables/settings/index.ts
Normal file
21
composables/settings/index.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
import type { FeatureFlags } from './featureFlags'
|
||||
import type { ColorMode, FontSize } from '~/types'
|
||||
import { STORAGE_KEY_SETTINGS } from '~/constants'
|
||||
|
||||
export interface UserSettings {
|
||||
featureFlags: Partial<FeatureFlags>
|
||||
colorMode?: ColorMode
|
||||
fontSize?: FontSize
|
||||
lang?: string
|
||||
zenMode?: boolean
|
||||
}
|
||||
|
||||
export function getDefaultUserSettings(): UserSettings {
|
||||
return {
|
||||
featureFlags: {},
|
||||
}
|
||||
}
|
||||
|
||||
export const userSettings = process.server
|
||||
? computed(getDefaultUserSettings)
|
||||
: useUserLocalStorage(STORAGE_KEY_SETTINGS, getDefaultUserSettings)
|
|
@ -10,9 +10,7 @@ export const STORAGE_KEY_SERVERS = 'elk-servers'
|
|||
export const STORAGE_KEY_CURRENT_USER = 'elk-current-user'
|
||||
export const STORAGE_KEY_NOTIFY_TAB = 'elk-notify-tab'
|
||||
export const STORAGE_KEY_FIRST_VISIT = 'elk-first-visit'
|
||||
export const STORAGE_KEY_ZEN_MODE = 'elk-zenmode'
|
||||
export const STORAGE_KEY_LANG = 'elk-lang'
|
||||
export const STORAGE_KEY_FEATURE_FLAGS = 'elk-feature-flags'
|
||||
export const STORAGE_KEY_SETTINGS = 'elk-settings'
|
||||
export const STORAGE_KEY_CUSTOM_EMOJIS = 'elk-custom-emojis'
|
||||
export const STORAGE_KEY_HIDE_EXPLORE_POSTS_TIPS = 'elk-hide-explore-posts-tips'
|
||||
export const STORAGE_KEY_HIDE_EXPLORE_NEWS_TIPS = 'elk-hide-explore-news-tips'
|
||||
|
@ -24,6 +22,6 @@ export const COOKIE_MAX_AGE = 10 * 365 * 24 * 60 * 60 * 1000
|
|||
|
||||
export const COOKIE_KEY_FONT_SIZE = 'elk-font-size'
|
||||
export const COOKIE_KEY_COLOR_MODE = 'elk-color-mode'
|
||||
export const COOKIE_KEY_LOCALE = 'elk-locale'
|
||||
export const COOKIE_KEY_LOCALE = 'elk-lang'
|
||||
|
||||
export const HANDLED_MASTO_URLS = /^(https?:\/\/)?([\w\d-]+\.)+\w+\/(@[@\w\d-\.]+)(\/objects)?(\/\d+)?$/
|
||||
|
|
|
@ -1,11 +1,18 @@
|
|||
<script lang="ts" setup>
|
||||
import { useFeatureFlag } from '~~/composables/settings/featureFlags'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const wideLayout = computed(() => route.meta.wideLayout ?? false)
|
||||
|
||||
const showUserPicker = logicAnd(
|
||||
useFeatureFlag('experimentalUserPicker'),
|
||||
() => useUsers().value.length > 1,
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div h-full :class="{ zen: isZenMode }">
|
||||
<div h-full :class="{ zen: userSettings.zenMode }">
|
||||
<main flex w-full mxa lg:max-w-80rem>
|
||||
<aside class="hidden sm:flex w-1/8 md:w-1/6 lg:w-1/5 xl:w-1/4 justify-end xl:me-4 zen-hide" relative>
|
||||
<div sticky top-0 w-20 xl:w-100 h-screen flex="~ col" lt-xl-items-center>
|
||||
|
|
|
@ -50,6 +50,7 @@ export default defineNuxtConfig({
|
|||
dirs: [
|
||||
'./composables/push-notifications',
|
||||
'./composables/tiptap',
|
||||
'./composables/settings',
|
||||
],
|
||||
},
|
||||
vite: {
|
||||
|
|
|
@ -87,6 +87,7 @@
|
|||
"@unocss/nuxt": "^0.48.0",
|
||||
"@vitejs/plugin-vue": "^3.2.0",
|
||||
"@vue-macros/nuxt": "^0.2.8",
|
||||
"@vueuse/math": "^9.10.0",
|
||||
"@vueuse/nuxt": "^9.9.0",
|
||||
"bumpp": "^8.2.1",
|
||||
"emoji-mart": "^5.4.0",
|
||||
|
|
|
@ -18,19 +18,19 @@ useHeadFixed({
|
|||
{{ $t('settings.feature_flags.title') }}
|
||||
</h3>
|
||||
<SettingsToggleItem
|
||||
:checked="currentUserFeatureFlags.experimentalVirtualScroll"
|
||||
:checked="getFeatureFlag('experimentalVirtualScroll')"
|
||||
@click="toggleFeatureFlag('experimentalVirtualScroll')"
|
||||
>
|
||||
{{ $t('settings.feature_flags.virtual_scroll') }}
|
||||
</SettingsToggleItem>
|
||||
<SettingsToggleItem
|
||||
:checked="currentUserFeatureFlags.experimentalGitHubCards"
|
||||
:checked="getFeatureFlag('experimentalGitHubCards')"
|
||||
@click="toggleFeatureFlag('experimentalGitHubCards')"
|
||||
>
|
||||
{{ $t('settings.feature_flags.github_cards') }}
|
||||
</SettingsToggleItem>
|
||||
<SettingsToggleItem
|
||||
:checked="currentUserFeatureFlags.experimentalUserPicker"
|
||||
:checked="getFeatureFlag('experimentalUserPicker')"
|
||||
@click="toggleFeatureFlag('experimentalUserPicker')"
|
||||
>
|
||||
{{ $t('settings.feature_flags.user_picker') }}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { COOKIE_MAX_AGE, STORAGE_KEY_LANG } from '~/constants'
|
||||
import { COOKIE_KEY_LOCALE, COOKIE_MAX_AGE } from '~/constants'
|
||||
|
||||
export default defineNuxtPlugin(async (nuxt) => {
|
||||
const i18n = nuxt.vueApp.config.globalProperties.$i18n
|
||||
const { setLocale, locales } = nuxt.vueApp.config.globalProperties.$i18n
|
||||
const cookieLocale = useCookie(STORAGE_KEY_LANG, { maxAge: COOKIE_MAX_AGE })
|
||||
const cookieLocale = useCookie(COOKIE_KEY_LOCALE, { maxAge: COOKIE_MAX_AGE })
|
||||
const isFirstVisit = cookieLocale.value == null
|
||||
|
||||
if (process.client && isFirstVisit) {
|
||||
|
|
|
@ -37,6 +37,7 @@ specifiers:
|
|||
'@vueuse/core': ^9.9.0
|
||||
'@vueuse/gesture': 2.0.0-beta.1
|
||||
'@vueuse/integrations': ^9.9.0
|
||||
'@vueuse/math': ^9.10.0
|
||||
'@vueuse/motion': 2.0.0-beta.12
|
||||
'@vueuse/nuxt': ^9.9.0
|
||||
blurhash: ^2.0.4
|
||||
|
@ -147,6 +148,7 @@ devDependencies:
|
|||
'@unocss/nuxt': 0.48.0
|
||||
'@vitejs/plugin-vue': 3.2.0
|
||||
'@vue-macros/nuxt': 0.2.8_3nbxte3dhogb6b7pemmre2vo4m
|
||||
'@vueuse/math': 9.10.0
|
||||
'@vueuse/nuxt': 9.9.0_nuxt@3.0.0
|
||||
bumpp: 8.2.1
|
||||
emoji-mart: 5.4.0
|
||||
|
@ -3658,6 +3660,16 @@ packages:
|
|||
- vue
|
||||
dev: false
|
||||
|
||||
/@vueuse/math/9.10.0:
|
||||
resolution: {integrity: sha512-DvCyQFhXToPgZzSs841TCylIVwuab5gNErjdFCNWX3vs+T3rC8wMnlaBCYTXb5g4ruqm6K/hBnHyalRnCj/VbQ==}
|
||||
dependencies:
|
||||
'@vueuse/shared': 9.10.0
|
||||
vue-demi: 0.13.11
|
||||
transitivePeerDependencies:
|
||||
- '@vue/composition-api'
|
||||
- vue
|
||||
dev: true
|
||||
|
||||
/@vueuse/metadata/8.9.4:
|
||||
resolution: {integrity: sha512-IwSfzH80bnJMzqhaapqJl9JRIiyQU0zsRGEgnxN6jhq7992cPUJIRfV+JHRIZXjYqbwt07E1gTEp0R0zPJ1aqw==}
|
||||
dev: false
|
||||
|
@ -3714,6 +3726,15 @@ packages:
|
|||
vue-demi: 0.13.11
|
||||
dev: false
|
||||
|
||||
/@vueuse/shared/9.10.0:
|
||||
resolution: {integrity: sha512-vakHJ2ZRklAzqmcVBL38RS7BxdBA4+5poG9NsSyqJxrt9kz0zX3P5CXMy0Hm6LFbZXUgvKdqAS3pUH1zX/5qTQ==}
|
||||
dependencies:
|
||||
vue-demi: 0.13.11
|
||||
transitivePeerDependencies:
|
||||
- '@vue/composition-api'
|
||||
- vue
|
||||
dev: true
|
||||
|
||||
/@vueuse/shared/9.9.0:
|
||||
resolution: {integrity: sha512-+D0XFwHG0T+uaIbCSlROBwm1wzs71B7n3KyDOxnvfEMMHDOzl09rYKwaE2AENmYwYPXfHPbSBRDD2gBVHbvTcg==}
|
||||
dependencies:
|
||||
|
|
Loading…
Reference in a new issue