<commit_before>const babelify = require('babelify');
const browserify = require('browserify-middleware');
const express = require('express');
const less = require('less-middleware');
const path = require('path');

const packages = require('../../admin/client/packages');

const app = new express();

// Serve the explorer stylesheet
app.get('/index.css', (req, res) => res.sendFile(path.resolve('./fields/explorer/index.css')));

// Serve script bundles
app.get('/js/explorer.js', browserify('./fields/explorer/index.js', {
	external: packages.concat(['FieldTypes']),
	transform: [babelify.configure({
		plugins: [
			require('babel-plugin-transform-object-rest-spread'),
			require('babel-plugin-transform-object-assign'),
		],
		presets: [
			require('babel-preset-es2015'),
			require('babel-preset-react'),
		],
	}), require('brfs')],
}));

// Serve stylesheet and static assets
const elementalPath = path.join(path.dirname(require.resolve('elemental')), '..');
const reactSelectPath = path.join(path.dirname(require.resolve('react-select')), '..');
const lessOptions = {
	render: {
		modifyVars: {
			adminPath: JSON.stringify('/'),
			customStylesPath: '',
			elementalPath: JSON.stringify(elementalPath),
			reactSelectPath: JSON.stringify(reactSelectPath),
		},
	},
};
app.use('/styles', less(path.resolve('./admin/public/styles'), lessOptions));
app.use('/styles/fonts', express.static(
	path.resolve('./admin/public/js/lib/tinymce/skins/keystone/fonts')
));
app.use(express.static('./admin/public'));

// Serve the index template
app.use('/', (req, res) => res.sendFile(path.resolve('./fields/explorer/index.html')));

app.listen(8000, function () {
	console.log('Field Types Explorer ready on http://localhost:8000');
});
<commit_msg>Adding stub API for relationship field testing<commit_after>const babelify = require('babelify');
const browserify = require('browserify-middleware');
const express = require('express');
const less = require('less-middleware');
const path = require('path');

const packages = require('../../admin/client/packages');

const app = new express();

// Serve the explorer stylesheet
app.get('/index.css', (req, res) => res.sendFile(path.resolve('./fields/explorer/index.css')));

// Serve script bundles
app.get('/js/explorer.js', browserify('./fields/explorer/index.js', {
	external: packages.concat(['FieldTypes']),
	transform: [babelify.configure({
		plugins: [
			require('babel-plugin-transform-object-rest-spread'),
			require('babel-plugin-transform-object-assign'),
		],
		presets: [
			require('babel-preset-es2015'),
			require('babel-preset-react'),
		],
	}), require('brfs')],
}));

// Serve stylesheet and static assets
const elementalPath = path.join(path.dirname(require.resolve('elemental')), '..');
const reactSelectPath = path.join(path.dirname(require.resolve('react-select')), '..');
const lessOptions = {
	render: {
		modifyVars: {
			adminPath: JSON.stringify('/'),
			customStylesPath: '',
			elementalPath: JSON.stringify(elementalPath),
			reactSelectPath: JSON.stringify(reactSelectPath),
		},
	},
};
app.use('/styles', less(path.resolve('./admin/public/styles'), lessOptions));
app.use('/styles/fonts', express.static(
	path.resolve('./admin/public/js/lib/tinymce/skins/keystone/fonts')
));
app.use(express.static('./admin/public'));

// Stub API for Relationships
app.get('/api/flavours', (req, res) => res.json({
	results: [
		{ id: 'chocolate', name: 'Chocolate' },
		{ id: 'vanilla', name: 'Vanilla' },
		{ id: 'strawberry', name: 'Strawberry' },
	],
	count: 3,
}));

// Serve the index template
app.use('/', (req, res) => res.sendFile(path.resolve('./fields/explorer/index.html')));

app.listen(8000, function () {
	console.log('Field Types Explorer ready on http://localhost:8000');
});
<|endoftext|>


<commit_before>$("document").ready(function($) {
/******************************
 * Row Model, stores rows (duh)
 ******************************/
    var RowModel = function() {
        this.rows = [];
    }

    RowModel.prototype.get = function(index) {
        console.log("RowModel.get", index);
        if (index < this.rows.length) {
            return this.rows[index];
        } else {
            return undefined;
        }
    }

    /**
     * Inserts a new row before the n'th row, and populates it with the given array of images.
     */
    RowModel.prototype.create = function(beforeIndex, images) {
        var newRow = {
            images: images.slice()
        }
        console.log("create", newRow);
        this.rows.splice(beforeIndex, 0, newRow);
    }

    /**
     * Removes the n'th row from the model.
     */
    RowModel.prototype.remove = function(rowIndex) {
        this.rows.splice(rowIndex, 1);
    }

    /**
     * Removes from the n'th row the m'th image
     */
    RowModel.prototype.removeImage = function(rowIndex, imageIndex) {
        console.log("RowModel.removeImage", rowIndex, imageIndex);
        var row = this.get(rowIndex);
        var image = row.images.splice(imageIndex, 1)[0];
        return image;
    }

    /**
     * Inserts into the n'th row, the given image at position m.
     */
    RowModel.prototype.insertImage = function(rowIndex, beforeIndex, image) {
        var row = this.get(rowIndex);
        row.images.splice(beforeIndex, 0, image);
    }

    RowModel.prototype.getNumRows = function() {
        return this.rows.length;
    }

/************
 * Controller
 ************/
    var Controller = function(model, view) {
        var self = this;

        self.model = model;
        self.view = view;

        self.view.bind('removeImage', function(rowIndex, imageIndex) {
            self._removeImage(rowIndex, imageIndex);
        })

        self.view.bind('addImages', function(images) {
            self._addImages(images);
        });

        self.view.bind('moveImageToRow', function(fromRow, fromIndex, toRow, toIndex){
            var image = self._removeImage(fromRow, fromIndex);
            self._insertImage(toRow, toIndex, image);
        });

        self.view.bind('moveImageToNewRow', function(fromRow, fromIndex, toBeforeRow){
            var image = self._removeImage(fromRow, fromIndex);
            self._addRow(toBeforeRow, [image]);
        });
    }

    /**
     * Removes the image from the given row, and deletes the row if it becomes empty as a result.
     */
    Controller.prototype._removeImage = function(rowIndex, imageIndex) {
        console.log("Controller._removeImage", rowIndex, imageIndex);
        var image = this.model.removeImage(rowIndex, imageIndex);
        var row = this.model.get(rowIndex);
        console.log("after removal:", row);

        if (row.images.length == 0) {
            this.model.remove(rowIndex);
            this.view.render('removeRow', {index: rowIndex});
        } else {
            this.view.render('updateRow', {index: rowIndex, images: row.images});
        }

        return image;
    }

    /**
     * distributes a large number of images among new rows.
     */
    Controller.prototype._addImages = function(images) {
        console.log("Controller._addImages", images);
        var imagesPerRow = 4;

        while(images.length > 0) {
            this._addRow(this.model.getNumRows(), images.splice(0, imagesPerRow));
        }
    }

    /**
     * adds a row at the specified index, containing the given images.
     */
    Controller.prototype._addRow = function(beforeIndex, images) {
        console.log("Controller._addRow", beforeIndex);
        this.model.create(beforeIndex, images);
        this.view.render('insertRow', {index: beforeIndex, images: images});
    }

    Controller.prototype._insertImage = function(toRow, toIndex, image) {
        console.log("Controller._insertImage", toRow, toIndex, image);
        this.model.insertImage(toRow, toIndex, image);
        var row = this.model.get(toRow);
        this.view.render('updateRow', {index:toRow, images:row.images});
    }

/**************
 * Layout View
 **************/
    var LayoutView = function() {
        this.$el = $("#layout-view");
        this.$rows = [];
        this.handlers = [];

        this.image_margin = 10;

        this.initialize();
    }

    /**
     * creates the scaffolding, removes all state, and registers browser events.
     */
    LayoutView.prototype.initialize = function() {
        var self = this;
        self.$el.empty();

        var dropbox = document.getElementById("controls");
        dropbox.addEventListener("dragenter", self._dragEnter, false);
        dropbox.addEventListener("dragover", self._dragOver, false);
        dropbox.addEventListener("drop", function(e){self._drop(e, self);}, false);
    }

    LayoutView.prototype.render = function(action, data) {
        console.log("LayoutView.render", action, data);
        switch(action) {
            case 'insertRow':
                this.insertRow(data.index, data.images);
                break;
            case 'removeRow':
                this.removeRow(data.index);
                break;
            case 'updateRow':
                this.updateRow(data.index, data.images);
                break;
        }
    }

    /**
     * creates the dom elements and registers event handlers for the new row.
     */
    LayoutView.prototype.insertRow = function(index, images) {
        console.log("LayoutView.insertRow", index);
        var $row = $("<div>")
            .addClass("row")
            .attr("title", "Row "+index);

        if (this.$rows.length <= index) {
            this.$el.append($row);
        } else {
            this.$el.children().eq(index).before($row);
        }

        this.$rows.splice(index, 0, $row);

        this._syncRowIndices();
        this.updateRow(index, images);
    }

    /**
     * deletes the row's dom element
     */
    LayoutView.prototype.removeRow = function(index) {
        this.$rows.splice(index, 1);
        this.$el.children().eq(index).remove();
        this._syncRowIndices();
    }

    /**
     * clears the row, and adds all the images again to correct for changes to order or membership.
     */
    LayoutView.prototype.updateRow = function(index, images) {
        console.log("LayoutView.updateRow", index, images);
        var self = this;
        var $row = this.$rows[index];

        $row.empty();

        for(var i = 0; i < images.length; i++) {
            var $img = $(images[i]);
            $img.bind('click',
                function(self, imageIndex){
                    return function(e) {
                        var rowIndex = $(e.target).parent().data("index");
                        self.handlers['removeImage'](rowIndex, imageIndex);
                    }
                }(self, i));
            $img.attr("draggable", true).bind("dragstart",
                function(self, imageIndex) {
                    return function(e) {
                        var rowIndex = $(e.target).parent().data("index");

                        var dt = e.originalEvent.dataTransfer;
                        dt.setData("text/plain", rowIndex+" "+imageIndex);
                        dt.effectAllowed = "move";

                        self._dragStart(self);
                    }
                }(self, i)
            ).bind("dragend", function(e) {
                self._dragEnd(self);
            });
            $row.append($img);
        }

        this._rebalanceChildren($row, this.image_margin);
    }

    /**
     * insert spacers as drop targets.
     */
    LayoutView.prototype._dragStart = function(self) {
        console.log("LayoutView._dragStart");
        function makeSpacer(i, j) {
            return $("<div>")
                .addClass("drop-space")
                .bind("drop", function(e){
                    var dt = e.originalEvent.dataTransfer;
                    console.log(dt.getData("text/plain"), "dropped into", i, j);

                    var pos = dt.getData("text/plain").split(" ");
                    var fromRow = pos[0];
                    var fromIndex = pos[1];

                    var toRow = i;
                    var toIndex = j;

                    self.handlers['moveImageToRow'](fromRow, fromIndex, toRow, toIndex);

                    e.originalEvent.preventDefault();
                    return false;
                })
                .bind("dragover dragenter", function(e){
                    $(e.target).addClass("drop-hover");
                    e.originalEvent.preventDefault();
                })
                .bind("dragleave", function(e){
                    $(e.target).removeClass("drop-hover");
                    e.originalEvent.preventDefault();
                });
        }

        self.$el.children().each(function(i, row){
            // insert spacers before each col and at the end of the row.
            $(row).children().each(function(j, col) {
                $(col).before(makeSpacer(i, j));
            });
            $(row).append(makeSpacer(i, $(row).children().length));
            self._rebalanceChildren($(row), self.image_margin);
        });

        // insert a spacer before each row and after the last row
        //self.$el.children().each(function(i, row){
        //    $(row).before(makeSpacer(i, -1));
        //});
        //self.$el.append(makeSpacer(self.$el.children().length, -1));
    }

    LayoutView.prototype._dragEnd = function(self) {
        console.log("LayoutView._dragEnd");
        self.$el.find(".drop-space").remove();
        self.$el.children().each(function(i, row) {
            self._rebalanceChildren($(row), self.image_margin);
        });
    }

    /**
     * calculates the css element widths, heights, and positions, based on parent element width
     * and the given pixel margin to maintain between the child elements
     */
    LayoutView.prototype._rebalanceChildren = function($el, margin) {
        console.log('_rebalanceChildren', $el);

        var total_width = $el.width();
        var num_children = $el.children().length;
        var available_width = total_width - (num_children - 1) * margin;

        var widths = [];
        var heights = [];
        var max_height = 0;

        $el.children().each(function(i, col) {
            $col = $(col);
            var w = $col.width();
            var h = $col.height();
            max_height = Math.max(max_height, h);
            widths.push(w);
            heights.push(h);
        });

        var content_width = 0;

        for(var i = 0; i < num_children; i++) {
            var scaled_width = max_height * widths[i] / heights[i];
            content_width += scaled_width;
        }

        var scaled_height = available_width * max_height / content_width;
        var used_width = 0;

        $el.height(scaled_height + "px");

        $el.children().each(function(i, col) {
            var $col = $(col);
            var scaled_width = widths[i] / heights[i] * scaled_height;
            var left = used_width + i * margin;

            $col.css('left', left + "px")
                .css('width', scaled_width + "px")
                .css('height', scaled_height + "px");

            used_width += scaled_width;
        });
    }

    LayoutView.prototype._syncRowIndices = function() {
        for (var i = 0; i < this.$rows.length; i++) {
            this.$rows[i].data("index", i);
        }
    }

    LayoutView.prototype.bind = function(name, handler) {
        this.handlers[name] = handler;
    }

    LayoutView.prototype.drop = function(e, self) {
        var dt = e.dataTransfer;
        var files = dt.files;
        var images = [];
        var still_loading = files.length;

        for (var i = 0; i < files.length; i++) {
            var file = files[i];
            var img = document.createElement("img");
            images.push(img);

            img.addEventListener("load", function(e) {
                still_loading--;
                if (still_loading == 0)
                    self.handlers['addImages'](images);
            });

            img.src = window.URL.createObjectURL(file);

        }
    }

    LayoutView.prototype._drop = function(e, self) {
        e.preventDefault();
        e.stopPropagation();
        self.drop(e, self);
    }

    LayoutView.prototype._dragEnter = function(e) {
        e.preventDefault();
        e.stopPropagation();
    }

    LayoutView.prototype._dragOver = function(e) {
        e.preventDefault();
        e.stopPropagation();
    }

/*************
 * Application
 *************/

var m = new RowModel();
var v = new LayoutView();
var c = new Controller(m, v);

console.log(c);

});
<commit_msg>Enable dropping into new rows, too.<commit_after>$("document").ready(function($) {
/******************************
 * Row Model, stores rows (duh)
 ******************************/
    var RowModel = function() {
        this.rows = [];
    }

    RowModel.prototype.get = function(index) {
        console.log("RowModel.get", index);
        if (index < this.rows.length) {
            return this.rows[index];
        } else {
            return undefined;
        }
    }

    /**
     * Inserts a new row before the n'th row, and populates it with the given array of images.
     */
    RowModel.prototype.create = function(beforeIndex, images) {
        var newRow = {
            images: images.slice()
        }
        console.log("create", newRow);
        this.rows.splice(beforeIndex, 0, newRow);
    }

    /**
     * Removes the n'th row from the model.
     */
    RowModel.prototype.remove = function(rowIndex) {
        this.rows.splice(rowIndex, 1);
    }

    /**
     * Removes from the n'th row the m'th image
     */
    RowModel.prototype.removeImage = function(rowIndex, imageIndex) {
        console.log("RowModel.removeImage", rowIndex, imageIndex);
        var row = this.get(rowIndex);
        var image = row.images.splice(imageIndex, 1)[0];
        return image;
    }

    /**
     * Inserts into the n'th row, the given image at position m.
     */
    RowModel.prototype.insertImage = function(rowIndex, beforeIndex, image) {
        var row = this.get(rowIndex);
        row.images.splice(beforeIndex, 0, image);
    }

    RowModel.prototype.getNumRows = function() {
        return this.rows.length;
    }

/************
 * Controller
 ************/
    var Controller = function(model, view) {
        var self = this;

        self.model = model;
        self.view = view;

        self.view.bind('removeImage', function(rowIndex, imageIndex) {
            self._removeImage(rowIndex, imageIndex);
        })

        self.view.bind('addImages', function(images) {
            self._addImages(images);
        });

        self.view.bind('moveImageToRow', function(fromRow, fromIndex, toRow, toIndex){
            var image = self._removeImage(fromRow, fromIndex);
            self._insertImage(toRow, toIndex, image);
        });

        self.view.bind('moveImageToNewRow', function(fromRow, fromIndex, toBeforeRow){
            var image = self._removeImage(fromRow, fromIndex);
            self._addRow(toBeforeRow, [image]);
        });
    }

    /**
     * Removes the image from the given row, and deletes the row if it becomes empty as a result.
     */
    Controller.prototype._removeImage = function(rowIndex, imageIndex) {
        console.log("Controller._removeImage", rowIndex, imageIndex);
        var image = this.model.removeImage(rowIndex, imageIndex);
        var row = this.model.get(rowIndex);
        console.log("after removal:", row);

        if (row.images.length == 0) {
            this.model.remove(rowIndex);
            this.view.render('removeRow', {index: rowIndex});
        } else {
            this.view.render('updateRow', {index: rowIndex, images: row.images});
        }

        return image;
    }

    /**
     * distributes a large number of images among new rows.
     */
    Controller.prototype._addImages = function(images) {
        console.log("Controller._addImages", images);
        var imagesPerRow = 4;

        while(images.length > 0) {
            this._addRow(this.model.getNumRows(), images.splice(0, imagesPerRow));
        }
    }

    /**
     * adds a row at the specified index, containing the given images.
     */
    Controller.prototype._addRow = function(beforeIndex, images) {
        console.log("Controller._addRow", beforeIndex);
        this.model.create(beforeIndex, images);
        this.view.render('insertRow', {index: beforeIndex, images: images});
    }

    Controller.prototype._insertImage = function(toRow, toIndex, image) {
        console.log("Controller._insertImage", toRow, toIndex, image);
        this.model.insertImage(toRow, toIndex, image);
        var row = this.model.get(toRow);
        this.view.render('updateRow', {index:toRow, images:row.images});
    }

/**************
 * Layout View
 **************/
    var LayoutView = function() {
        this.$el = $("#layout-view");
        this.$rows = [];
        this.handlers = [];

        this.image_margin = 10;

        this.initialize();
    }

    /**
     * creates the scaffolding, removes all state, and registers browser events.
     */
    LayoutView.prototype.initialize = function() {
        var self = this;
        self.$el.empty();

        var dropbox = document.getElementById("controls");
        dropbox.addEventListener("dragenter", self._dragEnter, false);
        dropbox.addEventListener("dragover", self._dragOver, false);
        dropbox.addEventListener("drop", function(e){self._drop(e, self);}, false);
    }

    LayoutView.prototype.render = function(action, data) {
        console.log("LayoutView.render", action, data);
        switch(action) {
            case 'insertRow':
                this.insertRow(data.index, data.images);
                break;
            case 'removeRow':
                this.removeRow(data.index);
                break;
            case 'updateRow':
                this.updateRow(data.index, data.images);
                break;
        }
    }

    /**
     * creates the dom elements and registers event handlers for the new row.
     */
    LayoutView.prototype.insertRow = function(index, images) {
        console.log("LayoutView.insertRow", index);
        var $row = $("<div>")
            .addClass("row")
            .attr("title", "Row "+index);

        if (this.$rows.length <= index) {
            this.$el.append($row);
        } else {
            this.$el.children(".row").eq(index).before($row);
        }

        this.$rows.splice(index, 0, $row);

        this._syncRowIndices();
        this.updateRow(index, images);
    }

    /**
     * deletes the row's dom element
     */
    LayoutView.prototype.removeRow = function(index) {
        this.$rows.splice(index, 1);
        this.$el.children(".row").eq(index).remove();
        this._syncRowIndices();
    }

    /**
     * clears the row, and adds all the images again to correct for changes to order or membership.
     */
    LayoutView.prototype.updateRow = function(index, images) {
        console.log("LayoutView.updateRow", index, images);
        var self = this;
        var $row = this.$rows[index];

        $row.empty();

        for(var i = 0; i < images.length; i++) {
            var $img = $(images[i]);
            $img.bind('click',
                function(self, imageIndex){
                    return function(e) {
                        var rowIndex = $(e.target).parent().data("index");
                        self.handlers['removeImage'](rowIndex, imageIndex);
                    }
                }(self, i));
            $img.attr("draggable", true).bind("dragstart",
                function(self, imageIndex) {
                    return function(e) {
                        var rowIndex = $(e.target).parent().data("index");

                        var dt = e.originalEvent.dataTransfer;
                        dt.setData("text/plain", rowIndex+" "+imageIndex);
                        dt.effectAllowed = "move";

                        self._dragStart(self);
                    }
                }(self, i)
            ).bind("dragend", function(e) {
                self._dragEnd(self);
            });
            $row.append($img);
        }

        this._rebalanceChildren($row, this.image_margin);
    }

    /**
     * insert spacers as drop targets.
     */
    LayoutView.prototype._dragStart = function(self) {
        console.log("LayoutView._dragStart");
        function makeSpacer(i, j) {
            var $spacer = $("<div>")
                .addClass("drop-space")
                .bind("drop", function(e){
                    var dt = e.originalEvent.dataTransfer;
                    console.log(dt.getData("text/plain"), "dropped into", i, j);

                    var pos = dt.getData("text/plain").split(" ");
                    var fromRow = pos[0];
                    var fromIndex = pos[1];

                    var toRow = i;
                    var toIndex = j;

                    if (toIndex >= 0) {
                        self.handlers['moveImageToRow'](fromRow, fromIndex, toRow, toIndex);
                    } else {
                        self.handlers['moveImageToNewRow'](fromRow, fromIndex, toRow);
                    }

                    e.originalEvent.preventDefault();
                    return false;
                })
                .bind("dragover dragenter", function(e){
                    $(e.target).addClass("drop-hover");
                    e.originalEvent.preventDefault();
                })
                .bind("dragleave", function(e){
                    $(e.target).removeClass("drop-hover");
                    e.originalEvent.preventDefault();
                });
            if (j < 0) {
                $spacer.addClass("spacer-row");
            }
            return $spacer;
        }

        self.$el.children().each(function(i, row){
            // insert spacers before each col and at the end of the row.
            $(row).children().each(function(j, col) {
                $(col).before(makeSpacer(i, j));
            });
            $(row).append(makeSpacer(i, $(row).children().length));
            self._rebalanceChildren($(row), self.image_margin);
        });

        // insert a spacer before each row and after the last row
        var numRows = self.$rows.length;
        self.$el.children().each(function(i, row){
            $(row).before(makeSpacer(i, -1));
        });
        self.$el.append(makeSpacer(numRows, -1));
    }

    LayoutView.prototype._dragEnd = function(self) {
        console.log("LayoutView._dragEnd");
        self.$el.find(".drop-space").remove();
        self.$el.removeClass("enable-spacers");
        self.$el.children(".row").each(function(i, row) {
            self._rebalanceChildren($(row), self.image_margin);
        });
    }

    /**
     * calculates the css element widths, heights, and positions, based on parent element width
     * and the given pixel margin to maintain between the child elements
     */
    LayoutView.prototype._rebalanceChildren = function($el, margin) {
        console.log('_rebalanceChildren', $el);

        var total_width = $el.width();
        var num_children = $el.children().length;
        var available_width = total_width - (num_children - 1) * margin;

        var widths = [];
        var heights = [];
        var max_height = 0;

        $el.children().each(function(i, col) {
            $col = $(col);
            var w = $col.width();
            var h = $col.height();
            max_height = Math.max(max_height, h);
            widths.push(w);
            heights.push(h);
        });

        var content_width = 0;

        for(var i = 0; i < num_children; i++) {
            var scaled_width = max_height * widths[i] / heights[i];
            content_width += scaled_width;
        }

        var scaled_height = available_width * max_height / content_width;
        var used_width = 0;

        $el.height(scaled_height + "px");

        $el.children().each(function(i, col) {
            var $col = $(col);
            var scaled_width = widths[i] / heights[i] * scaled_height;
            var left = used_width + i * margin;

            $col.css('left', left + "px")
                .css('width', scaled_width + "px")
                .css('height', scaled_height + "px");

            used_width += scaled_width;
        });
    }

    LayoutView.prototype._syncRowIndices = function() {
        for (var i = 0; i < this.$rows.length; i++) {
            this.$rows[i].data("index", i);
        }
    }

    LayoutView.prototype.bind = function(name, handler) {
        this.handlers[name] = handler;
    }

    LayoutView.prototype.drop = function(e, self) {
        var dt = e.dataTransfer;
        var files = dt.files;
        var images = [];
        var still_loading = files.length;

        for (var i = 0; i < files.length; i++) {
            var file = files[i];
            var img = document.createElement("img");
            images.push(img);

            img.addEventListener("load", function(e) {
                still_loading--;
                if (still_loading == 0)
                    self.handlers['addImages'](images);
            });

            img.src = window.URL.createObjectURL(file);

        }
    }

    LayoutView.prototype._drop = function(e, self) {
        e.preventDefault();
        e.stopPropagation();
        self.drop(e, self);
    }

    LayoutView.prototype._dragEnter = function(e) {
        e.preventDefault();
        e.stopPropagation();
    }

    LayoutView.prototype._dragOver = function(e) {
        e.preventDefault();
        e.stopPropagation();
    }

/*************
 * Application
 *************/

var m = new RowModel();
var v = new LayoutView();
var c = new Controller(m, v);

console.log(c);

});
<|endoftext|>


