<commit_before>define([
    'underscore',
    'knockout',
    'knockout-mapping',
    'leaflet',
    'uuid',
    'geojson-extent',
    'views/components/iiif-viewer',
    'leaflet-draw'
], function(_, ko, koMapping, L, uuid, geojsonExtent, IIIFViewerViewmodel) {
    var viewModel = function(params) {
        var self = this;

        var drawControl;
        var drawFeatures = ko.observableArray();
        var editItems = new L.FeatureGroup();
        var tools;

        this.widgets = params.widgets || [];
        this.newNodeId = null;
        this.featureLookup = {};
        this.selectedFeatureIds = ko.observableArray();
        this.lineColor = ko.observable("#3388ff");
        this.fillColor = ko.observable("#3388ff");
        this.lineWidth = ko.observable(3);
        this.pointRadius = ko.observable(10);
        this.lineOpacity = ko.observable(1);
        this.fillOpacity = ko.observable(0.2);
        this.showStylingTools = ko.observable(false);

        this.cancelDrawing = function() {
            _.each(tools, function(tool) {
                tool.disable();
            });
        };

        this.setDrawTool = function(tool) {
            self.cancelDrawing();
            if (tool && tools) tools[tool].enable();
        };

        self.widgets.forEach(function(widget) {
            var id = ko.unwrap(widget.node_id);
            self.featureLookup[id] = {
                features: ko.computed(function() {
                    var value = koMapping.toJS(self.tile.data[id]);
                    if (value) return value.features;
                    else return [];
                }),
                selectedTool: ko.observable()
            };
            self.featureLookup[id].selectedTool.subscribe(function(tool) {
                if (drawControl) {
                    if (tool) {
                        _.each(self.featureLookup, function(value, key) {
                            if (key !== id) {
                                value.selectedTool(null);
                            }
                        });
                        self.newNodeId = id;
                    }
                    self.setDrawTool(tool);
                    disableEditing();
                }
            });
        });

        this.selectedTool = ko.pureComputed(function() {
            var tool;
            _.find(self.featureLookup, function(value) {
                var selectedTool = value.selectedTool();
                if (selectedTool) tool = selectedTool;
            });
            return tool;
        });

        this.editing = ko.pureComputed(function() {
            return !!(self.selectedFeatureIds().length > 0 || self.selectedTool());
        });

        this.updateTiles = function() {
            _.each(self.featureLookup, function(value) {
                value.selectedTool(null);
            });
            self.widgets.forEach(function(widget) {
                var id = ko.unwrap(widget.node_id);
                var features = [];
                drawFeatures().forEach(function(feature){
                    if (feature.properties.nodeId === id) {
                        features.push(feature)
                    };
                });
                if (ko.isObservable(self.tile.data[id])) {
                    self.tile.data[id]({
                        type: 'FeatureCollection',
                        features: features
                    });
                }
                else {
                    /* updates geometry table, instead of overwrite */
                    var tileDataFeatures = self.tile.data[id].features();
                    self.tile.data[id].features([...tileDataFeatures, ...features]);
                }
            });
        };

        var updateDrawFeatures = function() {
            drawFeatures([]);
            self.widgets.forEach(function(widget) {
                var id = ko.unwrap(widget.node_id);
                var featureCollection = koMapping.toJS(self.tile.data[id]);
                if (featureCollection && featureCollection.features) {
                    featureCollection.features.forEach(function(feature) {
                        if (feature.properties.manifest && !params.manifest)
                            params.manifest = feature.properties.manifest;
                        if (feature.properties.canvas && !params.canvas)
                            params.canvas = feature.properties.canvas;
                        feature.properties.nodeId = id;
                    });
                    drawFeatures(drawFeatures().concat(featureCollection.features));
                }
            });
        };
        updateDrawFeatures();

        IIIFViewerViewmodel.apply(this, [params]);

        var setTab = this.canvas.subscribe(function(val){
            if (val) {
                self.expandGallery(false);
                self.activeTab(ko.unwrap(self.activeTab) || 'editor');
                setTab.dispose();
            }
        });

        this.expandGallery.subscribe(function(val){
            if (val) {
                self.hideSidePanel();
            }
        });

        this.getAnnotationCount = function(canvas) {
            return drawFeatures().filter(function(feature) {
                return feature.properties.canvas === canvas;
            }).length;
        };

        var disableEditing = function() {
            if (editingFeature) editingFeature.editing.disable();
            editingFeature = undefined;
            self.selectedFeatureIds([]);
        };
        var enableEditing = function(feature) {
            disableEditing();
            editingFeature = feature;
            editingFeature.options.editing || (editingFeature.options.editing = {});
            editingFeature.editing.enable();
            self.styleProperties(feature.feature.properties);
            self.selectedFeatureIds([feature.feature.id]);
        };

        this.styleProperties = ko.computed({
            read: function() {
                return {
                    color: self.lineColor(),
                    fillColor: self.fillColor(),
                    weight: self.lineWidth(),
                    radius: self.pointRadius(),
                    opacity: self.lineOpacity(),
                    fillOpacity: self.fillOpacity()
                };
            },
            write: function(style) {
                self.lineColor(style.color);
                self.fillColor(style.fillColor);
                self.lineWidth(style.weight);
                self.pointRadius(style.radius);
                self.lineOpacity(style.opacity);
                self.fillOpacity(style.fillOpacity);
            }
        });

        var featureClick;
        var drawLayer = ko.computed(function() {
            var selectedFeatureIds = self.selectedFeatureIds();
            var styleProperties = self.styleProperties();
            return L.geoJson({
                type: 'FeatureCollection',
                features: drawFeatures()
            }, {
                pointToLayer: function(feature, latlng) {
                    var style;
                    if (selectedFeatureIds.includes(feature.id)) style = styleProperties;
                    else style = {
                        color: feature.properties.color,
                        fillColor: feature.properties.fillColor,
                        weight: feature.properties.weight,
                        radius: feature.properties.radius,
                        opacity: feature.properties.opacity,
                        fillOpacity: feature.properties.fillOpacity
                    };
                    return L.circleMarker(latlng, style);
                },
                style: function(feature) {
                    var style;
                    if (selectedFeatureIds.includes(feature.id)) style = styleProperties;
                    else style = {
                        color: feature.properties.color,
                        fillColor: feature.properties.fillColor,
                        weight: feature.properties.weight,
                        radius: feature.properties.radius,
                        opacity: feature.properties.opacity,
                        fillOpacity: feature.properties.fillOpacity
                    };
                    return style;
                },
                filter: function(feature) {
                    return feature.properties.canvas === self.canvas();
                },
                onEachFeature: function(feature, layer) {
                    layer.on('click', function(e) {
                        enableEditing(e.target);
                        featureClick = true;
                    });
                }
            });
        });

        drawLayer.subscribe(function(newDrawLayer) {
            var map = self.map();
            var selectedFeatureIds = self.selectedFeatureIds();
            if (map) {
                editItems.clearLayers();
                editItems.addLayer(newDrawLayer);
                newDrawLayer.getLayers().forEach(function(layer) {
                    if (selectedFeatureIds.includes(layer.feature.id)) {
                        layer.options.editing || (layer.options.editing = {});
                        layer.editing.enable();
                    }
                });
            }
        });

        if (this.card) {
            this.card.triggerUpdate = updateDrawFeatures; // can be called by the provisional tile view model to update the drawing
        }

        this.disableDrawing = ko.computed(function() {
            return !self.canvas();
        });

        this.showFeature = function(feature) {
            self.canvas(feature.properties.canvas);
            if (self.manifest() !== feature.properties.manifest) {
                self.manifest(feature.properties.manifest);
                self.getManifestData();
            }
            setTimeout(function() {
                if (feature.geometry.type === 'Point') {
                    var coords = feature.geometry.coordinates;
                    self.map().panTo([coords[1], coords[0]]);
                } else {
                    var extent = geojsonExtent(feature);
                    self.map().fitBounds([
                        [extent[1], extent[0]],
                        [extent[3], extent[2]]
                    ]);
                }
            }, 200);
        };

        var editingFeature;
        this.editFeature = function(feature) {
            var layers = editItems.getLayers()[0].getLayers();
            if (self.manifest() !== feature.properties.manifest) {
                self.manifest(feature.properties.manifest);
                self.getManifestData();
            }
            self.canvas(feature.properties.canvas);
            layers.forEach(function(layer) {
                if (layer.feature.id === feature.id) enableEditing(layer);
            });
        };

        this.deleteFeature = function(feature) {
            drawFeatures().forEach(function(drawFeature) {
                if (drawFeature.id === feature.id) drawFeatures.remove(drawFeature);
            });
            self.updateTiles();
        };

        this.canvas.subscribe(disableEditing);

        this.map.subscribe(function(map) {
            if (map && !drawControl) {
                map.addLayer(editItems);
                editItems.addLayer(drawLayer());

                drawControl = new L.Control.Draw({
                    edit: {
                        featureGroup: editItems
                    }
                });
                map.addControl(drawControl);

                tools = {
                    'draw_point': new L.Draw.CircleMarker(map, drawControl.options.circlemarker),
                    'draw_line_string': new L.Draw.Polyline(map, drawControl.options.polyline),
                    'draw_polygon': new L.Draw.Polygon(map, drawControl.options.polygon)
                };
                self.styleProperties.subscribe(function(styleProperties) {
                    _.each(tools, function(tool) {
                        if (tool.type === "circlemarker") tool.setOptions(styleProperties);
                        else tool.setOptions({ shapeOptions: styleProperties });
                    });
                    self.selectedFeatureIds().forEach(function(id) {
                        drawFeatures().forEach(function(drawFeature) {
                            if (drawFeature.id === id) {
                                drawFeature.properties = Object.assign(
                                    drawFeature.properties,
                                    styleProperties
                                );
                            }
                        });
                        self.updateTiles();
                    });
                });

                map.on('draw:created', function(e) {
                    var feature = e.layer.toGeoJSON();
                    feature.id = uuid.generate();
                    feature.properties = Object.assign({
                        nodeId: self.newNodeId,
                        canvas: self.canvas(),
                        manifest: self.manifest()
                    }, self.styleProperties());
                    drawFeatures.push(feature);
                    self.updateTiles();
                    self.editFeature(feature);
                });

                map.on('draw:editvertex draw:editmove', function() {
                    var layers = editItems.getLayers()[0].getLayers();
                    drawFeatures().forEach(function(drawFeature) {
                        layers.forEach(function(layer) {
                            if (drawFeature.id === layer.feature.id)
                                drawFeature.geometry = layer.toGeoJSON().geometry;
                        });
                    });
                    self.updateTiles();
                });

                map.on('click', function() {
                    if (!featureClick) disableEditing();
                    featureClick = false;
                });

                if (self.form) self.form.on('tile-reset', function() {
                    disableEditing();
                    updateDrawFeatures();
                    _.each(self.featureLookup, function(value) {
                        if (value.selectedTool()) value.selectedTool('');
                    });
                });
            }
        });
    };
    return viewModel;
});
<commit_msg>nit #7569<commit_after>define([
    'underscore',
    'knockout',
    'knockout-mapping',
    'leaflet',
    'uuid',
    'geojson-extent',
    'views/components/iiif-viewer',
    'leaflet-draw'
], function(_, ko, koMapping, L, uuid, geojsonExtent, IIIFViewerViewmodel) {
    var viewModel = function(params) {
        var self = this;

        var drawControl;
        var drawFeatures = ko.observableArray();
        var editItems = new L.FeatureGroup();
        var tools;

        this.widgets = params.widgets || [];
        this.newNodeId = null;
        this.featureLookup = {};
        this.selectedFeatureIds = ko.observableArray();
        this.lineColor = ko.observable("#3388ff");
        this.fillColor = ko.observable("#3388ff");
        this.lineWidth = ko.observable(3);
        this.pointRadius = ko.observable(10);
        this.lineOpacity = ko.observable(1);
        this.fillOpacity = ko.observable(0.2);
        this.showStylingTools = ko.observable(false);

        this.cancelDrawing = function() {
            _.each(tools, function(tool) {
                tool.disable();
            });
        };

        this.setDrawTool = function(tool) {
            self.cancelDrawing();
            if (tool && tools) tools[tool].enable();
        };

        self.widgets.forEach(function(widget) {
            var id = ko.unwrap(widget.node_id);
            self.featureLookup[id] = {
                features: ko.computed(function() {
                    var value = koMapping.toJS(self.tile.data[id]);
                    if (value) return value.features;
                    else return [];
                }),
                selectedTool: ko.observable()
            };
            self.featureLookup[id].selectedTool.subscribe(function(tool) {
                if (drawControl) {
                    if (tool) {
                        _.each(self.featureLookup, function(value, key) {
                            if (key !== id) {
                                value.selectedTool(null);
                            }
                        });
                        self.newNodeId = id;
                    }
                    self.setDrawTool(tool);
                    disableEditing();
                }
            });
        });

        this.selectedTool = ko.pureComputed(function() {
            var tool;
            _.find(self.featureLookup, function(value) {
                var selectedTool = value.selectedTool();
                if (selectedTool) tool = selectedTool;
            });
            return tool;
        });

        this.editing = ko.pureComputed(function() {
            return !!(self.selectedFeatureIds().length > 0 || self.selectedTool());
        });

        this.updateTiles = function() {
            _.each(self.featureLookup, function(value) {
                value.selectedTool(null);
            });
            self.widgets.forEach(function(widget) {
                var id = ko.unwrap(widget.node_id);
                var features = [];
                drawFeatures().forEach(function(feature){
                    if (feature.properties.nodeId === id) {
                        features.push(feature);
                    }
                });
                if (ko.isObservable(self.tile.data[id])) {
                    self.tile.data[id]({
                        type: 'FeatureCollection',
                        features: features
                    });
                }
                else {
                    /* updates geometry table, instead of overwrite */
                    var tileDataFeatures = self.tile.data[id].features();
                    self.tile.data[id].features([...tileDataFeatures, ...features]);
                }
            });
        };

        var updateDrawFeatures = function() {
            drawFeatures([]);
            self.widgets.forEach(function(widget) {
                var id = ko.unwrap(widget.node_id);
                var featureCollection = koMapping.toJS(self.tile.data[id]);
                if (featureCollection && featureCollection.features) {
                    featureCollection.features.forEach(function(feature) {
                        if (feature.properties.manifest && !params.manifest)
                            params.manifest = feature.properties.manifest;
                        if (feature.properties.canvas && !params.canvas)
                            params.canvas = feature.properties.canvas;
                        feature.properties.nodeId = id;
                    });
                    drawFeatures(drawFeatures().concat(featureCollection.features));
                }
            });
        };
        updateDrawFeatures();

        IIIFViewerViewmodel.apply(this, [params]);

        var setTab = this.canvas.subscribe(function(val){
            if (val) {
                self.expandGallery(false);
                self.activeTab(ko.unwrap(self.activeTab) || 'editor');
                setTab.dispose();
            }
        });

        this.expandGallery.subscribe(function(val){
            if (val) {
                self.hideSidePanel();
            }
        });

        this.getAnnotationCount = function(canvas) {
            return drawFeatures().filter(function(feature) {
                return feature.properties.canvas === canvas;
            }).length;
        };

        var disableEditing = function() {
            if (editingFeature) editingFeature.editing.disable();
            editingFeature = undefined;
            self.selectedFeatureIds([]);
        };
        var enableEditing = function(feature) {
            disableEditing();
            editingFeature = feature;
            editingFeature.options.editing || (editingFeature.options.editing = {});
            editingFeature.editing.enable();
            self.styleProperties(feature.feature.properties);
            self.selectedFeatureIds([feature.feature.id]);
        };

        this.styleProperties = ko.computed({
            read: function() {
                return {
                    color: self.lineColor(),
                    fillColor: self.fillColor(),
                    weight: self.lineWidth(),
                    radius: self.pointRadius(),
                    opacity: self.lineOpacity(),
                    fillOpacity: self.fillOpacity()
                };
            },
            write: function(style) {
                self.lineColor(style.color);
                self.fillColor(style.fillColor);
                self.lineWidth(style.weight);
                self.pointRadius(style.radius);
                self.lineOpacity(style.opacity);
                self.fillOpacity(style.fillOpacity);
            }
        });

        var featureClick;
        var drawLayer = ko.computed(function() {
            var selectedFeatureIds = self.selectedFeatureIds();
            var styleProperties = self.styleProperties();
            return L.geoJson({
                type: 'FeatureCollection',
                features: drawFeatures()
            }, {
                pointToLayer: function(feature, latlng) {
                    var style;
                    if (selectedFeatureIds.includes(feature.id)) style = styleProperties;
                    else style = {
                        color: feature.properties.color,
                        fillColor: feature.properties.fillColor,
                        weight: feature.properties.weight,
                        radius: feature.properties.radius,
                        opacity: feature.properties.opacity,
                        fillOpacity: feature.properties.fillOpacity
                    };
                    return L.circleMarker(latlng, style);
                },
                style: function(feature) {
                    var style;
                    if (selectedFeatureIds.includes(feature.id)) style = styleProperties;
                    else style = {
                        color: feature.properties.color,
                        fillColor: feature.properties.fillColor,
                        weight: feature.properties.weight,
                        radius: feature.properties.radius,
                        opacity: feature.properties.opacity,
                        fillOpacity: feature.properties.fillOpacity
                    };
                    return style;
                },
                filter: function(feature) {
                    return feature.properties.canvas === self.canvas();
                },
                onEachFeature: function(feature, layer) {
                    layer.on('click', function(e) {
                        enableEditing(e.target);
                        featureClick = true;
                    });
                }
            });
        });

        drawLayer.subscribe(function(newDrawLayer) {
            var map = self.map();
            var selectedFeatureIds = self.selectedFeatureIds();
            if (map) {
                editItems.clearLayers();
                editItems.addLayer(newDrawLayer);
                newDrawLayer.getLayers().forEach(function(layer) {
                    if (selectedFeatureIds.includes(layer.feature.id)) {
                        layer.options.editing || (layer.options.editing = {});
                        layer.editing.enable();
                    }
                });
            }
        });

        if (this.card) {
            this.card.triggerUpdate = updateDrawFeatures; // can be called by the provisional tile view model to update the drawing
        }

        this.disableDrawing = ko.computed(function() {
            return !self.canvas();
        });

        this.showFeature = function(feature) {
            self.canvas(feature.properties.canvas);
            if (self.manifest() !== feature.properties.manifest) {
                self.manifest(feature.properties.manifest);
                self.getManifestData();
            }
            setTimeout(function() {
                if (feature.geometry.type === 'Point') {
                    var coords = feature.geometry.coordinates;
                    self.map().panTo([coords[1], coords[0]]);
                } else {
                    var extent = geojsonExtent(feature);
                    self.map().fitBounds([
                        [extent[1], extent[0]],
                        [extent[3], extent[2]]
                    ]);
                }
            }, 200);
        };

        var editingFeature;
        this.editFeature = function(feature) {
            var layers = editItems.getLayers()[0].getLayers();
            if (self.manifest() !== feature.properties.manifest) {
                self.manifest(feature.properties.manifest);
                self.getManifestData();
            }
            self.canvas(feature.properties.canvas);
            layers.forEach(function(layer) {
                if (layer.feature.id === feature.id) enableEditing(layer);
            });
        };

        this.deleteFeature = function(feature) {
            drawFeatures().forEach(function(drawFeature) {
                if (drawFeature.id === feature.id) drawFeatures.remove(drawFeature);
            });
            self.updateTiles();
        };

        this.canvas.subscribe(disableEditing);

        this.map.subscribe(function(map) {
            if (map && !drawControl) {
                map.addLayer(editItems);
                editItems.addLayer(drawLayer());

                drawControl = new L.Control.Draw({
                    edit: {
                        featureGroup: editItems
                    }
                });
                map.addControl(drawControl);

                tools = {
                    'draw_point': new L.Draw.CircleMarker(map, drawControl.options.circlemarker),
                    'draw_line_string': new L.Draw.Polyline(map, drawControl.options.polyline),
                    'draw_polygon': new L.Draw.Polygon(map, drawControl.options.polygon)
                };
                self.styleProperties.subscribe(function(styleProperties) {
                    _.each(tools, function(tool) {
                        if (tool.type === "circlemarker") tool.setOptions(styleProperties);
                        else tool.setOptions({ shapeOptions: styleProperties });
                    });
                    self.selectedFeatureIds().forEach(function(id) {
                        drawFeatures().forEach(function(drawFeature) {
                            if (drawFeature.id === id) {
                                drawFeature.properties = Object.assign(
                                    drawFeature.properties,
                                    styleProperties
                                );
                            }
                        });
                        self.updateTiles();
                    });
                });

                map.on('draw:created', function(e) {
                    var feature = e.layer.toGeoJSON();
                    feature.id = uuid.generate();
                    feature.properties = Object.assign({
                        nodeId: self.newNodeId,
                        canvas: self.canvas(),
                        manifest: self.manifest()
                    }, self.styleProperties());
                    drawFeatures.push(feature);
                    self.updateTiles();
                    self.editFeature(feature);
                });

                map.on('draw:editvertex draw:editmove', function() {
                    var layers = editItems.getLayers()[0].getLayers();
                    drawFeatures().forEach(function(drawFeature) {
                        layers.forEach(function(layer) {
                            if (drawFeature.id === layer.feature.id)
                                drawFeature.geometry = layer.toGeoJSON().geometry;
                        });
                    });
                    self.updateTiles();
                });

                map.on('click', function() {
                    if (!featureClick) disableEditing();
                    featureClick = false;
                });

                if (self.form) self.form.on('tile-reset', function() {
                    disableEditing();
                    updateDrawFeatures();
                    _.each(self.featureLookup, function(value) {
                        if (value.selectedTool()) value.selectedTool('');
                    });
                });
            }
        });
    };
    return viewModel;
});
<|endoftext|>


<commit_before>/* @flow */

import React, {Component} from 'react'
import {FlatButton} from 'material-ui'
import ProgressIndicator from './progress-indicator'
import {globalStyles, globalColors} from '../styles/style-guide'
import type {Props} from './button'

class Button extends Component {
  props: Props;

  _styles (type: Props.type): Object {
    let backgroundStyle = {}
    let labelStyle = {}
    let progressColor = globalColors.white
    let rippleStyle: {rippleColor?: string} = {rippleColor: 'rgba(0, 0, 0, 0.3)'}

    const disabled = this.props.disabled || this.props.waiting

    switch (this.props.type) {
      case 'Primary':
        backgroundStyle = {
          ...stylesButtonPrimary,
          opacity: disabled ? stylesButtonPrimary.disabledOpacity : 1
        }
        break
      case 'Follow':
        backgroundStyle = {
          ...stylesButtonFollow,
          opacity: disabled ? stylesButtonFollow.disabledOpacity : 1
        }
        break
      case 'Following':
        backgroundStyle = {
          ...stylesButtonFollowing,
          opacity: disabled ? stylesButtonFollowing.disabledOpacity : 1
        }
        labelStyle = {
          color: globalColors.green
        }
        progressColor = globalColors.black_75
        break
      case 'Unfollow':
        backgroundStyle = {
          ...stylesButtonUnfollow,
          opacity: disabled ? stylesButtonUnfollow.disabledOpacity : 1
        }
        break
      case 'Danger':
        backgroundStyle = {
          ...stylesButtonDanger,
          opacity: disabled ? stylesButtonDanger.disabledOpacity : 1
        }
        break
      case 'Secondary':
      default:
        backgroundStyle = {
          ...stylesButtonSecondary,
          backgroundColor: this.props.backgroundMode === 'Terminal' ? globalColors.blue_30 : stylesButtonSecondary.backgroundColor,
          opacity: disabled ? stylesButtonSecondary.disabledOpacity : 1
        }
        labelStyle = {
          color: this.props.backgroundMode === 'Terminal' ? globalColors.white : globalColors.black_75
        }
        progressColor = globalColors.black_75
    }
    return {backgroundStyle, labelStyle, progressColor, rippleStyle}
  }

