##// END OF EJS Templates
polymer: prepare for 3.x migration
ergo -
r3149:a1e63a84 default
parent child Browse files
Show More
This diff has been collapsed as it changes many lines, (506 lines changed) Show them Hide them
@@ -1,106 +1,610 b''
1 1 <link rel="import" href="../../../../../../bower_components/polymer/polymer.html">
2 2 <link rel="import" href="../../../../../../bower_components/iron-ajax/iron-ajax.html">
3 3
4 4 <!--
5 5
6 6 `<channelstream-connection>` allows you to connect and interact with channelstream server
7 7 abstracting websocket/long-polling connections from you.
8 8
9 9 In typical use, just slap some `<channelstream-connection>` at the top of your body:
10 10
11 11 <body>
12 12 <channelstream-connection
13 13 username="{{user.username}}"
14 14 connect-url="http://127.0.0.1:8000/demo/connect"
15 15 disconnect-url="http://127.0.0.1:8000/disconnect"
16 16 subscribe-url="http://127.0.0.1:8000/demo/subscribe"
17 17 message-url="http://127.0.0.1:8000/demo/message"
18 18 long-poll-url="http://127.0.0.1:8000/listen"
19 19 websocket-url="http://127.0.0.1:8000/ws"
20 20 channels-url='["channel1", "channel2"]' />
21 21
22 22 Then you can do `channelstreamElem.connect()` to kick off your connection.
23 23 This element also handles automatic reconnections.
24 24
25 25 ## Default handlers
26 26
27 27 By default element has a listener attached that will fire `startListening()` handler on `channelstream-connected` event.
28 28
29 29 By default element has a listener attached that will fire `retryConnection()` handler on `channelstream-connect-error` event,
30 30 this handler will forever try to re-establish connection to the server incrementing intervals between retries up to 1 minute.
31 31
32 32 -->
33 33 <dom-module id="channelstream-connection">
34 34 <template>
35 35
36 36 <iron-ajax
37 37 id="ajaxConnect"
38 38 url=""
39 39 handle-as="json"
40 40 method="post"
41 41 content-type="application/json"
42 42 loading="{{loadingConnect}}"
43 43 last-response="{{connectLastResponse}}"
44 44 on-response="_handleConnect"
45 45 on-error="_handleConnectError"
46 46 debounce-duration="100"></iron-ajax>
47 47
48 48 <iron-ajax
49 49 id="ajaxDisconnect"
50 50 url=""
51 51 handle-as="json"
52 52 method="post"
53 53 content-type="application/json"
54 54 loading="{{loadingDisconnect}}"
55 55 last-response="{{_disconnectLastResponse}}"
56 56 on-response="_handleDisconnect"
57 57 debounce-duration="100"></iron-ajax>
58 58
59 59 <iron-ajax
60 60 id="ajaxSubscribe"
61 61 url=""
62 62 handle-as="json"
63 63 method="post"
64 64 content-type="application/json"
65 65 loading="{{loadingSubscribe}}"
66 66 last-response="{{subscribeLastResponse}}"
67 67 on-response="_handleSubscribe"
68 68 debounce-duration="100"></iron-ajax>
69 69
70 70 <iron-ajax
71 71 id="ajaxUnsubscribe"
72 72 url=""
73 73 handle-as="json"
74 74 method="post"
75 75 content-type="application/json"
76 76 loading="{{loadingUnsubscribe}}"
77 77 last-response="{{unsubscribeLastResponse}}"
78 78 on-response="_handleUnsubscribe"
79 79 debounce-duration="100"></iron-ajax>
80 80
81 81 <iron-ajax
82 82 id="ajaxMessage"
83 83 url=""
84 84 handle-as="json"
85 85 method="post"
86 86 content-type="application/json"
87 87 loading="{{loadingMessage}}"
88 88 last-response="{{messageLastResponse}}"
89 89 on-response="_handleMessage"
90 90 on-error="_handleMessageError"
91 91 debounce-duration="100"></iron-ajax>
92 92
93 93 <iron-ajax
94 94 id="ajaxListen"
95 95 url=""
96 96 handle-as="text"
97 97 loading="{{loadingListen}}"
98 98 last-response="{{listenLastResponse}}"
99 99 on-request="_handleListenOpen"
100 100 on-error="_handleListenError"
101 101 on-response="_handleListenMessageEvent"
102 102 debounce-duration="100"></iron-ajax>
103 103
104 104 </template>
105 <script src="channelstream-connection.js"></script>
105 <script>
106 Polymer({
107 is: 'channelstream-connection',
108
109 /**
110 * Fired when `channels` array changes.
111 *
112 * @event channelstream-channels-changed
113 */
114
115 /**
116 * Fired when `connect()` method succeeds.
117 *
118 * @event channelstream-connected
119 */
120
121 /**
122 * Fired when `connect` fails.
123 *
124 * @event channelstream-connect-error
125 */
126
127 /**
128 * Fired when `disconnect()` succeeds.
129 *
130 * @event channelstream-disconnected
131 */
132
133 /**
134 * Fired when `message()` succeeds.
135 *
136 * @event channelstream-message-sent
137 */
138
139 /**
140 * Fired when `message()` fails.
141 *
142 * @event channelstream-message-error
143 */
144
145 /**
146 * Fired when `subscribe()` succeeds.
147 *
148 * @event channelstream-subscribed
149 */
150
151 /**
152 * Fired when `subscribe()` fails.
153 *
154 * @event channelstream-subscribe-error
155 */
156
157 /**
158 * Fired when `unsubscribe()` succeeds.
159 *
160 * @event channelstream-unsubscribed
161 */
162
163 /**
164 * Fired when `unsubscribe()` fails.
165 *
166 * @event channelstream-unsubscribe-error
167 */
168
169 /**
170 * Fired when listening connection receives a message.
171 *
172 * @event channelstream-listen-message
173 */
174
175 /**
176 * Fired when listening connection is opened.
177 *
178 * @event channelstream-listen-opened
179 */
180
181 /**
182 * Fired when listening connection is closed.
183 *
184 * @event channelstream-listen-closed
185 */
186
187 /**
188 * Fired when listening connection suffers an error.
189 *
190 * @event channelstream-listen-error
191 */
192
193 properties: {
194 isReady: Boolean,
195 /** List of channels user should be subscribed to. */
196 channels: {
197 type: Array,
198 value: function () {
199 return []
200 },
201 notify: true
202 },
203 /** Username of connecting user. */
204 username: {
205 type: String,
206 value: 'Anonymous',
207 reflectToAttribute: true
208 },
209 /** Connection identifier. */
210 connectionId: {
211 type: String,
212 reflectToAttribute: true
213 },
214 /** Websocket instance. */
215 websocket: {
216 type: Object,
217 value: null
218 },
219 /** Websocket connection url. */
220 websocketUrl: {
221 type: String,
222 value: ''
223 },
224 /** URL used in `connect()`. */
225 connectUrl: {
226 type: String,
227 value: ''
228 },
229 /** URL used in `disconnect()`. */
230 disconnectUrl: {
231 type: String,
232 value: ''
233 },
234 /** URL used in `subscribe()`. */
235 subscribeUrl: {
236 type: String,
237 value: ''
238 },
239 /** URL used in `unsubscribe()`. */
240 unsubscribeUrl: {
241 type: String,
242 value: ''
243 },
244 /** URL used in `message()`. */
245 messageUrl: {
246 type: String,
247 value: ''
248 },
249 /** Long-polling connection url. */
250 longPollUrl: {
251 type: String,
252 value: ''
253 },
254 /** Long-polling connection url. */
255 shouldReconnect: {
256 type: Boolean,
257 value: true
258 },
259 /** Should send heartbeats. */
260 heartbeats: {
261 type: Boolean,
262 value: true
263 },
264 /** How much should every retry interval increase (in milliseconds) */
265 increaseBounceIv: {
266 type: Number,
267 value: 2000
268 },
269 _currentBounceIv: {
270 type: Number,
271 reflectToAttribute: true,
272 value: 0
273 },
274 /** Should use websockets or long-polling by default */
275 useWebsocket: {
276 type: Boolean,
277 reflectToAttribute: true,
278 value: true
279 },
280 connected: {
281 type: Boolean,
282 reflectToAttribute: true,
283 value: false
284 }
285 },
286
287 observers: [
288 '_handleChannelsChange(channels.splices)'
289 ],
290
291 listeners: {
292 'channelstream-connected': 'startListening',
293 'channelstream-connect-error': 'retryConnection',
294 },
295
296 /**
297 * Mutators hold functions that you can set locally to change the data
298 * that the client is sending to all endpoints
299 * you can call it like `elem.mutators('connect', yourFunc())`
300 * mutators will be executed in order they were pushed onto arrays
301 *
302 */
303 mutators: {
304 connect: function () {
305 return []
306 }(),
307 message: function () {
308 return []
309 }(),
310 subscribe: function () {
311 return []
312 }(),
313 unsubscribe: function () {
314 return []
315 }(),
316 disconnect: function () {
317 return []
318 }()
319 },
320 ready: function () {
321 this.isReady = true;
322 },
323
324 /**
325 * Connects user and fetches connection id from the server.
326 *
327 */
328 connect: function () {
329 var request = this.$['ajaxConnect'];
330 request.url = this.connectUrl;
331 request.body = {
332 username: this.username,
333 channels: this.channels
334 };
335 for (var i = 0; i < this.mutators.connect.length; i++) {
336 this.mutators.connect[i](request);
337 }
338 request.generateRequest()
339 },
340 /**
341 * Overwrite with custom function that will
342 */
343 addMutator: function (type, func) {
344 this.mutators[type].push(func);
345 },
346 /**
347 * Subscribes user to channels.
348 *
349 */
350 subscribe: function (channels) {
351 var request = this.$['ajaxSubscribe'];
352 request.url = this.subscribeUrl;
353 request.body = {
354 channels: channels,
355 conn_id: this.connectionId
356 };
357 for (var i = 0; i < this.mutators.subscribe.length; i++) {
358 this.mutators.subscribe[i](request);
359 }
360 if (request.body.channels.length) {
361 request.generateRequest();
362 }
363 },
364 /**
365 * Unsubscribes user from channels.
366 *
367 */
368 unsubscribe: function (unsubscribe) {
369 var request = this.$['ajaxUnsubscribe'];
370
371 request.url = this.unsubscribeUrl;
372 request.body = {
373 channels: unsubscribe,
374 conn_id: this.connectionId
375 };
376 for (var i = 0; i < this.mutators.unsubscribe.length; i++) {
377 this.mutators.unsubscribe[i](request);
378 }
379 request.generateRequest()
380 },
381
382 /**
383 * calculates list of channels we should add user to based on difference
384 * between channels property and passed channel list
385 */
386 calculateSubscribe: function (channels) {
387 var currentlySubscribed = this.channels;
388 var toSubscribe = [];
389 for (var i = 0; i < channels.length; i++) {
390 if (currentlySubscribed.indexOf(channels[i]) === -1) {
391 toSubscribe.push(channels[i]);
392 }
393 }
394 return toSubscribe
395 },
396 /**
397 * calculates list of channels we should remove user from based difference
398 * between channels property and passed channel list
399 */
400 calculateUnsubscribe: function (channels) {
401 var currentlySubscribed = this.channels;
402 var toUnsubscribe = [];
403 for (var i = 0; i < channels.length; i++) {
404 if (currentlySubscribed.indexOf(channels[i]) !== -1) {
405 toUnsubscribe.push(channels[i]);
406 }
407 }
408 return toUnsubscribe
409 },
410 /**
411 * Marks the connection as expired.
412 *
413 */
414 disconnect: function () {
415 var request = this.$['ajaxDisconnect'];
416 request.url = this.disconnectUrl;
417 request.params = {
418 conn_id: this.connectionId
419 };
420 for (var i = 0; i < this.mutators.disconnect.length; i++) {
421 this.mutators.disconnect[i](request);
422 }
423 // mark connection as expired
424 request.generateRequest();
425 // disconnect existing connection
426 this.closeConnection();
427 },
428
429 /**
430 * Sends a message to the server.
431 *
432 */
433 message: function (message) {
434 var request = this.$['ajaxMessage'];
435 request.url = this.messageUrl;
436 request.body = message;
437 for (var i = 0; i < this.mutators.message.length; i++) {
438 this.mutators.message[i](request)
439 }
440 request.generateRequest();
441 },
442 /**
443 * Opens "long lived" (websocket/longpoll) connection to the channelstream server.
444 *
445 */
446 startListening: function (event) {
447 this.fire('start-listening', {});
448 if (this.useWebsocket) {
449 this.useWebsocket = window.WebSocket ? true : false;
450 }
451 if (this.useWebsocket) {
452 this.openWebsocket();
453 }
454 else {
455 this.openLongPoll();
456 }
457 },
458 /**
459 * Opens websocket connection.
460 *
461 */
462 openWebsocket: function () {
463 var url = this.websocketUrl + '?conn_id=' + this.connectionId;
464 this.websocket = new WebSocket(url);
465 this.websocket.onopen = this._handleListenOpen.bind(this);
466 this.websocket.onclose = this._handleListenCloseEvent.bind(this);
467 this.websocket.onerror = this._handleListenErrorEvent.bind(this);
468 this.websocket.onmessage = this._handleListenMessageEvent.bind(this);
469 },
470 /**
471 * Opens long-poll connection.
472 *
473 */
474 openLongPoll: function () {
475 var request = this.$['ajaxListen'];
476 request.url = this.longPollUrl + '?conn_id=' + this.connectionId;
477 request.generateRequest()
478 },
479 /**
480 * Retries `connect()` call while incrementing interval between tries up to 1 minute.
481 *
482 */
483 retryConnection: function () {
484 if (!this.shouldReconnect) {
485 return;
486 }
487 if (this._currentBounceIv < 60000) {
488 this._currentBounceIv = this._currentBounceIv + this.increaseBounceIv;
489 }
490 else {
491 this._currentBounceIv = 60000;
492 }
493 setTimeout(this.connect.bind(this), this._currentBounceIv);
494 },
495 /**
496 * Closes listening connection.
497 *
498 */
499 closeConnection: function () {
500 var request = this.$['ajaxListen'];
501 if (this.websocket && this.websocket.readyState === WebSocket.OPEN) {
502 this.websocket.onclose = null;
503 this.websocket.onerror = null;
504 this.websocket.close();
505 }
506 if (request.loading) {
507 request.lastRequest.abort();
508 }
509 this.connected = false;
510 },
511
512 _handleChannelsChange: function (event) {
513 // do not fire the event if set() didn't mutate anything
514 // is this a reliable way to do it?
515 if (!this.isReady || event === undefined) {
516 return
517 }
518 this.fire('channelstream-channels-changed', event)
519 },
520
521 _handleListenOpen: function (event) {
522 this.connected = true;
523 this.fire('channelstream-listen-opened', event);
524 this.createHeartBeats();
525 },
526
527 createHeartBeats: function () {
528 if (typeof self._heartbeat === 'undefined' && this.websocket !== null
529 && this.heartbeats) {
530 self._heartbeat = setInterval(this._sendHeartBeat.bind(this), 10000);
531 }
532 },
533
534 _sendHeartBeat: function () {
535 if (this.websocket.readyState === WebSocket.OPEN && this.heartbeats) {
536 this.websocket.send(JSON.stringify({type: 'heartbeat'}));
537 }
538 },
539
540 _handleListenError: function (event) {
541 this.connected = false;
542 this.retryConnection();
543 },
544 _handleConnectError: function (event) {
545 this.connected = false;
546 this.fire('channelstream-connect-error', event.detail);
547 },
548
549 _handleListenMessageEvent: function (event) {
550 var data = null;
551 // comes from iron-ajax
552 if (event.detail) {
553 data = JSON.parse(event.detail.response)
554 // comes from websocket
555 setTimeout(this.openLongPoll.bind(this), 0);
556 } else {
557 data = JSON.parse(event.data)
558 }
559 this.fire('channelstream-listen-message', data);
560
561 },
562
563 _handleListenCloseEvent: function (event) {
564 this.connected = false;
565 this.fire('channelstream-listen-closed', event.detail);
566 this.retryConnection();
567 },
568
569 _handleListenErrorEvent: function (event) {
570 this.connected = false;
571 this.fire('channelstream-listen-error', {})
572 },
573
574 _handleConnect: function (event) {
575 this.currentBounceIv = 0;
576 this.connectionId = event.detail.response.conn_id;
577 this.fire('channelstream-connected', event.detail.response);
578 },
579
580 _handleDisconnect: function (event) {
581 this.connected = false;
582 this.fire('channelstream-disconnected', {});
583 },
584
585 _handleMessage: function (event) {
586 this.fire('channelstream-message-sent', event.detail.response);
587 },
588 _handleMessageError: function (event) {
589 this.fire('channelstream-message-error', event.detail);
590 },
591
592 _handleSubscribe: function (event) {
593 this.fire('channelstream-subscribed', event.detail.response);
594 },
595
596 _handleSubscribeError: function (event) {
597 this.fire('channelstream-subscribe-error', event.detail);
598 },
599
600 _handleUnsubscribe: function (event) {
601 this.fire('channelstream-unsubscribed', event.detail.response);
602 },
603
604 _handleUnsubscribeError: function (event) {
605 this.fire('channelstream-unsubscribe-error', event.detail);
606 }
607 });
608
609 </script>
106 610 </dom-module>
@@ -1,17 +1,185 b''
1 1 <link rel="import" href="../../../../../../bower_components/polymer/polymer.html">
2 2 <link rel="import" href="../channelstream-connection/channelstream-connection.html">
3 3 <link rel="import" href="../rhodecode-toast/rhodecode-toast.html">
4 4 <link rel="import" href="../rhodecode-favicon/rhodecode-favicon.html">
5 5
6 6 <dom-module id="rhodecode-app">
7 7 <template>
8 8 <channelstream-connection
9 9 id="channelstream-connection"
10 10 on-channelstream-listen-message="receivedMessage"
11 11 on-channelstream-connected="handleConnected"
12 12 on-channelstream-subscribed="handleSubscribed">
13 13 </channelstream-connection>
14 14 <rhodecode-favicon></rhodecode-favicon>
15 15 </template>
16 <script src="rhodecode-app.js"></script>
16 <script>
17 ccLog = Logger.get('RhodeCodeApp');
18 ccLog.setLevel(Logger.OFF);
19
20 var rhodeCodeApp = Polymer({
21 is: 'rhodecode-app',
22 attached: function () {
23 ccLog.debug('rhodeCodeApp created');
24 $.Topic('/notifications').subscribe(this.handleNotifications.bind(this));
25 $.Topic('/favicon/update').subscribe(this.faviconUpdate.bind(this));
26 $.Topic('/connection_controller/subscribe').subscribe(
27 this.subscribeToChannelTopic.bind(this));
28 // this event can be used to coordinate plugins to do their
29 // initialization before channelstream is kicked off
30 $.Topic('/__MAIN_APP__').publish({});
31
32 for (var i = 0; i < alertMessagePayloads.length; i++) {
33 $.Topic('/notifications').publish(alertMessagePayloads[i]);
34 }
35 this.initPlugins();
36 // after rest of application loads and topics get fired, launch connection
37 $(document).ready(function () {
38 this.kickoffChannelstreamPlugin();
39 }.bind(this));
40 },
41
42 initPlugins: function(){
43 for (var i = 0; i < window.APPLICATION_PLUGINS.length; i++) {
44 var pluginDef = window.APPLICATION_PLUGINS[i];
45 if (pluginDef.component){
46 var pluginElem = document.createElement(pluginDef.component);
47 this.shadowRoot.appendChild(pluginElem);
48 if (typeof pluginElem.init !== 'undefined'){
49 pluginElem.init();
50 }
51 }
52 }
53 },
54 /** proxy to channelstream connection */
55 getChannelStreamConnection: function () {
56 return this.$['channelstream-connection'];
57 },
58
59 handleNotifications: function (data) {
60 var elem = document.getElementById('notifications');
61 if(elem){
62 elem.handleNotification(data);
63 }
64
65 },
66
67 faviconUpdate: function (data) {
68 this.shadowRoot.querySelector('rhodecode-favicon').counter = data.count;
69 },
70
71 /** opens connection to ws server */
72 kickoffChannelstreamPlugin: function (data) {
73 ccLog.debug('kickoffChannelstreamPlugin');
74 var channels = ['broadcast'];
75 var addChannels = this.checkViewChannels();
76 for (var i = 0; i < addChannels.length; i++) {
77 channels.push(addChannels[i]);
78 }
79 if (window.CHANNELSTREAM_SETTINGS && CHANNELSTREAM_SETTINGS.enabled){
80 var channelstreamConnection = this.getChannelStreamConnection();
81 channelstreamConnection.connectUrl = CHANNELSTREAM_URLS.connect;
82 channelstreamConnection.subscribeUrl = CHANNELSTREAM_URLS.subscribe;
83 channelstreamConnection.websocketUrl = CHANNELSTREAM_URLS.ws + '/ws';
84 channelstreamConnection.longPollUrl = CHANNELSTREAM_URLS.longpoll + '/listen';
85 // some channels might already be registered by topic
86 for (var i = 0; i < channels.length; i++) {
87 channelstreamConnection.push('channels', channels[i]);
88 }
89 // append any additional channels registered in other plugins
90 $.Topic('/connection_controller/subscribe').processPrepared();
91 channelstreamConnection.connect();
92 }
93 },
94
95 checkViewChannels: function () {
96 // subscribe to different channels data is sent.
97
98 var channels = [];
99 // subscribe to PR repo channel for PR's'
100 if (templateContext.pull_request_data.pull_request_id) {
101 var channelName = '/repo$' + templateContext.repo_name + '$/pr/' +
102 String(templateContext.pull_request_data.pull_request_id);
103 channels.push(channelName);
104 }
105
106 if (templateContext.commit_data.commit_id) {
107 var channelName = '/repo$' + templateContext.repo_name + '$/commit/' +
108 String(templateContext.commit_data.commit_id);
109 channels.push(channelName);
110 }
111
112 return channels;
113 },
114
115 /** subscribes users from channels in channelstream */
116 subscribeToChannelTopic: function (channels) {
117 var channelstreamConnection = this.getChannelStreamConnection();
118 var toSubscribe = channelstreamConnection.calculateSubscribe(channels);
119 ccLog.debug('subscribeToChannelTopic', toSubscribe);
120 if (toSubscribe.length > 0) {
121 // if we are connected then subscribe
122 if (channelstreamConnection.connected) {
123 channelstreamConnection.subscribe(toSubscribe);
124 }
125 // not connected? just push channels onto the stack
126 else {
127 for (var i = 0; i < toSubscribe.length; i++) {
128 channelstreamConnection.push('channels', toSubscribe[i]);
129 }
130 }
131 }
132 },
133
134 /** publish received messages into correct topic */
135 receivedMessage: function (event) {
136 for (var i = 0; i < event.detail.length; i++) {
137 var message = event.detail[i];
138 if (message.message.topic) {
139 ccLog.debug('publishing', message.message.topic);
140 $.Topic(message.message.topic).publish(message);
141 }
142 else if (message.type === 'presence'){
143 $.Topic('/connection_controller/presence').publish(message);
144 }
145 else {
146 ccLog.warn('unhandled message', message);
147 }
148 }
149 },
150
151 handleConnected: function (event) {
152 var channelstreamConnection = this.getChannelStreamConnection();
153 channelstreamConnection.set('channelsState',
154 event.detail.channels_info);
155 channelstreamConnection.set('userState', event.detail.state);
156 channelstreamConnection.set('channels', event.detail.channels);
157 this.propagageChannelsState();
158 },
159 handleSubscribed: function (event) {
160 var channelstreamConnection = this.getChannelStreamConnection();
161 var channelInfo = event.detail.channels_info;
162 var channelKeys = Object.keys(event.detail.channels_info);
163 for (var i = 0; i < channelKeys.length; i++) {
164 var key = channelKeys[i];
165 channelstreamConnection.set(['channelsState', key], channelInfo[key]);
166 }
167 channelstreamConnection.set('channels', event.detail.channels);
168 this.propagageChannelsState();
169 },
170 /** propagates channel states on topics */
171 propagageChannelsState: function (event) {
172 var channelstreamConnection = this.getChannelStreamConnection();
173 var channel_data = channelstreamConnection.channelsState;
174 var channels = channelstreamConnection.channels;
175 for (var i = 0; i < channels.length; i++) {
176 var key = channels[i];
177 $.Topic('/connection_controller/channel_update').publish(
178 {channel: key, state: channel_data[key]}
179 );
180 }
181 }
182 });
183
184 </script>
17 185 </dom-module>
@@ -1,7 +1,29 b''
1 1 <link rel="import" href="../../../../../../bower_components/polymer/polymer.html">
2 2
3 3 <dom-module id="rhodecode-favicon">
4 4 <template>
5 5 </template>
6 <script src="rhodecode-favicon.js"></script>
6 <script>
7 Polymer({
8 is: 'rhodecode-favicon',
9 properties: {
10 favicon: Object,
11 counter: {
12 type: Number,
13 observer: '_handleCounter'
14 }
15 },
16
17 ready: function () {
18 this.favicon = new Favico({
19 type: 'rectangle',
20 animation: 'none'
21 });
22 },
23 _handleCounter: function (newVal, oldVal) {
24 this.favicon.badge(this.counter);
25 }
26 });
27
28 </script>
7 29 </dom-module>
@@ -1,7 +1,11 b''
1 1 <link rel="import" href="../../../../../../bower_components/polymer/polymer.html">
2 2 <dom-module id="rhodecode-legacy-js">
3 3 <template>
4 4 <script src="../../../scripts.js"></script>
5 5 </template>
6 <script src="rhodecode-legacy-js.js"></script>
6 <script>
7 Polymer({
8 is: 'rhodecode-legacy-js',
9 });
10 </script>
7 11 </dom-module>
@@ -1,23 +1,123 b''
1 1 <link rel="import" href="../../../../../../bower_components/paper-button/paper-button.html">
2 2 <link rel="import" href="../../../../../../bower_components/iron-a11y-keys-behavior/iron-a11y-keys-behavior.html">
3 3 <link rel="import" href="../rhodecode-unsafe-html/rhodecode-unsafe-html.html">
4 4 <dom-module id="rhodecode-toast">
5 5 <template>
6 6 <style include="shared-styles"></style>
7 7 <link rel="stylesheet" href="rhodecode-toast.css">
8 8 <template is="dom-if" if="[[hasToasts]]">
9 9 <div class$="container toast-message-holder [[conditionalClass(isFixed)]]">
10 10 <template is="dom-repeat" items="[[toasts]]">
11 11 <div class$="alert alert-[[item.level]]">
12 12 <div on-tap="dismissNotification" class="toast-close" index-pos="[[index]]">
13 13 <span>[[_gettext('Close')]]</span>
14 14 </div>
15 15 <rhodecode-unsafe-html text="[[item.message]]"></rhodecode-unsafe-html>
16 16 </div>
17 17 </template>
18 18 </div>
19 19 </template>
20 20 </template>
21 21
22 <script src="rhodecode-toast.js"></script>
22 <script>
23 Polymer({
24 is: 'rhodecode-toast',
25 properties: {
26 toasts: {
27 type: Array,
28 value: function(){
29 return []
30 }
31 },
32 isFixed: {
33 type: Boolean,
34 value: false
35 },
36 hasToasts: {
37 type: Boolean,
38 computed: '_computeHasToasts(toasts.*)'
39 },
40 keyEventTarget: {
41 type: Object,
42 value: function() {
43 return document.body;
44 }
45 }
46 },
47 behaviors: [
48 Polymer.IronA11yKeysBehavior
49 ],
50 observers: [
51 '_changedToasts(toasts.splices)'
52 ],
53
54 keyBindings: {
55 'esc:keyup': '_hideOnEsc'
56 },
57
58 _hideOnEsc: function (event) {
59 return this.dismissNotifications();
60 },
61
62 _computeHasToasts: function(){
63 return this.toasts.length > 0;
64 },
65
66 _debouncedCalc: function(){
67 // calculate once in a while
68 this.debounce('debouncedCalc', this.toastInWindow, 25);
69 },
70
71 conditionalClass: function(){
72 return this.isFixed ? 'fixed': '';
73 },
74
75 toastInWindow: function() {
76 if (!this._headerNode){
77 return true
78 }
79 var headerHeight = this._headerNode.offsetHeight;
80 var scrollPosition = window.scrollY;
81
82 if (this.isFixed){
83 this.isFixed = 1 <= scrollPosition;
84 }
85 else{
86 this.isFixed = headerHeight <= scrollPosition;
87 }
88 },
89
90 attached: function(){
91 this._headerNode = document.querySelector('.header', document);
92 this.listen(window,'scroll', '_debouncedCalc');
93 this.listen(window,'resize', '_debouncedCalc');
94 this._debouncedCalc();
95 },
96 _changedToasts: function(newValue, oldValue){
97 $.Topic('/favicon/update').publish({count: this.toasts.length});
98 },
99 dismissNotification: function(e) {
100 $.Topic('/favicon/update').publish({count: this.toasts.length-1});
101 var idx = e.target.parentNode.indexPos
102 this.splice('toasts', idx, 1);
103
104 },
105 dismissNotifications: function(){
106 $.Topic('/favicon/update').publish({count: 0});
107 this.splice('toasts', 0);
108 },
109 handleNotification: function(data){
110 if (!templateContext.rhodecode_user.notification_status && !data.message.force) {
111 // do not act if notifications are disabled
112 return
113 }
114 this.push('toasts',{
115 level: data.message.level,
116 message: data.message.message
117 });
118 },
119 _gettext: _gettext
120 });
121
122 </script>
23 123 </dom-module>
@@ -1,21 +1,37 b''
1 1 <link rel="import" href="../../../../../../bower_components/paper-toggle-button/paper-toggle-button.html">
2 2 <link rel="import" href="../../../../../../bower_components/paper-spinner/paper-spinner.html">
3 3 <link rel="import" href="../../../../../../bower_components/paper-tooltip/paper-tooltip.html">
4 4
5 5 <dom-module id="rhodecode-toggle">
6 6
7 7 <style include="shared-styles"></style>
8 8 <link rel="stylesheet" href="rhodecode-toggle.css">
9 9
10 10 <template>
11 11 <div class="rc-toggle">
12 12 <paper-toggle-button checked={{checked}}>[[labelStatus(checked)]]</paper-toggle-button>
13 13 <paper-tooltip>[[tooltipText]]</paper-tooltip>
14 14 <template is="dom-if" if="[[shouldShow(noSpinner)]]">
15 15 <paper-spinner active=[[active]]></paper-spinner>
16 16 </template>
17 17 </div>
18 18 </template>
19 19
20 <script src="rhodecode-toggle.js"></script>
21 </dom-module> No newline at end of file
20 <script>
21 Polymer({
22 is: 'rhodecode-toggle',
23 properties: {
24 noSpinner: { type: Boolean, value: false, reflectToAttribute:true},
25 tooltipText: { type: String, value: "Click to toggle", reflectToAttribute:true},
26 checked: { type: Boolean, value: false, reflectToAttribute:true},
27 active: { type: Boolean, value: false, reflectToAttribute:true, notify:true}
28 },
29 shouldShow: function(){
30 return !this.noSpinner
31 },
32 labelStatus: function(isActive){
33 return this.checked? 'Enabled' : "Disabled"
34 }
35 });
36 </script>
37 </dom-module>
@@ -1,9 +1,23 b''
1 1 <link rel="import" href="../../../../../../bower_components/polymer/polymer.html">
2 2
3 3 <dom-module id="rhodecode-unsafe-html">
4 4 <template>
5 5 <style include="shared-styles"></style>
6 6 <slot></slot>
7 7 </template>
8 <script src="rhodecode-unsafe-html.js"></script>
8 <script>
9 Polymer({
10 is: 'rhodecode-unsafe-html',
11 properties: {
12 text: {
13 type: String,
14 observer: '_handleText'
15 }
16 },
17 _handleText: function(newVal, oldVal){
18 this.innerHTML = this.text;
19 }
20 })
21
22 </script>
9 23 </dom-module>
1 NO CONTENT: file was removed
This diff has been collapsed as it changes many lines, (502 lines changed) Show them Hide them
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now