<commit_before>import { Model } from 'objection';
import { withoutKey } from '../../utils/withoutKey';
import { renameKey } from '../../utils/renameKey';
import { applyAll } from '../../utils/applyAll';
import Role from './Role';

// Models for Membership, Project and User must be in same file to avoid circular imports

class Membership extends Model {
    static tableName = 'members';
    static relationMappings = () => ({
        project: {
            relation: Model.HasOneRelation,
            modelClass: Project,
            join: {
                from: 'members.project_id',
                to: 'projects.id'
            }
        },
        role: {
            relation: Model.HasOneThroughRelation,
            modelClass: Role,
            join: {
                from: 'members.id',
                through: {
                    from: 'member_roles.member_id',
                    to: 'member_roles.role_id'
                },
                to: 'roles.id'
            }
        },
        user: {
            relation: Model.HasOneRelation,
            modelClass: User,
            join: {
                from: 'members.user_id',
                to: 'users.id'
            }
        }
    });
}

class Project extends Model {
    static tableName = 'projects';
    static relationMappings = () => ({
        members: {
            relation: Model.HasManyRelation,
            modelClass: Membership,
            join: {
                from: 'projects.id',
                to: 'members.project_id'
            }
        },
    });
    static jsonSchema = () => ({
        type: 'object',
        required: ['identifier', 'name'],
        properties: {
            id: { type: 'integer' },
            name: { type: 'string', minLength: 1, maxLength: 255 },
            description: { type: 'string' },
        }
    });
    $formatJson(json) {
        // Called when we're about to return JSON to the outside world
        console.log('Project class about to format', json);
        json = super.$formatJson(json);
        const result = applyAll(json,
            renameKey('identifier', 'projectCode'),
        );
        console.log('Project class format result:', result);
        return result;
    }
    $parseJson(json) {
        // Called when we've received JSON from the outside world
        console.log('Project class about to parse', json);
        json = super.$parseJson(json);
        const result = applyAll(json,
            renameKey('projectCode', 'identifier')
        );
        console.log('Project class parse result:', result);
        return result;
    }
}

class User extends Model {
    static tableName = 'users';
    static relationMappings = () => ({
        projects: {
            relation: Model.ManyToManyRelation,
            modelClass: Project,
            join: {
                from: 'users.id',
                through: {
                    from: 'members.user_id',
                    to: 'members.project_id'
                },
                to: 'projects.id'
            }
        },
        memberships: {
            relation: Model.HasManyRelation,
            modelClass: Membership,
            join: {
                from: 'users.id',
                to: 'members.user_id'
            }
        }
    });
    static jsonSchema = () => ({
        type: 'object',
        required: ['login', 'name'],
        properties: {
            id: { type: 'integer' },
            name: { type: 'string', minLength: 1, maxLength: 255 },
            description: { type: 'string' },
        }
    });
    $formatJson(json) {
        // Called when we're about to return JSON to the outside world
        json = super.$formatJson(json);
        return applyAll(json,
            withoutKey('hashed_password'),
            renameKey('login', 'username'),
        );
    }
    $parseJson(json) {
        // Called when we've received JSON from the outside world
        json = super.$parseJson(json);
        const result = applyAll(json,
            renameKey('username', 'login')
        );
        return result;
    }
}

export { Membership, Project, User };
<commit_msg>jsonSchema should not be a function<commit_after>import { Model } from 'objection';
import { withoutKey } from '../../utils/withoutKey';
import { renameKey } from '../../utils/renameKey';
import { applyAll } from '../../utils/applyAll';
import Role from './Role';

// Models for Membership, Project and User must be in same file to avoid circular imports

class Membership extends Model {
    static tableName = 'members';
    static relationMappings = () => ({
        project: {
            relation: Model.HasOneRelation,
            modelClass: Project,
            join: {
                from: 'members.project_id',
                to: 'projects.id'
            }
        },
        role: {
            relation: Model.HasOneThroughRelation,
            modelClass: Role,
            join: {
                from: 'members.id',
                through: {
                    from: 'member_roles.member_id',
                    to: 'member_roles.role_id'
                },
                to: 'roles.id'
            }
        },
        user: {
            relation: Model.HasOneRelation,
            modelClass: User,
            join: {
                from: 'members.user_id',
                to: 'users.id'
            }
        }
    });
}

class Project extends Model {
    static tableName = 'projects';
    static relationMappings = () => ({
        members: {
            relation: Model.HasManyRelation,
            modelClass: Membership,
            join: {
                from: 'projects.id',
                to: 'members.project_id'
            }
        },
    });
    static jsonSchema = {
        type: 'object',
        required: ['identifier', 'name'],
        properties: {
            id: { type: 'integer' },
            name: { type: 'string', minLength: 1, maxLength: 255 },
            description: { type: 'string' },
        }
    };
    $formatJson(json) {
        // Called when we're about to return JSON to the outside world
        console.log('Project class about to format', json);
        json = super.$formatJson(json);
        const result = applyAll(json,
            renameKey('identifier', 'projectCode'),
        );
        console.log('Project class format result:', result);
        return result;
    }
    $parseJson(json) {
        // Called when we've received JSON from the outside world
        console.log('Project class about to parse', json);
        json = super.$parseJson(json);
        const result = applyAll(json,
            renameKey('projectCode', 'identifier')
        );
        console.log('Project class parse result:', result);
        return result;
    }
}

class User extends Model {
    static tableName = 'users';
    static relationMappings = () => ({
        projects: {
            relation: Model.ManyToManyRelation,
            modelClass: Project,
            join: {
                from: 'users.id',
                through: {
                    from: 'members.user_id',
                    to: 'members.project_id'
                },
                to: 'projects.id'
            }
        },
        memberships: {
            relation: Model.HasManyRelation,
            modelClass: Membership,
            join: {
                from: 'users.id',
                to: 'members.user_id'
            }
        }
    });
    static jsonSchema = () => ({
        type: 'object',
        required: ['login', 'name'],
        properties: {
            id: { type: 'integer' },
            name: { type: 'string', minLength: 1, maxLength: 255 },
            description: { type: 'string' },
        }
    });
    $formatJson(json) {
        // Called when we're about to return JSON to the outside world
        json = super.$formatJson(json);
        return applyAll(json,
            withoutKey('hashed_password'),
            renameKey('login', 'username'),
        );
    }
    $parseJson(json) {
        // Called when we've received JSON from the outside world
        json = super.$parseJson(json);
        const result = applyAll(json,
            renameKey('username', 'login')
        );
        return result;
    }
}

export { Membership, Project, User };
<|endoftext|>


<commit_before>var Hours = {

  // This file contains Opening Hours for the SiT cantinas.
  // SiTs new format for ajaxing hours is a POST api:
  // curl --data "diner=2532" https://www.sit.no/ajaxdiner/get

  api: 'https://www.sit.no/ajaxdiner/get',
  debugHours: 0,
  cantinas: {
    'administrasjon': 2379,
    'dmmh': 2534,
    'dragvoll': 1593, // got dinner info
    'dragvoll idrettssenter': 2517,
    'elektro': 2518,
    'hangaren': 2519, // got dinner info
    'kalvskinnet': 2529, // got dinner info
    'kjelhuset': 2520,
    'moholt': 2530, // got dinner info
    'mtfs': 2526,
    'ranheimsveien': 2531,
    'realfag': 2521, // got dinner info
    'rotvoll': 2532,
    'tunga': 2533,
    'tyholt': 2525, // got dinner info
    'øya': 2527,
    'storkiosk dragvoll': 2393,
    'storkiosk gløshaugen': 2524,
    'storkiosk øya': 2528,
    'sito dragvoll': 2602,
    'sito realfag': 2522,
    'sito stripa': 2523,
  },

  get: function (cantina, callback) {
    if (callback === undefined) {
      console.log('ERROR: Callback is required. In the callback you should insert the results into the DOM.');
      return;
    }

    cantina = cantina.toLowerCase();
    var postString = 'diner='+this.cantinas[cantina];

    var self = this;
    $.ajax({
      type: 'POST',
      data: postString,
      url: self.api,
      dataType: 'json',
      success: function(json) {
        if (self.debugHours) console.log('Untreated JSON:', json);

        // Strip away JSON and HTML
        allHours = self.stripJsonAndHtml(json);
        if (self.debugHours) console.log('Entire string:', allHours);

        // Find todays hours
        todaysHours = self.findTodaysHours(allHours);
        if (self.debugHours) console.log('Todays hours:', todaysHours);

        // Prettify todays hours
        prettyHours = self.prettifyTodaysHours(todaysHours);
        if (self.debugHours) console.log('Pretty hours:', prettyHours);

        callback(prettyHours);
      },
      error: function(jqXHR, text, err) {
        callback('Klarte ikke koble til sit.no/ajax');
      },
    });
  },

  stripJsonAndHtml: function(data) {
    var htmlString = data.html;
    return htmlString.replace(/<(?:.|\n)*?>/gm, '');
  },

  findTodaysHours: function(allHours) {
    var day = new Date().getDay();
    var pieces = allHours.split('\n');
    if (1 <= day && day <= 4) {
      return '- ' + pieces[0];
    }
    else if (day === 5) {
      return '- ' + pieces[1];
    }
    else if (day === 0 || day === 6) {
      return '- Kantinen er stengt';
    }
    else {
      console.log('ERROR: How in the world did you get here?');
      return '';
    }
  },

  prettifyTodaysHours: function(todays) {
    // All dots to colons
    todays = todays.replace(/\./gm,':');
    // Remove unnecessarily specific time info :00
    todays = todays.replace(/:00/gm, '');
    // Remove colon after day names
    todays = todays.replace(/: /gm, ' ');
    return todays;
  },

}
<commit_msg>Added more debugging routines, also variable renaming<commit_after>var Hours = {

  // This file contains Opening Hours for the SiT cantinas.
  // SiTs new format for ajaxing hours is a POST api:
  // curl --data "diner=2532" https://www.sit.no/ajaxdiner/get

  api: 'https://www.sit.no/ajaxdiner/get',
  msgClosed: 'Kantinen er nok stengt',
  msgConnectionError: 'Frakoblet fra sit.no/ajax',
  msgMalformedHours: 'Galt format på åpningstider',
  debugHours: 1, // General debugging
  debugHoursText: 1, // Deep debugging of a specific string, insert below
  debugHoursString: 'Mandag - torsdag 10.00 - 17.30\nFredag 10.00 - 14.00\nRealfagbygget på Gløshaugen 73 55 12 52 sit.kafe.realfag@sit.no', // debugHoursText must be true
  // debugHoursString is expected to be pre-stripped of JSON and HTML, otherwise intact
  cantinas: {
    'administrasjon': 2379,
    'dmmh': 2534,
    'dragvoll': 1593, // got dinner info
    'dragvoll idrettssenter': 2517,
    'elektro': 2518,
    'hangaren': 2519, // got dinner info
    'kalvskinnet': 2529, // got dinner info
    'kjelhuset': 2520,
    'moholt': 2530, // got dinner info
    'mtfs': 2526,
    'ranheimsveien': 2531,
    'realfag': 2521, // got dinner info
    'rotvoll': 2532,
    'tunga': 2533,
    'tyholt': 2525, // got dinner info
    'øya': 2527,
    'storkiosk dragvoll': 2393,
    'storkiosk gløshaugen': 2524,
    'storkiosk øya': 2528,
    'sito dragvoll': 2602,
    'sito realfag': 2522,
    'sito stripa': 2523,
  },

  get: function (cantina, callback) {
    if (callback === undefined) {
      console.log('ERROR: Callback is required. In the callback you should insert the results into the DOM.');
      return;
    }

    cantina = cantina.toLowerCase();
    var postString = 'diner='+this.cantinas[cantina];

    var self = this;
    $.ajax({
      type: 'POST',
      data: postString,
      url: self.api,
      dataType: 'json',
      success: function(json) {
        if (self.debugHours) console.log('Untreated JSON:', json);

        // Strip away JSON and HTML
        allHours = self.stripJsonAndHtml(json);
        if (self.debugHours) console.log('Entire string:', allHours);

        // Debugging a particular string now?
        if (self.debugHoursText) allHours = self.debugHoursString;

        // Find todays hours
        todaysHours = self.findTodaysHours(allHours);
        if (self.debugHours) console.log('Todays hours:', todaysHours);

        // Prettify todays hours
        prettyHours = self.prettifyTodaysHours(todaysHours);
        if (self.debugHours) console.log('Pretty hours:', prettyHours);

        callback(prettyHours);
      },
      error: function(jqXHR, text, err) {
        callback(self.msgConnectionError);
      },
    });
  },

  stripJsonAndHtml: function(data) {
    var htmlString = data.html;
    return htmlString.replace(/<(?:.|\n)*?>/gm, '');
  },

  findTodaysHours: function(allHours) {
    var day = new Date().getDay();
    var pieces = allHours.split('\n');
    if (1 <= day && day <= 4) {
      return '- ' + pieces[0];
    }
    else if (day === 5) {
      return '- ' + pieces[1];
    }
    else if (day === 0 || day === 6) {
      return '- ' + this.msgClosed;
    }
    else {
      console.log('ERROR: How in the world did you get here?');
      return this.msgMalformedHours;
    }
  },

  prettifyTodaysHours: function(todays) {
    // All dots to colons
    todays = todays.replace(/\./gm,':');
    // Remove unnecessarily specific time info :00
    todays = todays.replace(/:00/gm, '');
    // Remove colon after day names
    todays = todays.replace(/: /gm, ' ');
    return todays;
  },

}
<|endoftext|>


<commit_before>var _ = require('lodash');
var fs = require('fs');
var cluster = require('cluster');
var logger = require('log4js').getLogger('SATAN');

var Satan = function(defaultConfig, configFilePath) {
	this.workers = {};
	this.workerCount = 0;
	this.loadingCount = 0;
	this.targetWorkerCount = 0;
	this.defaultConfig = defaultConfig;
	this.configFile = {};
	this.configFilePath = configFilePath;
};

Satan.prototype.refreshConfiguration = function() {
	if (this.configFilePath) {
		try {
			this.configFile = JSON.parse(fs.readFileSync(this.configFilePath));
		} catch (e) {
			logger.error('Failed to read config file! Continuing anyway with previous configuration...');
			logger.error(e);
		}
	}

	this.config = _.defaults({
		arguments: (this.defaultConfig.arguments || []).concat(this.configFile.arguments || [])
	}, this.defaultConfig, this.configFile, {
		instances: 1,
		schedulingPolicy: 'rr',
		nodeArguments: process.execArgs
	});

	this.targetWorkerCount = this.config.instances;
};

Satan.prototype.prepare = function() {
	var self = this;

	switch (this.config.schedulingPolicy) {
		case 'rr':
			cluster.schedulingPolicy = cluster.SCHED_RR;
			break;
		case 'none':
			cluster.schedulingPolicy = cluster.SCHED_NODE;
			break;
		default:
			throw new Error("Invalid scheduling policy " + this.config.schedulingPolicy);
			break;
	}

	cluster.setupMaster({
		execArgs: this.config.nodeArguments,
		exec: this.config.script,
		args: this.config.arguments
	});

	process.on('SIGHUP', function() {
		logger.debug('Caught SIGHUP!');
		self.reload();
	});
};

Satan.prototype.ensureWorkerCount = function() {
	var instanceDelta = function() {
		return this.targetWorkerCount - this.workerCount - this.loadingCount;
	}.bind(this);

	if (instanceDelta() > 0) {
		logger.info('Upscaling to %s worker(s).', this.targetWorkerCount);

		while (instanceDelta() > 0) {
			this.spawnWorker();
		}
	} else if (instanceDelta() < 0) {
		logger.info('Downscaling to %s worker(s).', this.targetWorkerCount);

		while (instanceDelta() < 0) {
			this.removeWorker(Object.keys[this.workers][0]);
		}
	} else {
		return;
	}

	// Ensures that the config is correct
	this.ensureWorkerCount();
};

Satan.prototype.start = function() {
	this.refreshConfiguration();
	this.prepare();
	this.ensureWorkerCount();
};

Satan.prototype.reload = function() {
	var oldWorkers = Object.keys(this.workers);

	logger.info('Reloading configuration...');
	this.refreshConfiguration();
	this.ensureWorkerCount();

	logger.info('Reloading all remaining workers...');
	for (var i = 0; i < oldWorkers.length; i++) {
		var worker = this.workers[oldWorkers[i]];
		// Check if worker still exists
		if (!worker || worker.satanState != 'online') {
			continue;
		}

		this.reloadWorker(oldWorkers[i]);
	}
};

Satan.prototype.spawnWorker = function(notify) {
	var self = this;
	var worker = cluster.fork();

	if (notify === undefined) {
		notify = true;
	}

	worker.once('disconnect', function() {
		delete self.workers[worker.id];

		if (!this.ignoreDisconnect) {
			logger.warn('Worker %s disconnected!', worker.id);
			self.workerCount--;
		}

		self.ensureWorkerCount();
	});

	if (notify) {
		this.loadingCount++;
	}

	worker.satanState = 'loading';

	this.workers[worker.id] = worker;

	worker.once('online', function() {
		worker.satanState = 'online';

		if (notify) {
			self.workerCount++;
			self.loadingCount--;
		}
	});

	logger.info('Spawned worker #%s...', worker.id);

	return worker;
};

Satan.prototype.reloadWorker = function(id) {
	var worker = this.workers[id];

	if (!worker) {
		return;
	}

	logger.info('Reloading worker #%s...', id);

	worker.satanState = 'reloading';

	var newWorker = this.spawnWorker(false);

	newWorker.once('listening', function() {
		worker.ignoreDisconnect = true;
		worker.send('shutdown');
	});

	return newWorker;
};

Satan.prototype.removeWorker = function(id) {
	var worker = this.workers[id];

	if (!worker) {
		return;
	}

	logger.info('Removing worker #%s...', id);

	this.workerCount--;

	worker.ignoreDisconnect = true;
	worker.satanState = 'unloading';
	worker.send('shutdown');
};

module.exports = Satan;

<commit_msg>Change from SIGHUP to SIGUSR2.<commit_after>var _ = require('lodash');
var fs = require('fs');
var cluster = require('cluster');
var logger = require('log4js').getLogger('SATAN');

var Satan = function(defaultConfig, configFilePath) {
	this.workers = {};
	this.workerCount = 0;
	this.loadingCount = 0;
	this.targetWorkerCount = 0;
	this.defaultConfig = defaultConfig;
	this.configFile = {};
	this.configFilePath = configFilePath;
};

Satan.prototype.refreshConfiguration = function() {
	if (this.configFilePath) {
		try {
			this.configFile = JSON.parse(fs.readFileSync(this.configFilePath));
		} catch (e) {
			logger.error('Failed to read config file! Continuing anyway with previous configuration...');
			logger.error(e);
		}
	}

	this.config = _.defaults({
		arguments: (this.defaultConfig.arguments || []).concat(this.configFile.arguments || [])
	}, this.defaultConfig, this.configFile, {
		instances: 1,
		schedulingPolicy: 'rr',
		nodeArguments: process.execArgs
	});

	this.targetWorkerCount = this.config.instances;
};

Satan.prototype.prepare = function() {
	var self = this;

	switch (this.config.schedulingPolicy) {
		case 'rr':
			cluster.schedulingPolicy = cluster.SCHED_RR;
			break;
		case 'none':
			cluster.schedulingPolicy = cluster.SCHED_NODE;
			break;
		default:
			throw new Error("Invalid scheduling policy " + this.config.schedulingPolicy);
			break;
	}

	cluster.setupMaster({
		execArgs: this.config.nodeArguments,
		exec: this.config.script,
		args: this.config.arguments
	});

	process.on('SIGUSR2', function() {
		logger.debug('Caught SIGUSR2!');
		self.reload();
	});
};

Satan.prototype.ensureWorkerCount = function() {
	var instanceDelta = function() {
		return this.targetWorkerCount - this.workerCount - this.loadingCount;
	}.bind(this);

	if (instanceDelta() > 0) {
		logger.info('Upscaling to %s worker(s).', this.targetWorkerCount);

		while (instanceDelta() > 0) {
			this.spawnWorker();
		}
	} else if (instanceDelta() < 0) {
		logger.info('Downscaling to %s worker(s).', this.targetWorkerCount);

		while (instanceDelta() < 0) {
			this.removeWorker(Object.keys[this.workers][0]);
		}
	} else {
		return;
	}

	// Ensures that the config is correct
	this.ensureWorkerCount();
};

Satan.prototype.start = function() {
	this.refreshConfiguration();
	this.prepare();
	this.ensureWorkerCount();
};

Satan.prototype.reload = function() {
	var oldWorkers = Object.keys(this.workers);

	logger.info('Reloading configuration...');
	this.refreshConfiguration();
	this.ensureWorkerCount();

	logger.info('Reloading all remaining workers...');
	for (var i = 0; i < oldWorkers.length; i++) {
		var worker = this.workers[oldWorkers[i]];
		// Check if worker still exists
		if (!worker || worker.satanState != 'online') {
			continue;
		}

		this.reloadWorker(oldWorkers[i]);
	}
};

Satan.prototype.spawnWorker = function(notify) {
	var self = this;
	var worker = cluster.fork();

	if (notify === undefined) {
		notify = true;
	}

	worker.once('disconnect', function() {
		delete self.workers[worker.id];

		if (!this.ignoreDisconnect) {
			logger.warn('Worker %s disconnected!', worker.id);
			self.workerCount--;
		}

		self.ensureWorkerCount();
	});

	if (notify) {
		this.loadingCount++;
	}

	worker.satanState = 'loading';

	this.workers[worker.id] = worker;

	worker.once('online', function() {
		worker.satanState = 'online';

		if (notify) {
			self.workerCount++;
			self.loadingCount--;
		}
	});

	logger.info('Spawned worker #%s...', worker.id);

	return worker;
};

Satan.prototype.reloadWorker = function(id) {
	var worker = this.workers[id];

	if (!worker) {
		return;
	}

	logger.info('Reloading worker #%s...', id);

	worker.satanState = 'reloading';

	var newWorker = this.spawnWorker(false);

	newWorker.once('listening', function() {
		worker.ignoreDisconnect = true;
		worker.send('shutdown');
	});

	return newWorker;
};

Satan.prototype.removeWorker = function(id) {
	var worker = this.workers[id];

	if (!worker) {
		return;
	}

	logger.info('Removing worker #%s...', id);

	this.workerCount--;

	worker.ignoreDisconnect = true;
	worker.satanState = 'unloading';
	worker.send('shutdown');
};

module.exports = Satan;

<|endoftext|>


<commit_before>// FIXME decide default value for snapToPixel

goog.provide('ol.style.Image');

/**
 * @constructor
 * @param {ol.style.ImageOptions} options Options.
 */
ol.style.Image = function(options) {

  /**
   * @type {ol.Pixel}
   */
  this.anchor = options.anchor;

  /**
   * @type {HTMLCanvasElement|HTMLVideoElement|Image}
   */
  this.image = options.image;

  /**
   * @type {number|undefined}
   */
  this.rotation = options.rotation;

  /**
   * @type {ol.Size}
   */
  this.size = options.size;

  /**
   * @type {boolean|undefined}
   */
  this.snapToPixel = options.snapToPixel;

  /**
   * @type {boolean|undefined}
   */
  this.subtractViewRotation = options.subtractViewRotation;

};
<commit_msg>Make ol.style.Image loadable<commit_after>// FIXME decide default value for snapToPixel

goog.provide('ol.style.Image');
goog.provide('ol.style.ImageState');

goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.events');
goog.require('goog.events.EventTarget');
goog.require('goog.events.EventType');

/**
 * @enum {number}
 */
ol.style.ImageState = {
  IDLE: 0,
  LOADING: 1,
  LOADED: 2,
  ERROR: 3
};

/**
 * @constructor
 * @param {ol.style.ImageOptions} options Options.
 * @extends {goog.events.EventTarget}
 */
ol.style.Image = function(options) {

  goog.base(this);

  /**
   * @type {ol.Pixel}
   */
  this.anchor = options.anchor;

  /**
   * @type {HTMLCanvasElement|HTMLVideoElement|Image}
   */
  this.image = goog.isDefAndNotNull(options.image) ?
      options.image : new Image();
  if (!goog.isNull(options.crossOrigin)) {
    this.image.crossOrigin = options.crossOrigin;
  }

  /**
   * @type {ol.style.ImageState}
   */
  this.imageState = goog.isDef(options.imageState) ?
      options.imageState : ol.style.ImageState.IDLE;

  /**
   * @type {number|undefined}
   */
  this.rotation = options.rotation;

  /**
   * @type {ol.Size}
   */
  this.size = options.size;

  /**
   * @type {boolean|undefined}
   */
  this.snapToPixel = options.snapToPixel;

  /**
   * @type {boolean|undefined}
   */
  this.subtractViewRotation = options.subtractViewRotation;

  /**
   * @private
   * @type {Array.<number>}
   */
  this.imageListenerKeys_ = null;

  /**
   * @private
   * @type {string|undefined}
   */
  this.src_ = options.src;

};
goog.inherits(ol.style.Image, goog.events.EventTarget);

/**
 * @private
 */
ol.style.Image.prototype.dispatchChangeEvent_ = function() {
  this.dispatchEvent(goog.events.EventType.CHANGE);
};

/**
 * Tracks loading or read errors.
 *
 * @private
 */
ol.style.Image.prototype.handleImageError_ = function() {
  this.imageState = ol.style.ImageState.ERROR;
  this.unlistenImage_();
  this.dispatchChangeEvent_();
};

/**
 * Tracks successful image load.
 *
 * @private
 */
ol.style.Image.prototype.handleImageLoad_ = function() {
  this.imageState = ol.style.ImageState.LOADED;
  this.unlistenImage_();
  this.dispatchChangeEvent_();
};

/**
 * Load not yet loaded URI.
 */
ol.style.Image.prototype.load = function() {
  if (this.imageState == ol.style.ImageState.IDLE) {
    goog.asserts.assert(goog.isDef(this.src_));
    goog.asserts.assert(goog.isNull(this.imageListenerKeys_));
    this.imageState = ol.style.ImageState.LOADING;
    this.imageListenerKeys_ = [
      goog.events.listenOnce(this.image, goog.events.EventType.ERROR,
          this.handleImageError_, false, this),
      goog.events.listenOnce(this.image, goog.events.EventType.LOAD,
          this.handleImageLoad_, false, this)
    ];
    this.image.src = this.src_;
  }
};

/**
 * Discards event handlers which listen for load completion or errors.
 *
 * @private
 */
ol.style.Image.prototype.unlistenImage_ = function() {
  goog.asserts.assert(!goog.isNull(this.imageListenerKeys_));
  goog.array.forEach(this.imageListenerKeys_, goog.events.unlistenByKey);
  this.imageListenerKeys_ = null;
};
<|endoftext|>


<commit_before>$(document).ready(() => {
	const $autoLoggoutAlertModal = $('.auto-logout-alert-modal');
	const showModalOnRemainingSeconds = $autoLoggoutAlertModal.find('.form-group').data('showOnRemainingSec') || 3600;
	// default remaining session time in sec, will be overwritten by server
	const rstDefault = showModalOnRemainingSeconds * 2;
	const maxTotalRetrys = 1;

	let rst = rstDefault; // remaining session time in sec
	let processing = false;

	populateModalForm($autoLoggoutAlertModal, {});

	const showAutoLogoutModal = ((status) => {
		// switching between  to texts inlcuded in the modal
		if (status === 'error') {
			$autoLoggoutAlertModal.find('.sloth-default').hide();
			$autoLoggoutAlertModal.find('.sloth-error').show();
		} else {
			$autoLoggoutAlertModal.find('.sloth-default').show();
			$autoLoggoutAlertModal.find('.sloth-error').hide();
		}
		$autoLoggoutAlertModal.find('.modal-header').remove();
		$autoLoggoutAlertModal.modal('show');
	});

	const decRst = (() => {
		setTimeout(() => {
			if (rst >= 60) {
				rst -= 60;
				$('.js-time').text(Math.floor(rst / 60));
				// show auto loggout alert modal
				// don't show modal while processing
				if (!processing && rst <= showModalOnRemainingSeconds) {
					showAutoLogoutModal('default');
				}
				decRst();
			}
		}, 1000 * 60);
	});

	// Sync rst with Server every 5 mins
	const syncRst = (() => {
		setInterval(() => {
			$.post('/account/ttl', ((result) => {
				const { ttl } = result; // in sec
				if (typeof ttl === 'number') {
					rst = ttl;
				}
			}));
		}, 1000 * 60 * 5);
	});

	let retry = 0;
	let totalRetry = 0;
	// extend session
	const IStillLoveYou = (async () => {
		$.post('/account/ttl', { resetTimer: true }, () => {
			// on success
			processing = false;
			totalRetry = 0;
			retry = 0;
			rst = rstDefault;
			$.showNotification('Sitzung erfolgreich verlängert.', 'success', true);
		}).fail(() => {
			// retry 4 times before showing error
			if (retry < 4) {
				retry += 1;
				setTimeout(() => {
					IStillLoveYou();
				}, (2 ** retry) * 1000);
			} else {
				retry = 0;
				if (totalRetry === maxTotalRetrys) {
					/* eslint-disable-next-line max-len */
					$.showNotification('Deine Sitzung konnte nicht verlängert werden! Bitte speichere deine Arbeit und lade die Seite neu.', 'danger', false);
				} else {
					showAutoLogoutModal('error');
				}
				totalRetry += 1;
			}
		});
	});

	decRst(); // dec. rst
	syncRst(); // Sync rst with Server

	$autoLoggoutAlertModal.find('.btn-incRst').on('click', (e) => {
		e.stopPropagation();
		e.preventDefault();
		processing = true;
		$autoLoggoutAlertModal.modal('hide');
	});

	// on modal close (button or backdrop)
	$autoLoggoutAlertModal.on('hidden.bs.modal', () => {
		processing = true;
		IStillLoveYou();
	});
});
<commit_msg>reset to time<commit_after>$(document).ready(() => {
	const $autoLoggoutAlertModal = $('.auto-logout-alert-modal');
	const showModalOnRemainingSeconds = $autoLoggoutAlertModal.find('.form-group').data('showOnRemainingSec') || 3600;
	// default remaining session time in sec, will be overwritten by server
	const rstDefault = 7200;
	const maxTotalRetrys = 1;

	let rst = rstDefault; // remaining session time in sec
	let processing = false;

	populateModalForm($autoLoggoutAlertModal, {});

	const showAutoLogoutModal = ((status) => {
		// switching between  to texts inlcuded in the modal
		if (status === 'error') {
			$autoLoggoutAlertModal.find('.sloth-default').hide();
			$autoLoggoutAlertModal.find('.sloth-error').show();
		} else {
			$autoLoggoutAlertModal.find('.sloth-default').show();
			$autoLoggoutAlertModal.find('.sloth-error').hide();
		}
		$autoLoggoutAlertModal.find('.modal-header').remove();
		$autoLoggoutAlertModal.modal('show');
	});

	const decRst = (() => {
		setTimeout(() => {
			if (rst >= 60) {
				rst -= 60;
				$('.js-time').text(Math.floor(rst / 60));
				// show auto loggout alert modal
				// don't show modal while processing
				if (!processing && rst <= showModalOnRemainingSeconds) {
					showAutoLogoutModal('default');
				}
				decRst();
			}
		}, 1000 * 60);
	});

	// Sync rst with Server every 5 mins
	const syncRst = (() => {
		setInterval(() => {
			$.post('/account/ttl', ((result) => {
				const { ttl } = result; // in sec
				if (typeof ttl === 'number') {
					rst = ttl;
				}
			}));
		}, 1000 * 60 * 5);
	});

	let retry = 0;
	let totalRetry = 0;
	// extend session
	const IStillLoveYou = (async () => {
		$.post('/account/ttl', { resetTimer: true }, () => {
			// on success
			processing = false;
			totalRetry = 0;
			retry = 0;
			rst = rstDefault;
			$.showNotification('Sitzung erfolgreich verlängert.', 'success', true);
		}).fail(() => {
			// retry 4 times before showing error
			if (retry < 4) {
				retry += 1;
				setTimeout(() => {
					IStillLoveYou();
				}, (2 ** retry) * 1000);
			} else {
				retry = 0;
				if (totalRetry === maxTotalRetrys) {
					/* eslint-disable-next-line max-len */
					$.showNotification('Deine Sitzung konnte nicht verlängert werden! Bitte speichere deine Arbeit und lade die Seite neu.', 'danger', false);
				} else {
					showAutoLogoutModal('error');
				}
				totalRetry += 1;
			}
		});
	});

	decRst(); // dec. rst
	syncRst(); // Sync rst with Server

	$autoLoggoutAlertModal.find('.btn-incRst').on('click', (e) => {
		e.stopPropagation();
		e.preventDefault();
		processing = true;
		$autoLoggoutAlertModal.modal('hide');
	});

	// on modal close (button or backdrop)
	$autoLoggoutAlertModal.on('hidden.bs.modal', () => {
		processing = true;
		IStillLoveYou();
	});
});
<|endoftext|>


<commit_before>import React from 'react';
import ReactDOM from 'react-dom';
import vrtest from 'vrtest/client';
import webfontloader from 'webfontloader';
import TestViewer from './TestViewer';

// Get all the tests specifically written for preventing regressions.
const requireRegression = require.context('./tests', true, /js$/);
const regressions = requireRegression.keys().reduce((res, path) => {
  const [suite, name] = path
    .replace('./', '')
    .replace('.js', '')
    .split('/');
  res.push({
    path,
    suite: `regression-${suite}`,
    name,
    case: requireRegression(path).default,
  });
  return res;
}, []);

const blacklistSuite = [
  // Flaky
  'docs-demos-progress',
  'docs-discover-more', // GitHub images

  // Needs interaction
  'docs-demos-dialogs',
  'docs-demos-menus',
  'docs-demos-tooltips',

  // Useless
  'docs-', // Home
  'docs-guides',
];

const blacklistName = [
  'docs-getting-started-usage/Usage', // codesandbox inside
];

// Also use some of the demos to avoid code duplication.
const requireDemos = require.context('docs/src/pages', true, /js$/);
const demos = requireDemos.keys().reduce((res, path) => {
  const [name, ...suiteArray] = path
    .replace('./', '')
    .replace('.js', '')
    .split('/')
    .reverse();
  const suite = `docs-${suiteArray.reverse().join('-')}`;

  if (!blacklistSuite.includes(suite) && !blacklistName.includes(`${suite}/${name}`)) {
    res.push({
      path,
      suite,
      name,
      case: requireDemos(path).default,
    });
  }

  return res;
}, []);

const rootEl = document.createElement('div');
rootEl.style.display = 'inline-block';

vrtest.before(() => {
  if (document && document.body) {
    document.body.appendChild(rootEl);
  }

  return new Promise(resolve => {
    webfontloader.load({
      google: {
        families: ['Roboto:300,400,500', 'Material+Icons'],
      },
      timeout: 20000,
      active: () => {
        resolve('active');
      },
      inactive: () => {
        resolve('inactive');
      },
    });
  });
});

let suite;

const tests = regressions.concat(demos);
tests.forEach(test => {
  if (!suite || suite.name !== test.suite) {
    suite = vrtest.createSuite(test.suite);
  }

  suite.createTest(test.name, () => {
    const TestCase = test.case;
    ReactDOM.render(
      <TestViewer>
        <TestCase />
      </TestViewer>,
      rootEl,
    );
  });
});
<commit_msg>[test] Fix regression tests<commit_after>import React from 'react';
import ReactDOM from 'react-dom';
import vrtest from 'vrtest/client';
import webfontloader from 'webfontloader';
import TestViewer from './TestViewer';

// Get all the tests specifically written for preventing regressions.
const requireRegression = require.context('./tests', true, /js$/);
const regressions = requireRegression.keys().reduce((res, path) => {
  const [suite, name] = path
    .replace('./', '')
    .replace('.js', '')
    .split('/');
  res.push({
    path,
    suite: `regression-${suite}`,
    name,
    case: requireRegression(path).default,
  });
  return res;
}, []);

const blacklistSuite = [
  // Flaky
  'docs-demos-progress',
  'docs-discover-more', // GitHub images

  // Needs interaction
  'docs-demos-dialogs',
  'docs-demos-menus',
  'docs-demos-tooltips',

  // Useless
  'docs-', // Home
  'docs-guides',
];

const blacklistName = [
  'docs-getting-started-usage/Usage', // codesandbox inside
  'docs-demos-drawers/tileData', // raw data
  'docs-demos-grid-list/tileData', // raw data
];

// Also use some of the demos to avoid code duplication.
const requireDemos = require.context('docs/src/pages', true, /js$/);
const demos = requireDemos.keys().reduce((res, path) => {
  const [name, ...suiteArray] = path
    .replace('./', '')
    .replace('.js', '')
    .split('/')
    .reverse();
  const suite = `docs-${suiteArray.reverse().join('-')}`;

  if (!blacklistSuite.includes(suite) && !blacklistName.includes(`${suite}/${name}`)) {
    res.push({
      path,
      suite,
      name,
      case: requireDemos(path).default,
    });
  }

  return res;
}, []);

const rootEl = document.createElement('div');
rootEl.style.display = 'inline-block';

vrtest.before(() => {
  if (document && document.body) {
    document.body.appendChild(rootEl);
  }

  return new Promise(resolve => {
    webfontloader.load({
      google: {
        families: ['Roboto:300,400,500', 'Material+Icons'],
      },
      timeout: 20000,
      active: () => {
        resolve('active');
      },
      inactive: () => {
        resolve('inactive');
      },
    });
  });
});

let suite;

const tests = regressions.concat(demos);
tests.forEach(test => {
  if (!suite || suite.name !== test.suite) {
    suite = vrtest.createSuite(test.suite);
  }

  suite.createTest(test.name, () => {
    const TestCase = test.case;
    ReactDOM.render(
      <TestViewer>
        <TestCase />
      </TestViewer>,
      rootEl,
    );
  });
});
<|endoftext|>


<commit_before>module.exports = function (FacebookStrategy, GoogleStrategy, rethinkdb, appconfig, passport) {
  var GOOGLE_CLIENT_ID = appconfig.auth.google.clientID;
  var GOOGLE_CLIENT_SECRET = appconfig.auth.google.clientSecret;
  var FACEBOOK_CLIENT_ID = appconfig.auth.facebook.clientID;
  var FACEBOOK_CLIENT_SECRET = appconfig.auth.facebook.clientSecret;

  // Passport session setup.
  //   To support persistent login sessions, Passport needs to be able to
  //   serialize users into and deserialize users out of the session.  Typically,
  //   this will be as simple as storing the user ID when serializing, and finding
  //   the user by ID when deserializing.  However, since this example does not
  //   have a database of user records, the complete Google profile is
  //   serialized and deserialized.
  passport.serializeUser(function(user, done) {
    done(null, user);
  });

  passport.deserializeUser(function(obj, done) {
    done(null, obj);
  });

  var loginCallbackHandler = function (objectMapper, type) {
  return function (accessToken, refreshToken, profile, done) {
    console.log(profile);
    if (accessToken !== null) {
      rethinkdb
        .table('users')
        .getAll(profile.emails[0].value, { index: 'email' })
        //.filter({ type: type })
        .run(rethinkdb.conn)
        .then(function (cursor) {
          return cursor.toArray()
            .then(function (users) {
              if (users.length > 0) {
                return done(null, users[0]);
              }
              return rethinkdb.table('users')
                .insert(objectMapper(profile))
                .run(rethinkdb.conn)
                .then(function (response) {
                  return rethinkdb.table('users')
                    .get(response.generated_keys[0])
                    .run(rethinkdb.conn);
                })
                .then(function (newUser) {
                  done(null, newUser);
                });
            });
        })
        .catch(function (err) {
          console.log('Error Getting User', err);
        });
      }
    };
  };
  // Use the GoogleStrategy within Passport.
  //   Strategies in Passport require a `verify` function, which accept
  //   credentials (in this case, an accessToken, refreshToken, and Google
  //   profile), and invoke a callback with a user object.
  passport.use(new GoogleStrategy({
      clientID: GOOGLE_CLIENT_ID,
      clientSecret: GOOGLE_CLIENT_SECRET,
      callbackURL: appconfig.auth.google.callbackURL
    },
    loginCallbackHandler(function (profile) {
          return {
            'name': profile.displayName || null,
            'email': profile.emails[0].value,
            'avatarUrl': '',
            'type': 'google',
            'createdAt': rethinkdb.now()
          };
        }, 'google')
  ));

  passport.use(new FacebookStrategy({
      clientID: FACEBOOK_CLIENT_ID,
      clientSecret: FACEBOOK_CLIENT_SECRET,
      callbackURL: appconfig.auth.facebook.callbackURL
    },
    loginCallbackHandler(function (profile) {
      return {
        'name': profile.displayName || null,
        'email': profile.emails[0].value,
        'avatarUrl': '',
        'type': 'facebook',
        'createdAt': rethinkdb.now()
      };
    }, 'facebook')
  ));
}<commit_msg>Added avatar pics from Google, FB<commit_after>module.exports = function (FacebookStrategy, GoogleStrategy, rethinkdb, appconfig, passport) {
  var GOOGLE_CLIENT_ID = appconfig.auth.google.clientID;
  var GOOGLE_CLIENT_SECRET = appconfig.auth.google.clientSecret;
  var FACEBOOK_CLIENT_ID = appconfig.auth.facebook.clientID;
  var FACEBOOK_CLIENT_SECRET = appconfig.auth.facebook.clientSecret;

  // Passport session setup.
  //   To support persistent login sessions, Passport needs to be able to
  //   serialize users into and deserialize users out of the session.  Typically,
  //   this will be as simple as storing the user ID when serializing, and finding
  //   the user by ID when deserializing.  However, since this example does not
  //   have a database of user records, the complete Google profile is
  //   serialized and deserialized.
  passport.serializeUser(function(user, done) {
    done(null, user);
  });

  passport.deserializeUser(function(obj, done) {
    done(null, obj);
  });

  var loginCallbackHandler = function (objectMapper, type) {
  return function (accessToken, refreshToken, profile, done) {
    console.log(profile);
    if (accessToken !== null) {
      rethinkdb
        .table('users')
        .getAll(profile.emails[0].value, { index: 'email' })
        //.filter({ type: type })
        .run(rethinkdb.conn)
        .then(function (cursor) {
          return cursor.toArray()
            .then(function (users) {
              if (users.length > 0) {
                return done(null, users[0]);
              }
              return rethinkdb.table('users')
                .insert(objectMapper(profile))
                .run(rethinkdb.conn)
                .then(function (response) {
                  return rethinkdb.table('users')
                    .get(response.generated_keys[0])
                    .run(rethinkdb.conn);
                })
                .then(function (newUser) {
                  done(null, newUser);
                });
            });
        })
        .catch(function (err) {
          console.log('Error Getting User', err);
        });
      }
    };
  };
  // Use the GoogleStrategy within Passport.
  //   Strategies in Passport require a `verify` function, which accept
  //   credentials (in this case, an accessToken, refreshToken, and Google
  //   profile), and invoke a callback with a user object.
  passport.use(new GoogleStrategy({
      clientID: GOOGLE_CLIENT_ID,
      clientSecret: GOOGLE_CLIENT_SECRET,
      callbackURL: appconfig.auth.google.callbackURL
    },
    loginCallbackHandler(function (profile) {
          return {
            'name': profile.displayName || null,
            'email': profile.emails[0].value,
            'avatarUrl': profile.photos[0].value || '',
            'type': 'google',
            'createdAt': rethinkdb.now()
          };
        }, 'google')
  ));

  passport.use(new FacebookStrategy({
      clientID: FACEBOOK_CLIENT_ID,
      clientSecret: FACEBOOK_CLIENT_SECRET,
      callbackURL: appconfig.auth.facebook.callbackURL
    },
    loginCallbackHandler(function (profile) {
      return {
        'name': profile.displayName || null,
        'email': profile.emails[0].value,
        'avatarUrl': 'https://graph.facebook.com/' + profile.id + '/picture' || '',
        'type': 'facebook',
        'createdAt': rethinkdb.now()
      };
    }, 'facebook')
  ));
}<|endoftext|>


