forked from platypush/platypush
[media UI] Added support for generic media providers.
This commit is contained in:
parent
be28965d84
commit
60fb7bba5f
9 changed files with 253 additions and 67 deletions
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="browser-container">
|
<div class="browser">
|
||||||
<Loading v-if="loading" />
|
<Loading v-if="loading" />
|
||||||
|
|
||||||
<div class="nav" ref="nav">
|
<div class="nav" ref="nav">
|
||||||
|
@ -140,9 +140,7 @@ export default {
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import "src/style/items";
|
@import "src/style/items";
|
||||||
|
|
||||||
$nav-height: 2.5em;
|
.browser {
|
||||||
|
|
||||||
.browser-container {
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
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 {
|
.items {
|
||||||
height: calc(100% - #{$nav-height});
|
height: calc(100% - #{$nav-height});
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
|
|
@ -3,36 +3,45 @@
|
||||||
<div class="media-browser">
|
<div class="media-browser">
|
||||||
<Loading v-if="loading" />
|
<Loading v-if="loading" />
|
||||||
|
|
||||||
<div class="media-index grid" v-else-if="!collection">
|
<div class="media-index grid" v-else-if="!mediaProvider">
|
||||||
<div class="item" @click="collection = 'files'">
|
<div class="item"
|
||||||
|
v-for="(provider, name) in mediaProviders"
|
||||||
|
:key="name"
|
||||||
|
@click="mediaProvider = provider">
|
||||||
<div class="icon">
|
<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>
|
||||||
<div class="name">
|
<div class="name">
|
||||||
Files
|
{{ providersMetadata[name].name }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="media-browser fade-in" v-else>
|
<div class="media-browser-body" v-else-if="mediaProvider">
|
||||||
<Browser :is-media="true"
|
<component
|
||||||
:filter="filter"
|
:is="mediaProvider"
|
||||||
:has-back="true"
|
:filter="filter"
|
||||||
@back="collection = null"
|
ref="mediaProvider"
|
||||||
@path-change="$emit('path-change', $event)"
|
@back="mediaProvider = null"
|
||||||
@play="$emit('play', $event)"
|
@path-change="$emit('path-change', $event)"
|
||||||
v-if="collection === 'files'" />
|
@play="$emit('play', $event)" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</keep-alive>
|
</keep-alive>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { defineAsyncComponent, shallowRef } from "vue";
|
||||||
import Browser from "@/components/File/Browser";
|
import Browser from "@/components/File/Browser";
|
||||||
import Loading from "@/components/Loading";
|
import Loading from "@/components/Loading";
|
||||||
|
import Utils from "@/Utils";
|
||||||
|
import providersMetadata from "./Providers/meta.json";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
emits: ['path-change', 'play'],
|
emits: ['path-change', 'play'],
|
||||||
|
mixins: [Utils],
|
||||||
components: {
|
components: {
|
||||||
Browser,
|
Browser,
|
||||||
Loading,
|
Loading,
|
||||||
|
@ -48,39 +57,46 @@ export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
loading: false,
|
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>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@import "./style.scss";
|
||||||
|
|
||||||
.media-browser {
|
.media-browser {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
.item {
|
.media-browser-body {
|
||||||
height: 100px;
|
height: 100%;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -26,6 +26,10 @@
|
||||||
<span class="channel-name" v-text="item.channel" />
|
<span class="channel-name" v-text="item.channel" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="row creation-date" v-if="item.created_at">
|
||||||
|
{{ formatDateTime(item.created_at, true) }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -35,9 +39,11 @@ import Dropdown from "@/components/elements/Dropdown";
|
||||||
import DropdownItem from "@/components/elements/DropdownItem";
|
import DropdownItem from "@/components/elements/DropdownItem";
|
||||||
import Icons from "./icons.json";
|
import Icons from "./icons.json";
|
||||||
import MediaImage from "./MediaImage";
|
import MediaImage from "./MediaImage";
|
||||||
|
import Utils from "@/Utils";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {Dropdown, DropdownItem, MediaImage},
|
components: {Dropdown, DropdownItem, MediaImage},
|
||||||
|
mixins: [Utils],
|
||||||
emits: ['play', 'select', 'view', 'download'],
|
emits: ['play', 'select', 'view', 'download'],
|
||||||
props: {
|
props: {
|
||||||
item: {
|
item: {
|
||||||
|
@ -189,5 +195,11 @@ export default {
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
margin-right: .5em;
|
margin-right: .5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.creation-date {
|
||||||
|
font-size: .85em;
|
||||||
|
color: $default-fg-2;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"File": {
|
||||||
|
"name": "Files",
|
||||||
|
"icon": {
|
||||||
|
"class": "fas fa-folder",
|
||||||
|
"color": "#888888"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"YouTube": {
|
||||||
|
"name": "YouTube",
|
||||||
|
"icon": {
|
||||||
|
"class": "fab fa-youtube",
|
||||||
|
"color": "#FF0000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -71,3 +71,35 @@ body {
|
||||||
scrollbar-color: $scrollbar-thumb-bg $scrollbar-track-bg;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue