<commit_before>/*
 * Copyright (C) 2012 B3Partners B.V.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
/**
 * Drawing component
 * Creates a Drawing component
 * @author Meine Toonen
 */
Ext.define ("viewer.components.Drawing",{
    extend: "viewer.components.Component",
    iconPath: null,
    // Forms
    formdraw : null,
    formselect : null,
    formsave : null,
    formopen : null,
    vectorLayer:null,
    // Items in forms. Convience accessor
    colorPicker:null,
    label:null,
    title:null,
    comment:null,
    file:null,
    // Current active feature
    activeFeature:null,
    features:null,
    config:{
        title: "",
        iconUrl: "",
        tooltip: "",
        color: ""
    },
    constructor: function (conf){
        viewer.components.Drawing.superclass.constructor.call(this, conf);
        this.initConfig(conf);
        this.features = new Object();
        var me = this;
        this.renderButton({
            handler: function(){
                me.popup.show();
            },
            text: me.title,
            icon: me.iconUrl,
            tooltip: me.tooltip
        });

        // Needed to untoggle the buttons when drawing is finished
        this.drawingButtonIds = {
            'point': Ext.id(),
            'line': Ext.id(),
            'polygon': Ext.id(),
            'circle': Ext.id()
        };

        this.vectorLayer=this.viewerController.mapComponent.createVectorLayer({
            name:'drawingVectorLayer',
            geometrytypes:["Circle","Polygon","Point","LineString"],
            showmeasures:false,
            viewerController: this.viewerController,
            style: {
                fillcolor: "0x"+this.color,
                fillopacity: 50,
                strokecolor: "0xFF0000",
                strokeopacity: 50
            }
        });
        this.viewerController.mapComponent.getMap().addLayer(this.vectorLayer);

        this.vectorLayer.addListener (viewer.viewercontroller.controller.Event.ON_ACTIVE_FEATURE_CHANGED,this.activeFeatureChanged,this);
        this.vectorLayer.addListener (viewer.viewercontroller.controller.Event.ON_FEATURE_ADDED,this.activeFeatureFinished,this);
        this.viewerController.addListener(viewer.viewercontroller.controller.Event.ON_SELECTEDCONTENT_CHANGE,this.selectedContentChanged,this );
        this.iconPath=contextPath+"/viewer-html/components/resources/images/drawing/";
        this.loadWindow();
        return this;
    },
    selectedContentChanged : function (){
        this.viewerController.mapComponent.getMap().addLayer(this.vectorLayer);
    },
    /**
     * Create the GUI
     */
    loadWindow : function(){
        var me=this;

        this.colorPicker = Ext.create("Ext.ux.ColorField",{
            width: 40,
            showText: false,
            style: {
                marginTop: MobileManager.isMobile() ? '8px' : '0px',
                marginLeft: MobileManager.isMobile() ? '8px' : '0px',
                marginRight: MobileManager.isMobile() ? '8px' : '0px'
            },
            name: 'color',
            id:'color',
            value: this.color ? this.color : 'FF0000',
            listeners :{
                select : {
                    fn: this.colorChanged,
                    scope : this
                }
            }
        });

        this.label = Ext.create("Ext.form.field.Text",{
            name: 'labelObject',
            fieldLabel: 'Label geselecteerd object',
            labelWidth: MobileManager.isMobile() ? 200 : 150,
            style: {
                marginRight: MobileManager.isMobile() ? '15px' : '5px',
                marginTop: MobileManager.isMobile() ? '8px': '0px'
            },
            id: 'labelObject' + this.name,
            listeners:{
                change:{
                    fn: this.labelChanged,
                    scope:this
                }
            }
        });
        this.formdraw = new Ext.form.FormPanel({
            border: 0,
            style: {
                marginBottom: '10px'
            },
            items: [{
                xtype: 'fieldset',
                defaultType: 'textfield',
                padding: 0,
                style: {
                    border: '0px none'
                },
                items: [
                {
                    xtype: 'label',
                    text: 'Objecten op de kaart tekenen'
                },
                {
                    xtype: 'fieldset',
                    border: 0,
                    margin: 0,
                    padding: 0,
                    style: {
                        border: 0
                    },
                    layout:{
                        type:'hbox',
                        defaultMargins:{
                            top: 5,
                            right: 5,
                            bottom: 5,
                            left: 0
                        }
                    },
                    items: [{
                        xtype: 'button',
                        id: this.drawingButtonIds.point,
                        icon: this.iconPath+"bullet_red.png",
                        padding: MobileManager.isMobile() ? '10px' : '2px',
                        tooltip: "Teken een punt",
                        enableToggle: true,
                        toggleGroup: 'drawingTools',
                        listeners: {
                            click:{
                                scope: me,
                                fn: me.drawPoint
                            }
                        }
                    },
                    {
                        xtype: 'button',
                        id: this.drawingButtonIds.line,
                        icon: this.iconPath+"line_red.png",
                        padding: MobileManager.isMobile() ? '10px' : '2px',
                        tooltip: "Teken een lijn",
                        enableToggle: true,
                        toggleGroup: 'drawingTools',
                        listeners: {
                            click:{
                                scope: me,
                                fn: me.drawLine
                            }
                        }
                    },
                    {
                        xtype: 'button',
                        id: this.drawingButtonIds.polygon,
                        icon: this.iconPath+"shape_square_red.png",
                        padding: MobileManager.isMobile() ? '10px' : '2px',
                        tooltip: "Teken een polygoon",
                        enableToggle: true,
                        toggleGroup: 'drawingTools',
                        listeners: {
                            click:{
                                scope: me,
                                fn: me.drawPolygon
                            }
                        }
                    },
                    {
                        xtype: 'button',
                        id: this.drawingButtonIds.circle,
                        icon: this.iconPath+"shape_circle_red.png",
                        padding: MobileManager.isMobile() ? '10px' : '2px',
                        tooltip: "Teken een cirkel",
                        enableToggle: true,
                        toggleGroup: 'drawingTools',
                        listeners: {
                            click:{
                                scope: me,
                                fn: me.drawCircle
                            }
                        }
                    },
                    this.colorPicker,
                    {
                        xtype: 'button',
                        icon: this.iconPath+"delete.png",
                        tooltip: "Verwijder alle objecten",
                        padding: MobileManager.isMobile() ? '10px' : '2px',
                        listeners: {
                            click:{
                                scope: me,
                                fn: me.deleteAll
                            }
                        }
                    }]
                }
                ]
            }]
        });

        this.formselect = new Ext.form.FormPanel({
            border: 0,
            style: {
                marginBottom: '25px'
            },
            items: [
            {
                xtype: 'fieldset',
                defaultType: 'textfield',
                border: 0,
                style: {
                    border: '0px none',
                    marginBottom: '0px',
                    padding: '0px'
                },
                layout:'hbox',
                items: [
                this.label,
                {
                    xtype: 'button',
                    icon: this.iconPath+"delete.png",
                    tooltip: "Verwijder geselecteerd object",
                    padding: MobileManager.isMobile() ? '10px' : '2px',
                    listeners: {
                        click:{
                            scope: me,
                            fn: me.deleteObject
                        }
                    }
                }
                ]
            }
            ]
        });

        // Convience accessor
        this.title = Ext.create("Ext.form.field.Text",{
            fieldLabel: 'Titel',
            name: 'title',
            allowBlank:false,
            id: 'title'+ this.name
        });
        this.description = Ext.create("Ext.form.field.TextArea",
        {
            fieldLabel: 'Opmerking',
            allowBlank:false,
            name: 'description',
            id: 'description'
        });
        // Build the saving form
        this.formsave = new Ext.form.FormPanel({
            border: 0,
            standardSubmit: true,
            url: actionBeans["drawing"] + "?save",
            style: {
                marginBottom: '25px'
            },
            items: [
                {
                    xtype: 'label',
                    text: 'Op de kaart getekende objecten opslaan'
                },
                this.title,
                this.description,
                {
                    xtype: 'hiddenfield',
                    name: 'saveObject',
                    id: 'saveObject'
                },
                {
                    xtype: 'button',
                    text: 'Opslaan als bestand',
                    listeners: {
                        click:{
                            scope: me,
                            fn: me.saveFile
                        }
                    }
                }
            ]
        });

        this.file = Ext.create("Ext.form.field.File", {
            fieldLabel: 'Tekstbestand',
            name: 'featureFile',
            allowBlank:false,
            msgTarget: 'side',
            anchor: '100%',
            buttonText: 'Bladeren',
            id: 'featureFile'
        });
        this.formopen = new Ext.form.FormPanel({
            border: 0,
            style: {
                marginBottom: '25px'
            },
            items: [
            {
                xtype: 'label',
                text: 'Bestand met getekende objecten openen'
            },
            this.file,
            {
                xtype: 'button',
                text: 'bestand openen',
                listeners: {
                    click:{
                        scope: me,
                        fn: me.openFile
                    }
                }
            }
            ]
        });

        var items = [ this.formdraw, this.formselect ];
        if(!MobileManager.isMobile()) {
            items.push(this.formsave); items.push(this.formopen);
        }
        this.mainContainer = Ext.create('Ext.container.Container', {
            id: this.name + 'Container',
            width: '100%',
            height: '100%',
            layout: {
                type: 'vbox',
                align: 'stretch'
            },
            style: {
                backgroundColor: 'White'
            },
            renderTo: this.getContentDiv(),
            items: [
                {
                    id: this.name + 'ContentPanel',
                    xtype: "container",
                    autoScroll: true,
                    width: '100%',
                    style: {
                        marginLeft: '10px',
                        marginRight: '10px'
                    },
                    flex: 1,
                    items: items
                }, {
                    id: this.name + 'ClosingPanel',
                    xtype: "container",
                    width: '100%',
                    height: MobileManager.isMobile() ? 45 : 25,
                    style: {
                        marginTop: '10px',
                        marginRight: '5px'
                    },
                    layout: {
                        type:'hbox',
                        pack:'end'
                    },
                    items: [
                        {xtype: 'button', text: 'Sluiten', padding: MobileManager.isMobile() ? '10px' : '2px', handler: function() {
                            me.popup.hide();
                        }}
                    ]
                }
            ]
        });

        this.formselect.setVisible(false);
    },

    /**
     * Event handlers
     **/
    activeFeatureChanged : function (vectorLayer,feature){
        this.toggleSelectForm(true);
        if(this.features[feature.id] == undefined){
            feature.color = "0x"+this.color;
            this.features[feature.id] = feature;
        }else{
            var color = this.features[feature.id].color;
            color = color.substring(2);
            this.colorPicker.setColor(color);
            this.color = color;
        }
        this.activeFeature = this.features[feature.id];
        this.label.setValue(this.activeFeature.label);
    },
    //update the wkt of the active feature with the completed feature
    activeFeatureFinished : function (vectorLayer,feature){
        this.activeFeature.wktgeom = feature.wktgeom;
        Ext.Object.each(this.drawingButtonIds, function(key, id) {
            var button = Ext.getCmp(id);
            if(button) button.toggle(false);
        });
    },
    colorChanged : function (hexColor){
        this.color = '0x'+hexColor;
        this.vectorLayer.style.fillcolor = this.color;
        this.vectorLayer.adjustStyle();
        if(this.activeFeature != null){
            this.activeFeature.color = this.color;
            var feature = this.vectorLayer.getFeatureById(this.activeFeature.getId());
            this.activeFeature.wktgeom = feature.wktgeom;
            this.vectorLayer.removeFeature(this.activeFeature);
            this.vectorLayer.addFeature(this.activeFeature);
        }
    },
    labelChanged : function (field,newValue){
        if(this.activeFeature != null){
            this.vectorLayer.setLabel(this.activeFeature.getId(),newValue);
            this.activeFeature.label=newValue;
        }
    },
    toggleSelectForm : function(visible){
        this.formselect.setVisible(visible);
    },
    drawPoint: function(){
        this.vectorLayer.drawFeature("Point");
    },
    drawLine: function(){
        this.vectorLayer.drawFeature("LineString");
    },
    drawPolygon: function(){
        this.vectorLayer.drawFeature("Polygon");
    },
    drawCircle: function(){
        this.vectorLayer.drawFeature("Circle");
    },
    deleteAll: function(){
        this.vectorLayer.removeAllFeatures();
        this.toggleSelectForm(false);
        this.features = {};
        this.label.setValue("");
        this.title.setValue("");
        this.description.setValue("");
        if(this.activeFeature != null){
            this.activeFeature=null;
        }
    },
    deleteObject: function(){
        delete this.features[this.activeFeature.id];
        this.vectorLayer.removeFeature(this.activeFeature);
        this.toggleSelectForm(false);
        if(this.activeFeature != null){
            this.activeFeature=null;
        }
        this.label.setValue("");
    },
    saveFile: function(){
        var form = this.formsave.getForm();

        var features = new Array();
        for (var featurekey in this.features){
            var feature = this.features[featurekey];
            features.push(feature.toJsonObject());
        }
        form.setValues({
            "saveObject":Ext.JSON.encode(features)
        });
        this.formsave.submit({
            target: '_blank'
        } );
        return features;
    },
    openFile: function(){
        var form =this.formopen.getForm();
        if(form.isValid()){
            form.submit({
                scope:this,
                url: actionBeans["drawing"],
                waitMsg: 'Bezig met uploaden...',
                waitTitle: "Even wachten...",
                success: function(fp, o) {
                    var json = Ext.JSON.decode(o.result.content);
                    this.title.setValue( json.title);
                    this.description.setValue(json.description);
                    var features = Ext.JSON.decode(json.features);
                    for ( var i = 0 ; i < features.length;i++){
                        var feature = features[i];
                        var featureObject = Ext.create("viewer.viewercontroller.controller.Feature",feature);
                        this.vectorLayer.style.fillcolor = featureObject.color;
                        this.vectorLayer.adjustStyle();
                        this.vectorLayer.addFeature(featureObject);
                    }
                    if(features.length > 0){
                        var extent = o.result.extent;
                        this.viewerController.mapComponent.getMap().zoomToExtent(extent);
                    }

                },
                failure: function (){
                    Ext.Msg.alert('Mislukt', 'Uw bestand kon niet gelezen worden.');
                }
            });
        }
    },
    getExtComponents: function() {
        var compIds = [
            this.mainContainer.getId(),
            this.colorPicker.getId(),
            this.label.getId(),
            this.formdraw.getId(),
            this.formselect.getId(),
            this.title.getId(),
            this.description.getId()
        ];
        if(!MobileManager.isMobile()) {
            compIds.push(this.formsave.getId());
            compIds.push(this.file.getId());
            compIds.push(this.formopen.getId());
        }
        return compIds;
    }
});
 <commit_msg>Removed circle drawing for mobile devices (impossible to draw because circle needs mouse move event)<commit_after>/*
 * Copyright (C) 2012 B3Partners B.V.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
/**
 * Drawing component
 * Creates a Drawing component
 * @author Meine Toonen
 */
