refactor: move flags into settings, change icon (#5678)
* refactor: move flags into settings, change icon * fix: use ButtonStyled for app
This commit is contained in:
@@ -6,12 +6,13 @@ import {
|
|||||||
LanguagesIcon,
|
LanguagesIcon,
|
||||||
ModrinthIcon,
|
ModrinthIcon,
|
||||||
PaintbrushIcon,
|
PaintbrushIcon,
|
||||||
ReportIcon,
|
|
||||||
SettingsIcon,
|
SettingsIcon,
|
||||||
ShieldIcon,
|
ShieldIcon,
|
||||||
|
ToggleRightIcon,
|
||||||
} from '@modrinth/assets'
|
} from '@modrinth/assets'
|
||||||
import {
|
import {
|
||||||
commonMessages,
|
commonMessages,
|
||||||
|
commonSettingsMessages,
|
||||||
defineMessage,
|
defineMessage,
|
||||||
defineMessages,
|
defineMessages,
|
||||||
ProgressBar,
|
ProgressBar,
|
||||||
@@ -95,11 +96,8 @@ const tabs = [
|
|||||||
content: ResourceManagementSettings,
|
content: ResourceManagementSettings,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: defineMessage({
|
name: commonSettingsMessages.featureFlags,
|
||||||
id: 'app.settings.tabs.feature-flags',
|
icon: ToggleRightIcon,
|
||||||
defaultMessage: 'Feature flags',
|
|
||||||
}),
|
|
||||||
icon: ReportIcon,
|
|
||||||
content: FeatureFlagSettings,
|
content: FeatureFlagSettings,
|
||||||
developerOnly: true,
|
developerOnly: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Toggle } from '@modrinth/ui'
|
import { ButtonStyled, Toggle } from '@modrinth/ui'
|
||||||
import { ref, watch } from 'vue'
|
import { ref, watch } from 'vue'
|
||||||
|
|
||||||
import { get as getSettings, set as setSettings } from '@/helpers/settings.ts'
|
import { get as getSettings, set as setSettings } from '@/helpers/settings.ts'
|
||||||
@@ -31,11 +31,20 @@ watch(
|
|||||||
{{ option.replaceAll('_', ' ') }}
|
{{ option.replaceAll('_', ' ') }}
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
<Toggle
|
<ButtonStyled type="transparent">
|
||||||
id="advanced-rendering"
|
<button
|
||||||
:model-value="themeStore.getFeatureFlag(option)"
|
:disabled="themeStore.getFeatureFlag(option) === DEFAULT_FEATURE_FLAGS[option]"
|
||||||
@update:model-value="() => setFeatureFlag(option, !themeStore.getFeatureFlag(option))"
|
@click="setFeatureFlag(option, DEFAULT_FEATURE_FLAGS[option])"
|
||||||
/>
|
>
|
||||||
|
Reset to default
|
||||||
|
</button>
|
||||||
|
</ButtonStyled>
|
||||||
|
<Toggle
|
||||||
|
id="advanced-rendering"
|
||||||
|
:model-value="themeStore.getFeatureFlag(option)"
|
||||||
|
@update:model-value="() => setFeatureFlag(option, !themeStore.getFeatureFlag(option))"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -197,9 +197,6 @@
|
|||||||
"app.settings.tabs.default-instance-options": {
|
"app.settings.tabs.default-instance-options": {
|
||||||
"message": "Default instance options"
|
"message": "Default instance options"
|
||||||
},
|
},
|
||||||
"app.settings.tabs.feature-flags": {
|
|
||||||
"message": "Feature flags"
|
|
||||||
},
|
|
||||||
"app.settings.tabs.java-installations": {
|
"app.settings.tabs.java-installations": {
|
||||||
"message": "Java installations"
|
"message": "Java installations"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -473,7 +473,8 @@
|
|||||||
<SettingsIcon aria-hidden="true" /> {{ formatMessage(commonMessages.settingsLabel) }}
|
<SettingsIcon aria-hidden="true" /> {{ formatMessage(commonMessages.settingsLabel) }}
|
||||||
</template>
|
</template>
|
||||||
<template #flags>
|
<template #flags>
|
||||||
<ReportIcon aria-hidden="true" /> {{ formatMessage(messages.featureFlags) }}
|
<ToggleRightIcon aria-hidden="true" />
|
||||||
|
{{ formatMessage(commonSettingsMessages.featureFlags) }}
|
||||||
</template>
|
</template>
|
||||||
<template #projects>
|
<template #projects>
|
||||||
<BoxIcon aria-hidden="true" /> {{ formatMessage(messages.projects) }}
|
<BoxIcon aria-hidden="true" /> {{ formatMessage(messages.projects) }}
|
||||||
@@ -585,9 +586,9 @@
|
|||||||
<ScaleIcon aria-hidden="true" />
|
<ScaleIcon aria-hidden="true" />
|
||||||
{{ formatMessage(commonMessages.moderationLabel) }}
|
{{ formatMessage(commonMessages.moderationLabel) }}
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<NuxtLink v-if="flags.developerMode" class="iconified-button" to="/flags">
|
<NuxtLink v-if="flags.developerMode" class="iconified-button" to="/settings/flags">
|
||||||
<ReportIcon aria-hidden="true" />
|
<ToggleRightIcon aria-hidden="true" />
|
||||||
{{ formatMessage(messages.featureFlags) }}
|
{{ formatMessage(commonSettingsMessages.featureFlags) }}
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</template>
|
</template>
|
||||||
<NuxtLink class="iconified-button" to="/settings">
|
<NuxtLink class="iconified-button" to="/settings">
|
||||||
@@ -724,6 +725,7 @@ import {
|
|||||||
SettingsIcon,
|
SettingsIcon,
|
||||||
ShieldAlertIcon,
|
ShieldAlertIcon,
|
||||||
SunIcon,
|
SunIcon,
|
||||||
|
ToggleRightIcon,
|
||||||
TransferIcon,
|
TransferIcon,
|
||||||
UserIcon,
|
UserIcon,
|
||||||
UserSearchIcon,
|
UserSearchIcon,
|
||||||
@@ -734,6 +736,7 @@ import {
|
|||||||
ButtonStyled,
|
ButtonStyled,
|
||||||
commonMessages,
|
commonMessages,
|
||||||
commonProjectTypeCategoryMessages,
|
commonProjectTypeCategoryMessages,
|
||||||
|
commonSettingsMessages,
|
||||||
defineMessages,
|
defineMessages,
|
||||||
injectModrinthClient,
|
injectModrinthClient,
|
||||||
OverflowMenu,
|
OverflowMenu,
|
||||||
@@ -918,10 +921,6 @@ const messages = defineMessages({
|
|||||||
id: 'layout.nav.upgrade-to-modrinth-plus',
|
id: 'layout.nav.upgrade-to-modrinth-plus',
|
||||||
defaultMessage: 'Upgrade to Modrinth+',
|
defaultMessage: 'Upgrade to Modrinth+',
|
||||||
},
|
},
|
||||||
featureFlags: {
|
|
||||||
id: 'layout.nav.feature-flags',
|
|
||||||
defaultMessage: 'Feature flags',
|
|
||||||
},
|
|
||||||
projects: {
|
projects: {
|
||||||
id: 'layout.nav.projects',
|
id: 'layout.nav.projects',
|
||||||
defaultMessage: 'Projects',
|
defaultMessage: 'Projects',
|
||||||
@@ -1045,7 +1044,7 @@ const userMenuOptions = computed(() => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'flags',
|
id: 'flags',
|
||||||
link: '/flags',
|
link: '/settings/flags',
|
||||||
shown: flags.value.developerMode,
|
shown: flags.value.developerMode,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1709,9 +1709,6 @@
|
|||||||
"layout.nav.discover-content": {
|
"layout.nav.discover-content": {
|
||||||
"message": "Discover content"
|
"message": "Discover content"
|
||||||
},
|
},
|
||||||
"layout.nav.feature-flags": {
|
|
||||||
"message": "Feature flags"
|
|
||||||
},
|
|
||||||
"layout.nav.get-modrinth-app": {
|
"layout.nav.get-modrinth-app": {
|
||||||
"message": "Get Modrinth App"
|
"message": "Get Modrinth App"
|
||||||
},
|
},
|
||||||
|
|||||||
6
apps/frontend/src/middleware/flags-redirect.global.ts
Normal file
6
apps/frontend/src/middleware/flags-redirect.global.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export default defineNuxtRouteMiddleware((to) => {
|
||||||
|
if (to.path.startsWith('/flags')) {
|
||||||
|
const target = to.fullPath.replace('/flags', '/settings/flags')
|
||||||
|
return navigateTo(target, { redirectCode: 301 })
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { SearchIcon } from '@modrinth/assets'
|
|
||||||
import { StyledInput, Toggle } from '@modrinth/ui'
|
|
||||||
import Fuse from 'fuse.js'
|
|
||||||
import { computed, ref, shallowReactive } from 'vue'
|
|
||||||
|
|
||||||
import {
|
|
||||||
DEFAULT_FEATURE_FLAGS,
|
|
||||||
type FeatureFlag,
|
|
||||||
saveFeatureFlags,
|
|
||||||
useFeatureFlags,
|
|
||||||
} from '~/composables/featureFlags.ts'
|
|
||||||
|
|
||||||
const flags = shallowReactive(useFeatureFlags().value)
|
|
||||||
const searchQuery = ref('')
|
|
||||||
|
|
||||||
const allFlags = computed(() => Object.keys(flags) as FeatureFlag[])
|
|
||||||
|
|
||||||
const fuse = computed(
|
|
||||||
() =>
|
|
||||||
new Fuse(allFlags.value, {
|
|
||||||
threshold: 0.4,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
const filteredFlags = computed(() => {
|
|
||||||
if (!searchQuery.value.trim()) {
|
|
||||||
return allFlags.value
|
|
||||||
}
|
|
||||||
return fuse.value.search(searchQuery.value).map((result) => result.item)
|
|
||||||
})
|
|
||||||
|
|
||||||
useSeoMeta({
|
|
||||||
robots: 'noindex',
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="mx-auto my-4 box-border w-[calc(100%-2rem)] max-w-[800px]">
|
|
||||||
<h1 class="mb-4 text-2xl font-bold text-contrast">Feature flags</h1>
|
|
||||||
<div class="mb-2">
|
|
||||||
<StyledInput
|
|
||||||
v-model="searchQuery"
|
|
||||||
type="search"
|
|
||||||
:icon="SearchIcon"
|
|
||||||
placeholder="Search flags..."
|
|
||||||
wrapper-class="w-full rounded-xl bg-bg-raised"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-2">
|
|
||||||
<div
|
|
||||||
v-for="flag in filteredFlags"
|
|
||||||
:key="`flag-${flag}`"
|
|
||||||
class="flex flex-row flex-wrap items-center gap-2 rounded-2xl bg-bg-raised p-4"
|
|
||||||
>
|
|
||||||
<label :for="`toggle-${flag}`" class="flex-1">
|
|
||||||
<span class="block font-semibold capitalize">
|
|
||||||
{{ flag.replaceAll('_', ' ') }}
|
|
||||||
</span>
|
|
||||||
<p class="m-0 text-secondary">
|
|
||||||
Default:
|
|
||||||
<span :class="DEFAULT_FEATURE_FLAGS[flag] === false ? 'text-red' : 'text-green'">
|
|
||||||
{{ DEFAULT_FEATURE_FLAGS[flag] }}
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
</label>
|
|
||||||
<Toggle
|
|
||||||
:id="`toggle-${flag}`"
|
|
||||||
v-model="flags[flag]"
|
|
||||||
@update:model-value="() => saveFeatureFlags()"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<p v-if="filteredFlags.length === 0" class="text-center text-secondary">
|
|
||||||
No flags found matching "{{ searchQuery }}"
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -20,6 +20,13 @@
|
|||||||
icon: LanguagesIcon,
|
icon: LanguagesIcon,
|
||||||
badge: `${formatMessage(commonMessages.beta)}`,
|
badge: `${formatMessage(commonMessages.beta)}`,
|
||||||
},
|
},
|
||||||
|
flags.developerMode
|
||||||
|
? {
|
||||||
|
link: '/settings/flags',
|
||||||
|
label: formatMessage(commonSettingsMessages.featureFlags),
|
||||||
|
icon: ToggleRightIcon,
|
||||||
|
}
|
||||||
|
: null,
|
||||||
auth.user ? { type: 'heading', label: formatMessage(messages.account) } : null,
|
auth.user ? { type: 'heading', label: formatMessage(messages.account) } : null,
|
||||||
auth.user
|
auth.user
|
||||||
? {
|
? {
|
||||||
@@ -91,6 +98,7 @@ import {
|
|||||||
PaintbrushIcon,
|
PaintbrushIcon,
|
||||||
ServerIcon,
|
ServerIcon,
|
||||||
ShieldIcon,
|
ShieldIcon,
|
||||||
|
ToggleRightIcon,
|
||||||
UserIcon,
|
UserIcon,
|
||||||
} from '@modrinth/assets'
|
} from '@modrinth/assets'
|
||||||
import { commonMessages, commonSettingsMessages, defineMessages, useVIntl } from '@modrinth/ui'
|
import { commonMessages, commonSettingsMessages, defineMessages, useVIntl } from '@modrinth/ui'
|
||||||
@@ -116,6 +124,7 @@ const messages = defineMessages({
|
|||||||
|
|
||||||
const route = useNativeRoute()
|
const route = useNativeRoute()
|
||||||
const auth = await useAuth()
|
const auth = await useAuth()
|
||||||
|
const flags = useFeatureFlags()
|
||||||
|
|
||||||
useSeoMeta({
|
useSeoMeta({
|
||||||
robots: 'noindex',
|
robots: 'noindex',
|
||||||
|
|||||||
87
apps/frontend/src/pages/settings/flags.vue
Normal file
87
apps/frontend/src/pages/settings/flags.vue
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { SearchIcon } from '@modrinth/assets'
|
||||||
|
import { ButtonStyled, StyledInput, Toggle } from '@modrinth/ui'
|
||||||
|
import Fuse from 'fuse.js'
|
||||||
|
import { computed, ref, shallowReactive } from 'vue'
|
||||||
|
|
||||||
|
import {
|
||||||
|
DEFAULT_FEATURE_FLAGS,
|
||||||
|
type FeatureFlag,
|
||||||
|
saveFeatureFlags,
|
||||||
|
useFeatureFlags,
|
||||||
|
} from '~/composables/featureFlags.ts'
|
||||||
|
|
||||||
|
const flags = shallowReactive(useFeatureFlags().value)
|
||||||
|
const searchQuery = ref('')
|
||||||
|
|
||||||
|
const allFlags = computed(() => Object.keys(flags) as FeatureFlag[])
|
||||||
|
|
||||||
|
function resetFlag(flag: FeatureFlag) {
|
||||||
|
flags[flag] = DEFAULT_FEATURE_FLAGS[flag]
|
||||||
|
saveFeatureFlags()
|
||||||
|
}
|
||||||
|
|
||||||
|
const fuse = computed(
|
||||||
|
() =>
|
||||||
|
new Fuse(allFlags.value, {
|
||||||
|
threshold: 0.4,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
const filteredFlags = computed(() => {
|
||||||
|
if (!searchQuery.value.trim()) {
|
||||||
|
return allFlags.value
|
||||||
|
}
|
||||||
|
return fuse.value.search(searchQuery.value).map((result) => result.item)
|
||||||
|
})
|
||||||
|
|
||||||
|
useSeoMeta({
|
||||||
|
robots: 'noindex',
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="mb-2">
|
||||||
|
<StyledInput
|
||||||
|
v-model="searchQuery"
|
||||||
|
type="search"
|
||||||
|
:icon="SearchIcon"
|
||||||
|
placeholder="Search flags..."
|
||||||
|
wrapper-class="w-full rounded-xl bg-bg-raised"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<div
|
||||||
|
v-for="flag in filteredFlags"
|
||||||
|
:key="`flag-${flag}`"
|
||||||
|
class="flex flex-row flex-wrap items-center gap-2 rounded-2xl bg-bg-raised p-4"
|
||||||
|
>
|
||||||
|
<label :for="`toggle-${flag}`" class="flex-1">
|
||||||
|
<span class="block font-semibold capitalize">
|
||||||
|
{{ flag.replaceAll('_', ' ') }}
|
||||||
|
</span>
|
||||||
|
<p class="m-0 text-secondary">
|
||||||
|
Default:
|
||||||
|
<span :class="DEFAULT_FEATURE_FLAGS[flag] === false ? 'text-red' : 'text-green'">
|
||||||
|
{{ DEFAULT_FEATURE_FLAGS[flag] }}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</label>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<ButtonStyled type="transparent">
|
||||||
|
<button :disabled="flags[flag] === DEFAULT_FEATURE_FLAGS[flag]" @click="resetFlag(flag)">
|
||||||
|
Reset to default
|
||||||
|
</button>
|
||||||
|
</ButtonStyled>
|
||||||
|
<Toggle
|
||||||
|
:id="`toggle-${flag}`"
|
||||||
|
v-model="flags[flag]"
|
||||||
|
@update:model-value="() => saveFeatureFlags()"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p v-if="filteredFlags.length === 0" class="text-center text-secondary">
|
||||||
|
No flags found matching "{{ searchQuery }}"
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -2714,6 +2714,9 @@
|
|||||||
"settings.display.theme.title": {
|
"settings.display.theme.title": {
|
||||||
"defaultMessage": "Color theme"
|
"defaultMessage": "Color theme"
|
||||||
},
|
},
|
||||||
|
"settings.feature-flags.title": {
|
||||||
|
"defaultMessage": "Feature flags"
|
||||||
|
},
|
||||||
"settings.language.categories.default": {
|
"settings.language.categories.default": {
|
||||||
"defaultMessage": "Standard languages"
|
"defaultMessage": "Standard languages"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -856,6 +856,10 @@ export const commonSettingsMessages = defineMessages({
|
|||||||
id: 'settings.billing.title',
|
id: 'settings.billing.title',
|
||||||
defaultMessage: 'Billing and subscriptions',
|
defaultMessage: 'Billing and subscriptions',
|
||||||
},
|
},
|
||||||
|
featureFlags: {
|
||||||
|
id: 'settings.feature-flags.title',
|
||||||
|
defaultMessage: 'Feature flags',
|
||||||
|
},
|
||||||
language: {
|
language: {
|
||||||
id: 'settings.language.title',
|
id: 'settings.language.title',
|
||||||
defaultMessage: 'Language',
|
defaultMessage: 'Language',
|
||||||
|
|||||||
Reference in New Issue
Block a user