  render () {
    // First apply styles for the main button types.
    let {backgroundStyle, labelStyle, progressColor, rippleStyle} = this._styles(this.props.type)
    let smallStyle = {}

    // Then some overrides that apply to all button types.
    if (this.props.small) {
      smallStyle = stylesButtonSmall
      labelStyle = {
        ...labelStyle,
        ...stylesButtonSmallLabel
      }
    }

    if (this.props.waiting) {
      labelStyle = {
        ...labelStyle,
        opacity: 0
      }
    }

    let outerStyle = {position: 'relative'}
    if (this.props.style) {
      outerStyle = {...outerStyle, alignSelf: this.props.style.alignSelf}
    }

    if (this.props.fullWidth) {
        // Using minWidth here means we can't have a full-width button on the
        // same line/row as another button, the right thing is very unlikely to
        // happen.  The alternative is 'flex: 1' here, which would work but is
        // dangerous, because we'd be modifying our container.
        //
        // So let's just say that a fullWidth button can't have siblings.
      outerStyle = {...outerStyle, minWidth: '100%'}
      backgroundStyle = {...backgroundStyle, minWidth: '100%', height: 38}
    }

    if (this.props.waiting) {
      outerStyle = {...outerStyle, cursor: 'wait'}
    }

    let label = this.props.label

    if (this.props.more) {
      label = '•••'
    }

    return (

        <FlatButton
          onClick={this.props.onClick}
          onMouseEnter={this.props.onMouseEnter}
          onMouseLeave={this.props.onMouseLeave}
          style={{...backgroundStyle, ...smallStyle, ...this.props.style}}
          labelStyle={{...stylesButtonLabel, ...labelStyle, ...this.props.labelStyle}}
          {...rippleStyle}
          label={label}
          primary={this.props.type === 'Primary'}
          secondary={this.props.type === 'Secondary'}
          disabled={this.props.disabled || this.props.waiting}>
          {this.props.waiting && (
            <ProgressIndicator
              white={progressColor === globalColors.white}
              style={{...stylesProgress}}
            />)}
        </FlatButton>

    )
  }
}

const buttonCommon = {
  borderRadius: 55,
  fontSize: 16,
  height: 32,
  lineHeight: '32px',
  minWidth: 10
}

const stylesButtonPrimary = {
  ...buttonCommon,
  backgroundColor: globalColors.blue,
  disabledOpacity: 0.2
}
const stylesButtonSecondary = {
  ...buttonCommon,
  backgroundColor: globalColors.lightGrey2,
  disabledOpacity: 0.3,
  marginRight: 10
}
const stylesButtonDanger = {
  ...buttonCommon,
  backgroundColor: globalColors.red,
  disabledOpacity: 0.2,
  marginRight: 10
}
const stylesButtonFollow = {
  ...buttonCommon,
  backgroundColor: globalColors.green,
  disabledOpacity: 0.3,
  marginRight: 10,
  minWidth: 125
}
const stylesButtonFollowing = {
  ...buttonCommon,
  backgroundColor: globalColors.white,
  border: `solid 2px ${globalColors.green}`,
  lineHeight: '28px',
  marginRight: 10,
  minWidth: 125
}
const stylesButtonUnfollow = {
  ...buttonCommon,
  backgroundColor: globalColors.blue,
  // mimic border width from following state to work around animation jitter during FollowButton hover transition
  border: `solid 2px ${globalColors.blue}`,
  lineHeight: '28px',
  disabledOpacity: 0.2,
  marginRight: 10,
  minWidth: 125
}
const stylesButtonSmall = {
  height: 28,
  lineHeight: '27px'
}
const stylesButtonLabel = {
  ...globalStyles.fontSemibold,
  paddingLeft: 25,
  paddingRight: 25,
  verticalAlign: 'initial',
  color: globalColors.white,
  whiteSpace: 'nowrap',
  fontSize: 16,
  height: 'auto',
  lineHeight: 0,
  textTransform: 'none'
}
const stylesButtonSmallLabel = {
  ...globalStyles.fontSemibold,
  fontSize: 14,
  paddingLeft: 20,
  paddingRight: 20,
  lineHeight: 0
}
const stylesProgress = {
  position: 'absolute',
  height: 'calc(100% - 4px)',
  left: 0,
  right: 0,
  top: 0,
  bottom: 0,
  margin: 'auto'
}

export default Button
<commit_msg>Update style for "Unfollow" buttons to be gray<commit_after>/* @flow */

import React, {Component} from 'react'
import {FlatButton} from 'material-ui'
import ProgressIndicator from './progress-indicator'
import {globalStyles, globalColors} from '../styles/style-guide'
import type {Props} from './button'

class Button extends Component {
  props: Props;

  _styles (type: Props.type): Object {
    let backgroundStyle = {}
    let labelStyle = {}
    let progressColor = globalColors.white
    let rippleStyle: {rippleColor?: string} = {rippleColor: 'rgba(0, 0, 0, 0.3)'}

    const disabled = this.props.disabled || this.props.waiting

    switch (this.props.type) {
      case 'Primary':
        backgroundStyle = {
          ...stylesButtonPrimary,
          opacity: disabled ? stylesButtonPrimary.disabledOpacity : 1
        }
        break
      case 'Follow':
        backgroundStyle = {
          ...stylesButtonFollow,
          opacity: disabled ? stylesButtonFollow.disabledOpacity : 1
        }
        break
      case 'Following':
        backgroundStyle = {
          ...stylesButtonFollowing,
          opacity: disabled ? stylesButtonFollowing.disabledOpacity : 1
        }
        labelStyle = {
          color: globalColors.green
        }
        progressColor = globalColors.black_75
        break
      case 'Unfollow':
        backgroundStyle = {
          ...stylesButtonUnfollow,
          opacity: disabled ? stylesButtonUnfollow.disabledOpacity : 1
        }
        labelStyle = {
          color: globalColors.black_75
        }
        break
      case 'Danger':
        backgroundStyle = {
          ...stylesButtonDanger,
          opacity: disabled ? stylesButtonDanger.disabledOpacity : 1
        }
        break
      case 'Secondary':
      default:
        backgroundStyle = {
          ...stylesButtonSecondary,
          backgroundColor: this.props.backgroundMode === 'Terminal' ? globalColors.blue_30 : stylesButtonSecondary.backgroundColor,
          opacity: disabled ? stylesButtonSecondary.disabledOpacity : 1
        }
        labelStyle = {
          color: this.props.backgroundMode === 'Terminal' ? globalColors.white : globalColors.black_75
        }
        progressColor = globalColors.black_75
    }
    return {backgroundStyle, labelStyle, progressColor, rippleStyle}
  }

  render () {
    // First apply styles for the main button types.
    let {backgroundStyle, labelStyle, progressColor, rippleStyle} = this._styles(this.props.type)
    let smallStyle = {}

    // Then some overrides that apply to all button types.
    if (this.props.small) {
      smallStyle = stylesButtonSmall
      labelStyle = {
        ...labelStyle,
        ...stylesButtonSmallLabel
      }
    }

    if (this.props.waiting) {
      labelStyle = {
        ...labelStyle,
        opacity: 0
      }
    }

    let outerStyle = {position: 'relative'}
    if (this.props.style) {
      outerStyle = {...outerStyle, alignSelf: this.props.style.alignSelf}
    }

    if (this.props.fullWidth) {
        // Using minWidth here means we can't have a full-width button on the
        // same line/row as another button, the right thing is very unlikely to
        // happen.  The alternative is 'flex: 1' here, which would work but is
        // dangerous, because we'd be modifying our container.
        //
        // So let's just say that a fullWidth button can't have siblings.
      outerStyle = {...outerStyle, minWidth: '100%'}
      backgroundStyle = {...backgroundStyle, minWidth: '100%', height: 38}
    }

    if (this.props.waiting) {
      outerStyle = {...outerStyle, cursor: 'wait'}
    }

    let label = this.props.label

    if (this.props.more) {
      label = '•••'
    }

    return (

        <FlatButton
          onClick={this.props.onClick}
          onMouseEnter={this.props.onMouseEnter}
          onMouseLeave={this.props.onMouseLeave}
          style={{...backgroundStyle, ...smallStyle, ...this.props.style}}
          labelStyle={{...stylesButtonLabel, ...labelStyle, ...this.props.labelStyle}}
          {...rippleStyle}
          label={label}
          primary={this.props.type === 'Primary'}
          secondary={this.props.type === 'Secondary'}
          disabled={this.props.disabled || this.props.waiting}>
          {this.props.waiting && (
            <ProgressIndicator
              white={progressColor === globalColors.white}
              style={{...stylesProgress}}
            />)}
        </FlatButton>

    )
  }
}

const buttonCommon = {
  borderRadius: 55,
  fontSize: 16,
  height: 32,
  lineHeight: '32px',
  minWidth: 10
}

