-
- {{ formatMessage(messages.jumpIn) }}
-
-
-
-
+
+
+
+ populateJumpBackIn()"
- @play="
- () => {
- currentInstance = item.instance.id
- currentWorld = getWorldIdentifier(item.world)
- joinWorld(item.world, item.instance)
- }
- "
- @play-instance="
- () => {
- currentInstance = item.instance.id
- playInstance(item.instance)
- }
- "
- @stop="() => stopInstance(item.instance.id)"
- />
- markInstancePlayed(item)"
- />
-
+ ? serverData[item.world.address].refreshing &&
+ !serverData[item.world.address].status
+ : undefined
+ "
+ :supports-server-quick-play="
+ item.world.type === 'server' &&
+ hasServerQuickPlaySupport(gameVersions, item.instance.game_version || '')
+ "
+ :supports-world-quick-play="
+ item.world.type === 'singleplayer' &&
+ hasWorldQuickPlaySupport(gameVersions, item.instance.game_version || '')
+ "
+ :quarantined="item.instance.quarantined"
+ :server-status="
+ item.world.type === 'server' ? serverData[item.world.address].status : undefined
+ "
+ :rendered-motd="
+ item.world.type === 'server' ? serverData[item.world.address].renderedMotd : undefined
+ "
+ :current-protocol="protocolVersions[item.instance.id]"
+ :game-mode="
+ item.world.type === 'singleplayer' ? GAME_MODES[item.world.game_mode] : undefined
+ "
+ :instance-id="item.instance.id"
+ :instance-name="item.instance.name"
+ :instance-icon="item.instance.icon_path"
+ @refresh="
+ () =>
+ item.world.type === 'server'
+ ? refreshServer(item.world.address, item.instance.id)
+ : {}
+ "
+ @update="() => populateJumpBackIn()"
+ @play="
+ () => {
+ currentInstance = item.instance.id
+ currentWorld = getWorldIdentifier(item.world)
+ joinWorld(item.world, item.instance)
+ }
+ "
+ @play-instance="
+ () => {
+ currentInstance = item.instance.id
+ playInstance(item.instance)
+ }
+ "
+ @stop="() => stopInstance(item.instance.id)"
+ />
+ markInstancePlayed(item)"
+ />
+
+
+
-
+
+
+
diff --git a/apps/app-frontend/src/components/ui/world/WorldItem.vue b/apps/app-frontend/src/components/ui/world/WorldItem.vue
index 919c0b82b0..8147d0bce0 100644
--- a/apps/app-frontend/src/components/ui/world/WorldItem.vue
+++ b/apps/app-frontend/src/components/ui/world/WorldItem.vue
@@ -9,6 +9,7 @@ import {
MoreVerticalIcon,
NoSignalIcon,
PlayIcon,
+ SignalIcon,
SkullIcon,
SpinnerIcon,
StopCircleIcon,
@@ -25,15 +26,17 @@ import {
commonMessages,
defineMessages,
injectNotificationManager,
- ServerOnlinePlayers,
SmartClickable,
TagItem,
TeleportOverflowMenu,
useFormatDateTime,
+ useFormatNumber,
useRelativeTime,
useVIntl,
} from '@modrinth/ui'
+import { getPingLevel } from '@modrinth/utils'
import dayjs from 'dayjs'
+import { Tooltip } from 'floating-vue'
import type { Component } from 'vue'
import { computed } from 'vue'
import { useRouter } from 'vue-router'
@@ -53,6 +56,7 @@ import { LockIcon } from '../../../../../../packages/assets/generated-icons'
const { formatMessage } = useVIntl()
const formatRelativeTime = useRelativeTime()
+const formatNumber = useFormatNumber()
const formatDateTime = useFormatDateTime({
timeStyle: 'short',
dateStyle: 'long',
@@ -121,6 +125,9 @@ const props = withDefaults(
)
const playingOtherWorld = computed(() => props.playingInstance && !props.playingWorld)
+const hasPlayersTooltip = computed(
+ () => !!props.serverStatus?.players?.sample && props.serverStatus.players.sample.length > 0,
+)
const serverIncompatible = computed(
() =>
!!props.serverStatus &&
@@ -238,6 +245,10 @@ const messages = defineMessages({
id: 'app.world.world-item.incompatible-version',
defaultMessage: 'Incompatible version {version}',
},
+ playersOnline: {
+ id: 'app.world.world-item.players-online',
+ defaultMessage: '{count} online',
+ },
offline: {
id: 'app.world.world-item.offline',
defaultMessage: 'Offline',
@@ -315,13 +326,34 @@ const messages = defineMessages({
}}
-
-
+
-
+
+
+ {{
+ formatMessage(messages.playersOnline, {
+ count: formatNumber(serverStatus.players?.online ?? 0),
+ })
+ }}
+
+
+
+
+ {{ player.name }}
+
+
+
+
+
diff --git a/apps/app-frontend/src/composables/use-app-settings.ts b/apps/app-frontend/src/composables/use-app-settings.ts
index 4ef0a51609..d5e9ca22b0 100644
--- a/apps/app-frontend/src/composables/use-app-settings.ts
+++ b/apps/app-frontend/src/composables/use-app-settings.ts
@@ -13,6 +13,7 @@ export const DEFAULT_FEATURE_FLAGS = {
pride_fundraiser: true,
i18n_debug: false,
show_instance_play_time: true,
+ compact_instance_cards: false,
advanced_filters_collapsed: true,
always_show_copy_details: false,
hide_installed_modpacks: false,
diff --git a/apps/app-frontend/src/locales/en-US/index.json b/apps/app-frontend/src/locales/en-US/index.json
index 2753c00a8d..b68c532493 100644
--- a/apps/app-frontend/src/locales/en-US/index.json
+++ b/apps/app-frontend/src/locales/en-US/index.json
@@ -149,6 +149,12 @@
"app.ads-consent.title": {
"message": "Your privacy and how ads support Modrinth"
},
+ "app.appearance-settings.compact-mode.description": {
+ "message": "Display library instances in a compact row layout."
+ },
+ "app.appearance-settings.compact-mode.title": {
+ "message": "Compact mode"
+ },
"app.appearance-settings.default-landing-page.home": {
"message": "Home"
},
@@ -323,6 +329,9 @@
"app.home.jump-back-in.new-instance": {
"message": "New instance"
},
+ "app.home.jump-back-in.resize": {
+ "message": "Drag to resize"
+ },
"app.home.jump-back-in.title": {
"message": "Jump in"
},
@@ -1739,6 +1748,9 @@
"app.world.world-item.offline": {
"message": "Offline"
},
+ "app.world.world-item.players-online": {
+ "message": "{count} online"
+ },
"content.shared-instance.change-version-body": {
"message": "Changing the version only changes your local copy. Future shared instance updates may restore or change it again."
},
@@ -2066,6 +2078,12 @@
"instance.icon-editor.symbol.engine": {
"message": "Engine"
},
+ "instance.icon-editor.symbol.fabric": {
+ "message": "Fabric"
+ },
+ "instance.icon-editor.symbol.forge": {
+ "message": "Forge"
+ },
"instance.icon-editor.symbol.furnace": {
"message": "Furnace"
},
@@ -2087,6 +2105,9 @@
"instance.icon-editor.symbol.mr-pack": {
"message": "Mr Pack"
},
+ "instance.icon-editor.symbol.neoforge": {
+ "message": "NeoForge"
+ },
"instance.icon-editor.symbol.orb": {
"message": "Orb"
},
@@ -2102,6 +2123,9 @@
"instance.icon-editor.symbol.poke-ball": {
"message": "Poke Ball"
},
+ "instance.icon-editor.symbol.quilt": {
+ "message": "Quilt"
+ },
"instance.icon-editor.symbol.redstone-block": {
"message": "Redstone Block"
},
diff --git a/apps/app-frontend/src/pages/Browse.vue b/apps/app-frontend/src/pages/Browse.vue
index fda335e37c..dc586a74e9 100644
--- a/apps/app-frontend/src/pages/Browse.vue
+++ b/apps/app-frontend/src/pages/Browse.vue
@@ -38,7 +38,7 @@ import { computed, onBeforeUnmount, ref, shallowRef, watch } from 'vue'
import type { LocationQuery } from 'vue-router'
import { useRoute, useRouter } from 'vue-router'
-import ContextMenu from '@/components/ui/ContextMenu.vue'
+import ContextMenu from '@/components/ui/context-menu/index.vue'
import { useAppServerBrowse } from '@/composables/browse/use-app-server-browse'
import { useAppEvent } from '@/composables/use-app-event'
import { useAppSettings } from '@/composables/use-app-settings.ts'
diff --git a/apps/app-frontend/src/pages/Index.vue b/apps/app-frontend/src/pages/Index.vue
index 04a9b84833..c6157960d5 100644
--- a/apps/app-frontend/src/pages/Index.vue
+++ b/apps/app-frontend/src/pages/Index.vue
@@ -1,10 +1,10 @@