mirror of
https://github.com/modrinth/code.git
synced 2026-08-30 11:36:05 +00:00
feat: add compact mode for library
This commit is contained in:
@@ -30,6 +30,7 @@ import type {
|
||||
} from '@/components/ui/library/use-library'
|
||||
import { useLibrary } from '@/components/ui/library/use-library'
|
||||
import { FAVORITES_GROUP_ID, MAX_INSTANCE_GROUP_NAME_LENGTH } from '@/helpers/instance-groups'
|
||||
import { useTheming } from '@/store/state'
|
||||
|
||||
const INSTANCE_GRID_OBSERVER_ACTIVATION_DELAY = 500
|
||||
|
||||
@@ -48,6 +49,8 @@ const props = withDefaults(
|
||||
)
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const themeStore = useTheming()
|
||||
const compactMode = computed(() => themeStore.getFeatureFlag('compact_instance_cards'))
|
||||
const { addNotification } = injectNotificationManager()
|
||||
const {
|
||||
isSectionCollapsed,
|
||||
@@ -475,7 +478,12 @@ onMounted(startInstanceGridResizeObserver)
|
||||
<div ref="instanceGridContent">
|
||||
<TransitionGroup
|
||||
tag="section"
|
||||
class="grid min-h-[45px] w-full grid-cols-[repeat(auto-fill,minmax(min(10rem,100%),1fr))] max-xl:grid-cols-[repeat(auto-fill,minmax(min(8rem,100%),1fr))] gap-3 overflow-y-auto scroll-smooth"
|
||||
class="grid min-h-[45px] w-full gap-3 overflow-y-auto scroll-smooth"
|
||||
:class="
|
||||
compactMode
|
||||
? 'grid-cols-[repeat(auto-fill,minmax(min(15rem,100%),1fr))]'
|
||||
: 'grid-cols-[repeat(auto-fill,minmax(min(10rem,100%),1fr))] max-xl:grid-cols-[repeat(auto-fill,minmax(min(8rem,100%),1fr))]'
|
||||
"
|
||||
move-class="transition-transform duration-200 ease-out motion-reduce:transition-none"
|
||||
enter-active-class="transition-[opacity,transform] duration-[150ms] ease-out motion-reduce:transition-none"
|
||||
enter-from-class="opacity-0"
|
||||
|
||||
@@ -4,6 +4,7 @@ import { computed, ref } from 'vue'
|
||||
|
||||
import { getInstanceIconUrl } from '@/helpers/instance'
|
||||
import type { GameInstance } from '@/helpers/types'
|
||||
import { useTheming } from '@/store/state'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
@@ -16,6 +17,8 @@ const props = withDefaults(
|
||||
)
|
||||
|
||||
const iconSrc = computed(() => getInstanceIconUrl(props.instance.icon_path))
|
||||
const themeStore = useTheming()
|
||||
const compactMode = computed(() => themeStore.getFeatureFlag('compact_instance_cards'))
|
||||
|
||||
const nameRef = ref<HTMLElement | null>(null)
|
||||
const versionRef = ref<HTMLElement | null>(null)
|
||||
@@ -23,30 +26,40 @@ const versionRef = ref<HTMLElement | null>(null)
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="relative flex w-full min-w-0 select-none flex-col items-start justify-end gap-3 overflow-clip rounded-[20px] border border-solid bg-surface-3 p-3 text-left transition-all"
|
||||
class="relative flex w-full min-w-0 select-none overflow-clip border border-solid bg-surface-3 text-left transition-all"
|
||||
:class="{
|
||||
'flex-row items-center justify-start gap-2.5 rounded-xl p-2.5': compactMode,
|
||||
'flex-col items-start justify-end gap-3 rounded-[20px] p-3': !compactMode,
|
||||
'[border-color:color-mix(in_srgb,var(--color-text-primary)_40%,transparent)] brightness-110':
|
||||
selected,
|
||||
'border-surface-4': !selected,
|
||||
}"
|
||||
>
|
||||
<div
|
||||
class="relative flex aspect-square min-w-full shrink-0 items-center overflow-clip rounded-2xl"
|
||||
class="relative flex shrink-0 items-center overflow-clip"
|
||||
:class="compactMode ? 'size-10 rounded-lg' : 'aspect-square min-w-full rounded-2xl'"
|
||||
>
|
||||
<Avatar
|
||||
class="pointer-events-none !rounded-2xl outline-none"
|
||||
class="pointer-events-none outline-none"
|
||||
:class="compactMode ? '!rounded-lg' : '!rounded-2xl'"
|
||||
size="100%"
|
||||
:src="iconSrc"
|
||||
:tint-by="instance.id"
|
||||
alt=""
|
||||
no-shadow
|
||||
/>
|
||||
<slot name="loading" />
|
||||
<div class="absolute bottom-1.5 right-1.5 z-[1] flex size-12 items-center justify-center">
|
||||
<slot name="leading" />
|
||||
<slot name="loading" :compact="compactMode" />
|
||||
<div
|
||||
class="absolute z-[1] flex items-center justify-center"
|
||||
:class="compactMode ? 'inset-0' : 'bottom-1.5 right-1.5 size-12'"
|
||||
>
|
||||
<slot name="leading" :compact="compactMode" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex min-w-0 w-full flex-col items-start justify-center gap-1 px-0.5">
|
||||
<div
|
||||
class="flex min-w-0 w-full flex-col items-start justify-center gap-1 px-0.5"
|
||||
:class="{ 'pr-10': compactMode }"
|
||||
>
|
||||
<p
|
||||
ref="nameRef"
|
||||
v-tooltip="truncatedTooltip(nameRef, instance.name)"
|
||||
@@ -62,6 +75,6 @@ const versionRef = ref<HTMLElement | null>(null)
|
||||
{{ instance.loader }} {{ instance.game_version }}
|
||||
</p>
|
||||
</div>
|
||||
<slot name="overlay" />
|
||||
<slot name="overlay" :compact="compactMode" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -285,7 +285,7 @@ onMounted(() => {
|
||||
@mouseenter="checkProcess"
|
||||
@pointerdown="handlePointerDown"
|
||||
>
|
||||
<template #loading>
|
||||
<template #loading="{ compact }">
|
||||
<div
|
||||
v-if="loadingIndicatorVisible"
|
||||
class="pointer-events-none absolute inset-0 z-[1] flex items-center justify-center"
|
||||
@@ -293,13 +293,17 @@ onMounted(() => {
|
||||
<div class="absolute inset-0 bg-surface-1 opacity-30" />
|
||||
<SpinnerIcon
|
||||
v-tooltip="formatMessage(modLoading ? messages.loading : messages.installing)"
|
||||
class="relative size-[30%] animate-spin text-contrast"
|
||||
class="relative animate-spin text-contrast"
|
||||
:class="compact ? 'size-5' : 'size-[30%]'"
|
||||
tabindex="-1"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #leading>
|
||||
<div class="relative flex size-12 shrink-0 items-center justify-center">
|
||||
<template #leading="{ compact }">
|
||||
<div
|
||||
class="relative flex shrink-0 items-center justify-center"
|
||||
:class="compact ? 'size-10' : 'size-12'"
|
||||
>
|
||||
<div class="absolute inset-0 flex items-center justify-center">
|
||||
<IconButton
|
||||
v-if="playing"
|
||||
@@ -307,7 +311,7 @@ onMounted(() => {
|
||||
:label="formatMessage(messages.stop)"
|
||||
type="colored"
|
||||
color="red"
|
||||
size="lg"
|
||||
:size="compact ? 'md' : 'lg'"
|
||||
@click="(e) => stop(e, 'InstanceCard')"
|
||||
@mouseenter="checkProcess"
|
||||
>
|
||||
@@ -325,7 +329,7 @@ onMounted(() => {
|
||||
:label="formatMessage(messages.repair)"
|
||||
type="colored"
|
||||
color="brand"
|
||||
size="lg"
|
||||
:size="compact ? 'md' : 'lg'"
|
||||
class="origin-bottom scale-75 opacity-0 transition-opacity group-hover/card:scale-100 group-hover/card:opacity-100"
|
||||
@click="(e) => repair(e)"
|
||||
>
|
||||
@@ -342,7 +346,7 @@ onMounted(() => {
|
||||
:label="formatMessage(messages.play)"
|
||||
type="colored"
|
||||
color="brand"
|
||||
size="lg"
|
||||
:size="compact ? 'md' : 'lg'"
|
||||
class="origin-bottom scale-75 opacity-0 transition-opacity group-hover/card:scale-100 group-hover/card:opacity-100"
|
||||
@click="(e) => play(e, 'InstanceCard')"
|
||||
@mouseenter="checkProcess"
|
||||
@@ -352,10 +356,11 @@ onMounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #overlay>
|
||||
<template #overlay="{ compact }">
|
||||
<button
|
||||
type="button"
|
||||
class="selection-button group/selection absolute right-2 top-1.5 z-[2] flex size-[50px] cursor-pointer items-start pt-4 justify-center border-0 bg-transparent p-0"
|
||||
class="selection-button group/selection absolute z-[2] flex size-[50px] cursor-pointer items-start pt-4 justify-center border-0 bg-transparent p-0"
|
||||
:class="compact ? '-right-1 -top-1' : 'right-2 top-1.5'"
|
||||
:aria-label="formatMessage(selected ? messages.deselect : messages.select)"
|
||||
:aria-pressed="selected"
|
||||
@click.stop="toggleSelection"
|
||||
|
||||
@@ -10,6 +10,7 @@ const themeStore = useTheming()
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const worldsInHomeFlag: FeatureFlag = 'worlds_in_home'
|
||||
const compactInstanceCardsFlag: FeatureFlag = 'compact_instance_cards'
|
||||
const skipNonEssentialWarningsFlag: FeatureFlag = 'skip_non_essential_warnings'
|
||||
const skipUnknownPackWarningFlag: FeatureFlag = 'skip_unknown_pack_warning'
|
||||
const showPlayTimeFlag: FeatureFlag = 'show_instance_play_time'
|
||||
@@ -60,6 +61,14 @@ const messages = defineMessages({
|
||||
defaultMessage:
|
||||
'Show recently played worlds or instances in the "Jump in" section on the Home page.',
|
||||
},
|
||||
compactModeTitle: {
|
||||
id: 'app.appearance-settings.compact-mode.title',
|
||||
defaultMessage: 'Compact mode',
|
||||
},
|
||||
compactModeDescription: {
|
||||
id: 'app.appearance-settings.compact-mode.description',
|
||||
defaultMessage: 'Display library instances in a compact row layout.',
|
||||
},
|
||||
showPlayTimeTitle: {
|
||||
id: 'app.appearance-settings.show-play-time.title',
|
||||
defaultMessage: 'Show play time',
|
||||
@@ -172,6 +181,26 @@ watch(
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<div>
|
||||
<h3 class="m-0 text-lg font-semibold text-contrast">
|
||||
{{ formatMessage(messages.compactModeTitle) }}
|
||||
</h3>
|
||||
<p class="m-0 mt-1">{{ formatMessage(messages.compactModeDescription) }}</p>
|
||||
</div>
|
||||
<Toggle
|
||||
id="compact-mode"
|
||||
:model-value="themeStore.getFeatureFlag(compactInstanceCardsFlag)"
|
||||
@update:model-value="
|
||||
() => {
|
||||
const newValue = !themeStore.getFeatureFlag(compactInstanceCardsFlag)
|
||||
themeStore.featureFlags[compactInstanceCardsFlag] = newValue
|
||||
settings.feature_flags[compactInstanceCardsFlag] = newValue
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<div>
|
||||
<h3 class="m-0 text-lg font-semibold text-contrast">
|
||||
|
||||
@@ -15,6 +15,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,
|
||||
|
||||
@@ -60,6 +60,7 @@ pub enum FeatureFlag {
|
||||
ServerProjectQa,
|
||||
I18nDebug,
|
||||
ShowInstancePlayTime,
|
||||
CompactInstanceCards,
|
||||
SkipNonEssentialWarnings,
|
||||
AdvancedFiltersCollapsed,
|
||||
AlwaysShowCopyDetails,
|
||||
|
||||
Reference in New Issue
Block a user