<commit_before>define([
    "jquery",
    "base1/cockpit",
    "base1/angular",
    "kubernetes/app"
], function($, cockpit, angular) {
    'use strict';

    var _ = cockpit.gettext;

    return angular.module('kubernetes.images', ['ngRoute'])
        .config([
            '$routeProvider',
            function($routeProvider) {
                $routeProvider.when('/images', {
                    templateUrl: 'views/images.html',
                    controller: 'ImagesCtrl'
                });
            }
        ])

        .provider('ImageRegistry',
            function() {
                var unique = 0;
                var client = null;
                var scopes = { };
                var timeout = null;

                /* The actual singleton object we return */
                var repositories = { };

                function ImageRepository(repo) {
                    var self = this;
                    self.name = repo;
                    self.images = { };
                    self.imagestreams = { };

                    /*
                     * ImageRepository.tags:
                     *
                     * Returns a map of image -> tag. Tag value will be null
                     * for any image that is not tagged.
                     */
                    self.tags = function tags() {
                        var result = { };

                        /* Account for any untagged images */
                        angular.forEach(self.images, function(image) {
                            result[image.metadata.name] = null;
                        });

                        /* The only source of tags we have is currently from ImageStream items */
                        angular.forEach(self.imagestreams, function(imagestream) {
                            if (imagestream.status && imagestream.status.tags) {
                                imagestream.status.tags.forEach(function(tag) {
                                    if (tag.items) {
                                        tag.items.forEach(function(item) {
                                            result[item.image] = tag.tag;
                                        });
                                    }
                                });
                            }
                        });

                        return result;
                    };

                    self.latest = function latest() {
                        var result = null;
                        angular.forEach(self.images, function(image) {
                            var meta = image.dockerImageMetadata || { };
                            var created = meta.Created || null;
                            if (created && (!result || created > result))
                                result = created;
                        });
                        return result;
                    };
                }

                /*
                 * I'm not completely happy with this. We could be
                 * firing way too many $digests for the entire scope
                 * because other things from other code, could be triggering
                 * while waiting for the the timeout.
                 */

                function digest() {
                    timeout = null;
                    for (var id in scopes)
                        scopes[id].$digest();
                }

                function trigger() {
                    if (timeout === null)
                        timeout = window.setTimeout(digest, 100);
                }

                function repo_qualify(repo) {
                    if (repo && repo.indexOf('/') === -1)
                        return "library/" + repo;
                    return repo;
                }

                function image_repo(image) {
                    var ref = image.dockerImageReference || "";
                    return repo_qualify(ref.split('@')[0]);
                }

                function image_added(ev, image, key) {
                    var repo = image_repo(image);
                    if (repo) {
                        var repository = repositories[repo];
                        if (!repository)
                            repository = repositories[repo] = new ImageRepository(repo);
                        repository.images[key] = image;
                    }
                    trigger();
                }

                function image_removed(ev, image, key) {
                    var repo = image_repo(image);
                    if (repo) {
                        var repository = repositories[repo];
                        if (repository) {
                            delete repository.images[key];
                            if (repository_empty(repository))
                                delete repositories[repo];
                        }
                    }
                    trigger();
                }

                function imagestream_repo(imagestream) {
                    var spec = imagestream.spec || { };
                    return repo_qualify(spec.dockerImageRepository || "");
                }

                function imagestream_added(ev, imagestream, key) {
                    var repo = imagestream_repo(imagestream);
                    if (repo) {
                        var repository = repositories[repo];
                        if (!repository)
                            repository = repositories[repo] = new ImageRepository(repo);
                        repository.imagestreams[key] = imagestream;
                    }
                    trigger();
                }

                function imagestream_removed(ev, imagestream, key) {
                    var repo = imagestream_repo(imagestream);
                    if (repo) {
                        var repository = repositories[key];
                        if (repository) {
                            delete repository.imagestreams[key];
                            if (repository_empty(repository))
                                delete repositories[repo];
                        }
                    }
                    trigger();
                }

                function repository_empty(repository) {
                    var key;
                    for (key in repository.images)
                        return false;
                    for (key in repository.imagestreams)
                        return false;
                    return true;
                }

                var images, imagestreams;

                function start(scope) {
                    var key;

                    /*
                     * First time we start, ask the kubernetes client to
                     * load these types of objects. These are openshift specific.
                     */
                    if (unique === 0) {
                        client.include("images");
                        client.include("imagestreams");
                    }

                    if (angular.equals({}, scopes)) {
                        images = client.select("Image");
                        client.track(images);
                        $(images).on("added", image_added);
                        $(images).on("removed", image_removed);
                        $(images).on("updated", trigger);
                        for (key in images)
                            image_added(images[key], key);

                        imagestreams = client.select("ImageStream");
                        client.track(imagestreams);
                        $(imagestreams).on("added", imagestream_added);
                        $(imagestreams).on("removed", imagestream_removed);
                        $(imagestreams).on("updated", trigger);
                        for (key in imagestreams)
                            imagestream_added(imagestreams[key], key);
                    }

                    var id = unique++;
                    scopes[id] = scope;
                    return id;
                }

                function stop(id) {
                    delete scopes[id];
                    for (id in scopes)
                        return;

                    /* No scopes listening, stop */
                    client.track(images, false);
                    $(images).off();
                    images = null;

                    client.track(imagestreams, false);
                    $(imagestreams).off();
                    imagestreams = null;

                    window.clearTimeout(timeout);
                    timeout = null;
                }

                /* Bind the scope to the singleton */
                function ImageRegistry(scope) {
                    var self = this;
                    self.repositories = repositories;
                    var id = start(scope);
                    scope.$on("$destroy", function() {
                        stop(id);
                    });
                }

                /* Invoked for each caller of this provider */
                return {
                    $get: [
                        'kubernetesClient',
                        function(kubernetesClient) {
                            if (!client)
                                client = kubernetesClient;
                            return ImageRegistry;
                        }
                    ]
                };
            }
        )

        .controller('ImagesCtrl', [
            '$scope',
            'kubernetesClient',
            'ImageRegistry',
            function($scope, kubernetesClient, ImageRegistry) {
                $scope.registry = new ImageRegistry($scope);
                $scope.repositories = $scope.registry.repositories;
            }
        ])

        /*
         * Display a tags dict as returned by
         * ImageRepository.tags()
         */
        .filter('imagesTags', function() {
            return function(tags) {
                var extra = 0;
                var names = [];
                var image, tag;
                for (image in tags) {
                    tag = tags[image];
                    if (tag)
                        names.push(tag);
                    else
                        extra += 1;
                }

                names.sort();
                if (names.length > 5) {
                    extra += (names.length - 5);
                    names = names.slice(0, 5);
                }

                var format;
                var ret = names.join(", ");
                if (extra) {
                    if (ret)
                        format = cockpit.ngettext("more images", _(" + $0 more"), _(" + $0 more"), extra);
                    else
                        format = cockpit.ngettext(_("$0 image"), _("$0 images"), extra);
                    ret += cockpit.format(format, extra);
                }
                return ret;
            };
        });
});
<commit_msg>kubernetes: Parse image repo tags and source registry correctly<commit_after>define([
    "jquery",
    "base1/cockpit",
    "base1/angular",
    "kubernetes/app"
], function($, cockpit, angular) {
    'use strict';

    var _ = cockpit.gettext;

    return angular.module('kubernetes.images', ['ngRoute'])
        .config([
            '$routeProvider',
            function($routeProvider) {
                $routeProvider.when('/images', {
                    templateUrl: 'views/images.html',
                    controller: 'ImagesCtrl'
                });
            }
        ])

        .provider('ImageRegistry',
            function() {
                var unique = 0;
                var client = null;
                var scopes = { };
                var timeout = null;

                /* The actual singleton object we return */
                var repositories = { };

                function ImageRepository(repo) {
                    var self = this;
                    self.repo = repo;
                    self.images = { };
                    self.imagestreams = { };

                    /* Split out repository name and source */
                    var parts = repo.split("/");
                    if (parts.length == 3) {
                        self.name = parts.slice(1).join("/");
                        self.source = parts[1];
                    } else {
                        self.name = repo;
                        self.source = null;
                    }

                    /*
                     * ImageRepository.tags:
                     *
                     * Returns a map of image -> tag. Tag value will be null
                     * for any image that is not tagged.
                     */
                    self.tags = function tags() {
                        var result = { };

                        /* Account for any untagged images */
                        angular.forEach(self.images, function(image) {
                            result[image.metadata.name] = null;
                        });

                        /* The only source of tags we have is currently from ImageStream items */
                        angular.forEach(self.imagestreams, function(imagestream) {
                            if (imagestream.status && imagestream.status.tags) {
                                imagestream.status.tags.forEach(function(tag) {
                                    if (tag.items) {
                                        tag.items.forEach(function(item) {
                                            result[item.image] = tag.tag;
                                        });
                                    }
                                });
                            }
                        });

                        return result;
                    };

                    self.latest = function latest() {
                        var result = null;
                        angular.forEach(self.images, function(image) {
                            var meta = image.dockerImageMetadata || { };
                            var created = meta.Created || null;
                            if (created && (!result || created > result))
                                result = created;
                        });
                        return result;
                    };
                }

                /*
                 * I'm not completely happy with this. We could be
                 * firing way too many $digests for the entire scope
                 * because other things from other code, could be triggering
                 * while waiting for the the timeout.
                 */

                function digest() {
                    timeout = null;
                    for (var id in scopes)
                        scopes[id].$digest();
                }

                function trigger() {
                    if (timeout === null)
                        timeout = window.setTimeout(digest, 100);
                }

                function repo_qualify(repo) {
                    if (repo && repo.indexOf('/') === -1)
                        return "library/" + repo;
                    return repo;
                }

                function image_repo(image) {
                    var ref = image.dockerImageReference || "";
                    return repo_qualify(ref.split(':')[0].split('@')[0]);
                }

                function image_added(ev, image, key) {
                    var repo = image_repo(image);
                    if (repo) {
                        var repository = repositories[repo];
                        if (!repository)
                            repository = repositories[repo] = new ImageRepository(repo);
                        repository.images[key] = image;
                    }
                    trigger();
                }

                function image_removed(ev, image, key) {
                    var repo = image_repo(image);
                    if (repo) {
                        var repository = repositories[repo];
                        if (repository) {
                            delete repository.images[key];
                            if (repository_empty(repository))
                                delete repositories[repo];
                        }
                    }
                    trigger();
                }

                function imagestream_repo(imagestream) {
                    var spec = imagestream.spec || { };
                    return repo_qualify(spec.dockerImageRepository || "");
                }

                function imagestream_added(ev, imagestream, key) {
                    var repo = imagestream_repo(imagestream);
                    if (repo) {
                        var repository = repositories[repo];
                        if (!repository)
                            repository = repositories[repo] = new ImageRepository(repo);
                        repository.imagestreams[key] = imagestream;
                    }
                    trigger();
                }

                function imagestream_removed(ev, imagestream, key) {
                    var repo = imagestream_repo(imagestream);
                    if (repo) {
                        var repository = repositories[key];
                        if (repository) {
                            delete repository.imagestreams[key];
                            if (repository_empty(repository))
                                delete repositories[repo];
                        }
                    }
                    trigger();
                }

                function repository_empty(repository) {
                    var key;
                    for (key in repository.images)
                        return false;
                    for (key in repository.imagestreams)
                        return false;
                    return true;
                }

                var images, imagestreams;

                function start(scope) {
                    var key;

                    /*
                     * First time we start, ask the kubernetes client to
                     * load these types of objects. These are openshift specific.
                     */
                    if (unique === 0) {
                        client.include("images");
                        client.include("imagestreams");
                    }

                    if (angular.equals({}, scopes)) {
                        images = client.select("Image");
                        client.track(images);
                        $(images).on("added", image_added);
                        $(images).on("removed", image_removed);
                        $(images).on("updated", trigger);
                        for (key in images)
                            image_added(images[key], key);

                        imagestreams = client.select("ImageStream");
                        client.track(imagestreams);
                        $(imagestreams).on("added", imagestream_added);
                        $(imagestreams).on("removed", imagestream_removed);
                        $(imagestreams).on("updated", trigger);
                        for (key in imagestreams)
                            imagestream_added(imagestreams[key], key);
                    }

                    var id = unique++;
                    scopes[id] = scope;
                    return id;
                }

                function stop(id) {
                    delete scopes[id];
                    for (id in scopes)
                        return;

                    /* No scopes listening, stop */
                    client.track(images, false);
                    $(images).off();
                    images = null;

                    client.track(imagestreams, false);
                    $(imagestreams).off();
                    imagestreams = null;

                    window.clearTimeout(timeout);
                    timeout = null;
                }

                /* Bind the scope to the singleton */
                function ImageRegistry(scope) {
                    var self = this;
                    self.repositories = repositories;
                    var id = start(scope);
                    scope.$on("$destroy", function() {
                        stop(id);
                    });
                }

                /* Invoked for each caller of this provider */
                return {
                    $get: [
                        'kubernetesClient',
                        function(kubernetesClient) {
                            if (!client)
                                client = kubernetesClient;
                            return ImageRegistry;
                        }
                    ]
                };
            }
        )

        .controller('ImagesCtrl', [
            '$scope',
            'kubernetesClient',
            'ImageRegistry',
            function($scope, kubernetesClient, ImageRegistry) {
                $scope.registry = new ImageRegistry($scope);
                $scope.repositories = $scope.registry.repositories;
            }
        ])

        /*
         * Display a tags dict as returned by
         * ImageRepository.tags()
         */
        .filter('imagesTags', function() {
            return function(tags) {
                var extra = 0;
                var names = [];
                var image, tag;
                for (image in tags) {
                    tag = tags[image];
                    if (tag)
                        names.push(tag);
                    else
                        extra += 1;
                }

                names.sort();
                if (names.length > 5) {
                    extra += (names.length - 5);
                    names = names.slice(0, 5);
                }

                var format;
                var ret = names.join(", ");
                if (extra) {
                    if (ret)
                        format = cockpit.ngettext("more images", _(" + $0 more"), _(" + $0 more"), extra);
                    else
                        format = cockpit.ngettext(_("$0 image"), _("$0 images"), extra);
                    ret += cockpit.format(format, extra);
                }
                return ret;
            };
        });
});
<|endoftext|>


