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 |
--------------------------------------------------------------------------------
/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 |
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 |
8 |
9 |
10 |
13 |
14 |
15 |
16 |
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 |
--------------------------------------------------------------------------------
/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 |
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 |
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 |
6 |
7 | {{i18n 'whyNotAddAVideoFromAPlaylistOr'}} {{i18n 'searchForVideos'}}?
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/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 |
7 |
8 |
13 |
14 |
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 |
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 |
6 |
7 | {{i18n 'wouldYouLikeTo'}} {{i18n 'searchForVideos'}}?
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
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;
--------------------------------------------------------------------------------