[media UI] Added support for generic media providers.

This commit is contained in:
Fabio Manganiello 2023-11-13 02:25:14 +01:00
parent be28965d84
commit 60fb7bba5f
Signed by untrusted user: blacklight
GPG key ID: D90FBA7F76362774
9 changed files with 253 additions and 67 deletions

View file

@ -1,5 +1,5 @@
<template>
<div class="browser-container">
<div class="browser">
<Loading v-if="loading" />
<div class="nav" ref="nav">
@ -140,9 +140,7 @@ export default {
<style lang="scss" scoped>
@import "src/style/items";
$nav-height: 2.5em;
.browser-container {
.browser {
height: 100%;
display: flex;
flex-direction: column;
@ -154,33 +152,6 @@ $nav-height: 2.5em;
}
}
.nav {
width: 100%;
height: $nav-height;
padding: 0.5em 1em;
background: $tab-bg;
box-shadow: $border-shadow-bottom;
white-space: nowrap;
overflow: hidden;
.path {
cursor: pointer;
.token {
&:hover {
color: $default-hover-fg;
text-decoration: underline;
}
}
.separator {
font-size: 1em;
width: 1.2em;
padding: 0 1em;
}
}
}
.items {
height: calc(100% - #{$nav-height});
overflow: auto;

View file

@ -3,36 +3,45 @@
<div class="media-browser">
<Loading v-if="loading" />
<div class="media-index grid" v-else-if="!collection">
<div class="item" @click="collection = 'files'">
<div class="media-index grid" v-else-if="!mediaProvider">
<div class="item"
v-for="(provider, name) in mediaProviders"
:key="name"
@click="mediaProvider = provider">
<div class="icon">
<i class="fas fa-folder"></i>
<i v-bind="providersMetadata[name].icon"
:style="{ color: providersMetadata[name].icon?.color || 'inherit' }"
v-if="providersMetadata[name].icon" />
</div>
<div class="name">
Files
{{ providersMetadata[name].name }}
</div>
</div>
</div>
<div class="media-browser fade-in" v-else>
<Browser :is-media="true"
<div class="media-browser-body" v-else-if="mediaProvider">
<component
:is="mediaProvider"
:filter="filter"
:has-back="true"
@back="collection = null"
ref="mediaProvider"
@back="mediaProvider = null"
@path-change="$emit('path-change', $event)"
@play="$emit('play', $event)"
v-if="collection === 'files'" />
@play="$emit('play', $event)" />
</div>
</div>
</keep-alive>
</template>
<script>
import { defineAsyncComponent, shallowRef } from "vue";
import Browser from "@/components/File/Browser";
import Loading from "@/components/Loading";
import Utils from "@/Utils";
import providersMetadata from "./Providers/meta.json";
export default {
emits: ['path-change', 'play'],
mixins: [Utils],
components: {
Browser,
Loading,
@ -48,39 +57,46 @@ export default {
data() {
return {
loading: false,
collection: null,
mediaProvider: null,
mediaProviders: {},
providersMetadata: providersMetadata,
}
},
methods: {
getMediaProviderComponent(type) {
return shallowRef(
defineAsyncComponent(
() => import(`@/components/panels/Media/Providers/${type}`)
)
)
},
async refreshMediaProviders() {
const config = await this.request('config.get')
this.mediaProviders = {}
// The local File provider is always enabled
this.mediaProviders['File'] = this.getMediaProviderComponent('File')
if (config.youtube)
this.mediaProviders['YouTube'] = this.getMediaProviderComponent('YouTube')
},
},
mounted() {
this.refreshMediaProviders()
},
}
</script>
<style lang="scss" scoped>
@import "./style.scss";
.media-browser {
height: 100%;
.item {
height: 100px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
border: $default-border-2;
cursor: pointer;
&:hover {
background: $hover-bg;
}
.icon {
height: 60%;
display: inline-flex;
justify-content: center;
opacity: 0.5;
i {
font-size: 40px;
}
}
.media-browser-body {
height: 100%;
}
}
</style>

View file

@ -26,6 +26,10 @@
<span class="channel-name" v-text="item.channel" />
</a>
</div>
<div class="row creation-date" v-if="item.created_at">
{{ formatDateTime(item.created_at, true) }}
</div>
</div>
</div>
</template>
@ -35,9 +39,11 @@ import Dropdown from "@/components/elements/Dropdown";
import DropdownItem from "@/components/elements/DropdownItem";
import Icons from "./icons.json";
import MediaImage from "./MediaImage";
import Utils from "@/Utils";
export default {
components: {Dropdown, DropdownItem, MediaImage},
mixins: [Utils],
emits: ['play', 'select', 'view', 'download'],
props: {
item: {
@ -189,5 +195,11 @@ export default {
border-radius: 50%;
margin-right: .5em;
}
.creation-date {
font-size: .85em;
color: $default-fg-2;
flex: 1;
}
}
</style>

View file

@ -0,0 +1,32 @@
<template>
<div class="media-file-browser">
<Loading v-if="loading" />
<Browser :is-media="true"
:filter="filter"
:has-back="true"
@back="$emit('back')"
@path-change="$emit('path-change', $event)"
@play="$emit('play', $event)"
v-else />
</div>
</template>
<script>
import Browser from "@/components/File/Browser";
import Loading from "@/components/Loading";
import MediaProvider from "./Mixin";
export default {
mixins: [MediaProvider],
components: {
Browser,
Loading,
},
}
</script>
<style lang="scss" scoped>
.media-file-browser {
height: 100%;
}
</style>

View file

@ -0,0 +1,20 @@
<script>
import Utils from "@/Utils";
export default {
emits: ['back', 'path-change', 'play'],
mixins: [Utils],
props: {
filter: {
type: String,
default: '',
},
},
data() {
return {
loading: false,
}
},
}
</script>

View file

@ -0,0 +1,55 @@
<template>
<div class="nav">
<span class="path">
<span class="back token" title="Back" @click="$emit('back')">
<i class="fas fa-home" />
</span>
<span class="separator">
<i class="fas fa-chevron-right" />
</span>
</span>
<span class="path" v-for="(token, index) in path" :key="index">
<span class="token" :title="token.title" @click="onClick(token)">
<i class="icon" :class="icon" v-if="icon = token.icon?.['class']" />
<span v-if="token.title">{{ token.title }}</span>
</span>
<span class="separator"
v-if="(index > 0 || path.length > 1) && index < path.length - 1">
<i class="fas fa-chevron-right" />
</span>
</span>
</div>
</template>
<script>
export default {
emit: ['back'],
props: {
path: {
type: Array,
default: () => [],
},
},
methods: {
onClick(token) {
if (token.click)
token.click()
},
},
}
</script>
<style lang="scss" scoped>
@import "../style.scss";
.nav {
.path .token .icon {
margin-right: 0.5em;
}
}
</style>

View file

@ -0,0 +1,17 @@
{
"File": {
"name": "Files",
"icon": {
"class": "fas fa-folder",
"color": "#888888"
}
},
"YouTube": {
"name": "YouTube",
"icon": {
"class": "fab fa-youtube",
"color": "#FF0000"
}
}
}

View file

@ -0,0 +1,31 @@
.grid {
:deep(.item) {
height: 100px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
border: $default-border-2;
cursor: pointer;
&:hover {
background: $hover-bg;
}
.icon {
height: 60%;
display: inline-flex;
justify-content: center;
i {
font-size: 40px;
}
}
}
}
$media-nav-height: 2.5em;
:deep(.nav) {
height: $media-nav-height;
}

View file

@ -71,3 +71,35 @@ body {
scrollbar-color: $scrollbar-thumb-bg $scrollbar-track-bg;
}
// Browser navigator layout
$nav-height: 2.5em;
.browser {
:deep(.nav) {
width: 100%;
height: $nav-height;
padding: 0.5em 1em;
background: $tab-bg;
box-shadow: $border-shadow-bottom;
white-space: nowrap;
overflow: hidden;
.path {
cursor: pointer;
.token {
&:hover {
color: $default-hover-fg;
text-decoration: underline;
}
}
.separator {
font-size: 1em;
width: 1.2em;
padding: 0 1em;
}
}
}
}