const stylesButtonPrimary = {
  ...buttonCommon,
  backgroundColor: globalColors.blue,
  disabledOpacity: 0.2
}
const stylesButtonSecondary = {
  ...buttonCommon,
  backgroundColor: globalColors.lightGrey2,
  disabledOpacity: 0.3,
  marginRight: 10
}
const stylesButtonDanger = {
  ...buttonCommon,
  backgroundColor: globalColors.red,
  disabledOpacity: 0.2,
  marginRight: 10
}
const stylesButtonFollow = {
  ...buttonCommon,
  backgroundColor: globalColors.green,
  disabledOpacity: 0.3,
  marginRight: 10,
  minWidth: 125
}
const stylesButtonFollowing = {
  ...buttonCommon,
  backgroundColor: globalColors.white,
  border: `solid 2px ${globalColors.green}`,
  lineHeight: '28px',
  marginRight: 10,
  minWidth: 125
}
const stylesButtonUnfollow = {
  ...stylesButtonSecondary,
  // mimic border width from following state to work around animation jitter during FollowButton hover transition
  border: `solid 2px ${globalColors.lightGrey2}`,
  lineHeight: '28px',
  marginRight: 10,
  minWidth: 125
}
const stylesButtonSmall = {
  height: 28,
  lineHeight: '27px'
}
const stylesButtonLabel = {
  ...globalStyles.fontSemibold,
  paddingLeft: 25,
  paddingRight: 25,
  verticalAlign: 'initial',
  color: globalColors.white,
  whiteSpace: 'nowrap',
  fontSize: 16,
  height: 'auto',
  lineHeight: 0,
  textTransform: 'none'
}
const stylesButtonSmallLabel = {
  ...globalStyles.fontSemibold,
  fontSize: 14,
  paddingLeft: 20,
  paddingRight: 20,
  lineHeight: 0
}
const stylesProgress = {
  position: 'absolute',
  height: 'calc(100% - 4px)',
  left: 0,
  right: 0,
  top: 0,
  bottom: 0,
  margin: 'auto'
}

export default Button
<|endoftext|>


<commit_before>/* @flow */

import React, {Component} from 'react'
import {Icon} from '../common-adapters'
import {globalStyles, globalColors} from '../styles/style-guide'
import type {Props, DefaultProps} from './header'
import Text from './text'

export default class Header extends Component<DefaultProps, Props, void> {
  static defaultProps: DefaultProps;

  renderDefault () {
    return (

        {this.props.children}
        {this.props.icon && <Icon type='logo-24' />}
        <Text type='Body' style={{flex: 1, paddingLeft: 6}}>{this.props.title}</Text>
        {this.props.onClose && (
          <Icon style={styles.closeIcon} type='fa-close' onClick={() => this.props.onClose()} />
        )}

    )
  }

  renderStrong () {
    return (

        <Text type='Header' backgroundMode='Announcements' style={{flex: 1, ...globalStyles.flexBoxCenter, paddingTop: 6, cursor: 'default'}}>{this.props.title}</Text>
        {this.props.onClose && (
          <Icon type='fa-close' onClick={() => this.props.onClose()} />
        )}

    )
  }

  render () {
    if (this.props.type === 'Default') {
      return this.renderDefault()
    } else if (this.props.type === 'Strong') {
      return this.renderStrong()
    } else {
      return

    }
  }
}

Header.defaultProps = {type: 'Default'}

const styles = {
  container: {
    ...globalStyles.flexBoxRow,
    ...globalStyles.windowDragging,
    ...globalStyles.noSelect,
    paddingLeft: 10,
    paddingRight: 10
  },
  logo: {
    width: 22,
    height: 22,
    marginRight: 8
  },
  defaultContainer: {
    paddingTop: 6,
    paddingBottom: 6
  },
  strongContainer: {
    backgroundColor: globalColors.blue,
    paddingTop: 6,
    paddingBottom: 12
  },

  closeIcon: {
    ...globalStyles.windowDraggingClickable,
    ...globalStyles.clickable
  }
}
<commit_msg>should fix close button on linux<commit_after>/* @flow */

import React, {Component} from 'react'
import {Icon} from '../common-adapters'
import {globalStyles, globalColors} from '../styles/style-guide'
import type {Props, DefaultProps} from './header'
import Text from './text'

export default class Header extends Component<DefaultProps, Props, void> {
  static defaultProps: DefaultProps;

  renderDefault () {
    return (

        {this.props.children}
        {this.props.icon && <Icon type='logo-24' />}
        <Text type='Body' style={{flex: 1, paddingLeft: 6}}>{this.props.title}</Text>
        {this.props.onClose && (
          <Icon style={styles.closeIcon} type='fa-close' onClick={() => this.props.onClose()} />
        )}

    )
  }

  renderStrong () {
    return (

        <Text type='Header' backgroundMode='Announcements' style={{flex: 1, ...globalStyles.flexBoxCenter, paddingTop: 6, cursor: 'default'}}>{this.props.title}</Text>
        {this.props.onClose && (
          <Icon style={styles.closeIcon} type='fa-close' onClick={() => this.props.onClose()} />
        )}

    )
  }

  render () {
    if (this.props.type === 'Default') {
      return this.renderDefault()
    } else if (this.props.type === 'Strong') {
      return this.renderStrong()
    } else {
      return

    }
  }
}

Header.defaultProps = {type: 'Default'}

const styles = {
  container: {
    ...globalStyles.flexBoxRow,
    ...globalStyles.windowDragging,
    ...globalStyles.noSelect,
    paddingLeft: 10,
    paddingRight: 10
  },
  logo: {
    width: 22,
    height: 22,
    marginRight: 8
  },
  defaultContainer: {
    paddingTop: 6,
    paddingBottom: 6
  },
  strongContainer: {
    backgroundColor: globalColors.blue,
    paddingTop: 6,
    paddingBottom: 12
  },

  closeIcon: {
    ...globalStyles.windowDraggingClickable,
    ...globalStyles.clickable
  }
}
<|endoftext|>


