Show a side menu on all 'admin' pages

Whenever an 'admin' route is detected, show gr-admin-view, with a
sub-view in the main content area. This allows a single
sidebar to be shared amongst all admin pages, and allow easy navigation
between sections.

The 'more' dropdown is now just a link that goes to the first admin
item (project list). Design was concerned that there were too many ways
to navigate between sections otherwise.

Future changes will include:
- Mobile consideration
- Dynamically Updating project/group specific pages when viewing a
  project or a group

Change-Id: I3d8622f5bf960235c1feddb602cc7b3edfabd998
This commit is contained in:
Becky Siegel
2017-06-22 14:14:07 -07:00
parent 9aeb616725
commit f40489eb08
12 changed files with 418 additions and 127 deletions

View File

@@ -15,11 +15,72 @@ limitations under the License.
-->
<link rel="import" href="../../../bower_components/polymer/polymer.html">
<link rel="import" href="../../../behaviors/base-url-behavior/base-url-behavior.html">
<link rel="import" href="../../../styles/gr-menu-page-styles.html">
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../shared/gr-page-nav/gr-page-nav.html">
<link rel="import" href="../../shared/gr-placeholder/gr-placeholder.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
<link rel="import" href="../gr-admin-create-project/gr-admin-create-project.html">
<link rel="import" href="../gr-admin-group-list/gr-admin-group-list.html">
<link rel="import" href="../gr-admin-plugin-list/gr-admin-plugin-list.html">
<link rel="import" href="../gr-admin-project-list/gr-admin-project-list.html">
<link rel="import" href="../gr-admin-project/gr-admin-project.html">
<dom-module id="gr-admin-view">
<template>
<style include="shared-styles"></style>
<style include="gr-menu-page-styles"></style>
<gr-page-nav class$="[[_computeLoadingClass(_loading)]]">
<ul class="sectionContent">
<template is="dom-repeat" items="[[_filteredLinks]]">
<li class$="sectionTitle [[_computeSelectedClass(item.view, params)]]">
<a class="title" href="[[_computeLinkURL(item)]]"
rel$="[[_computeLinkRel(item)]]">[[item.name]]</a>
</li>
<template is="dom-repeat" items="[[item.children]]">
<li class$="[[_computeSelectedClass(item.view, params)]]">
<a href="[[_computeLinkURL(item)]]"
rel$="[[_computeLinkRel(item)]]">[[item.name]]</a>
</li>
</template>
</template>
</ul>
</gr-page-nav>
<template is="dom-if" if="[[_showProjectList]]" restamp="true">
<main class="table">
<gr-admin-project-list class="table" params="[[params]]">
</gr-admin-project-list>
</main>
</template>
<template is="dom-if" if="[[_showProjectMain]]" restamp="true">
<main>
<gr-admin-project project="[[params.project]]"></gr-admin-project>
</main>
</template>
<template is="dom-if" if="[[_showGroupList]]" restamp="true">
<main class="table">
<gr-admin-group-list class="table" params="[[params]]">
</gr-admin-group-list>
</main>
</template>
<template is="dom-if" if="[[_showPluginList]]" restamp="true">
<main class="table">
<gr-admin-plugin-list class="table"></gr-admin-plugin-list>
</main>
</template>
<template is="dom-if" if="[[_showCreateProject]]" restamp="true">
<main class="table">
<gr-admin-create-project
params="[[params]]"
id="createProject"></gr-admin-create-project>
</main>
</template>
<template is="dom-if" if="[[params.placeholder]]" restamp="true">
<gr-placeholder title="Admin" path="[[path]]"></gr-placeholder>
</template>
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
</template>
<script src="gr-admin-view.js"></script>
</dom-module>

View File

