fix(style) [WIP] Library search pages

This commit is contained in:
ArneBo 2025-01-05 18:30:55 +01:00 committed by upsiflu
parent 17bed3e9b4
commit 42805171fe
4 changed files with 281 additions and 326 deletions

View File

@ -1,10 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import type { OrderingProps } from '~/composables/navigation/useOrdering' import type { OrderingProps } from '~/composables/navigation/useOrdering'
import type { Album, BackendResponse } from '~/types' import type { Artist, BackendResponse } from '~/types'
import type { RouteRecordName } from 'vue-router' import type { RouteRecordName } from 'vue-router'
import type { OrderingField } from '~/store/ui' import type { OrderingField } from '~/store/ui'
import { computed, onMounted, ref, watch } from 'vue' import { computed, ref, watch, onMounted } from 'vue'
import { useRouteQuery } from '@vueuse/router' import { useRouteQuery } from '@vueuse/router'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { syncRef } from '@vueuse/core' import { syncRef } from '@vueuse/core'
@ -12,12 +12,19 @@ import { sortedUniq } from 'lodash-es'
import { useStore } from '~/store' import { useStore } from '~/store'
import axios from 'axios' import axios from 'axios'
import $ from 'jquery'
import TagsSelector from '~/components/library/TagsSelector.vue' import TagsSelector from '~/components/library/TagsSelector.vue'
import AlbumCard from '~/components/album/Card.vue' import Pagination from '~/components/ui/Pagination.vue'
import Pagination from '~/components/vui/Pagination.vue' import Link from '~/components/ui/Link.vue'
import Card from '~/components/ui/Card.vue'
import Layout from '~/components/ui/Layout.vue'
import Button from '~/components/ui/Button.vue' import Button from '~/components/ui/Button.vue'
import Input from '~/components/ui/Input.vue'
import Toggle from '~/components/ui/Toggle.vue'
import Alert from '~/components/ui/Alert.vue'
import Spacer from '~/components/ui/layout/Spacer.vue'
import Pills from '~/components/ui/Pills.vue'
import Loader from '~/components/ui/Loader.vue'
import useSharedLabels from '~/composables/locale/useSharedLabels' import useSharedLabels from '~/composables/locale/useSharedLabels'
import useOrdering from '~/composables/navigation/useOrdering' import useOrdering from '~/composables/navigation/useOrdering'
@ -41,6 +48,11 @@ const page = usePage()
const tags = useRouteQuery<string[]>('tag', []) const tags = useRouteQuery<string[]>('tag', [])
const tagList = computed(() => ({
current: tags.value,
others: []
}))
const q = useRouteQuery('query', '') const q = useRouteQuery('query', '')
const query = ref(q.value) const query = ref(q.value)
syncRef(q, query, { direction: 'ltr' }) syncRef(q, query, { direction: 'ltr' })
@ -107,7 +119,7 @@ onOrderingUpdate(() => {
fetchData() fetchData()
}) })
onMounted(() => $('.ui.dropdown').dropdown()) // onMounted(() => $('.ui.dropdown').dropdown())
const { t } = useI18n() const { t } = useI18n()
const labels = computed(() => ({ const labels = computed(() => ({
@ -115,51 +127,35 @@ const labels = computed(() => ({
title: t('components.library.Albums.title') title: t('components.library.Albums.title')
})) }))
const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value].sort((a, b) => a - b))) const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value].sort((a, b) => a - b)))
</script> </script>
<template> <template>
<main v-title="labels.title"> <Layout stack main v-title="labels.title">
<section class="ui vertical stripe segment"> <h2>{{ t('components.library.Albums.header.browse') }}</h2>
/front/src/components/library/Albums.vue <Layout form flex
<h2 class="ui header">
{{ t('components.library.Albums.header.browse') }}
</h2>
<form
:class="['ui', {'loading': isLoading}, 'form']" :class="['ui', {'loading': isLoading}, 'form']"
@submit.prevent="search" @submit.prevent="search"
> >
<div class="fields"> <Input search
<div class="field"> id="album-search"
<label for="albums-search">
{{ t('components.library.Albums.label.search') }}
</label>
<div class="ui action input">
<input
id="albums-search"
v-model="query" v-model="query"
type="text"
name="search" name="search"
:label="t('components.library.Albums.label.search')"
autofocus
:placeholder="labels.searchPlaceholder" :placeholder="labels.searchPlaceholder"
> >
<Button </Input>
type="submit" <Pills
:aria-label="t('components.library.Albums.button.search')" v-model="tagList"
> :label="t('components.library.Albums.label.tags')"
<i class="search icon" /> style="max-width: 150px;"
</Button> />
</div>
</div>
<div class="field">
<label for="tags-search">{{ t('components.library.Albums.label.tags') }}</label>
<tags-selector v-model="tags" />
</div>
<div class="field">
<label for="album-ordering">{{ t('components.library.Albums.ordering.label') }}</label>
<select <select
id="album-ordering" id="album-ordering"
:label="t('components.library.Albums.ordering.label')"
v-model="ordering" v-model="ordering"
class="ui dropdown" class="dropdown"
> >
<option <option
v-for="(option, key) in orderingOptions" v-for="(option, key) in orderingOptions"
@ -169,10 +165,8 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
{{ sharedLabels.filters[option[1]] }} {{ sharedLabels.filters[option[1]] }}
</option> </option>
</select> </select>
</div>
<div class="field">
<label for="album-ordering-direction">{{ t('components.library.Albums.ordering.direction.label') }}</label>
<select <select
:label="t('components.library.Albums.ordering.direction.label')"
id="album-ordering-direction" id="album-ordering-direction"
v-model="orderingDirection" v-model="orderingDirection"
class="ui dropdown" class="ui dropdown"
@ -184,10 +178,8 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
{{ t('components.library.Albums.ordering.direction.descending') }} {{ t('components.library.Albums.ordering.direction.descending') }}
</option> </option>
</select> </select>
</div>
<div class="field">
<label for="album-results">{{ t('components.library.Albums.pagination.results') }}</label>
<select <select
:label="t('components.library.Albums.pagination.results')"
id="album-results" id="album-results"
v-model="paginateBy" v-model="paginateBy"
class="ui dropdown" class="ui dropdown"
@ -200,54 +192,44 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
{{ opt }} {{ opt }}
</option> </option>
</select> </select>
</div> </Layout>
</div> <Layout grid
</form> v-if="result && result.results.length > 0"
<div
v-if="result"
transition-duration="0"
item-selector=".column"
percent-position="true"
stagger="0"
class=""
>
<div
v-if="result.results.length > 0"
style="display:flex; flex-wrap:wrap; gap: 32px; margin-top:32px;" style="display:flex; flex-wrap:wrap; gap: 32px; margin-top:32px;"
> >
<Loader v-if="isLoading"/>
<album-card <album-card
v-for="album in result.results" v-for="album in result.results"
:key="album.id" :key="album.id"
:album="album" :album="album"
/> />
</div> </Layout>
<div <Layout
v-else stack
class="ui placeholder segment sixteen wide column" v-else-if="result && result.results.length === 0"
style="text-align: center; display: flex; align-items: center"
> >
<div class="ui icon header"> <Alert yellow>
<i class="compact disc icon" /> <i class="compact disc icon" />
{{ t('components.library.Albums.empty.noResults') }} {{ t('components.library.Albums.empty.noResults') }}
</div> </Alert>
<router-link <Card
v-if="store.state.auth.authenticated" v-if="store.state.auth.authenticated"
:title="t('components.library.Albums.link.addMusic')"
solid
small
primary
:to="{name: 'content.index'}" :to="{name: 'content.index'}"
class="ui success button labeled icon"
> >
<i class="upload icon" /> <template #image>
{{ t('components.library.Albums.link.addMusic') }} <i class="bi bi-upload" style="font-size: 100px; position: relative; top: 50px;" />
</router-link> </template>
</div> </Card>
</div> </Layout>
<div class="ui center aligned basic segment"> <Spacer grow />
<pagination <Pagination
v-if="result && result.count > paginateBy" v-if="result && result.count > paginateBy"
v-model:current="page" :page="page"
:paginate-by="paginateBy" :pages="Math.ceil((result?.results.length || 0)/paginateBy)"
:total="result.count" />
/> </Layout>
</div>
</section>
</main>
</template> </template>

View File

@ -25,6 +25,7 @@ import Toggle from '~/components/ui/Toggle.vue'
import Alert from '~/components/ui/Alert.vue' import Alert from '~/components/ui/Alert.vue'
import Spacer from '~/components/ui/layout/Spacer.vue' import Spacer from '~/components/ui/layout/Spacer.vue'
import Pills from '~/components/ui/Pills.vue' import Pills from '~/components/ui/Pills.vue'
import Loader from '~/components/ui/Loader.vue'
import useSharedLabels from '~/composables/locale/useSharedLabels' import useSharedLabels from '~/composables/locale/useSharedLabels'
import useOrdering from '~/composables/navigation/useOrdering' import useOrdering from '~/composables/navigation/useOrdering'
@ -48,6 +49,11 @@ const page = usePage()
const tags = useRouteQuery<string[]>('tag', []) const tags = useRouteQuery<string[]>('tag', [])
const tagList = computed(() => ({
current: tags.value,
others: []
}))
const q = useRouteQuery('query', '') const q = useRouteQuery('query', '')
const query = ref(q.value) const query = ref(q.value)
syncRef(q, query, { direction: 'ltr' }) syncRef(q, query, { direction: 'ltr' })
@ -123,11 +129,6 @@ const labels = computed(() => ({
title: t('components.library.Artists.title') title: t('components.library.Artists.title')
})) }))
const tagList = computed(() => ({
current: tags.value,
others: []
}))
const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value].sort((a, b) => a - b))) const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value].sort((a, b) => a - b)))
</script> </script>
@ -208,20 +209,16 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value]
v-if="result && result.results.length > 0" v-if="result && result.results.length > 0"
style="display:flex; flex-wrap:wrap; gap: 32px; margin-top:32px;" style="display:flex; flex-wrap:wrap; gap: 32px; margin-top:32px;"
> >
<div <Loader v-if="isLoading"/>
v-if="isLoading"
class="ui inverted active dimmer"
>
<div class="ui loader" />
</div>
<ArtistCard <ArtistCard
v-for="artist in result.results" v-for="artist in result.results"
:key="artist.id" :key="artist.id"
:artist="artist" :artist="artist"
/> />
</Layout> </Layout>
<Layout stack <Layout
v-else-if="!isLoading" stack
v-else-if="result && result.results.length === 0"
> >
<Alert yellow> <Alert yellow>
<i class="compact disc icon" /> <i class="compact disc icon" />

