forked from platypush/platypush
[media
UI] Major redesign of the search results.
This commit is contained in:
parent
f7a25a478d
commit
d7093d18c5
9 changed files with 579 additions and 200 deletions
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div class="no-items-container">
|
||||
<div class="no-items fade-in">
|
||||
<div class="no-items fade-in" :class="{shadow: withShadow}">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -9,6 +9,12 @@
|
|||
<script>
|
||||
export default {
|
||||
name: "NoItems",
|
||||
props: {
|
||||
withShadow: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -45,7 +51,10 @@ export default {
|
|||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 1em;
|
||||
|
||||
&.shadow {
|
||||
box-shadow: $border-shadow-bottom;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -33,26 +33,32 @@
|
|||
@source-toggle="sources[$event] = !sources[$event]" />
|
||||
|
||||
<div class="body-container" :class="{'expanded-header': $refs.header?.filterVisible}">
|
||||
<Results :results="results" :selected-result="selectedResult" @select="onResultSelect($event)"
|
||||
@play="play" @info="$refs.mediaInfo.isVisible = true" @view="view" @download="download"
|
||||
:sources="sources" v-if="selectedView === 'search'" />
|
||||
<Results :results="results"
|
||||
:selected-result="selectedResult"
|
||||
:sources="sources"
|
||||
:loading="loading"
|
||||
@select="onResultSelect($event)"
|
||||
@play="play"
|
||||
@view="view"
|
||||
@download="download"
|
||||
v-if="selectedView === 'search'" />
|
||||
|
||||
<TorrentView :plugin-name="torrentPlugin" :is-media="true" @play="play"
|
||||
<TorrentView :plugin-name="torrentPlugin"
|
||||
:is-media="true"
|
||||
@play="play"
|
||||
v-else-if="selectedView === 'torrents'" />
|
||||
|
||||
<Browser :plugin-name="torrentPlugin" :is-media="true" :filter="browserFilter"
|
||||
@path-change="browserFilter = ''" @play="play($event)" v-else-if="selectedView === 'browser'" />
|
||||
<Browser :plugin-name="torrentPlugin"
|
||||
:is-media="true"
|
||||
:filter="browserFilter"
|
||||
@path-change="browserFilter = ''"
|
||||
@play="play($event)"
|
||||
v-else-if="selectedView === 'browser'" />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</MediaView>
|
||||
|
||||
<div class="media-info-container">
|
||||
<Modal title="Media info" ref="mediaInfo">
|
||||
<Info :item="results[selectedResult]" v-if="selectedResult != null" />
|
||||
</Modal>
|
||||
</div>
|
||||
|
||||
<div class="subtitles-container">
|
||||
<Modal title="Available subtitles" :visible="showSubtitlesModal" ref="subtitlesSelector"
|
||||
@close="showSubtitlesModal = false">
|
||||
|
@ -65,21 +71,8 @@
|
|||
</div>
|
||||
|
||||
<div class="play-url-container">
|
||||
<Modal title="Play URL" ref="playUrlModal" @open="$refs.playUrlInput.focus()">
|
||||
<form @submit.prevent="playUrl(urlPlay)">
|
||||
<div class="row">
|
||||
<label>
|
||||
Play URL (use the file:// prefix for local files)
|
||||
<input type="text" v-model="urlPlay" ref="playUrlInput" autofocus />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="row footer">
|
||||
<button type="submit" :disabled="!urlPlay?.length">
|
||||
<i class="fa fa-play"></i> Play
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<Modal title="Play URL" ref="playUrlModal" @open="onPlayUrlModalOpen">
|
||||
<UrlPlayer :value="urlPlay" @input="urlPlay = $event.target.value" @play="playUrl($event)" />
|
||||
</Modal>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -90,20 +83,33 @@
|
|||
import Loading from "@/components/Loading";
|
||||
import Modal from "@/components/Modal";
|
||||
import Utils from "@/Utils";
|
||||
|
||||
import Browser from "@/components/File/Browser";
|
||||
import Header from "@/components/panels/Media/Header";
|
||||
import MediaUtils from "@/components/Media/Utils";
|
||||
import MediaView from "@/components/Media/View";
|
||||
import Header from "@/components/panels/Media/Header";
|
||||
import Info from "@/components/panels/Media/Info";
|
||||
import Nav from "@/components/panels/Media/Nav";
|
||||
import Results from "@/components/panels/Media/Results";
|
||||
import Subtitles from "@/components/panels/Media/Subtitles";
|
||||
import TorrentView from "@/components/panels/Torrent/View";
|
||||
import Browser from "@/components/File/Browser";
|
||||
import UrlPlayer from "@/components/panels/Media/UrlPlayer";
|
||||
|
||||
export default {
|
||||
name: "Media",
|
||||
mixins: [Utils, MediaUtils],
|
||||
components: {Browser, Loading, MediaView, Header, Results, Modal, Info, Nav, TorrentView, Subtitles},
|
||||
components: {
|
||||
Browser,
|
||||
Header,
|
||||
Loading,
|
||||
MediaView,
|
||||
Modal,
|
||||
Nav,
|
||||
Results,
|
||||
Subtitles,
|
||||
TorrentView,
|
||||
UrlPlayer,
|
||||
},
|
||||
|
||||
props: {
|
||||
pluginName: {
|
||||
type: String,
|
||||
|
@ -249,6 +255,18 @@ export default {
|
|||
this.selectedPlayer.status = status
|
||||
},
|
||||
|
||||
onPlayUrlModalOpen() {
|
||||
const modal = this.$refs.playUrlModal
|
||||
this.urlPlay = ''
|
||||
modal.$nextTick(() => {
|
||||
const input = modal.$el.querySelector('input[type=text]')
|
||||
if (input) {
|
||||
input.focus()
|
||||
input.select()
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
onTorrentQueued(event) {
|
||||
this.notify({
|
||||
title: 'Torrent queued for download',
|
||||
|
@ -347,19 +365,17 @@ export default {
|
|||
if (this.selectedResult == null || this.selectedResult !== result) {
|
||||
this.selectedResult = result
|
||||
this.selectedSubtitles = null
|
||||
} else {
|
||||
this.selectedResult = null
|
||||
}
|
||||
},
|
||||
|
||||
showPlayUrlModal() {
|
||||
this.$refs.playUrlModal.show()
|
||||
this.$refs.playUrlInput.value = ''
|
||||
this.$nextTick(() => {
|
||||
this.$refs.playUrlInput.value = ''
|
||||
this.$refs.playUrlInput.focus()
|
||||
})
|
||||
},
|
||||
|
||||
async playUrl(url) {
|
||||
this.urlPlay = url
|
||||
this.loading = true
|
||||
|
||||
try {
|
||||
|
@ -452,16 +468,6 @@ export default {
|
|||
z-index: 10;
|
||||
}
|
||||
|
||||
:deep(.media-info-container) {
|
||||
.modal-container {
|
||||
.body {
|
||||
max-width: calc(100vw - 2px);
|
||||
padding: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.subtitles-container) {
|
||||
.body {
|
||||
padding: 0 !important;
|
||||
|
@ -471,45 +477,4 @@ export default {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.play-url-container) {
|
||||
.body {
|
||||
padding: 1em !important;
|
||||
}
|
||||
|
||||
form {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
input[type=text] {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
[type=submit] {
|
||||
background: initial;
|
||||
border-color: initial;
|
||||
border-radius: 1.5em;
|
||||
|
||||
&:hover {
|
||||
color: $default-hover-fg-2;
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
justify-content: right;
|
||||
padding: 0;
|
||||
margin-top: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.media-info-container) {
|
||||
.modal {
|
||||
max-width: 70em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,9 +1,16 @@
|
|||
<template>
|
||||
<div class="media-info">
|
||||
<div class="row header">
|
||||
<MediaImage :item="item" />
|
||||
<div class="image-container">
|
||||
<MediaImage :item="item" @play="$emit('play')" />
|
||||
</div>
|
||||
|
||||
<div class="title">
|
||||
<i :class="typeIcons[item.type]"
|
||||
:title="item.type"
|
||||
v-if="typeIcons[item?.type]">
|
||||
|
||||
</i>
|
||||
<a :href="item.url" target="_blank" v-if="item.url" v-text="item.title" />
|
||||
<span v-else v-text="item.title" />
|
||||
</div>
|
||||
|
@ -79,11 +86,10 @@
|
|||
<div class="right side" v-text="item.genres.join(', ')" />
|
||||
</div>
|
||||
|
||||
<div class="row" v-if="item?.channelId">
|
||||
<div class="row" v-if="channel">
|
||||
<div class="left side">Channel</div>
|
||||
<div class="right side">
|
||||
<a :href="`https://www.youtube.com/channel/${item.channelId}`" target="_blank"
|
||||
v-text="item.channelTitle || `https://www.youtube.com/channel/${item.channelId}`" />
|
||||
<a :href="channel.url" target="_blank" v-text="channel.title || channel.url" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -92,9 +98,9 @@
|
|||
<div class="right side" v-text="item.year" />
|
||||
</div>
|
||||
|
||||
<div class="row" v-if="item?.publishedAt">
|
||||
<div class="row" v-if="publishedDate">
|
||||
<div class="left side">Published at</div>
|
||||
<div class="right side" v-text="formatDate(item.publishedAt, true)" />
|
||||
<div class="right side" v-text="publishedDate" />
|
||||
</div>
|
||||
|
||||
<div class="row" v-if="item?.file">
|
||||
|
@ -140,17 +146,58 @@
|
|||
import Utils from "@/Utils";
|
||||
import MediaUtils from "@/components/Media/Utils";
|
||||
import MediaImage from "./MediaImage";
|
||||
import Icons from "./icons.json";
|
||||
|
||||
export default {
|
||||
name: "Info",
|
||||
components: {MediaImage},
|
||||
mixins: [Utils, MediaUtils],
|
||||
emits: ['play'],
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
typeIcons: Icons,
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
channel() {
|
||||
let ret = null
|
||||
if (this.item?.channelId)
|
||||
ret = {
|
||||
url: `https://www.youtube.com/channel/${this.item.channelId}`,
|
||||
}
|
||||
else if (this.item?.channel_url)
|
||||
ret = {
|
||||
url: this.item.channel_url,
|
||||
}
|
||||
|
||||
if (!ret)
|
||||
return null
|
||||
|
||||
if (this.item?.channelTitle)
|
||||
ret.title = this.item.channelTitle
|
||||
else if (this.item?.channel)
|
||||
ret.title = this.item.channel
|
||||
|
||||
return ret
|
||||
},
|
||||
|
||||
publishedDate() {
|
||||
if (this.item?.publishedAt)
|
||||
return this.formatDate(this.item.publishedAt, true)
|
||||
if (this.item?.created_at)
|
||||
return this.formatDate(this.item.created_at, true)
|
||||
|
||||
return null
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -159,10 +206,6 @@ export default {
|
|||
|
||||
.media-info {
|
||||
width: 100%;
|
||||
|
||||
@include from($tablet) {
|
||||
width: 640px;
|
||||
}
|
||||
}
|
||||
|
||||
.row {
|
||||
|
@ -231,6 +274,14 @@ export default {
|
|||
flex-direction: column;
|
||||
position: relative;
|
||||
|
||||
.image-container {
|
||||
@include from($desktop) {
|
||||
.image-container {
|
||||
width: 420px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
width: 100%;
|
||||
font-size: 1.5em;
|
||||
|
@ -238,6 +289,11 @@ export default {
|
|||
margin-top: 0.5em;
|
||||
text-align: center;
|
||||
overflow-wrap: break-word;
|
||||
|
||||
@include from($desktop) {
|
||||
flex: 1;
|
||||
padding-left: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
|
|
|
@ -0,0 +1,193 @@
|
|||
<template>
|
||||
<div class="item media-item" :class="{selected: selected}" v-if="!hidden">
|
||||
<div class="thumbnail">
|
||||
<MediaImage :item="item" @play="$emit('play')" />
|
||||
</div>
|
||||
|
||||
<div class="body">
|
||||
<div class="row title">
|
||||
<div class="col-11 left side" v-text="item.title" @click="$emit('select')" />
|
||||
<div class="col-1 right side">
|
||||
<Dropdown title="Actions" icon-class="fa fa-ellipsis-h">
|
||||
<DropdownItem icon-class="fa fa-play" text="Play" @click="$emit('play')"
|
||||
v-if="item.type !== 'torrent'" />
|
||||
<DropdownItem icon-class="fa fa-download" text="Download" @click="$emit('download')"
|
||||
v-if="item.type === 'torrent'" />
|
||||
<DropdownItem icon-class="fa fa-window-maximize" text="View in browser" @click="$emit('view')"
|
||||
v-if="item.type === 'file'" />
|
||||
<DropdownItem icon-class="fa fa-info-circle" text="Info" @click="$emit('select')" />
|
||||
</Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row subtitle" v-if="item.channel">
|
||||
<a class="channel" :href="item.channel_url" target="_blank">
|
||||
<img :src="item.channel_image" class="channel-image" v-if="item.channel_image" />
|
||||
<span class="channel-name" v-text="item.channel" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Dropdown from "@/components/elements/Dropdown";
|
||||
import DropdownItem from "@/components/elements/DropdownItem";
|
||||
import Icons from "./icons.json";
|
||||
import MediaImage from "./MediaImage";
|
||||
|
||||
export default {
|
||||
components: {Dropdown, DropdownItem, MediaImage},
|
||||
emits: ['play', 'select', 'view', 'download'],
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
|
||||
hidden: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
|
||||
selected: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
typeIcons: Icons,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "vars";
|
||||
|
||||
.media-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
cursor: initial !important;
|
||||
margin-bottom: 5px;
|
||||
border: 1px solid transparent;
|
||||
border-bottom: 1px solid transparent !important;
|
||||
|
||||
&.selected {
|
||||
box-shadow: $border-shadow-bottom;
|
||||
background: $selected-bg;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: none !important;
|
||||
box-shadow: $border-shadow;
|
||||
border-radius: 0.5em;
|
||||
}
|
||||
|
||||
.thumbnail {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.body {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
|
||||
.row {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 1.1em;
|
||||
font-weight: bold;
|
||||
padding: .5em 0;
|
||||
flex: 1;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.side {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
&.left {
|
||||
max-height: 3em;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
text-overflow: " [...]";
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
&.right {
|
||||
align-items: flex-end;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
:deep(.dropdown-container) {
|
||||
.item {
|
||||
flex-direction: row;
|
||||
box-shadow: none;
|
||||
cursor: pointer !important;
|
||||
|
||||
&:hover {
|
||||
background: $hover-bg !important;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
background: none;
|
||||
opacity: .7;
|
||||
|
||||
&:hover {
|
||||
color: $default-hover-fg-2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: .9em;
|
||||
color: $default-fg-2;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: .5em;
|
||||
flex: 1;
|
||||
|
||||
.channel {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.channel-name {
|
||||
display: inline-flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.channel-image {
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
border-radius: 50%;
|
||||
margin-right: .5em;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,5 +1,17 @@
|
|||
<template>
|
||||
<div class="image-container">
|
||||
<div class="image-container" :class="{ 'with-image': !!item?.image }">
|
||||
<div class="play-overlay" @click="$emit('play', item)">
|
||||
<i class="fas fa-play" />
|
||||
</div>
|
||||
|
||||
<span class="icon type-icon" v-if="typeIcons[item?.type]">
|
||||
<a :href="item.url" target="_blank" v-if="item.url">
|
||||
<i :class="typeIcons[item.type]" :title="item.type">
|
||||
|
||||
</i>
|
||||
</a>
|
||||
</span>
|
||||
|
||||
<img class="image" :src="item.image" :alt="item.title" v-if="item?.image" />
|
||||
<div class="image" v-else>
|
||||
<div class="inner">
|
||||
|
@ -7,7 +19,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<span class="imdb-link" v-if="item?.imdb_id">
|
||||
<span class="icon imdb-link" v-if="item?.imdb_id">
|
||||
<a :href="`https://www.imdb.com/title/${item.imdb_id}`" target="_blank">
|
||||
<i class="fab fa-imdb" />
|
||||
</a>
|
||||
|
@ -19,37 +31,67 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import Icons from "./icons.json";
|
||||
import MediaUtils from "@/components/Media/Utils";
|
||||
|
||||
export default {
|
||||
mixins: [MediaUtils],
|
||||
mixins: [Icons, MediaUtils],
|
||||
emits: ['play'],
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
typeIcons: Icons,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "vars";
|
||||
|
||||
.imdb-link {
|
||||
.icon {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
height: 1em;
|
||||
font-size: 2em;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
font-size: 30px;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
border-radius: 0.25em;
|
||||
color: $default-media-img-fg;
|
||||
|
||||
a {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: $default-media-img-fg;
|
||||
|
||||
&:hover {
|
||||
color: $default-hover-fg;
|
||||
}
|
||||
}
|
||||
|
||||
i {
|
||||
background: #ffff00;
|
||||
border-radius: 0.25em;
|
||||
margin: 2.5px;
|
||||
}
|
||||
|
||||
.fa-imdb {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
margin: 1px 2.5px 3px 2.5px;
|
||||
}
|
||||
|
||||
.fa-youtube {
|
||||
margin-left: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.imdb-link {
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.duration {
|
||||
|
@ -63,20 +105,43 @@ export default {
|
|||
border-radius: 0.25em;
|
||||
}
|
||||
|
||||
.type-icon {
|
||||
top: 0;
|
||||
left: 0;
|
||||
font-size: 25px;
|
||||
}
|
||||
|
||||
.image-container {
|
||||
width: 100%;
|
||||
min-height: 240px;
|
||||
max-width: 100%;
|
||||
min-height: 200px;
|
||||
aspect-ratio: 16 / 9;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
|
||||
img {
|
||||
@include from($tablet) {
|
||||
height: 480px;
|
||||
&.with-image {
|
||||
background: black;
|
||||
|
||||
.icon {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.play-overlay {
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.image {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
div.image {
|
||||
height: 400px;
|
||||
width: 100%;
|
||||
color: $default-media-img-fg;
|
||||
font-size: 5em;
|
||||
display: flex;
|
||||
|
@ -84,14 +149,38 @@ div.image {
|
|||
justify-content: center;
|
||||
|
||||
.inner {
|
||||
width: calc(100% - 20px);
|
||||
height: calc(100% - 20px);
|
||||
min-height: 240px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: $default-media-img-bg;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 1em;
|
||||
border-radius: 0.5em;
|
||||
}
|
||||
}
|
||||
|
||||
.play-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
border-radius: 2em;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 5em;
|
||||
color: $default-media-img-fg;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,42 +1,46 @@
|
|||
<template>
|
||||
<div class="media-results">
|
||||
<div class="no-content" v-if="!results?.length">
|
||||
<Loading v-if="loading" />
|
||||
<NoItems v-else-if="!results?.length" :with-shadow="false">
|
||||
No search results
|
||||
</NoItems>
|
||||
|
||||
<div class="media-grid" v-else>
|
||||
<Item v-for="(item, i) in results"
|
||||
:key="i"
|
||||
:item="item"
|
||||
:selected="selectedResult === i"
|
||||
:hidden="!sources[item.type]"
|
||||
@select="$emit('select', i)"
|
||||
@play="$emit('play', item)"
|
||||
@view="$emit('view', item)"
|
||||
@download="$emit('download', item)" />
|
||||
</div>
|
||||
|
||||
<div class="row item" :class="{selected: selectedResult === i, hidden: !sources[result.type]}"
|
||||
v-for="(result, i) in results" :key="i" @click="$emit('select', i)">
|
||||
<div class="col-10 left side">
|
||||
<div class="icon">
|
||||
<i :class="typeIcons[result.type]" />
|
||||
</div>
|
||||
<div class="title" v-text="result.title" />
|
||||
</div>
|
||||
|
||||
<div class="col-2 right side">
|
||||
<Dropdown title="Actions" icon-class="fa fa-ellipsis-h" @click="$emit('select', i)">
|
||||
<DropdownItem icon-class="fa fa-play" text="Play" @click="$emit('play', result)"
|
||||
v-if="result?.type !== 'torrent'" />
|
||||
<DropdownItem icon-class="fa fa-download" text="Download" @click="$emit('download', result)"
|
||||
v-if="result?.type === 'torrent'" />
|
||||
<DropdownItem icon-class="fa fa-window-maximize" text="View in browser" @click="$emit('view', result)"
|
||||
v-if="result?.type === 'file'" />
|
||||
<DropdownItem icon-class="fa fa-info" text="Info" @click="$emit('info', result)" />
|
||||
</Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
<Modal ref="infoModal" title="Media info" @close="$emit('select', null)">
|
||||
<Info :item="results[selectedResult]"
|
||||
@play="$emit('play', results[selectedResult])"
|
||||
v-if="selectedResult != null" />
|
||||
</Modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Dropdown from "@/components/elements/Dropdown";
|
||||
import DropdownItem from "@/components/elements/DropdownItem";
|
||||
import Info from "@/components/panels/Media/Info";
|
||||
import Item from "./Item";
|
||||
import Loading from "@/components/Loading";
|
||||
import Modal from "@/components/Modal";
|
||||
import NoItems from "@/components/elements/NoItems";
|
||||
|
||||
export default {
|
||||
name: "Results",
|
||||
components: {Dropdown, DropdownItem},
|
||||
emits: ['select', 'info', 'play', 'view', 'download'],
|
||||
components: {Info, Item, Loading, Modal, NoItems},
|
||||
emits: ['select', 'play', 'view', 'download'],
|
||||
props: {
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
|
||||
results: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
|
@ -52,22 +56,20 @@ export default {
|
|||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
typeIcons: {
|
||||
'file': 'fa fa-hdd',
|
||||
'torrent': 'fa fa-magnet',
|
||||
'youtube': 'fab fa-youtube',
|
||||
'plex': 'fa fa-plex',
|
||||
'jellyfin': 'fa fa-jellyfin',
|
||||
},
|
||||
}
|
||||
mounted() {
|
||||
this.$watch('selectedResult', (value) => {
|
||||
if (value == null)
|
||||
this.$refs.infoModal?.close()
|
||||
else
|
||||
this.$refs.infoModal?.show()
|
||||
})
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "src/style/items";
|
||||
@import "vars";
|
||||
|
||||
.media-results {
|
||||
width: 100%;
|
||||
|
@ -75,55 +77,37 @@ export default {
|
|||
background: $background-color;
|
||||
overflow: auto;
|
||||
|
||||
.item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.media-grid {
|
||||
width: 100%;
|
||||
display: grid;
|
||||
row-gap: 1em;
|
||||
column-gap: 1.5em;
|
||||
padding: 1em;
|
||||
|
||||
&.selected {
|
||||
background: $selected-bg;
|
||||
@include until($tablet) {
|
||||
grid-template-columns: repeat(1, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.side {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
&.left {
|
||||
overflow: hidden;
|
||||
text-overflow: " [...]";
|
||||
@media (min-width: 640px) and (max-width: $tablet) {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
&.right {
|
||||
justify-content: flex-end;
|
||||
margin-right: .5em;
|
||||
@include between($tablet, $desktop) {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
:deep(.dropdown-container) {
|
||||
.item {
|
||||
box-shadow: none;
|
||||
@include between($desktop, $widescreen) {
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
button {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
background: none;
|
||||
opacity: .7;
|
||||
|
||||
&:hover {
|
||||
color: $default-hover-fg-2;
|
||||
}
|
||||
}
|
||||
}
|
||||
@include from($widescreen) {
|
||||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
.no-content {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.icon {
|
||||
.fa-youtube {
|
||||
color: #d21;
|
||||
}
|
||||
.info-container {
|
||||
width: 100%;
|
||||
cursor: initial;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
<template>
|
||||
<form class="url-player" @submit.prevent="$emit('play', value)">
|
||||
<div class="row">
|
||||
<label>
|
||||
Play URL (use the file:// prefix for local files)
|
||||
<input type="text"
|
||||
v-model="value"
|
||||
ref="playUrlInput"
|
||||
autofocus />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="row footer">
|
||||
<button type="submit" :disabled="!value?.length">
|
||||
<i class="fa fa-play"></i> Play
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
emits: ['input', 'play'],
|
||||
props: {
|
||||
playUrl: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
value: this.playUrl,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.url-player {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
|
||||
.body {
|
||||
padding: 1em !important;
|
||||
}
|
||||
|
||||
input[type=text] {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
[type=submit] {
|
||||
background: initial;
|
||||
border-color: initial;
|
||||
border-radius: 1.5em;
|
||||
|
||||
&:hover {
|
||||
color: $default-hover-fg-2;
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
justify-content: right;
|
||||
padding: 0;
|
||||
margin-top: 1em;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"file": "fa fa-hdd",
|
||||
"torrent": "fa fa-magnet",
|
||||
"youtube": "fab fa-youtube",
|
||||
"plex": "fa fa-plex",
|
||||
"jellyfin": "fa fa-jellyfin"
|
||||
}
|
|
@ -3,3 +3,7 @@ $media-nav-width: 2.8em;
|
|||
$filter-header-height: 3em;
|
||||
$default-media-img-bg: #d0dad8;
|
||||
$default-media-img-fg: white;
|
||||
|
||||
.fa-youtube {
|
||||
color: #d21;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue