<commit_before>(function (flow, $, _, ace, Backbone, Blob, d3, FileReader, girder, tangelo, URL, workflow) {
    "use strict";

   // The view for selecting, running, and editing analyses
    flow.AnalysisManagementView = Backbone.View.extend({

        events: {
            'click #show-script': function () {
                var hide = d3.select("#show-script").classed("active");
                flow.setDisplay(hide ? "vis" : "editor");
                this.editor.resize();
                d3.select("#show-script-icon").classed("glyphicon-eye-open", hide);
                d3.select("#show-script-icon").classed("glyphicon-eye-close", !hide);
                d3.select("#show-script-text").text(hide ? "Show script" : "Hide script");

                // This may be showing the workflow view for the first time, so
                // make sure to redraw it.
                this.workflowEditor.update();
            },

            'click #edit': function () {
                var edit = d3.select("#edit").classed("active");
                this.editor.setReadOnly(edit);
                this.editor.renderer.$cursorLayer.element.style.opacity = edit ? 0 : 1;
                this.workflowEditor.editable(!edit);
                d3.selectAll(".edit-controls").classed("hidden", edit);
            },

            'change #mode': function () {
                if (this.analysis) {
                    this.analysis.get('meta').analysis.mode = $("#mode").val();
                    this.editor.getSession().setMode("ace/mode/" + this.analysis.get('meta').analysis.mode);
                }
            },

            'click #save': function () {
                var info, curWorkflow;
                if (this.analysis) {
                    $("#save").addClass("disabled");
                    info = this.analysis.get('meta').analysis;
                    info.name = this.$(".analysis-edit-name").val();
                    if (info.mode === "workflow") {
                        curWorkflow = this.workflowEditor.serialize();
                        info.inputs = curWorkflow.inputs;
                        info.outputs = curWorkflow.outputs;
                        info.steps = curWorkflow.steps;
                        info.connections = curWorkflow.connections;
                    } else {
                        info.script = this.editor.getValue().replace(/(?:\r\n|\r)/g, '\n');
                        info.inputs = this.inputVariables.toJSON();
                        info.outputs = this.outputVariables.toJSON();
                    }
                    girder.restRequest({
                        path: '/item/' + this.analysis.id + '?name=' + encodeURIComponent(info.name),
                        type: 'PUT',
                        error: null
                    }).done(_.bind(function (result) {
                        girder.restRequest({
                            path: '/item/' + this.analysis.id + '/metadata',
                            type: 'PUT',
                            contentType: 'application/json',
                            data: JSON.stringify(this.analysis.get('meta')),
                            error: null
                        }).done(_.bind(function () {
                            // Trigger recreating the analysis UI
                            $("#analysis").change();
                            // Trigger updating this analysis views
                            this.analysis.set('name', info.name);
                            $("#save").removeClass("disabled");
                            this.editor.savedVersion = this.analysis.get('meta').analysis.script;
                        }, this)).error(_.bind(function (error) {
                            // TODO report error
                        }, this));
                    }, this)).error(_.bind(function (error) {
                        // TODO report error
                    }, this));
                }
            },

            'click .delete-analysis': function () {
                $('#confirm-delete').modal('show');
            },

            'change #analysis': function () {
                this.changeAnalysis(this.analyses.get($("#analysis").val()));
            },

            'click #setup': function () {
                if (this.analysis) {
                    this.analysisSetupView.model = this.analysis;
                    this.analysisSetupView.render();
                }
            },

            'click .add-input-variable': function () {
                var model = new Backbone.Model();
                this.inputVariableEditView.model = model;
                this.inputVariables.add(model);
                this.inputVariableEditView.render();
            },

            'click .add-output-variable': function () {
                var model = new Backbone.Model();
                this.outputVariableEditView.model = model;
                this.outputVariables.add(model);
                this.outputVariableEditView.render();
            },

            'click #analysis-new': function () {
                var analysis = {
                    name: $("#analysis-name").val(),
                    inputs: [],
                    outputs: [],
                    mode: "python",
                    script: ""
                };
                this.createAnalysis(analysis);
            },

            'click #workflow-new': function () {
                var analysis = {
                    name: $("#analysis-name").val(),
                    inputs: [],
                    outputs: [],
                    mode: "workflow",
                    steps: [],
                    connections: []
                };
                this.createAnalysis(analysis);
            },

            'click #add-workstep': function () {
                var analysis = this.analyses.get($("#workstep").val());
                this.workflowEditor.add(analysis.get("meta").analysis, analysis.id, analysis.get("updated"));
            },

            'click #add-workvis': function () {
                var vis = this.visualizations.get($("#workvis").val());
                this.workflowEditor.add(vis.get("meta").visualization);
            },

            'click #analysis-download': function () {
                var blob = new Blob([JSON.stringify(this.analysis.get('meta').analysis, null, "    ")]),
                    filename = this.analysis.get('meta').analysis.name + '.json',
                    anchor = $('');
                $("body").append(anchor);
                anchor[0].click();
            },

            'change #analysis-files': function () {
                var files = $('#analysis-files')[0].files;
                _.each(files, function (file) {
                    this.upload(file);
                }, this);
            },

            'click #analysis-upload': function () {
                $('#analysis-files').click();
            }

        },

        initialize: function (settings) {
            this.analysis = null;
            this.datasets = settings.datasets;
            this.visualizations = settings.visualizations;
            this.presets = settings.presets;
            this.analysisSetupView = new flow.AnalysisSetupView({
                el: $('#analysis-setup-dialog'),
                model: this.analysis,
                datasets: this.datasets,
                visualizations: this.visualizations,
                presets: this.presets
            });

            this.editor = ace.edit("code-editor");
            this.editor.setTheme("ace/theme/chrome");
            this.editor.setHighlightActiveLine(false);
            this.editor.setHighlightGutterLine(false);
            this.editor.setShowPrintMargin(false);
            this.editor.setReadOnly(true);
            this.editor.setFontSize(14);
            this.editor.renderer.$cursorLayer.element.style.opacity = 0;

            // 'input' instead of 'change' because it's behind a timeout so
            // it gets fired less frequently.
            this.editor.on('input', _.bind(function () {
                if (this.editor.savedVersion !== this.editor.getValue()) {
                    if (window.onbeforeunload === null) {
                        window.onbeforeunload = function (e) {
                            return "You have unsaved changes in the editor.";
                        };
                    }
                } else {
                    window.onbeforeunload = null;
                }
            }, this));

            this.workflowEditor = workflow(d3.select("#workflow-editor"), girder);
            this.workflowEditor.editable(false);

            this.analyses = settings.analyses;
            this.analysesView = new flow.ItemsView({el: this.$('#analysis'), itemView: flow.ItemOptionView, collection: this.analyses});
            this.analysesView.render();

            this.workstepsView = new flow.ItemsView({el: this.$('#workstep'), itemView: flow.ItemOptionView, collection: this.analyses});
            this.workstepsView.render();

            this.workvisView = new flow.ItemsView({el: this.$('#workvis'), itemView: flow.ItemOptionView, collection: this.visualizations});
            this.workvisView.render();

            this.inputVariables = new Backbone.Collection();
            this.outputVariables = new Backbone.Collection();
            this.inputVariableEditView = new flow.VariableEditView({el: $('#input-variable-edit-dialog'), input: true});
            this.inputVariablesView = new flow.VariablesView({
                el: this.$('.input-variables'),
                itemView: flow.VariableView,
                itemOptions: {editView: this.inputVariableEditView, collection: this.inputVariables},
                collection: this.inputVariables
            });
            this.outputVariableEditView = new flow.VariableEditView({el: $('#output-variable-edit-dialog'), input: false});
            this.outputVariablesView = new flow.VariablesView({
                el: this.$('.output-variables'),
                itemView: flow.VariableView,
                itemOptions: {editView: this.outputVariableEditView, collection: this.outputVariables},
                collection: this.outputVariables
            });

            // Once the first analysis is added, make it the active analysis
            this.analyses.on('add', _.bind(function (item) {
                if (!this.analysis) {
                    this.changeAnalysis(item);
                }
            }, this));

            flow.events.on('flow:change-save-location', this.saveLocationChange, this);

            $('.really-delete-analysis').click(_.bind(function () {
                if (this.analysis) {
                    girder.restRequest({
                        path: 'item/' + this.analysis.id,
                        type: 'DELETE',
                        error: null
                    }).done(_.bind(function () {
                        this.analyses.remove(this.analysis);
                        // Trigger recreating the analysis UI
                        $("#analysis").change();
                        $('#confirm-delete').modal('hide');
                    }, this)).error(_.bind(function (error) {
                        // TODO report error
                    }, this));
                }
            }, this));
        },

        changeAnalysis: function (analysis) {
            var checkCanEdit = function () {
                if (!this.analysis.get('collection') || this.analysis.get('collection').get('_accessLevel') > 0) {
                    d3.select("#edit").classed("hidden", false);
                    d3.selectAll(".edit-controls").classed("hidden", !d3.select("#edit").classed("active"));
                }
            };

            if (this.analysis) {
                this.analysis.off('change:collection', null, this);
            }

            this.analysis = analysis;
            if (this.analysis) {
                if (this.analysis.get('meta').analysis.mode === "workflow") {
                    d3.select("#code-editor").classed("hidden", true);
                    d3.select("#workflow-editor").classed("hidden", false);
                    d3.selectAll(".analysis-edit-controls").classed("hidden", true);
                    d3.selectAll(".workflow-edit-controls").classed("hidden", false);
                    this.workflowEditor.data(this.analysis.get('meta').analysis);
                } else {
                    d3.select("#code-editor").classed("hidden", false);
                    d3.select("#workflow-editor").classed("hidden", true);
                    d3.selectAll(".analysis-edit-controls").classed("hidden", false);
                    d3.selectAll(".workflow-edit-controls").classed("hidden", true);
                    // So we can detect whether or not the text was changed.
                    this.editor.savedVersion = this.analysis.get('meta').analysis.script;
                    this.editor.setValue(this.analysis.get('meta').analysis.script);
                    this.editor.clearSelection();
                    this.$('#mode').val(this.analysis.get('meta').analysis.mode);
                    this.$('#mode').change();
                    this.inputVariables.set(this.analysis.get('meta').analysis.inputs);
                    this.outputVariables.set(this.analysis.get('meta').analysis.outputs);
                }
                this.$('.analysis-edit-name').val(this.analysis.get('meta').analysis.name);

                this.analysis.on('change:collection', checkCanEdit, this);
                _.bind(checkCanEdit, this)();
            } else {
                this.editor.savedVersion = '';
                this.editor.setValue('');
                this.inputVariables.set([]);
                this.outputVariables.set([]);
            }
        },

        upload: function (file) {
            var reader = new FileReader();

            reader.onload = _.bind(function (e) {
                this.createAnalysis(JSON.parse(e.target.result));
            }, this);

            reader.readAsText(file);
        },

        createAnalysis: function (analysis) {
            girder.restRequest({
                path: 'item/?name=' + encodeURIComponent(analysis.name) + '&folderId=' + flow.saveLocation.get('analysisFolder'),
                type: 'POST',
                error: null
            }).done(_.bind(function (result) {
                var analysisUri = 'item/' + result._id;
                girder.restRequest({
                    path: analysisUri + '/metadata',
                    type: 'PUT',
                    contentType: 'application/json',
                    data: JSON.stringify({analysis: analysis}),
                    error: null
                }).done(_.bind(function (result) {
                    var model = new Backbone.Model(result);
                    model.id = model.get('_id');
                    this.analyses.add(model);
                    $("#analysis").val(model.cid);
                    $("#analysis").change();
                    $("#analysis-name").val("");
                }, this)).error(_.bind(function (error) {
                    // TODO report error
                }, this));
            }, this)).error(_.bind(function (error) {
                console.log(JSON.stringify(JSON.parse(error.responseText), null, "  "));
            }, this));
        },

        saveLocationChange: function () {
            this.$('#new-analysis-form').toggleClass('hidden', flow.saveLocation === null);
        }

    });
}(window.flow, window.$, window._, window.ace, window.Backbone, window.Blob, window.d3, window.FileReader, window.girder, window.tangelo, window.URL, window.workflow));
<commit_msg>alter save button when script is modified<commit_after>(function (flow, $, _, ace, Backbone, Blob, d3, FileReader, girder, tangelo, URL, workflow) {
    "use strict";

   // The view for selecting, running, and editing analyses
    flow.AnalysisManagementView = Backbone.View.extend({

        events: {
            'click #show-script': function () {
                var hide = d3.select("#show-script").classed("active");
                flow.setDisplay(hide ? "vis" : "editor");
                this.editor.resize();
                d3.select("#show-script-icon").classed("glyphicon-eye-open", hide);
                d3.select("#show-script-icon").classed("glyphicon-eye-close", !hide);
                d3.select("#show-script-text").text(hide ? "Show script" : "Hide script");

                // This may be showing the workflow view for the first time, so
                // make sure to redraw it.
                this.workflowEditor.update();
            },

            'click #edit': function () {
                var edit = d3.select("#edit").classed("active");
                this.editor.setReadOnly(edit);
                this.editor.renderer.$cursorLayer.element.style.opacity = edit ? 0 : 1;
                this.workflowEditor.editable(!edit);
                d3.selectAll(".edit-controls").classed("hidden", edit);
            },

            'change #mode': function () {
                if (this.analysis) {
                    this.analysis.get('meta').analysis.mode = $("#mode").val();
                    this.editor.getSession().setMode("ace/mode/" + this.analysis.get('meta').analysis.mode);
                }
            },

            'click #save': function () {
                var info, curWorkflow;
                if (this.analysis) {
                    $("#save").addClass("disabled");
                    info = this.analysis.get('meta').analysis;
                    info.name = this.$(".analysis-edit-name").val();
                    if (info.mode === "workflow") {
                        curWorkflow = this.workflowEditor.serialize();
                        info.inputs = curWorkflow.inputs;
                        info.outputs = curWorkflow.outputs;
                        info.steps = curWorkflow.steps;
                        info.connections = curWorkflow.connections;
                    } else {
                        info.script = this.editor.getValue().replace(/(?:\r\n|\r)/g, '\n');
                        info.inputs = this.inputVariables.toJSON();
                        info.outputs = this.outputVariables.toJSON();
                    }
                    girder.restRequest({
                        path: '/item/' + this.analysis.id + '?name=' + encodeURIComponent(info.name),
                        type: 'PUT',
                        error: null
                    }).done(_.bind(function (result) {
                        girder.restRequest({
                            path: '/item/' + this.analysis.id + '/metadata',
                            type: 'PUT',
                            contentType: 'application/json',
                            data: JSON.stringify(this.analysis.get('meta')),
                            error: null
                        }).done(_.bind(function () {
                            // Trigger recreating the analysis UI
                            $("#analysis").change();
                            // Trigger updating this analysis views
                            this.analysis.set('name', info.name);
                            $("#save").removeClass("disabled");
                            this.editor.savedVersion = this.analysis.get('meta').analysis.script;
                        }, this)).error(_.bind(function (error) {
                            // TODO report error
                        }, this));
                    }, this)).error(_.bind(function (error) {
                        // TODO report error
                    }, this));
                }
            },

            'click .delete-analysis': function () {
                $('#confirm-delete').modal('show');
            },

            'change #analysis': function () {
                this.changeAnalysis(this.analyses.get($("#analysis").val()));
            },

            'click #setup': function () {
                if (this.analysis) {
                    this.analysisSetupView.model = this.analysis;
                    this.analysisSetupView.render();
                }
            },

            'click .add-input-variable': function () {
                var model = new Backbone.Model();
                this.inputVariableEditView.model = model;
                this.inputVariables.add(model);
                this.inputVariableEditView.render();
            },

            'click .add-output-variable': function () {
                var model = new Backbone.Model();
                this.outputVariableEditView.model = model;
                this.outputVariables.add(model);
                this.outputVariableEditView.render();
            },

            'click #analysis-new': function () {
                var analysis = {
                    name: $("#analysis-name").val(),
                    inputs: [],
                    outputs: [],
                    mode: "python",
                    script: ""
                };
                this.createAnalysis(analysis);
            },

            'click #workflow-new': function () {
                var analysis = {
                    name: $("#analysis-name").val(),
                    inputs: [],
                    outputs: [],
                    mode: "workflow",
                    steps: [],
                    connections: []
                };
                this.createAnalysis(analysis);
            },

            'click #add-workstep': function () {
                var analysis = this.analyses.get($("#workstep").val());
                this.workflowEditor.add(analysis.get("meta").analysis, analysis.id, analysis.get("updated"));
            },

            'click #add-workvis': function () {
                var vis = this.visualizations.get($("#workvis").val());
                this.workflowEditor.add(vis.get("meta").visualization);
            },

            'click #analysis-download': function () {
                var blob = new Blob([JSON.stringify(this.analysis.get('meta').analysis, null, "    ")]),
                    filename = this.analysis.get('meta').analysis.name + '.json',
                    anchor = $('');
                $("body").append(anchor);
                anchor[0].click();
            },

            'change #analysis-files': function () {
                var files = $('#analysis-files')[0].files;
                _.each(files, function (file) {
                    this.upload(file);
                }, this);
            },

            'click #analysis-upload': function () {
                $('#analysis-files').click();
            }

        },

        initialize: function (settings) {
            this.analysis = null;
            this.datasets = settings.datasets;
            this.visualizations = settings.visualizations;
            this.presets = settings.presets;
            this.analysisSetupView = new flow.AnalysisSetupView({
                el: $('#analysis-setup-dialog'),
                model: this.analysis,
                datasets: this.datasets,
                visualizations: this.visualizations,
                presets: this.presets
            });

            this.editor = ace.edit("code-editor");
            this.editor.setTheme("ace/theme/chrome");
            this.editor.setHighlightActiveLine(false);
            this.editor.setHighlightGutterLine(false);
            this.editor.setShowPrintMargin(false);
            this.editor.setReadOnly(true);
            this.editor.setFontSize(14);
            this.editor.renderer.$cursorLayer.element.style.opacity = 0;

            // 'input' instead of 'change' because it's behind a timeout so
            // it gets fired less frequently.
            this.editor.on('input', _.bind(function () {
                if (this.editor.savedVersion !== this.editor.getValue()) {
                    $("#save").addClass("btn-primary");
                    $("#save").removeClass("disabled");
                    if (window.onbeforeunload === null) {
                        window.onbeforeunload = function (e) {
                            return "You have unsaved changes in the editor.";
                        };
                    }
                } else {
                    window.onbeforeunload = null;
                    $("#save").removeClass("btn-primary");
                    $("#save").addClass("disabled");
                }
            }, this));

            this.workflowEditor = workflow(d3.select("#workflow-editor"), girder);
            this.workflowEditor.editable(false);

            this.analyses = settings.analyses;
            this.analysesView = new flow.ItemsView({el: this.$('#analysis'), itemView: flow.ItemOptionView, collection: this.analyses});
            this.analysesView.render();

            this.workstepsView = new flow.ItemsView({el: this.$('#workstep'), itemView: flow.ItemOptionView, collection: this.analyses});
            this.workstepsView.render();

            this.workvisView = new flow.ItemsView({el: this.$('#workvis'), itemView: flow.ItemOptionView, collection: this.visualizations});
            this.workvisView.render();

            this.inputVariables = new Backbone.Collection();
            this.outputVariables = new Backbone.Collection();
            this.inputVariableEditView = new flow.VariableEditView({el: $('#input-variable-edit-dialog'), input: true});
            this.inputVariablesView = new flow.VariablesView({
                el: this.$('.input-variables'),
                itemView: flow.VariableView,
                itemOptions: {editView: this.inputVariableEditView, collection: this.inputVariables},
                collection: this.inputVariables
            });
            this.outputVariableEditView = new flow.VariableEditView({el: $('#output-variable-edit-dialog'), input: false});
            this.outputVariablesView = new flow.VariablesView({
                el: this.$('.output-variables'),
                itemView: flow.VariableView,
                itemOptions: {editView: this.outputVariableEditView, collection: this.outputVariables},
                collection: this.outputVariables
            });

            // Once the first analysis is added, make it the active analysis
            this.analyses.on('add', _.bind(function (item) {
                if (!this.analysis) {
                    this.changeAnalysis(item);
                }
            }, this));

            flow.events.on('flow:change-save-location', this.saveLocationChange, this);

            $('.really-delete-analysis').click(_.bind(function () {
                if (this.analysis) {
                    girder.restRequest({
                        path: 'item/' + this.analysis.id,
                        type: 'DELETE',
                        error: null
                    }).done(_.bind(function () {
                        this.analyses.remove(this.analysis);
                        // Trigger recreating the analysis UI
                        $("#analysis").change();
                        $('#confirm-delete').modal('hide');
                    }, this)).error(_.bind(function (error) {
                        // TODO report error
                    }, this));
                }
            }, this));
        },

        changeAnalysis: function (analysis) {
            var checkCanEdit = function () {
                if (!this.analysis.get('collection') || this.analysis.get('collection').get('_accessLevel') > 0) {
                    d3.select("#edit").classed("hidden", false);
                    d3.selectAll(".edit-controls").classed("hidden", !d3.select("#edit").classed("active"));
                }
            };

            if (this.analysis) {
                this.analysis.off('change:collection', null, this);
            }

            this.analysis = analysis;
            if (this.analysis) {
                if (this.analysis.get('meta').analysis.mode === "workflow") {
                    d3.select("#code-editor").classed("hidden", true);
                    d3.select("#workflow-editor").classed("hidden", false);
                    d3.selectAll(".analysis-edit-controls").classed("hidden", true);
                    d3.selectAll(".workflow-edit-controls").classed("hidden", false);
                    this.workflowEditor.data(this.analysis.get('meta').analysis);
                } else {
                    d3.select("#code-editor").classed("hidden", false);
                    d3.select("#workflow-editor").classed("hidden", true);
                    d3.selectAll(".analysis-edit-controls").classed("hidden", false);
                    d3.selectAll(".workflow-edit-controls").classed("hidden", true);
                    // So we can detect whether or not the text was changed.
                    this.editor.savedVersion = this.analysis.get('meta').analysis.script;
                    this.editor.setValue(this.analysis.get('meta').analysis.script);
                    this.editor.clearSelection();
                    this.$('#mode').val(this.analysis.get('meta').analysis.mode);
                    this.$('#mode').change();
                    this.inputVariables.set(this.analysis.get('meta').analysis.inputs);
                    this.outputVariables.set(this.analysis.get('meta').analysis.outputs);
                }
                this.$('.analysis-edit-name').val(this.analysis.get('meta').analysis.name);

                this.analysis.on('change:collection', checkCanEdit, this);
                _.bind(checkCanEdit, this)();
            } else {
                this.editor.savedVersion = '';
                this.editor.setValue('');
                this.inputVariables.set([]);
                this.outputVariables.set([]);
            }
        },

        upload: function (file) {
            var reader = new FileReader();

            reader.onload = _.bind(function (e) {
                this.createAnalysis(JSON.parse(e.target.result));
            }, this);

            reader.readAsText(file);
        },

        createAnalysis: function (analysis) {
            girder.restRequest({
                path: 'item/?name=' + encodeURIComponent(analysis.name) + '&folderId=' + flow.saveLocation.get('analysisFolder'),
                type: 'POST',
                error: null
            }).done(_.bind(function (result) {
                var analysisUri = 'item/' + result._id;
                girder.restRequest({
                    path: analysisUri + '/metadata',
                    type: 'PUT',
                    contentType: 'application/json',
                    data: JSON.stringify({analysis: analysis}),
                    error: null
                }).done(_.bind(function (result) {
                    var model = new Backbone.Model(result);
                    model.id = model.get('_id');
                    this.analyses.add(model);
                    $("#analysis").val(model.cid);
                    $("#analysis").change();
                    $("#analysis-name").val("");
                }, this)).error(_.bind(function (error) {
                    // TODO report error
                }, this));
            }, this)).error(_.bind(function (error) {
                console.log(JSON.stringify(JSON.parse(error.responseText), null, "  "));
            }, this));
        },

        saveLocationChange: function () {
            this.$('#new-analysis-form').toggleClass('hidden', flow.saveLocation === null);
        }

    });
}(window.flow, window.$, window._, window.ace, window.Backbone, window.Blob, window.d3, window.FileReader, window.girder, window.tangelo, window.URL, window.workflow));
<|endoftext|>


<commit_before>(function (flow, $, _, ace, Backbone, Blob, d3, FileReader, girder, tangelo, URL, workflow) {
    "use strict";

   // The view for selecting, running, and editing analyses
    flow.AnalysisManagementView = Backbone.View.extend({

        events: {
            'click #show-script': function () {
                var hide = d3.select("#show-script").classed("active");
                flow.setDisplay(hide ? "vis" : "editor");
                this.editor.resize();
                d3.select("#show-script-icon").classed("glyphicon-eye-open", hide);
                d3.select("#show-script-icon").classed("glyphicon-eye-close", !hide);
                d3.select("#show-script-text").text(hide ? "Show script" : "Hide script");

                // This may be showing the workflow view for the first time, so
                // make sure to redraw it.
                this.workflowEditor.update();
            },

            'click #edit': function () {
                var edit = d3.select("#edit").classed("active");
                this.editor.setReadOnly(edit);
                this.editor.renderer.$cursorLayer.element.style.opacity = edit ? 0 : 1;
                d3.selectAll(".edit-controls").classed("hidden", edit);
            },

            'change #mode': function () {
                if (this.analysis) {
                    this.analysis.get('meta').analysis.mode = $("#mode").val();
                    this.editor.getSession().setMode("ace/mode/" + this.analysis.get('meta').analysis.mode);
                }
            },

            'click #save': function () {
                var info, curWorkflow;
                if (this.analysis) {
                    $("#save").addClass("disabled");
                    info = this.analysis.get('meta').analysis;
                    info.name = this.$(".analysis-edit-name").val();
                    if (info.mode === "workflow") {
                        curWorkflow = this.workflowEditor.serialize();
                        info.inputs = curWorkflow.inputs;
                        info.outputs = curWorkflow.outputs;
                        info.steps = curWorkflow.steps;
                        info.connections = curWorkflow.connections;
                    } else {
                        info.script = this.editor.getValue().replace(/(?:\r\n|\r)/g, '\n');
                        info.inputs = this.inputVariables.toJSON();
                        info.outputs = this.outputVariables.toJSON();
                    }
                    girder.restRequest({
                        path: '/item/' + this.analysis.id + '?name=' + encodeURIComponent(info.name),
                        type: 'PUT',
                        error: null
                    }).done(_.bind(function (result) {
                        girder.restRequest({
                            path: '/item/' + this.analysis.id + '/metadata',
                            type: 'PUT',
                            contentType: 'application/json',
                            data: JSON.stringify(this.analysis.get('meta')),
                            error: null
                        }).done(_.bind(function () {
                            // Trigger recreating the analysis UI
                            $("#analysis").change();
                            // Trigger updating this analysis views
                            this.analysis.set('name', info.name);
                            $("#save").removeClass("disabled");
                        }, this)).error(_.bind(function (error) {
                            // TODO report error
                        }, this));
                    }, this)).error(_.bind(function (error) {
                        // TODO report error
                    }, this));
                }
            },

            'click .delete-analysis': function () {
                $('#confirm-delete').modal('show');
            },

            'change #analysis': function () {
                this.changeAnalysis(this.analyses.get($("#analysis").val()));
            },

            'click #setup': function () {
                if (this.analysis) {
                    this.analysisSetupView.model = this.analysis;
                    this.analysisSetupView.render();
                }
            },

            'click .add-input-variable': function () {
                var model = new Backbone.Model();
                this.inputVariableEditView.model = model;
                this.inputVariables.add(model);
                this.inputVariableEditView.render();
            },

            'click .add-output-variable': function () {
                var model = new Backbone.Model();
                this.outputVariableEditView.model = model;
                this.outputVariables.add(model);
                this.outputVariableEditView.render();
            },

            'click #analysis-new': function () {
                var analysis = {
                    name: $("#analysis-name").val(),
                    inputs: [],
                    outputs: [],
                    mode: "python",
                    script: ""
                };
                this.createAnalysis(analysis);
            },

            'click #workflow-new': function () {
                var analysis = {
                    name: $("#analysis-name").val(),
                    inputs: [],
                    outputs: [],
                    mode: "workflow",
                    steps: [],
                    connections: []
                };
                this.createAnalysis(analysis);
            },

            'click #add-workstep': function () {
                var analysis = this.analyses.get($("#workstep").val());
                this.workflowEditor.add(analysis.get("meta").analysis);
            },

            'click #add-workvis': function () {
                var vis = this.visualizations.get($("#workvis").val());
                this.workflowEditor.add(vis.toJSON());
            },

            'click #analysis-download': function () {
                var blob = new Blob([JSON.stringify(this.analysis.get('meta').analysis, null, "    ")]),
                    filename = this.analysis.get('meta').analysis.name + '.json',
                    anchor = $('');
                $("body").append(anchor);
                anchor[0].click();
            },

            'change #analysis-files': function () {
                var files = $('#analysis-files')[0].files;
                _.each(files, function (file) {
                    this.upload(file);
                }, this);
            },

            'click #analysis-upload': function () {
                $('#analysis-files').click();
            }

        },

        initialize: function (settings) {
            this.analysis = null;
            this.datasets = settings.datasets;
            this.visualizations = settings.visualizations;
            this.presets = settings.presets;
            this.analysisSetupView = new flow.AnalysisSetupView({
                el: $('#analysis-setup-dialog'),
                model: this.analysis,
                datasets: this.datasets,
                visualizations: this.visualizations,
                presets: this.presets
            });

            this.editor = ace.edit("code-editor");
            this.editor.setTheme("ace/theme/chrome");
            this.editor.setHighlightActiveLine(false);
            this.editor.setHighlightGutterLine(false);
            this.editor.setShowPrintMargin(false);
            this.editor.setReadOnly(true);
            this.editor.setFontSize(14);
            this.editor.renderer.$cursorLayer.element.style.opacity = 0;
            this.workflowEditor = workflow(d3.select("#workflow-editor"));

            this.analyses = settings.analyses;
            this.analysesView = new flow.ItemsView({el: this.$('#analysis'), itemView: flow.ItemOptionView, collection: this.analyses});
            this.analysesView.render();

            this.workstepsView = new flow.ItemsView({el: this.$('#workstep'), itemView: flow.ItemOptionView, collection: this.analyses});
            this.workstepsView.render();

            this.workvisView = new flow.ItemsView({el: this.$('#workvis'), itemView: flow.ItemOptionView, collection: this.visualizations});
            this.workvisView.render();

            this.inputVariables = new Backbone.Collection();
            this.outputVariables = new Backbone.Collection();
            this.inputVariableEditView = new flow.VariableEditView({el: $('#input-variable-edit-dialog'), input: true});
            this.inputVariablesView = new flow.VariablesView({
                el: this.$('.input-variables'),
                itemView: flow.VariableView,
                itemOptions: {editView: this.inputVariableEditView, collection: this.inputVariables},
                collection: this.inputVariables
            });
            this.outputVariableEditView = new flow.VariableEditView({el: $('#output-variable-edit-dialog'), input: false});
            this.outputVariablesView = new flow.VariablesView({
                el: this.$('.output-variables'),
                itemView: flow.VariableView,
                itemOptions: {editView: this.outputVariableEditView, collection: this.outputVariables},
                collection: this.outputVariables
            });

            // Once the first analysis is added, make it the active analysis
            this.analyses.on('add', _.bind(function (item) {
                if (!this.analysis) {
                    this.changeAnalysis(item);
                }
            }, this));

            flow.events.on('flow:change-save-location', this.saveLocationChange, this);

            $('.really-delete-analysis').click(_.bind(function () {
                if (this.analysis) {
                    girder.restRequest({
                        path: 'item/' + this.analysis.id,
                        type: 'DELETE',
                        error: null
                    }).done(_.bind(function () {
                        this.analyses.remove(this.analysis);
                        // Trigger recreating the analysis UI
                        $("#analysis").change();
                        $('#confirm-delete').modal('hide');
                    }, this)).error(_.bind(function (error) {
                        // TODO report error
                    }, this));
                }
            }, this));
        },

        changeAnalysis: function (analysis) {
            var checkCanEdit = function () {
                if (!this.analysis.get('collection') || this.analysis.get('collection').get('_accessLevel') > 0) {
                    d3.select("#edit").classed("hidden", false);
                    d3.selectAll(".edit-controls").classed("hidden", !d3.select("#edit").classed("active"));
                }
            };

            if (this.analysis) {
                this.analysis.off('change:collection', null, this);
            }

            this.analysis = analysis;
            if (this.analysis) {
                if (this.analysis.get('meta').analysis.mode === "workflow") {
                    d3.select("#code-editor").classed("hidden", true);
                    d3.select("#workflow-editor").classed("hidden", false);
                    d3.selectAll(".analysis-edit-controls").classed("hidden", true);
                    d3.selectAll(".workflow-edit-controls").classed("hidden", false);
                    this.workflowEditor.data(this.analysis.get('meta').analysis);
                } else {
                    d3.select("#code-editor").classed("hidden", false);
                    d3.select("#workflow-editor").classed("hidden", true);
                    d3.selectAll(".analysis-edit-controls").classed("hidden", false);
                    d3.selectAll(".workflow-edit-controls").classed("hidden", true);
                    this.editor.setValue(this.analysis.get('meta').analysis.script);
                    this.editor.clearSelection();
                    this.$('#mode').val(this.analysis.get('meta').analysis.mode);
                    this.$('#mode').change();
                    this.inputVariables.set(this.analysis.get('meta').analysis.inputs);
                    this.outputVariables.set(this.analysis.get('meta').analysis.outputs);
                }
                this.$('.analysis-edit-name').val(this.analysis.get('meta').analysis.name);

                this.analysis.on('change:collection', checkCanEdit, this);
                _.bind(checkCanEdit, this)();
            } else {
                this.editor.setValue('');
                this.inputVariables.set([]);
                this.outputVariables.set([]);
            }
        },

        upload: function (file) {
            var reader = new FileReader();

            reader.onload = _.bind(function (e) {
                this.createAnalysis(JSON.parse(e.target.result));
            }, this);

            reader.readAsText(file);
        },

        createAnalysis: function (analysis) {
            girder.restRequest({
                path: 'item/?name=' + encodeURIComponent(analysis.name) + '&folderId=' + flow.saveLocation.get('analysisFolder'),
                type: 'POST',
                error: null
            }).done(_.bind(function (result) {
                var analysisUri = 'item/' + result._id;
                girder.restRequest({
                    path: analysisUri + '/metadata',
                    type: 'PUT',
                    contentType: 'application/json',
                    data: JSON.stringify({analysis: analysis}),
                    error: null
                }).done(_.bind(function (result) {
                    var model = new Backbone.Model(result);
                    model.id = model.get('_id');
                    this.analyses.add(model);
                    $("#analysis").val(model.cid);
                    $("#analysis").change();
                    $("#analysis-name").val("");
                }, this)).error(_.bind(function (error) {
                    // TODO report error
                }, this));
            }, this)).error(_.bind(function (error) {
                console.log(JSON.stringify(JSON.parse(error.responseText), null, "  "));
            }, this));
        },

        saveLocationChange: function () {
            this.$('#new-analysis-form').toggleClass('hidden', flow.saveLocation === null);
        }

    });

}(window.flow, window.$, window._, window.ace, window.Backbone, window.Blob, window.d3, window.FileReader, window.girder, window.tangelo, window.URL, window.workflow));
<commit_msg>Fixing adding visualization to workflow<commit_after>(function (flow, $, _, ace, Backbone, Blob, d3, FileReader, girder, tangelo, URL, workflow) {
    "use strict";

   // The view for selecting, running, and editing analyses
    flow.AnalysisManagementView = Backbone.View.extend({

        events: {
            'click #show-script': function () {
                var hide = d3.select("#show-script").classed("active");
                flow.setDisplay(hide ? "vis" : "editor");
                this.editor.resize();
                d3.select("#show-script-icon").classed("glyphicon-eye-open", hide);
                d3.select("#show-script-icon").classed("glyphicon-eye-close", !hide);
                d3.select("#show-script-text").text(hide ? "Show script" : "Hide script");

                // This may be showing the workflow view for the first time, so
                // make sure to redraw it.
                this.workflowEditor.update();
            },

            'click #edit': function () {
                var edit = d3.select("#edit").classed("active");
                this.editor.setReadOnly(edit);
                this.editor.renderer.$cursorLayer.element.style.opacity = edit ? 0 : 1;
                d3.selectAll(".edit-controls").classed("hidden", edit);
            },

            'change #mode': function () {
                if (this.analysis) {
                    this.analysis.get('meta').analysis.mode = $("#mode").val();
                    this.editor.getSession().setMode("ace/mode/" + this.analysis.get('meta').analysis.mode);
                }
            },

            'click #save': function () {
                var info, curWorkflow;
                if (this.analysis) {
                    $("#save").addClass("disabled");
                    info = this.analysis.get('meta').analysis;
                    info.name = this.$(".analysis-edit-name").val();
                    if (info.mode === "workflow") {
                        curWorkflow = this.workflowEditor.serialize();
                        info.inputs = curWorkflow.inputs;
                        info.outputs = curWorkflow.outputs;
                        info.steps = curWorkflow.steps;
                        info.connections = curWorkflow.connections;
                    } else {
                        info.script = this.editor.getValue().replace(/(?:\r\n|\r)/g, '\n');
                        info.inputs = this.inputVariables.toJSON();
                        info.outputs = this.outputVariables.toJSON();
                    }
                    girder.restRequest({
                        path: '/item/' + this.analysis.id + '?name=' + encodeURIComponent(info.name),
                        type: 'PUT',
                        error: null
                    }).done(_.bind(function (result) {
                        girder.restRequest({
                            path: '/item/' + this.analysis.id + '/metadata',
                            type: 'PUT',
                            contentType: 'application/json',
                            data: JSON.stringify(this.analysis.get('meta')),
                            error: null
                        }).done(_.bind(function () {
                            // Trigger recreating the analysis UI
                            $("#analysis").change();
                            // Trigger updating this analysis views
                            this.analysis.set('name', info.name);
                            $("#save").removeClass("disabled");
                        }, this)).error(_.bind(function (error) {
                            // TODO report error
                        }, this));
                    }, this)).error(_.bind(function (error) {
                        // TODO report error
                    }, this));
                }
            },

            'click .delete-analysis': function () {
                $('#confirm-delete').modal('show');
            },

            'change #analysis': function () {
                this.changeAnalysis(this.analyses.get($("#analysis").val()));
            },

            'click #setup': function () {
                if (this.analysis) {
                    this.analysisSetupView.model = this.analysis;
                    this.analysisSetupView.render();
                }
            },

            'click .add-input-variable': function () {
                var model = new Backbone.Model();
                this.inputVariableEditView.model = model;
                this.inputVariables.add(model);
                this.inputVariableEditView.render();
            },

            'click .add-output-variable': function () {
                var model = new Backbone.Model();
                this.outputVariableEditView.model = model;
                this.outputVariables.add(model);
                this.outputVariableEditView.render();
            },

            'click #analysis-new': function () {
                var analysis = {
                    name: $("#analysis-name").val(),
                    inputs: [],
                    outputs: [],
                    mode: "python",
                    script: ""
                };
                this.createAnalysis(analysis);
            },

            'click #workflow-new': function () {
                var analysis = {
                    name: $("#analysis-name").val(),
                    inputs: [],
                    outputs: [],
                    mode: "workflow",
                    steps: [],
                    connections: []
                };
                this.createAnalysis(analysis);
            },

            'click #add-workstep': function () {
                var analysis = this.analyses.get($("#workstep").val());
                this.workflowEditor.add(analysis.get("meta").analysis);
            },

            'click #add-workvis': function () {
                var vis = this.visualizations.get($("#workvis").val());
                this.workflowEditor.add(vis.get("meta").visualization);
            },

            'click #analysis-download': function () {
                var blob = new Blob([JSON.stringify(this.analysis.get('meta').analysis, null, "    ")]),
                    filename = this.analysis.get('meta').analysis.name + '.json',
                    anchor = $('');
                $("body").append(anchor);
                anchor[0].click();
            },

            'change #analysis-files': function () {
                var files = $('#analysis-files')[0].files;
                _.each(files, function (file) {
                    this.upload(file);
                }, this);
            },

            'click #analysis-upload': function () {
                $('#analysis-files').click();
            }

        },

        initialize: function (settings) {
            this.analysis = null;
            this.datasets = settings.datasets;
            this.visualizations = settings.visualizations;
            this.presets = settings.presets;
            this.analysisSetupView = new flow.AnalysisSetupView({
                el: $('#analysis-setup-dialog'),
                model: this.analysis,
                datasets: this.datasets,
                visualizations: this.visualizations,
                presets: this.presets
            });

            this.editor = ace.edit("code-editor");
            this.editor.setTheme("ace/theme/chrome");
            this.editor.setHighlightActiveLine(false);
            this.editor.setHighlightGutterLine(false);
            this.editor.setShowPrintMargin(false);
            this.editor.setReadOnly(true);
            this.editor.setFontSize(14);
            this.editor.renderer.$cursorLayer.element.style.opacity = 0;
            this.workflowEditor = workflow(d3.select("#workflow-editor"));

            this.analyses = settings.analyses;
            this.analysesView = new flow.ItemsView({el: this.$('#analysis'), itemView: flow.ItemOptionView, collection: this.analyses});
            this.analysesView.render();

            this.workstepsView = new flow.ItemsView({el: this.$('#workstep'), itemView: flow.ItemOptionView, collection: this.analyses});
            this.workstepsView.render();

            this.workvisView = new flow.ItemsView({el: this.$('#workvis'), itemView: flow.ItemOptionView, collection: this.visualizations});
            this.workvisView.render();

            this.inputVariables = new Backbone.Collection();
            this.outputVariables = new Backbone.Collection();
            this.inputVariableEditView = new flow.VariableEditView({el: $('#input-variable-edit-dialog'), input: true});
            this.inputVariablesView = new flow.VariablesView({
                el: this.$('.input-variables'),
                itemView: flow.VariableView,
                itemOptions: {editView: this.inputVariableEditView, collection: this.inputVariables},
                collection: this.inputVariables
            });
            this.outputVariableEditView = new flow.VariableEditView({el: $('#output-variable-edit-dialog'), input: false});
            this.outputVariablesView = new flow.VariablesView({
                el: this.$('.output-variables'),
                itemView: flow.VariableView,
                itemOptions: {editView: this.outputVariableEditView, collection: this.outputVariables},
                collection: this.outputVariables
            });

            // Once the first analysis is added, make it the active analysis
            this.analyses.on('add', _.bind(function (item) {
                if (!this.analysis) {
                    this.changeAnalysis(item);
                }
            }, this));

            flow.events.on('flow:change-save-location', this.saveLocationChange, this);

            $('.really-delete-analysis').click(_.bind(function () {
                if (this.analysis) {
                    girder.restRequest({
                        path: 'item/' + this.analysis.id,
                        type: 'DELETE',
                        error: null
                    }).done(_.bind(function () {
                        this.analyses.remove(this.analysis);
                        // Trigger recreating the analysis UI
                        $("#analysis").change();
                        $('#confirm-delete').modal('hide');
                    }, this)).error(_.bind(function (error) {
                        // TODO report error
                    }, this));
                }
            }, this));
        },

        changeAnalysis: function (analysis) {
            var checkCanEdit = function () {
                if (!this.analysis.get('collection') || this.analysis.get('collection').get('_accessLevel') > 0) {
                    d3.select("#edit").classed("hidden", false);
                    d3.selectAll(".edit-controls").classed("hidden", !d3.select("#edit").classed("active"));
                }
            };

            if (this.analysis) {
                this.analysis.off('change:collection', null, this);
            }

            this.analysis = analysis;
            if (this.analysis) {
                if (this.analysis.get('meta').analysis.mode === "workflow") {
                    d3.select("#code-editor").classed("hidden", true);
                    d3.select("#workflow-editor").classed("hidden", false);
                    d3.selectAll(".analysis-edit-controls").classed("hidden", true);
                    d3.selectAll(".workflow-edit-controls").classed("hidden", false);
                    this.workflowEditor.data(this.analysis.get('meta').analysis);
                } else {
                    d3.select("#code-editor").classed("hidden", false);
                    d3.select("#workflow-editor").classed("hidden", true);
                    d3.selectAll(".analysis-edit-controls").classed("hidden", false);
                    d3.selectAll(".workflow-edit-controls").classed("hidden", true);
                    this.editor.setValue(this.analysis.get('meta').analysis.script);
                    this.editor.clearSelection();
                    this.$('#mode').val(this.analysis.get('meta').analysis.mode);
                    this.$('#mode').change();
                    this.inputVariables.set(this.analysis.get('meta').analysis.inputs);
                    this.outputVariables.set(this.analysis.get('meta').analysis.outputs);
                }
                this.$('.analysis-edit-name').val(this.analysis.get('meta').analysis.name);

                this.analysis.on('change:collection', checkCanEdit, this);
                _.bind(checkCanEdit, this)();
            } else {
                this.editor.setValue('');
                this.inputVariables.set([]);
                this.outputVariables.set([]);
            }
        },

        upload: function (file) {
            var reader = new FileReader();

            reader.onload = _.bind(function (e) {
                this.createAnalysis(JSON.parse(e.target.result));
            }, this);

            reader.readAsText(file);
        },

        createAnalysis: function (analysis) {
            girder.restRequest({
                path: 'item/?name=' + encodeURIComponent(analysis.name) + '&folderId=' + flow.saveLocation.get('analysisFolder'),
                type: 'POST',
                error: null
            }).done(_.bind(function (result) {
                var analysisUri = 'item/' + result._id;
                girder.restRequest({
                    path: analysisUri + '/metadata',
                    type: 'PUT',
                    contentType: 'application/json',
                    data: JSON.stringify({analysis: analysis}),
                    error: null
                }).done(_.bind(function (result) {
                    var model = new Backbone.Model(result);
                    model.id = model.get('_id');
                    this.analyses.add(model);
                    $("#analysis").val(model.cid);
                    $("#analysis").change();
                    $("#analysis-name").val("");
                }, this)).error(_.bind(function (error) {
                    // TODO report error
                }, this));
            }, this)).error(_.bind(function (error) {
                console.log(JSON.stringify(JSON.parse(error.responseText), null, "  "));
            }, this));
        },

        saveLocationChange: function () {
            this.$('#new-analysis-form').toggleClass('hidden', flow.saveLocation === null);
        }

    });

}(window.flow, window.$, window._, window.ace, window.Backbone, window.Blob, window.d3, window.FileReader, window.girder, window.tangelo, window.URL, window.workflow));
<|endoftext|>