<commit_before>// common library functions, mostly for plotting but used elsewhere too
(function() {
if(!window.Plotly) { window.Plotly = {}; }
var lib = Plotly.Lib = {};

// dateTime2ms - make string s of the form YYYY-mm-dd HH:MM:SS.sss
// into milliseconds (relative to 1970-01-01, per javascript standard)
// may truncate after any full field, and sss can be any length
// even >3 digits, though javascript dates truncate to milliseconds
// returns false if it doesn't find a date

// 2-digit to 4-digit year conversion, where to cut off?
// from http://support.microsoft.com/kb/244664:
//   1930-2029 (the most retro of all...)
// but in my mac chrome from eg. d=new Date(Date.parse('8/19/50')):
//   1950-2049
// by Java, from http://stackoverflow.com/questions/2024273/convert-a-two-digit-year-to-a-four-digit-year:
//   now-80 - now+20
// or FileMaker Pro, from http://www.filemaker.com/12help/html/add_view_data.4.21.html:
//   now-70 - now+30
// but python strptime etc, via http://docs.python.org/py3k/library/time.html:
//   1969-2068 (super forward-looking, but static, not sliding!)
// lets go with now-70 to now+30, and if anyone runs into this problem they can learn
// the hard way not to use 2-digit years, as no choice we make now will cover all possibilities.
// mostly this will all be taken care of in initial parsing, should only be an issue
// for hand-entered data
// currently (2012) this range is:
//   1942-2041
lib.dateTime2ms = function(s) {
    var y,m,d,h;
    // split date and time parts
    s=String(s).split(' ');
    if(s.length>2) { return false; }
    var p = s[0].split('-'); // date part
    if(p.length>3 || (p.length!=3 && s.length>1)) { return false; }
    // year
    if(p[0].length==4) { y = Number(p[0]); }
    else if(p[0].length==2) {
        var yNow=new Date().getFullYear();
        y=((Number(p[0])-yNow+70)%100+200)%100+yNow-70;
    }
    else { return false; }
    if(!(y>=0)) { return false; }
    if(p.length==1) { return new Date(y,0,1).getTime(); } // year only

    // month
    m = Number(p[1])-1; // new Date() uses zero-based months
    if(p[1].length>2 || !(m>=0 && m<=11)) { return false; }
    if(p.length==2) { return new Date(y,m,1).getTime(); } // year-month

    // day
    d = Number(p[2]);
    if(p[2].length>2 || !(d>=1 && d<=31)) { return false; } // TODO: quick way to check # days in this month, instead of just using 31?

    // now save the date part
    d = new Date(y,m,d).getTime();
    if(s.length==1) { return d; } // year-month-day

    p = s[1].split(':');
    if(p.length>3) { return false; }

    // hour
    h = Number(p[0]);
    if(p[0].length>2 || !(h>=0 && h<=23)) { return false; }
    d += 3600000*h;
    if(p.length==1) { return d; }

    // minute
    m = Number(p[1]);
    if(p[1].length>2 || !(m>=0 && m<=59)) { return false; }
    d += 60000*m;
    if(p.length==2) { return d; }

    // second
    s = Number(p[2]);
    if(!(s>=0 && s<60)) { return false; }
    return d+s*1000;
};

// is string s a date? (see above)
lib.isDateTime = function(s){ return lib.dateTime2ms(s)!==false; };

// Turn ms into string of the form YYYY-mm-dd HH:MM:SS.sss
// Crop any trailing zeros in time, but always leave full date
// (we could choose to crop '-01' from date too)...
// Optional range r is the data range that applies, also in ms. If rng is big,
// the later parts of time will be omitted
lib.ms2DateTime = function(ms,r) {
    if(!r) { r=0; }
    var d = new Date(ms),
        s = $.datepicker.formatDate('yy-mm-dd',d);
    if(r<7776000000) {
        s+=' '+lib.lpad(d.getHours(),2);  // <90 days: add hours
        if(r<432000000) {
            s+=':'+lib.lpad(d.getMinutes(),2); // <5 days: add minutes
            if(r<10800000) {
                s+=':'+lib.lpad(d.getSeconds(),2);    // <3 hours: add seconds
                if(r<300000) {
                    s+='.'+lib.lpad(d.getMilliseconds(),3);  // <5 minutes: add ms
    }}}}
    return s.replace(/([:\s]00)*\.?[0]*$/,''); // strip trailing zeros
};

// findBin - find the bin for val - note that it can return outside the bin range
// any pos. or neg. integer for linear bins, or -1 or bins.length-1 for explicit.
// bins is either an object {start,size,end} or an array length #bins+1
// bins can be either increasing or decreasing but must be monotonic
// for linear bins, we can just calculate. For listed bins, run a binary search
// linelow (truthy) says the bin boundary should be attributed to the lower bin
// rather than the default upper bin
lib.findBin = function(val,bins,linelow) {
    if($.isNumeric(bins.start)) {
        return linelow ?
            Math.ceil((val-bins.start)/bins.size)-1 :
            Math.floor((val-bins.start)/bins.size);
    }
    else {
        var n1=0, n2=bins.length, c=0, sign=(bins[bins.length-1]>bins[0]?1:-1), test;
        if(bins[bins.length-1]>bins[0]) {
            test = linelow ? function(a,b){return a<b;} : function(a,b){return a<=b;};
        }
        else{
            test = linelow ? function(a,b){return a>=b;} : function(a,b){return a>b;};
        }
        while(n1<n2 && c++<100){ // c is just to avoid infinite loops if there's an error
            n=Math.floor((n1+n2)/2);
            if(test(bins[n],val)) { n1=n+1; }
            else { n2=n; }
        }
        if(c>90) { console.log('Long binary search...'); }
        return n1-1;
    }
};

// find distinct values in an array, lumping together ones that appear to
// just be off by a rounding error
// return the distinct values and the minimum difference between any two
lib.distinctVals = function(vals) {
    vals.sort(function(a,b){ return a-b; });
    var l = vals.length-1,
        minDiff = (vals[l]-vals[0])||1,
        errDiff = minDiff/(l||1)/10000,
        v2=[vals[0]];
    for(var i=0;i<l;i++) {
        if(vals[i+1]>vals[i]+errDiff) { // make sure values aren't just off by a rounding error
            minDiff=Math.min(minDiff,vals[i+1]-vals[i]);
            v2.push(vals[i+1]);
        }
    }
    return {vals:v2,minDiff:minDiff};
};

// convert a string s (such as 'xaxis.range[0]')
// representing a property of nested object o into set and get methods
// also return the string and object so we don't have to keep track of them
lib.nestedProperty = function(o,s) {
    var cont = o,
        aa = s.split('.'), i, j=0;
    // check for parts of the nesting hierarchy that are numbers (ie array elements)
    while(j<aa.length) {
        // look for non-bracket chars, then any number of [##] blocks
        var indexed = String(aa[j]).match(/^([^\[\]]+)((\[\-?[0-9]*\])+)$/);
        if(indexed) {
            var indices = indexed[2].substr(1,indexed[2].length-2).split('][');
            aa.splice(j,1,indexed[1]);
            for(i=0; i<indices.length; i++) {
                j++;
                aa.splice(j,0,Number(indices[i]));
            }
        }
        j++;
    }

    // Special array index -1 gets and sets properties of an entire
    // array at once.
    // eg: "annotations[-1].showarrow" sets showarrow for all annotations
    // set() can take either a single value to apply to all or an array
    // to apply different to each entry. Get can also return either
    var suffix = s.substr(s.indexOf('[-1]')+4), npArray;
    if(suffix.charAt(0)=='.') { suffix = suffix.substr(1); }
    function subNP(entry) { return lib.nestedProperty(entry,suffix); }
    function subSet(v) {
        for(i=0; i<npArray.length; i++) {
            npArray[i].set($.isArray(v) ? v[i%v.length] : v);
        }
    }
    function subGet(v) {
        var allsame = true, out = [];
        for(i=0; i<npArray.length; i++) {
            out[i] = npArray[i].get();
            if(out[i]!=out[0]) { allsame = false; }
        }
        return allsame ? out[0] : out;
    }

    // dive in to the 2nd to last level
    for(j=0; j<aa.length-1; j++) {
        if(aa[j]==-1) {
            npArray = cont.map(subNP);
            return {
                set: subSet,
                get: subGet,
                astr: s,
                parts: aa,
                obj: o
            };
        }
        // make the heirarchy if it doesn't exist
        if(!(aa[j] in cont)) {
            cont[aa[j]] = (typeof aa[j+1]==='string') ? {} : [];
        }
        cont = cont[aa[j]];
    }
    var prop = aa[j];

    return {
        set: function(v){
                if(v===undefined || v===null) { delete cont[prop]; }
                else { cont[prop]=v; }
            },
        get:function(){ return cont[prop]; },
        astr:s,
        parts:aa,
        obj:o
    };
};

// to prevent all event bubbling, in particular text selection during drag.
// see http://stackoverflow.com/questions/5429827/how-can-i-prevent-text-element-selection-with-cursor-drag
// for maximum effect use:
//      return pauseEvent(e);
lib.pauseEvent = function(e){
    if(e.stopPropagation) e.stopPropagation();
    if(e.preventDefault) e.preventDefault();
    e.cancelBubble=true;
    e.returnValue=false;
    return false;
};

// pad a number with zeroes, to given # of digits before the decimal point
lib.lpad = function(val,digits){
    return String(val+Math.pow(10,digits)).substr(1);
};

// STATISTICS FUNCTIONS

// aggregate numeric values, throwing out non-numerics.
//   f: aggregation function (ie Math.min, etc)
//   v: initial value (continuing from previous calls)
//      if there's no continuing value , use null for selector-type
//      functions (max,min), or 0 for summations
//   a: array to aggregate
//   len: maximum length of a to aggregate
lib.aggNums = function(f,v,a,len) {
    if(!len) { len=a.length; }
    if(!$.isNumeric(v)) { v=false; }
    for(i=0; i<len; i++) {
        if(!$.isNumeric(v)) { v=a[i]; }
        else if($.isNumeric(a[i])) { v=f(v,a[i]); }
    }
    return v;
};

// mean & std dev functions using aggNums, so it handles non-numerics nicely
// even need to use aggNums instead of .length, so we toss out non-numerics there
lib.len = function(data) { return lib.aggNums(function(a,b){return a+1;},0,data); };

lib.mean = function(data,len) {
    if(!len) { len = lib.len(data); }
    return lib.aggNums(function(a,b){return a+b;},0,data)/len;
};

lib.stdev = function(data,len,mean) {
    if(!len) { len = lib.len(data); }
    if(!$.isNumeric(mean)) { mean = lib.aggNums(function(a,b){return a+b;},0,data)/len; }
    return Math.sqrt(lib.aggNums(function(a,b){return a+Math.pow(b-mean,2);},0,data)/len);
};

// ------------------------------------------
// debugging tools
// ------------------------------------------

lib.VERBOSE = false; // set to true to get a lot more logging and tracing
lib.TIMER = new Date().getTime(); // first markTime call will return time from page load

// console.log that only runs if VERBOSE is on
lib.log = function(){ if(lib.VERBOSE){ console.log.apply(console,arguments); } };

// markTime - for debugging, mark the number of milliseconds since the previous call to markTime
// and log some arbitrary info too
lib.markTime = function(v){
    if(!lib.VERBOSE) { return; }
    var t2 = new Date().getTime();
    console.log(v,t2-lib.TIMER,'(msec)');
    lib.TIMER=t2;
};

// constrain - restrict a number v to be between v0 and v1
lib.constrain = function(v,v0,v1) { return Math.max(v0,Math.min(v1,v)); };

// similar to OS X's "growl" notifier
lib.notifier = function(text,tm){
    var num_notifs = $('div.notifier').length, mt = (num_notifs*100)+20;

    if( $('div.notifier').text().indexOf(text) > 0 ) return;

    var n = $('
'+
        '<button class="close" data-dismiss="alert" style="opacity:1;text-shadow:none;color:white;">×</button>'+
        '

'+text+'

');

    n.appendTo('.middle-center,#embedded_graph')
        .css({ 'margin-top':mt })
        .fadeIn(2000)
        .delay(tm=='long' ? 2000 : 1000)
        .fadeOut(2000,function(){ n.remove(); });
};

// do two bounding boxes from getBoundingClientRect,
// ie {left,right,top,bottom,width,height}, overlap?
lib.bBoxIntersect = function(a,b){
    return (a.left<=b.right && b.left<=a.right && a.top<=b.bottom && b.top<=a.bottom);
};

// minor performance booster for d3...
lib.identity = function(d){ return d; };

}()); // end Lib object definition<commit_msg>Operation Clean Master - restyle notifier<commit_after>// common library functions, mostly for plotting but used elsewhere too
(function() {
if(!window.Plotly) { window.Plotly = {}; }
var lib = Plotly.Lib = {};

// dateTime2ms - make string s of the form YYYY-mm-dd HH:MM:SS.sss
// into milliseconds (relative to 1970-01-01, per javascript standard)
// may truncate after any full field, and sss can be any length
// even >3 digits, though javascript dates truncate to milliseconds
// returns false if it doesn't find a date

// 2-digit to 4-digit year conversion, where to cut off?
// from http://support.microsoft.com/kb/244664:
//   1930-2029 (the most retro of all...)
// but in my mac chrome from eg. d=new Date(Date.parse('8/19/50')):
//   1950-2049
// by Java, from http://stackoverflow.com/questions/2024273/convert-a-two-digit-year-to-a-four-digit-year:
//   now-80 - now+20
// or FileMaker Pro, from http://www.filemaker.com/12help/html/add_view_data.4.21.html:
//   now-70 - now+30
// but python strptime etc, via http://docs.python.org/py3k/library/time.html:
//   1969-2068 (super forward-looking, but static, not sliding!)
// lets go with now-70 to now+30, and if anyone runs into this problem they can learn
// the hard way not to use 2-digit years, as no choice we make now will cover all possibilities.
// mostly this will all be taken care of in initial parsing, should only be an issue
// for hand-entered data
// currently (2012) this range is:
//   1942-2041
lib.dateTime2ms = function(s) {
    var y,m,d,h;
    // split date and time parts
    s=String(s).split(' ');
    if(s.length>2) { return false; }
    var p = s[0].split('-'); // date part
    if(p.length>3 || (p.length!=3 && s.length>1)) { return false; }
    // year
    if(p[0].length==4) { y = Number(p[0]); }
    else if(p[0].length==2) {
        var yNow=new Date().getFullYear();
        y=((Number(p[0])-yNow+70)%100+200)%100+yNow-70;
    }
    else { return false; }
    if(!(y>=0)) { return false; }
    if(p.length==1) { return new Date(y,0,1).getTime(); } // year only

    // month
    m = Number(p[1])-1; // new Date() uses zero-based months
    if(p[1].length>2 || !(m>=0 && m<=11)) { return false; }
    if(p.length==2) { return new Date(y,m,1).getTime(); } // year-month

    // day
    d = Number(p[2]);
    if(p[2].length>2 || !(d>=1 && d<=31)) { return false; } // TODO: quick way to check # days in this month, instead of just using 31?

    // now save the date part
    d = new Date(y,m,d).getTime();
    if(s.length==1) { return d; } // year-month-day

    p = s[1].split(':');
    if(p.length>3) { return false; }

    // hour
    h = Number(p[0]);
    if(p[0].length>2 || !(h>=0 && h<=23)) { return false; }
    d += 3600000*h;
    if(p.length==1) { return d; }

    // minute
    m = Number(p[1]);
    if(p[1].length>2 || !(m>=0 && m<=59)) { return false; }
    d += 60000*m;
    if(p.length==2) { return d; }

    // second
    s = Number(p[2]);
    if(!(s>=0 && s<60)) { return false; }
    return d+s*1000;
};

// is string s a date? (see above)
lib.isDateTime = function(s){ return lib.dateTime2ms(s)!==false; };

// Turn ms into string of the form YYYY-mm-dd HH:MM:SS.sss
// Crop any trailing zeros in time, but always leave full date
// (we could choose to crop '-01' from date too)...
// Optional range r is the data range that applies, also in ms. If rng is big,
// the later parts of time will be omitted
lib.ms2DateTime = function(ms,r) {
    if(!r) { r=0; }
    var d = new Date(ms),
        s = $.datepicker.formatDate('yy-mm-dd',d);
    if(r<7776000000) {
        s+=' '+lib.lpad(d.getHours(),2);  // <90 days: add hours
        if(r<432000000) {
            s+=':'+lib.lpad(d.getMinutes(),2); // <5 days: add minutes
            if(r<10800000) {
                s+=':'+lib.lpad(d.getSeconds(),2);    // <3 hours: add seconds
                if(r<300000) {
                    s+='.'+lib.lpad(d.getMilliseconds(),3);  // <5 minutes: add ms
    }}}}
    return s.replace(/([:\s]00)*\.?[0]*$/,''); // strip trailing zeros
};

// findBin - find the bin for val - note that it can return outside the bin range
// any pos. or neg. integer for linear bins, or -1 or bins.length-1 for explicit.
// bins is either an object {start,size,end} or an array length #bins+1
// bins can be either increasing or decreasing but must be monotonic
// for linear bins, we can just calculate. For listed bins, run a binary search
// linelow (truthy) says the bin boundary should be attributed to the lower bin
// rather than the default upper bin
lib.findBin = function(val,bins,linelow) {
    if($.isNumeric(bins.start)) {
        return linelow ?
            Math.ceil((val-bins.start)/bins.size)-1 :
            Math.floor((val-bins.start)/bins.size);
    }
    else {
        var n1=0, n2=bins.length, c=0, sign=(bins[bins.length-1]>bins[0]?1:-1), test;
        if(bins[bins.length-1]>bins[0]) {
            test = linelow ? function(a,b){return a<b;} : function(a,b){return a<=b;};
        }
        else{
            test = linelow ? function(a,b){return a>=b;} : function(a,b){return a>b;};
        }
        while(n1<n2 && c++<100){ // c is just to avoid infinite loops if there's an error
            n=Math.floor((n1+n2)/2);
            if(test(bins[n],val)) { n1=n+1; }
            else { n2=n; }
        }
        if(c>90) { console.log('Long binary search...'); }
        return n1-1;
    }
};

// find distinct values in an array, lumping together ones that appear to
// just be off by a rounding error
// return the distinct values and the minimum difference between any two
lib.distinctVals = function(vals) {
    vals.sort(function(a,b){ return a-b; });
    var l = vals.length-1,
        minDiff = (vals[l]-vals[0])||1,
        errDiff = minDiff/(l||1)/10000,
        v2=[vals[0]];
    for(var i=0;i<l;i++) {
        if(vals[i+1]>vals[i]+errDiff) { // make sure values aren't just off by a rounding error
            minDiff=Math.min(minDiff,vals[i+1]-vals[i]);
            v2.push(vals[i+1]);
        }
    }
    return {vals:v2,minDiff:minDiff};
};

// convert a string s (such as 'xaxis.range[0]')
// representing a property of nested object o into set and get methods
// also return the string and object so we don't have to keep track of them
lib.nestedProperty = function(o,s) {
    var cont = o,
        aa = s.split('.'), i, j=0;
    // check for parts of the nesting hierarchy that are numbers (ie array elements)
    while(j<aa.length) {
        // look for non-bracket chars, then any number of [##] blocks
        var indexed = String(aa[j]).match(/^([^\[\]]+)((\[\-?[0-9]*\])+)$/);
        if(indexed) {
            var indices = indexed[2].substr(1,indexed[2].length-2).split('][');
            aa.splice(j,1,indexed[1]);
            for(i=0; i<indices.length; i++) {
                j++;
                aa.splice(j,0,Number(indices[i]));
            }
        }
        j++;
    }

    // Special array index -1 gets and sets properties of an entire
    // array at once.
    // eg: "annotations[-1].showarrow" sets showarrow for all annotations
    // set() can take either a single value to apply to all or an array
    // to apply different to each entry. Get can also return either
    var suffix = s.substr(s.indexOf('[-1]')+4), npArray;
    if(suffix.charAt(0)=='.') { suffix = suffix.substr(1); }
    function subNP(entry) { return lib.nestedProperty(entry,suffix); }
    function subSet(v) {
        for(i=0; i<npArray.length; i++) {
            npArray[i].set($.isArray(v) ? v[i%v.length] : v);
        }
    }
    function subGet(v) {
        var allsame = true, out = [];
        for(i=0; i<npArray.length; i++) {
            out[i] = npArray[i].get();
            if(out[i]!=out[0]) { allsame = false; }
        }
        return allsame ? out[0] : out;
    }

    // dive in to the 2nd to last level
    for(j=0; j<aa.length-1; j++) {
        if(aa[j]==-1) {
            npArray = cont.map(subNP);
            return {
                set: subSet,
                get: subGet,
                astr: s,
                parts: aa,
                obj: o
            };
        }
        // make the heirarchy if it doesn't exist
        if(!(aa[j] in cont)) {
            cont[aa[j]] = (typeof aa[j+1]==='string') ? {} : [];
        }
        cont = cont[aa[j]];
    }
    var prop = aa[j];

    return {
        set: function(v){
                if(v===undefined || v===null) { delete cont[prop]; }
                else { cont[prop]=v; }
            },
        get:function(){ return cont[prop]; },
        astr:s,
        parts:aa,
        obj:o
    };
};

// to prevent all event bubbling, in particular text selection during drag.
// see http://stackoverflow.com/questions/5429827/how-can-i-prevent-text-element-selection-with-cursor-drag
// for maximum effect use:
//      return pauseEvent(e);
lib.pauseEvent = function(e){
    if(e.stopPropagation) e.stopPropagation();
    if(e.preventDefault) e.preventDefault();
    e.cancelBubble=true;
    e.returnValue=false;
    return false;
};

// pad a number with zeroes, to given # of digits before the decimal point
lib.lpad = function(val,digits){
    return String(val+Math.pow(10,digits)).substr(1);
};

// STATISTICS FUNCTIONS

// aggregate numeric values, throwing out non-numerics.
//   f: aggregation function (ie Math.min, etc)
//   v: initial value (continuing from previous calls)
//      if there's no continuing value , use null for selector-type
//      functions (max,min), or 0 for summations
//   a: array to aggregate
//   len: maximum length of a to aggregate
lib.aggNums = function(f,v,a,len) {
    if(!len) { len=a.length; }
    if(!$.isNumeric(v)) { v=false; }
    for(i=0; i<len; i++) {
        if(!$.isNumeric(v)) { v=a[i]; }
        else if($.isNumeric(a[i])) { v=f(v,a[i]); }
    }
    return v;
};

// mean & std dev functions using aggNums, so it handles non-numerics nicely
// even need to use aggNums instead of .length, so we toss out non-numerics there
lib.len = function(data) { return lib.aggNums(function(a,b){return a+1;},0,data); };

lib.mean = function(data,len) {
    if(!len) { len = lib.len(data); }
    return lib.aggNums(function(a,b){return a+b;},0,data)/len;
};

lib.stdev = function(data,len,mean) {
    if(!len) { len = lib.len(data); }
    if(!$.isNumeric(mean)) { mean = lib.aggNums(function(a,b){return a+b;},0,data)/len; }
    return Math.sqrt(lib.aggNums(function(a,b){return a+Math.pow(b-mean,2);},0,data)/len);
};

// ------------------------------------------
// debugging tools
// ------------------------------------------

lib.VERBOSE = false; // set to true to get a lot more logging and tracing
lib.TIMER = new Date().getTime(); // first markTime call will return time from page load

// console.log that only runs if VERBOSE is on
lib.log = function(){ if(lib.VERBOSE){ console.log.apply(console,arguments); } };

// markTime - for debugging, mark the number of milliseconds since the previous call to markTime
// and log some arbitrary info too
lib.markTime = function(v){
    if(!lib.VERBOSE) { return; }
    var t2 = new Date().getTime();
    console.log(v,t2-lib.TIMER,'(msec)');
    lib.TIMER=t2;
};

// constrain - restrict a number v to be between v0 and v1
lib.constrain = function(v,v0,v1) { return Math.max(v0,Math.min(v1,v)); };

// similar to OS X's "growl" notifier
lib.notifier = function(text,tm){
    var num_notifs = $('div.notifier').length, mt = (num_notifs*100)+20;

    if( $('div.notifier').text().indexOf(text) > 0 ) return;

    var n = $('
'+
        '<button class="close" data-dismiss="alert" style="opacity:1;text-shadow:none;color:white;">×</button>'+
        '

'+text+'

');

    n.appendTo('.middle-center,#embedded_graph')
        .css({ 'margin-top':mt })
        .fadeIn(2000)
        .delay(tm=='long' ? 2000 : 1000)
        .fadeOut(2000,function(){ n.remove(); });
};

// do two bounding boxes from getBoundingClientRect,
// ie {left,right,top,bottom,width,height}, overlap?
lib.bBoxIntersect = function(a,b){
    return (a.left<=b.right && b.left<=a.right && a.top<=b.bottom && b.top<=a.bottom);
};

// minor performance booster for d3...
lib.identity = function(d){ return d; };

}()); // end Lib object definition<|endoftext|>


<commit_before>/* global angular */

(function () {

    angular
        .module('groups')
        .component('scientillaGroupForm', {
            templateUrl: 'partials/scientilla-group-form.html',
            controller: GroupFormController,
            controllerAs: 'vm',
            bindings: {
                group: "<",
                onFailure: "&",
                onSubmit: "&",
                checkAndClose: "&"
            }
        });

    GroupFormController.$inject = [
        'GroupsService',
        'Notification',
        'AuthService',
        '$scope',
        'groupTypes',
        'groupTypeLabels',
        'ValidateService',
        '$timeout',
        'EventsService'
    ];

    function GroupFormController(
        GroupsService,
        Notification,
        AuthService,
        $scope,
        groupTypes,
        groupTypeLabels,
        ValidateService,
        $timeout,
        EventsService
    ) {
        const vm = this;
        vm.getUsersQuery = getUsersQuery;
        vm.cancel = cancel;
        vm.isValid = isValid;
        vm.submit = submit;
        vm.validate = validate;
        vm.fieldValueHasChanged = fieldValueHasChanged;

        vm.errors = [];
        vm.errorText = '';

        let formValues = {};
        let originalGroupJson = '';
        let timeout;
        const watchers = [];

        const delay = 500;

        const requiredFields = [
            'name'
        ];

        const rules = [];

        const types = _.cloneDeep(groupTypes);
        delete types.PROJECT;

        vm.formStructure = {
            name: {
                inputType: 'text',
                label: 'Title',
                defaultValue: vm.group.name || '',
                disabled: isDisabled(vm.group),
                ngIf: isAdmin,
                required: true,
                type: 'field'
            },
            slug: {
                inputType: 'text',
                label: 'Slug',
                defaultValue: vm.group.slug,
                disabled: isDisabled(vm.group),
                ngIf: isAdmin,
                type: 'field'
            },
            shortname: {
                inputType: 'text',
                label: 'Short Name',
                defaultValue: vm.group.shortname,
                disabled: isDisabled(vm.group),
                type: 'field'
            },
            description: {
                inputType: 'text',
                label: 'Description',
                defaultValue: vm.group.description,
                type: 'field'
            },
            scopusId: {
                inputType: 'text',
                label: 'Scopus ID',
                defaultValue: vm.group.scopusId,
                type: 'field'
            },
            type: {
                inputType: 'select',
                label: 'Group Type',
                defaultValue: vm.group.type || groupTypes.RESEARCH_LINE,
                values: Object.keys(types).map(k => ({label: groupTypeLabels[k], value: types[k]})),
                disabled: isDisabled(vm.group),
                ngIf: isAdmin,
                type: 'field'
            },
            code: {
                inputType: 'text',
                label: 'CDR/CODE',
                defaultValue: vm.group.code,
                disabled: isDisabled(vm.group),
                type: 'field'
            },
            active: {
                inputType: 'select',
                label: 'Active',
                defaultValue: vm.group.active === undefined ? true : vm.group.active,
                values: [
                    {label: 'Yes', value: true},
                    {label: 'No', value: false}
                ],
                disabled: isDisabled(vm.group),
                ngIf: isAdmin,
                type: 'field'
            },
            onChange: function (values) {
                formValues = values;
                vm.formStructure.code.disabled = isDisabled(values);
                vm.formStructure.type.disabled = isDisabled(values);
                vm.formStructure.name.disabled = isDisabled(values);
                vm.formStructure.slug.disabled = isDisabled(values);
                vm.formStructure.shortname.disabled = isDisabled(values);
                vm.formStructure.active.disabled = isDisabled(values);
            }
        };

        vm.$onInit = function () {
            switch(vm.group.type) {
                case groupTypes.INSTITUTE:
                case groupTypes.CENTER:
                case groupTypes.RESEARCH_DOMAIN:
                case groupTypes.RESEARCH_LINE:
                case groupTypes.FACILITY:
                case groupTypes.DIRECTORATE:
                    vm.formStructure.type.values = [{label: groupTypeLabels[vm.group.type], value: newGroupTypes[vm.group.type]}];
                    vm.formStructure.type.defaultValue = vm.group.type;
                    break;
                default:
                    const newGroupTypes = _.cloneDeep(groupTypes);
                    delete newGroupTypes.INSTITUTE;
                    delete newGroupTypes.CENTER;
                    delete newGroupTypes.RESEARCH_DOMAIN;
                    delete newGroupTypes.RESEARCH_LINE;
                    delete newGroupTypes.FACILITY;
                    delete newGroupTypes.DIRECTORATE;
                    delete newGroupTypes.PROJECT;
                    vm.formStructure.type.values = Object.keys(newGroupTypes).map(k => ({label: groupTypeLabels[k], value: newGroupTypes[k]}));
                    vm.formStructure.type.defaultValue = newGroupTypes.OTHER;
                    break;
            }

            delete vm.group.members;
            delete vm.group.memberships;

            watchers.push($scope.$watch('vm.group.name', nameChanged));

            const originalGroup = angular.copy(vm.group);
            if (!originalGroup.slug)
                originalGroup.slug = calculateSlug(originalGroup);

            if (originalGroup.type === undefined)
                originalGroup.type = groupTypes.OTHER;

            if (originalGroup.active === undefined)
                originalGroup.active = true;

            originalGroupJson = angular.toJson(originalGroup);
        };

        vm.$onDestroy = function () {
            for (const watcher in watchers) {
                if (_.isFunction(watcher)) {
                    watcher();
                }
            }
        };

        function validate(field = false) {
            if (field && field.name) {
                vm.errors[field.name] = ValidateService.validate(formValues, field.name, requiredFields, rules);

                if (typeof vm.errors[field.name] === 'undefined') {
                    delete vm.errors[field.name];
                }
            } else {
                vm.errors = ValidateService.validate(formValues, false, requiredFields, rules);
            }

            if (Object.keys(vm.errors).length > 0) {
                vm.errorText = 'Please correct the errors on this form!';
            } else {
                vm.errorText = '';
            }
        }

        function fieldValueHasChanged(field = false) {
            if (!field || !_.has(field, 'name') || _.isEmpty(field.name)) {
                return;
            }

            $timeout.cancel(timeout);

            timeout = $timeout(function () {
                validate(field);
            }, delay);
        }

        function isValid() {
            return Object.keys(vm.errors).length === 0;
        }

        function nameChanged() {
            if (!vm.group)
                return;
            if (!vm.group.id) {
                vm.group.slug = calculateSlug(vm.group);
            }
        }

        function calculateSlug(group) {
            const name = group.name ? group.name : "";
            return name.toLowerCase().replace(/\s+/gi, '-');
        }

        function submit(group) {
            if (!group) return;

            validate();

            if (Object.keys(vm.errors).length > 0) {
                return;
            }

            updateGroupData();

            GroupsService.doSave(vm.group)
                .then(function () {
                    vm.errorText = '';
                    Notification.success("Group data saved");
                    EventsService.publish(EventsService.GROUP_UPDATED);
                    originalGroupJson = angular.copy(vm.group);
                    if (_.isFunction(vm.onSubmit()))
                        vm.onSubmit()(1);
                }, function (res) {
                    var errors = res.data.invalidAttributes;
                    vm.errors = {};

                    angular.forEach(errors, function (fields, fieldIndex) {
                        angular.forEach(fields, function (error, errorIndex) {
                            if (error.rule === 'required') {
                                error.message = 'This field is required.';
                                errors[fieldIndex][errorIndex] = error;
                            }
                        });

                        vm.errors[fieldIndex] = errors[fieldIndex];
                    });

                    vm.errorText = 'Please correct the errors on this form!';
                });
        }

        function getUsersQuery(searchText) {
            const qs = {where: {or: [
                {name: {contains: searchText}},
                {surname: {contains: searchText}},
                {displayName: {contains: searchText}},
                {displaySurname: {contains: searchText}},
            ]}};
            const model = 'users';
            return {model: model, qs: qs};
        }

        function cancel() {
            updateGroupData();
            vm.checkAndClose()(() => originalGroupJson === angular.toJson(vm.group));
        }

        function updateGroupData() {
            for (const key of Object.keys(vm.formStructure)) {
                vm.group[key] = formValues[key];
            }

            if (!vm.group.slug)
                vm.group.slug = calculateSlug(formValues);

        }

        function isAdmin() {
            return AuthService.user.isAdmin();
        }

        function isDisabled(group) {
            switch (group.type) {
                case types.INSTITUTE:
                case types.CENTER:
                case types.RESEARCH_DOMAIN:
                case types.RESEARCH_LINE:
                case types.FACILITY:
                case types.DIRECTORATE:
                    return true;
                default:
                    return false;
            }
        }
    }
})();
<commit_msg>Fix use correct variable<commit_after>/* global angular */

(function () {

    angular
        .module('groups')
        .component('scientillaGroupForm', {
            templateUrl: 'partials/scientilla-group-form.html',
            controller: GroupFormController,
            controllerAs: 'vm',
            bindings: {
                group: "<",
                onFailure: "&",
                onSubmit: "&",
                checkAndClose: "&"
            }
        });

    GroupFormController.$inject = [
        'GroupsService',
        'Notification',
        'AuthService',
        '$scope',
        'groupTypes',
        'groupTypeLabels',
        'ValidateService',
        '$timeout',
        'EventsService'
    ];

    function GroupFormController(
        GroupsService,
        Notification,
        AuthService,
        $scope,
        groupTypes,
        groupTypeLabels,
        ValidateService,
        $timeout,
        EventsService
    ) {
        const vm = this;
        vm.getUsersQuery = getUsersQuery;
        vm.cancel = cancel;
        vm.isValid = isValid;
        vm.submit = submit;
        vm.validate = validate;
        vm.fieldValueHasChanged = fieldValueHasChanged;

        vm.errors = [];
        vm.errorText = '';

        let formValues = {};
        let originalGroupJson = '';
        let timeout;
        const watchers = [];

        const delay = 500;

        const requiredFields = [
            'name'
        ];

        const rules = [];

        const types = _.cloneDeep(groupTypes);
        delete types.PROJECT;

        vm.formStructure = {
            name: {
                inputType: 'text',
                label: 'Title',
                defaultValue: vm.group.name || '',
                disabled: isDisabled(vm.group),
                ngIf: isAdmin,
                required: true,
                type: 'field'
            },
            slug: {
                inputType: 'text',
                label: 'Slug',
                defaultValue: vm.group.slug,
                disabled: isDisabled(vm.group),
                ngIf: isAdmin,
                type: 'field'
            },
            shortname: {
                inputType: 'text',
                label: 'Short Name',
                defaultValue: vm.group.shortname,
                disabled: isDisabled(vm.group),
                type: 'field'
            },
            description: {
                inputType: 'text',
                label: 'Description',
                defaultValue: vm.group.description,
                type: 'field'
            },
            scopusId: {
                inputType: 'text',
                label: 'Scopus ID',
                defaultValue: vm.group.scopusId,
                type: 'field'
            },
            type: {
                inputType: 'select',
                label: 'Group Type',
                defaultValue: vm.group.type || groupTypes.RESEARCH_LINE,
                values: Object.keys(types).map(k => ({label: groupTypeLabels[k], value: types[k]})),
                disabled: isDisabled(vm.group),
                ngIf: isAdmin,
                type: 'field'
            },
            code: {
                inputType: 'text',
                label: 'CDR/CODE',
                defaultValue: vm.group.code,
                disabled: isDisabled(vm.group),
                type: 'field'
            },
            active: {
                inputType: 'select',
                label: 'Active',
                defaultValue: vm.group.active === undefined ? true : vm.group.active,
                values: [
                    {label: 'Yes', value: true},
                    {label: 'No', value: false}
                ],
                disabled: isDisabled(vm.group),
                ngIf: isAdmin,
                type: 'field'
            },
            onChange: function (values) {
                formValues = values;
                vm.formStructure.code.disabled = isDisabled(values);
                vm.formStructure.type.disabled = isDisabled(values);
                vm.formStructure.name.disabled = isDisabled(values);
                vm.formStructure.slug.disabled = isDisabled(values);
                vm.formStructure.shortname.disabled = isDisabled(values);
                vm.formStructure.active.disabled = isDisabled(values);
            }
        };

        vm.$onInit = function () {
            switch(vm.group.type) {
                case groupTypes.INSTITUTE:
                case groupTypes.CENTER:
                case groupTypes.RESEARCH_DOMAIN:
                case groupTypes.RESEARCH_LINE:
                case groupTypes.FACILITY:
                case groupTypes.DIRECTORATE:
                    vm.formStructure.type.values = [{label: groupTypeLabels[vm.group.type], value: groupTypes[vm.group.type]}];
                    vm.formStructure.type.defaultValue = vm.group.type;
                    break;
                default:
                    const newGroupTypes = _.cloneDeep(groupTypes);
                    delete newGroupTypes.INSTITUTE;
                    delete newGroupTypes.CENTER;
                    delete newGroupTypes.RESEARCH_DOMAIN;
                    delete newGroupTypes.RESEARCH_LINE;
                    delete newGroupTypes.FACILITY;
                    delete newGroupTypes.DIRECTORATE;
                    delete newGroupTypes.PROJECT;
                    vm.formStructure.type.values = Object.keys(newGroupTypes).map(k => ({label: groupTypeLabels[k], value: newGroupTypes[k]}));
                    vm.formStructure.type.defaultValue = newGroupTypes.OTHER;
                    break;
            }

            delete vm.group.members;
            delete vm.group.memberships;

            watchers.push($scope.$watch('vm.group.name', nameChanged));

            const originalGroup = angular.copy(vm.group);
            if (!originalGroup.slug)
                originalGroup.slug = calculateSlug(originalGroup);

            if (originalGroup.type === undefined)
                originalGroup.type = groupTypes.OTHER;

            if (originalGroup.active === undefined)
                originalGroup.active = true;

            originalGroupJson = angular.toJson(originalGroup);
        };

        vm.$onDestroy = function () {
            for (const watcher in watchers) {
                if (_.isFunction(watcher)) {
                    watcher();
                }
            }
        };

        function validate(field = false) {
            if (field && field.name) {
                vm.errors[field.name] = ValidateService.validate(formValues, field.name, requiredFields, rules);

                if (typeof vm.errors[field.name] === 'undefined') {
                    delete vm.errors[field.name];
                }
            } else {
                vm.errors = ValidateService.validate(formValues, false, requiredFields, rules);
            }

            if (Object.keys(vm.errors).length > 0) {
                vm.errorText = 'Please correct the errors on this form!';
            } else {
                vm.errorText = '';
            }
        }

        function fieldValueHasChanged(field = false) {
            if (!field || !_.has(field, 'name') || _.isEmpty(field.name)) {
                return;
            }

            $timeout.cancel(timeout);

            timeout = $timeout(function () {
                validate(field);
            }, delay);
        }

        function isValid() {
            return Object.keys(vm.errors).length === 0;
        }

        function nameChanged() {
            if (!vm.group)
                return;
            if (!vm.group.id) {
                vm.group.slug = calculateSlug(vm.group);
            }
        }

        function calculateSlug(group) {
            const name = group.name ? group.name : "";
            return name.toLowerCase().replace(/\s+/gi, '-');
        }

        function submit(group) {
            if (!group) return;

            validate();

            if (Object.keys(vm.errors).length > 0) {
                return;
            }

            updateGroupData();

            GroupsService.doSave(vm.group)
                .then(function () {
                    vm.errorText = '';
                    Notification.success("Group data saved");
                    EventsService.publish(EventsService.GROUP_UPDATED);
                    originalGroupJson = angular.copy(vm.group);
                    if (_.isFunction(vm.onSubmit()))
                        vm.onSubmit()(1);
                }, function (res) {
                    var errors = res.data.invalidAttributes;
                    vm.errors = {};

                    angular.forEach(errors, function (fields, fieldIndex) {
                        angular.forEach(fields, function (error, errorIndex) {
                            if (error.rule === 'required') {
                                error.message = 'This field is required.';
                                errors[fieldIndex][errorIndex] = error;
                            }
                        });

                        vm.errors[fieldIndex] = errors[fieldIndex];
                    });

                    vm.errorText = 'Please correct the errors on this form!';
                });
        }

        function getUsersQuery(searchText) {
            const qs = {where: {or: [
                {name: {contains: searchText}},
                {surname: {contains: searchText}},
                {displayName: {contains: searchText}},
                {displaySurname: {contains: searchText}},
            ]}};
            const model = 'users';
            return {model: model, qs: qs};
        }

        function cancel() {
            updateGroupData();
            vm.checkAndClose()(() => originalGroupJson === angular.toJson(vm.group));
        }

        function updateGroupData() {
            for (const key of Object.keys(vm.formStructure)) {
                vm.group[key] = formValues[key];
            }

            if (!vm.group.slug)
                vm.group.slug = calculateSlug(formValues);

        }

        function isAdmin() {
            return AuthService.user.isAdmin();
        }

        function isDisabled(group) {
            switch (group.type) {
                case types.INSTITUTE:
                case types.CENTER:
                case types.RESEARCH_DOMAIN:
                case types.RESEARCH_LINE:
                case types.FACILITY:
                case types.DIRECTORATE:
                    return true;
                default:
                    return false;
            }
        }
    }
})();
<|endoftext|>


<commit_before>/**
 *
 *      ioBroker pushover Adapter
 *
 *      (c) 2014-2016 bluefox
 *
 *      MIT License
 *
 */

/* jshint -W097 */// jshint strict:false
/*jslint node: true */
'use strict';
var utils =    require(__dirname + '/lib/utils'); // Get common adapter utils
var Pushover = require('pushover-notifications');

var adapter = utils.adapter('pushover');

adapter.on('message', function (obj) {
    if (obj && obj.command === 'send') {
        processMessage(obj);
    }
    processMessages();
});

adapter.on('ready', function () {
    main();
});

var stopTimer       = null;
var pushover;
var lastMessageTime = 0;
var lastMessageText = '';

// Terminate adapter after 30 seconds idle
function stop() {
    if (stopTimer) {
        clearTimeout(stopTimer);
    }

    // Stop only if subscribe mode
    if (adapter.common && adapter.common.mode === 'subscribe') {
        stopTimer = setTimeout(function () {
            stopTimer = null;
            adapter.stop();
        }, 30000);
    }
}

function processMessage(obj) {
    if (!obj || !obj.message) return;

    // filter out double messages
    var json = JSON.stringify(obj.message);
    if (lastMessageTime && lastMessageText === JSON.stringify(obj.message) && new Date().getTime() - lastMessageTime < 1000) {
        adapter.log.debug('Filter out double message [first was for ' + (new Date().getTime() - lastMessageTime) + 'ms]: ' + json);
        return;
    }
    lastMessageTime = new Date().getTime();
    lastMessageText = json;

    if (stopTimer) clearTimeout(stopTimer);

    sendNotification(obj.message, function(err, response) {
        if (obj.callback) adapter.sendTo(obj.from, 'send', { error: err, response: response}, obj.callback);
    });

    stop();
}

function processMessages() {
    adapter.getMessage(function (err, obj) {
        if (obj) {
            processMessage(obj);
            processMessages();
        }
    });
}

function main() {
    // Adapter is started only if some one writes into "system.adapter.pushover.X.messagebox" new value
    processMessages();
    stop();
}

function sendNotification(message, callback) {
    if (!message) message = {};

    if (!pushover) {
        if (adapter.config.user && adapter.config.token) {
            pushover = new Pushover({
                user:  adapter.config.user,
                token: adapter.config.token
            });
        } else {
            adapter.log.error('Cannot send notification while not configured');
        }
    }

    if (!pushover) return;

    if (typeof message !== 'object') {
        message = {message: message};
    }

    message.title     = message.title     || adapter.config.title;
    message.sound     = message.sound     || (adapter.config.sound ? adapter.config.sound : undefined);
    message.priority  = message.priority  || adapter.config.priority;
    message.url       = message.url       || adapter.config.url;
    message.url_title = message.url_title || adapter.config.url_title;
    message.device    = message.device    || adapter.config.device;

    // if timestamp in ms => make seconds // if greater than 2000.01.01 00:00:00
    if (message.timestamp && message.timestamp > 946681200000) {
        message.timestamp = Math.round(message.timestamp / 1000);
    }

    // mandatory parameters if priority is high (2)
    if (message.priority === 2) {
        message.retry  = parseInt(message.retry, 10)  || 60;
        message.expire = parseInt(message.expire, 10) || 3600;
    }

    adapter.log.info('Send pushover notification: ' + JSON.stringify(message));

    pushover.send(message, function (err, result) {
        if (err) {
            adapter.log.error('Cannot send notification: ' + JSON.stringify(err));
            if (callback) callback(err);
            return false;
        } else {
            if (callback) callback(null, result);
            return true;
        }
    });
}
<commit_msg>fix util.js usage and use utils.Adapter<commit_after>/**
 *
 *      ioBroker pushover Adapter
 *
 *      (c) 2014-2016 bluefox
 *
 *      MIT License
 *
 */

/* jshint -W097 */// jshint strict:false
/*jslint node: true */
'use strict';
var utils =    require(__dirname + '/lib/utils'); // Get common adapter utils
var Pushover = require('pushover-notifications');

var adapter = utils.Adapter('pushover');

adapter.on('message', function (obj) {
    if (obj && obj.command === 'send') {
        processMessage(obj);
    }
    processMessages();
});

adapter.on('ready', function () {
    main();
});

var stopTimer       = null;
var pushover;
var lastMessageTime = 0;
var lastMessageText = '';

// Terminate adapter after 30 seconds idle
function stop() {
    if (stopTimer) {
        clearTimeout(stopTimer);
    }

    // Stop only if subscribe mode
    if (adapter.common && adapter.common.mode === 'subscribe') {
        stopTimer = setTimeout(function () {
            stopTimer = null;
            adapter.stop();
        }, 30000);
    }
}

function processMessage(obj) {
    if (!obj || !obj.message) return;

    // filter out double messages
    var json = JSON.stringify(obj.message);
    if (lastMessageTime && lastMessageText === JSON.stringify(obj.message) && new Date().getTime() - lastMessageTime < 1000) {
        adapter.log.debug('Filter out double message [first was for ' + (new Date().getTime() - lastMessageTime) + 'ms]: ' + json);
        return;
    }
    lastMessageTime = new Date().getTime();
    lastMessageText = json;

    if (stopTimer) clearTimeout(stopTimer);

    sendNotification(obj.message, function(err, response) {
        if (obj.callback) adapter.sendTo(obj.from, 'send', { error: err, response: response}, obj.callback);
    });

    stop();
}

function processMessages() {
    adapter.getMessage(function (err, obj) {
        if (obj) {
            processMessage(obj);
            processMessages();
        }
    });
}

function main() {
    // Adapter is started only if some one writes into "system.adapter.pushover.X.messagebox" new value
    processMessages();
    stop();
}

function sendNotification(message, callback) {
    if (!message) message = {};

    if (!pushover) {
        if (adapter.config.user && adapter.config.token) {
            pushover = new Pushover({
                user:  adapter.config.user,
                token: adapter.config.token
            });
        } else {
            adapter.log.error('Cannot send notification while not configured');
        }
    }

    if (!pushover) return;

    if (typeof message !== 'object') {
        message = {message: message};
    }

    message.title     = message.title     || adapter.config.title;
    message.sound     = message.sound     || (adapter.config.sound ? adapter.config.sound : undefined);
    message.priority  = message.priority  || adapter.config.priority;
    message.url       = message.url       || adapter.config.url;
    message.url_title = message.url_title || adapter.config.url_title;
    message.device    = message.device    || adapter.config.device;

    // if timestamp in ms => make seconds // if greater than 2000.01.01 00:00:00
    if (message.timestamp && message.timestamp > 946681200000) {
        message.timestamp = Math.round(message.timestamp / 1000);
    }

    // mandatory parameters if priority is high (2)
    if (message.priority === 2) {
        message.retry  = parseInt(message.retry, 10)  || 60;
        message.expire = parseInt(message.expire, 10) || 3600;
    }

    adapter.log.info('Send pushover notification: ' + JSON.stringify(message));

    pushover.send(message, function (err, result) {
        if (err) {
            adapter.log.error('Cannot send notification: ' + JSON.stringify(err));
            if (callback) callback(err);
            return false;
        } else {
            if (callback) callback(null, result);
            return true;
        }
    });
}
<|endoftext|>