forked from platypush/platypush
[#414] Added ability to sort Jellyin results.
This also adds a new `FloatingDropdownButton` component.
This commit is contained in:
parent
bf82ad9bf0
commit
1a53c59382
5 changed files with 227 additions and 6 deletions
|
@ -59,11 +59,19 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
|
button() {
|
||||||
|
const el = this.$refs.button?.$el
|
||||||
|
if (!el)
|
||||||
|
return this.$refs.button
|
||||||
|
|
||||||
|
return el.querySelector('button')
|
||||||
|
},
|
||||||
|
|
||||||
buttonStyle() {
|
buttonStyle() {
|
||||||
if (!this.$refs.button)
|
if (!this.button)
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
return getComputedStyle(this.$refs.button)
|
return getComputedStyle(this.button)
|
||||||
},
|
},
|
||||||
|
|
||||||
buttonWidth() {
|
buttonWidth() {
|
||||||
|
@ -140,7 +148,7 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
adjustDropdownPos() {
|
adjustDropdownPos() {
|
||||||
const buttonRect = this.$refs.button.getBoundingClientRect()
|
const buttonRect = this.button.getBoundingClientRect()
|
||||||
const buttonPos = {
|
const buttonPos = {
|
||||||
left: buttonRect.left + window.scrollX,
|
left: buttonRect.left + window.scrollX,
|
||||||
top: buttonRect.top + window.scrollY,
|
top: buttonRect.top + window.scrollY,
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
<template>
|
||||||
|
<div class="floating-dropdown-container">
|
||||||
|
<FloatingButton :disabled="disabled"
|
||||||
|
:iconClass="iconClass"
|
||||||
|
:iconUrl="iconUrl"
|
||||||
|
:glow="glow"
|
||||||
|
:left="left"
|
||||||
|
:right="right"
|
||||||
|
:title="title"
|
||||||
|
:top="top"
|
||||||
|
:bottom="bottom"
|
||||||
|
ref="button"
|
||||||
|
@click.stop="toggle($event)" />
|
||||||
|
|
||||||
|
<div class="body-container hidden" ref="dropdownContainer">
|
||||||
|
<DropdownBody :id="id"
|
||||||
|
:keepOpenOnItemClick="keepOpenOnItemClick"
|
||||||
|
:style="style"
|
||||||
|
ref="dropdown"
|
||||||
|
@click="onClick">
|
||||||
|
<slot />
|
||||||
|
</DropdownBody>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Dropdown from "./Dropdown";
|
||||||
|
import DropdownBody from "./DropdownBody";
|
||||||
|
import FloatingButton from "@/components/elements/FloatingButton";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
mixins: [Dropdown, FloatingButton],
|
||||||
|
emits: ['click'],
|
||||||
|
components: {
|
||||||
|
DropdownBody,
|
||||||
|
FloatingButton,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.floating-dropdown-container {
|
||||||
|
.body-container {
|
||||||
|
position: relative;
|
||||||
|
display: inline-flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
button {
|
||||||
|
background: none;
|
||||||
|
border: 0;
|
||||||
|
padding: 0.5em;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $default-hover-fg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -24,7 +24,7 @@ import Index from "./Jellyfin/Index";
|
||||||
import Loading from "@/components/Loading";
|
import Loading from "@/components/Loading";
|
||||||
import MediaNav from "./Nav";
|
import MediaNav from "./Nav";
|
||||||
import MediaProvider from "./Mixin";
|
import MediaProvider from "./Mixin";
|
||||||
import Movies from "./Jellyfin/Collections/Movies/Index";
|
import Movies from "./Jellyfin/collections/Movies/Index";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [MediaProvider],
|
mixins: [MediaProvider],
|
||||||
|
|
|
@ -18,6 +18,8 @@
|
||||||
@remove-from-playlist="$emit('remove-from-playlist', $event)"
|
@remove-from-playlist="$emit('remove-from-playlist', $event)"
|
||||||
@select="selectedResult = $event"
|
@select="selectedResult = $event"
|
||||||
v-else />
|
v-else />
|
||||||
|
|
||||||
|
<SortButton :value="sort" @input="sort = $event" v-if="sortedMovies.length > 0" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -26,6 +28,7 @@ import Loading from "@/components/Loading";
|
||||||
import MediaProvider from "@/components/panels/Media/Providers/Mixin";
|
import MediaProvider from "@/components/panels/Media/Providers/Mixin";
|
||||||
import NoItems from "@/components/elements/NoItems";
|
import NoItems from "@/components/elements/NoItems";
|
||||||
import Results from "@/components/panels/Media/Results";
|
import Results from "@/components/panels/Media/Results";
|
||||||
|
import SortButton from "@/components/panels/Media/Providers/Jellyfin/components/SortButton";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [MediaProvider],
|
mixins: [MediaProvider],
|
||||||
|
@ -33,6 +36,7 @@ export default {
|
||||||
Loading,
|
Loading,
|
||||||
NoItems,
|
NoItems,
|
||||||
Results,
|
Results,
|
||||||
|
SortButton,
|
||||||
},
|
},
|
||||||
|
|
||||||
emits: [
|
emits: [
|
||||||
|
@ -52,9 +56,13 @@ export default {
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
movies: {},
|
movies: [],
|
||||||
loading_: false,
|
loading_: false,
|
||||||
selectedResult: null,
|
selectedResult: null,
|
||||||
|
sort: {
|
||||||
|
attr: 'title',
|
||||||
|
desc: false,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -64,8 +72,25 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
sortedMovies() {
|
sortedMovies() {
|
||||||
|
if (!this.movies) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
return [...this.movies].sort((a, b) => {
|
return [...this.movies].sort((a, b) => {
|
||||||
return a.title.localeCompare(b.title)
|
const attr = this.sort.attr
|
||||||
|
const desc = this.sort.desc
|
||||||
|
let aVal = a[attr]
|
||||||
|
let bVal = b[attr]
|
||||||
|
|
||||||
|
if (typeof aVal === 'number' || typeof bVal === 'number') {
|
||||||
|
aVal = aVal || 0
|
||||||
|
bVal = bVal || 0
|
||||||
|
return desc ? bVal - aVal : aVal - bVal
|
||||||
|
}
|
||||||
|
|
||||||
|
aVal = (aVal || '').toString().toLowerCase()
|
||||||
|
bVal = (bVal || '').toString().toLowerCase()
|
||||||
|
return desc ? bVal.localeCompare(aVal) : aVal.localeCompare(bVal)
|
||||||
}).map((movie) => {
|
}).map((movie) => {
|
||||||
return {
|
return {
|
||||||
item_type: movie.type,
|
item_type: movie.type,
|
||||||
|
@ -113,4 +138,8 @@ export default {
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import "@/components/panels/Media/Providers/Jellyfin/common.scss";
|
@import "@/components/panels/Media/Providers/Jellyfin/common.scss";
|
||||||
|
|
||||||
|
.index {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
|
@ -0,0 +1,124 @@
|
||||||
|
<template>
|
||||||
|
<div class="sort-buttons">
|
||||||
|
<Dropdown :icon-class="btnIconClass"
|
||||||
|
glow right
|
||||||
|
title="Sort Direction">
|
||||||
|
<div class="sort-buttons-dropdown-body">
|
||||||
|
<div class="title">Sort Direction</div>
|
||||||
|
<DropdownItem text="Ascending"
|
||||||
|
icon-class="fa fa-arrow-up-short-wide"
|
||||||
|
:item-class="{ active: !value?.desc }"
|
||||||
|
@input="onDescChange(false)" />
|
||||||
|
<DropdownItem text="Descending"
|
||||||
|
icon-class="fa fa-arrow-down-wide-short"
|
||||||
|
:item-class="{ active: value?.desc }"
|
||||||
|
@input="onDescChange(true)" />
|
||||||
|
|
||||||
|
<div class="title">Sort By</div>
|
||||||
|
<DropdownItem text="Name"
|
||||||
|
icon-class="fa fa-font"
|
||||||
|
:item-class="{ active: value?.attr === 'title' }"
|
||||||
|
@input="onAttrChange('title')" />
|
||||||
|
<DropdownItem text="Release Date"
|
||||||
|
icon-class="fa fa-calendar"
|
||||||
|
:item-class="{ active: value?.attr === 'year' }"
|
||||||
|
@input="onAttrChange('year')" />
|
||||||
|
<DropdownItem text="Critics Rating"
|
||||||
|
icon-class="fa fa-star"
|
||||||
|
:item-class="{ active: value?.attr === 'critic_rating' }"
|
||||||
|
@input="onAttrChange('critic_rating')" />
|
||||||
|
<DropdownItem text="Community Rating"
|
||||||
|
icon-class="fa fa-users"
|
||||||
|
:item-class="{ active: value?.attr === 'community_rating' }"
|
||||||
|
@input="onAttrChange('community_rating')" />
|
||||||
|
</div>
|
||||||
|
</Dropdown>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Dropdown from "@/components/elements/FloatingDropdownButton";
|
||||||
|
import DropdownItem from "@/components/elements/DropdownItem";
|
||||||
|
import Utils from '@/Utils'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
emits: ['input'],
|
||||||
|
mixins: [Utils],
|
||||||
|
components: {
|
||||||
|
Dropdown,
|
||||||
|
DropdownItem,
|
||||||
|
},
|
||||||
|
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
btnIconClass() {
|
||||||
|
return this.value?.desc ? 'fa fa-arrow-down-wide-short' : 'fa fa-arrow-up-short-wide'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
onAttrChange(attr) {
|
||||||
|
this.$emit('input', { attr, desc: !!this.value?.desc })
|
||||||
|
},
|
||||||
|
|
||||||
|
onDescChange(desc) {
|
||||||
|
this.$emit('input', { attr: this.value?.attr, desc })
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
value() {
|
||||||
|
this.setUrlArgs({
|
||||||
|
sort: this.value?.attr,
|
||||||
|
desc: this.value?.desc,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
const urlArgs = this.getUrlArgs()
|
||||||
|
const sortBy = urlArgs.sort
|
||||||
|
const desc = urlArgs.desc?.toString() === 'true'
|
||||||
|
|
||||||
|
if (sortBy || desc) {
|
||||||
|
this.$emit('input', { attr: sortBy, desc })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
unmounted() {
|
||||||
|
this.setUrlArgs({
|
||||||
|
sort: null,
|
||||||
|
desc: null,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.sort-buttons {
|
||||||
|
.floating-btn {
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sort-buttons-dropdown-body {
|
||||||
|
.title {
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
box-shadow: $border-shadow-bottom;
|
||||||
|
border-top: $default-border-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
&.active {
|
||||||
|
color: $selected-fg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
Loading…
Reference in a new issue