Ext.define ("viewer.components.Drawing",{
    extend: "viewer.components.Component",
    iconPath: null,
    // Forms
    formdraw : null,
    formselect : null,
    formsave : null,
    formopen : null,
    vectorLayer:null,
    // Items in forms. Convience accessor
    colorPicker:null,
    label:null,
    title:null,
    comment:null,
    file:null,
    // Current active feature
    activeFeature:null,
    features:null,
    config:{
        title: "",
        iconUrl: "",
        tooltip: "",
        color: ""
    },
    constructor: function (conf){
        viewer.components.Drawing.superclass.constructor.call(this, conf);
        this.initConfig(conf);
        this.features = new Object();
        var me = this;
        this.renderButton({
            handler: function(){
                me.popup.show();
            },
            text: me.title,
            icon: me.iconUrl,
            tooltip: me.tooltip
        });

        // Needed to untoggle the buttons when drawing is finished
        this.drawingButtonIds = {
            'point': Ext.id(),
            'line': Ext.id(),
            'polygon': Ext.id(),
            'circle': Ext.id()
        };

        this.vectorLayer=this.viewerController.mapComponent.createVectorLayer({
            name:'drawingVectorLayer',
            geometrytypes:["Circle","Polygon","Point","LineString"],
            showmeasures:false,
            viewerController: this.viewerController,
            style: {
                fillcolor: "0x"+this.color,
                fillopacity: 50,
                strokecolor: "0xFF0000",
                strokeopacity: 50
            }
        });
        this.viewerController.mapComponent.getMap().addLayer(this.vectorLayer);

        this.vectorLayer.addListener (viewer.viewercontroller.controller.Event.ON_ACTIVE_FEATURE_CHANGED,this.activeFeatureChanged,this);
        this.vectorLayer.addListener (viewer.viewercontroller.controller.Event.ON_FEATURE_ADDED,this.activeFeatureFinished,this);
        this.viewerController.addListener(viewer.viewercontroller.controller.Event.ON_SELECTEDCONTENT_CHANGE,this.selectedContentChanged,this );
        this.iconPath=contextPath+"/viewer-html/components/resources/images/drawing/";
        this.loadWindow();
        return this;
    },
    selectedContentChanged : function (){
        this.viewerController.mapComponent.getMap().addLayer(this.vectorLayer);
    },
    /**
     * Create the GUI
     */
    loadWindow : function(){
        var me=this;

        this.colorPicker = Ext.create("Ext.ux.ColorField",{
            width: 40,
            showText: false,
            style: {
                marginTop: MobileManager.isMobile() ? '8px' : '0px',
                marginLeft: MobileManager.isMobile() ? '8px' : '0px',
                marginRight: MobileManager.isMobile() ? '8px' : '0px'
            },
            name: 'color',
            id:'color',
            value: this.color ? this.color : 'FF0000',
            listeners :{
                select : {
                    fn: this.colorChanged,
                    scope : this
                }
            }
        });

        this.label = Ext.create("Ext.form.field.Text",{
            name: 'labelObject',
            fieldLabel: 'Label geselecteerd object',
            labelWidth: MobileManager.isMobile() ? 200 : 150,
            style: {
                marginRight: MobileManager.isMobile() ? '15px' : '5px',
                marginTop: MobileManager.isMobile() ? '8px': '0px'
            },
            id: 'labelObject' + this.name,
            listeners:{
                change:{
                    fn: this.labelChanged,
                    scope:this
                }
            }
        });
        var drawingItems = [{
            xtype: 'button',
            id: this.drawingButtonIds.point,
            icon: this.iconPath+"bullet_red.png",
            padding: MobileManager.isMobile() ? '10px' : '2px',
            tooltip: "Teken een punt",
            enableToggle: true,
            toggleGroup: 'drawingTools',
            listeners: {
                click:{
                    scope: me,
                    fn: me.drawPoint
                }
            }
        },
        {
            xtype: 'button',
            id: this.drawingButtonIds.line,
            icon: this.iconPath+"line_red.png",
            padding: MobileManager.isMobile() ? '10px' : '2px',
            tooltip: "Teken een lijn",
            enableToggle: true,
            toggleGroup: 'drawingTools',
            listeners: {
                click:{
                    scope: me,
                    fn: me.drawLine
                }
            }
        },
        {
            xtype: 'button',
            id: this.drawingButtonIds.polygon,
            icon: this.iconPath+"shape_square_red.png",
            padding: MobileManager.isMobile() ? '10px' : '2px',
            tooltip: "Teken een polygoon",
            enableToggle: true,
            toggleGroup: 'drawingTools',
            listeners: {
                click:{
                    scope: me,
                    fn: me.drawPolygon
                }
            }
        }];
        if(!MobileManager.isMobile()) {
            drawingItems.push({
                xtype: 'button',
                id: this.drawingButtonIds.circle,
                icon: this.iconPath+"shape_circle_red.png",
                padding: MobileManager.isMobile() ? '10px' : '2px',
                tooltip: "Teken een cirkel",
                enableToggle: true,
                toggleGroup: 'drawingTools',
                listeners: {
                    click:{
                        scope: me,
                        fn: me.drawCircle
                    }
                }
            });
        }
        drawingItems.push(this.colorPicker);
        drawingItems.push({
            xtype: 'button',
            icon: this.iconPath+"delete.png",
            tooltip: "Verwijder alle objecten",
            padding: MobileManager.isMobile() ? '10px' : '2px',
            listeners: {
                click:{
                    scope: me,
                    fn: me.deleteAll
                }
            }
        });

        this.formdraw = new Ext.form.FormPanel({
            border: 0,
            style: {
                marginBottom: '10px'
            },
            items: [{
                xtype: 'fieldset',
                defaultType: 'textfield',
                padding: 0,
                style: {
                    border: '0px none'
                },
                items: [
                {
                    xtype: 'label',
                    text: 'Objecten op de kaart tekenen'
                },
                {
                    xtype: 'fieldset',
                    border: 0,
                    margin: 0,
                    padding: 0,
                    style: {
                        border: 0
                    },
                    layout:{
                        type:'hbox',
                        defaultMargins:{
                            top: 5,
                            right: 5,
                            bottom: 5,
                            left: 0
                        }
                    },
                    items: drawingItems
                }
                ]
            }]
        });

        this.formselect = new Ext.form.FormPanel({
            border: 0,
            style: {
                marginBottom: '25px'
            },
            items: [
            {
                xtype: 'fieldset',
                defaultType: 'textfield',
                border: 0,
                style: {
                    border: '0px none',
                    marginBottom: '0px',
                    padding: '0px'
                },
                layout:'hbox',
                items: [
                this.label,
                {
                    xtype: 'button',
                    icon: this.iconPath+"delete.png",
                    tooltip: "Verwijder geselecteerd object",
                    padding: MobileManager.isMobile() ? '10px' : '2px',
                    listeners: {
                        click:{
                            scope: me,
                            fn: me.deleteObject
                        }
                    }
                }
                ]
            }
            ]
        });

        // Convience accessor
        this.title = Ext.create("Ext.form.field.Text",{
            fieldLabel: 'Titel',
            name: 'title',
            allowBlank:false,
            id: 'title'+ this.name
        });
        this.description = Ext.create("Ext.form.field.TextArea",
        {
            fieldLabel: 'Opmerking',
            allowBlank:false,
            name: 'description',
            id: 'description'
        });
        // Build the saving form
        this.formsave = new Ext.form.FormPanel({
            border: 0,
            standardSubmit: true,
            url: actionBeans["drawing"] + "?save",
            style: {
                marginBottom: '25px'
            },
            items: [
                {
                    xtype: 'label',
                    text: 'Op de kaart getekende objecten opslaan'
                },
                this.title,
                this.description,
                {
                    xtype: 'hiddenfield',
                    name: 'saveObject',
                    id: 'saveObject'
                },
                {
                    xtype: 'button',
                    text: 'Opslaan als bestand',
                    listeners: {
                        click:{
                            scope: me,
                            fn: me.saveFile
                        }
                    }
                }
            ]
        });

        this.file = Ext.create("Ext.form.field.File", {
            fieldLabel: 'Tekstbestand',
            name: 'featureFile',
            allowBlank:false,
            msgTarget: 'side',
            anchor: '100%',
            buttonText: 'Bladeren',
            id: 'featureFile'
        });
        this.formopen = new Ext.form.FormPanel({
            border: 0,
            style: {
                marginBottom: '25px'
            },
            items: [
            {
                xtype: 'label',
                text: 'Bestand met getekende objecten openen'
            },
            this.file,
            {
                xtype: 'button',
                text: 'bestand openen',
                listeners: {
                    click:{
                        scope: me,
                        fn: me.openFile
                    }
                }
            }
            ]
        });

        var items = [ this.formdraw, this.formselect ];
        if(!MobileManager.isMobile()) {
            items.push(this.formsave); items.push(this.formopen);
        }
        this.mainContainer = Ext.create('Ext.container.Container', {
            id: this.name + 'Container',
            width: '100%',
            height: '100%',
            layout: {
                type: 'vbox',
                align: 'stretch'
            },
            style: {
                backgroundColor: 'White'
            },
            renderTo: this.getContentDiv(),
            items: [
                {
                    id: this.name + 'ContentPanel',
                    xtype: "container",
                    autoScroll: true,
                    width: '100%',
                    style: {
                        marginLeft: '10px',
                        marginRight: '10px'
                    },
                    flex: 1,
                    items: items
                }, {
                    id: this.name + 'ClosingPanel',
                    xtype: "container",
                    width: '100%',
                    height: MobileManager.isMobile() ? 45 : 25,
                    style: {
                        marginTop: '10px',
                        marginRight: '5px'
                    },
                    layout: {
                        type:'hbox',
                        pack:'end'
                    },
                    items: [
                        {xtype: 'button', text: 'Sluiten', padding: MobileManager.isMobile() ? '10px' : '2px', handler: function() {
                            me.popup.hide();
                        }}
                    ]
                }
            ]
        });

        this.formselect.setVisible(false);
    },

    /**
     * Event handlers
     **/
    activeFeatureChanged : function (vectorLayer,feature){
        this.toggleSelectForm(true);
        if(this.features[feature.id] == undefined){
            feature.color = "0x"+this.color;
            this.features[feature.id] = feature;
        }else{
            var color = this.features[feature.id].color;
            color = color.substring(2);
            this.colorPicker.setColor(color);
            this.color = color;
        }
        this.activeFeature = this.features[feature.id];
        this.label.setValue(this.activeFeature.label);
    },
    //update the wkt of the active feature with the completed feature
    activeFeatureFinished : function (vectorLayer,feature){
        this.activeFeature.wktgeom = feature.wktgeom;
        Ext.Object.each(this.drawingButtonIds, function(key, id) {
            var button = Ext.getCmp(id);
            if(button) button.toggle(false);
        });
    },
    colorChanged : function (hexColor){
        this.color = '0x'+hexColor;
        this.vectorLayer.style.fillcolor = this.color;
        this.vectorLayer.adjustStyle();
        if(this.activeFeature != null){
            this.activeFeature.color = this.color;
            var feature = this.vectorLayer.getFeatureById(this.activeFeature.getId());
            this.activeFeature.wktgeom = feature.wktgeom;
            this.vectorLayer.removeFeature(this.activeFeature);
            this.vectorLayer.addFeature(this.activeFeature);
        }
    },
    labelChanged : function (field,newValue){
        if(this.activeFeature != null){
            this.vectorLayer.setLabel(this.activeFeature.getId(),newValue);
            this.activeFeature.label=newValue;
        }
    },
    toggleSelectForm : function(visible){
        this.formselect.setVisible(visible);
    },
    drawPoint: function(){
        this.vectorLayer.drawFeature("Point");
    },
    drawLine: function(){
        this.vectorLayer.drawFeature("LineString");
    },
    drawPolygon: function(){
        this.vectorLayer.drawFeature("Polygon");
    },
    drawCircle: function(){
        this.vectorLayer.drawFeature("Circle");
    },
    deleteAll: function(){
        this.vectorLayer.removeAllFeatures();
        this.toggleSelectForm(false);
        this.features = {};
        this.label.setValue("");
        this.title.setValue("");
        this.description.setValue("");
        if(this.activeFeature != null){
            this.activeFeature=null;
        }
    },
    deleteObject: function(){
        delete this.features[this.activeFeature.id];
        this.vectorLayer.removeFeature(this.activeFeature);
        this.toggleSelectForm(false);
        if(this.activeFeature != null){
            this.activeFeature=null;
        }
        this.label.setValue("");
    },
    saveFile: function(){
        var form = this.formsave.getForm();

        var features = new Array();
        for (var featurekey in this.features){
            var feature = this.features[featurekey];
            features.push(feature.toJsonObject());
        }
        form.setValues({
            "saveObject":Ext.JSON.encode(features)
        });
        this.formsave.submit({
            target: '_blank'
        } );
        return features;
    },
    openFile: function(){
        var form =this.formopen.getForm();
        if(form.isValid()){
            form.submit({
                scope:this,
                url: actionBeans["drawing"],
                waitMsg: 'Bezig met uploaden...',
                waitTitle: "Even wachten...",
                success: function(fp, o) {
                    var json = Ext.JSON.decode(o.result.content);
                    this.title.setValue( json.title);
                    this.description.setValue(json.description);
                    var features = Ext.JSON.decode(json.features);
                    for ( var i = 0 ; i < features.length;i++){
                        var feature = features[i];
                        var featureObject = Ext.create("viewer.viewercontroller.controller.Feature",feature);
                        this.vectorLayer.style.fillcolor = featureObject.color;
                        this.vectorLayer.adjustStyle();
                        this.vectorLayer.addFeature(featureObject);
                    }
                    if(features.length > 0){
                        var extent = o.result.extent;
                        this.viewerController.mapComponent.getMap().zoomToExtent(extent);
                    }

                },
                failure: function (){
                    Ext.Msg.alert('Mislukt', 'Uw bestand kon niet gelezen worden.');
                }
            });
        }
    },
    getExtComponents: function() {
        var compIds = [
            this.mainContainer.getId(),
            this.colorPicker.getId(),
            this.label.getId(),
            this.formdraw.getId(),
            this.formselect.getId(),
            this.title.getId(),
            this.description.getId()
        ];
        if(!MobileManager.isMobile()) {
            compIds.push(this.formsave.getId());
            compIds.push(this.file.getId());
            compIds.push(this.formopen.getId());
        }
        return compIds;
    }
});
 <|endoftext|>


