diff --git a/package.json b/package.json index ba811482..4f85f39a 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,16 @@ "style-loader": "^0.21.0", "webpack-uglify-js-plugin": "^1.1.9", "raw-loader": "1.0.0-beta.0", - "ts-loader": "^1.3.3" + "ts-loader": "^1.3.3", + "@webcomponents/webcomponentsjs": "^2.0.0", + "@polymer/polymer": "^3.0.0", + "@polymer/paper-button": "^3.0.0", + "@polymer/paper-spinner": "^3.0.0", + "@polymer/paper-tooltip": "^3.0.0", + "@polymer/paper-toast": "^3.0.0", + "@polymer/paper-toggle-button": "^3.0.0", + "@polymer/iron-ajax": "^3.0.0", + "@polymer/iron-autogrow-textarea": "^3.0.0", + "@polymer/iron-a11y-keys": "^3.0.0" } } diff --git a/pkgs/node-packages.nix b/pkgs/node-packages.nix index 43edef48..045f321d 100644 --- a/pkgs/node-packages.nix +++ b/pkgs/node-packages.nix @@ -4,6 +4,222 @@ let sources = { + "@polymer/font-roboto-3.0.2" = { + name = "_at_polymer_slash_font-roboto"; + packageName = "@polymer/font-roboto"; + version = "3.0.2"; + src = fetchurl { + url = "https://registry.npmjs.org/@polymer/font-roboto/-/font-roboto-3.0.2.tgz"; + sha512 = "tx5TauYSmzsIvmSqepUPDYbs4/Ejz2XbZ1IkD7JEGqkdNUJlh+9KU85G56Tfdk/xjEZ8zorFfN09OSwiMrIQWA=="; + }; + }; + "@polymer/iron-a11y-announcer-3.0.1" = { + name = "_at_polymer_slash_iron-a11y-announcer"; + packageName = "@polymer/iron-a11y-announcer"; + version = "3.0.1"; + src = fetchurl { + url = "https://registry.npmjs.org/@polymer/iron-a11y-announcer/-/iron-a11y-announcer-3.0.1.tgz"; + sha512 = "Xiqmpz0AEEbMNGYPpbrXBIrcI/xaR4tn77pmSLfxVKGGwjEUR/YrRgyIwXp4EN7lvst1dFC8kyl2hLga0uDIVQ=="; + }; + }; + "@polymer/iron-a11y-keys-3.0.1" = { + name = "_at_polymer_slash_iron-a11y-keys"; + packageName = "@polymer/iron-a11y-keys"; + version = "3.0.1"; + src = fetchurl { + url = "https://registry.npmjs.org/@polymer/iron-a11y-keys/-/iron-a11y-keys-3.0.1.tgz"; + sha512 = "zmTi8xHeY4ZMJLAitW2hAmW5zXZ35hVy/eHQUFadAlOccuBK3oRRmoPRQRaZgpyJrCVFDAQRXXzzJtUDil/0CA=="; + }; + }; + "@polymer/iron-a11y-keys-behavior-3.0.1" = { + name = "_at_polymer_slash_iron-a11y-keys-behavior"; + packageName = "@polymer/iron-a11y-keys-behavior"; + version = "3.0.1"; + src = fetchurl { + url = "https://registry.npmjs.org/@polymer/iron-a11y-keys-behavior/-/iron-a11y-keys-behavior-3.0.1.tgz"; + sha512 = "lnrjKq3ysbBPT/74l0Fj0U9H9C35Tpw2C/tpJ8a+5g8Y3YJs1WSZYnEl1yOkw6sEyaxOq/1DkzH0+60gGu5/PQ=="; + }; + }; + "@polymer/iron-ajax-3.0.1" = { + name = "_at_polymer_slash_iron-ajax"; + packageName = "@polymer/iron-ajax"; + version = "3.0.1"; + src = fetchurl { + url = "https://registry.npmjs.org/@polymer/iron-ajax/-/iron-ajax-3.0.1.tgz"; + sha512 = "7+TPEAfWsRdhj1Y8UeF1759ktpVu+c3sG16rJiUC3wF9+woQ9xI1zUm2d59i7Yc3aDEJrR/Q8Y262KlOvyGVNg=="; + }; + }; + "@polymer/iron-autogrow-textarea-3.0.1" = { + name = "_at_polymer_slash_iron-autogrow-textarea"; + packageName = "@polymer/iron-autogrow-textarea"; + version = "3.0.1"; + src = fetchurl { + url = "https://registry.npmjs.org/@polymer/iron-autogrow-textarea/-/iron-autogrow-textarea-3.0.1.tgz"; + sha512 = "FgSL7APrOSL9Vu812sBCFlQ17hvnJsBAV2C2e1UAiaHhB+dyfLq8gGdGUpqVWuGJ50q4Y/49QwCNnLf85AdVYA=="; + }; + }; + "@polymer/iron-behaviors-3.0.1" = { + name = "_at_polymer_slash_iron-behaviors"; + packageName = "@polymer/iron-behaviors"; + version = "3.0.1"; + src = fetchurl { + url = "https://registry.npmjs.org/@polymer/iron-behaviors/-/iron-behaviors-3.0.1.tgz"; + sha512 = "IMEwcv1lhf1HSQxuyWOUIL0lOBwmeaoSTpgCJeP9IBYnuB1SPQngmfRuHKgK6/m9LQ9F9miC7p3HeQQUdKAE0w=="; + }; + }; + "@polymer/iron-checked-element-behavior-3.0.1" = { + name = "_at_polymer_slash_iron-checked-element-behavior"; + packageName = "@polymer/iron-checked-element-behavior"; + version = "3.0.1"; + src = fetchurl { + url = "https://registry.npmjs.org/@polymer/iron-checked-element-behavior/-/iron-checked-element-behavior-3.0.1.tgz"; + sha512 = "aDr0cbCNVq49q+pOqa6CZutFh+wWpwPMLpEth9swx+GkAj+gCURhuQkaUYhIo5f2egDbEioR1aeHMnPlU9dQZA=="; + }; + }; + "@polymer/iron-fit-behavior-3.0.1" = { + name = "_at_polymer_slash_iron-fit-behavior"; + packageName = "@polymer/iron-fit-behavior"; + version = "3.0.1"; + src = fetchurl { + url = "https://registry.npmjs.org/@polymer/iron-fit-behavior/-/iron-fit-behavior-3.0.1.tgz"; + sha512 = "/M0B1L30k31vmwNBaGuZcxzUAhJSHoGccb/DF0CDKI/hT8UlkTvcyemaWdOpmHHLgY52ceKIkRwA3AeXrKyvaQ=="; + }; + }; + "@polymer/iron-flex-layout-3.0.1" = { + name = "_at_polymer_slash_iron-flex-layout"; + packageName = "@polymer/iron-flex-layout"; + version = "3.0.1"; + src = fetchurl { + url = "https://registry.npmjs.org/@polymer/iron-flex-layout/-/iron-flex-layout-3.0.1.tgz"; + sha512 = "7gB869czArF+HZcPTVSgvA7tXYFze9EKckvM95NB7SqYF+NnsQyhoXgKnpFwGyo95lUjUW9TFDLUwDXnCYFtkw=="; + }; + }; + "@polymer/iron-form-element-behavior-3.0.1" = { + name = "_at_polymer_slash_iron-form-element-behavior"; + packageName = "@polymer/iron-form-element-behavior"; + version = "3.0.1"; + src = fetchurl { + url = "https://registry.npmjs.org/@polymer/iron-form-element-behavior/-/iron-form-element-behavior-3.0.1.tgz"; + sha512 = "G/e2KXyL5AY7mMjmomHkGpgS0uAf4ovNpKhkuUTRnMuMJuf589bKqE85KN4ovE1Tzhv2hJoh/igyD6ekHiYU1A=="; + }; + }; + "@polymer/iron-meta-3.0.1" = { + name = "_at_polymer_slash_iron-meta"; + packageName = "@polymer/iron-meta"; + version = "3.0.1"; + src = fetchurl { + url = "https://registry.npmjs.org/@polymer/iron-meta/-/iron-meta-3.0.1.tgz"; + sha512 = "pWguPugiLYmWFV9UWxLWzZ6gm4wBwQdDy4VULKwdHCqR7OP7u98h+XDdGZsSlDPv6qoryV/e3tGHlTIT0mbzJA=="; + }; + }; + "@polymer/iron-overlay-behavior-3.0.2" = { + name = "_at_polymer_slash_iron-overlay-behavior"; + packageName = "@polymer/iron-overlay-behavior"; + version = "3.0.2"; + src = fetchurl { + url = "https://registry.npmjs.org/@polymer/iron-overlay-behavior/-/iron-overlay-behavior-3.0.2.tgz"; + sha512 = "j1qmt6mJHCwpe1mKOvqK5kcCUPQr5LSrlqpgRDbUuLgUfNJ/vGTipjrkBlfbEUagm5FEQdc1VLPLSQP6WVuP9g=="; + }; + }; + "@polymer/iron-resizable-behavior-3.0.1" = { + name = "_at_polymer_slash_iron-resizable-behavior"; + packageName = "@polymer/iron-resizable-behavior"; + version = "3.0.1"; + src = fetchurl { + url = "https://registry.npmjs.org/@polymer/iron-resizable-behavior/-/iron-resizable-behavior-3.0.1.tgz"; + sha512 = "FyHxRxFspVoRaeZSWpT3y0C9awomb4tXXolIJcZ7RvXhMP632V5lez+ch5G5SwK0LpnAPkg35eB0LPMFv+YMMQ=="; + }; + }; + "@polymer/iron-validatable-behavior-3.0.1" = { + name = "_at_polymer_slash_iron-validatable-behavior"; + packageName = "@polymer/iron-validatable-behavior"; + version = "3.0.1"; + src = fetchurl { + url = "https://registry.npmjs.org/@polymer/iron-validatable-behavior/-/iron-validatable-behavior-3.0.1.tgz"; + sha512 = "wwpYh6wOa4fNI+jH5EYKC7TVPYQ2OfgQqocWat7GsNWcsblKYhLYbwsvEY5nO0n2xKqNfZzDLrUom5INJN7msQ=="; + }; + }; + "@polymer/paper-behaviors-3.0.1" = { + name = "_at_polymer_slash_paper-behaviors"; + packageName = "@polymer/paper-behaviors"; + version = "3.0.1"; + src = fetchurl { + url = "https://registry.npmjs.org/@polymer/paper-behaviors/-/paper-behaviors-3.0.1.tgz"; + sha512 = "6knhj69fPJejv8qR0kCSUY+Q0XjaUf0OSnkjRjmTJPAwSrRYtgqE+l6P1FfA+py1X/cUjgne9EF5rMZAKJIg1g=="; + }; + }; + "@polymer/paper-button-3.0.1" = { + name = "_at_polymer_slash_paper-button"; + packageName = "@polymer/paper-button"; + version = "3.0.1"; + src = fetchurl { + url = "https://registry.npmjs.org/@polymer/paper-button/-/paper-button-3.0.1.tgz"; + sha512 = "JRNBc+Oj9EWnmyLr7FcCr8T1KAnEHPh6mosln9BUdkM+qYaYsudSICh3cjTIbnj6AuF5OJidoLkM1dlyj0j6Zg=="; + }; + }; + "@polymer/paper-ripple-3.0.1" = { + name = "_at_polymer_slash_paper-ripple"; + packageName = "@polymer/paper-ripple"; + version = "3.0.1"; + src = fetchurl { + url = "https://registry.npmjs.org/@polymer/paper-ripple/-/paper-ripple-3.0.1.tgz"; + sha512 = "dgOe12GyCF1VZBLUQqnzGWlf3xb255FajNCVB1VFj/AtskYtoamnafa7m3a+1vs+C8qbg4Benn5KwgxVDSW4cg=="; + }; + }; + "@polymer/paper-spinner-3.0.1" = { + name = "_at_polymer_slash_paper-spinner"; + packageName = "@polymer/paper-spinner"; + version = "3.0.1"; + src = fetchurl { + url = "https://registry.npmjs.org/@polymer/paper-spinner/-/paper-spinner-3.0.1.tgz"; + sha512 = "MYIU6qWZnhZ5yNFOBzROPgBteGfxKEnDZ6bCgjrvUtJkBuQEz0MQZzSE/zmZc0oaJ9u5QK5xAFuYdudsGv7+sQ=="; + }; + }; + "@polymer/paper-styles-3.0.1" = { + name = "_at_polymer_slash_paper-styles"; + packageName = "@polymer/paper-styles"; + version = "3.0.1"; + src = fetchurl { + url = "https://registry.npmjs.org/@polymer/paper-styles/-/paper-styles-3.0.1.tgz"; + sha512 = "y6hmObLqlCx602TQiSBKHqjwkE7xmDiFkoxdYGaNjtv4xcysOTdVJsDR/R9UHwIaxJ7gHlthMSykir1nv78++g=="; + }; + }; + "@polymer/paper-toast-3.0.1" = { + name = "_at_polymer_slash_paper-toast"; + packageName = "@polymer/paper-toast"; + version = "3.0.1"; + src = fetchurl { + url = "https://registry.npmjs.org/@polymer/paper-toast/-/paper-toast-3.0.1.tgz"; + sha512 = "pizuogzObniDdICUc6dSLrnDt2VzzoRne1gCmbD6sfOATVv5tc8UfrqhA2iHngbNBEbniBiciS3iogdp5KTVUQ=="; + }; + }; + "@polymer/paper-toggle-button-3.0.1" = { + name = "_at_polymer_slash_paper-toggle-button"; + packageName = "@polymer/paper-toggle-button"; + version = "3.0.1"; + src = fetchurl { + url = "https://registry.npmjs.org/@polymer/paper-toggle-button/-/paper-toggle-button-3.0.1.tgz"; + sha512 = "jadZB60fycT7YnSAH0H23LYo6/2HYmMZTtNr9LpdSIRFPLX6mqqxewex92cFz019bMKaRJgORn308hRlJo2u6A=="; + }; + }; + "@polymer/paper-tooltip-3.0.1" = { + name = "_at_polymer_slash_paper-tooltip"; + packageName = "@polymer/paper-tooltip"; + version = "3.0.1"; + src = fetchurl { + url = "https://registry.npmjs.org/@polymer/paper-tooltip/-/paper-tooltip-3.0.1.tgz"; + sha512 = "yiUk09opTEnE1lK+tb501ENb+yQBi4p++Ep0eGJAHesVYKVMPNgPphVKkIizkDaU+n0SE+zXfTsRbYyOMDYXSg=="; + }; + }; + "@polymer/polymer-3.1.0" = { + name = "_at_polymer_slash_polymer"; + packageName = "@polymer/polymer"; + version = "3.1.0"; + src = fetchurl { + url = "https://registry.npmjs.org/@polymer/polymer/-/polymer-3.1.0.tgz"; + sha512 = "hwN8IMERsFATz/9dSMxYHL+84J9uBkPuuarxJWlTsppZ4CAYTZKnepBfNrKoyNsafBmA3yXBiiKPPf+fJtza7A=="; + }; + }; "@types/clone-0.1.30" = { name = "_at_types_slash_clone"; packageName = "@types/clone"; @@ -193,6 +409,24 @@ let sha512 = "mJ3QKWtCchL1vhU/kZlJnLPuQZnlDOdZsyP0bbLWPGdYsQDnSBvyTLhzwBA3QAMlzEL9V4JHygEmK6/OTEyytA=="; }; }; + "@webcomponents/shadycss-1.5.2" = { + name = "_at_webcomponents_slash_shadycss"; + packageName = "@webcomponents/shadycss"; + version = "1.5.2"; + src = fetchurl { + url = "https://registry.npmjs.org/@webcomponents/shadycss/-/shadycss-1.5.2.tgz"; + sha512 = "0OyrmVc7S+INtzoqP2ofAo+OdVn2Nj0Qvq4wD9FEGN7nMmLRxaD2mzy6hD6EslzxUSuGH302CDU4KXiY66SEqg=="; + }; + }; + "@webcomponents/webcomponentsjs-2.1.3" = { + name = "_at_webcomponents_slash_webcomponentsjs"; + packageName = "@webcomponents/webcomponentsjs"; + version = "2.1.3"; + src = fetchurl { + url = "https://registry.npmjs.org/@webcomponents/webcomponentsjs/-/webcomponentsjs-2.1.3.tgz"; + sha512 = "0UHJNY88lR3pnEYtBVT7F8cuuxOiITQGWJa0LxoELqkBSB7IabzJFOj5K99PajD3CGAsWpjB0CAeijfe376Y1w=="; + }; + }; "@xtuc/ieee754-1.2.0" = { name = "_at_xtuc_slash_ieee754"; packageName = "@xtuc/ieee754"; @@ -1454,13 +1688,13 @@ let sha1 = "b534e7c734c4f81ec5fbe8aca2ad24354b962c6c"; }; }; - "caniuse-db-1.0.30000899" = { + "caniuse-db-1.0.30000900" = { name = "caniuse-db"; packageName = "caniuse-db"; - version = "1.0.30000899"; + version = "1.0.30000900"; src = fetchurl { - url = "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000899.tgz"; - sha512 = "MSCUohyoLU4/PGapapw/PLQkmQ+sFgzX6e3tM6ue8HX9HW9rBD5gRiAYKhC8r0QkvUE0pWTA8Ze6f3jrzBizVg=="; + url = "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000900.tgz"; + sha512 = "fvicVRDlhHIQpt/bmbLl3hDHKUZb5ZP8O2OuZLz2fSEPlUBbvwwbhhqhGS617ldN6bDoo9A3+MQKQyFq0p7UXA=="; }; }; "caniuse-lite-1.0.30000899" = { @@ -7482,6 +7716,30 @@ let version = "1.0.0"; src = ./..; dependencies = [ + sources."@polymer/font-roboto-3.0.2" + sources."@polymer/iron-a11y-announcer-3.0.1" + sources."@polymer/iron-a11y-keys-3.0.1" + sources."@polymer/iron-a11y-keys-behavior-3.0.1" + sources."@polymer/iron-ajax-3.0.1" + sources."@polymer/iron-autogrow-textarea-3.0.1" + sources."@polymer/iron-behaviors-3.0.1" + sources."@polymer/iron-checked-element-behavior-3.0.1" + sources."@polymer/iron-fit-behavior-3.0.1" + sources."@polymer/iron-flex-layout-3.0.1" + sources."@polymer/iron-form-element-behavior-3.0.1" + sources."@polymer/iron-meta-3.0.1" + sources."@polymer/iron-overlay-behavior-3.0.2" + sources."@polymer/iron-resizable-behavior-3.0.1" + sources."@polymer/iron-validatable-behavior-3.0.1" + sources."@polymer/paper-behaviors-3.0.1" + sources."@polymer/paper-button-3.0.1" + sources."@polymer/paper-ripple-3.0.1" + sources."@polymer/paper-spinner-3.0.1" + sources."@polymer/paper-styles-3.0.1" + sources."@polymer/paper-toast-3.0.1" + sources."@polymer/paper-toggle-button-3.0.1" + sources."@polymer/paper-tooltip-3.0.1" + sources."@polymer/polymer-3.1.0" sources."@types/clone-0.1.30" sources."@types/node-6.14.0" sources."@types/parse5-2.2.34" @@ -7503,6 +7761,8 @@ let sources."@webassemblyjs/wasm-parser-1.7.10" sources."@webassemblyjs/wast-parser-1.7.10" sources."@webassemblyjs/wast-printer-1.7.10" + sources."@webcomponents/shadycss-1.5.2" + sources."@webcomponents/webcomponentsjs-2.1.3" sources."@xtuc/ieee754-1.2.0" sources."@xtuc/long-4.2.1" sources."abbrev-1.1.1" @@ -7716,7 +7976,7 @@ let sources."browserslist-1.7.7" ]; }) - sources."caniuse-db-1.0.30000899" + sources."caniuse-db-1.0.30000900" sources."caniuse-lite-1.0.30000899" sources."caseless-0.12.0" sources."center-align-0.1.3" diff --git a/rhodecode/public/js/src/components/channelstream-connection/channelstream-connection.html b/rhodecode/public/js/src/components/channelstream-connection/channelstream-connection.html deleted file mode 100644 index 36d81d5b..00000000 --- a/rhodecode/public/js/src/components/channelstream-connection/channelstream-connection.html +++ /dev/null @@ -1,610 +0,0 @@ - - - - - - - - diff --git a/rhodecode/public/js/src/components/channelstream-connection/channelstream-connection.js b/rhodecode/public/js/src/components/channelstream-connection/channelstream-connection.js new file mode 100644 index 00000000..1d665f40 --- /dev/null +++ b/rhodecode/public/js/src/components/channelstream-connection/channelstream-connection.js @@ -0,0 +1,577 @@ +import {Polymer, html} from '@polymer/polymer/polymer-legacy'; +import '@polymer/iron-ajax/iron-ajax.js'; + +const elemTemplate = html` + + + + + + + + + + + +` + +Polymer({ + is: 'channelstream-connection', + + _template: elemTemplate, + + /** + * Fired when `channels` array changes. + * + * @event channelstream-channels-changed + */ + + /** + * Fired when `connect()` method succeeds. + * + * @event channelstream-connected + */ + + /** + * Fired when `connect` fails. + * + * @event channelstream-connect-error + */ + + /** + * Fired when `disconnect()` succeeds. + * + * @event channelstream-disconnected + */ + + /** + * Fired when `message()` succeeds. + * + * @event channelstream-message-sent + */ + + /** + * Fired when `message()` fails. + * + * @event channelstream-message-error + */ + + /** + * Fired when `subscribe()` succeeds. + * + * @event channelstream-subscribed + */ + + /** + * Fired when `subscribe()` fails. + * + * @event channelstream-subscribe-error + */ + + /** + * Fired when `unsubscribe()` succeeds. + * + * @event channelstream-unsubscribed + */ + + /** + * Fired when `unsubscribe()` fails. + * + * @event channelstream-unsubscribe-error + */ + + /** + * Fired when listening connection receives a message. + * + * @event channelstream-listen-message + */ + + /** + * Fired when listening connection is opened. + * + * @event channelstream-listen-opened + */ + + /** + * Fired when listening connection is closed. + * + * @event channelstream-listen-closed + */ + + /** + * Fired when listening connection suffers an error. + * + * @event channelstream-listen-error + */ + + properties: { + isReady: Boolean, + /** List of channels user should be subscribed to. */ + channels: { + type: Array, + value: function () { + return [] + }, + notify: true + }, + /** Username of connecting user. */ + username: { + type: String, + value: 'Anonymous', + reflectToAttribute: true + }, + /** Connection identifier. */ + connectionId: { + type: String, + reflectToAttribute: true + }, + /** Websocket instance. */ + websocket: { + type: Object, + value: null + }, + /** Websocket connection url. */ + websocketUrl: { + type: String, + value: '' + }, + /** URL used in `connect()`. */ + connectUrl: { + type: String, + value: '' + }, + /** URL used in `disconnect()`. */ + disconnectUrl: { + type: String, + value: '' + }, + /** URL used in `subscribe()`. */ + subscribeUrl: { + type: String, + value: '' + }, + /** URL used in `unsubscribe()`. */ + unsubscribeUrl: { + type: String, + value: '' + }, + /** URL used in `message()`. */ + messageUrl: { + type: String, + value: '' + }, + /** Long-polling connection url. */ + longPollUrl: { + type: String, + value: '' + }, + /** Long-polling connection url. */ + shouldReconnect: { + type: Boolean, + value: true + }, + /** Should send heartbeats. */ + heartbeats: { + type: Boolean, + value: true + }, + /** How much should every retry interval increase (in milliseconds) */ + increaseBounceIv: { + type: Number, + value: 2000 + }, + _currentBounceIv: { + type: Number, + reflectToAttribute: true, + value: 0 + }, + /** Should use websockets or long-polling by default */ + useWebsocket: { + type: Boolean, + reflectToAttribute: true, + value: true + }, + connected: { + type: Boolean, + reflectToAttribute: true, + value: false + } + }, + + observers: [ + '_handleChannelsChange(channels.splices)' + ], + + listeners: { + 'channelstream-connected': 'startListening', + 'channelstream-connect-error': 'retryConnection', + }, + + /** + * Mutators hold functions that you can set locally to change the data + * that the client is sending to all endpoints + * you can call it like `elem.mutators('connect', yourFunc())` + * mutators will be executed in order they were pushed onto arrays + * + */ + mutators: { + connect: function () { + return [] + }(), + message: function () { + return [] + }(), + subscribe: function () { + return [] + }(), + unsubscribe: function () { + return [] + }(), + disconnect: function () { + return [] + }() + }, + ready: function () { + this.isReady = true; + }, + + /** + * Connects user and fetches connection id from the server. + * + */ + connect: function () { + var request = this.$['ajaxConnect']; + request.url = this.connectUrl; + request.body = { + username: this.username, + channels: this.channels + }; + for (var i = 0; i < this.mutators.connect.length; i++) { + this.mutators.connect[i](request); + } + request.generateRequest() + }, + /** + * Overwrite with custom function that will + */ + addMutator: function (type, func) { + this.mutators[type].push(func); + }, + /** + * Subscribes user to channels. + * + */ + subscribe: function (channels) { + var request = this.$['ajaxSubscribe']; + request.url = this.subscribeUrl; + request.body = { + channels: channels, + conn_id: this.connectionId + }; + for (var i = 0; i < this.mutators.subscribe.length; i++) { + this.mutators.subscribe[i](request); + } + if (request.body.channels.length) { + request.generateRequest(); + } + }, + /** + * Unsubscribes user from channels. + * + */ + unsubscribe: function (unsubscribe) { + var request = this.$['ajaxUnsubscribe']; + + request.url = this.unsubscribeUrl; + request.body = { + channels: unsubscribe, + conn_id: this.connectionId + }; + for (var i = 0; i < this.mutators.unsubscribe.length; i++) { + this.mutators.unsubscribe[i](request); + } + request.generateRequest() + }, + + /** + * calculates list of channels we should add user to based on difference + * between channels property and passed channel list + */ + calculateSubscribe: function (channels) { + var currentlySubscribed = this.channels; + var toSubscribe = []; + for (var i = 0; i < channels.length; i++) { + if (currentlySubscribed.indexOf(channels[i]) === -1) { + toSubscribe.push(channels[i]); + } + } + return toSubscribe + }, + /** + * calculates list of channels we should remove user from based difference + * between channels property and passed channel list + */ + calculateUnsubscribe: function (channels) { + var currentlySubscribed = this.channels; + var toUnsubscribe = []; + for (var i = 0; i < channels.length; i++) { + if (currentlySubscribed.indexOf(channels[i]) !== -1) { + toUnsubscribe.push(channels[i]); + } + } + return toUnsubscribe + }, + /** + * Marks the connection as expired. + * + */ + disconnect: function () { + var request = this.$['ajaxDisconnect']; + request.url = this.disconnectUrl; + request.params = { + conn_id: this.connectionId + }; + for (var i = 0; i < this.mutators.disconnect.length; i++) { + this.mutators.disconnect[i](request); + } + // mark connection as expired + request.generateRequest(); + // disconnect existing connection + this.closeConnection(); + }, + + /** + * Sends a message to the server. + * + */ + message: function (message) { + var request = this.$['ajaxMessage']; + request.url = this.messageUrl; + request.body = message; + for (var i = 0; i < this.mutators.message.length; i++) { + this.mutators.message[i](request) + } + request.generateRequest(); + }, + /** + * Opens "long lived" (websocket/longpoll) connection to the channelstream server. + * + */ + startListening: function (event) { + this.fire('start-listening', {}); + if (this.useWebsocket) { + this.useWebsocket = window.WebSocket ? true : false; + } + if (this.useWebsocket) { + this.openWebsocket(); + } + else { + this.openLongPoll(); + } + }, + /** + * Opens websocket connection. + * + */ + openWebsocket: function () { + var url = this.websocketUrl + '?conn_id=' + this.connectionId; + this.websocket = new WebSocket(url); + this.websocket.onopen = this._handleListenOpen.bind(this); + this.websocket.onclose = this._handleListenCloseEvent.bind(this); + this.websocket.onerror = this._handleListenErrorEvent.bind(this); + this.websocket.onmessage = this._handleListenMessageEvent.bind(this); + }, + /** + * Opens long-poll connection. + * + */ + openLongPoll: function () { + var request = this.$['ajaxListen']; + request.url = this.longPollUrl + '?conn_id=' + this.connectionId; + request.generateRequest() + }, + /** + * Retries `connect()` call while incrementing interval between tries up to 1 minute. + * + */ + retryConnection: function () { + if (!this.shouldReconnect) { + return; + } + if (this._currentBounceIv < 60000) { + this._currentBounceIv = this._currentBounceIv + this.increaseBounceIv; + } + else { + this._currentBounceIv = 60000; + } + setTimeout(this.connect.bind(this), this._currentBounceIv); + }, + /** + * Closes listening connection. + * + */ + closeConnection: function () { + var request = this.$['ajaxListen']; + if (this.websocket && this.websocket.readyState === WebSocket.OPEN) { + this.websocket.onclose = null; + this.websocket.onerror = null; + this.websocket.close(); + } + if (request.loading) { + request.lastRequest.abort(); + } + this.connected = false; + }, + + _handleChannelsChange: function (event) { + // do not fire the event if set() didn't mutate anything + // is this a reliable way to do it? + if (!this.isReady || event === undefined) { + return + } + this.fire('channelstream-channels-changed', event) + }, + + _handleListenOpen: function (event) { + this.connected = true; + this.fire('channelstream-listen-opened', event); + this.createHeartBeats(); + }, + + createHeartBeats: function () { + if (typeof self._heartbeat === 'undefined' && this.websocket !== null + && this.heartbeats) { + self._heartbeat = setInterval(this._sendHeartBeat.bind(this), 10000); + } + }, + + _sendHeartBeat: function () { + if (this.websocket.readyState === WebSocket.OPEN && this.heartbeats) { + this.websocket.send(JSON.stringify({type: 'heartbeat'})); + } + }, + + _handleListenError: function (event) { + this.connected = false; + this.retryConnection(); + }, + _handleConnectError: function (event) { + this.connected = false; + this.fire('channelstream-connect-error', event.detail); + }, + + _handleListenMessageEvent: function (event) { + var data = null; + // comes from iron-ajax + if (event.detail) { + data = JSON.parse(event.detail.response) + // comes from websocket + setTimeout(this.openLongPoll.bind(this), 0); + } else { + data = JSON.parse(event.data) + } + this.fire('channelstream-listen-message', data); + + }, + + _handleListenCloseEvent: function (event) { + this.connected = false; + this.fire('channelstream-listen-closed', event.detail); + this.retryConnection(); + }, + + _handleListenErrorEvent: function (event) { + this.connected = false; + this.fire('channelstream-listen-error', {}) + }, + + _handleConnect: function (event) { + this.currentBounceIv = 0; + this.connectionId = event.detail.response.conn_id; + this.fire('channelstream-connected', event.detail.response); + }, + + _handleDisconnect: function (event) { + this.connected = false; + this.fire('channelstream-disconnected', {}); + }, + + _handleMessage: function (event) { + this.fire('channelstream-message-sent', event.detail.response); + }, + _handleMessageError: function (event) { + this.fire('channelstream-message-error', event.detail); + }, + + _handleSubscribe: function (event) { + this.fire('channelstream-subscribed', event.detail.response); + }, + + _handleSubscribeError: function (event) { + this.fire('channelstream-subscribe-error', event.detail); + }, + + _handleUnsubscribe: function (event) { + this.fire('channelstream-unsubscribed', event.detail.response); + }, + + _handleUnsubscribeError: function (event) { + this.fire('channelstream-unsubscribe-error', event.detail); + } +}); diff --git a/rhodecode/public/js/src/components/index.js b/rhodecode/public/js/src/components/index.js index 3dfb7084..d482a4ce 100644 --- a/rhodecode/public/js/src/components/index.js +++ b/rhodecode/public/js/src/components/index.js @@ -1,7 +1,7 @@ -import '../../../../../bower_components/iron-ajax/iron-ajax.html'; +import '@polymer/iron-ajax/iron-ajax.js'; import './root-styles.gen.html'; -import './channelstream-connection/channelstream-connection.html'; -import './rhodecode-toast/rhodecode-toast.html'; -import './rhodecode-toggle/rhodecode-toggle.html'; -import './rhodecode-unsafe-html/rhodecode-unsafe-html.html'; -import './rhodecode-app/rhodecode-app.html'; +import './channelstream-connection/channelstream-connection.js'; +import './rhodecode-toast/rhodecode-toast.js'; +import './rhodecode-toggle/rhodecode-toggle.js'; +import './rhodecode-unsafe-html/rhodecode-unsafe-html.js'; +import './rhodecode-app/rhodecode-app.js'; diff --git a/rhodecode/public/js/src/components/rhodecode-app/rhodecode-app.html b/rhodecode/public/js/src/components/rhodecode-app/rhodecode-app.html deleted file mode 100644 index b354610b..00000000 --- a/rhodecode/public/js/src/components/rhodecode-app/rhodecode-app.html +++ /dev/null @@ -1,195 +0,0 @@ - - - - - - - - - diff --git a/rhodecode/public/js/src/components/rhodecode-app/rhodecode-app.js b/rhodecode/public/js/src/components/rhodecode-app/rhodecode-app.js new file mode 100644 index 00000000..ad90b911 --- /dev/null +++ b/rhodecode/public/js/src/components/rhodecode-app/rhodecode-app.js @@ -0,0 +1,194 @@ +import {PolymerElement, html} from '@polymer/polymer/polymer-element.js'; +import '../channelstream-connection/channelstream-connection.js'; +import '../rhodecode-toast/rhodecode-toast.js'; +import '../rhodecode-favicon/rhodecode-favicon.js'; + +var ccLog = Logger.get('RhodeCodeApp'); +ccLog.setLevel(Logger.OFF); + +export class RhodecodeApp extends PolymerElement { + + static get is() { + return 'rhodecode-app'; + } + + static get template(){ + return html` + + + + ` + } + + connectedCallback() { + super.connectedCallback(); + ccLog.debug('rhodeCodeApp created'); + $.Topic('/notifications').subscribe(this.handleNotifications.bind(this)); + $.Topic('/favicon/update').subscribe(this.faviconUpdate.bind(this)); + $.Topic('/connection_controller/subscribe').subscribe( + this.subscribeToChannelTopic.bind(this)); + // this event can be used to coordinate plugins to do their + // initialization before channelstream is kicked off + $.Topic('/__MAIN_APP__').publish({}); + + for (var i = 0; i < alertMessagePayloads.length; i++) { + $.Topic('/notifications').publish(alertMessagePayloads[i]); + } + this.initPlugins(); + // after rest of application loads and topics get fired, launch connection + $(document).ready(function () { + this.kickoffChannelstreamPlugin(); + }.bind(this)); + } + + initPlugins() { + for (var i = 0; i < window.APPLICATION_PLUGINS.length; i++) { + var pluginDef = window.APPLICATION_PLUGINS[i]; + if (pluginDef.component) { + var pluginElem = document.createElement(pluginDef.component); + this.shadowRoot.appendChild(pluginElem); + if (typeof pluginElem.init !== 'undefined') { + pluginElem.init(); + } + } + } + } + + /** proxy to channelstream connection */ + getChannelStreamConnection() { + return this.$['channelstream-connection']; + } + + handleNotifications(data) { + var elem = document.getElementById('notifications'); + if (elem) { + elem.handleNotification(data); + } + + } + + faviconUpdate(data) { + this.shadowRoot.querySelector('rhodecode-favicon').counter = data.count; + } + + /** opens connection to ws server */ + kickoffChannelstreamPlugin(data) { + ccLog.debug('kickoffChannelstreamPlugin'); + var channels = ['broadcast']; + var addChannels = this.checkViewChannels(); + for (var i = 0; i < addChannels.length; i++) { + channels.push(addChannels[i]); + } + if (window.CHANNELSTREAM_SETTINGS && CHANNELSTREAM_SETTINGS.enabled) { + var channelstreamConnection = this.getChannelStreamConnection(); + channelstreamConnection.connectUrl = CHANNELSTREAM_URLS.connect; + channelstreamConnection.subscribeUrl = CHANNELSTREAM_URLS.subscribe; + channelstreamConnection.websocketUrl = CHANNELSTREAM_URLS.ws + '/ws'; + channelstreamConnection.longPollUrl = CHANNELSTREAM_URLS.longpoll + '/listen'; + // some channels might already be registered by topic + for (var i = 0; i < channels.length; i++) { + channelstreamConnection.push('channels', channels[i]); + } + // append any additional channels registered in other plugins + $.Topic('/connection_controller/subscribe').processPrepared(); + channelstreamConnection.connect(); + } + } + + checkViewChannels() { + // subscribe to different channels data is sent. + + var channels = []; + // subscribe to PR repo channel for PR's' + if (templateContext.pull_request_data.pull_request_id) { + var channelName = '/repo$' + templateContext.repo_name + '$/pr/' + + String(templateContext.pull_request_data.pull_request_id); + channels.push(channelName); + } + + if (templateContext.commit_data.commit_id) { + var channelName = '/repo$' + templateContext.repo_name + '$/commit/' + + String(templateContext.commit_data.commit_id); + channels.push(channelName); + } + + return channels; + } + + /** subscribes users from channels in channelstream */ + subscribeToChannelTopic(channels) { + var channelstreamConnection = this.getChannelStreamConnection(); + var toSubscribe = channelstreamConnection.calculateSubscribe(channels); + ccLog.debug('subscribeToChannelTopic', toSubscribe); + if (toSubscribe.length > 0) { + // if we are connected then subscribe + if (channelstreamConnection.connected) { + channelstreamConnection.subscribe(toSubscribe); + } + // not connected? just push channels onto the stack + else { + for (var i = 0; i < toSubscribe.length; i++) { + channelstreamConnection.push('channels', toSubscribe[i]); + } + } + } + } + + /** publish received messages into correct topic */ + receivedMessage(event) { + for (var i = 0; i < event.detail.length; i++) { + var message = event.detail[i]; + if (message.message.topic) { + ccLog.debug('publishing', message.message.topic); + $.Topic(message.message.topic).publish(message); + } + else if (message.type === 'presence') { + $.Topic('/connection_controller/presence').publish(message); + } + else { + ccLog.warn('unhandled message', message); + } + } + } + + handleConnected(event) { + var channelstreamConnection = this.getChannelStreamConnection(); + channelstreamConnection.set('channelsState', + event.detail.channels_info); + channelstreamConnection.set('userState', event.detail.state); + channelstreamConnection.set('channels', event.detail.channels); + this.propagageChannelsState(); + } + + handleSubscribed(event) { + var channelstreamConnection = this.getChannelStreamConnection(); + var channelInfo = event.detail.channels_info; + var channelKeys = Object.keys(event.detail.channels_info); + for (var i = 0; i < channelKeys.length; i++) { + var key = channelKeys[i]; + channelstreamConnection.set(['channelsState', key], channelInfo[key]); + } + channelstreamConnection.set('channels', event.detail.channels); + this.propagageChannelsState(); + } + + /** propagates channel states on topics */ + propagageChannelsState(event) { + var channelstreamConnection = this.getChannelStreamConnection(); + var channel_data = channelstreamConnection.channelsState; + var channels = channelstreamConnection.channels; + for (var i = 0; i < channels.length; i++) { + var key = channels[i]; + $.Topic('/connection_controller/channel_update').publish( + {channel: key, state: channel_data[key]} + ); + } + } + +} + +customElements.define(RhodecodeApp.is, RhodecodeApp); diff --git a/rhodecode/public/js/src/components/rhodecode-favicon/rhodecode-favicon.html b/rhodecode/public/js/src/components/rhodecode-favicon/rhodecode-favicon.html deleted file mode 100644 index af630dea..00000000 --- a/rhodecode/public/js/src/components/rhodecode-favicon/rhodecode-favicon.html +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - diff --git a/rhodecode/public/js/src/components/rhodecode-favicon/rhodecode-favicon.js b/rhodecode/public/js/src/components/rhodecode-favicon/rhodecode-favicon.js new file mode 100644 index 00000000..b78d6226 --- /dev/null +++ b/rhodecode/public/js/src/components/rhodecode-favicon/rhodecode-favicon.js @@ -0,0 +1,33 @@ +import {PolymerElement, html} from '@polymer/polymer/polymer-element.js'; + +export class RhodecodeFavicon extends PolymerElement { + + static get is() { + return 'rhodecode-favicon'; + } + + static get properties() { + return { + favicon: Object, + counter: { + type: Number, + observer: '_handleCounter' + } + } + } + + connectedCallback() { + super.connectedCallback(); + this.favicon = new Favico({ + type: 'rectangle', + animation: 'none' + }); + } + + _handleCounter(newVal, oldVal) { + this.favicon.badge(this.counter); + } + +} + +customElements.define(RhodecodeFavicon.is, RhodecodeFavicon); diff --git a/rhodecode/public/js/src/components/rhodecode-toast/rhodecode-toast.html b/rhodecode/public/js/src/components/rhodecode-toast/rhodecode-toast.html deleted file mode 100644 index 071bb4a6..00000000 --- a/rhodecode/public/js/src/components/rhodecode-toast/rhodecode-toast.html +++ /dev/null @@ -1,233 +0,0 @@ - - - - - - - - diff --git a/rhodecode/public/js/src/components/rhodecode-toast/rhodecode-toast.js b/rhodecode/public/js/src/components/rhodecode-toast/rhodecode-toast.js new file mode 100644 index 00000000..379adeac --- /dev/null +++ b/rhodecode/public/js/src/components/rhodecode-toast/rhodecode-toast.js @@ -0,0 +1,231 @@ +import {PolymerElement, html} from '@polymer/polymer/polymer-element.js'; +import '@polymer/paper-toggle-button/paper-toggle-button.js'; +import {mixinBehaviors} from '@polymer/polymer/lib/legacy/class.js'; +import {IronA11yKeysBehavior} from '@polymer/iron-a11y-keys-behavior/iron-a11y-keys-behavior.js'; +import '../rhodecode-unsafe-html/rhodecode-unsafe-html.js'; + +export class RhodecodeToast extends mixinBehaviors([IronA11yKeysBehavior], PolymerElement) { + + static get is() { + return 'rhodecode-toast'; + } + + static get template(){ + return html` + + + + ` + } + + static get properties() { + return { + toasts: { + type: Array, + value() { + return [] + } + }, + isFixed: { + type: Boolean, + value: false + }, + hasToasts: { + type: Boolean, + computed: '_computeHasToasts(toasts.*)' + }, + keyEventTarget: { + type: Object, + value() { + return document.body; + } + } + } + } + + get keyBindings() { + return { + 'esc:keyup': '_hideOnEsc' + } + } + + static get observers() { + return [ + '_changedToasts(toasts.splices)' + ] + } + + _hideOnEsc(event) { + return this.dismissNotifications(); + } + + _computeHasToasts() { + return this.toasts.length > 0; + } + + _debouncedCalc() { + // calculate once in a while + this.debounce('debouncedCalc', this.toastInWindow, 25); + } + + conditionalClass() { + return this.isFixed ? 'fixed' : ''; + } + + toastInWindow() { + if (!this._headerNode) { + return true + } + var headerHeight = this._headerNode.offsetHeight; + var scrollPosition = window.scrollY; + + if (this.isFixed) { + this.isFixed = 1 <= scrollPosition; + } + else { + this.isFixed = headerHeight <= scrollPosition; + } + } + + connectedCallback() { + super.connectedCallback(); + this._headerNode = document.querySelector('.header', document); + this.listen(window, 'scroll', '_debouncedCalc'); + this.listen(window, 'resize', '_debouncedCalc'); + this._debouncedCalc(); + } + + _changedToasts(newValue, oldValue) { + $.Topic('/favicon/update').publish({count: this.toasts.length}); + } + + dismissNotification(e) { + $.Topic('/favicon/update').publish({count: this.toasts.length - 1}); + var idx = e.target.parentNode.indexPos + this.splice('toasts', idx, 1); + + } + + dismissNotifications() { + $.Topic('/favicon/update').publish({count: 0}); + this.splice('toasts', 0); + } + + handleNotification(data) { + if (!templateContext.rhodecode_user.notification_status && !data.message.force) { + // do not act if notifications are disabled + return + } + this.push('toasts', { + level: data.message.level, + message: data.message.message + }); + } + + _gettext(x){ + return _gettext(x) + } + +} + +customElements.define(RhodecodeToast.is, RhodecodeToast); diff --git a/rhodecode/public/js/src/components/rhodecode-toggle/rhodecode-toggle.html b/rhodecode/public/js/src/components/rhodecode-toggle/rhodecode-toggle.html deleted file mode 100644 index 8af67abe..00000000 --- a/rhodecode/public/js/src/components/rhodecode-toggle/rhodecode-toggle.html +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - - - - - - - diff --git a/rhodecode/public/js/src/components/rhodecode-toggle/rhodecode-toggle.js b/rhodecode/public/js/src/components/rhodecode-toggle/rhodecode-toggle.js new file mode 100644 index 00000000..a5852ada --- /dev/null +++ b/rhodecode/public/js/src/components/rhodecode-toggle/rhodecode-toggle.js @@ -0,0 +1,57 @@ +import {PolymerElement, html} from '@polymer/polymer/polymer-element.js'; +import '@polymer/paper-toggle-button/paper-toggle-button.js'; +import '@polymer/paper-spinner/paper-spinner.js'; +import '@polymer/paper-tooltip/paper-tooltip.js'; + +export class RhodecodeToggle extends PolymerElement { + + static get is() { + return 'rhodecode-toggle'; + } + + static get template() { + return html` + +
+ [[labelStatus(checked)]] + + [[tooltipText]] + +
+ `; + } + + static get properties() { + return { + noSpinner: {type: Boolean, value: false, reflectToAttribute: true}, + tooltipText: {type: String, value: "Click to toggle", reflectToAttribute: true}, + checked: {type: Boolean, value: false, reflectToAttribute: true}, + active: {type: Boolean, value: false, reflectToAttribute: true, notify: true} + } + } + + shouldShow() { + return !this.noSpinner + } + + labelStatus(isActive) { + return this.checked ? 'Enabled' : "Disabled" + } +} + +customElements.define(RhodecodeToggle.is, RhodecodeToggle); diff --git a/rhodecode/public/js/src/components/rhodecode-unsafe-html/rhodecode-unsafe-html.html b/rhodecode/public/js/src/components/rhodecode-unsafe-html/rhodecode-unsafe-html.html deleted file mode 100644 index 143f1bf5..00000000 --- a/rhodecode/public/js/src/components/rhodecode-unsafe-html/rhodecode-unsafe-html.html +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - diff --git a/rhodecode/public/js/src/components/rhodecode-unsafe-html/rhodecode-unsafe-html.js b/rhodecode/public/js/src/components/rhodecode-unsafe-html/rhodecode-unsafe-html.js new file mode 100644 index 00000000..7290a207 --- /dev/null +++ b/rhodecode/public/js/src/components/rhodecode-unsafe-html/rhodecode-unsafe-html.js @@ -0,0 +1,30 @@ +import {PolymerElement, html} from '@polymer/polymer/polymer-element.js'; + +export class RhodecodeUnsafeHtml extends PolymerElement { + + static get is() { + return 'rhodecode-unsafe-html'; + } + + static get template() { + return html` + + + `; + } + + static get properties() { + return { + text: { + type: String, + observer: '_handleText' + } + } + } + + _handleText(newVal, oldVal) { + this.innerHTML = this.text; + } +} + +customElements.define(RhodecodeUnsafeHtml.is, RhodecodeUnsafeHtml);