diff --git a/tv_apps/tv-epg/index.html b/tv_apps/tv-epg/index.html index cd2748be3140..feb91fb19670 100644 --- a/tv_apps/tv-epg/index.html +++ b/tv_apps/tv-epg/index.html @@ -8,6 +8,7 @@ + @@ -29,6 +30,7 @@ + diff --git a/tv_apps/tv-epg/js/epg.js b/tv_apps/tv-epg/js/epg.js index 6168f3e3429a..5362ffc90de4 100644 --- a/tv_apps/tv-epg/js/epg.js +++ b/tv_apps/tv-epg/js/epg.js @@ -99,7 +99,7 @@ }; proto._onScanned = function epg__onScanned(stream) { - this.videoElement.src = stream; + this.videoElement.mozSrcObject = stream; this.epgController.fetchPrograms( this.visibleTimeOffset - this.visibleTimeSize, 3 * this.visibleTimeSize @@ -137,25 +137,16 @@ */ proto._addProgramSlot = function epg__addProgramSlot(index, time) { var rowElement; - var columnElement; - var textElement; - var progressElement; + var programElement; var i; for (i = 0; i < this.programListElement.children.length; i++) { rowElement = this.programListElement.children[i]; - columnElement = document.createElement('LI'); - columnElement.dataset.duration = '1'; - columnElement.dataset.startTime = time; - progressElement = document.createElement('DIV'); - progressElement.classList.add('background-progress'); - textElement = document.createElement('DIV'); - textElement.classList.add('title'); - columnElement.appendChild(progressElement); - columnElement.appendChild(textElement); + programElement = document.createElement('epg-program'); + programElement.startTime = time; if (index < this.timelineElement.children.length) { - rowElement.insertBefore(columnElement, rowElement.children[index]); + rowElement.insertBefore(programElement, rowElement.children[index]); } else { - rowElement.appendChild(columnElement); + rowElement.appendChild(programElement); } } }; @@ -181,43 +172,35 @@ proto._updateProgramSlot = function epg__updateProgramSlot(configs) { var rowElement = this.programListElement.children[configs.row]; - var columnElement = rowElement.children[configs.column]; - var duration = parseInt(columnElement.dataset.duration, 10); - var progressElement; - var currentColumn; + var programElement = rowElement.children[configs.column]; + var duration = programElement.duration; if (configs.title) { - columnElement.querySelector('.title').textContent = configs.title; + programElement.title = configs.title; } if (configs.duration) { duration = configs.duration; - columnElement.dataset.duration = duration; + programElement.duration = duration; } if (configs.isVisible) { - columnElement.classList.remove('hidden'); - this.spatialNavigator.add(columnElement); - currentColumn = this.initialTime - this.epgController.timelineOffset; - progressElement = columnElement.querySelector('.background-progress'); - if (configs.column + duration <= currentColumn) { - progressElement.style.transform = 'scaleX(1)'; - } else { - progressElement.classList.add('smooth'); - } + programElement.show(); + this.spatialNavigator.add(programElement); + programElement.resetProgressElement(this.initialTime); } else { - columnElement.classList.add('hidden'); + programElement.hide(); // If the old focus element only contains partial program segment, we have // to refocus to the first visible element of the same program. - if (columnElement === this.spatialNavigator.getFocusedElement()) { + if (programElement === this.spatialNavigator.getFocusedElement()) { this.spatialNavigator.focus( this.epgController.programTable[configs.column][configs.row].element); } - this.spatialNavigator.remove(columnElement); + this.spatialNavigator.remove(programElement); } if (configs.item) { - configs.item.element = columnElement; + configs.item.element = programElement; } }; @@ -236,23 +219,11 @@ var prevPrograms = this.epgController.programTable[timeIndex - 1]; var row; var programElement; - var startTime; - var duration; - var scaleX; // Update progress bar in every currently playing program for (row in playingPrograms) { programElement = playingPrograms[row].element; - startTime = parseInt(programElement.dataset.startTime, 10); - duration = parseInt(programElement.dataset.duration, 10); - - // There is a margin between two programs, so scale has to be normalized - scaleX = (time / this.timelineUnit - startTime) * EPG.COLUMN_WIDTH; - scaleX = scaleX / (duration * EPG.COLUMN_WIDTH - EPG.COLUMN_MARGIN); - scaleX = Math.min(scaleX, 1); - programElement.querySelector('.background-progress').style.transform = - 'scaleX(' + scaleX + ')'; - + programElement.progress = time / this.timelineUnit; this.timeMarkerElement.style.transform = 'translateX(' + ((time / this.timelineUnit - this.initialTime) * EPG.COLUMN_WIDTH) + @@ -263,8 +234,7 @@ if (prevPrograms && prevPrograms[row] && programElement !== prevPrograms[row].element) { programElement = prevPrograms[row].element; - programElement.querySelector('.background-progress').style.transform = - 'scaleX(1)'; + programElement.fillProgress(); } } }; @@ -287,15 +257,14 @@ }; }; - proto._onFocus = function epg__onFocus(element) { - var rowElement = element.parentElement; + proto._onFocus = function epg__onFocus(programElement) { + var rowElement = programElement.parentElement; var rowIndex = parseInt(rowElement.dataset.row, 10); var rowOffset = this.epgController.channelOffset; - var startTime = parseInt(element.dataset.startTime, 10); + var startTime = programElement.startTime; var timelineOffset = this.epgController.timelineOffset; - - element.classList.add('focus'); + programElement.classList.add('focus'); rowElement.classList.remove('hidden'); this._setTitlePadding({ setToNull: true @@ -373,20 +342,15 @@ var row; var timeOffset = this.visibleTimeOffset - this.epgController.timelineOffset; var programElement; - var programTitleElement; - var programStartTime; for(row = rowOffset; row < rowOffset + size; row++) { if (this.epgController.programTable[timeOffset][row]) { programElement = this.epgController.programTable[timeOffset][row].element; - programTitleElement = programElement.querySelector('.title'); if (opts && opts.setToNull) { - programTitleElement.style.paddingLeft = null; + programElement.titlePadding = null; } else { - programStartTime = parseInt(programElement.dataset.startTime, 10); - programTitleElement.style.paddingLeft = - EPG.COLUMN_WIDTH * (this.visibleTimeOffset - programStartTime) + - 'rem'; + programElement.titlePadding = EPG.COLUMN_WIDTH * + (this.visibleTimeOffset - programElement.startTime) + 'rem'; } } } @@ -415,8 +379,8 @@ this.dateElement.textContent = dtf.localeFormat(now, timeFormat); }; - proto._onUnfocus = function epg__onUnfocus(element) { - element.classList.remove('focus'); + proto._onUnfocus = function epg__onUnfocus(programElement) { + programElement.classList.remove('focus'); }; proto._onMove = function epg__onMove(key) { diff --git a/tv_apps/tv-epg/js/epg_program.js b/tv_apps/tv-epg/js/epg_program.js new file mode 100644 index 000000000000..2133947f848c --- /dev/null +++ b/tv_apps/tv-epg/js/epg_program.js @@ -0,0 +1,90 @@ +/* global EPG */ + +'use strict'; + +window.EPGProgram = (function(exports) { + var proto = Object.create(HTMLElement.prototype); + + proto.createdCallback = function() { + this._duration = 1; + this.dataset.duration = '1'; + this.titleElement = document.createElement('DIV'); + this.titleElement.classList.add('title'); + this.appendChild(this.titleElement); + + this.progressElement = document.createElement('DIV'); + this.progressElement.classList.add('background-progress'); + this.appendChild(this.progressElement); + }; + + Object.defineProperty(proto, 'progress', { + set: function(time) { + if (!this.startTime || !time) { + return; + } + + var scaleX = (time - this.startTime) * EPG.COLUMN_WIDTH; + scaleX = scaleX / (this.duration * EPG.COLUMN_WIDTH - EPG.COLUMN_MARGIN); + scaleX = Math.min(scaleX, 1); + this.progressElement.style.transform = 'scaleX(' + scaleX + ')'; + } + }); + + Object.defineProperty(proto, 'startTime', { + set: function(time) { + this._startTime = time; + }, + get: function() { + return this._startTime; + } + }); + + Object.defineProperty(proto, 'title', { + set: function(title) { + this.titleElement.textContent = title; + } + }); + + Object.defineProperty(proto, 'titlePadding', { + set: function(padding) { + this.titleElement.style.paddingLeft = padding; + } + }); + + Object.defineProperty(proto, 'duration', { + set: function(duration) { + this._duration = duration; + this.dataset.duration = duration; + }, + get: function() { + return this._duration; + } + }); + + proto.resetProgressElement = function(time) { + if (!this.startTime || !time) { + return; + } + + if (this.startTime + this.duration <= time) { + this.fillProgress(); + } else { + this.progressElement.classList.add('smooth'); + } + this.appendChild(this.progressElement); + }; + + proto.fillProgress = function() { + this.progressElement.style.transform = 'scaleX(1)'; + }; + + proto.hide = function() { + this.classList.add('hidden'); + }; + + proto.show = function() { + this.classList.remove('hidden'); + }; + + return document.registerElement('epg-program', { prototype: proto }); +})(window); diff --git a/tv_apps/tv-epg/style/epg_program.css b/tv_apps/tv-epg/style/epg_program.css new file mode 100644 index 000000000000..fc973c521ba7 --- /dev/null +++ b/tv_apps/tv-epg/style/epg_program.css @@ -0,0 +1,85 @@ +epg-program { + display: inline-block; + position: relative; + width: 33.5rem; + font-size: 2.5rem; + line-height: 10.8rem; + overflow: hidden; + padding-bottom: 0.3rem; + padding-right: 0.3rem; + border-bottom: 0.1rem #5e5e5e solid; + margin-top: 0.3rem; + background-color: #404040; + height: 11.2rem; + background-clip: content-box; + box-sizing: border-box; + outline: none; +} + +epg-program:after { + content: ''; + position: absolute; + top: 0; + right: 0; + width: 0.1rem; + background-color: #5e5e5e; + height: 10.8rem; +} + +epg-program.focus { + background-color: #00caf2; +} + +epg-program.hidden { + display: none; +} + +epg-program[data-duration="1"] { + width: 33.8rem; +} + +epg-program[data-duration="2"] { + width: 67.6rem; +} + +epg-program[data-duration="3"] { + width: 101.4rem; +} + +epg-program[data-duration="4"] { + width: 135.2rem; +} + +epg-program[data-duration="5"] { + width: 169rem; +} + +epg-program[data-duration="6"] { + width: 202.8rem; +} + +epg-program[data-duration="7"] { + width: 236.9rem; +} + +epg-program > .title { + margin-left: 2rem; + overflow: hidden; + text-overflow: ellipsis; + width: calc(100% - 2rem); + box-sizing: border-box; + position: absolute; +} + +epg-program > .background-progress { + position: absolute; + background-color: rgba(0, 0, 0, 0.2); + height: calc(100% - 0.3rem); + transform: scaleX(0); + transform-origin: 0; + width: calc(100% - 0.3rem); +} + +epg-program > .background-progress.smooth { + transition: transform 0.3s; +} diff --git a/tv_apps/tv-epg/style/tv_epg.css b/tv_apps/tv-epg/style/tv_epg.css index 56247f956e92..c95d160aede3 100644 --- a/tv_apps/tv-epg/style/tv_epg.css +++ b/tv_apps/tv-epg/style/tv_epg.css @@ -228,91 +228,6 @@ a:hover, a:active, a:focus { display: none; } -#program-list > ul > li { - display: inline-block; - position: relative; - width: 33.5rem; - font-size: 2.5rem; - line-height: 10.8rem; - overflow: hidden; - padding-bottom: 0.3rem; - padding-right: 0.3rem; - border-bottom: 0.1rem #5e5e5e solid; - margin-top: 0.3rem; - background-color: #404040; - height: 11.2rem; - background-clip: content-box; - box-sizing: border-box; -} - -#program-list > ul > li:after { - content: ''; - position: absolute; - top: 0; - right: 0; - width: 0.1rem; - background-color: #5e5e5e; - height: 10.8rem; -} - -#program-list > ul > li.focus { - background-color: #00caf2; -} - -#program-list > ul > li.hidden { - display: none; -} - -#program-list > ul > li[data-duration="1"] { - width: 33.8rem; -} - -#program-list > ul > li[data-duration="2"] { - width: 67.6rem; -} - -#program-list > ul > li[data-duration="3"] { - width: 101.4rem; -} - -#program-list > ul > li[data-duration="4"] { - width: 135.2rem; -} - -#program-list > ul > li[data-duration="5"] { - width: 169rem; -} - -#program-list > ul > li[data-duration="6"] { - width: 202.8rem; -} - -#program-list > ul > li[data-duration="7"] { - width: 236.9rem; -} - -#program-list > ul > li > .title { - margin-left: 2rem; - overflow: hidden; - text-overflow: ellipsis; - width: calc(100% - 2rem); - box-sizing: border-box; - position: absolute; -} - -#program-list > ul > li > .background-progress { - position: absolute; - background-color: rgba(0, 0, 0, 0.2); - height: calc(100% - 0.3rem); - transform: scaleX(0); - transform-origin: 0; - width: calc(100% - 0.3rem); -} - -#program-list > ul > li > .background-progress.smooth { - transition: transform 0.3s; -} - #bottom-container { width: 100%; box-sizing: border-box; diff --git a/tv_apps/tv-epg/test/unit/epg_program_test.js b/tv_apps/tv-epg/test/unit/epg_program_test.js new file mode 100644 index 000000000000..45d04fd84757 --- /dev/null +++ b/tv_apps/tv-epg/test/unit/epg_program_test.js @@ -0,0 +1,102 @@ +'use strict'; +/* jshint browser: true */ +/* global EPGProgram */ + +require('/js/epg_program.js'); + +suite('tv-epg/epg_program', function() { + + var epgProgram; + + suiteSetup(function() { + window.EPG = { + COLUMN_WIDTH: 33.8, + COLUMN_MARGIN: 0.6 + }; + }); + + setup(function() { + epgProgram = new EPGProgram(); + }); + + suite('createdCallback', function() { + test('Initialization', function() { + assert.equal(epgProgram.duration, 1); + assert.equal(epgProgram.dataset.duration, '1'); + assert.isDefined(epgProgram.titleElement); + assert.isDefined(epgProgram.progressElement); + }); + }); + + suite('resetProgressElement', function() { + setup(function() { + epgProgram.startTime = 10; + epgProgram.duration = 3; + }); + + test('Program has been played', function() { + epgProgram.resetProgressElement(20); + assert.equal(epgProgram.progressElement.style.transform, 'scaleX(1)'); + }); + + test('Program is still playing', function() { + epgProgram.resetProgressElement(12); + assert.equal(epgProgram.progressElement.style.transform, ''); + assert.isTrue(epgProgram.progressElement.classList.contains('smooth')); + }); + }); + + suite('fillProgress', function() { + test('Fill color of progress element', function() { + epgProgram.fillProgress(); + assert.equal(epgProgram.progressElement.style.transform, 'scaleX(1)'); + }); + }); + + suite('startTime', function() { + test('Set start time', function() { + epgProgram.startTime = 20; + assert.equal(epgProgram.startTime, 20); + }); + }); + + suite('title', function() { + test('Set title of title element', function() { + epgProgram.title = 'title'; + assert.equal(epgProgram.titleElement.textContent, 'title'); + }); + }); + + suite('setTitlePadding', function() { + test('Set padding of title element', function() { + epgProgram.titlePadding = '10rem'; + assert.equal(epgProgram.titleElement.style.paddingLeft, '10rem'); + }); + }); + + suite('setDuration', function() { + test('Set duration of epgProgram', function() { + epgProgram.duration = 10; + assert.equal(epgProgram.duration, 10); + assert.equal(epgProgram.dataset.duration, '10'); + }); + }); + + suite('hide', function() { + test('Hide epgProgram', function() { + epgProgram.hide(); + assert.isTrue(epgProgram.classList.contains('hidden')); + }); + }); + + suite('show', function() { + test('Show epgProgram', function() { + epgProgram.show(); + assert.isFalse(epgProgram.classList.contains('hidden')); + }); + }); + + suiteTeardown(function() { + window.EPG = null; + }); +}); diff --git a/tv_apps/tv-epg/test/unit/epg_test.js b/tv_apps/tv-epg/test/unit/epg_test.js index d3e76c8cf9d9..7b40770e3ce6 100644 --- a/tv_apps/tv-epg/test/unit/epg_test.js +++ b/tv_apps/tv-epg/test/unit/epg_test.js @@ -4,9 +4,10 @@ require('/shared/test/unit/mocks/smart-screen/mock_spatial_navigator.js'); require('/shared/test/unit/mocks/smart-screen/mock_key_navigation_adapter.js'); -require('/test/unit/mock_epg_controller.js'); require('/shared/test/unit/mocks/smart-screen/mock_clock.js'); require('/shared/test/unit/mocks/mock_l10n.js'); +require('/test/unit/mock_epg_controller.js'); +require('/js/epg_program.js'); require('/js/epg.js'); @@ -61,26 +62,6 @@ suite('tv-epg/epg', function() { window.navigator.mozL10n = realL10n; }); - suite('_onScanned', function() { - setup(function() { - var promise = { - then: function() { - return promise; - }, - catch: function() { - return promise; - } - }; - this.sinon.stub(epg.epgController, 'fetchPrograms').returns(promise); - }); - - test('Source of video-thumbnail should be assigned', function() { - var prefix = 'app://tv-epg.gaiamobile.org/test/unit'; - epg._onScanned('new-stream'); - assert.equal(epg.videoElement.src, prefix + '/new-stream'); - }); - }); - suite('_addTimeline', function() { setup(function() { epg.programListElement.appendChild(document.createElement('UL')); @@ -122,20 +103,12 @@ suite('tv-epg/epg', function() { }); suite('_updateProgramSlot', function() { - var columnElement; - var textElement; - var progressElement; + var programElement; setup(function() { var rowElement = document.createElement('UL'); - columnElement = document.createElement('LI'); - textElement = document.createElement('DIV'); - textElement.classList.add('title'); - progressElement = document.createElement('DIV'); - progressElement.classList.add('background-progress'); - columnElement.appendChild(textElement); - columnElement.appendChild(progressElement); - rowElement.appendChild(columnElement); + programElement = document.createElement('epg-program'); + rowElement.appendChild(programElement); epg.programListElement.appendChild(rowElement); }); @@ -152,9 +125,9 @@ suite('tv-epg/epg', function() { duration: 10 }; epg._updateProgramSlot(configs); - assert.equal(textElement.textContent, 'program1'); - assert.isTrue(columnElement.classList.contains('hidden')); - assert.equal(columnElement.dataset.duration, '10'); + assert.equal(programElement.titleElement.textContent, 'program1'); + assert.isTrue(programElement.classList.contains('hidden')); + assert.equal(programElement.dataset.duration, '10'); }); test('Visible column with item', function() { @@ -166,9 +139,9 @@ suite('tv-epg/epg', function() { duration: 10 }; epg._updateProgramSlot(configs); - assert.equal(textElement.textContent, 'program1'); - assert.isFalse(columnElement.classList.contains('hidden')); - assert.equal(columnElement.dataset.duration, '10'); + assert.equal(programElement.titleElement.textContent, 'program1'); + assert.isFalse(programElement.classList.contains('hidden')); + assert.equal(programElement.dataset.duration, '10'); }); teardown(function() { @@ -219,7 +192,7 @@ suite('tv-epg/epg', function() { }; } }); - columnElement = document.createElement('DIV'); + columnElement = document.createElement('epg-program'); rowElement = document.createElement('DIV'); rowElement.appendChild(columnElement); epg.visibleChannelOffset = 10; @@ -268,7 +241,7 @@ suite('tv-epg/epg', function() { 'rem, 0rem) translateZ(0.01rem)'; epg.epgController.timelineOffset = 4; - columnElement.dataset.startTime = epg.visibleTimeOffset - 1; + columnElement.startTime = epg.visibleTimeOffset - 1; epg._onFocus(columnElement); assert.equal( epg.timelineElement.style.transform, timelineAnswer); @@ -284,7 +257,7 @@ suite('tv-epg/epg', function() { 'rem, 0rem) translateZ(0.01rem)'; epg.epgController.timelineOffset = 14; - columnElement.dataset.startTime = epg.visibleTimeOffset + 4; + columnElement.startTime = epg.visibleTimeOffset + 4; epg._onFocus(columnElement); assert.equal( epg.timelineElement.style.transform, timelineAnswer); @@ -296,13 +269,9 @@ suite('tv-epg/epg', function() { suite('_setTitlePadding', function() { var programElement; - var titleElement; setup(function() { - programElement = document.createElement('DIV'); - programElement.dataset.startTime = 0; - titleElement = document.createElement('DIV'); - titleElement.classList.add('title'); - programElement.appendChild(titleElement); + programElement = document.createElement('epg-program'); + programElement.startTime = 0; epg.visibleTimeOffset = 1; epg.epgController.timelineOffset = 0; epg.epgController.programTable = { @@ -318,12 +287,12 @@ suite('tv-epg/epg', function() { epg._setTitlePadding({ setToNull: true }); - assert.equal(titleElement.style.paddingLeft, ''); + assert.equal(programElement.titleElement.style.paddingLeft, ''); }); test('Set padding-left of title element', function() { epg._setTitlePadding(); - assert.equal(titleElement.style.paddingLeft, '33.8rem'); + assert.equal(programElement.titleElement.style.paddingLeft, '33.8rem'); }); }); });