Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
2e2aea2
avatar ts-ification
Prospector Feb 18, 2026
5ade453
images icon
Prospector Feb 18, 2026
78c94e6
add more project stuff to api client
Prospector Feb 18, 2026
7c5f980
simplify adplaceholder css
Prospector Feb 18, 2026
047f728
remove empty div when modal closed
Prospector Feb 18, 2026
3fe7679
beginning of server project stuff
Prospector Feb 19, 2026
15e83bd
avatar ts-ification
Prospector Feb 18, 2026
412f43d
images icon
Prospector Feb 18, 2026
73e1fc4
add more project stuff to api client
Prospector Feb 18, 2026
1ad4fdb
simplify adplaceholder css
Prospector Feb 18, 2026
96700ed
remove empty div when modal closed
Prospector Feb 18, 2026
c957131
beginning of server project stuff
Prospector Feb 19, 2026
19f419f
ping to component
Prospector Feb 19, 2026
1e190ff
Merge remote-tracking branch 'origin/prospector/server-project-page' …
Prospector Feb 19, 2026
ffc80a4
use dynamic component for different server vs project page header
tdgao Feb 19, 2026
fba3c49
fix imports
tdgao Feb 19, 2026
445c80f
Merge branch 'truman/linked-server-instances' into prospector/server-…
tdgao Feb 19, 2026
dd70e83
remove scroll to top on patch project
tdgao Feb 19, 2026
0cb3776
fix flag svg in project page header
tdgao Feb 19, 2026
32008f9
add redirect for server project type
tdgao Feb 19, 2026
a2dccf6
fix server project hard coded list
tdgao Feb 19, 2026
66b6f6e
hook up server project discovery to backend
tdgao Feb 19, 2026
b934b79
fix server link
tdgao Feb 19, 2026
e41665d
add sidebar server info card
tdgao Feb 20, 2026
c3e205f
remove num followers
tdgao Feb 20, 2026
40c74dc
fix stupid ah type errors
tdgao Feb 20, 2026
3fef34c
remove astrisks
tdgao Feb 20, 2026
1b3c8cd
fix width on floating action bar
tdgao Feb 20, 2026
80e8a61
style fixes in server compatibility card
tdgao Feb 20, 2026
33c9dbe
fix compatibility card empty state
tdgao Feb 20, 2026
0ea4cfe
small button style update
tdgao Feb 20, 2026
2b0ba51
update server info card to query required content project
tdgao Feb 20, 2026
266f45e
update install to play with new content api (install not working)
tdgao Feb 20, 2026
f1a97e3
remove hard coded path
tdgao Feb 20, 2026
b52ef5d
fix install to play by bypassing cache in app backend install pack
tdgao Feb 20, 2026
3c4ef3c
add install server project util and fix install and update to play mo…
tdgao Feb 20, 2026
c68d5ca
implement proper server project instance header
tdgao Feb 20, 2026
c8ace75
small fix
tdgao Feb 20, 2026
5cff50b
add project sidebar server info to app project page
tdgao Feb 20, 2026
7833b16
move side bar server info queries to parent
tdgao Feb 20, 2026
7a5d6e9
add server tab in app
tdgao Feb 20, 2026
e808ec9
add server project card in search in app
tdgao Feb 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/app-frontend/src/components/ui/SearchCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
() => {
emit('open')
$router.push({
path: `/project/ipxQs0xE`,
path: `/project/${project.project_id ?? project.id}`,
query: { i: props.instance ? props.instance.path : undefined },
})
}
Expand Down
85 changes: 51 additions & 34 deletions apps/app-frontend/src/components/ui/modal/InstallToPlayModal.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<template>
<NewModal ref="modal" :header="formatMessage(messages.installToPlay)" :closable="true">
<div v-if="project" class="flex flex-col gap-6 max-w-[500px]">
<div v-if="requiredContentProject" class="flex flex-col gap-6 max-w-[500px]">
<Admonition type="info" :header="formatMessage(messages.contentRequired)">
{{ formatMessage(messages.serverRequiresMods) }}
</Admonition>
Expand All @@ -26,11 +26,15 @@
formatMessage(messages.requiredModpack)
}}</span>
<div class="flex items-center gap-3 rounded-xl bg-surface-2 p-3">
<Avatar :src="project.icon_url" :alt="project.title" size="48px" />
<Avatar
:src="requiredContentProject.icon_url"
:alt="requiredContentProject.title"
size="48px"
/>
<div class="flex flex-col gap-0.5">
<span class="font-semibold text-contrast">{{ project.title }}</span>
<span class="font-semibold text-contrast">{{ requiredContentProject.title }}</span>
<span class="text-sm text-secondary">
{{ loaderDisplay }} {{ project.game_versions?.[0] }}
{{ loaderDisplay }} {{ requiredContentProject.game_versions?.[0] }}
<template v-if="modCount">
· {{ formatMessage(messages.modCount, { count: modCount }) }}
</template>
Expand Down Expand Up @@ -60,7 +64,6 @@
</template>

