disclosures in search

This commit is contained in:
Prospector
2026-08-06 18:43:22 -07:00
parent ebf19eb828
commit 746bc9a55e
28 changed files with 329 additions and 184 deletions
+14
View File
@@ -1242,11 +1242,25 @@ const advancedFiltersCollapsed = computed({
}, },
}) })
const dismissedPhotosensitivityFilterWarning = computed({
get: () => themeStore.getFeatureFlag('dismissed_photosensitivity_filter_warning'),
set: (value) => {
themeStore.featureFlags['dismissed_photosensitivity_filter_warning'] = value
getSettings()
.then((settings) => {
settings.feature_flags['dismissed_photosensitivity_filter_warning'] = value
return setSettings(settings)
})
.catch(handleError)
},
})
provideBrowseManager({ provideBrowseManager({
tags, tags,
projectType, projectType,
...searchState, ...searchState,
advancedFiltersCollapsed, advancedFiltersCollapsed,
dismissedPhotosensitivityFilterWarning,
getProjectLink: (result: Labrinth.Search.v3.ResultSearchProject) => ({ getProjectLink: (result: Labrinth.Search.v3.ResultSearchProject) => ({
path: `/project/${result.project_id ?? result.slug}`, path: `/project/${result.project_id ?? result.slug}`,
query: getProjectBrowseQuery(), query: getProjectBrowseQuery(),
+1
View File
@@ -22,6 +22,7 @@ export const DEFAULT_FEATURE_FLAGS = {
friends_online_collapsed: false, friends_online_collapsed: false,
friends_offline_collapsed: true, friends_offline_collapsed: true,
friends_pending_collapsed: true, friends_pending_collapsed: true,
dismissed_photosensitivity_filter_warning: false,
} }
export const THEME_OPTIONS = ['dark', 'light', 'oled', 'retro', 'system'] as const export const THEME_OPTIONS = ['dark', 'light', 'oled', 'retro', 'system'] as const
@@ -58,6 +58,7 @@ export const DEFAULT_FEATURE_FLAGS = validateValues({
versionDevInfoCollapsed: true, versionDevInfoCollapsed: true,
alwaysShowVersionDevInfo: false, alwaysShowVersionDevInfo: false,
advancedFiltersCollapsed: true, advancedFiltersCollapsed: true,
dismissedPhotosensitivityFilterWarning: false,
} as const) } as const)
export type FeatureFlag = keyof typeof DEFAULT_FEATURE_FLAGS export type FeatureFlag = keyof typeof DEFAULT_FEATURE_FLAGS
@@ -416,6 +416,14 @@ const advancedFiltersCollapsed = computed({
}, },
}) })
const dismissedPhotosensitivityFilterWarning = computed({
get: () => flags.value.dismissedPhotosensitivityFilterWarning,
set: (value) => {
flags.value.dismissedPhotosensitivityFilterWarning = value
saveFeatureFlags()
},
})
const projectTypeId = computed(() => projectType.value?.id ?? 'mod') const projectTypeId = computed(() => projectType.value?.id ?? 'mod')
debug('projectTypeId:', projectTypeId.value) debug('projectTypeId:', projectTypeId.value)
@@ -525,6 +533,7 @@ provideBrowseManager({
serverOnlyLabel: computed(() => formatMessage(commonMessages.serverOnlyLabel)), serverOnlyLabel: computed(() => formatMessage(commonMessages.serverOnlyLabel)),
hiddenFilterTypes: computed(() => (showServerOnlyToggle.value ? ['environment'] : [])), hiddenFilterTypes: computed(() => (showServerOnlyToggle.value ? ['environment'] : [])),
advancedFiltersCollapsed, advancedFiltersCollapsed,
dismissedPhotosensitivityFilterWarning,
displayMode: resultsDisplayMode, displayMode: resultsDisplayMode,
cycleDisplayMode: cycleSearchDisplayMode, cycleDisplayMode: cycleSearchDisplayMode,
maxResultsOptions: currentMaxResultsOptions, maxResultsOptions: currentMaxResultsOptions,
+1
View File
@@ -70,6 +70,7 @@ pub enum FeatureFlag {
FriendsOnlineCollapsed, FriendsOnlineCollapsed,
FriendsOfflineCollapsed, FriendsOfflineCollapsed,
FriendsPendingCollapsed, FriendsPendingCollapsed,
DismissedPhotosensitivityFilterWarning,
} }
impl Settings { impl Settings {
@@ -0,0 +1,100 @@
<template>
<NewModal
ref="modal"
:header="formatMessage(messages.title)"
:on-hide="handleHide"
:on-after-hide="handleAfterHide"
:closable="false"
fade="warning"
max-width="544px"
>
<div class="flex flex-col gap-6">
<p class="m-0 leading-normal">
{{ formatMessage(messages.body1) }}
</p>
<p class="m-0 leading-normal">
{{ formatMessage(messages.body2) }}
</p>
<div class="flex justify-between items-center">
<Checkbox v-model="dontShowAgain" :label="formatMessage(messages.dontShowAgain)" />
<Button type="colored" color="brand" native-type="button" @click="acknowledge">
<CheckCircleIcon class="size-4" />
{{ formatMessage(messages.iUnderstand) }}
</Button>
</div>
</div>
</NewModal>
</template>
<script setup lang="ts">
import { CheckCircleIcon } from '@modrinth/assets'
import { ref, useTemplateRef } from 'vue'
import { Button } from '#ui/components/base/buttons'
import { defineMessages, useVIntl } from '../../composables/i18n'
import Checkbox from '../base/Checkbox.vue'
import NewModal from './NewModal.vue'
const emit = defineEmits<{
dismiss: [dontShowAgain: boolean]
}>()
const { formatMessage } = useVIntl()
const modal = useTemplateRef('modal')
const dontShowAgain = ref(false)
let pendingDontShowAgain = false
const messages = defineMessages({
title: {
id: 'search.photosensitivity-warning-modal.title',
defaultMessage: 'Results are not guaranteed to be safe',
},
body1: {
id: 'search.photosensitivity-warning-modal.body.1',
defaultMessage:
'We cannot guarantee that all content on Modrinth that may be dangerous for users with photosensitivity is labeled appropriately with a photosensitivity warning.',
},
body2: {
id: 'search.photosensitivity-warning-modal.body.2',
defaultMessage:
'The label is self-assigned by the users who uploaded the content, and do not indicate that the content has gone through any kind of safety testing. Using any content on Modrinth is at your own risk. Please stay safe.',
},
dontShowAgain: {
id: 'search.photosensitivity-warning-modal.dont-show-again',
defaultMessage: "Don't show this again",
},
iUnderstand: {
id: 'search.photosensitivity-warning-modal.i-understand',
defaultMessage: 'I understand',
},
})
function show() {
dontShowAgain.value = false
pendingDontShowAgain = false
modal.value?.show()
}
function hide() {
modal.value?.hide()
}
function handleHide() {
pendingDontShowAgain = dontShowAgain.value
}
function handleAfterHide() {
emit('dismiss', pendingDontShowAgain)
dontShowAgain.value = false
pendingDontShowAgain = false
}
function acknowledge() {
pendingDontShowAgain = dontShowAgain.value
modal.value?.hide()
}
defineExpose({ show, hide })
</script>
@@ -3,6 +3,7 @@ export { default as ConfirmModal } from './ConfirmModal.vue'
export { default as NewModal } from './NewModal.vue' export { default as NewModal } from './NewModal.vue'
export type { ServerProject as OpenInAppModalServerProject } from './OpenInAppModal.vue' export type { ServerProject as OpenInAppModalServerProject } from './OpenInAppModal.vue'
export { default as OpenInAppModal } from './OpenInAppModal.vue' export { default as OpenInAppModal } from './OpenInAppModal.vue'
export { default as PhotosensitivityWarningModal } from './PhotosensitivityWarningModal.vue'
export { default as ShareModal } from './ShareModal.vue' export { default as ShareModal } from './ShareModal.vue'
export type { Tab as TabbedModalTab } from './TabbedModal.vue' export type { Tab as TabbedModalTab } from './TabbedModal.vue'
export { default as TabbedModal } from './TabbedModal.vue' export { default as TabbedModal } from './TabbedModal.vue'
@@ -74,6 +74,7 @@ export interface BrowseManagerContext {
serverOnlyLabel?: ComputedRef<string> serverOnlyLabel?: ComputedRef<string>
hiddenFilterTypes?: ComputedRef<string[]> hiddenFilterTypes?: ComputedRef<string[]>
advancedFiltersCollapsed?: Ref<boolean> advancedFiltersCollapsed?: Ref<boolean>
dismissedPhotosensitivityFilterWarning?: Ref<boolean>
onInstalled?: (projectId: string) => void onInstalled?: (projectId: string) => void
displayMode?: Ref<'list' | 'grid' | 'gallery'> | ComputedRef<'list' | 'grid' | 'gallery'> displayMode?: Ref<'list' | 'grid' | 'gallery'> | ComputedRef<'list' | 'grid' | 'gallery'>
@@ -1,15 +1,18 @@
<script setup lang="ts"> <script setup lang="ts">
import { InfoIcon, XIcon } from '@modrinth/assets' import { InfoIcon, XIcon } from '@modrinth/assets'
import { computed, toValue } from 'vue' import { computed, nextTick, toValue, useTemplateRef, watch } from 'vue'
import { IconButton } from '#ui/components/base/buttons' import { IconButton } from '#ui/components/base/buttons'
import Toggle from '#ui/components/base/Toggle.vue' import Toggle from '#ui/components/base/Toggle.vue'
import PhotosensitivityWarningModal from '#ui/components/modal/PhotosensitivityWarningModal.vue'
import SearchSidebarFilter from '#ui/components/search/SearchSidebarFilter.vue' import SearchSidebarFilter from '#ui/components/search/SearchSidebarFilter.vue'
import { useVIntl } from '#ui/composables/i18n' import { useVIntl } from '#ui/composables/i18n'
import { commonMessages } from '#ui/utils/common-messages' import { commonMessages } from '#ui/utils/common-messages'
import { injectBrowseManager } from './providers/browse-manager' import { injectBrowseManager } from './providers/browse-manager'
const PHOTOSENSITIVITY_FILTER_OPTION = 'epilepsy_triggers'
const ctx = injectBrowseManager() const ctx = injectBrowseManager()
const { formatMessage } = useVIntl() const { formatMessage } = useVIntl()
@@ -18,6 +21,7 @@ const lockedMessages = computed(() => toValue(ctx.lockedFilterMessages))
const hiddenFilterTypes = computed(() => ctx.hiddenFilterTypes?.value ?? []) const hiddenFilterTypes = computed(() => ctx.hiddenFilterTypes?.value ?? [])
const advancedFiltersCollapsed = computed(() => ctx.advancedFiltersCollapsed?.value ?? true) const advancedFiltersCollapsed = computed(() => ctx.advancedFiltersCollapsed?.value ?? true)
const photosensitivityWarningModal = useTemplateRef('photosensitivityWarningModal')
function setAdvancedFiltersCollapsed(collapsed: boolean) { function setAdvancedFiltersCollapsed(collapsed: boolean) {
if (ctx.advancedFiltersCollapsed) { if (ctx.advancedFiltersCollapsed) {
@@ -25,6 +29,28 @@ function setAdvancedFiltersCollapsed(collapsed: boolean) {
} }
} }
function isPhotosensitivityExclusionSelected(filters: { type: string; option: string }[]) {
return filters.some(
(filter) => filter.type === 'advanced' && filter.option === PHOTOSENSITIVITY_FILTER_OPTION,
)
}
watch(
() => isPhotosensitivityExclusionSelected(ctx.currentFilters.value),
(isSelected, wasSelected) => {
if (isSelected && !wasSelected && !ctx.dismissedPhotosensitivityFilterWarning?.value) {
nextTick(() => photosensitivityWarningModal.value?.show())
}
},
{ immediate: true },
)
function onPhotosensitivityWarningDismiss(dontShowAgain: boolean) {
if (dontShowAgain && ctx.dismissedPhotosensitivityFilterWarning) {
ctx.dismissedPhotosensitivityFilterWarning.value = true
}
}
function closeFiltersMenu() { function closeFiltersMenu() {
if (ctx.filtersMenuOpen) { if (ctx.filtersMenuOpen) {
ctx.filtersMenuOpen.value = false ctx.filtersMenuOpen.value = false
@@ -88,6 +114,11 @@ function getFilterOpenByDefault(filterId: string): boolean {
<template> <template>
<slot name="prepend" /> <slot name="prepend" />
<PhotosensitivityWarningModal
ref="photosensitivityWarningModal"
@dismiss="onPhotosensitivityWarningDismiss"
/>
<div v-if="ctx.filtersMenuOpen?.value" class="fixed inset-0 z-40 bg-bg" /> <div v-if="ctx.filtersMenuOpen?.value" class="fixed inset-0 z-40 bg-bg" />
<div <div
-10
View File
@@ -3836,15 +3836,6 @@
"search.filter_type.advanced": { "search.filter_type.advanced": {
"defaultMessage": "Erweitert" "defaultMessage": "Erweitert"
}, },
"search.filter_type.advanced.exclude_datapack": {
"defaultMessage": "Datenpakete ausschließen"
},
"search.filter_type.advanced.exclude_mod": {
"defaultMessage": "Mods ausschließen"
},
"search.filter_type.advanced.exclude_plugin": {
"defaultMessage": "Plugins ausschließen"
},
"search.filter_type.environment": { "search.filter_type.environment": {
"defaultMessage": "Umgebung" "defaultMessage": "Umgebung"
}, },
@@ -6240,4 +6231,3 @@
"defaultMessage": "Typ" "defaultMessage": "Typ"
} }
} }
-10
View File
@@ -3836,15 +3836,6 @@
"search.filter_type.advanced": { "search.filter_type.advanced": {
"defaultMessage": "Erweitert" "defaultMessage": "Erweitert"
}, },
"search.filter_type.advanced.exclude_datapack": {
"defaultMessage": "Datenpakete ausschließen"
},
"search.filter_type.advanced.exclude_mod": {
"defaultMessage": "Mods ausschließen"
},
"search.filter_type.advanced.exclude_plugin": {
"defaultMessage": "Plugins ausschließen"
},
"search.filter_type.environment": { "search.filter_type.environment": {
"defaultMessage": "Umgebung" "defaultMessage": "Umgebung"
}, },
@@ -6240,4 +6231,3 @@
"defaultMessage": "Typ" "defaultMessage": "Typ"
} }
} }
+37 -7
View File
@@ -3876,16 +3876,31 @@
"defaultMessage": "Show more" "defaultMessage": "Show more"
}, },
"search.filter_type.advanced": { "search.filter_type.advanced": {
"defaultMessage": "Advanced" "defaultMessage": "Advanced exclusions"
}, },
"search.filter_type.advanced.exclude_datapack": { "search.filter_type.advanced.disclosure.advertisements": {
"defaultMessage": "Exclude data packs" "defaultMessage": "Advertisements"
}, },
"search.filter_type.advanced.exclude_mod": { "search.filter_type.advanced.disclosure.ai_content": {
"defaultMessage": "Exclude mods" "defaultMessage": "AI-generated content"
}, },
"search.filter_type.advanced.exclude_plugin": { "search.filter_type.advanced.disclosure.archived": {
"defaultMessage": "Exclude plugins" "defaultMessage": "Archived"
},
"search.filter_type.advanced.disclosure.derivative_work": {
"defaultMessage": "Derivative content"
},
"search.filter_type.advanced.disclosure.epilepsy_triggers": {
"defaultMessage": "Photosensitivity triggers"
},
"search.filter_type.advanced.disclosure.paid_features": {
"defaultMessage": "Paid features"
},
"search.filter_type.advanced.disclosure.system_interactions": {
"defaultMessage": "External system interactions"
},
"search.filter_type.advanced.disclosure.telemetry": {
"defaultMessage": "Telemetry"
}, },
"search.filter_type.environment": { "search.filter_type.environment": {
"defaultMessage": "Environment" "defaultMessage": "Environment"
@@ -3938,6 +3953,21 @@
"search.filter_type.shader_loader": { "search.filter_type.shader_loader": {
"defaultMessage": "Loader" "defaultMessage": "Loader"
}, },
"search.photosensitivity-warning-modal.body.1": {
"defaultMessage": "We cannot guarantee that all content on Modrinth that may be dangerous for users with photosensitivity is labeled appropriately with a photosensitivity warning."
},
"search.photosensitivity-warning-modal.body.2": {
"defaultMessage": "The label is self-assigned by the users who uploaded the content, and do not indicate that the content has gone through any kind of safety testing. Using any content on Modrinth is at your own risk. Please stay safe."
},
"search.photosensitivity-warning-modal.dont-show-again": {
"defaultMessage": "Don't show this again"
},
"search.photosensitivity-warning-modal.i-understand": {
"defaultMessage": "I understand"
},
"search.photosensitivity-warning-modal.title": {
"defaultMessage": "Results are not guaranteed to be safe"
},
"search.server_content_type.modpack": { "search.server_content_type.modpack": {
"defaultMessage": "Modded" "defaultMessage": "Modded"
}, },
-10
View File
@@ -3851,15 +3851,6 @@
"search.filter_type.advanced": { "search.filter_type.advanced": {
"defaultMessage": "Avanzado" "defaultMessage": "Avanzado"
}, },
"search.filter_type.advanced.exclude_datapack": {
"defaultMessage": "Excluir paquetes de datos"
},
"search.filter_type.advanced.exclude_mod": {
"defaultMessage": "Excluir mods"
},
"search.filter_type.advanced.exclude_plugin": {
"defaultMessage": "Excluir complementos"
},
"search.filter_type.environment": { "search.filter_type.environment": {
"defaultMessage": "Entorno" "defaultMessage": "Entorno"
}, },
@@ -6255,4 +6246,3 @@
"defaultMessage": "Tipo" "defaultMessage": "Tipo"
} }
} }
-9
View File
@@ -3830,15 +3830,6 @@
"search.filter_type.advanced": { "search.filter_type.advanced": {
"defaultMessage": "Avancé" "defaultMessage": "Avancé"
}, },
"search.filter_type.advanced.exclude_datapack": {
"defaultMessage": "Exclure les data packs"
},
"search.filter_type.advanced.exclude_mod": {
"defaultMessage": "Exclure les mods"
},
"search.filter_type.advanced.exclude_plugin": {
"defaultMessage": "Exclure les plugins"
},
"search.filter_type.environment": { "search.filter_type.environment": {
"defaultMessage": "Environnement" "defaultMessage": "Environnement"
}, },
-9
View File
@@ -3809,15 +3809,6 @@
"search.filter_type.advanced": { "search.filter_type.advanced": {
"defaultMessage": "Avanzate" "defaultMessage": "Avanzate"
}, },
"search.filter_type.advanced.exclude_datapack": {
"defaultMessage": "Escludi pacchetti di dati"
},
"search.filter_type.advanced.exclude_mod": {
"defaultMessage": "Escludi mod"
},
"search.filter_type.advanced.exclude_plugin": {
"defaultMessage": "Escludi plugin"
},
"search.filter_type.environment": { "search.filter_type.environment": {
"defaultMessage": "Ambiente" "defaultMessage": "Ambiente"
}, },
-3
View File
@@ -3572,9 +3572,6 @@
"search.filter_type.advanced": { "search.filter_type.advanced": {
"defaultMessage": "もっと見る" "defaultMessage": "もっと見る"
}, },
"search.filter_type.advanced.exclude_datapack": {
"defaultMessage": "データパックを除く"
},
"search.filter_type.environment": { "search.filter_type.environment": {
"defaultMessage": "環境" "defaultMessage": "環境"
}, },
-9
View File
@@ -3836,15 +3836,6 @@
"search.filter_type.advanced": { "search.filter_type.advanced": {
"defaultMessage": "고급" "defaultMessage": "고급"
}, },
"search.filter_type.advanced.exclude_datapack": {
"defaultMessage": "데이터 팩 제외"
},
"search.filter_type.advanced.exclude_mod": {
"defaultMessage": "모드 제외"
},
"search.filter_type.advanced.exclude_plugin": {
"defaultMessage": "플러그인 제외"
},
"search.filter_type.environment": { "search.filter_type.environment": {
"defaultMessage": "실행 환경" "defaultMessage": "실행 환경"
}, },
-9
View File
@@ -3770,15 +3770,6 @@
"search.filter_type.advanced": { "search.filter_type.advanced": {
"defaultMessage": "Gevorderd" "defaultMessage": "Gevorderd"
}, },
"search.filter_type.advanced.exclude_datapack": {
"defaultMessage": "Gegevenspakketten uitsluiten"
},
"search.filter_type.advanced.exclude_mod": {
"defaultMessage": "Mods uitsluiten"
},
"search.filter_type.advanced.exclude_plugin": {
"defaultMessage": "Plug-ins uitsluiten"
},
"search.filter_type.environment": { "search.filter_type.environment": {
"defaultMessage": "Omgeving" "defaultMessage": "Omgeving"
}, },
-9
View File
@@ -3815,15 +3815,6 @@
"search.filter_type.advanced": { "search.filter_type.advanced": {
"defaultMessage": "Zaawansowane" "defaultMessage": "Zaawansowane"
}, },
"search.filter_type.advanced.exclude_datapack": {
"defaultMessage": "Wyklucz paczki danych"
},
"search.filter_type.advanced.exclude_mod": {
"defaultMessage": "Wyklucz mody"
},
"search.filter_type.advanced.exclude_plugin": {
"defaultMessage": "Wyklucz pluginy"
},
"search.filter_type.environment": { "search.filter_type.environment": {
"defaultMessage": "Środowisko" "defaultMessage": "Środowisko"
}, },
-10
View File
@@ -3851,15 +3851,6 @@
"search.filter_type.advanced": { "search.filter_type.advanced": {
"defaultMessage": "Avançado" "defaultMessage": "Avançado"
}, },
"search.filter_type.advanced.exclude_datapack": {
"defaultMessage": "Excluir pacotes de dados"
},
"search.filter_type.advanced.exclude_mod": {
"defaultMessage": "Excluir mods"
},
"search.filter_type.advanced.exclude_plugin": {
"defaultMessage": "Excluir plugins"
},
"search.filter_type.environment": { "search.filter_type.environment": {
"defaultMessage": "Ambiente" "defaultMessage": "Ambiente"
}, },
@@ -6255,4 +6246,3 @@
"defaultMessage": "Tipo" "defaultMessage": "Tipo"
} }
} }
-9
View File
@@ -3764,15 +3764,6 @@
"search.filter_type.advanced": { "search.filter_type.advanced": {
"defaultMessage": "Расширенные" "defaultMessage": "Расширенные"
}, },
"search.filter_type.advanced.exclude_datapack": {
"defaultMessage": "Исключать наборы данных"
},
"search.filter_type.advanced.exclude_mod": {
"defaultMessage": "Исключать моды"
},
"search.filter_type.advanced.exclude_plugin": {
"defaultMessage": "Исключать плагины"
},
"search.filter_type.environment": { "search.filter_type.environment": {
"defaultMessage": "Среда" "defaultMessage": "Среда"
}, },
-10
View File
@@ -3593,15 +3593,6 @@
"search.filter_type.advanced": { "search.filter_type.advanced": {
"defaultMessage": "Napredno" "defaultMessage": "Napredno"
}, },
"search.filter_type.advanced.exclude_datapack": {
"defaultMessage": "Isključi data pakete"
},
"search.filter_type.advanced.exclude_mod": {
"defaultMessage": "Isključi modove"
},
"search.filter_type.advanced.exclude_plugin": {
"defaultMessage": "Isključi plugine"
},
"search.filter_type.environment": { "search.filter_type.environment": {
"defaultMessage": "Okruže" "defaultMessage": "Okruže"
}, },
@@ -5784,4 +5775,3 @@
"defaultMessage": "Tip" "defaultMessage": "Tip"
} }
} }
-7
View File
@@ -3455,12 +3455,6 @@
"search.filter_type.advanced": { "search.filter_type.advanced": {
"defaultMessage": "Avancerat" "defaultMessage": "Avancerat"
}, },
"search.filter_type.advanced.exclude_datapack": {
"defaultMessage": "Exkludera datapaket"
},
"search.filter_type.advanced.exclude_mod": {
"defaultMessage": "Exkludera moddar"
},
"search.filter_type.environment": { "search.filter_type.environment": {
"defaultMessage": "Miljö" "defaultMessage": "Miljö"
}, },
@@ -5442,4 +5436,3 @@
"defaultMessage": "Typ" "defaultMessage": "Typ"
} }
} }
-9
View File
@@ -3653,15 +3653,6 @@
"search.filter_type.advanced": { "search.filter_type.advanced": {
"defaultMessage": "Gelişmiş" "defaultMessage": "Gelişmiş"
}, },
"search.filter_type.advanced.exclude_datapack": {
"defaultMessage": "Veri paketlerini hariç tut"
},
"search.filter_type.advanced.exclude_mod": {
"defaultMessage": "Modları hariç tut"
},
"search.filter_type.advanced.exclude_plugin": {
"defaultMessage": "Eklentileri hariç tut"
},
"search.filter_type.environment": { "search.filter_type.environment": {
"defaultMessage": "Ortam" "defaultMessage": "Ortam"
}, },
-9
View File
@@ -3800,15 +3800,6 @@
"search.filter_type.advanced": { "search.filter_type.advanced": {
"defaultMessage": "Більше" "defaultMessage": "Більше"
}, },
"search.filter_type.advanced.exclude_datapack": {
"defaultMessage": "Виключити пакети даних"
},
"search.filter_type.advanced.exclude_mod": {
"defaultMessage": "Виключити моди"
},
"search.filter_type.advanced.exclude_plugin": {
"defaultMessage": "Виключити плаґіни"
},
"search.filter_type.environment": { "search.filter_type.environment": {
"defaultMessage": "Середовище" "defaultMessage": "Середовище"
}, },
-10
View File
@@ -3851,15 +3851,6 @@
"search.filter_type.advanced": { "search.filter_type.advanced": {
"defaultMessage": "高级" "defaultMessage": "高级"
}, },
"search.filter_type.advanced.exclude_datapack": {
"defaultMessage": "排除数据包"
},
"search.filter_type.advanced.exclude_mod": {
"defaultMessage": "排除模组"
},
"search.filter_type.advanced.exclude_plugin": {
"defaultMessage": "排除插件"
},
"search.filter_type.environment": { "search.filter_type.environment": {
"defaultMessage": "运行环境" "defaultMessage": "运行环境"
}, },
@@ -6255,4 +6246,3 @@
"defaultMessage": "类型" "defaultMessage": "类型"
} }
} }
-10
View File
@@ -3851,15 +3851,6 @@
"search.filter_type.advanced": { "search.filter_type.advanced": {
"defaultMessage": "進階篩選" "defaultMessage": "進階篩選"
}, },
"search.filter_type.advanced.exclude_datapack": {
"defaultMessage": "排除資料包"
},
"search.filter_type.advanced.exclude_mod": {
"defaultMessage": "排除模組"
},
"search.filter_type.advanced.exclude_plugin": {
"defaultMessage": "排除插件"
},
"search.filter_type.environment": { "search.filter_type.environment": {
"defaultMessage": "環境" "defaultMessage": "環境"
}, },
@@ -6255,4 +6246,3 @@
"defaultMessage": "類型" "defaultMessage": "類型"
} }
} }
+132 -24
View File
@@ -1,10 +1,25 @@
import type { Labrinth } from '@modrinth/api-client' import type { Labrinth } from '@modrinth/api-client'
import { ClientIcon, getCategoryIcon, getLoaderIcon, ServerIcon } from '@modrinth/assets' import {
ArchiveIcon,
CircleDollarSignIcon,
CircuitBoardIcon,
ClientIcon,
EyeIcon,
getCategoryIcon,
getLoaderIcon,
GitForkIcon,
MegaphoneIcon,
RadioTowerIcon,
ServerIcon,
SparklesIcon,
} from '@modrinth/assets'
import { sortedCategories } from '@modrinth/utils' import { sortedCategories } from '@modrinth/utils'
import { type Component, computed, readonly, type Ref, ref } from 'vue' import { type Component, computed, readonly, type Ref, ref } from 'vue'
import { type LocationQueryRaw, type LocationQueryValue, useRoute } from 'vue-router' import { type LocationQueryRaw, type LocationQueryValue, useRoute } from 'vue-router'
import { defineMessage, useVIntl } from '../composables/i18n' import { defineMessage, useVIntl } from '../composables/i18n'
import { getProjectTypeIcon } from './auto-icons'
import { getProjectTypeCategoryMessage } from './common-messages'
import { import {
DEFAULT_MOD_LOADERS, DEFAULT_MOD_LOADERS,
DEFAULT_PLUGIN_LOADERS, DEFAULT_PLUGIN_LOADERS,
@@ -120,6 +135,48 @@ const PROJECT_TYPE_EXCLUSION_FILTERS: Partial<Record<ProjectType, ProjectType[]>
datapack: ['mod', 'plugin'], datapack: ['mod', 'plugin'],
} }
const DISCLOSURE_TYPE_FILTERS = [
'ai_content',
'advertisements',
'epilepsy_triggers',
'system_interactions',
'telemetry',
'derivative_work',
'paid_features',
'archived',
] as const
type DisclosureTypeFilter = (typeof DISCLOSURE_TYPE_FILTERS)[number]
const DISCLOSURE_TYPE_SUPPORTED_PROJECT_TYPES: Record<
DisclosureTypeFilter,
readonly ProjectType[]
> = {
ai_content: ALL_PROJECT_TYPES,
advertisements: ALL_PROJECT_TYPES,
epilepsy_triggers: ALL_PROJECT_TYPES,
derivative_work: ALL_PROJECT_TYPES,
paid_features: ALL_PROJECT_TYPES,
archived: ALL_PROJECT_TYPES,
telemetry: ['mod', 'plugin', 'modpack', 'server'],
system_interactions: ['mod', 'plugin', 'modpack'],
}
const DISCLOSURE_TYPE_ICONS: Record<DisclosureTypeFilter, Component> = {
ai_content: SparklesIcon,
advertisements: MegaphoneIcon,
epilepsy_triggers: EyeIcon,
system_interactions: CircuitBoardIcon,
telemetry: RadioTowerIcon,
derivative_work: GitForkIcon,
paid_features: CircleDollarSignIcon,
archived: ArchiveIcon,
}
function isProjectTypeExclusionOption(optionId: string): optionId is ProjectType {
return (ALL_PROJECT_TYPES as string[]).includes(optionId)
}
export function useSearch( export function useSearch(
projectTypes: Ref<ProjectType[]>, projectTypes: Ref<ProjectType[]>,
tags: Ref<Tags>, tags: Ref<Tags>,
@@ -151,31 +208,64 @@ export function useSearch(
return formatCategory(formatMessage, categoryName) return formatCategory(formatMessage, categoryName)
} }
const formatExcludeProjectTypeLabel = (projectType: ProjectType): string => { const formatDisclosureTypeLabel = (disclosureType: DisclosureTypeFilter): string => {
switch (projectType) { switch (disclosureType) {
case 'mod': case 'ai_content':
return formatMessage( return formatMessage(
defineMessage({ defineMessage({
id: 'search.filter_type.advanced.exclude_mod', id: 'search.filter_type.advanced.disclosure.ai_content',
defaultMessage: 'Exclude mods', defaultMessage: 'AI-generated content',
}), }),
) )
case 'plugin': case 'advertisements':
return formatMessage( return formatMessage(
defineMessage({ defineMessage({
id: 'search.filter_type.advanced.exclude_plugin', id: 'search.filter_type.advanced.disclosure.advertisements',
defaultMessage: 'Exclude plugins', defaultMessage: 'Advertisements',
}), }),
) )
case 'datapack': case 'epilepsy_triggers':
return formatMessage( return formatMessage(
defineMessage({ defineMessage({
id: 'search.filter_type.advanced.exclude_datapack', id: 'search.filter_type.advanced.disclosure.epilepsy_triggers',
defaultMessage: 'Exclude data packs', defaultMessage: 'Photosensitivity triggers',
}),
)
case 'system_interactions':
return formatMessage(
defineMessage({
id: 'search.filter_type.advanced.disclosure.system_interactions',
defaultMessage: 'External system interactions',
}),
)
case 'telemetry':
return formatMessage(
defineMessage({
id: 'search.filter_type.advanced.disclosure.telemetry',
defaultMessage: 'Telemetry',
}),
)
case 'derivative_work':
return formatMessage(
defineMessage({
id: 'search.filter_type.advanced.disclosure.derivative_work',
defaultMessage: 'Derivative content',
}),
)
case 'paid_features':
return formatMessage(
defineMessage({
id: 'search.filter_type.advanced.disclosure.paid_features',
defaultMessage: 'Paid features',
}),
)
case 'archived':
return formatMessage(
defineMessage({
id: 'search.filter_type.advanced.disclosure.archived',
defaultMessage: 'Archived',
}), }),
) )
default:
return projectType
} }
} }
@@ -479,21 +569,35 @@ export function useSearch(
formatted_name: formatMessage( formatted_name: formatMessage(
defineMessage({ defineMessage({
id: 'search.filter_type.advanced', id: 'search.filter_type.advanced',
defaultMessage: 'Advanced', defaultMessage: 'Advanced exclusions',
}), }),
), ),
supported_project_types: ['mod', 'plugin', 'datapack'], supported_project_types: ALL_PROJECT_TYPES,
display: 'all', display: 'all',
query_param: 'a', query_param: 'a',
supports: ['exclude'], supports: ['exclude'],
searchable: false, searchable: false,
ordering: -1000, ordering: -1000,
options: excludeableProjectTypes.map((target) => ({ options: [
id: target, ...DISCLOSURE_TYPE_FILTERS.filter((disclosureType) =>
formatted_name: formatExcludeProjectTypeLabel(target), DISCLOSURE_TYPE_SUPPORTED_PROJECT_TYPES[disclosureType].some((projectType) =>
method: 'and', projectTypes.value.includes(projectType),
value: `all_project_types:${mapProjectTypeToSearch(target)}`, ),
})), ).map((disclosureType) => ({
id: disclosureType,
formatted_name: formatDisclosureTypeLabel(disclosureType),
icon: DISCLOSURE_TYPE_ICONS[disclosureType],
method: 'or' as const,
value: `disclosure_types:${disclosureType}`,
})),
...excludeableProjectTypes.map((target) => ({
id: target,
formatted_name: formatMessage(getProjectTypeCategoryMessage(target)),
icon: getProjectTypeIcon(target),
method: 'and' as const,
value: `all_project_types:${mapProjectTypeToSearch(target)}`,
})),
],
}, },
] ]
@@ -503,6 +607,7 @@ export function useSearch(
projectTypes.value.includes(projectType), projectTypes.value.includes(projectType),
), ),
) )
.filter((filterType) => filterType.id !== 'advanced' || filterType.options.length > 0)
.sort((a, b) => (b.ordering ?? 0) - (a.ordering ?? 0)) .sort((a, b) => (b.ordering ?? 0) - (a.ordering ?? 0))
}) })
@@ -526,7 +631,7 @@ export function useSearch(
console.error(`Filter type ${filterValue.type} not found`) console.error(`Filter type ${filterValue.type} not found`)
continue continue
} }
if (type.id === 'advanced') { if (type.id === 'advanced' && isProjectTypeExclusionOption(filterValue.option)) {
continue continue
} }
let option = type?.options.find((option) => option.id === filterValue.option) let option = type?.options.find((option) => option.id === filterValue.option)
@@ -620,7 +725,10 @@ export function useSearch(
} }
const excludedProjectTypes = filterValues const excludedProjectTypes = filterValues
.filter((filterValue) => filterValue.type === 'advanced') .filter(
(filterValue) =>
filterValue.type === 'advanced' && isProjectTypeExclusionOption(filterValue.option),
)
.map((filterValue) => .map((filterValue) =>
formatSearchFilterValue(mapProjectTypeToSearch(filterValue.option as ProjectType)), formatSearchFilterValue(mapProjectTypeToSearch(filterValue.option as ProjectType)),
) )