Skip to content

Commit 1cfbeff

Browse files
authored
fix: various fixes related to content tab on app and panel (#5605)
* fix: content filtering client only * fix: browse content bug Fixes #5570 * fix: Applying Mods & Updates filters at the same time doesn't work Fixes #5602 * fix: Browsing content: going back resets filters and installed state Fixes #5598 * fix: Mod tile background flickers when toggling enabled/disabled state Fixes #5600 * fix: Overhaul of "Content" tab on instances broke a lot Fixes #5567 * fix: Latest App update replacing all mods icons with a datapack/rescourcepack Fixes #5556 * fix: billing page api-client ditch useBaseFetch * fix: remove org icon from project card items * fix: lint
1 parent 7852529 commit 1cfbeff

File tree

8 files changed

+72
-50
lines changed

8 files changed

+72
-50
lines changed

apps/app-frontend/src/pages/Browse.vue

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,10 @@ const instanceHideInstalled = ref(false)
109109
const newlyInstalled = ref<string[]>([])
110110
const isServerInstance = ref(false)
111111
112+
const allInstalledIds = computed(
113+
() => new Set([...newlyInstalled.value, ...(installedProjectIds.value ?? [])]),
114+
)
115+
112116
const PERSISTENT_QUERY_PARAMS = ['i', 'ai']
113117
114118
await initInstanceContext()
@@ -485,17 +489,8 @@ async function refreshSearch() {
485489
link: `/browse/${projectType.value}`,
486490
query: params,
487491
})
488-
const queryString = Object.entries(params)
489-
.flatMap(([key, value]) => {
490-
const values = Array.isArray(value) ? value : [value]
491-
return values
492-
.filter((v): v is string => v != null)
493-
.map((v) => `${encodeURIComponent(key)}=${encodeURIComponent(v)}`)
494-
})
495-
.join('&')
496-
const newUrl = `${route.path}${queryString ? '?' + queryString : ''}`
497-
debugLog('updating URL', newUrl)
498-
window.history.replaceState(window.history.state, '', newUrl)
492+
debugLog('updating URL', params)
493+
router.replace({ path: route.path, query: params })
499494
500495
loading.value = false
501496
debugLog('refreshSearch complete', { version })
@@ -947,7 +942,7 @@ previousFilterState.value = JSON.stringify({
947942
loader.supported_project_types?.includes(projectType),
948943
),
949944
]"
950-
:installed="result.installed || newlyInstalled.includes(result.project_id || '')"
945+
:installed="result.installed || allInstalledIds.has(result.project_id || '')"
951946
@install="
952947
(id) => {
953948
newlyInstalled.push(id)

apps/app-frontend/src/pages/instance/Mods.vue

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -781,11 +781,17 @@ provideContentManager({
781781
linkedModpackProject.value
782782
? {
783783
project: linkedModpackProject.value,
784-
projectLink: `/project/${linkedModpackProject.value.slug ?? linkedModpackProject.value.id}`,
784+
projectLink: {
785+
path: `/project/${linkedModpackProject.value.slug ?? linkedModpackProject.value.id}`,
786+
query: { i: props.instance.path },
787+
},
785788
version: linkedModpackVersion.value ?? undefined,
786789
versionLink:
787790
linkedModpackProject.value && linkedModpackVersion.value
788-
? `/project/${linkedModpackProject.value.slug ?? linkedModpackProject.value.id}/version/${linkedModpackVersion.value.id}`
791+
? {
792+
path: `/project/${linkedModpackProject.value.slug ?? linkedModpackProject.value.id}/version/${linkedModpackVersion.value.id}`,
793+
query: { i: props.instance.path },
794+
}
789795
: undefined,
790796
owner: linkedModpackOwner.value
791797
? {
@@ -808,7 +814,7 @@ provideContentManager({
808814
isPackLocked,
809815
isBusy: isInstanceBusy,
810816
isBulkOperating,
811-
getItemId: (item) => item.file_name,
817+
getItemId: (item) => item.file_path ?? item.file_name,
812818
contentTypeLabel: ref(formatMessage(messages.contentTypeProject)),
813819
toggleEnabled: toggleDisableMod,
814820
bulkEnableItems: (items) =>
@@ -832,22 +838,27 @@ provideContentManager({
832838
dismissContentHint,
833839
shareItems: handleShareItems,
834840
mapToTableItem: (item) => ({
835-
id: item.file_name,
841+
id: item.file_path ?? item.file_name,
836842
project: item.project ?? {
837843
id: item.file_name,
838844
slug: null,
839845
title: item.file_name.replace('.disabled', ''),
840846
icon_url: null,
841847
},
842-
projectLink: item.project?.id ? `/project/${item.project.id}` : undefined,
848+
projectLink: item.project?.id
849+
? { path: `/project/${item.project.id}`, query: { i: props.instance.path } }
850+
: undefined,
843851
version: item.version ?? {
844852
id: item.file_name,
845853
version_number: formatMessage(messages.unknownVersion),
846854
file_name: item.file_name,
847855
},
848856
versionLink:
849857
item.project?.id && item.version?.id
850-
? `/project/${item.project.id}/version/${item.version.id}`
858+
? {
859+
path: `/project/${item.project.id}/version/${item.version.id}`,
860+
query: { i: props.instance.path },
861+
}
851862
: undefined,
852863
owner: item.owner
853864
? {
@@ -857,6 +868,7 @@ provideContentManager({
857868
: undefined,
858869
enabled: item.enabled,
859870
}),
871+
filterPersistKey: props.instance.path,
860872
})
861873
862874
await initProjects()

apps/frontend/src/pages/admin/billing/[id].vue

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -137,12 +137,12 @@
137137
class="mb-4 flex items-center justify-between border-0 border-b border-solid border-divider pb-4"
138138
>
139139
<div class="flex items-center gap-2">
140-
<Avatar :src="user.avatar_url" :alt="user.username" size="32px" circle />
141-
<h1 class="m-0 text-2xl font-extrabold">{{ user.username }}'s subscriptions</h1>
140+
<Avatar :src="user?.avatar_url" :alt="user?.username" size="32px" circle />
141+
<h1 class="m-0 text-2xl font-extrabold">{{ user?.username }}'s subscriptions</h1>
142142
</div>
143143
<div class="flex items-center gap-2">
144144
<ButtonStyled>
145-
<nuxt-link :to="`/user/${user.id}`">
145+
<nuxt-link :to="`/user/${user?.id}`">
146146
<UserIcon aria-hidden="true" />
147147
User profile
148148
<ExternalIcon class="h-4 w-4" />
@@ -346,7 +346,7 @@ import dayjs from 'dayjs'
346346
import ModrinthServersIcon from '~/components/ui/servers/ModrinthServersIcon.vue'
347347
348348
const { addNotification } = injectNotificationManager()
349-
const client = injectModrinthClient()
349+
const { labrinth } = injectModrinthClient()
350350
const formatPrice = useFormatPrice()
351351
const formatDateTime = useFormatDateTime({
352352
timeStyle: 'short',
@@ -373,11 +373,17 @@ const messages = defineMessages({
373373
},
374374
})
375375
376-
const { data: user, error: userError } = useQuery({
376+
const {
377+
data: user,
378+
error: userError,
379+
suspense: userSuspense,
380+
} = useQuery({
377381
queryKey: ['user', route.params.id],
378-
queryFn: () => client.labrinth.users_v2.get(route.params.id),
382+
queryFn: () => labrinth.users_v2.get(route.params.id),
379383
})
380384
385+
onServerPrefetch(userSuspense)
386+
381387
watch(userError, (error) => {
382388
if (error) {
383389
showError({
@@ -390,14 +396,14 @@ watch(userError, (error) => {
390396
391397
const { data: subscriptions } = useQuery({
392398
queryKey: computed(() => ['billing', 'subscriptions', user.value?.id]),
393-
queryFn: () => client.labrinth.billing_internal.getSubscriptions(user.value.id),
399+
queryFn: () => labrinth.billing_internal.getSubscriptions(user.value?.id),
394400
enabled: computed(() => !!user.value?.id),
395401
placeholderData: [],
396402
})
397403
398404
const { data: charges, refetch: refreshCharges } = useQuery({
399405
queryKey: computed(() => ['billing', 'payments', user.value?.id]),
400-
queryFn: () => client.labrinth.billing_internal.getPayments(user.value.id),
406+
queryFn: () => labrinth.billing_internal.getPayments(user.value?.id),
401407
enabled: computed(() => !!user.value?.id),
402408
placeholderData: [],
403409
})
@@ -458,7 +464,7 @@ async function applyCredit() {
458464
crediting.value = true
459465
try {
460466
const daysParsed = Math.max(1, Math.floor(Number(creditDays.value) || 1))
461-
await client.labrinth.billing_internal.credit({
467+
await labrinth.billing_internal.credit({
462468
subscription_ids: [selectedSubscription.value.id],
463469
days: daysParsed,
464470
send_email: creditSendEmail.value,
@@ -492,7 +498,7 @@ async function refundCharge() {
492498
? { type: 'none', unprovision: unprovision.value }
493499
: { type: 'full', unprovision: unprovision.value }
494500
495-
await client.labrinth.billing_internal.refundCharge(selectedCharge.value.id, payload)
501+
await labrinth.billing_internal.refundCharge(selectedCharge.value.id, payload)
496502
await refreshCharges()
497503
refundModal.value.hide()
498504
} catch (err) {
@@ -508,7 +514,7 @@ async function refundCharge() {
508514
async function modifyCharge() {
509515
modifying.value = true
510516
try {
511-
await client.labrinth.billing_internal.editSubscription(selectedSubscription.value.id, {
517+
await labrinth.billing_internal.editSubscription(selectedSubscription.value.id, {
512518
cancelled: cancel.value,
513519
})
514520
addNotification({

packages/ui/src/layouts/shared/content-tab/components/ContentCardItem.vue

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import {
33
DownloadIcon,
44
MoreVerticalIcon,
5-
OrganizationIcon,
65
SpinnerIcon,
76
TrashExclamationIcon,
87
TrashIcon,
@@ -88,7 +87,7 @@ const deleteHovered = ref(false)
8887
:class="{ 'opacity-50': disabled }"
8988
>
9089
<div
91-
class="flex min-w-0 items-center gap-4"
90+
class="flex min-w-0 items-center gap-4 transition-[filter,opacity] duration-200"
9291
:class="[
9392
hideActions ? 'flex-1' : 'flex-1 @[800px]:w-[350px] @[800px]:shrink-0 @[800px]:flex-none',
9493
enabled === false && !disabled ? 'grayscale opacity-50' : '',
@@ -158,10 +157,6 @@ const deleteHovered = ref(false)
158157
class="flex shrink-0 items-center gap-1 !decoration-secondary"
159158
:class="{ 'hover:underline': owner.link }"
160159
>
161-
<OrganizationIcon
162-
v-if="owner.type === 'organization'"
163-
class="size-4 text-secondary"
164-
/>
165160
<Avatar
166161
:src="owner.avatar_url"
167162
:alt="owner.name"
@@ -193,7 +188,7 @@ const deleteHovered = ref(false)
193188
</div>
194189

195190
<div
196-
class="hidden flex-col gap-0.5 @[800px]:flex"
191+
class="hidden flex-col gap-0.5 transition-[filter,opacity] duration-200 @[800px]:flex"
197192
:class="[
198193
hideActions ? 'flex-1' : 'flex-1 min-w-0',
199194
enabled === false && !disabled ? 'grayscale opacity-50' : '',

packages/ui/src/layouts/shared/content-tab/composables/content-filtering.ts

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
1+
import { useSessionStorage } from '@vueuse/core'
12
import type { Ref } from 'vue'
23
import { computed, ref, watch } from 'vue'
34

45
import type { ContentItem } from '../types'
56

6-
const CLIENT_ONLY_ENVIRONMENTS = new Set([
7-
'client_only',
8-
'client_only_server_optional',
9-
'singleplayer_only',
10-
])
7+
const CLIENT_ONLY_ENVIRONMENTS = new Set(['client_only', 'singleplayer_only'])
118

129
export function isClientOnlyEnvironment(env?: string | null): boolean {
1310
return !!env && CLIENT_ONLY_ENVIRONMENTS.has(env)
@@ -24,10 +21,13 @@ export interface ContentFilterConfig {
2421
showClientOnlyFilter?: boolean
2522
isPackLocked?: Ref<boolean>
2623
formatProjectType?: (type: string) => string
24+
persistKey?: string
2725
}
2826

2927
export function useContentFilters(items: Ref<ContentItem[]>, config?: ContentFilterConfig) {
30-
const selectedFilters = ref<string[]>([])
28+
const selectedFilters = config?.persistKey
29+
? useSessionStorage<string[]>(`content-filters:${config.persistKey}`, [])
30+
: ref<string[]>([])
3131

3232
const filterOptions = computed<ContentFilterOption[]>(() => {
3333
const options: ContentFilterOption[] = []
@@ -83,14 +83,23 @@ export function useContentFilters(items: Ref<ContentItem[]>, config?: ContentFil
8383

8484
function applyFilters(source: ContentItem[]): ContentItem[] {
8585
if (selectedFilters.value.length === 0) return source
86+
87+
const attributeFilters = new Set(['updates', 'disabled', 'client-only'])
88+
const typeFilters = selectedFilters.value.filter((f) => !attributeFilters.has(f))
89+
const activeAttributes = selectedFilters.value.filter((f) => attributeFilters.has(f))
90+
8691
return source.filter((item) => {
87-
for (const filter of selectedFilters.value) {
88-
if (filter === 'updates' && item.has_update) return true
89-
if (filter === 'disabled' && !item.enabled) return true
90-
if (filter === 'client-only' && isClientOnlyEnvironment(item.environment)) return true
91-
if (item.project_type === filter) return true
92+
if (typeFilters.length > 0 && !typeFilters.includes(item.project_type)) {
93+
return false
9294
}
93-
return false
95+
96+
for (const filter of activeAttributes) {
97+
if (filter === 'updates' && !item.has_update) return false
98+
if (filter === 'disabled' && item.enabled) return false
99+
if (filter === 'client-only' && !isClientOnlyEnvironment(item.environment)) return false
100+
}
101+
102+
return true
94103
})
95104
}
96105

packages/ui/src/layouts/shared/content-tab/layout.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ const { selectedFilters, filterOptions, toggleFilter, applyFilters } = useConten
229229
showClientOnlyFilter: ctx.showClientOnlyFilter ?? false,
230230
isPackLocked: ctx.isPackLocked,
231231
formatProjectType,
232+
persistKey: ctx.filterPersistKey,
232233
},
233234
)
234235

packages/ui/src/layouts/shared/content-tab/providers/content-manager.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,9 @@ export interface ContentManagerContext {
103103

104104
// Table item mapping (link generation differs per platform)
105105
mapToTableItem: (item: ContentItem) => ContentCardTableItem
106+
107+
// Filter persistence key — when set, selected filters are saved/restored via sessionStorage
108+
filterPersistKey?: string
106109
}
107110

108111
export const [injectContentManager, provideContentManager] = createContext<ContentManagerContext>(

packages/ui/src/layouts/wrapped/hosting/manage/content.vue

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -872,7 +872,7 @@ provideContentManager({
872872
})
873873
return filteredReasons.length > 0 ? formatMessage(filteredReasons[0].reason) : null
874874
}),
875-
getItemId: (item) => item.file_name,
875+
getItemId: (item) => item.file_path ?? item.file_name,
876876
contentTypeLabel: type,
877877
toggleEnabled: handleToggleEnabled,
878878
deleteItem: handleDeleteItem,
@@ -898,7 +898,7 @@ provideContentManager({
898898
mapToTableItem: (item) => {
899899
const projectType = item.project_type ?? type.value
900900
return {
901-
id: item.file_name,
901+
id: item.file_path ?? item.file_name,
902902
project: item.project,
903903
projectLink: item.project?.id ? `/${projectType}/${item.project.id}` : undefined,
904904
version: item.version,
@@ -912,6 +912,7 @@ provideContentManager({
912912
enabled: item.enabled,
913913
}
914914
},
915+
filterPersistKey: `server:${serverId}:${worldId.value}`,
915916
})
916917
</script>
917918

0 commit comments

Comments
 (0)