<script setup lang="ts">
import type { Labrinth } from '@modrinth/api-client'
import { DownloadIcon, XIcon } from '@modrinth/assets'
import {
Admonition,
Expand All @@ -73,37 +76,30 @@ import {
NewModal,
useVIntl,
} from '@modrinth/ui'
import { useQuery } from '@tanstack/vue-query'
import { computed, ref } from 'vue'

import { get_organization, get_team, get_version } from '@/helpers/cache.js'
import { install } from '@/store/install.js'
import { get_organization, get_project, get_team, get_version } from '@/helpers/cache.js'
import { installServerProject } from '@/store/install.js'
import type { Labrinth } from '@modrinth/api-client'

const modal = ref<InstanceType<typeof NewModal>>()
const project = ref<Labrinth.Projects.v2.Project | null>(null)
const modpackVersionId = ref<string | null>(null)
const modpackVersion = ref<any>(null)
const project = ref<any>(null)
const requiredContentProject = ref<any>(null)
const organization = ref<any>(null)
const teamMembers = ref<any[]>([])
const onInstallComplete = ref<() => void>(() => {})
const { formatMessage } = useVIntl()

const { data: organization } = useQuery({
queryKey: computed(() => ['organization', project.value?.organization]),
queryFn: () => get_organization(project.value!.organization!, 'must_revalidate'),
enabled: computed(() => !!project.value?.organization),
})

const { data: teamMembers } = useQuery({
queryKey: computed(() => ['team', project.value?.team]),
queryFn: () => get_team(project.value!.team, 'must_revalidate'),
enabled: computed(() => !!project.value?.team && !project.value?.organization),
})

const sharedBy = computed(() => {
if (organization.value) {
return {
name: organization.value.name,
icon_url: organization.value.icon_url,
}
}
if (teamMembers.value) {
if (teamMembers.value?.length) {
const owner = teamMembers.value.find((member: { is_owner: boolean }) => member.is_owner)
if (owner) {
return {
Expand All @@ -116,37 +112,58 @@ const sharedBy = computed(() => {
})

const loaderDisplay = computed(() => {
const loader = project.value?.loaders?.[0]
const loader = requiredContentProject.value?.loaders?.[0]
if (!loader) return ''
return formatLoader(formatMessage, loader)
})

// Fetch the most recent version to get mod count from dependencies
const latestVersionId = computed(() => project.value?.versions?.[0] ?? null)
const { data: latestVersion } = useQuery({
queryKey: computed(() => ['version', latestVersionId.value]),
queryFn: () => get_version(latestVersionId.value, 'must_revalidate'),
enabled: computed(() => !!latestVersionId.value),
})
const modCount = computed(() => latestVersion.value?.dependencies?.length)
const modCount = computed(() => modpackVersion.value?.dependencies?.length)

async function fetchData(versionId: string) {
// cache is making version null for some reason so bypassing for now
modpackVersion.value = await get_version(versionId, 'bypass')

if (modpackVersion.value?.project_id) {
requiredContentProject.value = await get_project(modpackVersion.value.project_id, 'bypass')
}

if (project.value?.organization) {
organization.value = await get_organization(project.value.organization, 'bypass')
} else if (project.value?.team_id) {
teamMembers.value = await get_team(project.value.team_id, 'bypass')
}
}

async function handleAccept() {
hide()
try {
await install(project.value!.id, null, null, 'ProjectPageInstallToPlayModal')
await installServerProject(project.value.id)
onInstallComplete.value()
} catch (error) {
console.error('Failed to install project from InstallToPlayModal:', error)
console.error('Failed to install server project from InstallToPlayModal:', error)
}
}

function handleDecline() {
hide()
}

function show(projectVal: Labrinth.Projects.v2.Project, callback: () => void = () => {}, e?: MouseEvent) {
async function show(
projectVal: Labrinth.Projects.v3.Project,
modpackVersionIdVal: string | null = null,
callback: () => void = () => {},
e?: MouseEvent,
) {
project.value = projectVal
modpackVersionId.value = modpackVersionIdVal
modpackVersion.value = null
requiredContentProject.value = null
organization.value = null
teamMembers.value = []
onInstallComplete.value = callback

if (modpackVersionIdVal) await fetchData(modpackVersionIdVal)

modal.value?.show(e)
}

Expand Down
28 changes: 23 additions & 5 deletions apps/app-frontend/src/components/ui/modal/UpdateToPlayModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ import { openUrl } from '@tauri-apps/plugin-opener'
import dayjs from 'dayjs'
import { computed, ref, watch } from 'vue'

import { get_project, get_project_many, get_version_many } from '@/helpers/cache.js'
import { get_project, get_project_many, get_version, get_version_many } from '@/helpers/cache.js'
import { update_managed_modrinth_version } from '@/helpers/profile'
import type { GameInstance } from '@/helpers/types'

Expand Down Expand Up @@ -267,7 +267,16 @@ async function checkUpdateAvailable(inst: GameInstance): Promise<DependencyDiff[
if (!inst.linked_data) return null

try {
const project = await get_project(inst.linked_data.project_id, 'must_revalidate')
// For server projects, linked_data.project_id is the server project but
// linked_data.version_id references a content modpack version from a different project.
// Detect this by comparing the version's project_id with linked_data.project_id.
let projectId = inst.linked_data.project_id
const linkedVersion = await get_version(inst.linked_data.version_id, 'must_revalidate')
if (linkedVersion && linkedVersion.project_id !== projectId) {
projectId = linkedVersion.project_id
}

const project = await get_project(projectId, 'must_revalidate')
if (!project || !project.versions || project.versions.length === 0) {
return null
}
Expand Down Expand Up @@ -332,15 +341,22 @@ async function handleUpdate() {

function handleReport() {
if (instance.value?.linked_data?.project_id) {
openUrl(`https://modrinth.com/report?item=project&itemID=${instance.value.linked_data.project_id}`)
openUrl(
`https://modrinth.com/report?item=project&itemID=${instance.value.linked_data.project_id}`,
)
}
}

function handleDecline() {
hide()
}

function show(instanceVal: GameInstance, activeVersionIdVal: string | null = null, callback: () => void = () => {}, e?: MouseEvent) {
function show(
instanceVal: GameInstance,
activeVersionIdVal: string | null = null,
callback: () => void = () => {},
e?: MouseEvent,
) {
instance.value = instanceVal
activeVersionId.value = activeVersionIdVal
onUpdateComplete.value = callback
Expand Down Expand Up @@ -400,7 +416,9 @@ const diffTypeMessages = defineMessages({

const hasUpdate = computed(() => {
if (!instance.value?.linked_data) return false
return latestVersionId.value != null && latestVersionId.value !== instance.value.linked_data.version_id
return (
latestVersionId.value != null && latestVersionId.value !== instance.value.linked_data.version_id
)
})

defineExpose({ show, hide, hasUpdate })
Expand Down
Loading
Loading