<commit_before>
module.exports = FrameOutputStream;

var util = require("util");
var Stream = require("stream");

function FrameOutputStream(destination){

    this._destination = destination;
    this._destination_finished = false;

    this._writing = false;

    var finish = this.finish.bind(this);

    destination.on("error", finish);
    destination.on("close", finish);
    destination.on("finish", finish);

    this._frame = null;
    this._queue = [];
}

FrameOutputStream.prototype._queueFrame = function(frame){

    if(this._frame === null){
        this._frame = frame;
    }
    else{
        this._queue.push(frame);
    }

    return frame;
};

FrameOutputStream.prototype.frame = function(command, headers, streamOptions){
    return this._queueFrame(new OutgoingFrame(this, command, headers, streamOptions));
};

FrameOutputStream.prototype.heartbeat = function(callback){

    if(this._frame){
        return;
    }

    this._destination.write("\n", "utf8", callback);
};

FrameOutputStream.prototype.finish = function(){
    this._destination_finished = true;
};

FrameOutputStream.prototype.hasFinished = function(){
    return this._destination_finished;
};

function OutgoingFrame(stream, command, headers, streamOptions){

    Stream.Writable.call(this, streamOptions);

    this._stream = stream;
    this._writes = [];
    this._write = this._writeHeaderAndBody;

    this.command = command;
    this.headers = headers || {};
}