View File

@ -4,7 +4,7 @@ import type { Artist, BackendResponse } from '~/types'
import type { RouteRecordName } from 'vue-router' import type { RouteRecordName } from 'vue-router'
import type { OrderingField } from '~/store/ui' import type { OrderingField } from '~/store/ui'
import { computed, ref, watch } from 'vue' import { computed, ref, watch, onMounted } from 'vue'
import { useRouteQuery } from '@vueuse/router' import { useRouteQuery } from '@vueuse/router'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { syncRef } from '@vueuse/core' import { syncRef } from '@vueuse/core'
@ -17,10 +17,13 @@ import TagsSelector from '~/components/library/TagsSelector.vue'
import RemoteSearchForm from '~/components/RemoteSearchForm.vue' import RemoteSearchForm from '~/components/RemoteSearchForm.vue'
import Modal from '~/components/ui/Modal.vue' import Modal from '~/components/ui/Modal.vue'
import ArtistCard from '~/components/artist/Card.vue' import ArtistCard from '~/components/artist/Card.vue'
import Pagination from '~/components/vui/Pagination.vue' import Pagination from '~/components/ui/Pagination.vue'
import Popover from "~/components/ui/Popover.vue" import Layout from '~/components/ui/Layout.vue'
import PopoverItem from "~/components/ui/popover/PopoverItem.vue"
import Button from '~/components/ui/Button.vue' import Button from '~/components/ui/Button.vue'
import Input from '~/components/ui/Input.vue'
import Alert from '~/components/ui/Alert.vue'
import Spacer from '~/components/ui/layout/Spacer.vue'
import Pills from '~/components/ui/Pills.vue'
import useSharedLabels from '~/composables/locale/useSharedLabels' import useSharedLabels from '~/composables/locale/useSharedLabels'
import useOrdering from '~/composables/navigation/useOrdering' import useOrdering from '~/composables/navigation/useOrdering'
@ -44,11 +47,16 @@ const page = usePage()
const tags = useRouteQuery<string[]>('tag', []) const tags = useRouteQuery<string[]>('tag', [])
const tagList = computed(() => ({
current: tags.value,
others: []
}))
const q = useRouteQuery('query', '') const q = useRouteQuery('query', '')
const query = ref(q.value) const query = ref(q.value)
syncRef(q, query, { direction: 'ltr' }) syncRef(q, query, { direction: 'ltr' })
const result = ref<BackendResponse<Artist>>() const result = ref<BackendResponse<Playlist>>()
const showSubscribeModal = ref(false) const showSubscribeModal = ref(false)
const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [ const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [
@ -112,6 +120,8 @@ onOrderingUpdate(() => {
fetchData() fetchData()
}) })
// onMounted(() => $('.ui.dropdown').dropdown())
const { t } = useI18n() const { t } = useI18n()
const labels = computed(() => ({ const labels = computed(() => ({
searchPlaceholder: t('components.library.Podcasts.placeholder.search'), searchPlaceholder: t('components.library.Podcasts.placeholder.search'),
@ -122,56 +132,42 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value]
</script> </script>
<template> <template>
<main v-title="labels.title"> <Layout stack main>
<section class="ui vertical stripe segment"> <h1>{{ t('components.library.Podcasts.header.browse') }}</h1>
<h2 class="ui header"> <Layout form flex
{{ t('components.library.Podcasts.header.browse') }}
</h2>
<form
:class="['ui', {'loading': isLoading}, 'form']" :class="['ui', {'loading': isLoading}, 'form']"
@submit.prevent="search" @submit.prevent="search"
> >
<div class="fields"> <Input search
<div class="field">
<label for="artist-search">
{{ t('components.library.Podcasts.label.search') }}
</label>
<div class="ui action input">
<input
id="artist-search" id="artist-search"
v-model="query" v-model="query"
type="text"
name="search" name="search"
:label="t('components.library.Podcasts.label.search')"
autofocus
:placeholder="labels.searchPlaceholder" :placeholder="labels.searchPlaceholder"
> >
<Button </Input>
icon="bi-search" <Pills
type="submit" v-model="tagList"
:aria-label="t('components.library.Podcasts.button.search')" :label="t('components.library.Podcasts.label.tags')"
style="max-width: 150px;"
/>
<select
id="artist-ordering"
:label="t('components.library.Podcasts.ordering.label')"
v-model="ordering"
class="dropdown"
> >
</Button> <option
</div>
</div>
<div class="field">
<label for="tags-search">{{ t('components.library.Podcasts.label.tags') }}</label>
<tags-selector v-model="tags" />
</div>
<div class="field">
<label for="artist-ordering">{{ t('components.library.Podcasts.ordering.label') }}</label>
<Popover v-model:open="artistOrdering">
<OptionsButton @click="artistOrdering = !artistOrdering" id="artist-ordering" v-model="ordering"/>
<PopoverItem
v-for="(option, key) in orderingOptions" v-for="(option, key) in orderingOptions"
:key="key" :key="key"
:value="option[0]" :value="option[0]"
> >
{{ sharedLabels.filters[option[1]] }} {{ sharedLabels.filters[option[1]] }}
</PopoverItem> </option>
</Popover> </select>
</div>
<div class="field">
<label for="artist-ordering-direction">{{ t('components.library.Podcasts.ordering.direction.label') }}</label>
<select <select
:label="t('components.library.Podcasts.ordering.direction.label')"
id="artist-ordering-direction" id="artist-ordering-direction"
v-model="orderingDirection" v-model="orderingDirection"
class="ui dropdown" class="ui dropdown"
@ -183,10 +179,8 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value]
{{ t('components.library.Podcasts.ordering.direction.descending') }} {{ t('components.library.Podcasts.ordering.direction.descending') }}
</option> </option>
</select> </select>
</div>
<div class="field">
<label for="artist-results">{{ t('components.library.Podcasts.pagination.results') }}</label>
<select <select
:label="t('components.library.Podcasts.pagination.results')"
id="artist-results" id="artist-results"
v-model="paginateBy" v-model="paginateBy"
class="ui dropdown" class="ui dropdown"
@ -199,64 +193,50 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value]
{{ opt }} {{ opt }}
</option> </option>
</select> </select>
</div> </Layout>
</div> <Layout grid
</form>
<div class="ui hidden divider" />
<div
v-if="result && result.results.length > 0" v-if="result && result.results.length > 0"
class="ui five app-cards cards" style="display:flex; flex-wrap:wrap; gap: 32px; margin-top:32px;"
> >
<div <Loader v-if="isLoading"/>
v-if="isLoading"
class="ui inverted active dimmer"
>
<div class="ui loader" />
</div>
<artist-card <artist-card
v-for="artist in result.results" v-for="artist in result.results"
:key="artist.id" :key="artist.id"
:artist="artist" :artist="artist"
/> />
</div> </Layout>
<div <Layout
v-else-if="!isLoading" stack
class="ui placeholder segment sixteen wide column" v-else-if="result && result.results.length === 0"
style="text-align: center; display: flex; align-items: center"
> >
<div class="ui icon header"> <Alert yellow>
<i class="podcast icon" />
{{ t('components.library.Podcasts.empty.noResults') }} {{ t('components.library.Podcasts.empty.noResults') }}
</div> </Alert>
<router-link <Layout flex>
<Button
v-if="store.state.auth.authenticated" v-if="store.state.auth.authenticated"
:to="{name: 'content.index'}" icon="bi-plus"
class="ui success button labeled icon" primary
@click.stop.prevent="showSubscribeModal = true"
> >
<i class="upload icon" />
{{ t('components.library.Podcasts.button.channel') }}
</router-link>
<h1
v-if="store.state.auth.authenticated"
class="ui with-actions header"
>
<div class="actions">
<a @click.stop.prevent="showSubscribeModal = true">
<i class="plus icon" />
{{ t('components.library.Podcasts.button.feed') }} {{ t('components.library.Podcasts.button.feed') }}
</a> </Button>
</div> <Button
</h1> primary
</div> v-if="store.state.auth.authenticated"
<div class="ui center aligned basic segment"> icon="bi-upload"
<pagination :to="{name: 'content.index'}"
>
{{ t('components.library.Podcasts.button.channel') }}
</Button>
</Layout>
</Layout>
<Spacer grow />
<Pagination
v-if="result && result.count > paginateBy" v-if="result && result.count > paginateBy"
v-model:current="page" :page="page"
:paginate-by="paginateBy" :pages="Math.ceil((result?.results.length || 0)/paginateBy)"
:total="result.count"
/> />
</div>
</section>
<Modal <Modal
v-model:show="showSubscribeModal" v-model:show="showSubscribeModal"
title="t('components.library.Podcasts.modal.subscription.header')" title="t('components.library.Podcasts.modal.subscription.header')"
@ -286,5 +266,7 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value]
</Button> </Button>
</div> </div>
</Modal> </Modal>
</main>
</Layout>
</template> </template>

View File

@ -1,7 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import ArtistCard from '~/components/artist/Card.vue'
import type { OrderingProps } from '~/composables/navigation/useOrdering' import type { OrderingProps } from '~/composables/navigation/useOrdering'
import type { Artist, BackendResponse } from '~/types' import type { Playlist, BackendResponse } from '~/types'
import type { RouteRecordName } from 'vue-router' import type { RouteRecordName } from 'vue-router'
import type { OrderingField } from '~/store/ui' import type { OrderingField } from '~/store/ui'
@ -14,6 +13,7 @@ import { useStore } from '~/store'
import axios from 'axios' import axios from 'axios'
import PlaylistCardList from '~/components/playlists/CardList.vue'
import Pagination from '~/components/ui/Pagination.vue' import Pagination from '~/components/ui/Pagination.vue'
import Layout from '~/components/ui/Layout.vue' import Layout from '~/components/ui/Layout.vue'
import Button from '~/components/ui/Button.vue' import Button from '~/components/ui/Button.vue'
@ -188,33 +188,27 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value]
v-if="result && result.results.length > 0" v-if="result && result.results.length > 0"
style="display:flex; flex-wrap:wrap; gap: 32px; margin-top:32px;" style="display:flex; flex-wrap:wrap; gap: 32px; margin-top:32px;"
> >
<div <Loader v-if="isLoading"/>
v-if="isLoading"
class="ui inverted active dimmer"
>
<div class="ui loader" />
</div>
<playlist-card-list <playlist-card-list
v-if="result && result.results.length > 0" v-if="result && result.results.length > 0"
:playlists="result.results" :playlists="result.results"
/> />
<div </Layout>
<Layout
stack
v-else-if="result && result.results.length === 0" v-else-if="result && result.results.length === 0"
class="ui placeholder segment sixteen wide column"
style="text-align: center; display: flex; align-items: center"
> >
<div class="ui icon header"> <Alert yellow>
<i class="list icon" />
{{ t('views.playlists.List.empty.noResults') }} {{ t('views.playlists.List.empty.noResults') }}
</div> </Alert>
<Button <Button
v-if="store.state.auth.authenticated" v-if="store.state.auth.authenticated"
icon="bi-list" icon="bi-list"
primary
@click="store.commit('playlists/showModal', true)" @click="store.commit('playlists/showModal', true)"
> >
{{ t('views.playlists.List.button.create') }} {{ t('views.playlists.List.button.create') }}
</Button> </Button>
</div>
</Layout> </Layout>
<Spacer grow /> <Spacer grow />
<Pagination <Pagination