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>