From ecd41a1f417e9555ef0913e4050f768de6862160 Mon Sep 17 00:00:00 2001 From: Fabio Manganiello Date: Tue, 18 Jun 2019 18:14:24 +0200 Subject: [PATCH] New media webplugin WIP --- .../css/source/common/elements/dropdown.scss | 6 ++ .../http/static/css/source/common/vars.scss | 1 + .../webpanel/plugins/media/devices.scss | 7 +- .../source/webpanel/plugins/media/index.scss | 3 +- .../source/webpanel/plugins/media/vars.scss | 1 + .../http/static/js/elements/dropdown.js | 3 +- .../http/static/js/plugins/media/devices.js | 85 ++++++++++++++++++- .../js/plugins/media/players/chromecast.js | 23 +++++ .../http/templates/elements/dropdown.html | 3 +- .../http/templates/plugins/media/devices.html | 9 +- platypush/plugins/media/chromecast.py | 35 ++++++-- 11 files changed, 158 insertions(+), 18 deletions(-) create mode 100644 platypush/backend/http/static/js/plugins/media/players/chromecast.js diff --git a/platypush/backend/http/static/css/source/common/elements/dropdown.scss b/platypush/backend/http/static/css/source/common/elements/dropdown.scss index e09836bbaf..8301598c83 100644 --- a/platypush/backend/http/static/css/source/common/elements/dropdown.scss +++ b/platypush/backend/http/static/css/source/common/elements/dropdown.scss @@ -11,6 +11,12 @@ .item { margin: 0 !important; padding: 1rem; + cursor: pointer; + + &.disabled { + color: $dropdown-disabled-color; + cursor: initial; + } .icon { margin: 0 .75rem; diff --git a/platypush/backend/http/static/css/source/common/vars.scss b/platypush/backend/http/static/css/source/common/vars.scss index ae0c0c9016..31e917525c 100644 --- a/platypush/backend/http/static/css/source/common/vars.scss +++ b/platypush/backend/http/static/css/source/common/vars.scss @@ -96,6 +96,7 @@ $header-bottom: $default-bottom; //// Dropdown element $dropdown-bg: rgba(241,243,242,0.9) !default; +$dropdown-disabled-color: #999 !default; $dropdown-shadow: 1px 1px 1px #bbb !default; //// Modal element diff --git a/platypush/backend/http/static/css/source/webpanel/plugins/media/devices.scss b/platypush/backend/http/static/css/source/webpanel/plugins/media/devices.scss index 186c7a83b7..97ed4c2517 100644 --- a/platypush/backend/http/static/css/source/webpanel/plugins/media/devices.scss +++ b/platypush/backend/http/static/css/source/webpanel/plugins/media/devices.scss @@ -20,13 +20,18 @@ .item { display: flex; align-items: center; - cursor: pointer; .text { text-align: left; margin-left: 2rem; } + &:first-child { + border-bottom: $default-border-3; + color: $devices-dropdown-refresh-fg; + font-size: .8em; + } + &:hover { background: $hover-bg } diff --git a/platypush/backend/http/static/css/source/webpanel/plugins/media/index.scss b/platypush/backend/http/static/css/source/webpanel/plugins/media/index.scss index fb81dc59b6..a6338a447a 100644 --- a/platypush/backend/http/static/css/source/webpanel/plugins/media/index.scss +++ b/platypush/backend/http/static/css/source/webpanel/plugins/media/index.scss @@ -20,8 +20,7 @@ .item { display: flex; - align-item: center; - cursor: pointer; + align-items: center; .text { margin-left: 1rem; diff --git a/platypush/backend/http/static/css/source/webpanel/plugins/media/vars.scss b/platypush/backend/http/static/css/source/webpanel/plugins/media/vars.scss index 32d027254b..8f1695816a 100644 --- a/platypush/backend/http/static/css/source/webpanel/plugins/media/vars.scss +++ b/platypush/backend/http/static/css/source/webpanel/plugins/media/vars.scss @@ -10,4 +10,5 @@ $control-time-color: #666; $empty-results-color: #506050; $devices-dropdown-z-index: 2; +$devices-dropdown-refresh-fg: #666; diff --git a/platypush/backend/http/static/js/elements/dropdown.js b/platypush/backend/http/static/js/elements/dropdown.js index abf2eb9034..dbba7a5bf0 100644 --- a/platypush/backend/http/static/js/elements/dropdown.js +++ b/platypush/backend/http/static/js/elements/dropdown.js @@ -18,7 +18,7 @@ Vue.component('dropdown', { methods: { clicked: function(item) { - if (item.click) { + if (item.click && !item.disabled) { item.click(); } @@ -35,7 +35,6 @@ var clickHndl = function(event) { } var element = event.target; - while (element) { if (element == openedDropdown) { return; diff --git a/platypush/backend/http/static/js/plugins/media/devices.js b/platypush/backend/http/static/js/plugins/media/devices.js index 52afa5d2e7..ee17a58056 100644 --- a/platypush/backend/http/static/js/plugins/media/devices.js +++ b/platypush/backend/http/static/js/plugins/media/devices.js @@ -1,3 +1,6 @@ +// Will be filled by dynamically loading device scripts +var mediaPlayers = {}; + Vue.component('media-devices', { template: '#tmpl-media-devices', props: { @@ -9,12 +12,19 @@ Vue.component('media-devices', { return { showDevicesMenu: false, selectedDevice: {}, + loading: false, + devices: [], }; }, computed: { - dropdownItems: function() { - var items = [ + staticItems: function() { + return [ + { + text: 'Refresh', + type: 'refresh', + icon: 'sync-alt', + }, { name: this.playerPlugin, text: this.playerPlugin, @@ -28,16 +38,42 @@ Vue.component('media-devices', { icon: 'laptop', }, ]; + }, + + dropdownItems: function() { + const items = this.staticItems.concat( + this.devices.map(dev => { + return { + name: dev.name, + text: dev.name, + type: dev.__type__, + icon: dev.icon, + iconClass: dev.iconClass, + device: dev, + }; + }) + ); const self = this; + const onClick = (item) => { return () => { + if (self.loading) { + return; + } + self.selectDevice(item); }; }; for (var i=0; i < items.length; i++) { - items[i].click = onClick(items[i]); + if (items[i].type === 'refresh') { + items[i].click = this.refreshDevices; + } else { + items[i].click = onClick(items[i]); + } + + items[i].disabled = this.loading; } return items; @@ -45,6 +81,48 @@ Vue.component('media-devices', { }, methods: { + refreshDevices: async function() { + if (this.loading) { + return; + } + + this.loading = true; + var devices; + + try { + const promises = Object.entries(mediaPlayers).map((p) => { + const player = p[0]; + const handler = p[1]; + + return new Promise((resolve, reject) => { + handler.scan().then(devs => { + for (var i=0; i < devs.length; i++) { + devs[i].__type__ = player; + + if (handler.icon) { + devs[i].icon = handler.icon instanceof Function ? handler.icon(devs[i]) : handler.icon; + } else if (handler.iconClass) { + devs[i].iconClass = handler.iconClass instanceof Function ? handler.iconClass(devs[i]) : handler.iconClass; + } + } + + resolve(devs); + }); + }); + }); + + this.devices = (await Promise.all(promises)).reduce((list, devs) => { + for (const d of devs) { + list.push(d); + } + + return list; + }, []); + } finally { + this.loading = false; + } + }, + selectDevice: function(device) { this.selectedDevice = device; this.bus.$emit('selected-device', device); @@ -57,6 +135,7 @@ Vue.component('media-devices', { created: function() { this.selectDevice(this.dropdownItems.filter(_ => _.type === 'local')[0]); + this.refreshDevices(); }, }); diff --git a/platypush/backend/http/static/js/plugins/media/players/chromecast.js b/platypush/backend/http/static/js/plugins/media/players/chromecast.js new file mode 100644 index 0000000000..ef5fad3348 --- /dev/null +++ b/platypush/backend/http/static/js/plugins/media/players/chromecast.js @@ -0,0 +1,23 @@ +mediaPlayers.chromecast = { + iconClass: function(item) { + if (item.type === 'audio') { + return 'fa fa-volume-up'; + } else { + return 'fab fa-chromecast'; + } + }, + + scan: async function() { + return await request('media.chromecast.get_chromecasts'); + }, + + status: function(device) { + }, + + play: function(item) { + }, + + stop: function() { + }, +}; + diff --git a/platypush/backend/http/templates/elements/dropdown.html b/platypush/backend/http/templates/elements/dropdown.html index 34a3339917..0b22fd5b90 100644 --- a/platypush/backend/http/templates/elements/dropdown.html +++ b/platypush/backend/http/templates/elements/dropdown.html @@ -1,8 +1,9 @@ +{% for script in utils.search_directory(static_folder + '/js/plugins/media/players', 'js', recursive=True) %} + +{% endfor %} +