Merge pull request !2871 from rhodecode-enterprise-ce Polymer-lit-migration-1
Changes from branch: Polymer lit migration 1
This commit is contained in:
commit
10ad0686cb
29 changed files with 5694 additions and 14679 deletions
7
.babelrc
7
.babelrc
|
|
@ -4,10 +4,9 @@
|
||||||
"env",
|
"env",
|
||||||
{
|
{
|
||||||
"targets": {
|
"targets": {
|
||||||
"browsers": [
|
"esmodules": true
|
||||||
"last 2 versions"
|
},
|
||||||
]
|
"exclude": ["transform-es2015-classes"]
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
|
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -39,7 +39,7 @@ rhodecode/public/js/src/components/**/*.css
|
||||||
/rcextensions/
|
/rcextensions/
|
||||||
/result
|
/result
|
||||||
/rhodecode/public/css/style.css
|
/rhodecode/public/css/style.css
|
||||||
/rhodecode/public/css/style-polymer.css
|
/rhodecode/public/css/style-lit.css
|
||||||
/rhodecode/public/css/style-ipython.css
|
/rhodecode/public/css/style-ipython.css
|
||||||
/rhodecode/public/js/rhodecode-components.html
|
/rhodecode/public/js/rhodecode-components.html
|
||||||
/rhodecode/public/js/rhodecode-components.js
|
/rhodecode/public/js/rhodecode-components.js
|
||||||
|
|
|
||||||
|
|
@ -13,10 +13,10 @@ module.exports = function(grunt) {
|
||||||
|
|
||||||
grunt.loadNpmTasks('grunt-contrib-less');
|
grunt.loadNpmTasks('grunt-contrib-less');
|
||||||
grunt.loadNpmTasks('grunt-contrib-concat');
|
grunt.loadNpmTasks('grunt-contrib-concat');
|
||||||
grunt.loadNpmTasks('grunt-contrib-uglify');
|
grunt.loadNpmTasks('grunt-terser');
|
||||||
grunt.loadNpmTasks('grunt-contrib-watch');
|
grunt.loadNpmTasks('grunt-contrib-watch');
|
||||||
grunt.loadNpmTasks('grunt-contrib-jshint');
|
grunt.loadNpmTasks('grunt-contrib-jshint');
|
||||||
grunt.loadNpmTasks('grunt-contrib-copy');
|
grunt.loadNpmTasks('grunt-contrib-copy');
|
||||||
grunt.loadNpmTasks('grunt-webpack');
|
grunt.loadNpmTasks('grunt-webpack');
|
||||||
grunt.registerTask('default', ['less:production', 'less:components', 'copy', 'webpack', 'concat:dist', 'uglify:dist']);
|
grunt.registerTask('default', ['less:production', 'less:components', 'copy', 'webpack', 'concat:dist', 'terser:dist']);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
3
Makefile
3
Makefile
|
|
@ -160,9 +160,10 @@ docs-cleanup:
|
||||||
|
|
||||||
.PHONY: web-build
|
.PHONY: web-build
|
||||||
# web-build: Build JS packages static/js
|
# web-build: Build JS packages static/js
|
||||||
|
# TODO need to upgrade to webpack 5 and remove -e NODE_OPTIONS=--openssl-legacy-provider
|
||||||
web-build:
|
web-build:
|
||||||
rm -rf node_modules
|
rm -rf node_modules
|
||||||
docker run -it --rm -v $(PWD):/project --workdir=/project rhodecode/static-files-build:16 -c "npm install && /project/node_modules/.bin/grunt"
|
docker run -it --rm -e NODE_OPTIONS=--openssl-legacy-provider -v $(PWD):/project --workdir=/project rhodecode/static-files-build:24 -c "npm install && /project/node_modules/.bin/grunt"
|
||||||
# run static file check
|
# run static file check
|
||||||
./rhodecode/tests/scripts/static-file-check.sh rhodecode/public/
|
./rhodecode/tests/scripts/static-file-check.sh rhodecode/public/
|
||||||
rm -rf node_modules
|
rm -rf node_modules
|
||||||
|
|
|
||||||
|
|
@ -667,13 +667,14 @@ search.location = %(here)s/data/index
|
||||||
channelstream.enabled = true
|
channelstream.enabled = true
|
||||||
|
|
||||||
; server address for channelstream server on the backend
|
; server address for channelstream server on the backend
|
||||||
channelstream.server = channelstream:9800
|
channelstream.server = channelstream:8000
|
||||||
|
|
||||||
; location of the channelstream server from outside world
|
; location of the channelstream server from outside world
|
||||||
; use ws:// for http or wss:// for https. This address needs to be handled
|
; use ws:// for http or wss:// for https. This address needs to be handled
|
||||||
; by external HTTP server such as Nginx or Apache
|
; by external HTTP server such as Nginx or Apache
|
||||||
; see Nginx/Apache configuration examples in our docs
|
; see Nginx/Apache configuration examples in our docs
|
||||||
channelstream.ws_url = ws://rhodecode.yourserver.com/_channelstream
|
; For development, comment this out to use auto-generated proxy URL
|
||||||
|
;channelstream.ws_url = ws://rhodecode.yourserver.com/_channelstream
|
||||||
channelstream.secret = ENV_GENERATED
|
channelstream.secret = ENV_GENERATED
|
||||||
channelstream.history.location = /var/opt/rhodecode_data/channelstream_history
|
channelstream.history.location = /var/opt/rhodecode_data/channelstream_history
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -629,7 +629,7 @@ search.location = %(here)s/data/index
|
||||||
channelstream.enabled = true
|
channelstream.enabled = true
|
||||||
|
|
||||||
; server address for channelstream server on the backend
|
; server address for channelstream server on the backend
|
||||||
channelstream.server = channelstream:9800
|
channelstream.server = channelstream:8000
|
||||||
|
|
||||||
; location of the channelstream server from outside world
|
; location of the channelstream server from outside world
|
||||||
; use ws:// for http or wss:// for https. This address needs to be handled
|
; use ws:// for http or wss:// for https. This address needs to be handled
|
||||||
|
|
|
||||||
|
|
@ -52,10 +52,12 @@ Supported Databases
|
||||||
Supported Browsers
|
Supported Browsers
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
* Chrome
|
* Chrome 67+
|
||||||
* Safari
|
* Firefox 63+
|
||||||
* Firefox
|
* Safari 11.1+
|
||||||
* Internet Explorer 10 & 11
|
* Edge 79+
|
||||||
|
|
||||||
|
Note: Internet Explorer is no longer supported
|
||||||
|
|
||||||
System Requirements
|
System Requirements
|
||||||
-------------------
|
-------------------
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,8 @@
|
||||||
"dest": "<%= dirs.js.dest %>/vendors"
|
"dest": "<%= dirs.js.dest %>/vendors"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "<%= dirs.css.src %>/style-polymer.css",
|
"src": "<%= dirs.css.src %>/style-lit.css",
|
||||||
"dest": "<%= dirs.js.dest %>/src/components/style-polymer.css"
|
"dest": "<%= dirs.js.dest %>/src/components/style-lit.css"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -102,10 +102,47 @@
|
||||||
"nonull": true
|
"nonull": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"uglify": {
|
"terser": {
|
||||||
"dist": {
|
"dist": {
|
||||||
"src": "<%= dirs.js.dest %>/scripts.js",
|
"options": {
|
||||||
"dest": "<%= dirs.js.dest %>/scripts.min.js"
|
"compress": {
|
||||||
|
"ecma": 6,
|
||||||
|
"arrows": false,
|
||||||
|
"collapse_vars": false,
|
||||||
|
"comparisons": false,
|
||||||
|
"computed_props": false,
|
||||||
|
"hoist_funs": false,
|
||||||
|
"hoist_props": false,
|
||||||
|
"hoist_vars": false,
|
||||||
|
"inline": false,
|
||||||
|
"loops": false,
|
||||||
|
"negate_iife": false,
|
||||||
|
"properties": false,
|
||||||
|
"reduce_funcs": false,
|
||||||
|
"reduce_vars": false,
|
||||||
|
"switches": false,
|
||||||
|
"toplevel": false,
|
||||||
|
"typeofs": false,
|
||||||
|
"booleans": true,
|
||||||
|
"if_return": true,
|
||||||
|
"sequences": true,
|
||||||
|
"unused": true,
|
||||||
|
"conditionals": true,
|
||||||
|
"dead_code": true,
|
||||||
|
"evaluate": true
|
||||||
|
},
|
||||||
|
"mangle": {
|
||||||
|
"keep_classnames": true,
|
||||||
|
"keep_fnames": false,
|
||||||
|
"reserved": ["noChange", "nothing"]
|
||||||
|
},
|
||||||
|
"output": {
|
||||||
|
"ecma": 6
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"files": {
|
||||||
|
"<%= dirs.js.dest %>/scripts.min.js": "<%= dirs.js.dest %>/scripts.js"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"less": {
|
"less": {
|
||||||
|
|
@ -113,11 +150,12 @@
|
||||||
"options": {
|
"options": {
|
||||||
"compress": false,
|
"compress": false,
|
||||||
"yuicompress": false,
|
"yuicompress": false,
|
||||||
"optimization": 0
|
"optimization": 0,
|
||||||
|
"math": "always"
|
||||||
},
|
},
|
||||||
"files": {
|
"files": {
|
||||||
"<%= dirs.css.dest %>/style.css": "<%= dirs.css.src %>/main.less",
|
"<%= dirs.css.dest %>/style.css": "<%= dirs.css.src %>/main.less",
|
||||||
"<%= dirs.css.dest %>/style-polymer.css": "<%= dirs.css.src %>/polymer.less",
|
"<%= dirs.css.dest %>/style-lit.css": "<%= dirs.css.src %>/lit-components.less",
|
||||||
"<%= dirs.css.dest %>/style-ipython.css": "<%= dirs.css.src %>/ipython.less"
|
"<%= dirs.css.dest %>/style-ipython.css": "<%= dirs.css.src %>/ipython.less"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -125,15 +163,19 @@
|
||||||
"options": {
|
"options": {
|
||||||
"compress": true,
|
"compress": true,
|
||||||
"yuicompress": true,
|
"yuicompress": true,
|
||||||
"optimization": 2
|
"optimization": 2,
|
||||||
|
"math": "always"
|
||||||
},
|
},
|
||||||
"files": {
|
"files": {
|
||||||
"<%= dirs.css.dest %>/style.css": "<%= dirs.css.src %>/main.less",
|
"<%= dirs.css.dest %>/style.css": "<%= dirs.css.src %>/main.less",
|
||||||
"<%= dirs.css.dest %>/style-polymer.css": "<%= dirs.css.src %>/polymer.less",
|
"<%= dirs.css.dest %>/style-lit.css": "<%= dirs.css.src %>/lit-components.less",
|
||||||
"<%= dirs.css.dest %>/style-ipython.css": "<%= dirs.css.src %>/ipython.less"
|
"<%= dirs.css.dest %>/style-ipython.css": "<%= dirs.css.src %>/ipython.less"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
|
"options": {
|
||||||
|
"math": "always"
|
||||||
|
},
|
||||||
"files": [
|
"files": [
|
||||||
{
|
{
|
||||||
"cwd": "<%= dirs.js.src %>/components/",
|
"cwd": "<%= dirs.js.src %>/components/",
|
||||||
|
|
@ -156,7 +198,6 @@
|
||||||
"tasks": [
|
"tasks": [
|
||||||
"less:development",
|
"less:development",
|
||||||
"less:components",
|
"less:components",
|
||||||
"concat:polymercss",
|
|
||||||
"webpack",
|
"webpack",
|
||||||
"concat:dist"
|
"concat:dist"
|
||||||
]
|
]
|
||||||
|
|
@ -169,7 +210,6 @@
|
||||||
],
|
],
|
||||||
"tasks": [
|
"tasks": [
|
||||||
"less:components",
|
"less:components",
|
||||||
"concat:polymercss",
|
|
||||||
"webpack",
|
"webpack",
|
||||||
"concat:dist"
|
"concat:dist"
|
||||||
]
|
]
|
||||||
|
|
|
||||||
18420
package-lock.json
generated
18420
package-lock.json
generated
File diff suppressed because it is too large
Load diff
37
package.json
37
package.json
|
|
@ -9,18 +9,11 @@
|
||||||
"url": "https://code.rhodecode.com/rhodecode-enterprise-ce"
|
"url": "https://code.rhodecode.com/rhodecode-enterprise-ce"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@polymer/iron-a11y-keys": "^3.0.0",
|
"@webcomponents/webcomponentsjs": "^2.8.0",
|
||||||
"@polymer/iron-ajax": "^3.0.0",
|
|
||||||
"@polymer/iron-autogrow-textarea": "^3.0.0",
|
|
||||||
"@polymer/paper-button": "^3.0.0",
|
|
||||||
"@polymer/paper-spinner": "^3.0.0",
|
|
||||||
"@polymer/paper-toast": "^3.0.0",
|
|
||||||
"@polymer/paper-toggle-button": "^3.0.0",
|
|
||||||
"@polymer/paper-tooltip": "^3.0.0",
|
|
||||||
"@polymer/polymer": "^3.0.0",
|
|
||||||
"@webcomponents/webcomponentsjs": "^2.0.0",
|
|
||||||
"babel-core": "^6.26.3",
|
"babel-core": "^6.26.3",
|
||||||
"babel-loader": "^7.1.2",
|
"babel-loader": "^7.1.2",
|
||||||
|
"babel-plugin-syntax-class-properties": "^6.13.0",
|
||||||
|
"babel-plugin-transform-class-properties": "^6.24.1",
|
||||||
"babel-plugin-transform-object-rest-spread": "^6.26.0",
|
"babel-plugin-transform-object-rest-spread": "^6.26.0",
|
||||||
"babel-preset-env": "^1.6.0",
|
"babel-preset-env": "^1.6.0",
|
||||||
"clipboard": "^2.0.1",
|
"clipboard": "^2.0.1",
|
||||||
|
|
@ -29,15 +22,15 @@
|
||||||
"dropzone": "^5.5.0",
|
"dropzone": "^5.5.0",
|
||||||
"exports-loader": "^0.6.4",
|
"exports-loader": "^0.6.4",
|
||||||
"favico.js": "^0.3.10",
|
"favico.js": "^0.3.10",
|
||||||
"grunt": "^0.4.5",
|
"grunt": "^1.6.1",
|
||||||
"grunt-cli": "^1.4.3",
|
"grunt-cli": "^1.4.3",
|
||||||
"grunt-contrib-concat": "^0.5.1",
|
"grunt-contrib-concat": "^2.1.0",
|
||||||
"grunt-contrib-copy": "^1.0.0",
|
"grunt-contrib-copy": "^1.0.0",
|
||||||
"grunt-contrib-jshint": "^0.12.0",
|
"grunt-contrib-jshint": "^3.2.0",
|
||||||
"grunt-contrib-less": "^1.1.0",
|
"grunt-contrib-less": "^3.0.0",
|
||||||
"grunt-contrib-uglify": "^4.0.1",
|
"grunt-contrib-watch": "^1.1.0",
|
||||||
"grunt-contrib-watch": "^0.6.1",
|
"grunt-terser": "^2.0.0",
|
||||||
"grunt-webpack": "^3.1.3",
|
"grunt-webpack": "^4.0.3",
|
||||||
"html-loader": "^0.4.4",
|
"html-loader": "^0.4.4",
|
||||||
"html-webpack-plugin": "^3.2.0",
|
"html-webpack-plugin": "^3.2.0",
|
||||||
"imports-loader": "^0.7.1",
|
"imports-loader": "^0.7.1",
|
||||||
|
|
@ -47,15 +40,17 @@
|
||||||
"luxon": "^3.7.2",
|
"luxon": "^3.7.2",
|
||||||
"mark.js": "8.11.1",
|
"mark.js": "8.11.1",
|
||||||
"mousetrap": "^1.6.1",
|
"mousetrap": "^1.6.1",
|
||||||
"polymer-webpack-loader": "^2.0.1",
|
|
||||||
"raw-loader": "1.0.0-beta.0",
|
"raw-loader": "1.0.0-beta.0",
|
||||||
"sticky-sidebar": "3.3.1",
|
"sticky-sidebar": "3.3.1",
|
||||||
"style-loader": "^0.21.0",
|
"style-loader": "^0.21.0",
|
||||||
"sweetalert2": "^9.10.12",
|
"sweetalert2": "^9.10.12",
|
||||||
"ts-loader": "^1.3.3",
|
"ts-loader": "^1.3.3",
|
||||||
"waypoints": "4.0.1",
|
"waypoints": "4.0.1",
|
||||||
"webpack": "4.23.1",
|
"webpack": "4.46.0",
|
||||||
"webpack-cli": "3.1.2",
|
"webpack-cli": "3.3.12"
|
||||||
"webpack-uglify-js-plugin": "^1.1.9"
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"lit": "^2.8.0",
|
||||||
|
"regenerator-runtime": "^0.13.9"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,8 @@ controllers
|
||||||
import logging
|
import logging
|
||||||
import socket
|
import socket
|
||||||
import base64
|
import base64
|
||||||
|
import hashlib
|
||||||
|
import os
|
||||||
|
|
||||||
import markupsafe
|
import markupsafe
|
||||||
import ipaddress
|
import ipaddress
|
||||||
|
|
|
||||||
|
|
@ -249,13 +249,13 @@
|
||||||
|
|
||||||
.field-sm {
|
.field-sm {
|
||||||
.label {
|
.label {
|
||||||
padding-top: @input-padding-px / 2 + @input-border-thickness;
|
padding-top: (@input-padding-px / 2) + @input-border-thickness;
|
||||||
}
|
}
|
||||||
.checkboxes,
|
.checkboxes,
|
||||||
.radios {
|
.radios {
|
||||||
// TODO: johbo: We get a 4px margin from the from-bootstrap,
|
// TODO: johbo: We get a 4px margin from the from-bootstrap,
|
||||||
// compensating here to align well with labels on the left.
|
// compensating here to align well with labels on the left.
|
||||||
padding-top: @input-padding-px / 2 + @input-border-thickness - 3px;
|
padding-top: (@input-padding-px / 2) + @input-border-thickness - 3px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2117,7 +2117,7 @@ BIN_FILENODE = 7
|
||||||
.notification-header{
|
.notification-header{
|
||||||
display: table;
|
display: table;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: floor(@basefontsize/2) 0;
|
padding: floor((@basefontsize / 2)) 0;
|
||||||
line-height: 1em;
|
line-height: 1em;
|
||||||
|
|
||||||
.desc, .delete-notifications, .read-notifications{
|
.desc, .delete-notifications, .read-notifications{
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@
|
||||||
.circle (@radius:20,@color:#000) {
|
.circle (@radius:20,@color:#000) {
|
||||||
height: @radius;
|
height: @radius;
|
||||||
width: @radius;
|
width: @radius;
|
||||||
padding: round(@radius/2);
|
padding: round((@radius / 2));
|
||||||
font-size: @radius;
|
font-size: @radius;
|
||||||
line-height:1em;
|
line-height:1em;
|
||||||
.border-radius(50%);
|
.border-radius(50%);
|
||||||
|
|
|
||||||
|
|
@ -1,577 +1,445 @@
|
||||||
import {Polymer, html} from '@polymer/polymer/polymer-legacy';
|
import {LitElement} from 'lit';
|
||||||
import '@polymer/iron-ajax/iron-ajax.js';
|
|
||||||
|
|
||||||
const elemTemplate = html`
|
/**
|
||||||
<iron-ajax
|
* Simplified Lit version of channelstream-connection
|
||||||
id="ajaxConnect"
|
* Replaces iron-ajax with native fetch()
|
||||||
url=""
|
*/
|
||||||
handle-as="json"
|
export class ChannelstreamConnection extends LitElement {
|
||||||
method="post"
|
static properties = {
|
||||||
content-type="application/json"
|
channels: {type: Array},
|
||||||
loading="{{loadingConnect}}"
|
username: {type: String},
|
||||||
last-response="{{connectLastResponse}}"
|
connectionId: {type: String},
|
||||||
on-response="_handleConnect"
|
websocket: {type: Object},
|
||||||
on-error="_handleConnectError"
|
websocketUrl: {type: String},
|
||||||
debounce-duration="100"></iron-ajax>
|
connectUrl: {type: String},
|
||||||
|
disconnectUrl: {type: String},
|
||||||
|
subscribeUrl: {type: String},
|
||||||
|
unsubscribeUrl: {type: String},
|
||||||
|
messageUrl: {type: String},
|
||||||
|
longPollUrl: {type: String},
|
||||||
|
shouldReconnect: {type: Boolean},
|
||||||
|
heartbeats: {type: Boolean},
|
||||||
|
increaseBounceIv: {type: Number},
|
||||||
|
_currentBounceIv: {type: Number},
|
||||||
|
useWebsocket: {type: Boolean},
|
||||||
|
connected: {type: Boolean},
|
||||||
|
channelsState: {type: Object},
|
||||||
|
userState: {type: Object}
|
||||||
|
};
|
||||||
|
|
||||||
<iron-ajax
|
constructor() {
|
||||||
id="ajaxDisconnect"
|
super();
|
||||||
url=""
|
this.channels = [];
|
||||||
handle-as="json"
|
this.username = 'Anonymous';
|
||||||
method="post"
|
this.connectionId = '';
|
||||||
content-type="application/json"
|
this.websocket = null;
|
||||||
loading="{{loadingDisconnect}}"
|
this.websocketUrl = '';
|
||||||
last-response="{{_disconnectLastResponse}}"
|
this.connectUrl = '';
|
||||||
on-response="_handleDisconnect"
|
this.disconnectUrl = '';
|
||||||
debounce-duration="100"></iron-ajax>
|
this.subscribeUrl = '';
|
||||||
|
this.unsubscribeUrl = '';
|
||||||
|
this.messageUrl = '';
|
||||||
|
this.longPollUrl = '';
|
||||||
|
this.shouldReconnect = true;
|
||||||
|
this.heartbeats = true;
|
||||||
|
this.increaseBounceIv = 2000;
|
||||||
|
this._currentBounceIv = 0;
|
||||||
|
this.useWebsocket = true;
|
||||||
|
this.connected = false;
|
||||||
|
this.channelsState = {};
|
||||||
|
this.userState = {};
|
||||||
|
|
||||||
<iron-ajax
|
// Mutators - simplified to just arrays
|
||||||
id="ajaxSubscribe"
|
this.mutators = {
|
||||||
url=""
|
connect: [],
|
||||||
handle-as="json"
|
message: [],
|
||||||
method="post"
|
subscribe: [],
|
||||||
content-type="application/json"
|
unsubscribe: [],
|
||||||
loading="{{loadingSubscribe}}"
|
disconnect: []
|
||||||
last-response="{{subscribeLastResponse}}"
|
};
|
||||||
on-response="_handleSubscribe"
|
|
||||||
debounce-duration="100"></iron-ajax>
|
|
||||||
|
|
||||||
<iron-ajax
|
this._heartbeatInterval = null;
|
||||||
id="ajaxUnsubscribe"
|
}
|
||||||
url=""
|
|
||||||
handle-as="json"
|
|
||||||
method="post"
|
|
||||||
content-type="application/json"
|
|
||||||
loading="{{loadingUnsubscribe}}"
|
|
||||||
last-response="{{unsubscribeLastResponse}}"
|
|
||||||
on-response="_handleUnsubscribe"
|
|
||||||
debounce-duration="100"></iron-ajax>
|
|
||||||
|
|
||||||
<iron-ajax
|
// No render needed - this component has no template
|
||||||
id="ajaxMessage"
|
render() {
|
||||||
url=""
|
return null;
|
||||||
handle-as="json"
|
}
|
||||||
method="post"
|
|
||||||
content-type="application/json"
|
|
||||||
loading="{{loadingMessage}}"
|
|
||||||
last-response="{{messageLastResponse}}"
|
|
||||||
on-response="_handleMessage"
|
|
||||||
on-error="_handleMessageError"
|
|
||||||
debounce-duration="100"></iron-ajax>
|
|
||||||
|
|
||||||
<iron-ajax
|
updated(changedProperties) {
|
||||||
id="ajaxListen"
|
if (changedProperties.has('channels')) {
|
||||||
url=""
|
this.dispatchEvent(new CustomEvent('channelstream-channels-changed', {
|
||||||
handle-as="text"
|
detail: {value: this.channels}
|
||||||
loading="{{loadingListen}}"
|
}));
|
||||||
last-response="{{listenLastResponse}}"
|
|
||||||
on-request="_handleListenOpen"
|
|
||||||
on-error="_handleListenError"
|
|
||||||
on-response="_handleListenMessageEvent"
|
|
||||||
debounce-duration="100"></iron-ajax>
|
|
||||||
`
|
|
||||||
|
|
||||||
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: [
|
connectedCallback() {
|
||||||
'_handleChannelsChange(channels.splices)'
|
super.connectedCallback();
|
||||||
],
|
|
||||||
|
|
||||||
listeners: {
|
// Set up self-event listeners (replaces Lit's 'listeners' property)
|
||||||
'channelstream-connected': 'startListening',
|
this.addEventListener('channelstream-connected', this.startListening.bind(this));
|
||||||
'channelstream-connect-error': 'retryConnection',
|
this.addEventListener('channelstream-connect-error', this.retryConnection.bind(this));
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mutators hold functions that you can set locally to change the data
|
* Helper method for fetch requests
|
||||||
* 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: {
|
async _fetchJSON(url, options = {}) {
|
||||||
connect: function () {
|
const {body, headers, ...fetchOptions} = options;
|
||||||
return []
|
|
||||||
}(),
|
const response = await fetch(url, {
|
||||||
message: function () {
|
method: 'POST',
|
||||||
return []
|
headers: {
|
||||||
}(),
|
'Content-Type': 'application/json',
|
||||||
subscribe: function () {
|
...headers
|
||||||
return []
|
},
|
||||||
}(),
|
body: body ? JSON.stringify(body) : undefined,
|
||||||
unsubscribe: function () {
|
...fetchOptions
|
||||||
return []
|
});
|
||||||
}(),
|
|
||||||
disconnect: function () {
|
if (!response.ok) {
|
||||||
return []
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||||
}()
|
}
|
||||||
},
|
|
||||||
ready: function () {
|
return response.json();
|
||||||
this.isReady = true;
|
}
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Connects user and fetches connection id from the server.
|
* Connects user and fetches connection id from the server
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
connect: function () {
|
async connect() {
|
||||||
var request = this.$['ajaxConnect'];
|
const body = {
|
||||||
request.url = this.connectUrl;
|
|
||||||
request.body = {
|
|
||||||
username: this.username,
|
username: this.username,
|
||||||
channels: this.channels
|
channels: this.channels
|
||||||
};
|
};
|
||||||
for (var i = 0; i < this.mutators.connect.length; i++) {
|
|
||||||
this.mutators.connect[i](request);
|
// Apply mutators
|
||||||
|
for (let mutator of this.mutators.connect) {
|
||||||
|
mutator({body});
|
||||||
}
|
}
|
||||||
request.generateRequest()
|
|
||||||
},
|
try {
|
||||||
|
const response = await this._fetchJSON(this.connectUrl, {body});
|
||||||
|
this._handleConnect({detail: {response}});
|
||||||
|
} catch (error) {
|
||||||
|
this._handleConnectError({detail: {error}});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Overwrite with custom function that will
|
* Subscribes user to channels
|
||||||
*/
|
*/
|
||||||
addMutator: function (type, func) {
|
async subscribe(channels) {
|
||||||
this.mutators[type].push(func);
|
const body = {
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Subscribes user to channels.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
subscribe: function (channels) {
|
|
||||||
var request = this.$['ajaxSubscribe'];
|
|
||||||
request.url = this.subscribeUrl;
|
|
||||||
request.body = {
|
|
||||||
channels: channels,
|
channels: channels,
|
||||||
conn_id: this.connectionId
|
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;
|
// Apply mutators
|
||||||
request.body = {
|
for (let mutator of this.mutators.subscribe) {
|
||||||
channels: unsubscribe,
|
mutator({body});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (body.channels.length) {
|
||||||
|
try {
|
||||||
|
const response = await this._fetchJSON(this.subscribeUrl, {body});
|
||||||
|
this._handleSubscribe({detail: {response}});
|
||||||
|
} catch (error) {
|
||||||
|
this.dispatchEvent(new CustomEvent('channelstream-subscribe-error', {detail: error}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unsubscribes user from channels
|
||||||
|
*/
|
||||||
|
async unsubscribe(channels) {
|
||||||
|
const body = {
|
||||||
|
channels: channels,
|
||||||
conn_id: this.connectionId
|
conn_id: this.connectionId
|
||||||
};
|
};
|
||||||
for (var i = 0; i < this.mutators.unsubscribe.length; i++) {
|
|
||||||
this.mutators.unsubscribe[i](request);
|
// Apply mutators
|
||||||
|
for (let mutator of this.mutators.unsubscribe) {
|
||||||
|
mutator({body});
|
||||||
}
|
}
|
||||||
request.generateRequest()
|
|
||||||
},
|
try {
|
||||||
|
const response = await this._fetchJSON(this.unsubscribeUrl, {body});
|
||||||
|
this._handleUnsubscribe({detail: {response}});
|
||||||
|
} catch (error) {
|
||||||
|
this.dispatchEvent(new CustomEvent('channelstream-unsubscribe-error', {detail: error}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* calculates list of channels we should add user to based on difference
|
* Calculates list of channels to add user to
|
||||||
* between channels property and passed channel list
|
|
||||||
*/
|
*/
|
||||||
calculateSubscribe: function (channels) {
|
calculateSubscribe(channels) {
|
||||||
var currentlySubscribed = this.channels;
|
const currentlySubscribed = this.channels;
|
||||||
var toSubscribe = [];
|
const toSubscribe = [];
|
||||||
for (var i = 0; i < channels.length; i++) {
|
for (let i = 0; i < channels.length; i++) {
|
||||||
if (currentlySubscribed.indexOf(channels[i]) === -1) {
|
if (currentlySubscribed.indexOf(channels[i]) === -1) {
|
||||||
toSubscribe.push(channels[i]);
|
toSubscribe.push(channels[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return toSubscribe
|
return toSubscribe;
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* calculates list of channels we should remove user from based difference
|
* Calculates list of channels to remove user from
|
||||||
* between channels property and passed channel list
|
|
||||||
*/
|
*/
|
||||||
calculateUnsubscribe: function (channels) {
|
calculateUnsubscribe(channels) {
|
||||||
var currentlySubscribed = this.channels;
|
const currentlySubscribed = this.channels;
|
||||||
var toUnsubscribe = [];
|
const toUnsubscribe = [];
|
||||||
for (var i = 0; i < channels.length; i++) {
|
for (let i = 0; i < channels.length; i++) {
|
||||||
if (currentlySubscribed.indexOf(channels[i]) !== -1) {
|
if (currentlySubscribed.indexOf(channels[i]) !== -1) {
|
||||||
toUnsubscribe.push(channels[i]);
|
toUnsubscribe.push(channels[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return toUnsubscribe
|
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.
|
* Marks the connection as expired
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
message: function (message) {
|
async disconnect() {
|
||||||
var request = this.$['ajaxMessage'];
|
const params = new URLSearchParams({conn_id: this.connectionId});
|
||||||
request.url = this.messageUrl;
|
|
||||||
request.body = message;
|
// Apply mutators
|
||||||
for (var i = 0; i < this.mutators.message.length; i++) {
|
for (let mutator of this.mutators.disconnect) {
|
||||||
this.mutators.message[i](request)
|
mutator({params});
|
||||||
}
|
}
|
||||||
request.generateRequest();
|
|
||||||
},
|
try {
|
||||||
|
const response = await fetch(`${this.disconnectUrl}?${params}`);
|
||||||
|
const data = await response.json();
|
||||||
|
this._handleDisconnect({detail: {response: data}});
|
||||||
|
} catch (error) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disconnect existing connection
|
||||||
|
this.closeConnection();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens "long lived" (websocket/longpoll) connection to the channelstream server.
|
* Sends a message to the server
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
startListening: function (event) {
|
async message(messageData) {
|
||||||
this.fire('start-listening', {});
|
const body = messageData;
|
||||||
|
|
||||||
|
// Apply mutators
|
||||||
|
for (let mutator of this.mutators.message) {
|
||||||
|
mutator({body});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await this._fetchJSON(this.messageUrl, {body});
|
||||||
|
this._handleMessage({detail: {response}});
|
||||||
|
} catch (error) {
|
||||||
|
this._handleMessageError({detail: {error}});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens long lived connection (websocket/longpoll)
|
||||||
|
*/
|
||||||
|
startListening(event) {
|
||||||
|
this.dispatchEvent(new CustomEvent('start-listening', {detail: {}}));
|
||||||
|
|
||||||
if (this.useWebsocket) {
|
if (this.useWebsocket) {
|
||||||
this.useWebsocket = window.WebSocket ? true : false;
|
this.useWebsocket = window.WebSocket ? true : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.useWebsocket) {
|
if (this.useWebsocket) {
|
||||||
this.openWebsocket();
|
this.openWebsocket();
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
this.openLongPoll();
|
this.openLongPoll();
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens websocket connection.
|
* Opens websocket connection
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
openWebsocket: function () {
|
openWebsocket() {
|
||||||
var url = this.websocketUrl + '?conn_id=' + this.connectionId;
|
const url = this.websocketUrl + '?conn_id=' + this.connectionId;
|
||||||
this.websocket = new WebSocket(url);
|
this.websocket = new WebSocket(url);
|
||||||
this.websocket.onopen = this._handleListenOpen.bind(this);
|
this.websocket.onopen = this._handleListenOpen.bind(this);
|
||||||
this.websocket.onclose = this._handleListenCloseEvent.bind(this);
|
this.websocket.onclose = this._handleListenCloseEvent.bind(this);
|
||||||
this.websocket.onerror = this._handleListenErrorEvent.bind(this);
|
this.websocket.onerror = this._handleListenErrorEvent.bind(this);
|
||||||
this.websocket.onmessage = this._handleListenMessageEvent.bind(this);
|
this.websocket.onmessage = this._handleListenMessageEvent.bind(this);
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens long-poll connection.
|
* Opens long-poll connection
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
openLongPoll: function () {
|
async openLongPoll() {
|
||||||
var request = this.$['ajaxListen'];
|
const url = this.longPollUrl + '?conn_id=' + this.connectionId;
|
||||||
request.url = this.longPollUrl + '?conn_id=' + this.connectionId;
|
|
||||||
request.generateRequest()
|
try {
|
||||||
},
|
this._handleListenOpen();
|
||||||
|
const response = await fetch(url);
|
||||||
|
const text = await response.text();
|
||||||
|
this._handleListenMessageEvent({data: text});
|
||||||
|
} catch (error) {
|
||||||
|
this._handleListenError({detail: {error}});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retries `connect()` call while incrementing interval between tries up to 1 minute.
|
* Retries connect() call with increasing interval
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
retryConnection: function () {
|
retryConnection() {
|
||||||
if (!this.shouldReconnect) {
|
if (!this.shouldReconnect) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._currentBounceIv < 60000) {
|
if (this._currentBounceIv < 60000) {
|
||||||
this._currentBounceIv = this._currentBounceIv + this.increaseBounceIv;
|
this._currentBounceIv = this._currentBounceIv + this.increaseBounceIv;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
this._currentBounceIv = 60000;
|
this._currentBounceIv = 60000;
|
||||||
}
|
}
|
||||||
|
|
||||||
setTimeout(this.connect.bind(this), this._currentBounceIv);
|
setTimeout(this.connect.bind(this), this._currentBounceIv);
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Closes listening connection.
|
* Closes listening connection
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
closeConnection: function () {
|
closeConnection() {
|
||||||
var request = this.$['ajaxListen'];
|
|
||||||
if (this.websocket && this.websocket.readyState === WebSocket.OPEN) {
|
if (this.websocket && this.websocket.readyState === WebSocket.OPEN) {
|
||||||
this.websocket.onclose = null;
|
this.websocket.onclose = null;
|
||||||
this.websocket.onerror = null;
|
this.websocket.onerror = null;
|
||||||
this.websocket.close();
|
this.websocket.close();
|
||||||
}
|
}
|
||||||
if (request.loading) {
|
|
||||||
request.lastRequest.abort();
|
if (this._heartbeatInterval) {
|
||||||
|
clearInterval(this._heartbeatInterval);
|
||||||
|
this._heartbeatInterval = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.connected = false;
|
this.connected = false;
|
||||||
},
|
}
|
||||||
|
|
||||||
_handleChannelsChange: function (event) {
|
/**
|
||||||
// do not fire the event if set() didn't mutate anything
|
* Add mutator function
|
||||||
// is this a reliable way to do it?
|
*/
|
||||||
if (!this.isReady || event === undefined) {
|
addMutator(type, func) {
|
||||||
return
|
this.mutators[type].push(func);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create heartbeat interval
|
||||||
|
*/
|
||||||
|
createHeartBeats() {
|
||||||
|
if (!this._heartbeatInterval && this.websocket !== null && this.heartbeats) {
|
||||||
|
this._heartbeatInterval = setInterval(this._sendHeartBeat.bind(this), 10000);
|
||||||
}
|
}
|
||||||
this.fire('channelstream-channels-changed', event)
|
}
|
||||||
},
|
|
||||||
|
|
||||||
_handleListenOpen: function (event) {
|
/**
|
||||||
this.connected = true;
|
* Send heartbeat
|
||||||
this.fire('channelstream-listen-opened', event);
|
*/
|
||||||
this.createHeartBeats();
|
_sendHeartBeat() {
|
||||||
},
|
if (this.websocket && this.websocket.readyState === WebSocket.OPEN && this.heartbeats) {
|
||||||
|
|
||||||
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'}));
|
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);
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
// Event handlers
|
||||||
|
_handleListenOpen(event) {
|
||||||
|
this.connected = true;
|
||||||
|
this.dispatchEvent(new CustomEvent('channelstream-listen-opened', {detail: event}));
|
||||||
|
this.createHeartBeats();
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleListenError(event) {
|
||||||
|
this.connected = false;
|
||||||
|
this.retryConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleConnectError(event) {
|
||||||
|
this.connected = false;
|
||||||
|
this.dispatchEvent(new CustomEvent('channelstream-connect-error', {detail: event.detail}));
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleListenMessageEvent(event) {
|
||||||
|
let data = null;
|
||||||
|
|
||||||
|
// From long-poll (fetch)
|
||||||
|
if (event.detail && event.detail.response) {
|
||||||
|
data = JSON.parse(event.detail.response);
|
||||||
|
setTimeout(this.openLongPoll.bind(this), 0);
|
||||||
|
}
|
||||||
|
// From websocket
|
||||||
|
else if (event.data) {
|
||||||
|
data = JSON.parse(event.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
this.dispatchEvent(new CustomEvent('channelstream-listen-message', {detail: data}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleListenCloseEvent(event) {
|
||||||
|
this.connected = false;
|
||||||
|
this.dispatchEvent(new CustomEvent('channelstream-listen-closed', {detail: event}));
|
||||||
|
this.retryConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleListenErrorEvent(event) {
|
||||||
|
this.connected = false;
|
||||||
|
this.dispatchEvent(new CustomEvent('channelstream-listen-error', {detail: {}}));
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleConnect(event) {
|
||||||
|
this._currentBounceIv = 0;
|
||||||
|
this.connectionId = event.detail.response.conn_id;
|
||||||
|
this.dispatchEvent(new CustomEvent('channelstream-connected', {detail: event.detail.response}));
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleDisconnect(event) {
|
||||||
|
this.connected = false;
|
||||||
|
this.dispatchEvent(new CustomEvent('channelstream-disconnected', {detail: {}}));
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleMessage(event) {
|
||||||
|
this.dispatchEvent(new CustomEvent('channelstream-message-sent', {detail: event.detail.response}));
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleMessageError(event) {
|
||||||
|
this.dispatchEvent(new CustomEvent('channelstream-message-error', {detail: event.detail}));
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleSubscribe(event) {
|
||||||
|
this.dispatchEvent(new CustomEvent('channelstream-subscribed', {detail: event.detail.response}));
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleUnsubscribe(event) {
|
||||||
|
this.dispatchEvent(new CustomEvent('channelstream-unsubscribed', {detail: event.detail.response}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polymer compatibility methods - keep these for rhodecode-app
|
||||||
|
push(path, value) {
|
||||||
|
if (path === 'channels') {
|
||||||
|
this.channels = [...this.channels, value];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set(path, value) {
|
||||||
|
if (typeof path === 'string') {
|
||||||
|
this[path] = value;
|
||||||
|
this.requestUpdate(path);
|
||||||
|
} else if (Array.isArray(path)) {
|
||||||
|
// Handle nested paths like ['channelsState', key] or ['userState', key]
|
||||||
|
if (path.length === 2 && path[0] === 'channelsState') {
|
||||||
|
this.channelsState = {...this.channelsState, [path[1]]: value};
|
||||||
|
} else if (path.length === 2 && path[0] === 'userState') {
|
||||||
|
this.userState = {...this.userState, [path[1]]: value};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('channelstream-connection', ChannelstreamConnection);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import '@polymer/iron-ajax/iron-ajax.js';
|
import 'regenerator-runtime/runtime.js';
|
||||||
import './shared-styles.js';
|
import './shared-styles.js';
|
||||||
import './channelstream-connection/channelstream-connection.js';
|
import './channelstream-connection/channelstream-connection.js';
|
||||||
import './rhodecode-toast/rhodecode-toast.js';
|
import './rhodecode-toast/rhodecode-toast.js';
|
||||||
|
|
|
||||||
|
|
@ -1,211 +1,222 @@
|
||||||
import {PolymerElement, html} from '@polymer/polymer/polymer-element.js';
|
import { html, LitElement, css } from "lit";
|
||||||
import '../channelstream-connection/channelstream-connection.js';
|
|
||||||
import '../rhodecode-toast/rhodecode-toast.js';
|
|
||||||
import '../rhodecode-favicon/rhodecode-favicon.js';
|
|
||||||
|
|
||||||
var ccLog = Logger.get('RhodeCodeApp');
|
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);
|
ccLog.setLevel(Logger.OFF);
|
||||||
|
|
||||||
export class RhodecodeApp extends PolymerElement {
|
export class RhodecodeApp extends LitElement {
|
||||||
|
static get is() {
|
||||||
|
return "rhodecode-app";
|
||||||
|
}
|
||||||
|
|
||||||
static get is() {
|
render() {
|
||||||
return 'rhodecode-app';
|
return html`
|
||||||
|
<channelstream-connection
|
||||||
|
id="channelstream-connection"
|
||||||
|
@channelstream-listen-message="${this.receivedMessage}"
|
||||||
|
@channelstream-connected="${this.handleConnected}"
|
||||||
|
@channelstream-subscribed="${this.handleSubscribed}"
|
||||||
|
>
|
||||||
|
</channelstream-connection>
|
||||||
|
<rhodecode-favicon></rhodecode-favicon>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
super.connectedCallback();
|
||||||
|
ccLog.debug("rhodeCodeApp created");
|
||||||
|
}
|
||||||
|
|
||||||
|
firstUpdated() {
|
||||||
|
$.Topic("/notifications").subscribe(this.handleNotifications.bind(this));
|
||||||
|
$.Topic("/comment").subscribe(this.handleComment.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),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
static get template(){
|
initPlugins() {
|
||||||
return html`
|
for (var i = 0; i < window.APPLICATION_PLUGINS.length; i++) {
|
||||||
<channelstream-connection
|
var pluginDef = window.APPLICATION_PLUGINS[i];
|
||||||
id="channelstream-connection"
|
if (pluginDef.component) {
|
||||||
on-channelstream-listen-message="receivedMessage"
|
var pluginElem = document.createElement(pluginDef.component);
|
||||||
on-channelstream-connected="handleConnected"
|
this.shadowRoot.appendChild(pluginElem);
|
||||||
on-channelstream-subscribed="handleSubscribed">
|
if (typeof pluginElem.init !== "undefined") {
|
||||||
</channelstream-connection>
|
pluginElem.init();
|
||||||
<rhodecode-favicon></rhodecode-favicon>
|
|
||||||
`
|
|
||||||
}
|
|
||||||
|
|
||||||
connectedCallback() {
|
|
||||||
super.connectedCallback();
|
|
||||||
ccLog.debug('rhodeCodeApp created');
|
|
||||||
$.Topic('/notifications').subscribe(this.handleNotifications.bind(this));
|
|
||||||
$.Topic('/comment').subscribe(this.handleComment.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));
|
/** proxy to channelstream connection */
|
||||||
|
getChannelStreamConnection() {
|
||||||
|
return this.renderRoot.querySelector("#channelstream-connection");
|
||||||
|
}
|
||||||
|
|
||||||
|
handleNotifications(data) {
|
||||||
|
var elem = document.getElementById("notifications");
|
||||||
|
if (elem) {
|
||||||
|
elem.handleNotification(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleComment(data) {
|
||||||
|
if (data.message.comment_data.length !== 0) {
|
||||||
|
if (window.refreshAllComments !== undefined) {
|
||||||
|
refreshAllComments();
|
||||||
|
}
|
||||||
|
var json_data = data.message.comment_data;
|
||||||
|
|
||||||
|
if (window.commentsController !== undefined) {
|
||||||
|
window.commentsController.attachComment(json_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";
|
||||||
|
channelstreamConnection.username =
|
||||||
|
window.templateContext.rhodecode_user.username;
|
||||||
|
// 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
initPlugins() {
|
if (templateContext.commit_data.commit_id) {
|
||||||
for (var i = 0; i < window.APPLICATION_PLUGINS.length; i++) {
|
var channelName =
|
||||||
var pluginDef = window.APPLICATION_PLUGINS[i];
|
"/repo$" +
|
||||||
if (pluginDef.component) {
|
templateContext.repo_name +
|
||||||
var pluginElem = document.createElement(pluginDef.component);
|
"$/commit/" +
|
||||||
this.shadowRoot.appendChild(pluginElem);
|
String(templateContext.commit_data.commit_id);
|
||||||
if (typeof pluginElem.init !== 'undefined') {
|
channels.push(channelName);
|
||||||
pluginElem.init();
|
}
|
||||||
}
|
|
||||||
}
|
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]);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** proxy to channelstream connection */
|
/** publish received messages into correct topic */
|
||||||
getChannelStreamConnection() {
|
receivedMessage(event) {
|
||||||
return this.$['channelstream-connection'];
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
handleNotifications(data) {
|
handleConnected(event) {
|
||||||
var elem = document.getElementById('notifications');
|
var channelstreamConnection = this.getChannelStreamConnection();
|
||||||
if (elem) {
|
channelstreamConnection.set("channelsState", event.detail.channels_info);
|
||||||
elem.handleNotification(data);
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
handleComment(data) {
|
/** propagates channel states on topics */
|
||||||
|
propagageChannelsState(event) {
|
||||||
if (data.message.comment_data.length !== 0) {
|
var channelstreamConnection = this.getChannelStreamConnection();
|
||||||
if (window.refreshAllComments !== undefined) {
|
var channel_data = channelstreamConnection.channelsState;
|
||||||
refreshAllComments()
|
var channels = channelstreamConnection.channels;
|
||||||
}
|
for (var i = 0; i < channels.length; i++) {
|
||||||
var json_data = data.message.comment_data;
|
var key = channels[i];
|
||||||
|
$.Topic("/connection_controller/channel_update").publish({
|
||||||
if (window.commentsController !== undefined) {
|
channel: key,
|
||||||
|
state: channel_data[key],
|
||||||
window.commentsController.attachComment(json_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);
|
customElements.define(RhodecodeApp.is, RhodecodeApp);
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,30 @@
|
||||||
import {PolymerElement, html} from '@polymer/polymer/polymer-element.js';
|
import {LitElement} from 'lit';
|
||||||
|
|
||||||
export class RhodecodeFavicon extends PolymerElement {
|
export class RhodecodeFavicon extends LitElement {
|
||||||
|
static properties = {
|
||||||
|
favicon: {type: Object},
|
||||||
|
counter: {type: Number}
|
||||||
|
};
|
||||||
|
|
||||||
static get is() {
|
constructor() {
|
||||||
return 'rhodecode-favicon';
|
super();
|
||||||
|
this.favicon = null;
|
||||||
|
this.counter = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
super.connectedCallback();
|
||||||
|
this.favicon = new Favico({
|
||||||
|
type: 'rectangle',
|
||||||
|
animation: 'none'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updated(changedProperties) {
|
||||||
|
if (changedProperties.has('counter') && this.favicon) {
|
||||||
|
this.favicon.badge(this.counter);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
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);
|
customElements.define('rhodecode-favicon', RhodecodeFavicon);
|
||||||
|
|
|
||||||
|
|
@ -1,231 +1,241 @@
|
||||||
import {PolymerElement, html} from '@polymer/polymer/polymer-element.js';
|
import {LitElement, html, css} from 'lit';
|
||||||
import '@polymer/paper-toggle-button/paper-toggle-button.js';
|
import {classMap} from 'lit/directives/class-map.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';
|
import '../rhodecode-unsafe-html/rhodecode-unsafe-html.js';
|
||||||
|
|
||||||
export class RhodecodeToast extends mixinBehaviors([IronA11yKeysBehavior], PolymerElement) {
|
export class RhodecodeToast extends LitElement {
|
||||||
|
static styles = css`
|
||||||
static get is() {
|
.absolute-center {
|
||||||
return 'rhodecode-toast';
|
margin: auto;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static get template(){
|
.top-left-rounded-corner {
|
||||||
return html`
|
-webkit-border-top-left-radius: 2px;
|
||||||
<style include="shared-styles">
|
-khtml-border-radius-topleft: 2px;
|
||||||
/* inset border for buttons - does not work in ie */
|
border-top-left-radius: 2px;
|
||||||
/* rounded borders */
|
}
|
||||||
/* rounded borders - bottom only */
|
|
||||||
/* rounded borders - top only */
|
|
||||||
/* text shadow */
|
|
||||||
/* centers text in a circle - input diameter of circle and color */
|
|
||||||
/* pill version of the circle */
|
|
||||||
.absolute-center {
|
|
||||||
margin: auto;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
bottom: 0;
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.top-left-rounded-corner {
|
.top-right-rounded-corner {
|
||||||
-webkit-border-top-left-radius: 2px;
|
-webkit-border-top-right-radius: 2px;
|
||||||
-khtml-border-radius-topleft: 2px;
|
-khtml-border-radius-topright: 2px;
|
||||||
border-top-left-radius: 2px;
|
border-top-right-radius: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.top-right-rounded-corner {
|
.bottom-left-rounded-corner {
|
||||||
-webkit-border-top-right-radius: 2px;
|
-webkit-border-bottom-left-radius: 2px;
|
||||||
-khtml-border-radius-topright: 2px;
|
-khtml-border-radius-bottomleft: 2px;
|
||||||
border-top-right-radius: 2px;
|
border-bottom-left-radius: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottom-left-rounded-corner {
|
.bottom-right-rounded-corner {
|
||||||
-webkit-border-bottom-left-radius: 2px;
|
-webkit-border-bottom-right-radius: 2px;
|
||||||
-khtml-border-radius-bottomleft: 2px;
|
-khtml-border-radius-bottomright: 2px;
|
||||||
border-bottom-left-radius: 2px;
|
border-bottom-right-radius: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottom-right-rounded-corner {
|
.top-left-rounded-corner-mid {
|
||||||
-webkit-border-bottom-right-radius: 2px;
|
-webkit-border-top-left-radius: 2px;
|
||||||
-khtml-border-radius-bottomright: 2px;
|
-khtml-border-radius-topleft: 2px;
|
||||||
border-bottom-right-radius: 2px;
|
border-top-left-radius: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.top-left-rounded-corner-mid {
|
.top-right-rounded-corner-mid {
|
||||||
-webkit-border-top-left-radius: 2px;
|
-webkit-border-top-right-radius: 2px;
|
||||||
-khtml-border-radius-topleft: 2px;
|
-khtml-border-radius-topright: 2px;
|
||||||
border-top-left-radius: 2px;
|
border-top-right-radius: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.top-right-rounded-corner-mid {
|
.bottom-left-rounded-corner-mid {
|
||||||
-webkit-border-top-right-radius: 2px;
|
-webkit-border-bottom-left-radius: 2px;
|
||||||
-khtml-border-radius-topright: 2px;
|
-khtml-border-radius-bottomleft: 2px;
|
||||||
border-top-right-radius: 2px;
|
border-bottom-left-radius: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottom-left-rounded-corner-mid {
|
.bottom-right-rounded-corner-mid {
|
||||||
-webkit-border-bottom-left-radius: 2px;
|
-webkit-border-bottom-right-radius: 2px;
|
||||||
-khtml-border-radius-bottomleft: 2px;
|
-khtml-border-radius-bottomright: 2px;
|
||||||
border-bottom-left-radius: 2px;
|
border-bottom-right-radius: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottom-right-rounded-corner-mid {
|
.alert {
|
||||||
-webkit-border-bottom-right-radius: 2px;
|
clear: both;
|
||||||
-khtml-border-radius-bottomright: 2px;
|
padding: 15px;
|
||||||
border-bottom-right-radius: 2px;
|
margin: 10px 0;
|
||||||
}
|
border: 1px solid;
|
||||||
|
border-radius: 2px;
|
||||||
|
color: #7E7F7F;
|
||||||
|
border-color: #84a5d2;
|
||||||
|
background-color: #e6edf6;
|
||||||
|
}
|
||||||
|
|
||||||
.alert {
|
.alert-success {
|
||||||
margin: 10px 0;
|
border-color: #0ac878;
|
||||||
}
|
background-color: #daf7eb;
|
||||||
|
}
|
||||||
|
|
||||||
.toast-close {
|
.alert-error {
|
||||||
margin: 0;
|
border-color: #e85e4d;
|
||||||
float: right;
|
background-color: #fbdfdb;
|
||||||
cursor: pointer;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.toast-message-holder {
|
.alert-warning {
|
||||||
background: rgba(255, 255, 255, 0.25);
|
border-color: #ffc854;
|
||||||
}
|
background-color: #fff4dd;
|
||||||
|
}
|
||||||
|
|
||||||
.toast-message-holder.fixed {
|
.alert-info {
|
||||||
position: fixed;
|
border-color: #84a5d2;
|
||||||
padding: 10px 0;
|
background-color: #e6edf6;
|
||||||
margin-left: 10px;
|
}
|
||||||
margin-right: 10px;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
z-index: 100;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<template is="dom-if" if="[[hasToasts]]">
|
.toast-close {
|
||||||
<div class$="container toast-message-holder [[conditionalClass(isFixed)]]">
|
margin: 0;
|
||||||
<template is="dom-repeat" items="[[toasts]]">
|
float: right;
|
||||||
<div class$="alert alert-[[item.level]]">
|
cursor: pointer;
|
||||||
<div on-click="dismissNotification" class="toast-close" index-pos="[[index]]">
|
}
|
||||||
<span>[[_gettext('Close')]]</span>
|
|
||||||
</div>
|
.toast-message-holder {
|
||||||
<rhodecode-unsafe-html text="[[item.message]]"></rhodecode-unsafe-html>
|
background: rgba(255, 255, 255, 0.25);
|
||||||
</div>
|
}
|
||||||
</template>
|
|
||||||
|
.toast-message-holder.fixed {
|
||||||
|
position: fixed;
|
||||||
|
padding: 10px 0;
|
||||||
|
margin-left: 10px;
|
||||||
|
margin-right: 10px;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
static properties = {
|
||||||
|
toasts: {type: Array},
|
||||||
|
isFixed: {type: Boolean}
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.toasts = [];
|
||||||
|
this.isFixed = false;
|
||||||
|
this._headerNode = null;
|
||||||
|
this._debouncedCalcBound = this._debouncedCalc.bind(this);
|
||||||
|
this._handleKeyupBound = this._handleKeyup.bind(this);
|
||||||
|
this._debounceTimeout = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
get hasToasts() {
|
||||||
|
return this.toasts && this.toasts.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
super.connectedCallback();
|
||||||
|
this._headerNode = document.querySelector('.header');
|
||||||
|
window.addEventListener('scroll', this._debouncedCalcBound);
|
||||||
|
window.addEventListener('resize', this._debouncedCalcBound);
|
||||||
|
window.addEventListener('keyup', this._handleKeyupBound);
|
||||||
|
this._debouncedCalc();
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback() {
|
||||||
|
super.disconnectedCallback();
|
||||||
|
window.removeEventListener('scroll', this._debouncedCalcBound);
|
||||||
|
window.removeEventListener('resize', this._debouncedCalcBound);
|
||||||
|
window.removeEventListener('keyup', this._handleKeyupBound);
|
||||||
|
if (this._debounceTimeout) {
|
||||||
|
clearTimeout(this._debounceTimeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updated(changedProperties) {
|
||||||
|
if (changedProperties.has('toasts')) {
|
||||||
|
$.Topic('/favicon/update').publish({count: this.toasts.length});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (!this.hasToasts) {
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div class=${classMap({
|
||||||
|
'container': true,
|
||||||
|
'toast-message-holder': true,
|
||||||
|
'fixed': this.isFixed
|
||||||
|
})}>
|
||||||
|
${this.toasts.map((item, index) => html`
|
||||||
|
<div class="alert alert-${item.level}">
|
||||||
|
<div class="toast-close" @click=${() => this.dismissNotification(index)}>
|
||||||
|
<span>${this._gettext('Close')}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
<rhodecode-unsafe-html .text=${item.message}></rhodecode-unsafe-html>
|
||||||
`
|
</div>
|
||||||
|
`)}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleKeyup(event) {
|
||||||
|
if (event.key === 'Escape') {
|
||||||
|
this.dismissNotifications();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static get properties() {
|
_debouncedCalc() {
|
||||||
return {
|
if (this._debounceTimeout) {
|
||||||
toasts: {
|
clearTimeout(this._debounceTimeout);
|
||||||
type: Array,
|
|
||||||
value() {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
isFixed: {
|
|
||||||
type: Boolean,
|
|
||||||
value: false
|
|
||||||
},
|
|
||||||
hasToasts: {
|
|
||||||
type: Boolean,
|
|
||||||
computed: '_computeHasToasts(toasts.*)'
|
|
||||||
},
|
|
||||||
keyEventTarget: {
|
|
||||||
type: Object,
|
|
||||||
value() {
|
|
||||||
return document.body;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
this._debounceTimeout = setTimeout(() => {
|
||||||
|
this.toastInWindow();
|
||||||
|
}, 25);
|
||||||
|
}
|
||||||
|
|
||||||
get keyBindings() {
|
toastInWindow() {
|
||||||
return {
|
if (!this._headerNode) {
|
||||||
'esc:keyup': '_hideOnEsc'
|
return true;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
const headerHeight = this._headerNode.offsetHeight;
|
||||||
|
const scrollPosition = window.scrollY;
|
||||||
|
|
||||||
static get observers() {
|
if (this.isFixed) {
|
||||||
return [
|
this.isFixed = 1 <= scrollPosition;
|
||||||
'_changedToasts(toasts.splices)'
|
} else {
|
||||||
]
|
this.isFixed = headerHeight <= scrollPosition;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_hideOnEsc(event) {
|
dismissNotification(index) {
|
||||||
return this.dismissNotifications();
|
$.Topic('/favicon/update').publish({count: this.toasts.length - 1});
|
||||||
}
|
this.toasts = [
|
||||||
|
...this.toasts.slice(0, index),
|
||||||
_computeHasToasts() {
|
...this.toasts.slice(index + 1)
|
||||||
return this.toasts.length > 0;
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
_debouncedCalc() {
|
dismissNotifications() {
|
||||||
// calculate once in a while
|
$.Topic('/favicon/update').publish({count: 0});
|
||||||
this.debounce('debouncedCalc', this.toastInWindow, 25);
|
this.toasts = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
conditionalClass() {
|
handleNotification(data) {
|
||||||
return this.isFixed ? 'fixed' : '';
|
if (!templateContext.rhodecode_user.notification_status && !data.message.force) {
|
||||||
}
|
return;
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
this.toasts = [...this.toasts, {
|
||||||
|
level: data.message.level,
|
||||||
|
message: data.message.message
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
_gettext(x) {
|
||||||
|
return _gettext(x);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define(RhodecodeToast.is, RhodecodeToast);
|
customElements.define('rhodecode-toast', RhodecodeToast);
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,19 @@
|
||||||
import {PolymerElement, html} from '@polymer/polymer/polymer-element.js';
|
import {LitElement, html} from 'lit';
|
||||||
|
import {unsafeHTML} from 'lit/directives/unsafe-html.js';
|
||||||
|
|
||||||
export class RhodecodeUnsafeHtml extends PolymerElement {
|
export class RhodecodeUnsafeHtml extends LitElement {
|
||||||
|
static properties = {
|
||||||
|
text: {type: String}
|
||||||
|
};
|
||||||
|
|
||||||
static get is() {
|
constructor() {
|
||||||
return 'rhodecode-unsafe-html';
|
super();
|
||||||
}
|
this.text = '';
|
||||||
|
}
|
||||||
|
|
||||||
static get template() {
|
render() {
|
||||||
return html`
|
return html`${unsafeHTML(this.text)}`;
|
||||||
<style include="shared-styles"></style>
|
}
|
||||||
<slot></slot>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get properties() {
|
|
||||||
return {
|
|
||||||
text: {
|
|
||||||
type: String,
|
|
||||||
observer: '_handleText'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_handleText(newVal, oldVal) {
|
|
||||||
this.innerHTML = this.text;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define(RhodecodeUnsafeHtml.is, RhodecodeUnsafeHtml);
|
customElements.define('rhodecode-unsafe-html', RhodecodeUnsafeHtml);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
const styleElement = document.createElement('dom-module');
|
const styleElement = document.createElement('dom-module');
|
||||||
// this will use raw-loader
|
// this is a workaround to make shared styles work for Lit components.
|
||||||
import sharedCSS from './style-polymer.css';
|
// Needs proper refactoring to make sure styling follows Lit patterns
|
||||||
|
import sharedCSS from './style-lit.css';
|
||||||
|
|
||||||
|
styleElement.setAttribute('id', 'shared-styles');
|
||||||
styleElement.innerHTML =
|
styleElement.innerHTML =
|
||||||
`<template>
|
`<template>
|
||||||
<style>
|
<style>
|
||||||
|
|
@ -9,4 +11,4 @@ styleElement.innerHTML =
|
||||||
</style>
|
</style>
|
||||||
</template>`;
|
</template>`;
|
||||||
|
|
||||||
styleElement.register('shared-styles');
|
document.head.appendChild(styleElement);
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,6 @@ c.template_context['attachment_store'] = {
|
||||||
</%def>
|
</%def>
|
||||||
${self.robots()}
|
${self.robots()}
|
||||||
<link rel="icon" href="${h.asset('images/favicon.ico', ver=c.rhodecode_version_hash)}" sizes="16x16 32x32" type="image/png" />
|
<link rel="icon" href="${h.asset('images/favicon.ico', ver=c.rhodecode_version_hash)}" sizes="16x16 32x32" type="image/png" />
|
||||||
<script src="${h.asset('js/vendors/webcomponentsjs/custom-elements-es5-adapter.js', ver=c.rhodecode_version_hash)}"></script>
|
|
||||||
<script src="${h.asset('js/vendors/webcomponentsjs/webcomponents-bundle.js', ver=c.rhodecode_version_hash)}"></script>
|
<script src="${h.asset('js/vendors/webcomponentsjs/webcomponents-bundle.js', ver=c.rhodecode_version_hash)}"></script>
|
||||||
|
|
||||||
## CSS definitions
|
## CSS definitions
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@
|
||||||
|
|
||||||
<h3>Alert Messages</h3>
|
<h3>Alert Messages</h3>
|
||||||
<p>
|
<p>
|
||||||
Alert messages are produced using the custom Polymer element
|
Alert messages are produced using the custom Lit element
|
||||||
<code>rhodecode-toast</code> which is passed a message and level.
|
<code>rhodecode-toast</code> which is passed a message and level.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -588,7 +588,7 @@ search.location = %(here)s/.rc-test-data/data/index
|
||||||
channelstream.enabled = false
|
channelstream.enabled = false
|
||||||
|
|
||||||
; server address for channelstream server on the backend
|
; server address for channelstream server on the backend
|
||||||
channelstream.server = channelstream:9800
|
channelstream.server = channelstream:8000
|
||||||
|
|
||||||
; location of the channelstream server from outside world
|
; location of the channelstream server from outside world
|
||||||
; use ws:// for http or wss:// for https. This address needs to be handled
|
; use ws:// for http or wss:// for https. This address needs to be handled
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ robots.txt \
|
||||||
js/scripts.min.js \
|
js/scripts.min.js \
|
||||||
js/rhodecode-components.js \
|
js/rhodecode-components.js \
|
||||||
css/style.css \
|
css/style.css \
|
||||||
css/style-polymer.css \
|
css/style-lit.css \
|
||||||
css/style-ipython.css"
|
css/style-ipython.css"
|
||||||
|
|
||||||
static_path=$1
|
static_path=$1
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
## ** BUILD **
|
## ** BUILD **
|
||||||
# docker build --tag rhodecode/static-files-build:16 -f static-build.dockerfile .
|
# docker build --tag rhodecode/static-files-build:16 -f static-build.dockerfile .
|
||||||
# docker build --tag rhodecode/static-files-build:18 -f static-build.dockerfile .
|
# docker build --tag rhodecode/static-files-build:24 -f static-build.dockerfile .
|
||||||
|
|
||||||
## run grunt on top of the source code
|
## run grunt on top of the source code
|
||||||
## cd rhodecode-enterprise-ce
|
## cd rhodecode-enterprise-ce
|
||||||
|
|
@ -14,8 +14,9 @@
|
||||||
## docker run -it --entrypoint bash --rm -v $PWD:/project --workdir=/project rhodecode/static-files-build:16 -c "npm install && grunt -v"
|
## docker run -it --entrypoint bash --rm -v $PWD:/project --workdir=/project rhodecode/static-files-build:16 -c "npm install && grunt -v"
|
||||||
|
|
||||||
# THIS WORKS NOW
|
# THIS WORKS NOW
|
||||||
FROM node:16-buster
|
#FROM node:16-buster
|
||||||
#FROM node:18-buster
|
FROM node:24.11.1-bookworm-slim
|
||||||
|
|
||||||
|
|
||||||
WORKDIR /static_build
|
WORKDIR /static_build
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,11 +15,19 @@ let babelRCOptions = {
|
||||||
"presets": [
|
"presets": [
|
||||||
["env", {
|
["env", {
|
||||||
"targets": {
|
"targets": {
|
||||||
"browsers": ["last 2 versions"]
|
"esmodules": true
|
||||||
}
|
},
|
||||||
|
"exclude": [
|
||||||
|
"transform-es2015-classes",
|
||||||
|
"transform-regenerator",
|
||||||
|
"transform-async-to-generator"
|
||||||
|
]
|
||||||
}]
|
}]
|
||||||
],
|
],
|
||||||
"plugins": ["transform-object-rest-spread"]
|
"plugins": [
|
||||||
|
["transform-class-properties", { "loose": true }],
|
||||||
|
"transform-object-rest-spread"
|
||||||
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
@ -32,8 +40,8 @@ module.exports = {
|
||||||
path: path.resolve(destinationDirectory)
|
path: path.resolve(destinationDirectory)
|
||||||
},
|
},
|
||||||
// Tell Webpack which directories to look in to resolve import statements.
|
// Tell Webpack which directories to look in to resolve import statements.
|
||||||
// Normally Webpack will look in node_modules by default but since we’re overriding
|
// Normally Webpack will look in node_modules by default but since we're overriding
|
||||||
// the property we’ll need to tell it to look there.
|
// the property we'll need to tell it to look there.
|
||||||
resolve: {
|
resolve: {
|
||||||
modules: [
|
modules: [
|
||||||
path.resolve(__dirname, 'node_modules'),
|
path.resolve(__dirname, 'node_modules'),
|
||||||
|
|
@ -45,34 +53,15 @@ module.exports = {
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
test: /style-polymer.css/,
|
test: /style-lit.css/,
|
||||||
use: 'raw-loader'
|
use: 'raw-loader'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// If you see a file that ends in .html, send it to these loaders.
|
// Babel-loader for JS files
|
||||||
test: /\.html$/,
|
|
||||||
// This is an example of chained loaders in Webpack.
|
|
||||||
// Chained loaders run last to first. So it will run
|
|
||||||
// polymer-webpack-loader, and hand the output to
|
|
||||||
// babel-loader. This let's us transpile JS in our `<script>` elements.
|
|
||||||
use: [
|
|
||||||
{loader: 'babel-loader', options: babelRCOptions},
|
|
||||||
{loader: 'polymer-webpack-loader',
|
|
||||||
options: {
|
|
||||||
processStyleLinks: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// If you see a file that ends in .js, just send it to the babel-loader.
|
|
||||||
test: /\.js$/,
|
test: /\.js$/,
|
||||||
|
exclude: /node_modules/,
|
||||||
use: {loader: 'babel-loader', options: babelRCOptions}
|
use: {loader: 'babel-loader', options: babelRCOptions}
|
||||||
// Optionally exclude node_modules from transpilation except for polymer-webpack-loader:
|
|
||||||
// exclude: /node_modules\/(?!polymer-webpack-loader\/).*/
|
|
||||||
},
|
},
|
||||||
// this is required because of bug:
|
|
||||||
// https://github.com/webpack-contrib/polymer-webpack-loader/issues/49
|
|
||||||
{
|
{
|
||||||
test: /intl-messageformat.min.js/,
|
test: /intl-messageformat.min.js/,
|
||||||
use: 'imports-loader?this=>window'
|
use: 'imports-loader?this=>window'
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue