fix(front): make layout more consistent across pages

merge-requests/2949/head
Flupsi 2025-08-15 15:37:38 +02:00 zatwierdzone przez Arne Bollinger
rodzic c3c76ce701
commit 81a9c64ccd
4 zmienionych plików z 285 dodań i 295 usunięć

Wyświetl plik

@ -8,7 +8,6 @@ import { useI18n } from 'vue-i18n'
import axios from 'axios'
import clip from 'text-clipper'
import Layout from '~/components/ui/Layout.vue'
import Button from '~/components/ui/Button.vue'
import Alert from '~/components/ui/Alert.vue'
import Spacer from '~/components/ui/Spacer.vue'
@ -153,8 +152,9 @@ const submit = async () => {
>
{{ t('components.common.RenderedDescription.button.cancel') }}
</Button>
<Spacer grow />
<Button
:class="['ui', {'loading': isLoading}, 'right', 'floated', 'button']"
:is-loading
type="submit"
:disabled="isLoading"
solid
@ -180,19 +180,4 @@ const submit = async () => {
overflow: hidden;
text-overflow: ellipsis;
}
/* .description {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
white-space: normal;
&.truncated {
-webkit-line-clamp: 1;
line-clamp: 1;
max-height: 72px;
flex-shrink: 1;
}
} */
</style>

Wyświetl plik

@ -139,160 +139,160 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value]
page-heading
/>
<Section>
<Layout
form
flex
:class="['ui', { 'loading': isLoading }, 'form']"
@submit.prevent="search"
>
<Input
id="artist-search"
v-model="query"
search
name="search"
:label="t('components.library.Artists.label.search')"
autofocus
:placeholder="labels.searchPlaceholder"
/>
<Pills
v-if="typeof tags === 'object'"
:get="model => { tags = model.currents.map(({ label }) => label) }"
:set="model => ({
currents: tags.map(tag => ({ type: 'custom' as const, label: tag })),
others: dataStore.tags().value
.filter(({ name }) => result?.results?.some((object) => object.tags?.includes(name)) && !tags.includes(name))
.map(({ name }) => ({ type: 'preset' as const, label: name })),
})"
:label="t('components.library.Artists.label.tags')"
style="max-width: 350px;"
/>
<Layout
stack
no-gap
label
for="artist-ordering"
form
flex
:class="['ui', { 'loading': isLoading }, 'form']"
@submit.prevent="search"
>
<span class="label">
{{ t('components.library.Artists.ordering.label') }}
</span>
<select
id="artist-ordering"
v-model="ordering"
class="dropdown"
<Input
id="artist-search"
v-model="query"
search
name="search"
:label="t('components.library.Artists.label.search')"
autofocus
:placeholder="labels.searchPlaceholder"
/>
<Pills
v-if="typeof tags === 'object'"
:get="model => { tags = model.currents.map(({ label }) => label) }"
:set="model => ({
currents: tags.map(tag => ({ type: 'custom' as const, label: tag })),
others: dataStore.tags().value
.filter(({ name }) => result?.results?.some((object) => object.tags?.includes(name)) && !tags.includes(name))
.map(({ name }) => ({ type: 'preset' as const, label: name })),
})"
:label="t('components.library.Artists.label.tags')"
style="max-width: 350px;"
/>
<Layout
stack
no-gap
label
for="artist-ordering"
>
<option
v-for="(option, key) in orderingOptions"
:key="key"
:value="option[0]"
<span class="label">
{{ t('components.library.Artists.ordering.label') }}
</span>
<select
id="artist-ordering"
v-model="ordering"
class="dropdown"
>
{{ sharedLabels.filters[option[1]] }}
</option>
</select>
</Layout>
<Layout
stack
no-gap
label
for="artist-ordering-direction"
>
<span class="label">
{{ t('components.library.Artists.ordering.direction.label') }}
</span>
<select
id="artist-ordering-direction"
v-model="orderingDirection"
class="dropdown"
<option
v-for="(option, key) in orderingOptions"
:key="key"
:value="option[0]"
>
{{ sharedLabels.filters[option[1]] }}
</option>
</select>
</Layout>
<Layout
stack
no-gap
label
for="artist-ordering-direction"
>
<option value="+">
{{ t('components.library.Artists.ordering.direction.ascending') }}
</option>
<option value="-">
{{ t('components.library.Artists.ordering.direction.descending') }}
</option>
</select>
</Layout>
<Layout
stack
no-gap
label
for="artist-results"
>
<span class="label">
{{ t('components.library.Artists.pagination.results') }}
</span>
<select
id="artist-results"
v-model="paginateBy"
class="dropdown"
>
<option
v-for="opt in paginateOptions"
:key="opt"
:value="opt"
<span class="label">
{{ t('components.library.Artists.ordering.direction.label') }}
</span>
<select
id="artist-ordering-direction"
v-model="orderingDirection"
class="dropdown"
>
{{ opt }}
</option>
</select>
<option value="+">
{{ t('components.library.Artists.ordering.direction.ascending') }}
</option>
<option value="-">
{{ t('components.library.Artists.ordering.direction.descending') }}
</option>
</select>
</Layout>
<Layout
stack
no-gap
label
for="artist-results"
>
<span class="label">
{{ t('components.library.Artists.pagination.results') }}
</span>
<select
id="artist-results"
v-model="paginateBy"
class="dropdown"
>
<option
v-for="opt in paginateOptions"
:key="opt"
:value="opt"
>
{{ opt }}
</option>
</select>
</Layout>
<Toggle
id="exclude-compilation"
v-model="excludeCompilation"
:label="t('components.library.Artists.label.excludeCompilation')"
true-value="true"
false-value="null"
type="checkbox"
/>
</Layout>
<Toggle
id="exclude-compilation"
v-model="excludeCompilation"
:label="t('components.library.Artists.label.excludeCompilation')"
true-value="true"
false-value="null"
type="checkbox"
<Loader v-if="isLoading" />
<Spacer />
<Pagination
v-if="page && result && result.count > paginateBy"
v-model:page="page"
:pages="Math.ceil(result.count / paginateBy)"
/>
</Layout>
<Loader v-if="isLoading" />
<Spacer />
<Pagination
v-if="page && result && result.count > paginateBy"
v-model:page="page"
:pages="Math.ceil(result.count / paginateBy)"
/>
<Layout
v-if="result && result.results.length > 0"
grid
style="display:flex; flex-wrap:wrap; gap: 32px; margin-top:32px;"
>
<ArtistCard
v-for="artist in result.results"
:key="artist.id"
:artist="artist"
/>
</Layout>
<Layout
v-else-if="result && result.results.length === 0"
stack
>
<Alert yellow>
<i class="compact disc icon" />
{{ t('components.library.Artists.empty.noResults') }}
</Alert>
<Card
v-if="store.state.auth.authenticated"
:title="t('components.library.Artists.button.upload')"
solid
small
primary
style="text-align: center;"
:to="useModal('upload').to"
<Layout
v-if="result && result.results.length > 0"
grid
style="display:flex; flex-wrap:wrap; gap: 32px; margin-top:32px;"
>
<template #image>
<i
class="bi bi-upload"
style="font-size: 100px; position: relative; top: 50px;"
/>
</template>
</Card>
</Layout>
<Spacer grow />
<Pagination
v-if="page && result && result.count > paginateBy"
v-model:page="page"
:pages="Math.ceil(result.count / paginateBy)"
/>
</Section>
<ArtistCard
v-for="artist in result.results"
:key="artist.id"
:artist="artist"
/>
</Layout>
<Layout
v-else-if="result && result.results.length === 0"
stack
>
<Alert yellow>
<i class="compact disc icon" />
{{ t('components.library.Artists.empty.noResults') }}
</Alert>
<Card
v-if="store.state.auth.authenticated"
:title="t('components.library.Artists.button.upload')"
solid
small
primary
style="text-align: center;"
:to="useModal('upload').to"
>
<template #image>
<i
class="bi bi-upload"
style="font-size: 100px; position: relative; top: 50px;"
/>
</template>
</Card>
</Layout>
<Spacer grow />
<Pagination
v-if="page && result && result.count > paginateBy"
v-model:page="page"
:pages="Math.ceil(result.count / paginateBy)"
/>
</Section>
</Layout>
</template>

Wyświetl plik

@ -18,7 +18,6 @@ import Header from '~/components/ui/Header.vue'
import Section from '~/components/ui/Section.vue'
import Pagination from '~/components/ui/Pagination.vue'
import RadioCard from '~/components/radios/Card.vue'
import Button from '~/components/ui/Button.vue'
import Alert from '~/components/ui/Alert.vue'
import Input from '~/components/ui/Input.vue'
import Link from '~/components/ui/Link.vue'
@ -118,7 +117,7 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
<Layout
main
stack
gap-64
gap-84
>
<Header
page-heading
@ -126,6 +125,7 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
/>
<Section
align-left
:columns-per-item="3"
:h2="t('components.library.Radios.header.instance')"
>
<radio-card
@ -156,143 +156,147 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
/>
</Section>
<Section
:h2="t('components.library.Radios.header.user')"
:action="{
to: {name: 'library.radios.build'},
text:t('components.library.Radios.button.create'),
icon: 'bi-plus',
solid: true,
primary: true,
disabled: !store.state.auth.authenticated || undefined
}"
:h2="t('components.library.Radios.header.user')"
align-left
:columns-per-item="3"
:action="{
to: {name: 'library.radios.build'},
text: t('components.library.Radios.button.create'),
icon: 'bi-plus',
solid: true,
primary: true,
disabled: !store.state.auth.authenticated || undefined
}"
>
<Spacer no-size />
<Layout
flex
form
:class="['ui', {'loading': isLoading}, 'form']"
@submit.prevent="search"
>
<Input
id="radios-search"
v-model="query"
search
name="search"
:label="t('components.library.Radios.label.search')"
:placeholder="labels.searchPlaceholder"
/>
<Spacer no-size />
<Layout
stack
no-gap
label
for="radios-ordering"
flex
form
full
:class="['ui', {'loading': isLoading}, 'form']"
@submit.prevent="search"
>
<span class="label">
{{ t('components.library.Radios.ordering.label') }}
</span>
<select
id="radios-ordering"
v-model="ordering"
class="dropdown"
<Input
id="radios-search"
v-model="query"
search
name="search"
:label="t('components.library.Radios.label.search')"
:placeholder="labels.searchPlaceholder"
/>
<Layout
stack
no-gap
label
for="radios-ordering"
>
<option
v-for="(option, key) in orderingOptions"
:key="key"
:value="option[0]"
<span class="label">
{{ t('components.library.Radios.ordering.label') }}
</span>
<select
id="radios-ordering"
v-model="ordering"
class="dropdown"
>
{{ sharedLabels.filters[option[1]] }}
</option>
</select>
</Layout>
<Layout
stack
no-gap
label
for="radios-ordering-direction"
>
<span class="label">
{{ t('components.library.Radios.ordering.direction.label') }}
</span>
<select
id="radios-ordering-direction"
v-model="orderingDirection"
class="dropdown"
<option
v-for="(option, key) in orderingOptions"
:key="key"
:value="option[0]"
>
{{ sharedLabels.filters[option[1]] }}
</option>
</select>
</Layout>
<Layout
stack
no-gap
label
for="radios-ordering-direction"
>
<option value="+">
{{ t('components.library.Radios.ordering.direction.ascending') }}
</option>
<option value="-">
{{ t('components.library.Radios.ordering.direction.descending') }}
</option>
</select>
</Layout>
<Layout
stack
no-gap
label
for="radios-results"
>
<span class="label">
{{ t('components.library.Radios.pagination.results') }}
</span>
<select
id="radios-results"
v-model="paginateBy"
class="dropdown"
>
<option
v-for="opt in paginateOptions"
:key="opt"
:value="opt"
<span class="label">
{{ t('components.library.Radios.ordering.direction.label') }}
</span>
<select
id="radios-ordering-direction"
v-model="orderingDirection"
class="dropdown"
>
{{ opt }}
</option>
</select>
<option value="+">
{{ t('components.library.Radios.ordering.direction.ascending') }}
</option>
<option value="-">
{{ t('components.library.Radios.ordering.direction.descending') }}
</option>
</select>
</Layout>
<Layout
stack
no-gap
label
for="radios-results"
>
<span class="label">
{{ t('components.library.Radios.pagination.results') }}
</span>
<select
id="radios-results"
v-model="paginateBy"
class="dropdown"
>
<option
v-for="opt in paginateOptions"
:key="opt"
:value="opt"
>
{{ opt }}
</option>
</select>
</Layout>
</Layout>
</Layout>
<Alert
v-if="result && result.results.length === 0"
blue
style="align-items: center;"
>
<i
class="bi bi-broadcast-pin"
style="font-size: 80px"
/>
<Spacer />
{{ t('components.library.Radios.empty.noResults') }}
<Spacer />
<Button
v-if="store.state.auth.authenticated"
primary
style="align-self:center;"
:to="{name: 'library.radios.build'}"
icon="bi-boombox-fill"
<Alert
v-if="result && result.results.length === 0"
blue
style="align-items: center; grid-column: 1 / -1;"
>
{{ t('components.library.Radios.button.add') }}
</Button>
</Alert>
<Layout
v-if="result && result.results.length > 0"
flex
>
<Pagination
v-if="page && result && result.count > paginateBy"
v-model:page="page"
:pages="Math.ceil(result.count / paginateBy)"
/>
<radio-card
v-for="radio in result.results"
:key="radio.id"
type="custom"
:custom-radio="radio"
/>
<Pagination
v-if="page && result && result.count > paginateBy"
v-model:page="page"
:pages="Math.ceil(result.count / paginateBy)"
/>
</Layout>
<i
class="bi bi-broadcast-pin"
style="font-size: 80px"
/>
<Spacer />
{{ t('components.library.Radios.empty.noResults') }}
<Spacer />
<Link
v-if="store.state.auth.authenticated"
primary
style="align-self:center;"
:to="{name: 'library.radios.build'}"
icon="bi-boombox-fill"
>
{{ t('components.library.Radios.button.add') }}
</Link>
</Alert>
<Layout
v-if="result && result.results.length > 0"
full
flex
>
<Pagination
v-if="page && result && result.count > paginateBy"
v-model:page="page"
:pages="Math.ceil(result.count / paginateBy)"
/>
<radio-card
v-for="radio in result.results"
:key="radio.id"
type="custom"
:custom-radio="radio"
/>
<Pagination
v-if="page && result && result.count > paginateBy"
v-model:page="page"
:pages="Math.ceil(result.count / paginateBy)"
/>
</Layout>
</Section>
</Layout>
</template>

Wyświetl plik

@ -45,14 +45,15 @@ const isIconOnly = computed(() =>
)
)
const button = ref()
const link = ref()
onMounted(() => {
if (props.autofocus) button.value.focus()
if (props.autofocus) link.value.focus()
})
</script>
<template>
{{ isExternalLink? ' ' : ' ' }}
<component
:is="isExternalLink ? 'a' : RouterLink"
v-bind="color(props, ['interactive'])(
@ -61,7 +62,7 @@ onMounted(() => {
)(
align(props, 'solid' in props ? { alignText: 'center' } : {})(
)))"
ref="button"
ref="link"
:autofocus="autofocus || undefined"
:class="[
$style.link,