mirror of
https://github.com/modrinth/code.git
synced 2026-08-28 10:34:53 +00:00
feat: clean up browse shared layout logic + introduce queuing (#6030)
* feat: clean up edge case behaviour and add queued to install logic * fix: remove version choice modal * feat: queued flow * feat: standardize headers in app on proj pages * fix: clear btn * feat: installing floating popup * fix: lint * fix: onboarding/reset logic change for modpacks * qa: big ol qa * fix: lint * fix: lint --------- Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
This commit is contained in:
@@ -21,7 +21,7 @@ import Checkbox from '#ui/components/base/Checkbox.vue'
|
||||
import type { Option as OverflowMenuOption } from '#ui/components/base/OverflowMenu.vue'
|
||||
import TeleportOverflowMenu from '#ui/components/base/TeleportOverflowMenu.vue'
|
||||
import Toggle from '#ui/components/base/Toggle.vue'
|
||||
import { useVIntl } from '#ui/composables/i18n'
|
||||
import { defineMessages, useVIntl } from '#ui/composables/i18n'
|
||||
import { commonMessages } from '#ui/utils/common-messages'
|
||||
import { truncatedTooltip } from '#ui/utils/truncate'
|
||||
|
||||
@@ -34,6 +34,13 @@ import type {
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const messages = defineMessages({
|
||||
selectProject: {
|
||||
id: 'content.card.select-project',
|
||||
defaultMessage: 'Select {project}',
|
||||
},
|
||||
})
|
||||
|
||||
interface Props {
|
||||
project: ContentCardProject
|
||||
projectLink?: string | RouteLocationRaw
|
||||
@@ -111,7 +118,10 @@ const deleteHovered = ref(false)
|
||||
<div
|
||||
role="row"
|
||||
class="flex h-[74px] items-center justify-between gap-4 px-3"
|
||||
:class="{ 'opacity-50': disabled }"
|
||||
:class="{
|
||||
'opacity-50 grayscale': disabled && !installing,
|
||||
'opacity-50': installing,
|
||||
}"
|
||||
>
|
||||
<div
|
||||
class="flex min-w-0 items-center gap-4"
|
||||
@@ -122,7 +132,7 @@ const deleteHovered = ref(false)
|
||||
<Checkbox
|
||||
v-if="showCheckbox"
|
||||
:model-value="selected ?? false"
|
||||
:aria-label="`Select ${project.title}`"
|
||||
:aria-label="formatMessage(messages.selectProject, { project: project.title })"
|
||||
class="shrink-0"
|
||||
@update:model-value="selected = $event"
|
||||
/>
|
||||
|
||||
@@ -89,6 +89,7 @@ const { listContainer, totalHeight, visibleRange, visibleTop, visibleItems } = u
|
||||
{
|
||||
itemHeight: 74,
|
||||
bufferSize: 5,
|
||||
initialItemCount: 20,
|
||||
enabled: toRef(props, 'virtualized'),
|
||||
},
|
||||
)
|
||||
|
||||
@@ -119,16 +119,18 @@ const collapsedOptions = computed(() => {
|
||||
|
||||
const containerRef = ref<HTMLElement | null>(null)
|
||||
const isExpanded = ref(true)
|
||||
const observer = new ResizeObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
isExpanded.value = entry.contentRect.width >= 700
|
||||
}
|
||||
})
|
||||
let observer: ResizeObserver | null = null
|
||||
onMounted(() => {
|
||||
observer = new ResizeObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
isExpanded.value = entry.contentRect.width >= 700
|
||||
}
|
||||
})
|
||||
if (containerRef.value) observer.observe(containerRef.value)
|
||||
})
|
||||
onUnmounted(() => {
|
||||
observer.disconnect()
|
||||
observer?.disconnect()
|
||||
observer = null
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { PowerIcon, PowerOffIcon, XIcon } from '@modrinth/assets'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import Avatar from '#ui/components/base/Avatar.vue'
|
||||
import ButtonStyled from '#ui/components/base/ButtonStyled.vue'
|
||||
import FloatingActionBar from '#ui/components/base/FloatingActionBar.vue'
|
||||
import { defineMessages, useVIntl } from '#ui/composables/i18n'
|
||||
@@ -93,6 +94,13 @@ const emit = defineEmits<{
|
||||
}>()
|
||||
|
||||
const shown = computed(() => props.selectedItems.length > 0 || props.isBulkOperating)
|
||||
const iconStackOffset = 24
|
||||
const visibleItems = computed(() => props.selectedItems.slice(0, 3))
|
||||
const overflowCount = computed(() => Math.max(0, props.selectedItems.length - 3))
|
||||
const iconStackWidth = computed(() => {
|
||||
if (props.selectedItems.length === 0) return 0
|
||||
return 32 + (visibleItems.value.length - 1 + (overflowCount.value > 0 ? 1 : 0)) * iconStackOffset
|
||||
})
|
||||
|
||||
const allDisabled = computed(() => props.selectedItems.every((m) => !m.enabled))
|
||||
const allEnabled = computed(() => props.selectedItems.every((m) => m.enabled))
|
||||
@@ -130,10 +138,41 @@ const bulkProgressMessage = computed(() => {
|
||||
<template>
|
||||
<FloatingActionBar :shown="shown" :aria-label="ariaLabel">
|
||||
<div class="flex items-center gap-0.5">
|
||||
<span class="px-4 py-2.5 text-base font-semibold text-contrast tabular-nums">
|
||||
<div
|
||||
v-if="selectedItems.length > 0"
|
||||
class="relative h-8 shrink-0"
|
||||
:style="{ width: `${iconStackWidth}px` }"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div
|
||||
v-for="(item, index) in visibleItems"
|
||||
:key="item.id"
|
||||
v-tooltip="item.project?.title ?? item.file_name"
|
||||
class="absolute top-0 flex h-8 w-8 items-center justify-center overflow-hidden rounded-lg border-[1.5px] border-solid border-surface-3 bg-surface-4"
|
||||
:style="{ left: `${index * iconStackOffset}px`, zIndex: visibleItems.length - index }"
|
||||
>
|
||||
<Avatar
|
||||
:src="item.project?.icon_url"
|
||||
:alt="item.project?.title ?? item.file_name"
|
||||
:tint-by="item.id"
|
||||
size="100%"
|
||||
no-shadow
|
||||
class="selected-content-avatar"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="overflowCount > 0"
|
||||
class="absolute top-0 flex h-8 w-8 items-center justify-center rounded-lg border-[1.5px] border-solid border-surface-3 bg-surface-4 text-xs font-bold text-contrast"
|
||||
:style="{ left: `${visibleItems.length * iconStackOffset}px`, zIndex: 0 }"
|
||||
>
|
||||
+{{ overflowCount }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span class="px-3 py-2 text-base font-semibold text-contrast tabular-nums">
|
||||
{{ selectedCountText }}
|
||||
</span>
|
||||
<div class="mx-1 h-6 w-px bg-surface-5" />
|
||||
<div class="mx-0.5 h-6 w-px bg-surface-5" />
|
||||
<ButtonStyled type="transparent">
|
||||
<button
|
||||
v-tooltip="formatMessage(commonMessages.clearButton)"
|
||||
@@ -223,4 +262,8 @@ const bulkProgressMessage = computed(() => {
|
||||
.animate-indeterminate {
|
||||
animation: indeterminate 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
:deep(.selected-content-avatar) {
|
||||
background-color: var(--color-button-bg);
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user