<commit_before>import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import theme from './theme.css';

const spacings = [0, 1, 2, 3, 4, 5, 6, 7, 8];

class Box extends PureComponent {
  static propTypes = {
    children: PropTypes.node,
    className: PropTypes.string,
    element: PropTypes.node,
    marginBottom: PropTypes.oneOf(spacings),
    marginLeft: PropTypes.oneOf(spacings),
    marginRight: PropTypes.oneOf(spacings),
    marginTop: PropTypes.oneOf(spacings),
    paddingBottom: PropTypes.oneOf(spacings),
    paddingLeft: PropTypes.oneOf(spacings),
    paddingRight: PropTypes.oneOf(spacings),
    paddingTop: PropTypes.oneOf(spacings),
  };

  static defaultProps = {
    element: 'div',
    marginBottom: 0,
    marginLeft: 0,
    marginRight: 0,
    marginTop: 0,
    paddingBottom: 0,
    paddingLeft: 0,
    paddingRight: 0,
    paddingTop: 0,
  };

  render() {
    const { children, className, element, marginBottom, marginLeft, marginRight, marginTop, paddingBottom, paddingLeft, paddingRight, paddingTop, ...others } = this.props;

    const classNames = cx(
      theme['box'],
      {
        [theme[`margin-bottom-${marginBottom}`]]: marginBottom > 0,
        [theme[`margin-left-${marginLeft}`]]: marginLeft > 0,
        [theme[`margin-right-${marginRight}`]]: marginRight > 0,
        [theme[`margin-top-${marginTop}`]]: marginTop > 0,
        [theme[`padding-bottom-${paddingBottom}`]]: paddingBottom > 0,
        [theme[`padding-left-${paddingLeft}`]]: paddingLeft > 0,
        [theme[`padding-right-${paddingRight}`]]: paddingRight > 0,
        [theme[`padding-top-${paddingTop}`]]: paddingTop > 0,
      },
      className,
    );

    const Element = element;

    return (
      <Element className={classNames} {...others}>
        {children}
      </Element>
    );
  }
}

export default Box;
<commit_msg>Prettier Linting™<commit_after>import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import theme from './theme.css';

const spacings = [0, 1, 2, 3, 4, 5, 6, 7, 8];

class Box extends PureComponent {
  static propTypes = {
    children: PropTypes.node,
    className: PropTypes.string,
    element: PropTypes.node,
    marginBottom: PropTypes.oneOf(spacings),
    marginLeft: PropTypes.oneOf(spacings),
    marginRight: PropTypes.oneOf(spacings),
    marginTop: PropTypes.oneOf(spacings),
    paddingBottom: PropTypes.oneOf(spacings),
    paddingLeft: PropTypes.oneOf(spacings),
    paddingRight: PropTypes.oneOf(spacings),
    paddingTop: PropTypes.oneOf(spacings),
  };

  static defaultProps = {
    element: 'div',
    marginBottom: 0,
    marginLeft: 0,
    marginRight: 0,
    marginTop: 0,
    paddingBottom: 0,
    paddingLeft: 0,
    paddingRight: 0,
    paddingTop: 0,
  };

  render() {
    const {
      children,
      className,
      element,
      marginBottom,
      marginLeft,
      marginRight,
      marginTop,
      paddingBottom,
      paddingLeft,
      paddingRight,
      paddingTop,
      ...others
    } = this.props;

    const classNames = cx(
      theme['box'],
      {
        [theme[`margin-bottom-${marginBottom}`]]: marginBottom > 0,
        [theme[`margin-left-${marginLeft}`]]: marginLeft > 0,
        [theme[`margin-right-${marginRight}`]]: marginRight > 0,
        [theme[`margin-top-${marginTop}`]]: marginTop > 0,
        [theme[`padding-bottom-${paddingBottom}`]]: paddingBottom > 0,
        [theme[`padding-left-${paddingLeft}`]]: paddingLeft > 0,
        [theme[`padding-right-${paddingRight}`]]: paddingRight > 0,
        [theme[`padding-top-${paddingTop}`]]: paddingTop > 0,
      },
      className,
    );

    const Element = element;

    return (
      <Element className={classNames} {...others}>
        {children}
      </Element>
    );
  }
}

export default Box;
<|endoftext|>


<commit_before>/*

Run a GraphQL query from the server with the proper context

*/
import { graphql } from 'graphql';
import { executableSchema } from './apollo_server.js';
import { Collections } from '../modules/collections.js';
import DataLoader from 'dataloader';
import findByIds from '../modules/findbyids.js';
import { getDefaultFragmentText, extractFragmentName, getFragmentText } from '../modules/fragments.js';

// note: if no context is passed, default to running queries with full admin privileges
export const runQuery = async (query, variables = {}, context = { currentUser: {isAdmin: true} }) => {

  // within the scope of this specific query,
  // decorate each collection with a new Dataloader object and add it to context
  Collections.forEach(collection => {
    collection.loader = new DataLoader(ids => findByIds(collection, ids, context), { cache: true });
    context[collection.options.collectionName] = collection;
  });

  // see http://graphql.org/graphql-js/graphql/#graphql
  const result = await graphql(executableSchema, query, {}, context, variables);

  if (result.errors) {
    // eslint-disable-next-line no-console
    console.log('runQuery error: '+result.errors[0].message);
    throw new Error(result.errors[0].message);
  }

  return result;
}

/*

Given a collection and a fragment, build a query to fetch one document.
If no fragment is passed, default to default fragment

*/
export const buildQuery = (collection, {fragmentName, fragmentText}) => {

  const collectionName = collection.options.collectionName;
  const resolverName = `${collectionName}Single`;

  const defaultFragmentName = `${collectionName}DefaultFragment`;
  const defaultFragmentText = getDefaultFragmentText(collection, { onlyViewable: false });

  // default to default name and text
  let name = defaultFragmentName;
  let text = defaultFragmentText;

  if (fragmentName) { // if fragmentName is passed, use that to get name and text
    name = fragmentName;
    text = getFragmentText(fragmentName);
  } else if (fragmentText) { // if fragmentText is passed, use that to get name and text
    name = extractFragmentName(fragmentText);
    text = fragmentText;
  }

  const query = `
    query ${resolverName}Query ($documentId: String){
      ${resolverName}(documentId: $documentId){
        ...${name}
      }
    }
    ${text}
  `

  return query;
}

Meteor.startup(() => {

  Collections.forEach(collection => {

    const collectionName = collection.options.collectionName;
    const resolverName = `${collectionName}Single`;

    collection.queryOne = async (documentId, { fragmentName, fragmentText, context }) => {
      const query = buildQuery(collection, { fragmentName, fragmentText });
      const result = await runQuery(query, { documentId }, context);
      return result.data[resolverName];
    }

  });

});<commit_msg>More graphql error logging<commit_after>/*

Run a GraphQL query from the server with the proper context

*/
import { graphql } from 'graphql';
import { executableSchema } from './apollo_server.js';
import { Collections } from '../modules/collections.js';
import DataLoader from 'dataloader';
import findByIds from '../modules/findbyids.js';
import { getDefaultFragmentText, extractFragmentName, getFragmentText } from '../modules/fragments.js';

// note: if no context is passed, default to running queries with full admin privileges
export const runQuery = async (query, variables = {}, context = { currentUser: {isAdmin: true} }) => {

  // within the scope of this specific query,
  // decorate each collection with a new Dataloader object and add it to context
  Collections.forEach(collection => {
    collection.loader = new DataLoader(ids => findByIds(collection, ids, context), { cache: true });
    context[collection.options.collectionName] = collection;
  });

  // see http://graphql.org/graphql-js/graphql/#graphql
  const result = await graphql(executableSchema, query, {}, context, variables);

  if (result.errors) {
    // eslint-disable-next-line no-console
    console.error(`runQuery error: ${result.errors[0].message}`);
    console.error(result.errors);
    throw new Error(result.errors[0].message);
  }

  return result;
}

/*

Given a collection and a fragment, build a query to fetch one document.
If no fragment is passed, default to default fragment

*/
export const buildQuery = (collection, {fragmentName, fragmentText}) => {

  const collectionName = collection.options.collectionName;
  const resolverName = `${collectionName}Single`;

  const defaultFragmentName = `${collectionName}DefaultFragment`;
  const defaultFragmentText = getDefaultFragmentText(collection, { onlyViewable: false });

  // default to default name and text
  let name = defaultFragmentName;
  let text = defaultFragmentText;

  if (fragmentName) { // if fragmentName is passed, use that to get name and text
    name = fragmentName;
    text = getFragmentText(fragmentName);
  } else if (fragmentText) { // if fragmentText is passed, use that to get name and text
    name = extractFragmentName(fragmentText);
    text = fragmentText;
  }

  const query = `
    query ${resolverName}Query ($documentId: String){
      ${resolverName}(documentId: $documentId){
        ...${name}
      }
    }
    ${text}
  `

  return query;
}

Meteor.startup(() => {

  Collections.forEach(collection => {

    const collectionName = collection.options.collectionName;
    const resolverName = `${collectionName}Single`;

    collection.queryOne = async (documentId, { fragmentName, fragmentText, context }) => {
      const query = buildQuery(collection, { fragmentName, fragmentText });
      const result = await runQuery(query, { documentId }, context);
      return result.data[resolverName];
    }

  });

});<|endoftext|>


<commit_before>import React, { Component } from 'react'
import { withApollo, gql } from 'react-apollo'
import _ from 'lodash'

import { FIRSTLOAD_CHATROOMS_QUERY } from './ChatList'
import { FIRSTLOAD_USER_CHATROOMS_QUERY } from './UserChatList'

class NewChat extends Component {

  constructor(props) {
    super(props);
    this.state = {
      title: '',
    }
  }

