Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
120 commits
Select commit Hold shift + click to select a range
6e4abf6
feat: add create server project in dropdown
tdgao Jan 15, 2026
b5c6995
fix: url text input wrapper before
tdgao Jan 16, 2026
8536ca5
feat: create project modal to use typescript
tdgao Jan 16, 2026
2dcc031
add server project related fields
tdgao Jan 16, 2026
6753e95
fix spacing
tdgao Jan 16, 2026
93805bc
feat: update nav links for servers
tdgao Jan 16, 2026
6328d4d
feat: base content card component
IMB11 Jan 16, 2026
548c1f3
fix: tooltips + colors
IMB11 Jan 16, 2026
847d752
feat: fix orgs
IMB11 Jan 16, 2026
bd5f036
Merge branch 'main' into worlds-and-shared-instances-components
IMB11 Jan 16, 2026
6e84c92
feat: implement create server project version and server general sett…
tdgao Jan 16, 2026
765e3fb
update links page
tdgao Jan 16, 2026
b54276f
feat: add ContentModpackCard
IMB11 Jan 19, 2026
74cc230
fix: extract types
IMB11 Jan 19, 2026
4a26d17
feat: selection v-model
IMB11 Jan 19, 2026
2dc3dad
fix: update header
tdgao Jan 19, 2026
9732087
add show icon in selected for combobox with stories
tdgao Jan 19, 2026
ea2431b
Merge branch 'worlds-and-shared-instances-components' into truman/lin…
tdgao Jan 19, 2026
63a90f7
use new showIconInSelected prop
tdgao Jan 19, 2026
1501b11
feat: add project combobox
tdgao Jan 19, 2026
b5a5a6a
clean up project combobox
tdgao Jan 19, 2026
e4536b5
Merge branch 'worlds-and-shared-instances-components' into truman/lin…
tdgao Jan 19, 2026
1591fcc
fix: update file dropzone size
tdgao Jan 19, 2026
51d84ca
feat: start install to play modal
tdgao Jan 19, 2026
95ea6df
fix: events
IMB11 Jan 20, 2026
7135ef4
implement open in app modal
tdgao Jan 20, 2026
2e90087
Merge branch 'worlds-and-shared-instances-components' into truman/lin…
tdgao Jan 20, 2026
35b9621
Merge branch 'worlds-and-shared-instances-components' into truman/lin…
tdgao Jan 20, 2026
db3f745
implement install function and move install to play modal into app-fr…
tdgao Jan 21, 2026
f46a4fb
feat: figma alignments
IMB11 Jan 22, 2026
e0c871c
feat: migrate toggle to tailwind
IMB11 Jan 22, 2026
1850de7
fix: row borders
IMB11 Jan 22, 2026
144bde8
feat: disabled state
IMB11 Jan 22, 2026
ff5ed40
feat: virtual list impl for card table based on window scroll
IMB11 Jan 22, 2026
6f84acb
fix: lint
IMB11 Jan 22, 2026
bd5fbb3
feat: virtualization + smaller contentcard items
IMB11 Jan 22, 2026
d4af422
feat: fix gap + border issues on last elm
IMB11 Jan 22, 2026
067b346
fix: use TeleportOverflowMenu
IMB11 Jan 22, 2026
4877eae
fix: hasUpdate type
IMB11 Jan 22, 2026
af35e92
fix: fallback to svg if src is invalid on avatar component
IMB11 Jan 22, 2026
1712ed2
fix: storybook
IMB11 Jan 24, 2026
16f46a4
feat: start on updater modal
IMB11 Jan 24, 2026
1e70473
feat: finish content updater modal
IMB11 Jan 26, 2026
0c12fbe
feat: i18n pass
IMB11 Jan 26, 2026
61e69ac
Merge branch 'main' into worlds-and-shared-instances-components
IMB11 Jan 26, 2026
e165180
remove install to play modal from ui package
tdgao Jan 26, 2026
138b5e9
Merge branch 'worlds-and-shared-instances-components' into truman/lin…
tdgao Jan 26, 2026
71074d8
pnpm prepr
tdgao Jan 26, 2026
50f7381
pnpm prepr
tdgao Jan 26, 2026
f281a80
add api routes for server projects
tdgao Jan 26, 2026
bfaed9a
update get project route
tdgao Jan 26, 2026
5232929
fixes from merge with main
tdgao Jan 26, 2026
e63787c
feat: reusable table component
tdgao Jan 26, 2026
292b3f8
feat: add column width prop for table and fix stories
tdgao Jan 26, 2026
b1972af
feat: add table overflow menu story example
tdgao Jan 26, 2026
ccee5e0
Merge branch 'worlds-and-shared-instances-components' into truman/lin…
tdgao Jan 26, 2026
8392ae3
feat: add surface-1.5 and use in table
tdgao Jan 26, 2026
51398a1
Merge branch 'worlds-and-shared-instances-components' into truman/lin…
tdgao Jan 26, 2026
9783cbf
chore: export table in index
tdgao Jan 26, 2026
021b859
Merge branch 'worlds-and-shared-instances-components' into truman/lin…
tdgao Jan 26, 2026
11769df
add mocked server proj versions table
tdgao Jan 26, 2026
02a9f4c
fix: allow more loose typing on columns
tdgao Jan 26, 2026
e67f251
update active tag
tdgao Jan 28, 2026
1d7c8a0
update above table buttons
tdgao Jan 28, 2026
a8c4e2d
feat: update table component to derive key from column instead of data
tdgao Jan 28, 2026
a0858ef
Merge branch 'worlds-and-shared-instances-components' into truman/lin…
tdgao Jan 28, 2026
1032469
feat: implement fetch modcount for project inside install to play
tdgao Jan 28, 2026
2092ba8
fix hide modal on install to play
tdgao Jan 29, 2026
6c2ab76
small copy updates to install to play modal
tdgao Jan 29, 2026
93e292d
start update to play moadl
tdgao Jan 29, 2026
6fb15eb
feat: implement update to play modal
tdgao Jan 29, 2026
bba62c6
feat: implement required and supported versions
tdgao Jan 30, 2026
dd7b8fd
add todos
tdgao Jan 30, 2026
3eea154
fix: combobox disabled state
tdgao Jan 30, 2026
68a09b1
implement locking required version and hiding supported versions when…
tdgao Jan 30, 2026
43ab52a
Merge branch 'main' into truman/linked-server-instances
tdgao Jan 30, 2026
7e6470e
pnpm prepr
tdgao Jan 30, 2026
acccddf
delete duplicate keys
tdgao Jan 30, 2026
04b3488
remove server project feature flag and mock get project v3 route
tdgao Feb 3, 2026
d935a8f
hook up create server project endpoint
tdgao Feb 3, 2026
4ce81f0
implement transfer to org on create project
tdgao Feb 3, 2026
65d0803
update table
tdgao Feb 3, 2026
87aecc2
use requested_status and organization_id
tdgao Feb 3, 2026
3877d40
update create version modal to hook up to backend
tdgao Feb 3, 2026
892f8e5
fix date
tdgao Feb 3, 2026
2f131d4
Merge branch 'main' into truman/linked-server-instances
tdgao Feb 3, 2026
672a18d
fix type error
tdgao Feb 3, 2026
7aeabf6
fix uploaded file in custom mrpack content
tdgao Feb 3, 2026
091a74c
add recommended version copy
tdgao Feb 3, 2026
e5d3eff
pnpm prepr
tdgao Feb 3, 2026
d48f624
small updates
tdgao Feb 3, 2026
7f45734
Merge remote-tracking branch 'origin/main' into truman/linked-server-…
Prospector Feb 4, 2026
37b9610
prepr
Prospector Feb 4, 2026
c958091
owner selector copy
Prospector Feb 4, 2026
c893dfc
add todo
tdgao Feb 5, 2026
0c18af1
fix: hover on publish button
tdgao Feb 5, 2026
98eeb9f
add gallery routes to api client
tdgao Feb 5, 2026
a901157
use featured gallary image as banner
tdgao Feb 5, 2026
66953d8
filter recommended version select from support mc versions
tdgao Feb 5, 2026
d915a32
add combobox sublabel
tdgao Feb 5, 2026
cf9ca8b
fix submit buttons
tdgao Feb 5, 2026
2ad5cd5
copy updates and reorganizing settings
tdgao Feb 5, 2026
f7f31c0
feat: update visibility section
tdgao Feb 5, 2026
407e196
hide environment moderation for server project
tdgao Feb 5, 2026
a10c242
update copy
tdgao Feb 5, 2026
a8bb03f
Merge branch 'main' into truman/linked-server-instances
tdgao Feb 5, 2026
062a332
qa fixes for update to play model
tdgao Feb 5, 2026
cb49902
fix import
tdgao Feb 5, 2026
1a6ed67
small qa fix
tdgao Feb 5, 2026
963d35c
fix hide on creaet
tdgao Feb 5, 2026
904803c
fix submit when error
tdgao Feb 5, 2026
f819ce3
fix links page
tdgao Feb 5, 2026
40dc007
add country field
tdgao Feb 6, 2026
3b39d75
stub server ping
tdgao Feb 6, 2026
0778e19
feat: update project and instance headers to match designs
tdgao Feb 6, 2026
8bfb007
update copy
tdgao Feb 6, 2026
fd3b562
hook up update to play
tdgao Feb 6, 2026
17145e3
add server project header
tdgao Feb 7, 2026
f81e3f3
move content page header actions slot to be in line with title/summar…
tdgao Feb 7, 2026
0982d2c
better content header for small screen width
tdgao Feb 7, 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
20 changes: 14 additions & 6 deletions apps/app-frontend/src/components/ui/modal/InstallToPlayModal.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<NewModal ref="modal" :header="formatMessage(messages.installToPlay)" :closable="true">
<div class="flex flex-col gap-6 max-w-[500px]">
<Admonition type="info" :header="formatMessage(messages.sharedServerInstance)">
<Admonition type="info" :header="formatMessage(messages.contentRequired)">
{{ formatMessage(messages.serverRequiresMods) }}
</Admonition>

