superdesk/superdesk-client-core

View on GitHub
scripts/apps/dashboard/world-clock/world-clock.ts

Summary

Maintainability
C
1 day
Test Coverage
import _ from 'lodash';
import './world-clock.scss';
import d3 from 'd3';
import {gettext} from 'core/utils';
import {getTimezoneLabel} from './timezones-all-labels';

angular.module('superdesk.apps.dashboard.world-clock', [
    'superdesk.apps.dashboard', 'superdesk.core.datetime',
])
    /**
     * @ngdoc controller
     * @module superdesk.apps.dashboard
     * @name WorldClockConfigController
     * @description
     *   Controller for the world clock widget configuration modal.
     */
    .controller('WorldClockConfigController', ['$scope', 'notify', 'tzdata',
        function($scope, notify, tzdata) {
            $scope.availableZones = [];
            $scope.getTimezoneLabel = getTimezoneLabel;

            tzdata.$promise.then(() => {
                $scope.availableZones = tzdata.getTzNames();
            });

            $scope.searchZones = function(searchString) {
                if (searchString) {
                    $scope.availableZones = tzdata
                        .getTzNames()
                        .filter((tz) =>
                            getTimezoneLabel(tz)
                                .toLowerCase()
                                .includes(searchString.toLowerCase()),
                        );
                } else {
                    $scope.availableZones = tzdata.getTzNames();
                }
            };

            $scope.notify = function(action, zone) {
                if (action === 'add') {
                    notify.success(gettext('World clock added: {{zone}}', {zone}), 3000);
                } else if (action === 'remove') {
                    notify.success(gettext('World clock removed: {{zone}}', {zone}), 3000);
                }
            };

            $scope.notIn = function(haystack) {
                return function(needle) {
                    return haystack.indexOf(needle) === -1;
                };
            };

            $scope.configuration.zones = $scope.configuration.zones || [];
        }])

    /**
     * @ngdoc controller
     * @module superdesk.apps.dashboard
     * @name WorldClockController
     * @description
     *   Controller for the directive - the one that creates
     *   a dashboard widget for displaying the current time in different
     *   time zones around the world.
     */
    .controller('WorldClockController', ['$scope', '$interval', 'tzdata', 'moment',
        function($scope, $interval, tzdata, moment) {
            var interval, INTERVAL_DELAY = 500;

            function updateUTC() {
                $scope.utc = moment();
                $scope.$digest();
            }

            // XXX: a hack-ish workaround to expose the object loaded via
            // RequireJS to the testing code which does not use the latter
            this._moment = moment;

            tzdata.$promise.then(() => {
                moment.tz.add(
                    _.pick(tzdata, ['zones', 'links']),
                );
            });

            interval = $interval(updateUTC, INTERVAL_DELAY, 0, false);
            $scope.$on('$destroy', function stopTimeout() {
                $interval.cancel(interval);
            });
        }])
    /**
     * sdClock analog clock
     */
    .directive('sdClock', () => {
        var pi = Math.PI,
            scales = {
                s: d3.scale
                    .linear()
                    .domain([0, 59 + 999 / 1000])
                    .range([0, 2 * pi]),
                m: d3.scale
                    .linear()
                    .domain([0, 59 + 59 / 60])
                    .range([0, 2 * pi]),
                h: d3.scale
                    .linear()
                    .domain([0, 11 + 59 / 60])
                    .range([0, 2 * pi]),
            };

        return {
            scope: {
                utc: '=',
                tz: '@',
            },
            link: function(scope, element, attrs) {
                var width = 105,
                    height = 100,
                    r = Math.min(width, height) * 0.8 * 0.5,
                    dayBg = '#d4d8de',
                    dayClockhands = '#313131',
                    dayNumbers = '#a0a0a0',
                    nightBg = '#1b1e23',
                    nightClockhands = '#e0e0e0',
                    nightNumbers = '#848484';

                var svg = d3.select(element[0])
                    .append('svg')
                    .attr('width', width)
                    .attr('height', height);

                var clock = svg.append('g')
                    .attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')');

                // background circle
                clock.append('circle')
                    .attr('r', r)
                    .attr('class', 'clock-outer')
                    .style('stroke-width', 1.5);

                // inner dot
                clock.append('circle')
                    .attr('r', 1.5)
                    .attr('class', 'clock-inner');

                // numbers
                clock.selectAll('.number-lines')
                    .data(_.range(0, 59, 5))
                    .enter()
                    .append('path')
                    .attr('d', (d) => {
                        var angle = scales.m(d);
                        var arc = d3.svg.arc()
                            .innerRadius(r * 0.7)
                            .outerRadius(r * 0.9)
                            .startAngle(angle)
                            .endAngle(angle);

                        return arc();
                    })
                    .attr('class', 'number-lines')
                    .style('stroke-width', 1.5);

                // format data for given time
                function getData(timeStr) {
                    var time = timeStr.split(':');

                    return [
                        {unit: 'h', val: parseInt(time[0], 10) + parseInt(time[1], 10) / 60, r: 0.5},
                        {unit: 'm', val: parseInt(time[1], 10), r: 0.8},
                    ];
                }

                scope.$watch('utc', (utc) => {
                    var time = utc ? utc.tz(scope.tz).format('HH:mm:ss') : '00:00:00';
                    var data = getData(time);
                    var isDay = data[0].val >= 8 && data[0].val < 20;

                    if (isDay) {
                        clock.selectAll('.clock-outer').style('fill', dayBg);
                        clock.selectAll('.clock-inner').style('fill', dayBg);
                        clock.selectAll('.number-lines').style('stroke', dayNumbers);
                    } else {
                        clock.selectAll('.clock-outer').style('fill', nightBg);
                        clock.selectAll('.clock-inner').style('fill', nightBg);
                        clock.selectAll('.number-lines').style('stroke', nightNumbers);
                    }

                    clock.selectAll('.clockhand').remove();
                    clock.selectAll('.clockhand')
                        .data(data)
                        .enter()
                        .append('path')
                        .attr('d', (d) => {
                            var angle = scales[d.unit](d.val);
                            var arc = d3.svg.arc()
                                .innerRadius(r * 0)
                                .outerRadius(r * d.r)
                                .startAngle(angle)
                                .endAngle(angle);

                            return arc();
                        })
                        .attr('class', 'clockhand')
                        .style('stroke-width', 2)
                        .style('stroke', isDay ? dayClockhands : nightClockhands);
                });
            },
        };
    })
    .config(['dashboardWidgetsProvider', function(dashboardWidgets) {
        dashboardWidgets.addWidget('world-clock', {
            label: gettext('World Clock'),
            multiple: true,
            icon: 'time',
            max_sizex: 2,
            max_sizey: 1,
            sizex: 1,
            sizey: 1,
            classes: 'tabs modal--nested-fix',
            thumbnail: 'scripts/apps/dashboard/world-clock/thumbnail.svg',
            template: 'scripts/apps/dashboard/world-clock/widget-worldclock.html',
            configurationTemplate: 'scripts/apps/dashboard/world-clock/configuration.html',
            configuration: {zones: ['Europe/London', 'Asia/Tokyo', 'Europe/Moscow']},
            description: gettext('World clock widget'),
        });
    }]);