@@ -14,11 +14,163 @@
(function() {
'use strict';
const ADMIN_LINKS = [{
name: 'Projects',
url: '/admin/projects',
view: 'gr-admin-project-list',
viewableToAll: true,
children: [{
name: 'Create Project',
capability: 'createProject',
section: 'Projects',
url: '/admin/create-project',
view: 'gr-admin-create-project',
}],
}, {
name: 'Groups',
section: 'Groups',
url: '/admin/groups',
view: 'gr-admin-group-list',
children: [{
name: 'Create Group',
capability: 'createGroup',
url: '/admin/create-group',
view: 'gr-admin-create-group',
}],
}, {
name: 'Plugins',
capability: 'viewPlugins',
section: 'Plugins',
url: '/admin/plugins',
view: 'gr-admin-plugin-list',
}];
const ACCOUNT_CAPABILITIES = ['createProject', 'createGroup', 'viewPlugins'];
Polymer({
is: 'gr-admin-view',
properties: {
params: Object,
path: String,
adminView: String,
_filteredLinks: Array,
_showDownload: {
type: Boolean,
value: false,
},
_showCreateProject: Boolean,
_showProjectMain: Boolean,
_showProjectList: Boolean,
_showGroupList: Boolean,
_showPluginList: Boolean,
},
behaviors: [
Gerrit.BaseUrlBehavior,
],
observers: [
'_paramsChanged(params)',
],
attached() {
this.reload();
},
reload() {
return this.$.restAPI.getAccount().then(account => {
this._account = account;
if (!account) {
// Return so that account capabilities don't load with no account.
return this._filteredLinks = this._filterLinks(link => {
return link.viewableToAll;
});
}
this._loadAccountCapabilities();
});
},
_filterLinks(filterFn) {
const links = ADMIN_LINKS.filter(filterFn);
for (const link of links) {
link.children = link.children ? link.children.filter(filterFn) : [];
}
return links;
},
_loadAccountCapabilities() {
return this.$.restAPI.getAccountCapabilities(ACCOUNT_CAPABILITIES)
.then(capabilities => {
this._filteredLinks = this._filterLinks(link => {
return !link.capability ||
capabilities.hasOwnProperty(link.capability);
});
});
},
_computeSideLinks(unformattedLinks) {
const topLevelLinks = unformattedLinks.filter(link => {
return link.topLevel;
});
const nestedLinks = unformattedLinks.filter(link => {
return !link.topLevel;
});
return topLevelLinks.map(item => {
const section = {
name: item.name,
url: item.url,
view: item.view,
};
const newLinks = nestedLinks.filter(group => {
return group.section === section.name;
});
section.links = newLinks;
return section;
});
},
_paramsChanged(params) {
this.set('_showCreateProject',
params.adminView === 'gr-admin-create-project');
this.set('_showProjectMain', params.adminView === 'gr-admin-project');
this.set('_showProjectList',
params.adminView === 'gr-admin-project-list');
this.set('_showGroupList', params.adminView === 'gr-admin-group-list');
this.set('_showPluginList', params.adminView === 'gr-admin-plugin-list');
},
// TODO (beckysiegel): Update these functions after router abstraction is
// updated. They are currently copied from gr-dropdown (and should be
// updated there as well once complete).
_computeURLHelper(host, path) {
return '//' + host + this.getBaseUrl() + path;
},
_computeRelativeURL(path) {
const host = window.location.host;
return this._computeURLHelper(host, path);
},
_computeLinkURL(link) {
if (typeof link.url === 'undefined') {
return '';
}
if (link.target) {
return link.url;
}
return this._computeRelativeURL(link.url);
},
_computeLinkRel(link) {
return link.target ? 'noopener' : null;
},
_computeSelectedClass(itemView, params) {
return itemView === params.adminView ? 'selected' : '';
},
});
})();

View File

@@ -0,0 +1,138 @@
<!DOCTYPE html>
<!--
Copyright (C) 2017 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-admin-view</title>
<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
<script src="../../../bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-admin-view.html">
<script>void(0);</script>
<test-fixture id="basic">
<template>
<gr-admin-view></gr-admin-view>
</template>
</test-fixture>
<script>
suite('gr-admin-view tests', () => {
let element;
let sandbox;
setup(() => {
sandbox = sinon.sandbox.create();
element = fixture('basic');
});
teardown(() => {
sandbox.restore();
});
test('_computeURLHelper', () => {
const path = '/test';
const host = 'http://www.testsite.com';
const computedPath = element._computeURLHelper(host, path);
assert.equal(computedPath, '//http://www.testsite.com/test');
});
test('link URLs', () => {
assert.equal(
element._computeLinkURL({url: '/test'}),
'//' + window.location.host + '/test');
assert.equal(
element._computeLinkURL({url: '/test', target: '_blank'}),
'/test');
});
test('current page gets selected and is displayed', () => {
element._filteredLinks = [{
name: 'Projects',
url: '/admin/projects',
view: 'gr-admin-project-list',
children: [{
url: '/admin/create-project',
name: 'Create Project',
section: 'Projects',
view: 'gr-admin-create-project',
viewableToAll: true,
}],
}];
element.params = {
adminView: 'gr-admin-project-list',
};
flushAsynchronousOperations();
assert.equal(Polymer.dom(element.root).querySelectorAll(
'.selected').length, 1);
assert.ok(element.$$('gr-admin-project-list'));
assert.isNotOk(element.$$('gr-admin-create-project'));
});
test('_filteredLinks admin', done => {
sandbox.stub(element.$.restAPI, 'getAccountCapabilities', () => {
return Promise.resolve({
createGroup: true,
createProject: true,
viewPlugins: true,
});
});
element._loadAccountCapabilities().then(() => {
assert.equal(element._filteredLinks.length, 3);
// Projects
assert.equal(element._filteredLinks[0].children.length, 1);
// Groups
assert.equal(element._filteredLinks[1].children.length, 1);
// Plugins
assert.equal(element._filteredLinks[2].children.length, 0);
done();
});
});
test('_filteredLinks non admin authenticated', done => {
sandbox.stub(element.$.restAPI, 'getAccountCapabilities', () => {
return Promise.resolve({});
});
element._loadAccountCapabilities().then(() => {
assert.equal(element._filteredLinks.length, 2);
// Projects
assert.equal(element._filteredLinks[0].children.length, 0);
// Groups
assert.equal(element._filteredLinks[1].children.length, 0);
done();
});
});
test('_filteredLinks non admin unathenticated', done => {
element.reload().then(() => {
assert.equal(element._filteredLinks.length, 1);
// Projects
assert.equal(element._filteredLinks[0].children.length, 0);
done();
});
});
});
</script>

View File

@@ -13,15 +13,13 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<link rel="import" href="../../../behaviors/base-url-behavior/base-url-behavior.html">
<link rel="import" href="../../../bower_components/polymer/polymer.html">
<link rel="import" href="../../../behaviors/base-url-behavior/base-url-behavior.html">
<link rel="import" href="../../shared/gr-dropdown/gr-dropdown.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
<link rel="import" href="../gr-account-dropdown/gr-account-dropdown.html">
<link rel="import" href="../../shared/gr-dropdown/gr-dropdown.html">
<link rel="import" href="../gr-search-bar/gr-search-bar.html">
<link rel="import" href="../../../styles/shared-styles.html">
<dom-module id="gr-main-header">
<template>
@@ -68,6 +66,9 @@ limitations under the License.
display: inline-block;
position: relative;
}
.linksTitle:hover {
opacity: .75;
}
.rightItems {
align-items: center;
display: flex;
@@ -82,6 +83,10 @@ limitations under the License.
gr-dropdown {
padding: 0.5em;
}
.more {
padding: 1em;
text-decoration: none;
}
.accountContainer:not(.loggedIn):not(.loggedOut) .loginButton,
.accountContainer:not(.loggedIn):not(.loggedOut) gr-account-dropdown,
.accountContainer.loggedIn .loginButton,
@@ -135,11 +140,16 @@ limitations under the License.
</gr-dropdown>
</li>
</template>
<li>
<a class="more linksTitle" href$="[[_computeRelativeURL('/admin/projects')]]">
More</a>
</li>
</ul>
<div class="rightItems">
<gr-search-bar value="{{searchQuery}}" role="search"></gr-search-bar>
<div class="accountContainer" id="accountContainer">
<a class="loginButton" href$="[[_loginURL]]" on-tap="_loginTapHandler">Sign in</a>
<a class="loginButton" href$="[[_loginURL]]"
on-tap="_loginTapHandler">Sign in</a>
<gr-account-dropdown account="[[_account]]"></gr-account-dropdown>
</div>
</div>

View File

@@ -14,33 +14,6 @@
(function() {
'use strict';
const ADMIN_LINKS = [
{
url: '/admin/groups',
name: 'Groups',
},
{
url: '/admin/create-group',
name: 'Create Group',
capability: 'createGroup',
},
{
url: '/admin/projects',
name: 'Projects',
viewableToAll: true,
},
{
url: '/admin/create-project',
name: 'Create Project',
capability: 'createProject',
},
{
url: '/admin/plugins',
name: 'Plugins',
capability: 'viewPlugins',
},
];
const DEFAULT_LINKS = [{
title: 'Changes',
links: [
@@ -116,8 +89,7 @@
},
_links: {
type: Array,
computed: '_computeLinks(_defaultLinks, _userLinks, _adminLinks, ' +
'_docBaseUrl)',
computed: '_computeLinks(_defaultLinks, _userLinks, _docBaseUrl)',
},
_loginURL: {
type: String,
@@ -171,7 +143,7 @@
return '//' + window.location.host + this.getBaseUrl() + path;
},
_computeLinks(defaultLinks, userLinks, adminLinks, docBaseUrl) {
_computeLinks(defaultLinks, userLinks, docBaseUrl) {
const links = defaultLinks.slice();
if (userLinks && userLinks.length > 0) {
links.push({
@@ -179,9 +151,6 @@
links: userLinks,
});
}
if (!adminLinks || !adminLinks.length) {
adminLinks = ADMIN_LINKS.filter(link => link.viewableToAll);
}
const docLinks = this._getDocLinks(docBaseUrl, DOCUMENTATION_LINKS);
if (docLinks.length) {
links.push({
@@ -189,10 +158,6 @@
links: docLinks,
});
}
links.push({
title: 'More',
links: adminLinks,
});
return links;
},
@@ -249,18 +214,6 @@
this._userLinks =
prefs.my.map(this._fixMyMenuItem).filter(this._isSupportedLink);
});
this._loadAccountCapabilities();
},
_loadAccountCapabilities() {
const params = ['createProject', 'createGroup', 'viewPlugins'];
return this.$.restAPI.getAccountCapabilities(params)
.then(capabilities => {
this._adminLinks = ADMIN_LINKS.filter(link => {
return !link.capability ||
capabilities.hasOwnProperty(link.capability);
});
});
},
_fixMyMenuItem(linkObj) {

View File

@@ -77,29 +77,6 @@ limitations under the License.
]);
});
test('_loadAccountCapabilities admin', done => {
sandbox.stub(element.$.restAPI, 'getAccountCapabilities', () => {
return Promise.resolve({
createGroup: true,
createProject: true,
viewPlugins: true,
});
});
element._loadAccountCapabilities().then(() => {
assert.equal(element._adminLinks.length, 5);
done();
});
});
test('_loadAccountCapabilities non admin', done => {
sandbox.stub(element.$.restAPI, 'getAccountCapabilities', () => {
return Promise.resolve({});
});
element._loadAccountCapabilities().then(() => {
assert.equal(element._adminLinks.length, 2);
done();
});
});
test('user links', () => {
const defaultLinks = [{
@@ -113,30 +90,14 @@ limitations under the License.
name: 'Facebook',
url: 'https://facebook.com',
}];
const adminLinks = [{
url: '/admin/groups',
name: 'Groups',
}];
const defaultAdminLink = {
title: 'More',
links: [{
url: '/admin/projects',
name: 'Projects',
viewableToAll: true,
}],
};
// When no admin links are passed, it should use the default.
assert.deepEqual(element._computeLinks(defaultLinks, [], []),
defaultLinks.concat(defaultAdminLink));
assert.deepEqual(element._computeLinks(defaultLinks, []), defaultLinks);
assert.deepEqual(
element._computeLinks(defaultLinks, userLinks, adminLinks),
element._computeLinks(defaultLinks, userLinks),
defaultLinks.concat({
title: 'Your',
links: userLinks,
}, {
title: 'More',
links: adminLinks,
}));
});

View File

