-
+
No channels found.
@@ -20,7 +20,14 @@
-
+
@@ -32,8 +39,15 @@ import Loading from "@/components/Loading";
import Utils from "@/Utils";
export default {
- emits: ['play', 'select'],
mixins: [Utils],
+ emits: [
+ 'add-to-playlist',
+ 'download',
+ 'download-audio',
+ 'play',
+ 'select',
+ ],
+
components: {
Channel,
Loading,
@@ -42,7 +56,7 @@ export default {
props: {
selectedChannel: {
- type: String,
+ type: Object,
default: null,
},
@@ -79,10 +93,18 @@ export default {
this.loading = false
}
},
+
+ initView() {
+ const args = this.getUrlArgs()
+ if (args.channel) {
+ this.$emit('select', {id: args.channel})
+ }
+ },
},
- mounted() {
- this.loadSubscriptions()
+ async mounted() {
+ await this.loadSubscriptions()
+ this.initView()
},
}
diff --git a/platypush/backend/http/webapp/src/components/panels/Media/Providers/YouTube/header.scss b/platypush/backend/http/webapp/src/components/panels/Media/Providers/YouTube/header.scss
new file mode 100644
index 0000000000..b564d9af74
--- /dev/null
+++ b/platypush/backend/http/webapp/src/components/panels/Media/Providers/YouTube/header.scss
@@ -0,0 +1,128 @@
+$banner-height: var(--banner-height);
+$info-bg: rgba(0, 0, 0, 0.5);
+$info-fg: rgba(255, 255, 255, 0.9);
+
+@include until($tablet) {
+ .playlist-container, .channel {
+ --banner-height: 5em;
+ }
+}
+
+@include from($tablet) {
+ .playlist-container, .channel {
+ --banner-height: 100px;
+ }
+}
+
+.header {
+ border-bottom: $default-border-2;
+ margin-bottom: 0.5em;
+ position: relative;
+ overflow-y: auto;
+
+ .banner {
+ height: $banner-height;
+ display: flex;
+ background-color: black;
+ justify-content: center;
+
+ img {
+ max-width: 800px;
+ max-height: 100%;
+ flex: 1;
+ }
+ }
+
+ .image {
+ height: 100px;
+ margin: -2.5em 2em 0.5em 0.5em;
+
+ img {
+ height: 100%;
+ border-radius: 50%;
+ }
+ }
+
+ .row {
+ display: flex;
+
+ @include from($desktop) {
+ flex-direction: row;
+ }
+
+ .info {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ height: 100%;
+ background-color: $info-bg;
+
+ .row {
+ width: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex: 1;
+ padding: 0 0.5em;
+ }
+ }
+ }
+
+ .info-container {
+ position: absolute;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ background-color: $info-bg;
+ color: $info-fg;
+ display: flex;
+ flex-direction: column;
+
+ a {
+ color: $info-fg !important;
+
+ &:hover {
+ color: $default-hover-fg !important;
+ }
+ }
+
+ .title {
+ letter-spacing: 0.1em;
+ color: $info-fg !important;
+ }
+
+ .n-items {
+ /* Align to the right */
+ margin-left: auto;
+ padding: 0 0.5em;
+ }
+ }
+
+ .title-container {
+ max-height: var(--banner-height);
+ overflow-y: hidden;
+ }
+
+ .title {
+ height: 100%;
+ align-content: center;
+ color: $default-fg-2;
+ font-size: 1.7em;
+ font-weight: bold;
+ margin: 0.5em 0;
+ text-decoration: dotted;
+
+ &:hover {
+ color: $default-hover-fg;
+ }
+ }
+
+ .description {
+ font-size: 0.9em;
+ margin-right: 0.5em;
+ }
+}
+
+.media-results {
+ height: calc(100% - #{$banner-height} - 1em);
+}
diff --git a/platypush/backend/http/webapp/src/components/panels/Media/Results.vue b/platypush/backend/http/webapp/src/components/panels/Media/Results.vue
index b89684a8cd..6c602fd9e2 100644
--- a/platypush/backend/http/webapp/src/components/panels/Media/Results.vue
+++ b/platypush/backend/http/webapp/src/components/panels/Media/Results.vue
@@ -4,17 +4,24 @@
+ @download="$emit('download', item)"
+ @download-audio="$emit('download-audio', item)"
+ />
@@ -29,13 +36,28 @@ import Modal from "@/components/Modal";
export default {
components: {Info, Item, Loading, Modal},
- emits: ['select', 'play', 'view', 'download', 'scroll-end'],
+ emits: [
+ 'add-to-playlist',
+ 'download',
+ 'download-audio',
+ 'open-channel',
+ 'play',
+ 'remove-from-playlist',
+ 'scroll-end',
+ 'select',
+ 'view',
+ ],
+
props: {
loading: {
type: Boolean,
default: false,
},
+ pluginName: {
+ type: String,
+ },
+
results: {
type: Array,
default: () => [],
@@ -59,6 +81,10 @@ export default {
type: Number,
default: 25,
},
+
+ playlist: {
+ default: null,
+ },
},
data() {
@@ -69,14 +95,18 @@ export default {
computed: {
visibleResults() {
- return this.results
+ let results = this.results
.filter((item) => {
- if (!this.filter)
+ if (!this.filter?.length)
return true
return item.title.toLowerCase().includes(this.filter.toLowerCase())
})
- .slice(0, this.maxResultIndex)
+
+ if (this.maxResultIndex != null)
+ results = results.slice(0, this.maxResultIndex)
+
+ return results
},
},
@@ -91,12 +121,19 @@ export default {
return
this.$emit('scroll-end')
- this.maxResultIndex += this.resultIndexStep
+
+ if (this.resultIndexStep != null)
+ this.maxResultIndex += this.resultIndexStep
},
},
mounted() {
this.$watch('selectedResult', (value) => {
+ if (value?.item_type === 'playlist' || value?.item_type === 'channel') {
+ this.$emit('select', null)
+ return
+ }
+
if (value == null)
this.$refs.infoModal?.close()
else
@@ -114,6 +151,7 @@ export default {
width: 100%;
height: 100%;
background: $background-color;
+ position: relative;
.grid {
height: 100%;
diff --git a/platypush/backend/http/webapp/src/components/panels/Music/Playlists.vue b/platypush/backend/http/webapp/src/components/panels/Music/Playlists.vue
index 5c5126d905..3aa1ac5c35 100644
--- a/platypush/backend/http/webapp/src/components/panels/Music/Playlists.vue
+++ b/platypush/backend/http/webapp/src/components/panels/Music/Playlists.vue
@@ -16,6 +16,7 @@
@add-to-queue="$emit('load-tracks', {tracks: $event, play: false})"
@add-to-queue-and-play="$emit('load-tracks', {tracks: $event, play: true})"
@back="$emit('playlist-edit', null)"
+ @download="$emit('download', $event)"
@info="$emit('info', $event)"
@move="$emit('track-move', {...$event, playlist: editedPlaylist})"
@play="$emit('load-tracks', {tracks: [$event], play: true})"
@@ -104,6 +105,7 @@ export default {
emits: [
'add-to-playlist',
+ 'download',
'info',
'load',
'load-tracks',
diff --git a/platypush/backend/http/webapp/src/router/index.js b/platypush/backend/http/webapp/src/router/index.js
index 85538f4796..8ac91e24bc 100644
--- a/platypush/backend/http/webapp/src/router/index.js
+++ b/platypush/backend/http/webapp/src/router/index.js
@@ -1,45 +1,39 @@
import { createWebHistory, createRouter } from "vue-router";
-import Dashboard from "@/views/Dashboard.vue";
-import NotFound from "@/views/NotFound";
-import Login from "@/views/Login";
-import Register from "@/views/Register";
-import Panel from "@/views/Panel";
-import Plugin from "@/views/Plugin";
const routes = [
{
path: "/",
name: "Panel",
- component: Panel,
+ component: () => import(/* webpackChunkName: "panel" */ "@/views/Panel"),
},
{
path: "/dashboard/:name",
name: "Dashboard",
- component: Dashboard,
+ component: () => import(/* webpackChunkName: "dashboard" */ "@/views/Dashboard"),
},
{
path: "/plugin/:plugin",
name: "Plugin",
- component: Plugin,
+ component: () => import(/* webpackChunkName: "plugin" */ "@/views/Plugin"),
},
{
path: "/login",
name: "Login",
- component: Login,
+ component: () => import(/* webpackChunkName: "login" */ "@/views/Login"),
},
{
path: "/register",
name: "Register",
- component: Register,
+ component: () => import(/* webpackChunkName: "register" */ "@/views/Register"),
},
{
path: "/:catchAll(.*)",
- component: NotFound,
+ component: () => import(/* webpackChunkName: "notfound" */ "@/views/NotFound"),
},
];
diff --git a/platypush/backend/http/webapp/src/style/animations.scss b/platypush/backend/http/webapp/src/style/animations.scss
index 3523a814da..4d2ef72b82 100644
--- a/platypush/backend/http/webapp/src/style/animations.scss
+++ b/platypush/backend/http/webapp/src/style/animations.scss
@@ -26,3 +26,30 @@
display: none;
}
}
+
+.glow {
+ animation-duration: 2s;
+ -webkit-animation-duration: 2s;
+ animation-fill-mode: both;
+ animation-name: glow;
+ -webkit-animation-name: glow;
+}
+
+.loop {
+ animation-iteration-count: infinite;
+ -webkit-animation-iteration-count: infinite;
+}
+
+@keyframes glow {
+ 0% {opacity: 1; box-shadow: 0 0 5px #fff;}
+ 10% {opacity: 0.9; box-shadow: 0 0 10px $active-glow-fg-1;}
+ 20% {opacity: 0.8; box-shadow: 0 0 20px $active-glow-fg-1;}
+ 30% {opacity: 0.7; box-shadow: 0 0 30px $active-glow-fg-1;}
+ 40% {opacity: 0.6; box-shadow: 0 0 40px $active-glow-fg-1;}
+ 50% {opacity: 0.5; box-shadow: 0 0 50px $active-glow-fg-1;}
+ 60% {opacity: 0.6; box-shadow: 0 0 40px $active-glow-fg-1;}
+ 70% {opacity: 0.7; box-shadow: 0 0 30px $active-glow-fg-1;}
+ 80% {opacity: 0.8; box-shadow: 0 0 20px $active-glow-fg-1;}
+ 90% {opacity: 0.9; box-shadow: 0 0 10px $active-glow-fg-1;}
+ 100% {opacity: 1; box-shadow: 0 0 5px #fff;}
+}
diff --git a/platypush/backend/http/webapp/src/style/themes/light.scss b/platypush/backend/http/webapp/src/style/themes/light.scss
index 37db58665d..87121a48e1 100644
--- a/platypush/backend/http/webapp/src/style/themes/light.scss
+++ b/platypush/backend/http/webapp/src/style/themes/light.scss
@@ -87,6 +87,8 @@ $default-link-fg: #5f7869 !default;
/// Active
$active-glow-bg-1: #d4ffe3 !default;
$active-glow-bg-2: #9cdfb0 !default;
+$active-glow-fg-1: #32b646 !default;
+$active-glow-fg-2: #5f7869 !default;
/// Hover
$default-hover-fg: #35b870 !default;
diff --git a/platypush/backend/http/webapp/src/utils/Api.vue b/platypush/backend/http/webapp/src/utils/Api.vue
index 12305fcee7..52ab708855 100644
--- a/platypush/backend/http/webapp/src/utils/Api.vue
+++ b/platypush/backend/http/webapp/src/utils/Api.vue
@@ -72,7 +72,11 @@ export default {
action: action,
args: args,
}, timeout, showError);
- }
+ },
+
+ timeout(ms) {
+ return new Promise(resolve => setTimeout(resolve, ms));
+ },
},
}
diff --git a/platypush/backend/http/webapp/src/utils/DateTime.vue b/platypush/backend/http/webapp/src/utils/DateTime.vue
index c6bb8f2716..36917f2e5d 100644
--- a/platypush/backend/http/webapp/src/utils/DateTime.vue
+++ b/platypush/backend/http/webapp/src/utils/DateTime.vue
@@ -3,13 +3,17 @@ export default {
name: "DateTime",
methods: {
formatDate(date, year=false) {
- if (typeof date === 'string')
+ if (typeof date === 'number')
+ date = new Date(date * 1000)
+ else if (typeof date === 'string')
date = new Date(Date.parse(date))
return date.toDateString().substring(0, year ? 15 : 10)
},
formatTime(date, seconds=true) {
+ if (typeof date === 'number')
+ date = new Date(date * 1000)
if (typeof date === 'string')
date = new Date(Date.parse(date))
@@ -17,6 +21,8 @@ export default {
},
formatDateTime(date, year=false, seconds=true, skipTimeIfMidnight=false) {
+ if (typeof date === 'number')
+ date = new Date(date * 1000)
if (typeof date === 'string')
date = new Date(Date.parse(date))
diff --git a/platypush/backend/http/webapp/src/utils/Types.vue b/platypush/backend/http/webapp/src/utils/Types.vue
index 809ae76de5..09b858a1c3 100644
--- a/platypush/backend/http/webapp/src/utils/Types.vue
+++ b/platypush/backend/http/webapp/src/utils/Types.vue
@@ -114,6 +114,10 @@ export default {
return true
},
+
+ round(value, decimals) {
+ return Number(Math.round(value+'e'+decimals)+'e-'+decimals);
+ },
},
}
diff --git a/platypush/backend/http/webapp/src/utils/Url.vue b/platypush/backend/http/webapp/src/utils/Url.vue
index 4a7e17d9e3..6bd00db4ac 100644
--- a/platypush/backend/http/webapp/src/utils/Url.vue
+++ b/platypush/backend/http/webapp/src/utils/Url.vue
@@ -16,7 +16,7 @@ export default {
.reduce((acc, obj) => {
const tokens = obj.split('=')
if (tokens[0]?.length)
- acc[tokens[0]] = tokens[1]
+ acc[tokens[0]] = decodeURIComponent(tokens[1])
return acc
}, {})
},
@@ -40,10 +40,24 @@ export default {
window.location.href = location
},
+ encodeValue(value) {
+ if (!value?.length || value === 'null' || value === 'undefined')
+ return ''
+
+ // Don't re-encode the value if it's already encoded
+ if (value.match(/%[0-9A-F]{2}/i))
+ return value
+
+ return encodeURIComponent(value)
+ },
+
fragmentFromArgs(args) {
return Object.entries(args)
+ .filter(
+ ([key, value]) => this.encodeValue(key)?.length && this.encodeValue(value)?.length
+ )
.map(
- ([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`
+ ([key, value]) => `${this.encodeValue(key)}=${this.encodeValue(value)}`
)
.join('&')
},