Commit 011f4ec7 authored by Christopher Lam's avatar Christopher Lam Committed by Commit Bot

[App Management] Push content of expandable-app-list to clients.

This CL pushes the per-row contents of expandable-app-list to being
specified at the client layer, rather than branching with dom-ifs
internally.

Bug: 906508
Change-Id: Icc3b324a408a0a0dd2d487745eda3793e910f143
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1481135
Commit-Queue: calamity <calamity@chromium.org>
Reviewed-by: default avatarAlan Cutter <alancutter@chromium.org>
Cr-Commit-Position: refs/heads/master@{#637968}
parent b5f87d5b
......@@ -116,7 +116,9 @@ if (!is_android) {
js_library("expandable_app_list") {
deps = [
":app_item",
":constants",
":store_client",
"//third_party/polymer/v1_0/components-chromium/iron-collapse:iron-collapse-extracted",
]
}
......
<link rel="import" href="chrome://resources/html/polymer.html">
<link rel="import" href="app_item.html">
<link rel="import" href="constants.html">
<link rel="import" href="shared_style.html">
<link rel="import" href="store_client.html">
<link rel="import" href="permission_toggle.html">
......@@ -10,68 +11,25 @@
<dom-module id="app-management-expandable-app-list">
<template>
<style include="app-management-shared-css">
.app-management-item-arrow {
margin-inline-end: 8px;
padding: 12px;
}
#app-list-title {
padding: 16px 24px;
}
app-management-permission-toggle {
margin-inline-end: 24px;
#collapse {
display: block;
min-height: var(--collapsed-height);
overflow: hidden;
}
</style>
<!-- TODO(ceciliani) Avoid using dom-if, and use slot by getting |items|
from dom-repeat -->
<!-- TODO(calamity) Make a more generic polymer element for expandable
list. -->
<div class="card-container">
<div id="app-list-title" class="header-text">[[listTitle]]</div>
<template is="dom-repeat" items="[[displayedApps]]">
<template is="dom-if" if="[[!notificationsViewSelected_()]]">
<app-management-app-item app="[[item]]">
<paper-icon-button-light slot="right-content"
class="subpage-arrow app-management-item-arrow" actionable>
<button></button>
</paper-icon-button-light>
</app-management-app-item>
</template>
<template is="dom-if" if="[[notificationsViewSelected_()]]">
<app-management-app-item app="[[item]]">
<app-management-permission-toggle slot="right-content"
app="[[item]]"
permission-type="CONTENT_SETTINGS_TYPE_NOTIFICATIONS">
</app-management-permission-toggle>
</app-management-app-item>
</template>
</template>
<iron-collapse opened="[[listExpanded_]]">
<template is="dom-repeat" items="[[collapsedApps]]">
<template is="dom-if" if="[[!notificationsViewSelected_()]]">
<app-management-app-item app="[[item]]">
<paper-icon-button-light slot="right-content"
class="subpage-arrow app-management-item-arrow" actionable>
<button></button>
</paper-icon-button-light>
</app-management-app-item>
</template>
<template is="dom-if" if="[[notificationsViewSelected_()]]">
<app-management-app-item app="[[item]]">
<app-management-permission-toggle slot="right-content"
app="[[item]]"
permission-type="CONTENT_SETTINGS_TYPE_NOTIFICATIONS">
</app-management-permission-toggle>
</app-management-app-item>
</template>
</template>
<iron-collapse id="collapse">
<slot></slot>
</iron-collapse>
<div id="expander-row" class="expander-list-row"
on-click="toggleListExpanded_">
<span>[[moreAppsString_(collapsedApps.length,listExpanded_)]]</span>
<span>[[moreAppsString_(apps.length, listExpanded_)]]</span>
<paper-icon-button-light class="expand-button">
<button>
<iron-icon icon="[[getCollapsedIcon_(listExpanded_)]]">
......
......@@ -2,79 +2,112 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* This is an expanding container for a list of apps that shows some items by
* default and can be expanded to show more.
*
* Note: The implementation assumes children are all the same height.
*
* Example usage:
* <app-management-expandable-app-list apps="[[appsList]]">
* <template is="dom-repeat" items="[[appsList]]" as="app" notify-dom-change>
* <app-management-app-item app="[[app]]"></app-management-app-item>
* </template>
* </app-management-expandable-app-list>
*/
Polymer({
is: 'app-management-expandable-app-list',
behaviors: [
app_management.StoreClient,
],
properties: {
/**
* @private {Page}
*/
currentPage_: {
type: Object,
observer: 'onViewChanged_',
},
/**
* List of apps displayed before expanding the app list.
* @type {Array<App>}
*/
displayedApps: Array,
/**
* Title of the expandable list.
* @type {String}
*/
listTitle: String,
listTitle: {
type: String,
value: '',
observer: 'onListTitleChanged_',
},
/**
* List of apps displayed after expanding app list.
* @type {Array<App>}
*/
collapsedApps: {
type: Array,
observer: 'onAppsChanged_',
/** The number of apps to collapse down to. */
collapsedSize: {
type: Number,
value: NUMBER_OF_APPS_DISPLAYED_DEFAULT,
},
/**
* @private {boolean}
*/
listExpanded_: Boolean,
/** @private {boolean} */
listExpanded_: {
type: Boolean,
observer: 'onListExpandedChanged_',
},
},
listeners: {
'dom-change': 'onDomChange_',
},
attached: function() {
this.watch('currentPage_', state => state.currentPage);
// Hide on reattach.
this.listExpanded_ = false;
this.$.collapse.hide();
this.updateFromStore();
// Recalculate child heights on reattach.
this.onDomChange_();
},
/**
* @private
*/
onAppsChanged_: function() {
this.$['expander-row'].hidden = this.collapsedApps.length === 0;
},
/** @private */
onAppsChanged_: function(change) {},
/**
* Collapse the list when changing a page if it is open so that list is always
* collapsed when entering the page.
* @private
*/
onViewChanged_: function() {
/** @private */
onListTitleChanged_() {
this.$['app-list-title'].hidden = !this.listTitle;
this.listExpanded_ = false;
},
/**
* @private
*/
/** @private */
onDomChange_: function() {
let collapsedHeight = 0;
let numChildren = 0;
for (const child of this.$.collapse.getContentChildren()) {
// Wait until we have an actual child element rather than just the
// dom-repeat.
if (child.tagName == 'DOM-REPEAT' || child.tagName == 'TEMPLATE') {
continue;
}
if (numChildren < this.collapsedSize) {
collapsedHeight += child.offsetHeight;
}
numChildren++;
}
this.style.setProperty(
'--collapsed-height', String(collapsedHeight) + 'px');
this.$['expander-row'].hidden = numChildren <= this.collapsedSize;
},
/** @private */
toggleListExpanded_: function() {
this.listExpanded_ = !this.listExpanded_;
},
/** @private */
onListExpandedChanged_() {
// TODO(calamity): Hiding should display:none after the animation to prevent
// tabbing into hidden items.
const collapse = this.$.collapse;
// Since iron-collapse does not support a 'min-height' property, we force it
// to animate to the collapsed height.
if (this.listExpanded_) {
// Reset the opened state, or show won't work.
collapse.hide();
collapse.show();
} else {
// This technically leaves the collapse open.
collapse.updateSize('var(--collapsed-height)', true);
}
},
/**
* @param {boolean} listExpanded
* @return {string}
......@@ -91,11 +124,8 @@ Polymer({
* @private
*/
moreAppsString_: function(numApps, listExpanded) {
return listExpanded ? loadTimeData.getString('lessApps') :
loadTimeData.getStringF('moreApps', numApps);
},
notificationsViewSelected_: function() {
return this.currentPage_.pageType === PageType.NOTIFICATIONS;
return listExpanded ?
loadTimeData.getString('lessApps') :
loadTimeData.getStringF('moreApps', numApps - this.collapsedSize);
},
});
......@@ -107,6 +107,9 @@ cr.define('app_management', function() {
await this.page.$.flushForTesting();
}
};
/** @type {number} */
this.guid = 0;
}
async getApps() {
......@@ -170,12 +173,16 @@ cr.define('app_management', function() {
openNativeSettings(appId) {}
/**
* @param {string} id
* @param {string} optId
* @param {Object=} optConfig
* @return {!Promise<!App>}
*/
async addApp(id, optConfig) {
this.page.onAppAdded(FakePageHandler.createApp(id, optConfig));
async addApp(optId, optConfig) {
optId = optId || String(this.guid++);
const app = FakePageHandler.createApp(optId, optConfig);
this.page.onAppAdded(app);
await this.$.flushForTesting();
return app;
}
/**
......
......@@ -38,11 +38,23 @@
justify-content: space-between;
padding: 0 24px;
}
.app-management-item-arrow {
margin-inline-end: 8px;
padding: 12px;
}
</style>
<app-management-expandable-app-list
displayed-apps="[[displayedApps_]]"
collapsed-apps="[[collapsedApps_]]"
apps="[[appsList]]"
list-title="$i18n{appListTitle}">
<template is="dom-repeat" items="[[appsList]]" as="app" notify-dom-change>
<app-management-app-item app="[[app]]">
<paper-icon-button-light slot="right-content"
class="subpage-arrow app-management-item-arrow" actionable>
<button></button>
</paper-icon-button-light>
</app-management-app-item>
</template>
</app-management-expandable-app-list>
<div class="card-container">
......
......@@ -22,16 +22,7 @@ Polymer({
* List of apps displayed before expanding the app list.
* @private {Array<App>}
*/
displayedApps_: {
type: Array,
value: () => [],
},
/**
* List of apps displayed after expanding app list.
* @private {Array<App>}
*/
collapsedApps_: {
appsList: {
type: Array,
value: () => [],
},
......@@ -56,10 +47,7 @@ Polymer({
* @private
*/
onAppsChanged_: function() {
const appList = Object.values(this.apps_);
this.displayedApps_ = appList.slice(0, NUMBER_OF_APPS_DISPLAYED_DEFAULT);
this.collapsedApps_ =
appList.slice(NUMBER_OF_APPS_DISPLAYED_DEFAULT, appList.length);
this.appsList = Object.values(this.apps_);
},
/** @private */
......
......@@ -12,6 +12,10 @@
margin-inline-start: 0;
}
app-management-permission-toggle {
margin-inline-end: 24px;
}
#notification-view-header {
align-items: center;
display: flex;
......@@ -35,8 +39,17 @@
<div id="notification-title" class="page-title">$i18n{notifications}</div>
</div>
<app-management-expandable-app-list
displayed-apps="[[displayedApps_]]"
collapsed-apps="[[collapsedApps_]]">
apps="[[appsList_]]"
collapsed-size="[[getCollapsedSize_(appsList_)]]">
<template is="dom-repeat" items="[[appsList_]]"
as="app" notify-dom-change>
<app-management-app-item app="[[app]]">
<app-management-permission-toggle slot="right-content"
app="[[app]]"
permission-type="CONTENT_SETTINGS_TYPE_NOTIFICATIONS">
</app-management-permission-toggle>
</app-management-app-item>
</template>
</app-management-expandable-app-list>
</template>
<script src="notifications_view.js"></script>
......
......@@ -18,12 +18,18 @@ Polymer({
observer: 'onAppsChanged_',
},
/** @private {!Array<!App>} */
appsList_: {
type: Array,
computed: 'calculateAppsList_(allowed_.*, blocked_.*)',
},
/**
* List of apps with notification permission
* displayed before expanding the app list.
* @private {!Array<App>}
*/
displayedApps_: {
allowed_: {
type: Array,
value: () => [],
},
......@@ -33,18 +39,10 @@ Polymer({
* displayed after expanding app list.
* @private {!Array<App>}
*/
collapsedApps_: {
blocked_: {
type: Array,
value: () => [],
},
/**
* @private {boolean}
*/
listExpanded_: {
type: Boolean,
value: false,
},
},
attached: function() {
......@@ -66,15 +64,10 @@ Polymer({
*/
onViewLoaded_: function() {
const state = this.getState();
this.displayedApps_ =
this.allowed_ =
Array.from(state.notifications.allowedIds, id => state.apps[id]);
this.collapsedApps_ =
this.blocked_ =
Array.from(state.notifications.blockedIds, id => state.apps[id]);
if (this.displayedApps_.length === 0) {
this.displayedApps_ = this.collapsedApps_;
this.collapsedApps_ = [];
}
},
/**
......@@ -85,10 +78,8 @@ Polymer({
*/
onAppsChanged_() {
const unhandledAppIds = new Set(Object.keys(this.apps_));
this.displayedApps_ =
this.updateAppList_(this.displayedApps_, unhandledAppIds);
this.collapsedApps_ =
this.updateAppList_(this.collapsedApps_, unhandledAppIds);
this.allowed_ = this.updateAppList_(this.allowed_, unhandledAppIds);
this.blocked_ = this.updateAppList_(this.blocked_, unhandledAppIds);
// If any new apps have been added, append them to the appropriate list.
for (const appId of unhandledAppIds) {
......@@ -100,13 +91,29 @@ Polymer({
}
if (allowed === OptionalBool.kTrue) {
this.displayedApps_.push(app);
this.push('allowed_', app);
} else {
this.collapsedApps_.push(app);
this.push('blocked_', app);
}
}
},
/**
* @private
* @return {!Array<!App>}
*/
calculateAppsList_() {
return this.allowed_.concat(this.blocked_);
},
/**
* @private
* @return {number}
*/
getCollapsedSize_() {
return this.allowed_.length || this.blocked_.length;
},
/**
* Creates a new list of apps with the same order as the original appList,
* but using the updated apps from this.apps_. As each app is added to the
......@@ -129,7 +136,6 @@ Polymer({
/** @private */
onClickBackButton_: function() {
this.listExpanded_ = false;
if (!window.history.state) {
this.dispatch(app_management.actions.changePage(PageType.MAIN));
} else {
......
......@@ -7,22 +7,25 @@
suite('<app-management-main-view>', function() {
let mainView;
let fakeHandler;
let appIdCounter;
let store;
/**
* @param {number} numApps
*/
async function addApps(numApps) {
for (let i = 0; i < numApps; i++) {
await fakeHandler.addApp((appIdCounter++).toString());
await fakeHandler.addApp();
}
}
setup(function() {
appIdCounter = 0;
function getAppItems() {
return mainView.$$('app-management-expandable-app-list')
.querySelectorAll('app-management-app-item');
}
setup(function() {
fakeHandler = setupFakeHandler();
replaceStore();
store = replaceStore();
mainView = document.createElement('app-management-main-view');
replaceBody(mainView);
......@@ -30,41 +33,31 @@ suite('<app-management-main-view>', function() {
test('simple app addition', async function() {
// Ensure there is no apps initially
expectEquals(
0,
mainView.$$('app-management-expandable-app-list')
.root.querySelectorAll('app-management-app-item')
.length);
expectEquals(0, getAppItems().length);
const appId = '1';
await fakeHandler.addApp(appId);
const app = await fakeHandler.addApp();
let appItems = mainView.$$('app-management-expandable-app-list')
.root.querySelectorAll('app-management-app-item');
let appItems = getAppItems();
expectEquals(1, appItems.length);
expectEquals(app.id, appItems[0].app.id);
expectEquals(appId, appItems[0].app.id);
store.setReducersEnabled(false);
appItems[0].click();
const expected = app_management.actions.changePage(PageType.DETAIL, app.id);
assertDeepEquals(expected, store.lastAction);
});
test('more apps bar visibility', async function() {
// The more apps bar shouldn't appear when there are 4 apps.
await addApps(4);
expectEquals(
4,
mainView.$$('app-management-expandable-app-list')
.root.querySelectorAll('app-management-app-item')
.length);
await addApps(NUMBER_OF_APPS_DISPLAYED_DEFAULT);
expectEquals(NUMBER_OF_APPS_DISPLAYED_DEFAULT, getAppItems().length);
expectTrue(mainView.$$('app-management-expandable-app-list')
.$['expander-row']
.hidden);
// The more apps bar appears when there are 5 apps.
await addApps(1);
expectEquals(
5,
mainView.$$('app-management-expandable-app-list')
.root.querySelectorAll('app-management-app-item')
.length);
expectEquals(NUMBER_OF_APPS_DISPLAYED_DEFAULT + 1, getAppItems().length);
expectFalse(mainView.$$('app-management-expandable-app-list')
.$['expander-row']
.hidden);
......
......@@ -19,9 +19,8 @@ suite('<app-management-router>', function() {
setup(async function() {
fakeHandler = setupFakeHandler();
store = new app_management.TestStore();
store = replaceStore();
await fakeHandler.addApp('1');
store.replaceSingleton();
router = document.createElement('app-management-router');
replaceBody(router);
});
......
......@@ -27,13 +27,14 @@ function setupFakeHandler() {
}
/**
* Replace the app management store instance with a new, empty store.
* Replace the app management store instance with a new, empty TestStore.
* @return {app_management.TestStore}
*/
function replaceStore() {
app_management.Store.instance_ = new app_management.Store();
app_management.Store.getInstance().init(
app_management.util.createEmptyState());
let store = new app_management.TestStore();
store.setReducersEnabled(true);
store.replaceSingleton();
return store;
}
/**
......
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