<commit_before>var child_process = require('child_process')
var fs = require('fs');
var max_sleep = 300
var step = 10
//Added a comment that achieves no real goal.
//Non-blocking loop
//Adjustable step
//Status line
//Add a rough time indicator
//First cut at libfaketime support

var commit_messages = [
  "Fixing an important issue with the universe.",
  "Someone poisoned the waterhole!",
  "You feeling lucky punk?",
  "Five bullets or six?",
  "Fixed memory parsing error #1337",
  "Cleaned out the intertubes",
  "AAAAAAAAAAARGH!",
  "Fixed a typo",
  "Updated the readme",
  "Fixing a typo added in the last readme update",
  "GET A HAIRCUT!",
  "Updated th<commit_msg>How many more of these can I make?<commit_after>var child_process = require('child_process')
var fs = require('fs');
var max_sleep = 300
var step = 10
//Added a comment that achieves no real goal.
//Non-blocking loop
//Adjustable step
//Status line
//Add a rough time indicator
//First cut at libfaketime support

var commit_messages = [
  "Fixing an important issue with the universe.",
  "Someone poisoned the waterhole!",
  "You feeling lucky punk?",
  "Five bullets or six?",
  "Fixed memory parsing error #1337",
  "Cleaned out the intertubes",
  "AAAAAAAAAAARGH!",
  "Fixed a typo",
  "Updated the readme",
  "Fixing a typo added in the last readme update",
  "GET A HAIRCUT!",
  "Updated the<|endoftext|>


<commit_before>
function OSType() {
	var OSName="Unknown OS";
	if (navigator.appVersion.indexOf("Win")!=-1) OSName="Windows";
	if (navigator.appVersion.indexOf("Mac")!=-1) OSName="MacOS";
	if (navigator.appVersion.indexOf("X11")!=-1) OSName="UNIX";
	if (navigator.appVersion.indexOf("Linux")!=-1) OSName="Linux";

	return OSName;
}

/*
Windows XP - C:\Documents and Settings\*UserName*\Local Settings\Application Data\Google\Chrome\User Data\Default\Extensions
Windows 7\8 - C:\Users\*UserName*\AppData\Local\Google\Chrome\User Data\Default\Extensions
Linux - ~/.config/google-chrome/Default/Extensions/
Mac - /Users/`username`/Library/Application Support/Google/Chrome/Default/Extensions
Chromium on Linux - ~/.config/chromium/Default/Extensions
*/
///// TODO
// Support Unix
// Support installation to non-default location
// Add support to Windows XP
// Add support to Chromium - problematic since it can run from any folder
function getChromeExtPath(username) {
	var OsName = OSType();
	var path;

	switch (OsName) {
	case "MacOS":
		path = "/Users/" + username + "/Library/Application Support/Google/Chrome/Default/Extensions/";
		break;
	case "UNIX":
		//not supported yet
		break;
	case "Linux":
		path = "~/.config/google-chrome/Default/Extensions/";
		break;
	case "Windows":
	default: //fallthrough from windows
		path = "C:/Users/" + username + "/AppData/Local/Google/Chrome/User Data/Default/Extensions/";
	}
	return path;
}

var getAllExtFunc = function (listOfExt) {
//Used http://paul.kinlan.me/creating-a-new-new-tab-page-for-chrome/ as reference
	var len = listOfExt.length;
	var path = getChromeExtPath(username);
	var img ="";
	var fullPath;
	$(".chrome-ext-list").append("

");

	for(var i=0; i< len; i++)
	{
		//console.log('
' + listOfExt[i].name + '
');
		if (listOfExt[i].installType === "development")
		{
			fullPath="#";
			var clipboard = '<button class="copyUrl" data-href="#">Extension under development</button>';
			img = "";
		}
		else
		{
			fullPath = path + listOfExt[i].id + "/";
			if ('icons' in listOfExt[i] && listOfExt[i].icons.length > 0)
			{
				img = "";
			}
			else
			{
				img = "";
			}
			var clipboard = '<button class="copyUrl" data-href="' + fullPath +'">Copy to clipboard</button>';
		}
		var html = "
" + img + "	" + '' + listOfExt[i].name + '' + "	" + clipboard + '
' + "	";
		$(".chrome-ext-list").append(html);
	}
	$(".chrome-ext-list").append("

");
};

var Storage = {
	storageOption: function(type) {
		//For now only use localStoage
		return localStorage;
	},
	save: function (type, key, data) {
		Storage.storageOption(type).setItem(key,data);/*, function () {
		console.log("saved data");
		});*/
	},
	load: function (type, key) {
		return Storage.storageOption(type).getItem(key);/*, function (object) {
			console.log("read : " +object + object[key]);
			return object[key];
		})*/}
};//End of Storage

// Copy to clipboard helper function
//http://www.pakzilla.com/2012/03/20/how-to-copy-to-clipboard-in-chrome-extension/
function copyToClipboard( text ){
	var copyDiv = document.createElement('div');
	copyDiv.contentEditable = true;
	document.body.appendChild(copyDiv);
	copyDiv.innerHTML = text;
	copyDiv.unselectable = "off";
	copyDiv.focus();
	document.execCommand('SelectAll');
	document.execCommand("Copy", false, null);
	document.body.removeChild(copyDiv);
}

/* UI Elements and General startup sequence*/
//The div to populate with all the information
var $extTable = $('.chrome-ext-list');
var username = (typeof Storage.load(null,'path') != "undefined"?Storage.load(null,'path'):"");
console.log(" username is " + username);
//Attach the event listener for the click event after document loads
$(document).ready( function() {
	//check if we have a username saved
	var dbdata = Storage.load(null,"path");
	console.log("dbdata is " + dbdata);
	if (dbdata!= null && username != null &&
		(typeof dbdata != "undefined" || OSType === "Linux"))
	{
		$(".windows").hide();
		chrome.management.getAll(getAllExtFunc);
	}
	$('#regular-ext').click(function() {
		console.log("clicked regualr view");
		chrome.tabs.update({url:"chrome-internal://newtab/"});
		return false;
	});
	$("#save").click(function() {
		username = $("input").val();
		//Get the list of all chrome extensions with their information
		chrome.management.getAll(getAllExtFunc);
		Storage.save(null,'path',username);
		$(".windows").hide();
	});
});

$(".copyUrl").live('click',function() {
	console.log($(this), $(this).data("href"));
	copyToClipboard($(this).data("href"));
});
<commit_msg>fixed bug for Linux of not showing the user dialog<commit_after>
function OSType() {
	var OSName="Unknown OS";
	if (navigator.appVersion.indexOf("Win")!=-1) OSName="Windows";
	if (navigator.appVersion.indexOf("Mac")!=-1) OSName="MacOS";
	if (navigator.appVersion.indexOf("X11")!=-1) OSName="UNIX";
	if (navigator.appVersion.indexOf("Linux")!=-1) OSName="Linux";

	return OSName;
}

/*
Windows XP - C:\Documents and Settings\*UserName*\Local Settings\Application Data\Google\Chrome\User Data\Default\Extensions
Windows 7\8 - C:\Users\*UserName*\AppData\Local\Google\Chrome\User Data\Default\Extensions
Linux - ~/.config/google-chrome/Default/Extensions/
Mac - /Users/`username`/Library/Application Support/Google/Chrome/Default/Extensions
Chromium on Linux - ~/.config/chromium/Default/Extensions
*/
///// TODO
// Support Unix
// Support installation to non-default location
// Add support to Windows XP
// Add support to Chromium - problematic since it can run from any folder
function getChromeExtPath(username) {
	var OsName = OSType();
	var path;

	switch (OsName) {
	case "MacOS":
		path = "/Users/" + username + "/Library/Application Support/Google/Chrome/Default/Extensions/";
		break;
	case "UNIX":
		//not supported yet
		break;
	case "Linux":
		path = "~/.config/google-chrome/Default/Extensions/";
		break;
	case "Windows":
	default: //fallthrough from windows
		path = "C:/Users/" + username + "/AppData/Local/Google/Chrome/User Data/Default/Extensions/";
	}
	return path;
}

var getAllExtFunc = function (listOfExt) {
//Used http://paul.kinlan.me/creating-a-new-new-tab-page-for-chrome/ as reference
	var len = listOfExt.length;
	var path = getChromeExtPath(username);
	var img ="";
	var fullPath;
	$(".chrome-ext-list").append("

");

	for(var i=0; i< len; i++)
	{
		//console.log('
' + listOfExt[i].name + '
');
		if (listOfExt[i].installType === "development")
		{
			fullPath="#";
			var clipboard = '<button class="copyUrl" data-href="#">Extension under development</button>';
			img = "";
		}
		else
		{
			fullPath = path + listOfExt[i].id + "/";
			if ('icons' in listOfExt[i] && listOfExt[i].icons.length > 0)
			{
				img = "";
			}
			else
			{
				img = "";
			}
			var clipboard = '<button class="copyUrl" data-href="' + fullPath +'">Copy to clipboard</button>';
		}
		var html = "
" + img + "	" + '' + listOfExt[i].name + '' + "	" + clipboard + '
' + "	";
		$(".chrome-ext-list").append(html);
	}
	$(".chrome-ext-list").append("

");
};

var Storage = {
	storageOption: function(type) {
		//For now only use localStoage
		return localStorage;
	},
	save: function (type, key, data) {
		Storage.storageOption(type).setItem(key,data);/*, function () {
		console.log("saved data");
		});*/
	},
	load: function (type, key) {
		return Storage.storageOption(type).getItem(key);/*, function (object) {
			console.log("read : " +object + object[key]);
			return object[key];
		})*/}
};//End of Storage

// Copy to clipboard helper function
//http://www.pakzilla.com/2012/03/20/how-to-copy-to-clipboard-in-chrome-extension/
function copyToClipboard( text ){
	var copyDiv = document.createElement('div');
	copyDiv.contentEditable = true;
	document.body.appendChild(copyDiv);
	copyDiv.innerHTML = text;
	copyDiv.unselectable = "off";
	copyDiv.focus();
	document.execCommand('SelectAll');
	document.execCommand("Copy", false, null);
	document.body.removeChild(copyDiv);
}

/* UI Elements and General startup sequence*/
//The div to populate with all the information
var $extTable = $('.chrome-ext-list');
var username = (typeof Storage.load(null,'path') != "undefined"?Storage.load(null,'path'):"");
console.log(" username is " + username);
//Attach the event listener for the click event after document loads
$(document).ready( function() {
	//check if we have a username saved
	var dbdata = Storage.load(null,"path");
	var OSName = OSType();
	console.log("dbdata is " + dbdata);
	if (dbdata!= null && username != null &&
		(typeof dbdata != "undefined" || OSName === "Linux"))
	{
		$(".windows").hide();
		chrome.management.getAll(getAllExtFunc);
	}
	$('#regular-ext').click(function() {
		console.log("clicked regualr view");
		chrome.tabs.update({url:"chrome-internal://newtab/"});
		return false;
	});
	$("#save").click(function() {
		username = $("input").val();
		//Get the list of all chrome extensions with their information
		chrome.management.getAll(getAllExtFunc);
		Storage.save(null,'path',username);
		$(".windows").hide();
	});
});

$(".copyUrl").live('click',function() {
	console.log($(this), $(this).data("href"));
	copyToClipboard($(this).data("href"));
});
<|endoftext|>


<commit_before>/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2016 Mickael Jeanroy
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

const fs = require('fs');
const path = require('path');
const gulp = require('gulp');
const babel = require('gulp-babel');
const jasmine = require('gulp-jasmine');
const eslint = require('gulp-eslint');
const gutil = require('gulp-util');
const git = require('gulp-git');
const bump = require('gulp-bump');
const runSequence = require('run-sequence');
const del = require('del');

const ROOT = __dirname;
const SRC = path.join(ROOT, 'src');
const TEST = path.join(ROOT, 'test');
const DIST = path.join(ROOT, 'dist');

gulp.task('clean', () => {
  return del(DIST);
});

gulp.task('lint', () => {
  const src = [
    path.join(SRC, '**', '*.js'),
    path.join(TEST, '**', '*.js'),
    path.join(ROOT, '*.js'),
  ];

  return gulp.src(src)
        .pipe(eslint())
        .pipe(eslint.format())
        .pipe(eslint.failAfterError());
});

gulp.task('test', ['build'], () => {
  const specs = path.join(TEST, '**', '*-spec.js');
  return gulp.src(specs).pipe(jasmine());
});

gulp.task('build', ['lint', 'clean'], () => {
  return gulp.src(path.join(__dirname, 'src', '**/*.js'))
    .pipe(babel())
    .pipe(gulp.dest(DIST));
});

gulp.task('commit:pre', () => {
  const dist = path.join(__dirname, 'dist');
  const packageJson = path.join(__dirname, 'package.json');
  return gulp.src([packageJson, dist])
    .pipe(git.add({args: '-f'}))
    .pipe(git.commit('release: release version'));
});

gulp.task('commit:post', () => {
  const dist = path.join(__dirname, 'dist');
  return gulp.src(dist)
    .pipe(git.rm({args: '-r'}))
    .pipe(git.commit('release: prepare next release'));
});

gulp.task('tag', (done) => {
  const pkg = fs.readFileSync(path.join(__dirname, 'package.json'), 'utf8');
  const version = JSON.parse(pkg).version;
  git.tag(`v${version}`, `release: tag version ${version}`, done);
});

['major', 'minor', 'patch'].forEach((level) => {
  gulp.task(`bump:${level}`, () => {
    return gulp.src(path.join(__dirname, 'package.json'))
      .pipe(bump({type: level})
      .on('error', gutil.log))
      .pipe(gulp.dest(__dirname));
  });

  gulp.task('release:' + level, ['build'], () => {
    runSequence(`bump:${level}`, 'commit:pre', 'tag', 'commit:post');
  });
});

gulp.task('default', ['build']);
<commit_msg>core: fix formatting<commit_after>/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2016 Mickael Jeanroy
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

const fs = require('fs');
const path = require('path');
const gulp = require('gulp');
const babel = require('gulp-babel');
const jasmine = require('gulp-jasmine');
const eslint = require('gulp-eslint');
const gutil = require('gulp-util');
const git = require('gulp-git');
const bump = require('gulp-bump');
const runSequence = require('run-sequence');
const del = require('del');

const ROOT = __dirname;
const SRC = path.join(ROOT, 'src');
const TEST = path.join(ROOT, 'test');
const DIST = path.join(ROOT, 'dist');

gulp.task('clean', () => {
  return del(DIST);
});

gulp.task('lint', () => {
  const src = [
    path.join(SRC, '**', '*.js'),
    path.join(TEST, '**', '*.js'),
    path.join(ROOT, '*.js'),
  ];

  return gulp.src(src)
    .pipe(eslint())
    .pipe(eslint.format())
    .pipe(eslint.failAfterError());
});

gulp.task('test', ['build'], () => {
  const specs = path.join(TEST, '**', '*-spec.js');
  return gulp.src(specs).pipe(jasmine());
});

gulp.task('build', ['lint', 'clean'], () => {
  return gulp.src(path.join(__dirname, 'src', '**/*.js'))
    .pipe(babel())
    .pipe(gulp.dest(DIST));
});

gulp.task('commit:pre', () => {
  const dist = path.join(__dirname, 'dist');
  const packageJson = path.join(__dirname, 'package.json');
  return gulp.src([packageJson, dist])
    .pipe(git.add({args: '-f'}))
    .pipe(git.commit('release: release version'));
});

gulp.task('commit:post', () => {
  const dist = path.join(__dirname, 'dist');
  return gulp.src(dist)
    .pipe(git.rm({args: '-r'}))
    .pipe(git.commit('release: prepare next release'));
});

gulp.task('tag', (done) => {
  const pkg = fs.readFileSync(path.join(__dirname, 'package.json'), 'utf8');
  const version = JSON.parse(pkg).version;
  git.tag(`v${version}`, `release: tag version ${version}`, done);
});

['major', 'minor', 'patch'].forEach((level) => {
  gulp.task(`bump:${level}`, () => {
    return gulp.src(path.join(__dirname, 'package.json'))
      .pipe(bump({type: level})
      .on('error', gutil.log))
      .pipe(gulp.dest(__dirname));
  });

  gulp.task('release:' + level, ['build'], () => {
    runSequence(`bump:${level}`, 'commit:pre', 'tag', 'commit:post');
  });
});

gulp.task('default', ['build']);
<|endoftext|>


<commit_before>goog.provide('ol.style.Image');
goog.provide('ol.style.ImageState');

/**
 * @enum {number}
 */
ol.style.ImageState = {
  IDLE: 0,
  LOADING: 1,
  LOADED: 2,
  ERROR: 3
};

/**
 * @typedef {{opacity: number,
 *            origin: Array.<number>,
 *            rotateWithView: boolean,
 *            rotation: number,
 *            scale: number,
 *            snapToPixel: boolean}}
 */
ol.style.ImageOptions;

/**
 * @constructor
 * @param {ol.style.ImageOptions} options Options.
 * @todo api
 */
ol.style.Image = function(options) {

  /**
   * @private
   * @type {number}
   */
  this.opacity_ = options.opacity;

  /**
   * @private
   * @type {Array.<number>}
   */
  this.origin_ = options.origin;

  /**
   * @private
   * @type {boolean}
   */
  this.rotateWithView_ = options.rotateWithView;

  /**
   * @private
   * @type {number}
   */
  this.rotation_ = options.rotation;

  /**
   * @private
   * @type {number}
   */
  this.scale_ = options.scale;

  /**
   * @private
   * @type {boolean}
   */
  this.snapToPixel_ = options.snapToPixel;

};

/**
 * @return {number} Opacity.
 */
ol.style.Image.prototype.getOpacity = function() {
  return this.opacity_;
};

/**
 * @return {Array.<number>} Origin.
 */
ol.style.Image.prototype.getOrigin = function() {
  return this.origin_;
};

/**
 * @return {boolean} Rotate with map.
 */
ol.style.Image.prototype.getRotateWithView = function() {
  return this.rotateWithView_;
};

/**
 * @return {number} Rotation.
 * @todo api
 */
ol.style.Image.prototype.getRotation = function() {
  return this.rotation_;
};

/**
 * @return {number} Scale.
 * @todo api
 */
ol.style.Image.prototype.getScale = function() {
  return this.scale_;
};

/**
 * @return {boolean} Snap to pixel?
 */
ol.style.Image.prototype.getSnapToPixel = function() {
  return this.snapToPixel_;
};

/**
 * @function
 * @return {Array.<number>} Anchor.
 */
ol.style.Image.prototype.getAnchor = goog.abstractMethod;

/**
 * @function
 * @param {number} pixelRatio Pixel ratio.
 * @return {HTMLCanvasElement|HTMLVideoElement|Image} Image element.
 */
ol.style.Image.prototype.getImage = goog.abstractMethod;

/**
 * @return {ol.style.ImageState} Image state.
 */
ol.style.Image.prototype.getImageState = goog.abstractMethod;

/**
 * @param {number} pixelRatio Pixel ratio.
 * @return {HTMLCanvasElement|HTMLVideoElement|Image} Image element.
 */
ol.style.Image.prototype.getHitDetectionImage = goog.abstractMethod;

/**
 * @function
 * @return {ol.Size} Size.
 */
ol.style.Image.prototype.getSize = goog.abstractMethod;

/**
 * @param {function(this: T, goog.events.Event)} listener Listener function.
 * @param {T} thisArg Value to use as `this` when executing `listener`.
 * @return {goog.events.Key|undefined} Listener key.
 * @template T
 */
ol.style.Image.prototype.listenImageChange = goog.abstractMethod;

/**
 * Load not yet loaded URI.
 */
ol.style.Image.prototype.load = goog.abstractMethod;

/**
 * @param {function(this: T, goog.events.Event)} listener Listener function.
 * @param {T} thisArg Value to use as `this` when executing `listener`.
 * @template T
 */
ol.style.Image.prototype.unlistenImageChange = goog.abstractMethod;
<commit_msg>Do not make ol.style.Image exportable<commit_after>goog.provide('ol.style.Image');
goog.provide('ol.style.ImageState');

/**
 * @enum {number}
 */
ol.style.ImageState = {
  IDLE: 0,
  LOADING: 1,
  LOADED: 2,
  ERROR: 3
};

/**
 * @typedef {{opacity: number,
 *            origin: Array.<number>,
 *            rotateWithView: boolean,
 *            rotation: number,
 *            scale: number,
 *            snapToPixel: boolean}}
 */
ol.style.ImageOptions;

/**
 * @constructor
 * @param {ol.style.ImageOptions} options Options.
 */
ol.style.Image = function(options) {

  /**
   * @private
   * @type {number}
   */
  this.opacity_ = options.opacity;

  /**
   * @private
   * @type {Array.<number>}
   */
  this.origin_ = options.origin;

  /**
   * @private
   * @type {boolean}
   */
  this.rotateWithView_ = options.rotateWithView;

  /**
   * @private
   * @type {number}
   */
  this.rotation_ = options.rotation;

  /**
   * @private
   * @type {number}
   */
  this.scale_ = options.scale;

  /**
   * @private
   * @type {boolean}
   */
  this.snapToPixel_ = options.snapToPixel;

};

/**
 * @return {number} Opacity.
 */
ol.style.Image.prototype.getOpacity = function() {
  return this.opacity_;
};

/**
 * @return {Array.<number>} Origin.
 */
ol.style.Image.prototype.getOrigin = function() {
  return this.origin_;
};

/**
 * @return {boolean} Rotate with map.
 */
ol.style.Image.prototype.getRotateWithView = function() {
  return this.rotateWithView_;
};

/**
 * @return {number} Rotation.
 * @todo api
 */
ol.style.Image.prototype.getRotation = function() {
  return this.rotation_;
};

/**
 * @return {number} Scale.
 * @todo api
 */
ol.style.Image.prototype.getScale = function() {
  return this.scale_;
};

/**
 * @return {boolean} Snap to pixel?
 */
ol.style.Image.prototype.getSnapToPixel = function() {
  return this.snapToPixel_;
};

/**
 * @function
 * @return {Array.<number>} Anchor.
 */
ol.style.Image.prototype.getAnchor = goog.abstractMethod;

/**
 * @function
 * @param {number} pixelRatio Pixel ratio.
 * @return {HTMLCanvasElement|HTMLVideoElement|Image} Image element.
 */
ol.style.Image.prototype.getImage = goog.abstractMethod;

/**
 * @return {ol.style.ImageState} Image state.
 */
ol.style.Image.prototype.getImageState = goog.abstractMethod;

/**
 * @param {number} pixelRatio Pixel ratio.
 * @return {HTMLCanvasElement|HTMLVideoElement|Image} Image element.
 */
ol.style.Image.prototype.getHitDetectionImage = goog.abstractMethod;

/**
 * @function
 * @return {ol.Size} Size.
 */
ol.style.Image.prototype.getSize = goog.abstractMethod;

/**
 * @param {function(this: T, goog.events.Event)} listener Listener function.
 * @param {T} thisArg Value to use as `this` when executing `listener`.
 * @return {goog.events.Key|undefined} Listener key.
 * @template T
 */
ol.style.Image.prototype.listenImageChange = goog.abstractMethod;

/**
 * Load not yet loaded URI.
 */
ol.style.Image.prototype.load = goog.abstractMethod;

/**
 * @param {function(this: T, goog.events.Event)} listener Listener function.
 * @param {T} thisArg Value to use as `this` when executing `listener`.
 * @template T
 */
ol.style.Image.prototype.unlistenImageChange = goog.abstractMethod;
<|endoftext|>