util.inherits(OutgoingFrame, Stream.Writable);

OutgoingFrame.prototype.end = function(chunk, encoding, callback){

    if(typeof chunk === 'function'){
        callback = chunk;
        chunk = null;
        encoding = null;
    } else if(typeof encoding === 'function'){
        callback = encoding;
        encoding = null;
    }

    if(chunk !== undefined && chunk !== null){
        this.write(chunk, encoding);
    }

    var self = this;

    // Write trailing NULL byte to end this frame
    this.write("\x00", "utf-8", function(error){

        if(callback){
            callback(error);
        }

        // Ready to send the next frame
        dequeue(self._stream);

        Stream.Writable.prototype.end.apply(self);
    });
};

OutgoingFrame.prototype._writeHeaderAndBody = function(chunk, encoding, callback){
    this._writeHeader();
    this._writeBody(chunk, encoding, callback);
};

OutgoingFrame.prototype._writeHeader = function(callback){

    var header = encode(this.command) + "\n";

    for(var key in this.headers){

        var value = this.headers[key];

        if(value !== null && value !== undefined){
            header += encode(key) + ":" + encode(value) + "\n";
        }
    }

    header += "\n";

    this._pushWriteRequest(header, "utf-8", callback);

    this._write = this._writeBody;
};

OutgoingFrame.prototype._writeBody = function(chunk, encoding, callback){

    this._pushWriteRequest(chunk, encoding, callback);

    service(this._stream);
};

OutgoingFrame.prototype._pushWriteRequest = function(chunk, encoding, callback){

    this._writes.push({
        chunk: chunk,
        encoding: encoding,
        callback: callback || function(){}
    });
};

function service(stream){

    if(stream._writing || stream._frame === null || stream._frame._writes.length < 1){
        return;
    }

    if(stream._destination_finished){
        var error = new Error("cannot send frame on closed stream");
        stream._frame.emit("error", error);
        dequeue(stream, error);
        return;
    }

    var frame = stream._frame;
    var writeOp = frame._writes.shift();

    stream._writing = stream._destination.write(writeOp.chunk, writeOp.encoding, function(error){

        stream._writing = false;

        if(error && frame.emit){
            frame.emit("error", error);
        }

        writeOp.callback(error);

        // The write callback may have called dequeue

        if(!error){
            // Attempt next write operation
            service(stream);
        }
        else{
            // Send errors to subsequent write callbacks and let the next
            // frame attempt to write and cause an error
            dequeue(stream, error);
        }

    }) === false;
}

function dequeue(stream, errorForUnwritten){

    if(errorForUnwritten && stream._frame && stream._frame._writes.length > 0){

        var writes = stream._frame._writes;
        var countWrites = writes.length;

        stream._frame._writes = [];

        process.nextTick(function(){
            for(var i = 0; i < countWrites; i++){
                writes[i].callback(errorForUnwritten);
            }
        });
    }

    if(stream._queue.length > 0){
        stream._frame = stream._queue.shift();
    }
    else{
        stream._frame = null;
    }

    service(stream);
}

function encode(value){

    value = "" + value;

    value = value.replace("\\", "\\\\");
    value = value.replace("\n", "\\n");
    value = value.replace(":", "\\c");

    return value;
}
<commit_msg>Only emit error event once per frame<commit_after>
module.exports = FrameOutputStream;

var util = require("util");
var Stream = require("stream");

function FrameOutputStream(destination){

    this._destination = destination;
    this._destination_finished = false;

    this._writing = false;

    var finish = this.finish.bind(this);

    destination.on("error", finish);
    destination.on("close", finish);
    destination.on("finish", finish);

    this._frame = null;
    this._queue = [];
}

FrameOutputStream.prototype._queueFrame = function(frame){

    if(this._frame === null){
        this._frame = frame;
    }
    else{
        this._queue.push(frame);
    }

    return frame;
};

FrameOutputStream.prototype.frame = function(command, headers, streamOptions){
    return this._queueFrame(new OutgoingFrame(this, command, headers, streamOptions));
};

FrameOutputStream.prototype.heartbeat = function(callback){

    if(this._frame){
        return;
    }

    this._destination.write("\n", "utf8", callback);
};

FrameOutputStream.prototype.finish = function(){
    this._destination_finished = true;
};

FrameOutputStream.prototype.hasFinished = function(){
    return this._destination_finished;
};

function OutgoingFrame(stream, command, headers, streamOptions){

    Stream.Writable.call(this, streamOptions);

    this._stream = stream;
    this._writes = [];
    this._write = this._writeHeaderAndBody;

    this.command = command;
    this.headers = headers || {};
}

util.inherits(OutgoingFrame, Stream.Writable);

OutgoingFrame.prototype.end = function(chunk, encoding, callback){

    if(typeof chunk === 'function'){
        callback = chunk;
        chunk = null;
        encoding = null;
    } else if(typeof encoding === 'function'){
        callback = encoding;
        encoding = null;
    }

    if(chunk !== undefined && chunk !== null){
        this.write(chunk, encoding);
    }

    var self = this;

    // Write trailing NULL byte to end this frame
    this.write("\x00", "utf-8", function(error){

        if(callback){
            callback(error);
        }

        // Ready to send the next frame
        dequeue(self._stream);

        Stream.Writable.prototype.end.apply(self);
    });
};

OutgoingFrame.prototype._writeHeaderAndBody = function(chunk, encoding, callback){
    this._writeHeader();
    this._writeBody(chunk, encoding, callback);
};

OutgoingFrame.prototype._writeHeader = function(callback){

    var header = encode(this.command) + "\n";

    for(var key in this.headers){

        var value = this.headers[key];

        if(value !== null && value !== undefined){
            header += encode(key) + ":" + encode(value) + "\n";
        }
    }

    header += "\n";

    this._pushWriteRequest(header, "utf-8", callback);

    this._write = this._writeBody;
};

OutgoingFrame.prototype._writeBody = function(chunk, encoding, callback){

    this._pushWriteRequest(chunk, encoding, callback);

    service(this._stream);
};

OutgoingFrame.prototype._pushWriteRequest = function(chunk, encoding, callback){

    this._writes.push({
        chunk: chunk,
        encoding: encoding,
        callback: callback || function(){}
    });
};

function service(stream){

    if(stream._writing || stream._frame === null || stream._frame._writes.length < 1){
        return;
    }

    if(stream._destination_finished){
        var error = new Error("cannot send frame on closed stream");
        stream._frame.emit("error", error);
        dequeue(stream);
        return;
    }

    var frame = stream._frame;
    var writeOp = frame._writes.shift();

    stream._writing = stream._destination.write(writeOp.chunk, writeOp.encoding, function(error){

        stream._writing = false;

        if(error && frame.emit){
            frame.emit("error", error);
        }

        writeOp.callback(error);

        // The write callback may have called dequeue

        if(!error){
            // Attempt next write operation
            service(stream);
        }
        else{
            // Send errors to subsequent write callbacks and let the next
            // frame attempt to write and cause an error
            dequeue(stream);
        }

    }) === false;
}

function dequeue(stream){

    if(stream._queue.length > 0){
        stream._frame = stream._queue.shift();
    }
    else{
        stream._frame = null;
    }

    service(stream);
}

function encode(value){

    value = "" + value;

    value = value.replace("\\", "\\\\");
    value = value.replace("\n", "\\n");
    value = value.replace(":", "\\c");

    return value;
}
<|endoftext|>


<commit_before>/*!
 * OS.js - JavaScript Cloud/Web Desktop Platform
 *
 * Copyright (c) 2011-2016, Anders Evenrud <andersevenrud@gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * @author  Anders Evenrud <andersevenrud@gmail.com>
 * @licence Simplified BSD License
 */
(function(WindowManager, Window, GUI, Utils, API, VFS) {
  'use strict';

  function createCreateDialog(title, dir, cb) {
    API.createDialog('Input', {
      value: title,
      message: OSjs.Applications.CoreWM._('Create in {0}', dir)
    }, function(ev, button, result) {
      if ( result ) {
        cb(new VFS.File(Utils.pathJoin(dir, result)));
      }
    });
  }

  /////////////////////////////////////////////////////////////////////////////
  // SHORTCUT DIALOG
  /////////////////////////////////////////////////////////////////////////////

  function IconViewShortcutDialog(item, scheme, closeCallback) {
    Window.apply(this, ['IconViewShortcutDialog', {
      title: 'Edit Launcher',
      icon: 'status/appointment-soon.png',
      width: 400,
      height: 220,
      allow_maximize: false,
      allow_resize: false,
      allow_minimize: false
    }]);

    this.scheme = scheme;
    this.values = {
      path: item.path,
      filename: item.filename,
      args: item.args || {}
    };
    this.cb = closeCallback || function() {};
  }

  IconViewShortcutDialog.prototype = Object.create(Window.prototype);
  IconViewShortcutDialog.constructor = Window;

  IconViewShortcutDialog.prototype.init = function(wm, app) {
    var self = this;
    var root = Window.prototype.init.apply(this, arguments);
    this.scheme.render(this, this._name);

    this.scheme.find(this, 'InputShortcutLaunch').set('value', this.values.path);
    this.scheme.find(this, 'InputShortcutLabel').set('value', this.values.filename);
    this.scheme.find(this, 'InputTooltipFormatString').set('value', JSON.stringify(this.values.args || {}));

    this.scheme.find(this, 'ButtonApply').on('click', function() {
      self.applySettings();
      self._close('ok');
    });

    this.scheme.find(this, 'ButtonCancel').on('click', function() {
      self._close();
    });

    return root;
  };

  IconViewShortcutDialog.prototype.applySettings = function() {
    this.values.path = this.scheme.find(this, 'InputShortcutLaunch').get('value');
    this.values.filename = this.scheme.find(this, 'InputShortcutLabel').get('value');
    this.values.args = JSON.parse(this.scheme.find(this, 'InputTooltipFormatString').get('value') || {});
  };

  IconViewShortcutDialog.prototype._close = function(button) {
    this.cb(button, this.values);
    return Window.prototype._close.apply(this, arguments);
  };

  IconViewShortcutDialog.prototype._destroy = function() {
    this.scheme = null;
    return Window.prototype._destroy.apply(this, arguments);
  };

  /////////////////////////////////////////////////////////////////////////////
  // ICON VIEW
  /////////////////////////////////////////////////////////////////////////////

  function DesktopIconView(wm) {
    var self = this;

    this.dialog = null;
    this.$iconview = null;
    this.$element = document.createElement('gui-icon-view');
    this.$element.setAttribute('data-multiple', 'false');
    //this.$element.setAttribute('no-selection', 'true');
    this.$element.id = 'CoreWMDesktopIconView';
    this.shortcutCache = [];

    GUI.Elements['gui-icon-view'].build(this.$element);

    GUI.Helpers.createDroppable(this.$element, {
      onOver: function(ev, el, args) {
        wm.onDropOver(ev, el, args);
      },

      onLeave : function() {
        wm.onDropLeave();
      },

      onDrop : function() {
        wm.onDrop();
      },

      onItemDropped: function(ev, el, item, args) {
        wm.onDropItem(ev, el, item, args);
      },

      onFilesDropped: function(ev, el, files, args) {
        wm.onDropFile(ev, el, files, args);
      }
    });

    this.$iconview = new GUI.ElementDataView(this.$element);
    this.$iconview.on('activate', function(ev) {
      if ( ev && ev.detail ) {
        ev.detail.entries.forEach(function(entry) {
          var item = entry.data;
          var file = new VFS.File(item);
          API.open(file, item.args);
        });
      }
    }).on('contextmenu', function(ev) {
      if ( ev && ev.detail && ev.detail.entries ) {
        self.createContextMenu(ev.detail.entries[0], ev);
      }
    });

    this._refresh();
    this.resize(wm);
  }

  DesktopIconView.prototype.destroy = function() {
    Utils.$remove(this.$element);
    this.$element = null;
    this.$iconview = null;

    if ( this.dialog ) {
      this.dialog.destroy();
    }
    this.dialog = null;

    this.shortcutCache = [];
  };

  DesktopIconView.prototype.blur = function() {
    var cel = new GUI.ElementDataView(this.$element);
    cel.set('value', null);
  };

  DesktopIconView.prototype.getRoot = function() {
    return this.$element;
  };

  DesktopIconView.prototype.resize = function(wm) {
    var el = this.getRoot();
    var s  = wm.getWindowSpace();

    if ( el ) {
      el.style.top    = (s.top) + 'px';
      el.style.left   = (s.left) + 'px';
      el.style.width  = (s.width) + 'px';
      el.style.height = (s.height) + 'px';
    }
  };

  DesktopIconView.prototype._refresh = function(wm) {
    var self = this;
    var pm = OSjs.Core.getPackageManager();

    var desktopPath = OSjs.Core.getWindowManager().getSetting('desktopPath');
    var shortcutPath = Utils.pathJoin(desktopPath, '.shortcuts.json');

    VFS.read(shortcutPath, function(e, r) {
      var entries = [];

      if ( r && r instanceof Array ) {
        self.shortcutCache = r;

        entries = r.map(function(i) {
          var iter = new VFS.File(i);
          var type = 'shortcut';
          var icon;

          if ( iter.type === 'application' ) {
            var appname = Utils.filename(iter.path);
            var meta = pm.getPackage(appname);
            if ( meta ) {
              icon = API.getIcon(meta.icon, '32x32', appname);
            }
            type = 'application';
          }

          return {
            _type: type,
            icon: icon || API.getFileIcon(iter, '32x32'),
            label: iter.filename,
            value: iter,
            args: iter.args || {}
          };
        });
      }

      VFS.scandir(desktopPath, function(error, result) {
        if ( self.$iconview && !error ) {

          entries = entries.concat(result.map(function(iter) {
            return {
              _type: 'vfs',
              icon: API.getFileIcon(iter, '32x32'),
              label: iter.filename,
              value: iter
            };
          }).filter(function(iter) {
            if ( iter.value.path === shortcutPath ) {
              return false;
            }
            return true;
          }));

          entries.sort(function(a, b) {
            return (a.filename > b.filename) ? 1 : ((b.filename > a.filename) ? -1 : 0);
          });

          self.$iconview.clear().add(entries);
        }
      });
    }, {type: 'json'});
  };

  DesktopIconView.prototype._save = function(refresh) {
    var desktopPath = OSjs.Core.getWindowManager().getSetting('desktopPath');
    var path = Utils.pathJoin(desktopPath, '.shortcuts.json');

    VFS.write(path, JSON.stringify(this.shortcutCache, null, 4), function(e, r) {
      if ( refresh ) {
        //self._refresh(); // Caught by VFS message in main.js
      }
    });
  };

  DesktopIconView.prototype.updateShortcut = function(data, values) {
    var found = this.getShortcutByPath(data.path);
    if ( found.item ) {
      var o = this.shortcutCache[found.index];
      Object.keys(values).forEach(function(k) {
        o[k] = values[k];
      });

      this._save(true);
    }
  };

  DesktopIconView.prototype.getShortcutByPath = function(path) {
    var found = null;
    var index = -1;

    this.shortcutCache.forEach(function(i, idx) {
      if ( !found ) {
        if ( i.path === path ) {
          found = i;
          index = idx;
        }
      }
    });

    return {item: found, index: index};
  };

  DesktopIconView.prototype.addShortcut = function(data, wm, save) {
    var found = this.getShortcutByPath(data.path);
    if ( !found.item ) {
      (['icon']).forEach(function(k) {
        if ( data[k] ) {
          delete data[k];
        }
      });

      if ( data.type === 'application' ) {
        data.args = data.args || {};
      }

      this.shortcutCache.push(data);
      this._save(true);
    }
  };

  DesktopIconView.prototype.removeShortcut = function(data, wm) {
    var found = this.getShortcutByPath(data.path);

    if ( !found.item ) {
      this.shortcutCache.splice(found.index, 1);
      this._save(true);
    }
  };

  DesktopIconView.prototype._getContextMenu = function(item) {
    var self = this;
    var mm = OSjs.Core.getMountManager();
    var desktopPath = OSjs.Core.getWindowManager().getSetting('desktopPath');
    var menu = [
      {
        title: API._('LBL_UPLOAD'),
        onClick: function() {
          API.createDialog('FileUpload', {
            dest: desktopPath
          }, function() {
            self._refresh();
          });
        }
      },
      {
        title: API._('LBL_CREATE'),
        menu: [{
          title: API._('LBL_FILE'),
          onClick: function() {
            createCreateDialog('New file', desktopPath, function(f) {
              VFS.write(f, '', function(err) {
                if ( err ) {
                  API.error('CoreWM', API._('ERR_VFSMODULE_MKFILE'), err);
                }
              });
            });
          }
        }, {
          title: API._('LBL_DIRECTORY'),
          onClick: function() {
            createCreateDialog('New directory', desktopPath, function(f) {
              VFS.mkdir(f, function(err) {
                if ( err ) {
                  API.error('CoreWM', API._('ERR_VFSMODULE_MKDIR'), err);
                }
              });
            });
          }
        }]
      }
    ];

    if ( item ) {
      var file = item.data;

      if ( file.type === 'application' ) {
        menu.push({
          title: OSjs.Applications.CoreWM._('Edit shortcut'),
          onClick: function() {
            self.openShortcutEdit(file);
          }
        });
      }

      if ( mm.getRootFromPath(file.path) !== desktopPath  ) {
        menu.push({
          title: OSjs.Applications.CoreWM._('Remove shortcut'),
          onClick: function() {
            self.removeShortcut(file);
          }
        });
      } else {
        menu.push({
          title: API._('LBL_DELETE'),
          onClick: function() {
            VFS.unlink(file, function() {
              //self._refresh(); // Caught by VFS message in main.js
            });
          }
        });
      }
    }

    return menu;
  };

  DesktopIconView.prototype.createContextMenu = function(item, ev) {
    var wm = OSjs.Core.getWindowManager();
    var menu = wm._getContextMenu(item);
    API.createMenu(menu, ev);
  };

  DesktopIconView.prototype.openShortcutEdit = function(item) {
    if ( this.dialog ) {
      this.dialog._close();
    }

    var self = this;
    var wm = OSjs.Core.getWindowManager();

    this.dialog = new IconViewShortcutDialog(item, wm._scheme, function(button, values) {
      if ( button === 'ok' ) {
        self.updateShortcut(item, values);
      }
      self.dialog = null;
    });

    wm.addWindow(this.dialog, true);
  };

  /////////////////////////////////////////////////////////////////////////////
  // EXPORTS
  /////////////////////////////////////////////////////////////////////////////

  OSjs.Applications                          = OSjs.Applications || {};
  OSjs.Applications.CoreWM                   = OSjs.Applications.CoreWM || {};
  OSjs.Applications.CoreWM.DesktopIconView   = DesktopIconView;

})(OSjs.Core.WindowManager, OSjs.Core.Window, OSjs.GUI, OSjs.Utils, OSjs.API, OSjs.VFS);
<commit_msg>CoreWM: Fixed iconview shortcut removal not working<commit_after>/*!
 * OS.js - JavaScript Cloud/Web Desktop Platform
 *
 * Copyright (c) 2011-2016, Anders Evenrud <andersevenrud@gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * @author  Anders Evenrud <andersevenrud@gmail.com>
 * @licence Simplified BSD License
 */