  onCreateChat = async (e) => {
    e.preventDefault()

    const currentUserId = this.props.currentUserId
    const currentUsername = this.props.currentUsername

    if(!currentUserId) {
      alert('You must log in first.')
      return
    }

    try {
      const userCount = (await this.props.client.query({ query: USER_COUNT_QUERY })).data._allUsersMeta.count
      const randomSkip = _.random(0, userCount - 2)
      if(userCount - 2 < 0) {
        alert("Oops, can't find any users.")
        return
      }

      // alert('random skip is' + randomSkip)

      // To prevent duplicate, get two random users, at least one will never be a duplicate.
      const twoRandomUsers  = (await this.props.client.query({
        query: GET_USERS_QUERY,
        variables: {
          first: 2,
          skip: randomSkip,
        }
      })).data.allUsers

      const twoRandomUserIds = twoRandomUsers.map(u => u.id)

      // alert('fetched users are' + twoRandomUsers.map(u => u.username).join(' '))

      let anotherUserId;
      let anotherUsername;

      // if both not same as current user id, can random
      if (twoRandomUserIds[0] !== currentUserId && twoRandomUserIds[1] !== currentUserId) {
        anotherUserId = _.sample(twoRandomUserIds)
        anotherUsername = anotherUserId === twoRandomUserIds[0] ? twoRandomUsers[0].username : twoRandomUsers[1].username
      } else {
        // some is duplicate, choose one that's not
        anotherUserId = twoRandomUserIds[0] !== currentUserId ? twoRandomUserIds[0] : twoRandomUserIds[1]
        anotherUsername = twoRandomUserIds[0] !== currentUserId ? twoRandomUsers[0].username : twoRandomUsers[1].username
      }

      const { data: { createChatroom: { id } } } = await this.props.client.mutate({
        mutation: CREATE_CHAT_MUTATION,
        variables: {
          title: this.state.title,
          userIds: [currentUserId, anotherUserId],
          createdById: currentUserId,
        },
        // You may simply use this, in which case we don't need to update the store manually in 'update'
        // But this is slower.
        // refetchQueries: [
        //   {
        //     query: FIRSTLOAD_CHATROOMS_QUERY,
        //   },
        //   {
        //     query: FIRSTLOAD_USER_CHATROOMS_QUERY,
        //     variables: {
        //       forUserId: currentUserId,
        //     }
        //   }
        // ],
        optimisticResponse: {
          __typename: 'Mutation',
          createChatroom: {
            __typename: 'Chatroom',
            id: '',
            createdAt: (new Date()).toISOString(),
            title: this.state.title,
            users: [
              {
                __typename: 'User',
                id: '',
                username: currentUsername,
              },
              {
                __typename: 'User',
                id: '',
                username: anotherUsername,
              }
            ],
          }
        },
        update: (store, { data: { createChatroom }}) => {

          // Update the store (so that the graphql components across the app will get updated)

          // Why doesn't it be smart and update all 'related' queries & components !??
          // https://github.com/apollographql/apollo-client/issues/1697

          try  {
            // All chatlist
            // 1. read from store
            const allChatroomsData = store.readQuery({
              query: FIRSTLOAD_CHATROOMS_QUERY,
            })

            // Must update messagesMeta manually, since the newly added object doesn't has one
            createChatroom._messagesMeta = {
              count: 0,
              __typename: '_QueryMeta',
            }

            // 2. append at first position
            allChatroomsData.allChatrooms.unshift(createChatroom)
            allChatroomsData._allChatroomsMeta.count += 1

            // 3. write back
            store.writeQuery({
              query: FIRSTLOAD_CHATROOMS_QUERY,
              data: allChatroomsData,
            })
          } catch (err) {
            // Probably query allChatrooms doesn't exist. (e.g., in case user enters directly to '/talk' page)
            // console.log('Error: ', err)
          }

          try {
            // User chatlist
            // 1. read from store
            const userChatroomsData = store.readQuery({
              query: FIRSTLOAD_USER_CHATROOMS_QUERY,
              variables: {
                forUserId: currentUserId,
              },
            })

            // Must update messagesMeta manually, since the newly added object doesn't has one
            createChatroom._messagesMeta = {
              count: 0,
              __typename: '_QueryMeta',
            }

            // 2. append at first position
            userChatroomsData.allChatrooms.unshift(createChatroom)
            userChatroomsData._allChatroomsMeta.count += 1

            // 3. write back
            store.writeQuery({
              query: FIRSTLOAD_USER_CHATROOMS_QUERY,
              data: userChatroomsData,
              variables: {
                forUserId: currentUserId,
              },
            })
          } catch (err) {
            // This shouldn't error, since we can add chat only in '/talk' page.
            // This means FIRSTLOAD_USER_CHATROOMS_QUERY should exist.
            console.err('Error: ', err)
          }

        },
      })

      this.setState({
        title: '',
      })
      this.props.onCreateNewChatroom(id)
    } catch(err) {
      alert("Oops: " + err.graphQLErrors[0].message);
    }
  }

  render() {
    const confirmDisabled = !this.state.title
    return (

        <form onSubmit={this.onCreateChat}>
          <input
              value={this.state.title}
              onChange={e => this.setState({ title: e.target.value})}
              placeholder="insert a topic here"
              type="text"
              className="add-chat-input"
          />

          <button
              className="primary-button"
              type="submit"
              disabled={confirmDisabled}
          >
              Create chat
          </button>

        </form>

)
    }
}

const CREATE_CHAT_MUTATION = gql`
  mutation CreateChatroomMutation($title: String!, $createdById: ID!, $userIds: [ID!]!) {
    createChatroom(title: $title, usersIds: $userIds, createdById: $createdById) {
      id,
      createdAt,
      title,
      createdBy {
        id
      },
      users {
        id,
        username,
      }
    }
  }
`

const USER_COUNT_QUERY = gql`
  query UserCountQuery {
    _allUsersMeta {
      count
    }
  }
`

const GET_USERS_QUERY =  gql`
  query GetUsersQuery($first: Int!, $skip: Int!) {
    allUsers(first: $first, skip: $skip) {
      id,
      username,
    }
  }
`

NewChat.propTypes = {
  currentUserId: React.PropTypes.string.isRequired,
  currentUsername: React.PropTypes.string.isRequired,
}

export default withApollo(NewChat)
<commit_msg>Fix adding user in mutation<commit_after>import React, { Component } from 'react'
import { withApollo, gql } from 'react-apollo'
import _ from 'lodash'

import { FIRSTLOAD_CHATROOMS_QUERY } from './ChatList'
import { FIRSTLOAD_USER_CHATROOMS_QUERY } from './UserChatList'

class NewChat extends Component {

  constructor(props) {
    super(props);
    this.state = {
      title: '',
    }
  }

  onCreateChat = async (e) => {
    e.preventDefault()

    const currentUserId = this.props.currentUserId
    const currentUsername = this.props.currentUsername

    if(!currentUserId) {
      alert('You must log in first.')
      return
    }

    try {
      const userCount = (await this.props.client.query({ query: USER_COUNT_QUERY })).data._allUsersMeta.count
      const randomSkip = _.random(0, userCount - 2)
      if(userCount - 2 < 0) {
        alert("Oops, can't find any users.")
        return
      }

      // alert('random skip is' + randomSkip)

      // To prevent duplicate, get two random users, at least one will never be a duplicate.
      const twoRandomUsers  = (await this.props.client.query({
        query: GET_USERS_QUERY,
        variables: {
          first: 2,
          skip: randomSkip,
        }
      })).data.allUsers

      const twoRandomUserIds = twoRandomUsers.map(u => u.id)

      // alert('fetched users are' + twoRandomUsers.map(u => u.username).join(' '))

      let anotherUserId;
      let anotherUsername;

      // if both not same as current user id, can random
      if (twoRandomUserIds[0] !== currentUserId && twoRandomUserIds[1] !== currentUserId) {
        anotherUserId = _.sample(twoRandomUserIds)
        anotherUsername = anotherUserId === twoRandomUserIds[0] ? twoRandomUsers[0].username : twoRandomUsers[1].username
      } else {
        // some is duplicate, choose one that's not
        anotherUserId = twoRandomUserIds[0] !== currentUserId ? twoRandomUserIds[0] : twoRandomUserIds[1]
        anotherUsername = twoRandomUserIds[0] !== currentUserId ? twoRandomUsers[0].username : twoRandomUsers[1].username
      }

      const { data: { createChatroom: { id } } } = await this.props.client.mutate({
        mutation: CREATE_CHAT_MUTATION,
        variables: {
          title: this.state.title,
          userIds: [currentUserId, anotherUserId],
          createdById: currentUserId,
        },
        // You may simply use this, in which case we don't need to update the store manually in 'update'
        // But this is slower.
        // refetchQueries: [
        //   {
        //     query: FIRSTLOAD_CHATROOMS_QUERY,
        //   },
        //   {
        //     query: FIRSTLOAD_USER_CHATROOMS_QUERY,
        //     variables: {
        //       forUserId: currentUserId,
        //     }
        //   }
        // ],
        optimisticResponse: {
          __typename: 'Mutation',
          createChatroom: {
            __typename: 'Chatroom',
            id: '',
            createdAt: (new Date()).toISOString(),
            title: this.state.title,
            stateType: 0,
            createdBy: {
              __typename: 'User',
              id: '',
            },
            users: [
              {
                __typename: 'User',
                id: '',
                username: currentUsername,
              },
              {
                __typename: 'User',
                id: '',
                username: anotherUsername,
              }
            ],
          }
        },
        update: (store, { data: { createChatroom }}) => {

          // Update the store (so that the graphql components across the app will get updated)

          // Why doesn't it be smart and update all 'related' queries & components !??
          // https://github.com/apollographql/apollo-client/issues/1697

          try  {
            // All chatlist
            // 1. read from store
            const allChatroomsData = store.readQuery({
              query: FIRSTLOAD_CHATROOMS_QUERY,
            })

            // Must update messagesMeta manually, since the newly added object doesn't has one
            createChatroom._messagesMeta = {
              count: 0,
              __typename: '_QueryMeta',
            }

            // 2. append at first position
            allChatroomsData.allChatrooms.unshift(createChatroom)
            allChatroomsData._allChatroomsMeta.count += 1

            // 3. write back
            store.writeQuery({
              query: FIRSTLOAD_CHATROOMS_QUERY,
              data: allChatroomsData,
            })
          } catch (err) {
            // Probably query allChatrooms doesn't exist. (e.g., in case user enters directly to '/talk' page)
          }

          try {
            // User chatlist
            // 1. read from store
            const userChatroomsData = store.readQuery({
              query: FIRSTLOAD_USER_CHATROOMS_QUERY,
              variables: {
                forUserId: currentUserId,
              },
            })

            // Must update messagesMeta manually, since the newly added object doesn't has one
            createChatroom._messagesMeta = {
              count: 0,
              __typename: '_QueryMeta',
            }

            // 2. append at first position
            userChatroomsData.allChatrooms.unshift(createChatroom)
            userChatroomsData._allChatroomsMeta.count += 1

            // 3. write back
            store.writeQuery({
              query: FIRSTLOAD_USER_CHATROOMS_QUERY,
              data: userChatroomsData,
              variables: {
                forUserId: currentUserId,
              },
            })
          } catch (err) {
            // This shouldn't error, since we can add chat only in '/talk' page.
            // This means FIRSTLOAD_USER_CHATROOMS_QUERY should exist.
            console.err('Error: ', err)
          }

        },
      })

      this.setState({
        title: '',
      })
      this.props.onCreateNewChatroom(id)
    } catch(err) {
      alert("Oops: " + err.graphQLErrors[0].message);
    }
  }

  render() {
    const confirmDisabled = !this.state.title
    return (

        <form onSubmit={this.onCreateChat}>
          <input
              value={this.state.title}
              onChange={e => this.setState({ title: e.target.value})}
              placeholder="insert a topic here"
              type="text"
              className="add-chat-input"
          />

          <button
              className="primary-button"
              type="submit"
              disabled={confirmDisabled}
          >
              Create chat
          </button>

        </form>

)
    }
}

const CREATE_CHAT_MUTATION = gql`
  mutation CreateChatroomMutation($title: String!, $createdById: ID!, $userIds: [ID!]!) {
    createChatroom(title: $title, usersIds: $userIds, createdById: $createdById) {
      id,
      createdAt,
      title,
      createdBy {
        id
      },
      users {
        id,
        username,
      }
      stateType
    }
  }
`

const USER_COUNT_QUERY = gql`
  query UserCountQuery {
    _allUsersMeta {
      count
    }
  }
`

const GET_USERS_QUERY =  gql`
  query GetUsersQuery($first: Int!, $skip: Int!) {
    allUsers(first: $first, skip: $skip) {
      id,
      username,
    }
  }
`

NewChat.propTypes = {
  currentUserId: React.PropTypes.string.isRequired,
  currentUsername: React.PropTypes.string.isRequired,
}

export default withApollo(NewChat)
<|endoftext|>


<commit_before>'use strict';

angular.module('flexeWebApp', ['ngRoute'])
  .config(function ($routeProvider) {
    $routeProvider
      .when('/jobs/:jobId', {
        templateUrl: 'views/job.html',
        controller: 'JobCtrl'
      })
      .when('/login', {
        templateUrl: 'views/login.html',
        controller: 'LoginCtrl'
      })
      .when('/home', {
        templateUrl: 'views/home.html',
        controller: 'HomeCtrl'
      })
      .when('/contact', {
        templateUrl: 'views/contact.html',
        controller: 'ContactCtrl'
      })
      .when('/about', {
        templateUrl: 'views/about.html',
        controller: 'AboutCtrl'
      })
      .otherwise({
          redirectTo: '/home'
      });
  })
  .config( function($provide) {
    $provide.decorator('$log', ['$delegate', function($delegate) {
      // $delegate.debug = function(message) { };
      return $delegate;
    }]);
  })
  .run( function($rootScope, $location, User) {
    // register listener to watch route changes
    $rootScope.$on( '$routeChangeStart', function(event, next, current) {
      if ( User.isLoggedIn() === false ) {
        // not a logged user, we should be going to #login
        if ( next.templateUrl === 'views/login.html' ) {
          // already going to #login, no redirect needed
        } else {
          // not going to #login, we should redirect now
          $location.path( '/login' );
        }
      }
    })
  })
  .run( function(FlexeWebBackend) {
      FlexeWebBackend.setLocation('http://107.170.237.217');
      // FlexeWebBackend.setLocation('http://127.0.0.1:8001');
  });
<commit_msg>Silence debugging messages.<commit_after>'use strict';

angular.module('flexeWebApp', ['ngRoute'])
  .config(function ($routeProvider) {
    $routeProvider
      .when('/jobs/:jobId', {
        templateUrl: 'views/job.html',
        controller: 'JobCtrl'
      })
      .when('/login', {
        templateUrl: 'views/login.html',
        controller: 'LoginCtrl'
      })
      .when('/home', {
        templateUrl: 'views/home.html',
        controller: 'HomeCtrl'
      })
      .when('/contact', {
        templateUrl: 'views/contact.html',
        controller: 'ContactCtrl'
      })
      .when('/about', {
        templateUrl: 'views/about.html',
        controller: 'AboutCtrl'
      })
      .otherwise({
          redirectTo: '/home'
      });
  })
  .config( function($provide) {
    $provide.decorator('$log', ['$delegate', function($delegate) {
      $delegate.debug = function(message) { };
      return $delegate;
    }]);
  })
  .run( function($rootScope, $location, User) {
    // register listener to watch route changes
    $rootScope.$on( '$routeChangeStart', function(event, next, current) {
      if ( User.isLoggedIn() === false ) {
        // not a logged user, we should be going to #login
        if ( next.templateUrl === 'views/login.html' ) {
          // already going to #login, no redirect needed
        } else {
          // not going to #login, we should redirect now
          $location.path( '/login' );
        }
      }
    })
  })
  .run( function(FlexeWebBackend) {
      FlexeWebBackend.setLocation('http://107.170.237.217');
      // FlexeWebBackend.setLocation('http://127.0.0.1:8001');
  });
<|endoftext|>


<commit_before>const Mapeo = require('./mapeo')
const createTileServer = require('./tile-server')
const rabbit = require('electron-rabbit')
const logger = require('../logger')

var mapeo
var handlers = {}

handlers.close = async () => {
  return new Promise((resolve, reject) => {
    if (!mapeo) return reject(new Error('oops: mapeo not open'))
    mapeo.close(function (err) {
      logger.log('now its done', err)
      if (err) return reject(err)
      resolve()
    })
  })
}

handlers.listen = async ({ datadir, userDataPath, port, tileport }) => {
  mapeo = new Mapeo({ datadir, userDataPath, ipcSend: rabbit.send })

  return new Promise((resolve, reject) => {
    logger.log('listening')
    var pending = 2

    // TODO(KM): combine mapeo/server and tile server
    mapeo.listen(port, function () {
      logger.log('got port, resolving', mapeo.server.address().port)
      if (--pending === 0) resolve(mapeo.server.address().port)
    })

    var tileServer = createTileServer(userDataPath)
    tileServer.listen(tileport, function () {
      logger.log('tile server listening on :', tileServer.address().port)
      if (--pending === 0) resolve(mapeo.server.address().port)
    })
  })
}

handlers['import-tiles'] = async (filename) => {
  return new Promise((resolve, reject) => {
    mapeo.tiles.go(filename, function (err) {
      if (err) return reject(err)
      else resolve()
    })
  })
}

handlers['import-data'] = async (filename) => {
  mapeo.core.importer.importFromFile(filename)
}

handlers['sync-start'] = async (target) => {
  mapeo.syncStart(target)
}

handlers['sync-join'] = async () => {
  mapeo.syncJoin()
}

handlers['sync-leave'] = async () => {
  mapeo.syncLeave()
}

handlers['export-data'] = async (args) => {
  logger.log('did i get this', args)
  return new Promise((resolve, reject) => {
    mapeo.exportData(args, function (err) {
      if (err) return reject(err)
      else resolve()
    })
  })
}

handlers['zoom-to-data-get-centroid'] = async (type) => {
  return new Promise((resolve, reject) => {
    mapeo.getDatasetCentroid(type, function (err, loc) {
      logger.log('RESPONSE(getDatasetCentroid):', loc)
      if (err) return reject(err)
      resolve(loc)
    })
  })
}

module.exports = handlers
<commit_msg>fix: mapeo only needs to close once<commit_after>const Mapeo = require('./mapeo')
const createTileServer = require('./tile-server')
const rabbit = require('electron-rabbit')
const logger = require('../logger')

var mapeo
var handlers = {}

handlers.close = async () => {
  return new Promise((resolve, reject) => {
    if (!mapeo) return resolve()
    mapeo.close(function (err) {
      mapeo = null
      logger.log('now its done', err)
      if (err) return reject(err)
      resolve()
    })
  })
}

handlers.listen = async ({ datadir, userDataPath, port, tileport }) => {
  mapeo = new Mapeo({ datadir, userDataPath, ipcSend: rabbit.send })

  return new Promise((resolve, reject) => {
    logger.log('listening')
    var pending = 2

    // TODO(KM): combine mapeo/server and tile server
    mapeo.listen(port, function () {
      logger.log('got port, resolving', mapeo.server.address().port)
      if (--pending === 0) resolve(mapeo.server.address().port)
    })

    var tileServer = createTileServer(userDataPath)
    tileServer.listen(tileport, function () {
      logger.log('tile server listening on :', tileServer.address().port)
      if (--pending === 0) resolve(mapeo.server.address().port)
    })
  })
}

handlers['import-tiles'] = async (filename) => {
  return new Promise((resolve, reject) => {
    mapeo.tiles.go(filename, function (err) {
      if (err) return reject(err)
      else resolve()
    })
  })
}

handlers['import-data'] = async (filename) => {
  mapeo.core.importer.importFromFile(filename)
}

handlers['sync-start'] = async (target) => {
  mapeo.syncStart(target)
}

handlers['sync-join'] = async () => {
  mapeo.syncJoin()
}

handlers['sync-leave'] = async () => {
  mapeo.syncLeave()
}

handlers['export-data'] = async (args) => {
  logger.log('did i get this', args)
  return new Promise((resolve, reject) => {
    mapeo.exportData(args, function (err) {
      if (err) return reject(err)
      else resolve()
    })
  })
}

handlers['zoom-to-data-get-centroid'] = async (type) => {
  return new Promise((resolve, reject) => {
    mapeo.getDatasetCentroid(type, function (err, loc) {
      logger.log('RESPONSE(getDatasetCentroid):', loc)
      if (err) return reject(err)
      resolve(loc)
    })
  })
}

