Merge changes from topic "ts-syntax-layer" * changes: Convert files to typescript Rename files to preserve history
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts index 811b011..b311efc 100644 --- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts +++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts
@@ -165,6 +165,7 @@ margin-bottom: var(--spacing-l); max-height: var(--relation-chain-max-height, 2em); overflow: hidden; + position: relative; /* for arrowToCurrentChange to have position:absolute and be hidden */ } .commitContainer { display: flex;
diff --git a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.js b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.js index 9b6d674..b28b33a 100644 --- a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.js +++ b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.js
@@ -222,17 +222,6 @@ return GerritNav.getUrlForChangeById(changeNum, project, opt_patchNum); } - _computeChangeContainerClass(currentChange, relatedChange) { - const classes = ['changeContainer']; - if ([relatedChange, currentChange].includes(undefined)) { - return classes; - } - if (this._changesEqual(relatedChange, currentChange)) { - classes.push('thisChange'); - } - return classes.join(' '); - } - /** * Do the given objects describe the same change? Compares the changes by * their numbers.
diff --git a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_html.ts b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_html.ts index 1bc99e6..d4cd0f6 100644 --- a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_html.ts +++ b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_html.ts
@@ -37,9 +37,8 @@ .changeContainer { display: flex; } - .changeContainer.thisChange:before { - content: '➔'; - width: 1.2em; + .arrowToCurrentChange { + position: absolute; } h4, section div { @@ -105,9 +104,15 @@ items="[[_relatedResponse.changes]]" as="related" > - <div - class$="rightIndent [[_computeChangeContainerClass(change, related)]]" - > + <template is="dom-if" if="[[_changesEqual(related, change)]]"> + <span + role="img" + class="arrowToCurrentChange" + aria-label="Arrow marking current change" + >➔</span + > + </template> + <div class="rightIndent changeContainer"> <a href$="[[_computeChangeURL(related._change_number, related.project, related._revision_number)]]" class$="[[_computeLinkClass(related)]]" @@ -131,7 +136,15 @@ items="[[_submittedTogether.changes]]" as="related" > - <div class$="[[_computeChangeContainerClass(change, related)]]"> + <template is="dom-if" if="[[_changesEqual(related, change)]]"> + <span + role="img" + class="arrowToCurrentChange" + aria-label="Arrow marking current change" + >➔</span + > + </template> + <div class="changeContainer"> <a href$="[[_computeChangeURL(related._number, related.project)]]" class$="[[_computeLinkClass(related)]]" @@ -143,6 +156,8 @@ tabindex="-1" title="Submittable" class$="submittableCheck [[_computeLinkClass(related)]]" + role="img" + aria-label="Submittable" >✓</span > </div>
diff --git a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.js b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.js index 7cce9e2..e4aeccf 100644 --- a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.js +++ b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.js
@@ -209,19 +209,6 @@ ]); }); - test('_computeChangeContainerClass', () => { - const change1 = {change_id: 123, _number: 0}; - const change2 = {change_id: 456, _change_number: 1}; - const change3 = {change_id: 123, _number: 2}; - - assert.notEqual(element._computeChangeContainerClass( - change1, change1).indexOf('thisChange'), -1); - assert.equal(element._computeChangeContainerClass( - change1, change2).indexOf('thisChange'), -1); - assert.equal(element._computeChangeContainerClass( - change1, change3).indexOf('thisChange'), -1); - }); - test('_changesEqual', () => { const change1 = {change_id: 123, _number: 0}; const change2 = {change_id: 456, _number: 1};
diff --git a/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper.ts b/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper.ts index 8eeb184..6fc7a17 100644 --- a/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper.ts +++ b/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper.ts
@@ -65,9 +65,8 @@ /** * Get value of the property from wrapped object. Waits for the property * to be initialized if it isn't defined. - * */ - get(name: string) { + get(name: string): Promise<unknown> { if (this._elementHasProperty(name)) { return Promise.resolve(this.element[name]); } @@ -80,7 +79,7 @@ }); this._promises.set(name, promise); } - return this._promises.get(name); + return this._promises.get(name)!; } /**
diff --git a/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator.js b/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator.js deleted file mode 100644 index 72cdc46..0000000 --- a/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator.js +++ /dev/null
@@ -1,176 +0,0 @@ -/** - * @license - * 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. - */ -import '../../shared/gr-js-api-interface/gr-js-api-interface.js'; -import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners.js'; -import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js'; -import {PolymerElement} from '@polymer/polymer/polymer-element.js'; -import {htmlTemplate} from './gr-endpoint-decorator_html.js'; -import {getPluginEndpoints} from '../../shared/gr-js-api-interface/gr-plugin-endpoints.js'; -import {getPluginLoader} from '../../shared/gr-js-api-interface/gr-plugin-loader.js'; - -const INIT_PROPERTIES_TIMEOUT_MS = 10000; - -/** @extends PolymerElement */ -class GrEndpointDecorator extends GestureEventListeners( - LegacyElementMixin( - PolymerElement)) { - static get template() { return htmlTemplate; } - - static get is() { return 'gr-endpoint-decorator'; } - - static get properties() { - return { - name: String, - /** @type {!Map} */ - _domHooks: { - type: Map, - value() { return new Map(); }, - }, - /** - * This map prevents importing the same endpoint twice. - * Without caching, if a plugin is loaded after the loaded plugins - * callback fires, it will be imported twice and appear twice on the page. - * - * @type {!Map} - */ - _initializedPlugins: { - type: Map, - value() { return new Map(); }, - }, - }; - } - - /** @override */ - detached() { - super.detached(); - for (const [el, domHook] of this._domHooks) { - domHook.handleInstanceDetached(el); - } - getPluginEndpoints().onDetachedEndpoint(this.name, this._endpointCallBack); - } - - _initDecoration(name, plugin, slot) { - const el = document.createElement(name); - return this._initProperties(el, plugin, - this.getContentChildren().find( - el => el.nodeName !== 'GR-ENDPOINT-PARAM')) - .then(el => { - const slotEl = slot ? - this.querySelector(`gr-endpoint-slot[name=${slot}]`) : - null; - if (slot && slotEl) { - slotEl.parentNode.insertBefore(el, slotEl.nextSibling); - } else { - this._appendChild(el); - } - return el; - }); - } - - _initReplacement(name, plugin) { - this.getContentChildNodes() - .filter(node => node.nodeName !== 'GR-ENDPOINT-PARAM') - .forEach(node => node.remove()); - const el = document.createElement(name); - return this._initProperties(el, plugin).then( - el => this._appendChild(el)); - } - - _getEndpointParams() { - return Array.from( - this.querySelectorAll('gr-endpoint-param')); - } - - /** - * @param {!Element} el - * @param {!Object} plugin - * @param {!Element=} opt_content - * @return {!Promise<Element>} - */ - _initProperties(el, plugin, opt_content) { - el.plugin = plugin; - if (opt_content) { - el.content = opt_content; - } - const expectProperties = this._getEndpointParams().map(paramEl => { - const helper = plugin.attributeHelper(paramEl); - const paramName = paramEl.getAttribute('name'); - return helper.get('value').then( - value => helper.bind('value', - value => plugin.attributeHelper(el).set(paramName, value)) - ); - }); - let timeoutId; - const timeout = new Promise( - resolve => timeoutId = setTimeout(() => { - console.warn( - 'Timeout waiting for endpoint properties initialization: ' + - `plugin ${plugin.getPluginName()}, endpoint ${this.name}`); - }, INIT_PROPERTIES_TIMEOUT_MS)); - return Promise.race([timeout, Promise.all(expectProperties)]) - .then(() => el) - .finally(() => { - if (timeoutId) clearTimeout(timeoutId); - }); - } - - _appendChild(el) { - return this.root.appendChild(el); - } - - _initModule({moduleName, plugin, type, domHook, slot}) { - const name = plugin.getPluginName() + '.' + moduleName; - if (this._initializedPlugins.get(name)) { - return; - } - let initPromise; - switch (type) { - case 'decorate': - initPromise = this._initDecoration(moduleName, plugin, slot); - break; - case 'replace': - initPromise = this._initReplacement(moduleName, plugin); - break; - } - if (!initPromise) { - console.warn('Unable to initialize module ' + name); - } - this._initializedPlugins.set(name, true); - initPromise.then(el => { - domHook.handleInstanceAttached(el); - this._domHooks.set(el, domHook); - }); - } - - /** @override */ - ready() { - super.ready(); - this._endpointCallBack = this._initModule.bind(this); - getPluginEndpoints().onNewEndpoint(this.name, this._endpointCallBack); - if (this.name) { - getPluginLoader().awaitPluginsLoaded() - .then(() => getPluginEndpoints().getAndImportPlugins(this.name)) - .then(() => - getPluginEndpoints() - .getDetails(this.name) - .forEach(this._initModule, this) - ); - } - } -} - -customElements.define(GrEndpointDecorator.is, GrEndpointDecorator);
diff --git a/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator.ts b/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator.ts new file mode 100644 index 0000000..674297d --- /dev/null +++ b/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator.ts
@@ -0,0 +1,199 @@ +/** + * @license + * 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. + */ +import '../../shared/gr-js-api-interface/gr-js-api-interface'; +import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners'; +import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin'; +import {PolymerElement} from '@polymer/polymer/polymer-element'; +import {htmlTemplate} from './gr-endpoint-decorator_html'; +import { + getPluginEndpoints, + ModuleInfo, +} from '../../shared/gr-js-api-interface/gr-plugin-endpoints'; +import {getPluginLoader} from '../../shared/gr-js-api-interface/gr-plugin-loader'; +import {customElement, property} from '@polymer/decorators'; +import {HookApi, PluginApi} from '../gr-plugin-types'; + +const INIT_PROPERTIES_TIMEOUT_MS = 10000; + +@customElement('gr-endpoint-decorator') +class GrEndpointDecorator extends GestureEventListeners( + LegacyElementMixin(PolymerElement) +) { + static get template() { + return htmlTemplate; + } + + @property({type: String}) + name!: string; + + @property({type: Object}) + _domHooks = new Map<HTMLElement, HookApi>(); + + @property({type: Object}) + _initializedPlugins = new Map<string, boolean>(); + + /** + * This is the callback that the plugin endpoint manager should be calling + * when a new element is registered for this endpoint. It points to + * _initModule(). + */ + _endpointCallBack: (info: ModuleInfo) => void = () => {}; + + /** @override */ + detached() { + super.detached(); + for (const [el, domHook] of this._domHooks) { + domHook.handleInstanceDetached(el); + } + getPluginEndpoints().onDetachedEndpoint(this.name, this._endpointCallBack); + } + + _initDecoration( + name: string, + plugin: PluginApi, + slot?: string + ): Promise<HTMLElement> { + const el = document.createElement(name); + return this._initProperties( + el, + plugin, + this.getContentChildren().find(el => el.nodeName !== 'GR-ENDPOINT-PARAM') + ).then(el => { + const slotEl = slot + ? this.querySelector(`gr-endpoint-slot[name=${slot}]`) + : null; + if (slot && slotEl?.parentNode) { + slotEl.parentNode.insertBefore(el, slotEl.nextSibling); + } else { + this._appendChild(el); + } + return el; + }); + } + + _initReplacement(name: string, plugin: PluginApi): Promise<HTMLElement> { + this.getContentChildNodes() + .filter(node => node.nodeName !== 'GR-ENDPOINT-PARAM') + .forEach(node => (node as ChildNode).remove()); + const el = document.createElement(name); + return this._initProperties(el, plugin).then((el: HTMLElement) => + this._appendChild(el) + ); + } + + _getEndpointParams() { + return Array.from(this.querySelectorAll('gr-endpoint-param')); + } + + _initProperties( + htmlEl: HTMLElement, + plugin: PluginApi, + content?: HTMLElement + ) { + const el = htmlEl as HTMLElement & { + plugin?: PluginApi; + content?: HTMLElement; + }; + el.plugin = plugin; + if (content) { + el.content = content; + } + const expectProperties = this._getEndpointParams().map(paramEl => { + const helper = plugin.attributeHelper(paramEl); + const paramName = paramEl.getAttribute('name'); + if (!paramName) throw Error('plugin endpoint parameter missing a name'); + return helper + .get('value') + .then(() => + helper.bind('value', value => + plugin.attributeHelper(el).set(paramName, value) + ) + ); + }); + // TODO(TS): Should be a number, but TS thinks that is must be some weird + // NodeJS.Timeout object. + let timeoutId: any; + const timeout = new Promise( + () => + (timeoutId = setTimeout(() => { + console.warn( + 'Timeout waiting for endpoint properties initialization: ' + + `plugin ${plugin.getPluginName()}, endpoint ${this.name}` + ); + }, INIT_PROPERTIES_TIMEOUT_MS)) + ); + return Promise.race([timeout, Promise.all(expectProperties)]) + .then(() => el) + .finally(() => { + if (timeoutId) clearTimeout(timeoutId); + }); + } + + _appendChild(el: HTMLElement): HTMLElement { + if (!this.root) throw Error('plugin endpoint decorator missing root'); + return this.root.appendChild(el); + } + + _initModule({moduleName, plugin, type, domHook, slot}: ModuleInfo) { + const name = plugin.getPluginName() + '.' + moduleName; + if (this._initializedPlugins.get(name)) { + return; + } + let initPromise; + switch (type) { + case 'decorate': + initPromise = this._initDecoration(moduleName, plugin, slot); + break; + case 'replace': + initPromise = this._initReplacement(moduleName, plugin); + break; + } + if (!initPromise) { + throw Error(`unknown endpoint type ${type} used by plugin ${name}`); + } + this._initializedPlugins.set(name, true); + initPromise.then(el => { + if (domHook) { + domHook.handleInstanceAttached(el); + this._domHooks.set(el, domHook); + } + }); + } + + /** @override */ + ready() { + super.ready(); + this._endpointCallBack = (info: ModuleInfo) => this._initModule(info); + getPluginEndpoints().onNewEndpoint(this.name, this._endpointCallBack); + if (this.name) { + getPluginLoader() + .awaitPluginsLoaded() + .then(() => getPluginEndpoints().getAndImportPlugins(this.name)) + .then(() => + getPluginEndpoints() + .getDetails(this.name) + .forEach(this._initModule, this) + ); + } + } +} + +declare global { + interface HTMLElementTagNameMap { + 'gr-endpoint-decorator': GrEndpointDecorator; + } +}
diff --git a/polygerrit-ui/app/elements/plugins/gr-plugin-types.ts b/polygerrit-ui/app/elements/plugins/gr-plugin-types.ts index 7ac670b..538c78a 100644 --- a/polygerrit-ui/app/elements/plugins/gr-plugin-types.ts +++ b/polygerrit-ui/app/elements/plugins/gr-plugin-types.ts
@@ -37,6 +37,8 @@ getAllAttached(): HTMLElement[]; getLastAttached(): Promise<HTMLElement>; getModuleName(): string; + handleInstanceDetached(instance: HTMLElement): void; + handleInstanceAttached(instance: HTMLElement): void; } export enum TargetElement {
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints.ts b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints.ts index 2aabf34..3935ef1 100644 --- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints.ts +++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints.ts
@@ -21,7 +21,7 @@ type Callback = (value: any) => void; -interface ModuleInfo { +export interface ModuleInfo { moduleName: string; plugin: PluginApi; pluginUrl?: URL;
diff --git a/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.ts b/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.ts index aabbac1..4ac868d 100644 --- a/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.ts +++ b/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.ts
@@ -63,7 +63,18 @@ if (this.focusableNodes) { return this.focusableNodes; } - return super._focusableNodes; + // TODO(TS): to avoid ts error for: + // Only public and protected methods of the base class are accessible + // via the 'super' keyword. + // we call IronFocsablesHelper directly here + // Currently IronFocsablesHelper is not exported from iron-focusables-helper + // as it should so we use Polymer.IronFocsablesHelper here instead + // (can not use the IronFocsablesHelperClass + // in case different behavior due to singleton) + // once the type contains the exported member, + // should replace with: + // import {IronFocusablesHelper} from '@polymer/iron-overlay-behavior/iron-focusables-helper'; + return (window.Polymer as any).IronFocusablesHelper.getTabbableNodes(this); } /** @override */
diff --git a/polygerrit-ui/app/scripts/bundled-polymer.js b/polygerrit-ui/app/scripts/bundled-polymer.js index 780d82a..6fef454 100644 --- a/polygerrit-ui/app/scripts/bundled-polymer.js +++ b/polygerrit-ui/app/scripts/bundled-polymer.js
@@ -66,6 +66,9 @@ import 'polymer-bridges/polymer/lib/elements/custom-style_bridge.js'; import 'polymer-bridges/polymer/lib/legacy/mutable-data-behavior_bridge.js'; import 'polymer-bridges/polymer/polymer-legacy_bridge.js'; + +// This is needed due to the Polymer.IronFocusablesHelper in gr-overlay.ts +import 'polymer-bridges/iron-overlay-behavior/iron-focusables-helper_bridge.js'; import {importHref} from './import-href.js'; window.Polymer.importHref = importHref;