(function(WindowManager, Window, GUI, Utils, API, VFS) {
  'use strict';

  function createCreateDialog(title, dir, cb) {
    API.createDialog('Input', {
      value: title,
      message: OSjs.Applications.CoreWM._('Create in {0}', dir)
    }, function(ev, button, result) {
      if ( result ) {
        cb(new VFS.File(Utils.pathJoin(dir, result)));
      }
    });
  }

  /////////////////////////////////////////////////////////////////////////////
  // SHORTCUT DIALOG
  /////////////////////////////////////////////////////////////////////////////

  function IconViewShortcutDialog(item, scheme, closeCallback) {
    Window.apply(this, ['IconViewShortcutDialog', {
      title: 'Edit Launcher',
      icon: 'status/appointment-soon.png',
      width: 400,
      height: 220,
      allow_maximize: false,
      allow_resize: false,
      allow_minimize: false
    }]);

    this.scheme = scheme;
    this.values = {
      path: item.path,
      filename: item.filename,
      args: item.args || {}
    };
    this.cb = closeCallback || function() {};
  }

  IconViewShortcutDialog.prototype = Object.create(Window.prototype);
  IconViewShortcutDialog.constructor = Window;

  IconViewShortcutDialog.prototype.init = function(wm, app) {
    var self = this;
    var root = Window.prototype.init.apply(this, arguments);
    this.scheme.render(this, this._name);

    this.scheme.find(this, 'InputShortcutLaunch').set('value', this.values.path);
    this.scheme.find(this, 'InputShortcutLabel').set('value', this.values.filename);
    this.scheme.find(this, 'InputTooltipFormatString').set('value', JSON.stringify(this.values.args || {}));

    this.scheme.find(this, 'ButtonApply').on('click', function() {
      self.applySettings();
      self._close('ok');
    });

    this.scheme.find(this, 'ButtonCancel').on('click', function() {
      self._close();
    });

    return root;
  };

  IconViewShortcutDialog.prototype.applySettings = function() {
    this.values.path = this.scheme.find(this, 'InputShortcutLaunch').get('value');
    this.values.filename = this.scheme.find(this, 'InputShortcutLabel').get('value');
    this.values.args = JSON.parse(this.scheme.find(this, 'InputTooltipFormatString').get('value') || {});
  };

  IconViewShortcutDialog.prototype._close = function(button) {
    this.cb(button, this.values);
    return Window.prototype._close.apply(this, arguments);
  };

  IconViewShortcutDialog.prototype._destroy = function() {
    this.scheme = null;
    return Window.prototype._destroy.apply(this, arguments);
  };

  /////////////////////////////////////////////////////////////////////////////
  // ICON VIEW
  /////////////////////////////////////////////////////////////////////////////

  function DesktopIconView(wm) {
    var self = this;

    this.dialog = null;
    this.$iconview = null;
    this.$element = document.createElement('gui-icon-view');
    this.$element.setAttribute('data-multiple', 'false');
    //this.$element.setAttribute('no-selection', 'true');
    this.$element.id = 'CoreWMDesktopIconView';
    this.shortcutCache = [];

    GUI.Elements['gui-icon-view'].build(this.$element);

    GUI.Helpers.createDroppable(this.$element, {
      onOver: function(ev, el, args) {
        wm.onDropOver(ev, el, args);
      },

      onLeave : function() {
        wm.onDropLeave();
      },

      onDrop : function() {
        wm.onDrop();
      },

      onItemDropped: function(ev, el, item, args) {
        wm.onDropItem(ev, el, item, args);
      },

      onFilesDropped: function(ev, el, files, args) {
        wm.onDropFile(ev, el, files, args);
      }
    });

    this.$iconview = new GUI.ElementDataView(this.$element);
    this.$iconview.on('activate', function(ev) {
      if ( ev && ev.detail ) {
        ev.detail.entries.forEach(function(entry) {
          var item = entry.data;
          var file = new VFS.File(item);
          API.open(file, item.args);
        });
      }
    }).on('contextmenu', function(ev) {
      if ( ev && ev.detail && ev.detail.entries ) {
        self.createContextMenu(ev.detail.entries[0], ev);
      }
    });

    this._refresh();
    this.resize(wm);
  }

  DesktopIconView.prototype.destroy = function() {
    Utils.$remove(this.$element);
    this.$element = null;
    this.$iconview = null;

    if ( this.dialog ) {
      this.dialog.destroy();
    }
    this.dialog = null;

    this.shortcutCache = [];
  };

  DesktopIconView.prototype.blur = function() {
    var cel = new GUI.ElementDataView(this.$element);
    cel.set('value', null);
  };

  DesktopIconView.prototype.getRoot = function() {
    return this.$element;
  };

  DesktopIconView.prototype.resize = function(wm) {
    var el = this.getRoot();
    var s  = wm.getWindowSpace();

    if ( el ) {
      el.style.top    = (s.top) + 'px';
      el.style.left   = (s.left) + 'px';
      el.style.width  = (s.width) + 'px';
      el.style.height = (s.height) + 'px';
    }
  };

  DesktopIconView.prototype._refresh = function(wm) {
    var self = this;
    var pm = OSjs.Core.getPackageManager();

    var desktopPath = OSjs.Core.getWindowManager().getSetting('desktopPath');
    var shortcutPath = Utils.pathJoin(desktopPath, '.shortcuts.json');

    VFS.read(shortcutPath, function(e, r) {
      var entries = [];

      if ( r && r instanceof Array ) {
        self.shortcutCache = r;

        entries = r.map(function(i) {
          var iter = new VFS.File(i);
          var type = 'shortcut';
          var icon;

          if ( iter.type === 'application' ) {
            var appname = Utils.filename(iter.path);
            var meta = pm.getPackage(appname);
            if ( meta ) {
              icon = API.getIcon(meta.icon, '32x32', appname);
            }
            type = 'application';
          }

          return {
            _type: type,
            icon: icon || API.getFileIcon(iter, '32x32'),
            label: iter.filename,
            value: iter,
            args: iter.args || {}
          };
        });
      }

      VFS.scandir(desktopPath, function(error, result) {
        if ( self.$iconview && !error ) {

          entries = entries.concat(result.map(function(iter) {
            return {
              _type: 'vfs',
              icon: API.getFileIcon(iter, '32x32'),
              label: iter.filename,
              value: iter
            };
          }).filter(function(iter) {
            if ( iter.value.path === shortcutPath ) {
              return false;
            }
            return true;
          }));

          entries.sort(function(a, b) {
            return (a.filename > b.filename) ? 1 : ((b.filename > a.filename) ? -1 : 0);
          });

          self.$iconview.clear().add(entries);
        }
      });
    }, {type: 'json'});
  };

  DesktopIconView.prototype._save = function(refresh) {
    var desktopPath = OSjs.Core.getWindowManager().getSetting('desktopPath');
    var path = Utils.pathJoin(desktopPath, '.shortcuts.json');

    VFS.write(path, JSON.stringify(this.shortcutCache, null, 4), function(e, r) {
      if ( refresh ) { // Normally caught by VFS message in main.js
        //self._refresh();
      }
    });
  };

  DesktopIconView.prototype.updateShortcut = function(data, values) {
    var found = this.getShortcutByPath(data.path);
    if ( found.item ) {
      var o = this.shortcutCache[found.index];
      Object.keys(values).forEach(function(k) {
        o[k] = values[k];
      });

      this._save(true);
    }
  };

  DesktopIconView.prototype.getShortcutByPath = function(path) {
    var found = null;
    var index = -1;

    this.shortcutCache.forEach(function(i, idx) {
      if ( !found ) {
        if ( i.path === path ) {
          found = i;
          index = idx;
        }
      }
    });

    return {item: found, index: index};
  };

  DesktopIconView.prototype.addShortcut = function(data, wm, save) {
    var found = this.getShortcutByPath(data.path);
    if ( !found.item ) {
      (['icon']).forEach(function(k) {
        if ( data[k] ) {
          delete data[k];
        }
      });

      if ( data.type === 'application' ) {
        data.args = data.args || {};
      }

      this.shortcutCache.push(data);
      this._save(true);
    }
  };

  DesktopIconView.prototype.removeShortcut = function(data, wm) {
    var found = this.getShortcutByPath(data.path);

    if ( found.item ) {
      this.shortcutCache.splice(found.index, 1);
      this._save(true);
    }
  };

  DesktopIconView.prototype._getContextMenu = function(item) {
    var self = this;
    var mm = OSjs.Core.getMountManager();
    var desktopPath = OSjs.Core.getWindowManager().getSetting('desktopPath');
    var menu = [
      {
        title: API._('LBL_UPLOAD'),
        onClick: function() {
          API.createDialog('FileUpload', {
            dest: desktopPath
          }, function() {
            self._refresh();
          });
        }
      },
      {
        title: API._('LBL_CREATE'),
        menu: [{
          title: API._('LBL_FILE'),
          onClick: function() {
            createCreateDialog('New file', desktopPath, function(f) {
              VFS.write(f, '', function(err) {
                if ( err ) {
                  API.error('CoreWM', API._('ERR_VFSMODULE_MKFILE'), err);
                }
              });
            });
          }
        }, {
          title: API._('LBL_DIRECTORY'),
          onClick: function() {
            createCreateDialog('New directory', desktopPath, function(f) {
              VFS.mkdir(f, function(err) {
                if ( err ) {
                  API.error('CoreWM', API._('ERR_VFSMODULE_MKDIR'), err);
                }
              });
            });
          }
        }]
      }
    ];

    if ( item ) {
      var file = item.data;

      if ( file.type === 'application' ) {
        menu.push({
          title: OSjs.Applications.CoreWM._('Edit shortcut'),
          onClick: function() {
            self.openShortcutEdit(file);
          }
        });
      }

      if ( mm.getRootFromPath(file.path) !== desktopPath  ) {
        menu.push({
          title: OSjs.Applications.CoreWM._('Remove shortcut'),
          onClick: function() {
            self.removeShortcut(file);
          }
        });
      } else {
        menu.push({
          title: API._('LBL_DELETE'),
          onClick: function() {
            VFS.unlink(file, function() {
              //self._refresh(); // Caught by VFS message in main.js
            });
          }
        });
      }
    }

    return menu;
  };

  DesktopIconView.prototype.createContextMenu = function(item, ev) {
    var wm = OSjs.Core.getWindowManager();
    var menu = wm._getContextMenu(item);
    API.createMenu(menu, ev);
  };

  DesktopIconView.prototype.openShortcutEdit = function(item) {
    if ( this.dialog ) {
      this.dialog._close();
    }

    var self = this;
    var wm = OSjs.Core.getWindowManager();

    this.dialog = new IconViewShortcutDialog(item, wm._scheme, function(button, values) {
      if ( button === 'ok' ) {
        self.updateShortcut(item, values);
      }
      self.dialog = null;
    });

    wm.addWindow(this.dialog, true);
  };

  /////////////////////////////////////////////////////////////////////////////
  // EXPORTS
  /////////////////////////////////////////////////////////////////////////////

  OSjs.Applications                          = OSjs.Applications || {};
  OSjs.Applications.CoreWM                   = OSjs.Applications.CoreWM || {};
  OSjs.Applications.CoreWM.DesktopIconView   = DesktopIconView;

})(OSjs.Core.WindowManager, OSjs.Core.Window, OSjs.GUI, OSjs.Utils, OSjs.API, OSjs.VFS);
<|endoftext|>