<commit_before>import { dispatch as d3_dispatch } from 'd3-dispatch';
import { select as d3_select } from 'd3-selection';
import _debounce from 'lodash-es/debounce';
import * as countryCoder from '@ideditor/country-coder';

import { presetManager } from '../../presets';
import { fileFetcher } from '../../core/file_fetcher';
import { t, localizer } from '../../core/localizer';
import { utilGetSetValue, utilNoAuto, utilRebind, utilTotalExtent } from '../../util';
import { svgIcon } from '../../svg/icon';
import { cardinal } from '../../osm/node';

export {
    uiFieldText as uiFieldColour,
    uiFieldText as uiFieldEmail,
    uiFieldText as uiFieldIdentifier,
    uiFieldText as uiFieldNumber,
    uiFieldText as uiFieldTel,
    uiFieldText as uiFieldUrl
};

export function uiFieldText(field, context) {
    var dispatch = d3_dispatch('change');
    var input = d3_select(null);
    var outlinkButton = d3_select(null);
    var wrap = d3_select(null);
    var _entityIDs = [];
    var _tags;
    var _phoneFormats = {};

    if (field.type === 'tel') {
        fileFetcher.get('phone_formats')
            .then(function(d) {
                _phoneFormats = d;
                updatePhonePlaceholder();
            })
            .catch(function() { /* ignore */ });
    }

    function calcLocked() {
        // Protect certain fields that have a companion `*:wikidata` value
        var isLocked = (field.id === 'brand' || field.id === 'network' || field.id === 'operator' || field.id === 'flag') &&
            _entityIDs.length &&
            _entityIDs.some(function(entityID) {
                var entity = context.graph().hasEntity(entityID);
                if (!entity) return false;

                // Features linked to Wikidata are likely important and should be protected
                if (entity.tags.wikidata) return true;

                var preset = presetManager.match(entity, context.graph());
                var isSuggestion = preset && preset.suggestion;

                // Lock the field if there is a value and a companion `*:wikidata` value
                var which = field.id;   // 'brand', 'network', 'operator', 'flag'
                return isSuggestion && !!entity.tags[which] && !!entity.tags[which + ':wikidata'];
            });

        field.locked(isLocked);
    }

    function i(selection) {
        calcLocked();
        var isLocked = field.locked();

        wrap = selection.selectAll('.form-field-input-wrap')
            .data([0]);

        wrap = wrap.enter()
            .append('div')
            .attr('class', 'form-field-input-wrap form-field-input-' + field.type)
            .merge(wrap);

        input = wrap.selectAll('input')
            .data([0]);

        input = input.enter()
            .append('input')
            .attr('type', field.type === 'identifier' ? 'text' : field.type)
            .attr('id', field.domId)
            .classed(field.type, true)
            .call(utilNoAuto)
            .merge(input);

        input
            .classed('disabled', !!isLocked)
            .attr('readonly', isLocked || null)
            .on('input', change(true))
            .on('blur', change())
            .on('change', change());

        if (field.type === 'tel') {
            updatePhonePlaceholder();

        } else if (field.type === 'number') {
            var rtl = (localizer.textDirection() === 'rtl');

            input.attr('type', 'text');

            var inc = field.increment;

            var buttons = wrap.selectAll('.increment, .decrement')
                .data(rtl ? [inc, -inc] : [-inc, inc]);

            buttons.enter()
                .append('button')
                .attr('class', function(d) {
                    var which = (d > 0 ? 'increment' : 'decrement');
                    return 'form-field-button ' + which;
                })
                .attr('title', function(d){
                    var which = (d > 0 ? 'increment' : 'decrement');
                    return t(`inspector.${which}`);
                })
                .merge(buttons)
                .on('click', function(d3_event, d) {
                    d3_event.preventDefault();
                    var raw_vals = input.node().value || '0';
                    var vals = raw_vals.split(';');
                    vals = vals.map(function(v) {
                        var num = parseFloat(v.trim(), 10);
                        if (isFinite(num)) return clamped(num + d);

                        const compassDir = cardinal[v.trim().toLowerCase()];
                        if (compassDir !== undefined) return clamped(compassDir + d);

                        return v.trim(); // do nothing if the value is neither a number, nor a cardinal direction
                    });
                    input.node().value = vals.join(';');
                    change()();
                });
        } else if (field.type === 'identifier' && field.urlFormat && field.pattern) {

            input.attr('type', 'text');

            outlinkButton = wrap.selectAll('.foreign-id-permalink')
                .data([0]);

            outlinkButton.enter()
                .append('button')
                .call(svgIcon('#iD-icon-out-link'))
                .attr('class', 'form-field-button foreign-id-permalink')
                .attr('title', function() {
                    var domainResults = /^https?:\/\/(.{1,}?)\//.exec(field.urlFormat);
                    if (domainResults.length >= 2 && domainResults[1]) {
                        var domain = domainResults[1];
                        return t('icons.view_on', { domain: domain });
                    }
                    return '';
                })
                .on('click', function(d3_event) {
                    d3_event.preventDefault();

                    var value = validIdentifierValueForLink();
                    if (value) {
                        var url = field.urlFormat.replace(/{value}/, encodeURIComponent(value));
                        window.open(url, '_blank');
                    }
                })
                .merge(outlinkButton);
        } else if (field.type === 'url') {
            input.attr('type', 'text');

            outlinkButton = wrap.selectAll('.foreign-id-permalink')
                .data([0]);

            outlinkButton.enter()
                .append('button')
                .call(svgIcon('#iD-icon-out-link'))
                .attr('class', 'form-field-button foreign-id-permalink')
                .attr('title', () => t('icons.visit_website'))
                .on('click', function(d3_event) {
                    d3_event.preventDefault();

                    const value = validIdentifierValueForLink();
                    if (value) window.open(value, '_blank');
                })
                .merge(outlinkButton);
        } else if (field.key.split(':').includes('colour')) {
            input.attr('type', 'text');

            updateColourPreview();
        }
    }

    function isColourValid(colour) {
        if (!colour.match(/^(#([0-9a-fA-F]{3}){1,2}|\w+)$/)) {
            // OSM only supports hex or named colors
            return false;
        } else if (!CSS.supports('color', colour) || ['unset', 'inherit', 'initial', 'revert'].includes(colour)) {
            // see https://stackoverflow.com/a/68217760/1627467
            return false;
        }
        return true;
    }
    function updateColourPreview() {
        wrap.selectAll('.colour-preview')
            .remove();

        const colour = utilGetSetValue(input);

        if (!isColourValid(colour) && colour !== '') return;

        var colourSelector = wrap.selectAll('.colour-selector')
            .data([0]);
        outlinkButton = wrap.selectAll('.colour-preview')
            .data([colour]);

        colourSelector
            .enter()
            .append('input')
            .attr('type', 'color')
            .attr('class', 'form-field-button colour-selector')
            .attr('value', colour)
            .on('input', _debounce(function(d3_event) {
                d3_event.preventDefault();
                var colour = this.value;
                if (!isColourValid(colour)) return;
                utilGetSetValue(input, this.value);
                change()();
                updateColourPreview();
            }, 100));

        outlinkButton = outlinkButton
            .enter()
            .append('div')
            .attr('class', 'form-field-button colour-preview')
            .append('div')
            .style('background-color', d => d)
            .attr('class', 'colour-box');
        if (colour === '') {
            outlinkButton = outlinkButton
                .call(svgIcon('#iD-icon-edit'));
        }
        outlinkButton
            .on('click', () => wrap.select('.colour-selector').node().click())
            .merge(outlinkButton);
    }

    function updatePhonePlaceholder() {
        if (input.empty() || !Object.keys(_phoneFormats).length) return;

        var extent = combinedEntityExtent();
        var countryCode = extent && countryCoder.iso1A2Code(extent.center());
        var format = countryCode && _phoneFormats[countryCode.toLowerCase()];
        if (format) input.attr('placeholder', format);
    }

    function validIdentifierValueForLink() {
        const value = utilGetSetValue(input).trim();

        if (field.type === 'url' && value) {
            try {
                return (new URL(value)).href;
            } catch (e) {
                return null;
            }
        }
        if (field.type === 'identifier' && field.pattern) {
            return value && value.match(new RegExp(field.pattern))[0];
        }
        return null;
    }

    // clamp number to min/max
    function clamped(num) {
        if (field.minValue !== undefined) {
            num = Math.max(num, field.minValue);
        }
        if (field.maxValue !== undefined) {
            num = Math.min(num, field.maxValue);
        }
        return num;
    }

    function change(onInput) {
        return function() {
            var t = {};
            var val = utilGetSetValue(input);
            if (!onInput) val = context.cleanTagValue(val);

            // don't override multiple values with blank string
            if (!val && Array.isArray(_tags[field.key])) return;

            if (!onInput) {
                if (field.type === 'number' && val) {
                    var vals = val.split(';');
                    vals = vals.map(function(v) {
                        var num = parseFloat(v.trim(), 10);
                        return isFinite(num) ? clamped(num) : v.trim();
                    });
                    val = vals.join(';');
                }
                utilGetSetValue(input, val);
            }
            t[field.key] = val || undefined;
            dispatch.call('change', this, t, onInput);
        };
    }

    i.entityIDs = function(val) {
        if (!arguments.length) return _entityIDs;
        _entityIDs = val;
        return i;
    };

    i.tags = function(tags) {
        _tags = tags;

        var isMixed = Array.isArray(tags[field.key]);

        utilGetSetValue(input, !isMixed && tags[field.key] ? tags[field.key] : '')
            .attr('title', isMixed ? tags[field.key].filter(Boolean).join('\n') : undefined)
            .attr('placeholder', isMixed ? t('inspector.multiple_values') : (field.placeholder() || t('inspector.unknown')))
            .classed('mixed', isMixed);

        if (field.type === 'tel') updatePhonePlaceholder();

        if (field.key.split(':').includes('colour')) updateColourPreview();

        if (outlinkButton && !outlinkButton.empty()) {
            var disabled = !validIdentifierValueForLink();
            outlinkButton.classed('disabled', disabled);
        }
    };

    i.focus = function() {
        var node = input.node();
        if (node) node.focus();
    };

    function combinedEntityExtent() {
        return _entityIDs && _entityIDs.length && utilTotalExtent(_entityIDs, context.graph());
    }

    return utilRebind(i, dispatch, 'on');
}
<commit_msg>use colour field type instead of relying on the tag key<commit_after>import { dispatch as d3_dispatch } from 'd3-dispatch';
import { select as d3_select } from 'd3-selection';
import _debounce from 'lodash-es/debounce';
import * as countryCoder from '@ideditor/country-coder';

import { presetManager } from '../../presets';
import { fileFetcher } from '../../core/file_fetcher';
import { t, localizer } from '../../core/localizer';
import { utilGetSetValue, utilNoAuto, utilRebind, utilTotalExtent } from '../../util';
import { svgIcon } from '../../svg/icon';
import { cardinal } from '../../osm/node';

export {
    uiFieldText as uiFieldColour,
    uiFieldText as uiFieldEmail,
    uiFieldText as uiFieldIdentifier,
    uiFieldText as uiFieldNumber,
    uiFieldText as uiFieldTel,
    uiFieldText as uiFieldUrl
};

export function uiFieldText(field, context) {
    var dispatch = d3_dispatch('change');
    var input = d3_select(null);
    var outlinkButton = d3_select(null);
    var wrap = d3_select(null);
    var _entityIDs = [];
    var _tags;
    var _phoneFormats = {};

    if (field.type === 'tel') {
        fileFetcher.get('phone_formats')
            .then(function(d) {
                _phoneFormats = d;
                updatePhonePlaceholder();
            })
            .catch(function() { /* ignore */ });
    }

    function calcLocked() {
        // Protect certain fields that have a companion `*:wikidata` value
        var isLocked = (field.id === 'brand' || field.id === 'network' || field.id === 'operator' || field.id === 'flag') &&
            _entityIDs.length &&
            _entityIDs.some(function(entityID) {
                var entity = context.graph().hasEntity(entityID);
                if (!entity) return false;

                // Features linked to Wikidata are likely important and should be protected
                if (entity.tags.wikidata) return true;

                var preset = presetManager.match(entity, context.graph());
                var isSuggestion = preset && preset.suggestion;

                // Lock the field if there is a value and a companion `*:wikidata` value
                var which = field.id;   // 'brand', 'network', 'operator', 'flag'
                return isSuggestion && !!entity.tags[which] && !!entity.tags[which + ':wikidata'];
            });

        field.locked(isLocked);
    }

    function i(selection) {
        calcLocked();
        var isLocked = field.locked();

        wrap = selection.selectAll('.form-field-input-wrap')
            .data([0]);

        wrap = wrap.enter()
            .append('div')
            .attr('class', 'form-field-input-wrap form-field-input-' + field.type)
            .merge(wrap);

        input = wrap.selectAll('input')
            .data([0]);

        input = input.enter()
            .append('input')
            .attr('type', field.type === 'identifier' ? 'text' : field.type)
            .attr('id', field.domId)
            .classed(field.type, true)
            .call(utilNoAuto)
            .merge(input);

        input
            .classed('disabled', !!isLocked)
            .attr('readonly', isLocked || null)
            .on('input', change(true))
            .on('blur', change())
            .on('change', change());

        if (field.type === 'tel') {
            updatePhonePlaceholder();

        } else if (field.type === 'number') {
            var rtl = (localizer.textDirection() === 'rtl');

            input.attr('type', 'text');

            var inc = field.increment;

            var buttons = wrap.selectAll('.increment, .decrement')
                .data(rtl ? [inc, -inc] : [-inc, inc]);

            buttons.enter()
                .append('button')
                .attr('class', function(d) {
                    var which = (d > 0 ? 'increment' : 'decrement');
                    return 'form-field-button ' + which;
                })
                .attr('title', function(d){
                    var which = (d > 0 ? 'increment' : 'decrement');
                    return t(`inspector.${which}`);
                })
                .merge(buttons)
                .on('click', function(d3_event, d) {
                    d3_event.preventDefault();
                    var raw_vals = input.node().value || '0';
                    var vals = raw_vals.split(';');
                    vals = vals.map(function(v) {
                        var num = parseFloat(v.trim(), 10);
                        if (isFinite(num)) return clamped(num + d);

                        const compassDir = cardinal[v.trim().toLowerCase()];
                        if (compassDir !== undefined) return clamped(compassDir + d);

                        return v.trim(); // do nothing if the value is neither a number, nor a cardinal direction
                    });
                    input.node().value = vals.join(';');
                    change()();
                });
        } else if (field.type === 'identifier' && field.urlFormat && field.pattern) {

            input.attr('type', 'text');

            outlinkButton = wrap.selectAll('.foreign-id-permalink')
                .data([0]);

            outlinkButton.enter()
                .append('button')
                .call(svgIcon('#iD-icon-out-link'))
                .attr('class', 'form-field-button foreign-id-permalink')
                .attr('title', function() {
                    var domainResults = /^https?:\/\/(.{1,}?)\//.exec(field.urlFormat);
                    if (domainResults.length >= 2 && domainResults[1]) {
                        var domain = domainResults[1];
                        return t('icons.view_on', { domain: domain });
                    }
                    return '';
                })
                .on('click', function(d3_event) {
                    d3_event.preventDefault();

                    var value = validIdentifierValueForLink();
                    if (value) {
                        var url = field.urlFormat.replace(/{value}/, encodeURIComponent(value));
                        window.open(url, '_blank');
                    }
                })
                .merge(outlinkButton);
        } else if (field.type === 'url') {
            input.attr('type', 'text');

            outlinkButton = wrap.selectAll('.foreign-id-permalink')
                .data([0]);

            outlinkButton.enter()
                .append('button')
                .call(svgIcon('#iD-icon-out-link'))
                .attr('class', 'form-field-button foreign-id-permalink')
                .attr('title', () => t('icons.visit_website'))
                .on('click', function(d3_event) {
                    d3_event.preventDefault();

                    const value = validIdentifierValueForLink();
                    if (value) window.open(value, '_blank');
                })
                .merge(outlinkButton);
        } else if (field.type === 'colour') {
            input.attr('type', 'text');

            updateColourPreview();
        }
    }

    function isColourValid(colour) {
        if (!colour.match(/^(#([0-9a-fA-F]{3}){1,2}|\w+)$/)) {
            // OSM only supports hex or named colors
            return false;
        } else if (!CSS.supports('color', colour) || ['unset', 'inherit', 'initial', 'revert'].includes(colour)) {
            // see https://stackoverflow.com/a/68217760/1627467
            return false;
        }
        return true;
    }
    function updateColourPreview() {
        wrap.selectAll('.colour-preview')
            .remove();

        const colour = utilGetSetValue(input);

        if (!isColourValid(colour) && colour !== '') return;

        var colourSelector = wrap.selectAll('.colour-selector')
            .data([0]);
        outlinkButton = wrap.selectAll('.colour-preview')
            .data([colour]);

        colourSelector
            .enter()
            .append('input')
            .attr('type', 'color')
            .attr('class', 'form-field-button colour-selector')
            .attr('value', colour)
            .on('input', _debounce(function(d3_event) {
                d3_event.preventDefault();
                var colour = this.value;
                if (!isColourValid(colour)) return;
                utilGetSetValue(input, this.value);
                change()();
                updateColourPreview();
            }, 100));

        outlinkButton = outlinkButton
            .enter()
            .append('div')
            .attr('class', 'form-field-button colour-preview')
            .append('div')
            .style('background-color', d => d)
            .attr('class', 'colour-box');
        if (colour === '') {
            outlinkButton = outlinkButton
                .call(svgIcon('#iD-icon-edit'));
        }
        outlinkButton
            .on('click', () => wrap.select('.colour-selector').node().click())
            .merge(outlinkButton);
    }

    function updatePhonePlaceholder() {
        if (input.empty() || !Object.keys(_phoneFormats).length) return;

        var extent = combinedEntityExtent();
        var countryCode = extent && countryCoder.iso1A2Code(extent.center());
        var format = countryCode && _phoneFormats[countryCode.toLowerCase()];
        if (format) input.attr('placeholder', format);
    }

    function validIdentifierValueForLink() {
        const value = utilGetSetValue(input).trim();

        if (field.type === 'url' && value) {
            try {
                return (new URL(value)).href;
            } catch (e) {
                return null;
            }
        }
        if (field.type === 'identifier' && field.pattern) {
            return value && value.match(new RegExp(field.pattern))[0];
        }
        return null;
    }

    // clamp number to min/max
    function clamped(num) {
        if (field.minValue !== undefined) {
            num = Math.max(num, field.minValue);
        }
        if (field.maxValue !== undefined) {
            num = Math.min(num, field.maxValue);
        }
        return num;
    }

    function change(onInput) {
        return function() {
            var t = {};
            var val = utilGetSetValue(input);
            if (!onInput) val = context.cleanTagValue(val);

            // don't override multiple values with blank string
            if (!val && Array.isArray(_tags[field.key])) return;

            if (!onInput) {
                if (field.type === 'number' && val) {
                    var vals = val.split(';');
                    vals = vals.map(function(v) {
                        var num = parseFloat(v.trim(), 10);
                        return isFinite(num) ? clamped(num) : v.trim();
                    });
                    val = vals.join(';');
                }
                utilGetSetValue(input, val);
            }
            t[field.key] = val || undefined;
            dispatch.call('change', this, t, onInput);
        };
    }

    i.entityIDs = function(val) {
        if (!arguments.length) return _entityIDs;
        _entityIDs = val;
        return i;
    };

    i.tags = function(tags) {
        _tags = tags;

        var isMixed = Array.isArray(tags[field.key]);

        utilGetSetValue(input, !isMixed && tags[field.key] ? tags[field.key] : '')
            .attr('title', isMixed ? tags[field.key].filter(Boolean).join('\n') : undefined)
            .attr('placeholder', isMixed ? t('inspector.multiple_values') : (field.placeholder() || t('inspector.unknown')))
            .classed('mixed', isMixed);

        if (field.type === 'tel') updatePhonePlaceholder();

        if (field.key.split(':').includes('colour')) updateColourPreview();

        if (outlinkButton && !outlinkButton.empty()) {
            var disabled = !validIdentifierValueForLink();
            outlinkButton.classed('disabled', disabled);
        }
    };

    i.focus = function() {
        var node = input.node();
        if (node) node.focus();
    };

    function combinedEntityExtent() {
        return _entityIDs && _entityIDs.length && utilTotalExtent(_entityIDs, context.graph());
    }

    return utilRebind(i, dispatch, 'on');
}
<|endoftext|>


<commit_before>#!/usr/bin/env node

// Powerline-style prompt in js instead of python.
// 100% inspired by https://github.com/milkbikis/powerline-bash

var
	child = require('child_process');

var COLOR =
{
	PATH_BG: 237,  // dark grey
	PATH_FG: 250,  // light grey
	CWD_FG: 254, // nearly-white grey
	SEPARATOR_FG: 244,

	REPO_CLEAN_BG: 148, // a light green color
	REPO_CLEAN_FG: 0,  // black
	REPO_DIRTY_BG: 161, // pink/red
	REPO_DIRTY_FG: 15, // white

	CMD_PASSED_BG: 236,
	CMD_PASSED_FG: 15,
	CMD_FAILED_BG: 161,
	CMD_FAILED_FG: 15,

	SVN_CHANGES_BG: 148,
	SVN_CHANGES_FG: 22, // dark green

	VIRTUAL_ENV_BG: 35, // a mid-tone green
	VIRTUAL_ENV_FG: 22
};

var SYMBOLS =
{
	'compatible':
	{
		separator: '\u25b6',
		separator_thin: '\u276f'
	},
	'patched':
	{
		separator: '\u2B80',
		separator_thin: '\u2B81'
	}
};

//---------------------------------------------------

var COLOR_TEMPLATES =
{
	'bash': function(s) { return '\\[\\e' + s + '\\]'; },
	'zsh': function(s) { return '%{' + s + '%}'; }
};

function Shell(which)
{
	if (Object.keys(COLOR_TEMPLATES).indexOf(which) === -1)
		throw new Error('shell ' + which + ' not supported');

	this.name = which;
	this.colorTemplate = COLOR_TEMPLATES[which];
	this.reset = this.colorTemplate('[0m');
}

Shell.prototype.color = function(prefix, code)
{
	return this.colorTemplate('[' + prefix + ';5;' + code + 'm');
};

Shell.prototype.fgcolor = function(code)
{
	return this.color('38', code);
};

Shell.prototype.bgcolor = function(code)
{
	return this.color('48', code);
};

//---------------------------------------------------

function Powerline(options)
{
	options = options || {};
	this.options = {};
	this.options.shell = options.shell || 'zsh';
	this.options.mode = options.mode || 'patched';
	this.options.error = options.hasOwnProperty('error') ? options.error : false;
	this.options.depth = options.depth || 5;
	this.options.showRepo = options.hasOwnProperty('showRepo') ? options.showRepo : true;
	this.options.showPath = options.hasOwnProperty('showPath') ? options.showPath : true;
	this.options.cwdOnly = options.cwdOnly || false;

	this.shell = new Shell(this.options.shell);

	this.separator = SYMBOLS[this.options.mode].separator;
	this.separator_thin = SYMBOLS[this.options.mode].separator_thin;

	this.segments = [];
	this.cwd = process.env.PWD || process.cwd();
}

Powerline.prototype.buildPrompt = function(callback)
{
	var self = this;

	this.addVirtualEnvSegment();
	this.addCWDSegment();
	this.addRepoSegment(function()
	{
		self.addRootIndicator();
		callback();
	});
};

Powerline.prototype.draw = function(code)
{
	var result = [];
	var shifted = this.segments.slice(1);
	shifted.push(null);

	for (var i = 0; i < this.segments.length; i++)
	{
		var item = this.segments[i];
		var next = shifted[i];

		result.push(item.draw(next));
	}

	result.push(this.shell.reset);
	return result.join('');
};

Powerline.prototype.addCWDSegment = function()
{
	if (!this.options.showPath)
		return;

	var home = process.env['HOME'];
	var cwd = this.cwd;

	if (cwd.indexOf(home) === 0)
		cwd = cwd.replace(home, '~');

	if (cwd[0] === '/')
		cwd = cwd.substring(1, cwd.length);

	var names = cwd.split('/');
	if (!this.options.cwdOnly && (this.options.depth > 1))
	{
		if (names.length > this.options.depth)
		{
			var diff = names.length - this.options.depth;
			var start = this.options.depth > 4 ? 2 : 1;
			names.splice(start, diff, '\u2026');
		}

		for (var i = 0; i < names.length - 1; i++)
		{
			this.segments.push(new Segment(
				this,
				' ' + names[i] + ' ',
				COLOR.PATH_FG,
				COLOR.PATH_BG,
				this.separator_thin,
				COLOR.SEPARATOR_FG
			));
		}
	}

	this.segments.push(new Segment(
		this,
		' ' + names[names.length - 1] + ' ',
		COLOR.CWD_FG,
		COLOR.PATH_BG
	));
};

Powerline.prototype.addRootIndicator = function()
{
	var bg = this.options.error ? COLOR.CMD_FAILED_BG : COLOR.CMD_PASSED_BG;
	var fg = this.options.error ? COLOR.CMD_FAILED_FG : COLOR.CMD_PASSED_FG;
	this.segments.push(new Segment(this, ' \\$ ', fg, bg));
};

Powerline.prototype.addVirtualEnvSegment = function()
{
	var env = process.env['VIRTUAL_ENV'];
	if (!env)
		return;

	var path = require('path');

	this.segments.push(new Segment(this,
		' ' + path.basename(env) + ' ',
		COLOR.VIRTUAL_ENV_FG,
		COLOR.VIRTUAL_ENV_BG
	));
};

Powerline.prototype.addRepoSegment = function(callback)
{
	if (!this.options.showRepo)
		return callback();

	var self = this;

	self.addGitSegment(function(found)
	{
		if (found) return callback();
		self.addSVNSegment(function(found)
		{
			if (found) return callback();
			self.addHGSegment(callback);
		});
	});
};

Powerline.prototype.addGitSegment = function(callback)
{
	var self = this;

	var hasPending = false;
	var hasUntracked = false;
	var branch;

	child.exec('git status -sb --ignore-submodules', function(err, stdout, stderr)
	{
		if (err || !stdout)
			return callback(false);

		var lines = stdout.trim().split('\n');

		var status = lines.shift().trim();
		var matches = status.match(/^## ([^\.\s]*)/);
		if (matches)
			branch = matches[1];

		matches = status.match(/ahead\s+(\d+)/);
		if (matches)
			branch += ' \u21E1' + matches[1];

		matches = status.match(/behind\s+(\d+)/);
		if (matches)
			branch += ' \u21E3' + matches[1];

		for (var i = 0; i < lines.length; i++)
		{
			if ((lines[i][1] === 'M') || (lines[i][0] === 'M'))
				hasPending = true;
			else if (lines[i][0] === '?')
				hasUntracked = true;

			if (hasUntracked && hasPending)
				break;
		}

		if (hasUntracked)
			branch += ' +';

		var fg = hasPending ? COLOR.REPO_DIRTY_FG : COLOR.REPO_CLEAN_FG;
		var bg = hasPending ? COLOR.REPO_DIRTY_BG : COLOR.REPO_CLEAN_BG;

		self.segments.push(new Segment(self, ' ' + branch, fg, bg));
		callback(true);
	});
};

Powerline.prototype.addSVNSegment = function(callback)
{
	var self = this;
	var fs = require('fs');

	if (!fs.existsSync('.svn'))
		return callback(false);

	child.exec('svn status | grep -c "^[ACDIMRX\\!\\~]"', function(err, stdout, stderr)
	{
		// TODO that grep command always exits with an error; fix
		if (!stdout || !stdout.length)
			return callback(true);

		var changes = parseInt(stdout.trim(), 10);
		if (changes > 0)
		{
			self.segments.push(new Segment(self,
				' ' + changes + ' ',
				COLOR.SVN_CHANGES_FG,
				COLOR.SVN_CHANGES_BG
			));
		}
		callback(true);
	});
};

Powerline.prototype.addHGSegment = function(callback)
{
	// TODO
	callback(false);
};

//---------------------------------------------------

function Segment(powerline, content, fg, bg, separator, separatorFG)
{
	this.shell = powerline.shell;
	this.content = content;
	this.fg = fg;
	this.bg = bg;
	this.separator = separator || powerline.separator;
	this.separatorFG = separatorFG || bg;
}

Segment.prototype.draw = function(nextSegment)
{
	var sep, pieces;

	if (nextSegment)
		sep = this.shell.bgcolor(nextSegment.bg);
	else
		sep = this.shell.reset;

	pieces = [
		this.shell.fgcolor(this.fg),
		this.shell.bgcolor(this.bg),
		this.content,
		sep,
		this.shell.fgcolor(this.separatorFG),
		this.separator
	];

	return pieces.join('');
};

//---------------------------------------------------

function parseOptions(args)
{
	args = args || [];
	var options = {};

	while (args.length)
	{
		var opt = args.shift();
		switch (opt)
		{
			case '--cwd-only':
				options.showRepo = false;
				options.showPath = true;
				options.cwdOnly = true;
				break;

			case '--shell':
				options.shell = args.shift();
				break;

			case '--mode':
				options.mode = args.shift();
				break;

			case '--depth':
				options.depth = parseInt(args.shift(), 10);
				break;

			case '--repo-only':
				options.showRepo = true;
				options.showPath = false;
				break;

			case '--no-repo':
				options.showRepo = false;
				break;

			default:
				options.error = (opt !== '0');
		}
	}

	return options;
}

if (require.main === module)
{
	var options = parseOptions(process.argv.slice(2));
	var p = new Powerline(options);
	p.buildPrompt(function()
	{
		process.stdout.write(p.draw());
	});
}

exports.Powerline = Powerline;
exports.Segment = Segment;
exports.parseOptions = parseOptions;

<commit_msg>Some experimentation with symbols: a branch symbol if you're not on master, an X if the last command resulted in an error.<commit_after>#!/usr/bin/env node

// Powerline-style prompt in js instead of python.
// 100% inspired by https://github.com/milkbikis/powerline-bash

var
	child = require('child_process');

// echo "⮀ ± ⭠ ➦ ✔ ✘ ⚡"

var COLOR =
{
	PATH_BG: 237,  // dark grey
	PATH_FG: 250,  // light grey
	CWD_FG: 254, // nearly-white grey
	SEPARATOR_FG: 244,

	REPO_CLEAN_BG: 148, // a light green color
	REPO_CLEAN_FG: 0,  // black
	REPO_DIRTY_BG: 161, // pink/red
	REPO_DIRTY_FG: 15, // white

	CMD_PASSED_BG: 236,
	CMD_PASSED_FG: 15,
	CMD_FAILED_BG: 161,
	CMD_FAILED_FG: 15,

	SVN_CHANGES_BG: 148,
	SVN_CHANGES_FG: 22, // dark green

	VIRTUAL_ENV_BG: 35, // a mid-tone green
	VIRTUAL_ENV_FG: 22
};

var SYMBOLS =
{
	'compatible':
	{
		separator: '\u25b6',
		separator_thin: '\u276f'
	},
	'patched':
	{
		separator: '\u2B80',
		separator_thin: '\u2B81'
	}
};

//---------------------------------------------------

var COLOR_TEMPLATES =
{
	'bash': function(s) { return '\\[\\e' + s + '\\]'; },
	'zsh': function(s) { return '%{' + s + '%}'; }
};

function Shell(which)
{
	if (Object.keys(COLOR_TEMPLATES).indexOf(which) === -1)
		throw new Error('shell ' + which + ' not supported');

	this.name = which;
	this.colorTemplate = COLOR_TEMPLATES[which];
	this.reset = this.colorTemplate('[0m');
}

Shell.prototype.color = function(prefix, code)
{
	return this.colorTemplate('[' + prefix + ';5;' + code + 'm');
};

Shell.prototype.fgcolor = function(code)
{
	return this.color('38', code);
};

Shell.prototype.bgcolor = function(code)
{
	return this.color('48', code);
};

//---------------------------------------------------

function Powerline(options)
{
	options = options || {};
	this.options = {};
	this.options.shell = options.shell || 'zsh';
	this.options.mode = options.mode || 'patched';
	this.options.depth = options.depth || 5;
	this.options.showRepo = options.hasOwnProperty('showRepo') ? options.showRepo : true;
	this.options.showPath = options.hasOwnProperty('showPath') ? options.showPath : true;
	this.options.cwdOnly = options.cwdOnly || false;

	this.shell = new Shell(this.options.shell);

	this.separator = SYMBOLS[this.options.mode].separator;
	this.separator_thin = SYMBOLS[this.options.mode].separator_thin;

	this.segments = [];
	this.cwd = process.env.PWD || process.cwd();
	this.isRoot = process.getuid() === 0;
	this.error = options.hasOwnProperty('error') ? options.error : false;
}

Powerline.prototype.buildPrompt = function(callback)
{
	var self = this;

	this.addVirtualEnvSegment();
	this.addCWDSegment();
	this.addRepoSegment(function()
	{
		self.addRootIndicator();
		callback();
	});
};

Powerline.prototype.draw = function(code)
{
	var result = [];
	var shifted = this.segments.slice(1);
	shifted.push(null);

	for (var i = 0; i < this.segments.length; i++)
	{
		var item = this.segments[i];
		var next = shifted[i];

		result.push(item.draw(next));
	}

	result.push(this.shell.reset);
	return result.join('');
};

Powerline.prototype.addContextSegment = function()
{
	// if root or on another system, say so
	var ctx = '';
	if (this.isRoot)
		ctx += 'root';

	this.segments.push(new Segment(this,
		' ' + ctx + ' ',
		COLOR.VIRTUAL_ENV_FG,
		COLOR.VIRTUAL_ENV_BG
	));
};

Powerline.prototype.addCWDSegment = function()
{
	if (!this.options.showPath)
		return;

	var home = process.env.HOME;
	var cwd = this.cwd;

	if (cwd.indexOf(home) === 0)
		cwd = cwd.replace(home, '~');

	if (cwd[0] === '/')
		cwd = cwd.substring(1, cwd.length);

	var names = cwd.split('/');
	if (!this.options.cwdOnly && (this.options.depth > 1))
	{
		if (names.length > this.options.depth)
		{
			var diff = names.length - this.options.depth;
			var start = this.options.depth > 4 ? 2 : 1;
			names.splice(start, diff, '\u2026');
		}

		for (var i = 0; i < names.length - 1; i++)
		{
			this.segments.push(new Segment(
				this,
				' ' + names[i] + ' ',
				COLOR.PATH_FG,
				COLOR.PATH_BG,
				this.separator_thin,
				COLOR.SEPARATOR_FG
			));
		}
	}

	this.segments.push(new Segment(
		this,
		' ' + names[names.length - 1] + ' ',
		COLOR.CWD_FG,
		COLOR.PATH_BG
	));
};

Powerline.prototype.addRootIndicator = function()
{
	var bg = this.error ? COLOR.CMD_FAILED_BG : COLOR.CMD_PASSED_BG;
	var fg = this.error ? COLOR.CMD_FAILED_FG : COLOR.CMD_PASSED_FG;

	var symbol = ' ';
	if (this.isRoot)
		symbol += '\u26a1';
	if (this.error)
		symbol += '✘';
	if (symbol.length === 1)
		symbol += '\\$';
	symbol += ' ';

	this.segments.push(new Segment(this, symbol, fg, bg));
};

Powerline.prototype.addVirtualEnvSegment = function()
{
	var env = process.env.VIRTUAL_ENV;
	if (!env)
		return;

	var path = require('path');

	this.segments.push(new Segment(this,
		' ' + path.basename(env) + ' ',
		COLOR.VIRTUAL_ENV_FG,
		COLOR.VIRTUAL_ENV_BG
	));
};

Powerline.prototype.addRepoSegment = function(callback)
{
	if (!this.options.showRepo)
		return callback();

	var self = this;

	self.addGitSegment(function(found)
	{
		if (found) return callback();
		self.addSVNSegment(function(found)
		{
			if (found) return callback();
			self.addHGSegment(callback);
		});
	});
};

Powerline.prototype.addGitSegment = function(callback)
{
	var self = this;

	var hasPending = false;
	var hasUntracked = false;
	var branch;

	child.exec('git status -sb --ignore-submodules', function(err, stdout, stderr)
	{
		if (err || !stdout)
			return callback(false);

		var lines = stdout.trim().split('\n');

		var status = lines.shift().trim();
		var matches = status.match(/^## ([^\.\s]*)/);
		if (matches)
			branch = matches[1];
		if (branch !== 'master')
			branch = '⭠ ' + branch;

		matches = status.match(/ahead\s+(\d+)/);
		if (matches)
			branch += ' \u21E1' + matches[1];

		matches = status.match(/behind\s+(\d+)/);
		if (matches)
			branch += ' \u21E3' + matches[1];

		for (var i = 0; i < lines.length; i++)
		{
			if ((lines[i][1] === 'M') || (lines[i][0] === 'M'))
				hasPending = true;
			else if (lines[i][0] === '?')
				hasUntracked = true;

			if (hasUntracked && hasPending)
				break;
		}

		if (hasUntracked)
			branch += ' +';

		var fg = hasPending ? COLOR.REPO_DIRTY_FG : COLOR.REPO_CLEAN_FG;
		var bg = hasPending ? COLOR.REPO_DIRTY_BG : COLOR.REPO_CLEAN_BG;

		self.segments.push(new Segment(self, ' ' + branch, fg, bg));
		callback(true);
	});
};

Powerline.prototype.addSVNSegment = function(callback)
{
	var self = this;
	var fs = require('fs');

	if (!fs.existsSync('.svn'))
		return callback(false);

	child.exec('svn status | grep -c "^[ACDIMRX\\!\\~]"', function(err, stdout, stderr)
	{
		// TODO that grep command always exits with an error; fix
		if (!stdout || !stdout.length)
			return callback(true);

		var changes = parseInt(stdout.trim(), 10);
		if (changes > 0)
		{
			self.segments.push(new Segment(self,
				' ' + changes + ' ',
				COLOR.SVN_CHANGES_FG,
				COLOR.SVN_CHANGES_BG
			));
		}
		callback(true);
	});
};

Powerline.prototype.addHGSegment = function(callback)
{
	// TODO
	callback(false);
};

//---------------------------------------------------

function Segment(powerline, content, fg, bg, separator, separatorFG)
{
	this.shell = powerline.shell;
	this.content = content;
	this.fg = fg;
	this.bg = bg;
	this.separator = separator || powerline.separator;
	this.separatorFG = separatorFG || bg;
}

Segment.prototype.draw = function(nextSegment)
{
	var sep, pieces;

	if (nextSegment)
		sep = this.shell.bgcolor(nextSegment.bg);
	else
		sep = this.shell.reset;

	pieces = [
		this.shell.fgcolor(this.fg),
		this.shell.bgcolor(this.bg),
		this.content,
		sep,
		this.shell.fgcolor(this.separatorFG),
		this.separator
	];

	return pieces.join('');
};

//---------------------------------------------------

function parseOptions(args)
{
	args = args || [];
	var options = {};

	while (args.length)
	{
		var opt = args.shift();
		switch (opt)
		{
			case '--cwd-only':
				options.showRepo = false;
				options.showPath = true;
				options.cwdOnly = true;
				break;

			case '--shell':
				options.shell = args.shift();
				break;

			case '--mode':
				options.mode = args.shift();
				break;

			case '--depth':
				options.depth = parseInt(args.shift(), 10);
				break;

			case '--repo-only':
				options.showRepo = true;
				options.showPath = false;
				break;

			case '--no-repo':
				options.showRepo = false;
				break;

			default:
				options.error = (opt !== '0');
		}
	}

	return options;
}

if (require.main === module)
{
	var options = parseOptions(process.argv.slice(2));
	var p = new Powerline(options);
	p.buildPrompt(function()
	{
		process.stdout.write(p.draw());
	});
}

exports.Powerline = Powerline;
exports.Segment = Segment;
exports.parseOptions = parseOptions;

<|endoftext|>


<commit_before>//Imports
import React, {useEffect, useState} from 'react';
import { HashRouter, Route, hashHistory } from 'react-router-dom';

import Home from './components/Home';
import Hero from './components/Hero';
import Preview_block from './components/Preview_block';
import CTA_block from './components/CTA_block';
import CTA_block_analysis from './components/CTA_block_analysis';
import CTA_block_insights from './components/CTA_block_insights';
import Footer from './components/Footer';
import Sub_Footer from './components/Sub_Footer';
import Hero_collapsable from './components/Hero_collapsable';
import Navigation_scoda from './components/Navigation_scoda';
import About from './components/About';

import Hero_Collapsable from './components/Hero.Collapsible';
import DataStoriesDetails from './components/DataStoriesDetails';
import DataStories from './components/DataStories';

import DataStoriesSpacial from './components/DataStories.Spacial';
import DataStoriesUrbanSafety from './components/DataStories.UrbanSafety';
import DataStoriesEPW from './components/DataStories.EPW';
import IndicatorExplorer from './components/IndicatorExplorer';
import CBIndicatorExplorer from './components/codebook-explorer/CBIndicatorExplorer'
import TabsAndFilters from './components/TabsAndFilters'
import DemographicModeller from './components/DemographicModeller';
import DataStoriesAffordability from './components/DataStories.Affordability';
import Codebook from './components/Codebook';

const _heroTitle = 'Data Management System';
const _heroContent = ' Central to activities on this platform is the Data Management System, where our evidence base is securely housed. We use the CKAN data portal framework, the same system used by various governments around the world, to store, manage, and share data across the SCODA ecosystem. From this dynamic, version-controlled and API-responsive data-store, we can efficiently gather and process the myriad sources required for insight generation.Central to activities on this platform is the Data Management System, where our evidence base is securely housed. We use the CKAN data portal framework, the same system used by various governments around the world, to store, manage, and share data across the SCODA ecosystem. From this dynamic, version-controlled and API-responsive data-store, we can efficiently gather and process the myriad sources required for insight generation.';
const _heroInfoTitle = 'Evidence Tools';
const _heroPrimaryColor = '#F05252';
const _heroSecondaryColor = '#F64343';

const _heroTitle_insight = 'Data Stories';
const _heroContent_insight = 'Data stories are a way to understand complex information and data using a narrative that appeals to interested readers.

They can be thought of as a series of dashboards connected with explainer text.  The topic is clearly defined and explored using information broken into understandable chunks which result in a finding or conclusion with calls to action to encourage the reader to learn and explore more.';
const _heroInfoTitle_insight = 'INSIGHT TOOLS';
const _heroPrimaryColor_insight = '#4F9DA6';
const _heroSecondaryColor_insight = '#4F9DA6';

const _heroTitle_explore = 'Data Explorer';
const _heroContent_explore = `Our Indicators: We use a diverse range of carefully selected and tested indicators to compile our State of Cities Report.
You can browse, analyse and download these individual indicators using our Data Explorer tool.`;
const _heroInfoTitle_explore = 'ANALYSIS TOOLS';
const _heroPrimaryColor_explore = '#FFAD5A';
const _heroSecondaryColor_explore = '#FFAD5A';

const _heroTitle_demo = 'Demographics Modeller';
const _heroContent_demo = 'With the demographic projection tool we provide both a powerful predictor of internal migration within South African metros, as well as a framework for the development of open, collaborative model building that is based on diverse and novel data sources.';
const _heroInfoTitle_demo = 'ANALYSIS TOOLS';
const _heroPrimaryColor_demo = '#FFAD5A';
const _heroSecondaryColor_demo = '#FFAD5A';

const _heroTitle_codebook = 'Codebook';
const _heroContent_codebook = 'The codebook is a live list of common city indicators providing definitions, methodologies for collection, and data availability of comparable city data. The codebook can be used to locate city data, understand how indicators have been constructed, where data is sourced from, and linked to the data explorer where information can be visualised and analysed.';
const _heroInfoTitle_codebook = 'ANALYSIS TOOLS';
const _heroPrimaryColor_codebook = '#FFAD5A';
const _heroSecondaryColor_codebook = '#FFAD5A';

// Linear gradients
const _heroLinearGradient_demo = `linear-gradient(112deg, rgb(255, 173, 90) calc(57% - 1px), rgb(255, 173, 90), #fff calc(38% + 1px))`
const _heroLinearGradient_codebook = `linear-gradient(112deg, rgb(255, 173, 90) calc(40% - 1px), rgb(255, 173, 90), #fff calc(38% + 1px))`
const _heroLinearGradient_datastories = `linear-gradient(112deg, rgba(79,157,166,1) calc(54% - 1px), rgba(79,157,166,1), #fff calc(38% + 1px))`

const _tabs = [
  {
      name: 'SACN', mode:'active',url: 'http://www.sacn.co.za'
  },
  {
      name: 'eThekwini', mode: 'none', url: 'http://www.google.com'
  }
];

// import more components
export default (
    <HashRouter history={hashHistory}>

      {/* DATA STORY DETAILS */}
    <Route path='/data-stories-details'  component={()=> <Navigation_scoda box_shadow={true} />}/>
    <Route exact path="/data-stories-details" component={()=> <Hero_Collapsable title={_heroTitle_insight}
            content={_heroContent_insight}
            infoTitle={_heroInfoTitle_insight}
            primaryColor={_heroPrimaryColor_insight}
            secondaryColor={_heroSecondaryColor_insight}
            linearGradient={_heroLinearGradient_datastories}/> } />

      <Route exact path="/data-stories-details" component={()=>
            <DataStoriesDetails />} />
      <Route path='/data-stories-details' component={Footer} />

      {/* CODE BOOK */}
      <Route path="/codebook" component={() => <Navigation_scoda box_shadow={true} />} />
      <Route exact path="/codebook" component={() => <Hero_Collapsable title={_heroTitle_codebook}
            content={_heroContent_codebook}
            infoTitle={_heroInfoTitle_codebook}
            primaryColor={_heroPrimaryColor_codebook}
            secondaryColor={_heroSecondaryColor_codebook}
            linearGradient={_heroLinearGradient_codebook}/> } />
      <Route exact path="/codebook" component={() => <Codebook />} />
      <Route path="/codebook" component={Footer} />

      <Route path="/ds-state-of-finance-2018" component={()=> <Navigation_scoda box_shadow={true} />} />
      <Route path="/ds-state-of-finance-2018" component={()=> <DataStories />} />
      <Route path="/ds-state-of-finance-2018" component={Footer} />

      <Route path="/ds-spatial-transformation-2018" component={()=> <Navigation_scoda box_shadow={true} />} />
      <Route path="/ds-spatial-transformation-2018" component={()=> <DataStoriesSpacial />} />
      <Route path="/ds-spatial-transformation-2018" component={Footer} />

      <Route path="/ds-urban-safety-2018" component={()=> <Navigation_scoda box_shadow={true} />} />
      <Route path="/ds-urban-safety-2018" component={()=> <DataStoriesUrbanSafety />} />
      <Route path="/ds-urban-safety-2018" component={Footer} />

      <Route path="/ds-epw-metro-2018" component={()=> <Navigation_scoda box_shadow={true} />} />
      <Route path="/ds-epw-metro-2018" component={()=> <DataStoriesEPW />} />
      <Route path="/ds-epw-metro-2018" component={Footer} />

      <Route path="/ds-affordability-2018" component={()=> <Navigation_scoda box_shadow={true} />} />
      <Route path="/ds-affordability-2018" component={()=> <DataStoriesAffordability />} />
      <Route path="/ds-affordability-2018" component={Footer} />

      <Route exact path="/tabs-and-filters" component={()=><TabsAndFilters tabs={_tabs}/>} />

      <Route path='/preview-block' component={Preview_block} />
      <Route path='/cta-block' component={CTA_block} />
      <Route path='/sub-footer' component={Sub_Footer} />
      <Route path='/footer' component={Footer} />

      <Route path='/datastory' component={DataStories} />

      <Route path='/home' component={()=> <Navigation_scoda logoHide={true} box_shadow={false} />} />
      <Route path='/home' component={Hero} />
      <Route path='/home' component={Preview_block} />
      <Route path='/home' component={CTA_block} />
      <Route path='/home' component={CTA_block_analysis} />
      <Route path='/home' component={CTA_block_insights} />
      <Route path='/home' component={Sub_Footer} />
      <Route path='/home' component={Footer} />

      <Route path='/about-us' component={()=> <Navigation_scoda logoHide={true} box_shadow={false} />} />
      <Route path='/about-us' component={About} />
      <Route path='/about-us' component={Sub_Footer} />
      <Route path='/about-us' component={Footer} />

      <Route path='/demographic-modeller' component={()=> <Navigation_scoda box_shadow={true} />} />
      <Route exact path="/demographic-modeller" component={()=> <Hero_Collapsable title={_heroTitle_demo}
            content={_heroContent_demo}
            infoTitle={_heroInfoTitle_demo}
            primaryColor={_heroPrimaryColor_demo}
            secondaryColor={_heroSecondaryColor_demo}
            linearGradient={_heroLinearGradient_demo}/>}/>
      <Route exact path="/demographic-modeller" component={()=><DemographicModeller />} />
      <Route path="/demographic-modeller" component={Footer} />

      <Route path="/explorer" component={()=> <Navigation_scoda box_shadow={true} />} />
      <Route exact path="/explorer" component={()=> <Hero_Collapsable title={_heroTitle_explore}
            content={_heroContent_explore}
            infoTitle={_heroInfoTitle_explore}
            primaryColor={_heroPrimaryColor_explore}
            secondaryColor={_heroSecondaryColor_explore}
            linearGradient={_heroLinearGradient_codebook}/>}/>
      <Route exact path="/explorer" component={()=> <IndicatorExplorer />}/>
      <Route path="/explorer" component={Footer} />

      <Route exact path='/codebook-explorer/:id' render={(props)=> <Navigation_scoda box_shadow={true} {...props}/>} />
      <Route exact path='/codebook-explorer/:id' render={(props)=> <Hero_Collapsable {...props} title={_heroTitle_explore}
            content={_heroContent_explore}
            infoTitle={_heroInfoTitle_explore}
            primaryColor={_heroPrimaryColor_explore}
            secondaryColor={_heroSecondaryColor_explore}/>}/>
        <Route exact path='/codebook-explorer/:id' render={(props)=> <CBIndicatorExplorer className=""  {...props}/>} />
      <Route path="/codebook-explorer/:id" component={Footer} />
    </HashRouter>
);<commit_msg>Scoda Routes<commit_after>//Imports
import React, {useEffect, useState} from 'react';
import { HashRouter, Route, hashHistory } from 'react-router-dom';

import Home from './components/Home';
import Hero from './components/Hero';
import Preview_block from './components/Preview_block';
import CTA_block from './components/CTA_block';
import CTA_block_analysis from './components/CTA_block_analysis';
import CTA_block_insights from './components/CTA_block_insights';
import Footer from './components/Footer';
import Sub_Footer from './components/Sub_Footer';
import Hero_collapsable from './components/Hero_collapsable';
import Navigation_scoda from './components/Navigation_scoda';
import About from './components/About';

import Hero_Collapsable from './components/Hero.Collapsible';
import DataStoriesDetails from './components/DataStoriesDetails';
import DataStories from './components/DataStories';

import DataStoriesSpacial from './components/DataStories.Spacial';
import DataStoriesUrbanSafety from './components/DataStories.UrbanSafety';
import DataStoriesEPW from './components/DataStories.EPW';
import IndicatorExplorer from './components/IndicatorExplorer';
import CBIndicatorExplorer from './components/codebook-explorer/CBIndicatorExplorer'
import TabsAndFilters from './components/TabsAndFilters'
import DemographicModeller from './components/DemographicModeller';
import DataStoriesAffordability from './components/DataStories.Affordability';
import Codebook from './components/Codebook';
import Socr_Dashboards from './components/Socr.Dashboards'

const _heroTitle = 'Data Management System';
const _heroContent = ' Central to activities on this platform is the Data Management System, where our evidence base is securely housed. We use the CKAN data portal framework, the same system used by various governments around the world, to store, manage, and share data across the SCODA ecosystem. From this dynamic, version-controlled and API-responsive data-store, we can efficiently gather and process the myriad sources required for insight generation.Central to activities on this platform is the Data Management System, where our evidence base is securely housed. We use the CKAN data portal framework, the same system used by various governments around the world, to store, manage, and share data across the SCODA ecosystem. From this dynamic, version-controlled and API-responsive data-store, we can efficiently gather and process the myriad sources required for insight generation.';
const _heroInfoTitle = 'Evidence Tools';
const _heroPrimaryColor = '#F05252';
const _heroSecondaryColor = '#F64343';

const _heroTitle_insight = 'Data Stories';
const _heroContent_insight = 'Data stories are a way to understand complex information and data using a narrative that appeals to interested readers.

They can be thought of as a series of dashboards connected with explainer text.  The topic is clearly defined and explored using information broken into understandable chunks which result in a finding or conclusion with calls to action to encourage the reader to learn and explore more.';
const _heroInfoTitle_insight = 'INSIGHT TOOLS';
const _heroPrimaryColor_insight = '#4F9DA6';
const _heroSecondaryColor_insight = '#4F9DA6';

const _heroTitle_explore = 'Data Explorer';
const _heroContent_explore = `Our Indicators: We use a diverse range of carefully selected and tested indicators to compile our State of Cities Report.
You can browse, analyse and download these individual indicators using our Data Explorer tool.`;
const _heroInfoTitle_explore = 'ANALYSIS TOOLS';
const _heroPrimaryColor_explore = '#FFAD5A';
const _heroSecondaryColor_explore = '#FFAD5A';

const _heroTitle_demo = 'Demographics Modeller';
const _heroContent_demo = 'With the demographic projection tool we provide both a powerful predictor of internal migration within South African metros, as well as a framework for the development of open, collaborative model building that is based on diverse and novel data sources.';
const _heroInfoTitle_demo = 'ANALYSIS TOOLS';
const _heroPrimaryColor_demo = '#FFAD5A';
const _heroSecondaryColor_demo = '#FFAD5A';

const _heroTitle_codebook = 'Codebook';
const _heroContent_codebook = 'The codebook is a live list of common city indicators providing definitions, methodologies for collection, and data availability of comparable city data. The codebook can be used to locate city data, understand how indicators have been constructed, where data is sourced from, and linked to the data explorer where information can be visualised and analysed.';
const _heroInfoTitle_codebook = 'ANALYSIS TOOLS';
const _heroPrimaryColor_codebook = '#FFAD5A';
const _heroSecondaryColor_codebook = '#FFAD5A';

// Linear gradients
const _heroLinearGradient_demo = `linear-gradient(112deg, rgb(255, 173, 90) calc(57% - 1px), rgb(255, 173, 90), #fff calc(38% + 1px))`
const _heroLinearGradient_codebook = `linear-gradient(112deg, rgb(255, 173, 90) calc(40% - 1px), rgb(255, 173, 90), #fff calc(38% + 1px))`
const _heroLinearGradient_datastories = `linear-gradient(112deg, rgba(79,157,166,1) calc(54% - 1px), rgba(79,157,166,1), #fff calc(38% + 1px))`

const _tabs = [
  {
      name: 'SACN', mode:'active',url: 'http://www.sacn.co.za'
  },
  {
      name: 'eThekwini', mode: 'none', url: 'http://www.google.com'
  }
];

// import more components
export default (
    <HashRouter history={hashHistory}>

      {/* DATA STORY DETAILS */}
    <Route path='/data-stories-details'  component={()=> <Navigation_scoda box_shadow={true} />}/>
    <Route exact path="/data-stories-details" component={()=> <Hero_Collapsable title={_heroTitle_insight}
            content={_heroContent_insight}
            infoTitle={_heroInfoTitle_insight}
            primaryColor={_heroPrimaryColor_insight}
            secondaryColor={_heroSecondaryColor_insight}
            linearGradient={_heroLinearGradient_datastories}/> } />

      <Route exact path="/data-stories-details" component={()=>
            <DataStoriesDetails />} />
      <Route path='/data-stories-details' component={Footer} />

      {/* CODE BOOK */}
      <Route path="/codebook" component={() => <Navigation_scoda box_shadow={true} />} />
      <Route exact path="/codebook" component={() => <Hero_Collapsable title={_heroTitle_codebook}
            content={_heroContent_codebook}
            infoTitle={_heroInfoTitle_codebook}
            primaryColor={_heroPrimaryColor_codebook}
            secondaryColor={_heroSecondaryColor_codebook}
            linearGradient={_heroLinearGradient_codebook}/> } />
      <Route exact path="/codebook" component={() => <Codebook />} />
      <Route path="/codebook" component={Footer} />

      <Route path="/ds-state-of-finance-2018" component={()=> <Navigation_scoda box_shadow={true} />} />
      <Route path="/ds-state-of-finance-2018" component={()=> <DataStories />} />
      <Route path="/ds-state-of-finance-2018" component={Footer} />

      <Route path="/ds-spatial-transformation-2018" component={()=> <Navigation_scoda box_shadow={true} />} />
      <Route path="/ds-spatial-transformation-2018" component={()=> <DataStoriesSpacial />} />
      <Route path="/ds-spatial-transformation-2018" component={Footer} />

      <Route path="/ds-urban-safety-2018" component={()=> <Navigation_scoda box_shadow={true} />} />
      <Route path="/ds-urban-safety-2018" component={()=> <DataStoriesUrbanSafety />} />
      <Route path="/ds-urban-safety-2018" component={Footer} />

      <Route path="/ds-epw-metro-2018" component={()=> <Navigation_scoda box_shadow={true} />} />
      <Route path="/ds-epw-metro-2018" component={()=> <DataStoriesEPW />} />
      <Route path="/ds-epw-metro-2018" component={Footer} />

      <Route path="/ds-affordability-2018" component={()=> <Navigation_scoda box_shadow={true} />} />
      <Route path="/ds-affordability-2018" component={()=> <DataStoriesAffordability />} />
      <Route path="/ds-affordability-2018" component={Footer} />

      <Route exact path="/tabs-and-filters" component={()=><TabsAndFilters tabs={_tabs}/>} />

      <Route path='/preview-block' component={Preview_block} />
      <Route path='/cta-block' component={CTA_block} />
      <Route path='/sub-footer' component={Sub_Footer} />
      <Route path='/footer' component={Footer} />

      <Route path='/datastory' component={DataStories} />

      <Route path='/home' component={()=> <Navigation_scoda logoHide={true} box_shadow={false} />} />
      <Route path='/home' component={Hero} />
      <Route path='/home' component={Preview_block} />
      <Route path='/home' component={CTA_block} />
      <Route path='/home' component={CTA_block_analysis} />
      <Route path='/home' component={CTA_block_insights} />
      <Route path='/home' component={Sub_Footer} />
      <Route path='/home' component={Footer} />

      <Route path='/about-us' component={()=> <Navigation_scoda logoHide={true} box_shadow={false} />} />
      <Route path='/about-us' component={About} />
      <Route path='/about-us' component={Sub_Footer} />
      <Route path='/about-us' component={Footer} />

      <Route path='/demographic-modeller' component={()=> <Navigation_scoda box_shadow={true} />} />
      <Route exact path="/demographic-modeller" component={()=> <Hero_Collapsable title={_heroTitle_demo}
            content={_heroContent_demo}
            infoTitle={_heroInfoTitle_demo}
            primaryColor={_heroPrimaryColor_demo}
            secondaryColor={_heroSecondaryColor_demo}
            linearGradient={_heroLinearGradient_demo}/>}/>
      <Route exact path="/demographic-modeller" component={()=><DemographicModeller />} />
      <Route path="/demographic-modeller" component={Footer} />

      <Route path="/explorer" component={()=> <Navigation_scoda box_shadow={true} />} />
      <Route exact path="/explorer" component={()=> <Hero_Collapsable title={_heroTitle_explore}
            content={_heroContent_explore}
            infoTitle={_heroInfoTitle_explore}
            primaryColor={_heroPrimaryColor_explore}
            secondaryColor={_heroSecondaryColor_explore}
            linearGradient={_heroLinearGradient_codebook}/>}/>
      <Route exact path="/explorer" component={()=> <IndicatorExplorer />}/>
      <Route path="/explorer" component={Footer} />

      <Route exact path='/codebook-explorer/:id' render={(props)=> <Navigation_scoda box_shadow={true} {...props}/>} />
      <Route exact path='/codebook-explorer/:id' render={(props)=> <Hero_Collapsable {...props} title={_heroTitle_explore}
            content={_heroContent_explore}
            infoTitle={_heroInfoTitle_explore}
            primaryColor={_heroPrimaryColor_explore}
            secondaryColor={_heroSecondaryColor_explore}/>}/>
        <Route exact path='/codebook-explorer/:id' render={(props)=> <CBIndicatorExplorer className=""  {...props}/>} />
      <Route path="/codebook-explorer/:id" component={Footer} />

      <Route path='/socr-dashboards' component={()=> <Navigation_scoda logoHide={false} box_shadow={true} />} />
      <Route path='/socr-dashboards' component={Socr_Dashboards} />
      <Route path='/socr-dashboards' component={Footer} />
    </HashRouter>
);<|endoftext|>


<commit_before>const {Subject, ReplaySubject} = require('rxjs')
const {finalize, takeUntil, switchMap, first, map} = require('rxjs/operators')
const {Worker, MessageChannel} = require('worker_threads')
const {deserializeError} = require('serialize-error')
const path = require('path')
const _ = require('lodash')
const log = require('@sepal/log')

const WORKER_PATH = path.join(__dirname, 'worker.js')

const createWorker = () =>
    new Worker(WORKER_PATH)

const createChannels = names =>
    _.transform(names, (data, name) => {
        const {port1: localPort, port2: remotePort} = new MessageChannel()
        data.localPorts[name] = localPort
        data.remotePorts[name] = remotePort
    }, {
        localPorts: {},
        remotePorts: {}
    })

const setupPorts = (worker, ports) =>
    worker.postMessage(ports, _.values(ports))

const bootstrapWorker$ = (jobName, channelNames) => {
    const worker$ = new Subject()
    const worker = createWorker(jobName)
    const {localPorts, remotePorts} = createChannels(channelNames)
    worker.once('message', status => {
        if (status === 'READY') {
            worker$.next({worker, ports: localPorts})
        } else {
            worker$.error('Cannot initialize worker.')
        }
    })
    setupPorts(worker, remotePorts)
    return worker$.pipe(
        first()
    )
}

const initWorker$ = (jobName, jobPath) => {
    const init$ = new ReplaySubject()
    const dispose$ = new Subject()

    const submit$ = args => {
        const result$ = new Subject()
        const stop$ = new Subject()

        const handleValue = value => {
            log.trace(`Job: worker <${jobName}> value: ${value}`)
            result$.next(value)
        }

        const handleError = serializedError => {
            const error = deserializeError(serializedError)
            log.error(`Job: worker <${jobName}> error: ${error.message} (${error.type})`)
            result$.error(error)
            stop$.next()
        }

        const handleComplete = () => {
            log.info(`Job: worker <${jobName}> completed`)
            result$.complete()
            stop$.next()
        }

        const handleWorkerMessage = message => {
            message.value && handleValue(message.value)
            message.error && handleError(message.error)
            message.complete && handleComplete()
        }

        const start = port => {
            port.on('message', handleWorkerMessage)
            port.postMessage({start: {jobName, jobPath, args}})
        }

        const stop = port => {
            port.postMessage({stop: true})
            port.off('message', handleWorkerMessage)
        }

        const run$ = port => {
            start(port)
            return result$.pipe(
                finalize(() => stop(port))
            )
        }

        return init$.pipe(
            switchMap(port => run$(port)),
            takeUntil(stop$)
        )
    }

    // bootstrapWorker$(jobName, ['job', 'rateLimit'])
    return bootstrapWorker$(jobName, ['job']).pipe(
        map(
            ({worker, ports: {job: jobPort, rateLimitPort}}) => {
                init$.next(jobPort)
                // const subscription = rateLimiter(rateLimitPort)
                // jobPort.on('message', handleWorkerMessage)

                dispose$.pipe(
                    first()
                ).subscribe(
                    () => {
                        worker.unref() // is this correct? terminate() probably isn't...
                        // subscription.cleanup()
                        log.info(`Job: worker <${jobName}> disposed`)
                    }
                )
                log.info('Job: worker ready')

                return {submit$, dispose$}
            }
        ))
}

module.exports = {
    initWorker$
}
<commit_msg>Cleanup.<commit_after>const {Subject, ReplaySubject} = require('rxjs')
const {finalize, takeUntil, switchMap, first, map} = require('rxjs/operators')
const {Worker, MessageChannel} = require('worker_threads')
const {deserializeError} = require('serialize-error')
const path = require('path')
const _ = require('lodash')
const log = require('@sepal/log')

const WORKER_PATH = path.join(__dirname, 'worker.js')

const createWorker = () =>
    new Worker(WORKER_PATH)

const createChannels = names =>
    _.transform(names, (data, name) => {
        const {port1: localPort, port2: remotePort} = new MessageChannel()
        data.localPorts[name] = localPort
        data.remotePorts[name] = remotePort
    }, {
        localPorts: {},
        remotePorts: {}
    })

const setupPorts = (worker, ports) =>
    worker.postMessage(ports, _.values(ports))

const bootstrapWorker$ = (jobName, channelNames) => {
    const worker$ = new Subject()
    const worker = createWorker(jobName)
    const {localPorts, remotePorts} = createChannels(channelNames)
    worker.once('message', status => {
        if (status === 'READY') {
            worker$.next({worker, ports: localPorts})
        } else {
            worker$.error('Cannot initialize worker.')
        }
    })
    setupPorts(worker, remotePorts)
    return worker$.pipe(
        first()
    )
}

const initWorker$ = (jobName, jobPath) => {
    const init$ = new ReplaySubject()
    const dispose$ = new Subject()

    const submit$ = args => {
        const result$ = new Subject()
        const stop$ = new Subject()

        const handleValue = value => {
            log.trace(`Job: worker <${jobName}> value: ${value}`)
            result$.next(value)
        }

        const handleError = serializedError => {
            const error = deserializeError(serializedError)
            log.error(`Job: worker <${jobName}> error: ${error.message} (${error.type})`)
            result$.error(error)
            stop$.next()
        }

        const handleComplete = () => {
            log.info(`Job: worker <${jobName}> completed`)
            result$.complete()
            stop$.next()
        }

        const handleWorkerMessage = message => {
            message.value && handleValue(message.value)
            message.error && handleError(message.error)
            message.complete && handleComplete()
        }

        const start = port => {
            port.on('message', handleWorkerMessage)
            port.postMessage({start: {jobName, jobPath, args}})
        }

        const stop = port => {
            port.postMessage({stop: true})
            port.off('message', handleWorkerMessage)
        }

        const run$ = port => {
            start(port)
            return result$.pipe(
                finalize(() => stop(port))
            )
        }

        return init$.pipe(
            switchMap(port => run$(port)),
            takeUntil(stop$)
        )
    }

    // bootstrapWorker$(jobName, ['job', 'rateLimit'])
    return bootstrapWorker$(jobName, ['job']).pipe(
        map(
            // ({worker, ports: {job: jobPort, rateLimitPort}}) => {
            ({worker, ports: {job: jobPort}}) => {
                init$.next(jobPort)
                // const subscription = rateLimiter(rateLimitPort)
                // jobPort.on('message', handleWorkerMessage)

                dispose$.pipe(
                    first()
                ).subscribe(
                    () => {
                        worker.unref() // is this correct? terminate() probably isn't...
                        // subscription.cleanup()
                        log.info(`Job: worker <${jobName}> disposed`)
                    }
                )
                log.info('Job: worker ready')

                return {submit$, dispose$}
            }
        ))
}

module.exports = {
    initWorker$
}
<|endoftext|>


<commit_before>Tests.prototype.BatteryTests = function() {
    module('Battery (navigator.battery)');
    test("should exist", function() {
        expect(1);
        ok(navigator.battery != null, "navigator.battery should not be null.");
    });
};<commit_msg>Adding tests cases for Battery Status API<commit_after>Tests.prototype.BatteryTests = function() {
    module('Battery (navigator.battery)');
    test("should exist", function() {
        expect(1);
        ok(navigator.battery != null, "navigator.battery should not be null.");
    });
    /**
     * Tests add for Battery Status API spec
     * http://www.w3.org/TR/2011/WD-battery-status-20111129/
     */
    test("should have properties", function() {
        expect(4);
        ok(typeof navigator.battery.charging != 'undefined' && navigator.battery.charging != null, "navigator.battery.charging should be a boolean value.");
        ok(typeof navigator.battery.chargingTime != 'undefined' && navigator.battery.chargingTime != null, "navigator.battery.chargingTime should be a double value.");
        ok(typeof navigator.battery.level != 'undefined' && navigator.battery.level != null, "navigator.battery.level should be a double value.");
        ok(typeof navigator.battery.dischargingTime != 'undefined' && navigator.battery.dischargingTime != null, "navigator.battery.dischargingTime should be a double value.");
    });
};<|endoftext|>