Completed
Push — master ( b60087...027b80 )
by Pieter
05:30
created

Connection.reconnect   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
c 1
b 0
f 0
nc 2
nop 0
dl 0
loc 10
rs 9.4285
1
const GitAmp = (function(exports, $) {
2
    'use strict';
3
4
    /**
5
     * AudioPlayer
6
     */
7
    const AudioPlayer = (function() {
8
        // something somewhere needs a global volume variable
9
        // not sure what thing it is, but adding this line works
10
        exports.volume = 0.6;
11
12
        const maxPitch = 100.0;
13
        const logUsed  = 1.0715307808111486871978099;
14
15
        const maximumSimultaneousNotes = 2;
16
        const soundLength = 300;
17
18
        function AudioPlayer() {
19
            this.currentlyPlayingSounds = 0;
20
21
            this.sounds = {
22
                celesta: this.initializeCelesta(),
23
                clav: this.initializeClav(),
24
                swells: this.initializeSwells()
25
            };
26
27
            //noinspection JSUnresolvedVariable
28
            exports.Howler.volume(0.7);
29
        }
30
31
        AudioPlayer.prototype.initializeCelesta = function() {
32
            const sounds = [];
33
34
            for (let i = 1; i <= 24; i++) {
35
                let filename = (i > 9) ? 'c0' + i : 'c00' + i;
36
37
                //noinspection JSUnresolvedFunction
38
                sounds.push(new Howl({
0 ignored issues
show
Bug introduced by
The variable Howl seems to be never declared. If this is a global, consider adding a /** global: Howl */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
39
                    src : [
40
                        'https://d1fz9d31zqor6x.cloudfront.net/sounds/celesta/' + filename + '.ogg',
41
                        'https://d1fz9d31zqor6x.cloudfront.net/sounds/celesta/' + filename + '.mp3'
42
                    ],
43
                    volume : 0.7,
44
                    buffer: true
45
                }));
46
            }
47
48
            return sounds;
49
        };
50
51
        AudioPlayer.prototype.initializeClav = function() {
52
            const sounds = [];
53
54
            for (let i = 1; i <= 24; i++) {
55
                let filename = (i > 9) ? 'c0' + i : 'c00' + i;
56
57
                //noinspection JSUnresolvedFunction
58
                sounds.push(new Howl({
0 ignored issues
show
Bug introduced by
The variable Howl seems to be never declared. If this is a global, consider adding a /** global: Howl */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
59
                    src : [
60
                        'https://d1fz9d31zqor6x.cloudfront.net/sounds/clav/' + filename + '.ogg',
61
                        'https://d1fz9d31zqor6x.cloudfront.net/sounds/clav/' + filename + '.mp3'
62
                    ],
63
                    volume : 0.7,
64
                    buffer: true
65
                }));
66
            }
67
68
            return sounds;
69
        };
70
71
        AudioPlayer.prototype.initializeSwells = function() {
72
            const sounds = [];
73
74
            for (let i = 1; i <= 3; i++) {
75
                //noinspection JSUnresolvedFunction
76
                sounds.push(new Howl({
0 ignored issues
show
Bug introduced by
The variable Howl seems to be never declared. If this is a global, consider adding a /** global: Howl */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
77
                    src : [
78
                        'https://d1fz9d31zqor6x.cloudfront.net/sounds/swells/swell' + i + '.ogg',
79
                        'https://d1fz9d31zqor6x.cloudfront.net/sounds/swells/swell' + i + '.mp3'
80
                    ],
81
                    volume : 0.7,
82
                    buffer: true
83
                }));
84
            }
85
86
            return sounds;
87
        };
88
89
        AudioPlayer.prototype.getSoundIndex = function(size, type) {
90
            const pitch = 100 - Math.min(maxPitch, Math.log(size + logUsed) / Math.log(logUsed));
91
            let index   = Math.floor(pitch / 100.0 * this.sounds[type].length);
92
93
            index += Math.floor(Math.random() * 4) - 2;
94
            index = Math.min(this.sounds[type].length - 1, index);
95
            index = Math.max(1, index);
96
97
            return index;
98
        };
99
100
        AudioPlayer.prototype.playSound = function(sound) {
101
            if (this.currentlyPlayingSounds >= maximumSimultaneousNotes) {
102
                return;
103
            }
104
105
            sound.play();
106
107
            this.currentlyPlayingSounds++;
108
109
            setTimeout(function() {
110
                this.currentlyPlayingSounds--;
111
            }.bind(this), soundLength);
112
        };
113
114
        AudioPlayer.prototype.playCelesta = function(size) {
115
            this.playSound(this.sounds.celesta[this.getSoundIndex(size, 'celesta')]);
116
        };
117
118
        AudioPlayer.prototype.playClav = function(size) {
119
            this.playSound(this.sounds.clav[this.getSoundIndex(size, 'clav')]);
120
        };
121
122
        AudioPlayer.prototype.playSwell = function() {
123
            this.playSound(this.sounds.swells[Math.round(Math.random() * (this.sounds.swells.length - 1))]);
124
        };
125
126
        return AudioPlayer;
127
    }());
128
129
    /**
130
     * Gui
131
     */
132
    const Gui = (function() {
133
        const scaleFactor = 6;
134
        const textColor   = '#ffffff';
135
        const maxLife     = 20000;
136
137
        function Event(event, svg) {
138
            this.event = event;
139
            this.svg   = svg;
140
        }
141
142
        Event.prototype.getSize = function() {
143
            return Math.max(Math.sqrt(Math.abs(this.event.getMessage().length)) * scaleFactor, 3);
144
        };
145
146
        Event.prototype.getText = function() {
147
            switch(this.event.getType()){
0 ignored issues
show
Coding Style introduced by
As per coding-style, switch statements should have a default case.
Loading history...
148
                case 'PushEvent':
149
                    return this.event.getActorName() + ' pushed to ' + this.event.getRepositoryName();
150
                case 'PullRequestEvent':
151
                    return this.event.getActorName() + ' ' + this.event.getAction() + ' ' + ' a PR for ' + this.event.getRepositoryName();
152
                case 'IssuesEvent':
153
                    return this.event.getActorName() + ' ' + this.event.getAction() + ' an issue in ' + this.event.getRepositoryName();
154
                case 'IssueCommentEvent':
155
                    return this.event.getActorName() + ' commented in ' + this.event.getRepositoryName();
156
                case 'ForkEvent':
157
                    return this.event.getActorName() + ' forked ' + this.event.getRepositoryName();
158
                case 'CreateEvent':
159
                    return this.event.getActorName() + ' created ' + this.event.getRepositoryName();
160
                case 'WatchEvent':
161
                    return this.event.getActorName() + ' watched ' + this.event.getRepositoryName();
162
            }
0 ignored issues
show
Comprehensibility introduced by
There is no default case in this switch, so nothing gets returned when all cases fail. You might want to consider adding a default or return undefined explicitly.
Loading history...
163
        };
164
165
        Event.prototype.getBackgroundColor = function() {
166
            switch(this.event.getType()){
0 ignored issues
show
Coding Style introduced by
As per coding-style, switch statements should have a default case.
Loading history...
167
                case 'PushEvent':
168
                    return '#22B65D';
169
                case 'PullRequestEvent':
170
                    return '#8F19BB';
171
                case 'IssuesEvent':
172
                    return '#ADD913';
173
                case 'IssueCommentEvent':
174
                    return '#FF4901';
175
                case 'ForkEvent':
176
                    return '#0184FF';
177
                case 'CreateEvent':
178
                    return '#00C0C0';
179
                case 'WatchEvent':
180
                    return '#E60062';
181
            }
0 ignored issues
show
Comprehensibility introduced by
There is no default case in this switch, so nothing gets returned when all cases fail. You might want to consider adding a default or return undefined explicitly.
Loading history...
182
        };
183
184
        Event.prototype.getRingAnimationDuration = function() {
185
            if (this.event.getType() === 'PullRequestEvent') {
186
                return 10000;
187
            }
188
189
            return 3000;
190
        };
191
192
        Event.prototype.getRingRadius = function() {
193
            if (this.event.getType() === 'PullRequestEvent') {
194
                return 600;
195
            }
196
197
            return 80;
198
        };
199
200
        Event.prototype.draw = function(width, height) {
201
            let no_label = false;
0 ignored issues
show
Unused Code introduced by
The variable no_label seems to be never used. Consider removing it.
Loading history...
202
            let size     = this.getSize();
203
204
            const self = this;
205
206
            //noinspection JSUnresolvedFunction
207
            Math.seedrandom(this.event.getMessage());
208
            let x = Math.random() * (width - size) + size;
209
            let y = Math.random() * (height - size) + size;
210
211
            let circle_group = this.svg.append('g')
212
                .attr('transform', 'translate(' + x + ', ' + y + ')')
213
                .attr('fill', this.getBackgroundColor())
214
                .style('opacity', 1);
215
216
            let ring = circle_group.append('circle');
217
            ring.attr({r: size, stroke: 'none'});
218
            ring.transition()
219
                .attr('r', size + this.getRingRadius())
220
                .style('opacity', 0)
221
                .ease(Math.sqrt)
222
                .duration(this.getRingAnimationDuration())
223
                .remove();
224
225
            let circle_container = circle_group.append('a');
226
            circle_container.attr('xlink:href', this.event.getUrl());
227
            circle_container.attr('target', '_blank');
228
            circle_container.attr('fill', textColor);
229
230
            let circle = circle_container.append('circle');
231
            circle.classed(this.event.getType(), true);
232
            circle.attr('r', size)
233
                .attr('fill', this.getBackgroundColor())
234
                .transition()
235
                .duration(maxLife)
236
                .style('opacity', 0)
237
                .remove();
238
239
            circle_container.on('mouseover', function() {
240
                circle_container.append('text')
241
                    .text(self.getText())
242
                    .classed('label', true)
243
                    .attr('text-anchor', 'middle')
244
                    .attr('font-size', '0.8em')
245
                    .transition()
246
                    .delay(1000)
247
                    .style('opacity', 0)
248
                    .duration(2000)
249
                    .each(function() { no_label = true; })
0 ignored issues
show
Unused Code introduced by
The variable no_label seems to be never used. Consider removing it.
Loading history...
250
                    .remove();
251
            });
252
253
            circle_container.append('text')
254
                .text(this.getText())
255
                .classed('article-label', true)
256
                .attr('text-anchor', 'middle')
257
                .attr('font-size', '0.8em')
258
                .transition()
259
                .delay(2000)
260
                .style('opacity', 0)
261
                .duration(5000)
262
                .each(function() { no_label = true; })
0 ignored issues
show
Unused Code introduced by
The variable no_label seems to be never used. Consider removing it.
Loading history...
263
                .remove();
264
        };
265
266
        function Gui() {
267
            //noinspection JSUnresolvedVariable
268
            this.svg = exports.d3.select('#area').append('svg');
269
270
            exports.addEventListener('resize', this.resize.bind(this));
271
272
            this.setupVolumeSlider();
273
            this.resize();
274
        }
275
276
        Gui.prototype.setupVolumeSlider = function() {
277
            //noinspection JSUnresolvedFunction
278
            $('#volumeSlider').slider({
279
                max: 100,
280
                min: 0,
281
                value: volume * 100,
0 ignored issues
show
Bug introduced by
The variable volume seems to be never declared. If this is a global, consider adding a /** global: volume */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
282
                slide: function (event, ui) {
283
                    //noinspection JSUnresolvedVariable
284
                    exports.Howler.volume(ui.value/100.0);
285
                },
286
                change: function (event, ui) {
287
                    //noinspection JSUnresolvedVariable
288
                    exports.Howler.volume(ui.value/100.0);
289
                }
290
            });
291
        };
292
293
        Gui.prototype.getWidth = function() {
294
            return exports.innerWidth;
295
        };
296
297
        Gui.prototype.getHeight = function() {
298
            return exports.innerHeight - $('header').height();
299
        };
300
301
        Gui.prototype.resize = function() {
302
            this.svg.attr('width', this.getWidth());
303
            this.svg.attr('height', this.getHeight());
304
        };
305
306
        Gui.prototype.drawEvent = function(event) {
307
            if (document.hidden) {
308
                return;
309
            }
310
311
            new Event(event, this.svg).draw(this.getWidth(), this.getHeight());
312
313
            // Remove HTML of decayed events
314
            // Keep it less than 50
315
            let $area = $('#area');
316
            if($area.find('svg g').length > 50){
317
                $area.find('svg g:lt(10)').remove();
318
            }
319
        };
320
321
        return Gui;
322
    }());
323
324
    /**
325
     * ConnectedUsersMessage
326
     */
327
    function ConnectedUsersMessage(response) {
328
        //noinspection JSUnresolvedVariable
329
        this.count = response.connectedUsers;
330
    }
331
332
    ConnectedUsersMessage.prototype.getCount = function() {
333
        return this.count;
334
    };
335
336
    /**
337
     * EventMessage
338
     */
339
    function EventMessage(event) {
340
        this.event = event;
341
    }
342
343
    EventMessage.prototype.getId = function() {
344
        //noinspection JSUnresolvedVariable
345
        return this.event.id;
346
    };
347
348
    EventMessage.prototype.getType = function() {
349
        //noinspection JSUnresolvedVariable
350
        return this.event.type;
351
    };
352
353
    EventMessage.prototype.getAction = function() {
354
        //noinspection JSUnresolvedVariable
355
        return this.event.action;
356
    };
357
358
    EventMessage.prototype.getRepositoryName = function() {
359
        //noinspection JSUnresolvedVariable
360
        return this.event.repoName;
361
    };
362
363
    EventMessage.prototype.getActorName = function() {
364
        //noinspection JSUnresolvedVariable
365
        return this.event.actorName;
366
    };
367
368
    EventMessage.prototype.getUrl = function() {
369
        //noinspection JSUnresolvedVariable
370
        return this.event.eventUrl;
371
    };
372
373
    EventMessage.prototype.getMessage = function() {
374
        //noinspection JSUnresolvedVariable
375
        return this.event.message;
376
    };
377
378
    /**
379
     * EventMessageCollection
380
     */
381
    function EventMessageCollection(response) {
382
        this.events = [];
383
384
        for (let i = 0; i < response.length; i++) {
385
            this.events.push(new EventMessage(response[i]));
386
        }
387
    }
388
389
    EventMessageCollection.prototype.forEach = function(callback) {
390
        for (let i = 0; i < this.events.length; i++) {
391
            callback(this.events[i]);
392
        }
393
    };
394
395
    /**
396
     * EventMessagesFactory
397
     */
398
    function EventMessagesFactory () {
399
    }
400
401
    EventMessagesFactory.prototype.build = function(response) {
402
        const parsedResponse = JSON.parse(response.data);
403
404
        if (parsedResponse.hasOwnProperty('connectedUsers')) {
405
            return new ConnectedUsersMessage(parsedResponse);
406
        }
407
408
        return new EventMessageCollection(parsedResponse);
409
    };
410
411
    /**
412
     * EventQueue
413
     */
414
    function EventQueue() {
415
        this.queue = [];
416
    }
417
418
    EventQueue.prototype.append = function(eventMessages) {
419
        eventMessages.forEach(function(event) {
420
            if (this.exists(event)) {
421
                return;
422
            }
423
424
            this.queue.push(event);
425
        }.bind(this));
426
427
        if (this.queue.length > 1000) {
428
            this.queue = this.queue.slice(0, 1000);
429
        }
430
    };
431
432
    EventQueue.prototype.exists = function(event) {
433
        for (let i = 0; i < this.queue.length; i++) {
434
            if (event.getId() === this.queue[i].getId()) {
435
                return true;
436
            }
437
        }
438
439
        return false;
440
    };
441
442
    EventQueue.prototype.get = function() {
443
        return this.queue.shift();
444
    };
445
446
    EventQueue.prototype.count = function() {
447
        return this.queue.length;
448
    };
449
450
    /**
451
     * Connection
452
     */
453
    function Connection(eventMessageFactory) {
454
        this.eventMessageFactory = eventMessageFactory;
455
456
        this.connection = null;
457
        this.handlers   = [];
458
    }
459
460
    Connection.prototype.start = function() {
461
        let protocol = 'ws://';
462
463
        if (exports.location.protocol === "https:") {
464
            protocol = 'wss://';
465
        }
466
467
        try {
468
            this.connection = new WebSocket(protocol + exports.location.host + '/ws');
0 ignored issues
show
Bug introduced by
The variable WebSocket seems to be never declared. If this is a global, consider adding a /** global: WebSocket */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
469
470
            this.connection.addEventListener('message', this.handleMessage.bind(this));
471
            this.connection.addEventListener('open', this.handleOpen.bind(this));
472
            this.connection.addEventListener('close', this.reconnect.bind(this));
473
            this.connection.addEventListener('error', this.reconnect.bind(this));
474
        } catch(e) {
475
            this.connection = null;
476
        }
477
    };
478
479
    Connection.prototype.registerHandler = function(handler) {
480
        this.handlers.push(handler);
481
    };
482
483
    Connection.prototype.handleMessage = function(response) {
484
        const message = this.eventMessageFactory.build(response);
485
486
        for (let i = 0; i < this.handlers.length; i++) {
487
            this.handlers[i](message);
488
        }
489
    };
490
491
    Connection.prototype.handleOpen = function() {
492
        const elements = document.querySelectorAll('.events-remaining-text, .events-remaining-value, .online-users-div');
493
494
        for (let i = 0; i < elements.length; i++) {
495
            elements[i].style.visibility = 'visible';
496
        }
497
    };
498
499
    Connection.prototype.reconnect = function() {
500
        // prevent piling up reconnect
501
        if (this.connection.readyState === 0 || this.connection.readyState === 1) {
502
            return;
503
        }
504
505
        setTimeout(function() {
506
            this.start();
507
        }.bind(this), 5000);
508
    };
509
510
    /**
511
     * Application
512
     */
513
    function Application() {
514
        this.queue = new EventQueue();
515
        this.audio = new AudioPlayer();
516
        this.gui   = new Gui();
517
    }
518
519
    Application.prototype.run = function() {
520
        const connection = new Connection(new EventMessagesFactory());
521
522
        connection.registerHandler(this.process.bind(this));
523
524
        connection.start();
525
526
        this.loop();
527
    };
528
529
    Application.prototype.process = function(message) {
530
        if (message instanceof ConnectedUsersMessage) {
531
            document.getElementsByClassName('online-users-count')[0].textContent = message.getCount();
532
533
            return;
534
        }
535
536
        this.queue.append(message);
537
    };
538
539
    Application.prototype.loop = function() {
540
        setTimeout(function() {
541
            this.loop();
542
543
            if (!this.queue.count()) {
544
                return;
545
            }
546
547
            this.processEvent(this.queue.get());
548
549
            document.getElementsByClassName('events-remaining-value')[0].textContent = this.queue.count();
550
        }.bind(this), Math.floor(Math.random() * 1000) + 500);
551
    };
552
553
    Application.prototype.processEvent = function(event) {
554
        if (!event.getMessage()) {
555
            return;
556
        }
557
558
        if (event.getType() === 'IssuesEvent' || event.getType() === 'IssueCommentEvent') {
559
            this.audio.playClav(event.getMessage().length * 1.1);
560
        } else if(event.getType() === 'PushEvent') {
561
            this.audio.playCelesta(event.getMessage().length * 1.1);
562
        }else{
563
            this.audio.playSwell();
564
        }
565
566
        this.gui.drawEvent(event);
567
    };
568
569
    return Application;
570
}(window, jQuery));
571
572
$(function() {
573
    new GitAmp().run();
574
});
575