@@ -113,7 +113,8 @@
restAPI.getLoggedIn().then(loggedIn => {
if (loggedIn) {
app.params = {
view: 'gr-admin-group-list',
view: 'gr-admin-view',
adminView: 'gr-admin-group-list',
offset: data.params[1] || 0,
filter: null,
};
@@ -127,7 +128,8 @@
restAPI.getLoggedIn().then(loggedIn => {
if (loggedIn) {
app.params = {
view: 'gr-admin-group-list',
view: 'gr-admin-view',
adminView: 'gr-admin-group-list',
offset: data.params.offset,
filter: data.params.filter,
};
@@ -141,7 +143,8 @@
restAPI.getLoggedIn().then(loggedIn => {
if (loggedIn) {
app.params = {
view: 'gr-admin-group-list',
view: 'gr-admin-view',
adminView: 'gr-admin-group-list',
filter: data.params.filter || null,
};
} else {
@@ -157,7 +160,8 @@
if (loggedIn &&
(permission.administrateServer || permission.createProject)) {
app.params = {
view: 'gr-admin-create-project',
view: 'gr-admin-view',
adminView: 'gr-admin-create-project',
};
} else {
page.redirect('/login/' + encodeURIComponent(data.canonicalPath));
@@ -169,7 +173,8 @@
// Matches /admin/projects[,<offset>][/].
page(/^\/admin\/projects(,(\d+))?(\/)?$/, loadUser, data => {
app.params = {
view: 'gr-admin-project-list',
view: 'gr-admin-view',
adminView: 'gr-admin-project-list',
offset: data.params[1] || 0,
filter: null,
};
@@ -177,7 +182,8 @@
page('/admin/projects/q/filter::filter,:offset', loadUser, data => {
app.params = {
view: 'gr-admin-project-list',
view: 'gr-admin-view',
adminView: 'gr-admin-project-list',
offset: data.params.offset,
filter: data.params.filter,
};
@@ -185,7 +191,8 @@
page('/admin/projects/q/filter::filter', loadUser, data => {
app.params = {
view: 'gr-admin-project-list',
view: 'gr-admin-view',
adminView: 'gr-admin-project-list',
filter: data.params.filter || null,
};
});
@@ -193,8 +200,9 @@
// Matches /admin/projects/<project>
page(/^\/admin\/projects\/(.+)$/, loadUser, data => {
app.params = {
view: 'gr-admin-project',
view: 'gr-admin-view',
project: data.params[0],
adminView: 'gr-admin-project',
};
});
@@ -202,7 +210,8 @@
restAPI.getLoggedIn().then(loggedIn => {
if (loggedIn) {
app.params = {
view: 'gr-admin-plugin-list',
view: 'gr-admin-view',
adminView: 'gr-admin-plugin-list',
};
} else {
page.redirect('/login/' + encodeURIComponent(data.canonicalPath));
@@ -214,6 +223,7 @@
restAPI.getLoggedIn().then(loggedIn => {
if (loggedIn) {
data.params.view = 'gr-admin-view';
data.params.placeholder = true;
app.params = data.params;
} else {
page.redirect('/login/' + encodeURIComponent(data.canonicalPath));

View File

@@ -15,14 +15,7 @@ limitations under the License.
-->
<link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="../behaviors/base-url-behavior/base-url-behavior.html">
<link rel="import" href="../behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html">
<link rel="import" href="../styles/app-theme.html">
<link rel="import" href="./admin/gr-admin-create-project/gr-admin-create-project.html">
<link rel="import" href="./admin/gr-admin-group-list/gr-admin-group-list.html">
<link rel="import" href="./admin/gr-admin-plugin-list/gr-admin-plugin-list.html">
<link rel="import" href="./admin/gr-admin-project-list/gr-admin-project-list.html">
<link rel="import" href="./admin/gr-admin-project/gr-admin-project.html">
<link rel="import" href="./admin/gr-admin-view/gr-admin-view.html">
<link rel="import" href="./change-list/gr-change-list-view/gr-change-list-view.html">
<link rel="import" href="./change-list/gr-dashboard-view/gr-dashboard-view.html">
@@ -41,6 +34,9 @@ limitations under the License.
<link rel="import" href="./shared/gr-fixed-panel/gr-fixed-panel.html">
<link rel="import" href="./shared/gr-overlay/gr-overlay.html">
<link rel="import" href="./shared/gr-rest-api-interface/gr-rest-api-interface.html">
<link rel="import" href="../behaviors/base-url-behavior/base-url-behavior.html">
<link rel="import" href="../behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html">
<link rel="import" href="../styles/app-theme.html">
<link rel="import" href="../styles/shared-styles.html">
<script src="../scripts/util.js"></script>
@@ -166,7 +162,8 @@ limitations under the License.
id="createProject"></gr-admin-create-project>
</template>
<template is="dom-if" if="[[_showAdminView]]" restamp="true">
<gr-admin-view path="[[_path]]"></gr-admin-view>
<gr-admin-view path="[[_path]]"
params=[[params]]></gr-admin-view>
</template>
<template is="dom-if" if="[[_showCLAView]]" restamp="true">
<gr-cla-view path="[[_path]]"></gr-cla-view>

View File

@@ -48,13 +48,8 @@
_showChangeView: Boolean,
_showDiffView: Boolean,
_showSettingsView: Boolean,
_showProjectListView: Boolean,
_showAdminProject: Boolean,
_showPluginListView: Boolean,
_createProject: Boolean,
_showAdminView: Boolean,
_showCLAView: Boolean,
_showGroupListView: Boolean,
_viewState: Object,
_lastError: Object,
_lastSearchPage: String,
@@ -141,11 +136,7 @@
this.set('_showChangeView', view === 'gr-change-view');
this.set('_showDiffView', view === 'gr-diff-view');
this.set('_showSettingsView', view === 'gr-settings-view');
this.set('_showGroupListView', view === 'gr-admin-group-list');
this.set('_showProjectListView', view === 'gr-admin-project-list');
this.set('_showAdminProject', view === 'gr-admin-project');
this.set('_showPluginListView', view === 'gr-admin-plugin-list');
this.set('_createProject', view === 'gr-admin-create-project');
this.set('_showAdminView', view === 'gr-admin-view');
this.set('_showCLAView', view === 'gr-cla-view');
if (this.params.justRegistered) {

View File

@@ -13,9 +13,8 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<link rel="import" href="../../../bower_components/polymer/polymer.html">
<link rel="import" href="../../../behaviors/gr-tooltip-behavior/gr-tooltip-behavior.html">
<link rel="import" href="../../../bower_components/polymer/polymer.html">
<link rel="import" href="../../../styles/shared-styles.html">
<dom-module id="gr-page-nav">
@@ -41,11 +40,17 @@ limitations under the License.
border-top: 1px solid transparent;
padding: 0 2em;
}
#nav ::content .subsectionItem {
padding-left: 3em;
}
#nav ::content .hideSubsection {
display: none;
}
#nav ::content li.sectionTitle {
padding: 0 2em 0 1.5em;
}
#nav ::content li.sectionTitle:not(:first-child) {
padding-top: 1em;
margin-top: 1em;
}
#nav ::content .title {
display: flex;

View File

@@ -24,6 +24,12 @@ limitations under the License.
margin: 2em auto;
max-width: 46em;
}
main.table {
margin-top: 0;
margin-right: 0;
margin-left: 14em;
max-width: none;
}
h2.edited:after {
color: #444;
content: ' *';
@@ -36,6 +42,9 @@ limitations under the License.
main {
margin: 2em 0 2em 15em;
}
main.table {
margin-left: 14em;
}
}
@media only screen and (max-width: 53em) {
.loading {
@@ -44,6 +53,9 @@ limitations under the License.
main {
margin: 2em 1em;
}
main.table {
margin: 0;
}
}
</style>
</template>

View File

@@ -35,6 +35,7 @@ limitations under the License.
'admin/gr-admin-plugin-list/gr-admin-plugin-list_test.html',
'admin/gr-admin-project/gr-admin-project_test.html',
'admin/gr-admin-project-list/gr-admin-project-list_test.html',
'admin/gr-admin-view/gr-admin-view_test.html',
'change-list/gr-change-list-item/gr-change-list-item_test.html',
'change-list/gr-change-list-view/gr-change-list-view_test.html',
'change-list/gr-change-list/gr-change-list_test.html',