Expand All @@ -22,10 +22,10 @@
</div>

<div class="flex flex-col gap-2">
<span class="font-semibold text-contrast">
{{ formatMessage(messages.sharedInstance) }}
</span>
<div class="flex items-center gap-3 rounded-xl bg-surface-4 p-3">
<span class="font-semibold text-contrast">{{
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" />
<div class="flex flex-col gap-0.5">
<span class="font-semibold text-contrast">{{ project.title }}</span>
Expand Down Expand Up @@ -162,10 +162,18 @@ const messages = defineMessages({
id: 'app.modal.install-to-play.shared-server-instance',
defaultMessage: 'Shared server instance',
},
contentRequired: {
id: 'app.modal.install-to-play.content-required',
defaultMessage: 'Content required',
},
serverRequiresMods: {
id: 'app.modal.install-to-play.server-requires-mods',
defaultMessage:
'This server requires mods to play. Click install to set up the required files from Modrinth.',
'This server requires mods to play. Click Install to set up the required files from Modrinth, then launch directly into the server.',
},
requiredModpack: {
id: 'app.modal.install-to-play.required-modpack',
defaultMessage: 'Required modpack',
},
sharedByToday: {
id: 'app.modal.install-to-play.shared-by-today',
Expand Down
29 changes: 24 additions & 5 deletions apps/app-frontend/src/components/ui/modal/UpdateToPlayModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<NewModal ref="modal" :header="formatMessage(messages.updateToPlay)" :closable="true" no-padding>
<div class="max-w-[500px]">
<div class="flex flex-col gap-4 p-4">
<Admonition type="warning" :header="formatMessage(messages.updateRequired)">
<Admonition type="info" :header="formatMessage(messages.updateRequired)">
{{ formatMessage(messages.updateRequiredDescription, { name: instance.name }) }}
</Admonition>

Expand All @@ -26,9 +26,12 @@
</div>
</div>
</div>
<div v-if="diffs.length" class="flex flex-col bg-surface-2 p-4 max-h-[272px] overflow-y-auto">
<div
v-if="diffs.length"
class="flex flex-col bg-surface-2 p-4 max-h-[272px] overflow-y-auto border-t border-b border-r-0 border-l-0 border-solid border-surface-5"
>
<div
v-for="diff in diffs"
v-for="(diff, index) in diffs"
:key="diff.project_id"
class="grid grid-cols-[auto_1fr_1fr_1fr] items-center min-h-10 h-10 gap-2"
>
Expand All @@ -37,7 +40,10 @@
<PlusIcon v-if="diff.type === 'added'" />
<MinusIcon v-else-if="diff.type === 'removed'" />
<RefreshCwIcon v-else />
<div class="bg-surface-5 w-[1px] h-2 relative top-1"></div>
<div
:class="index === diffs.length - 1 ? 'bg-transparent' : 'bg-surface-5'"
class="w-[1px] h-2 relative top-1"
></div>
</div>

<div class="flex gap-1 col-span-2">
Expand Down Expand Up @@ -378,5 +384,18 @@ const diffTypeMessages = defineMessages({
},
})

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

function showIfUpdateAvailable(e?: MouseEvent): boolean {
if (hasUpdate.value) {
show(e)
return true
}
return false
}

defineExpose({ show, hide, hasUpdate, showIfUpdateAvailable })
</script>
89 changes: 56 additions & 33 deletions apps/app-frontend/src/pages/instance/Index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,40 @@
<ExportModal ref="exportModal" :instance="instance" />
<InstanceSettingsModal ref="settingsModal" :instance="instance" :offline="offline" />
<UpdateToPlayModal ref="updateToPlayModal" :instance="instance" />
<ButtonStyled v-if="themeStore.featureFlags.server_project_qa">
<button @click="updateToPlayModal.show()">Update to play modal</button>
</ButtonStyled>
<ContentPageHeader>
<template #icon>
<Avatar :src="icon" :alt="instance.name" size="96px" :tint-by="instance.path" />
<Avatar :src="icon" :alt="instance.name" size="64px" :tint-by="instance.path" />
</template>
<template #title>
{{ instance.name }}
</template>
<template #summary> </template>
<template #stats>
<div
class="flex items-center gap-2 font-semibold transform capitalize border-0 border-solid border-divider pr-4 md:border-r"
>
<GameIcon class="h-6 w-6 text-secondary" />
{{ instance.loader }} {{ instance.game_version }}
</div>
<div class="flex items-center gap-2 font-semibold">
<TimerIcon class="h-6 w-6 text-secondary" />
<template v-if="timePlayed > 0">
{{ timePlayedHumanized }}
</template>
<template v-else> Never played </template>
<div class="flex items-center flex-wrap gap-2">
<div class="flex items-center gap-2 capitalize font-medium">
{{ instance.loader }} {{ instance.game_version }}
</div>

<div class="w-1.5 h-1.5 rounded-full bg-surface-5"></div>

<div class="flex items-center gap-2 font-medium">
<template v-if="timePlayed > 0">
{{ timePlayedHumanized }}
</template>
<template v-else> Never played </template>
</div>

<div v-if="isServerInstance" class="w-1.5 h-1.5 rounded-full bg-surface-5"></div>

<div v-if="isServerInstance" class="flex gap-1.5 items-center font-medium">
Linked to
<Avatar
:src="project.icon_url"
:alt="project.title"
:tint-by="instance.path"
size="24px"
/>
{{ project.title }}
</div>
</div>
</template>
<template #actions>
Expand Down Expand Up @@ -169,7 +179,6 @@ import {
ExternalIcon,
EyeIcon,
FolderOpenIcon,
GameIcon,
GlobeIcon,
HashIcon,
MoreVerticalIcon,
Expand All @@ -179,7 +188,6 @@ import {
ServerIcon,
SettingsIcon,
StopCircleIcon,
TimerIcon,
UpdatedIcon,
UserPlusIcon,
XIcon,
Expand Down Expand Up @@ -211,7 +219,7 @@ import { get_by_profile_path } from '@/helpers/process'
import { finish_install, get, get_full_path, kill, run } from '@/helpers/profile'
import { showProfileInFolder } from '@/helpers/utils.js'
import { handleSevereError } from '@/store/error.js'
import { useBreadcrumbs, useLoading } from '@/store/state'
import { useBreadcrumbs, useLoading, useTheming } from '@/store/state'

dayjs.extend(duration)
dayjs.extend(relativeTime)
Expand All @@ -221,6 +229,7 @@ const route = useRoute()

const router = useRouter()
const breadcrumbs = useBreadcrumbs()
const themeStore = useTheming()

const offline = ref(!navigator.onLine)
window.addEventListener('offline', () => {
Expand All @@ -234,25 +243,35 @@ const instance = ref()
const modrinthVersions = ref([])
const playing = ref(false)
const loading = ref(false)
const updateToPlayModal = ref()
const updateToPlayModal = ref() // TODO: show this modal when an update is available when click play button

const isServerInstance = ref(false)
const project = ref()

async function fetchInstance() {
instance.value = await get(route.params.id).catch(handleError)

if (!offline.value && instance.value.linked_data && instance.value.linked_data.project_id) {
get_project(instance.value.linked_data.project_id, 'must_revalidate')
.catch(handleError)
.then((project) => {
if (project && project.versions) {
get_version_many(project.versions, 'must_revalidate')
.catch(handleError)
.then((versions) => {
modrinthVersions.value = versions.sort(
(a, b) => dayjs(b.date_published) - dayjs(a.date_published),
)
})
try {
project.value = await get_project(instance.value.linked_data.project_id, 'must_revalidate')

if (project.value && project.value.versions) {
const versions = await get_version_many(project.value.versions, 'must_revalidate')
modrinthVersions.value = versions.sort(
(a, b) => dayjs(b.date_published) - dayjs(a.date_published),
)
if (Object.keys(project.value).includes('minecraft_server')) {
isServerInstance.value = true
}
})
console.log(project)
// todo, remove on release
if (themeStore.featureFlags.server_project_qa) {
isServerInstance.value = true
}
}
} catch (error) {
handleError(error)
}
}

await updatePlayState()
Expand Down Expand Up @@ -309,6 +328,10 @@ const loadingBar = useLoading()
const options = ref(null)

const startInstance = async (context) => {
if (updateToPlayModal.value?.showIfUpdateAvailable()) {
return
}

loading.value = true
try {
await run(route.params.id)
Expand Down
2 changes: 1 addition & 1 deletion apps/app-frontend/src/pages/instance/Mods.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<input
v-model="searchFilter"
type="text"
:placeholder="`Search ${filteredProjects.length} project${filteredProjects.length === 1 ? '' : 's'}...`"
:placeholder="`Search ${filteredProjects.length} project${filteredProjects.length === 1 ? '' : 's'}... test`"
class="text-input search-input"
autocomplete="off"
/>
Expand Down
58 changes: 52 additions & 6 deletions apps/app-frontend/src/pages/project/Index.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<template>
<div>
<InstallToPlayModal ref="installToPlayModal" :project="data" />
<Teleport to="#sidebar-teleport-target">
<ProjectSidebarCompatibility
:project="data"
Expand All @@ -23,10 +22,8 @@
class="project-sidebar-section"
/>
</Teleport>
<InstallToPlayModal ref="installToPlayModal" :project="data" />
<div class="flex flex-col gap-4 p-6">
<ButtonStyled v-if="themeStore.featureFlags.server_project_qa">
<button @click="installToPlayModal.show()">Install to play modal</button>
</ButtonStyled>
<InstanceIndicator v-if="instance" :instance="instance" />
<template v-if="data">
<Teleport
Expand All @@ -35,7 +32,47 @@
>
<ProjectBackgroundGradient :project="data" />
</Teleport>
<ProjectHeader :project="data" @contextmenu.prevent.stop="handleRightClick">
<ServerProjectHeader
v-if="isServerProject"
:project="data"
@contextmenu.prevent.stop="handleRightClick"
>
<template #actions>
<ButtonStyled size="large" color="brand">
<button @click="installToPlayModal.show()">
<PlayIcon />
Play
</button>
</ButtonStyled>
<ButtonStyled size="large" circular type="transparent">
<OverflowMenu
:tooltip="`More options`"
:options="[
{
id: 'open-in-browser',
link: `https://modrinth.com/${data.project_type}/${data.slug}`,
external: true,
},
{
divider: true,
},
{
id: 'report',
color: 'red',
hoverFilled: true,
link: `https://modrinth.com/report?item=project&itemID=${data.id}`,
},
]"
aria-label="More options"
>
<MoreVerticalIcon aria-hidden="true" />
<template #open-in-browser> <ExternalIcon /> Open in browser </template>
<template #report> <ReportIcon /> Report </template>
</OverflowMenu>
</ButtonStyled>
</template>
</ServerProjectHeader>
<ProjectHeader v-else :project="data" @contextmenu.prevent.stop="handleRightClick">
<template #actions>
<ButtonStyled size="large" color="brand">
<button
Expand Down Expand Up @@ -142,6 +179,7 @@ import {
GlobeIcon,
HeartIcon,
MoreVerticalIcon,
PlayIcon,
ReportIcon,
} from '@modrinth/assets'
import {
Expand All @@ -154,6 +192,7 @@ import {
ProjectSidebarCreators,
ProjectSidebarDetails,
ProjectSidebarLinks,
ServerProjectHeader,
} from '@modrinth/ui'
import { openUrl } from '@tauri-apps/plugin-opener'
import dayjs from 'dayjs'
Expand Down Expand Up @@ -190,8 +229,9 @@ const instanceProjects = ref(null)

const installed = ref(false)
const installedVersion = ref(null)
const isServerProject = ref(false)

const installToPlayModal = ref()
const installToPlayModal = ref(null) // TODO, only show install to play modal for server project types that have .mrpack for content

const instanceFilters = computed(() => {
if (!instance.value) {
Expand Down Expand Up @@ -245,6 +285,12 @@ async function fetchProjectData() {
installedVersion.value = installedFile.metadata.version_id
}
}
isServerProject.value = Object.keys(project).includes('minecraft_server')
// todo, remove on release
if (themeStore.featureFlags.server_project_qa) {
isServerProject.value = true
}

breadcrumbs.setName('Project', data.value.title)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<template>
<div class="space-y-2.5">
<div class="flex items-center justify-between">
<div v-if="!noHeader" class="flex items-center justify-between">
<span class="font-semibold text-contrast">
Minecraft versions <span class="text-red">*</span>
</span>
Expand Down Expand Up @@ -42,6 +42,7 @@
versionType === 'all' && !group.isReleaseGroup ? 'w-max' : 'w-16',
modelValue.includes(version) ? '!text-contrast' : '',
]"
:disabled="disabled"
@click="() => handleToggleVersion(version)"
@blur="
() => {
Expand Down Expand Up @@ -73,6 +74,8 @@ type GameVersion = Labrinth.Tags.v2.GameVersion
const props = defineProps<{
modelValue: string[]
gameVersions: Labrinth.Tags.v2.GameVersion[]
noHeader?: boolean
disabled?: boolean
}>()

const emit = defineEmits<{
Expand Down
Loading