<commit_before>/**
 * Upload handler used by the upload to S3 module that depends on File API support, and, therefore, makes use of
 * `XMLHttpRequest` level 2 to upload `File`s and `Blob`s directly to S3 buckets via the associated AWS API.
 *
 * @param options Options passed from the base handler
 * @param uploadCompleteCallback Callback to invoke when the upload has completed, regardless of success.
 * @param onUuidChanged Callback to invoke when the associated items UUID has changed by order of the server.
 * @param logCallback Used to posting log messages.
 */
qq.s3.UploadHandlerXhr = function(options, uploadCompleteCallback, onUuidChanged, log) {
    "use strict";

    var fileState = [],
        expectedStatus = 200,
        onProgress = options.onProgress,
        onComplete = options.onComplete,
        onUpload = options.onUpload,
        onGetKeyName = options.getKeyName,
        filenameParam = options.filenameParam,
        paramsStore = options.paramsStore,
        endpointStore = options.endpointStore,
        accessKey = options.accessKey,
        acl = options.acl,
        validation = options.validation,
        getSignatureAjaxRequester = new qq.s3.PolicySignatureAjaxRequestor({
            endpoint: options.signatureEndpoint,
            cors: options.cors,
            log: log
        }),
        api;

    //TODO eliminate duplication w/ handler.xhr.js
    function createXhr(id) {
        var xhr = new XMLHttpRequest();

        fileState[id].xhr = xhr;

        return xhr;
    }

    //TODO eliminate duplication w/ handler.xhr.js
    function handleUpload(id) {
        var fileOrBlob = fileState[id].file || fileState[id].blobData.blob,
            name = api.getName(id),
            xhr;

        fileState[id].loaded = 0;
        fileState[id].type = fileOrBlob.type;

        xhr = createXhr(id);

        xhr.upload.onprogress = function(e){
            if (e.lengthComputable){
                fileState[id].loaded = e.loaded;
                onProgress(id, name, e.loaded, e.total);
            }
        };

        xhr.onreadystatechange = getReadyStateChangeHandler(id);

        prepareForSend(id, fileOrBlob).then(function(toSend) {
            log('Sending upload request for ' + id);
            xhr.send(toSend);
        });
    }

    //TODO eliminate duplication w/ handler.xhr.js
    function getReadyStateChangeHandler(id) {
        var xhr = fileState[id].xhr;

        return function() {
            if (xhr.readyState === 4) {
                uploadCompleted(id);
            }
        };
    }

    function generateAwsParams(id) {
        var customParams = paramsStore.getParams(id);
        customParams[filenameParam] = api.getName(id);

        return qq.s3.util.generateAwsParams({
                endpoint: endpointStore.getEndpoint(id),
                params: customParams,
                type: fileState[id].type,
                key: fileState[id].key,
                accessKey: accessKey,
                acl: acl,
                expectedStatus: expectedStatus,
                minFileSize: validation.minSizeLimit,
                maxFileSize: validation.maxSizeLimit,
                log: log
            },
            qq.bind(getSignatureAjaxRequester.getSignature, this, id));
    }

    function prepareForSend(id, fileOrBlob) {
        var formData = new FormData(),
            endpoint = endpointStore.getEndpoint(id),
            url = endpoint,
            xhr = fileState[id].xhr,
            promise = new qq.Promise();

        generateAwsParams(id).then(function(awsParams) {
            xhr.open("POST", url, true);

            qq.obj2FormData(awsParams, formData);

            // AWS requires the file field be named "file".
            formData.append("file", fileOrBlob);

            promise.success(formData);
        },
        function(errorMessage) {
            promise.failure(errorMessage);
            uploadCompleted(id, {error: errorMessage});
        });

        return promise;
    }

    function parseResponse(id) {
        var xhr = fileState[id].xhr,
            response = {};

        try {
            log(qq.format("Received response status {} with body: {}", xhr.status, xhr.responseText));

            if (xhr.status === expectedStatus) {
                response.success = true;
            }
            else {
                response.error = parseError(xhr.responseText);
            }
        }
        catch(error) {
            log('Error when attempting to parse xhr response text (' + error + ')', 'error');
        }

        return response;
    }

    function parseError(awsResponseXml) {
        var parser = new DOMParser(),
            parsedDoc = parser.parseFromString(awsResponseXml, "application/xml"),
            messageElements = parsedDoc.getElementsByTagName("Message");

        if (messageElements.length > 0) {
            return messageElements[0].textContent;
        }
    }

    function uploadCompleted(id, errorDetails) {
        var xhr = fileState[id].xhr,
            name = api.getName(id),
            size = api.getSize(id),
            response = errorDetails || parseResponse(id);

        qq.log(qq.format("Upload attempt for file ID {} to S3 is complete", id));

        onProgress(id, name, size, size);
        onComplete(id, name, response, xhr);

        if (fileState[id]) {
            delete fileState[id].xhr;
        }

        uploadCompleteCallback(id);
    }

    function handleStartUploadSignal(id, retry) {
        var name = api.getName(id);

        if (api.isValid(id)) {
            if (fileState[id].key) {
                onUpload(id, name);
                handleUpload(id);
            }
            else {
                // The S3 uploader module will either calculate the key or ask the server for it
                // and will call us back once it is known.
                onGetKeyName(id, name).then(function(key) {
                    fileState[id].key = key;
                    onUpload(id, name);
                    handleUpload(id);
                });
            }
        }
    }

    api = new qq.UploadHandlerXhrApi(fileState, handleStartUploadSignal, options.onCancel, onUuidChanged, log);

    return api;
};
<commit_msg>feat(client/js/s3/handler.xhr.js): #898 - start of chunked upload support for S3 uploader<commit_after>/**
 * Upload handler used by the upload to S3 module that depends on File API support, and, therefore, makes use of
 * `XMLHttpRequest` level 2 to upload `File`s and `Blob`s directly to S3 buckets via the associated AWS API.
 *
 * @param options Options passed from the base handler
 * @param uploadCompleteCallback Callback to invoke when the upload has completed, regardless of success.
 * @param onUuidChanged Callback to invoke when the associated items UUID has changed by order of the server.
 * @param logCallback Used to posting log messages.
 */
qq.s3.UploadHandlerXhr = function(options, uploadCompleteCallback, onUuidChanged, log) {
    "use strict";

    var fileState = [],
        expectedStatus = 200,
        onProgress = options.onProgress,
        onComplete = options.onComplete,
        onUpload = options.onUpload,
        onGetKeyName = options.getKeyName,
        filenameParam = options.filenameParam,
        paramsStore = options.paramsStore,
        endpointStore = options.endpointStore,
        accessKey = options.accessKey,
        acl = options.acl,
        validation = options.validation,
        chunkingPossible = options.chunking.enabled && qq.supportedFeatures.chunking,
        getSignatureAjaxRequester = new qq.s3.PolicySignatureAjaxRequestor({
            expectingPolicy: true,
            endpoint: options.signatureEndpoint,
            cors: options.cors,
            log: log
        }),
        initiateMultipartRequester = new qq.s3.InitiateMultipartAjaxRequester({
            endpointStore: endpointStore,
            paramsStore: paramsStore,
            signatureEndpoint: options.signatureEndpoint,
            accessKey: options.accessKey,
            acl: options.acl,
            cors: options.cors,
            getKey: function(id) {
                return fileState[id].key;
            },
            getContentType: function(id) {
                return api.getFile(id).type;
            },
            log: log
        }),
        api;

    //TODO eliminate duplication w/ traditional handler.xhr.js
    function createXhr(id) {
        var xhr = new XMLHttpRequest();

        fileState[id].xhr = xhr;

        return xhr;
    }

    /**
     * Determine if the associated file should be chunked.
     *
     * @param id ID of the associated file
     * @returns {*} true if chunking is enabled, possible, and the file can be split into more than 1 part
     */
    function shouldChunkThisFile(id) {
        if (fileState[id].chunked === undefined) {
            if (chunkingPossible && getTotalChunks(id) > 1) {
                fileState[id].chunked = true;
            }
            else {
                fileState[id].chunked = false;
            }
        }

        return fileState[id].chunked;
    }

    //TODO eliminate duplication w/ traditional handler.xhr.js
    /**
     * @param id ID of the associated file
     * @returns {number} Number of parts this file can be divided into
     */
    function getTotalChunks(id) {
        var fileSize = api.getSize(id),
            chunkSize = options.chunking.partSize;

        return Math.ceil(fileSize / chunkSize);
    }

    /**
     * Initiate the upload process and possibly delegate to a more specific handler if chunking is required.
     *
     * @param id Associated file ID
     */
    function handleUpload(id) {
        var fileOrBlob = fileState[id].file || fileState[id].blobData.blob,
            name = api.getName(id),
            xhr;

        fileState[id].loaded = 0;
        fileState[id].type = fileOrBlob.type;

        xhr = createXhr(id);

        if (shouldChunkThisFile(id)) {
            handleChunkedUpload(id);
        }
        else {
            xhr.upload.onprogress = function(e){
                if (e.lengthComputable){
                    fileState[id].loaded = e.loaded;
                    onProgress(id, name, e.loaded, e.total);
                }
            };

            xhr.onreadystatechange = getReadyStateChangeHandler(id);

            prepareForSend(id, fileOrBlob).then(function(toSend) {
                log('Sending upload request for ' + id);
                xhr.send(toSend);
            });
        }
    }

    // TODO comments
    function handleChunkedUpload(id) {
        // TODO handle failure?
        maybeInitiateMultipart(id).then(function() {
            // TODO start sending chunks
        }, function(errorMessage) {
            uploadCompleted(id, {error: errorMessage});
        });
    }

    /**
     * Sends an "Initiate Multipart Upload" request to S3 via the REST API, but only if the MPU has not already been
     * initiated.
     *
     * @param id Associated file ID
     * @returns {qq.Promise} A promise that is fulfilled when the initiate request has been sent and the response has been parsed.
     */
    function maybeInitiateMultipart(id) {
        if (!fileState[id].initiated) {
            return initiateMultipartRequester.send(id, fileState[id].key).then(function() {
                fileState[id].initiated = true;
            });
        }
        else {
            return new qq.Promise().success();
        }
    }

    //TODO eliminate duplication w/ traditional handler.xhr.js
    function getReadyStateChangeHandler(id) {
        var xhr = fileState[id].xhr;

        return function() {
            if (xhr.readyState === 4) {
                uploadCompleted(id);
            }
        };
    }

    function generateAwsParams(id) {
        var customParams = paramsStore.getParams(id);
        customParams[filenameParam] = api.getName(id);

        return qq.s3.util.generateAwsParams({
                endpoint: endpointStore.getEndpoint(id),
                params: customParams,
                type: fileState[id].type,
                key: fileState[id].key,
                accessKey: accessKey,
                acl: acl,
                expectedStatus: expectedStatus,
                minFileSize: validation.minSizeLimit,
                maxFileSize: validation.maxSizeLimit,
                log: log
            },
            qq.bind(getSignatureAjaxRequester.getSignature, this, id));
    }

    function prepareForSend(id, fileOrBlob) {
        var formData = new FormData(),
            endpoint = endpointStore.getEndpoint(id),
            url = endpoint,
            xhr = fileState[id].xhr,
            promise = new qq.Promise();

        generateAwsParams(id).then(function(awsParams) {
            xhr.open("POST", url, true);

            qq.obj2FormData(awsParams, formData);

            // AWS requires the file field be named "file".
            formData.append("file", fileOrBlob);

            promise.success(formData);
        },
        function(errorMessage) {
            promise.failure(errorMessage);
            uploadCompleted(id, {error: errorMessage});
        });

        return promise;
    }

    function parseResponse(id) {
        var xhr = fileState[id].xhr,
            response = {};

        try {
            log(qq.format("Received response status {} with body: {}", xhr.status, xhr.responseText));

            if (xhr.status === expectedStatus) {
                response.success = true;
            }
            else {
                response.error = parseError(xhr.responseText);
            }
        }
        catch(error) {
            log('Error when attempting to parse xhr response text (' + error + ')', 'error');
        }

        return response;
    }

    function parseError(awsResponseXml) {
        var parser = new DOMParser(),
            parsedDoc = parser.parseFromString(awsResponseXml, "application/xml"),
            messageElements = parsedDoc.getElementsByTagName("Message");

        if (messageElements.length > 0) {
            return messageElements[0].textContent;
        }
    }

    function uploadCompleted(id, errorDetails) {
        var xhr = fileState[id].xhr,
            name = api.getName(id),
            size = api.getSize(id),
            response = errorDetails || parseResponse(id);

        qq.log(qq.format("Upload attempt for file ID {} to S3 is complete", id));

        onProgress(id, name, size, size);
        onComplete(id, name, response, xhr);

        if (fileState[id]) {
            delete fileState[id].xhr;
        }

        uploadCompleteCallback(id);
    }

    function handleStartUploadSignal(id, retry) {
        var name = api.getName(id);

        if (api.isValid(id)) {
            if (fileState[id].key) {
                onUpload(id, name);
                handleUpload(id);
            }
            else {
                // The S3 uploader module will either calculate the key or ask the server for it
                // and will call us back once it is known.
                onGetKeyName(id, name).then(function(key) {
                    fileState[id].key = key;
                    onUpload(id, name);
                    handleUpload(id);
                });
            }
        }
    }

    api = new qq.UploadHandlerXhrApi(fileState, handleStartUploadSignal, options.onCancel, onUuidChanged, log);

    return api;
};
<|endoftext|>


<commit_before>define(['jquery', 'markdown', 'jquery-ui', 'jquery.editable', 'jquery.editable.datepicker', 'jquery-ui.timepicker'], function($, markdown) {

    var defaults = {
        submit: 'Ok',
        style: 'display: inline',
    }

    var types = {
        date: {
            type: 'datepicker',
            onblur: 'ignore',
            datepicker: { dateFormat: 'dd-mm-yy' },
        },

        text: {
            width: '100px',
            height: '20px',
            type: 'text',
        },

        textlong: {
            width: '180px',
            height: '25px',
            type: 'text',
        },

        markdown: {
            type: 'textarea',
            rows: 5,
            onblur: 'ignore',
            callback: function(value, settings) {
                $(this).html(markdown.toHTML(value))
            },
            data: function(value,settings) {
                return settings.model.get(settings.attr)
            },
        },

        textarea: {
            type: 'textarea',
            rows: 5,
            onblur: 'ignore',
        },

        select: {
            type: 'select',
            onblur: 'ignore',
            callback: function(value, settings) {
                $(this).html(_.result(settings, 'data')[value]);
            }
        },

        autocomplete: {
            type: 'autocomplete',
            onblur: 'ignore',
        },

        datetime: {
            type: 'datetime',
            onblur: 'ignore',
        }
    }

    /**
     * jEditable editing of current model in place
     */
    return Marionette.Controller.extend({
        /**
         * @constructor
         * @param {object} options - Pass through the current model and $el.
         */
        initialize: function(options) {
            this.model = options.model
            this.el = options.el
            this.addTypes()
        },

        addTypes: function() {
            // Autocomplete
            $.editable.addInputType('autocomplete', {
              element: function(settings, original) {
                $(this).append('<input type="text"/>');
                var hidden = $('<input id="prac" type="hidden"/>');
                $(this).append(hidden);
                return(hidden);
              },

              plugin : function(settings, original) {
                var set = $.extend({ select: function(e,ui) { $('#prac').val(ui.item.id) } }, settings.autocomplete)
                $('input[type=text]', this).autocomplete(set);
              },

              submit: function(settings, original) {
                if (!$('input[type=hidden]', this).val()) {
                  original.reset()
                  return false
                }

              },
            })

            // Datetimepicker
            $.editable.addInputType('datetime', {
                element : function(settings, original) {
                    settings.onblur = function(e) {
                    };

                    var ele = $('<input value="'+original+'" />')
                    $(this).append(ele)
                    $(this).children('input').datetimepicker({ dateFormat: "dd-mm-yy" })

                    return ele
                },
            })
        },

        /**
         * Create an editable on the page
         * @constructor
         * @param {string} attr - The class into which to inject the editable, should match the model attribute
         * @param {string} type - The type of editable to show.
         * @param {object} options - Options to pass through to the jeditable constructor
         * @param {boolean} refetch - Refetch the model after saving (for selects, etc)
         */
        create: function(attr, type, options, refetch) {
            var submit = function(value, settings) {
                this.model.set(attr, value)
                console.log('valid', this.model.isValid(true), attr, 'changed', this.model.changedAttributes())
                var self = this
                this.model.save(this.model.changedAttributes(), { patch: true,
                    success: function() {
                        if (refetch) self.model.fetch()
                    },
                    error: function(a,b,c) {
                        console.log('editable error', a, 'xhr', b, 'opts',c)
                    }
                })

                return refetch ? '' : _.escape(value)
            }

            var onsubmit = function(settings, td) {
                //console.log('onsubmit', settings, td, this)
                var invalid = this.model.preValidate(attr, $('[name=value]', td).val())

                if (invalid) {
                    $('input,select', td).addClass('ferror')
                    if ($('.errormessage', td).length) $(td).children('form').append(''+invalid+'')
                } else {
                    $('input,select', td).removeClass('ferror')
                    $('.errormessage', td).remove()
                }

                return invalid ? false: true
            }

            this.el.find('.'+attr).editable(submit.bind(this), $.extend({}, defaults, types[type], options, { onsubmit: onsubmit.bind(this), attr: attr, model: this.model })).addClass('editable')
        }

    })

})<commit_msg>show error messages for ediable validation<commit_after>define(['jquery', 'markdown', 'jquery-ui', 'jquery.editable', 'jquery.editable.datepicker', 'jquery-ui.timepicker'], function($, markdown) {

    var defaults = {
        submit: 'Ok',
        style: 'display: inline',
    }

    var types = {
        date: {
            type: 'datepicker',
            onblur: 'ignore',
            datepicker: { dateFormat: 'dd-mm-yy' },
        },

        text: {
            width: '100px',
            height: '20px',
            type: 'text',
        },

        textlong: {
            width: '180px',
            height: '25px',
            type: 'text',
        },

        markdown: {
            type: 'textarea',
            rows: 5,
            onblur: 'ignore',
            callback: function(value, settings) {
                $(this).html(markdown.toHTML(value))
            },
            data: function(value,settings) {
                return settings.model.get(settings.attr)
            },
        },

        textarea: {
            type: 'textarea',
            rows: 5,
            onblur: 'ignore',
        },

        select: {
            type: 'select',
            onblur: 'ignore',
            callback: function(value, settings) {
                $(this).html(_.result(settings, 'data')[value]);
            }
        },

        autocomplete: {
            type: 'autocomplete',
            onblur: 'ignore',
        },

        datetime: {
            type: 'datetime',
            onblur: 'ignore',
        }
    }

    /**
     * jEditable editing of current model in place
     */
    return Marionette.Controller.extend({
        /**
         * @constructor
         * @param {object} options - Pass through the current model and $el.
         */
        initialize: function(options) {
            this.model = options.model
            this.el = options.el
            this.addTypes()
        },

        addTypes: function() {
            // Autocomplete
            $.editable.addInputType('autocomplete', {
              element: function(settings, original) {
                $(this).append('<input type="text"/>');
                var hidden = $('<input id="prac" type="hidden"/>');
                $(this).append(hidden);
                return(hidden);
              },

              plugin : function(settings, original) {
                var set = $.extend({ select: function(e,ui) { $('#prac').val(ui.item.id) } }, settings.autocomplete)
                $('input[type=text]', this).autocomplete(set);
              },

              submit: function(settings, original) {
                if (!$('input[type=hidden]', this).val()) {
                  original.reset()
                  return false
                }

              },
            })

            // Datetimepicker
            $.editable.addInputType('datetime', {
                element : function(settings, original) {
                    settings.onblur = function(e) {
                    };

                    var ele = $('<input value="'+original+'" />')
                    $(this).append(ele)
                    $(this).children('input').datetimepicker({ dateFormat: "dd-mm-yy" })

                    return ele
                },
            })
        },

        /**
         * Create an editable on the page
         * @constructor
         * @param {string} attr - The class into which to inject the editable, should match the model attribute
         * @param {string} type - The type of editable to show.
         * @param {object} options - Options to pass through to the jeditable constructor
         * @param {boolean} refetch - Refetch the model after saving (for selects, etc)
         */
        create: function(attr, type, options, refetch) {
            var submit = function(value, settings) {
                this.model.set(attr, value)
                console.log('valid', this.model.isValid(true), attr, 'changed', this.model.changedAttributes())
                var self = this
                this.model.save(this.model.changedAttributes(), { patch: true,
                    success: function() {
                        if (refetch) self.model.fetch()
                    },
                    error: function(a,b,c) {
                        console.log('editable error', a, 'xhr', b, 'opts',c)
                    }
                })

                return refetch ? '' : _.escape(value)
            }

            var onsubmit = function(settings, td) {
                //console.log('onsubmit', settings, td, this)
                var invalid = this.model.preValidate(attr, $('[name=value]', td).val())

                if (invalid) {
                    $('input,select', td).addClass('ferror')
                    if (!$('.errormessage', td).length) $(td).append(''+invalid+'')
                } else {
                    $('input,select', td).removeClass('ferror')
                    $('.errormessage', td).remove()
                }

                return invalid ? false: true
            }

            this.el.find('.'+attr).editable(submit.bind(this), $.extend({}, defaults, types[type], options, { onsubmit: onsubmit.bind(this), attr: attr, model: this.model })).addClass('editable')
        }

    })

})<|endoftext|>


<commit_before>// Constructeur de la classe ProcessingImage
function ProcessingImage(src) {

    this.img = new Image();
    this.img.src = src;

    var myImage = this;

    this.img.onload = function() {
        myImage.w = this.width;
        myImage.h = this.height;

        canvas = document.getElementById('canvas');

        myImage.initialx = canvas.width/2 - this.width/2;
        myImage.initialy = canvas.height/2 - this.height/2;

        myImage.x = myImage.initialx;
        myImage.y = myImage.initialy;
    }
}

//Dessine l'image
ProcessingImage.prototype.draw = function(ctx) {
    var locx = this.x;
    var locy = this.y;
    ctx.drawImage(this.img,locx,locy);
}

// Determine si un point est dans l'image
ProcessingImage.prototype.contains = function(mx, my) {

  return  (this.x <= mx) && (this.x + (this.w) >= mx) &&
          (this.y <= my) && (this.y + (this.h) >= my);
}

//Constructeur de la baseline
function Baseline(lines) {
    this.lines = lines;
    this.visible = false;

}

Baseline.prototype.draw = function(ctx, image){
    if(this.visible)
    {
        for (index = 0; index < this.lines.length; index++) {
            ctx.beginPath();
            ctx.moveTo(image.x,image.y + this.lines[index]);
            ctx.lineTo(image.x + image.w,image.y + this.lines[index]);
            ctx.strokeStyle = '#2ecc71';
            ctx.stroke();
        }
    }

}

function Rectangle(rect){
    this.rect = rect;
    this.color = '#3498db';
    this.selectedColor = '#e74c3c';
    this.visible = true;
    this.selected = false;
}

Rectangle.prototype.draw = function(ctx, image){
    if(this.visible)
    {
        if(this.selected)
            ctx.fillStyle = this.selectedColor;
        else
            ctx.fillStyle = this.color;

        ctx.globalAlpha=0.2;
        ctx.fillRect(image.x + this.rect.x,image.y + this.rect.y, this.rect.w, this.rect.h);

    }
}

//Constructeur de la bounding box
function BoundingBox(rects) {
    this.rects = rects;

}

BoundingBox.prototype.draw = function(ctx, image){
    for (index = 0; index < this.rects.length; index++) {
        this.rects[index].draw(ctx, image);
    }
}
BoundingBox.prototype.contains = function(mx, my, image){
    for (index = 0; index < this.rects.length; index++) {
        if((this.rects[index].rect.x + image.x <= mx)
            && (this.rects[index].rect.x + image.x + this.rects[index].rect.w >=mx)
            && (this.rects[index].rect.y + image.y <= my)
            && (this.rects[index].rect.y + image.y + this.rects[index].rect.h >= my))
            return index;
    }
    return 'false';
}
BoundingBox.prototype.select = function (id){
    for (index = 0; index < this.rects.length; index++) {
        this.rects[index].selected = false;
    }
    if(id != false)
        this.rects[id].selected = true;
}

function CanvasState(canvas, image, baseline, boundingBox, previewCanvas) {

    var myState = this;

    this.canvas = canvas;
    this.width = canvas.width;
    this.height = canvas.height;
    this.ctx = canvas.getContext('2d');

    this.image = image;

    this.baseline = baseline;
    this.boundingBox = boundingBox;

    this.previewCanvas = previewCanvas;

    this.dragging = false;

    this.scale = 1.0;

    this.panX = 0;
    this.panY = 0;

    //Coordonnées de la selection sur l'image
    this.dragoffx = 0;
    this.dragoffy = 0;

    canvas.addEventListener('selectstart', function(e) { e.preventDefault(); return false; }, false);

    canvas.addEventListener('click', function(e) {

        var mouse = myState.getMouse(e);
        var mx = mouse.x;
        var my = mouse.y;

        var id = myState.boundingBox.contains(mx, my, myState.image);
        if(id != 'false')
        {
            myState.boundingBox.select(id);
            myState.previewCanvas.zoomTo(myState.boundingBox.rects[id]);
            myState.previewCanvas.visible = true;
            $(".container_right").show();
        }
        else
        {
            myState.boundingBox.select(false);
            $(".container_right").hide();
        }

    }, true);
    canvas.addEventListener('mousedown', function(e) {

        var mouse = myState.getMouse(e);
        var mx = mouse.x;
        var my = mouse.y;

        var image = myState.image;

        if (image.contains(mx, my)) {

            myState.dragoffx = mx - image.x;
            myState.dragoffy = my - image.y;
            myState.dragging = true;
            return;
          }
    }, true);

    canvas.addEventListener('mousemove', function(e) {
        if (myState.dragging){
            var mouse = myState.getMouse(e);
            image.x = (mouse.x) - myState.dragoffx;
            image.y = (mouse.y) - myState.dragoffy;
        }
    }, true);

    canvas.addEventListener('mouseup', function(e) {
        myState.dragging = false;
    }, true);

    document.getElementById('zoom-in').addEventListener('click', function(e){
        myState.scale += 0.1;
    }, true);

    document.getElementById('zoom-out').addEventListener('click', function(e){
        myState.scale -= 0.1;
    }, true);

    document.getElementById('zoom-reset').addEventListener('click', function(e){
        myState.scale = 1;
        myState.image.x = myState.image.initialx;
        myState.image.y = myState.image.initialy;
    }, true);

    document.getElementById('baseline').addEventListener('change', function(e){
        if(this.checked){
            myState.baseline.visible = true;
        }
        else{
            myState.baseline.visible = false;
        }
    }, true);

    document.getElementById('valid-reset').addEventListener('click', function(e){

        var data = new FormData();
        data.append('file', myState.image.img.src);

        $.ajax({
            url: 'remove.php?restart',
            type: 'POST',
            data: data,
            cache: false,
            processData: false, // Don't process the files
            contentType: false, // Set content type to false as jQuery will tell the server its a query string request
            success: function(data, textStatus, jqXHR)
            {
                var parsedData = JSON.parse(data);
                if (parsedData == true) {
                    location.reload();
                    console.log("Page réinitialisée.");
                }
                else
                {
                    console.log('ERRORS: ' + data.error);
                }
            },
            error: function(jqXHR, textStatus, errorThrown)
            {
                console.log('ERRORS: ' + textStatus);
            }
          });

    }, true);

    this.interval = 30;
    setInterval(function() { myState.draw(); }, myState.interval);

}

