Migrate UI to Polymer 3
Change-Id: Icdbfdfce47577f87fa71a9f55fb038f8c0c32216
diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/.eslintignore
diff --git a/.eslintrc.json b/.eslintrc.json
new file mode 100644
index 0000000..b586e29
--- /dev/null
+++ b/.eslintrc.json
@@ -0,0 +1,166 @@
+{
+ "extends": ["eslint:recommended", "google"],
+ "parserOptions": {
+ "ecmaVersion": 8,
+ "sourceType": "module"
+ },
+ "env": {
+ "browser": true,
+ "es6": true
+ },
+ "globals": {
+ "__dirname": false,
+ "app": false,
+ "page": false,
+ "Polymer": false,
+ "process": false,
+ "require": false,
+ "Gerrit": false,
+ "Promise": false,
+ "assert": false,
+ "test": false,
+ "flushAsynchronousOperations": false
+ },
+ "rules": {
+ "arrow-parens": ["error", "as-needed"],
+ "block-spacing": ["error", "always"],
+ "brace-style": ["error", "1tbs", { "allowSingleLine": true }],
+ "camelcase": "off",
+ "comma-dangle": ["error", {
+ "arrays": "always-multiline",
+ "objects": "always-multiline",
+ "imports": "always-multiline",
+ "exports": "always-multiline",
+ "functions": "never"
+ }],
+ "eol-last": "off",
+ "indent": ["error", 2, {
+ "MemberExpression": 2,
+ "FunctionDeclaration": {"body": 1, "parameters": 2},
+ "FunctionExpression": {"body": 1, "parameters": 2},
+ "CallExpression": {"arguments": 2 },
+ "ArrayExpression": 1,
+ "ObjectExpression": 1,
+ "SwitchCase": 1
+ }],
+ "keyword-spacing": ["error", { "after": true, "before": true }],
+ "lines-between-class-members": ["error", "always"],
+ "max-len": [
+ "error",
+ 80,
+ 2,
+ {
+ "ignoreComments": true,
+ "ignorePattern": "^import .*;$"
+ }
+ ],
+ "new-cap": ["error", { "capIsNewExceptions": ["Polymer", "LegacyElementMixin", "GestureEventListeners", "LegacyDataMixin"] }],
+ "no-console": "off",
+ "no-multiple-empty-lines": [ "error", { "max": 1 } ],
+ "no-prototype-builtins": "off",
+ "no-redeclare": "off",
+ "no-restricted-syntax": [
+ "error",
+ {
+ "selector": "ExpressionStatement > CallExpression > MemberExpression[object.name='test'][property.name='only']",
+ "message": "Remove test.only."
+ },
+ {
+ "selector": "ExpressionStatement > CallExpression > MemberExpression[object.name='suite'][property.name='only']",
+ "message": "Remove suite.only."
+ }
+ ],
+ "no-undef": "off",
+ "no-useless-escape": "off",
+ "no-var": "error",
+ "object-shorthand": ["error", "always"],
+ "padding-line-between-statements": [
+ "error",
+ {
+ "blankLine": "always",
+ "prev": "class",
+ "next": "*"
+ },
+ {
+ "blankLine": "always",
+ "prev": "*",
+ "next": "class"
+ }
+ ],
+ "prefer-arrow-callback": "error",
+ "prefer-const": "error",
+ "prefer-spread": "error",
+ "quote-props": ["error", "consistent-as-needed"],
+ "require-jsdoc": "off",
+ "semi": [2, "always"],
+ "template-curly-spacing": "error",
+ "valid-jsdoc": "off",
+ "require-jsdoc": 0,
+ "valid-jsdoc": 0,
+ "jsdoc/check-alignment": 2,
+ "jsdoc/check-examples": 0,
+ "jsdoc/check-indentation": 0,
+ "jsdoc/check-param-names": 0,
+ "jsdoc/check-syntax": 0,
+ "jsdoc/check-tag-names": 0,
+ "jsdoc/check-types": 0,
+ "jsdoc/implements-on-classes": 2,
+ "jsdoc/match-description": 0,
+ "jsdoc/newline-after-description": 2,
+ "jsdoc/no-types": 0,
+ "jsdoc/no-undefined-types": 0,
+ "jsdoc/require-description": 0,
+ "jsdoc/require-description-complete-sentence": 0,
+ "jsdoc/require-example": 0,
+ "jsdoc/require-hyphen-before-param-description": 0,
+ "jsdoc/require-jsdoc": 0,
+ "jsdoc/require-param": 0,
+ "jsdoc/require-param-description": 0,
+ "jsdoc/require-param-name": 2,
+ "jsdoc/require-param-type": 2,
+ "jsdoc/require-returns": 0,
+ "jsdoc/require-returns-check": 0,
+ "jsdoc/require-returns-description": 0,
+ "jsdoc/require-returns-type": 2,
+ "jsdoc/valid-types": 2,
+ "jsdoc/require-file-overview": ["error", {
+ "tags": {
+ "license": {
+ "mustExist": true,
+ "preventDuplicates": true
+ }
+ }
+ }],
+ "import/named": 2,
+ "import/no-unresolved": 2,
+ "import/no-self-import": 2,
+ // The no-cycle rule is slow, because it doesn't cache dependencies.
+ // Disable it.
+ "import/no-cycle": 0,
+ "import/no-useless-path-segments": 2,
+ "import/no-unused-modules": 2,
+ "import/no-default-export": 2
+ },
+ "plugins": [
+ "html",
+ "jsdoc",
+ "import"
+ ],
+ "settings": {
+ "html/report-bad-indent": "error"
+ },
+ "overrides": [
+ {
+ "files": ["*_html.js", "*-styles.js"],
+ "rules": {
+ "max-len": "off"
+ }
+ },
+ {
+ "files": ["*.html"],
+ "rules": {
+ "jsdoc/require-file-overview": "off"
+ }
+ }
+ ]
+}
diff --git a/BUILD b/BUILD
index 7b71db2..e73191c 100644
--- a/BUILD
+++ b/BUILD
@@ -1,3 +1,7 @@
+load("@npm_bazel_rollup//:index.bzl", "rollup_bundle")
+load("//tools/bzl:js.bzl", "polygerrit_plugin")
+load("//tools/bzl:genrule2.bzl", "genrule2")
+load("//tools/js:eslint.bzl", "eslint")
load("//tools/bzl:plugin.bzl", "gerrit_plugin")
gerrit_plugin(
@@ -9,4 +13,56 @@
"Gerrit-HttpModule: com.googlesource.gerrit.plugins.imagare.HttpModule",
],
resources = glob(["src/main/**/*"]),
+ resource_jars = [":gr-imagare-static"],
+)
+
+genrule2(
+ name = "gr-imagare-static",
+ srcs = [":gr-imagare"],
+ outs = ["gr-imagare-static.jar"],
+ cmd = " && ".join([
+ "mkdir $$TMP/static",
+ "cp -r $(locations :gr-imagare) $$TMP/static",
+ "cd $$TMP",
+ "zip -Drq $$ROOT/$@ -g .",
+ ]),
+)
+
+rollup_bundle(
+ name = "imagare-bundle",
+ srcs = glob(["gr-imagare/*.js"]),
+ entry_point = "gr-imagare/gr-imagare.js",
+ rollup_bin = "//tools/node_tools:rollup-bin",
+ sourcemap = "hidden",
+ format = 'iife',
+ deps = [
+ "@tools_npm//rollup-plugin-node-resolve",
+ ],
+)
+
+polygerrit_plugin(
+ name = "gr-imagare",
+ app = "imagare-bundle.js",
+ plugin_name = "imagare",
+)
+
+# Define the eslinter for the plugin
+# The eslint macro creates 2 rules: lint_test and lint_bin
+eslint(
+ name = "lint",
+ srcs = glob([
+ "gr-imagare/**/*.js",
+ ]),
+ config = ".eslintrc.json",
+ data = [],
+ extensions = [
+ ".js",
+ ],
+ ignore = ".eslintignore",
+ plugins = [
+ "@npm//eslint-config-google",
+ "@npm//eslint-plugin-html",
+ "@npm//eslint-plugin-import",
+ "@npm//eslint-plugin-jsdoc",
+ ],
)
diff --git a/gr-imagare/gr-imagare-inline.js b/gr-imagare/gr-imagare-inline.js
new file mode 100644
index 0000000..761d0ef
--- /dev/null
+++ b/gr-imagare/gr-imagare-inline.js
@@ -0,0 +1,207 @@
+/**
+ * @license
+ * Copyright (C) 2016 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.
+ */
+
+const LINK_DECORATIONS = {
+ NONE: 1,
+ INLINE: 2,
+ TOOLTIP: 3,
+};
+
+class GrImagareInline extends Polymer.GestureEventListeners(
+ Polymer.LegacyElementMixin(
+ Polymer.Element)) {
+ /** @returns {?} template for this component */
+ static get template() { return Polymer.html``; }
+
+ /** @returns {string} name of the component */
+ static get is() { return 'gr-imagare-inline'; }
+
+ /**
+ * Defines properties of the component
+ *
+ * @returns {?}
+ */
+ static get properties() {
+ return {
+ _expandedObserver: MutationObserver,
+ _messageAddedObserver: MutationObserver,
+ _messages: Object,
+ _link_decoration: Number,
+ _pattern: String,
+ _decorator_fn: Function,
+ };
+ }
+
+ attached() {
+ super.attached();
+ this._getAccountPrefs().then(() => {
+ if (this._link_decoration === LINK_DECORATIONS.NONE) {
+ return;
+ }
+
+ this._expandedObserver = new MutationObserver(mutations => {
+ mutations.forEach(mut => {
+ if (!mut.target.classList.contains('expanded')) {
+ return;
+ }
+ const links = this._getLinksFromMessage(mut.target);
+
+ if (!links) {
+ return;
+ }
+
+ for (const link of links) {
+ this._decorator_fn(link);
+ }
+ });
+ });
+
+ this._messageAddedObserver = new MutationObserver(mutations => {
+ mutations.forEach(mut => {
+ mut.addedNodes.forEach(node => {
+ if (node.tagName === 'GR-MESSAGE') {
+ this._addExpandedObservers(node);
+ }
+ });
+ });
+ });
+
+ this._messageAddedObserver.observe(
+ util.querySelector(document.body, 'gr-messages-list'),
+ {
+ childList: true,
+ });
+
+ this._addObserversToMessages();
+ });
+ }
+
+ detached() {
+ super.detached();
+ this._expandedObserver.disconnect();
+ this._messageAddedObserver.disconnect();
+ }
+
+ _addObserversToMessages() {
+ this._messages = this._getMessages();
+
+ if (!this._messages) {
+ return;
+ }
+
+ for (const message of this._messages) {
+ this._addExpandedObservers(message);
+ }
+ }
+
+ _addExpandedObservers(message) {
+ this._expandedObserver.observe(message, {
+ attributes: true,
+ attributeOldValue: true,
+ attributFilter: ['class'],
+ });
+ }
+
+ _getAccountPrefs() {
+ return this.plugin.restApi('/accounts/self/imagare~preference')
+ .get('')
+ .then(prefs => {
+ if (!prefs || !prefs.link_decoration) {
+ this._link_decoration = LINK_DECORATIONS.NONE;
+ this._pattern = '.*';
+ } else {
+ this._link_decoration = LINK_DECORATIONS[
+ prefs.link_decoration.toUpperCase()
+ ];
+ this._pattern = prefs.pattern || '.*';
+ }
+
+ switch (this._link_decoration) {
+ case LINK_DECORATIONS.INLINE:
+ this._decorator_fn = this._insertImage.bind(this);
+ break;
+ case LINK_DECORATIONS.TOOLTIP:
+ this._decorator_fn = this._addTooltip.bind(this);
+ break;
+ case LINK_DECORATIONS.NONE:
+ default:
+ this._decorator_fn = () => {};
+ }
+ });
+ }
+
+ _getMessages() {
+ const messageList = util.querySelector(document.body, 'gr-messages-list');
+ if (messageList) {
+ return util.querySelectorAll(messageList, 'gr-message');
+ }
+ }
+
+ _getLinksFromMessage(message) {
+ let links = [];
+ const linkedTexts = util.querySelectorAll(message, 'gr-linked-text');
+ for (const e of linkedTexts) {
+ const aTags = util.querySelectorAll(e, 'a');
+ if (aTags && aTags.length > 0) {
+ for (const a of aTags) {
+ if (util.querySelectorAll(a, 'img').length > 0) {
+ continue;
+ }
+ if (!a.href.match(this._pattern)) {
+ continue;
+ }
+
+ links = links.concat(a);
+ }
+ }
+ }
+ return links.length > 0 ? links : null;
+ }
+
+ _createImage(url) {
+ const img = document.createElement('img');
+ img.setAttribute('src', url);
+ img.setAttribute('style', 'max-width: 100%; height: auto;');
+
+ return img;
+ }
+
+ _insertImage(link) {
+ if (!link) {
+ return;
+ }
+
+ link.replaceWith(this._createImage(link.href));
+ }
+
+ _addTooltip(link) {
+ if (!link) {
+ return;
+ }
+
+ link.onmouseover = event => {
+ const img = this._createImage(link.href);
+ img.onmouseout = event => {
+ event.target.replaceWith(link);
+ };
+
+ event.target.replaceWith(img);
+ };
+ }
+}
+
+customElements.define(GrImagareInline.is, GrImagareInline);
diff --git a/gr-imagare/gr-imagare-list-item.js b/gr-imagare/gr-imagare-list-item.js
new file mode 100644
index 0000000..1c2f228
--- /dev/null
+++ b/gr-imagare/gr-imagare-list-item.js
@@ -0,0 +1,126 @@
+/**
+ * @license
+ * Copyright (C) 2016 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 {htmlTemplate} from './gr-imagare-list-item_html.js';
+
+class GrImagareListItem extends Polymer.GestureEventListeners(
+ Polymer.LegacyElementMixin(
+ Polymer.Element)) {
+ /** @returns {?} template for this component */
+ static get template() { return htmlTemplate; }
+
+ /** @returns {string} name of the component */
+ static get is() { return 'gr-imagare-list-item'; }
+
+ /**
+ * Defines properties of the component
+ *
+ * @returns {?}
+ */
+ static get properties() {
+ return {
+ imageUrl: {
+ type: String,
+ reflectToAttribute: true,
+ },
+ imageName: {
+ type: String,
+ reflectToAttribute: true,
+ },
+ imageData: {
+ type: String,
+ reflectToAttribute: true,
+ },
+ uploaded: {
+ type: Boolean,
+ observer: '_uploadedChanged',
+ reflectToAttribute: true,
+ },
+ _originalImageName: String,
+ _editing: {
+ type: Boolean,
+ value: false,
+ },
+ _imageSrc: String,
+ };
+ }
+
+ attached() {
+ super.attached();
+ this._originalImageName = this.imageName;
+ this._setImage();
+ }
+
+ _handleCancelRenameName() {
+ this.imageName = this._originalImageName;
+ this._editing = false;
+ }
+
+ _handleClearImage() {
+ this.fire('clear');
+ }
+
+ _handleDeleteImage() {
+ this.fire('delete');
+ }
+
+ _handleEditImage() {
+ this._editing = true;
+ }
+
+ _handleSaveName() {
+ this._editing = false;
+
+ if (this._originalImageName === this.imageName) {
+ return;
+ }
+
+ const oldFileType = this._originalImageName.split('.').pop();
+ const newFileType = this.imageName.split('.').pop();
+ if (oldFileType !== newFileType) {
+ this.imageName += `.${oldFileType}`;
+ }
+
+ this.fire(
+ 'editName',
+ {oldName: this._originalImageName, newName: this.imageName});
+ }
+
+ _handleUploadImage() {
+ this.fire('upload');
+ }
+
+ _openDeleteDialog() {
+ this.$.deleteOverlay.open();
+ }
+
+ _setImage() {
+ if (this.uploaded) {
+ this.$.thumbnail.setAttribute('src', this.imageUrl);
+ } else {
+ this.$.thumbnail.setAttribute('src', this.imageData);
+ }
+ }
+
+ _uploadedChanged(uploaded) {
+ this.$.uploading.hidden = !uploaded;
+ this.$.staging.hidden = uploaded;
+ this._setImage();
+ }
+}
+
+customElements.define(GrImagareListItem.is, GrImagareListItem);
diff --git a/src/main/resources/static/gr-imagare-list-item.html b/gr-imagare/gr-imagare-list-item_html.js
similarity index 80%
rename from src/main/resources/static/gr-imagare-list-item.html
rename to gr-imagare/gr-imagare-list-item_html.js
index 2f66329..63ae3f2 100644
--- a/src/main/resources/static/gr-imagare-list-item.html
+++ b/gr-imagare/gr-imagare-list-item_html.js
@@ -1,19 +1,21 @@
-<!--
-@license
-Copyright (C) 2019 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.
--->
+/**
+ * @license
+ * Copyright (C) 2019 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.
+ */
-<dom-module id="gr-imagare-list-item">
- <template>
+export const htmlTemplate = Polymer.html`
<style include="shared-styles"></style>
<style include="gr-subpage-styles"></style>
<style>
@@ -130,6 +132,4 @@
</gr-overlay>
</div>
</div>
- </template>
- <script src="gr-imagare-list-item.js"></script>
-</dom-module>
+`;
diff --git a/gr-imagare/gr-imagare-pref-menu-item.js b/gr-imagare/gr-imagare-pref-menu-item.js
new file mode 100644
index 0000000..39bf2d7
--- /dev/null
+++ b/gr-imagare/gr-imagare-pref-menu-item.js
@@ -0,0 +1,30 @@
+/**
+ * @license
+ * Copyright (C) 2016 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 {htmlTemplate} from './gr-imagare-pref-menu-item_html.js';
+
+class GrImagarePrefMenuItem extends Polymer.GestureEventListeners(
+ Polymer.LegacyElementMixin(
+ Polymer.Element)) {
+ /** @returns {?} template for this component */
+ static get template() { return htmlTemplate; }
+
+ /** @returns {string} name of the component */
+ static get is() { return 'gr-imagare-pref-menu-item'; }
+}
+
+customElements.define(GrImagarePrefMenuItem.is, GrImagarePrefMenuItem);
diff --git a/gr-imagare/gr-imagare-pref-menu-item_html.js b/gr-imagare/gr-imagare-pref-menu-item_html.js
new file mode 100644
index 0000000..b3a7c6b
--- /dev/null
+++ b/gr-imagare/gr-imagare-pref-menu-item_html.js
@@ -0,0 +1,26 @@
+/**
+ * @license
+ * Copyright (C) 2019 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.
+ */
+
+export const htmlTemplate = Polymer.html`
+ <style include="shared-styles"></style>
+ <style include="gr-page-nav-styles"></style>
+ <div class="navStyles">
+ <li>
+ <a href="#ImagarePreferences">Imagare Preferences</a>
+ </li>
+ </div>
+`;
diff --git a/gr-imagare/gr-imagare-preferences.js b/gr-imagare/gr-imagare-preferences.js
new file mode 100644
index 0000000..25ec08d
--- /dev/null
+++ b/gr-imagare/gr-imagare-preferences.js
@@ -0,0 +1,118 @@
+/**
+ * @license
+ * Copyright (C) 2019 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 {htmlTemplate} from './gr-imagare-preferences_html.js';
+
+class GrImagarePreferences extends Polymer.GestureEventListeners(
+ Polymer.LegacyElementMixin(
+ Polymer.Element)) {
+ /** @returns {?} template for this component */
+ static get template() { return htmlTemplate; }
+
+ /** @returns {string} name of the component */
+ static get is() { return 'gr-imagare-preferences'; }
+
+ /**
+ * Defines properties of the component
+ *
+ * @returns {?}
+ */
+ static get properties() {
+ return {
+ _defaultImageProject: String,
+ _linkDecoration: String,
+ _stageImages: Boolean,
+ _prefsChanged: {
+ type: Boolean,
+ value: false,
+ },
+ _query: {
+ type: Function,
+ value() {
+ return this._queryProjects.bind(this);
+ },
+ },
+ };
+ }
+
+ attached() {
+ super.attached();
+ this._getUserPreferences();
+ }
+
+ _getUserPreferences() {
+ this.plugin.restApi('/accounts/self/')
+ .get(`imagare~preference`)
+ .then(config => {
+ if (!config) {
+ return;
+ }
+
+ this._linkDecoration = config.link_decoration;
+ this._defaultImageProject = config.default_project;
+ this._stageImages = config.stage;
+ }).catch(response => {
+ this.fire('show-error', {message: response});
+ });
+ }
+
+ _handleImagarePrefsSave() {
+ this.plugin.restApi('/accounts/self/')
+ .put(`imagare~preference`, {
+ default_project: this._defaultImageProject,
+ link_decoration: this._linkDecoration,
+ stage: this._stageImages,
+ }).then(() => {
+ this._prefsChanged = false;
+ }).catch(response => {
+ this.fire('show-error', {message: response});
+ });
+ }
+
+ _handlePrefsChanged() {
+ this._prefsChanged = true;
+ }
+
+ _handleStageImagesChanged(event) {
+ this._handlePrefsChanged();
+ this._stageImages = event.target.checked;
+ }
+
+ _queryProjects(input) {
+ let query;
+ if (!input || input === this._defaultImageProject) {
+ query = '';
+ } else {
+ query = `?prefix=${input}`;
+ }
+
+ return this.plugin.restApi('/a/projects/').get(query)
+ .then(response => {
+ const projects = [];
+ for (const key in response) {
+ if (!response.hasOwnProperty(key)) { continue; }
+ projects.push({
+ name: key,
+ value: decodeURIComponent(response[key].id),
+ });
+ }
+ return projects;
+ });
+ }
+}
+
+customElements.define(GrImagarePreferences.is, GrImagarePreferences);
diff --git a/src/main/resources/static/gr-imagare-preferences.html b/gr-imagare/gr-imagare-preferences_html.js
similarity index 67%
rename from src/main/resources/static/gr-imagare-preferences.html
rename to gr-imagare/gr-imagare-preferences_html.js
index ae26388..4dc8072 100644
--- a/src/main/resources/static/gr-imagare-preferences.html
+++ b/gr-imagare/gr-imagare-preferences_html.js
@@ -1,19 +1,21 @@
-<!--
-@license
-Copyright (C) 2019 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.
--->
+/**
+ * @license
+ * Copyright (C) 2019 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.
+ */
-<dom-module id="gr-imagare-preferences">
- <template>
+export const htmlTemplate = Polymer.html`
<style include="shared-styles"></style>
<style include="gr-form-styles"></style>
<h2 id="ImagarePreferences">Imagare Preferences</h2>
@@ -55,6 +57,4 @@
Save Changes
</gr-button>
</fieldset>
- </template>
- <script src="gr-imagare-preferences.js"></script>
-</dom-module>
+`;
diff --git a/gr-imagare/gr-imagare-upload.js b/gr-imagare/gr-imagare-upload.js
new file mode 100644
index 0000000..2a3b125
--- /dev/null
+++ b/gr-imagare/gr-imagare-upload.js
@@ -0,0 +1,393 @@
+/**
+ * @license
+ * Copyright (C) 2019 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 './gr-imagare-list-item.js';
+import {htmlTemplate} from './gr-imagare-upload_html.js';
+
+function preventDefaultFn(event) {
+ event.preventDefault();
+}
+
+class GrImagareUpload extends Polymer.GestureEventListeners(
+ Polymer.LegacyElementMixin(
+ Polymer.Element)) {
+ /** @returns {?} template for this component */
+ static get template() { return htmlTemplate; }
+
+ /** @returns {string} name of the component */
+ static get is() { return 'gr-imagare-upload'; }
+
+ /**
+ * Defines properties of the component
+ *
+ * @returns {?}
+ */
+ static get properties() {
+ return {
+ _loading: {
+ type: Boolean,
+ value: true,
+ },
+ _allUploaded: {
+ type: Boolean,
+ value: false,
+ },
+ _imageProject: String,
+ _defaultImageProject: String,
+ _images: {
+ type: Map,
+ value: () => new Map(),
+ },
+ _stageImages: {
+ type: Boolean,
+ value: true,
+ },
+ _undefinedFileCounter: {
+ type: Number,
+ value: 0,
+ },
+ _query: {
+ type: Function,
+ value() {
+ return this._queryProjects.bind(this);
+ },
+ },
+ };
+ }
+
+ static get listeners() {
+ return {
+ clear: '_handleClearImage',
+ delete: '_handleDeleteImage',
+ editName: '_handleEditImageName',
+ upload: '_handleUploadImage',
+ };
+ }
+
+ attached() {
+ super.attached();
+ this.fire('title-change', {title: 'Image Upload'});
+
+ window.addEventListener('dragover', preventDefaultFn, false);
+ window.addEventListener('drop', preventDefaultFn, false);
+ this.$.dragDropArea.addEventListener('paste', preventDefaultFn, false);
+
+ this._getUserPreferences();
+ }
+
+ detached() {
+ window.removeEventListener('dragover', preventDefaultFn, false);
+ window.removeEventListener('drop', preventDefaultFn, false);
+ this.$.dragDropArea.removeEventListener('paste', preventDefaultFn, false);
+ }
+
+ _computeFilenameWithCorrectType(filedata, filename) {
+ const realFiletype = filedata.slice(
+ filedata.indexOf('/') + 1,
+ filedata.indexOf(';'));
+
+ let givenFiletype;
+
+ if (filename.indexOf('.') !== -1) {
+ givenFiletype = filename.split('.').pop();
+ }
+
+ if (!givenFiletype || realFiletype !== givenFiletype) {
+ filename += `.${realFiletype}`;
+ }
+
+ return filename;
+ }
+
+ _computeLoadingClass(loading) {
+ return loading ? 'loading' : '';
+ }
+
+ _computeSettingsUrl() {
+ return `${location.origin}/settings#ImagarePreferences`;
+ }
+
+ _computeUploadAllDisabled() {
+ if (this._images) {
+ for (const value of this._images.values()) {
+ if (!value.uploaded) {
+ this._allUploaded = false;
+ return;
+ }
+ }
+ }
+
+ this._allUploaded = true;
+ }
+
+ _createImageObject(name, data, url, list_entry, uploaded, ref) {
+ return {data, list_entry, name, ref, url, uploaded};
+ }
+
+ _createListEntry(name, data, url) {
+ const imagePanel = document.createElement('gr-imagare-list-item');
+ imagePanel.setAttribute('image-name', name);
+
+ if (data) {
+ imagePanel.setAttribute('image-data', data);
+ }
+
+ if (url) {
+ imagePanel.setAttribute('image-url', url);
+ imagePanel.uploaded = true;
+ } else {
+ imagePanel.uploaded = false;
+ }
+
+ this.$.imageList.appendChild(imagePanel);
+
+ return imagePanel;
+ }
+
+ _deleteImage(image) {
+ this.plugin.restApi('/projects')
+ .delete(`/${this._imageProject}/imagare~images/${image.ref}`)
+ .then(() => {
+ image.list_entry.remove();
+ this._images.delete(image.name);
+ if (!this.$.imageList.hasChildNodes()) {
+ this.$.imageListContainer.hidden = true;
+ }
+ }).catch(response => {
+ this.fire('show-error', {message: response});
+ });
+ }
+
+ _extractImageRef(url) {
+ return url.split('/').slice(-2)[0];
+ }
+
+ _getUserPreferences() {
+ this.plugin.restApi('/accounts/self/')
+ .get(`imagare~preference`)
+ .then(config => {
+ if (!config) {
+ return;
+ }
+
+ this._defaultImageProject = config.default_project;
+ this._imageProject = config.default_project;
+ this._stageImages = config.stage;
+ this._loading = false;
+ });
+ }
+
+ _handleClearAllImages() {
+ while (this.$.imageList.firstChild) {
+ this.$.imageList.removeChild(this.$.imageList.firstChild);
+ }
+ this.$.imageListContainer.hidden = true;
+
+ this._images.clear();
+ }
+
+ _handleClearImage(event) {
+ event.stopPropagation();
+ this._images.delete(event.target.imageName);
+ event.target.remove();
+ if (!this.$.imageList.hasChildNodes()) {
+ this.$.imageListContainer.hidden = true;
+ }
+ }
+
+ _handleDeleteImage(event) {
+ event.stopPropagation();
+ this._deleteImage(this._images.get(event.target.imageName));
+ }
+
+ _handleDrop(event) {
+ event.preventDefault();
+ event.stopPropagation();
+
+ for (const file of event.dataTransfer.files) {
+ if (!file.type.match('image/.*')) {
+ this.fire('show-error', {message: `No image file: ${file.name}`});
+ }
+ const fr = new FileReader();
+ fr.file = file;
+ fr.onload = fileLoadEvent => this._handleFileLoadEvent(
+ fr.file.name, fileLoadEvent);
+ fr.readAsDataURL(file);
+ }
+ }
+
+ _handleEditImageName(event) {
+ event.stopPropagation();
+ const editedImage = this._images.get(event.detail.oldName);
+ if (this._images.has(event.detail.newName)) {
+ this.fire(
+ 'show-error',
+ {message: 'An image with the same name was already staged.'});
+ editedImage.list_entry.setAttribute('image-name', event.detail.oldName);
+ } else {
+ editedImage.name = event.detail.newName;
+ this._images.set(editedImage.name, editedImage);
+ this._images.delete(event.detail.oldName);
+ }
+ }
+
+ _handleFileLoadEvent(filename, event) {
+ const correctedFilename = this._computeFilenameWithCorrectType(
+ event.target.result, filename);
+ if (this._stageImages) {
+ this._stageImage(correctedFilename, event.target.result);
+ } else {
+ const image = this._createImageObject(
+ correctedFilename, event.target.result);
+ this._images.set(correctedFilename, image);
+ this._uploadImage(image);
+ }
+ }
+
+ _handleKeyPress(event) {
+ const ctrlDown = event.ctrlKey || event.metaKey;
+ if (!ctrlDown) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
+
+ _handleImagePathChanged(event) {
+ for (const file of event.target.files) {
+ const fr = new FileReader();
+ fr.file = file;
+ fr.onload = fileLoadEvent => this._handleFileLoadEvent(
+ fr.file.name, fileLoadEvent);
+ fr.readAsDataURL(file);
+ }
+
+ event.target.value = '';
+ }
+
+ _handlePaste(event) {
+ const clipboardData = event.clipboardData
+ || event.originalEvent.clipboardData;
+ const items = clipboardData.items;
+ if (JSON.stringify(items)) {
+ let blob;
+ for (const item of items) {
+ if (item.type.indexOf('image') === 0) {
+ blob = item.getAsFile();
+ }
+ }
+ if (blob) {
+ const fr = new FileReader();
+ fr.onload = fileLoadEvent => {
+ const filename = `undefined-${this._undefinedFileCounter}`;
+ this._undefinedFileCounter++;
+ this._handleFileLoadEvent(filename, fileLoadEvent);
+ };
+ fr.readAsDataURL(blob);
+ } else {
+ event.preventDefault();
+ this.fire('show-error', {message: `No image file`});
+ }
+ }
+ }
+
+ _handleUploadAllImages() {
+ for (const image of this._images.values()) {
+ this._uploadImage(image);
+ }
+ }
+
+ _handleUploadImage(event) {
+ event.stopPropagation();
+ const image = this._createImageObject(
+ event.target.imageName,
+ event.target.imageData,
+ null,
+ event.target);
+ this._images.set(image.name, image);
+ this._uploadImage(image);
+ }
+
+ _queryProjects(input) {
+ let query;
+ if (!input || input === this._defaultImageProject) {
+ query = '';
+ } else {
+ query = `?prefix=${input}`;
+ }
+
+ return this.plugin.restApi('/a/projects/').get(query)
+ .then(response => {
+ const projects = [];
+ for (const key in response) {
+ if (!response.hasOwnProperty(key)) { continue; }
+ projects.push({
+ name: key,
+ value: decodeURIComponent(response[key].id),
+ });
+ }
+ return projects;
+ });
+ }
+
+ _stageImage(name, data) {
+ if (this._images.has(name)) {
+ const fileName = name.slice(0, name.lastIndexOf('.'));
+ const fileExtension = name.slice(name.lastIndexOf('.'));
+ name = `${fileName}-${this._undefinedFileCounter}${fileExtension}`;
+ this._undefinedFileCounter++;
+ }
+ const imagePanel = this._createListEntry(name, data, null);
+ this._images.set(
+ name, this._createImageObject(name, data, null, imagePanel));
+ this.$.imageListContainer.hidden = false;
+ this._computeUploadAllDisabled();
+ }
+
+ _uploadImage(image) {
+ if (image && image.uploaded) {
+ return;
+ }
+
+ this.plugin.restApi('/projects')
+ .post(`/${this._imageProject}/imagare~images`, {
+ image_data: image.data,
+ file_name: image.name,
+ })
+ .then(response => {
+ if (!image.list_entry) {
+ image.list_entry = this._createListEntry(
+ image.name, image.data, response.url);
+ } else {
+ image.list_entry.setAttribute('image-url', response.url);
+ image.list_entry.uploaded = true;
+ }
+
+ this._images.set(
+ image.name,
+ this._createImageObject(
+ image.name, image.data, response.url, image.list_entry, true,
+ this._extractImageRef(response.url)));
+
+ this.$.imageListContainer.hidden = false;
+ this._computeUploadAllDisabled();
+ }).catch(response => {
+ this.fire('show-error', {message: response});
+ });
+ }
+}
+
+customElements.define(GrImagareUpload.is, GrImagareUpload);
diff --git a/src/main/resources/static/gr-imagare-upload.html b/gr-imagare/gr-imagare-upload_html.js
similarity index 77%
rename from src/main/resources/static/gr-imagare-upload.html
rename to gr-imagare/gr-imagare-upload_html.js
index 814ade2..cf84eee 100644
--- a/src/main/resources/static/gr-imagare-upload.html
+++ b/gr-imagare/gr-imagare-upload_html.js
@@ -1,22 +1,21 @@
-<!--
-@license
-Copyright (C) 2019 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.
--->
+/**
+ * @license
+ * Copyright (C) 2019 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.
+ */
-<link rel="import"
- href="./gr-imagare-list-item.html">
-
-<dom-module id="gr-imagare-upload">
- <template>
+export const htmlTemplate = Polymer.html`
<style include="shared-styles"></style>
<style include="gr-subpage-styles"></style>
<style include="gr-form-styles"></style>
@@ -70,8 +69,8 @@
<fieldset>
<h2>Settings</h2>
<section>
- The user preferences for the image upload can be changed
- <a href="[[_computeSettingsUrl()]]">here</a>.
+ The preferences for the image upload can be changed
+ in the <a href="[[_computeSettingsUrl()]]">user settings</a>.
</section>
<section>
<span class="title">Project</span>
@@ -127,6 +126,4 @@
</div>
</main>
</div>
- </template>
- <script src="gr-imagare-upload.js"></script>
-</dom-module>
+`;
diff --git a/gr-imagare/gr-imagare.js b/gr-imagare/gr-imagare.js
new file mode 100644
index 0000000..e6094b7
--- /dev/null
+++ b/gr-imagare/gr-imagare.js
@@ -0,0 +1,35 @@
+/**
+ * @license
+ * Copyright (C) 2019 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 './gr-imagare-inline.js';
+import './gr-imagare-preferences.js';
+import './gr-imagare-pref-menu-item.js';
+import './gr-imagare-upload.js';
+
+Gerrit.install(plugin => {
+ plugin.restApi('/config/server/').get('imagare~config').then(config => {
+ if (config && config.enable_image_server) {
+ plugin.screen('upload', 'gr-imagare-upload');
+ }
+ });
+ plugin.registerCustomComponent(
+ 'change-view-integration', 'gr-imagare-inline');
+ plugin.registerCustomComponent(
+ 'settings-screen', 'gr-imagare-preferences');
+ plugin.registerCustomComponent(
+ 'settings-menu-item', 'gr-imagare-pref-menu-item');
+});
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..eda0784
--- /dev/null
+++ b/package.json
@@ -0,0 +1,13 @@
+{
+ "name": "imagare",
+ "description": "Imagare plugin",
+ "browser": true,
+ "scripts": {
+ "safe_bazelisk": "if which bazelisk >/dev/null; then bazel_bin=bazelisk; else bazel_bin=bazel; fi && $bazel_bin",
+ "eslint": "npm run safe_bazelisk test :lint_test",
+ "eslintfix": "npm run safe_bazelisk run :lint_bin -- -- --fix $(pwd)"
+ },
+ "devDependencies": {},
+ "license": "Apache-2.0",
+ "private": true
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/imagare/HttpModule.java b/src/main/java/com/googlesource/gerrit/plugins/imagare/HttpModule.java
index f7960e8..634a7fe 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/imagare/HttpModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/imagare/HttpModule.java
@@ -39,6 +39,6 @@
serveRegex("^" + ImageServlet.PATH_PREFIX + "(.+)?$").with(ImageServlet.class);
}
- DynamicSet.bind(binder(), WebUiPlugin.class).toInstance(new JavaScriptPlugin("imagare.html"));
+ DynamicSet.bind(binder(), WebUiPlugin.class).toInstance(new JavaScriptPlugin("imagare.js"));
}
}
diff --git a/src/main/resources/static/gr-imagare-inline.html b/src/main/resources/static/gr-imagare-inline.html
deleted file mode 100644
index 9b698fa..0000000
--- a/src/main/resources/static/gr-imagare-inline.html
+++ /dev/null
@@ -1,19 +0,0 @@
- <!--
-@license
-Copyright (C) 2019 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.
--->
-
-<dom-module id="gr-imagare-inline">
- <template>
- </template>
- <script src="gr-imagare-inline.js"></script>
-</dom-module>
diff --git a/src/main/resources/static/gr-imagare-inline.js b/src/main/resources/static/gr-imagare-inline.js
deleted file mode 100644
index 903b53e..0000000
--- a/src/main/resources/static/gr-imagare-inline.js
+++ /dev/null
@@ -1,192 +0,0 @@
-// Copyright (C) 2019 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.
-
-(function () {
- 'use strict';
-
- const LINK_DECORATIONS = {
- NONE: 1,
- INLINE: 2,
- TOOLTIP: 3,
- };
-
- Polymer({
- is: 'gr-imagare-inline',
-
- properties: {
- _expandedObserver: MutationObserver,
- _messageAddedObserver: MutationObserver,
- _messages: Object,
- _link_decoration: Number,
- _pattern: String,
- _decorator_fn: Function,
- },
-
- attached() {
- this._getAccountPrefs().then(() => {
- if (this._link_decoration === LINK_DECORATIONS.NONE) {
- return;
- }
-
- this._expandedObserver = new MutationObserver(mutations => {
- mutations.forEach(mut => {
- if (!mut.target.classList.contains('expanded')){
- return;
- }
- let links = this._getLinksFromMessage(mut.target);
-
- if (!links) {
- return;
- }
-
- for (const link of links) {
- this._decorator_fn(link);
- }
- });
- });
-
- this._messageAddedObserver = new MutationObserver(mutations => {
- mutations.forEach(mut => {
- mut.addedNodes.forEach(node => {
- if (node.tagName === "GR-MESSAGE") {
- this._addExpandedObservers(node);
- }
- });
- });
- });
-
- this._messageAddedObserver.observe(
- // TODO(Thomas): The util methods were removed in change 270988, which
- // will break this code in newer Gerrit versions (3.3+). At this point
- // the plugin should implement the querySelector(All)-methods itself.
- util.querySelector(document.body, 'gr-messages-list'),
- {
- childList: true,
- });
-
- this._addObserversToMessages();
- });
- },
-
- detached() {
- this._expandedObserver.disconnect();
- this._messageAddedObserver.disconnect();
- },
-
- _addObserversToMessages() {
- this._messages = this._getMessages();
-
- if (!this._messages) {
- return;
- }
-
- for (const message of this._messages) {
- this._addExpandedObservers(message);
- }
- },
-
- _addExpandedObservers(message) {
- this._expandedObserver.observe(message, {
- attributes: true,
- attributeOldValue: true,
- attributFilter: ['class'],
- });
- },
-
- _getAccountPrefs() {
- return this.plugin.restApi('/accounts/self/imagare~preference')
- .get('')
- .then(prefs => {
- if (!prefs || !prefs.link_decoration) {
- this._link_decoration = LINK_DECORATIONS.NONE;
- this._pattern = '.*';
- } else {
- this._link_decoration = LINK_DECORATIONS[prefs.link_decoration.toUpperCase()];
- this._pattern = prefs.pattern || '.*';
- }
-
- switch (this._link_decoration) {
- case LINK_DECORATIONS.INLINE:
- this._decorator_fn = this._insertImage.bind(this);
- break;
- case LINK_DECORATIONS.TOOLTIP:
- this._decorator_fn = this._addTooltip.bind(this);
- break;
- case LINK_DECORATIONS.NONE:
- default:
- this._decorator_fn = () => {};
- }
- });
- },
-
- _getMessages() {
- let messageList = util.querySelector(document.body, 'gr-messages-list');
- if (messageList) {
- return util.querySelectorAll(messageList, 'gr-message');
- }
- },
-
- _getLinksFromMessage(message) {
- let links = [];
- let linkedTexts = util.querySelectorAll(message, 'gr-linked-text');
- for (const e of linkedTexts) {
- let aTags = util.querySelectorAll(e, 'a');
- if (aTags && aTags.length > 0){
- for (const a of aTags){
- if (util.querySelectorAll(a, 'img').length > 0) {
- continue;
- }
- if (!a.href.match(this._pattern)) {
- continue;
- }
-
- links = links.concat(a);
- }
- }
- }
- return links.length > 0 ? links : null;
- },
-
- _createImage(url) {
- let img = document.createElement('img');
- img.setAttribute("src", url);
- img.setAttribute("style", "max-width: 100%; height: auto;");
-
- return img;
- },
-
- _insertImage(link) {
- if (!link) {
- return;
- }
-
- link.replaceWith(this._createImage(link.href));
- },
-
- _addTooltip(link) {
- if (!link) {
- return;
- }
-
- link.onmouseover = (event) => {
- let img = this._createImage(link.href);
- img.onmouseout = (event) => {
- event.target.replaceWith(link);
- }
-
- event.target.replaceWith(img);
- }
- },
- });
-})();
diff --git a/src/main/resources/static/gr-imagare-list-item.js b/src/main/resources/static/gr-imagare-list-item.js
deleted file mode 100644
index 5f1cffe..0000000
--- a/src/main/resources/static/gr-imagare-list-item.js
+++ /dev/null
@@ -1,107 +0,0 @@
-// Copyright (C) 2019 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.
-
-(function () {
- 'use strict';
-
- Polymer({
- is: 'gr-imagare-list-item',
-
- properties: {
- imageUrl: {
- type: String,
- reflectToAttribute: true,
- },
- imageName: {
- type: String,
- reflectToAttribute: true,
- },
- imageData: {
- type: String,
- reflectToAttribute: true,
- },
- uploaded: {
- type: Boolean,
- observer: '_uploadedChanged',
- reflectToAttribute: true,
- },
- _originalImageName: String,
- _editing: {
- type: Boolean,
- value: false,
- },
- _imageSrc: String,
- },
-
- attached() {
- this._originalImageName = this.imageName;
- this._setImage();
- },
-
- _handleCancelRenameName() {
- this.imageName = this._originalImageName;
- this._editing = false;
- },
-
- _handleClearImage() {
- this.fire("clear");
- },
-
- _handleDeleteImage() {
- this.fire("delete");
- },
-
- _handleEditImage() {
- this._editing = true;
- },
-
- _handleSaveName() {
- this._editing = false;
-
- if (this._originalImageName === this.imageName) {
- return;
- }
-
- let oldFileType = this._originalImageName.split('.').pop();
- let newFileType = this.imageName.split('.').pop();
- if (oldFileType !== newFileType) {
- this.imageName += `.${oldFileType}`;
- }
-
- this.fire("editName", {oldName: this._originalImageName, newName: this.imageName});
- },
-
- _handleUploadImage() {
- this.fire("upload");
- },
-
- _openDeleteDialog() {
- this.$.deleteOverlay.open();
- },
-
- _setImage() {
- if (this.uploaded) {
- this.$.thumbnail.setAttribute('src', this.imageUrl);
- } else {
- this.$.thumbnail.setAttribute('src', this.imageData);
- }
- },
-
- _uploadedChanged(uploaded) {
- this.$.uploading.hidden = !uploaded;
- this.$.staging.hidden = uploaded;
- this._setImage();
- },
- });
-})();
diff --git a/src/main/resources/static/gr-imagare-pref-menu-item.html b/src/main/resources/static/gr-imagare-pref-menu-item.html
deleted file mode 100644
index ee8b989..0000000
--- a/src/main/resources/static/gr-imagare-pref-menu-item.html
+++ /dev/null
@@ -1,29 +0,0 @@
-<!--
-@license
-Copyright (C) 2019 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.
--->
-
-<dom-module id="gr-imagare-pref-menu-item">
- <template>
- <style include="shared-styles"></style>
- <style include="gr-page-nav-styles">
- li {
- padding-left: 1.5em;
- padding-right: 1.5em;
- }
- </style>
- <li class="navStyles">
- <a href="#ImagarePreferences">Imagare Preferences</a>
- </li>
- </template>
- <script src="gr-imagare-pref-menu-item.js"></script>
-</dom-module>
diff --git a/src/main/resources/static/gr-imagare-pref-menu-item.js b/src/main/resources/static/gr-imagare-pref-menu-item.js
deleted file mode 100644
index 3e1a7b0..0000000
--- a/src/main/resources/static/gr-imagare-pref-menu-item.js
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright (C) 2019 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.
-
-(function () {
- 'use strict';
-
- Polymer({
- is: 'gr-imagare-pref-menu-item',
- });
-})();
diff --git a/src/main/resources/static/gr-imagare-preferences.js b/src/main/resources/static/gr-imagare-preferences.js
deleted file mode 100644
index d1f868f..0000000
--- a/src/main/resources/static/gr-imagare-preferences.js
+++ /dev/null
@@ -1,101 +0,0 @@
-// Copyright (C) 2019 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.
-
-(function () {
- 'use strict';
-
- Polymer({
- is: 'gr-imagare-preferences',
-
- properties: {
- _defaultImageProject: String,
- _linkDecoration: String,
- _stageImages: Boolean,
- _prefsChanged: {
- type: Boolean,
- value: false
- },
- _query: {
- type: Function,
- value() {
- return this._queryProjects.bind(this);
- },
- },
- },
-
- attached() {
- this._getUserPreferences();
- },
-
- _getUserPreferences() {
- this.plugin.restApi('/accounts/self/')
- .get(`imagare~preference`)
- .then(config => {
- if (!config) {
- return;
- }
-
- this._linkDecoration = config.link_decoration;
- this._defaultImageProject = config.default_project;
- this._stageImages = config.stage;
- }).catch(response => {
- this.fire('show-error', {message: response});
- });
- },
-
- _handleImagarePrefsSave(){
- this.plugin.restApi('/accounts/self/')
- .put(`imagare~preference`, {
- default_project: this._defaultImageProject,
- link_decoration: this._linkDecoration,
- stage: this._stageImages,
- }).then(() => {
- this._prefsChanged = false;
- }).catch(response => {
- this.fire('show-error', {message: response});
- });
- },
-
- _handlePrefsChanged() {
- this._prefsChanged = true;
- },
-
- _handleStageImagesChanged(event){
- this._handlePrefsChanged();
- this._stageImages = event.target.checked;
- },
-
- _queryProjects(input) {
- let query;
- if (!input || input === this._defaultImageProject) {
- query = '';
- } else {
- query = `?prefix=${input}`;
- }
-
- return this.plugin.restApi('/a/projects/').get(query)
- .then(response => {
- const projects = [];
- for (const key in response) {
- if (!response.hasOwnProperty(key)) { continue; }
- projects.push({
- name: key,
- value: decodeURIComponent(response[key].id),
- });
- }
- return projects;
- });
- },
- });
-})();
diff --git a/src/main/resources/static/gr-imagare-upload.js b/src/main/resources/static/gr-imagare-upload.js
deleted file mode 100644
index 40150c1..0000000
--- a/src/main/resources/static/gr-imagare-upload.js
+++ /dev/null
@@ -1,374 +0,0 @@
-// Copyright (C) 2019 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.
-
-(function () {
- 'use strict';
-
- function preventDefaultFn(event) {
- event.preventDefault();
- }
-
- Polymer({
- is: 'gr-imagare-upload',
-
- properties: {
- _loading: {
- type: Boolean,
- value: true,
- },
- _allUploaded: {
- type: Boolean,
- value: false,
- },
- _imageProject: String,
- _defaultImageProject: String,
- _images: {
- type: Map,
- value: () => new Map(),
- },
- _stageImages: {
- type: Boolean,
- value: true,
- },
- _undefinedFileCounter: {
- type: Number,
- value: 0,
- },
- _query: {
- type: Function,
- value() {
- return this._queryProjects.bind(this);
- },
- },
- },
-
- listeners: {
- clear: '_handleClearImage',
- delete: '_handleDeleteImage',
- editName: '_handleEditImageName',
- upload: '_handleUploadImage',
- },
-
- attached() {
- this.fire('title-change', { title: 'Image Upload' });
-
- window.addEventListener('dragover', preventDefaultFn, false);
- window.addEventListener('drop', preventDefaultFn, false);
- this.$.dragDropArea.addEventListener('paste', preventDefaultFn, false);
-
- this._getUserPreferences();
- },
-
- detached() {
- window.removeEventListener('dragover', preventDefaultFn, false);
- window.removeEventListener('drop', preventDefaultFn, false);
- this.$.dragDropArea.removeEventListener('paste', preventDefaultFn, false);
- },
-
- _computeFilenameWithCorrectType(filedata, filename) {
- let realFiletype = filedata.slice(
- filedata.indexOf('/') + 1,
- filedata.indexOf(';'));
-
- let givenFiletype;
-
- if (filename.indexOf(".") !== -1) {
- givenFiletype = filename.split('.').pop();
- }
-
- if (!givenFiletype || realFiletype !== givenFiletype) {
- filename += `.${realFiletype}`;
- }
-
- return filename;
- },
-
- _computeLoadingClass(loading) {
- return loading ? 'loading' : '';
- },
-
- _computeSettingsUrl() {
- return `${location.origin}/settings#ImagarePreferences`;
- },
-
- _computeUploadAllDisabled() {
- if (this._images) {
- for (let value of this._images.values()) {
- if (!value.uploaded) {
- this._allUploaded = false;
- return;
- }
- }
- }
-
- this._allUploaded = true;
- },
-
- _createImageObject(name, data, url, list_entry, uploaded, ref) {
- return {
- data: data,
- list_entry: list_entry,
- name: name,
- ref: ref,
- url: url,
- uploaded: uploaded,
- }
- },
-
- _createListEntry(name, data, url) {
- let imagePanel = document.createElement('gr-imagare-list-item');
- imagePanel.setAttribute("image-name", name);
-
- if (data) {
- imagePanel.setAttribute("image-data", data);
- }
-
- if (url) {
- imagePanel.setAttribute("image-url", url);
- imagePanel.uploaded = true;
- } else {
- imagePanel.uploaded = false;
- }
-
- this.$.imageList.appendChild(imagePanel);
-
- return imagePanel;
- },
-
- _deleteImage(image) {
- this.plugin.restApi('/projects')
- .delete(`/${this._imageProject}/imagare~images/${image.ref}`)
- .then(() => {
- image.list_entry.remove();
- this._images.delete(image.name);
- if (!this.$.imageList.hasChildNodes()) {
- this.$.imageListContainer.hidden = true;
- }
- }).catch(response => {
- this.fire('show-error', { message: response });
- });
- },
-
- _extractImageRef(url) {
- return url.split('/').slice(-2)[0];
- },
-
- _getUserPreferences() {
- this.plugin.restApi('/accounts/self/')
- .get(`imagare~preference`)
- .then(config => {
- if (!config) {
- return;
- }
-
- this._defaultImageProject = config.default_project;
- this._imageProject = config.default_project;
- this._stageImages = config.stage;
- this._loading = false;
- });
- },
-
- _handleClearAllImages() {
- while (this.$.imageList.firstChild) {
- this.$.imageList.removeChild(this.$.imageList.firstChild);
- }
- this.$.imageListContainer.hidden = true;
-
- this._images.clear()
- },
-
- _handleClearImage(event) {
- event.stopPropagation();
- this._images.delete(event.target.imageName);
- event.target.remove();
- if (!this.$.imageList.hasChildNodes()) {
- this.$.imageListContainer.hidden = true;
- }
- },
-
- _handleDeleteImage(event) {
- event.stopPropagation();
- this._deleteImage(this._images.get(event.target.imageName));
- },
-
- _handleDrop(event) {
- event.preventDefault();
- event.stopPropagation();
-
- for (let file of event.dataTransfer.files) {
- if (!file.type.match('image/.*')) {
- this.fire('show-error', { message: `No image file: ${file.name}` });
- }
- let fr = new FileReader();
- fr.file = file;
- fr.onload = fileLoadEvent => this._handleFileLoadEvent(
- fr.file.name, fileLoadEvent);
- fr.readAsDataURL(file);
- }
- },
-
- _handleEditImageName(event) {
- event.stopPropagation();
- let editedImage = this._images.get(event.detail.oldName);
- if (this._images.has(event.detail.newName)) {
- this.fire('show-error', { message: 'An image with the same name was already staged.' });
- editedImage.list_entry.setAttribute("image-name", event.detail.oldName);
- } else {
- editedImage.name = event.detail.newName;
- this._images.set(editedImage.name, editedImage);
- this._images.delete(event.detail.oldName);
- }
- },
-
- _handleFileLoadEvent(filename, event) {
- let correctedFilename = this._computeFilenameWithCorrectType(
- event.target.result, filename);
- if (this._stageImages) {
- this._stageImage(correctedFilename, event.target.result);
- } else {
- let image = this._createImageObject(correctedFilename, event.target.result);
- this._images.set(correctedFilename, image);
- this._uploadImage(image);
- }
- },
-
- _handleKeyPress(event) {
- let ctrlDown = event.ctrlKey || event.metaKey;
- if (!ctrlDown) {
- event.preventDefault();
- event.stopPropagation();
- }
- },
-
- _handleImagePathChanged(event) {
- for (let file of event.target.files) {
- let fr = new FileReader();
- fr.file = file;
- fr.onload = fileLoadEvent => this._handleFileLoadEvent(
- fr.file.name, fileLoadEvent);
- fr.readAsDataURL(file);
- }
-
- event.target.value = '';
- },
-
- _handlePaste(event) {
- let clipboardData = event.clipboardData || event.originalEvent.clipboardData;
- let items = clipboardData.items;
- if (JSON.stringify(items)) {
- let blob;
- for (let item of items) {
- if (item.type.indexOf("image") === 0) {
- blob = item.getAsFile();
- }
- }
- if (blob) {
- let fr = new FileReader();
- fr.onload = fileLoadEvent => {
- let filename = `undefined-${this._undefinedFileCounter}`;
- this._undefinedFileCounter++;
- this._handleFileLoadEvent(filename, fileLoadEvent);
- };
- fr.readAsDataURL(blob);
- } else {
- event.preventDefault();
- this.fire('show-error', { message: `No image file` });
- }
- }
- },
-
- _handleUploadAllImages() {
- for (let image of this._images.values()) {
- this._uploadImage(image);
- }
- },
-
- _handleUploadImage(event) {
- event.stopPropagation();
- let image = this._createImageObject(
- event.target.imageName,
- event.target.imageData,
- null,
- event.target);
- this._images.set(image.name, image);
- this._uploadImage(image);
- },
-
- _queryProjects(input) {
- let query;
- if (!input || input === this._defaultImageProject) {
- query = '';
- } else {
- query = `?prefix=${input}`;
- }
-
- return this.plugin.restApi('/a/projects/').get(query)
- .then(response => {
- const projects = [];
- for (const key in response) {
- if (!response.hasOwnProperty(key)) { continue; }
- projects.push({
- name: key,
- value: decodeURIComponent(response[key].id),
- });
- }
- return projects;
- });
- },
-
- _stageImage(name, data) {
- if (this._images.has(name)) {
- let fileName = name.slice(0, name.lastIndexOf('.'));
- let fileExtension = name.slice(name.lastIndexOf('.'));
- name = `${fileName}-${this._undefinedFileCounter}${fileExtension}`;
- this._undefinedFileCounter++;
- }
- let imagePanel = this._createListEntry(name, data, null);
- this._images.set(name, this._createImageObject(name, data, null, imagePanel));
- this.$.imageListContainer.hidden = false;
- this._computeUploadAllDisabled();
- },
-
- _uploadImage(image) {
- if (image && image.uploaded) {
- return;
- }
-
- this.plugin.restApi('/projects')
- .post(`/${this._imageProject}/imagare~images`, {
- image_data: image.data,
- file_name: image.name,
- })
- .then(response => {
- if (!image.list_entry) {
- image.list_entry = this._createListEntry(image.name, image.data, response.url);
- } else {
- image.list_entry.setAttribute("image-url", response.url);
- image.list_entry.uploaded = true;
- }
-
- this._images.set(
- image.name,
- this._createImageObject(
- image.name, image.data, response.url, image.list_entry, true,
- this._extractImageRef(response.url)));
-
- this.$.imageListContainer.hidden = false;
- this._computeUploadAllDisabled();
- }).catch(response => {
- this.fire('show-error', { message: response });
- });
- }
- });
-})();
diff --git a/src/main/resources/static/imagare.html b/src/main/resources/static/imagare.html
deleted file mode 100644
index 7e3ee3b..0000000
--- a/src/main/resources/static/imagare.html
+++ /dev/null
@@ -1,36 +0,0 @@
-<!--
-@license
-Copyright (C) 2019 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.
--->
-
-<link rel="import" href="./gr-imagare-inline.html">
-<link rel="import" href="./gr-imagare-preferences.html">
-<link rel="import" href="./gr-imagare-pref-menu-item.html">
-<link rel="import" href="./gr-imagare-upload.html">
-
-<dom-module id="imagare">
- <script>
- Gerrit.install(plugin => {
- plugin.restApi('/config/server/').get('imagare~config').then(config => {
- if (config && config.enable_image_server) {
- plugin.screen('upload', 'gr-imagare-upload');
- }
- });
- plugin.registerCustomComponent('change-view-integration', 'gr-imagare-inline');
- plugin.registerCustomComponent('settings-screen', 'gr-imagare-preferences');
- plugin.registerCustomComponent('settings-menu-item', 'gr-imagare-pref-menu-item');
- });
- </script>
-</dom-module>