module.exports = handlers
<|endoftext|>


<commit_before>(function (window, undefined) {
    "use strict";

    if (!window.Trios2D)
        throw new Error("You must include Trios2D in your HTML");

    var Trios2D = window.Trios2D,
        Vector = Trios2D.Vector,
        GameObject = Trios2D.GameObject,
        Component = Trios2D.Component;

    function Physics() {
        // Calling Component Constructor
        Component.apply(this);

        this._velocity = new Vector();
        this._aceleration = new Vector();

    }

    Physics.prototype = Object.create({
        get position() {
            if (this.gameObject) {
                return this.gameObject.position;
            }
        },

        set position(val) {
            if (this.gameObject) {
                this.gameObject.position = val;
            }
        },

        get velocity() {
            if (this.position == this._lastPosition) {
                return this._velocity;
            } else {
                // immediat position change would make the velocity increases in the update frame where it was made
                return this.position.sub(this._lastPosition).divide(this._lastDelta / 1000);
            }
        },

        set velocity(val) {
            this._velocity = val;
        },

        get aceleration() {
            if (this.velocity == this._lastVelocity) {
                return this._aceleration;
            } else {
                // immediat velocity change would make the aceleration increases in the update frame where it was made
                return this.velocity.sub(this._lastVelocity).divide(this._lastDelta / 1000);
            }
        },

        set aceleration(val) {
            this._aceleration = val;
        }

    }, Component.prototype);

    Physics.prototype.update = function update(delta, gameObject) {

        if (this._aceleration.hasValue()) {
            this._velocity = this._velocity.add(this._aceleration.multiply(delta / 1000));
        }

        if (this._velocity.hasValue()) {
            this.position = this.position.add(this._velocity.multiply(delta / 1000));
        }

        this._lastPosition = this.position;
        this._lastVelocity = this._velocity;
        this._lastDelta = delta;
    };

    window.Trios2D.Components.Physics = Physics;

}(window));
<commit_msg>bug adding properties soved<commit_after>(function (window, undefined) {
    "use strict";

    if (!window.Trios2D)
        throw new Error("You must include Trios2D in your HTML");

    var Trios2D = window.Trios2D,
        Vector = Trios2D.Vector,
        GameObject = Trios2D.GameObject,
        Component = Trios2D.Component;

    function Physics() {
        // Calling Component Constructor
        Component.apply(this);

        this._velocity = new Vector();
        this._aceleration = new Vector();

    }

    Physics.prototype = Object.create(Component.prototype, {
        position: {
            get: function position() {
                if (this.gameObject) {
                    return this.gameObject.position;
                }
            },

            set: function position(val) {
                if (this.gameObject) {
                    this.gameObject.position = val;
                }
            },

            enumerable: true
        },

        velocity: {
            get: function velocity() {
                if (this.position.equal(this._lastPosition)) {
                    return this._velocity;
                } else {
                    // immediat position change would make the velocity increases in the update frame where it was made
                    return this.position.sub(this._lastPosition).divide(this._lastDelta / 1000);
                }
            },

            set: function  velocity(val) {
                this._velocity = val;
            }
        },

        aceleration: {
            get: function aceleration() {
                if (this.velocity.equal(this._lastVelocity)) {
                    return this._aceleration;
                } else {
                    // immediat velocity change would make the aceleration increases in the update frame where it was made
                    return this.velocity.sub(this._lastVelocity).divide(this._lastDelta / 1000);
                }
            },

            set: function aceleration(val) {
                this._aceleration = val;
            }
        }

    });

    Physics.prototype.update = function update(delta, gameObject) {

        if (this._aceleration.hasValue()) {
            this._velocity = this._velocity.add(this._aceleration.multiply(delta / 1000));
        }

        if (this._velocity.hasValue()) {
            this.position = this.position.add(this._velocity.multiply(delta / 1000));
        }

        this._lastPosition = this.position;
        this._lastVelocity = this._velocity;
        this._lastDelta = delta;
    };

    window.Trios2D.Components.Physics = Physics;

}(window));
<|endoftext|>


<commit_before>import createLogger from '@xen-orchestra/log'
import kindOf from 'kindof'
import ms from 'ms'
import schemaInspector from 'schema-inspector'
import { forEach, isFunction } from 'lodash'
import { MethodNotFound } from 'json-rpc-peer'

import * as methods from '../api'
import * as sensitiveValues from '../sensitive-values'
import { noop, serializeError } from '../utils'

import * as errors from 'xo-common/api-errors'

// ===================================================================

const log = createLogger('xo:api')

const PERMISSIONS = {
  none: 0,
  read: 1,
  write: 2,
  admin: 3,
}

// TODO:
// - error when adding a server to a pool with incompatible version
// - error when halted VM migration failure is due to XS < 7
const XAPI_ERROR_TO_XO_ERROR = {
  EHOSTUNREACH: errors.serverUnreachable,
  HOST_OFFLINE: ([host], getId) => errors.hostOffline({ host: getId(host) }),
  NO_HOSTS_AVAILABLE: errors.noHostsAvailable,
  NOT_SUPPORTED_DURING_UPGRADE: errors.notSupportedDuringUpgrade,
  OPERATION_BLOCKED: ([ref, code], getId) =>
    errors.operationBlocked({ objectId: getId(ref), code }),
  PATCH_PRECHECK_FAILED_ISO_MOUNTED: ([patch]) =>
    errors.patchPrecheck({ errorType: 'isoMounted', patch }),
  PIF_VLAN_EXISTS: ([pif], getId) =>
    errors.objectAlreadyExists({ objectId: getId(pif), objectType: 'PIF' }),
  SESSION_AUTHENTICATION_FAILED: errors.authenticationFailed,
  VDI_IN_USE: ([vdi, operation], getId) =>
    errors.vdiInUse({ vdi: getId(vdi), operation }),
  VM_BAD_POWER_STATE: ([vm, expected, actual], getId) =>
    errors.vmBadPowerState({ vm: getId(vm), expected, actual }),
  VM_IS_TEMPLATE: errors.vmIsTemplate,
  VM_LACKS_FEATURE: ([vm], getId) => errors.vmLacksFeature({ vm: getId(vm) }),
  VM_LACKS_FEATURE_SHUTDOWN: ([vm], getId) =>
    errors.vmLacksFeature({ vm: getId(vm), feature: 'shutdown' }),
  VM_MISSING_PV_DRIVERS: ([vm], getId) =>
    errors.vmMissingPvDrivers({ vm: getId(vm) }),
}

const hasPermission = (user, permission) =>
  PERMISSIONS[user.permission] >= PERMISSIONS[permission]

function checkParams(method, params) {
  const schema = method.params
  if (!schema) {
    return
  }

  const result = schemaInspector.validate(
    {
      type: 'object',
      properties: schema,
    },
    params
  )

  if (!result.valid) {
    throw errors.invalidParameters(result.error)
  }
}

function checkPermission(method) {
  /* jshint validthis: true */

  const { permission } = method

  // No requirement.
  if (permission === undefined) {
    return
  }

  const { user } = this
  if (!user) {
    throw errors.unauthorized(permission)
  }

  // The only requirement is login.
  if (!permission) {
    return
  }

  if (!hasPermission(user, permission)) {
    throw errors.unauthorized(permission)
  }
}

async function resolveParams(method, params) {
  const resolve = method.resolve
  if (!resolve) {
    return params
  }

  const { user } = this
  if (!user) {
    throw errors.unauthorized()
  }

  const userId = user.id

  // Do not alter the original object.
  params = { ...params }

  const permissions = []
  forEach(resolve, ([param, types, permission = 'administrate'], key) => {
    const id = params[param]
    if (id === undefined) {
      return
    }

    const object = this.getObject(id, types)

    // This parameter has been handled, remove it.
    delete params[param]

    // Register this new value.
    params[key] = object

    // Permission default to 'administrate' but can be set to a falsy
    // value (except null or undefined which trigger the default
    // value) to simply do a resolve without checking any permissions.
    if (permission) {
      permissions.push([object.id, permission])
    }
  })

  await this.checkPermissions(userId, permissions)

  return params
}

// -------------------------------------------------------------------

export default class Api {
  constructor(xo) {
    this._logger = null
    this._methods = { __proto__: null }
    this._xo = xo

    this.addApiMethods(methods)
    xo.on('start', async () => {
      this._logger = await xo.getLogger('api')
    })
  }

  get apiMethods() {
    return this._methods
  }

  addApiMethod(name, method) {
    const methods = this._methods

    if (name in methods) {
      throw new Error(`API method ${name} already exists`)
    }

    methods[name] = method

    let remove = () => {
      delete methods[name]
      remove = noop
    }
    return () => remove()
  }

  addApiMethods(methods) {
    let base = ''
    const removes = []

    const addMethod = (method, name) => {
      name = base + name

      if (isFunction(method)) {
        removes.push(this.addApiMethod(name, method))
        return
      }

      const oldBase = base
      base = name + '.'
      forEach(method, addMethod)
      base = oldBase
    }

    try {
      forEach(methods, addMethod)
    } catch (error) {
      // Remove all added methods.
      forEach(removes, remove => remove())

      // Forward the error
      throw error
    }

    let remove = () => {
      forEach(removes, remove => remove())
      remove = noop
    }
    return remove
  }

