<commit_before>'use strict';
/*
 * Copyright 2014 Next Century Corporation
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

/**
 * This Angular JS directive adds a map to a page and has controls for selecting which data available through
 * a Neon connection should be plotted.
 *
 * @example
 *    <heat-map></heat-map>

 *    <div heat-map></div>
 *
 * @class neonDemo.directives.heatMap
 * @constructor
 */
angular.module('heatMapDirective', []).directive('heatMap', ['ConnectionService', '$timeout',
    function (connectionService, $timeout) {

        return {
            templateUrl: 'partials/heatMap.html',
            restrict: 'EA',
            scope: {
                // map of categories to colors used for the legend
                colorMappings: '&'
            },
            controller: function ($scope) {

            },
            link: function ($scope, element, attr) {

                element.addClass('heat-map');

                /**
                 * Initializes the name of the directive's scope variables
                 * and the Neon Messenger used to monitor data change events.
                 * @method initialize
                 */
                $scope.initialize = function () {
                    $scope.databaseName = '';
                    $scope.tableName = '';
                    $scope.fields = [];
                    $scope.latitudeField = '';
                    $scope.longitudeField = '';
                    $scope.sizeByField = '';
                    $scope.colorByField = '';
                    $scope.showPoints = false;
                    $scope.cacheMap = false;
                    $scope.initializing = true;

                    $scope.filterKey = neon.widget.getInstanceId("map");
                    $scope.showFilter = false;

                    // Setup our map.
                    $scope.mapId = uuid();
                    element.append('

');
                    $scope.map = new coreMap.Map($scope.mapId, {
                        height: 500,
                        width: "100%",
                        responsive: false,
                        onZoomRect: onZoomChanged
                    });

                    $scope.draw();
                    $scope.map.register("moveend", this, onMoved);

                    // Setup our messenger.
                    $scope.messenger = new neon.eventing.Messenger();

                    $scope.messenger.events({
                        activeDatasetChanged: onDatasetChanged,
                        filtersChanged: onFiltersChanged
                    });

                    // Enable the tooltips.
                    $(element).find('label.btn-default').tooltip();

                };

                var onMoved = function (message) {
                    // Commenting out for now until we decide whether or not to filter data queries based on visible area.
                    // May need to make this a user-toggleable capability.
                    // $scope.queryForMapData();
                };

                /**
                 * Event handler for filter changed events issued over Neon's messaging channels.
                 * @param {Object} message A Neon filter changed message.
                 * @method onFiltersChanged
                 * @private
                 */
                var onFiltersChanged = function (message) {
                    $scope.queryForMapData();
                };

                var onZoomChanged = function (bounds) {
                    var extent = boundsToExtent(bounds);
                    var filter = $scope.createFilterFromExtent(extent);
                    $scope.messenger.replaceFilter($scope.filterKey, filter, function () {
                        $scope.$apply(function () {
                            $scope.queryForMapData();
                            $scope.zoomRectBounds = {left: extent.minimumLongitude, bottom:extent.minimumLatitude, right: extent.maximumLongitude, top: extent.maximumLatitude};
                            // Show the Clear Filter button.
                            $scope.showFilter = true;
                            $scope.error = "";
                        });
                    }, function () {
                        // Notify the user of the error.
                        $scope.error = "Error: Failed to create filter.";
                    });
                };

                /**
                 * Event handler for dataset changed events issued over Neon's messaging channels.
                 * @param {Object} message A Neon dataset changed message.
                 * @param {String} message.database The database that was selected.
                 * @param {String} message.table The table within the database that was selected.
                 * @method onDatasetChanged
                 * @private
                 */
                var onDatasetChanged = function (message) {
                    $scope.initializing = true;
                    $scope.databaseName = message.database;
                    $scope.tableName = message.table;
                    $scope.latitudeField = "";
                    $scope.latitudeField = "";
                    $scope.longitudeField = "";
                    $scope.longitudeField = "";
                    $scope.colorByField = "";
                    $scope.colorByField = "";
                    $scope.sizeByField = "";
                    $scope.sizeByField = "";
                    $scope.zoomRectBounds = undefined;
                    // used to track the id of the rectangle drawn on the map so it can be removed when a new box is drawn
                    $scope.zoomRectId = undefined;
                    $scope.hideClearFilterButton();

                    // Repopulate the field selectors and get the default values.
                    connectionService.getActiveConnection().getFieldNames($scope.tableName, function (results) {
                        $scope.$apply(function () {
                            populateFieldNames(results);
                            $scope.latitudeField = connectionService.getFieldMapping($scope.database, $scope.tableName, "latitude");
                            $scope.latitudeField = $scope.latitudeField.mapping || $scope.fields[0];
                            $scope.longitudeField = connectionService.getFieldMapping($scope.database, $scope.tableName, "longitude");
                            $scope.longitudeField = $scope.longitudeField.mapping || $scope.fields[0];
                            $scope.colorByField = connectionService.getFieldMapping($scope.database, $scope.tableName, "color-by");
                            $scope.colorByField = $scope.colorByField.mapping || $scope.fields[0];
                            $scope.sizeByField = connectionService.getFieldMapping($scope.database, $scope.tableName, "size-by");
                            $scope.sizeByField = $scope.sizeByField.mapping || $scope.fields[0];
                            $timeout(function () {
                                $scope.initializing = false;
                                $scope.queryForMapData();
                            });
                        });
                    });
                };

                /**
                 * Helper method for setting the fields available for filter clauses.
                 * @param {Array} fields An array of field name strings.
                 * @method populateFieldNames
                 * @private
                 */
                var populateFieldNames = function (fields) {
                    $scope.fields = fields;
                };

                var boundsToExtent = function (bounds) {
                    var llPoint = new OpenLayers.LonLat(bounds.left, bounds.bottom);
                    var urPoint = new OpenLayers.LonLat(bounds.right, bounds.top);

                    var minLon = Math.min(llPoint.lon, urPoint.lon);
                    var maxLon = Math.max(llPoint.lon, urPoint.lon);

                    var minLat = Math.min(llPoint.lat, urPoint.lat);
                    var maxLat = Math.max(llPoint.lat, urPoint.lat);

                    return {
                        minimumLatitude: minLat,
                        minimumLongitude: minLon,
                        maximumLatitude: maxLat,
                        maximumLongitude: maxLon
                    };

                }

                var clearZoomRect = function() {
                    if ( $scope.zoomRectId !== undefined ) {
                        $scope.map.removeBox($scope.zoomRectId);
                        $scope.zoomRectId = undefined;
                    }
                }

                /**
                 * Triggers a Neon query that will aggregate the time data for the currently selected dataset.
                 * @method queryForMapData
                 */
                $scope.queryForMapData = function () {
                    if (!$scope.initializing && $scope.latitudeField !== "" && $scope.longitudeField !== "") {
                        var query = $scope.buildQuery();
                        connectionService.getActiveConnection().executeQuery(query, function (queryResults) {
                            $scope.$apply(function () {
                                $scope.updateMapData(queryResults);
                            });
                        });
                    }
                };

                /**
                 * Redraws the map
                 */
                $scope.draw = function () {
                    if (!$scope.initializing) {
                        $scope.map.draw();
                    }
                };

                /**
                 * Updates the data bound to the map managed by this directive.  This will trigger a change in
                 * the chart's visualization.
                 * @param {Object} queryResults Results returned from a Neon query.
                 * @param {Array} queryResults.data The aggregate numbers for the heat chart cells.
                 * @method updateMapData
                 */
                $scope.updateMapData = function (queryResults) {
                    $scope.map.setData(queryResults.data);
                    $scope.draw();
                    // color mappings need to be updated after drawing since they are set during drawing
                    $scope.colorMappings = $scope.map.getColorMappings();
                };

                $scope.buildQuery = function () {
                    var query = new neon.query.Query().selectFrom($scope.databaseName, $scope.tableName)
                    var groupByFields = [$scope.latitudeField, $scope.longitudeField];

                    if ($scope.colorByField) {
                        groupByFields.push($scope.colorByField);
                        query = query.groupBy($scope.latitudeField, $scope.longitudeField, $scope.colorByField)
                    }
                    else {
                        query = query.groupBy($scope.latitudeField, $scope.longitudeField);
                    }

                    if ($scope.sizeByField) {
                        query.aggregate(neon.query.SUM, $scope.sizeByField, $scope.sizeByField);
                    }
                    else {
                        query.aggregate(neon.query.COUNT, '*', coreMap.Map.DEFAULT_SIZE_MAPPING);
                    }

                    return query;
                }

                $scope.hideClearFilterButton = function () {
                    // hide the Clear Filter button.
                    $scope.showFilter = false;
                    $scope.error = "";
                };

                /**
                 * Create a Neon query to pull data limited to the current extent of the map.
                 * @method createFilterFromExtent
                 */
                $scope.createFilterFromExtent = function (extent) {

                    var leftClause = neon.query.where($scope.longitudeField, ">=", extent.minimumLongitude);
                    var rightClause = neon.query.where($scope.longitudeField, "<=", extent.maximumLongitude);
                    var bottomClause = neon.query.where($scope.latitudeField, ">=", extent.minimumLatitude);
                    var topClause = neon.query.where($scope.latitudeField, "<=", extent.maximumLatitude);
                    var filterClause = neon.query.and(leftClause, rightClause, bottomClause, topClause);

                    //Deal with different dateline crossing scenarios.
                    if (extent.minimumLongitude < -180 && extent.maximumLongitude > 180) {
                        filterClause = neon.query.and(topClause, bottomClause);
                    }
                    else if (extent.minimumLongitude < -180) {
                        leftClause = neon.query.where($scope.longitudeField, ">=", extent.minimumLongitude + 360);
                        var leftDateLine = neon.query.where($scope.longitudeField, "<=", 180);
                        var rightDateLine = neon.query.where($scope.longitudeField, ">=", -180);
                        var datelineClause = neon.query.or(neon.query.and(leftClause, leftDateLine), neon.query.and(rightClause, rightDateLine));
                        filterClause = neon.query.and(topClause, bottomClause, datelineClause);
                    }
                    else if (extent.maximumLongitude > 180) {
                        rightClause = neon.query.where($scope.longitudeField, "<=", extent.maximumLongitude - 360);
                        var rightDateLine = neon.query.where($scope.longitudeField, ">=", -180);
                        var leftDateLine = neon.query.where($scope.longitudeField, "<=", 180);
                        var datelineClause = neon.query.or(neon.query.and(leftClause, leftDateLine), neon.query.and(rightClause, rightDateLine));
                        filterClause = neon.query.and(topClause, bottomClause, datelineClause);
                    }

                    return new neon.query.Filter().selectFrom($scope.databaseName, $scope.tableName).where(filterClause);
                };

                /**
                 * Clear Neon query to pull data limited to the current extent of the map.
                 * @method clearFilter
                 */
                $scope.clearFilter = function () {
                    $scope.messenger.removeFilter($scope.filterKey, function () {
                        $scope.$apply(function () {
                            $scope.zoomRectBounds = undefined;
                            $scope.queryForMapData();
                            $scope.hideClearFilterButton();
                        });
                    }, function () {
                        // Notify the user of the error.
                        $scope.error = "Error: Failed to clear filter.";
                    });
                };

                // Update the latitude field used by the map.
                $scope.$watch('latitudeField', function (newVal, oldVal) {
                    if (newVal && newVal !== oldVal) {
                        $scope.map.latitudeMapping = newVal;
                        $scope.draw();
                    }
                });

                // Update the longitude field used by the map.
                $scope.$watch('longitudeField', function (newVal, oldVal) {
                    if (newVal && newVal !== oldVal) {
                        $scope.map.longitudeMapping = newVal;
                        $scope.draw();
                    }
                });

                // Update the sizing field used by the map.
                $scope.$watch('sizeByField', function (newVal, oldVal) {
                    if (newVal !== oldVal) {
                        if (newVal) {
                            $scope.map.sizeMapping = newVal;
                        }
                        else {
                            $scope.map.sizeMapping = coreMap.Map.DEFAULT_SIZE_MAPPING;
                        }

                        $scope.queryForMapData();
                    }
                });

                // Update the coloring field used by the map.
                $scope.$watch('colorByField', function (newVal, oldVal) {
                    if (newVal !== oldVal) {
                        if (newVal) {
                            $scope.map.categoryMapping = newVal;
                        }
                        else {
                            $scope.map.categoryMapping = undefined;
                        }
                        $scope.queryForMapData();
                    }
                });

                // Toggle the points and clusters view when the user toggles between them.
                $scope.$watch('showPoints', function (newVal, oldVal) {
                    if (newVal !== oldVal) {
                        $scope.map.toggleLayers();
                    }
                });

                // Handle toggling map caching.
                $scope.$watch('cacheMap', function (newVal, oldVal) {
                    if (newVal !== oldVal) {
                        if (newVal) {
                            $scope.map.clearCache();
                            $scope.map.toggleCaching();
                        }
                        else {
                            $scope.map.toggleCaching();
                        }
                    }
                });

                $scope.$watch('zoomRectBounds', function (newVal) {
                    clearZoomRect();

                    // newVal may be undefined when clearing out the box
                    if (newVal !== undefined) {
                        $scope.zoomRectId = $scope.map.drawBox(newVal);
                    }
                });

                // Wait for neon to be ready, the create our messenger and intialize the view and data.
                neon.ready(function () {
                    $scope.initialize();
                });

            }
        };
    }]);
<commit_msg>NEON-1116 Clearing any visual zoom rectangles before reinitializing our references to them after a dataset change.<commit_after>'use strict';
/*
 * Copyright 2014 Next Century Corporation
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

/**
 * This Angular JS directive adds a map to a page and has controls for selecting which data available through
 * a Neon connection should be plotted.
 *
 * @example
 *    <heat-map></heat-map>

 *    <div heat-map></div>
 *
 * @class neonDemo.directives.heatMap
 * @constructor
 */
angular.module('heatMapDirective', []).directive('heatMap', ['ConnectionService', '$timeout',
    function (connectionService, $timeout) {

        return {
            templateUrl: 'partials/heatMap.html',
            restrict: 'EA',
            scope: {
                // map of categories to colors used for the legend
                colorMappings: '&'
            },
            controller: function ($scope) {

            },
            link: function ($scope, element, attr) {

                element.addClass('heat-map');

                /**
                 * Initializes the name of the directive's scope variables
                 * and the Neon Messenger used to monitor data change events.
                 * @method initialize
                 */
                $scope.initialize = function () {
                    $scope.databaseName = '';
                    $scope.tableName = '';
                    $scope.fields = [];
                    $scope.latitudeField = '';
                    $scope.longitudeField = '';
                    $scope.sizeByField = '';
                    $scope.colorByField = '';
                    $scope.showPoints = false;
                    $scope.cacheMap = false;
                    $scope.initializing = true;

                    $scope.filterKey = neon.widget.getInstanceId("map");
                    $scope.showFilter = false;

                    // Setup our map.
                    $scope.mapId = uuid();
                    element.append('

');
                    $scope.map = new coreMap.Map($scope.mapId, {
                        height: 500,
                        width: "100%",
                        responsive: false,
                        onZoomRect: onZoomChanged
                    });

                    $scope.draw();
                    $scope.map.register("moveend", this, onMoved);

                    // Setup our messenger.
                    $scope.messenger = new neon.eventing.Messenger();

                    $scope.messenger.events({
                        activeDatasetChanged: onDatasetChanged,
                        filtersChanged: onFiltersChanged
                    });

                    // Enable the tooltips.
                    $(element).find('label.btn-default').tooltip();

                };

                var onMoved = function (message) {
                    // Commenting out for now until we decide whether or not to filter data queries based on visible area.
                    // May need to make this a user-toggleable capability.
                    // $scope.queryForMapData();
                };

                /**
                 * Event handler for filter changed events issued over Neon's messaging channels.
                 * @param {Object} message A Neon filter changed message.
                 * @method onFiltersChanged
                 * @private
                 */
                var onFiltersChanged = function (message) {
                    $scope.queryForMapData();
                };

                var onZoomChanged = function (bounds) {
                    var extent = boundsToExtent(bounds);
                    var filter = $scope.createFilterFromExtent(extent);
                    $scope.messenger.replaceFilter($scope.filterKey, filter, function () {
                        $scope.$apply(function () {
                            $scope.queryForMapData();
                            $scope.zoomRectBounds = {left: extent.minimumLongitude, bottom:extent.minimumLatitude, right: extent.maximumLongitude, top: extent.maximumLatitude};
                            // Show the Clear Filter button.
                            $scope.showFilter = true;
                            $scope.error = "";
                        });
                    }, function () {
                        // Notify the user of the error.
                        $scope.error = "Error: Failed to create filter.";
                    });
                };

                /**
                 * Event handler for dataset changed events issued over Neon's messaging channels.
                 * @param {Object} message A Neon dataset changed message.
                 * @param {String} message.database The database that was selected.
                 * @param {String} message.table The table within the database that was selected.
                 * @method onDatasetChanged
                 * @private
                 */
                var onDatasetChanged = function (message) {
                    $scope.initializing = true;
                    $scope.databaseName = message.database;
                    $scope.tableName = message.table;
                    $scope.latitudeField = "";
                    $scope.latitudeField = "";
                    $scope.longitudeField = "";
                    $scope.longitudeField = "";
                    $scope.colorByField = "";
                    $scope.colorByField = "";
                    $scope.sizeByField = "";
                    $scope.sizeByField = "";

                    // Clear the zoom Rect from the map before reinitializing it.
                    clearZoomRect();
                    $scope.zoomRectBounds = undefined;

                    // used to track the id of the rectangle drawn on the map so it can be removed when a new box is drawn
                    $scope.zoomRectId = undefined;
                    $scope.hideClearFilterButton();

                    // Repopulate the field selectors and get the default values.
                    connectionService.getActiveConnection().getFieldNames($scope.tableName, function (results) {
                        $scope.$apply(function () {
                            populateFieldNames(results);
                            $scope.latitudeField = connectionService.getFieldMapping($scope.database, $scope.tableName, "latitude");
                            $scope.latitudeField = $scope.latitudeField.mapping || $scope.fields[0];
                            $scope.longitudeField = connectionService.getFieldMapping($scope.database, $scope.tableName, "longitude");
                            $scope.longitudeField = $scope.longitudeField.mapping || $scope.fields[0];
                            $scope.colorByField = connectionService.getFieldMapping($scope.database, $scope.tableName, "color-by");
                            $scope.colorByField = $scope.colorByField.mapping || $scope.fields[0];
                            $scope.sizeByField = connectionService.getFieldMapping($scope.database, $scope.tableName, "size-by");
                            $scope.sizeByField = $scope.sizeByField.mapping || $scope.fields[0];
                            $timeout(function () {
                                $scope.initializing = false;
                                $scope.queryForMapData();
                            });
                        });
                    });
                };

                /**
                 * Helper method for setting the fields available for filter clauses.
                 * @param {Array} fields An array of field name strings.
                 * @method populateFieldNames
                 * @private
                 */
                var populateFieldNames = function (fields) {
                    $scope.fields = fields;
                };

                var boundsToExtent = function (bounds) {
                    var llPoint = new OpenLayers.LonLat(bounds.left, bounds.bottom);
                    var urPoint = new OpenLayers.LonLat(bounds.right, bounds.top);

                    var minLon = Math.min(llPoint.lon, urPoint.lon);
                    var maxLon = Math.max(llPoint.lon, urPoint.lon);

                    var minLat = Math.min(llPoint.lat, urPoint.lat);
                    var maxLat = Math.max(llPoint.lat, urPoint.lat);

                    return {
                        minimumLatitude: minLat,
                        minimumLongitude: minLon,
                        maximumLatitude: maxLat,
                        maximumLongitude: maxLon
                    };

                }

                var clearZoomRect = function() {
                    if ( $scope.zoomRectId !== undefined ) {
                        $scope.map.removeBox($scope.zoomRectId);
                        $scope.zoomRectId = undefined;
                    }
                }

                /**
                 * Triggers a Neon query that will aggregate the time data for the currently selected dataset.
                 * @method queryForMapData
                 */
                $scope.queryForMapData = function () {
                    if (!$scope.initializing && $scope.latitudeField !== "" && $scope.longitudeField !== "") {
                        var query = $scope.buildQuery();
                        connectionService.getActiveConnection().executeQuery(query, function (queryResults) {
                            $scope.$apply(function () {
                                $scope.updateMapData(queryResults);
                            });
                        });
                    }
                };

                /**
                 * Redraws the map
                 */
                $scope.draw = function () {
                    if (!$scope.initializing) {
                        $scope.map.draw();
                    }
                };

                /**
                 * Updates the data bound to the map managed by this directive.  This will trigger a change in
                 * the chart's visualization.
                 * @param {Object} queryResults Results returned from a Neon query.
                 * @param {Array} queryResults.data The aggregate numbers for the heat chart cells.
                 * @method updateMapData
                 */
                $scope.updateMapData = function (queryResults) {
                    $scope.map.setData(queryResults.data);
                    $scope.draw();
                    // color mappings need to be updated after drawing since they are set during drawing
                    $scope.colorMappings = $scope.map.getColorMappings();
                };

                $scope.buildQuery = function () {
                    var query = new neon.query.Query().selectFrom($scope.databaseName, $scope.tableName)
                    var groupByFields = [$scope.latitudeField, $scope.longitudeField];

                    if ($scope.colorByField) {
                        groupByFields.push($scope.colorByField);
                        query = query.groupBy($scope.latitudeField, $scope.longitudeField, $scope.colorByField)
                    }
                    else {
                        query = query.groupBy($scope.latitudeField, $scope.longitudeField);
                    }

                    if ($scope.sizeByField) {
                        query.aggregate(neon.query.SUM, $scope.sizeByField, $scope.sizeByField);
                    }
                    else {
                        query.aggregate(neon.query.COUNT, '*', coreMap.Map.DEFAULT_SIZE_MAPPING);
                    }

                    return query;
                }

                $scope.hideClearFilterButton = function () {
                    // hide the Clear Filter button.
                    $scope.showFilter = false;
                    $scope.error = "";
                };

                /**
                 * Create a Neon query to pull data limited to the current extent of the map.
                 * @method createFilterFromExtent
                 */
                $scope.createFilterFromExtent = function (extent) {

                    var leftClause = neon.query.where($scope.longitudeField, ">=", extent.minimumLongitude);
                    var rightClause = neon.query.where($scope.longitudeField, "<=", extent.maximumLongitude);
                    var bottomClause = neon.query.where($scope.latitudeField, ">=", extent.minimumLatitude);
                    var topClause = neon.query.where($scope.latitudeField, "<=", extent.maximumLatitude);
                    var filterClause = neon.query.and(leftClause, rightClause, bottomClause, topClause);

                    //Deal with different dateline crossing scenarios.
                    if (extent.minimumLongitude < -180 && extent.maximumLongitude > 180) {
                        filterClause = neon.query.and(topClause, bottomClause);
                    }
                    else if (extent.minimumLongitude < -180) {
                        leftClause = neon.query.where($scope.longitudeField, ">=", extent.minimumLongitude + 360);
                        var leftDateLine = neon.query.where($scope.longitudeField, "<=", 180);
                        var rightDateLine = neon.query.where($scope.longitudeField, ">=", -180);
                        var datelineClause = neon.query.or(neon.query.and(leftClause, leftDateLine), neon.query.and(rightClause, rightDateLine));
                        filterClause = neon.query.and(topClause, bottomClause, datelineClause);
                    }
                    else if (extent.maximumLongitude > 180) {
                        rightClause = neon.query.where($scope.longitudeField, "<=", extent.maximumLongitude - 360);
                        var rightDateLine = neon.query.where($scope.longitudeField, ">=", -180);
                        var leftDateLine = neon.query.where($scope.longitudeField, "<=", 180);
                        var datelineClause = neon.query.or(neon.query.and(leftClause, leftDateLine), neon.query.and(rightClause, rightDateLine));
                        filterClause = neon.query.and(topClause, bottomClause, datelineClause);
                    }

                    return new neon.query.Filter().selectFrom($scope.databaseName, $scope.tableName).where(filterClause);
                };

                /**
                 * Clear Neon query to pull data limited to the current extent of the map.
                 * @method clearFilter
                 */
                $scope.clearFilter = function () {
                    $scope.messenger.removeFilter($scope.filterKey, function () {
                        $scope.$apply(function () {
                            $scope.zoomRectBounds = undefined;
                            $scope.queryForMapData();
                            $scope.hideClearFilterButton();
                        });
                    }, function () {
                        // Notify the user of the error.
                        $scope.error = "Error: Failed to clear filter.";
                    });
                };

                // Update the latitude field used by the map.
                $scope.$watch('latitudeField', function (newVal, oldVal) {
                    if (newVal && newVal !== oldVal) {
                        $scope.map.latitudeMapping = newVal;
                        $scope.draw();
                    }
                });

                // Update the longitude field used by the map.
                $scope.$watch('longitudeField', function (newVal, oldVal) {
                    if (newVal && newVal !== oldVal) {
                        $scope.map.longitudeMapping = newVal;
                        $scope.draw();
                    }
                });

                // Update the sizing field used by the map.
                $scope.$watch('sizeByField', function (newVal, oldVal) {
                    if (newVal !== oldVal) {
                        if (newVal) {
                            $scope.map.sizeMapping = newVal;
                        }
                        else {
                            $scope.map.sizeMapping = coreMap.Map.DEFAULT_SIZE_MAPPING;
                        }

                        $scope.queryForMapData();
                    }
                });

                // Update the coloring field used by the map.
                $scope.$watch('colorByField', function (newVal, oldVal) {
                    if (newVal !== oldVal) {
                        if (newVal) {
                            $scope.map.categoryMapping = newVal;
                        }
                        else {
                            $scope.map.categoryMapping = undefined;
                        }
                        $scope.queryForMapData();
                    }
                });

                // Toggle the points and clusters view when the user toggles between them.
                $scope.$watch('showPoints', function (newVal, oldVal) {
                    if (newVal !== oldVal) {
                        $scope.map.toggleLayers();
                    }
                });

                // Handle toggling map caching.
                $scope.$watch('cacheMap', function (newVal, oldVal) {
                    if (newVal !== oldVal) {
                        if (newVal) {
                            $scope.map.clearCache();
                            $scope.map.toggleCaching();
                        }
                        else {
                            $scope.map.toggleCaching();
                        }
                    }
                });

                $scope.$watch('zoomRectBounds', function (newVal) {
                    clearZoomRect();

                    // newVal may be undefined when clearing out the box
                    if (newVal !== undefined) {
                        $scope.zoomRectId = $scope.map.drawBox(newVal);
                    }
                });

                // Wait for neon to be ready, the create our messenger and intialize the view and data.
                neon.ready(function () {
                    $scope.initialize();
                });

            }
        };
    }]);
<|endoftext|>


<commit_before>'use strict'
var yo = require('yo-yo')

// -------------- styling ----------------------
var csjs = require('csjs-inject')
var remix = require('ethereum-remix')
var styleGuide = remix.ui.styleGuide
var styles = styleGuide()

var EventManager = remix.lib.EventManager
var helper = require('../../lib/helper')
var ethJSUtil = require('ethereumjs-util')
var BN = ethJSUtil.BN
var executionContext = require('../../execution-context')

var css = csjs`
  .log {
    display: flex;
    align-items: baseline;
  }
  .txTable, .tr, .td {
    border: 1px solid ${styles.colors.lightOrange};
    border-collapse: collapse;
    font-size: 10px;
    color: ${styles.colors.grey};
  }
  #txTable {
    width: 450px;
    margin-top: 10px;
    align-self: center;
  }
  .tr, .td {
    padding: 4px;
  }
  .buttons {
    display: flex;
  }
  .debug, .details {
    ${styles.button}
    min-height: 18px;
    max-height: 18px;
    width: 45px;
    min-width: 45px;
    font-size: 10px;
    margin-left: 5px;
  }
  .debug {
    background-color: ${styles.colors.lightOrange};
  }
  .details {
    background-color: ${styles.colors.lightGrey};
  }
`
/**
  * This just export a function that register to `newTransaction` and forward them to the logger.
  * Emit debugRequested
  *
  */
class TxLogger {
  constructor (opts = {}) {
    this.event = new EventManager()
    this.opts = opts
    this.logKnownTX = opts.api.editorpanel.registerCommand('knownTransaction', (args, cmds, append) => {
      var data = args[0]
      var el = renderKnownTransaction(this, data)
      append(el)
    })
    this.logUnknownTX = opts.api.editorpanel.registerCommand('unknownTransaction', (args, cmds, append) => {
      var data = args[0]
      var el = renderUnknownTransaction(this, data)
      append(el)
    })
    this.logEmptyBlock = opts.api.editorpanel.registerCommand('emptyBlock', (args, cmds, append) => {
      var data = args[0]
      var el = renderEmptyBlock(this, data)
      append(el)
    })

    opts.events.txListener.register('newBlock', (block) => {
      if (!block.transactions.length) {
        this.logEmptyBlock({ block: block })
      }
    })

    opts.events.txListener.register('newTransaction', (tx) => {
      log(this, tx, opts.api)
    })
  }
}

function log (self, tx, api) {
  var resolvedTransaction = api.resolvedTransaction(tx.hash)
  if (resolvedTransaction) {
    api.parseLogs(tx, resolvedTransaction.contractName, api.compiledContracts(), (error, logs) => {
      if (!error) {
        self.logKnownTX({ tx: tx, resolvedData: resolvedTransaction, logs: logs })
      }
    })
  } else {
    // contract unknown - just displaying raw tx.
    self.logUnknownTX({ tx: tx })
  }
}

function renderKnownTransaction (self, data) {
  var from = data.tx.from
  var to = ''

  if (data.tx.blockHash) {
    to = data.tx.to
  } else if (data.tx.hash) {  // call (constructor of function call)
    var name = data.resolvedData.contractName + '.' + data.resolvedData.fn
    var logs = ',' + data.logs.length + ' logs'
    if (data.resolvedData.fn === '(constructor)') {
      to = name + logs
    } else {
      to = data.resolvedData.to
    }
  }

  function debug () {
    self.event.trigger('debugRequested', [data.tx.hash])
  }
  var tx = yo`

        ${context(self, data)}

        <button class=${css.details} onclick=${txDetails}>Details</button>
        <button class=${css.debug} onclick=${debug}>Debug</button>

  `

  var table
  function txDetails () {
    if (table && table.parentNode) {
      tx.removeChild(table)
    } else {
      table = createTable({
        from, to, val: data.tx.value, input: data.tx.input, hash: data.tx.hash
      })
      tx.appendChild(table)
    }
  }

  return tx
}

function renderUnknownTransaction (self, data) {
  var from = data.tx.from
  var to = data.tx.to
  function debug () {
    self.event.trigger('debugRequested', [data.tx.hash])
  }
  var tx = yo`

        ${context(self, data)}

          <button class=${css.details} onclick=${txDetails}>Details</button>
          <button class=${css.debug} onclick=${debug}>Debug</button>

  `
  var table
  function txDetails () {
    if (table && table.parentNode) {
      tx.removeChild(table)
    } else {
      table = createTable({
        from, to, val: data.tx.value, input: data.tx.input, hash: data.tx.hash
      })
      tx.appendChild(table)
    }
  }
  return tx
}

function renderEmptyBlock (self, data) {
  return yo`<span>block ${data.block.number} - O transactions</span>`
}

function context (self, data) {
  var from = helper.shortenHexData(data.tx.from)
  var to = ''
  if (executionContext.getProvider() === 'vm') {
    if (data.resolvedData.to) {
      to = `${data.resolvedData.contractName}.${data.resolvedData.fn}, ${data.resolvedData.to}, ${data.logs.length} logs`
    } else {
      to = `${data.resolvedData.contractName}.${data.resolvedData.fn}, ${data.logs.length} logs`
    }
    return yo`<span><span class='${css.txVM}'>[vm]</span> from: ${from}, to:${to}, value:${data.tx.value} wei</span>`
  } else {
    var hash = helper.shortenHexData(data.tx.blockHash)
    var block = data.tx.blockNumber
    var i = data.tx.transactionIndex
    var val = data.tx.value
    return yo`<span><span class='${css.txBlock}'>[block:${block} txIndex:${i}]</span> from:${from}, to:${hash}, value:${value(val)} wei</span>`
  }
}

function value (v) {
  try {
    if (v.indexOf && v.indexOf('0x') === 0) {
      return (new BN(v.replace('0x', ''), 16)).toString(10)
    } else {
      return v.toString(10)
    }
  } catch (e) {
    console.log(e)
    return v
  }
}

module.exports = TxLogger

// helpers

function createTable (opts) {
  var from = opts.from
  var to = opts.to
  var val = opts.val
  var input = opts.input
  var hash = opts.hash
  return yo`

      from
      ${from}

      to:
      ${to}

      value:
      ${value(val)} wei

      data:
      ${helper.shortenHexData(input)}

      hash:
      ${helper.shortenHexData((hash))}

  `
}
<commit_msg>Add gas to the table<commit_after>'use strict'
var yo = require('yo-yo')

// -------------- styling ----------------------
var csjs = require('csjs-inject')
var remix = require('ethereum-remix')
var styleGuide = remix.ui.styleGuide
var styles = styleGuide()

var EventManager = remix.lib.EventManager
var helper = require('../../lib/helper')
var ethJSUtil = require('ethereumjs-util')
var BN = ethJSUtil.BN
var executionContext = require('../../execution-context')

var css = csjs`
  .log {
    display: flex;
    align-items: baseline;
  }
  .txTable, .tr, .td {
    border: 1px solid ${styles.colors.lightOrange};
    border-collapse: collapse;
    font-size: 10px;
    color: ${styles.colors.grey};
  }
  #txTable {
    width: 450px;
    margin-top: 10px;
    align-self: center;
  }
  .tr, .td {
    padding: 4px;
  }
  .buttons {
    display: flex;
  }
  .debug, .details {
    ${styles.button}
    min-height: 18px;
    max-height: 18px;
    width: 45px;
    min-width: 45px;
    font-size: 10px;
    margin-left: 5px;
  }
  .debug {
    background-color: ${styles.colors.lightOrange};
  }
  .details {
    background-color: ${styles.colors.lightGrey};
  }
`
/**
  * This just export a function that register to `newTransaction` and forward them to the logger.
  * Emit debugRequested
  *
  */
class TxLogger {
  constructor (opts = {}) {
    this.event = new EventManager()
    this.opts = opts
    this.logKnownTX = opts.api.editorpanel.registerCommand('knownTransaction', (args, cmds, append) => {
      var data = args[0]
      var el = renderKnownTransaction(this, data)
      append(el)
    })
    this.logUnknownTX = opts.api.editorpanel.registerCommand('unknownTransaction', (args, cmds, append) => {
      var data = args[0]
      var el = renderUnknownTransaction(this, data)
      append(el)
    })
    this.logEmptyBlock = opts.api.editorpanel.registerCommand('emptyBlock', (args, cmds, append) => {
      var data = args[0]
      var el = renderEmptyBlock(this, data)
      append(el)
    })

    opts.events.txListener.register('newBlock', (block) => {
      if (!block.transactions.length) {
        this.logEmptyBlock({ block: block })
      }
    })

    opts.events.txListener.register('newTransaction', (tx) => {
      log(this, tx, opts.api)
    })
  }
}

function log (self, tx, api) {
  var resolvedTransaction = api.resolvedTransaction(tx.hash)
  if (resolvedTransaction) {
    api.parseLogs(tx, resolvedTransaction.contractName, api.compiledContracts(), (error, logs) => {
      if (!error) {
        self.logKnownTX({ tx: tx, resolvedData: resolvedTransaction, logs: logs })
      }
    })
  } else {
    // contract unknown - just displaying raw tx.
    self.logUnknownTX({ tx: tx })
  }
}

function renderKnownTransaction (self, data) {
  var from = data.tx.from
  var to = ''

  if (data.tx.blockHash) {
    to = data.tx.to
  } else if (data.tx.hash) {  // call (constructor of function call)
    var name = data.resolvedData.contractName + '.' + data.resolvedData.fn
    var logs = ',' + data.logs.length + ' logs'
    if (data.resolvedData.fn === '(constructor)') {
      to = name + logs
    } else {
      to = data.resolvedData.to
    }
  }

  function debug () {
    self.event.trigger('debugRequested', [data.tx.hash])
  }
  var tx = yo`

        ${context(self, data)}

        <button class=${css.details} onclick=${txDetails}>Details</button>
        <button class=${css.debug} onclick=${debug}>Debug</button>

  `

  var table
  function txDetails () {
    if (table && table.parentNode) {
      tx.removeChild(table)
    } else {
      table = createTable({
        from, to, val: data.tx.value, input: data.tx.input, hash: data.tx.hash, gas: data.tx.gas
      })
      tx.appendChild(table)
    }
  }

  return tx
}

function renderUnknownTransaction (self, data) {
  var from = data.tx.from
  var to = data.tx.to
  function debug () {
    self.event.trigger('debugRequested', [data.tx.hash])
  }
  var tx = yo`

        ${context(self, data)}

          <button class=${css.details} onclick=${txDetails}>Details</button>
          <button class=${css.debug} onclick=${debug}>Debug</button>

  `
  var table
  function txDetails () {
    if (table && table.parentNode) {
      tx.removeChild(table)
    } else {
      table = createTable({
        from, to, val: data.tx.value, input: data.tx.input, hash: data.tx.hash, gas: data.tx.gas
      })
      tx.appendChild(table)
    }
  }
  return tx
}

function renderEmptyBlock (self, data) {
  return yo`<span>block ${data.block.number} - O transactions</span>`
}

function context (self, data) {
  var from = helper.shortenHexData(data.tx.from)
  var to = ''
  if (executionContext.getProvider() === 'vm') {
    if (data.resolvedData.to) {
      to = `${data.resolvedData.contractName}.${data.resolvedData.fn}, ${data.resolvedData.to}, ${data.logs.length} logs`
    } else {
      to = `${data.resolvedData.contractName}.${data.resolvedData.fn}, ${data.logs.length} logs`
    }
    return yo`<span><span class='${css.txVM}'>[vm]</span> from: ${from}, to:${to}, value:${data.tx.value} wei</span>`
  } else {
    var hash = helper.shortenHexData(data.tx.blockHash)
    var block = data.tx.blockNumber
    var i = data.tx.transactionIndex
    var val = data.tx.value
    return yo`<span><span class='${css.txBlock}'>[block:${block} txIndex:${i}]</span> from:${from}, to:${hash}, value:${value(val)} wei</span>`
  }
}

function value (v) {
  try {
    if (v.indexOf && v.indexOf('0x') === 0) {
      return (new BN(v.replace('0x', ''), 16)).toString(10)
    } else {
      return v.toString(10)
    }
  } catch (e) {
    console.log(e)
    return v
  }
}

module.exports = TxLogger

// helpers

function createTable (opts) {
  var from = opts.from
  var to = opts.to
  var val = opts.val
  var input = opts.input
  var hash = opts.hash
  var gas = opts.gas
  return yo`

      from
      ${from}

      to:
      ${to}

      value:
      ${value(val)} wei

      data:
      ${input}

      hash:
      ${hash}

      gas:
      ${gas}

  `
}
<|endoftext|>


<commit_before>import Template from "./template";
import paper from "paper";

export default class Transposer extends Template {
    constructor() {
        super();
    }

    __setupDefinitions() {
        this.__unique = {
            position: "Point"
        };

        this.__heritable = {
            componentSpacing: "Float",
            rotation: "Float",
            valveRadius: "Float",
            height: "Float",
            valveGap: "Float",
            valveSpacing: "Float",
            controlChannelWidth: "Float"
        };

        this.__defaults = {
            componentSpacing: 1000,
            rotation: 0,
            valveRadius: 1.2 * 1000,
            height: 250,
            valveGap: 0.6 * 1000,
            valveSpacing: 0.6 * 1000,
            flowChannelWidth: 500,
            controlChannelWidth: 500
        };

        this.__units = {
            componentSpacing: "μm",
            rotation: "°",
            valveRadius: "μm",
            height: "μm",
            valveGap: "μm",
            valveSpacing: "μm",
            flowChannelWidth: "μm",
            controlChannelWidth: "μm"
        };

        this.__minimum = {
            componentSpacing: 0,
            valveRadius: 0.1 * 100,
            height: 0.1 * 100,
            valveGap: 0.5 * 10,
            valveSpacing: 0.1 * 1000,
            flowChannelWidth: 0.1,
            controlChannelWidth: 0.1,
            rotation: 0
        };

        this.__maximum = {
            componentSpacing: 10000,
            valveRadius: 0.2 * 10000,
            height: 1.2 * 1000,
            valveGap: 0.1 * 10000,
            valveSpacing: 0.1 * 10000,
            flowChannelWidth: 0.1 * 10000,
            controlChannelWidth: 0.1 * 10000,
            rotation: 360
        };

        this.__featureParams = {
            componentSpacing: "componentSpacing",
            position: "position",
            rotation: "rotation",
            valveRadius: "valveRadius",
            valveGap: "valveGap",
            valveSpacing: "valveSpacing",
            flowChannelWidth: "flowChannelWidth",
            controlChannelWidth: "controlChannelWidth"
        };

        this.__targetParams = {
            componentSpacing: "componentSpacing",
            position: "position",
            rotation: "rotation",
            valveRadius: "valveRadius",
            valveGap: "valveGap",
            valveSpacing: "valveSpacing",
            flowChannelWidth: "flowChannelWidth",
            controlChannelWidth: "controlChannelWidth"
        };

        this.__placementTool = "MultilayerPositionTool";

        this.__toolParams = {
            position: "position"
        };

        this.__renderKeys = ["FLOW", "CONTROL", "INVERSE"];

        this.__mint = "TRANSPOSER";
    }

    render2D(params, key="FLOW") {
        if (key == "FLOW") {
            return this.__drawFlow(params);
        } else if (key == "CONTROL") {
            return this.__drawControl(params);
        }
    }

    render2DTarget(key, params) {
        let ret = new paper.CompoundPath();
        let flow = this.render2D(params, "FLOW");
        let control = this.render2D(params, "CONTROL");
        ret.addChild(control);
        ret.addChild(flow);
        ret.fillColor = params["color"];
        ret.fillColor.alpha = 0.5;
        return ret;
    }

    __drawFlow(params) {
        let position = params["position"];
        let valveGap = params["valveGap"];
        let radius = params["valveRadius"];
        let color = params["color"];
        let rotation = params["rotation"];
        let channelWidth = params["flowChannelWidth"];
        let valvespacing = params["valveSpacing"];
        let transposer_flow = new paper.CompoundPath();

        let px = position[0];
        let py = position[1];

        //Draw top left channel
        let topleftpoint = new paper.Point(px, py - channelWidth / 2);
        let bottomrightpoint = new paper.Point(px + 4 * valvespacing + 2 * radius + 2 * channelWidth, py + channelWidth / 2);
        let channel = new paper.Path.Rectangle(topleftpoint, bottomrightpoint);

        transposer_flow.addChild(channel);

        //Draw Valve
        this.__createTransposerValve(transposer_flow, bottomrightpoint.x + radius, topleftpoint.y + channelWidth / 2, valveGap, radius, "H", channelWidth);

        //Draw top right channel
        topleftpoint = new paper.Point(px + 4 * valvespacing + 4 * radius + 2 * channelWidth, py - channelWidth / 2);
        bottomrightpoint = new paper.Point(px + 6 * valvespacing + 4 * radius + 3 * channelWidth, py + channelWidth / 2);
        channel = new paper.Path.Rectangle(topleftpoint, bottomrightpoint);

        transposer_flow.addChild(channel);

        //Draw middle channels
        topleftpoint = new paper.Point(px + 3 * valvespacing + channelWidth + 2 * radius, py + channelWidth / 2);
        bottomrightpoint = new paper.Point(topleftpoint.x + channelWidth, topleftpoint.y + valvespacing);
        channel = new paper.Path.Rectangle(topleftpoint, bottomrightpoint);

        transposer_flow.addChild(channel);
        this.__createTransposerValve(transposer_flow, topleftpoint.x + channelWidth / 2, bottomrightpoint.y + radius, valveGap, radius, "V", channelWidth);

        //2
        topleftpoint = new paper.Point(topleftpoint.x, bottomrightpoint.y + 2 * radius);
        bottomrightpoint = new paper.Point(topleftpoint.x + channelWidth, topleftpoint.y + 2 * valvespacing + channelWidth);
        channel = new paper.Path.Rectangle(topleftpoint, bottomrightpoint);

        transposer_flow.addChild(channel);

        this.__createTransposerValve(transposer_flow, topleftpoint.x + channelWidth / 2, bottomrightpoint.y + radius, valveGap, radius, "V", channelWidth);

        //3
        topleftpoint = new paper.Point(topleftpoint.x, bottomrightpoint.y + 2 * radius);
        bottomrightpoint = new paper.Point(topleftpoint.x + channelWidth, topleftpoint.y + valvespacing);
        channel = new paper.Path.Rectangle(topleftpoint, bottomrightpoint);

        transposer_flow.addChild(channel);

        //Bottom Channels
        topleftpoint = new paper.Point(px, py + 1.5 * channelWidth + 4 * valvespacing + 2 * 2 * radius);
        bottomrightpoint = new paper.Point(topleftpoint.x + 2 * valvespacing + channelWidth, topleftpoint.y + channelWidth);
        channel = new paper.Path.Rectangle(topleftpoint, bottomrightpoint);

        transposer_flow.addChild(channel);

        this.__createTransposerValve(transposer_flow, bottomrightpoint.x + radius, topleftpoint.y + channelWidth / 2, valveGap, radius, "H", channelWidth);

        //2
        topleftpoint = new paper.Point(bottomrightpoint.x + 2 * radius, topleftpoint.y);
        bottomrightpoint = new paper.Point(topleftpoint.x + 4 * valvespacing + 2 * channelWidth + 2 * radius, topleftpoint.y + channelWidth);
        channel = new paper.Path.Rectangle(topleftpoint, bottomrightpoint);

        transposer_flow.addChild(channel);

        //Draw the right channels
        topleftpoint = new paper.Point(px + 5 * valvespacing + 2 * channelWidth + 4 * radius, py + channelWidth / 2);
        bottomrightpoint = new paper.Point(topleftpoint.x + channelWidth, topleftpoint.y + valvespacing);
        channel = new paper.Path.Rectangle(topleftpoint, bottomrightpoint);

        transposer_flow.addChild(channel);

        this.__createTransposerValve(transposer_flow, topleftpoint.x + channelWidth / 2, bottomrightpoint.y + radius, valveGap, radius, "V", channelWidth);

        //2
        topleftpoint = new paper.Point(topleftpoint.x, bottomrightpoint.y + 2 * radius);
        bottomrightpoint = new paper.Point(topleftpoint.x + channelWidth, topleftpoint.y + valvespacing + channelWidth);
        channel = new paper.Path.Rectangle(topleftpoint, bottomrightpoint);

        transposer_flow.addChild(channel);

        transposer_flow.fillColor = color;

        //Draw the left channels
        topleftpoint = new paper.Point(px + valvespacing, py + 1.5 * channelWidth + 2 * valvespacing + 2 * radius);
        bottomrightpoint = new paper.Point(topleftpoint.x + channelWidth, topleftpoint.y + valvespacing);
        channel = new paper.Path.Rectangle(topleftpoint, bottomrightpoint);

        transposer_flow.addChild(channel);

        this.__createTransposerValve(transposer_flow, topleftpoint.x + channelWidth / 2, bottomrightpoint.y + radius, valveGap, radius, "V", channelWidth);

        //2
        topleftpoint = new paper.Point(topleftpoint.x, bottomrightpoint.y + 2 * radius);
        bottomrightpoint = new paper.Point(topleftpoint.x + channelWidth, topleftpoint.y + valvespacing + channelWidth);
        channel = new paper.Path.Rectangle(topleftpoint, bottomrightpoint);

        transposer_flow.addChild(channel);

        transposer_flow.fillColor = color;

        transposer_flow.rotate(rotation, px + 3 * valvespacing + 1.5 * channelWidth + 2 * radius, py + channelWidth + 2 * valvespacing + 2 * radius);

        return transposer_flow;
    }

    __createTransposerValve(compound_path, xpos, ypos, valveGap, radius, orientation, channel_width) {
        let center = new paper.Point(xpos, ypos);

        //Create the basic circle
        let circ = new paper.Path.Circle(center, radius);

        //Add the tiny channel pieces that jut out
        let rec = new paper.Path.Rectangle({
            point: new paper.Point(xpos - channel_width / 2, ypos - radius),
            size: [channel_width, radius],
            stokeWidth: 0
        });

        circ = circ.unite(rec);

        rec = new paper.Path.Rectangle({
            point: new paper.Point(xpos - channel_width / 2, ypos),
            size: [channel_width, radius],
            stokeWidth: 0
        });

        circ = circ.unite(rec);

        let cutout = paper.Path.Rectangle({
            from: new paper.Point(xpos - radius, ypos - valveGap / 2),
            to: new paper.Point(xpos + radius, ypos + valveGap / 2)
        });

        //cutout.fillColor = "white";
        let valve = circ.subtract(cutout);

        //TODO Rotate
        if (orientation == "H") {
            valve.rotate(90, center);
        }

        compound_path.addChild(valve);
    }

    __drawControl(params) {
        let position = params["position"];
        let valveGap = params["valveGap"];
        let radius = params["valveRadius"];
        let color = params["color"];
        let rotation = params["rotation"];
        let channelWidth = params["controlChannelWidth"];
        let valvespacing = params["valveSpacing"];
        let transposer_control = new paper.CompoundPath();

        let px = position[0];
        let py = position[1];

        //Top right valve
        let center = new paper.Point(px + 4 * valvespacing + 2 * channelWidth + 2 * radius + radius, py);
        let circle = new paper.Path.Circle(center, radius);
        transposer_control.addChild(circle);

        //2nd row valves

        center = new paper.Point(px + 1.5 * channelWidth + 3 * valvespacing + 2 * radius, py + channelWidth / 2 + valvespacing + radius);
        circle = new paper.Path.Circle(center, radius);
        transposer_control.addChild(circle);

        let crosschannelstart = center;

        center = new paper.Point(center.x + 2 * valvespacing + 2 * radius + channelWidth, py + channelWidth / 2 + valvespacing + radius);
        circle = new paper.Path.Circle(center, radius);
        transposer_control.addChild(circle);

        let crosschannelend = center;

        //Draw the cross channel connecting the 2nd row valves
        let rect = new paper.Path.Rectangle({
            from: new paper.Point(crosschannelstart.x, crosschannelstart.y - channelWidth / 2),
            to: new paper.Point(crosschannelend.x, crosschannelstart.y + channelWidth / 2)
        });

        transposer_control.addChild(rect);

        //3rd Row valves

        center = new paper.Point(px + 0.5 * channelWidth + valvespacing, py + 1.5 * channelWidth + 3 * valvespacing + 2 * radius + radius);
        circle = new paper.Path.Circle(center, radius);
        transposer_control.addChild(circle);

        crosschannelstart = center;

        center = new paper.Point(center.x + 2 * valvespacing + 2 * radius + channelWidth, center.y);
        circle = new paper.Path.Circle(center, radius);
        transposer_control.addChild(circle);

        crosschannelend = center;

        //Draw the cross channel connecting the 3nd row valves
        rect = new paper.Path.Rectangle({
            from: new paper.Point(crosschannelstart.x, crosschannelstart.y - channelWidth / 2),
            to: new paper.Point(crosschannelend.x, crosschannelstart.y + channelWidth / 2)
        });

        transposer_control.addChild(rect);

        //Bottom Row valve
        center = new paper.Point(px + channelWidth + 2 * valvespacing + radius, py + 4 * valvespacing + 4 * radius + 2 * channelWidth);
        circle = new paper.Path.Circle(center, radius);
        transposer_control.addChild(circle);

        //Finally we draw the cross channel
        let topleftpoint = new paper.Point(px + valvespacing, py + channelWidth / 2 + 2 * radius + 2 * valvespacing);
        let bottomleftpoint = new paper.Point(topleftpoint.x + +4 * valvespacing + 3 * channelWidth + 4 * radius, topleftpoint.y + channelWidth);
        let rectangle = new paper.Path.Rectangle(topleftpoint, bottomleftpoint);
        transposer_control.addChild(rectangle);

        transposer_control.rotate(rotation, px + 3 * valvespacing + 1.5 * channelWidth + 2 * radius, py + channelWidth + 2 * valvespacing + 2 * radius);

        transposer_control.fillColor = color;
        return transposer_control;
    }
}
<commit_msg>add ports to transposer<commit_after>import Template from "./template";
import paper from "paper";
import ComponentPort from "../core/componentPort";

export default class Transposer extends Template {
    constructor() {
        super();
    }

    __setupDefinitions() {
        this.__unique = {
            position: "Point"
        };

        this.__heritable = {
            componentSpacing: "Float",
            rotation: "Float",
            valveRadius: "Float",
            height: "Float",
            valveGap: "Float",
            valveSpacing: "Float",
            flowChannelWidth: "Float",
            controlChannelWidth: "Float"
        };

        this.__defaults = {
            componentSpacing: 1000,
            rotation: 0,
            valveRadius: 1.2 * 1000,
            height: 250,
            valveGap: 0.6 * 1000,
            valveSpacing: 0.6 * 1000,
            flowChannelWidth: 500,
            controlChannelWidth: 500
        };

        this.__units = {
            componentSpacing: "μm",
            rotation: "°",
            valveRadius: "μm",
            height: "μm",
            valveGap: "μm",
            valveSpacing: "μm",
            flowChannelWidth: "μm",
            controlChannelWidth: "μm"
        };

        this.__minimum = {
            componentSpacing: 0,
            valveRadius: 0.1 * 100,
            height: 0.1 * 100,
            valveGap: 0.5 * 10,
            valveSpacing: 0.1 * 1000,
            flowChannelWidth: 0.1,
            controlChannelWidth: 0.1,
            rotation: 0
        };

        this.__maximum = {
            componentSpacing: 10000,
            valveRadius: 0.2 * 10000,
            height: 1.2 * 1000,
            valveGap: 0.1 * 10000,
            valveSpacing: 0.1 * 10000,
            flowChannelWidth: 0.1 * 10000,
            controlChannelWidth: 0.1 * 10000,
            rotation: 360
        };

        this.__featureParams = {
            componentSpacing: "componentSpacing",
            position: "position",
            rotation: "rotation",
            valveRadius: "valveRadius",
            valveGap: "valveGap",
            valveSpacing: "valveSpacing",
            flowChannelWidth: "flowChannelWidth",
            controlChannelWidth: "controlChannelWidth"
        };

        this.__targetParams = {
            componentSpacing: "componentSpacing",
            position: "position",
            rotation: "rotation",
            valveRadius: "valveRadius",
            valveGap: "valveGap",
            valveSpacing: "valveSpacing",
            flowChannelWidth: "flowChannelWidth",
            controlChannelWidth: "controlChannelWidth"
        };

        this.__placementTool = "MultilayerPositionTool";

        this.__toolParams = {
            position: "position"
        };

        this.__renderKeys = ["FLOW", "CONTROL", "INVERSE"];

        this.__mint = "TRANSPOSER";
    }

    getPorts(params) {
        let ports = [];
        let position = params["position"];
        let valveGap = params["valveGap"];
        let radius = params["valveRadius"];
        let color = params["color"];
        let rotation = params["rotation"];
        let channelWidth = params["flowChannelWidth"];
        let valvespacing = params["valveSpacing"];

        ports.push(new ComponentPort(0, 0, "1", "FLOW"));
        ports.push(new ComponentPort(6 * valvespacing + 4 * radius + 3 * channelWidth, 0, "2", "FLOW"));
        ports.push(new ComponentPort(0, 2 * channelWidth + 4 * valvespacing + 2 * 2 * radius, "3", "FLOW"));
        ports.push(new ComponentPort(6 * valvespacing + 4 * radius + 3 * channelWidth, 2 * channelWidth + 4 * valvespacing + 2 * 2 * radius, "4", "FLOW"));

        return ports;
    }

    render2D(params, key="FLOW") {
        if (key == "FLOW") {
            return this.__drawFlow(params);
        } else if (key == "CONTROL") {
            return this.__drawControl(params);
        }
    }

    render2DTarget(key, params) {
        let ret = new paper.CompoundPath();
        let flow = this.render2D(params, "FLOW");
        let control = this.render2D(params, "CONTROL");
        ret.addChild(control);
        ret.addChild(flow);
        ret.fillColor = params["color"];
        ret.fillColor.alpha = 0.5;
        return ret;
    }

    __drawFlow(params) {
        let position = params["position"];
        let valveGap = params["valveGap"];
        let radius = params["valveRadius"];
        let color = params["color"];
        let rotation = params["rotation"];
        let channelWidth = params["flowChannelWidth"];
        let valvespacing = params["valveSpacing"];
        let transposer_flow = new paper.CompoundPath();

        let px = position[0];
        let py = position[1];

        //Draw top left channel
        let topleftpoint = new paper.Point(px, py - channelWidth / 2);
        let bottomrightpoint = new paper.Point(px + 4 * valvespacing + 2 * radius + 2 * channelWidth, py + channelWidth / 2);
        let channel = new paper.Path.Rectangle(topleftpoint, bottomrightpoint);

        transposer_flow.addChild(channel);

        //Draw Valve
        this.__createTransposerValve(transposer_flow, bottomrightpoint.x + radius, topleftpoint.y + channelWidth / 2, valveGap, radius, "H", channelWidth);

        //Draw top right channel
        topleftpoint = new paper.Point(px + 4 * valvespacing + 4 * radius + 2 * channelWidth, py - channelWidth / 2);
        bottomrightpoint = new paper.Point(px + 6 * valvespacing + 4 * radius + 3 * channelWidth, py + channelWidth / 2);
        channel = new paper.Path.Rectangle(topleftpoint, bottomrightpoint);

        transposer_flow.addChild(channel);

        //Draw middle channels
        topleftpoint = new paper.Point(px + 3 * valvespacing + channelWidth + 2 * radius, py + channelWidth / 2);
        bottomrightpoint = new paper.Point(topleftpoint.x + channelWidth, topleftpoint.y + valvespacing);
        channel = new paper.Path.Rectangle(topleftpoint, bottomrightpoint);

        transposer_flow.addChild(channel);
        this.__createTransposerValve(transposer_flow, topleftpoint.x + channelWidth / 2, bottomrightpoint.y + radius, valveGap, radius, "V", channelWidth);

        //2
        topleftpoint = new paper.Point(topleftpoint.x, bottomrightpoint.y + 2 * radius);
        bottomrightpoint = new paper.Point(topleftpoint.x + channelWidth, topleftpoint.y + 2 * valvespacing + channelWidth);
        channel = new paper.Path.Rectangle(topleftpoint, bottomrightpoint);

        transposer_flow.addChild(channel);

        this.__createTransposerValve(transposer_flow, topleftpoint.x + channelWidth / 2, bottomrightpoint.y + radius, valveGap, radius, "V", channelWidth);

        //3
        topleftpoint = new paper.Point(topleftpoint.x, bottomrightpoint.y + 2 * radius);
        bottomrightpoint = new paper.Point(topleftpoint.x + channelWidth, topleftpoint.y + valvespacing);
        channel = new paper.Path.Rectangle(topleftpoint, bottomrightpoint);

        transposer_flow.addChild(channel);

        //Bottom Channels
        topleftpoint = new paper.Point(px, py + 1.5 * channelWidth + 4 * valvespacing + 2 * 2 * radius);
        bottomrightpoint = new paper.Point(topleftpoint.x + 2 * valvespacing + channelWidth, topleftpoint.y + channelWidth);
        channel = new paper.Path.Rectangle(topleftpoint, bottomrightpoint);

        transposer_flow.addChild(channel);

        this.__createTransposerValve(transposer_flow, bottomrightpoint.x + radius, topleftpoint.y + channelWidth / 2, valveGap, radius, "H", channelWidth);

        //2
        topleftpoint = new paper.Point(bottomrightpoint.x + 2 * radius, topleftpoint.y);
        bottomrightpoint = new paper.Point(topleftpoint.x + 4 * valvespacing + 2 * channelWidth + 2 * radius, topleftpoint.y + channelWidth);
        channel = new paper.Path.Rectangle(topleftpoint, bottomrightpoint);

        transposer_flow.addChild(channel);

        //Draw the right channels
        topleftpoint = new paper.Point(px + 5 * valvespacing + 2 * channelWidth + 4 * radius, py + channelWidth / 2);
        bottomrightpoint = new paper.Point(topleftpoint.x + channelWidth, topleftpoint.y + valvespacing);
        channel = new paper.Path.Rectangle(topleftpoint, bottomrightpoint);

        transposer_flow.addChild(channel);

        this.__createTransposerValve(transposer_flow, topleftpoint.x + channelWidth / 2, bottomrightpoint.y + radius, valveGap, radius, "V", channelWidth);

        //2
        topleftpoint = new paper.Point(topleftpoint.x, bottomrightpoint.y + 2 * radius);
        bottomrightpoint = new paper.Point(topleftpoint.x + channelWidth, topleftpoint.y + valvespacing + channelWidth);
        channel = new paper.Path.Rectangle(topleftpoint, bottomrightpoint);

        transposer_flow.addChild(channel);

        transposer_flow.fillColor = color;

        //Draw the left channels
        topleftpoint = new paper.Point(px + valvespacing, py + 1.5 * channelWidth + 2 * valvespacing + 2 * radius);
        bottomrightpoint = new paper.Point(topleftpoint.x + channelWidth, topleftpoint.y + valvespacing);
        channel = new paper.Path.Rectangle(topleftpoint, bottomrightpoint);

        transposer_flow.addChild(channel);

        this.__createTransposerValve(transposer_flow, topleftpoint.x + channelWidth / 2, bottomrightpoint.y + radius, valveGap, radius, "V", channelWidth);

        //2
        topleftpoint = new paper.Point(topleftpoint.x, bottomrightpoint.y + 2 * radius);
        bottomrightpoint = new paper.Point(topleftpoint.x + channelWidth, topleftpoint.y + valvespacing + channelWidth);
        channel = new paper.Path.Rectangle(topleftpoint, bottomrightpoint);

        transposer_flow.addChild(channel);

        transposer_flow.fillColor = color;

        transposer_flow.rotate(rotation, px + 3 * valvespacing + 1.5 * channelWidth + 2 * radius, py + channelWidth + 2 * valvespacing + 2 * radius);

        return transposer_flow;
    }

    __createTransposerValve(compound_path, xpos, ypos, valveGap, radius, orientation, channel_width) {
        let center = new paper.Point(xpos, ypos);

        //Create the basic circle
        let circ = new paper.Path.Circle(center, radius);

        //Add the tiny channel pieces that jut out
        let rec = new paper.Path.Rectangle({
            point: new paper.Point(xpos - channel_width / 2, ypos - radius),
            size: [channel_width, radius],
            stokeWidth: 0
        });

        circ = circ.unite(rec);

        rec = new paper.Path.Rectangle({
            point: new paper.Point(xpos - channel_width / 2, ypos),
            size: [channel_width, radius],
            stokeWidth: 0
        });

        circ = circ.unite(rec);

        let cutout = paper.Path.Rectangle({
            from: new paper.Point(xpos - radius, ypos - valveGap / 2),
            to: new paper.Point(xpos + radius, ypos + valveGap / 2)
        });

        //cutout.fillColor = "white";
        let valve = circ.subtract(cutout);

        //TODO Rotate
        if (orientation == "H") {
            valve.rotate(90, center);
        }

        compound_path.addChild(valve);
    }

    __drawControl(params) {
        let position = params["position"];
        let valveGap = params["valveGap"];
        let radius = params["valveRadius"];
        let color = params["color"];
        let rotation = params["rotation"];
        let channelWidth = params["controlChannelWidth"];
        let valvespacing = params["valveSpacing"];
        let transposer_control = new paper.CompoundPath();

        let px = position[0];
        let py = position[1];

        //Top right valve
        let center = new paper.Point(px + 4 * valvespacing + 2 * channelWidth + 2 * radius + radius, py);
        let circle = new paper.Path.Circle(center, radius);
        transposer_control.addChild(circle);

        //2nd row valves

        center = new paper.Point(px + 1.5 * channelWidth + 3 * valvespacing + 2 * radius, py + channelWidth / 2 + valvespacing + radius);
        circle = new paper.Path.Circle(center, radius);
        transposer_control.addChild(circle);

        let crosschannelstart = center;

        center = new paper.Point(center.x + 2 * valvespacing + 2 * radius + channelWidth, py + channelWidth / 2 + valvespacing + radius);
        circle = new paper.Path.Circle(center, radius);
        transposer_control.addChild(circle);

        let crosschannelend = center;

        //Draw the cross channel connecting the 2nd row valves
        let rect = new paper.Path.Rectangle({
            from: new paper.Point(crosschannelstart.x, crosschannelstart.y - channelWidth / 2),
            to: new paper.Point(crosschannelend.x, crosschannelstart.y + channelWidth / 2)
        });

        transposer_control.addChild(rect);

        //3rd Row valves

        center = new paper.Point(px + 0.5 * channelWidth + valvespacing, py + 1.5 * channelWidth + 3 * valvespacing + 2 * radius + radius);
        circle = new paper.Path.Circle(center, radius);
        transposer_control.addChild(circle);

        crosschannelstart = center;

        center = new paper.Point(center.x + 2 * valvespacing + 2 * radius + channelWidth, center.y);
        circle = new paper.Path.Circle(center, radius);
        transposer_control.addChild(circle);

        crosschannelend = center;

        //Draw the cross channel connecting the 3nd row valves
        rect = new paper.Path.Rectangle({
            from: new paper.Point(crosschannelstart.x, crosschannelstart.y - channelWidth / 2),
            to: new paper.Point(crosschannelend.x, crosschannelstart.y + channelWidth / 2)
        });

        transposer_control.addChild(rect);

        //Bottom Row valve
        center = new paper.Point(px + channelWidth + 2 * valvespacing + radius, py + 4 * valvespacing + 4 * radius + 2 * channelWidth);
        circle = new paper.Path.Circle(center, radius);
        transposer_control.addChild(circle);

        //Finally we draw the cross channel
        let topleftpoint = new paper.Point(px + valvespacing, py + channelWidth / 2 + 2 * radius + 2 * valvespacing);
        let bottomleftpoint = new paper.Point(topleftpoint.x + +4 * valvespacing + 3 * channelWidth + 4 * radius, topleftpoint.y + channelWidth);
        let rectangle = new paper.Path.Rectangle(topleftpoint, bottomleftpoint);
        transposer_control.addChild(rectangle);

        transposer_control.rotate(rotation, px + 3 * valvespacing + 1.5 * channelWidth + 2 * radius, py + channelWidth + 2 * valvespacing + 2 * radius);

        transposer_control.fillColor = color;
        return transposer_control;
    }
}
<|endoftext|>


<commit_before>function initSiteIconUpload(spaceName) {
	var publicBag = spaceName + "_public",
		csrf = window.getCSRFToken(),
		form = $('#upload')[0],
		$submit = $(form).find("[type=submit]");

	$("#siteicon").attr("src", "/bags/" + publicBag + "/tiddlers/SiteIcon");
	$submit.attr("disabled", true);
	$(form)
		.attr("action", "/bags/" + publicBag + "/tiddlers?csrf_token=" + csrf + "&redirect=/tiddlers")
		.find("[type=file]")
			.change(function(ev) {
				($(this).val() !== "") ? $submit.attr("disabled", false) : $submit.attr("disabled", true);
			})
			.end()
		.ajaxForm({
			success: function(a) {
				$("#siteicon").attr("src", "/bags/" + publicBag + "/tiddlers/SiteIcon?r=" + Math.random());
				$submit.attr("disabled", true);
			},
			error: function() {
				$("#messageArea").html("Error uploading SiteIcon.");
			}
		});

	// check HTML5 features available
	if(isFileAPIEnabled() && isDndEnabled()) {
		var dndc = new DNDFileController('dropzone', publicBag);
	} else {
		// drag and drop not available
		$("#dropzone").hide();
	}
}

// feature detection
var isDndEnabled = function() {
	return 'draggable' in document.createElement('span');
};

var isFileAPIEnabled = function() {
	return !!(window.File && window.FileList && window.FileReader);
};

// do i need CRSF token?
function DNDFileController(id, publicBag) {
	var $el = $("#" + id),
		el_ = document.getElementById(id);

	this.dragenter = function(e) {
		e.stopPropagation();
		e.preventDefault();
		$("html").addClass("dragging");
	};

	this.dragover = function(e) {
		e.stopPropagation();
		e.preventDefault();
		e.dataTransfer.dropEffect = 'copy'; // Explicitly show this is a copy.
	};

	this.dragleave = function(e) {
		e.stopPropagation();
		e.preventDefault();
		$("html").removeClass("dragging");
	}

	this.drop = function(e) {
		e.stopPropagation();
		e.preventDefault();

		var files = e.dataTransfer.files; // FileList object.

		// Loop through the FileList and render image files as thumbnails.
		for (var i = 0, f; f = files[i]; i++) {

			// Only process image files.
			if (!f.type.match('image.*')) {
				continue;
			}
			handleFile(f);
		}

		$("html").removeClass("dragging");
		return false;
	};

	// could you turn back to this.... and use call?
	var handleFile = function(file) {

		// read in files
		var reader = new FileReader();
		reader.onerror = function(ev) {
			console.log('error', ev);
		}

		// things to do once file is read in
		reader.onload = function(ev) {
			var data = ev.target.result.replace(/^data[^,]*,/, ''),
				$dzimg = $("#dropzone img");

			$dzimg.removeClass("notloading");

			var tiddler = {
					tags: [],
					text: data,
					title: "SiteIcon",
					type: file.type,
				};

			$.ajax({
				url: '/bags/' + publicBag + '/tiddlers/' + tiddler.title,
				contentType: 'application/json',
				type: 'PUT',
				data: JSON.stringify(tiddler),
				processData: false,
				success: function() {
					$("#siteicon").attr("src", "/bags/" + publicBag + "/tiddlers/SiteIcon?r=" + Math.random());
					$dzimg.addClass("notloading");
				},
				error: function(xhr, error, exc) {
					$('#messageArea').html("Error uploading SiteIcon.");
					$dzimg.addClass("notloading");
				}
			});
		};

		reader.readAsDataURL(file);
	};

	// don't use jQuery because it changes event properties...
	el_.addEventListener("dragenter", this.dragenter, false);
	el_.addEventListener("dragover", this.dragover, false);
	el_.addEventListener("dragleave", this.dragleave, false);
	el_.addEventListener("drop", this.drop, false);
};
<commit_msg>Cache jQuery DOM selection.<commit_after>function initSiteIconUpload(spaceName) {
	var publicBag = spaceName + "_public",
		csrf = window.getCSRFToken(),
		form = $('#upload')[0],
		$submit = $(form).find("[type=submit]");

	$("#siteicon").attr("src", "/bags/" + publicBag + "/tiddlers/SiteIcon");
	$submit.attr("disabled", true);
	$(form)
		.attr("action", "/bags/" + publicBag + "/tiddlers?csrf_token=" + csrf + "&redirect=/tiddlers")
		.find("[type=file]")
			.change(function(ev) {
				($(this).val() !== "") ? $submit.attr("disabled", false) : $submit.attr("disabled", true);
			})
			.end()
		.ajaxForm({
			success: function(a) {
				$("#siteicon").attr("src", "/bags/" + publicBag + "/tiddlers/SiteIcon?r=" + Math.random());
				$submit.attr("disabled", true);
			},
			error: function() {
				$("#messageArea").html("Error uploading SiteIcon.");
			}
		});

	// check HTML5 features available
	if(isFileAPIEnabled() && isDndEnabled()) {
		var dndc = new DNDFileController('dropzone', publicBag);
	} else {
		// drag and drop not available
		$("#dropzone").hide();
	}
}

// feature detection
var isDndEnabled = function() {
	return 'draggable' in document.createElement('span');
};

var isFileAPIEnabled = function() {
	return !!(window.File && window.FileList && window.FileReader);
};

// seems to work without needing CSRF token, is that correct?
function DNDFileController(id, publicBag) {
	var $el = $("#" + id),
		el_ = document.getElementById(id),
		$html = $("html");

	this.dragenter = function(e) {
		e.stopPropagation();
		e.preventDefault();
		$html.addClass("dragging");
	};

	this.dragover = function(e) {
		e.stopPropagation();
		e.preventDefault();
		e.dataTransfer.dropEffect = 'copy'; // Explicitly show this is a copy.
	};

	this.dragleave = function(e) {
		e.stopPropagation();
		e.preventDefault();
		$html.removeClass("dragging");
	}

	this.drop = function(e) {
		e.stopPropagation();
		e.preventDefault();

		var files = e.dataTransfer.files; // FileList object.

		// Loop through the FileList
		for (var i = 0, f; f = files[i]; i++) {

			// Only process image files.
			if (!f.type.match('image.*')) {
				continue;
			}
			handleFile(f);
		}

		$html.removeClass("dragging");
		return false;
	};

	var handleFile = function(file) {

		// read in files
		var reader = new FileReader();
		reader.onerror = function(ev) {
			console.log('error', ev);
		}

		// things to do once file is read in
		reader.onload = function(ev) {
			var data = ev.target.result.replace(/^data[^,]*,/, ''),
				$dzimg = $("#dropzone img");

			$dzimg.removeClass("notloading");

			var tiddler = {
					tags: [],
					text: data,
					title: "SiteIcon",
					type: file.type,
				};

			$.ajax({
				url: '/bags/' + publicBag + '/tiddlers/' + tiddler.title,
				contentType: 'application/json',
				type: 'PUT',
				data: JSON.stringify(tiddler),
				processData: false,
				success: function() {
					$("#siteicon").attr("src", "/bags/" + publicBag + "/tiddlers/SiteIcon?r=" + Math.random());
					$dzimg.addClass("notloading");
				},
				error: function(xhr, error, exc) {
					$('#messageArea').html("Error uploading SiteIcon.");
					$dzimg.addClass("notloading");
				}
			});
		};

		reader.readAsDataURL(file);
	};

	// don't use jQuery because it changes event properties...
	el_.addEventListener("dragenter", this.dragenter, false);
	el_.addEventListener("dragover", this.dragover, false);
	el_.addEventListener("dragleave", this.dragleave, false);
	el_.addEventListener("drop", this.drop, false);
};
<|endoftext|>