Commit 7d6a33d8 authored by Becca Hughes's avatar Becca Hughes Committed by Commit Bot

[Media Feeds] Factor out media data table

Factor out the media data table.

BUG=1059352

Change-Id: I1ffc498d270491eeb3a433de1e7e6c3fc722e3b3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2116783Reviewed-by: default avatarEsmael Elmoslimany <aee@chromium.org>
Reviewed-by: default avatarTommy Steimel <steimel@chromium.org>
Commit-Queue: Becca Hughes <beccahughes@chromium.org>
Cr-Commit-Position: refs/heads/master@{#753420}
parent 54b1ca9d
......@@ -40,6 +40,7 @@ This file specifies browser resources for developer-facing chrome:// pages
<include name="IDR_INTERVENTIONS_INTERNALS_UNSUPPORTED_PAGE_HTML" file="resources\interventions_internals\unsupported_page.html" flattenhtml="true" allowexternalscript="true" compress="gzip" type="BINDATA" />
<include name="IDR_LOCAL_STATE_HTML" file="resources\local_state\local_state.html" flattenhtml="true" allowexternalscript="true" type="BINDATA" compress="gzip" />
<include name="IDR_LOCAL_STATE_JS" file="resources\local_state\local_state.js" flattenhtml="true" allowexternalscript="true" type="BINDATA" compress="gzip" />
<include name="IDR_MEDIA_DATA_TABLE_JS" file="resources\media\media_data_table.js" flattenhtml="true" type="BINDATA" compress="gzip" />
<include name="IDR_MEDIA_ENGAGEMENT_HTML" file="resources\media\media_engagement.html" flattenhtml="true" type="BINDATA" compress="gzip" allowexternalscript="true" />
<include name="IDR_MEDIA_ENGAGEMENT_JS" file="resources\media\media_engagement.js" flattenhtml="true" type="BINDATA" compress="gzip" />
<include name="IDR_MEDIA_ENGAGEMENT_SCORE_DETAILS_MOJOM_LITE_JS" file="${root_gen_dir}\chrome\browser\media\media_engagement_score_details.mojom-lite.js" use_base_dir="false" type="BINDATA" compress="gzip" />
......
......@@ -21,13 +21,36 @@ grit("webrtc_logs_resources") {
}
js_type_check("closure_compile") {
deps = [ ":media_feeds" ]
deps = [
":media_data_table",
":media_feeds",
":media_history",
]
}
js_library("media_history") {
deps = [
":media_data_table",
"//chrome/browser/media:mojo_bindings_js_library_for_compile",
"//ui/webui/resources/js:assert",
"//ui/webui/resources/js:util",
"//ui/webui/resources/js/cr/ui:tabs",
]
}
js_library("media_feeds") {
deps = [
":media_data_table",
"//chrome/browser/media/feeds:mojo_bindings_js_library_for_compile",
"//ui/webui/resources/js:assert",
"//ui/webui/resources/js:util",
]
}
js_library("media_data_table") {
deps = [
"//ui/webui/resources/js:assert",
"//ui/webui/resources/js:cr",
"//ui/webui/resources/js:util",
]
}
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
'use strict';
cr.define('cr.ui', function() {
/**
* TODO(beccahughes): Description
*/
/* #export */ class MediaDataTable {
/**
* @param {HTMLElement} table
* @param {!cr.ui.MediaDataTableDelegate} delegate
*/
constructor(table, delegate) {
/** @private {HTMLElement} */
this.table_ = table;
/** @private {Array<Object>} */
this.data_ = [];
/** @private {cr.ui.MediaDataTableDelegate} */
this.delegate_ = delegate;
// Set table header sort handlers.
const headers = this.table_.querySelectorAll('th[sort-key]');
headers.forEach(header => {
header.addEventListener('click', this.handleSortClick_.bind(this));
});
}
handleSortClick_(e) {
if (e.target.classList.contains('sort-column')) {
e.target.toggleAttribute('sort-reverse');
} else {
document.querySelector('.sort-column').classList.remove('sort-column');
e.target.classList.add('sort-column');
}
this.render();
}
render() {
// Find the body of the table and clear it.
const body = this.table_.querySelectorAll('tbody')[0];
body.innerHTML = '';
// Get the sort key from the columns to determine which data should be in
// which column.
const headerCells = Array.from(this.table_.querySelectorAll('thead th'));
const dataAndSortKeys = headerCells.map((e) => {
return e.getAttribute('sort-key') ? e.getAttribute('sort-key') :
e.getAttribute('data-key');
});
const currentSortCol = this.table_.querySelectorAll('.sort-column')[0];
const currentSortKey = currentSortCol.getAttribute('sort-key');
const currentSortReverse = currentSortCol.hasAttribute('sort-reverse');
// Sort the data based on the current sort key.
this.data_.sort((a, b) => {
return (currentSortReverse ? -1 : 1) *
this.delegate_.compareTableItem(currentSortKey, a, b);
});
// Generate the table rows.
this.data_.forEach((dataRow) => {
const tr = document.createElement('tr');
body.appendChild(tr);
dataAndSortKeys.forEach((key) => {
const td = document.createElement('td');
// Keys with a period denote nested objects.
let data = dataRow;
const expandedKey = key.split('.');
expandedKey.forEach((k) => {
data = data[k];
key = k;
});
this.delegate_.insertDataField(td, data, key);
tr.appendChild(td);
});
});
}
/**
* @param {Array} data The data to update
*/
setData(data) {
this.data_ = data;
this.render();
}
}
/** @interface */
/* #export */ class MediaDataTableDelegate {
/**
* Formats a field to be displayed in the data table and inserts it into the
* element.
* @param {Element} td
* @param {?Object} data
* @param {string} key
*/
insertDataField(td, data, key) {}
/**
* Compares two objects based on |sortKey|.
* @param {string} sortKey The name of the property to sort by.
* @param {?Object} a The first object to compare.
* @param {?Object} b The second object to compare.
* @return {number} A negative number if |a| should be ordered
* before |b|, a positive number otherwise.
*/
compareTableItem(sortKey, a, b) {}
}
// #cr_define_end
return {MediaDataTable, MediaDataTableDelegate};
});
......@@ -17,6 +17,10 @@
<script src="chrome/browser/media/feeds/media_feeds_store.mojom-lite.js">
</script>
<script src="chrome://resources/js/cr.js"></script>
<script src="chrome://resources/js/cr/ui.js"></script>
<script src="chrome://media-feeds/media-data-table.js"></script>
<script src="chrome://media-feeds/media-feeds.js"></script>
<style>
body {
......@@ -79,7 +83,7 @@
<body>
<h1>Media Feeds</h1>
<button id="copy-all-to-clipboard">Copy all to clipboard</button>
<table>
<table id="feeds-table">
<thead>
<tr id="feed-table-header">
<th sort-key="id" class="sort-column" sort-reverse>
......@@ -118,7 +122,7 @@
<th sort-key="lastFetchContentTypes">
Last Fetch Content Types
</th>
<th>
<th data-key="logos">
Logos
</th>
</tr>
......
......@@ -2,108 +2,135 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// TODO(https://crbug.com/1059352): Factor out the sortable table.
'use strict';
// Allow a function to be provided by tests, which will be called when
// the page has been populated with media feeds details.
const pageIsPopulatedResolver = new PromiseResolver();
const mediaFeedsPageIsPopulatedResolver = new PromiseResolver();
function whenPageIsPopulatedForTest() {
return pageIsPopulatedResolver.promise;
return mediaFeedsPageIsPopulatedResolver.promise;
}
(function() {
let detailsProvider = null;
let info = null;
let feedTableBody = null;
let sortReverse = true;
let sortKey = 'id';
/**
* Creates a single row in the feeds table.
* @param {!mediaFeeds.mojom.MediaFeed} rowInfo The info to create the row.
* @return {!Node}
let delegate = null;
let feedsTable = null;
let store = null;
/** @implements {cr.ui.MediaDataTableDelegate} */
class MediaFeedsTableDelegate {
/**
* Formats a field to be displayed in the data table and inserts it into the
* element.
* @param {Element} td
* @param {?Object} data
* @param {string} key
*/
function createRow(rowInfo) {
const template = $('datarow');
const td = template.content.querySelectorAll('td');
td[0].textContent = rowInfo.id;
td[1].textContent = rowInfo.url.url;
td[2].textContent = rowInfo.displayName;
td[3].textContent = convertMojoTimeToJS(rowInfo.lastDiscoveryTime).toString();
if (rowInfo.lastFetchTime != null) {
td[4].textContent = convertMojoTimeToJS(rowInfo.lastFetchTime).toString();
}
if (rowInfo.userStatus == mediaFeeds.mojom.FeedUserStatus.kAuto) {
td[5].textContent = 'Auto';
} else if (rowInfo.userStatus == mediaFeeds.mojom.FeedUserStatus.kDisabled) {
td[5].textContent = 'Disabled';
insertDataField(td, data, key) {
if (data === undefined || data === null) {
return;
}
if (rowInfo.lastFetchResult == mediaFeeds.mojom.FetchResult.kNone) {
td[6].textContent = 'None';
} else if (rowInfo.lastFetchResult == mediaFeeds.mojom.FetchResult.kSuccess) {
td[6].textContent = 'Success';
} else if (
rowInfo.lastFetchResult ==
mediaFeeds.mojom.FetchResult.kFailedBackendError) {
td[6].textContent = 'Failed (Backend Error)';
if (key === 'url') {
// Format a mojo GURL.
td.textContent = data.url;
} else if (
rowInfo.lastFetchResult ==
mediaFeeds.mojom.FetchResult.kFailedNetworkError) {
td[6].textContent = 'Failed (Network Error)';
key === 'lastDiscoveryTime' || key === 'lastFetchTime' ||
key === 'cacheExpiryTime') {
// Format a mojo time.
td.textContent =
convertMojoTimeToJS(/** @type {mojoBase.mojom.Time} */ (data))
.toString();
} else if (key === 'userStatus') {
// Format a FeedUserStatus.
if (data == mediaFeeds.mojom.FeedUserStatus.kAuto) {
td.textContent = 'Auto';
} else if (data == mediaFeeds.mojom.FeedUserStatus.kDisabled) {
td.textContent = 'Disabled';
}
td[7].textContent = rowInfo.fetchFailedCount;
if (rowInfo.cacheExpiryTime != null) {
td[8].textContent = convertMojoTimeToJS(rowInfo.cacheExpiryTime).toString();
} else if (key === 'lastFetchResult') {
// Format a FetchResult.
if (data == mediaFeeds.mojom.FetchResult.kNone) {
td.textContent = 'None';
} else if (data == mediaFeeds.mojom.FetchResult.kSuccess) {
td.textContent = 'Success';
} else if (data == mediaFeeds.mojom.FetchResult.kFailedBackendError) {
td.textContent = 'Failed (Backend Error)';
} else if (data == mediaFeeds.mojom.FetchResult.kFailedNetworkError) {
td.textContent = 'Failed (Network Error)';
}
td[9].textContent = rowInfo.lastFetchItemCount;
td[10].textContent = rowInfo.lastFetchPlayNextCount;
} else if (key === 'lastFetchContentTypes') {
// Format a MediaFeedItemType.
const contentTypes = [];
if (rowInfo.lastFetchContentTypes &
mediaFeeds.mojom.MediaFeedItemType.kVideo) {
const itemType = parseInt(data, 10);
if (itemType & mediaFeeds.mojom.MediaFeedItemType.kVideo) {
contentTypes.push('Video');
} else if (
rowInfo.lastFetchContentTypes &
mediaFeeds.mojom.MediaFeedItemType.kTVSeries) {
} else if (itemType & mediaFeeds.mojom.MediaFeedItemType.kTVSeries) {
contentTypes.push('TV Series');
} else if (
rowInfo.lastFetchContentTypes &
mediaFeeds.mojom.MediaFeedItemType.kMovie) {
} else if (itemType & mediaFeeds.mojom.MediaFeedItemType.kMovie) {
contentTypes.push('Movie');
}
td[11].textContent =
td.textContent =
contentTypes.length === 0 ? 'None' : contentTypes.join(',');
} else if (key === 'logos') {
// Format an array of mojo media images.
rowInfo.logos.forEach((image) => {
data.forEach((image) => {
const a = document.createElement('a');
a.href = image.src.url;
a.textContent = image.src.url;
a.target = '_blank';
td[12].appendChild(a);
td[12].appendChild(document.createElement('br'));
td.appendChild(a);
td.appendChild(document.createElement('br'));
});
} else {
td.textContent = data;
}
}
/**
* Compares two objects based on |sortKey|.
* @param {string} sortKey The name of the property to sort by.
* @param {?Object} a The first object to compare.
* @param {?Object} b The second object to compare.
* @return {number} A negative number if |a| should be ordered
* before |b|, a positive number otherwise.
*/
compareTableItem(sortKey, a, b) {
const val1 = a[sortKey];
const val2 = b[sortKey];
return document.importNode(template.content, true);
if (sortKey === 'url') {
return val1.url > val2.url ? 1 : -1;
} else if (
sortKey === 'id' || sortKey === 'displayName' ||
sortKey === 'userStatus' || sortKey === 'lastFetchResult' ||
sortKey === 'fetchFailedCount' || sortKey === 'lastFetchItemCount' ||
sortKey === 'lastFetchPlayNextCount' ||
sortKey === 'lastFetchContentTypes') {
return val1 > val2 ? 1 : -1;
} else if (
sortKey === 'lastDiscoveryTime' || sortKey === 'lastFetchTime' ||
sortKey === 'cacheExpiryTime') {
return val1.internalValue > val2.internalValue ? 1 : -1;
}
assertNotReached('Unsupported sort key: ' + sortKey);
return 0;
}
}
/**
* Converts a mojo time to a JS time.
* @param {!mojoBase.mojom.Time} mojoTime
* @param {mojoBase.mojom.Time} mojoTime
* @return {Date}
*/
function convertMojoTimeToJS(mojoTime) {
if (mojoTime === null) {
return new Date();
}
// The new Date().getTime() returns the number of milliseconds since the
// UNIX epoch (1970-01-01 00::00:00 UTC), while |internalValue| of the
// device.mojom.Geoposition represents the value of microseconds since the
......@@ -118,109 +145,23 @@ function convertMojoTimeToJS(mojoTime) {
return new Date(timeInMs - epochDeltaInMs);
}
/**
* Remove all rows from the feeds table.
*/
function clearTable() {
feedTableBody.innerHTML = '';
}
/**
* Sort the feed info based on |sortKey| and |sortReverse|.
*/
function sortInfo() {
info.sort((a, b) => {
return (sortReverse ? -1 : 1) * compareTableItem(sortKey, a, b);
});
}
/**
* Compares two MediaFeed objects based on |sortKey|.
* @param {string} sortKey The name of the property to sort by.
* @param {mediaFeeds.mojom.MediaFeed} a First object to compare.
* @param {mediaFeeds.mojom.MediaFeed} b Second object to compare.
* @return {number|boolean} A negative number if |a| should be ordered before
* |b|, a positive number otherwise.
*/
function compareTableItem(sortKey, a, b) {
if (sortKey == 'url') {
return a.url.url > b.url.url ? 1 : -1;
} else if (sortKey == 'id') {
return a.id > b.id;
} else if (sortKey == 'lastDiscoveryTime') {
return (
a.lastDiscoveryTime.internalValue > b.lastDiscoveryTime.internalValue);
} else if (sortKey == 'displayName') {
return a.displayName > b.displayName;
} else if (sortKey == 'userStatus') {
return a.userStatus > b.userStatus;
} else if (sortKey == 'lastFetchResult') {
return a.lastFetchResult > b.lastFetchResult;
} else if (sortKey == 'fetchFailedCount') {
return a.fetchFailedCount > b.fetchFailedCount;
} else if (sortKey == 'cacheExpiryTime') {
return a.cacheExpiryTime > b.cacheExpiryTime;
} else if (sortKey == 'lastFetchItemCount') {
return a.lastFetchItemCount > b.lastFetchItemCount;
} else if (sortKey == 'lastFetchPlayNextCount') {
return a.lastFetchPlayNextCount > b.lastFetchPlayNextCount;
} else if (sortKey == 'lastFetchContentTypes') {
return a.lastFetchContentTypes > b.lastFetchContentTypes;
}
assertNotReached('Unsupported sort key: ' + sortKey);
return 0;
}
/**
* Regenerates the feed table from |info|.
*/
function renderTable() {
clearTable();
sortInfo();
info.forEach(rowInfo => feedTableBody.appendChild(createRow(rowInfo)));
}
/**
* Retrieve feed info and render the feed table.
*/
function updateFeedTable() {
detailsProvider.getMediaFeeds().then(response => {
info = response.feeds;
renderTable();
pageIsPopulatedResolver.resolve();
function updateFeedsTable() {
store.getMediaFeeds().then(response => {
feedsTable.setData(response.feeds);
mediaFeedsPageIsPopulatedResolver.resolve();
});
}
document.addEventListener('DOMContentLoaded', () => {
detailsProvider = mediaFeeds.mojom.MediaFeedsStore.getRemote();
feedTableBody = $('feed-table-body');
updateFeedTable();
// Set table header sort handlers.
const feedTableHeader = $('feed-table-header');
const headers = feedTableHeader.children;
for (let i = 0; i < headers.length; i++) {
headers[i].addEventListener('click', (e) => {
const newSortKey = e.target.getAttribute('sort-key');
if (sortKey == newSortKey) {
sortReverse = !sortReverse;
} else {
sortKey = newSortKey;
sortReverse = false;
}
const oldSortColumn = document.querySelector('.sort-column');
oldSortColumn.classList.remove('sort-column');
e.target.classList.add('sort-column');
if (sortReverse) {
e.target.setAttribute('sort-reverse', '');
} else {
e.target.removeAttribute('sort-reverse');
}
renderTable();
});
}
store = mediaFeeds.mojom.MediaFeedsStore.getRemote();
delegate = new MediaFeedsTableDelegate();
feedsTable = new cr.ui.MediaDataTable($('feeds-table'), delegate);
updateFeedsTable();
// Add handler to 'copy all to clipboard' button
const copyAllToClipboardButton = $('copy-all-to-clipboard');
......
......@@ -24,6 +24,7 @@
<script src="chrome://resources/js/cr/ui/focus_outline_manager.js"></script>
<script src="chrome://resources/js/cr/ui/tabs.js"></script>
<script src="chrome://media-history/media-data-table.js"></script>
<script src="chrome://media-history/media-history.js"></script>
<style>
html,
......
......@@ -6,9 +6,9 @@
// Allow a function to be provided by tests, which will be called when
// the page has been populated.
const pageIsPopulatedResolver = new PromiseResolver();
const mediaHistoryPageIsPopulatedResolver = new PromiseResolver();
function whenPageIsPopulatedForTest() {
return pageIsPopulatedResolver.promise;
return mediaHistoryPageIsPopulatedResolver.promise;
}
(function() {
......@@ -18,12 +18,13 @@ let statsTableBody = null;
let originsTable = null;
let playbacksTable = null;
let sessionsTable = null;
let delegate = null;
/**
* Creates a single row in the stats table.
* @param {string} name The name of the table.
* @param {string} count The row count of the table.
* @return {!HTMLElement}
* @param {number} count The row count of the table.
* @return {!Node}
*/
function createStatsRow(name, count) {
const template = $('stats-row');
......@@ -33,109 +34,48 @@ function createStatsRow(name, count) {
return document.importNode(template.content, true);
}
/**
* Compares two MediaHistoryOriginRow objects based on |sortKey|.
* @param {string} sortKey The name of the property to sort by.
* @param {number|mojo_base.mojom.TimeDelta|url.mojom.Origin} The first object
* to compare.
* @param {number|mojo_base.mojom.TimeDelta|url.mojom.Origin} The second object
* to compare.
* @return {number} A negative number if |a| should be ordered before
* |b|, a positive number otherwise.
*/
function compareTableItem(sortKey, a, b) {
const val1 = a[sortKey];
const val2 = b[sortKey];
// Compare the hosts of the origin ignoring schemes.
if (sortKey == 'origin') {
return val1.host > val2.host ? 1 : -1;
}
// Compare the url property.
if (sortKey == 'url') {
return val1.url > val2.url ? 1 : -1;
}
// Compare mojo_base.mojom.TimeDelta microseconds value.
if (sortKey == 'cachedAudioVideoWatchtime' ||
sortKey == 'actualAudioVideoWatchtime' || sortKey == 'watchtime' ||
sortKey == 'duration' || sortKey == 'position') {
return val1.microseconds - val2.microseconds;
}
if (sortKey.startsWith('metadata.')) {
// Keys with a period denote nested objects.
let nestedA = a;
let nestedB = b;
const expandedKey = sortKey.split('.');
expandedKey.forEach((k) => {
nestedA = nestedA[k];
nestedB = nestedB[k];
});
return nestedA > nestedB;
}
if (sortKey == 'lastUpdatedTime') {
return val1 - val2;
}
assertNotReached('Unsupported sort key: ' + sortKey);
return 0;
}
/**
* Parses utf16 coded string.
* @param {!mojoBase.mojom.String16} arr
* @return {string}
*/
function decodeString16(arr) {
return arr.data.map(ch => String.fromCodePoint(ch)).join('');
}
/**
/** @implements {cr.ui.MediaDataTableDelegate} */
class MediaHistoryTableDelegate {
/**
* Formats a field to be displayed in the data table and inserts it into the
* element.
* @param {HTMLTableRowElement} td
* @param {?object} data
* @param {Element} td
* @param {?Object} data
* @param {string} key
*/
function insertDataField(td, data, key) {
insertDataField(td, data, key) {
if (data === undefined || data === null) {
return;
}
if (key == 'origin') {
if (key === 'origin') {
// Format a mojo origin.
td.textContent = data.scheme + '://' + data.host;
if (data.scheme == 'http' && data.port != '80') {
td.textContent += ':' + data.port;
} else if (data.scheme == 'https' && data.port != '443') {
td.textContent += ':' + data.port;
}
} else if (key == 'lastUpdatedTime') {
const {scheme, host, port} = data;
td.textContent = new URL(`${scheme}://${host}:${port}`).origin;
} else if (key === 'lastUpdatedTime') {
// Format a JS timestamp.
td.textContent = data ? new Date(data).toISOString() : '';
} else if (
key == 'cachedAudioVideoWatchtime' ||
key == 'actualAudioVideoWatchtime' || key == 'watchtime' ||
key == 'duration' || key == 'position') {
key === 'cachedAudioVideoWatchtime' ||
key === 'actualAudioVideoWatchtime' || key === 'watchtime' ||
key === 'duration' || key === 'position') {
// Format a mojo timedelta.
const secs = (data.microseconds / 1000000);
td.textContent = secs.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,');
} else if (key == 'url') {
td.textContent =
secs.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,');
} else if (key === 'url') {
// Format a mojo GURL.
td.textContent = data.url;
} else if (key == 'hasAudio' || key == 'hasVideo') {
} else if (key === 'hasAudio' || key === 'hasVideo') {
// Format a boolean.
td.textContent = data ? 'Yes' : 'No';
} else if (
key == 'title' || key == 'artist' || key == 'album' ||
key == 'sourceTitle') {
key === 'title' || key === 'artist' || key === 'album' ||
key === 'sourceTitle') {
// Format a mojo string16.
td.textContent = decodeString16(data);
} else if (key == 'artwork') {
td.textContent = decodeString16(
/** @type {mojoBase.mojom.String16} */ (data));
} else if (key === 'artwork') {
// Format an array of mojo media images.
data.forEach((image) => {
const a = document.createElement('a');
......@@ -148,104 +88,76 @@ function insertDataField(td, data, key) {
} else {
td.textContent = data;
}
}
}
class DataTable {
/**
* @param {!HTMLTableElement} table
* Compares two objects based on |sortKey|.
* @param {string} sortKey The name of the property to sort by.
* @param {?Object} a The first object to compare.
* @param {?Object} b The second object to compare.
* @return {number} A negative number if |a| should be ordered
* before |b|, a positive number otherwise.
*/
constructor(table) {
/** @private {!HTMLTableElement} */
this.table_ = table;
/** @private {Object[]} */
this.data_ = [];
// Set table header sort handlers.
const headers = this.table_.querySelectorAll('th[sort-key]');
headers.forEach((header) => {
header.addEventListener('click', this.handleSortClick.bind(this));
}, this);
}
handleSortClick(e) {
const isCurrentSortColumn = e.target.classList.contains('sort-column');
compareTableItem(sortKey, a, b) {
const val1 = a[sortKey];
const val2 = b[sortKey];
// If we are not the sort column then we should become the sort column.
if (!isCurrentSortColumn) {
const oldSortColumn = document.querySelector('.sort-column');
oldSortColumn.classList.remove('sort-column');
e.target.classList.add('sort-column');
// Compare the hosts of the origin ignoring schemes.
if (sortKey === 'origin') {
return val1.host > val2.host ? 1 : -1;
}
// If we are the current sort column then we should toggle the reverse
// attribute to sort in reverse.
if (isCurrentSortColumn && e.target.hasAttribute('sort-reverse')) {
e.target.removeAttribute('sort-reverse');
} else {
e.target.setAttribute('sort-reverse', '');
// Compare the url property.
if (sortKey === 'url') {
return val1.url > val2.url ? 1 : -1;
}
this.render();
// Compare mojo_base.mojom.TimeDelta microseconds value.
if (sortKey === 'cachedAudioVideoWatchtime' ||
sortKey === 'actualAudioVideoWatchtime' || sortKey === 'watchtime' ||
sortKey === 'duration' || sortKey === 'position') {
return val1.microseconds - val2.microseconds;
}
render() {
// Find the body of the table and clear it.
const body = this.table_.querySelectorAll('tbody')[0];
body.innerHTML = '';
// Get the sort key from the columns to determine which data should be in
// which column.
const headerCells = Array.from(this.table_.querySelectorAll('thead th'));
const dataAndSortKeys = headerCells.map((e) => {
return e.getAttribute('sort-key') ? e.getAttribute('sort-key') :
e.getAttribute('data-key');
});
const currentSortCol = this.table_.querySelectorAll('.sort-column')[0];
const currentSortKey = currentSortCol.getAttribute('sort-key');
const currentSortReverse = currentSortCol.hasAttribute('sort-reverse');
// Sort the data based on the current sort key.
this.data_.sort((a, b) => {
return (currentSortReverse ? -1 : 1) *
compareTableItem(currentSortKey, a, b);
});
// Generate the table rows.
this.data_.forEach((dataRow) => {
const tr = document.createElement('tr');
body.appendChild(tr);
dataAndSortKeys.forEach((key) => {
const td = document.createElement('td');
if (sortKey.startsWith('metadata.')) {
// Keys with a period denote nested objects.
let data = dataRow;
const expandedKey = key.split('.');
let nestedA = a;
let nestedB = b;
const expandedKey = sortKey.split('.');
expandedKey.forEach((k) => {
data = data[k];
key = k;
nestedA = nestedA[k];
nestedB = nestedB[k];
});
insertDataField(td, data, key);
tr.appendChild(td);
});
});
return nestedA > nestedB ? 1 : -1;
}
/**
* @param {object[]} data The data to update
if (sortKey === 'lastUpdatedTime') {
return val1 - val2;
}
assertNotReached('Unsupported sort key: ' + sortKey);
return 0;
}
}
/**
* Parses utf16 coded string.
* @param {mojoBase.mojom.String16} arr
* @return {string}
*/
setData(data) {
this.data_ = data;
this.render();
function decodeString16(arr) {
if (!arr) {
return '';
}
return arr.data.map(ch => String.fromCodePoint(ch)).join('');
}
/**
* Regenerates the stats table.
* @param {!MediaHistoryStats} stats The stats for the Media History store.
* @param {!mediaHistory.mojom.MediaHistoryStats} stats The stats for the Media
* History store.
*/
function renderStatsTable(stats) {
statsTableBody.innerHTML = '';
......@@ -280,7 +192,7 @@ function showTab(name) {
}
// Return an empty promise if there is no tab.
return new Promise();
return new Promise(() => {});
}
document.addEventListener('DOMContentLoaded', function() {
......@@ -288,9 +200,11 @@ document.addEventListener('DOMContentLoaded', function() {
statsTableBody = $('stats-table-body');
originsTable = new DataTable($('origins-table'));
playbacksTable = new DataTable($('playbacks-table'));
sessionsTable = new DataTable($('sessions-table'));
delegate = new MediaHistoryTableDelegate();
originsTable = new cr.ui.MediaDataTable($('origins-table'), delegate);
playbacksTable = new cr.ui.MediaDataTable($('playbacks-table'), delegate);
sessionsTable = new cr.ui.MediaDataTable($('sessions-table'), delegate);
cr.ui.decorate('tabbox', cr.ui.TabBox);
......@@ -305,7 +219,7 @@ document.addEventListener('DOMContentLoaded', function() {
window.location.hash = 'tab-stats';
} else {
showTab(window.location.hash.substr(5))
.then(pageIsPopulatedResolver.resolve);
.then(mediaHistoryPageIsPopulatedResolver.resolve);
}
// When the tab updates, update the anchor.
......
......@@ -25,6 +25,7 @@ MediaFeedsUI::MediaFeedsUI(content::WebUI* web_ui)
// Setup the data source behind chrome://media-feeds.
std::unique_ptr<content::WebUIDataSource> source(
content::WebUIDataSource::Create(chrome::kChromeUIMediaFeedsHost));
source->AddResourcePath("media-data-table.js", IDR_MEDIA_DATA_TABLE_JS);
source->AddResourcePath("media-feeds.js", IDR_MEDIA_FEEDS_JS);
source->AddResourcePath(
"services/media_session/public/mojom/media_session.mojom-lite.js",
......
......@@ -25,6 +25,7 @@ MediaHistoryUI::MediaHistoryUI(content::WebUI* web_ui)
// Setup the data source behind chrome://media-history.
std::unique_ptr<content::WebUIDataSource> source(
content::WebUIDataSource::Create(chrome::kChromeUIMediaHistoryHost));
source->AddResourcePath("media-data-table.js", IDR_MEDIA_DATA_TABLE_JS);
source->AddResourcePath("media-history.js", IDR_MEDIA_HISTORY_JS);
source->AddResourcePath(
"services/media_session/public/mojom/media_session.mojom-lite.js",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment