From 96edd759a8d33dea8dc2fd096ec8555808a94001 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 30 May 2015 00:52:33 -0700 Subject: [PATCH 001/114] media_player.cast: support thumbnail + title --- homeassistant/components/media_player/cast.py | 24 ++++++++++++------- requirements.txt | 4 ++-- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/media_player/cast.py b/homeassistant/components/media_player/cast.py index 35d9748c663..0685b31f439 100644 --- a/homeassistant/components/media_player/cast.py +++ b/homeassistant/components/media_player/cast.py @@ -15,10 +15,12 @@ except ImportError: # We will throw error later pass +from homeassistant.const import ATTR_ENTITY_PICTURE + # ATTR_MEDIA_ALBUM, ATTR_MEDIA_IMAGE_URL, -# ATTR_MEDIA_TITLE, ATTR_MEDIA_ARTIST, +# ATTR_MEDIA_ARTIST, from homeassistant.components.media_player import ( - MediaPlayerDevice, STATE_NO_APP, ATTR_MEDIA_STATE, + MediaPlayerDevice, STATE_NO_APP, ATTR_MEDIA_STATE, ATTR_MEDIA_TITLE, ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_DURATION, ATTR_MEDIA_VOLUME, MEDIA_STATE_PLAYING, MEDIA_STATE_PAUSED, MEDIA_STATE_STOPPED, MEDIA_STATE_UNKNOWN) @@ -95,7 +97,8 @@ class CastDevice(MediaPlayerDevice): def state_attributes(self): """ Returns the state attributes. """ cast_status = self.cast.status - media_status = self.cast.media_status + media_controller = self.cast.media_controller + media_status = media_controller.status state_attr = { ATTR_MEDIA_STATE: self.media_state, @@ -105,12 +108,17 @@ class CastDevice(MediaPlayerDevice): if cast_status: state_attr[ATTR_MEDIA_VOLUME] = cast_status.volume_level, - if media_status: - if media_status.content_id: - state_attr[ATTR_MEDIA_CONTENT_ID] = media_status.content_id + if media_status.content_id: + state_attr[ATTR_MEDIA_CONTENT_ID] = media_status.content_id - if media_status.duration: - state_attr[ATTR_MEDIA_DURATION] = media_status.duration + if media_status.duration: + state_attr[ATTR_MEDIA_DURATION] = media_status.duration + + if media_controller.title: + state_attr[ATTR_MEDIA_TITLE] = media_controller.title + + if media_controller.thumbnail: + state_attr[ATTR_ENTITY_PICTURE] = media_controller.thumbnail return state_attr diff --git a/requirements.txt b/requirements.txt index 6d2e032af45..c1c5403be3e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,7 +18,7 @@ phue>=0.8 ledcontroller>=1.0.7 # media_player.cast -pychromecast>=0.6 +pychromecast>=0.6.0.3 # keyboard pyuserinput>=0.1.9 @@ -41,7 +41,7 @@ pydispatcher>=2.0.5 # isy994 PyISY>=1.0.2 -# sensor.systemmonitor +# sensor.systemmonitor psutil>=2.2.1 # pushover notifications From a7b79fc8b26f7886b7f4dce59d568369a1fed0c7 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 1 Jun 2015 00:24:17 -0700 Subject: [PATCH 002/114] Initial refactor media player --- homeassistant/components/keyboard.py | 6 +- .../components/media_player/__init__.py | 339 +++++++++++++----- homeassistant/components/media_player/demo.py | 143 ++++---- homeassistant/const.py | 4 +- tests/test_component_media_player.py | 7 +- 5 files changed, 345 insertions(+), 154 deletions(-) diff --git a/homeassistant/components/keyboard.py b/homeassistant/components/keyboard.py index b4959b48055..b59fe8d39dc 100644 --- a/homeassistant/components/keyboard.py +++ b/homeassistant/components/keyboard.py @@ -8,7 +8,7 @@ import logging from homeassistant.const import ( SERVICE_VOLUME_UP, SERVICE_VOLUME_DOWN, SERVICE_VOLUME_MUTE, - SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREV_TRACK, + SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_PLAY_PAUSE) @@ -43,7 +43,7 @@ def media_next_track(hass): def media_prev_track(hass): """ Press the keyboard button for prev track. """ - hass.services.call(DOMAIN, SERVICE_MEDIA_PREV_TRACK) + hass.services.call(DOMAIN, SERVICE_MEDIA_PREVIOUS_TRACK) def setup(hass, config): @@ -79,7 +79,7 @@ def setup(hass, config): lambda service: keyboard.tap_key(keyboard.media_next_track_key)) - hass.services.register(DOMAIN, SERVICE_MEDIA_PREV_TRACK, + hass.services.register(DOMAIN, SERVICE_MEDIA_PREVIOUS_TRACK, lambda service: keyboard.tap_key(keyboard.media_prev_track_key)) diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py index 8a080e828da..1d11ef2a37d 100644 --- a/homeassistant/components/media_player/__init__.py +++ b/homeassistant/components/media_player/__init__.py @@ -10,11 +10,12 @@ from homeassistant.components import discovery from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent from homeassistant.const import ( - ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON, + STATE_OFF, STATE_UNKNOWN, STATE_PLAYING, + ATTR_ENTITY_ID, ATTR_ENTITY_PICTURE, SERVICE_TURN_OFF, SERVICE_TURN_ON, SERVICE_VOLUME_UP, SERVICE_VOLUME_DOWN, SERVICE_VOLUME_SET, SERVICE_VOLUME_MUTE, SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PAUSE, - SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREV_TRACK) + SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREVIOUS_TRACK) DOMAIN = 'media_player' DEPENDENCIES = [] @@ -28,29 +29,67 @@ DISCOVERY_PLATFORMS = { SERVICE_YOUTUBE_VIDEO = 'play_youtube_video' -STATE_NO_APP = 'idle' - -ATTR_STATE = 'state' -ATTR_OPTIONS = 'options' -ATTR_MEDIA_STATE = 'media_state' +ATTR_MEDIA_VOLUME_LEVEL = 'volume_level' +ATTR_MEDIA_VOLUME_MUTED = 'volume_muted' ATTR_MEDIA_CONTENT_ID = 'media_content_id' +ATTR_MEDIA_CONTENT_TYPE = 'media_content_type' +ATTR_MEDIA_DURATION = 'media_duration' ATTR_MEDIA_TITLE = 'media_title' ATTR_MEDIA_ARTIST = 'media_artist' ATTR_MEDIA_ALBUM = 'media_album' -ATTR_MEDIA_IMAGE_URL = 'media_image_url' -ATTR_MEDIA_VOLUME = 'media_volume' -ATTR_MEDIA_IS_VOLUME_MUTED = 'media_is_volume_muted' -ATTR_MEDIA_DURATION = 'media_duration' -ATTR_MEDIA_DATE = 'media_date' +ATTR_MEDIA_SERIES_TITLE = 'media_series_title' +ATTR_MEDIA_SEASON = 'media_season' +ATTR_MEDIA_EPISODE = 'media_episode' +ATTR_APP_ID = 'app_id' +ATTR_APP_NAME = 'app_name' +ATTR_SUPPORTED_MEDIA_COMMANDS = 'supported_media_commands' -MEDIA_STATE_UNKNOWN = 'unknown' -MEDIA_STATE_PLAYING = 'playing' -MEDIA_STATE_PAUSED = 'paused' -MEDIA_STATE_STOPPED = 'stopped' +MEDIA_TYPE_MUSIC = 'music' +MEDIA_TYPE_TVSHOW = 'tvshow' +MEDIA_TYPE_VIDEO = 'movie' +SUPPORT_PAUSE = 1 +SUPPORT_SEEK = 2 +SUPPORT_VOLUME_SET = 4 +SUPPORT_VOLUME_MUTE = 8 +SUPPORT_PREVIOUS_TRACK = 16 +SUPPORT_NEXT_TRACK = 32 +SUPPORT_YOUTUBE = 64 +SUPPORT_TURN_ON = 128 +SUPPORT_TURN_OFF = 256 YOUTUBE_COVER_URL_FORMAT = 'http://img.youtube.com/vi/{}/1.jpg' +SERVICE_TO_METHOD = { + SERVICE_TURN_ON: 'turn_on', + SERVICE_TURN_OFF: 'turn_off', + SERVICE_VOLUME_UP: 'volume_up', + SERVICE_VOLUME_DOWN: 'volume_down', + SERVICE_MEDIA_PLAY_PAUSE: 'media_play_pause', + SERVICE_MEDIA_PLAY: 'media_play', + SERVICE_MEDIA_PAUSE: 'media_pause', + SERVICE_MEDIA_NEXT_TRACK: 'media_next_track', + SERVICE_MEDIA_PREVIOUS_TRACK: 'media_previous_track', +} + +ATTR_TO_PROPERTY = { + ATTR_MEDIA_VOLUME_LEVEL: 'volume_level', + ATTR_MEDIA_VOLUME_MUTED: 'is_volume_muted', + ATTR_MEDIA_CONTENT_ID: 'media_content_id', + ATTR_MEDIA_CONTENT_TYPE: 'media_content_type', + ATTR_MEDIA_DURATION: 'media_duration', + ATTR_ENTITY_PICTURE: 'media_image_url', + ATTR_MEDIA_TITLE: 'media_title', + ATTR_MEDIA_ARTIST: 'media_artist', + ATTR_MEDIA_ALBUM: 'media_album', + ATTR_MEDIA_SERIES_TITLE: 'media_series_title', + ATTR_MEDIA_SEASON: 'media_season', + ATTR_MEDIA_EPISODE: 'media_episode', + ATTR_APP_ID: 'app_id', + ATTR_APP_NAME: 'app_name', + ATTR_SUPPORTED_MEDIA_COMMANDS: 'supported_media_commands', +} + def is_on(hass, entity_id=None): """ Returns true if specified media player entity_id is on. @@ -58,7 +97,7 @@ def is_on(hass, entity_id=None): entity_ids = [entity_id] if entity_id else hass.states.entity_ids(DOMAIN) - return any(not hass.states.is_state(entity_id, STATE_NO_APP) + return any(not hass.states.is_state(entity_id, STATE_OFF) for entity_id in entity_ids) @@ -90,21 +129,22 @@ def volume_down(hass, entity_id=None): hass.services.call(DOMAIN, SERVICE_VOLUME_DOWN, data) -def volume_mute(hass, entity_id=None): - """ Send the media player the command to toggle its mute state. """ - data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} +def mute_volume(hass, mute, entity_id=None): + """ Send the media player the command for volume down. """ + data = {ATTR_MEDIA_VOLUME_MUTED: mute} + + if entity_id: + data[ATTR_ENTITY_ID] = entity_id hass.services.call(DOMAIN, SERVICE_VOLUME_MUTE, data) -def volume_set(hass, entity_id=None, volume=None): - """ Set volume on media player. """ - data = { - key: value for key, value in [ - (ATTR_ENTITY_ID, entity_id), - (ATTR_MEDIA_VOLUME, volume), - ] if value is not None - } +def set_volume_level(hass, volume, entity_id=None): + """ Send the media player the command for volume down. """ + data = {ATTR_MEDIA_VOLUME_LEVEL: volume} + + if entity_id: + data[ATTR_ENTITY_ID] = entity_id hass.services.call(DOMAIN, SERVICE_VOLUME_SET, data) @@ -137,24 +177,11 @@ def media_next_track(hass, entity_id=None): hass.services.call(DOMAIN, SERVICE_MEDIA_NEXT_TRACK, data) -def media_prev_track(hass, entity_id=None): +def media_previous_track(hass, entity_id=None): """ Send the media player the command for prev track. """ data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} - hass.services.call(DOMAIN, SERVICE_MEDIA_PREV_TRACK, data) - - -SERVICE_TO_METHOD = { - SERVICE_TURN_ON: 'turn_on', - SERVICE_TURN_OFF: 'turn_off', - SERVICE_VOLUME_UP: 'volume_up', - SERVICE_VOLUME_DOWN: 'volume_down', - SERVICE_MEDIA_PLAY_PAUSE: 'media_play_pause', - SERVICE_MEDIA_PLAY: 'media_play', - SERVICE_MEDIA_PAUSE: 'media_pause', - SERVICE_MEDIA_NEXT_TRACK: 'media_next_track', - SERVICE_MEDIA_PREV_TRACK: 'media_prev_track', -} + hass.services.call(DOMAIN, SERVICE_MEDIA_PREVIOUS_TRACK, data) def setup(hass, config): @@ -183,17 +210,16 @@ def setup(hass, config): def volume_set_service(service, volume): """ Set specified volume on the media player. """ target_players = component.extract_from_service(service) + volume = service.data.get(ATTR_MEDIA_VOLUME_LEVEL) - for player in target_players: - player.volume_set(volume) + if volume is not None: + for player in target_players: + player.set_volume_level(volume) - if player.should_poll: - player.update_ha_state(True) + if player.should_poll: + player.update_ha_state(True) - hass.services.register(DOMAIN, SERVICE_VOLUME_SET, - lambda service: - volume_set_service( - service, service.data.get('volume'))) + hass.services.register(DOMAIN, SERVICE_VOLUME_SET, volume_set_service) def volume_mute_service(service, mute): """ Mute (true) or unmute (false) the media player. """ @@ -239,51 +265,198 @@ def setup(hass, config): class MediaPlayerDevice(Entity): """ ABC for media player devices. """ + # pylint: disable=too-many-public-methods,no-self-use + + # Implement these for your media player + + @property + def state(self): + """ State of the player. """ + return STATE_UNKNOWN + + @property + def volume_level(self): + """ Volume level of the media player (0..1). """ + return None + + @property + def is_volume_muted(self): + """ Boolean if volume is currently muted. """ + return None + + @property + def media_content_id(self): + """ Content ID of current playing media. """ + return None + + @property + def media_content_type(self): + """ Content type of current playing media. """ + return None + + @property + def media_duration(self): + """ Duration of current playing media in seconds. """ + return None + + @property + def media_image_url(self): + """ Image url of current playing media. """ + return None + + @property + def media_title(self): + """ Title of current playing media. """ + return None + + @property + def media_artist(self): + """ Artist of current playing media. (Music track only) """ + return None + + @property + def media_album(self): + """ Album of current playing media. (Music track only) """ + return None + + @property + def media_series_title(self): + """ Series title of current playing media. (TV Show only)""" + return None + + @property + def media_season(self): + """ Season of current playing media. (TV Show only) """ + return None + + @property + def media_episode(self): + """ Episode of current playing media. (TV Show only) """ + return None + + @property + def app_id(self): + """ ID of the current running app. """ + return None + + @property + def app_name(self): + """ Name of the current running app. """ + return None + + @property + def supported_media_commands(self): + """ Flags of media commands that are supported. """ + return 0 + + @property + def device_state_attributes(self): + """ Extra attributes a device wants to expose. """ + return None def turn_on(self): - """ turn media player on. """ - pass + """ turn the media player on. """ + raise NotImplementedError() def turn_off(self): - """ turn media player off. """ - pass + """ turn the media player off. """ + raise NotImplementedError() - def volume_up(self): - """ volume_up media player. """ - pass + def mute_volume(self, mute): + """ mute the volume. """ + raise NotImplementedError() - def volume_down(self): - """ volume_down media player. """ - pass - - def volume_mute(self, mute): - """ mute (true) or unmute (false) media player. """ - pass - - def volume_set(self, volume): - """ set volume level of media player. """ - pass - - def media_play_pause(self): - """ media_play_pause media player. """ - pass + def set_volume_level(self, volume): + """ set volume level, range 0..1. """ + raise NotImplementedError() def media_play(self): - """ media_play media player. """ - pass + """ Send play commmand. """ + raise NotImplementedError() def media_pause(self): - """ media_pause media player. """ - pass + """ Send pause command. """ + raise NotImplementedError() - def media_prev_track(self): - """ media_prev_track media player. """ - pass + def media_previous_track(self): + """ Send previous track command. """ + raise NotImplementedError() def media_next_track(self): - """ media_next_track media player. """ - pass + """ Send next track command. """ + raise NotImplementedError() def play_youtube(self, media_id): """ Plays a YouTube media. """ - pass + raise NotImplementedError() + + # No need to overwrite these. + @property + def support_pause(self): + """ Boolean if pause is supported. """ + return bool(self.supported_media_commands & SUPPORT_PAUSE) + + @property + def support_seek(self): + """ Boolean if seek is supported. """ + return bool(self.supported_media_commands & SUPPORT_SEEK) + + @property + def support_volume_set(self): + """ Boolean if setting volume is supported. """ + return bool(self.supported_media_commands & SUPPORT_VOLUME_SET) + + @property + def support_volume_mute(self): + """ Boolean if muting volume is supported. """ + return bool(self.supported_media_commands & SUPPORT_VOLUME_MUTE) + + @property + def support_previous_track(self): + """ Boolean if previous track command supported. """ + return bool(self.supported_media_commands & SUPPORT_PREVIOUS_TRACK) + + @property + def support_next_track(self): + """ Boolean if next track command supported. """ + return bool(self.supported_media_commands & SUPPORT_NEXT_TRACK) + + @property + def support_youtube(self): + """ Boolean if YouTube is supported. """ + return bool(self.supported_media_commands & SUPPORT_YOUTUBE) + + def volume_up(self): + """ volume_up media player. """ + if self.volume_level < 1: + self.set_volume_level(min(1, self.volume_level + .1)) + + def volume_down(self): + """ volume_down media player. """ + if self.volume_level > 0: + self.set_volume_level(max(0, self.volume_level - .1)) + + def media_play_pause(self): + """ media_play_pause media player. """ + if self.player_state == STATE_PLAYING: + self.media_pause() + else: + self.media_play() + + @property + def state_attributes(self): + """ Return the state attributes. """ + if self.state == STATE_OFF: + state_attr = {} + else: + state_attr = { + attr: getattr(self, prop) for attr, prop + in ATTR_TO_PROPERTY.items() if getattr(self, prop) + } + + device_attr = self.device_state_attributes + + if device_attr: + state_attr.update(device_attr) + + return state_attr diff --git a/homeassistant/components/media_player/demo.py b/homeassistant/components/media_player/demo.py index fdc17594b14..da2eaec69f1 100644 --- a/homeassistant/components/media_player/demo.py +++ b/homeassistant/components/media_player/demo.py @@ -5,121 +5,136 @@ homeassistant.components.media_player.demo Demo implementation of the media player. """ +from homeassistant.const import ( + STATE_PLAYING, STATE_PAUSED, STATE_OFF) + from homeassistant.components.media_player import ( - MediaPlayerDevice, STATE_NO_APP, ATTR_MEDIA_STATE, - ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_TITLE, ATTR_MEDIA_DURATION, - ATTR_MEDIA_VOLUME, MEDIA_STATE_PLAYING, MEDIA_STATE_STOPPED, - YOUTUBE_COVER_URL_FORMAT, ATTR_MEDIA_IS_VOLUME_MUTED) -from homeassistant.const import ATTR_ENTITY_PICTURE + MediaPlayerDevice, YOUTUBE_COVER_URL_FORMAT, MEDIA_TYPE_VIDEO, + SUPPORT_PAUSE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_MUTE, SUPPORT_YOUTUBE, + SUPPORT_TURN_ON, SUPPORT_TURN_OFF) # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): """ Sets up the cast platform. """ add_devices([ - DemoMediaPlayer( + DemoYoutubePlayer( 'Living Room', 'eyU3bRy2x44', '♥♥ The Best Fireplace Video (3 hours)'), - DemoMediaPlayer('Bedroom', 'kxopViU98Xo', 'Epic sax guy 10 hours') + DemoYoutubePlayer('Bedroom', 'kxopViU98Xo', 'Epic sax guy 10 hours') ]) -class DemoMediaPlayer(MediaPlayerDevice): +YOUTUBE_PLAYER_SUPPORT = \ + SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \ + SUPPORT_YOUTUBE | SUPPORT_TURN_ON | SUPPORT_TURN_OFF + + +class DemoYoutubePlayer(MediaPlayerDevice): """ A Demo media player that only supports YouTube. """ + # We only implement the methods that we support + # pylint: disable=abstract-method def __init__(self, name, youtube_id=None, media_title=None): self._name = name - self.is_playing = youtube_id is not None + self._player_state = STATE_PLAYING self.youtube_id = youtube_id - self.media_title = media_title - self.volume = 1.0 - self.is_volume_muted = False + self._media_title = media_title + self._volume_level = 1.0 + self._volume_muted = False @property def should_poll(self): - """ No polling needed for a demo componentn. """ + """ We will push an update after each command. """ return False @property def name(self): - """ Returns the name of the device. """ + """ Name of the media player. """ return self._name @property def state(self): - """ Returns the state of the device. """ - return STATE_NO_APP if self.youtube_id is None else "YouTube" + """ State of the player. """ + return self._player_state @property - def state_attributes(self): - """ Returns the state attributes. """ - if self.youtube_id is None: - return + def volume_level(self): + """ Volume level of the media player (0..1). """ + return self._volume_level - state_attr = { - ATTR_MEDIA_CONTENT_ID: self.youtube_id, - ATTR_MEDIA_TITLE: self.media_title, - ATTR_MEDIA_DURATION: 100, - ATTR_MEDIA_VOLUME: self.volume, - ATTR_MEDIA_IS_VOLUME_MUTED: self.is_volume_muted, - ATTR_ENTITY_PICTURE: - YOUTUBE_COVER_URL_FORMAT.format(self.youtube_id) - } + @property + def is_volume_muted(self): + """ Boolean if volume is currently muted. """ + return self._volume_muted - if self.is_playing: - state_attr[ATTR_MEDIA_STATE] = MEDIA_STATE_PLAYING - else: - state_attr[ATTR_MEDIA_STATE] = MEDIA_STATE_STOPPED + @property + def media_content_id(self): + """ Content ID of current playing media. """ + return self.youtube_id - return state_attr + @property + def media_content_type(self): + """ Content type of current playing media. """ + return MEDIA_TYPE_VIDEO + + @property + def media_duration(self): + """ Duration of current playing media in seconds. """ + return 360 + + @property + def media_image_url(self): + """ Image url of current playing media. """ + return YOUTUBE_COVER_URL_FORMAT.format(self.youtube_id) + + @property + def media_title(self): + """ Title of current playing media. """ + return self._media_title + + @property + def app_name(self): + """ Current running app. """ + return "YouTube" + + @property + def supported_media_commands(self): + """ Flags of media commands that are supported. """ + return YOUTUBE_PLAYER_SUPPORT def turn_on(self): - """ turn_off media player. """ - self.youtube_id = "eyU3bRy2x44" - self.is_playing = False + """ turn the media player on. """ + self._player_state = STATE_PLAYING self.update_ha_state() def turn_off(self): - """ turn_off media player. """ - self.youtube_id = None - self.is_playing = False + """ turn the media player off. """ + self._player_state = STATE_OFF self.update_ha_state() - def volume_up(self): - """ volume_up media player. """ - if self.volume < 1: - self.volume += 0.1 - self.update_ha_state() - - def volume_down(self): - """ volume_down media player. """ - if self.volume > 0: - self.volume -= 0.1 - self.update_ha_state() - - def volume_mute(self, mute): - """ mute (true) or unmute (false) media player. """ - self.is_volume_muted = mute + def mute_volume(self, mute): + """ mute the volume. """ + self._volume_muted = mute self.update_ha_state() - def media_play_pause(self): - """ media_play_pause media player. """ - self.is_playing = not self.is_playing + def set_volume_level(self, volume): + """ set volume level, range 0..1. """ + self._volume_level = volume self.update_ha_state() def media_play(self): - """ media_play media player. """ - self.is_playing = True + """ Send play commmand. """ + self._player_state = STATE_PLAYING self.update_ha_state() def media_pause(self): - """ media_pause media player. """ - self.is_playing = False + """ Send pause command. """ + self._player_state = STATE_PAUSED self.update_ha_state() def play_youtube(self, media_id): """ Plays a YouTube media. """ self.youtube_id = media_id - self.media_title = 'Demo media title' - self.is_playing = True + self._media_title = 'some YouTube video' self.update_ha_state() diff --git a/homeassistant/const.py b/homeassistant/const.py index a4ea2651d28..639f8159338 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -40,6 +40,8 @@ STATE_NOT_HOME = 'not_home' STATE_UNKNOWN = "unknown" STATE_OPEN = 'open' STATE_CLOSED = 'closed' +STATE_PLAYING = 'playing' +STATE_PAUSED = 'paused' # #### STATE AND EVENT ATTRIBUTES #### # Contains current time for a TIME_CHANGED event @@ -104,7 +106,7 @@ SERVICE_MEDIA_PLAY_PAUSE = "media_play_pause" SERVICE_MEDIA_PLAY = "media_play" SERVICE_MEDIA_PAUSE = "media_pause" SERVICE_MEDIA_NEXT_TRACK = "media_next_track" -SERVICE_MEDIA_PREV_TRACK = "media_prev_track" +SERVICE_MEDIA_PREVIOUS_TRACK = "media_previous_track" # #### API / REMOTE #### SERVER_PORT = 8123 diff --git a/tests/test_component_media_player.py b/tests/test_component_media_player.py index fdde02e5594..b7f0b847e80 100644 --- a/tests/test_component_media_player.py +++ b/tests/test_component_media_player.py @@ -10,9 +10,10 @@ import unittest import homeassistant as ha from homeassistant.const import ( + STATE_OFF, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_VOLUME_UP, SERVICE_VOLUME_DOWN, SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PAUSE, - SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREV_TRACK, ATTR_ENTITY_ID) + SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREVIOUS_TRACK, ATTR_ENTITY_ID) import homeassistant.components.media_player as media_player from helpers import mock_service @@ -29,7 +30,7 @@ class TestMediaPlayer(unittest.TestCase): self.hass = ha.HomeAssistant() self.test_entity = media_player.ENTITY_ID_FORMAT.format('living_room') - self.hass.states.set(self.test_entity, media_player.STATE_NO_APP) + self.hass.states.set(self.test_entity, STATE_OFF) self.test_entity2 = media_player.ENTITY_ID_FORMAT.format('bedroom') self.hass.states.set(self.test_entity2, "YouTube") @@ -56,7 +57,7 @@ class TestMediaPlayer(unittest.TestCase): SERVICE_MEDIA_PLAY: media_player.media_play, SERVICE_MEDIA_PAUSE: media_player.media_pause, SERVICE_MEDIA_NEXT_TRACK: media_player.media_next_track, - SERVICE_MEDIA_PREV_TRACK: media_player.media_prev_track + SERVICE_MEDIA_PREVIOUS_TRACK: media_player.media_previous_track } for service_name, service_method in services.items(): From 5eaf3d40ad6e4960da585c36455e3752b588d10d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 1 Jun 2015 23:42:19 -0700 Subject: [PATCH 003/114] Add demo music player --- .../components/media_player/__init__.py | 9 +- homeassistant/components/media_player/demo.py | 182 ++++++++++++++---- 2 files changed, 150 insertions(+), 41 deletions(-) diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py index 1d11ef2a37d..cacdc4c6891 100644 --- a/homeassistant/components/media_player/__init__.py +++ b/homeassistant/components/media_player/__init__.py @@ -37,6 +37,7 @@ ATTR_MEDIA_DURATION = 'media_duration' ATTR_MEDIA_TITLE = 'media_title' ATTR_MEDIA_ARTIST = 'media_artist' ATTR_MEDIA_ALBUM = 'media_album' +ATTR_MEDIA_TRACK = 'media_track' ATTR_MEDIA_SERIES_TITLE = 'media_series_title' ATTR_MEDIA_SEASON = 'media_season' ATTR_MEDIA_EPISODE = 'media_episode' @@ -82,6 +83,7 @@ ATTR_TO_PROPERTY = { ATTR_MEDIA_TITLE: 'media_title', ATTR_MEDIA_ARTIST: 'media_artist', ATTR_MEDIA_ALBUM: 'media_album', + ATTR_MEDIA_TRACK: 'media_track', ATTR_MEDIA_SERIES_TITLE: 'media_series_title', ATTR_MEDIA_SEASON: 'media_season', ATTR_MEDIA_EPISODE: 'media_episode', @@ -319,6 +321,11 @@ class MediaPlayerDevice(Entity): """ Album of current playing media. (Music track only) """ return None + @property + def media_track(self): + """ Track number of current playing media. (Music track only) """ + return None + @property def media_series_title(self): """ Series title of current playing media. (TV Show only)""" @@ -438,7 +445,7 @@ class MediaPlayerDevice(Entity): def media_play_pause(self): """ media_play_pause media player. """ - if self.player_state == STATE_PLAYING: + if self.state == STATE_PLAYING: self.media_pause() else: self.media_play() diff --git a/homeassistant/components/media_player/demo.py b/homeassistant/components/media_player/demo.py index da2eaec69f1..6e1d1a019bc 100644 --- a/homeassistant/components/media_player/demo.py +++ b/homeassistant/components/media_player/demo.py @@ -9,9 +9,11 @@ from homeassistant.const import ( STATE_PLAYING, STATE_PAUSED, STATE_OFF) from homeassistant.components.media_player import ( - MediaPlayerDevice, YOUTUBE_COVER_URL_FORMAT, MEDIA_TYPE_VIDEO, + MediaPlayerDevice, YOUTUBE_COVER_URL_FORMAT, + MEDIA_TYPE_VIDEO, MEDIA_TYPE_MUSIC, SUPPORT_PAUSE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_MUTE, SUPPORT_YOUTUBE, - SUPPORT_TURN_ON, SUPPORT_TURN_OFF) + SUPPORT_TURN_ON, SUPPORT_TURN_OFF, SUPPORT_PREVIOUS_TRACK, + SUPPORT_NEXT_TRACK) # pylint: disable=unused-argument @@ -21,7 +23,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None): DemoYoutubePlayer( 'Living Room', 'eyU3bRy2x44', '♥♥ The Best Fireplace Video (3 hours)'), - DemoYoutubePlayer('Bedroom', 'kxopViU98Xo', 'Epic sax guy 10 hours') + DemoYoutubePlayer('Bedroom', 'kxopViU98Xo', 'Epic sax guy 10 hours'), + DemoMusicPlayer(), ]) @@ -29,17 +32,17 @@ YOUTUBE_PLAYER_SUPPORT = \ SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \ SUPPORT_YOUTUBE | SUPPORT_TURN_ON | SUPPORT_TURN_OFF +MUSIC_PLAYER_SUPPORT = \ + SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \ + SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_PREVIOUS_TRACK | \ + SUPPORT_NEXT_TRACK -class DemoYoutubePlayer(MediaPlayerDevice): - """ A Demo media player that only supports YouTube. """ - # We only implement the methods that we support - # pylint: disable=abstract-method - def __init__(self, name, youtube_id=None, media_title=None): +class AbstractDemoPlayer(MediaPlayerDevice): + """ Base class for demo media players. """ + def __init__(self, name): self._name = name self._player_state = STATE_PLAYING - self.youtube_id = youtube_id - self._media_title = media_title self._volume_level = 1.0 self._volume_muted = False @@ -68,6 +71,47 @@ class DemoYoutubePlayer(MediaPlayerDevice): """ Boolean if volume is currently muted. """ return self._volume_muted + def turn_on(self): + """ turn the media player on. """ + self._player_state = STATE_PLAYING + self.update_ha_state() + + def turn_off(self): + """ turn the media player off. """ + self._player_state = STATE_OFF + self.update_ha_state() + + def mute_volume(self, mute): + """ mute the volume. """ + self._volume_muted = mute + self.update_ha_state() + + def set_volume_level(self, volume): + """ set volume level, range 0..1. """ + self._volume_level = volume + self.update_ha_state() + + def media_play(self): + """ Send play commmand. """ + self._player_state = STATE_PLAYING + self.update_ha_state() + + def media_pause(self): + """ Send pause command. """ + self._player_state = STATE_PAUSED + self.update_ha_state() + + +class DemoYoutubePlayer(AbstractDemoPlayer): + """ A Demo media player that only supports YouTube. """ + # We only implement the methods that we support + # pylint: disable=abstract-method + + def __init__(self, name, youtube_id=None, media_title=None): + super().__init__(name) + self.youtube_id = youtube_id + self._media_title = media_title + @property def media_content_id(self): """ Content ID of current playing media. """ @@ -103,38 +147,96 @@ class DemoYoutubePlayer(MediaPlayerDevice): """ Flags of media commands that are supported. """ return YOUTUBE_PLAYER_SUPPORT - def turn_on(self): - """ turn the media player on. """ - self._player_state = STATE_PLAYING - self.update_ha_state() - - def turn_off(self): - """ turn the media player off. """ - self._player_state = STATE_OFF - self.update_ha_state() - - def mute_volume(self, mute): - """ mute the volume. """ - self._volume_muted = mute - self.update_ha_state() - - def set_volume_level(self, volume): - """ set volume level, range 0..1. """ - self._volume_level = volume - self.update_ha_state() - - def media_play(self): - """ Send play commmand. """ - self._player_state = STATE_PLAYING - self.update_ha_state() - - def media_pause(self): - """ Send pause command. """ - self._player_state = STATE_PAUSED - self.update_ha_state() - def play_youtube(self, media_id): """ Plays a YouTube media. """ self.youtube_id = media_id self._media_title = 'some YouTube video' self.update_ha_state() + + +class DemoMusicPlayer(AbstractDemoPlayer): + """ A Demo media player that only supports YouTube. """ + # We only implement the methods that we support + # pylint: disable=abstract-method + + tracks = [ + ('Technohead', 'I Wanna Be A Hippy (Flamman & Abraxas Radio Mix)'), + ('Paul Elstak', 'Luv U More'), + ('Dune', 'Hardcore Vibes'), + ('Nakatomi', 'Children Of The Night'), + ('Party Animals', + 'Have You Ever Been Mellow? (Flamman & Abraxas Radio Mix)'), + ('Rob G.*', 'Ecstasy, You Got What I Need'), + ('Lipstick', "I'm A Raver"), + ('4 Tune Fairytales', 'My Little Fantasy (Radio Edit)'), + ('Prophet', "The Big Boys Don't Cry"), + ('Lovechild', 'All Out Of Love (DJ Weirdo & Sim Remix)'), + ('Stingray & Sonic Driver', 'Cold As Ice (El Bruto Remix)'), + ('Highlander', 'Hold Me Now (Bass-D & King Matthew Remix)'), + ('Juggernaut', 'Ruffneck Rules Da Artcore Scene (12" Edit)'), + ('Diss Reaction', 'Jiiieehaaaa '), + ('Flamman And Abraxas', 'Good To Go (Radio Mix)'), + ('Critical Mass', 'Dancing Together'), + ('Charly Lownoise & Mental Theo', 'Ultimate Sex Track (Bass-D & King Matthew Remix)'), + ] + + def __init__(self): + super().__init__('Walkman') + self._cur_track = 0 + + @property + def media_content_id(self): + """ Content ID of current playing media. """ + return 'bounzz-1' + + @property + def media_content_type(self): + """ Content type of current playing media. """ + return MEDIA_TYPE_MUSIC + + @property + def media_duration(self): + """ Duration of current playing media in seconds. """ + return 213 + + @property + def media_image_url(self): + """ Image url of current playing media. """ + return 'http://graph.facebook.com/107771475912710/picture' + + @property + def media_title(self): + """ Title of current playing media. """ + return self.tracks[self._cur_track][1] + + @property + def media_artist(self): + """ Artist of current playing media. (Music track only) """ + return self.tracks[self._cur_track][0] + + @property + def media_album(self): + """ Album of current playing media. (Music track only) """ + return "Bounzz" + + @property + def media_track(self): + """ Track number of current playing media. (Music track only) """ + return self._cur_track + 1 + + @property + def supported_media_commands(self): + """ Flags of media commands that are supported. """ + return MUSIC_PLAYER_SUPPORT + + def media_previous_track(self): + """ Send previous track command. """ + if self._cur_track > 0: + self._cur_track -= 1 + self.update_ha_state() + + def media_next_track(self): + """ Send next track command. """ + if self._cur_track < len(self.tracks)-1: + self._cur_track += 1 + self.update_ha_state() From bacff3de8dabd0f5c80058c67a2cb0c1df5a9252 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 1 Jun 2015 23:53:36 -0700 Subject: [PATCH 004/114] Add demo TV show player --- .../components/media_player/__init__.py | 2 +- homeassistant/components/media_player/demo.py | 84 ++++++++++++++++++- 2 files changed, 82 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py index cacdc4c6891..25cc7cf4438 100644 --- a/homeassistant/components/media_player/__init__.py +++ b/homeassistant/components/media_player/__init__.py @@ -59,7 +59,7 @@ SUPPORT_YOUTUBE = 64 SUPPORT_TURN_ON = 128 SUPPORT_TURN_OFF = 256 -YOUTUBE_COVER_URL_FORMAT = 'http://img.youtube.com/vi/{}/1.jpg' +YOUTUBE_COVER_URL_FORMAT = 'https://img.youtube.com/vi/{}/1.jpg' SERVICE_TO_METHOD = { SERVICE_TURN_ON: 'turn_on', diff --git a/homeassistant/components/media_player/demo.py b/homeassistant/components/media_player/demo.py index 6e1d1a019bc..85b45ef534e 100644 --- a/homeassistant/components/media_player/demo.py +++ b/homeassistant/components/media_player/demo.py @@ -10,7 +10,7 @@ from homeassistant.const import ( from homeassistant.components.media_player import ( MediaPlayerDevice, YOUTUBE_COVER_URL_FORMAT, - MEDIA_TYPE_VIDEO, MEDIA_TYPE_MUSIC, + MEDIA_TYPE_VIDEO, MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, SUPPORT_PAUSE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_MUTE, SUPPORT_YOUTUBE, SUPPORT_TURN_ON, SUPPORT_TURN_OFF, SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK) @@ -24,7 +24,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): 'Living Room', 'eyU3bRy2x44', '♥♥ The Best Fireplace Video (3 hours)'), DemoYoutubePlayer('Bedroom', 'kxopViU98Xo', 'Epic sax guy 10 hours'), - DemoMusicPlayer(), + DemoMusicPlayer(), DemoTVShowPlayer(), ]) @@ -37,6 +37,11 @@ MUSIC_PLAYER_SUPPORT = \ SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_PREVIOUS_TRACK | \ SUPPORT_NEXT_TRACK +NETFLIX_PLAYER_SUPPORT = \ + SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \ + SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_PREVIOUS_TRACK | \ + SUPPORT_NEXT_TRACK + class AbstractDemoPlayer(MediaPlayerDevice): """ Base class for demo media players. """ @@ -202,7 +207,7 @@ class DemoMusicPlayer(AbstractDemoPlayer): @property def media_image_url(self): """ Image url of current playing media. """ - return 'http://graph.facebook.com/107771475912710/picture' + return 'https://graph.facebook.com/107771475912710/picture' @property def media_title(self): @@ -240,3 +245,76 @@ class DemoMusicPlayer(AbstractDemoPlayer): if self._cur_track < len(self.tracks)-1: self._cur_track += 1 self.update_ha_state() + + +class DemoTVShowPlayer(AbstractDemoPlayer): + """ A Demo media player that only supports YouTube. """ + # We only implement the methods that we support + # pylint: disable=abstract-method + + def __init__(self): + super().__init__('Lounge room') + self._cur_episode = 1 + self._episode_count = 13 + + @property + def media_content_id(self): + """ Content ID of current playing media. """ + return 'house-of-cards-1' + + @property + def media_content_type(self): + """ Content type of current playing media. """ + return MEDIA_TYPE_TVSHOW + + @property + def media_duration(self): + """ Duration of current playing media in seconds. """ + return 3600 + + @property + def media_image_url(self): + """ Image url of current playing media. """ + return 'https://graph.facebook.com/HouseofCards/picture' + + @property + def media_title(self): + """ Title of current playing media. """ + return 'Chapter {}'.format(self._cur_episode) + + @property + def media_series_title(self): + """ Series title of current playing media. (TV Show only)""" + return 'House of Cards' + + @property + def media_season(self): + """ Season of current playing media. (TV Show only) """ + return 1 + + @property + def media_episode(self): + """ Episode of current playing media. (TV Show only) """ + return self._cur_episode + + @property + def app_name(self): + """ Current running app. """ + return "Netflix" + + @property + def supported_media_commands(self): + """ Flags of media commands that are supported. """ + return NETFLIX_PLAYER_SUPPORT + + def media_previous_track(self): + """ Send previous track command. """ + if self._cur_episode > 1: + self._cur_episode -= 1 + self.update_ha_state() + + def media_next_track(self): + """ Send next track command. """ + if self._cur_episode < self._episode_count: + self._cur_episode += 1 + self.update_ha_state() From 1585a91aecd79a7b419af9d642924869c004286b Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 1 Jun 2015 23:55:54 -0700 Subject: [PATCH 005/114] Add idle state to const.py --- homeassistant/const.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/const.py b/homeassistant/const.py index 639f8159338..a9bc5783087 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -42,6 +42,7 @@ STATE_OPEN = 'open' STATE_CLOSED = 'closed' STATE_PLAYING = 'playing' STATE_PAUSED = 'paused' +STATE_IDLE = 'idle' # #### STATE AND EVENT ATTRIBUTES #### # Contains current time for a TIME_CHANGED event From 8e6ccea08537981d420b54b7f32e979b48bef592 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 2 Jun 2015 22:58:10 -0700 Subject: [PATCH 006/114] Update media player state card --- .../cards/state-card-media_player.html | 40 ++++++++++++++++--- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/frontend/www_static/polymer/cards/state-card-media_player.html b/homeassistant/components/frontend/www_static/polymer/cards/state-card-media_player.html index c475846be8d..ab26365c298 100644 --- a/homeassistant/components/frontend/www_static/polymer/cards/state-card-media_player.html +++ b/homeassistant/components/frontend/www_static/polymer/cards/state-card-media_player.html @@ -33,8 +33,8 @@
-
[[computePrimaryText(stateObj)]]
-
[[computeSecondaryText(stateObj)]]
+
[[computePrimaryText(stateObj, isPlaying)]]
+
[[computeSecondaryText(stateObj, isPlaying)]]
@@ -42,6 +42,7 @@ @@ -9568,14 +9581,6 @@ window.hass.uiUtil.formatDate = function(dateObj) { observer: '_nameChanged' }, - /** - * Array of fully-qualitifed icon names in the iconset. - */ - iconNames: { - type: Array, - notify: true - }, - /** * The size of an individual icon. Note that icons must be square. * @@ -9590,6 +9595,18 @@ window.hass.uiUtil.formatDate = function(dateObj) { }, + /** + * Construct an array of all icon names in this iconset. + * + * @return {!Array} Array of icon names. + */ + getIconNames: function() { + this._icons = this._createIconMap(); + return Object.keys(this._icons).map(function(n) { + return this.name + ':' + n; + }, this); + }, + /** * Applies an icon to the given element. * @@ -9632,26 +9649,11 @@ window.hass.uiUtil.formatDate = function(dateObj) { /** * - * When name is changed, either register a new iconset with the included - * icons, or if there are no children, set up a meta-iconset. + * When name is changed, register iconset metadata * */ _nameChanged: function() { new Polymer.IronMeta({type: 'iconset', key: this.name, value: this}); - // icons (descendents) must exist a-priori - this._icons = this._createIconMap(); - this.iconNames = this._getIconNames(); - }, - - /** - * Array of all icon names in this iconset. - * - * @return {!Array} Array of icon names. - */ - _getIconNames: function() { - return Object.keys(this._icons).map(function(n) { - return this.name + ':' + n; - }, this); }, /** @@ -9679,6 +9681,9 @@ window.hass.uiUtil.formatDate = function(dateObj) { * matching `id`. */ _cloneIcon: function(id) { + // create the icon map on-demand, since the iconset itself has no discrete + // signal to know when it's children are fully parsed + this._icons = this._icons || this._createIconMap(); return this._prepareSvgClone(this._icons[id], this.size); }, @@ -11331,8 +11336,12 @@ window.hass.uiUtil.formatDate = function(dateObj) { @@ -13699,7 +13708,7 @@ is separate from validation, and `allowed-pattern` does not affect how the input :root { --paper-toggle-button-unchecked-bar-color: #000000; --paper-toggle-button-unchecked-button-color: var(--paper-grey-50); - --paper-toggle-button-unchecked-ink-color: var(--dark-primary-color); + --paper-toggle-button-unchecked-ink-color: var(--primary-text-color); --paper-toggle-button-checked-bar-color: var(--google-green-500); --paper-toggle-button-checked-button-color: var(--google-green-500); @@ -17827,7 +17836,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN is: 'paper-checkbox', behaviors: [ - Polymer.PaperRadioButtonBehavior + Polymer.PaperInkyFocusBehavior ], hostAttributes: { @@ -17900,12 +17909,14 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN if (checked) { return 'checked'; } + return ''; }, _computeCheckmarkClass: function(checked) { if (!checked) { return 'hidden'; } + return ''; } }) @@ -20323,7 +20334,7 @@ iron-selector:not(.narrow-layout) #main ::content [paper-drawer-toggle] { pointer-events: auto; } - .toolbar-tools > ::content [title] { + .toolbar-tools > ::content .title { @apply(--paper-font-title); @apply(--layout-flex); @@ -20354,15 +20365,15 @@ iron-selector:not(.narrow-layout) #main ::content [paper-drawer-toggle] { margin-right: -8px; } - .toolbar-tools > ::content > [title], - .toolbar-tools > ::content[select=".middle"] > [title], - .toolbar-tools > ::content[select=".bottom"] > [title] { + .toolbar-tools > ::content > .title, + .toolbar-tools > ::content[select=".middle"] > .title, + .toolbar-tools > ::content[select=".bottom"] > .title { margin-left: 56px; } - .toolbar-tools > ::content > paper-icon-button + [title], - .toolbar-tools > ::content[select=".middle"] paper-icon-button + [title], - .toolbar-tools > ::content[select=".bottom"] paper-icon-button + [title] { + .toolbar-tools > ::content > paper-icon-button + .title, + .toolbar-tools > ::content[select=".middle"] paper-icon-button + .title, + .toolbar-tools > ::content[select=".bottom"] paper-icon-button + .title { margin-left: 0; } @@ -20483,7 +20494,7 @@ iron-selector:not(.narrow-layout) #main ::content [paper-drawer-toggle] { for (var content, index = 0; content = contents[index]; index++) { var nodes = Polymer.dom(content).getDistributedNodes(); for (var node, jndex = 0; node = nodes[jndex]; jndex++) { - if (node.hasAttribute && node.hasAttribute('title')) { + if (node.classList && node.classList.contains('title')) { if (node.id) { labelledBy.push(node.id); } else { @@ -20769,7 +20780,7 @@ iron-selector:not(.narrow-layout) #main ::content [paper-drawer-toggle] { behaviors: [ Polymer.PaperButtonBehavior, - Polymer.PaperRadioButtonBehavior + Polymer.PaperInkyFocusBehavior ], properties: { @@ -21962,7 +21973,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN is: 'paper-toggle-button', behaviors: [ - Polymer.PaperRadioButtonBehavior + Polymer.PaperInkyFocusBehavior ], hostAttributes: { @@ -22368,7 +22379,11 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN }, cardTapped: function() { - uiActions.showMoreInfoDialog(this.stateObj.entityId); + // Debounce wrapper added as workaround for bug + // https://github.com/PolymerElements/iron-overlay-behavior/issues/14 + this.debounce('show-more-info-dialog', function() { + uiActions.showMoreInfoDialog(this.stateObj.entityId); + }, 1); }, }); })(); @@ -23238,7 +23253,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
- +
@@ -23289,6 +23304,50 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN observer: '_updateCached' }, + /** + * Bound to the textarea's `autocomplete` attribute. + */ + autocomplete: { + type: String, + value: 'off' + }, + + /** + * Bound to the textarea's `autofocus` attribute. + */ + autofocus: { + type: String, + value: 'off' + }, + + /** + * Bound to the textarea's `inputmode` attribute. + */ + inputmode: { + type: String + }, + + /** + * Bound to the textarea's `name` attribute. + */ + name: { + type: String + }, + + /** + * Bound to the textarea's `placeholder` attribute. + */ + placeholder: { + type: String + }, + + /** + * Bound to the textarea's `readonly` attribute. + */ + readonly: { + type: String + }, + /** * Set to true to mark the textarea as required. */ @@ -24265,6 +24324,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN background: var(--paper-dialog-background-color, --primary-background-color); color: var(--paper-dialog-color, --primary-text-color); + @apply(--layout-scroll); @apply(--paper-font-body1); @apply(--shadow-elevation-16dp); @apply(--paper-dialog); diff --git a/homeassistant/components/frontend/www_static/polymer/cards/state-card.html b/homeassistant/components/frontend/www_static/polymer/cards/state-card.html index 65e1a9e4818..a1d46c07282 100644 --- a/homeassistant/components/frontend/www_static/polymer/cards/state-card.html +++ b/homeassistant/components/frontend/www_static/polymer/cards/state-card.html @@ -41,7 +41,11 @@ }, cardTapped: function() { - uiActions.showMoreInfoDialog(this.stateObj.entityId); + // Debounce wrapper added as workaround for bug + // https://github.com/PolymerElements/iron-overlay-behavior/issues/14 + this.debounce('show-more-info-dialog', function() { + uiActions.showMoreInfoDialog(this.stateObj.entityId); + }, 1); }, }); })(); diff --git a/homeassistant/components/frontend/www_static/webcomponents-lite.min.js b/homeassistant/components/frontend/www_static/webcomponents-lite.min.js index f6acf267eba..a193b1da1ca 100644 --- a/homeassistant/components/frontend/www_static/webcomponents-lite.min.js +++ b/homeassistant/components/frontend/www_static/webcomponents-lite.min.js @@ -7,6 +7,7 @@ * Code distributed by Google as part of the polymer project is also * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt */ -// @version 0.7.2 -window.WebComponents=window.WebComponents||{},function(e){var t=e.flags||{},n="webcomponents-lite.js",r=document.querySelector('script[src*="'+n+'"]');if(!t.noOpts){if(location.search.slice(1).split("&").forEach(function(e){var n,r=e.split("=");r[0]&&(n=r[0].match(/wc-(.+)/))&&(t[n[1]]=r[1]||!0)}),r)for(var o,i=0;o=r.attributes[i];i++)"src"!==o.name&&(t[o.name]=o.value||!0);if(t.log){var a=t.log.split(",");t.log={},a.forEach(function(e){t.log[e]=!0})}else t.log={}}t.shadow=t.shadow||t.shadowdom||t.polyfill,t.shadow="native"===t.shadow?!1:t.shadow||!HTMLElement.prototype.createShadowRoot,t.register&&(window.CustomElements=window.CustomElements||{flags:{}},window.CustomElements.flags.register=t.register),e.flags=t}(WebComponents),function(e){"use strict";function t(e){return void 0!==h[e]}function n(){s.call(this),this._isInvalid=!0}function r(e){return""==e&&n.call(this),e.toLowerCase()}function o(e){var t=e.charCodeAt(0);return t>32&&127>t&&-1==[34,35,60,62,63,96].indexOf(t)?e:encodeURIComponent(e)}function i(e){var t=e.charCodeAt(0);return t>32&&127>t&&-1==[34,35,60,62,96].indexOf(t)?e:encodeURIComponent(e)}function a(e,a,s){function c(e){b.push(e)}var d=a||"scheme start",l=0,u="",_=!1,g=!1,b=[];e:for(;(e[l-1]!=f||0==l)&&!this._isInvalid;){var w=e[l];switch(d){case"scheme start":if(!w||!m.test(w)){if(a){c("Invalid scheme.");break e}u="",d="no scheme";continue}u+=w.toLowerCase(),d="scheme";break;case"scheme":if(w&&v.test(w))u+=w.toLowerCase();else{if(":"!=w){if(a){if(f==w)break e;c("Code point not allowed in scheme: "+w);break e}u="",l=0,d="no scheme";continue}if(this._scheme=u,u="",a)break e;t(this._scheme)&&(this._isRelative=!0),d="file"==this._scheme?"relative":this._isRelative&&s&&s._scheme==this._scheme?"relative or authority":this._isRelative?"authority first slash":"scheme data"}break;case"scheme data":"?"==w?(query="?",d="query"):"#"==w?(this._fragment="#",d="fragment"):f!=w&&" "!=w&&"\n"!=w&&"\r"!=w&&(this._schemeData+=o(w));break;case"no scheme":if(s&&t(s._scheme)){d="relative";continue}c("Missing scheme."),n.call(this);break;case"relative or authority":if("/"!=w||"/"!=e[l+1]){c("Expected /, got: "+w),d="relative";continue}d="authority ignore slashes";break;case"relative":if(this._isRelative=!0,"file"!=this._scheme&&(this._scheme=s._scheme),f==w){this._host=s._host,this._port=s._port,this._path=s._path.slice(),this._query=s._query,this._username=s._username,this._password=s._password;break e}if("/"==w||"\\"==w)"\\"==w&&c("\\ is an invalid code point."),d="relative slash";else if("?"==w)this._host=s._host,this._port=s._port,this._path=s._path.slice(),this._query="?",this._username=s._username,this._password=s._password,d="query";else{if("#"!=w){var y=e[l+1],E=e[l+2];("file"!=this._scheme||!m.test(w)||":"!=y&&"|"!=y||f!=E&&"/"!=E&&"\\"!=E&&"?"!=E&&"#"!=E)&&(this._host=s._host,this._port=s._port,this._username=s._username,this._password=s._password,this._path=s._path.slice(),this._path.pop()),d="relative path";continue}this._host=s._host,this._port=s._port,this._path=s._path.slice(),this._query=s._query,this._fragment="#",this._username=s._username,this._password=s._password,d="fragment"}break;case"relative slash":if("/"!=w&&"\\"!=w){"file"!=this._scheme&&(this._host=s._host,this._port=s._port,this._username=s._username,this._password=s._password),d="relative path";continue}"\\"==w&&c("\\ is an invalid code point."),d="file"==this._scheme?"file host":"authority ignore slashes";break;case"authority first slash":if("/"!=w){c("Expected '/', got: "+w),d="authority ignore slashes";continue}d="authority second slash";break;case"authority second slash":if(d="authority ignore slashes","/"!=w){c("Expected '/', got: "+w);continue}break;case"authority ignore slashes":if("/"!=w&&"\\"!=w){d="authority";continue}c("Expected authority, got: "+w);break;case"authority":if("@"==w){_&&(c("@ already seen."),u+="%40"),_=!0;for(var L=0;L>>0)+(t++ +"__")};n.prototype={set:function(t,n){var r=t[this.name];return r&&r[0]===t?r[1]=n:e(t,this.name,{value:[t,n],writable:!0}),this},get:function(e){var t;return(t=e[this.name])&&t[0]===e?t[1]:void 0},"delete":function(e){var t=e[this.name];return t&&t[0]===e?(t[0]=t[1]=void 0,!0):!1},has:function(e){var t=e[this.name];return t?t[0]===e:!1}},window.WeakMap=n}(),function(e){function t(e){w.push(e),b||(b=!0,m(r))}function n(e){return window.ShadowDOMPolyfill&&window.ShadowDOMPolyfill.wrapIfNeeded(e)||e}function r(){b=!1;var e=w;w=[],e.sort(function(e,t){return e.uid_-t.uid_});var t=!1;e.forEach(function(e){var n=e.takeRecords();o(e),n.length&&(e.callback_(n,e),t=!0)}),t&&r()}function o(e){e.nodes_.forEach(function(t){var n=v.get(t);n&&n.forEach(function(t){t.observer===e&&t.removeTransientObservers()})})}function i(e,t){for(var n=e;n;n=n.parentNode){var r=v.get(n);if(r)for(var o=0;o0){var o=n[r-1],i=p(o,e);if(i)return void(n[r-1]=i)}else t(this.observer);n[r]=e},addListeners:function(){this.addListeners_(this.target)},addListeners_:function(e){var t=this.options;t.attributes&&e.addEventListener("DOMAttrModified",this,!0),t.characterData&&e.addEventListener("DOMCharacterDataModified",this,!0),t.childList&&e.addEventListener("DOMNodeInserted",this,!0),(t.childList||t.subtree)&&e.addEventListener("DOMNodeRemoved",this,!0)},removeListeners:function(){this.removeListeners_(this.target)},removeListeners_:function(e){var t=this.options;t.attributes&&e.removeEventListener("DOMAttrModified",this,!0),t.characterData&&e.removeEventListener("DOMCharacterDataModified",this,!0),t.childList&&e.removeEventListener("DOMNodeInserted",this,!0),(t.childList||t.subtree)&&e.removeEventListener("DOMNodeRemoved",this,!0)},addTransientObserver:function(e){if(e!==this.target){this.addListeners_(e),this.transientObservedNodes.push(e);var t=v.get(e);t||v.set(e,t=[]),t.push(this)}},removeTransientObservers:function(){var e=this.transientObservedNodes;this.transientObservedNodes=[],e.forEach(function(e){this.removeListeners_(e);for(var t=v.get(e),n=0;np&&(h=s[p]);p++)a(h)?(c++,n()):(h.addEventListener("load",r),h.addEventListener("error",i));else n()}function a(e){return u?e.__loaded||e["import"]&&"loading"!==e["import"].readyState:e.__importParsed}function s(e){for(var t,n=0,r=e.length;r>n&&(t=e[n]);n++)c(t)&&d(t)}function c(e){return"link"===e.localName&&"import"===e.rel}function d(e){var t=e["import"];t?o({target:e}):(e.addEventListener("load",o),e.addEventListener("error",o))}var l="import",u=Boolean(l in document.createElement("link")),h=Boolean(window.ShadowDOMPolyfill),p=function(e){return h?ShadowDOMPolyfill.wrapIfNeeded(e):e},f=p(document),m={get:function(){var e=HTMLImports.currentScript||document.currentScript||("complete"!==document.readyState?document.scripts[document.scripts.length-1]:null);return p(e)},configurable:!0};Object.defineProperty(document,"_currentScript",m),Object.defineProperty(f,"_currentScript",m);var v=/Trident|Edge/.test(navigator.userAgent),_=v?"complete":"interactive",g="readystatechange";u&&(new MutationObserver(function(e){for(var t,n=0,r=e.length;r>n&&(t=e[n]);n++)t.addedNodes&&s(t.addedNodes)}).observe(document.head,{childList:!0}),function(){if("loading"===document.readyState)for(var e,t=document.querySelectorAll("link[rel=import]"),n=0,r=t.length;r>n&&(e=t[n]);n++)d(e)}()),t(function(e){HTMLImports.ready=!0,HTMLImports.readyTime=(new Date).getTime();var t=f.createEvent("CustomEvent");t.initCustomEvent("HTMLImportsLoaded",!0,!0,e),f.dispatchEvent(t)}),e.IMPORT_LINK_TYPE=l,e.useNative=u,e.rootDocument=f,e.whenReady=t,e.isIE=v}(HTMLImports),function(e){var t=[],n=function(e){t.push(e)},r=function(){t.forEach(function(t){t(e)})};e.addModule=n,e.initializeModules=r}(HTMLImports),HTMLImports.addModule(function(e){var t=/(url\()([^)]*)(\))/g,n=/(@import[\s]+(?!url\())([^;]*)(;)/g,r={resolveUrlsInStyle:function(e,t){var n=e.ownerDocument,r=n.createElement("a");return e.textContent=this.resolveUrlsInCssText(e.textContent,t,r),e},resolveUrlsInCssText:function(e,r,o){var i=this.replaceUrls(e,o,r,t);return i=this.replaceUrls(i,o,r,n)},replaceUrls:function(e,t,n,r){return e.replace(r,function(e,r,o,i){var a=o.replace(/["']/g,"");return n&&(a=new URL(a,n).href),t.href=a,a=t.href,r+"'"+a+"'"+i})}};e.path=r}),HTMLImports.addModule(function(e){var t={async:!0,ok:function(e){return e.status>=200&&e.status<300||304===e.status||0===e.status},load:function(n,r,o){var i=new XMLHttpRequest;return(e.flags.debug||e.flags.bust)&&(n+="?"+Math.random()),i.open("GET",n,t.async),i.addEventListener("readystatechange",function(e){if(4===i.readyState){var n=i.getResponseHeader("Location"),a=null;if(n)var a="/"===n.substr(0,1)?location.origin+n:n;r.call(o,!t.ok(i)&&i,i.response||i.responseText,a)}}),i.send(),i},loadDocument:function(e,t,n){this.load(e,t,n).responseType="document"}};e.xhr=t}),HTMLImports.addModule(function(e){var t=e.xhr,n=e.flags,r=function(e,t){this.cache={},this.onload=e,this.oncomplete=t,this.inflight=0,this.pending={}};r.prototype={addNodes:function(e){this.inflight+=e.length;for(var t,n=0,r=e.length;r>n&&(t=e[n]);n++)this.require(t);this.checkDone()},addNode:function(e){this.inflight++,this.require(e),this.checkDone()},require:function(e){var t=e.src||e.href;e.__nodeUrl=t,this.dedupe(t,e)||this.fetch(t,e)},dedupe:function(e,t){if(this.pending[e])return this.pending[e].push(t),!0;return this.cache[e]?(this.onload(e,t,this.cache[e]),this.tail(),!0):(this.pending[e]=[t],!1)},fetch:function(e,r){if(n.load&&console.log("fetch",e,r),e)if(e.match(/^data:/)){var o=e.split(","),i=o[0],a=o[1];a=i.indexOf(";base64")>-1?atob(a):decodeURIComponent(a),setTimeout(function(){this.receive(e,r,null,a)}.bind(this),0)}else{var s=function(t,n,o){this.receive(e,r,t,n,o)}.bind(this);t.load(e,s)}else setTimeout(function(){this.receive(e,r,{error:"href must be specified"},null)}.bind(this),0)},receive:function(e,t,n,r,o){this.cache[e]=r;for(var i,a=this.pending[e],s=0,c=a.length;c>s&&(i=a[s]);s++)this.onload(e,i,r,n,o),this.tail();this.pending[e]=null},tail:function(){--this.inflight,this.checkDone()},checkDone:function(){this.inflight||this.oncomplete()}},e.Loader=r}),HTMLImports.addModule(function(e){var t=function(e){this.addCallback=e,this.mo=new MutationObserver(this.handler.bind(this))};t.prototype={handler:function(e){for(var t,n=0,r=e.length;r>n&&(t=e[n]);n++)"childList"===t.type&&t.addedNodes.length&&this.addedNodes(t.addedNodes)},addedNodes:function(e){this.addCallback&&this.addCallback(e);for(var t,n=0,r=e.length;r>n&&(t=e[n]);n++)t.children&&t.children.length&&this.addedNodes(t.children)},observe:function(e){this.mo.observe(e,{childList:!0,subtree:!0})}},e.Observer=t}),HTMLImports.addModule(function(e){function t(e){return"link"===e.localName&&e.rel===l}function n(e){var t=r(e);return"data:text/javascript;charset=utf-8,"+encodeURIComponent(t)}function r(e){return e.textContent+o(e)}function o(e){var t=e.ownerDocument;t.__importedScripts=t.__importedScripts||0;var n=e.ownerDocument.baseURI,r=t.__importedScripts?"-"+t.__importedScripts:"";return t.__importedScripts++,"\n//# sourceURL="+n+r+".js\n"}function i(e){var t=e.ownerDocument.createElement("style");return t.textContent=e.textContent,a.resolveUrlsInStyle(t),t}var a=e.path,s=e.rootDocument,c=e.flags,d=e.isIE,l=e.IMPORT_LINK_TYPE,u="link[rel="+l+"]",h={documentSelectors:u,importsSelectors:[u,"link[rel=stylesheet]","style","script:not([type])",'script[type="application/javascript"]','script[type="text/javascript"]'].join(","),map:{link:"parseLink",script:"parseScript",style:"parseStyle"},dynamicElements:[],parseNext:function(){var e=this.nextToParse();e&&this.parse(e)},parse:function(e){if(this.isParsed(e))return void(c.parse&&console.log("[%s] is already parsed",e.localName));var t=this[this.map[e.localName]];t&&(this.markParsing(e),t.call(this,e))},parseDynamic:function(e,t){this.dynamicElements.push(e),t||this.parseNext()},markParsing:function(e){c.parse&&console.log("parsing",e),this.parsingElement=e},markParsingComplete:function(e){e.__importParsed=!0,this.markDynamicParsingComplete(e),e.__importElement&&(e.__importElement.__importParsed=!0,this.markDynamicParsingComplete(e.__importElement)),this.parsingElement=null,c.parse&&console.log("completed",e)},markDynamicParsingComplete:function(e){var t=this.dynamicElements.indexOf(e);t>=0&&this.dynamicElements.splice(t,1)},parseImport:function(e){if(HTMLImports.__importsParsingHook&&HTMLImports.__importsParsingHook(e),e["import"]&&(e["import"].__importParsed=!0),this.markParsingComplete(e),e.dispatchEvent(e.__resource&&!e.__error?new CustomEvent("load",{bubbles:!1}):new CustomEvent("error",{bubbles:!1})),e.__pending)for(var t;e.__pending.length;)t=e.__pending.shift(),t&&t({target:e});this.parseNext()},parseLink:function(e){t(e)?this.parseImport(e):(e.href=e.href,this.parseGeneric(e))},parseStyle:function(e){var t=e;e=i(e),t.__appliedElement=e,e.__importElement=t,this.parseGeneric(e)},parseGeneric:function(e){this.trackElement(e),this.addElementToDocument(e)},rootImportForElement:function(e){for(var t=e;t.ownerDocument.__importLink;)t=t.ownerDocument.__importLink;return t},addElementToDocument:function(e){var t=this.rootImportForElement(e.__importElement||e);t.parentNode.insertBefore(e,t)},trackElement:function(e,t){var n=this,r=function(r){t&&t(r),n.markParsingComplete(e),n.parseNext()};if(e.addEventListener("load",r),e.addEventListener("error",r),d&&"style"===e.localName){var o=!1;if(-1==e.textContent.indexOf("@import"))o=!0;else if(e.sheet){o=!0;for(var i,a=e.sheet.cssRules,s=a?a.length:0,c=0;s>c&&(i=a[c]);c++)i.type===CSSRule.IMPORT_RULE&&(o=o&&Boolean(i.styleSheet))}o&&setTimeout(function(){e.dispatchEvent(new CustomEvent("load",{bubbles:!1}))})}},parseScript:function(t){var r=document.createElement("script");r.__importElement=t,r.src=t.src?t.src:n(t),e.currentScript=t,this.trackElement(r,function(t){r.parentNode.removeChild(r),e.currentScript=null}),this.addElementToDocument(r)},nextToParse:function(){return this._mayParse=[],!this.parsingElement&&(this.nextToParseInDoc(s)||this.nextToParseDynamic())},nextToParseInDoc:function(e,n){if(e&&this._mayParse.indexOf(e)<0){this._mayParse.push(e);for(var r,o=e.querySelectorAll(this.parseSelectorsForNode(e)),i=0,a=o.length;a>i&&(r=o[i]);i++)if(!this.isParsed(r))return this.hasResource(r)?t(r)?this.nextToParseInDoc(r["import"],r):r:void 0}return n},nextToParseDynamic:function(){return this.dynamicElements[0]},parseSelectorsForNode:function(e){var t=e.ownerDocument||e;return t===s?this.documentSelectors:this.importsSelectors},isParsed:function(e){return e.__importParsed},needsDynamicParsing:function(e){return this.dynamicElements.indexOf(e)>=0},hasResource:function(e){return t(e)&&void 0===e["import"]?!1:!0}};e.parser=h,e.IMPORT_SELECTOR=u}),HTMLImports.addModule(function(e){function t(e){return n(e,a)}function n(e,t){return"link"===e.localName&&e.getAttribute("rel")===t}function r(e){return!!Object.getOwnPropertyDescriptor(e,"baseURI")}function o(e,t){var n=document.implementation.createHTMLDocument(a);n._URL=t;var o=n.createElement("base");o.setAttribute("href",t),n.baseURI||r(n)||Object.defineProperty(n,"baseURI",{value:t});var i=n.createElement("meta");return i.setAttribute("charset","utf-8"),n.head.appendChild(i),n.head.appendChild(o),n.body.innerHTML=e,window.HTMLTemplateElement&&HTMLTemplateElement.bootstrap&&HTMLTemplateElement.bootstrap(n),n}var i=e.flags,a=e.IMPORT_LINK_TYPE,s=e.IMPORT_SELECTOR,c=e.rootDocument,d=e.Loader,l=e.Observer,u=e.parser,h={documents:{},documentPreloadSelectors:s,importsPreloadSelectors:[s].join(","),loadNode:function(e){p.addNode(e)},loadSubtree:function(e){var t=this.marshalNodes(e);p.addNodes(t)},marshalNodes:function(e){return e.querySelectorAll(this.loadSelectorsForNode(e))},loadSelectorsForNode:function(e){var t=e.ownerDocument||e;return t===c?this.documentPreloadSelectors:this.importsPreloadSelectors},loaded:function(e,n,r,a,s){if(i.load&&console.log("loaded",e,n),n.__resource=r,n.__error=a,t(n)){var c=this.documents[e];void 0===c&&(c=a?null:o(r,s||e),c&&(c.__importLink=n,this.bootDocument(c)),this.documents[e]=c),n["import"]=c}u.parseNext()},bootDocument:function(e){this.loadSubtree(e),this.observer.observe(e),u.parseNext()},loadedAll:function(){u.parseNext()}},p=new d(h.loaded.bind(h),h.loadedAll.bind(h));if(h.observer=new l,!document.baseURI){var f={get:function(){var e=document.querySelector("base");return e?e.href:window.location.href},configurable:!0};Object.defineProperty(document,"baseURI",f),Object.defineProperty(c,"baseURI",f)}e.importer=h,e.importLoader=p}),HTMLImports.addModule(function(e){var t=e.parser,n=e.importer,r={added:function(e){for(var r,o,i,a,s=0,c=e.length;c>s&&(a=e[s]);s++)r||(r=a.ownerDocument,o=t.isParsed(r)),i=this.shouldLoadNode(a),i&&n.loadNode(a),this.shouldParseNode(a)&&o&&t.parseDynamic(a,i)},shouldLoadNode:function(e){return 1===e.nodeType&&o.call(e,n.loadSelectorsForNode(e))},shouldParseNode:function(e){return 1===e.nodeType&&o.call(e,t.parseSelectorsForNode(e))}};n.observer.addCallback=r.added.bind(r);var o=HTMLElement.prototype.matches||HTMLElement.prototype.matchesSelector||HTMLElement.prototype.webkitMatchesSelector||HTMLElement.prototype.mozMatchesSelector||HTMLElement.prototype.msMatchesSelector}),function(e){function t(){HTMLImports.importer.bootDocument(o)}var n=e.initializeModules,r=e.isIE;if(!e.useNative){r&&"function"!=typeof window.CustomEvent&&(window.CustomEvent=function(e,t){t=t||{};var n=document.createEvent("CustomEvent");return n.initCustomEvent(e,Boolean(t.bubbles),Boolean(t.cancelable),t.detail),n},window.CustomEvent.prototype=window.Event.prototype),n();var o=e.rootDocument;"complete"===document.readyState||"interactive"===document.readyState&&!window.attachEvent?t():document.addEventListener("DOMContentLoaded",t)}}(HTMLImports),window.CustomElements=window.CustomElements||{flags:{}},function(e){var t=e.flags,n=[],r=function(e){n.push(e)},o=function(){n.forEach(function(t){t(e)})};e.addModule=r,e.initializeModules=o,e.hasNative=Boolean(document.registerElement),e.useNative=!t.register&&e.hasNative&&!window.ShadowDOMPolyfill&&(!window.HTMLImports||HTMLImports.useNative)}(CustomElements),CustomElements.addModule(function(e){function t(e,t){n(e,function(e){return t(e)?!0:void r(e,t)}),r(e,t)}function n(e,t,r){var o=e.firstElementChild;if(!o)for(o=e.firstChild;o&&o.nodeType!==Node.ELEMENT_NODE;)o=o.nextSibling;for(;o;)t(o,r)!==!0&&n(o,t,r),o=o.nextElementSibling;return null}function r(e,n){for(var r=e.shadowRoot;r;)t(r,n),r=r.olderShadowRoot}function o(e,t){i(e,t,[])}function i(e,t,n){if(e=wrap(e),!(n.indexOf(e)>=0)){n.push(e);for(var r,o=e.querySelectorAll("link[rel="+a+"]"),s=0,c=o.length;c>s&&(r=o[s]);s++)r["import"]&&i(r["import"],t,n);t(e)}}var a=window.HTMLImports?HTMLImports.IMPORT_LINK_TYPE:"none";e.forDocumentTree=o,e.forSubtree=t}),CustomElements.addModule(function(e){function t(e){return n(e)||r(e)}function n(t){return e.upgrade(t)?!0:void s(t)}function r(e){w(e,function(e){return n(e)?!0:void 0})}function o(e){s(e),h(e)&&w(e,function(e){s(e)})}function i(e){M.push(e),L||(L=!0,setTimeout(a))}function a(){L=!1;for(var e,t=M,n=0,r=t.length;r>n&&(e=t[n]);n++)e();M=[]}function s(e){E?i(function(){c(e)}):c(e)}function c(e){e.__upgraded__&&(e.attachedCallback||e.detachedCallback)&&!e.__attached&&h(e)&&(e.__attached=!0,e.attachedCallback&&e.attachedCallback())}function d(e){l(e),w(e,function(e){l(e)})}function l(e){E?i(function(){u(e)}):u(e)}function u(e){e.__upgraded__&&(e.attachedCallback||e.detachedCallback)&&e.__attached&&!h(e)&&(e.__attached=!1,e.detachedCallback&&e.detachedCallback())}function h(e){for(var t=e,n=wrap(document);t;){if(t==n)return!0;t=t.parentNode||t.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&t.host}}function p(e){if(e.shadowRoot&&!e.shadowRoot.__watched){b.dom&&console.log("watching shadow-root for: ",e.localName);for(var t=e.shadowRoot;t;)v(t),t=t.olderShadowRoot}}function f(e){if(b.dom){var n=e[0];if(n&&"childList"===n.type&&n.addedNodes&&n.addedNodes){for(var r=n.addedNodes[0];r&&r!==document&&!r.host;)r=r.parentNode;var o=r&&(r.URL||r._URL||r.host&&r.host.localName)||"";o=o.split("/?").shift().split("/").pop()}console.group("mutations (%d) [%s]",e.length,o||"")}e.forEach(function(e){"childList"===e.type&&(T(e.addedNodes,function(e){e.localName&&t(e)}),T(e.removedNodes,function(e){e.localName&&d(e)}))}),b.dom&&console.groupEnd()}function m(e){for(e=wrap(e),e||(e=wrap(document));e.parentNode;)e=e.parentNode;var t=e.__observer;t&&(f(t.takeRecords()),a())}function v(e){if(!e.__observer){var t=new MutationObserver(f);t.observe(e,{childList:!0,subtree:!0}),e.__observer=t}}function _(e){e=wrap(e),b.dom&&console.group("upgradeDocument: ",e.baseURI.split("/").pop()),t(e),v(e),b.dom&&console.groupEnd()}function g(e){y(e,_)}var b=e.flags,w=e.forSubtree,y=e.forDocumentTree,E=!window.MutationObserver||window.MutationObserver===window.JsMutationObserver;e.hasPolyfillMutations=E;var L=!1,M=[],T=Array.prototype.forEach.call.bind(Array.prototype.forEach),N=Element.prototype.createShadowRoot;N&&(Element.prototype.createShadowRoot=function(){var e=N.call(this);return CustomElements.watchShadow(this),e}),e.watchShadow=p,e.upgradeDocumentTree=g,e.upgradeSubtree=r,e.upgradeAll=t,e.attachedNode=o,e.takeRecords=m}),CustomElements.addModule(function(e){function t(t){if(!t.__upgraded__&&t.nodeType===Node.ELEMENT_NODE){var r=t.getAttribute("is"),o=e.getRegisteredDefinition(r||t.localName);if(o){if(r&&o.tag==t.localName)return n(t,o);if(!r&&!o["extends"])return n(t,o)}}}function n(t,n){return a.upgrade&&console.group("upgrade:",t.localName),n.is&&t.setAttribute("is",n.is),r(t,n),t.__upgraded__=!0,i(t),e.attachedNode(t),e.upgradeSubtree(t),a.upgrade&&console.groupEnd(),t}function r(e,t){Object.__proto__?e.__proto__=t.prototype:(o(e,t.prototype,t["native"]),e.__proto__=t.prototype)}function o(e,t,n){for(var r={},o=t;o!==n&&o!==HTMLElement.prototype;){for(var i,a=Object.getOwnPropertyNames(o),s=0;i=a[s];s++)r[i]||(Object.defineProperty(e,i,Object.getOwnPropertyDescriptor(o,i)),r[i]=1);o=Object.getPrototypeOf(o)}}function i(e){e.createdCallback&&e.createdCallback()}var a=e.flags;e.upgrade=t,e.upgradeWithDefinition=n,e.implementPrototype=r}),CustomElements.addModule(function(e){function t(t,r){var c=r||{};if(!t)throw new Error("document.registerElement: first argument `name` must not be empty");if(t.indexOf("-")<0)throw new Error("document.registerElement: first argument ('name') must contain a dash ('-'). Argument provided was '"+String(t)+"'.");if(o(t))throw new Error("Failed to execute 'registerElement' on 'Document': Registration failed for type '"+String(t)+"'. The type name is invalid.");if(d(t))throw new Error("DuplicateDefinitionError: a type with name '"+String(t)+"' is already registered");return c.prototype||(c.prototype=Object.create(HTMLElement.prototype)),c.__name=t.toLowerCase(),c.lifecycle=c.lifecycle||{},c.ancestry=i(c["extends"]),a(c),s(c),n(c.prototype),l(c.__name,c),c.ctor=u(c),c.ctor.prototype=c.prototype,c.prototype.constructor=c.ctor,e.ready&&_(document),c.ctor}function n(e){if(!e.setAttribute._polyfilled){var t=e.setAttribute;e.setAttribute=function(e,n){r.call(this,e,n,t)};var n=e.removeAttribute;e.removeAttribute=function(e){r.call(this,e,null,n)},e.setAttribute._polyfilled=!0}}function r(e,t,n){e=e.toLowerCase();var r=this.getAttribute(e);n.apply(this,arguments);var o=this.getAttribute(e);this.attributeChangedCallback&&o!==r&&this.attributeChangedCallback(e,r,o)}function o(e){for(var t=0;t=0&&w(r,HTMLElement),r)}function f(e,t){var n=e[t];e[t]=function(){var e=n.apply(this,arguments);return g(e),e}}var m,v=e.isIE11OrOlder,_=e.upgradeDocumentTree,g=e.upgradeAll,b=e.upgradeWithDefinition,w=e.implementPrototype,y=e.useNative,E=["annotation-xml","color-profile","font-face","font-face-src","font-face-uri","font-face-format","font-face-name","missing-glyph"],L={},M="http://www.w3.org/1999/xhtml",T=document.createElement.bind(document),N=document.createElementNS.bind(document);m=Object.__proto__||y?function(e,t){return e instanceof t}:function(e,t){for(var n=e;n;){if(n===t.prototype)return!0;n=n.__proto__}return!1},f(Node.prototype,"cloneNode"),f(document,"importNode"),v&&!function(){var e=document.importNode;document.importNode=function(){var t=e.apply(document,arguments);if(t.nodeType==t.DOCUMENT_FRAGMENT_NODE){var n=document.createDocumentFragment();return n.appendChild(t),n}return t}}(),document.registerElement=t,document.createElement=p,document.createElementNS=h,e.registry=L,e["instanceof"]=m,e.reservedTagList=E,e.getRegisteredDefinition=d,document.register=document.registerElement}),function(e){function t(){a(wrap(document)),window.HTMLImports&&(HTMLImports.__importsParsingHook=function(e){a(wrap(e["import"]))}),CustomElements.ready=!0,setTimeout(function(){CustomElements.readyTime=Date.now(),window.HTMLImports&&(CustomElements.elapsed=CustomElements.readyTime-HTMLImports.readyTime),document.dispatchEvent(new CustomEvent("WebComponentsReady",{bubbles:!0}))})}var n=e.useNative,r=e.initializeModules,o=/Trident/.test(navigator.userAgent);if(n){var i=function(){};e.watchShadow=i,e.upgrade=i,e.upgradeAll=i,e.upgradeDocumentTree=i,e.upgradeSubtree=i,e.takeRecords=i,e["instanceof"]=function(e,t){return e instanceof t}}else r();var a=e.upgradeDocumentTree;if(window.wrap||(window.ShadowDOMPolyfill?(window.wrap=ShadowDOMPolyfill.wrapIfNeeded,window.unwrap=ShadowDOMPolyfill.unwrapIfNeeded):window.wrap=window.unwrap=function(e){return e}),o&&"function"!=typeof window.CustomEvent&&(window.CustomEvent=function(e,t){t=t||{};var n=document.createEvent("CustomEvent");return n.initCustomEvent(e,Boolean(t.bubbles),Boolean(t.cancelable),t.detail),n},window.CustomEvent.prototype=window.Event.prototype),"complete"===document.readyState||e.flags.eager)t();else if("interactive"!==document.readyState||window.attachEvent||window.HTMLImports&&!window.HTMLImports.ready){var s=window.HTMLImports&&!HTMLImports.ready?"HTMLImportsLoaded":"DOMContentLoaded";window.addEventListener(s,t)}else t();e.isIE11OrOlder=o}(window.CustomElements),"undefined"==typeof HTMLTemplateElement&&!function(){var e="template";HTMLTemplateElement=function(){},HTMLTemplateElement.prototype=Object.create(HTMLElement.prototype),HTMLTemplateElement.decorate=function(e){e.content||(e.content=e.ownerDocument.createDocumentFragment());for(var t;t=e.firstChild;)e.content.appendChild(t)},HTMLTemplateElement.bootstrap=function(t){for(var n,r=t.querySelectorAll(e),o=0,i=r.length;i>o&&(n=r[o]);o++)HTMLTemplateElement.decorate(n)},addEventListener("DOMContentLoaded",function(){HTMLTemplateElement.bootstrap(document)});var t=document.createElement;document.createElement=function(){"use strict";var e=t.apply(document,arguments);return"template"==e.localName&&HTMLTemplateElement.decorate(e),e}}(),function(e){var t=document.createElement("style");t.textContent="body {transition: opacity ease-in 0.2s; } \nbody[unresolved] {opacity: 0; display: block; overflow: hidden; position: relative; } \n";var n=document.querySelector("head");n.insertBefore(t,n.firstChild)}(window.WebComponents); \ No newline at end of file +// @version 0.7.3 +window.WebComponents=window.WebComponents||{},function(e){var t=e.flags||{},n="webcomponents-lite.js",r=document.querySelector('script[src*="'+n+'"]');if(!t.noOpts){if(location.search.slice(1).split("&").forEach(function(e){var n,r=e.split("=");r[0]&&(n=r[0].match(/wc-(.+)/))&&(t[n[1]]=r[1]||!0)}),r)for(var o,i=0;o=r.attributes[i];i++)"src"!==o.name&&(t[o.name]=o.value||!0);if(t.log){var a=t.log.split(",");t.log={},a.forEach(function(e){t.log[e]=!0})}else t.log={}}t.shadow=t.shadow||t.shadowdom||t.polyfill,t.shadow="native"===t.shadow?!1:t.shadow||!HTMLElement.prototype.createShadowRoot,t.register&&(window.CustomElements=window.CustomElements||{flags:{}},window.CustomElements.flags.register=t.register),e.flags=t}(window.WebComponents),function(e){"use strict";function t(e){return void 0!==h[e]}function n(){s.call(this),this._isInvalid=!0}function r(e){return""==e&&n.call(this),e.toLowerCase()}function o(e){var t=e.charCodeAt(0);return t>32&&127>t&&-1==[34,35,60,62,63,96].indexOf(t)?e:encodeURIComponent(e)}function i(e){var t=e.charCodeAt(0);return t>32&&127>t&&-1==[34,35,60,62,96].indexOf(t)?e:encodeURIComponent(e)}function a(e,a,s){function c(e){b.push(e)}var d=a||"scheme start",l=0,u="",_=!1,g=!1,b=[];e:for(;(e[l-1]!=f||0==l)&&!this._isInvalid;){var w=e[l];switch(d){case"scheme start":if(!w||!m.test(w)){if(a){c("Invalid scheme.");break e}u="",d="no scheme";continue}u+=w.toLowerCase(),d="scheme";break;case"scheme":if(w&&v.test(w))u+=w.toLowerCase();else{if(":"!=w){if(a){if(f==w)break e;c("Code point not allowed in scheme: "+w);break e}u="",l=0,d="no scheme";continue}if(this._scheme=u,u="",a)break e;t(this._scheme)&&(this._isRelative=!0),d="file"==this._scheme?"relative":this._isRelative&&s&&s._scheme==this._scheme?"relative or authority":this._isRelative?"authority first slash":"scheme data"}break;case"scheme data":"?"==w?(this._query="?",d="query"):"#"==w?(this._fragment="#",d="fragment"):f!=w&&" "!=w&&"\n"!=w&&"\r"!=w&&(this._schemeData+=o(w));break;case"no scheme":if(s&&t(s._scheme)){d="relative";continue}c("Missing scheme."),n.call(this);break;case"relative or authority":if("/"!=w||"/"!=e[l+1]){c("Expected /, got: "+w),d="relative";continue}d="authority ignore slashes";break;case"relative":if(this._isRelative=!0,"file"!=this._scheme&&(this._scheme=s._scheme),f==w){this._host=s._host,this._port=s._port,this._path=s._path.slice(),this._query=s._query,this._username=s._username,this._password=s._password;break e}if("/"==w||"\\"==w)"\\"==w&&c("\\ is an invalid code point."),d="relative slash";else if("?"==w)this._host=s._host,this._port=s._port,this._path=s._path.slice(),this._query="?",this._username=s._username,this._password=s._password,d="query";else{if("#"!=w){var y=e[l+1],E=e[l+2];("file"!=this._scheme||!m.test(w)||":"!=y&&"|"!=y||f!=E&&"/"!=E&&"\\"!=E&&"?"!=E&&"#"!=E)&&(this._host=s._host,this._port=s._port,this._username=s._username,this._password=s._password,this._path=s._path.slice(),this._path.pop()),d="relative path";continue}this._host=s._host,this._port=s._port,this._path=s._path.slice(),this._query=s._query,this._fragment="#",this._username=s._username,this._password=s._password,d="fragment"}break;case"relative slash":if("/"!=w&&"\\"!=w){"file"!=this._scheme&&(this._host=s._host,this._port=s._port,this._username=s._username,this._password=s._password),d="relative path";continue}"\\"==w&&c("\\ is an invalid code point."),d="file"==this._scheme?"file host":"authority ignore slashes";break;case"authority first slash":if("/"!=w){c("Expected '/', got: "+w),d="authority ignore slashes";continue}d="authority second slash";break;case"authority second slash":if(d="authority ignore slashes","/"!=w){c("Expected '/', got: "+w);continue}break;case"authority ignore slashes":if("/"!=w&&"\\"!=w){d="authority";continue}c("Expected authority, got: "+w);break;case"authority":if("@"==w){_&&(c("@ already seen."),u+="%40"),_=!0;for(var L=0;L>>0)+(t++ +"__")};n.prototype={set:function(t,n){var r=t[this.name];return r&&r[0]===t?r[1]=n:e(t,this.name,{value:[t,n],writable:!0}),this},get:function(e){var t;return(t=e[this.name])&&t[0]===e?t[1]:void 0},"delete":function(e){var t=e[this.name];return t&&t[0]===e?(t[0]=t[1]=void 0,!0):!1},has:function(e){var t=e[this.name];return t?t[0]===e:!1}},window.WeakMap=n}(),function(e){function t(e){w.push(e),b||(b=!0,m(r))}function n(e){return window.ShadowDOMPolyfill&&window.ShadowDOMPolyfill.wrapIfNeeded(e)||e}function r(){b=!1;var e=w;w=[],e.sort(function(e,t){return e.uid_-t.uid_});var t=!1;e.forEach(function(e){var n=e.takeRecords();o(e),n.length&&(e.callback_(n,e),t=!0)}),t&&r()}function o(e){e.nodes_.forEach(function(t){var n=v.get(t);n&&n.forEach(function(t){t.observer===e&&t.removeTransientObservers()})})}function i(e,t){for(var n=e;n;n=n.parentNode){var r=v.get(n);if(r)for(var o=0;o0){var o=n[r-1],i=p(o,e);if(i)return void(n[r-1]=i)}else t(this.observer);n[r]=e},addListeners:function(){this.addListeners_(this.target)},addListeners_:function(e){var t=this.options;t.attributes&&e.addEventListener("DOMAttrModified",this,!0),t.characterData&&e.addEventListener("DOMCharacterDataModified",this,!0),t.childList&&e.addEventListener("DOMNodeInserted",this,!0),(t.childList||t.subtree)&&e.addEventListener("DOMNodeRemoved",this,!0)},removeListeners:function(){this.removeListeners_(this.target)},removeListeners_:function(e){var t=this.options;t.attributes&&e.removeEventListener("DOMAttrModified",this,!0),t.characterData&&e.removeEventListener("DOMCharacterDataModified",this,!0),t.childList&&e.removeEventListener("DOMNodeInserted",this,!0),(t.childList||t.subtree)&&e.removeEventListener("DOMNodeRemoved",this,!0)},addTransientObserver:function(e){if(e!==this.target){this.addListeners_(e),this.transientObservedNodes.push(e);var t=v.get(e);t||v.set(e,t=[]),t.push(this)}},removeTransientObservers:function(){var e=this.transientObservedNodes;this.transientObservedNodes=[],e.forEach(function(e){this.removeListeners_(e);for(var t=v.get(e),n=0;np&&(h=s[p]);p++)a(h)?(c++,n()):(h.addEventListener("load",r),h.addEventListener("error",i));else n()}function a(e){return u?e.__loaded||e["import"]&&"loading"!==e["import"].readyState:e.__importParsed}function s(e){for(var t,n=0,r=e.length;r>n&&(t=e[n]);n++)c(t)&&d(t)}function c(e){return"link"===e.localName&&"import"===e.rel}function d(e){var t=e["import"];t?o({target:e}):(e.addEventListener("load",o),e.addEventListener("error",o))}var l="import",u=Boolean(l in document.createElement("link")),h=Boolean(window.ShadowDOMPolyfill),p=function(e){return h?ShadowDOMPolyfill.wrapIfNeeded(e):e},f=p(document),m={get:function(){var e=HTMLImports.currentScript||document.currentScript||("complete"!==document.readyState?document.scripts[document.scripts.length-1]:null);return p(e)},configurable:!0};Object.defineProperty(document,"_currentScript",m),Object.defineProperty(f,"_currentScript",m);var v=/Trident|Edge/.test(navigator.userAgent),_=v?"complete":"interactive",g="readystatechange";u&&(new MutationObserver(function(e){for(var t,n=0,r=e.length;r>n&&(t=e[n]);n++)t.addedNodes&&s(t.addedNodes)}).observe(document.head,{childList:!0}),function(){if("loading"===document.readyState)for(var e,t=document.querySelectorAll("link[rel=import]"),n=0,r=t.length;r>n&&(e=t[n]);n++)d(e)}()),t(function(e){HTMLImports.ready=!0,HTMLImports.readyTime=(new Date).getTime();var t=f.createEvent("CustomEvent");t.initCustomEvent("HTMLImportsLoaded",!0,!0,e),f.dispatchEvent(t)}),e.IMPORT_LINK_TYPE=l,e.useNative=u,e.rootDocument=f,e.whenReady=t,e.isIE=v}(window.HTMLImports),function(e){var t=[],n=function(e){t.push(e)},r=function(){t.forEach(function(t){t(e)})};e.addModule=n,e.initializeModules=r}(window.HTMLImports),window.HTMLImports.addModule(function(e){var t=/(url\()([^)]*)(\))/g,n=/(@import[\s]+(?!url\())([^;]*)(;)/g,r={resolveUrlsInStyle:function(e,t){var n=e.ownerDocument,r=n.createElement("a");return e.textContent=this.resolveUrlsInCssText(e.textContent,t,r),e},resolveUrlsInCssText:function(e,r,o){var i=this.replaceUrls(e,o,r,t);return i=this.replaceUrls(i,o,r,n)},replaceUrls:function(e,t,n,r){return e.replace(r,function(e,r,o,i){var a=o.replace(/["']/g,"");return n&&(a=new URL(a,n).href),t.href=a,a=t.href,r+"'"+a+"'"+i})}};e.path=r}),window.HTMLImports.addModule(function(e){var t={async:!0,ok:function(e){return e.status>=200&&e.status<300||304===e.status||0===e.status},load:function(n,r,o){var i=new XMLHttpRequest;return(e.flags.debug||e.flags.bust)&&(n+="?"+Math.random()),i.open("GET",n,t.async),i.addEventListener("readystatechange",function(e){if(4===i.readyState){var n=i.getResponseHeader("Location"),a=null;if(n)var a="/"===n.substr(0,1)?location.origin+n:n;r.call(o,!t.ok(i)&&i,i.response||i.responseText,a)}}),i.send(),i},loadDocument:function(e,t,n){this.load(e,t,n).responseType="document"}};e.xhr=t}),window.HTMLImports.addModule(function(e){var t=e.xhr,n=e.flags,r=function(e,t){this.cache={},this.onload=e,this.oncomplete=t,this.inflight=0,this.pending={}};r.prototype={addNodes:function(e){this.inflight+=e.length;for(var t,n=0,r=e.length;r>n&&(t=e[n]);n++)this.require(t);this.checkDone()},addNode:function(e){this.inflight++,this.require(e),this.checkDone()},require:function(e){var t=e.src||e.href;e.__nodeUrl=t,this.dedupe(t,e)||this.fetch(t,e)},dedupe:function(e,t){if(this.pending[e])return this.pending[e].push(t),!0;return this.cache[e]?(this.onload(e,t,this.cache[e]),this.tail(),!0):(this.pending[e]=[t],!1)},fetch:function(e,r){if(n.load&&console.log("fetch",e,r),e)if(e.match(/^data:/)){var o=e.split(","),i=o[0],a=o[1];a=i.indexOf(";base64")>-1?atob(a):decodeURIComponent(a),setTimeout(function(){this.receive(e,r,null,a)}.bind(this),0)}else{var s=function(t,n,o){this.receive(e,r,t,n,o)}.bind(this);t.load(e,s)}else setTimeout(function(){this.receive(e,r,{error:"href must be specified"},null)}.bind(this),0)},receive:function(e,t,n,r,o){this.cache[e]=r;for(var i,a=this.pending[e],s=0,c=a.length;c>s&&(i=a[s]);s++)this.onload(e,i,r,n,o),this.tail();this.pending[e]=null},tail:function(){--this.inflight,this.checkDone()},checkDone:function(){this.inflight||this.oncomplete()}},e.Loader=r}),window.HTMLImports.addModule(function(e){var t=function(e){this.addCallback=e,this.mo=new MutationObserver(this.handler.bind(this))};t.prototype={handler:function(e){for(var t,n=0,r=e.length;r>n&&(t=e[n]);n++)"childList"===t.type&&t.addedNodes.length&&this.addedNodes(t.addedNodes)},addedNodes:function(e){this.addCallback&&this.addCallback(e);for(var t,n=0,r=e.length;r>n&&(t=e[n]);n++)t.children&&t.children.length&&this.addedNodes(t.children)},observe:function(e){this.mo.observe(e,{childList:!0,subtree:!0})}},e.Observer=t}),window.HTMLImports.addModule(function(e){function t(e){return"link"===e.localName&&e.rel===l}function n(e){var t=r(e);return"data:text/javascript;charset=utf-8,"+encodeURIComponent(t)}function r(e){return e.textContent+o(e)}function o(e){var t=e.ownerDocument;t.__importedScripts=t.__importedScripts||0;var n=e.ownerDocument.baseURI,r=t.__importedScripts?"-"+t.__importedScripts:"";return t.__importedScripts++,"\n//# sourceURL="+n+r+".js\n"}function i(e){var t=e.ownerDocument.createElement("style");return t.textContent=e.textContent,a.resolveUrlsInStyle(t),t}var a=e.path,s=e.rootDocument,c=e.flags,d=e.isIE,l=e.IMPORT_LINK_TYPE,u="link[rel="+l+"]",h={documentSelectors:u,importsSelectors:[u,"link[rel=stylesheet]","style","script:not([type])",'script[type="application/javascript"]','script[type="text/javascript"]'].join(","),map:{link:"parseLink",script:"parseScript",style:"parseStyle"},dynamicElements:[],parseNext:function(){var e=this.nextToParse();e&&this.parse(e)},parse:function(e){if(this.isParsed(e))return void(c.parse&&console.log("[%s] is already parsed",e.localName));var t=this[this.map[e.localName]];t&&(this.markParsing(e),t.call(this,e))},parseDynamic:function(e,t){this.dynamicElements.push(e),t||this.parseNext()},markParsing:function(e){c.parse&&console.log("parsing",e),this.parsingElement=e},markParsingComplete:function(e){e.__importParsed=!0,this.markDynamicParsingComplete(e),e.__importElement&&(e.__importElement.__importParsed=!0,this.markDynamicParsingComplete(e.__importElement)),this.parsingElement=null,c.parse&&console.log("completed",e)},markDynamicParsingComplete:function(e){var t=this.dynamicElements.indexOf(e);t>=0&&this.dynamicElements.splice(t,1)},parseImport:function(e){if(HTMLImports.__importsParsingHook&&HTMLImports.__importsParsingHook(e),e["import"]&&(e["import"].__importParsed=!0),this.markParsingComplete(e),e.dispatchEvent(e.__resource&&!e.__error?new CustomEvent("load",{bubbles:!1}):new CustomEvent("error",{bubbles:!1})),e.__pending)for(var t;e.__pending.length;)t=e.__pending.shift(),t&&t({target:e});this.parseNext()},parseLink:function(e){t(e)?this.parseImport(e):(e.href=e.href,this.parseGeneric(e))},parseStyle:function(e){var t=e;e=i(e),t.__appliedElement=e,e.__importElement=t,this.parseGeneric(e)},parseGeneric:function(e){this.trackElement(e),this.addElementToDocument(e)},rootImportForElement:function(e){for(var t=e;t.ownerDocument.__importLink;)t=t.ownerDocument.__importLink;return t},addElementToDocument:function(e){var t=this.rootImportForElement(e.__importElement||e);t.parentNode.insertBefore(e,t)},trackElement:function(e,t){var n=this,r=function(r){t&&t(r),n.markParsingComplete(e),n.parseNext()};if(e.addEventListener("load",r),e.addEventListener("error",r),d&&"style"===e.localName){var o=!1;if(-1==e.textContent.indexOf("@import"))o=!0;else if(e.sheet){o=!0;for(var i,a=e.sheet.cssRules,s=a?a.length:0,c=0;s>c&&(i=a[c]);c++)i.type===CSSRule.IMPORT_RULE&&(o=o&&Boolean(i.styleSheet))}o&&setTimeout(function(){e.dispatchEvent(new CustomEvent("load",{bubbles:!1}))})}},parseScript:function(t){var r=document.createElement("script");r.__importElement=t,r.src=t.src?t.src:n(t),e.currentScript=t,this.trackElement(r,function(t){r.parentNode.removeChild(r),e.currentScript=null}),this.addElementToDocument(r)},nextToParse:function(){return this._mayParse=[],!this.parsingElement&&(this.nextToParseInDoc(s)||this.nextToParseDynamic())},nextToParseInDoc:function(e,n){if(e&&this._mayParse.indexOf(e)<0){this._mayParse.push(e);for(var r,o=e.querySelectorAll(this.parseSelectorsForNode(e)),i=0,a=o.length;a>i&&(r=o[i]);i++)if(!this.isParsed(r))return this.hasResource(r)?t(r)?this.nextToParseInDoc(r["import"],r):r:void 0}return n},nextToParseDynamic:function(){return this.dynamicElements[0]},parseSelectorsForNode:function(e){var t=e.ownerDocument||e;return t===s?this.documentSelectors:this.importsSelectors},isParsed:function(e){return e.__importParsed},needsDynamicParsing:function(e){return this.dynamicElements.indexOf(e)>=0},hasResource:function(e){return t(e)&&void 0===e["import"]?!1:!0}};e.parser=h,e.IMPORT_SELECTOR=u}),window.HTMLImports.addModule(function(e){function t(e){return n(e,a)}function n(e,t){return"link"===e.localName&&e.getAttribute("rel")===t}function r(e){return!!Object.getOwnPropertyDescriptor(e,"baseURI")}function o(e,t){var n=document.implementation.createHTMLDocument(a);n._URL=t;var o=n.createElement("base");o.setAttribute("href",t),n.baseURI||r(n)||Object.defineProperty(n,"baseURI",{value:t});var i=n.createElement("meta");return i.setAttribute("charset","utf-8"),n.head.appendChild(i),n.head.appendChild(o),n.body.innerHTML=e,window.HTMLTemplateElement&&HTMLTemplateElement.bootstrap&&HTMLTemplateElement.bootstrap(n),n}var i=e.flags,a=e.IMPORT_LINK_TYPE,s=e.IMPORT_SELECTOR,c=e.rootDocument,d=e.Loader,l=e.Observer,u=e.parser,h={documents:{},documentPreloadSelectors:s,importsPreloadSelectors:[s].join(","),loadNode:function(e){p.addNode(e)},loadSubtree:function(e){var t=this.marshalNodes(e);p.addNodes(t)},marshalNodes:function(e){return e.querySelectorAll(this.loadSelectorsForNode(e))},loadSelectorsForNode:function(e){var t=e.ownerDocument||e;return t===c?this.documentPreloadSelectors:this.importsPreloadSelectors},loaded:function(e,n,r,a,s){if(i.load&&console.log("loaded",e,n),n.__resource=r,n.__error=a,t(n)){var c=this.documents[e];void 0===c&&(c=a?null:o(r,s||e),c&&(c.__importLink=n,this.bootDocument(c)),this.documents[e]=c),n["import"]=c}u.parseNext()},bootDocument:function(e){this.loadSubtree(e),this.observer.observe(e),u.parseNext()},loadedAll:function(){u.parseNext()}},p=new d(h.loaded.bind(h),h.loadedAll.bind(h));if(h.observer=new l,!document.baseURI){var f={get:function(){var e=document.querySelector("base");return e?e.href:window.location.href},configurable:!0};Object.defineProperty(document,"baseURI",f),Object.defineProperty(c,"baseURI",f)}e.importer=h,e.importLoader=p}),window.HTMLImports.addModule(function(e){var t=e.parser,n=e.importer,r={added:function(e){for(var r,o,i,a,s=0,c=e.length;c>s&&(a=e[s]);s++)r||(r=a.ownerDocument,o=t.isParsed(r)),i=this.shouldLoadNode(a),i&&n.loadNode(a),this.shouldParseNode(a)&&o&&t.parseDynamic(a,i)},shouldLoadNode:function(e){return 1===e.nodeType&&o.call(e,n.loadSelectorsForNode(e))},shouldParseNode:function(e){return 1===e.nodeType&&o.call(e,t.parseSelectorsForNode(e))}};n.observer.addCallback=r.added.bind(r);var o=HTMLElement.prototype.matches||HTMLElement.prototype.matchesSelector||HTMLElement.prototype.webkitMatchesSelector||HTMLElement.prototype.mozMatchesSelector||HTMLElement.prototype.msMatchesSelector}),function(e){function t(){HTMLImports.importer.bootDocument(o)}var n=e.initializeModules,r=e.isIE;if(!e.useNative){r&&"function"!=typeof window.CustomEvent&&(window.CustomEvent=function(e,t){t=t||{};var n=document.createEvent("CustomEvent");return n.initCustomEvent(e,Boolean(t.bubbles),Boolean(t.cancelable),t.detail),n},window.CustomEvent.prototype=window.Event.prototype),n();var o=e.rootDocument;"complete"===document.readyState||"interactive"===document.readyState&&!window.attachEvent?t():document.addEventListener("DOMContentLoaded",t)}}(window.HTMLImports),window.CustomElements=window.CustomElements||{flags:{}},function(e){var t=e.flags,n=[],r=function(e){n.push(e)},o=function(){n.forEach(function(t){t(e)})};e.addModule=r,e.initializeModules=o,e.hasNative=Boolean(document.registerElement),e.useNative=!t.register&&e.hasNative&&!window.ShadowDOMPolyfill&&(!window.HTMLImports||HTMLImports.useNative)}(window.CustomElements),window.CustomElements.addModule(function(e){function t(e,t){n(e,function(e){return t(e)?!0:void r(e,t)}),r(e,t)}function n(e,t,r){var o=e.firstElementChild;if(!o)for(o=e.firstChild;o&&o.nodeType!==Node.ELEMENT_NODE;)o=o.nextSibling;for(;o;)t(o,r)!==!0&&n(o,t,r),o=o.nextElementSibling;return null}function r(e,n){for(var r=e.shadowRoot;r;)t(r,n),r=r.olderShadowRoot}function o(e,t){i(e,t,[])}function i(e,t,n){if(e=wrap(e),!(n.indexOf(e)>=0)){n.push(e);for(var r,o=e.querySelectorAll("link[rel="+a+"]"),s=0,c=o.length;c>s&&(r=o[s]);s++)r["import"]&&i(r["import"],t,n);t(e)}}var a=window.HTMLImports?HTMLImports.IMPORT_LINK_TYPE:"none";e.forDocumentTree=o,e.forSubtree=t}),window.CustomElements.addModule(function(e){function t(e){return n(e)||r(e)}function n(t){return e.upgrade(t)?!0:void s(t)}function r(e){w(e,function(e){return n(e)?!0:void 0})}function o(e){s(e),h(e)&&w(e,function(e){s(e)})}function i(e){M.push(e),L||(L=!0,setTimeout(a))}function a(){L=!1;for(var e,t=M,n=0,r=t.length;r>n&&(e=t[n]);n++)e();M=[]}function s(e){E?i(function(){c(e)}):c(e)}function c(e){e.__upgraded__&&(e.attachedCallback||e.detachedCallback)&&!e.__attached&&h(e)&&(e.__attached=!0,e.attachedCallback&&e.attachedCallback())}function d(e){l(e),w(e,function(e){l(e)})}function l(e){E?i(function(){u(e)}):u(e)}function u(e){e.__upgraded__&&(e.attachedCallback||e.detachedCallback)&&e.__attached&&!h(e)&&(e.__attached=!1,e.detachedCallback&&e.detachedCallback())}function h(e){for(var t=e,n=wrap(document);t;){if(t==n)return!0;t=t.parentNode||t.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&t.host}}function p(e){if(e.shadowRoot&&!e.shadowRoot.__watched){b.dom&&console.log("watching shadow-root for: ",e.localName);for(var t=e.shadowRoot;t;)v(t),t=t.olderShadowRoot}}function f(e){if(b.dom){var n=e[0];if(n&&"childList"===n.type&&n.addedNodes&&n.addedNodes){for(var r=n.addedNodes[0];r&&r!==document&&!r.host;)r=r.parentNode;var o=r&&(r.URL||r._URL||r.host&&r.host.localName)||"";o=o.split("/?").shift().split("/").pop()}console.group("mutations (%d) [%s]",e.length,o||"")}e.forEach(function(e){"childList"===e.type&&(T(e.addedNodes,function(e){e.localName&&t(e)}),T(e.removedNodes,function(e){e.localName&&d(e)}))}),b.dom&&console.groupEnd()}function m(e){for(e=wrap(e),e||(e=wrap(document));e.parentNode;)e=e.parentNode;var t=e.__observer;t&&(f(t.takeRecords()),a())}function v(e){if(!e.__observer){var t=new MutationObserver(f);t.observe(e,{childList:!0,subtree:!0}),e.__observer=t}}function _(e){e=wrap(e),b.dom&&console.group("upgradeDocument: ",e.baseURI.split("/").pop()),t(e),v(e),b.dom&&console.groupEnd()}function g(e){y(e,_)}var b=e.flags,w=e.forSubtree,y=e.forDocumentTree,E=!window.MutationObserver||window.MutationObserver===window.JsMutationObserver;e.hasPolyfillMutations=E;var L=!1,M=[],T=Array.prototype.forEach.call.bind(Array.prototype.forEach),N=Element.prototype.createShadowRoot;N&&(Element.prototype.createShadowRoot=function(){var e=N.call(this);return CustomElements.watchShadow(this),e}),e.watchShadow=p,e.upgradeDocumentTree=g,e.upgradeSubtree=r,e.upgradeAll=t,e.attachedNode=o,e.takeRecords=m}),window.CustomElements.addModule(function(e){function t(t){if(!t.__upgraded__&&t.nodeType===Node.ELEMENT_NODE){var r=t.getAttribute("is"),o=e.getRegisteredDefinition(r||t.localName);if(o){if(r&&o.tag==t.localName)return n(t,o);if(!r&&!o["extends"])return n(t,o)}}}function n(t,n){return a.upgrade&&console.group("upgrade:",t.localName),n.is&&t.setAttribute("is",n.is),r(t,n),t.__upgraded__=!0,i(t),e.attachedNode(t),e.upgradeSubtree(t),a.upgrade&&console.groupEnd(),t}function r(e,t){Object.__proto__?e.__proto__=t.prototype:(o(e,t.prototype,t["native"]),e.__proto__=t.prototype)}function o(e,t,n){for(var r={},o=t;o!==n&&o!==HTMLElement.prototype;){for(var i,a=Object.getOwnPropertyNames(o),s=0;i=a[s];s++)r[i]||(Object.defineProperty(e,i,Object.getOwnPropertyDescriptor(o,i)),r[i]=1);o=Object.getPrototypeOf(o)}}function i(e){e.createdCallback&&e.createdCallback()}var a=e.flags;e.upgrade=t,e.upgradeWithDefinition=n,e.implementPrototype=r}),window.CustomElements.addModule(function(e){function t(t,r){var c=r||{};if(!t)throw new Error("document.registerElement: first argument `name` must not be empty");if(t.indexOf("-")<0)throw new Error("document.registerElement: first argument ('name') must contain a dash ('-'). Argument provided was '"+String(t)+"'.");if(o(t))throw new Error("Failed to execute 'registerElement' on 'Document': Registration failed for type '"+String(t)+"'. The type name is invalid.");if(d(t))throw new Error("DuplicateDefinitionError: a type with name '"+String(t)+"' is already registered");return c.prototype||(c.prototype=Object.create(HTMLElement.prototype)),c.__name=t.toLowerCase(),c.lifecycle=c.lifecycle||{},c.ancestry=i(c["extends"]),a(c),s(c),n(c.prototype),l(c.__name,c),c.ctor=u(c),c.ctor.prototype=c.prototype,c.prototype.constructor=c.ctor,e.ready&&_(document),c.ctor}function n(e){if(!e.setAttribute._polyfilled){var t=e.setAttribute;e.setAttribute=function(e,n){r.call(this,e,n,t)};var n=e.removeAttribute;e.removeAttribute=function(e){r.call(this,e,null,n)},e.setAttribute._polyfilled=!0}}function r(e,t,n){e=e.toLowerCase();var r=this.getAttribute(e);n.apply(this,arguments);var o=this.getAttribute(e);this.attributeChangedCallback&&o!==r&&this.attributeChangedCallback(e,r,o); + +}function o(e){for(var t=0;t=0&&w(r,HTMLElement),r)}function f(e,t){var n=e[t];e[t]=function(){var e=n.apply(this,arguments);return g(e),e}}var m,v=e.isIE11OrOlder,_=e.upgradeDocumentTree,g=e.upgradeAll,b=e.upgradeWithDefinition,w=e.implementPrototype,y=e.useNative,E=["annotation-xml","color-profile","font-face","font-face-src","font-face-uri","font-face-format","font-face-name","missing-glyph"],L={},M="http://www.w3.org/1999/xhtml",T=document.createElement.bind(document),N=document.createElementNS.bind(document);m=Object.__proto__||y?function(e,t){return e instanceof t}:function(e,t){for(var n=e;n;){if(n===t.prototype)return!0;n=n.__proto__}return!1},f(Node.prototype,"cloneNode"),f(document,"importNode"),v&&!function(){var e=document.importNode;document.importNode=function(){var t=e.apply(document,arguments);if(t.nodeType==t.DOCUMENT_FRAGMENT_NODE){var n=document.createDocumentFragment();return n.appendChild(t),n}return t}}(),document.registerElement=t,document.createElement=p,document.createElementNS=h,e.registry=L,e["instanceof"]=m,e.reservedTagList=E,e.getRegisteredDefinition=d,document.register=document.registerElement}),function(e){function t(){a(wrap(document)),window.HTMLImports&&(HTMLImports.__importsParsingHook=function(e){a(wrap(e["import"]))}),CustomElements.ready=!0,setTimeout(function(){CustomElements.readyTime=Date.now(),window.HTMLImports&&(CustomElements.elapsed=CustomElements.readyTime-HTMLImports.readyTime),document.dispatchEvent(new CustomEvent("WebComponentsReady",{bubbles:!0}))})}var n=e.useNative,r=e.initializeModules,o=/Trident/.test(navigator.userAgent);if(n){var i=function(){};e.watchShadow=i,e.upgrade=i,e.upgradeAll=i,e.upgradeDocumentTree=i,e.upgradeSubtree=i,e.takeRecords=i,e["instanceof"]=function(e,t){return e instanceof t}}else r();var a=e.upgradeDocumentTree;if(window.wrap||(window.ShadowDOMPolyfill?(window.wrap=ShadowDOMPolyfill.wrapIfNeeded,window.unwrap=ShadowDOMPolyfill.unwrapIfNeeded):window.wrap=window.unwrap=function(e){return e}),o&&"function"!=typeof window.CustomEvent&&(window.CustomEvent=function(e,t){t=t||{};var n=document.createEvent("CustomEvent");return n.initCustomEvent(e,Boolean(t.bubbles),Boolean(t.cancelable),t.detail),n},window.CustomEvent.prototype=window.Event.prototype),"complete"===document.readyState||e.flags.eager)t();else if("interactive"!==document.readyState||window.attachEvent||window.HTMLImports&&!window.HTMLImports.ready){var s=window.HTMLImports&&!HTMLImports.ready?"HTMLImportsLoaded":"DOMContentLoaded";window.addEventListener(s,t)}else t();e.isIE11OrOlder=o}(window.CustomElements),"undefined"==typeof HTMLTemplateElement&&!function(){var e="template";HTMLTemplateElement=function(){},HTMLTemplateElement.prototype=Object.create(HTMLElement.prototype),HTMLTemplateElement.decorate=function(e){e.content||(e.content=e.ownerDocument.createDocumentFragment());for(var t;t=e.firstChild;)e.content.appendChild(t)},HTMLTemplateElement.bootstrap=function(t){for(var n,r=t.querySelectorAll(e),o=0,i=r.length;i>o&&(n=r[o]);o++)HTMLTemplateElement.decorate(n)},addEventListener("DOMContentLoaded",function(){HTMLTemplateElement.bootstrap(document)});var t=document.createElement;document.createElement=function(){"use strict";var e=t.apply(document,arguments);return"template"==e.localName&&HTMLTemplateElement.decorate(e),e}}(),function(e){var t=document.createElement("style");t.textContent="body {transition: opacity ease-in 0.2s; } \nbody[unresolved] {opacity: 0; display: block; overflow: hidden; position: relative; } \n";var n=document.querySelector("head");n.insertBefore(t,n.firstChild)}(window.WebComponents); \ No newline at end of file From 452b082c820065ed722504e6bbb88cf02095e521 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 8 Jun 2015 22:49:32 -0700 Subject: [PATCH 014/114] Update media player core --- .../more-infos/more-info-media_player.html | 4 +- .../components/media_player/__init__.py | 67 +++++++------ homeassistant/components/media_player/cast.py | 99 ++++++++++--------- 3 files changed, 87 insertions(+), 83 deletions(-) diff --git a/homeassistant/components/frontend/www_static/polymer/more-infos/more-info-media_player.html b/homeassistant/components/frontend/www_static/polymer/more-infos/more-info-media_player.html index e36970f325c..9d16ab9e561 100644 --- a/homeassistant/components/frontend/www_static/polymer/more-infos/more-info-media_player.html +++ b/homeassistant/components/frontend/www_static/polymer/more-infos/more-info-media_player.html @@ -131,7 +131,7 @@ this.isOff = newVal.state == 'off'; this.isPlaying = newVal.state == 'playing'; this.volumeSliderValue = newVal.attributes.volume_level * 100; - this.isMuted = newVal.attributes.volume_muted; + this.isMuted = newVal.attributes.is_volume_muted; this.supportsPause = (newVal.attributes.supported_media_commands & 1) !== 0; this.supportsVolumeSet = (newVal.attributes.supported_media_commands & 4) !== 0; this.supportsVolumeMute = (newVal.attributes.supported_media_commands & 8) !== 0; @@ -192,7 +192,7 @@ if (!this.supportsVolumeMute) { return; } - this.callService('volume_mute', { volume_muted: !this.isMuted }); + this.callService('volume_mute', { is_volume_muted: !this.isMuted }); }, volumeSliderChanged: function(ev) { diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py index 4116f1c4d28..0071760967f 100644 --- a/homeassistant/components/media_player/__init__.py +++ b/homeassistant/components/media_player/__init__.py @@ -30,14 +30,15 @@ DISCOVERY_PLATFORMS = { SERVICE_YOUTUBE_VIDEO = 'play_youtube_video' ATTR_MEDIA_VOLUME_LEVEL = 'volume_level' -ATTR_MEDIA_VOLUME_MUTED = 'volume_muted' +ATTR_MEDIA_VOLUME_MUTED = 'is_volume_muted' ATTR_MEDIA_SEEK_POSITION = 'seek_position' ATTR_MEDIA_CONTENT_ID = 'media_content_id' ATTR_MEDIA_CONTENT_TYPE = 'media_content_type' ATTR_MEDIA_DURATION = 'media_duration' ATTR_MEDIA_TITLE = 'media_title' ATTR_MEDIA_ARTIST = 'media_artist' -ATTR_MEDIA_ALBUM = 'media_album' +ATTR_MEDIA_ALBUM_NAME = 'media_album_name' +ATTR_MEDIA_ALBUM_ARTIST = 'media_album_artist' ATTR_MEDIA_TRACK = 'media_track' ATTR_MEDIA_SERIES_TITLE = 'media_series_title' ATTR_MEDIA_SEASON = 'media_season' @@ -74,24 +75,24 @@ SERVICE_TO_METHOD = { SERVICE_MEDIA_PREVIOUS_TRACK: 'media_previous_track', } -ATTR_TO_PROPERTY = { - ATTR_MEDIA_VOLUME_LEVEL: 'volume_level', - ATTR_MEDIA_VOLUME_MUTED: 'is_volume_muted', - ATTR_MEDIA_CONTENT_ID: 'media_content_id', - ATTR_MEDIA_CONTENT_TYPE: 'media_content_type', - ATTR_MEDIA_DURATION: 'media_duration', - ATTR_ENTITY_PICTURE: 'media_image_url', - ATTR_MEDIA_TITLE: 'media_title', - ATTR_MEDIA_ARTIST: 'media_artist', - ATTR_MEDIA_ALBUM: 'media_album', - ATTR_MEDIA_TRACK: 'media_track', - ATTR_MEDIA_SERIES_TITLE: 'media_series_title', - ATTR_MEDIA_SEASON: 'media_season', - ATTR_MEDIA_EPISODE: 'media_episode', - ATTR_APP_ID: 'app_id', - ATTR_APP_NAME: 'app_name', - ATTR_SUPPORTED_MEDIA_COMMANDS: 'supported_media_commands', -} +ATTR_TO_PROPERTY = [ + ATTR_MEDIA_VOLUME_LEVEL, + ATTR_MEDIA_VOLUME_MUTED, + ATTR_MEDIA_CONTENT_ID, + ATTR_MEDIA_CONTENT_TYPE, + ATTR_MEDIA_DURATION, + ATTR_MEDIA_TITLE, + ATTR_MEDIA_ARTIST, + ATTR_MEDIA_ALBUM_NAME, + ATTR_MEDIA_ALBUM_ARTIST, + ATTR_MEDIA_TRACK, + ATTR_MEDIA_SERIES_TITLE, + ATTR_MEDIA_SEASON, + ATTR_MEDIA_EPISODE, + ATTR_APP_ID, + ATTR_APP_NAME, + ATTR_SUPPORTED_MEDIA_COMMANDS, +] def is_on(hass, entity_id=None): @@ -242,10 +243,7 @@ def setup(hass, config): if player.should_poll: player.update_ha_state(True) - hass.services.register(DOMAIN, SERVICE_VOLUME_MUTE, - lambda service: - volume_mute_service( - service, )) + hass.services.register(DOMAIN, SERVICE_VOLUME_MUTE, volume_mute_service) def media_seek_service(service): """ Seek to a position. """ @@ -262,10 +260,7 @@ def setup(hass, config): if player.should_poll: player.update_ha_state(True) - hass.services.register(DOMAIN, SERVICE_MEDIA_SEEK, - lambda service: - media_seek_service( - service, )) + hass.services.register(DOMAIN, SERVICE_MEDIA_SEEK, media_seek_service) def play_youtube_video_service(service, media_id): """ Plays specified media_id on the media player. """ @@ -346,8 +341,13 @@ class MediaPlayerDevice(Entity): return None @property - def media_album(self): - """ Album of current playing media. (Music track only) """ + def media_album_name(self): + """ Album name of current playing media. (Music track only) """ + return None + + @property + def media_album_artist(self): + """ Album arist of current playing media. (Music track only) """ return None @property @@ -490,10 +490,13 @@ class MediaPlayerDevice(Entity): state_attr = {} else: state_attr = { - attr: getattr(self, prop) for attr, prop - in ATTR_TO_PROPERTY.items() if getattr(self, prop) + attr: getattr(self, attr) for attr + in ATTR_TO_PROPERTY if getattr(self, attr) } + if self.media_image_url: + state_attr[ATTR_ENTITY_PICTURE] = self.media_image_url + device_attr = self.device_state_attributes if device_attr: diff --git a/homeassistant/components/media_player/cast.py b/homeassistant/components/media_player/cast.py index 61515f5354a..77cdf79a112 100644 --- a/homeassistant/components/media_player/cast.py +++ b/homeassistant/components/media_player/cast.py @@ -21,11 +21,14 @@ from homeassistant.const import ( from homeassistant.components.media_player import ( MediaPlayerDevice, SUPPORT_PAUSE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_MUTE, - SUPPORT_YOUTUBE, - SUPPORT_TURN_ON, SUPPORT_TURN_OFF, - SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK) + SUPPORT_TURN_ON, SUPPORT_TURN_OFF, SUPPORT_YOUTUBE, + SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK, + MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO) CAST_SPLASH = 'https://home-assistant.io/images/cast/splash.png' +SUPPORT_CAST = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \ + SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_PREVIOUS_TRACK | \ + SUPPORT_NEXT_TRACK | SUPPORT_YOUTUBE # pylint: disable=unused-argument @@ -61,6 +64,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class CastDevice(MediaPlayerDevice): """ Represents a Cast device on the network. """ + # pylint: disable=too-many-public-methods + def __init__(self, host): self.cast = pychromecast.Chromecast(host) self.youtube = youtube.YouTubeController() @@ -73,7 +78,7 @@ class CastDevice(MediaPlayerDevice): self.cast_status = self.cast.status self.media_status = self.cast.media_controller.status - """Entity properties and methods""" + # Entity properties and methods @property def should_poll(self): @@ -84,18 +89,18 @@ class CastDevice(MediaPlayerDevice): """ Returns the name of the device. """ return self.cast.device.friendly_name - """MediaPlayerDevice properties and methods""" + # MediaPlayerDevice properties and methods @property def state(self): """ State of the player. """ - media_controller = self.cast.media_controller - - if media_controller.is_playing: + if self.media_status is None: + return STATE_UNKNOWN + elif self.media_status.player_is_playing: return STATE_PLAYING - elif media_controller.is_paused: + elif self.media_status.player_is_paused: return STATE_PAUSED - elif media_controller.is_idle: + elif self.media_status.player_is_idle: return STATE_IDLE elif self.cast.is_idle: return STATE_OFF @@ -105,79 +110,85 @@ class CastDevice(MediaPlayerDevice): @property def volume_level(self): """ Volume level of the media player (0..1). """ - if self.cast_status is None: - return None - else: - return self.cast_status.volume_level + return self.cast_status.volume_level if self.cast_status else None @property def is_volume_muted(self): """ Boolean if volume is currently muted. """ - if self.cast_status is None: - return None - else: - return self.cast_status.volume_muted + return self.cast_status.volume_muted if self.cast_status else None @property def media_content_id(self): """ Content ID of current playing media. """ - if self.media_status is None: - return None - else: - return self.media_status.content_id + return self.media_status.content_id if self.media_status else None @property def media_content_type(self): """ Content type of current playing media. """ + if self.media_status is None: + return None + elif self.media_status.media_is_tvshow: + return MEDIA_TYPE_TVSHOW + elif self.media_status.media_is_movie: + return MEDIA_TYPE_VIDEO + elif self.media_status.media_is_musictrack: + return MEDIA_TYPE_MUSIC return None @property def media_duration(self): """ Duration of current playing media in seconds. """ - if self.media_status is None: - return None - else: - return self.media_status.duration + return self.media_status.duration if self.media_status else None @property def media_image_url(self): """ Image url of current playing media. """ - return self.cast.media_controller.thumbnail + if self.media_status is None: + return None + + images = self.media_status.images + + return images[0].url if images else None @property def media_title(self): """ Title of current playing media. """ - return self.cast.media_controller.title + return self.media_status.title if self.media_status else None @property def media_artist(self): """ Artist of current playing media. (Music track only) """ - return None + return self.media_status.artist if self.media_status else None @property def media_album(self): """ Album of current playing media. (Music track only) """ - return None + return self.media_status.album_name if self.media_status else None + + @property + def media_album_artist(self): + """ Album arist of current playing media. (Music track only) """ + return self.media_status.album_artist if self.media_status else None @property def media_track(self): """ Track number of current playing media. (Music track only) """ - return None + return self.media_status.track if self.media_status else None @property def media_series_title(self): """ Series title of current playing media. (TV Show only)""" - return None + return self.media_status.series_title if self.media_status else None @property def media_season(self): """ Season of current playing media. (TV Show only) """ - return None + return self.media_status.season if self.media_status else None @property def media_episode(self): """ Episode of current playing media. (TV Show only) """ - return None + return self.media_status.episode if self.media_status else None @property def app_id(self): @@ -192,14 +203,7 @@ class CastDevice(MediaPlayerDevice): @property def supported_media_commands(self): """ Flags of media commands that are supported. """ - return SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \ - SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_PREVIOUS_TRACK | \ - SUPPORT_NEXT_TRACK - - @property - def device_state_attributes(self): - """ Extra attributes a device wants to expose. """ - return None + return SUPPORT_CAST def turn_on(self): """ Turns on the ChromeCast. """ @@ -211,11 +215,8 @@ class CastDevice(MediaPlayerDevice): self.cast.play_media( CAST_SPLASH, pychromecast.STREAM_TYPE_BUFFERED) - def turn_off(self): - """ Service to exit any running app on the specimedia player ChromeCast and - shows idle screen. Will quit all ChromeCasts if nothing specified. - """ + """ Turns Chromecast off. """ self.cast.quit_app() def mute_volume(self, mute): @@ -244,13 +245,13 @@ class CastDevice(MediaPlayerDevice): def media_seek(self, position): """ Seek the media to a specific location. """ - self.case.media_controller.seek(position) + self.cast.media_controller.seek(position) def play_youtube(self, media_id): """ Plays a YouTube media. """ self.youtube.play_video(media_id) - """implementation of chromecast status_listener methods""" + # implementation of chromecast status_listener methods def new_cast_status(self, status): """ Called when a new cast status is received. """ @@ -260,4 +261,4 @@ class CastDevice(MediaPlayerDevice): def new_media_status(self, status): """ Called when a new media status is received. """ self.media_status = status - self.update_ha_state() \ No newline at end of file + self.update_ha_state() From 90919a66d9adcf996ae66eef0a18baf5236c7fd1 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 8 Jun 2015 22:49:43 -0700 Subject: [PATCH 015/114] Fix Cast media player support --- homeassistant/components/media_player/demo.py | 33 +++++++++++++++---- requirements.txt | 2 +- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/media_player/demo.py b/homeassistant/components/media_player/demo.py index e8bb0aca35b..5d8c5fd635b 100644 --- a/homeassistant/components/media_player/demo.py +++ b/homeassistant/components/media_player/demo.py @@ -34,16 +34,17 @@ YOUTUBE_PLAYER_SUPPORT = \ MUSIC_PLAYER_SUPPORT = \ SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \ - SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_PREVIOUS_TRACK | \ - SUPPORT_NEXT_TRACK + SUPPORT_TURN_ON | SUPPORT_TURN_OFF NETFLIX_PLAYER_SUPPORT = \ - SUPPORT_PAUSE | SUPPORT_TURN_ON | SUPPORT_TURN_OFF | \ - SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK + SUPPORT_PAUSE | SUPPORT_TURN_ON | SUPPORT_TURN_OFF class AbstractDemoPlayer(MediaPlayerDevice): """ Base class for demo media players. """ + # We only implement the methods that we support + # pylint: disable=abstract-method + def __init__(self, name): self._name = name self._player_state = STATE_PLAYING @@ -181,7 +182,8 @@ class DemoMusicPlayer(AbstractDemoPlayer): ('Diss Reaction', 'Jiiieehaaaa '), ('Flamman And Abraxas', 'Good To Go (Radio Mix)'), ('Critical Mass', 'Dancing Together'), - ('Charly Lownoise & Mental Theo', 'Ultimate Sex Track (Bass-D & King Matthew Remix)'), + ('Charly Lownoise & Mental Theo', + 'Ultimate Sex Track (Bass-D & King Matthew Remix)'), ] def __init__(self): @@ -221,6 +223,7 @@ class DemoMusicPlayer(AbstractDemoPlayer): @property def media_album(self): """ Album of current playing media. (Music track only) """ + # pylint: disable=no-self-use return "Bounzz" @property @@ -231,7 +234,15 @@ class DemoMusicPlayer(AbstractDemoPlayer): @property def supported_media_commands(self): """ Flags of media commands that are supported. """ - return MUSIC_PLAYER_SUPPORT + support = MUSIC_PLAYER_SUPPORT + + if self._cur_track > 1: + support |= SUPPORT_PREVIOUS_TRACK + + if self._cur_track < len(self.tracks)-1: + support |= SUPPORT_NEXT_TRACK + + return support def media_previous_track(self): """ Send previous track command. """ @@ -304,7 +315,15 @@ class DemoTVShowPlayer(AbstractDemoPlayer): @property def supported_media_commands(self): """ Flags of media commands that are supported. """ - return NETFLIX_PLAYER_SUPPORT + support = NETFLIX_PLAYER_SUPPORT + + if self._cur_episode > 1: + support |= SUPPORT_PREVIOUS_TRACK + + if self._cur_episode < self._episode_count: + support |= SUPPORT_NEXT_TRACK + + return support def media_previous_track(self): """ Send previous track command. """ diff --git a/requirements.txt b/requirements.txt index 04f4b122b8e..24b7bc1b12a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,7 +18,7 @@ phue>=0.8 ledcontroller>=1.0.7 # Chromecast bindings (media_player.cast) -pychromecast>=0.6.4 +pychromecast>=0.6.5 # Keyboard (keyboard) pyuserinput>=0.1.9 From 5008b25a2d2ca859922e78097d5be4e048c3bbe5 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 8 Jun 2015 23:06:41 -0700 Subject: [PATCH 016/114] Fix MPD media player support --- homeassistant/components/media_player/demo.py | 2 +- homeassistant/components/media_player/mpd.py | 115 +++++++++--------- 2 files changed, 60 insertions(+), 57 deletions(-) diff --git a/homeassistant/components/media_player/demo.py b/homeassistant/components/media_player/demo.py index 5d8c5fd635b..24f1e91c3ec 100644 --- a/homeassistant/components/media_player/demo.py +++ b/homeassistant/components/media_player/demo.py @@ -221,7 +221,7 @@ class DemoMusicPlayer(AbstractDemoPlayer): return self.tracks[self._cur_track][0] @property - def media_album(self): + def media_album_name(self): """ Album of current playing media. (Music track only) """ # pylint: disable=no-self-use return "Bounzz" diff --git a/homeassistant/components/media_player/mpd.py b/homeassistant/components/media_player/mpd.py index 53faa37a605..64b927999be 100644 --- a/homeassistant/components/media_player/mpd.py +++ b/homeassistant/components/media_player/mpd.py @@ -32,16 +32,22 @@ Location of your Music Player Daemon. import logging import socket +from homeassistant.const import ( + STATE_PLAYING, STATE_PAUSED, STATE_OFF) + from homeassistant.components.media_player import ( - MediaPlayerDevice, STATE_NO_APP, ATTR_MEDIA_STATE, - ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_TITLE, ATTR_MEDIA_ARTIST, - ATTR_MEDIA_ALBUM, ATTR_MEDIA_DATE, ATTR_MEDIA_DURATION, - ATTR_MEDIA_VOLUME, MEDIA_STATE_PAUSED, MEDIA_STATE_PLAYING, - MEDIA_STATE_STOPPED, MEDIA_STATE_UNKNOWN) + MediaPlayerDevice, + SUPPORT_PAUSE, SUPPORT_VOLUME_SET, SUPPORT_TURN_OFF, + SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK, + MEDIA_TYPE_MUSIC) _LOGGER = logging.getLogger(__name__) +SUPPORT_MPD = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_TURN_OFF | \ + SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK + + # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): """ Sets up the MPD platform. """ @@ -81,13 +87,15 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class MpdDevice(MediaPlayerDevice): """ Represents a MPD server. """ + # MPD confuses pylint + # pylint: disable=no-member, abstract-method + def __init__(self, server, port, location): from mpd import MPDClient self.server = server self.port = port self._name = location - self.state_attr = {ATTR_MEDIA_STATE: MEDIA_STATE_STOPPED} self.client = MPDClient() self.client.timeout = 10 @@ -99,67 +107,66 @@ class MpdDevice(MediaPlayerDevice): """ Returns the name of the device. """ return self._name - # pylint: disable=no-member @property def state(self): - """ Returns the state of the device. """ - status = self.client.status() - - if status is None: - return STATE_NO_APP - else: - return self.client.currentsong()['artist'] - - @property - def media_state(self): """ Returns the media state. """ - media_controller = self.client.status() - - if media_controller['state'] == 'play': - return MEDIA_STATE_PLAYING - elif media_controller['state'] == 'pause': - return MEDIA_STATE_PAUSED - elif media_controller['state'] == 'stop': - return MEDIA_STATE_STOPPED - else: - return MEDIA_STATE_UNKNOWN - - # pylint: disable=no-member - @property - def state_attributes(self): - """ Returns the state attributes. """ status = self.client.status() + + if status['state'] == 'play': + return STATE_PLAYING + elif status['state'] == 'pause': + return STATE_PAUSED + else: + return STATE_OFF + + @property + def media_content_id(self): + """ Content ID of current playing media. """ current_song = self.client.currentsong() + return current_song['id'] - if not status and not current_song: - state_attr = {} + @property + def media_content_type(self): + """ Content type of current playing media. """ + return MEDIA_TYPE_MUSIC - if current_song['id']: - state_attr[ATTR_MEDIA_CONTENT_ID] = current_song['id'] + @property + def media_duration(self): + """ Duration of current playing media in seconds. """ + current_song = self.client.currentsong() + return current_song['time'] - if current_song['date']: - state_attr[ATTR_MEDIA_DATE] = current_song['date'] + @property + def media_title(self): + """ Title of current playing media. """ + current_song = self.client.currentsong() + return current_song['title'] - if current_song['title']: - state_attr[ATTR_MEDIA_TITLE] = current_song['title'] + @property + def media_artist(self): + """ Artist of current playing media. (Music track only) """ + current_song = self.client.currentsong() + return current_song['artist'] - if current_song['time']: - state_attr[ATTR_MEDIA_DURATION] = current_song['time'] + @property + def media_album_name(self): + """ Album of current playing media. (Music track only) """ + current_song = self.client.currentsong() + return current_song['album'] - if current_song['artist']: - state_attr[ATTR_MEDIA_ARTIST] = current_song['artist'] - - if current_song['album']: - state_attr[ATTR_MEDIA_ALBUM] = current_song['album'] - - state_attr[ATTR_MEDIA_VOLUME] = status['volume'] - - return state_attr + @property + def volume_level(self): + status = self.client.status() + return int(status['volume'])/100 def turn_off(self): """ Service to exit the running MPD. """ self.client.stop() + def set_volume_level(self, volume): + """ Sets volume """ + self.client.setvol(int(volume * 100)) + def volume_up(self): """ Service to send the MPD the command for volume up. """ current_volume = self.client.status()['volume'] @@ -174,10 +181,6 @@ class MpdDevice(MediaPlayerDevice): if int(current_volume) >= 0: self.client.setvol(int(current_volume) - 5) - def media_play_pause(self): - """ Service to send the MPD the command for play/pause. """ - self.client.pause() - def media_play(self): """ Service to send the MPD the command for play/pause. """ self.client.start() @@ -190,6 +193,6 @@ class MpdDevice(MediaPlayerDevice): """ Service to send the MPD the command for next track. """ self.client.next() - def media_prev_track(self): + def media_previous_track(self): """ Service to send the MPD the command for previous track. """ self.client.previous() From 6c1a309c407ce4afc0ab763f7426d9f427e4c75f Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 9 Jun 2015 00:26:15 -0700 Subject: [PATCH 017/114] Minor CSS fixes for frontend --- homeassistant/components/frontend/version.py | 2 +- .../components/frontend/www_static/frontend.html | 14 ++++++++------ .../polymer/layouts/home-assistant-main.html | 12 +++++++----- .../www_static/polymer/layouts/partial-base.html | 2 +- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/frontend/version.py b/homeassistant/components/frontend/version.py index 6646e9da2be..00184d2224b 100644 --- a/homeassistant/components/frontend/version.py +++ b/homeassistant/components/frontend/version.py @@ -1,2 +1,2 @@ """ DO NOT MODIFY. Auto-generated by build_frontend script """ -VERSION = "6b9ac5df0217decb6a939e04824a96ff" +VERSION = "e53215fa1416dbbc7819452b4f38689c" diff --git a/homeassistant/components/frontend/www_static/frontend.html b/homeassistant/components/frontend/www_static/frontend.html index 6bf54e6d64e..335b5400834 100644 --- a/homeassistant/components/frontend/www_static/frontend.html +++ b/homeassistant/components/frontend/www_static/frontend.html @@ -21188,7 +21188,7 @@ iron-selector:not(.narrow-layout) #main ::content [paper-drawer-toggle] { -
+
@@ -26314,6 +26314,10 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN cursor: pointer; } + paper-icon-item.logout { + margin-top: 16px; + } + .divider { border-top: 1px solid #e0e0e0; } @@ -26337,10 +26341,10 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN -
Home Assistant
+
Home Assistant
- + States @@ -26366,9 +26370,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN -
- - + Log Out diff --git a/homeassistant/components/frontend/www_static/polymer/layouts/home-assistant-main.html b/homeassistant/components/frontend/www_static/polymer/layouts/home-assistant-main.html index 11ecff37f8a..a3b9ea3246b 100644 --- a/homeassistant/components/frontend/www_static/polymer/layouts/home-assistant-main.html +++ b/homeassistant/components/frontend/www_static/polymer/layouts/home-assistant-main.html @@ -39,6 +39,10 @@ cursor: pointer; } + paper-icon-item.logout { + margin-top: 16px; + } + .divider { border-top: 1px solid #e0e0e0; } @@ -62,10 +66,10 @@ -
Home Assistant
+
Home Assistant
- @@ -93,9 +97,7 @@ -
- - + Log Out diff --git a/homeassistant/components/frontend/www_static/polymer/layouts/partial-base.html b/homeassistant/components/frontend/www_static/polymer/layouts/partial-base.html index 0229af9318a..66f67015f81 100644 --- a/homeassistant/components/frontend/www_static/polymer/layouts/partial-base.html +++ b/homeassistant/components/frontend/www_static/polymer/layouts/partial-base.html @@ -10,7 +10,7 @@ -
+
From ca373b5aa579ce6d984db2e77c20cf88da0298bb Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 9 Jun 2015 20:53:50 -0700 Subject: [PATCH 018/114] Frontend: Workaround for tap bug in paper-dialog --- homeassistant/components/frontend/version.py | 2 +- .../components/frontend/www_static/frontend.html | 10 ++++------ .../frontend/www_static/polymer/cards/state-card.html | 10 ++++------ 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/frontend/version.py b/homeassistant/components/frontend/version.py index 00184d2224b..3e032816d91 100644 --- a/homeassistant/components/frontend/version.py +++ b/homeassistant/components/frontend/version.py @@ -1,2 +1,2 @@ """ DO NOT MODIFY. Auto-generated by build_frontend script """ -VERSION = "e53215fa1416dbbc7819452b4f38689c" +VERSION = "1cc966bcef26a859d053bd5c46769a99" diff --git a/homeassistant/components/frontend/www_static/frontend.html b/homeassistant/components/frontend/www_static/frontend.html index 335b5400834..a970f484450 100644 --- a/homeassistant/components/frontend/www_static/frontend.html +++ b/homeassistant/components/frontend/www_static/frontend.html @@ -22375,15 +22375,13 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN }, listeners: { - 'tap': 'cardTapped', + // listening for click instead of tap as a work around + // https://github.com/PolymerElements/iron-overlay-behavior/issues/14 + 'click': 'cardTapped', }, cardTapped: function() { - // Debounce wrapper added as workaround for bug - // https://github.com/PolymerElements/iron-overlay-behavior/issues/14 - this.debounce('show-more-info-dialog', function() { - uiActions.showMoreInfoDialog(this.stateObj.entityId); - }, 1); + uiActions.showMoreInfoDialog(this.stateObj.entityId); }, }); })(); diff --git a/homeassistant/components/frontend/www_static/polymer/cards/state-card.html b/homeassistant/components/frontend/www_static/polymer/cards/state-card.html index a1d46c07282..341d21212a1 100644 --- a/homeassistant/components/frontend/www_static/polymer/cards/state-card.html +++ b/homeassistant/components/frontend/www_static/polymer/cards/state-card.html @@ -37,15 +37,13 @@ }, listeners: { - 'tap': 'cardTapped', + // listening for click instead of tap as a work around + // https://github.com/PolymerElements/iron-overlay-behavior/issues/14 + 'click': 'cardTapped', }, cardTapped: function() { - // Debounce wrapper added as workaround for bug - // https://github.com/PolymerElements/iron-overlay-behavior/issues/14 - this.debounce('show-more-info-dialog', function() { - uiActions.showMoreInfoDialog(this.stateObj.entityId); - }, 1); + uiActions.showMoreInfoDialog(this.stateObj.entityId); }, }); })(); From 801eabe598c687d7bf9b205fc4a58f9e70c30250 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 10 Jun 2015 23:51:33 -0700 Subject: [PATCH 019/114] Bugfixes for media player more info dialog --- .../polymer/more-infos/more-info-media_player.html | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/frontend/www_static/polymer/more-infos/more-info-media_player.html b/homeassistant/components/frontend/www_static/polymer/more-infos/more-info-media_player.html index 9d16ab9e561..2008fddf41e 100644 --- a/homeassistant/components/frontend/www_static/polymer/more-infos/more-info-media_player.html +++ b/homeassistant/components/frontend/www_static/polymer/more-infos/more-info-media_player.html @@ -29,7 +29,8 @@
+ on-tap='handleTogglePower' + hidden$='[[computeHidePowerButton(isOff, supportsTurnOn, supportsTurnOff)]]'>
-