├── src ├── template │ ├── dialog │ │ ├── error.hbs │ │ ├── linkUserId.hbs │ │ ├── googleSignIn.hbs │ │ ├── clearStream.hbs │ │ ├── deletePlaylist.hbs │ │ ├── updateStreamus.hbs │ │ ├── exportPlaylist.hbs │ │ ├── editPlaylist.hbs │ │ ├── createPlaylist.hbs │ │ ├── dialog.hbs │ │ └── aboutStreamus.hbs │ ├── appBar │ │ ├── playlistTitle.hbs │ │ ├── activePaneFilter.hbs │ │ ├── appBar.hbs │ │ └── searchInputArea.hbs │ ├── stream │ │ ├── clearStreamButton.hbs │ │ ├── saveStreamButton.hbs │ │ ├── streamItems.hbs │ │ ├── streamItem.hbs │ │ └── stream.hbs │ ├── streamControlBar │ │ ├── nextButton.hbs │ │ ├── radioButton.hbs │ │ ├── previousButton.hbs │ │ ├── shuffleButton.hbs │ │ ├── playPauseButton.hbs │ │ ├── repeatButton.hbs │ │ ├── timeLabelArea.hbs │ │ ├── volumeArea.hbs │ │ └── streamControlBar.hbs │ ├── listItemButton │ │ ├── addListItemButton.hbs │ │ ├── deleteListItemButton.hbs │ │ ├── playListItemButton.hbs │ │ ├── saveListItemButton.hbs │ │ ├── optionsListItemButton.hbs │ │ └── playPauseVideoButton.hbs │ ├── element │ │ ├── radioGroup.hbs │ │ ├── switch.hbs │ │ ├── slider.hbs │ │ ├── radioButton.hbs │ │ ├── simpleListItem.hbs │ │ └── checkbox.hbs │ ├── leftPane │ │ ├── leftPane.hbs │ │ ├── playlistItems.hbs │ │ ├── playlistItem.hbs │ │ ├── signIn.hbs │ │ └── activePlaylistArea.hbs │ ├── playlist │ │ ├── playlists.hbs │ │ ├── playlist.hbs │ │ └── playlistsArea.hbs │ ├── activePane │ │ ├── activePane.hbs │ │ └── activePanes.hbs │ ├── backgroundArea.hbs │ ├── search │ │ ├── searchResults.hbs │ │ └── searchResult.hbs │ ├── icon │ │ ├── playIcon_18.hbs │ │ ├── playIcon_30.hbs │ │ ├── volumeOffIcon_24.hbs │ │ ├── previousIcon_24.hbs │ │ ├── nextIcon_24.hbs │ │ ├── pauseIcon_30.hbs │ │ ├── volumeDownIcon_24.hbs │ │ ├── menuIcon_24.hbs │ │ ├── closeIcon_24.hbs │ │ ├── volumeUpIcon_24.hbs │ │ ├── repeatIcon_18.hbs │ │ ├── settingsIcon_24.hbs │ │ ├── optionsIcon_18.hbs │ │ ├── shuffleIcon_18.hbs │ │ ├── deleteIcon_18.hbs │ │ ├── repeatOneIcon_18.hbs │ │ ├── addIcon_18.hbs │ │ ├── searchIcon_24.hbs │ │ ├── pauseIcon_18.hbs │ │ ├── volumeMuteIcon_24.hbs │ │ ├── saveIcon_18.hbs │ │ └── radioIcon_18.hbs │ ├── simpleMenu │ │ ├── simpleMenuItem.hbs │ │ └── simpleMenu.hbs │ ├── tooltip │ │ └── tooltip.hbs │ ├── notification │ │ └── notification.hbs │ ├── behavior │ │ └── resizeEmitter.hbs │ ├── foregroundArea.hbs │ └── selectionBar │ │ └── selectionBar.hbs ├── js │ ├── test │ │ ├── main.js │ │ ├── common │ │ │ └── commonSpecLoader.js │ │ ├── foreground │ │ │ ├── view │ │ │ │ ├── notification │ │ │ │ │ ├── notificationSpecLoader.js │ │ │ │ │ └── notificationView.spec.js │ │ │ │ ├── selectionBar │ │ │ │ │ ├── selectionBarSpecLoader.js │ │ │ │ │ └── selectionBarView.spec.js │ │ │ │ ├── tooltip │ │ │ │ │ ├── tooltipSpecLoader.js │ │ │ │ │ └── tooltipView.spec.js │ │ │ │ ├── behavior │ │ │ │ │ └── behaviorSpecLoader.js │ │ │ │ ├── search │ │ │ │ │ ├── searchSpecLoader.js │ │ │ │ │ ├── searchResultsView.spec.js │ │ │ │ │ └── searchResultView.spec.js │ │ │ │ ├── playlist │ │ │ │ │ ├── playlistSpecLoader.js │ │ │ │ │ ├── playlistsView.spec.js │ │ │ │ │ ├── playlistsAreaView.spec.js │ │ │ │ │ └── playlistView.spec.js │ │ │ │ ├── leftPane │ │ │ │ │ ├── leftPaneSpecLoader.js │ │ │ │ │ ├── playlistItemsView.spec.js │ │ │ │ │ ├── activePlaylistAreaView.spec.js │ │ │ │ │ └── playlistItemView.spec.js │ │ │ │ ├── activePane │ │ │ │ │ ├── activePaneSpecLoader.js │ │ │ │ │ ├── activePanesView.spec.js │ │ │ │ │ └── activePanesRegion.spec.js │ │ │ │ ├── simpleMenu │ │ │ │ │ ├── simpleMenuSpecLoader.js │ │ │ │ │ ├── simpleMenuItemsView.spec.js │ │ │ │ │ ├── simpleMenuView.spec.js │ │ │ │ │ └── simpleMenuItemView.spec.js │ │ │ │ ├── appBar │ │ │ │ │ ├── appBarSpecLoader.js │ │ │ │ │ ├── adminMenuAreaView.spec.js │ │ │ │ │ ├── appBarView.spec.js │ │ │ │ │ └── searchInputAreaView.spec.js │ │ │ │ ├── stream │ │ │ │ │ ├── streamSpecLoader.js │ │ │ │ │ ├── streamItemsView.spec.js │ │ │ │ │ ├── clearStreamButtonView.spec.js │ │ │ │ │ ├── saveStreamButtonView.spec.js │ │ │ │ │ ├── streamView.spec.js │ │ │ │ │ └── streamItemView.spec.js │ │ │ │ ├── element │ │ │ │ │ ├── elementSpecLoader.js │ │ │ │ │ ├── spinnerView.spec.js │ │ │ │ │ ├── sliderView.spec.js │ │ │ │ │ ├── switchView.spec.js │ │ │ │ │ ├── checkboxView.spec.js │ │ │ │ │ ├── radioGroupView.spec.js │ │ │ │ │ ├── radioButtonView.spec.js │ │ │ │ │ └── simpleListItemView.spec.js │ │ │ │ ├── dialog │ │ │ │ │ ├── errorView.spec.js │ │ │ │ │ ├── linkUserIdView.spec.js │ │ │ │ │ ├── clearStreamView.spec.js │ │ │ │ │ ├── googleSignInView.spec.js │ │ │ │ │ ├── aboutStreamusView.spec.js │ │ │ │ │ ├── updateStreamusView.spec.js │ │ │ │ │ ├── aboutStreamusDialogView.spec.js │ │ │ │ │ ├── errorDialogView.spec.js │ │ │ │ │ ├── editPlaylistView.spec.js │ │ │ │ │ ├── settingsView.spec.js │ │ │ │ │ ├── exportPlaylistView.spec.js │ │ │ │ │ ├── settingsDialogView.spec.js │ │ │ │ │ ├── deletePlaylistView.spec.js │ │ │ │ │ ├── updateStreamusDialogView.spec.js │ │ │ │ │ ├── editPlaylistDialogView.spec.js │ │ │ │ │ ├── createPlaylistView.spec.js │ │ │ │ │ ├── deletePlaylistDialogView.spec.js │ │ │ │ │ ├── clearStreamDialogView.spec.js │ │ │ │ │ ├── createPlaylistDialogView.spec.js │ │ │ │ │ ├── exportPlaylistDialogView.spec.js │ │ │ │ │ ├── linkUserIdDialogView.spec.js │ │ │ │ │ └── googleSignInDialogView.spec.js │ │ │ │ ├── listItemButton │ │ │ │ │ ├── listItemButtonsView.spec.js │ │ │ │ │ ├── deletePlaylistButtonView.spec.js │ │ │ │ │ ├── playlistOptionsButtonView.spec.js │ │ │ │ │ ├── listItemButtonSpecLoader.js │ │ │ │ │ ├── deleteListItemButtonView.spec.js │ │ │ │ │ ├── videoOptionsButtonView.spec.js │ │ │ │ │ ├── addVideoButtonView.spec.js │ │ │ │ │ ├── saveVideoButtonView.spec.js │ │ │ │ │ ├── addPlaylistButtonView.spec.js │ │ │ │ │ ├── playPlaylistButtonView.spec.js │ │ │ │ │ └── playPauseVideoButtonView.spec.js │ │ │ │ ├── streamControlBar │ │ │ │ │ ├── volumeAreaView.spec.js │ │ │ │ │ ├── streamControlBarView.spec.js │ │ │ │ │ ├── radioButtonView.spec.js │ │ │ │ │ ├── repeatButtonView.spec.js │ │ │ │ │ ├── shuffleButtonView.spec.js │ │ │ │ │ ├── streamControlBarSpecLoader.js │ │ │ │ │ └── playPauseButtonView.spec.js │ │ │ │ ├── foregroundAreaView.spec.js │ │ │ │ └── viewTestUtility.js │ │ │ └── model │ │ │ │ └── streamControlBar │ │ │ │ ├── timeLabelArea.spec.js │ │ │ │ └── timeSlider.spec.js │ │ ├── mochaSetup.js │ │ ├── background │ │ │ ├── model │ │ │ │ ├── playlistItem.spec.js │ │ │ │ └── clientErrorManager.spec.js │ │ │ ├── collection │ │ │ │ ├── playlists.spec.js │ │ │ │ └── streamItems.spec.js │ │ │ └── backgroundSpecLoader.js │ │ ├── contentScript │ │ │ ├── contentScriptSpecLoader.js │ │ │ └── youTubePlayer │ │ │ │ └── model │ │ │ │ └── port.spec.js │ │ └── test.js │ ├── background │ │ ├── main.js │ │ ├── enum │ │ │ ├── omniboxModifier.js │ │ │ ├── videoType.js │ │ │ ├── entityType.js │ │ │ ├── youTubeQuality.js │ │ │ ├── syncActionType.js │ │ │ ├── youTubeServiceType.js │ │ │ └── chromeCommand.js │ │ ├── model │ │ │ ├── dataSourceManager.js │ │ │ ├── searchResult.js │ │ │ ├── shareCode.js │ │ │ ├── radioButton.js │ │ │ └── shuffleButton.js │ │ ├── plugins.js │ │ ├── view │ │ │ ├── clipboardRegion.js │ │ │ ├── clipboardView.js │ │ │ └── backgroundAreaView.js │ │ ├── key │ │ │ └── youTubeAPIKey.js.example │ │ └── collection │ │ │ └── videos.js │ ├── foreground │ │ ├── main.js │ │ ├── enum │ │ │ ├── fixedPosition.js │ │ │ ├── orientation.js │ │ │ ├── activePaneType.js │ │ │ └── keyCode.js │ │ ├── model │ │ │ ├── notification │ │ │ │ └── notification.js │ │ │ ├── appBar │ │ │ │ └── adminMenuArea.js │ │ │ ├── listItemButton │ │ │ │ └── listItemButton.js │ │ │ ├── tooltip │ │ │ │ └── tooltip.js │ │ │ ├── dialog │ │ │ │ ├── editPlaylist.js │ │ │ │ ├── dialog.js │ │ │ │ ├── createPlaylist.js │ │ │ │ └── exportPlaylist.js │ │ │ ├── element │ │ │ │ ├── switch.js │ │ │ │ ├── radioButton.js │ │ │ │ ├── simpleListItem.js │ │ │ │ ├── checkbox.js │ │ │ │ └── radioGroup.js │ │ │ ├── activePane │ │ │ │ └── activePane.js │ │ │ ├── simpleMenu │ │ │ │ ├── simpleMenuItem.js │ │ │ │ └── simpleMenu.js │ │ │ ├── streamControlBar │ │ │ │ ├── timeLabelArea.js │ │ │ │ └── timeSlider.js │ │ │ └── stream │ │ │ │ └── clearStreamButton.js │ │ ├── collection │ │ │ ├── element │ │ │ │ ├── switches.js │ │ │ │ ├── simpleListItems.js │ │ │ │ ├── radioGroups.js │ │ │ │ └── checkboxes.js │ │ │ └── simpleMenu │ │ │ │ └── simpleMenuItems.js │ │ ├── view │ │ │ ├── element │ │ │ │ ├── spinnerView.js │ │ │ │ ├── radioButtonView.js │ │ │ │ ├── radioGroupView.js │ │ │ │ └── switchView.js │ │ │ ├── dialog │ │ │ │ ├── clearStreamView.js │ │ │ │ ├── linkUserIdView.js │ │ │ │ ├── googleSignInView.js │ │ │ │ ├── updateStreamusView.js │ │ │ │ ├── deletePlaylistView.js │ │ │ │ ├── aboutStreamusDialogView.js │ │ │ │ ├── errorDialogView.js │ │ │ │ ├── errorView.js │ │ │ │ ├── updateStreamusDialogView.js │ │ │ │ ├── clearStreamDialogView.js │ │ │ │ ├── settingsDialogView.js │ │ │ │ ├── deletePlaylistDialogView.js │ │ │ │ ├── googleSignInDialogView.js │ │ │ │ ├── linkUserIdDialogView.js │ │ │ │ ├── editPlaylistDialogView.js │ │ │ │ ├── exportPlaylistDialogView.js │ │ │ │ └── createPlaylistDialogView.js │ │ │ ├── behavior │ │ │ │ └── dialogContent.js │ │ │ ├── appBar │ │ │ │ └── appBarRegion.js │ │ │ ├── streamControlBar │ │ │ │ ├── streamControlBarRegion.js │ │ │ │ ├── nextButtonView.js │ │ │ │ └── previousButtonView.js │ │ │ ├── notification │ │ │ │ └── notificationRegion.js │ │ │ ├── activePane │ │ │ │ ├── activePanesRegion.js │ │ │ │ └── activePanesView.js │ │ │ ├── listItemButton │ │ │ │ ├── playlistOptionsButtonView.js │ │ │ │ ├── videoOptionsButtonView.js │ │ │ │ └── deleteListItemButtonView.js │ │ │ └── tooltip │ │ │ │ └── tooltipView.js │ │ └── plugins.js │ ├── common │ │ ├── enum │ │ │ ├── exportFileType.js │ │ │ ├── direction.js │ │ │ ├── layoutType.js │ │ │ ├── videoQuality.js │ │ │ ├── repeatButtonState.js │ │ │ ├── youTubePlayerState.js │ │ │ ├── playerState.js │ │ │ ├── listItemType.js │ │ │ ├── dataSourceType.js │ │ │ ├── videoCommand.js │ │ │ ├── desktopNotificationDuration.js │ │ │ └── youTubePlayerError.js │ │ └── shim │ │ │ ├── backbone.cocktail.shim.js │ │ │ ├── backbone.marionette.toJson.shim.js │ │ │ ├── handlebars.helpers.shim.js │ │ │ ├── lodash.mixin.shim.js │ │ │ ├── lodash.reference.shim.js │ │ │ └── backbone.marionette.region.shim.js │ └── contentScript │ │ └── youTubePlayer │ │ ├── main.js │ │ ├── plugins.js │ │ ├── model │ │ └── videoStream.js │ │ ├── sandboxInject.js │ │ └── application.js ├── less │ ├── navBar.less │ ├── adminMenuArea.less │ ├── selectionBar.less │ ├── activePane.less │ ├── flexRow.less │ ├── searchArea.less │ ├── text.less │ ├── reset.less │ ├── transition.less │ ├── appBar.less │ ├── tooltip.less │ ├── font.less │ ├── timeArea.less │ ├── menu.less │ ├── resizeEmitter.less │ ├── activePaneFilter.less │ ├── boxShadow.less │ ├── contentBar.less │ ├── overlay.less │ ├── grouping.less │ ├── streamControlBar.less │ ├── volumeArea.less │ ├── notification.less │ ├── flexColumn.less │ ├── scrollbar.less │ ├── sortable.less │ ├── instruction.less │ ├── radio.less │ ├── searchInputArea.less │ ├── switch.less │ └── color.less ├── img │ ├── logo.png │ ├── red_0bar_19.png │ ├── red_0bar_38.png │ ├── red_1bar_19.png │ ├── red_1bar_38.png │ ├── red_2bar_19.png │ ├── red_2bar_38.png │ ├── red_3bar_19.png │ ├── red_3bar_38.png │ ├── red_4bar_19.png │ ├── red_4bar_38.png │ ├── green_0bar_19.png │ ├── green_0bar_38.png │ ├── green_1bar_19.png │ ├── green_1bar_38.png │ ├── green_2bar_19.png │ ├── green_2bar_38.png │ ├── green_3bar_19.png │ ├── green_3bar_38.png │ ├── green_4bar_19.png │ ├── green_4bar_38.png │ ├── yellow_0bar_19.png │ ├── yellow_0bar_38.png │ ├── yellow_1bar_19.png │ ├── yellow_1bar_38.png │ ├── yellow_2bar_19.png │ ├── yellow_2bar_38.png │ ├── yellow_3bar_19.png │ ├── yellow_3bar_38.png │ ├── yellow_4bar_19.png │ ├── yellow_4bar_38.png │ ├── streamus_icon128.png │ ├── streamus_icon16.png │ ├── streamus_icon19.png │ ├── streamus_icon38.png │ ├── streamus_icon48.png │ └── streamus_icon_white19.png ├── font │ ├── Roboto-Medium.ttf │ └── Roboto-Regular.ttf ├── css │ └── contentScript │ │ ├── youTube.css │ │ └── beatport.css ├── background.html ├── foreground.html └── test.html ├── .jscsrc ├── .gitignore ├── .travis.yml ├── .gitattributes └── Streamus Chrome Extension.sln /src/template/dialog/error.hbs: -------------------------------------------------------------------------------- 1 | {{text}} -------------------------------------------------------------------------------- /src/template/appBar/playlistTitle.hbs: -------------------------------------------------------------------------------- 1 | {{title}} -------------------------------------------------------------------------------- /src/js/test/main.js: -------------------------------------------------------------------------------- 1 | System.import('test/plugins'); -------------------------------------------------------------------------------- /src/template/dialog/linkUserId.hbs: -------------------------------------------------------------------------------- 1 | {{i18n 'linkAccount'}} -------------------------------------------------------------------------------- /src/template/stream/clearStreamButton.hbs: -------------------------------------------------------------------------------- 1 | {{i18n 'clear'}} -------------------------------------------------------------------------------- /src/template/stream/saveStreamButton.hbs: -------------------------------------------------------------------------------- 1 | {{i18n 'saveAll'}} -------------------------------------------------------------------------------- /src/template/streamControlBar/nextButton.hbs: -------------------------------------------------------------------------------- 1 | {{{nextIcon}}} -------------------------------------------------------------------------------- /src/js/background/main.js: -------------------------------------------------------------------------------- 1 | System.import('background/plugins'); -------------------------------------------------------------------------------- /src/js/foreground/main.js: -------------------------------------------------------------------------------- 1 | System.import('foreground/plugins'); -------------------------------------------------------------------------------- /src/less/navBar.less: -------------------------------------------------------------------------------- 1 | .navBar { 2 | max-width: 400px; 3 | } -------------------------------------------------------------------------------- /src/template/dialog/googleSignIn.hbs: -------------------------------------------------------------------------------- 1 | {{i18n 'googleSignIn'}} -------------------------------------------------------------------------------- /src/template/listItemButton/addListItemButton.hbs: -------------------------------------------------------------------------------- 1 | {{{addIcon}}} -------------------------------------------------------------------------------- /src/template/streamControlBar/radioButton.hbs: -------------------------------------------------------------------------------- 1 | {{{radioIcon}}} -------------------------------------------------------------------------------- /src/template/dialog/clearStream.hbs: -------------------------------------------------------------------------------- 1 | {{i18n 'clearStreamQuestion'}} -------------------------------------------------------------------------------- /src/template/dialog/deletePlaylist.hbs: -------------------------------------------------------------------------------- 1 | {{i18n 'delete'}} {{title}}? -------------------------------------------------------------------------------- /src/template/dialog/updateStreamus.hbs: -------------------------------------------------------------------------------- 1 | {{i18n 'anUpdateIsAvailable'}} -------------------------------------------------------------------------------- /src/template/element/radioGroup.hbs: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /src/template/listItemButton/deleteListItemButton.hbs: -------------------------------------------------------------------------------- 1 | {{{deleteIcon}}} -------------------------------------------------------------------------------- /src/template/listItemButton/playListItemButton.hbs: -------------------------------------------------------------------------------- 1 | {{{playIcon}}} -------------------------------------------------------------------------------- /src/template/listItemButton/saveListItemButton.hbs: -------------------------------------------------------------------------------- 1 | {{{saveIcon}}} -------------------------------------------------------------------------------- /src/template/streamControlBar/previousButton.hbs: -------------------------------------------------------------------------------- 1 | {{{previousIcon}}} -------------------------------------------------------------------------------- /src/template/streamControlBar/shuffleButton.hbs: -------------------------------------------------------------------------------- 1 | {{{shuffleIcon}}} -------------------------------------------------------------------------------- /src/js/test/common/commonSpecLoader.js: -------------------------------------------------------------------------------- 1 | import 'test/common/utility.spec'; -------------------------------------------------------------------------------- /src/less/adminMenuArea.less: -------------------------------------------------------------------------------- 1 | .adminMenuArea-menu { 2 | right: 16px; 3 | } -------------------------------------------------------------------------------- /src/template/listItemButton/optionsListItemButton.hbs: -------------------------------------------------------------------------------- 1 | {{{optionsIcon}}} -------------------------------------------------------------------------------- /src/js/background/enum/omniboxModifier.js: -------------------------------------------------------------------------------- 1 | export default { 2 | Add: 'add' 3 | }; -------------------------------------------------------------------------------- /src/template/leftPane/leftPane.hbs: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /src/template/playlist/playlists.hbs: -------------------------------------------------------------------------------- 1 |
    -------------------------------------------------------------------------------- /src/template/activePane/activePane.hbs: -------------------------------------------------------------------------------- 1 |
    -------------------------------------------------------------------------------- /src/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeoMix/StreamusChromeExtension/HEAD/src/img/logo.png -------------------------------------------------------------------------------- /src/js/common/enum/exportFileType.js: -------------------------------------------------------------------------------- 1 | export default { 2 | Csv: 'csv', 3 | Json: 'json' 4 | }; -------------------------------------------------------------------------------- /src/js/background/enum/videoType.js: -------------------------------------------------------------------------------- 1 | export default { 2 | None: 'none', 3 | YouTube: 'youTube' 4 | }; -------------------------------------------------------------------------------- /src/less/selectionBar.less: -------------------------------------------------------------------------------- 1 | @import "color"; 2 | 3 | .selectionBar { 4 | color: @dark--primary; 5 | } -------------------------------------------------------------------------------- /src/js/background/enum/entityType.js: -------------------------------------------------------------------------------- 1 | export default { 2 | None: 'none', 3 | Playlist: 'playlist' 4 | }; -------------------------------------------------------------------------------- /src/template/stream/streamItems.hbs: -------------------------------------------------------------------------------- 1 |
      -------------------------------------------------------------------------------- /src/img/red_0bar_19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeoMix/StreamusChromeExtension/HEAD/src/img/red_0bar_19.png -------------------------------------------------------------------------------- /src/img/red_0bar_38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeoMix/StreamusChromeExtension/HEAD/src/img/red_0bar_38.png -------------------------------------------------------------------------------- /src/img/red_1bar_19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeoMix/StreamusChromeExtension/HEAD/src/img/red_1bar_19.png -------------------------------------------------------------------------------- /src/img/red_1bar_38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeoMix/StreamusChromeExtension/HEAD/src/img/red_1bar_38.png -------------------------------------------------------------------------------- /src/img/red_2bar_19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeoMix/StreamusChromeExtension/HEAD/src/img/red_2bar_19.png -------------------------------------------------------------------------------- /src/img/red_2bar_38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeoMix/StreamusChromeExtension/HEAD/src/img/red_2bar_38.png -------------------------------------------------------------------------------- /src/img/red_3bar_19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeoMix/StreamusChromeExtension/HEAD/src/img/red_3bar_19.png -------------------------------------------------------------------------------- /src/img/red_3bar_38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeoMix/StreamusChromeExtension/HEAD/src/img/red_3bar_38.png -------------------------------------------------------------------------------- /src/img/red_4bar_19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeoMix/StreamusChromeExtension/HEAD/src/img/red_4bar_19.png -------------------------------------------------------------------------------- /src/img/red_4bar_38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeoMix/StreamusChromeExtension/HEAD/src/img/red_4bar_38.png -------------------------------------------------------------------------------- /src/js/common/enum/direction.js: -------------------------------------------------------------------------------- 1 | export default { 2 | None: 'none', 3 | Down: 'down', 4 | Up: 'up' 5 | }; -------------------------------------------------------------------------------- /src/js/common/enum/layoutType.js: -------------------------------------------------------------------------------- 1 | export default { 2 | FullPane: 'fullPane', 3 | SplitPane: 'splitPane' 4 | }; -------------------------------------------------------------------------------- /src/template/backgroundArea.hbs: -------------------------------------------------------------------------------- 1 |
      2 |
      -------------------------------------------------------------------------------- /src/template/leftPane/playlistItems.hbs: -------------------------------------------------------------------------------- 1 |
        -------------------------------------------------------------------------------- /src/font/Roboto-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeoMix/StreamusChromeExtension/HEAD/src/font/Roboto-Medium.ttf -------------------------------------------------------------------------------- /src/img/green_0bar_19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeoMix/StreamusChromeExtension/HEAD/src/img/green_0bar_19.png -------------------------------------------------------------------------------- /src/img/green_0bar_38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeoMix/StreamusChromeExtension/HEAD/src/img/green_0bar_38.png -------------------------------------------------------------------------------- /src/img/green_1bar_19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeoMix/StreamusChromeExtension/HEAD/src/img/green_1bar_19.png -------------------------------------------------------------------------------- /src/img/green_1bar_38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeoMix/StreamusChromeExtension/HEAD/src/img/green_1bar_38.png -------------------------------------------------------------------------------- /src/img/green_2bar_19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeoMix/StreamusChromeExtension/HEAD/src/img/green_2bar_19.png -------------------------------------------------------------------------------- /src/img/green_2bar_38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeoMix/StreamusChromeExtension/HEAD/src/img/green_2bar_38.png -------------------------------------------------------------------------------- /src/img/green_3bar_19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeoMix/StreamusChromeExtension/HEAD/src/img/green_3bar_19.png -------------------------------------------------------------------------------- /src/img/green_3bar_38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeoMix/StreamusChromeExtension/HEAD/src/img/green_3bar_38.png -------------------------------------------------------------------------------- /src/img/green_4bar_19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeoMix/StreamusChromeExtension/HEAD/src/img/green_4bar_19.png -------------------------------------------------------------------------------- /src/img/green_4bar_38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeoMix/StreamusChromeExtension/HEAD/src/img/green_4bar_38.png -------------------------------------------------------------------------------- /src/img/yellow_0bar_19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeoMix/StreamusChromeExtension/HEAD/src/img/yellow_0bar_19.png -------------------------------------------------------------------------------- /src/img/yellow_0bar_38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeoMix/StreamusChromeExtension/HEAD/src/img/yellow_0bar_38.png -------------------------------------------------------------------------------- /src/img/yellow_1bar_19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeoMix/StreamusChromeExtension/HEAD/src/img/yellow_1bar_19.png -------------------------------------------------------------------------------- /src/img/yellow_1bar_38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeoMix/StreamusChromeExtension/HEAD/src/img/yellow_1bar_38.png -------------------------------------------------------------------------------- /src/img/yellow_2bar_19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeoMix/StreamusChromeExtension/HEAD/src/img/yellow_2bar_19.png -------------------------------------------------------------------------------- /src/img/yellow_2bar_38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeoMix/StreamusChromeExtension/HEAD/src/img/yellow_2bar_38.png -------------------------------------------------------------------------------- /src/img/yellow_3bar_19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeoMix/StreamusChromeExtension/HEAD/src/img/yellow_3bar_19.png -------------------------------------------------------------------------------- /src/img/yellow_3bar_38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeoMix/StreamusChromeExtension/HEAD/src/img/yellow_3bar_38.png -------------------------------------------------------------------------------- /src/img/yellow_4bar_19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeoMix/StreamusChromeExtension/HEAD/src/img/yellow_4bar_19.png -------------------------------------------------------------------------------- /src/img/yellow_4bar_38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeoMix/StreamusChromeExtension/HEAD/src/img/yellow_4bar_38.png -------------------------------------------------------------------------------- /src/less/activePane.less: -------------------------------------------------------------------------------- 1 | @import "utility"; 2 | 3 | .activePane:not(:first-child) { 4 | .u-bordered--left; 5 | } -------------------------------------------------------------------------------- /src/font/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeoMix/StreamusChromeExtension/HEAD/src/font/Roboto-Regular.ttf -------------------------------------------------------------------------------- /src/img/streamus_icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeoMix/StreamusChromeExtension/HEAD/src/img/streamus_icon128.png -------------------------------------------------------------------------------- /src/img/streamus_icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeoMix/StreamusChromeExtension/HEAD/src/img/streamus_icon16.png -------------------------------------------------------------------------------- /src/img/streamus_icon19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeoMix/StreamusChromeExtension/HEAD/src/img/streamus_icon19.png -------------------------------------------------------------------------------- /src/img/streamus_icon38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeoMix/StreamusChromeExtension/HEAD/src/img/streamus_icon38.png -------------------------------------------------------------------------------- /src/img/streamus_icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeoMix/StreamusChromeExtension/HEAD/src/img/streamus_icon48.png -------------------------------------------------------------------------------- /src/js/foreground/enum/fixedPosition.js: -------------------------------------------------------------------------------- 1 | export default { 2 | None: 'none', 3 | Top: 'top', 4 | Bottom: 'bottom' 5 | }; -------------------------------------------------------------------------------- /src/js/foreground/enum/orientation.js: -------------------------------------------------------------------------------- 1 | export default { 2 | Horizontal: 'horizontal', 3 | Vertical: 'vertical' 4 | }; -------------------------------------------------------------------------------- /src/less/flexRow.less: -------------------------------------------------------------------------------- 1 | .flexRow { 2 | display: flex; 3 | flex: 1; 4 | flex-direction: row; 5 | min-height: 0; 6 | } -------------------------------------------------------------------------------- /src/template/search/searchResults.hbs: -------------------------------------------------------------------------------- 1 |
          -------------------------------------------------------------------------------- /src/js/common/enum/videoQuality.js: -------------------------------------------------------------------------------- 1 | export default { 2 | Highest: 'highest', 3 | Auto: 'auto', 4 | Lowest: 'lowest' 5 | }; -------------------------------------------------------------------------------- /src/img/streamus_icon_white19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeoMix/StreamusChromeExtension/HEAD/src/img/streamus_icon_white19.png -------------------------------------------------------------------------------- /src/js/test/foreground/view/notification/notificationSpecLoader.js: -------------------------------------------------------------------------------- 1 | import 'test/foreground/view/notification/notificationView.spec'; -------------------------------------------------------------------------------- /src/js/test/foreground/view/selectionBar/selectionBarSpecLoader.js: -------------------------------------------------------------------------------- 1 | import 'test/foreground/view/selectionBar/selectionBarView.spec'; -------------------------------------------------------------------------------- /src/template/icon/playIcon_18.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "google", 3 | "maximumLineLength": 140, 4 | "requireLineFeedAtFileEnd": null, 5 | "esnext": true 6 | } -------------------------------------------------------------------------------- /src/js/background/enum/youTubeQuality.js: -------------------------------------------------------------------------------- 1 | export default { 2 | Highres: 'highres', 3 | Default: 'default', 4 | Small: 'small' 5 | }; -------------------------------------------------------------------------------- /src/js/foreground/enum/activePaneType.js: -------------------------------------------------------------------------------- 1 | export default { 2 | None: 'none', 3 | Stream: 'stream', 4 | Playlist: 'playlist' 5 | }; -------------------------------------------------------------------------------- /src/js/common/enum/repeatButtonState.js: -------------------------------------------------------------------------------- 1 | export default { 2 | Off: 'off', 3 | RepeatVideo: 'repeatVideo', 4 | RepeatAll: 'repeatAll' 5 | }; -------------------------------------------------------------------------------- /src/template/icon/playIcon_30.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/template/icon/volumeOffIcon_24.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/js/contentScript/youTubePlayer/main.js: -------------------------------------------------------------------------------- 1 | System.baseURL = chrome.extension.getURL('/'); 2 | System.import('contentScript/youTubePlayer/plugins'); -------------------------------------------------------------------------------- /src/template/element/switch.hbs: -------------------------------------------------------------------------------- 1 | 4 | 5 |
          -------------------------------------------------------------------------------- /src/template/simpleMenu/simpleMenuItem.hbs: -------------------------------------------------------------------------------- 1 |
          2 |
          3 | {{text}} 4 |
          5 |
          -------------------------------------------------------------------------------- /src/js/test/mochaSetup.js: -------------------------------------------------------------------------------- 1 | import mocha from 'mocha'; 2 | 3 | // Make describe/it defined for viewing in browser 4 | mocha.setup({ 5 | ui: 'bdd' 6 | }); -------------------------------------------------------------------------------- /src/less/searchArea.less: -------------------------------------------------------------------------------- 1 | .searchArea { 2 | &.is-fullWidth { 3 | width: 100%; 4 | } 5 | 6 | &.is-halfWidth { 7 | width: 50%; 8 | } 9 | } -------------------------------------------------------------------------------- /src/template/tooltip/tooltip.hbs: -------------------------------------------------------------------------------- 1 |
          2 | {{text}} 3 |
          -------------------------------------------------------------------------------- /src/js/background/enum/syncActionType.js: -------------------------------------------------------------------------------- 1 | export default { 2 | None: 'none', 3 | Updated: 'updated', 4 | Added: 'added', 5 | Removed: 'removed' 6 | }; -------------------------------------------------------------------------------- /src/js/common/shim/backbone.cocktail.shim.js: -------------------------------------------------------------------------------- 1 | import Backbone from 'backbone'; 2 | import Cocktail from 'backbone.cocktail'; 3 | 4 | Cocktail.patch(Backbone); -------------------------------------------------------------------------------- /src/js/test/background/model/playlistItem.spec.js: -------------------------------------------------------------------------------- 1 | import PlaylistItem from 'background/model/playlistItem'; 2 | 3 | describe('PlaylistItem', function() {}); -------------------------------------------------------------------------------- /src/css/contentScript/youTube.css: -------------------------------------------------------------------------------- 1 | #streamus-playlistSelect { 2 | margin-right: 10px; 3 | } 4 | 5 | #streamus-addVideoButton { 6 | margin-top: -2px; 7 | } -------------------------------------------------------------------------------- /src/js/test/contentScript/contentScriptSpecLoader.js: -------------------------------------------------------------------------------- 1 | // /youTubePlayer/ 2 | // /youTubePlayer/model/ 3 | import 'test/contentScript/youTubePlayer/model/port.spec'; -------------------------------------------------------------------------------- /src/template/notification/notification.hbs: -------------------------------------------------------------------------------- 1 |
          2 | {{message}} 3 |
          -------------------------------------------------------------------------------- /src/js/foreground/enum/keyCode.js: -------------------------------------------------------------------------------- 1 | export default { 2 | Enter: 13, 3 | Space: 32, 4 | ArrowLeft: 37, 5 | ArrowUp: 38, 6 | ArrowRight: 39, 7 | ArrowDown: 40 8 | }; -------------------------------------------------------------------------------- /src/js/test/foreground/view/tooltip/tooltipSpecLoader.js: -------------------------------------------------------------------------------- 1 | import 'test/foreground/view/tooltip/tooltipRegion.spec'; 2 | import 'test/foreground/view/tooltip/tooltipView.spec'; -------------------------------------------------------------------------------- /src/js/common/enum/youTubePlayerState.js: -------------------------------------------------------------------------------- 1 | export default { 2 | Unstarted: -1, 3 | Ended: 0, 4 | Playing: 1, 5 | Paused: 2, 6 | Buffering: 3, 7 | VideoCued: 5 8 | }; -------------------------------------------------------------------------------- /src/js/test/foreground/view/behavior/behaviorSpecLoader.js: -------------------------------------------------------------------------------- 1 | import 'test/foreground/view/behavior/scrollable.spec'; 2 | import 'test/foreground/view/behavior/tooltipable.spec'; -------------------------------------------------------------------------------- /src/template/streamControlBar/playPauseButton.hbs: -------------------------------------------------------------------------------- 1 | 2 | {{{pauseIcon}}} 3 | 4 | 5 | 6 | {{{playIcon}}} 7 | -------------------------------------------------------------------------------- /src/less/text.less: -------------------------------------------------------------------------------- 1 | @import "utility"; 2 | 3 | .text { 4 | cursor: default; 5 | -webkit-user-select: text; 6 | 7 | &.is-clickable { 8 | .u-clickable; 9 | } 10 | } -------------------------------------------------------------------------------- /src/template/listItemButton/playPauseVideoButton.hbs: -------------------------------------------------------------------------------- 1 | 2 | {{{pauseIcon}}} 3 | 4 | 5 | 6 | {{{playIcon}}} 7 | -------------------------------------------------------------------------------- /src/js/common/enum/playerState.js: -------------------------------------------------------------------------------- 1 | export default { 2 | Unstarted: 'unstarted', 3 | Ended: 'ended', 4 | Playing: 'playing', 5 | Paused: 'paused', 6 | Buffering: 'buffering' 7 | }; -------------------------------------------------------------------------------- /src/template/element/slider.hbs: -------------------------------------------------------------------------------- 1 |
          2 |
          3 |
          4 |
          -------------------------------------------------------------------------------- /src/template/icon/previousIcon_24.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/template/streamControlBar/repeatButton.hbs: -------------------------------------------------------------------------------- 1 | 2 | {{{repeatIcon}}} 3 | 4 | 5 | 6 | {{{repeatOneIcon}}} 7 | -------------------------------------------------------------------------------- /src/less/reset.less: -------------------------------------------------------------------------------- 1 | *, 2 | *:before, 3 | *:after { 4 | box-sizing: border-box; 5 | } 6 | 7 | ul, 8 | ol, 9 | li { 10 | padding: 0; 11 | margin: 0; 12 | list-style: none; 13 | } -------------------------------------------------------------------------------- /src/template/icon/nextIcon_24.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/less/transition.less: -------------------------------------------------------------------------------- 1 | @transition-easeOutSine: cubic-bezier(.39,.575,.565,1); 2 | @transition-duration--veryFast: .1s; 3 | @transition-duration--fast: .2s; 4 | @transition-duration--slow: .28s; -------------------------------------------------------------------------------- /src/js/common/enum/listItemType.js: -------------------------------------------------------------------------------- 1 | export default { 2 | None: 'none', 3 | PlaylistItem: 'playlistItem', 4 | StreamItem: 'streamItem', 5 | SearchResult: 'searchResult', 6 | Playlist: 'playlist' 7 | }; -------------------------------------------------------------------------------- /src/template/icon/pauseIcon_30.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/template/icon/volumeDownIcon_24.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/js/background/enum/youTubeServiceType.js: -------------------------------------------------------------------------------- 1 | export default { 2 | Search: 'search', 3 | PlaylistItems: 'playlistItems', 4 | Videos: 'videos', 5 | Channels: 'channels', 6 | Playlists: 'playlists' 7 | }; -------------------------------------------------------------------------------- /src/template/icon/menuIcon_24.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/js/foreground/model/notification/notification.js: -------------------------------------------------------------------------------- 1 | import {Model} from 'backbone'; 2 | 3 | var Notification = Model.extend({ 4 | defaults: { 5 | message: '' 6 | } 7 | }); 8 | 9 | export default Notification; -------------------------------------------------------------------------------- /src/template/activePane/activePanes.hbs: -------------------------------------------------------------------------------- 1 |
          2 |
          -------------------------------------------------------------------------------- /src/template/appBar/activePaneFilter.hbs: -------------------------------------------------------------------------------- 1 |
          2 | {{title}} 3 |
          4 |
          -------------------------------------------------------------------------------- /src/js/foreground/model/appBar/adminMenuArea.js: -------------------------------------------------------------------------------- 1 | import {Model} from 'backbone'; 2 | 3 | var AdminMenuArea = Model.extend({ 4 | defaults: { 5 | menuShown: false 6 | } 7 | }); 8 | 9 | export default AdminMenuArea; -------------------------------------------------------------------------------- /src/js/common/enum/dataSourceType.js: -------------------------------------------------------------------------------- 1 | export default { 2 | None: 'none', 3 | YouTubePlaylist: 'youTubePlaylist', 4 | SharedPlaylist: 'sharedPlaylist', 5 | UserInput: 'userInput', 6 | YouTubeVideo: 'youTubeVideo' 7 | }; -------------------------------------------------------------------------------- /src/js/foreground/model/listItemButton/listItemButton.js: -------------------------------------------------------------------------------- 1 | import {Model} from 'backbone'; 2 | 3 | var ListItemButton = Model.extend({ 4 | defaults: { 5 | enabled: true 6 | } 7 | }); 8 | 9 | export default ListItemButton; -------------------------------------------------------------------------------- /src/template/behavior/resizeEmitter.hbs: -------------------------------------------------------------------------------- 1 |
          2 |
          3 |
          4 |
          5 |
          6 |
          -------------------------------------------------------------------------------- /src/js/test/foreground/view/search/searchSpecLoader.js: -------------------------------------------------------------------------------- 1 | import 'test/foreground/view/search/searchResultsView.spec'; 2 | import 'test/foreground/view/search/searchResultView.spec'; 3 | import 'test/foreground/view/search/searchView.spec'; -------------------------------------------------------------------------------- /src/template/element/radioButton.hbs: -------------------------------------------------------------------------------- 1 |
          2 |
          3 |
          4 |
          5 | 6 |
          7 | {{labelText}} 8 |
          -------------------------------------------------------------------------------- /src/js/foreground/model/tooltip/tooltip.js: -------------------------------------------------------------------------------- 1 | import {Model} from 'backbone'; 2 | 3 | var Tooltip = Model.extend({ 4 | defaults: { 5 | text: '', 6 | top: 0, 7 | left: 0 8 | } 9 | }); 10 | 11 | export default Tooltip; -------------------------------------------------------------------------------- /src/js/test/background/collection/playlists.spec.js: -------------------------------------------------------------------------------- 1 | import Playlists from 'background/collection/playlists'; 2 | 3 | describe('Playlists', function() { 4 | beforeEach(function() { 5 | this.playlists = new Playlists(); 6 | }); 7 | }); -------------------------------------------------------------------------------- /src/js/foreground/collection/element/switches.js: -------------------------------------------------------------------------------- 1 | import {Collection} from 'backbone'; 2 | import Switch from 'foreground/model/element/switch'; 3 | 4 | var Switches = Collection.extend({ 5 | model: Switch 6 | }); 7 | 8 | export default Switches; -------------------------------------------------------------------------------- /src/js/foreground/model/dialog/editPlaylist.js: -------------------------------------------------------------------------------- 1 | import {Model} from 'backbone'; 2 | 3 | var EditPlaylist = Model.extend({ 4 | defaults: { 5 | playlist: null, 6 | valid: true 7 | } 8 | }); 9 | 10 | export default EditPlaylist; -------------------------------------------------------------------------------- /src/js/test/foreground/view/playlist/playlistSpecLoader.js: -------------------------------------------------------------------------------- 1 | import 'test/foreground/view/playlist/playlistsAreaView.spec'; 2 | import 'test/foreground/view/playlist/playlistsView.spec'; 3 | import 'test/foreground/view/playlist/playlistView.spec'; -------------------------------------------------------------------------------- /src/js/test/background/collection/streamItems.spec.js: -------------------------------------------------------------------------------- 1 | import StreamItems from 'background/collection/streamItems'; 2 | 3 | describe('StreamItems', function() { 4 | beforeEach(function() { 5 | this.streamItems = new StreamItems(); 6 | }); 7 | }); -------------------------------------------------------------------------------- /src/less/appBar.less: -------------------------------------------------------------------------------- 1 | @import "flexColumn"; 2 | 3 | .appBar-panel { 4 | // Panels shown via elements in the AppBar should align to the AppBar itself. 5 | // Since buttons are 48px at AppBar is 56px, need 4px to align. 6 | margin-top: 4px; 7 | } -------------------------------------------------------------------------------- /src/js/foreground/model/element/switch.js: -------------------------------------------------------------------------------- 1 | import {Model} from 'backbone'; 2 | 3 | var Switch = Model.extend({ 4 | defaults: { 5 | checked: false, 6 | labelText: '', 7 | property: '' 8 | } 9 | }); 10 | 11 | export default Switch; -------------------------------------------------------------------------------- /src/js/test/foreground/view/leftPane/leftPaneSpecLoader.js: -------------------------------------------------------------------------------- 1 | import 'test/foreground/view/leftPane/activePlaylistAreaView.spec'; 2 | import 'test/foreground/view/leftPane/playlistItemsView.spec'; 3 | import 'test/foreground/view/leftPane/playlistItemView.spec'; -------------------------------------------------------------------------------- /src/js/test/foreground/view/activePane/activePaneSpecLoader.js: -------------------------------------------------------------------------------- 1 | import 'test/foreground/view/activePane/activePanesRegion.spec'; 2 | import 'test/foreground/view/activePane/activePanesView.spec'; 3 | import 'test/foreground/view/activePane/activePaneView.spec'; -------------------------------------------------------------------------------- /src/template/element/simpleListItem.hbs: -------------------------------------------------------------------------------- 1 |
          2 | 3 |
          4 |
          5 | {{title}} 6 |
          7 | 8 |
          9 |
          -------------------------------------------------------------------------------- /src/js/common/enum/videoCommand.js: -------------------------------------------------------------------------------- 1 | export default { 2 | PlayVideo: 'playVideo', 3 | PauseVideo: 'pauseVideo', 4 | SetVolume: 'setVolume', 5 | Mute: 'mute', 6 | UnMute: 'unMute', 7 | SeekTo: 'seekTo', 8 | SetPlaybackQuality: 'setPlaybackQuality' 9 | }; -------------------------------------------------------------------------------- /src/js/foreground/model/element/radioButton.js: -------------------------------------------------------------------------------- 1 | import {Model} from 'backbone'; 2 | 3 | var RadioButton = Model.extend({ 4 | defaults: { 5 | checked: false, 6 | labelText: '', 7 | value: '' 8 | } 9 | }); 10 | 11 | export default RadioButton; -------------------------------------------------------------------------------- /src/js/test/foreground/view/simpleMenu/simpleMenuSpecLoader.js: -------------------------------------------------------------------------------- 1 | import 'test/foreground/view/simpleMenu/simpleMenuItemsView.spec'; 2 | import 'test/foreground/view/simpleMenu/simpleMenuItemView.spec'; 3 | import 'test/foreground/view/simpleMenu/simpleMenuView.spec'; -------------------------------------------------------------------------------- /src/template/streamControlBar/timeLabelArea.hbs: -------------------------------------------------------------------------------- 1 |
          2 |
          /
          3 |
          -------------------------------------------------------------------------------- /src/js/common/enum/desktopNotificationDuration.js: -------------------------------------------------------------------------------- 1 | export default { 2 | OneSecond: 'oneSecond', 3 | TwoSeconds: 'twoSeconds', 4 | ThreeSeconds: 'threeSeconds', 5 | FourSeconds: 'fourSeconds', 6 | FiveSeconds: 'fiveSeconds', 7 | TenSeconds: 'tenSeconds' 8 | }; -------------------------------------------------------------------------------- /src/template/icon/closeIcon_24.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/js/foreground/collection/element/simpleListItems.js: -------------------------------------------------------------------------------- 1 | import {Collection} from 'backbone'; 2 | import SimpleListItem from 'foreground/model/element/simpleListItem'; 3 | 4 | var SimpleListItems = Collection.extend({ 5 | model: SimpleListItem 6 | }); 7 | 8 | export default SimpleListItems; -------------------------------------------------------------------------------- /src/js/contentScript/youTubePlayer/plugins.js: -------------------------------------------------------------------------------- 1 | import 'marionette'; 2 | import Application from 'contentScript/youTubePlayer/application'; 3 | 4 | // Finally, load the application which will initialize the youTubePlayer: 5 | window.Application = new Application(); 6 | window.Application.start(); -------------------------------------------------------------------------------- /src/js/foreground/view/element/spinnerView.js: -------------------------------------------------------------------------------- 1 | import {LayoutView} from 'marionette'; 2 | import spinnerTemplate from 'template/element/spinner.hbs!'; 3 | 4 | var SpinnerView = LayoutView.extend({ 5 | tagName: 'spinner', 6 | template: spinnerTemplate 7 | }); 8 | 9 | export default SpinnerView; -------------------------------------------------------------------------------- /src/template/icon/volumeUpIcon_24.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/js/test/foreground/view/appBar/appBarSpecLoader.js: -------------------------------------------------------------------------------- 1 | import 'test/foreground/view/appBar/activePaneFilterView.spec'; 2 | import 'test/foreground/view/appBar/adminMenuAreaView.spec'; 3 | import 'test/foreground/view/appBar/appBarView.spec'; 4 | import 'test/foreground/view/appBar/searchInputAreaView.spec'; -------------------------------------------------------------------------------- /src/template/dialog/exportPlaylist.hbs: -------------------------------------------------------------------------------- 1 |
          2 |
          {{i18n 'fileType'}}
          3 |
          4 | 9 |
          10 |
          -------------------------------------------------------------------------------- /src/template/icon/repeatIcon_18.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/template/icon/settingsIcon_24.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/template/simpleMenu/simpleMenu.hbs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/less/tooltip.less: -------------------------------------------------------------------------------- 1 | @import "color"; 2 | @import "utility"; 3 | @import "boxShadow"; 4 | @import "transition"; 5 | 6 | .tooltip-content { 7 | max-width: 280px; 8 | padding: 5px 8px; 9 | line-height: 16px; 10 | color: white; 11 | background-color: @gray--tooltip; 12 | border-radius: 2px; 13 | } -------------------------------------------------------------------------------- /src/js/foreground/model/activePane/activePane.js: -------------------------------------------------------------------------------- 1 | import {Model} from 'backbone'; 2 | import ActivePaneType from 'foreground/enum/activePaneType'; 3 | 4 | var ActivePane = Model.extend({ 5 | defaults: { 6 | type: ActivePaneType.None, 7 | relatedModel: null 8 | } 9 | }); 10 | 11 | export default ActivePane; -------------------------------------------------------------------------------- /src/js/test/test.js: -------------------------------------------------------------------------------- 1 | // Load independent modules for each group of tests instead of one giant dumping ground for all of them. 2 | import 'test/background/backgroundSpecLoader'; 3 | import 'test/common/commonSpecLoader'; 4 | import 'test/contentScript/contentScriptSpecLoader'; 5 | import 'test/foreground/foregroundSpecLoader'; -------------------------------------------------------------------------------- /src/less/font.less: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Roboto'; 3 | src: url('../font/Roboto-Regular.ttf'); 4 | font-weight: 400; 5 | font-style: normal; 6 | } 7 | 8 | @font-face { 9 | font-family: 'Roboto'; 10 | src: url('../font/Roboto-Medium.ttf'); 11 | font-weight: 600; 12 | font-style: normal; 13 | } -------------------------------------------------------------------------------- /src/template/icon/optionsIcon_18.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/js/contentScript/youTubePlayer/model/videoStream.js: -------------------------------------------------------------------------------- 1 | import {Model} from 'backbone'; 2 | 3 | var VideoStream = Model.extend({ 4 | defaults: { 5 | currentTime: 0, 6 | muted: false, 7 | volume: 50, 8 | suggestedQuality: 'default', 9 | isSeeking: false 10 | } 11 | }); 12 | 13 | export default VideoStream; -------------------------------------------------------------------------------- /src/js/foreground/model/element/simpleListItem.js: -------------------------------------------------------------------------------- 1 | import {Model} from 'backbone'; 2 | 3 | var SimpleListItem = Model.extend({ 4 | defaults: function() { 5 | return { 6 | property: '', 7 | labelKey: '', 8 | value: '', 9 | options: [] 10 | }; 11 | } 12 | }); 13 | 14 | export default SimpleListItem; -------------------------------------------------------------------------------- /src/js/test/foreground/view/stream/streamSpecLoader.js: -------------------------------------------------------------------------------- 1 | import 'test/foreground/view/stream/clearStreamButtonView.spec'; 2 | import 'test/foreground/view/stream/saveStreamButtonView.spec'; 3 | import 'test/foreground/view/stream/streamItemsView.spec'; 4 | import 'test/foreground/view/stream/streamItemView.spec'; 5 | import 'test/foreground/view/stream/streamView.spec'; -------------------------------------------------------------------------------- /src/template/icon/shuffleIcon_18.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/template/dialog/editPlaylist.hbs: -------------------------------------------------------------------------------- 1 |
          2 | 3 |
          4 | 0 / {{titleMaxLength}} 5 |
          6 |
          -------------------------------------------------------------------------------- /src/template/icon/deleteIcon_18.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/template/icon/repeatOneIcon_18.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/template/icon/addIcon_18.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/background.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Streamus 7 | 8 | 9 | 10 |
          11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/js/foreground/collection/element/radioGroups.js: -------------------------------------------------------------------------------- 1 | import {Collection} from 'backbone'; 2 | import RadioGroup from 'foreground/model/element/radioGroup'; 3 | 4 | var RadioGroups = Collection.extend({ 5 | model: RadioGroup, 6 | 7 | getByProperty: function(property) { 8 | return this.findWhere({ 9 | property: property 10 | }); 11 | } 12 | }); 13 | 14 | export default RadioGroups; -------------------------------------------------------------------------------- /src/js/foreground/collection/element/checkboxes.js: -------------------------------------------------------------------------------- 1 | import {Collection} from 'backbone'; 2 | import Checkbox from 'foreground/model/element/checkbox'; 3 | 4 | var Checkboxes = Collection.extend({ 5 | model: Checkbox, 6 | 7 | isChecked: function(property) { 8 | return this.findWhere({ 9 | property: property 10 | }).get('checked'); 11 | } 12 | }); 13 | 14 | export default Checkboxes; -------------------------------------------------------------------------------- /src/template/icon/searchIcon_24.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/less/timeArea.less: -------------------------------------------------------------------------------- 1 | @import "color"; 2 | 3 | .timeArea { 4 | display: flex; 5 | flex-direction: column; 6 | } 7 | 8 | .timeArea-label { 9 | font-size: 13px; 10 | line-height: 22px; 11 | color: @dark--secondary; 12 | // Even though these labels are text, I don't think it's reasonable to show highlighting over them when clicked. 13 | -webkit-user-select: none; 14 | display: inline-block; 15 | } -------------------------------------------------------------------------------- /src/template/icon/pauseIcon_18.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/less/menu.less: -------------------------------------------------------------------------------- 1 | @import "color"; 2 | @import "transition"; 3 | @import "utility"; 4 | 5 | .menu-items { 6 | max-width: 200px; 7 | padding: 8px 0; 8 | border-radius: 2px; 9 | } 10 | 11 | // The maximum height of a simple menu should be one or more rows less than the view height. 12 | // This ensures a tappable area outside of the simple menu with which to dismiss the menu. 13 | .menu-simpleItems { 14 | max-height: 308px; 15 | } -------------------------------------------------------------------------------- /src/css/contentScript/beatport.css: -------------------------------------------------------------------------------- 1 | .streamusButton, 2 | .streamusButton:hover { 3 | color: rgb(15,157,88) !important; 4 | } 5 | 6 | .streamusButton > svg { 7 | fill: rgb(15, 157, 88); 8 | } 9 | 10 | .streamusButton:hover { 11 | text-shadow: 0 0 15px rgba(15,157,88,0.6) !important; 12 | -webkit-filter: drop-shadow(0 0 7px rgba(15,157,88,0.6)) !important; 13 | filter: drop-shadow(0 0 7px rgba(15,157,88,0.6)) !important; 14 | } -------------------------------------------------------------------------------- /src/template/playlist/playlist.hbs: -------------------------------------------------------------------------------- 1 |
          2 | 3 |
          4 |
          5 | {{title}} 6 |
          7 | 8 |
          9 |
          10 | 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Streamus 2 | dist 3 | release 4 | compiled 5 | src/js/background/key/*.js 6 | !src/js/background/key/*.example.js 7 | 8 | #grunt-webstore-upload private variables 9 | .webstoreUploadCredentials 10 | 11 | # CSS generated by compiling LESS should not be committed 12 | src/css/*.css 13 | 14 | # JSPM 15 | jspm_packages/*/* 16 | jspm_packages 17 | 18 | # Node 19 | node_modules 20 | 21 | # Visual Studio 22 | *.suo 23 | *.user 24 | obj 25 | bin -------------------------------------------------------------------------------- /src/template/icon/volumeMuteIcon_24.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/template/element/checkbox.hbs: -------------------------------------------------------------------------------- 1 | {{#if labelText}} 2 | {{#if iconOnLeft}} 3 | 4 | 5 | 8 | {{else}} 9 | 12 | 13 | 14 | {{/if}} 15 | {{else}} 16 | 17 | {{/if}} -------------------------------------------------------------------------------- /src/js/foreground/view/dialog/clearStreamView.js: -------------------------------------------------------------------------------- 1 | import {LayoutView} from 'marionette'; 2 | import DialogContent from 'foreground/view/behavior/dialogContent'; 3 | import clearStreamTemplate from 'template/dialog/clearStream.hbs!'; 4 | 5 | var ClearStreamView = LayoutView.extend({ 6 | template: clearStreamTemplate, 7 | behaviors: { 8 | DialogContent: { 9 | behaviorClass: DialogContent 10 | } 11 | } 12 | }); 13 | 14 | export default ClearStreamView; -------------------------------------------------------------------------------- /src/js/foreground/view/dialog/linkUserIdView.js: -------------------------------------------------------------------------------- 1 | import {LayoutView} from 'marionette'; 2 | import DialogContent from 'foreground/view/behavior/dialogContent'; 3 | import linkUserIdTemplate from 'template/dialog/linkUserId.hbs!'; 4 | 5 | var LinkUserIdView = LayoutView.extend({ 6 | template: linkUserIdTemplate, 7 | 8 | behaviors: { 9 | DialogContent: { 10 | behaviorClass: DialogContent 11 | } 12 | } 13 | }); 14 | 15 | export default LinkUserIdView; -------------------------------------------------------------------------------- /src/js/test/foreground/view/element/elementSpecLoader.js: -------------------------------------------------------------------------------- 1 | import 'test/foreground/view/element/checkboxView.spec'; 2 | import 'test/foreground/view/element/radioButtonView.spec'; 3 | import 'test/foreground/view/element/radioGroupView.spec'; 4 | import 'test/foreground/view/element/simpleListItemView.spec'; 5 | import 'test/foreground/view/element/sliderView.spec'; 6 | import 'test/foreground/view/element/spinnerView.spec'; 7 | import 'test/foreground/view/element/switchView.spec'; -------------------------------------------------------------------------------- /src/js/background/model/dataSourceManager.js: -------------------------------------------------------------------------------- 1 | import {Model} from 'backbone'; 2 | import DataSource from 'background/model/dataSource'; 3 | 4 | // Builds DataSource objects which originate from the background's instance of BB. 5 | // Necessary for getting expected results from 'instanceof' checks. 6 | var DataSourceManager = Model.extend({ 7 | getDataSource: function(options) { 8 | return new DataSource(options); 9 | } 10 | }); 11 | 12 | export default DataSourceManager; -------------------------------------------------------------------------------- /src/js/foreground/model/simpleMenu/simpleMenuItem.js: -------------------------------------------------------------------------------- 1 | import _ from 'common/shim/lodash.reference.shim'; 2 | import {Model} from 'backbone'; 3 | import FixedPosition from 'foreground/enum/fixedPosition'; 4 | 5 | var SimpleMenuItem = Model.extend({ 6 | defaults: { 7 | text: '', 8 | value: null, 9 | active: false, 10 | disabled: false, 11 | fixedPosition: FixedPosition.None, 12 | onClick: _.noop 13 | } 14 | }); 15 | 16 | export default SimpleMenuItem; -------------------------------------------------------------------------------- /src/js/foreground/view/dialog/googleSignInView.js: -------------------------------------------------------------------------------- 1 | import {LayoutView} from 'marionette'; 2 | import DialogContent from 'foreground/view/behavior/dialogContent'; 3 | import googleSignInTemplate from 'template/dialog/googleSignIn.hbs!'; 4 | 5 | var GoogleSignInView = LayoutView.extend({ 6 | template: googleSignInTemplate, 7 | 8 | behaviors: { 9 | DialogContent: { 10 | behaviorClass: DialogContent 11 | } 12 | } 13 | }); 14 | 15 | export default GoogleSignInView; -------------------------------------------------------------------------------- /src/less/resizeEmitter.less: -------------------------------------------------------------------------------- 1 | .resizeEmitter { 2 | visibility: hidden; 3 | } 4 | 5 | .resizeEmitter, .resizeEmitter > div, .resizeEmitter-contract:before { 6 | content: " "; 7 | display: block; 8 | position: absolute; 9 | top: 0; 10 | left: 0; 11 | height: 100%; 12 | width: 100%; 13 | overflow: hidden; 14 | } 15 | 16 | .resizeEmitter > div { 17 | background: #eee; 18 | overflow: auto; 19 | } 20 | 21 | .resizeEmitter-contract:before { 22 | width: 200%; 23 | height: 200%; 24 | } -------------------------------------------------------------------------------- /src/js/test/foreground/view/dialog/errorView.spec.js: -------------------------------------------------------------------------------- 1 | import ErrorView from 'foreground/view/dialog/errorView'; 2 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 3 | 4 | describe('ErrorView', function() { 5 | beforeEach(function() { 6 | this.documentFragment = document.createDocumentFragment(); 7 | this.view = new ErrorView(); 8 | }); 9 | 10 | afterEach(function() { 11 | this.view.destroy(); 12 | }); 13 | 14 | ViewTestUtility.ensureBasicAssumptions.call(this); 15 | }); -------------------------------------------------------------------------------- /src/less/activePaneFilter.less: -------------------------------------------------------------------------------- 1 | .activePaneFilter { 2 | height: 48px; 3 | display: flex; 4 | flex: 1; 5 | align-items: center; 6 | padding: 8px; 7 | margin-right: 16px; 8 | cursor: default; 9 | 10 | &.is-enabled { 11 | cursor: pointer; 12 | } 13 | } 14 | 15 | .activePaneFilter-filterIcon { 16 | @triangleSize: 5px; 17 | border-left: @triangleSize solid transparent; 18 | border-right: @triangleSize solid transparent; 19 | border-top: @triangleSize solid; 20 | margin-left: 6px; 21 | } -------------------------------------------------------------------------------- /src/js/contentScript/youTubePlayer/sandboxInject.js: -------------------------------------------------------------------------------- 1 | (function injectScripts() { 2 | 'use strict'; 3 | 4 | var parent = document.body || document.head || document.documentElement; 5 | 6 | if (!parent) { 7 | setTimeout(injectScripts, 0); 8 | return; 9 | } 10 | 11 | var playerApi = document.createElement('script'); 12 | playerApi.type = 'text/javascript'; 13 | playerApi.src = chrome.extension.getURL('/js/contentScript/youTubePlayer/playerApi.js'); 14 | parent.appendChild(playerApi); 15 | })(); -------------------------------------------------------------------------------- /src/js/foreground/model/simpleMenu/simpleMenu.js: -------------------------------------------------------------------------------- 1 | import {Model} from 'backbone'; 2 | 3 | var SimpleMenu = Model.extend({ 4 | defaults: { 5 | simpleMenuItems: null, 6 | fixedMenuItem: null, 7 | listItemHeight: 0, 8 | isContextMenu: false, 9 | reposition: false, 10 | repositionData: { 11 | top: 0, 12 | left: 0, 13 | containerHeight: 0, 14 | containerWidth: 0 15 | }, 16 | offsetTop: 0, 17 | offsetLeft: 0 18 | } 19 | }); 20 | 21 | export default SimpleMenu; -------------------------------------------------------------------------------- /src/foreground.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Streamus 7 | 8 | 9 | 10 | 11 | 12 |
          13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/js/test/foreground/view/element/spinnerView.spec.js: -------------------------------------------------------------------------------- 1 | import SpinnerView from 'foreground/view/element/spinnerView'; 2 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 3 | 4 | describe('SpinnerView', function() { 5 | beforeEach(function() { 6 | this.documentFragment = document.createDocumentFragment(); 7 | this.view = new SpinnerView(); 8 | }); 9 | 10 | afterEach(function() { 11 | this.view.destroy(); 12 | }); 13 | 14 | ViewTestUtility.ensureBasicAssumptions.call(this); 15 | }); -------------------------------------------------------------------------------- /src/js/background/plugins.js: -------------------------------------------------------------------------------- 1 | import 'common/shim/lodash.mixin.shim'; 2 | import 'common/shim/backbone.cocktail.shim'; 3 | import 'common/shim/backbone.marionette.view.shim'; 4 | import 'common/shim/backbone.marionette.region.shim'; 5 | import 'common/shim/backbone.marionette.toJson.shim'; 6 | import 'common/shim/handlebars.helpers.shim'; 7 | 8 | // Finally, load the application: 9 | import Application from 'background/application'; 10 | var streamusBG = new Application(); 11 | window.StreamusBG = streamusBG; 12 | streamusBG.start(); -------------------------------------------------------------------------------- /src/js/test/foreground/view/dialog/linkUserIdView.spec.js: -------------------------------------------------------------------------------- 1 | import LinkUserIdView from 'foreground/view/dialog/linkUserIdView'; 2 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 3 | 4 | describe('LinkUserIdView', function() { 5 | beforeEach(function() { 6 | this.documentFragment = document.createDocumentFragment(); 7 | this.view = new LinkUserIdView(); 8 | }); 9 | 10 | afterEach(function() { 11 | this.view.destroy(); 12 | }); 13 | 14 | ViewTestUtility.ensureBasicAssumptions.call(this); 15 | }); -------------------------------------------------------------------------------- /src/js/foreground/view/behavior/dialogContent.js: -------------------------------------------------------------------------------- 1 | import {Behavior} from 'marionette'; 2 | import Scrollable from 'foreground/view/behavior/scrollable'; 3 | 4 | var DialogContent = Behavior.extend({ 5 | behaviors: { 6 | Scrollable: { 7 | behaviorClass: Scrollable 8 | } 9 | }, 10 | 11 | onRender: function() { 12 | // Prefer to do this in initialize, but $el isn't available to Behavior until after view's initialize. 13 | this.$el.addClass('dialog-content'); 14 | } 15 | }); 16 | 17 | export default DialogContent; -------------------------------------------------------------------------------- /src/js/test/foreground/view/dialog/clearStreamView.spec.js: -------------------------------------------------------------------------------- 1 | import ClearStreamView from 'foreground/view/dialog/clearStreamView'; 2 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 3 | 4 | describe('ClearStreamView', function() { 5 | beforeEach(function() { 6 | this.documentFragment = document.createDocumentFragment(); 7 | this.view = new ClearStreamView(); 8 | }); 9 | 10 | afterEach(function() { 11 | this.view.destroy(); 12 | }); 13 | 14 | ViewTestUtility.ensureBasicAssumptions.call(this); 15 | }); -------------------------------------------------------------------------------- /src/js/test/foreground/view/stream/streamItemsView.spec.js: -------------------------------------------------------------------------------- 1 | import StreamItemsView from 'foreground/view/stream/streamItemsView'; 2 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 3 | 4 | describe('StreamItemsView', function() { 5 | beforeEach(function() { 6 | this.documentFragment = document.createDocumentFragment(); 7 | this.view = new StreamItemsView(); 8 | }); 9 | 10 | afterEach(function() { 11 | this.view.destroy(); 12 | }); 13 | 14 | ViewTestUtility.ensureBasicAssumptions.call(this); 15 | }); -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - '0.12' 5 | before_install: 6 | - npm install -g grunt-cli 7 | - npm install -g jspm 8 | - jspm config registries.github.auth $JSPM_GITHUB_AUTH_TOKEN 9 | before_script: 10 | - grunt 'file-creator:youTubeAPIKey' 11 | - grunt 'less' 12 | - jspm install 13 | install: npm install 14 | env: 15 | global: 16 | - secure: N/yV6FNB4bOhqk5AgGTMylbHTk/2uZK/kfOmIiDRhK6eIeuDcyb/KyasAeFztnzRxbjeLx1pCimSafthLbL4VxP1MtNag/qYwg/58tQ89guuvkFuAdYAVk28Ub6giW6mAQZyfzehrwOOVZeMhl7w4Kg9UNC3Y4iGF746pkworGM= -------------------------------------------------------------------------------- /src/js/foreground/view/dialog/updateStreamusView.js: -------------------------------------------------------------------------------- 1 | import _ from 'common/shim/lodash.reference.shim'; 2 | import {LayoutView} from 'marionette'; 3 | import DialogContent from 'foreground/view/behavior/dialogContent'; 4 | import updateStreamusTemplate from 'template/dialog/updateStreamus.hbs!'; 5 | 6 | var UpdateStreamusView = LayoutView.extend({ 7 | template: updateStreamusTemplate, 8 | 9 | behaviors: { 10 | DialogContent: { 11 | behaviorClass: DialogContent 12 | } 13 | } 14 | }); 15 | 16 | export default UpdateStreamusView; -------------------------------------------------------------------------------- /src/js/test/foreground/view/dialog/googleSignInView.spec.js: -------------------------------------------------------------------------------- 1 | import GoogleSignInView from 'foreground/view/dialog/googleSignInView'; 2 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 3 | 4 | describe('GoogleSignInView', function() { 5 | beforeEach(function() { 6 | this.documentFragment = document.createDocumentFragment(); 7 | this.view = new GoogleSignInView(); 8 | }); 9 | 10 | afterEach(function() { 11 | this.view.destroy(); 12 | }); 13 | 14 | ViewTestUtility.ensureBasicAssumptions.call(this); 15 | }); -------------------------------------------------------------------------------- /src/js/test/foreground/view/appBar/adminMenuAreaView.spec.js: -------------------------------------------------------------------------------- 1 | import AdminMenuAreaView from 'foreground/view/appBar/adminMenuAreaView'; 2 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 3 | 4 | describe('AdminMenuAreaView', function() { 5 | beforeEach(function() { 6 | this.documentFragment = document.createDocumentFragment(); 7 | this.view = new AdminMenuAreaView(); 8 | }); 9 | 10 | afterEach(function() { 11 | this.view.destroy(); 12 | }); 13 | 14 | ViewTestUtility.ensureBasicAssumptions.call(this); 15 | }); -------------------------------------------------------------------------------- /src/js/test/foreground/view/dialog/aboutStreamusView.spec.js: -------------------------------------------------------------------------------- 1 | import AboutStreamusView from 'foreground/view/dialog/aboutStreamusView'; 2 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 3 | 4 | describe('AboutStreamusView', function() { 5 | beforeEach(function() { 6 | this.documentFragment = document.createDocumentFragment(); 7 | this.view = new AboutStreamusView(); 8 | }); 9 | 10 | afterEach(function() { 11 | this.view.destroy(); 12 | }); 13 | 14 | ViewTestUtility.ensureBasicAssumptions.call(this); 15 | }); -------------------------------------------------------------------------------- /src/js/test/foreground/view/search/searchResultsView.spec.js: -------------------------------------------------------------------------------- 1 | import SearchResultsView from 'foreground/view/search/searchResultsView'; 2 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 3 | 4 | describe('SearchResultsView', function() { 5 | beforeEach(function() { 6 | this.documentFragment = document.createDocumentFragment(); 7 | this.view = new SearchResultsView(); 8 | }); 9 | 10 | afterEach(function() { 11 | this.view.destroy(); 12 | }); 13 | 14 | ViewTestUtility.ensureBasicAssumptions.call(this); 15 | }); -------------------------------------------------------------------------------- /src/js/test/foreground/view/dialog/updateStreamusView.spec.js: -------------------------------------------------------------------------------- 1 | import UpdateStreamusView from 'foreground/view/dialog/updateStreamusView'; 2 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 3 | 4 | describe('UpdateStreamusView', function() { 5 | beforeEach(function() { 6 | this.documentFragment = document.createDocumentFragment(); 7 | this.view = new UpdateStreamusView(); 8 | }); 9 | 10 | afterEach(function() { 11 | this.view.destroy(); 12 | }); 13 | 14 | ViewTestUtility.ensureBasicAssumptions.call(this); 15 | }); -------------------------------------------------------------------------------- /src/js/test/foreground/view/leftPane/playlistItemsView.spec.js: -------------------------------------------------------------------------------- 1 | import PlaylistItemsView from 'foreground/view/leftPane/playlistItemsView'; 2 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 3 | 4 | describe('PlaylistItemsView', function() { 5 | beforeEach(function() { 6 | this.documentFragment = document.createDocumentFragment(); 7 | this.view = new PlaylistItemsView(); 8 | }); 9 | 10 | afterEach(function() { 11 | this.view.destroy(); 12 | }); 13 | 14 | ViewTestUtility.ensureBasicAssumptions.call(this); 15 | }); -------------------------------------------------------------------------------- /src/js/foreground/model/dialog/dialog.js: -------------------------------------------------------------------------------- 1 | import {Model} from 'backbone'; 2 | 3 | var Dialog = Model.extend({ 4 | defaults: { 5 | title: '', 6 | submitButtonText: chrome.i18n.getMessage('ok'), 7 | cancelButtonText: chrome.i18n.getMessage('cancel'), 8 | showCancelButton: true, 9 | reminderProperty: '', 10 | reminderText: chrome.i18n.getMessage('remind'), 11 | alwaysSaveReminder: false 12 | }, 13 | 14 | hasReminder: function() { 15 | return this.get('reminderProperty') !== ''; 16 | } 17 | }); 18 | 19 | export default Dialog; -------------------------------------------------------------------------------- /src/js/foreground/view/appBar/appBarRegion.js: -------------------------------------------------------------------------------- 1 | import {Region} from 'marionette'; 2 | import AppBarView from 'foreground/view/appBar/appBarView'; 3 | 4 | var AppBarRegion = Region.extend({ 5 | initialize: function() { 6 | this.listenTo(StreamusFG.channels.foregroundArea.vent, 'rendered', this._onForegroundAreaRendered); 7 | }, 8 | 9 | _onForegroundAreaRendered: function() { 10 | this.show(new AppBarView({ 11 | signInManager: StreamusFG.backgroundProperties.signInManager 12 | })); 13 | } 14 | }); 15 | 16 | export default AppBarRegion; -------------------------------------------------------------------------------- /src/js/test/foreground/view/simpleMenu/simpleMenuItemsView.spec.js: -------------------------------------------------------------------------------- 1 | import SimpleMenuItemsView from 'foreground/view/simpleMenu/simpleMenuItemsView'; 2 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 3 | 4 | describe('SimpleMenuItemsView', function() { 5 | beforeEach(function() { 6 | this.documentFragment = document.createDocumentFragment(); 7 | this.view = new SimpleMenuItemsView(); 8 | }); 9 | 10 | afterEach(function() { 11 | this.view.destroy(); 12 | }); 13 | 14 | ViewTestUtility.ensureBasicAssumptions.call(this); 15 | }); -------------------------------------------------------------------------------- /src/js/test/foreground/view/listItemButton/listItemButtonsView.spec.js: -------------------------------------------------------------------------------- 1 | import ListItemButtonsView from 'foreground/view/listItemButton/listItemButtonsView'; 2 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 3 | 4 | describe('ListItemButtonsView', function() { 5 | beforeEach(function() { 6 | this.documentFragment = document.createDocumentFragment(); 7 | this.view = new ListItemButtonsView(); 8 | }); 9 | 10 | afterEach(function() { 11 | this.view.destroy(); 12 | }); 13 | 14 | ViewTestUtility.ensureBasicAssumptions.call(this); 15 | }); -------------------------------------------------------------------------------- /src/js/common/enum/youTubePlayerError.js: -------------------------------------------------------------------------------- 1 | // The possible error values that the YouTube player might throw. 2 | // Data comes from: https://developers.google.com/youtube/js_api_reference#onError 3 | // The values of these strings need to be numbers because they interface with a third-party API. 4 | export default { 5 | None: -1, 6 | InvalidParameter: 2, 7 | // Undocumented error that YouTube throws when it breaks due to internet issues and other unforseen circumstances. 8 | ReallyBad: 5, 9 | VideoNotFound: 100, 10 | NoPlayEmbedded: 101, 11 | NoPlayEmbedded2: 150 12 | }; -------------------------------------------------------------------------------- /src/js/test/foreground/view/dialog/aboutStreamusDialogView.spec.js: -------------------------------------------------------------------------------- 1 | import AboutStreamusDialogView from 'foreground/view/dialog/aboutStreamusDialogView'; 2 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 3 | 4 | describe('AboutStreamusDialogView', function() { 5 | beforeEach(function() { 6 | this.documentFragment = document.createDocumentFragment(); 7 | this.view = new AboutStreamusDialogView(); 8 | }); 9 | 10 | afterEach(function() { 11 | this.view.destroy(); 12 | }); 13 | 14 | ViewTestUtility.ensureBasicAssumptions.call(this); 15 | }); -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain -------------------------------------------------------------------------------- /src/js/foreground/model/element/checkbox.js: -------------------------------------------------------------------------------- 1 | import {Model} from 'backbone'; 2 | 3 | var Checkbox = Model.extend({ 4 | defaults: { 5 | // Primary checkboxes are colored more boldly than secondary. 6 | primary: true, 7 | checked: false, 8 | checking: false, 9 | labelText: '', 10 | // Often times a checkbox has a 1:1 relationship with a model property. 11 | // Linking that property to its checkbox allows working with a collection of checkboxes more easily. 12 | property: '', 13 | iconOnLeft: false 14 | } 15 | }); 16 | 17 | export default Checkbox; -------------------------------------------------------------------------------- /src/less/boxShadow.less: -------------------------------------------------------------------------------- 1 | .m-boxShadow-underline(@color) { 2 | box-shadow: inset 0 -1px @color; 3 | } 4 | 5 | @boxShadow-divider--bottom: inset 0 -1px rgba(0, 0, 0, .12); 6 | @boxShadow-divider--top: inset 0 1px rgba(0, 0, 0, .12); 7 | @boxShadow-divider--left: inset 1px 0 rgba(0, 0, 0, .12); 8 | @boxShadow-divier--right: inset -1px 0 rgba(0, 0, 0, .12); 9 | @boxShadow-outline: inset 0 0 1px rgba(0, 0, 0, .26); 10 | 11 | // As a general rule of thumb, always apply ambient and key lighting to material. 12 | @boxShadow-elevation--low: 0 0 2px rgba(0, 0, 0, 0.14), 2px 2px 4px rgba(0, 0, 0, 0.28); -------------------------------------------------------------------------------- /src/js/test/foreground/view/dialog/errorDialogView.spec.js: -------------------------------------------------------------------------------- 1 | import ErrorDialogView from 'foreground/view/dialog/errorDialogView'; 2 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 3 | 4 | describe('ErrorDialogView', function() { 5 | beforeEach(function() { 6 | this.documentFragment = document.createDocumentFragment(); 7 | this.view = new ErrorDialogView({ 8 | player: TestUtility.buildPlayer() 9 | }); 10 | }); 11 | 12 | afterEach(function() { 13 | this.view.destroy(); 14 | }); 15 | 16 | ViewTestUtility.ensureBasicAssumptions.call(this); 17 | }); -------------------------------------------------------------------------------- /src/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Streamus Tests 7 | 8 | 9 | 10 | 11 | 12 |
          13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/js/background/view/clipboardRegion.js: -------------------------------------------------------------------------------- 1 | import {Region} from 'marionette'; 2 | import ClipboardView from 'background/view/clipboardView'; 3 | 4 | var ClipboardRegion = Region.extend({ 5 | initialize: function() { 6 | this.listenTo(StreamusBG.channels.backgroundArea.vent, 'rendered', this._onBackgroundAreaRendered); 7 | }, 8 | 9 | _onBackgroundAreaRendered: function() { 10 | this._showClipboardView(); 11 | }, 12 | 13 | _showClipboardView: function() { 14 | var clipboardView = new ClipboardView(); 15 | this.show(clipboardView); 16 | } 17 | }); 18 | 19 | export default ClipboardRegion; -------------------------------------------------------------------------------- /src/js/test/foreground/view/streamControlBar/volumeAreaView.spec.js: -------------------------------------------------------------------------------- 1 | import VolumeAreaView from 'foreground/view/streamControlBar/volumeAreaView'; 2 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 3 | 4 | describe('VolumeAreaView', function() { 5 | beforeEach(function() { 6 | this.documentFragment = document.createDocumentFragment(); 7 | this.view = new VolumeAreaView({ 8 | player: TestUtility.buildPlayer() 9 | }); 10 | }); 11 | 12 | afterEach(function() { 13 | this.view.destroy(); 14 | }); 15 | 16 | ViewTestUtility.ensureBasicAssumptions.call(this); 17 | }); -------------------------------------------------------------------------------- /src/js/foreground/plugins.js: -------------------------------------------------------------------------------- 1 | import 'common/shim/backbone.marionette.view.shim'; 2 | import 'common/shim/backbone.marionette.region.shim'; 3 | import 'common/shim/backbone.marionette.toJson.shim'; 4 | import 'common/shim/handlebars.helpers.shim'; 5 | import Application from 'foreground/application'; 6 | 7 | var backgroundPage = chrome.extension.getBackgroundPage(); 8 | var streamusFG = new Application({ 9 | backgroundProperties: backgroundPage.StreamusBG.getExposedProperties(), 10 | backgroundChannels: backgroundPage.StreamusBG.getExposedChannels() 11 | }); 12 | window.StreamusFG = streamusFG; 13 | streamusFG.start(); -------------------------------------------------------------------------------- /src/js/test/foreground/view/element/sliderView.spec.js: -------------------------------------------------------------------------------- 1 | import SliderView from 'foreground/view/element/sliderView'; 2 | import Slider from 'foreground/model/element/slider'; 3 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 4 | 5 | describe('SliderView', function() { 6 | beforeEach(function() { 7 | this.documentFragment = document.createDocumentFragment(); 8 | this.view = new SliderView({ 9 | model: new Slider() 10 | }); 11 | }); 12 | 13 | afterEach(function() { 14 | this.view.destroy(); 15 | }); 16 | 17 | ViewTestUtility.ensureBasicAssumptions.call(this); 18 | }); -------------------------------------------------------------------------------- /src/js/test/foreground/view/element/switchView.spec.js: -------------------------------------------------------------------------------- 1 | import SwitchView from 'foreground/view/element/switchView'; 2 | import Switch from 'foreground/model/element/switch'; 3 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 4 | 5 | describe('SwitchView', function() { 6 | beforeEach(function() { 7 | this.documentFragment = document.createDocumentFragment(); 8 | this.view = new SwitchView({ 9 | model: new Switch() 10 | }); 11 | }); 12 | 13 | afterEach(function() { 14 | this.view.destroy(); 15 | }); 16 | 17 | ViewTestUtility.ensureBasicAssumptions.call(this); 18 | }); -------------------------------------------------------------------------------- /src/less/contentBar.less: -------------------------------------------------------------------------------- 1 | @import "flexRow"; 2 | @import "color"; 3 | @import "boxShadow"; 4 | 5 | .contentBar { 6 | display: flex; 7 | padding: 0 4px; 8 | align-items: center; 9 | justify-content: flex-end; 10 | } 11 | 12 | .contentBar-navButton { 13 | margin-right: 12px; 14 | } 15 | 16 | .contentBar-title { 17 | font-size: 17px; 18 | } 19 | 20 | .contentBar--top { 21 | height: 56px; 22 | color: white; 23 | fill: white; 24 | background-color: @blue--500; 25 | } 26 | 27 | .contentBar--bottom { 28 | height: 48px; 29 | } 30 | 31 | .contentBar--bottom-text--left { 32 | margin-left: 12px; 33 | } -------------------------------------------------------------------------------- /src/js/foreground/view/streamControlBar/streamControlBarRegion.js: -------------------------------------------------------------------------------- 1 | import {Region} from 'marionette'; 2 | import StreamControlBarView from 'foreground/view/streamControlBar/streamControlBarView'; 3 | 4 | var StreamControlBarRegion = Region.extend({ 5 | initialize: function() { 6 | this.listenTo(StreamusFG.channels.foregroundArea.vent, 'rendered', this._onForegroundAreaRendered); 7 | }, 8 | 9 | _onForegroundAreaRendered: function() { 10 | this.show(new StreamControlBarView({ 11 | player: StreamusFG.backgroundProperties.player 12 | })); 13 | } 14 | }); 15 | 16 | export default StreamControlBarRegion; -------------------------------------------------------------------------------- /src/js/foreground/view/dialog/deletePlaylistView.js: -------------------------------------------------------------------------------- 1 | import _ from 'common/shim/lodash.reference.shim'; 2 | import {LayoutView} from 'marionette'; 3 | import DialogContent from 'foreground/view/behavior/dialogContent'; 4 | import deletePlaylistTemplate from 'template/dialog/deletePlaylist.hbs!'; 5 | 6 | var DeletePlaylistView = LayoutView.extend({ 7 | template: deletePlaylistTemplate, 8 | 9 | behaviors: { 10 | DialogContent: { 11 | behaviorClass: DialogContent 12 | } 13 | }, 14 | 15 | deletePlaylist: function() { 16 | this.model.destroy(); 17 | } 18 | }); 19 | 20 | export default DeletePlaylistView; -------------------------------------------------------------------------------- /src/js/test/foreground/view/tooltip/tooltipView.spec.js: -------------------------------------------------------------------------------- 1 | import TooltipView from 'foreground/view/tooltip/tooltipView'; 2 | import Tooltip from 'foreground/model/tooltip/tooltip'; 3 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 4 | 5 | describe('TooltipView', function() { 6 | beforeEach(function() { 7 | this.documentFragment = document.createDocumentFragment(); 8 | this.view = new TooltipView({ 9 | model: new Tooltip() 10 | }); 11 | }); 12 | 13 | afterEach(function() { 14 | this.view.destroy(); 15 | }); 16 | 17 | ViewTestUtility.ensureBasicAssumptions.call(this); 18 | }); -------------------------------------------------------------------------------- /src/js/test/foreground/view/element/checkboxView.spec.js: -------------------------------------------------------------------------------- 1 | import CheckboxView from 'foreground/view/element/checkboxView'; 2 | import Checkbox from 'foreground/model/element/checkbox'; 3 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 4 | 5 | describe('CheckboxView', function() { 6 | beforeEach(function() { 7 | this.documentFragment = document.createDocumentFragment(); 8 | this.view = new CheckboxView({ 9 | model: new Checkbox() 10 | }); 11 | }); 12 | 13 | afterEach(function() { 14 | this.view.destroy(); 15 | }); 16 | 17 | ViewTestUtility.ensureBasicAssumptions.call(this); 18 | }); -------------------------------------------------------------------------------- /src/js/common/shim/backbone.marionette.toJson.shim.js: -------------------------------------------------------------------------------- 1 | // 2 | // toJsonShim 3 | // Marionette v2.x Views use toJSON for serialization, which isn't 4 | // the intended use of that method. This resolves that problem. 5 | // 6 | 7 | import _ from 'common/shim/lodash.reference.shim'; 8 | import {View, ItemView} from 'marionette'; 9 | 10 | View.prototype.serializeModel = function(model) { 11 | model = model || this.model; 12 | return _.clone(model.attributes); 13 | }; 14 | 15 | ItemView.prototype.serializeCollection = function(collection) { 16 | return collection.map(function(model) { return this.serializeModel(model); }, this); 17 | }; -------------------------------------------------------------------------------- /src/js/test/foreground/view/appBar/appBarView.spec.js: -------------------------------------------------------------------------------- 1 | import AppBarView from 'foreground/view/appBar/appBarView'; 2 | import SignInManager from 'background/model/signInManager'; 3 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 4 | 5 | describe('AppBarView', function() { 6 | beforeEach(function() { 7 | this.documentFragment = document.createDocumentFragment(); 8 | this.view = new AppBarView({ 9 | signInManager: new SignInManager() 10 | }); 11 | }); 12 | 13 | afterEach(function() { 14 | this.view.destroy(); 15 | }); 16 | 17 | ViewTestUtility.ensureBasicAssumptions.call(this); 18 | }); -------------------------------------------------------------------------------- /src/template/appBar/appBar.hbs: -------------------------------------------------------------------------------- 1 |
          2 |
          3 | {{{menuIcon}}} 4 |
          5 | 6 | 9 | 10 |
          11 |
          12 |
          13 |
          -------------------------------------------------------------------------------- /src/js/test/foreground/view/streamControlBar/streamControlBarView.spec.js: -------------------------------------------------------------------------------- 1 | import StreamControlBarView from 'foreground/view/streamControlBar/streamControlBarView'; 2 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 3 | 4 | describe('StreamControlBarView', function() { 5 | beforeEach(function() { 6 | this.documentFragment = document.createDocumentFragment(); 7 | this.view = new StreamControlBarView({ 8 | player: TestUtility.buildPlayer() 9 | }); 10 | }); 11 | 12 | afterEach(function() { 13 | this.view.destroy(); 14 | }); 15 | 16 | ViewTestUtility.ensureBasicAssumptions.call(this); 17 | }); -------------------------------------------------------------------------------- /src/js/background/key/youTubeAPIKey.js.example: -------------------------------------------------------------------------------- 1 | // Google API's Simple API Access Key. 2 | // Production key is omitted from GitHub for security. 3 | 4 | // You are encouraged to generate your own key here: https://code.google.com/apis/console/ 5 | // You may also attempt to use this testing key, but it may be revoked at any time: AIzaSyDBCJuq0aey3bL3K6C0l4mKzT_y8zy9Msw 6 | 7 | // A valid key will look something like: 8 | // Key for browser apps (with referers) 9 | // API key: ------------------------------- 10 | // Referers: Any referer allowed 11 | // Activated on: Apr 6, 2014 2:46 PM 12 | // Activated by: ------------ you 13 | export default ''; -------------------------------------------------------------------------------- /src/js/background/model/searchResult.js: -------------------------------------------------------------------------------- 1 | import _ from 'common/shim/lodash.reference.shim'; 2 | import {Model} from 'backbone'; 3 | import ListItemType from 'common/enum/listItemType'; 4 | 5 | var SearchResult = Model.extend({ 6 | defaults: function() { 7 | return { 8 | id: _.uniqueId('searchResult_'), 9 | selected: false, 10 | // Whether the item was the first to be selected or one of many. 11 | // Important for proper shift+click functionality. 12 | firstSelected: false, 13 | listItemType: ListItemType.SearchResult, 14 | video: null 15 | }; 16 | } 17 | }); 18 | 19 | export default SearchResult; -------------------------------------------------------------------------------- /src/js/test/foreground/view/appBar/searchInputAreaView.spec.js: -------------------------------------------------------------------------------- 1 | import SearchInputAreaView from 'foreground/view/appBar/searchInputAreaView'; 2 | import Search from 'background/model/search'; 3 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 4 | 5 | describe('SearchAreaView', function() { 6 | beforeEach(function() { 7 | this.documentFragment = document.createDocumentFragment(); 8 | this.view = new SearchInputAreaView({ 9 | search: new Search() 10 | }); 11 | }); 12 | 13 | afterEach(function() { 14 | this.view.destroy(); 15 | }); 16 | 17 | ViewTestUtility.ensureBasicAssumptions.call(this); 18 | }); -------------------------------------------------------------------------------- /src/js/test/foreground/view/element/radioGroupView.spec.js: -------------------------------------------------------------------------------- 1 | import RadioGroupView from 'foreground/view/element/radioGroupView'; 2 | import RadioGroup from 'foreground/model/element/radioGroup'; 3 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 4 | 5 | describe('RadioGroupView', function() { 6 | beforeEach(function() { 7 | this.documentFragment = document.createDocumentFragment(); 8 | this.view = new RadioGroupView({ 9 | model: new RadioGroup() 10 | }); 11 | }); 12 | 13 | afterEach(function() { 14 | this.view.destroy(); 15 | }); 16 | 17 | ViewTestUtility.ensureBasicAssumptions.call(this); 18 | }); -------------------------------------------------------------------------------- /src/js/test/foreground/view/playlist/playlistsView.spec.js: -------------------------------------------------------------------------------- 1 | import PlaylistsView from 'foreground/view/playlist/playlistsView'; 2 | import Playlists from 'background/collection/playlists'; 3 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 4 | 5 | describe('PlaylistsView', function() { 6 | beforeEach(function() { 7 | this.documentFragment = document.createDocumentFragment(); 8 | this.view = new PlaylistsView({ 9 | collection: new Playlists() 10 | }); 11 | }); 12 | 13 | afterEach(function() { 14 | this.view.destroy(); 15 | }); 16 | 17 | ViewTestUtility.ensureBasicAssumptions.call(this); 18 | }); -------------------------------------------------------------------------------- /src/js/test/foreground/view/element/radioButtonView.spec.js: -------------------------------------------------------------------------------- 1 | import RadioButtonView from 'foreground/view/element/radioButtonView'; 2 | import RadioButton from 'foreground/model/element/radioButton'; 3 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 4 | 5 | describe('RadioButtonView', function() { 6 | beforeEach(function() { 7 | this.documentFragment = document.createDocumentFragment(); 8 | this.view = new RadioButtonView({ 9 | model: new RadioButton() 10 | }); 11 | }); 12 | 13 | afterEach(function() { 14 | this.view.destroy(); 15 | }); 16 | 17 | ViewTestUtility.ensureBasicAssumptions.call(this); 18 | }); -------------------------------------------------------------------------------- /src/js/test/foreground/view/simpleMenu/simpleMenuView.spec.js: -------------------------------------------------------------------------------- 1 | import SimpleMenuView from 'foreground/view/simpleMenu/simpleMenuView'; 2 | import SimpleMenu from 'foreground/model/simpleMenu/simpleMenu'; 3 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 4 | 5 | describe('SimpleMenuView', function() { 6 | beforeEach(function() { 7 | this.documentFragment = document.createDocumentFragment(); 8 | this.view = new SimpleMenuView({ 9 | model: new SimpleMenu() 10 | }); 11 | }); 12 | 13 | afterEach(function() { 14 | this.view.destroy(); 15 | }); 16 | 17 | ViewTestUtility.ensureBasicAssumptions.call(this); 18 | }); -------------------------------------------------------------------------------- /src/js/test/foreground/view/playlist/playlistsAreaView.spec.js: -------------------------------------------------------------------------------- 1 | import PlaylistsAreaView from 'foreground/view/playlist/playlistsAreaView'; 2 | import Playlists from 'background/collection/playlists'; 3 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 4 | 5 | describe('PlaylistsAreaView', function() { 6 | beforeEach(function() { 7 | this.documentFragment = document.createDocumentFragment(); 8 | this.view = new PlaylistsAreaView({ 9 | playlists: new Playlists() 10 | }); 11 | }); 12 | 13 | afterEach(function() { 14 | this.view.destroy(); 15 | }); 16 | 17 | ViewTestUtility.ensureBasicAssumptions.call(this); 18 | }); -------------------------------------------------------------------------------- /src/template/dialog/createPlaylist.hbs: -------------------------------------------------------------------------------- 1 |
          2 | 3 |
          4 | 0 / {{titleMaxLength}} 5 |
          6 |
          7 | 8 |
          9 | 10 |
          11 |
          -------------------------------------------------------------------------------- /src/js/test/foreground/view/streamControlBar/radioButtonView.spec.js: -------------------------------------------------------------------------------- 1 | import RadioButtonView from 'foreground/view/streamControlBar/radioButtonView'; 2 | import RadioButton from 'background/model/radioButton'; 3 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 4 | 5 | describe('RadioButtonView', function() { 6 | beforeEach(function() { 7 | this.documentFragment = document.createDocumentFragment(); 8 | this.view = new RadioButtonView({ 9 | model: new RadioButton() 10 | }); 11 | }); 12 | 13 | afterEach(function() { 14 | this.view.destroy(); 15 | }); 16 | 17 | ViewTestUtility.ensureBasicAssumptions.call(this); 18 | }); -------------------------------------------------------------------------------- /src/template/playlist/playlistsArea.hbs: -------------------------------------------------------------------------------- 1 |
          2 | 3 | -------------------------------------------------------------------------------- /src/js/test/foreground/view/streamControlBar/repeatButtonView.spec.js: -------------------------------------------------------------------------------- 1 | import RepeatButtonView from 'foreground/view/streamControlBar/repeatButtonView'; 2 | import RepeatButton from 'background/model/repeatButton'; 3 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 4 | 5 | describe('RepeatButtonView', function() { 6 | beforeEach(function() { 7 | this.documentFragment = document.createDocumentFragment(); 8 | this.view = new RepeatButtonView({ 9 | model: new RepeatButton() 10 | }); 11 | }); 12 | 13 | afterEach(function() { 14 | this.view.destroy(); 15 | }); 16 | 17 | ViewTestUtility.ensureBasicAssumptions.call(this); 18 | }); -------------------------------------------------------------------------------- /src/js/foreground/view/dialog/aboutStreamusDialogView.js: -------------------------------------------------------------------------------- 1 | import Dialog from 'foreground/model/dialog/dialog'; 2 | import AboutStreamusView from 'foreground/view/dialog/aboutStreamusView'; 3 | import DialogView from 'foreground/view/dialog/dialogView'; 4 | 5 | var AboutStreamusDialogView = DialogView.extend({ 6 | id: 'aboutStreamusDialog', 7 | 8 | initialize: function() { 9 | this.model = new Dialog(); 10 | 11 | this.contentView = new AboutStreamusView({ 12 | tabManager: StreamusFG.backgroundProperties.tabManager 13 | }); 14 | 15 | DialogView.prototype.initialize.apply(this, arguments); 16 | } 17 | }); 18 | 19 | export default AboutStreamusDialogView; -------------------------------------------------------------------------------- /src/js/test/foreground/view/element/simpleListItemView.spec.js: -------------------------------------------------------------------------------- 1 | import SimpleListItemView from 'foreground/view/element/simpleListItemView'; 2 | import SimpleListItem from 'foreground/model/element/simpleListItem'; 3 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 4 | 5 | describe('SimpleListItemView', function() { 6 | beforeEach(function() { 7 | this.documentFragment = document.createDocumentFragment(); 8 | this.view = new SimpleListItemView({ 9 | model: new SimpleListItem() 10 | }); 11 | }); 12 | 13 | afterEach(function() { 14 | this.view.destroy(); 15 | }); 16 | 17 | ViewTestUtility.ensureBasicAssumptions.call(this); 18 | }); -------------------------------------------------------------------------------- /src/js/test/foreground/view/notification/notificationView.spec.js: -------------------------------------------------------------------------------- 1 | import NotificationView from 'foreground/view/notification/notificationView'; 2 | import Notification from 'foreground/model/notification/notification'; 3 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 4 | 5 | describe('NotificationView', function() { 6 | beforeEach(function() { 7 | this.documentFragment = document.createDocumentFragment(); 8 | this.view = new NotificationView({ 9 | model: new Notification() 10 | }); 11 | }); 12 | 13 | afterEach(function() { 14 | this.view.destroy(); 15 | }); 16 | 17 | ViewTestUtility.ensureBasicAssumptions.call(this); 18 | }); -------------------------------------------------------------------------------- /src/js/test/foreground/view/streamControlBar/shuffleButtonView.spec.js: -------------------------------------------------------------------------------- 1 | import ShuffleButtonView from 'foreground/view/streamControlBar/shuffleButtonView'; 2 | import ShuffleButton from 'background/model/shuffleButton'; 3 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 4 | 5 | describe('ShuffleButtonView', function() { 6 | beforeEach(function() { 7 | this.documentFragment = document.createDocumentFragment(); 8 | this.view = new ShuffleButtonView({ 9 | model: new ShuffleButton() 10 | }); 11 | }); 12 | 13 | afterEach(function() { 14 | this.view.destroy(); 15 | }); 16 | 17 | ViewTestUtility.ensureBasicAssumptions.call(this); 18 | }); -------------------------------------------------------------------------------- /src/less/overlay.less: -------------------------------------------------------------------------------- 1 | @import "transition"; 2 | 3 | .overlay { 4 | position: absolute; 5 | top: 0; 6 | right: 0; 7 | bottom: 0; 8 | left: 0; 9 | z-index: 2; 10 | pointer-events: none; 11 | } 12 | 13 | .overlay--faded { 14 | background: rgba(0, 0, 0, 0.5); 15 | opacity: 0; 16 | will-change: opacity; 17 | transition-duration: @transition-duration--fast; 18 | transition-property: opacity; 19 | transition-timing-function: @transition-easeOutSine; 20 | // When click-and-holding over a faded overlay the cursor disappears unless cursor: default is set. 21 | cursor: default; 22 | 23 | &.is-visible { 24 | opacity: 1; 25 | pointer-events: all; 26 | } 27 | } -------------------------------------------------------------------------------- /src/js/test/foreground/view/simpleMenu/simpleMenuItemView.spec.js: -------------------------------------------------------------------------------- 1 | import SimpleMenuItemView from 'foreground/view/simpleMenu/simpleMenuItemView'; 2 | import SimpleMenuItem from 'foreground/model/simpleMenu/simpleMenuItem'; 3 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 4 | 5 | describe('SimpleMenuItemView', function() { 6 | beforeEach(function() { 7 | this.documentFragment = document.createDocumentFragment(); 8 | this.view = new SimpleMenuItemView({ 9 | model: new SimpleMenuItem() 10 | }); 11 | }); 12 | 13 | afterEach(function() { 14 | this.view.destroy(); 15 | }); 16 | 17 | ViewTestUtility.ensureBasicAssumptions.call(this); 18 | }); -------------------------------------------------------------------------------- /src/js/contentScript/youTubePlayer/application.js: -------------------------------------------------------------------------------- 1 | import {Application} from 'marionette'; 2 | import YouTubePlayerView from 'contentScript/youTubePlayer/view/youTubePlayerView'; 3 | import Port from 'contentScript/youTubePlayer/model/port'; 4 | 5 | var YouTubePlayerApplication = Application.extend({ 6 | initialize: function() { 7 | this.on('start', this._onStart); 8 | }, 9 | 10 | _onStart: function() { 11 | this.port = new Port(); 12 | this.port.connect(); 13 | 14 | $(document).ready(function() { 15 | var youTubePlayerView = new YouTubePlayerView(); 16 | youTubePlayerView.render(); 17 | }); 18 | } 19 | }); 20 | 21 | export default YouTubePlayerApplication; -------------------------------------------------------------------------------- /src/js/test/foreground/view/foregroundAreaView.spec.js: -------------------------------------------------------------------------------- 1 | import ForegroundAreaView from 'foreground/view/foregroundAreaView'; 2 | import AnalyticsManager from 'background/model/analyticsManager'; 3 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 4 | 5 | describe('ForegroundAreaView', function() { 6 | beforeEach(function() { 7 | this.documentFragment = document.createDocumentFragment(); 8 | this.view = new ForegroundAreaView({ 9 | el: false, 10 | analyticsManager: new AnalyticsManager() 11 | }); 12 | }); 13 | 14 | afterEach(function() { 15 | this.view.destroy(); 16 | }); 17 | 18 | ViewTestUtility.ensureBasicAssumptions.call(this); 19 | }); -------------------------------------------------------------------------------- /src/template/search/searchResult.hbs: -------------------------------------------------------------------------------- 1 |
          2 |
          3 |
          4 |
          5 | 6 |
          7 |
          8 | {{get video 'title'}} 9 |
          10 | 11 |
          12 | {{get video 'prettyDuration'}} 13 |
          14 |
          15 | 16 | -------------------------------------------------------------------------------- /src/template/stream/streamItem.hbs: -------------------------------------------------------------------------------- 1 |
          2 |
          3 |
          4 |
          5 | 6 |
          7 |
          8 | {{get video 'title'}} 9 |
          10 | 11 |
          12 | {{get video 'prettyDuration'}} 13 |
          14 |
          15 | 16 | -------------------------------------------------------------------------------- /src/js/common/shim/handlebars.helpers.shim.js: -------------------------------------------------------------------------------- 1 | import Handlebars from 'handlebars'; 2 | 3 | // Provide handlebars with the ability to look up a model's attributes from within a template. 4 | Handlebars.registerHelper('get', function(model, attribute) { 5 | return model.get(attribute); 6 | }); 7 | 8 | Handlebars.registerHelper('ternary', function(test, yes, no) { 9 | return test ? yes : no; 10 | }); 11 | 12 | Handlebars.registerHelper('i18n', function(messageName) { 13 | var message = chrome.i18n.getMessage(messageName); 14 | 15 | if (message === '') { 16 | throw new Error('Failed to find message: ' + messageName); 17 | } 18 | 19 | return chrome.i18n.getMessage(messageName); 20 | }); -------------------------------------------------------------------------------- /src/template/icon/saveIcon_18.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/js/foreground/view/dialog/errorDialogView.js: -------------------------------------------------------------------------------- 1 | import Dialog from 'foreground/model/dialog/dialog'; 2 | import ErrorView from 'foreground/view/dialog/errorView'; 3 | import DialogView from 'foreground/view/dialog/dialogView'; 4 | 5 | var ErrorDialogView = DialogView.extend({ 6 | id: 'errorDialog', 7 | player: null, 8 | 9 | initialize: function(options) { 10 | this.player = options.player; 11 | 12 | this.model = new Dialog({ 13 | showCancelButton: false 14 | }); 15 | 16 | this.contentView = new ErrorView({ 17 | text: options.text 18 | }); 19 | 20 | DialogView.prototype.initialize.apply(this, arguments); 21 | } 22 | }); 23 | 24 | export default ErrorDialogView; -------------------------------------------------------------------------------- /src/js/foreground/view/dialog/errorView.js: -------------------------------------------------------------------------------- 1 | import _ from 'common/shim/lodash.reference.shim'; 2 | import {LayoutView} from 'marionette'; 3 | import DialogContent from 'foreground/view/behavior/dialogContent'; 4 | import errorTemplate from 'template/dialog/error.hbs!'; 5 | 6 | var ErrorView = LayoutView.extend({ 7 | template: errorTemplate, 8 | templateHelpers: function() { 9 | return { 10 | text: this.text 11 | }; 12 | }, 13 | 14 | behaviors: { 15 | DialogContent: { 16 | behaviorClass: DialogContent 17 | } 18 | }, 19 | 20 | text: '', 21 | 22 | initialize: function(options) { 23 | this.text = options.text; 24 | } 25 | }); 26 | 27 | export default ErrorView; -------------------------------------------------------------------------------- /src/js/background/enum/chromeCommand.js: -------------------------------------------------------------------------------- 1 | // These values are synced with manifest.json's commands declaration. 2 | export default { 3 | ShowVideoDetails: 'showVideoDetails', 4 | SaveVideo: 'saveVideo', 5 | DeleteVideo: 'deleteVideo', 6 | CopyVideoUrl: 'copyVideoUrl', 7 | CopyVideoTitleAndUrl: 'copyVideoTitleAndUrl', 8 | NextVideo: 'nextVideo', 9 | ToggleVideo: 'toggleVideo', 10 | PreviousVideo: 'previousVideo', 11 | ToggleRadio: 'toggleRadio', 12 | ToggleRepeat: 'toggleRepeat', 13 | ToggleShuffle: 'toggleShuffle', 14 | IncreaseVolume: 'increaseVolume', 15 | DecreaseVolume: 'decreaseVolume', 16 | OpenInTab: 'openInTab', 17 | SeekForward: 'seekForward', 18 | SeekBackward: 'seekBackward' 19 | }; -------------------------------------------------------------------------------- /src/template/appBar/searchInputArea.hbs: -------------------------------------------------------------------------------- 1 |
          2 |
          3 | {{{searchIcon}}} 4 |
          5 | 6 | 7 | 8 | 11 |
          12 | 13 | -------------------------------------------------------------------------------- /src/js/test/foreground/view/dialog/editPlaylistView.spec.js: -------------------------------------------------------------------------------- 1 | import EditPlaylistView from 'foreground/view/dialog/editPlaylistView'; 2 | import EditPlaylist from 'foreground/model/dialog/editPlaylist'; 3 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 4 | 5 | describe('EditPlaylistView', function() { 6 | beforeEach(function() { 7 | this.documentFragment = document.createDocumentFragment(); 8 | this.view = new EditPlaylistView({ 9 | model: new EditPlaylist({ 10 | playlist: TestUtility.buildPlaylist() 11 | }) 12 | }); 13 | }); 14 | 15 | afterEach(function() { 16 | this.view.destroy(); 17 | }); 18 | 19 | ViewTestUtility.ensureBasicAssumptions.call(this); 20 | }); -------------------------------------------------------------------------------- /src/less/grouping.less: -------------------------------------------------------------------------------- 1 | @import "color"; 2 | 3 | // It's important to use last-of-type with a
          element because 4 | // scrollable behavior can inject additional
          elements which conflict with last-child. 5 | .grouping:not(:last-of-type) { 6 | margin-bottom: 10px; 7 | } 8 | 9 | .grouping-header { 10 | margin-bottom: 8px; 11 | font-size: 13px; 12 | font-weight: 600; 13 | line-height: 16px; 14 | color: @blue--500; 15 | } 16 | 17 | .grouping-item { 18 | display: flex; 19 | font-size: 15px; 20 | flex-direction: column; 21 | justify-content: center; 22 | } 23 | 24 | // Avoid having blank padding for reminders which aren't shown. 25 | .grouping-item:empty { 26 | display: none; 27 | } -------------------------------------------------------------------------------- /src/js/background/view/clipboardView.js: -------------------------------------------------------------------------------- 1 | import {LayoutView} from 'marionette'; 2 | 3 | var ClipboardView = LayoutView.extend({ 4 | id: 'clipboard', 5 | tagName: 'textarea', 6 | template: false, 7 | 8 | initialize: function() { 9 | this.listenTo(StreamusBG.channels.clipboard.commands, 'copy:text', this._copyText); 10 | }, 11 | 12 | // http://stackoverflow.com/questions/5235719/how-to-copy-text-to-clipboard-from-a-google-chrome-extension 13 | // Copies text to the clipboard. Has to happen on background page due to elevated privileges. 14 | _copyText: function(text) { 15 | this.$el.val(text).select(); 16 | document.execCommand('copy', false, null); 17 | } 18 | }); 19 | 20 | export default ClipboardView; -------------------------------------------------------------------------------- /src/template/dialog/dialog.hbs: -------------------------------------------------------------------------------- 1 |
          2 |
          3 |
          4 | 5 |
          6 |
          7 | 8 |
          9 | {{cancelButtonText}} 10 |
          11 | 12 |
          13 | {{submitButtonText}} 14 |
          15 |
          16 |
          17 |
          -------------------------------------------------------------------------------- /src/template/icon/radioIcon_18.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/js/foreground/model/streamControlBar/timeLabelArea.js: -------------------------------------------------------------------------------- 1 | import {Model} from 'backbone'; 2 | import LocalStorage from 'lib/backbone.localStorage'; 3 | 4 | var TimeLabelArea = Model.extend({ 5 | localStorage: new LocalStorage('TimeLabelArea'), 6 | 7 | defaults: { 8 | // Need to set the ID for Backbone.LocalStorage 9 | id: 'TimeLabelArea', 10 | showRemainingTime: false 11 | }, 12 | 13 | initialize: function() { 14 | // Load from Backbone.LocalStorage 15 | this.fetch(); 16 | }, 17 | 18 | toggleShowRemainingTime: function() { 19 | var showRemainingTime = this.get('showRemainingTime'); 20 | this.save('showRemainingTime', !showRemainingTime); 21 | } 22 | }); 23 | 24 | export default TimeLabelArea; -------------------------------------------------------------------------------- /src/js/test/foreground/view/dialog/settingsView.spec.js: -------------------------------------------------------------------------------- 1 | import SettingsView from 'foreground/view/dialog/settingsView'; 2 | import SignInManager from 'background/model/signInManager'; 3 | import Settings from 'background/model/settings'; 4 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 5 | 6 | describe('SettingsView', function() { 7 | beforeEach(function() { 8 | this.documentFragment = document.createDocumentFragment(); 9 | this.view = new SettingsView({ 10 | model: new Settings(), 11 | signInManager: new SignInManager() 12 | }); 13 | }); 14 | 15 | afterEach(function() { 16 | this.view.destroy(); 17 | }); 18 | 19 | ViewTestUtility.ensureBasicAssumptions.call(this); 20 | }); -------------------------------------------------------------------------------- /src/js/test/foreground/view/dialog/exportPlaylistView.spec.js: -------------------------------------------------------------------------------- 1 | import ExportPlaylist from 'foreground/model/dialog/exportPlaylist'; 2 | import ExportPlaylistView from 'foreground/view/dialog/exportPlaylistView'; 3 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 4 | 5 | describe('ExportPlaylistView', function() { 6 | beforeEach(function() { 7 | this.documentFragment = document.createDocumentFragment(); 8 | this.view = new ExportPlaylistView({ 9 | model: new ExportPlaylist({ 10 | playlist: TestUtility.buildPlaylist() 11 | }) 12 | }); 13 | }); 14 | 15 | afterEach(function() { 16 | this.view.destroy(); 17 | }); 18 | 19 | ViewTestUtility.ensureBasicAssumptions.call(this); 20 | }); -------------------------------------------------------------------------------- /src/less/streamControlBar.less: -------------------------------------------------------------------------------- 1 | @import "color"; 2 | @import "transition"; 3 | @import "boxShadow"; 4 | 5 | .streamControlBar { 6 | height: 72px; 7 | color: @dark--primary; 8 | display: flex; 9 | } 10 | 11 | .streamControlBar-title { 12 | font-size: 17px; 13 | line-height: 28px; 14 | } 15 | 16 | .streamControlBar-playbackControls { 17 | display: flex; 18 | box-shadow: @boxShadow-divider--left; 19 | align-items: center; 20 | padding-left: 8px; 21 | padding-right: 4px; 22 | } 23 | 24 | .streamControlBar-streamControls { 25 | display: flex; 26 | align-items: center; 27 | padding-right: 8px; 28 | } 29 | 30 | .streamControlBar-content { 31 | padding-left: 16px; 32 | padding-right: 8px; 33 | padding-top: 12px; 34 | } -------------------------------------------------------------------------------- /src/less/volumeArea.less: -------------------------------------------------------------------------------- 1 | @import "boxShadow"; 2 | @import "color"; 3 | @import "transition"; 4 | 5 | .volumeArea:hover, 6 | .volumeArea:active { 7 | .volumeArea-slidePanel { 8 | opacity: 1; 9 | transform: scaleY(1); 10 | pointer-events: all; 11 | } 12 | } 13 | 14 | .volumeArea-slidePanel { 15 | width: 48px; 16 | height: 120px; 17 | transform: scaleY(0); 18 | opacity: 0; 19 | transform-origin: top; 20 | will-change: transform, opacity; 21 | transition: opacity @transition-duration--fast @transition-easeOutSine, transform @transition-duration--fast @transition-easeOutSine; 22 | } 23 | 24 | .volumeArea-slidePanel-content { 25 | height: 100%; 26 | border-radius: 2px; 27 | align-items: center; 28 | padding: 16px 0; 29 | } -------------------------------------------------------------------------------- /src/js/background/view/backgroundAreaView.js: -------------------------------------------------------------------------------- 1 | import {LayoutView} from 'marionette'; 2 | import ClipboardRegion from 'background/view/clipboardRegion'; 3 | import backgroundAreaTemplate from 'template/backgroundArea.hbs!'; 4 | 5 | var BackgroundAreaView = LayoutView.extend({ 6 | el: '#backgroundArea', 7 | template: backgroundAreaTemplate, 8 | 9 | regions: { 10 | clipboard: { 11 | el: 'clipboard', 12 | regionClass: ClipboardRegion 13 | } 14 | }, 15 | 16 | initialize: function() { 17 | this.model.get('analyticsManager').sendPageView('/background.html'); 18 | }, 19 | 20 | onRender: function() { 21 | StreamusBG.channels.backgroundArea.vent.trigger('rendered'); 22 | } 23 | }); 24 | 25 | export default BackgroundAreaView; -------------------------------------------------------------------------------- /src/js/foreground/view/dialog/updateStreamusDialogView.js: -------------------------------------------------------------------------------- 1 | import Dialog from 'foreground/model/dialog/dialog'; 2 | import UpdateStreamusView from 'foreground/view/dialog/updateStreamusView'; 3 | import DialogView from 'foreground/view/dialog/dialogView'; 4 | 5 | var UpdateStreamusDialogView = DialogView.extend({ 6 | id: 'updateStreamusDialog', 7 | 8 | initialize: function() { 9 | this.model = new Dialog({ 10 | submitButtonText: chrome.i18n.getMessage('update') 11 | }); 12 | 13 | this.contentView = new UpdateStreamusView(); 14 | 15 | DialogView.prototype.initialize.apply(this, arguments); 16 | }, 17 | 18 | onSubmit: function() { 19 | chrome.runtime.reload(); 20 | } 21 | }); 22 | 23 | export default UpdateStreamusDialogView; -------------------------------------------------------------------------------- /src/js/test/foreground/model/streamControlBar/timeLabelArea.spec.js: -------------------------------------------------------------------------------- 1 | import TimeLabelArea from 'foreground/model/streamControlBar/timeLabelArea'; 2 | 3 | describe('TimeLabelArea', function() { 4 | beforeEach(function() { 5 | this.timeLabelArea = new TimeLabelArea(); 6 | }); 7 | 8 | it('should be able to toggle its showRemainingTime property', function() { 9 | var currentShowRemainingTime = this.timeLabelArea.get('showRemainingTime'); 10 | this.timeLabelArea.toggleShowRemainingTime(); 11 | var updatedShowRemainingTime = this.timeLabelArea.get('showRemainingTime'); 12 | expect(updatedShowRemainingTime).not.to.equal(currentShowRemainingTime); 13 | expect(updatedShowRemainingTime).to.equal(!currentShowRemainingTime); 14 | }); 15 | }); -------------------------------------------------------------------------------- /src/js/test/foreground/view/playlist/playlistView.spec.js: -------------------------------------------------------------------------------- 1 | import PlaylistView from 'foreground/view/playlist/playlistView'; 2 | import Playlist from 'background/model/playlist'; 3 | import ListItemType from 'common/enum/listItemType'; 4 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 5 | 6 | describe('PlaylistView', function() { 7 | beforeEach(function() { 8 | this.documentFragment = document.createDocumentFragment(); 9 | this.view = new PlaylistView({ 10 | model: new Playlist(), 11 | type: ListItemType.Playlist, 12 | parentId: 'playlists-list' 13 | }); 14 | }); 15 | 16 | afterEach(function() { 17 | this.view.destroy(); 18 | }); 19 | 20 | ViewTestUtility.ensureBasicAssumptions.call(this); 21 | }); -------------------------------------------------------------------------------- /src/less/notification.less: -------------------------------------------------------------------------------- 1 | @import "color"; 2 | 3 | .notification { 4 | // https://github.com/MeoMix/StreamusChromeExtension/issues/564 5 | // Don't want the whitespace area around the notification to be unclickable. 6 | pointer-events: none !important; 7 | } 8 | 9 | .notification-content { 10 | max-width: 568px; 11 | min-width: 288px; 12 | padding: 0 24px; 13 | margin-bottom: 16px; 14 | margin-left: 16px; 15 | // The spec says this should be 14px, but I think 15px looks better. 16 | font-size: 15px; 17 | line-height: 48px; 18 | color: @white; 19 | cursor: default; 20 | // http://www.google.com/design/spec/components/snackbars-toasts.html#snackbars-toasts-usage 21 | background-color: @gray--snackbar; 22 | border-radius: 2px; 23 | } -------------------------------------------------------------------------------- /src/less/flexColumn.less: -------------------------------------------------------------------------------- 1 | @import "utility"; 2 | @import "overlay"; 3 | 4 | @columnWidth--wide: 55; 5 | @columnWidth--thin: 45; 6 | 7 | .flexColumn { 8 | display: flex; 9 | flex: 1; 10 | flex-direction: column; 11 | min-height: 0; 12 | } 13 | 14 | // https://code.google.com/p/chromium/issues/detail?id=484460&q=flex&colspec=ID%20Pri%20M%20Week%20ReleaseBlock%20Cr%20Status%20Owner%20Summary%20OS%20Modified 15 | // https://github.com/philipwalton/flexbugs#1-minimum-content-sizing-of-flex-items-not-honored 16 | .flexColumn-bugFix { 17 | flex-shrink: 0; 18 | } 19 | 20 | .flexColumn--wide { 21 | width: (@columnWidth--wide * 1%); 22 | flex: @columnWidth--wide; 23 | } 24 | 25 | .flexColumn--thin { 26 | width: (@columnWidth--thin * 1%); 27 | flex: @columnWidth--thin; 28 | } -------------------------------------------------------------------------------- /src/template/leftPane/playlistItem.hbs: -------------------------------------------------------------------------------- 1 |
          2 | 3 |
          4 |
          5 |
          6 |
          7 | 8 |
          9 |
          10 | {{get video 'title'}} 11 |
          12 | 13 |
          14 | {{get video 'prettyDuration'}} 15 |
          16 |
          17 | 18 | -------------------------------------------------------------------------------- /src/js/foreground/view/notification/notificationRegion.js: -------------------------------------------------------------------------------- 1 | import {Region} from 'marionette'; 2 | import Notification from 'foreground/model/notification/notification'; 3 | import NotificationView from 'foreground/view/notification/notificationView'; 4 | 5 | var NotificationRegion = Region.extend({ 6 | initialize: function() { 7 | this.listenTo(StreamusFG.channels.notification.commands, 'show:notification', this._showNotification); 8 | this.listenTo(StreamusFG.backgroundChannels.notification.commands, 'show:notification', this._showNotification); 9 | }, 10 | 11 | _showNotification: function(notificationOptions) { 12 | this.show(new NotificationView({ 13 | model: new Notification(notificationOptions) 14 | })); 15 | } 16 | }); 17 | 18 | export default NotificationRegion; -------------------------------------------------------------------------------- /src/js/test/foreground/view/streamControlBar/streamControlBarSpecLoader.js: -------------------------------------------------------------------------------- 1 | import 'test/foreground/view/streamControlBar/nextButtonView.spec'; 2 | import 'test/foreground/view/streamControlBar/playPauseButtonView.spec'; 3 | import 'test/foreground/view/streamControlBar/previousButtonView.spec'; 4 | import 'test/foreground/view/streamControlBar/radioButtonView.spec'; 5 | import 'test/foreground/view/streamControlBar/repeatButtonView.spec'; 6 | import 'test/foreground/view/streamControlBar/shuffleButtonView.spec'; 7 | import 'test/foreground/view/streamControlBar/streamControlBarView.spec'; 8 | import 'test/foreground/view/streamControlBar/timeLabelAreaView.spec'; 9 | import 'test/foreground/view/streamControlBar/timeSliderView.spec'; 10 | import 'test/foreground/view/streamControlBar/volumeAreaView.spec'; -------------------------------------------------------------------------------- /src/template/leftPane/signIn.hbs: -------------------------------------------------------------------------------- 1 |
          2 |
          3 |
          4 | {{signingInMessage}} 5 |
          6 |
          7 |
          8 | 9 |
          10 | 13 |
          14 | 15 |
          16 |
          17 | {{signInFailedMessage}} 18 |
          19 |
          20 | {{pleaseWaitMessage}} {{signInRetryTimer}} 21 |
          22 |
          23 |
          -------------------------------------------------------------------------------- /src/js/foreground/model/dialog/createPlaylist.js: -------------------------------------------------------------------------------- 1 | import {Model} from 'backbone'; 2 | 3 | var CreatePlaylist = Model.extend({ 4 | defaults: { 5 | valid: false, 6 | dataSourceValid: true, 7 | titleValid: false 8 | }, 9 | 10 | initialize: function() { 11 | this.on('change:dataSourceValid', this._onChangeDataSourceValid); 12 | this.on('change:titleValid', this._onChangeTitleValid); 13 | }, 14 | 15 | _onChangeDataSourceValid: function(model, dataSourceValid) { 16 | var valid = dataSourceValid && this.get('titleValid'); 17 | this.set('valid', valid); 18 | }, 19 | 20 | _onChangeTitleValid: function(model, titleValid) { 21 | var valid = titleValid && this.get('dataSourceValid'); 22 | this.set('valid', valid); 23 | } 24 | }); 25 | 26 | export default CreatePlaylist; -------------------------------------------------------------------------------- /src/js/foreground/view/dialog/clearStreamDialogView.js: -------------------------------------------------------------------------------- 1 | import Dialog from 'foreground/model/dialog/dialog'; 2 | import ClearStreamView from 'foreground/view/dialog/clearStreamView'; 3 | import DialogView from 'foreground/view/dialog/dialogView'; 4 | 5 | var ClearStreamDialogView = DialogView.extend({ 6 | id: 'clearStreamDialog', 7 | streamItems: null, 8 | 9 | initialize: function(options) { 10 | this.streamItems = options.streamItems; 11 | 12 | this.model = new Dialog({ 13 | reminderProperty: 'remindClearStream' 14 | }); 15 | 16 | this.contentView = new ClearStreamView(); 17 | 18 | DialogView.prototype.initialize.apply(this, arguments); 19 | }, 20 | 21 | onSubmit: function() { 22 | this.streamItems.clear(); 23 | } 24 | }); 25 | 26 | export default ClearStreamDialogView; -------------------------------------------------------------------------------- /src/js/test/foreground/view/listItemButton/deletePlaylistButtonView.spec.js: -------------------------------------------------------------------------------- 1 | import DeletePlaylistButtonView from 'foreground/view/listItemButton/deletePlaylistButtonView'; 2 | import Playlist from 'background/model/playlist'; 3 | import ListItemButton from 'foreground/model/listItemButton/listItemButton'; 4 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 5 | 6 | describe('DeletePlaylistButtonView', function() { 7 | beforeEach(function() { 8 | this.documentFragment = document.createDocumentFragment(); 9 | this.view = new DeletePlaylistButtonView({ 10 | model: new ListItemButton(), 11 | playlist: new Playlist() 12 | }); 13 | }); 14 | 15 | afterEach(function() { 16 | this.view.destroy(); 17 | }); 18 | 19 | ViewTestUtility.ensureBasicAssumptions.call(this); 20 | }); -------------------------------------------------------------------------------- /src/template/streamControlBar/volumeArea.hbs: -------------------------------------------------------------------------------- 1 |
          2 | 3 | {{{volumeUpIcon}}} 4 | 5 | 6 | 7 | {{{volumeDownIcon}}} 8 | 9 | 10 | 11 | {{{volumeOffIcon}}} 12 | 13 | 14 | 15 | {{{volumeMuteIcon}}} 16 | 17 |
          18 | 19 |
          20 |
          21 | 22 |
          23 |
          -------------------------------------------------------------------------------- /src/js/test/foreground/view/listItemButton/playlistOptionsButtonView.spec.js: -------------------------------------------------------------------------------- 1 | import PlaylistOptionsButtonView from 'foreground/view/listItemButton/playlistOptionsButtonView'; 2 | import ListItemButton from 'foreground/model/listItemButton/listItemButton'; 3 | import Playlist from 'background/model/playlist'; 4 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 5 | 6 | describe('PlaylistOptionsButtonView', function() { 7 | beforeEach(function() { 8 | this.documentFragment = document.createDocumentFragment(); 9 | this.view = new PlaylistOptionsButtonView({ 10 | model: new ListItemButton(), 11 | playlist: new Playlist() 12 | }); 13 | }); 14 | 15 | afterEach(function() { 16 | this.view.destroy(); 17 | }); 18 | 19 | ViewTestUtility.ensureBasicAssumptions.call(this); 20 | }); -------------------------------------------------------------------------------- /src/js/test/foreground/view/stream/clearStreamButtonView.spec.js: -------------------------------------------------------------------------------- 1 | import ClearStreamButtonView from 'foreground/view/stream/clearStreamButtonView'; 2 | import ClearStreamButton from 'foreground/model/stream/clearStreamButton'; 3 | import StreamItems from 'background/collection/streamItems'; 4 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 5 | 6 | describe('ClearStreamButtonView', function() { 7 | beforeEach(function() { 8 | this.documentFragment = document.createDocumentFragment(); 9 | this.view = new ClearStreamButtonView({ 10 | model: new ClearStreamButton({ 11 | streamItems: new StreamItems() 12 | }) 13 | }); 14 | }); 15 | 16 | afterEach(function() { 17 | this.view.destroy(); 18 | }); 19 | 20 | ViewTestUtility.ensureBasicAssumptions.call(this); 21 | }); -------------------------------------------------------------------------------- /src/template/foregroundArea.hbs: -------------------------------------------------------------------------------- 1 |
          2 |
          3 |
          4 |
          5 |
          6 |
          7 |
          8 | 9 |
          10 |
          11 |
          12 |
          13 |
          -------------------------------------------------------------------------------- /src/js/test/foreground/view/listItemButton/listItemButtonSpecLoader.js: -------------------------------------------------------------------------------- 1 | import 'test/foreground/view/listItemButton/addPlaylistButtonView.spec'; 2 | import 'test/foreground/view/listItemButton/addVideoButtonView.spec'; 3 | import 'test/foreground/view/listItemButton/deleteListItemButtonView.spec'; 4 | import 'test/foreground/view/listItemButton/deletePlaylistButtonView.spec'; 5 | import 'test/foreground/view/listItemButton/listItemButtonsView.spec'; 6 | import 'test/foreground/view/listItemButton/playlistOptionsButtonView.spec'; 7 | import 'test/foreground/view/listItemButton/playPauseVideoButtonView.spec'; 8 | import 'test/foreground/view/listItemButton/playPlaylistButtonView.spec'; 9 | import 'test/foreground/view/listItemButton/saveVideoButtonView.spec'; 10 | import 'test/foreground/view/listItemButton/videoOptionsButtonView.spec'; -------------------------------------------------------------------------------- /src/js/test/foreground/view/listItemButton/deleteListItemButtonView.spec.js: -------------------------------------------------------------------------------- 1 | import DeleteListItemButtonView from 'foreground/view/listItemButton/deleteListItemButtonView'; 2 | import PlaylistItem from 'background/model/playlistItem'; 3 | import ListItemButton from 'foreground/model/listItemButton/listItemButton'; 4 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 5 | 6 | describe('DeleteListItemButtonView', function() { 7 | beforeEach(function() { 8 | this.documentFragment = document.createDocumentFragment(); 9 | this.view = new DeleteListItemButtonView({ 10 | model: new ListItemButton(), 11 | playlistItem: new PlaylistItem() 12 | }); 13 | }); 14 | 15 | afterEach(function() { 16 | this.view.destroy(); 17 | }); 18 | 19 | ViewTestUtility.ensureBasicAssumptions.call(this); 20 | }); -------------------------------------------------------------------------------- /src/js/foreground/model/dialog/exportPlaylist.js: -------------------------------------------------------------------------------- 1 | import {Model} from 'backbone'; 2 | import LocalStorage from 'lib/backbone.localStorage'; 3 | import ExportFileType from 'common/enum/exportFileType'; 4 | 5 | var ExportPlaylist = Model.extend({ 6 | localStorage: new LocalStorage('ExportPlaylist'), 7 | 8 | defaults: { 9 | // Need to set id for Backbone.LocalStorage 10 | id: 'ExportPlaylist', 11 | playlist: null, 12 | fileType: ExportFileType.Csv 13 | }, 14 | 15 | // Don't want to save the playlist to localStorage -- only the configuration variables 16 | blacklist: ['playlist'], 17 | toJSON: function() { 18 | return this.omit(this.blacklist); 19 | }, 20 | 21 | initialize: function() { 22 | // Load from Backbone.LocalStorage 23 | this.fetch(); 24 | } 25 | }); 26 | 27 | export default ExportPlaylist; -------------------------------------------------------------------------------- /src/js/test/foreground/view/listItemButton/videoOptionsButtonView.spec.js: -------------------------------------------------------------------------------- 1 | import VideoOptionsButtonView from 'foreground/view/listItemButton/videoOptionsButtonView'; 2 | import ListItemButton from 'foreground/model/listItemButton/listItemButton'; 3 | import Video from 'background/model/video'; 4 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 5 | 6 | describe('VideoOptionsButtonView', function() { 7 | beforeEach(function() { 8 | this.documentFragment = document.createDocumentFragment(); 9 | this.view = new VideoOptionsButtonView({ 10 | model: new ListItemButton(), 11 | video: new Video(), 12 | player: TestUtility.buildPlayer() 13 | }); 14 | }); 15 | 16 | afterEach(function() { 17 | this.view.destroy(); 18 | }); 19 | 20 | ViewTestUtility.ensureBasicAssumptions.call(this); 21 | }); -------------------------------------------------------------------------------- /src/js/common/shim/lodash.mixin.shim.js: -------------------------------------------------------------------------------- 1 | import _ from 'common/shim/lodash.reference.shim'; 2 | 3 | _.mixin({ 4 | // Inspired by: https://gist.github.com/danro/7846358 5 | // Pass requestAnimationFrame as the throttleMechanism. Necessary because RAF is scoped to the current window. 6 | // Leverage requestAnimationFrame for throttling function calls instead of setTimeout for better perf. 7 | throttleFramerate: function(throttleMechanisim, callback) { 8 | var wait = false; 9 | var args = null; 10 | var context = null; 11 | 12 | return function() { 13 | if (!wait) { 14 | wait = true; 15 | args = arguments; 16 | context = this; 17 | throttleMechanisim(function() { 18 | wait = false; 19 | callback.apply(context, args); 20 | }); 21 | } 22 | }; 23 | } 24 | }); -------------------------------------------------------------------------------- /src/js/foreground/view/dialog/settingsDialogView.js: -------------------------------------------------------------------------------- 1 | import Dialog from 'foreground/model/dialog/dialog'; 2 | import DialogView from 'foreground/view/dialog/dialogView'; 3 | import SettingsView from 'foreground/view/dialog/settingsView'; 4 | 5 | var SettingsDialogView = DialogView.extend({ 6 | id: 'settingsDialog', 7 | 8 | initialize: function() { 9 | this.model = new Dialog({ 10 | submitButtonText: chrome.i18n.getMessage('save') 11 | }); 12 | 13 | this.contentView = new SettingsView({ 14 | model: StreamusFG.backgroundProperties.settings, 15 | signInManager: StreamusFG.backgroundProperties.signInManager 16 | }); 17 | 18 | DialogView.prototype.initialize.apply(this, arguments); 19 | }, 20 | 21 | onSubmit: function() { 22 | this.contentView.save(); 23 | } 24 | }); 25 | 26 | export default SettingsDialogView; -------------------------------------------------------------------------------- /src/template/selectionBar/selectionBar.hbs: -------------------------------------------------------------------------------- 1 |
          2 |
          3 | {{{closeIcon}}} 4 |
          5 | 6 |
          7 |
          8 |
          9 | 10 |
          11 | {{i18n 'play'}} 12 |
          13 | 14 |
          15 | {{i18n 'add'}} 16 |
          17 | 18 |
          19 | {{i18n 'save'}} 20 |
          21 | 22 |
          23 | {{i18n 'delete'}} 24 |
          25 |
          -------------------------------------------------------------------------------- /src/js/test/foreground/view/leftPane/activePlaylistAreaView.spec.js: -------------------------------------------------------------------------------- 1 | import ActivePlaylistAreaView from 'foreground/view/leftPane/activePlaylistAreaView'; 2 | import Playlist from 'background/model/playlist'; 3 | import StreamItems from 'background/collection/streamItems'; 4 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 5 | 6 | describe('ActivePlaylistAreaView', function() { 7 | beforeEach(function() { 8 | this.documentFragment = document.createDocumentFragment(); 9 | 10 | var playlist = new Playlist(); 11 | this.view = new ActivePlaylistAreaView({ 12 | model: playlist, 13 | collection: playlist.get('items'), 14 | streamItems: new StreamItems() 15 | }); 16 | }); 17 | 18 | afterEach(function() { 19 | this.view.destroy(); 20 | }); 21 | 22 | ViewTestUtility.ensureBasicAssumptions.call(this); 23 | }); -------------------------------------------------------------------------------- /src/js/background/model/shareCode.js: -------------------------------------------------------------------------------- 1 | import {Model} from 'backbone'; 2 | import EntityType from 'background/enum/entityType'; 3 | 4 | var ShareCode = Model.extend({ 5 | defaults: { 6 | id: null, 7 | entityType: EntityType.None, 8 | entityId: null, 9 | shortId: null, 10 | urlFriendlyEntityTitle: '' 11 | }, 12 | 13 | urlRoot: function() { 14 | return StreamusBG.serverUrl + 'ShareCode/'; 15 | }, 16 | 17 | copyUrl: function() { 18 | var entityType = this.get('entityType'); 19 | var shortId = this.get('shortId'); 20 | var urlFriendlyEntityTitle = this.get('urlFriendlyEntityTitle'); 21 | var shareUrl = 'https://streamus.com/share/' + entityType + '/' + shortId + '/' + urlFriendlyEntityTitle; 22 | 23 | StreamusBG.channels.clipboard.commands.trigger('copy:text', shareUrl); 24 | } 25 | }); 26 | 27 | export default ShareCode; -------------------------------------------------------------------------------- /src/js/foreground/view/dialog/deletePlaylistDialogView.js: -------------------------------------------------------------------------------- 1 | import Dialog from 'foreground/model/dialog/dialog'; 2 | import DeletePlaylistView from 'foreground/view/dialog/deletePlaylistView'; 3 | import DialogView from 'foreground/view/dialog/dialogView'; 4 | 5 | var DeletePlaylistDialogView = DialogView.extend({ 6 | id: 'deletePlaylistDialog', 7 | 8 | initialize: function(options) { 9 | this.model = new Dialog({ 10 | submitButtonText: chrome.i18n.getMessage('delete'), 11 | reminderProperty: 'remindDeletePlaylist' 12 | }); 13 | 14 | this.contentView = new DeletePlaylistView({ 15 | model: options.playlist 16 | }); 17 | 18 | DialogView.prototype.initialize.apply(this, arguments); 19 | }, 20 | 21 | onSubmit: function() { 22 | this.contentView.deletePlaylist(); 23 | } 24 | }); 25 | 26 | export default DeletePlaylistDialogView; -------------------------------------------------------------------------------- /src/js/foreground/view/dialog/googleSignInDialogView.js: -------------------------------------------------------------------------------- 1 | import Dialog from 'foreground/model/dialog/dialog'; 2 | import GoogleSignInView from 'foreground/view/dialog/googleSignInView'; 3 | import DialogView from 'foreground/view/dialog/dialogView'; 4 | 5 | var GoogleSignInDialogView = DialogView.extend({ 6 | id: 'googleSignInDialog', 7 | signInManager: null, 8 | 9 | initialize: function(options) { 10 | this.signInManager = options.signInManager; 11 | 12 | this.model = new Dialog({ 13 | reminderProperty: 'remindGoogleSignIn', 14 | alwaysSaveReminder: true 15 | }); 16 | 17 | this.contentView = new GoogleSignInView(); 18 | 19 | DialogView.prototype.initialize.apply(this, arguments); 20 | }, 21 | 22 | onSubmit: function() { 23 | this.signInManager.set('needGoogleSignIn', false); 24 | } 25 | }); 26 | 27 | export default GoogleSignInDialogView; -------------------------------------------------------------------------------- /src/js/test/foreground/view/listItemButton/addVideoButtonView.spec.js: -------------------------------------------------------------------------------- 1 | import AddVideoButtonView from 'foreground/view/listItemButton/addVideoButtonView'; 2 | import Video from 'background/model/video'; 3 | import StreamItems from 'background/collection/streamItems'; 4 | import ListItemButton from 'foreground/model/listItemButton/listItemButton'; 5 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 6 | 7 | describe('AddVideoButtonView', function() { 8 | beforeEach(function() { 9 | this.documentFragment = document.createDocumentFragment(); 10 | this.view = new AddVideoButtonView({ 11 | model: new ListItemButton(), 12 | video: new Video(), 13 | streamItems: new StreamItems() 14 | }); 15 | }); 16 | 17 | afterEach(function() { 18 | this.view.destroy(); 19 | }); 20 | 21 | ViewTestUtility.ensureBasicAssumptions.call(this); 22 | }); -------------------------------------------------------------------------------- /src/js/test/foreground/view/listItemButton/saveVideoButtonView.spec.js: -------------------------------------------------------------------------------- 1 | import SaveVideoButtonView from 'foreground/view/listItemButton/saveVideoButtonView'; 2 | import Video from 'background/model/video'; 3 | import SignInManager from 'background/model/signInManager'; 4 | import ListItemButton from 'foreground/model/listItemButton/listItemButton'; 5 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 6 | 7 | describe('SaveVideoButtonView', function() { 8 | beforeEach(function() { 9 | this.documentFragment = document.createDocumentFragment(); 10 | this.view = new SaveVideoButtonView({ 11 | model: new ListItemButton(), 12 | video: new Video(), 13 | signInManager: new SignInManager() 14 | }); 15 | }); 16 | 17 | afterEach(function() { 18 | this.view.destroy(); 19 | }); 20 | 21 | ViewTestUtility.ensureBasicAssumptions.call(this); 22 | }); -------------------------------------------------------------------------------- /src/template/stream/stream.hbs: -------------------------------------------------------------------------------- 1 |
          2 |
          3 |
          4 | {{i18n 'streamEmpty'}} 5 |
          6 |
          7 | {{i18n 'whyNotAddAVideoFromAPlaylistOr'}} {{i18n 'searchForVideos'}}? 8 |
          9 |
          10 | 11 |
          12 |
          13 | 14 |
          15 |
          16 |
          17 |
          18 |
          19 |
          20 |
          -------------------------------------------------------------------------------- /src/js/test/foreground/view/dialog/settingsDialogView.spec.js: -------------------------------------------------------------------------------- 1 | import SettingsDialogView from 'foreground/view/dialog/settingsDialogView'; 2 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 3 | 4 | describe('SettingsDialogView', function() { 5 | beforeEach(function() { 6 | this.documentFragment = document.createDocumentFragment(); 7 | this.view = new SettingsDialogView(); 8 | }); 9 | 10 | afterEach(function() { 11 | this.view.destroy(); 12 | }); 13 | 14 | ViewTestUtility.ensureBasicAssumptions.call(this); 15 | 16 | describe('onSubmit', function() { 17 | it('should save configured settings', function() { 18 | sinon.stub(this.view.contentView, 'save'); 19 | 20 | this.view.onSubmit(); 21 | expect(this.view.contentView.save.calledOnce).to.equal(true); 22 | 23 | this.view.contentView.save.restore(); 24 | }); 25 | }); 26 | }); -------------------------------------------------------------------------------- /src/js/test/foreground/view/dialog/deletePlaylistView.spec.js: -------------------------------------------------------------------------------- 1 | import DeletePlaylistView from 'foreground/view/dialog/deletePlaylistView'; 2 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 3 | 4 | describe('DeletePlaylistView', function() { 5 | beforeEach(function() { 6 | this.documentFragment = document.createDocumentFragment(); 7 | this.view = new DeletePlaylistView({ 8 | model: TestUtility.buildPlaylist() 9 | }); 10 | }); 11 | 12 | afterEach(function() { 13 | this.view.destroy(); 14 | }); 15 | 16 | ViewTestUtility.ensureBasicAssumptions.call(this); 17 | 18 | it('should destroy its model when calling deletePlaylist', function() { 19 | sinon.stub(this.view.model, 'destroy'); 20 | 21 | this.view.deletePlaylist(); 22 | expect(this.view.model.destroy.calledOnce).to.equal(true); 23 | 24 | this.view.model.destroy.restore(); 25 | }); 26 | }); -------------------------------------------------------------------------------- /src/js/test/foreground/view/dialog/updateStreamusDialogView.spec.js: -------------------------------------------------------------------------------- 1 | import UpdateStreamusDialogView from 'foreground/view/dialog/updateStreamusDialogView'; 2 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 3 | 4 | describe('UpdateStreamusDialogView', function() { 5 | beforeEach(function() { 6 | this.documentFragment = document.createDocumentFragment(); 7 | this.view = new UpdateStreamusDialogView(); 8 | }); 9 | 10 | afterEach(function() { 11 | this.view.destroy(); 12 | }); 13 | 14 | ViewTestUtility.ensureBasicAssumptions.call(this); 15 | 16 | describe('onSubmit', function() { 17 | it('should reload the extension', function() { 18 | sinon.stub(chrome.runtime, 'reload'); 19 | 20 | this.view.onSubmit(); 21 | expect(chrome.runtime.reload.calledOnce).to.equal(true); 22 | 23 | chrome.runtime.reload.restore(); 24 | }); 25 | }); 26 | }); -------------------------------------------------------------------------------- /src/js/test/foreground/view/listItemButton/addPlaylistButtonView.spec.js: -------------------------------------------------------------------------------- 1 | import AddPlaylistButtonView from 'foreground/view/listItemButton/addPlaylistButtonView'; 2 | import Playlist from 'background/model/playlist'; 3 | import StreamItems from 'background/collection/streamItems'; 4 | import ListItemButton from 'foreground/model/listItemButton/listItemButton'; 5 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 6 | 7 | describe('AddPlaylistButtonView', function() { 8 | beforeEach(function() { 9 | this.documentFragment = document.createDocumentFragment(); 10 | this.view = new AddPlaylistButtonView({ 11 | model: new ListItemButton(), 12 | playlist: new Playlist(), 13 | streamItems: new StreamItems() 14 | }); 15 | }); 16 | 17 | afterEach(function() { 18 | this.view.destroy(); 19 | }); 20 | 21 | ViewTestUtility.ensureBasicAssumptions.call(this); 22 | }); -------------------------------------------------------------------------------- /src/js/test/foreground/view/listItemButton/playPlaylistButtonView.spec.js: -------------------------------------------------------------------------------- 1 | import PlayPlaylistButtonView from 'foreground/view/listItemButton/playPlaylistButtonView'; 2 | import Playlist from 'background/model/playlist'; 3 | import ListItemButton from 'foreground/model/listItemButton/listItemButton'; 4 | import StreamItems from 'background/collection/streamItems'; 5 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 6 | 7 | describe('PlayPlaylistButtonView', function() { 8 | beforeEach(function() { 9 | this.documentFragment = document.createDocumentFragment(); 10 | this.view = new PlayPlaylistButtonView({ 11 | model: new ListItemButton(), 12 | playlist: new Playlist(), 13 | streamItems: new StreamItems() 14 | }); 15 | }); 16 | 17 | afterEach(function() { 18 | this.view.destroy(); 19 | }); 20 | 21 | ViewTestUtility.ensureBasicAssumptions.call(this); 22 | }); -------------------------------------------------------------------------------- /src/template/streamControlBar/streamControlBar.hbs: -------------------------------------------------------------------------------- 1 |
          2 |
          3 |
          4 |
          5 |
          6 |
          7 | 8 |
          9 |
          10 |
          11 |
          12 |
          13 | 14 |
          15 |
          16 |
          17 |
          18 |
          19 |
          20 |
          21 |
          22 |
          -------------------------------------------------------------------------------- /src/js/foreground/view/dialog/linkUserIdDialogView.js: -------------------------------------------------------------------------------- 1 | import Dialog from 'foreground/model/dialog/dialog'; 2 | import LinkUserIdView from 'foreground/view/dialog/linkUserIdView'; 3 | import DialogView from 'foreground/view/dialog/dialogView'; 4 | 5 | var LinkUserIdDialogView = DialogView.extend({ 6 | id: 'linkUserIdDialog', 7 | signInManager: null, 8 | 9 | initialize: function(options) { 10 | this.signInManager = options.signInManager; 11 | 12 | this.model = new Dialog({ 13 | reminderProperty: 'remindLinkUserId', 14 | submitButtonText: chrome.i18n.getMessage('link'), 15 | alwaysSaveReminder: true 16 | }); 17 | 18 | this.contentView = new LinkUserIdView(); 19 | 20 | DialogView.prototype.initialize.apply(this, arguments); 21 | }, 22 | 23 | onSubmit: function() { 24 | this.signInManager.saveGooglePlusId(); 25 | } 26 | }); 27 | 28 | export default LinkUserIdDialogView; -------------------------------------------------------------------------------- /src/js/test/background/backgroundSpecLoader.js: -------------------------------------------------------------------------------- 1 | // /collection/ 2 | import 'test/background/collection/clientErrors.spec'; 3 | import 'test/background/collection/playlistItems.spec'; 4 | import 'test/background/collection/playlists.spec'; 5 | import 'test/background/collection/searchResults.spec'; 6 | import 'test/background/collection/videos.spec'; 7 | import 'test/background/collection/streamItems.spec'; 8 | 9 | // /model/ 10 | import 'test/background/model/activePlaylistManager.spec'; 11 | import 'test/background/model/clientErrorManager.spec'; 12 | import 'test/background/model/dataSource.spec'; 13 | import 'test/background/model/playlistItem.spec'; 14 | import 'test/background/model/playlistItems.spec'; 15 | import 'test/background/model/relatedVideosManager.spec'; 16 | import 'test/background/model/signInManager.spec'; 17 | import 'test/background/model/user.spec'; 18 | import 'test/background/model/youTubeV3API.spec'; -------------------------------------------------------------------------------- /src/less/scrollbar.less: -------------------------------------------------------------------------------- 1 | @import "color"; 2 | @import "transition"; 3 | 4 | .scrollbar-container { 5 | position: relative; 6 | overflow: hidden; 7 | 8 | &:hover .scrollbar-thumb { 9 | background-color: @dark--quaternary; 10 | } 11 | } 12 | 13 | .scrollbar-track, 14 | .scrollbar-thumb { 15 | position: absolute; 16 | top: 0; 17 | right: 0; 18 | transition: background-color @transition-duration--fast ease-out, width @transition-duration--fast ease-out; 19 | } 20 | 21 | .scrollbar-track { 22 | height: 100%; 23 | width: 12px; 24 | 25 | &:hover, 26 | &:active { 27 | .scrollbar-thumb { 28 | width: 12px; 29 | background-color: @dark--tertiary; 30 | } 31 | } 32 | } 33 | 34 | .scrollbar-thumb { 35 | width: 8px; 36 | height: 1px; 37 | transform-origin: 50% 0; 38 | } 39 | 40 | .scrollbar-thumb { 41 | &.is-clicked { 42 | background-color: @dark--tertiary; 43 | } 44 | } -------------------------------------------------------------------------------- /src/js/test/foreground/view/stream/saveStreamButtonView.spec.js: -------------------------------------------------------------------------------- 1 | import SaveStreamButtonView from 'foreground/view/stream/saveStreamButtonView'; 2 | import SaveStreamButton from 'foreground/model/stream/saveStreamButton'; 3 | import StreamItems from 'background/collection/streamItems'; 4 | import SignInManager from 'background/model/signInManager'; 5 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 6 | 7 | describe('SaveStreamButtonView', function() { 8 | beforeEach(function() { 9 | this.documentFragment = document.createDocumentFragment(); 10 | this.view = new SaveStreamButtonView({ 11 | model: new SaveStreamButton({ 12 | streamItems: new StreamItems(), 13 | signInManager: new SignInManager() 14 | }) 15 | }); 16 | }); 17 | 18 | afterEach(function() { 19 | this.view.destroy(); 20 | }); 21 | 22 | ViewTestUtility.ensureBasicAssumptions.call(this); 23 | }); -------------------------------------------------------------------------------- /src/js/foreground/view/dialog/editPlaylistDialogView.js: -------------------------------------------------------------------------------- 1 | import Dialog from 'foreground/model/dialog/dialog'; 2 | import EditPlaylistView from 'foreground/view/dialog/editPlaylistView'; 3 | import EditPlaylist from 'foreground/model/dialog/editPlaylist'; 4 | import DialogView from 'foreground/view/dialog/dialogView'; 5 | 6 | var EditPlaylistDialogView = DialogView.extend({ 7 | id: 'editPlaylistDialog', 8 | 9 | initialize: function(options) { 10 | this.model = new Dialog({ 11 | submitButtonText: chrome.i18n.getMessage('update') 12 | }); 13 | 14 | this.contentView = new EditPlaylistView({ 15 | model: new EditPlaylist({ 16 | playlist: options.playlist 17 | }) 18 | }); 19 | 20 | DialogView.prototype.initialize.apply(this, arguments); 21 | }, 22 | 23 | onSubmit: function() { 24 | this.contentView.editPlaylist(); 25 | } 26 | }); 27 | 28 | export default EditPlaylistDialogView; -------------------------------------------------------------------------------- /src/js/foreground/view/element/radioButtonView.js: -------------------------------------------------------------------------------- 1 | import {LayoutView} from 'marionette'; 2 | import radioButtonTemplate from 'template/element/radioButton.hbs!'; 3 | 4 | var RadioButtonView = LayoutView.extend({ 5 | tagName: 'radio-button', 6 | template: radioButtonTemplate, 7 | 8 | events: { 9 | 'click': '_onClick' 10 | }, 11 | 12 | modelEvents: { 13 | 'change:checked': '_onChangeChecked' 14 | }, 15 | 16 | onRender: function() { 17 | this._setCheckedState(this.model.get('checked')); 18 | }, 19 | 20 | _onClick: function() { 21 | this.model.set('checked', true); 22 | }, 23 | 24 | _onChangeChecked: function(model, checked) { 25 | this._setCheckedState(checked); 26 | }, 27 | 28 | _setCheckedState: function(checked) { 29 | this.$el.toggleClass('is-checked', checked); 30 | this.$el.toggleClass('is-unchecked', !checked); 31 | } 32 | }); 33 | 34 | export default RadioButtonView; -------------------------------------------------------------------------------- /src/js/test/foreground/view/viewTestUtility.js: -------------------------------------------------------------------------------- 1 | import _ from 'common/shim/lodash.reference.shim'; 2 | 3 | var ViewTestUtility = { 4 | ensureBasicAssumptions: function() { 5 | describe('Basic Assumptions', function() { 6 | it('should show', function() { 7 | this.documentFragment.appendChild(this.view.render().el); 8 | this.view.triggerMethod('show'); 9 | }); 10 | 11 | it('should be able to find all referenced ui targets', function() { 12 | this.documentFragment.appendChild(this.view.render().el); 13 | 14 | _.forIn(this.view.ui, function(element) { 15 | if (element.length === 0) { 16 | console.error('Selector ' + element.selector + ' has length 0 on view', this.view.el); 17 | } 18 | 19 | expect(element.length).to.not.equal(0); 20 | }, this); 21 | }); 22 | }); 23 | } 24 | }; 25 | 26 | export default ViewTestUtility; -------------------------------------------------------------------------------- /Streamus Chrome Extension.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2012 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Streamus Chrome Extension", "Streamus Chrome Extension.csproj", "{2EAC119F-E979-44B7-BCF6-0B477BE94B8F}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|Any CPU = Debug|Any CPU 9 | Release|Any CPU = Release|Any CPU 10 | EndGlobalSection 11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 12 | {2EAC119F-E979-44B7-BCF6-0B477BE94B8F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 13 | {2EAC119F-E979-44B7-BCF6-0B477BE94B8F}.Release|Any CPU.ActiveCfg = Release|Any CPU 14 | {2EAC119F-E979-44B7-BCF6-0B477BE94B8F}.Release|Any CPU.Build.0 = Release|Any CPU 15 | EndGlobalSection 16 | GlobalSection(SolutionProperties) = preSolution 17 | HideSolutionNode = FALSE 18 | EndGlobalSection 19 | EndGlobal 20 | -------------------------------------------------------------------------------- /src/js/test/foreground/view/streamControlBar/playPauseButtonView.spec.js: -------------------------------------------------------------------------------- 1 | import PlayPauseButtonView from 'foreground/view/streamControlBar/playPauseButtonView'; 2 | import PlayPauseButton from 'background/model/playPauseButton'; 3 | import StreamItems from 'background/collection/streamItems'; 4 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 5 | 6 | describe('PlayPauseButtonView', function() { 7 | beforeEach(function() { 8 | this.documentFragment = document.createDocumentFragment(); 9 | 10 | var player = TestUtility.buildPlayer(); 11 | 12 | this.view = new PlayPauseButtonView({ 13 | model: new PlayPauseButton({ 14 | player: player, 15 | streamItems: new StreamItems() 16 | }), 17 | player: player 18 | }); 19 | }); 20 | 21 | afterEach(function() { 22 | this.view.destroy(); 23 | }); 24 | 25 | ViewTestUtility.ensureBasicAssumptions.call(this); 26 | }); -------------------------------------------------------------------------------- /src/js/foreground/view/dialog/exportPlaylistDialogView.js: -------------------------------------------------------------------------------- 1 | import ExportPlaylist from 'foreground/model/dialog/exportPlaylist'; 2 | import Dialog from 'foreground/model/dialog/dialog'; 3 | import ExportPlaylistView from 'foreground/view/dialog/exportPlaylistView'; 4 | import DialogView from 'foreground/view/dialog/dialogView'; 5 | 6 | var ExportPlaylistDialogView = DialogView.extend({ 7 | id: 'exportPlaylistDialog', 8 | 9 | initialize: function(options) { 10 | this.model = new Dialog({ 11 | submitButtonText: chrome.i18n.getMessage('export') 12 | }); 13 | 14 | this.contentView = new ExportPlaylistView({ 15 | model: new ExportPlaylist({ 16 | playlist: options.playlist 17 | }) 18 | }); 19 | 20 | DialogView.prototype.initialize.apply(this, arguments); 21 | }, 22 | 23 | onSubmit: function() { 24 | this.contentView.saveAndExport(); 25 | } 26 | }); 27 | 28 | export default ExportPlaylistDialogView; -------------------------------------------------------------------------------- /src/js/test/foreground/view/leftPane/playlistItemView.spec.js: -------------------------------------------------------------------------------- 1 | import PlaylistItemView from 'foreground/view/leftPane/playlistItemView'; 2 | import PlaylistItem from 'background/model/playlistItem'; 3 | import StreamItems from 'background/collection/streamItems'; 4 | import ListItemType from 'common/enum/listItemType'; 5 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 6 | 7 | describe('PlaylistItemView', function() { 8 | beforeEach(function() { 9 | this.documentFragment = document.createDocumentFragment(); 10 | this.view = new PlaylistItemView({ 11 | model: new PlaylistItem(), 12 | streamItems: new StreamItems(), 13 | player: TestUtility.buildPlayer(), 14 | type: ListItemType.PlaylistItem, 15 | parentId: 'playlistItems-list' 16 | }); 17 | }); 18 | 19 | afterEach(function() { 20 | this.view.destroy(); 21 | }); 22 | 23 | ViewTestUtility.ensureBasicAssumptions.call(this); 24 | }); -------------------------------------------------------------------------------- /src/js/test/foreground/view/listItemButton/playPauseVideoButtonView.spec.js: -------------------------------------------------------------------------------- 1 | import PlayPauseVideoButtonView from 'foreground/view/listItemButton/playPauseVideoButtonView'; 2 | import StreamItems from 'background/collection/streamItems'; 3 | import ListItemButton from 'foreground/model/listItemButton/listItemButton'; 4 | import Video from 'background/model/video'; 5 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 6 | 7 | describe('PlayPauseVideoButtonView', function() { 8 | beforeEach(function() { 9 | this.documentFragment = document.createDocumentFragment(); 10 | this.view = new PlayPauseVideoButtonView({ 11 | model: new ListItemButton(), 12 | video: new Video(), 13 | streamItems: new StreamItems(), 14 | player: TestUtility.buildPlayer() 15 | }); 16 | }); 17 | 18 | afterEach(function() { 19 | this.view.destroy(); 20 | }); 21 | 22 | ViewTestUtility.ensureBasicAssumptions.call(this); 23 | }); -------------------------------------------------------------------------------- /src/js/foreground/view/activePane/activePanesRegion.js: -------------------------------------------------------------------------------- 1 | import {Region} from 'marionette'; 2 | import ActivePanesView from 'foreground/view/activePane/activePanesView'; 3 | import ActivePanes from 'foreground/collection/activePane/activePanes'; 4 | 5 | var ActivePaneRegion = Region.extend({ 6 | initialize: function() { 7 | this.listenTo(StreamusFG.channels.foregroundArea.vent, 'rendered', this._onForegroundAreaRendered); 8 | }, 9 | 10 | _onForegroundAreaRendered: function() { 11 | this._showActivePanesView(); 12 | }, 13 | 14 | _showActivePanesView: function() { 15 | this.show(new ActivePanesView({ 16 | collection: new ActivePanes(null, { 17 | stream: StreamusFG.backgroundProperties.stream, 18 | settings: StreamusFG.backgroundProperties.settings, 19 | activePlaylistManager: StreamusFG.backgroundProperties.activePlaylistManager 20 | }) 21 | })); 22 | } 23 | }); 24 | 25 | export default ActivePaneRegion; -------------------------------------------------------------------------------- /src/js/common/shim/lodash.reference.shim.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | // Multiple instances of lodash should not be instantiated because lodash uses an internal counter, idCounter, 3 | // to generate unique IDs. This may result in collisions because the foreground and background pages share 4 | // references with each other. i.e. 'foo_100' could appear on both pages. 5 | // Force the foreground to leverage the background's lodash instance. 6 | var getBackgroundPageExists = window.chrome && chrome.extension && chrome.extension.getBackgroundPage; 7 | var backgroundPage = getBackgroundPageExists ? chrome.extension.getBackgroundPage() : null; 8 | var isForeground = backgroundPage && backgroundPage.window !== window; 9 | var _ = isForeground ? backgroundPage.window._ : require('lodash'); 10 | // TODO: Global can be removed once BB upgraded to 1.2.3 (which seems buggy) or SystemJS supports 'format cjs' + deps injection. 11 | window._ = _; 12 | module.exports = _; 13 | }(this)); -------------------------------------------------------------------------------- /src/less/sortable.less: -------------------------------------------------------------------------------- 1 | @import "color"; 2 | 3 | // This is the jQuery UI draggable object that shows # of items being dragged 4 | .sortable-selectedItemsCount { 5 | position: absolute; 6 | // !important is being used to override jQuery UI's width/height settings 7 | width: auto !important; 8 | height: auto !important; 9 | padding: 2px 8px; 10 | font-weight: 600; 11 | color: @white; 12 | text-align: center; 13 | background-color: @gray--tooltip; 14 | border-radius: 18px; 15 | } 16 | 17 | .sortable-placeholder { 18 | font-size: 15px; 19 | color: white; 20 | background: @dark--quinary; 21 | // Don't allow duplicates to be dropped into playlist items 22 | &.is-notDroppable { 23 | background-color: @red--500; 24 | } 25 | 26 | &.is-warnDroppable { 27 | background-color: @yellow--500; 28 | } 29 | } 30 | 31 | .sortable--unsortable .sortable-placeholder { 32 | display: none; 33 | } 34 | 35 | .is-dragging .is-selected { 36 | opacity: .5; 37 | } -------------------------------------------------------------------------------- /src/js/test/foreground/view/stream/streamView.spec.js: -------------------------------------------------------------------------------- 1 | import StreamView from 'foreground/view/stream/streamView'; 2 | import Stream from 'background/model/stream'; 3 | import ShuffleButton from 'background/model/shuffleButton'; 4 | import RadioButton from 'background/model/radioButton'; 5 | import RepeatButton from 'background/model/repeatButton'; 6 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 7 | 8 | describe('StreamView', function() { 9 | beforeEach(function() { 10 | this.documentFragment = document.createDocumentFragment(); 11 | this.view = new StreamView({ 12 | model: new Stream({ 13 | player: TestUtility.buildPlayer(), 14 | shuffleButton: new ShuffleButton(), 15 | radioButton: new RadioButton(), 16 | repeatButton: new RepeatButton() 17 | }) 18 | }); 19 | }); 20 | 21 | afterEach(function() { 22 | this.view.destroy(); 23 | }); 24 | 25 | ViewTestUtility.ensureBasicAssumptions.call(this); 26 | }); -------------------------------------------------------------------------------- /src/js/test/foreground/view/dialog/editPlaylistDialogView.spec.js: -------------------------------------------------------------------------------- 1 | import EditPlaylistDialogView from 'foreground/view/dialog/editPlaylistDialogView'; 2 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 3 | 4 | describe('EditPlaylistDialogView', function() { 5 | beforeEach(function() { 6 | this.documentFragment = document.createDocumentFragment(); 7 | this.view = new EditPlaylistDialogView({ 8 | playlist: TestUtility.buildPlaylist() 9 | }); 10 | }); 11 | 12 | afterEach(function() { 13 | this.view.destroy(); 14 | }); 15 | 16 | ViewTestUtility.ensureBasicAssumptions.call(this); 17 | 18 | describe('onSubmit', function() { 19 | it('should edit its playlist', function() { 20 | sinon.stub(this.view.contentView, 'editPlaylist'); 21 | 22 | this.view.onSubmit(); 23 | expect(this.view.contentView.editPlaylist.calledOnce).to.equal(true); 24 | 25 | this.view.contentView.editPlaylist.restore(); 26 | }); 27 | }); 28 | }); -------------------------------------------------------------------------------- /src/js/test/foreground/view/search/searchResultView.spec.js: -------------------------------------------------------------------------------- 1 | import SearchResultView from 'foreground/view/search/searchResultView'; 2 | import SearchResult from 'background/model/searchResult'; 3 | import StreamItems from 'background/collection/streamItems'; 4 | import ListItemType from 'common/enum/listItemType'; 5 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 6 | 7 | describe('SearchResultView', function() { 8 | beforeEach(function() { 9 | this.documentFragment = document.createDocumentFragment(); 10 | this.view = new SearchResultView({ 11 | model: new SearchResult({ 12 | video: TestUtility.buildVideo() 13 | }), 14 | streamItems: new StreamItems(), 15 | player: TestUtility.buildPlayer(), 16 | type: ListItemType.SearchResult, 17 | parentId: 'searchResults-list' 18 | }); 19 | }); 20 | 21 | afterEach(function() { 22 | this.view.destroy(); 23 | }); 24 | 25 | ViewTestUtility.ensureBasicAssumptions.call(this); 26 | }); -------------------------------------------------------------------------------- /src/js/test/foreground/view/dialog/createPlaylistView.spec.js: -------------------------------------------------------------------------------- 1 | import CreatePlaylistView from 'foreground/view/dialog/createPlaylistView'; 2 | import CreatePlaylist from 'foreground/model/dialog/createPlaylist'; 3 | import Playlists from 'background/collection/playlists'; 4 | import DataSourceManager from 'background/model/dataSourceManager'; 5 | import TestUtility from 'test/testUtility'; 6 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 7 | 8 | describe('CreatePlaylistView', function() { 9 | beforeEach(function() { 10 | this.documentFragment = document.createDocumentFragment(); 11 | this.view = new CreatePlaylistView({ 12 | model: new CreatePlaylist(), 13 | playlists: new Playlists([], { 14 | userId: TestUtility.getGuid() 15 | }), 16 | dataSourceManager: new DataSourceManager() 17 | }); 18 | }); 19 | 20 | afterEach(function() { 21 | this.view.destroy(); 22 | }); 23 | 24 | ViewTestUtility.ensureBasicAssumptions.call(this); 25 | }); -------------------------------------------------------------------------------- /src/template/dialog/aboutStreamus.hbs: -------------------------------------------------------------------------------- 1 |
          2 |
          3 | {{i18n 'applicationDetails'}} 4 |
          5 | 6 |
          7 |
            8 |
          • 9 | {{i18n 'openHomepage'}} 10 |
          • 11 |
          • 12 | {{i18n 'openPatchNotes'}} 13 |
          • 14 |
          • 15 |
            16 |
            17 |
            18 | {{i18n 'version'}} 19 |
            20 | 21 |
            22 | {{applicationDetails.version}} 23 |
            24 |
            25 |
            26 |
          • 27 |
          28 |
          29 |
          -------------------------------------------------------------------------------- /src/js/test/foreground/view/dialog/deletePlaylistDialogView.spec.js: -------------------------------------------------------------------------------- 1 | import DeletePlaylistDialogView from 'foreground/view/dialog/deletePlaylistDialogView'; 2 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 3 | 4 | describe('DeletePlaylistDialogView', function() { 5 | beforeEach(function() { 6 | this.documentFragment = document.createDocumentFragment(); 7 | this.view = new DeletePlaylistDialogView({ 8 | playlist: TestUtility.buildPlaylist() 9 | }); 10 | }); 11 | 12 | afterEach(function() { 13 | this.view.destroy(); 14 | }); 15 | 16 | ViewTestUtility.ensureBasicAssumptions.call(this); 17 | 18 | describe('onSubmit', function() { 19 | it('should delete its playlist', function() { 20 | sinon.stub(this.view.contentView, 'deletePlaylist'); 21 | 22 | this.view.onSubmit(); 23 | expect(this.view.contentView.deletePlaylist.calledOnce).to.equal(true); 24 | 25 | this.view.contentView.deletePlaylist.restore(); 26 | }); 27 | }); 28 | }); -------------------------------------------------------------------------------- /src/js/foreground/view/element/radioGroupView.js: -------------------------------------------------------------------------------- 1 | import {CompositeView} from 'marionette'; 2 | import RadioButtonView from 'foreground/view/element/radioButtonView'; 3 | import KeyCode from 'foreground/enum/keyCode'; 4 | import radioGroupTemplate from 'template/element/radioGroup.hbs!'; 5 | 6 | var RadioGroupView = CompositeView.extend({ 7 | tagName: 'radio-group', 8 | template: radioGroupTemplate, 9 | childViewContainer: '@ui.buttons', 10 | childView: RadioButtonView, 11 | 12 | attributes: { 13 | tabIndex: 0 14 | }, 15 | 16 | ui: { 17 | buttons: 'buttons' 18 | }, 19 | 20 | events: { 21 | 'keydown': '_onKeyDown' 22 | }, 23 | 24 | _onKeyDown: function(event) { 25 | if (event.keyCode === KeyCode.ArrowLeft || event.keyCode === KeyCode.ArrowUp) { 26 | this.collection.checkPrevious(); 27 | } else if (event.keyCode === KeyCode.ArrowRight || event.keyCode === KeyCode.ArrowDown) { 28 | this.collection.checkNext(); 29 | } 30 | } 31 | }); 32 | 33 | export default RadioGroupView; -------------------------------------------------------------------------------- /src/template/leftPane/activePlaylistArea.hbs: -------------------------------------------------------------------------------- 1 |
          2 |
          3 |
          4 | {{i18n 'playlistEmpty'}} 5 |
          6 |
          7 | {{i18n 'wouldYouLikeTo'}} {{i18n 'searchForVideos'}}? 8 |
          9 |
          10 | 11 |
          12 |
          13 | 14 |
          15 |
          16 |
          17 |
          18 | 19 |
          20 | {{i18n 'playAll'}} 21 |
          22 | 23 |
          24 | {{i18n 'addAll'}} 25 |
          26 |
          -------------------------------------------------------------------------------- /src/js/foreground/collection/simpleMenu/simpleMenuItems.js: -------------------------------------------------------------------------------- 1 | import {Collection} from 'backbone'; 2 | import SimpleMenuItem from 'foreground/model/simpleMenu/simpleMenuItem'; 3 | 4 | var SimpleMenuItems = Collection.extend({ 5 | model: SimpleMenuItem, 6 | 7 | initialize: function() { 8 | this.on('change:active', this._onChangeActive); 9 | }, 10 | 11 | getActive: function() { 12 | return this.findWhere({ 13 | active: true 14 | }); 15 | }, 16 | 17 | // Enforce that only one model can be active at a time by deactivating all other models when one becomes active. 18 | _onChangeActive: function(model, active) { 19 | if (active) { 20 | this._deactivateAllExcept(model); 21 | } 22 | }, 23 | 24 | // Ensure only one menu item can be active at a time. 25 | _deactivateAllExcept: function(changedModel) { 26 | this.each(function(model) { 27 | if (model !== changedModel) { 28 | model.set('active', false); 29 | } 30 | }); 31 | } 32 | }); 33 | 34 | export default SimpleMenuItems; -------------------------------------------------------------------------------- /src/less/instruction.less: -------------------------------------------------------------------------------- 1 | @import "color"; 2 | @import "utility"; 3 | @import "flexColumn"; 4 | 5 | .instruction-wrapper { 6 | .flexColumn; 7 | // Contain the position: absolute instructions 8 | position: relative; 9 | cursor: default; 10 | } 11 | 12 | .instruction { 13 | // position: absolute so that .list near instructions isn't bumped down to support drag-and-drop behavior 14 | position: absolute; 15 | top: 33%; 16 | // Position over .list (which is relative due to scrollable behavior) to enable clicking hyperlinks in instruction. 17 | z-index: 1; 18 | // Width: 100% so that text-align: center works properly with position: absolute 19 | width: 100%; 20 | // Keep all instruction heights the same so that they stay aligned to each-other. 21 | height: 60px; 22 | padding: 0 16px; 23 | color: @dark--primary; 24 | text-align: center; 25 | transform: translateY(-50%); 26 | } 27 | 28 | .instruction-header { 29 | font-size: 24px; 30 | line-height: 46px; 31 | } 32 | 33 | .instruction-content { 34 | .u-textSecondary; 35 | } -------------------------------------------------------------------------------- /src/js/test/foreground/view/dialog/clearStreamDialogView.spec.js: -------------------------------------------------------------------------------- 1 | import ClearStreamDialogView from 'foreground/view/dialog/clearStreamDialogView'; 2 | import StreamItems from 'background/collection/streamItems'; 3 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 4 | 5 | describe('ClearStreamDialogView', function() { 6 | beforeEach(function() { 7 | this.streamItems = new StreamItems(); 8 | 9 | this.documentFragment = document.createDocumentFragment(); 10 | this.view = new ClearStreamDialogView({ 11 | streamItems: this.streamItems 12 | }); 13 | }); 14 | 15 | afterEach(function() { 16 | this.view.destroy(); 17 | }); 18 | 19 | ViewTestUtility.ensureBasicAssumptions.call(this); 20 | 21 | describe('onSubmit', function() { 22 | it('should clear StreamItems', function() { 23 | sinon.stub(this.streamItems, 'clear'); 24 | 25 | this.view.onSubmit(); 26 | expect(this.streamItems.clear.calledOnce).to.equal(true); 27 | 28 | this.streamItems.clear.restore(); 29 | }); 30 | }); 31 | }); -------------------------------------------------------------------------------- /src/js/test/foreground/view/dialog/createPlaylistDialogView.spec.js: -------------------------------------------------------------------------------- 1 | import CreatePlaylistDialogView from 'foreground/view/dialog/createPlaylistDialogView'; 2 | import Playlists from 'background/collection/playlists'; 3 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 4 | 5 | describe('CreatePlaylistDialogView', function() { 6 | beforeEach(function() { 7 | this.documentFragment = document.createDocumentFragment(); 8 | this.view = new CreatePlaylistDialogView({ 9 | playlists: new Playlists() 10 | }); 11 | }); 12 | 13 | afterEach(function() { 14 | this.view.destroy(); 15 | }); 16 | 17 | ViewTestUtility.ensureBasicAssumptions.call(this); 18 | 19 | describe('onSubmit', function() { 20 | it('should create a playlist', function() { 21 | sinon.stub(this.view.contentView, 'createPlaylist'); 22 | 23 | this.view.onSubmit(); 24 | expect(this.view.contentView.createPlaylist.calledOnce).to.equal(true); 25 | 26 | this.view.contentView.createPlaylist.restore(); 27 | }); 28 | }); 29 | }); -------------------------------------------------------------------------------- /src/js/foreground/model/streamControlBar/timeSlider.js: -------------------------------------------------------------------------------- 1 | import _ from 'common/shim/lodash.reference.shim'; 2 | import {Model} from 'backbone'; 3 | 4 | var TimeSlider = Model.extend({ 5 | defaults: { 6 | currentTime: 0, 7 | isBeingDragged: false, 8 | isEnabled: false, 9 | player: null 10 | }, 11 | 12 | initialize: function() { 13 | var player = this.get('player'); 14 | this._setEnabledState(player.get('loadedVideo')); 15 | this.listenTo(player, 'change:loadedVideo', this._onPlayerChangeLoadedVideo); 16 | }, 17 | 18 | incrementCurrentTime: function(incrementValue) { 19 | var incrementedCurrentTime = this.get('currentTime') + incrementValue; 20 | this.set('currentTime', incrementedCurrentTime); 21 | 22 | return incrementedCurrentTime; 23 | }, 24 | 25 | _onPlayerChangeLoadedVideo: function(model, loadedVideo) { 26 | this._setEnabledState(loadedVideo); 27 | }, 28 | 29 | _setEnabledState: function(loadedVideo) { 30 | this.set('isEnabled', !_.isNull(loadedVideo)); 31 | } 32 | }); 33 | 34 | export default TimeSlider; -------------------------------------------------------------------------------- /src/js/foreground/view/streamControlBar/nextButtonView.js: -------------------------------------------------------------------------------- 1 | import {LayoutView} from 'marionette'; 2 | import nextButtonTemplate from 'template/streamControlBar/nextButton.hbs!'; 3 | import nextIconTemplate from 'template/icon/nextIcon_24.hbs!'; 4 | 5 | var NextButtonView = LayoutView.extend({ 6 | id: 'nextButton', 7 | className: 'button button--icon button--icon--primary button--large', 8 | template: nextButtonTemplate, 9 | templateHelpers: { 10 | nextIcon: nextIconTemplate 11 | }, 12 | 13 | events: { 14 | 'click': '_onClick' 15 | }, 16 | 17 | modelEvents: { 18 | 'change:enabled': '_onChangeEnabled' 19 | }, 20 | 21 | onRender: function() { 22 | this._setState(this.model.get('enabled')); 23 | }, 24 | 25 | _onClick: function() { 26 | this.model.tryActivateNextStreamItem(); 27 | }, 28 | 29 | _onChangeEnabled: function(model, enabled) { 30 | this._setState(enabled); 31 | }, 32 | 33 | _setState: function(enabled) { 34 | this.$el.toggleClass('is-disabled', !enabled); 35 | } 36 | }); 37 | 38 | export default NextButtonView; -------------------------------------------------------------------------------- /src/less/radio.less: -------------------------------------------------------------------------------- 1 | @import "color"; 2 | @import "transition"; 3 | 4 | radio-group { 5 | outline: none; 6 | } 7 | 8 | radio-button { 9 | display: flex; 10 | height: 48px; 11 | cursor: pointer; 12 | align-items: center; 13 | 14 | &.is-checked { 15 | .radio-on { 16 | transform: scale(0.5); 17 | } 18 | 19 | .radio-off { 20 | border-color: @green--500; 21 | } 22 | } 23 | } 24 | 25 | .radio-container { 26 | position: relative; 27 | display: inline-block; 28 | width: 16px; 29 | height: 16px; 30 | vertical-align: middle; 31 | } 32 | 33 | .radio-state { 34 | position: absolute; 35 | width: 100%; 36 | height: 100%; 37 | border-radius: 50%; 38 | } 39 | 40 | .radio-off { 41 | border: solid 2px; 42 | border-color: @dark--secondary; 43 | transition: border-color ease @transition-duration--slow; 44 | } 45 | 46 | .radio-on { 47 | background-color: @green--500; 48 | transform: scale(0); 49 | transition: transform ease @transition-duration--slow; 50 | } 51 | 52 | .radio-label { 53 | margin-left: 10px; 54 | cursor: pointer; 55 | } -------------------------------------------------------------------------------- /src/js/common/shim/backbone.marionette.region.shim.js: -------------------------------------------------------------------------------- 1 | import _ from 'common/shim/lodash.reference.shim'; 2 | import {Region, Error}from 'marionette'; 3 | 4 | Region.buildRegion = function(regionConfig, DefaultRegionClass) { 5 | if (_.isString(regionConfig)) { 6 | regionConfig = '[data-region=' + regionConfig + ']'; 7 | return this._buildRegionFromSelector(regionConfig, DefaultRegionClass); 8 | } 9 | 10 | if (regionConfig.selector || regionConfig.el || regionConfig.regionClass) { 11 | if (regionConfig.selector) { 12 | regionConfig.selector = '[data-region=' + regionConfig.selector + ']'; 13 | } 14 | if (regionConfig.el) { 15 | regionConfig.el = '[data-region=' + regionConfig.el + ']'; 16 | } 17 | return this._buildRegionFromObject(regionConfig, DefaultRegionClass); 18 | } 19 | 20 | if (_.isFunction(regionConfig)) { 21 | return this._buildRegionFromRegionClass(regionConfig); 22 | } 23 | 24 | throw new Error({ 25 | message: 'Improper region configuration type.', 26 | url: 'marionette.region.html#region-configuration-types' 27 | }); 28 | }; -------------------------------------------------------------------------------- /src/js/test/foreground/view/activePane/activePanesView.spec.js: -------------------------------------------------------------------------------- 1 | import ActivePanesView from 'foreground/view/activePane/activePanesView'; 2 | import ActivePanes from 'foreground/collection/activePane/activePanes'; 3 | import SignInManager from 'background/model/signInManager'; 4 | import Settings from 'background/model/settings'; 5 | import ActivePlaylistManager from 'background/model/activePlaylistManager'; 6 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 7 | 8 | describe('ActivePanesView', function() { 9 | beforeEach(function() { 10 | this.documentFragment = document.createDocumentFragment(); 11 | this.view = new ActivePanesView({ 12 | collection: new ActivePanes(null, { 13 | stream: TestUtility.buildStream(), 14 | settings: new Settings(), 15 | activePlaylistManager: new ActivePlaylistManager({ 16 | signInManager: new SignInManager() 17 | }) 18 | }) 19 | }); 20 | }); 21 | 22 | afterEach(function() { 23 | this.view.destroy(); 24 | }); 25 | 26 | ViewTestUtility.ensureBasicAssumptions.call(this); 27 | }); -------------------------------------------------------------------------------- /src/js/foreground/model/element/radioGroup.js: -------------------------------------------------------------------------------- 1 | import {Collection, Model} from 'backbone'; 2 | import RadioButtons from 'foreground/collection/element/radioButtons'; 3 | 4 | var RadioGroup = Model.extend({ 5 | defaults: { 6 | // Often times a radio group has a 1:1 relationship with a model property. 7 | // Linking that property to its radio group allows working with a collection of radio groups more easily. 8 | property: '', 9 | buttons: null 10 | }, 11 | 12 | initialize: function() { 13 | this._ensureButtonsCollection(); 14 | }, 15 | 16 | _ensureButtonsCollection: function() { 17 | var buttons = this.get('buttons'); 18 | 19 | // Need to convert buttons array to Backbone.Collection 20 | if (!(buttons instanceof Collection)) { 21 | // Silent because buttons is just being properly set. 22 | this.set('buttons', new RadioButtons(buttons, { 23 | silent: true 24 | })); 25 | } 26 | }, 27 | 28 | getCheckedValue: function() { 29 | return this.get('buttons').getChecked().get('value'); 30 | } 31 | }); 32 | 33 | export default RadioGroup; -------------------------------------------------------------------------------- /src/js/foreground/view/streamControlBar/previousButtonView.js: -------------------------------------------------------------------------------- 1 | import {LayoutView} from 'marionette'; 2 | import previousButtonTemplate from 'template/streamControlBar/previousButton.hbs!'; 3 | import previousIconTemplate from 'template/icon/previousIcon_24.hbs!'; 4 | 5 | var PreviousButton = LayoutView.extend({ 6 | id: 'previousButton', 7 | className: 'button button--icon button--icon--primary button--large', 8 | template: previousButtonTemplate, 9 | templateHelpers: { 10 | previousIcon: previousIconTemplate 11 | }, 12 | 13 | events: { 14 | 'click': '_onClick' 15 | }, 16 | 17 | modelEvents: { 18 | 'change:enabled': '_onChangeEnabled' 19 | }, 20 | 21 | onRender: function() { 22 | this._setState(this.model.get('enabled')); 23 | }, 24 | 25 | _onClick: function() { 26 | this.model.tryDoTimeBasedPrevious(); 27 | }, 28 | 29 | _onChangeEnabled: function(model, enabled) { 30 | this._setState(enabled); 31 | }, 32 | 33 | _setState: function(enabled) { 34 | this.$el.toggleClass('is-disabled', !enabled); 35 | } 36 | }); 37 | 38 | export default PreviousButton; -------------------------------------------------------------------------------- /src/js/foreground/view/element/switchView.js: -------------------------------------------------------------------------------- 1 | import {LayoutView} from 'marionette'; 2 | import switchTemplate from 'template/element/switch.hbs!'; 3 | 4 | var SwitchView = LayoutView.extend({ 5 | tagName: 'switch', 6 | template: switchTemplate, 7 | 8 | ui: { 9 | icon: 'icon' 10 | }, 11 | 12 | events: { 13 | 'click': '_onClick' 14 | }, 15 | 16 | modelEvents: { 17 | 'change:checked': '_onChangeChecked' 18 | }, 19 | 20 | onRender: function() { 21 | var checked = this.model.get('checked'); 22 | this._setCheckedState(checked); 23 | }, 24 | 25 | _onClick: function() { 26 | this.model.set('checked', !this.model.get('checked')); 27 | }, 28 | 29 | _onChangeChecked: function(model, checked) { 30 | this._setCheckedState(checked); 31 | }, 32 | 33 | _setCheckedState: function(checked) { 34 | this.$el.toggleClass('is-checked', checked); 35 | this.$el.toggleClass('is-unchecked', !checked); 36 | this.ui.icon.toggleClass('is-checked', checked); 37 | this.ui.icon.toggleClass('is-unchecked', !checked); 38 | } 39 | }); 40 | 41 | export default SwitchView; -------------------------------------------------------------------------------- /src/js/test/background/model/clientErrorManager.spec.js: -------------------------------------------------------------------------------- 1 | import ClientErrorManager from 'background/model/clientErrorManager'; 2 | import SignInManager from 'background/model/signInManager'; 3 | 4 | describe('ClientErrorManager', function() { 5 | beforeEach(function() { 6 | this.clientErrorManager = new ClientErrorManager({ 7 | signInManager: new SignInManager() 8 | }); 9 | this.reportedErrors = this.clientErrorManager.get('reportedErrors'); 10 | sinon.stub(this.reportedErrors.model.prototype, 'sync'); 11 | }); 12 | 13 | afterEach(function() { 14 | this.reportedErrors.model.prototype.sync.restore(); 15 | }); 16 | 17 | it('should log an error message properly', function() { 18 | this.clientErrorManager._logError(new Error('test message')); 19 | 20 | expect(this.reportedErrors.length).to.equal(1); 21 | }); 22 | 23 | it('should not log the same error message more than once', function() { 24 | for (var i = 0; i < 5; i++) { 25 | this.clientErrorManager._logError(new Error('test message')); 26 | } 27 | 28 | expect(this.reportedErrors.length).to.equal(1); 29 | }); 30 | }); -------------------------------------------------------------------------------- /src/js/test/foreground/view/stream/streamItemView.spec.js: -------------------------------------------------------------------------------- 1 | import StreamItemView from 'foreground/view/stream/streamItemView'; 2 | import StreamItem from 'background/model/streamItem'; 3 | import StreamItems from 'background/collection/streamItems'; 4 | import PlayPauseButton from 'background/model/playPauseButton'; 5 | import ListItemType from 'common/enum/listItemType'; 6 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 7 | 8 | describe('StreamItemView', function() { 9 | beforeEach(function() { 10 | this.documentFragment = document.createDocumentFragment(); 11 | 12 | var player = TestUtility.buildPlayer(); 13 | 14 | this.view = new StreamItemView({ 15 | model: new StreamItem(), 16 | player: player, 17 | playPauseButton: new PlayPauseButton({ 18 | player: player, 19 | streamItems: new StreamItems() 20 | }), 21 | type: ListItemType.StreamItem, 22 | parentId: 'streamItems-list' 23 | }); 24 | }); 25 | 26 | afterEach(function() { 27 | this.view.destroy(); 28 | }); 29 | 30 | ViewTestUtility.ensureBasicAssumptions.call(this); 31 | }); -------------------------------------------------------------------------------- /src/js/test/foreground/view/dialog/exportPlaylistDialogView.spec.js: -------------------------------------------------------------------------------- 1 | import ExportPlaylist from 'foreground/model/dialog/exportPlaylist'; 2 | import ExportPlaylistDialogView from 'foreground/view/dialog/exportPlaylistDialogView'; 3 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 4 | 5 | describe('ExportPlaylistDialogView', function() { 6 | beforeEach(function() { 7 | this.documentFragment = document.createDocumentFragment(); 8 | this.view = new ExportPlaylistDialogView({ 9 | model: new ExportPlaylist({ 10 | playlist: TestUtility.buildPlaylist() 11 | }) 12 | }); 13 | }); 14 | 15 | afterEach(function() { 16 | this.view.destroy(); 17 | }); 18 | 19 | ViewTestUtility.ensureBasicAssumptions.call(this); 20 | 21 | describe('onSubmit', function() { 22 | it('should export its playlist', function() { 23 | sinon.stub(this.view.contentView, 'saveAndExport'); 24 | 25 | this.view.onSubmit(); 26 | expect(this.view.contentView.saveAndExport.calledOnce).to.equal(true); 27 | 28 | this.view.contentView.saveAndExport.restore(); 29 | }); 30 | }); 31 | }); -------------------------------------------------------------------------------- /src/js/foreground/view/dialog/createPlaylistDialogView.js: -------------------------------------------------------------------------------- 1 | import _ from 'common/shim/lodash.reference.shim'; 2 | import Dialog from 'foreground/model/dialog/dialog'; 3 | import CreatePlaylistView from 'foreground/view/dialog/createPlaylistView'; 4 | import CreatePlaylist from 'foreground/model/dialog/createPlaylist'; 5 | import DialogView from 'foreground/view/dialog/dialogView'; 6 | 7 | var CreatePlaylistDialogView = DialogView.extend({ 8 | id: 'createPlaylistDialog', 9 | 10 | initialize: function(options) { 11 | this.model = new Dialog({ 12 | submitButtonText: chrome.i18n.getMessage('create') 13 | }); 14 | 15 | this.contentView = new CreatePlaylistView({ 16 | model: new CreatePlaylist(), 17 | dataSourceManager: StreamusFG.backgroundProperties.dataSourceManager, 18 | playlists: options.playlists, 19 | videos: _.isUndefined(options.videos) ? [] : options.videos 20 | }); 21 | 22 | DialogView.prototype.initialize.apply(this, arguments); 23 | }, 24 | 25 | onSubmit: function() { 26 | this.contentView.createPlaylist(); 27 | } 28 | }); 29 | 30 | export default CreatePlaylistDialogView; -------------------------------------------------------------------------------- /src/js/test/foreground/view/dialog/linkUserIdDialogView.spec.js: -------------------------------------------------------------------------------- 1 | import LinkUserIdDialogView from 'foreground/view/dialog/linkUserIdDialogView'; 2 | import SignInManager from 'background/model/signInManager'; 3 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 4 | 5 | describe('LinkUserIdDialogView', function() { 6 | beforeEach(function() { 7 | this.signInManager = new SignInManager(); 8 | 9 | this.documentFragment = document.createDocumentFragment(); 10 | this.view = new LinkUserIdDialogView({ 11 | signInManager: this.signInManager 12 | }); 13 | }); 14 | 15 | afterEach(function() { 16 | this.view.destroy(); 17 | }); 18 | 19 | ViewTestUtility.ensureBasicAssumptions.call(this); 20 | 21 | describe('onSubmit', function() { 22 | it('should tell SignInManager to save the current user\'s GooglePlusId', function() { 23 | sinon.stub(this.signInManager, 'saveGooglePlusId'); 24 | 25 | this.view.onSubmit(); 26 | expect(this.signInManager.saveGooglePlusId.calledOnce).to.equal(true); 27 | 28 | this.signInManager.saveGooglePlusId.restore(); 29 | }); 30 | }); 31 | }); -------------------------------------------------------------------------------- /src/js/foreground/view/activePane/activePanesView.js: -------------------------------------------------------------------------------- 1 | import {CollectionView} from 'marionette'; 2 | import ActivePaneType from 'foreground/enum/activePaneType'; 3 | import ActivePaneView from 'foreground/view/activePane/activePaneView'; 4 | import ViewEntityContainer from 'foreground/view/behavior/viewEntityContainer'; 5 | import activePanesTemplate from 'template/activePane/activePanes.hbs!'; 6 | 7 | // TODO: Show a sign-in view. 8 | var ActivePanesView = CollectionView.extend({ 9 | className: 'activePanes flexRow', 10 | childView: ActivePaneView, 11 | template: activePanesTemplate, 12 | 13 | childViewOptions: function() { 14 | return { 15 | streamItems: StreamusFG.backgroundProperties.stream.get('items') 16 | }; 17 | }, 18 | 19 | behaviors: { 20 | ViewEntityContainer: { 21 | behaviorClass: ViewEntityContainer, 22 | viewEntityNames: ['collection'] 23 | } 24 | }, 25 | 26 | // Sort the panes such that the stream appears on the right side. 27 | viewComparator: function(activePane) { 28 | return activePane.get('type') === ActivePaneType.Stream ? 1 : 0; 29 | } 30 | }); 31 | 32 | export default ActivePanesView; -------------------------------------------------------------------------------- /src/js/test/foreground/view/activePane/activePanesRegion.spec.js: -------------------------------------------------------------------------------- 1 | import ActivePanesRegion from 'foreground/view/activePane/activePanesRegion'; 2 | 3 | describe('ActivePanesRegion', function() { 4 | beforeEach(function() { 5 | $('body').append('
          '); 6 | 7 | this.region = new ActivePanesRegion({ 8 | el: '[data-region=activePanes]' 9 | }); 10 | }); 11 | 12 | afterEach(function() { 13 | this.region.empty(); 14 | $('body').remove('[data-region=activePanes]'); 15 | }); 16 | 17 | describe('_onForegroundAreaRendered', function() { 18 | it('should call _showActivePanesView', function() { 19 | sinon.stub(this.region, '_showActivePanesView'); 20 | this.region._onForegroundAreaRendered(); 21 | expect(this.region._showActivePanesView.calledOnce).to.equal(true); 22 | this.region._showActivePanesView.restore(); 23 | }); 24 | }); 25 | 26 | describe('_showActivePanesView', function() { 27 | it('should successfully show a view', function() { 28 | this.region._showActivePanesView(); 29 | expect(this.region.hasView()).to.equal(true); 30 | }); 31 | }); 32 | }); -------------------------------------------------------------------------------- /src/js/test/foreground/model/streamControlBar/timeSlider.spec.js: -------------------------------------------------------------------------------- 1 | import TimeSlider from 'foreground/model/streamControlBar/timeSlider'; 2 | 3 | describe('TimeSlider', function() { 4 | beforeEach(function() { 5 | var player = TestUtility.buildPlayer(); 6 | 7 | this.timeSlider = new TimeSlider({ 8 | currentTime: player.get('currentTime'), 9 | player: player 10 | }); 11 | }); 12 | 13 | describe('_setEnabledState', function() { 14 | it('should set isEnabled to true if a loaded video exists', function() { 15 | this.timeSlider._setEnabledState({}); 16 | expect(this.timeSlider.get('isEnabled')).to.equal(true); 17 | }); 18 | 19 | it('should set isEnabled to false if a loaded video does not exist', function() { 20 | this.timeSlider._setEnabledState(null); 21 | expect(this.timeSlider.get('isEnabled')).to.equal(false); 22 | }); 23 | }); 24 | 25 | describe('incrementCurrentTime', function() { 26 | it('should be able to add a value to its current time', function() { 27 | var incrementedTime = this.timeSlider.incrementCurrentTime(1); 28 | expect(incrementedTime).to.equal(1); 29 | }); 30 | }); 31 | }); -------------------------------------------------------------------------------- /src/js/test/foreground/view/dialog/googleSignInDialogView.spec.js: -------------------------------------------------------------------------------- 1 | import GoogleSignInDialogView from 'foreground/view/dialog/googleSignInDialogView'; 2 | import SignInManager from 'background/model/signInManager'; 3 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 4 | 5 | describe('GoogleSignInDialogView', function() { 6 | beforeEach(function() { 7 | this.signInManager = new SignInManager(); 8 | this.documentFragment = document.createDocumentFragment(); 9 | this.view = new GoogleSignInDialogView({ 10 | signInManager: this.signInManager 11 | }); 12 | }); 13 | 14 | afterEach(function() { 15 | this.view.destroy(); 16 | }); 17 | 18 | ViewTestUtility.ensureBasicAssumptions.call(this); 19 | 20 | describe('onSubmit', function() { 21 | it('should tell SignInManager not to notify again', function() { 22 | sinon.stub(this.signInManager, 'set'); 23 | 24 | this.view.onSubmit(); 25 | expect(this.signInManager.set.calledOnce).to.equal(true); 26 | expect(this.signInManager.set.calledWith('needGoogleSignIn', false)).to.equal(true); 27 | 28 | this.signInManager.set.restore(); 29 | }); 30 | }); 31 | }); -------------------------------------------------------------------------------- /src/less/searchInputArea.less: -------------------------------------------------------------------------------- 1 | @import "transition"; 2 | 3 | .searchInputArea { 4 | border-radius: 2px; 5 | padding-left: 4px; 6 | justify-content: flex-end; 7 | overflow: hidden; 8 | } 9 | 10 | .searchInputArea-searchInputWrapper { 11 | transform: translateX(0); 12 | transition: transform @transition-easeOutSine @transition-duration--fast, background-color @transition-easeOutSine @transition-duration--fast; 13 | 14 | &.is-active { 15 | background-color: rgba(255,255,255,.15); 16 | 17 | .searchInputArea-searchInput { 18 | opacity: 1; 19 | } 20 | } 21 | 22 | &.is-inactive { 23 | transform: translateX(calc(100% - 48px)); 24 | } 25 | } 26 | 27 | .searchInputArea-clearSearchIcon, 28 | .searchInputArea-searchIcon { 29 | padding: 0 12px; 30 | } 31 | 32 | .searchInputArea-searchInput { 33 | font: inherit; 34 | font-size: 17px; 35 | height: 36px; 36 | color: inherit; 37 | background-color: transparent; 38 | outline: none; 39 | border: none; 40 | opacity: 0; 41 | transition: opacity @transition-easeOutSine @transition-duration--fast; 42 | flex: 1; 43 | 44 | &::-webkit-input-placeholder { 45 | color: inherit; 46 | } 47 | } -------------------------------------------------------------------------------- /src/js/test/contentScript/youTubePlayer/model/port.spec.js: -------------------------------------------------------------------------------- 1 | import Port from 'contentScript/youTubePlayer/model/port'; 2 | 3 | describe('Port', function() { 4 | beforeEach(function() { 5 | this.port = new Port(); 6 | }); 7 | 8 | describe('connect', function() { 9 | afterEach(function() { 10 | this.port.disconnect(); 11 | }); 12 | 13 | it('should connect successfully', function() { 14 | this.port.connect(); 15 | expect(this.port.get('isConnected')).to.equal(true); 16 | }); 17 | }); 18 | 19 | describe('disconnect', function() { 20 | it('should disconnect successfully', function() { 21 | this.port.connect(); 22 | this.port.disconnect(); 23 | expect(this.port.get('isConnected')).to.equal(false); 24 | }); 25 | }); 26 | 27 | describe('postMessage', function() { 28 | it('should successfully send a message if connected', function() { 29 | this.port.connect(); 30 | 31 | sinon.spy(this.port._port, 'postMessage'); 32 | this.port.postMessage('message'); 33 | expect(this.port._port.postMessage.calledOnce).to.equal(true); 34 | this.port._port.postMessage.restore(); 35 | }); 36 | }); 37 | }); -------------------------------------------------------------------------------- /src/js/foreground/view/listItemButton/playlistOptionsButtonView.js: -------------------------------------------------------------------------------- 1 | import {LayoutView} from 'marionette'; 2 | import ListItemButton from 'foreground/view/behavior/listItemButton'; 3 | import PlaylistActions from 'foreground/model/playlist/playlistActions'; 4 | import optionsListItemButtonTemplate from 'template/listItemButton/optionsListItemButton.hbs!'; 5 | import optionsIconTemplate from 'template/icon/optionsIcon_18.hbs!'; 6 | 7 | var PlaylistOptionsButtonView = LayoutView.extend({ 8 | template: optionsListItemButtonTemplate, 9 | templateHelpers: { 10 | optionsIcon: optionsIconTemplate 11 | }, 12 | 13 | attributes: { 14 | 'data-tooltip-text': chrome.i18n.getMessage('moreOptions') 15 | }, 16 | 17 | behaviors: { 18 | ListItemButton: { 19 | behaviorClass: ListItemButton 20 | } 21 | }, 22 | 23 | playlist: null, 24 | 25 | initialize: function(options) { 26 | this.playlist = options.playlist; 27 | }, 28 | 29 | onClick: function() { 30 | var offset = this.$el.offset(); 31 | var playlistActions = new PlaylistActions(); 32 | playlistActions.showContextMenu(this.playlist, offset.top, offset.left); 33 | } 34 | }); 35 | 36 | export default PlaylistOptionsButtonView; -------------------------------------------------------------------------------- /src/js/foreground/model/stream/clearStreamButton.js: -------------------------------------------------------------------------------- 1 | import {Model} from 'backbone'; 2 | 3 | var ClearStreamButton = Model.extend({ 4 | defaults: { 5 | enabled: false, 6 | streamItems: null 7 | }, 8 | 9 | initialize: function() { 10 | var streamItems = this.get('streamItems'); 11 | 12 | this.listenTo(streamItems, 'add:completed', this._onStreamItemsAddCompleted); 13 | this.listenTo(streamItems, 'remove', this._onStreamItemsRemove); 14 | this.listenTo(streamItems, 'reset', this._onStreamItemsReset); 15 | 16 | this.set('enabled', !streamItems.isEmpty()); 17 | }, 18 | 19 | getStateMessage: function() { 20 | var isEnabled = this.get('enabled'); 21 | var stateMessage = chrome.i18n.getMessage(isEnabled ? 'clearStream' : 'streamEmpty'); 22 | return stateMessage; 23 | }, 24 | 25 | _onStreamItemsAddCompleted: function(collection) { 26 | this.set('enabled', !collection.isEmpty()); 27 | }, 28 | 29 | _onStreamItemsRemove: function(model, collection) { 30 | this.set('enabled', !collection.isEmpty()); 31 | }, 32 | 33 | _onStreamItemsReset: function(collection) { 34 | this.set('enabled', !collection.isEmpty()); 35 | } 36 | }); 37 | 38 | export default ClearStreamButton; -------------------------------------------------------------------------------- /src/js/test/foreground/view/selectionBar/selectionBarView.spec.js: -------------------------------------------------------------------------------- 1 | import SelectionBarView from 'foreground/view/selectionBar/selectionBarView'; 2 | import SelectionBar from 'foreground/model/selectionBar/selectionBar'; 3 | import StreamItems from 'background/collection/streamItems'; 4 | import SearchResults from 'background/collection/searchResults'; 5 | import SignInManager from 'background/model/signInManager'; 6 | import ActivePlaylistManager from 'background/model/activePlaylistManager'; 7 | import ViewTestUtility from 'test/foreground/view/viewTestUtility'; 8 | 9 | describe('SelectionBarView', function() { 10 | beforeEach(function() { 11 | this.documentFragment = document.createDocumentFragment(); 12 | this.view = new SelectionBarView({ 13 | model: new SelectionBar({ 14 | streamItems: new StreamItems(), 15 | searchResults: new SearchResults(), 16 | signInManager: new SignInManager(), 17 | activePlaylistManager: new ActivePlaylistManager({ 18 | signInManager: new SignInManager() 19 | }) 20 | }) 21 | }); 22 | }); 23 | 24 | afterEach(function() { 25 | this.view.destroy(); 26 | }); 27 | 28 | ViewTestUtility.ensureBasicAssumptions.call(this); 29 | }); -------------------------------------------------------------------------------- /src/js/background/collection/videos.js: -------------------------------------------------------------------------------- 1 | import _ from 'common/shim/lodash.reference.shim'; 2 | import {Collection} from 'backbone'; 3 | import Video from 'background/model/video'; 4 | import Utility from 'common/utility'; 5 | 6 | var Videos = Collection.extend({ 7 | model: Video, 8 | 9 | // Return a string similiar to '15 videos, 4 hours' influenced by 10 | // the collection's length and sum of video durations. 11 | getDisplayInfo: function() { 12 | var totalItemsDuration = this._getTotalDuration(); 13 | var prettyTimeWithWords = Utility.prettyPrintTimeWithWords(totalItemsDuration); 14 | var videoString = chrome.i18n.getMessage(this.length === 1 ? 'video' : 'videos'); 15 | 16 | var displayInfo = this.length + ' ' + videoString + ', ' + prettyTimeWithWords; 17 | return displayInfo; 18 | }, 19 | 20 | // Returns the sum of the durations of all videos in the collection (in seconds) 21 | _getTotalDuration: function() { 22 | var videoDurations = this.pluck('duration'); 23 | var totalDuration = _.reduce(videoDurations, function(memo, videoDuration) { 24 | return memo + parseInt(videoDuration, 10); 25 | }, 0); 26 | 27 | return totalDuration; 28 | } 29 | }); 30 | 31 | export default Videos; -------------------------------------------------------------------------------- /src/js/foreground/view/tooltip/tooltipView.js: -------------------------------------------------------------------------------- 1 | import {LayoutView} from 'marionette'; 2 | import tooltipTemplate from 'template/tooltip/tooltip.hbs!'; 3 | 4 | var TooltipView = LayoutView.extend({ 5 | className: 'panel panel--detached', 6 | template: tooltipTemplate, 7 | 8 | ui: { 9 | panelContent: 'panelContent' 10 | }, 11 | 12 | modelEvents: { 13 | 'change:text': '_onChangeText' 14 | }, 15 | 16 | // Move the tooltip's location to a spot on the page and fade it in 17 | showAtOffset: function(offset) { 18 | this.$el.css('transform', 'translate(' + offset.left + 'px, ' + offset.top + 'px)'); 19 | this.$el.addClass('is-visible'); 20 | }, 21 | 22 | // Fade out the tooltip and then destroy it once completely hidden 23 | hide: function() { 24 | if (this.isRendered) { 25 | this.ui.panelContent.one('webkitTransitionEnd', this._onTransitionOutComplete.bind(this)); 26 | this.$el.removeClass('is-visible'); 27 | } else { 28 | this.destroy(); 29 | } 30 | }, 31 | 32 | _onChangeText: function(model, text) { 33 | this.ui.panelContent.text(text); 34 | }, 35 | 36 | _onTransitionOutComplete: function() { 37 | this.destroy(); 38 | } 39 | }); 40 | 41 | export default TooltipView; -------------------------------------------------------------------------------- /src/js/foreground/view/listItemButton/videoOptionsButtonView.js: -------------------------------------------------------------------------------- 1 | import {LayoutView} from 'marionette'; 2 | import ListItemButton from 'foreground/view/behavior/listItemButton'; 3 | import VideoActions from 'foreground/model/video/videoActions'; 4 | import optionsListItemButtonTemplate from 'template/listItemButton/optionsListItemButton.hbs!'; 5 | import optionsIconTemplate from 'template/icon/optionsIcon_18.hbs!'; 6 | 7 | var VideoOptionsButtonView = LayoutView.extend({ 8 | template: optionsListItemButtonTemplate, 9 | templateHelpers: { 10 | optionsIcon: optionsIconTemplate 11 | }, 12 | 13 | attributes: { 14 | 'data-tooltip-text': chrome.i18n.getMessage('moreOptions') 15 | }, 16 | 17 | behaviors: { 18 | ListItemButton: { 19 | behaviorClass: ListItemButton 20 | } 21 | }, 22 | 23 | video: null, 24 | player: null, 25 | 26 | initialize: function(options) { 27 | this.video = options.video; 28 | this.player = options.player; 29 | }, 30 | 31 | onClick: function() { 32 | var offset = this.$el.offset(); 33 | var videoActions = new VideoActions(); 34 | videoActions.showContextMenu(this.video, offset.top, offset.left, this.player); 35 | } 36 | }); 37 | 38 | export default VideoOptionsButtonView; -------------------------------------------------------------------------------- /src/js/foreground/view/listItemButton/deleteListItemButtonView.js: -------------------------------------------------------------------------------- 1 | import _ from 'common/shim/lodash.reference.shim'; 2 | import {LayoutView} from 'marionette'; 3 | import ListItemButton from 'foreground/view/behavior/listItemButton'; 4 | import deleteListItemButtonTemplate from 'template/listItemButton/deleteListItemButton.hbs!'; 5 | import deleteIconTemplate from 'template/icon/deleteIcon_18.hbs!'; 6 | 7 | var DeleteListItemButtonView = LayoutView.extend({ 8 | template: deleteListItemButtonTemplate, 9 | templateHelpers: { 10 | deleteIcon: deleteIconTemplate 11 | }, 12 | 13 | behaviors: { 14 | ListItemButton: { 15 | behaviorClass: ListItemButton 16 | } 17 | }, 18 | 19 | attributes: { 20 | 'data-tooltip-text': chrome.i18n.getMessage('delete') 21 | }, 22 | 23 | listItem: null, 24 | 25 | initialize: function(options) { 26 | this.listItem = options.listItem; 27 | // Ensure that the user isn't able to destroy the model more than once. 28 | this._deleteListItem = _.once(this._deleteListItem); 29 | }, 30 | 31 | onClick: function() { 32 | this._deleteListItem(); 33 | }, 34 | 35 | _deleteListItem: function() { 36 | this.listItem.destroy(); 37 | } 38 | }); 39 | 40 | export default DeleteListItemButtonView; -------------------------------------------------------------------------------- /src/less/switch.less: -------------------------------------------------------------------------------- 1 | @import "color"; 2 | @import "boxShadow"; 3 | @import "transition"; 4 | 5 | switch { 6 | display: flex; 7 | cursor: pointer; 8 | align-items: center; 9 | } 10 | 11 | .switch-label { 12 | margin-right: 12px; 13 | cursor: pointer; 14 | flex: 1; 15 | } 16 | 17 | .switch-icon { 18 | width: 30px; 19 | height: 15px; 20 | border-radius: 15px; 21 | transition: background @transition-duration--slow ease; 22 | 23 | &.is-checked { 24 | // http://www.google.com/design/spec/components/switches.html#switches-switch 25 | // Track On: Swatch 500, Alpha 50% 26 | background-color: fadeout(@blue--500, 50%); 27 | 28 | &:after { 29 | left: 15px; 30 | background-color: @blue--500; 31 | } 32 | } 33 | 34 | &.is-unchecked { 35 | background-color: @dark--tertiary; 36 | 37 | &:after { 38 | left: -5px; 39 | background-color: @gray--50; 40 | } 41 | } 42 | } 43 | 44 | .switch-icon:after { 45 | position: relative; 46 | top: -2px; 47 | display: inline-block; 48 | width: 20px; 49 | height: 20px; 50 | border-radius: 50%; 51 | content: ""; 52 | box-shadow: @boxShadow-elevation--low; 53 | transition: left @transition-duration--slow ease, background @transition-duration--slow ease; 54 | } -------------------------------------------------------------------------------- /src/less/color.less: -------------------------------------------------------------------------------- 1 | @black: rgba(0, 0, 0, 1); 2 | @white: rgba(255, 255, 255, 1); 3 | 4 | // Used for text: 5 | @dark--primary: rgba(0, 0, 0, .87); 6 | // Used for secondary text: 7 | @dark--secondary: rgba(0, 0, 0, .54); 8 | @light--secondary: rgba(255, 255, 255, .7); 9 | // Use tertiary for text hints and disabled text. 10 | @dark--tertiary: rgba(0, 0, 0, .26); 11 | // Use for dividers 12 | @dark--quaternary: rgba(0, 0, 0, .12); 13 | // Use for :hover highlighting. 14 | @dark--quinary: rgba(0, 0, 0, .03); 15 | 16 | // Tooltip color is not specified in spec. So, I used the snackbar color. 17 | // http://www.google.com/design/spec/components/tooltips.html#tooltips-usage 18 | @gray--tooltip: rgba(50, 50, 50, .9); 19 | 20 | // http://www.google.com/design/spec/components/buttons.html#buttons-flat-raised-buttons 21 | @gray--hoverButton: rgba(153, 153, 153, .2); 22 | @gray--activeButton: rgba(153, 153, 153, .4); 23 | 24 | // http://www.google.com/design/spec/components/snackbars-toasts.html#snackbars-toasts-specs 25 | @gray--snackbar: rgba(50, 50, 50, 1); 26 | 27 | @gray--50: rgb(250,250,250); 28 | 29 | // These colors are taken from https://inbox.google.com 30 | @blue--500: rgb(66,133,244); 31 | @blue--700: rgb(40,112,238); 32 | @green--500: rgb(15,157,88); 33 | @red--500: rgb(219,68,55); 34 | @yellow--500: rgb(244,180,0); -------------------------------------------------------------------------------- /src/js/background/model/radioButton.js: -------------------------------------------------------------------------------- 1 | import {Model} from 'backbone'; 2 | import LocalStorage from 'lib/backbone.localStorage'; 3 | import ChromeCommand from 'background/enum/chromeCommand'; 4 | 5 | var RadioButton = Model.extend({ 6 | localStorage: new LocalStorage('RadioButton'), 7 | 8 | defaults: { 9 | // Need to set the ID for Backbone.LocalStorage 10 | id: 'RadioButton', 11 | enabled: false 12 | }, 13 | 14 | initialize: function() { 15 | // Load from Backbone.LocalStorage 16 | this.fetch(); 17 | 18 | chrome.commands.onCommand.addListener(this._onChromeCommandsCommand.bind(this)); 19 | }, 20 | 21 | toggleEnabled: function() { 22 | this.save({ 23 | enabled: !this.get('enabled') 24 | }); 25 | }, 26 | 27 | getStateMessage: function() { 28 | var isEnabled = this.get('enabled'); 29 | var stateMessage = chrome.i18n.getMessage(isEnabled ? 'radioOn' : 'radioOff'); 30 | return stateMessage; 31 | }, 32 | 33 | _onChromeCommandsCommand: function(command) { 34 | if (command === ChromeCommand.ToggleRadio) { 35 | this.toggleEnabled(); 36 | 37 | StreamusBG.channels.backgroundNotification.commands.trigger('show:notification', { 38 | message: this.getStateMessage() 39 | }); 40 | } 41 | } 42 | }); 43 | 44 | export default RadioButton; -------------------------------------------------------------------------------- /src/js/background/model/shuffleButton.js: -------------------------------------------------------------------------------- 1 | import {Model} from 'backbone'; 2 | import LocalStorage from 'lib/backbone.localStorage'; 3 | import ChromeCommand from 'background/enum/chromeCommand'; 4 | 5 | var ShuffleButton = Model.extend({ 6 | localStorage: new LocalStorage('ShuffleButton'), 7 | 8 | defaults: { 9 | // Need to set the ID for Backbone.LocalStorage 10 | id: 'ShuffleButton', 11 | enabled: false 12 | }, 13 | 14 | initialize: function() { 15 | // Load from Backbone.LocalStorage 16 | this.fetch(); 17 | 18 | chrome.commands.onCommand.addListener(this._onChromeCommandsCommand.bind(this)); 19 | }, 20 | 21 | toggleEnabled: function() { 22 | this.save({ 23 | enabled: !this.get('enabled') 24 | }); 25 | }, 26 | 27 | getStateMessage: function() { 28 | var isEnabled = this.get('enabled'); 29 | var stateMessage = chrome.i18n.getMessage(isEnabled ? 'shufflingOn' : 'shufflingOff'); 30 | return stateMessage; 31 | }, 32 | 33 | _onChromeCommandsCommand: function(command) { 34 | if (command === ChromeCommand.ToggleShuffle) { 35 | this.toggleEnabled(); 36 | 37 | StreamusBG.channels.backgroundNotification.commands.trigger('show:notification', { 38 | message: this.getStateMessage() 39 | }); 40 | } 41 | } 42 | }); 43 | 44 | export default ShuffleButton; --------------------------------------------------------------------------------