// Efface le canvas pour redessiner
CanvasState.prototype.clear = function() {
  this.ctx.clearRect(0, 0, this.width, this.height);
}

// Dessine le canvas avec ces éléments
CanvasState.prototype.draw = function() {
  if (!this.valid) {
    var ctx = this.ctx;

    var newWidth = this.width * this.scale;
    var newHeight = this.height * this.scale;
    this.panX = -((newWidth-this.width)/2);
    this.panY = -((newHeight-this.height)/2);

    this.clear();

    ctx.save();

    ctx.translate(this.panX, this.panY);

    ctx.scale(this.scale, this.scale);

    this.image.draw(ctx);

    this.baseline.draw(ctx, this.image);

    this.boundingBox.draw(ctx, this.image);

    this.previewCanvas.draw();

    ctx.restore();

  }
}

// Retourne les coordonnées de la souris
CanvasState.prototype.getMouse = function(e) {
    var rect = this.canvas.getBoundingClientRect();
    var mx = Math.floor((e.clientX-rect.left)/(rect.right-rect.left)*this.canvas.width);
    var my = Math.floor((e.clientY-rect.top)/(rect.bottom-rect.top)*this.canvas.height);

    var mouseXT = parseInt((mx - this.panX) / this.scale);
    var mouseYT = parseInt((my - this.panY) / this.scale);

    return {x: mouseXT, y: mouseYT};
}

function PreviewCanvas(canvas, image)
{
    this.canvas = canvas;
    this.width = canvas.width;
    this.height = canvas.height;
    this.ctx = canvas.getContext('2d');

    this.image = image;

    this.visible = false;
    this.scale = 9;

    var myPreviewCanvas = this;

}

// Efface le canvas pour redessiner
PreviewCanvas.prototype.clear = function() {
    this.ctx.clearRect(0, 0, this.width, this.height);
}
// Dessine le canvas avec ces éléments
PreviewCanvas.prototype.draw = function() {
    if(this.visible)
    {
        var ctx = this.ctx;
        this.clear();
        ctx.save();

        var newWidth = this.width * this.scale;
        var newHeight = this.height * this.scale;
        var panX = -((newWidth-this.width)/2);
        var panY = -((newHeight-this.height)/2);

        ctx.translate(panX, panY);

        ctx.scale(this.scale,this.scale);
        this.image.draw(ctx);
        ctx.restore();
    }

}
PreviewCanvas.prototype.zoomTo = function(rect){

    this.image.x = (this.width/2) - rect.rect.x - rect.rect.w/2;
    this.image.y = (this.height/2) - rect.rect.y - rect.rect.h/2;
}

function init(src) {
    var image = new ProcessingImage(src);
    var imagePreview = new ProcessingImage(src);
    var baseline = new Baseline([33, 94, 155]);
    var rect = new Rectangle({x:14, y:20, w:8, h: 14});
    var rect2 = new Rectangle({x:23, y:20, w:8, h: 14});
    var rect3 = new Rectangle({x:32, y:20, w:8, h: 14});
    var boundingBox = new BoundingBox([rect, rect2, rect3]);

    var previewCanvas = new PreviewCanvas(document.getElementById('small_canvas'), imagePreview);
    var normalCanvas = new CanvasState(document.getElementById('canvas'), image, baseline, boundingBox, previewCanvas);

}

<commit_msg>Modifications<commit_after>// Constructeur de la classe ProcessingImage
function ProcessingImage(src) {

    this.img = new Image();
    this.img.src = src;

    var myImage = this;

    this.img.onload = function() {
        myImage.w = this.width;
        myImage.h = this.height;

        canvas = document.getElementById('canvas');

        myImage.initialx = canvas.width/2 - this.width/2;
        myImage.initialy = canvas.height/2 - this.height/2;

        myImage.x = myImage.initialx;
        myImage.y = myImage.initialy;
    }
}

//Dessine l'image
ProcessingImage.prototype.draw = function(ctx) {
    var locx = this.x;
    var locy = this.y;
    ctx.drawImage(this.img,locx,locy);
}

// Determine si un point est dans l'image
ProcessingImage.prototype.contains = function(mx, my) {

  return  (this.x <= mx) && (this.x + (this.w) >= mx) &&
          (this.y <= my) && (this.y + (this.h) >= my);
}

//Constructeur de la baseline
function Baseline(lines) {
    this.lines = lines;
    this.visible = false;

}

Baseline.prototype.draw = function(ctx, image){
    if(this.visible)
    {
        for (index = 0; index < this.lines.length; index++) {
            ctx.beginPath();
            ctx.moveTo(image.x,image.y + this.lines[index]);
            ctx.lineTo(image.x + image.w,image.y + this.lines[index]);
            ctx.strokeStyle = '#2ecc71';
            ctx.stroke();
        }
    }

}

function Rectangle(rect){
    this.rect = rect;
    this.color = '#3498db';
    this.selectedColor = '#e74c3c';
    this.visible = true;
    this.selected = false;
}

Rectangle.prototype.draw = function(ctx, image){
    if(this.visible)
    {
        if(this.selected)
            ctx.fillStyle = this.selectedColor;
        else
            ctx.fillStyle = this.color;

        ctx.globalAlpha=0.2;
        ctx.fillRect(image.x + this.rect.x,image.y + this.rect.y, this.rect.w, this.rect.h);

    }
}

//Constructeur de la bounding box
function BoundingBox(rects) {
    this.rects = rects;

}

BoundingBox.prototype.draw = function(ctx, image){
    for (index = 0; index < this.rects.length; index++) {
        this.rects[index].draw(ctx, image);
    }
}
BoundingBox.prototype.contains = function(mx, my, image){
    for (index = 0; index < this.rects.length; index++) {
        if((this.rects[index].rect.x + image.x <= mx)
            && (this.rects[index].rect.x + image.x + this.rects[index].rect.w >=mx)
            && (this.rects[index].rect.y + image.y <= my)
            && (this.rects[index].rect.y + image.y + this.rects[index].rect.h >= my))
            return index;
    }
    return 'false';
}
BoundingBox.prototype.select = function (id){
    for (index = 0; index < this.rects.length; index++) {
        this.rects[index].selected = false;
    }
    if(id != 'false')
        this.rects[id].selected = true;
}

function CanvasState(canvas, image, baseline, boundingBox, previewCanvas) {

    var myState = this;

    this.canvas = canvas;
    this.width = canvas.width;
    this.height = canvas.height;
    this.ctx = canvas.getContext('2d');

    this.image = image;

    this.baseline = baseline;
    this.boundingBox = boundingBox;

    this.previewCanvas = previewCanvas;

    this.dragging = false;

    this.scale = 1.0;

    this.panX = 0;
    this.panY = 0;

    //Coordonnées de la selection sur l'image
    this.dragoffx = 0;
    this.dragoffy = 0;

    canvas.addEventListener('selectstart', function(e) { e.preventDefault(); return false; }, false);

    canvas.addEventListener('click', function(e) {

        var mouse = myState.getMouse(e);
        var mx = mouse.x;
        var my = mouse.y;

        var id = myState.boundingBox.contains(mx, my, myState.image);
        if(id != 'false')
        {
            myState.boundingBox.select(id);
            myState.previewCanvas.zoomTo(myState.boundingBox.rects[id]);
            myState.previewCanvas.visible = true;
            $(".container_right").show();
        }
        else
        {
            myState.boundingBox.select('false');
            $(".container_right").hide();
        }

    }, true);
    canvas.addEventListener('mousedown', function(e) {

        var mouse = myState.getMouse(e);
        var mx = mouse.x;
        var my = mouse.y;

        var image = myState.image;

        if (image.contains(mx, my)) {

            myState.dragoffx = mx - image.x;
            myState.dragoffy = my - image.y;
            myState.dragging = true;
            return;
          }
    }, true);

    canvas.addEventListener('mousemove', function(e) {
        if (myState.dragging){
            var mouse = myState.getMouse(e);
            image.x = (mouse.x) - myState.dragoffx;
            image.y = (mouse.y) - myState.dragoffy;
        }
    }, true);

    canvas.addEventListener('mouseup', function(e) {
        myState.dragging = false;
    }, true);

    document.getElementById('zoom-in').addEventListener('click', function(e){
        myState.scale += 0.1;
    }, true);

    document.getElementById('zoom-out').addEventListener('click', function(e){
        myState.scale -= 0.1;
    }, true);

    document.getElementById('zoom-reset').addEventListener('click', function(e){
        myState.scale = 1;
        myState.image.x = myState.image.initialx;
        myState.image.y = myState.image.initialy;
    }, true);

    document.getElementById('baseline').addEventListener('change', function(e){
        if(this.checked){
            myState.baseline.visible = true;
        }
        else{
            myState.baseline.visible = false;
        }
    }, true);

    document.getElementById('valid-reset').addEventListener('click', function(e){

        var data = new FormData();
        data.append('file', myState.image.img.src);

        $.ajax({
            url: 'remove.php?restart',
            type: 'POST',
            data: data,
            cache: false,
            processData: false, // Don't process the files
            contentType: false, // Set content type to false as jQuery will tell the server its a query string request
            success: function(data, textStatus, jqXHR)
            {
                var parsedData = JSON.parse(data);
                if (parsedData == true) {
                    location.reload();
                    console.log("Page réinitialisée.");
                }
                else
                {
                    console.log('ERRORS: ' + data.error);
                }
            },
            error: function(jqXHR, textStatus, errorThrown)
            {
                console.log('ERRORS: ' + textStatus);
            }
          });

    }, true);

    this.interval = 30;
    setInterval(function() { myState.draw(); }, myState.interval);

}

// Efface le canvas pour redessiner
CanvasState.prototype.clear = function() {
  this.ctx.clearRect(0, 0, this.width, this.height);
}

// Dessine le canvas avec ces éléments
CanvasState.prototype.draw = function() {
  if (!this.valid) {
    var ctx = this.ctx;

    var newWidth = this.width * this.scale;
    var newHeight = this.height * this.scale;
    this.panX = -((newWidth-this.width)/2);
    this.panY = -((newHeight-this.height)/2);

    this.clear();

    ctx.save();

    ctx.translate(this.panX, this.panY);

    ctx.scale(this.scale, this.scale);

    this.image.draw(ctx);

    this.baseline.draw(ctx, this.image);

    this.boundingBox.draw(ctx, this.image);

    this.previewCanvas.draw();

    ctx.restore();

  }
}

// Retourne les coordonnées de la souris
CanvasState.prototype.getMouse = function(e) {
    var rect = this.canvas.getBoundingClientRect();
    var mx = Math.floor((e.clientX-rect.left)/(rect.right-rect.left)*this.canvas.width);
    var my = Math.floor((e.clientY-rect.top)/(rect.bottom-rect.top)*this.canvas.height);

    var mouseXT = parseInt((mx - this.panX) / this.scale);
    var mouseYT = parseInt((my - this.panY) / this.scale);

    return {x: mouseXT, y: mouseYT};
}

function PreviewCanvas(canvas, image)
{
    this.canvas = canvas;
    this.width = canvas.width;
    this.height = canvas.height;
    this.ctx = canvas.getContext('2d');

    this.image = image;

    this.visible = false;
    this.scale = 9;

    var myPreviewCanvas = this;

}

// Efface le canvas pour redessiner
PreviewCanvas.prototype.clear = function() {
    this.ctx.clearRect(0, 0, this.width, this.height);
}
// Dessine le canvas avec ces éléments
PreviewCanvas.prototype.draw = function() {
    if(this.visible)
    {
        var ctx = this.ctx;
        this.clear();
        ctx.save();

        var newWidth = this.width * this.scale;
        var newHeight = this.height * this.scale;
        var panX = -((newWidth-this.width)/2);
        var panY = -((newHeight-this.height)/2);

        ctx.translate(panX, panY);

        ctx.scale(this.scale,this.scale);
        this.image.draw(ctx);
        ctx.restore();
    }

}
PreviewCanvas.prototype.zoomTo = function(rect){

    this.image.x = (this.width/2) - rect.rect.x - rect.rect.w/2;
    this.image.y = (this.height/2) - rect.rect.y - rect.rect.h/2;
}

function init(src) {
    var image = new ProcessingImage(src);
    var imagePreview = new ProcessingImage(src);
    var baseline = new Baseline([33, 94, 155]);
    var rect = new Rectangle({x:14, y:20, w:8, h: 14});
    var rect2 = new Rectangle({x:23, y:20, w:8, h: 14});
    var rect3 = new Rectangle({x:32, y:20, w:8, h: 14});
    var boundingBox = new BoundingBox([rect, rect2, rect3]);

    var previewCanvas = new PreviewCanvas(document.getElementById('small_canvas'), imagePreview);
    var normalCanvas = new CanvasState(document.getElementById('canvas'), image, baseline, boundingBox, previewCanvas);

}

<|endoftext|>


<commit_before>// **Extends** Backbone.Model
//
// **Usage** ```require(['regs-data'], function(RegModel) {})```
//
// Currently represents the whole of a regulation.
// Not really in use until there's an API.
//
// The idea here is that Backbone Models are created with a certain type of content in mind.
// Content that:
//
// * there are many instances of with the same general properties (hence Collections)
// * that will be updated both on the server and client, frequently
//
// This is not the case with our content and so this module is intended as a replacement of
// Backbone's Model scheme
// The Backbone Model API should be kept as much as makes sense. Existing entities should
// be reused to aim for the effect of a native Backbone experience, but shouldn't try so hard
// as to be confusing. Ex. the return value of Model.fetch should be the same as in core Backbone,
// even if the internals are different
define('regs-data', ['underscore', 'backbone', './regs-helpers', './regs-dispatch'], function(_, Backbone, RegsHelpers, Dispatch) {
    'use strict';

    // represents a whole regulation
    Backbone.RegModel = Backbone.Model.extend({
        // an index of all of the entities in the reg, whether or not they've been loaded
        regStructure: [],

        // content = markup to string representations of each reg paragraph/entity
        // loaded into the browser (rendered or not)
        content: {},

        // recurses over the parsed reg tree and creates an in-browser representation
        // of a reg. may or may not be functional based on current tree.
        // only in use for tests right now...
        // refactor to parse just out of the DOM
        parse: function(jsonObj) {
            if (typeof jsonObj === 'object') {
                for (var key in jsonObj) {
                    if (jsonObj.hasOwnProperty(key)) {
                        if (key === 'label') {
                            this.set(jsonObj[key]['text'], jsonObj['text']);
                        }

                        if (RegsHelpers.isIterable(jsonObj[key])) {
                            this.parse(jsonObj[key]);
                        }
                    }
                }
            }

            return this;
        },

        // store a new reg entity
        set: function(sectionId, sectionText) {
            var cached = this.has(sectionId),
                section;

            if (!(cached)) {
                this.content[sectionId] = sectionText;
                section = sectionText;

                if (_.indexOf(this.regStructure, sectionId) === -1) {
                    this.regStructure.push(sectionId);
                }
            }
            else {
                section = cached;
            }

            return section;
        },

        // **Param**
        // id, string, dash-delimited reg entity id
        //
        // **Returns** the markup string to render an entity or
        // false if the entity hasn't been loaded
        has: function(id) {
            if (this.content[id]) {
                return this.content[id];
            }
            return false;
        },

        // **Params**
        //
        // * ```id```: string, dash-delimited reg entity id
        //
        // **Returns** representation of the reg entity requested in the format requested,
        get: function(id) {
            var obj = this.has(id) || this.request(id);
            return obj;
        },

        // Basically an alias for ```this.get```, included for API continuity
        fetch: function(id) {
            return this.get(id);
        },

        request: function(id) {
            var url = [],
                sitePath,
                i,
                pathLen,
                promise;

            sitePath = _.compact(document.location.pathname.split('/'));
            pathLen = sitePath.length;

            for (i=0; i<=pathLen; i++) {
                if (sitePath[i] === 'regulation') {
                    break;
                }
                else {
                    url.push(sitePath[i]);
                }
            }

            url.push('partial');
            url.push(id);
            url.push(Dispatch.getVersion());

            url = '/' + url.join('/');

            promise = $.ajax({
                url: url,
                success: function(data) { this.set(id, data); }.bind(this)
            });

            return promise;
        },

        // **Param**
        // ```id```: string, dash-delimited reg entity id
        //
        // **Returns** an array of strings, dash-delimited reg entity ids of children
        //
        // The magic of the flexibility of the app is that we can pass around these
        // dash-delimited ids and get the job done pretty seamlessly w/o worrying about
        // what exactly we're dealing with. We just rely on a set of rules.
        getChildren: function(id) {
            var kids = [],
                regStructureLen = this.regStructure.length,
                regex = new RegExp(id + '[-,a-z,0-9]');

            while (regStructureLen--) {
                if (regex.test(this.regStructure[regStructureLen])) {
                    kids.push(this.regStructure[regStructureLen]);
                }
            }

            return kids;
        },

        // **Param**
        // ```id```: string, dash-delimited reg entity id
        //
        // **Returns** string, dash-delimited reg entity id of parent to
        // the id passed in or boolean false if the requested id has no parent
        //
        // The converse of ```this.getChildren```
        getParent: function(id) {
            var parent, z;
            z  = id.split('-');
            z.pop();
            parent = z.join('-');

            if (this.regStructure.indexOf(parent) !== -1) {
                return this.content[parent];
            }

            return false;
        },

        // We don't have need for the following methods.
        // This is my half-baked way of overriding them so that they
        // can be legally called as Backbone runs its course, but have
        // no effect.
        sync: function() {
            return;
        },

        save: function() {
            return;
        },

        destroy: function() {
            return;
        }
    });

    var reg = new Backbone.RegModel();

    return reg;
});
<commit_msg>fixing ajax url formatting<commit_after>// **Extends** Backbone.Model
//
// **Usage** ```require(['regs-data'], function(RegModel) {})```
//
// Currently represents the whole of a regulation.
// Not really in use until there's an API.
//
// The idea here is that Backbone Models are created with a certain type of content in mind.
// Content that:
//
// * there are many instances of with the same general properties (hence Collections)
// * that will be updated both on the server and client, frequently
//
// This is not the case with our content and so this module is intended as a replacement of
// Backbone's Model scheme
// The Backbone Model API should be kept as much as makes sense. Existing entities should
// be reused to aim for the effect of a native Backbone experience, but shouldn't try so hard
// as to be confusing. Ex. the return value of Model.fetch should be the same as in core Backbone,
// even if the internals are different
define('regs-data', ['underscore', 'backbone', './regs-helpers', './regs-dispatch'], function(_, Backbone, RegsHelpers, Dispatch) {
    'use strict';

    // represents a whole regulation
    Backbone.RegModel = Backbone.Model.extend({
        // an index of all of the entities in the reg, whether or not they've been loaded
        regStructure: [],

        // content = markup to string representations of each reg paragraph/entity
        // loaded into the browser (rendered or not)
        content: {},

        // recurses over the parsed reg tree and creates an in-browser representation
        // of a reg. may or may not be functional based on current tree.
        // only in use for tests right now...
        // refactor to parse just out of the DOM
        parse: function(jsonObj) {
            if (typeof jsonObj === 'object') {
                for (var key in jsonObj) {
                    if (jsonObj.hasOwnProperty(key)) {
                        if (key === 'label') {
                            this.set(jsonObj[key]['text'], jsonObj['text']);
                        }

                        if (RegsHelpers.isIterable(jsonObj[key])) {
                            this.parse(jsonObj[key]);
                        }
                    }
                }
            }

            return this;
        },

        // store a new reg entity
        set: function(sectionId, sectionText) {
            var cached = this.has(sectionId),
                section;

            if (!(cached)) {
                this.content[sectionId] = sectionText;
                section = sectionText;

                if (_.indexOf(this.regStructure, sectionId) === -1) {
                    this.regStructure.push(sectionId);
                }
            }
            else {
                section = cached;
            }

            return section;
        },

        // **Param**
        // id, string, dash-delimited reg entity id
        //
        // **Returns** the markup string to render an entity or
        // false if the entity hasn't been loaded
        has: function(id) {
            if (this.content[id]) {
                return this.content[id];
            }
            return false;
        },

        // **Params**
        //
        // * ```id```: string, dash-delimited reg entity id
        //
        // **Returns** representation of the reg entity requested in the format requested,
        get: function(id) {
            var obj = this.has(id) || this.request(id);
            return obj;
        },

        // Basically an alias for ```this.get```, included for API continuity
        fetch: function(id) {
            return this.get(id);
        },

        request: function(id) {
            var url = [],
                sitePath,
                i,
                pathLen,
                promise;

            sitePath = document.location.pathname.split('/');
            pathLen = sitePath.length;

            for (i=0; i<=pathLen; i++) {
                if (sitePath[i] === 'regulation') {
                    break;
                }
                else if (sitePath[i] !== '') {
                    url.push(sitePath[i]);
                }
            }

            url.push('partial');
            url.push(id);
            url.push(Dispatch.getVersion());

            url = '/' + _.compact(url).join('/');

            promise = $.ajax({
                url: url,
                success: function(data) { this.set(id, data); }.bind(this)
            });

            return promise;
        },

        // **Param**
        // ```id```: string, dash-delimited reg entity id
        //
        // **Returns** an array of strings, dash-delimited reg entity ids of children
        //
        // The magic of the flexibility of the app is that we can pass around these
        // dash-delimited ids and get the job done pretty seamlessly w/o worrying about
        // what exactly we're dealing with. We just rely on a set of rules.
        getChildren: function(id) {
            var kids = [],
                regStructureLen = this.regStructure.length,
                regex = new RegExp(id + '[-,a-z,0-9]');

            while (regStructureLen--) {
                if (regex.test(this.regStructure[regStructureLen])) {
                    kids.push(this.regStructure[regStructureLen]);
                }
            }

            return kids;
        },

        // **Param**
        // ```id```: string, dash-delimited reg entity id
        //
        // **Returns** string, dash-delimited reg entity id of parent to
        // the id passed in or boolean false if the requested id has no parent
        //
        // The converse of ```this.getChildren```
        getParent: function(id) {
            var parent, z;
            z  = id.split('-');
            z.pop();
            parent = z.join('-');

            if (this.regStructure.indexOf(parent) !== -1) {
                return this.content[parent];
            }

            return false;
        },

        // We don't have need for the following methods.
        // This is my half-baked way of overriding them so that they
        // can be legally called as Backbone runs its course, but have
        // no effect.
        sync: function() {
            return;
        },

        save: function() {
            return;
        },

        destroy: function() {
            return;
        }
    });

    var reg = new Backbone.RegModel();

    return reg;
});
<|endoftext|>


<commit_before>/** @jsx m */

"use strict";

var Layout = require("../layouts/DefaultLayout.js");

var ProfilePage = {};

ProfilePage.controller = function(options) {
  options = _.extend({id: "ProfilePage"}, options);
  this.layoutController = new Layout.controller(options);
};

ProfilePage.view = function(ctrl) {
  var user = ctrl.user.cortex;
  var articlesContent;
  if (user.articles.count() === 0) {
    articlesContent = (

        You have no articles. You can link articles by searching for them and then requesting to be linked.

    );
  } else {
    var list = user.articles.map(function(article) {
      return
{article.title.val()}

    });
    articlesContent =
{list}
;
  }

  var commentsContent;
  if (user.comments.count() === 0) {
    commentsContent = (

        You have no comments.

    );
  } else {
    var list = user.comments.map(function(comment) {
      return
{comment.body.val()}

    });
    commentsContent =
{list}
;
  }

  var content = (

{[user.first_name.val(), user.middle_name.val(), user.last_name.val()].join(" ")} ({user.email.val()})

Articles

      {articlesContent}

Comments

      {commentsContent}

  );

  return new Layout.view(ctrl.layoutController, content);
};

module.exports = ProfilePage;
<commit_msg>Get the profile page working again<commit_after>/** @jsx m */

"use strict";

var Layout = require("../layouts/DefaultLayout.js");

var _ = require("underscore");

var ProfilePage = {};

ProfilePage.controller = function(options) {
  options = _.extend({id: "ProfilePage"}, options);
  this.layoutController = new Layout.controller(options);
  this.user = options.user;
};

ProfilePage.view = function(ctrl) {
  var user = ctrl.user;
  var articlesContent;
  if (user.get("articles").length === 0) {
    articlesContent = (

        You have no articles. You can link articles by searching for them and then requesting to be linked.

    );
  } else {
    var list = _.map(user.get("articles"), function(article) {
      return
{article.title}

    });
    articlesContent =
{list}
;
  }

  var commentsContent;
  if (user.get("comments").length === 0) {
    commentsContent = (

        You have no comments.

    );
  } else {
    var list = _.map(user.get("comments"), function(comment) {
      return
{comment.body}

    });
    commentsContent =
{list}
;
  }

  var content = (

{[user.get("first_name"), user.get("middle_name"), user.get("last_name")].join(" ")} ({user.get("email")})

Articles

      {articlesContent}

Comments

      {commentsContent}

  );

  return new Layout.view(ctrl.layoutController, content);
};

module.exports = ProfilePage;
<|endoftext|>


<commit_before>
Template.playlist.helpers({
    listPlaylist: function() {
       return Playlist.find({'position': {$gte: 1}}, {sort: {'position': 1}});
    },
    durationDisplay: function() {
    	return timeFormat(this.duration, this.duration);
   	},
    playlistDuration: function() {
      var Songs = Playlist.find({'position': {$gte: 1}});
      // the total duration
      var totalDuration = 0;
      Songs.forEach(function (song) {
        totalDuration += song.duration;
      });
      // return the formatted timestring
      return timeFormat(totalDuration, totalDuration);
    }
});

Template.playlist.events({
	'click .delete': function (event) {
		Meteor.call('delete', this.position);
	}
});

Template.playlist.onCreated(function() {
	Meteor.subscribe('playlist');
});
<commit_msg>Fixed the bug, that the delete event in the playlist template is triggered twice<commit_after>
Template.playlist.helpers({
    listPlaylist: function() {
       return Playlist.find({'position': {$gte: 1}}, {sort: {'position': 1}});
    },
    durationDisplay: function() {
    	return timeFormat(this.duration, this.duration);
   	},
    playlistDuration: function() {
      var Songs = Playlist.find({'position': {$gte: 1}});
      // the total duration
      var totalDuration = 0;
      Songs.forEach(function (song) {
        totalDuration += song.duration;
      });
      // return the formatted timestring
      return timeFormat(totalDuration, totalDuration);
    }
});

Template.playlist.events({
	'click .delete': function (event) {
    // without this the event is triggered twice for some reason
    event.stopPropagation();
		Meteor.call('delete', this.position);
	}
});

Template.playlist.onCreated(function() {
  this.autorun(function() {
	   this.subscribe('playlist');
  }.bind(this));
});
<|endoftext|>


<commit_before>
const RG = require('./rg');
const FromJSON = require('./game.fromjson');

export const LOAD = Object.freeze(
    {EMPTY: 'EMPTY', LOADED: 'LOADED', JSON: 'JSON', ON_DISK: 'ON_DISK'});
export const CREATE = Object.freeze(
    {EMPTY: 'EMPTY', CREATED: 'CREATED', POPULATED: 'POPULATED'});

/* Chunk manager handles loading/saving of world chunks (World.AreaTiles)
 * from/to memory/disk. It also keeps track of the state of each chunk.
 * */
export default class ChunkManager {

    constructor(game, area, loadState) {
        const [sizeX, sizeY] = [area.getSizeX(), area.getSizeY()];
        this.sizeX = sizeX;
        this.sizeY = sizeY;
        this.area = area;
        this.game = game;
        this.state = [];
        for (let x = 0; x < sizeX; x++) {
            this.state[x] = [];
            for (let y = 0; y < sizeY; y++) {
                const chunkState = {loadState};
                this.state[x].push(chunkState);
            }
        }

        // From how far the levels are loaded
        this.loadDistX = 1;
        this.loadDistY = 1;

        this.onDiskDistX = sizeX;
        this.onDiskDistY = sizeY;
    }

    setPlayerTile(px, py, oldX, oldY) {
        const moveDir = this.getMoveDir(px, py, oldX, oldY);
        const loadedTiles = [];
        for (let x = 0; x < this.sizeX; x++) {
            for (let y = 0; y < this.sizeY; y++) {
                if (this.inLoadRange(px, py, x, y)) {
                    if (!this.isLoaded(x, y)) {
                        // this.loadTile(px, py, x, y, moveDir);
                        loadedTiles.push([x, y]);
                    }
                }
                else if (this.isLoaded(x, y)) {
                    this.unloadTile(px, py, x, y, moveDir);
                }
            }
        }
        if (loadedTiles.length > 0) {
            this.loadTiles(px, py, loadedTiles, moveDir);
        }
    }

    isLoaded(x, y) {
        return this.state[x][y].loadState === LOAD.LOADED;
    }

    /* Returns true if given tile (tx,ty) is within load range from
     * player px,py .*/
    inLoadRange(px, py, tx, ty) {
        for (let x = px - this.loadDistX; x <= px + this.loadDistX; x++) {
            for (let y = py - this.loadDistY; y <= py + this.loadDistY; y++) {
                if (tx === x && ty === y) {return true;}
            }
        }
        return false;
    }

    loadAllTiles() {
        this.setLoadStateAll(LOAD.LOADED);
    }

    /* Loads the serialized/on-disk tile. */
    loadTiles(px, py, loadedTilesXY, moveDir) {
        const areaTiles = this.area.getTiles();
        // areaTiles[tx][ty] = this.createTile(areaTiles[tx][ty]);
        const loadedAreaTiles = loadedTilesXY.map(
            xy => areaTiles[xy[0]][xy[1]]);
        this.createTiles(loadedAreaTiles);

        loadedTilesXY.forEach(xy => {
            const [tx, ty] = xy;
            this.state[tx][ty].loadState = LOAD.LOADED;
            // Need to create the connections on adjacent tiles
            if (moveDir === 'WEST') {
                const newX = tx - 1;
                if (newX < this.area.getSizeX()) {
                    this.addConnections('WEST', areaTiles[tx][tx - 1]);
                }
            }
            else if (moveDir === 'EAST') {
                const newX = tx + 1;
                if (newX < this.area.getSizeX()) {
                    this.addConnections('EAST', areaTiles[tx][tx + 1]);
                }

            }
            else if (moveDir === 'SOUTH') {
                const newY = ty - 1;
                if (newY >= 0) {
                    this.addConnections('SOUTH', areaTiles[tx][ty - 1]);
                }
            }
            else if (moveDir === 'NORTH') {
                const newY = ty + 1;
                if (newY < this.area.getSizeY) {
                    this.addConnections('NORTH', areaTiles[tx][ty + 1]);
                }
            }
        });
    }

    /* Unloads the tile from memory. */
    unloadTile(px, py, tx, ty, moveDir) {
        const areaTiles = this.area.getTiles();
        this.state[tx][ty].loadState = LOAD.JSON;

        const levels = areaTiles[tx][ty].getLevels();
        this.game.removeLevels(levels);

        areaTiles[tx][ty] = areaTiles[tx][ty].toJSON();

        // Need to replace connections on adjacent tiles
        if (moveDir === 'WEST') {
            const newX = tx - 1;
            if (newX < this.area.getSizeX()) {
                this.removeConnections('EAST', areaTiles[tx][tx - 1]);
            }
        }
        else if (moveDir === 'EAST') {
            const newX = tx + 1;
            if (newX < this.area.getSizeX()) {
                this.removeConnections('WEST', areaTiles[tx][tx + 1]);
            }
        }
        else if (moveDir === 'NORTH') {
            const newY = ty - 1;
            if (newY >= 0) {
                this.removeConnections('SOUTH', areaTiles[tx][ty - 1]);
            }
        }
        else if (moveDir === 'SOUTH') {
            const newY = ty + 1;
            if (newY < this.area.getSizeY) {
                this.removeConnections('NORTH', areaTiles[tx][ty + 1]);
            }
        }
    }

    getLoadState(x, y) {
        return this.state[x][y].loadState;
    }

    serializeArea() {
        this.setLoadStateAll(LOAD.JSON);
        const levels = this.area.getLevels();
        this.game.removeLevels(levels);
        this.levels = levels.map(l => l.toJSON());
        const tiles = this.area.getTiles();
        for (let x = 0; x < this.sizeX; x++) {
            for (let y = 0; y < this.sizeY; y++) {
                tiles[x][y] = tiles[x][y].toJSON();
            }
        }
    }

    toJSON() {
        return {
            state: this.state
        };
    }

    setLoadStateAll(state) {
        this.state.forEach((col, x) => {
            col.forEach((tile, y) => {
                this.state[x][y].loadState = state;
            });
        });
    }

    createTiles(tilesJSON) {
        const fromJSON = new FromJSON();
        fromJSON.chunkMode = true;
        fromJSON.createTiles(this.game, tilesJSON);
    }

    addConnections(dir, tile) {
        const addedConns = this.getReplacedConnections(dir, tile);
    }

    removeConnections(dir, tile) {
        const replacedConns = this.getReplacedConnections(dir, tile);
        replacedConns.forEach(conn => {
            conn.setTargetLevel(conn.getTargetLevel().getID());
            conn.setTargetStairs(conn.getTargetStairs().getID());
        });
    }

    getReplacedConnections(dir, tile) {
        const level = tile.getLevel();
        const conns = level.getConnections();
        let replacedConns = [];
        if (dir === 'SOUTH') {
            replacedConns = conns.filter(conn => conn.getY() === tile.rows - 1);
        }
        else if (dir === 'NORTH') {
            replacedConns = conns.filter(conn => conn.getY() === 0);
        }
        else if (dir === 'EAST') {
            replacedConns = conns.filter(conn => conn.getX() === tile.cols - 1);
        }
        else if (dir === 'WEST') {
            replacedConns = conns.filter(conn => conn.getX() === 0);
        }
        return replacedConns;
    }

    /* Returns the player movement direction. */
    getMoveDir(px, py, oldX, oldY) {
        let [dx, dy] = [0, 0];
        let moveDir = '';
        if (!RG.isNullOrUndef([oldX, oldY])) {
            dx = px - oldX;
            dy = py - oldY;
            if (dx !== 0 && dy !== 0) {
                RG.err('ChunkManager', 'setPlayerTile',
                    'Diagonal move not supported');
            }
            if (dx > 0) {moveDir = 'EAST';}
            else if (dx < 0) {moveDir = 'WEST';}
            if (dy > 0) {moveDir = 'SOUTH';}
            else if (dy < 0) {moveDir = 'NORTH';}
        }
        return moveDir;
    }
}

<commit_msg>Added printTileConnections() for debugging. Removed addConnections(), unnecessary. Fixed removeConnections() indices.<commit_after>
const RG = require('./rg');
const FromJSON = require('./game.fromjson');

export const LOAD = Object.freeze(
    {EMPTY: 'EMPTY', LOADED: 'LOADED', JSON: 'JSON', ON_DISK: 'ON_DISK'});
export const CREATE = Object.freeze(
    {EMPTY: 'EMPTY', CREATED: 'CREATED', POPULATED: 'POPULATED'});

export function printTileConnections(msg, tileToConnect, id = -1) {
    console.log(msg);
    if (typeof tileToConnect.getLevel === 'function') {
        if (tileToConnect.getLevel().getID() === id || id === -1) {
            const conns0 = tileToConnect.getLevel().getConnections();
            conns0.forEach(c => {
                const targetLevel = c.getTargetLevel();
                if (Number.isInteger(targetLevel)) {
                    console.log(`\tTarget ID ${targetLevel} found`);
                }
                else {
                    console.log(`\tMap.Level ${targetLevel.getID()} found`);
                }
            });
        }
    }
    else {
        console.log('Skipping printTileConnections due to json input');

    }
}

/* Chunk manager handles loading/saving of world chunks (World.AreaTiles)
 * from/to memory/disk. It also keeps track of the state of each chunk.
 * */
export default class ChunkManager {

    constructor(game, area, loadState) {
        const [sizeX, sizeY] = [area.getSizeX(), area.getSizeY()];
        this.sizeX = sizeX;
        this.sizeY = sizeY;
        this.area = area;
        this.game = game;
        this.state = [];
        for (let x = 0; x < sizeX; x++) {
            this.state[x] = [];
            for (let y = 0; y < sizeY; y++) {
                const chunkState = {loadState};
                this.state[x].push(chunkState);
            }
        }

        // From how far the levels are loaded
        this.loadDistX = 1;
        this.loadDistY = 1;

        this.onDiskDistX = sizeX;
        this.onDiskDistY = sizeY;
    }

    setPlayerTile(px, py, oldX, oldY) {
        const moveDir = this.getMoveDir(px, py, oldX, oldY);
        // printTileConnections('setPlayerTile XXX',
        // this.area.getTiles()[1][0], 4);
        const loadedTiles = [];
        for (let x = 0; x < this.sizeX; x++) {
            for (let y = 0; y < this.sizeY; y++) {
                if (this.inLoadRange(px, py, x, y)) {
                    if (!this.isLoaded(x, y)) {
                        // this.loadTile(px, py, x, y, moveDir);
                        loadedTiles.push([x, y]);
                    }
                }
                else if (this.isLoaded(x, y)) {
                    this.unloadTile(px, py, x, y, moveDir);
                }
            }
        }
        if (loadedTiles.length > 0) {
            this.loadTiles(px, py, loadedTiles, moveDir);
        }
    }

    isLoaded(x, y) {
        return this.state[x][y].loadState === LOAD.LOADED;
    }

    /* Returns true if given tile (tx,ty) is within load range from
     * player px,py .*/
    inLoadRange(px, py, tx, ty) {
        for (let x = px - this.loadDistX; x <= px + this.loadDistX; x++) {
            for (let y = py - this.loadDistY; y <= py + this.loadDistY; y++) {
                if (tx === x && ty === y) {return true;}
            }
        }
        return false;
    }

    loadAllTiles() {
        this.setLoadStateAll(LOAD.LOADED);
    }

    /* Loads the serialized/on-disk tile. */
    loadTiles(px, py, loadedTilesXY) {
        const areaTiles = this.area.getTiles();
        console.log('loadFIles: ' + JSON.stringify(loadedTilesXY));
        const loadedAreaTiles = loadedTilesXY.map(
            xy => areaTiles[xy[0]][xy[1]]
        );

        // const tile10 = areaTiles[1][0];
        // printTileConnections('loadTiles XXX before', tile10, 4);

        this.createTiles(loadedAreaTiles);

        // printTileConnections('loadTiles XXX after', tile10, 4);

        loadedTilesXY.forEach(xy => {
            console.log(`ChunkManager load now tile ${xy}`);
            const [tx, ty] = xy;
            this.state[tx][ty].loadState = LOAD.LOADED;
            // Need to create the connections on adjacent tiles
            /*
            const newTile = areaTiles[tx][ty];

            if (moveDir === 'WEST') {
                const newX = tx + 1;
                const tileToConnect = areaTiles[tx + 1][ty];
                if (newX < this.area.getSizeX()) {
                    this.addConnections('WEST', tileToConnect, newTile);
                }
            }
            else if (moveDir === 'EAST') {
                const newX = tx - 1;
                const tileToConnect = areaTiles[tx - 1][ty];
                if (newX < this.area.getSizeX()) {
                    this.addConnections('EAST', tileToConnect, newTile);
                }

            }
            else if (moveDir === 'SOUTH') {
                const newY = ty - 1;
                if (newY >= 0) {
                    this.addConnections('SOUTH', areaTiles[tx][ty - 1]);
                }
            }
            else if (moveDir === 'NORTH') {
                const newY = ty + 1;
                if (newY < this.area.getSizeY) {
                    this.addConnections('NORTH', areaTiles[tx][ty + 1]);
                }
            }
            */
        });
    }

    /* Unloads the tile from memory. */
    unloadTile(px, py, tx, ty, moveDir) {
        const areaTiles = this.area.getTiles();
        this.state[tx][ty].loadState = LOAD.JSON;

        const levels = areaTiles[tx][ty].getLevels();
        this.game.removeLevels(levels);

        areaTiles[tx][ty] = areaTiles[tx][ty].toJSON();

        // Need to replace connections on adjacent tiles
        if (moveDir === 'WEST') {
            const newX = tx - 1;
            if (newX < this.area.getSizeX()) {
                this.removeConnections('EAST', areaTiles[tx - 1][ty]);
            }
        }
        else if (moveDir === 'EAST') {
            const newX = tx + 1;
            if (newX < this.area.getSizeX()) {
                this.removeConnections('WEST', areaTiles[tx + 1][ty]);
            }
        }
        else if (moveDir === 'NORTH') {
            const newY = ty - 1;
            if (newY >= 0) {
                this.removeConnections('SOUTH', areaTiles[tx][ty - 1]);
            }
        }
        else if (moveDir === 'SOUTH') {
            const newY = ty + 1;
            if (newY < this.area.getSizeY) {
                this.removeConnections('NORTH', areaTiles[tx][ty + 1]);
            }
        }
    }

    getLoadState(x, y) {
        return this.state[x][y].loadState;
    }

    serializeArea() {
        this.setLoadStateAll(LOAD.JSON);
        const levels = this.area.getLevels();
        this.game.removeLevels(levels);
        this.levels = levels.map(l => l.toJSON());
        const tiles = this.area.getTiles();
        for (let x = 0; x < this.sizeX; x++) {
            for (let y = 0; y < this.sizeY; y++) {
                tiles[x][y] = tiles[x][y].toJSON();
            }
        }
    }

    toJSON() {
        return {
            state: this.state
        };
    }

    setLoadStateAll(state) {
        this.state.forEach((col, x) => {
            col.forEach((tile, y) => {
                this.state[x][y].loadState = state;
            });
        });
    }

    createTiles(tilesJSON) {
        const fromJSON = new FromJSON();
        fromJSON.chunkMode = true;
        fromJSON.createTiles(this.game, tilesJSON);
    }

    addConnections(dir, tileToConnect, newTile) {
        printTileConnections('XXX', tileToConnect);
        const oppositeDir = this.getOpposite(dir);
        const addedConns = this.getReplacedConnections(dir, tileToConnect);
        const newConns = this.getReplacedConnections(oppositeDir, newTile);
        const fromJSON = new FromJSON();
        const conns = addedConns.concat(newConns);
        const levels = [tileToConnect.getLevel(), newTile.getLevel()];
        fromJSON.connectTileLevels(levels, conns);
    }

    removeConnections(dir, tile) {
        const replacedConns = this.getReplacedConnections(dir, tile);
        replacedConns.forEach(conn => {
            const targetConn = conn.getTargetStairs();
            const connObj = {
                targetLevel: conn.getTargetLevel().getID(),
                targetStairs: {x: targetConn.getX(), y: targetConn.getY()}
            };

            conn.setConnObj(connObj);
        });
    }

    getReplacedConnections(dir, tile) {
        const level = tile.getLevel();
        const conns = level.getConnections();
        if (conns.length === 0) {
            RG.err('ChunkManager', 'getReplacedConnections',
                'No connections found.');
        }
        let replacedConns = [];
        if (dir === 'SOUTH') {
            replacedConns = conns.filter(conn => conn.getY() === tile.rows - 1);
        }
        else if (dir === 'NORTH') {
            replacedConns = conns.filter(conn => conn.getY() === 0);
        }
        else if (dir === 'EAST') {
            replacedConns = conns.filter(conn => conn.getX() === tile.cols - 1);
        }
        else if (dir === 'WEST') {
            replacedConns = conns.filter(conn => conn.getX() === 0);
        }
        return replacedConns;
    }

    getOpposite(dir) {
        switch (dir) {
            case 'NORTH': return 'SOUTH';
            case 'SOUTH': return 'NORTH';
            case 'EAST': return 'WEST';
            case 'WEST': return 'EAST';
            default: RG.err('ChunkManager', 'getOpposite',
                `Illegal dir ${dir} given.`);
        }
        return '';
    }

    /* Returns the player movement direction. */
    getMoveDir(px, py, oldX, oldY) {
        let [dx, dy] = [0, 0];
        let moveDir = '';
        if (!RG.isNullOrUndef([oldX, oldY])) {
            dx = px - oldX;
            dy = py - oldY;
            if (dx !== 0 && dy !== 0) {
                RG.err('ChunkManager', 'setPlayerTile',
                    'Diagonal move not supported');
            }
            if (dx > 0) {moveDir = 'EAST';}
            else if (dx < 0) {moveDir = 'WEST';}
            if (dy > 0) {moveDir = 'SOUTH';}
            else if (dy < 0) {moveDir = 'NORTH';}
        }
        return moveDir;
    }
}

<|endoftext|>