  async callApiMethod(session, name, params = {}) {
    const startTime = Date.now()

    const method = this._methods[name]
    if (!method) {
      throw new MethodNotFound(name)
    }

    // FIXME: it can cause issues if there any property assignments in
    // XO methods called from the API.
    const context = Object.create(this._xo, {
      api: {
        // Used by system.*().
        value: this,
      },
      session: {
        value: session,
      },
    })

    // Fetch and inject the current user.
    const userId = session.get('user_id', undefined)
    context.user = userId && (await this._xo.getUser(userId))
    const userName = context.user ? context.user.email : '(unknown user)'

    try {
      await checkPermission.call(context, method)

      // API methods are in a namespace.
      // Some methods use the namespace or an id parameter like:
      //
      // vm.detachPci vm=<string>
      // vm.ejectCd id=<string>
      //
      // The goal here is to standardize the calls by always providing
      // an id parameter when possible to simplify calls to the API.
      if (params != null && params.id === undefined) {
        const namespace = name.slice(0, name.indexOf('.'))
        const id = params[namespace]
        if (typeof id === 'string') {
          params.id = id
        }
      }

      checkParams.call(context, method, params)

      const resolvedParams = await resolveParams.call(context, method, params)

      let result = await method.call(context, resolvedParams)

      // If nothing was returned, consider this operation a success
      // and return true.
      if (result === undefined) {
        result = true
      }

      log.debug(
        `${userName} | ${name}(...) [${ms(
          Date.now() - startTime
        )}] ==> ${kindOf(result)}`
      )

      return result
    } catch (error) {
      params = sensitiveValues.replace(params, '* obfuscated *')
      const data = {
        userId,
        method: name,
        params,
        duration: Date.now() - startTime,
        error: serializeError(error),
      }
      const message = `${userName} | ${name}(${JSON.stringify(params)}) [${ms(
        Date.now() - startTime
      )}] =!> ${error}`

      this._logger.error(message, data)

      if (this._xo._config.verboseLogsOnErrors) {
        log.warn(message, { error })
      } else {
        log.warn(
          `${userName} | ${name}(...) [${ms(
            Date.now() - startTime
          )}] =!> ${error}`
        )
      }

      const xoError = XAPI_ERROR_TO_XO_ERROR[error.code]
      if (xoError) {
        throw xoError(error.params, ref => {
          try {
            return this._xo.getObject(ref).id
          } catch (e) {
            return ref
          }
        })
      }

      throw error
    }
  }
}
<commit_msg>feat(xo-server/api): emit event on call/resolution of xo method (#3770)<commit_after>import createLogger from '@xen-orchestra/log'
import kindOf from 'kindof'
import ms from 'ms'
import schemaInspector from 'schema-inspector'
import { forEach, isFunction } from 'lodash'
import { MethodNotFound } from 'json-rpc-peer'

import * as methods from '../api'
import * as sensitiveValues from '../sensitive-values'
import { noop, serializeError } from '../utils'

import * as errors from 'xo-common/api-errors'

// ===================================================================

const log = createLogger('xo:api')

const PERMISSIONS = {
  none: 0,
  read: 1,
  write: 2,
  admin: 3,
}

// TODO:
// - error when adding a server to a pool with incompatible version
// - error when halted VM migration failure is due to XS < 7
const XAPI_ERROR_TO_XO_ERROR = {
  EHOSTUNREACH: errors.serverUnreachable,
  HOST_OFFLINE: ([host], getId) => errors.hostOffline({ host: getId(host) }),
  NO_HOSTS_AVAILABLE: errors.noHostsAvailable,
  NOT_SUPPORTED_DURING_UPGRADE: errors.notSupportedDuringUpgrade,
  OPERATION_BLOCKED: ([ref, code], getId) =>
    errors.operationBlocked({ objectId: getId(ref), code }),
  PATCH_PRECHECK_FAILED_ISO_MOUNTED: ([patch]) =>
    errors.patchPrecheck({ errorType: 'isoMounted', patch }),
  PIF_VLAN_EXISTS: ([pif], getId) =>
    errors.objectAlreadyExists({ objectId: getId(pif), objectType: 'PIF' }),
  SESSION_AUTHENTICATION_FAILED: errors.authenticationFailed,
  VDI_IN_USE: ([vdi, operation], getId) =>
    errors.vdiInUse({ vdi: getId(vdi), operation }),
  VM_BAD_POWER_STATE: ([vm, expected, actual], getId) =>
    errors.vmBadPowerState({ vm: getId(vm), expected, actual }),
  VM_IS_TEMPLATE: errors.vmIsTemplate,
  VM_LACKS_FEATURE: ([vm], getId) => errors.vmLacksFeature({ vm: getId(vm) }),
  VM_LACKS_FEATURE_SHUTDOWN: ([vm], getId) =>
    errors.vmLacksFeature({ vm: getId(vm), feature: 'shutdown' }),
  VM_MISSING_PV_DRIVERS: ([vm], getId) =>
    errors.vmMissingPvDrivers({ vm: getId(vm) }),
}

const hasPermission = (user, permission) =>
  PERMISSIONS[user.permission] >= PERMISSIONS[permission]

function checkParams(method, params) {
  const schema = method.params
  if (!schema) {
    return
  }

  const result = schemaInspector.validate(
    {
      type: 'object',
      properties: schema,
    },
    params
  )

  if (!result.valid) {
    throw errors.invalidParameters(result.error)
  }
}

function checkPermission(method) {
  /* jshint validthis: true */

  const { permission } = method

  // No requirement.
  if (permission === undefined) {
    return
  }

  const { user } = this
  if (!user) {
    throw errors.unauthorized(permission)
  }

  // The only requirement is login.
  if (!permission) {
    return
  }

  if (!hasPermission(user, permission)) {
    throw errors.unauthorized(permission)
  }
}

async function resolveParams(method, params) {
  const resolve = method.resolve
  if (!resolve) {
    return params
  }

  const { user } = this
  if (!user) {
    throw errors.unauthorized()
  }

  const userId = user.id

  // Do not alter the original object.
  params = { ...params }

  const permissions = []
  forEach(resolve, ([param, types, permission = 'administrate'], key) => {
    const id = params[param]
    if (id === undefined) {
      return
    }

    const object = this.getObject(id, types)

    // This parameter has been handled, remove it.
    delete params[param]

    // Register this new value.
    params[key] = object

    // Permission default to 'administrate' but can be set to a falsy
    // value (except null or undefined which trigger the default
    // value) to simply do a resolve without checking any permissions.
    if (permission) {
      permissions.push([object.id, permission])
    }
  })

  await this.checkPermissions(userId, permissions)

  return params
}

// -------------------------------------------------------------------

export default class Api {
  constructor(xo) {
    this._logger = null
    this._methods = { __proto__: null }
    this._xo = xo

    this.addApiMethods(methods)
    xo.on('start', async () => {
      this._logger = await xo.getLogger('api')
    })
  }

  get apiMethods() {
    return this._methods
  }

  addApiMethod(name, method) {
    const methods = this._methods

    if (name in methods) {
      throw new Error(`API method ${name} already exists`)
    }

    methods[name] = method

    let remove = () => {
      delete methods[name]
      remove = noop
    }
    return () => remove()
  }

  addApiMethods(methods) {
    let base = ''
    const removes = []

    const addMethod = (method, name) => {
      name = base + name

      if (isFunction(method)) {
        removes.push(this.addApiMethod(name, method))
        return
      }

      const oldBase = base
      base = name + '.'
      forEach(method, addMethod)
      base = oldBase
    }

    try {
      forEach(methods, addMethod)
    } catch (error) {
      // Remove all added methods.
      forEach(removes, remove => remove())

      // Forward the error
      throw error
    }

    let remove = () => {
      forEach(removes, remove => remove())
      remove = noop
    }
    return remove
  }

  async callApiMethod(session, name, params = {}) {
    const xo = this._xo
    const startTime = Date.now()

    const method = this._methods[name]
    if (!method) {
      throw new MethodNotFound(name)
    }

    // FIXME: it can cause issues if there any property assignments in
    // XO methods called from the API.
    const context = Object.create(xo, {
      api: {
        // Used by system.*().
        value: this,
      },
      session: {
        value: session,
      },
    })

    // Fetch and inject the current user.
    const userId = session.get('user_id', undefined)
    context.user = userId && (await xo.getUser(userId))
    const userName = context.user ? context.user.email : '(unknown user)'

    const data = {
      userId,
      method: name,
      params: sensitiveValues.replace(params, '* obfuscated *'),
    }

    const callId = Math.random()
      .toString(36)
      .slice(2)

    xo.emit('xo:preCall', {
      ...data,
      callId,
    })

    try {
      await checkPermission.call(context, method)

      // API methods are in a namespace.
      // Some methods use the namespace or an id parameter like:
      //
      // vm.detachPci vm=<string>
      // vm.ejectCd id=<string>
      //
      // The goal here is to standardize the calls by always providing
      // an id parameter when possible to simplify calls to the API.
      if (params != null && params.id === undefined) {
        const namespace = name.slice(0, name.indexOf('.'))
        const id = params[namespace]
        if (typeof id === 'string') {
          params.id = id
        }
      }

      checkParams.call(context, method, params)

      const resolvedParams = await resolveParams.call(context, method, params)

      let result = await method.call(context, resolvedParams)

      // If nothing was returned, consider this operation a success
      // and return true.
      if (result === undefined) {
        result = true
      }

      log.debug(
        `${userName} | ${name}(...) [${ms(
          Date.now() - startTime
        )}] ==> ${kindOf(result)}`
      )

      xo.emit('xo:postCall', {
        callId,
        method: name,
        result,
      })

      return result
    } catch (error) {
      const serializedError = serializeError(error)

      xo.emit('xo:postCall', {
        callId,
        error: serializedError,
        method: name,
      })

      const message = `${userName} | ${name}(${JSON.stringify(
        data.params
      )}) [${ms(Date.now() - startTime)}] =!> ${error}`

      this._logger.error(message, {
        ...data,
        duration: Date.now() - startTime,
        error: serializedError,
      })

      if (xo._config.verboseLogsOnErrors) {
        log.warn(message, { error })
      } else {
        log.warn(
          `${userName} | ${name}(...) [${ms(
            Date.now() - startTime
          )}] =!> ${error}`
        )
      }

      const xoError = XAPI_ERROR_TO_XO_ERROR[error.code]
      if (xoError) {
        throw xoError(error.params, ref => {
          try {
            return xo.getObject(ref).id
          } catch (e) {
            return ref
          }
        })
      }

      throw error
    }
  }
}
<|endoftext|>