summaryrefslogtreecommitdiffstats
path: root/public/js/progress-bar.js
blob: be24f1c5714cc93d91866bd0b9534da152a990e5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
(function (Icinga) {

    "use strict";

    class ProgressBar extends Icinga.EventListener {
        constructor(icinga)
        {
            super(icinga);

            /**
             * Frame update threshold. If it reaches zero, the view is updated
             *
             * Currently, only every third frame is updated.
             *
             * @type {number}
             */
            this.frameUpdateThreshold = 3;

            /**
             * Threshold at which animations get smoothed out (in milliseconds)
             *
             * @type {number}
             */
            this.smoothUpdateThreshold = 250;

            this.on('rendered', '#main > .container', this.onRendered, this);
        }

        onRendered(event)
        {
            const _this = event.data.self;
            const container = event.target;

            container.querySelectorAll('[data-animate-progress]').forEach(progress => {
                const frequency = (
                    (Number(progress.dataset.endTime) - Number(progress.dataset.startTime)
                ) * 1000) / progress.parentElement.offsetWidth;

                _this.updateProgress(
                    now => _this.animateProgress(progress, now), frequency);
            });
        }

        animateProgress(progress, now)
        {
            if (! progress.isConnected) {
                return false; // Exit early if the node is removed from the DOM
            }

            const durationScale = 100;

            const startTime = Number(progress.dataset.startTime);
            const endTime = Number(progress.dataset.endTime);
            const duration = endTime - startTime;
            const end = new Date(endTime * 1000.0);

            let leftNow = durationScale * (1 - (end - now) / (duration * 1000.0));
            if (leftNow > durationScale) {
                leftNow = durationScale;
            } else if (leftNow < 0) {
                leftNow = 0;
            }

            const switchAfter = Number(progress.dataset.switchAfter);
            if (! isNaN(switchAfter)) {
                const switchClass = progress.dataset.switchClass;
                const switchAt = new Date((startTime * 1000.0) + (switchAfter * 1000.0));
                if (now < switchAt) {
                    progress.classList.add(switchClass);
                } else if (progress.classList.contains(switchClass)) {
                    progress.classList.remove(switchClass);
                }
            }

            const bar = progress.querySelector(':scope > .bar');
            bar.style.width = leftNow + '%';

            return leftNow !== durationScale;
        }

        updateProgress(callback, frequency, now = null)
        {
            if (now === null) {
                now = new Date();
            }

            if (! callback(now)) {
                return;
            }

            if (frequency < this.smoothUpdateThreshold) {
                let counter = this.frameUpdateThreshold;
                const onNextFrame = timeSinceOrigin => {
                    if (--counter === 0) {
                        this.updateProgress(callback, frequency, new Date(performance.timeOrigin + timeSinceOrigin));
                    } else {
                        requestAnimationFrame(onNextFrame);
                    }
                };
                requestAnimationFrame(onNextFrame);
            } else {
                setTimeout(() => this.updateProgress(callback, frequency), frequency);
            }
        }
    }

    Icinga.Behaviors = Icinga.Behaviors || {};

    Icinga.Behaviors.ProgressBar = ProgressBar;
})(Icinga);