<commit_before>'use strict';
var config = require('../../configs');

var http = require('http');

/**
 *The AlexaSkill Module that has the AlexaSkill prototype and helper functions
 */

var apiKey=config.getAPIKey();
var i=0,j=0;
/**
 * This function will return the train status as a string.
 * Sample output : Train departed from KARUKKUTTY(KUC) and late by 24 minutes.
 */
exports.getJsonLiveStatus= function (train_no,doj,eventCallback){

    var url =config.getBaseUrl()+"live/train/"+train_no+"/doj/"+doj+"/apikey/"+apiKey+"/";
    var state="";
    var status= "";
    http.get(url, function(res) {
        var body = '';

        res.on('data', function (chunk) {
            body += chunk;
        });

        res.on('end', function () {
            var stringResult = JSON.parse(body);
            var status=stringResult.position;
            if(stringResult.response_code!='200')
                    status="There was an error processing your request.";
            var result={speech:status,status:stringResult.position,heading:'Train Number: '+train_no};
            eventCallback(result);

        });
    }).on('error', function (e) {
        console.log("Got error: ", e);
        status= "Sorry, we could not process your request.";

        var result={speech:status,status:status,heading:null};
        eventCallback(result);
    });
   return status;
}

/**
 * This function will return the stations in a route as a string.
 * Sample output : KANYAKUMARI,Source,14:10,1,  NAGARCOIL JN,14:30,14:35,1,  KULITTHURAI,15:14,15:15,1,  TRIVANDRUM CNTL,15:55,16:00,1,  KOLLAM JN,17:00,17:05,1,  KAYANKULAM,17:34,17:36,1 (Station     **name,Arriavl time,Departure time,Day of arrival.)
 */
exports.getJsonTrainRoute=function (train_no,eventCallback){

    var url =config.getBaseUrl()+'route/train/'+train_no+'/apikey/'+ apiKey+'/';
       var station_string="";
    var state = "";
    http.get(url, function(res) {
        var body = '';

        res.on('data', function (chunk) {
            body += chunk;
        });

        res.on('end', function () {
            var stringResult = JSON.parse(body);
            var station_names=[];
            var station_arrival=[];
            var station_dep=[];
            var day=[];
            for (i=0; i<stringResult.route.length; i++){
                station_names[i]=stringResult.route[i].fullname;
                station_arrival[i]=stringResult.route[i].scharr;
                station_dep[i]=stringResult.route[i].schdep;
                day[i]=stringResult.route[i].day;
                }
             var m=0;
            for (j=0; j<i; j++){
               m=j+1;
               station_string = station_string + "Station"+m+ '. ';
               station_string=station_string+station_names[j]+",Arrives at : "+station_arrival[j]+",Departs at :"+station_dep[j]+",on day "+day[j] +".  ";

               }
               if(stringResult.response_code!='200')
                    station_string="There was an error processing your request.";
            var result={speech:station_string,status:station_string,heading:"Route of Train Number: "+train_no};
            eventCallback(result);

        });
    }).on('error', function (e) {
        console.log("Got error: ", e);
        status= "Sorry, we could not process your request.";
        var result={speech:status,status:status,heading:null};
        eventCallback(result);
    });
}

/**
 * This function will return the seat availability as string.
 * Sample output : AVAILABLE 11

*/
exports.getJsonSeatAvailability = function (train_no, source, dest, date, _class, quota,eventCallback){
    var url =config.getBaseUrl() +'/check_seat/train/'+train_no+'/source/'+ source +'/dest/'+dest+'/date/'+ date+'/class/'+_class+'/quota/'+quota+'/apikey/'+ apiKey+'/';
    var status = "";
      http.get(url, function(res) {
        var body = '';

        res.on('data', function (chunk) {
            body += chunk;
        });

        res.on('end', function () {
            var stringResult = JSON.parse(body);
            var index3=0;
            var index4=0;
            var status=stringResult.availability[0].status;
            if(status.charAt(0)=='G'&&status.charAt(1)=='N'&&status.charAt(2)=='W')
               {
                 index3=status.indexOf("/WL");
                 index4=status.indexOf("\n",index3);
                 var waiting=status.substring(index3+3,index3+8);
                 var status=waiting+" seats are in waiting list.";
                }
                if(stringResult.response_code!='200')
                    status="There was an error processing your request.";
             var result={speech:status,status:status,heading:"Seat availability of Train: "+train_no};
             eventCallback(result);
        });
    }).on('error', function (e) {
             var result={speech:"Sorry, we could not process your request.",status:"Sorry, we could not process your request.",heading:null};
             eventCallback(result);
    });
}

/**
 * This function will return the train numbers of trains between stations as a string.
 * Sample output : 12617  16382  16346  19577  16334

*/
exports.getJsonTrainBtw =function (source, dest, date, eventCallback){
         var url=config.getBaseUrl()+"between/source/"+source+"/dest/"+dest+"/date/"+date+"/apikey/"+apiKey+"/";
         var stat="";
         var train_string="";
        http.get(url, function(res) {
        var body = '';

        res.on('data', function (chunk) {
            body += chunk;
        });

        res.on('end', function () {
            var stringResult = JSON.parse(body);
            var train_no=[];
            var src_departure_time=[];
            var dest_arrival_time=[];
            var days=[];
            var index=0;
            for(i=0; i<stringResult["train"].length; i++){
                days[i]="";
                train_no[i]=stringResult["train"][i].number;
                src_departure_time[i]=stringResult["train"][i].src_departure_time;
                dest_arrival_time[i]=stringResult["train"][i].dest_arrival_time;
                for  (j=0; j<stringResult["train"][i]["days"].length; j++){
                        if(stringResult["train"][i]["days"][j].runs==="Y")
                          {
                            switch(j){
                               case 0: days[i]=days[i]+"monday, ";
                                       break;
                               case 1: days[i]=days[i]+"tuesday, ";
                                       break;
                               case 2: days[i]=days[i]+"wednesday, ";
                                       break;
                               case 3: days[i]=days[i]+"thursday, ";
                                       break;
                               case 4: days[i]=days[i]+"friday, ";
                                       break;
                               case 5: days[i]=days[i]+"saturday, ";
                                       break;
                               case 6: days[i]=days[i]+"sunday, ";
                                       break;
                              }
                            }
                     }
              }
             stat= stat+ "Total "+i+" trains are between "+source+ " and "+dest+.";
             var m=0;
            for (j=0; j<i; j++){
               m=j+1;
               if(m<=3)
                 {
                  stat = stat + "Train "+m+ '.';
                   stat = stat+" Source departure time :"+src_departure_time[j]+", Destination arrival time "+dest_arrival_time[j]+", Days of run "+days[j]+".";
                   }
               train_string = train_string + "Train "+m+ '\n';
               train_string =train_string+" Source departure time :"+src_departure_time[j]+"\n Destination arrival time "+dest_arrival_time[j]+"\n Days of run "+days[j]+"\n";

               }

               if(stringResult.response_code!='200'){
                    train_string="There was an error processing your request.";
                    stat =train_string;}
             stat=stat+ "For details of all other trains see the result card."
             var result={speech:stat,status:train_string,heading:"Trains running between "+source+" and "+dest};
             eventCallback(result);

        });
    }).on('error', function (e) {
        console.log("Got error: ", e);
        train_string = "sorry, we could not process your request.";
        var result={speech:train_string,status:train_string,heading:null};
        eventCallback(result);
    });
}

exports.getJsonPNRstatus=function (pnr_no, eventCallback){
    var result="";
    var stat="";
      var train_name="Hi";
     var url =config.getBaseUrl() +'pnr_status/pnr/'+pnr_no+'/apikey/'+ apiKey+'/';
    http.get(url, function(res) {
        var body = '';

        res.on('data', function (chunk) {
            body += chunk;
        });

        res.on('end', function () {
           var train_no;
            var doj="";
            var Class="";
            var chart_prepared="";
            var total_passengers;
            var booking_status=[];
            var current_status=[];
            var coach_position =[];
            var stringResult = JSON.parse(body);

            train_no= stringResult.train_no;
            doj= stringResult.doj;
            Class= stringResult.class;
            chart_prepared= stringResult.chart_prepared;
            total_passengers= stringResult.total_passengers;
           result= result+"Starting date is "+doj+"\n Class is "+Class+"\n Chart prepared "+chart_prepared+"\n Total number of passengers "+total_passengers+"\n Details of each passengers\n";
           result= result+"Starting date is "+doj+". Class is "+Class+". Chart prepared "+chart_prepared+". Total number of passengers "+total_passengers+". Details of each passengers.";

            for ( i=0; i<stringResult["passengers"].length; i++){
               booking_status[i]=stringResult["passengers"][i].booking_status;
               current_status[i]=stringResult["passengers"][i].current_status;
               coach_position[i]=stringResult["passengers"][i].coach_position;

              }
             var m=0;
            for (j=0; j<i; j++){
               m=j+1;
               result = result + "passenger "+m+ '\n';
               result=result+" Booking status "+booking_status[j]+"\n Current status "+current_status[j]+"\n Coach position "+coach_position[j]+"\n";
               stat=stat+" passenger "+m+", current status is "+current_status[j]+".";
               }

               stat=stat+" For further details see the result card.";
                if(stringResult.response_code=='410'){
                    result="PNR does not exist.";
                    stat=result;
                   }
            var result1={speech:stat,status:result,heading:"PNR status of: "+pnr_no};

            eventCallback(result1);

        });
    }).on('error', function (e) {
        console.log("Got error: ", e);
         result = "sorry, we could not process your request.";
        var result1={speech:result,status:result,heading:null};
        eventCallback(result1);
    });
}

exports.getJsonTrainArrivals=function (station_code,hrs, eventCallback){
    var result="";
    var url =config.getBaseUrl() +'arrivals/station/'+station_code+'/hours/'+hrs+'/apikey/'+ apiKey+'/';

    http.get(url, function(res) {
        var body = '';

        res.on('data', function (chunk) {
            body += chunk;
        });

        res.on('end', function () {
           var train_no=[];
            var actdep=[];
            var delaydep=[];
            var scharr=[];
            var delayarr=[];
            var schdep =[];
            var stringResult = JSON.parse(body);
             for (i=0; i<stringResult["train"].length; i++){
                train_no[i]=stringResult["train"][i].number;
                scharr[i]=stringResult["train"][i].scharr;
                delayarr[i]=stringResult["train"][i].delayarr;
                schdep[i]=stringResult["train"][i].schdep;
                actdep[i]=stringResult["train"][i].actdep;
                delaydep[i]=stringResult["train"][i].delaydep;

              }
             var m=0;
            for ( j=0; j<i; j++){
               m=j+1;
               result = result + "Train "+m+ '.';
               result=result+", Scheduled arrival "+scharr[j]+", Delayed arrival "+delayarr[j]+", Scheduled departure "+schdep[j]+", actual departure "+actdep[j]+", delayed departure "+delaydep[j]+".";

               }
               if(stringResult.response_code!='200')
                    result="There was an error processing your request.";
            var result1={speech:result,status:result,heading:"Train arrivals at station: "+station_code};
            eventCallback(result1);

        });
    }).on('error', function (e) {
        console.log("Got error: ", e);
         result = "sorry, we could not process your request.";
         var result1={speech:result,status:result,heading:null};
         eventCallback(result1);
    });
}

/*exports.getJsonTrainName = function (train_no){
    var state="";
    var result="";
    var url =baseUrl +'/name_number/train/'+train_no+'/apikey/'+ apikey+'/';
    http.get(url, function(res) {
        var body = '';

        res.on('data', function (chunk) {
            body += chunk;
        });

        res.on('end', function () {
            var stringResult = JSON.stringify(body);
            var index=0;
            var index2=0
            var train_numbers=[];
            var train_string="";
            var index=stringResult.indexOf("name")
            var index1=stringResult.indexOf("}",index)
            var name =stringResult.substring(index+10,index1-8);
            result=name;
            if(stringResult.indexOf("error") > -1) {
                 state = "error";
                }
            else
                 state = "success";

        });
    }).on('error', function (e) {
        state = "error";
    });
}

function numberToName(train_no,eventCallback){
    var baseURL="http://api.railwayapi.com";
    var apiKey="bkxel1825";
    var state="";
    var result="";
    var url =baseURL +'/name_number/train/'+train_no+'/apikey/'+ apiKey+'/';
    http.get(url, function(res) {
        var body = '';

        res.on('data', function (chunk) {
            body += chunk;
        });

        res.on('end', function () {
            var stringResult = JSON.parse(body);
            eventCallback(stringResult.train.name);

        });
    }).on('error', function (e) {
        state = "error";
    });
}
*/

<commit_msg>Bug in apiParser file fixed. Unxexpected . error<commit_after>'use strict';
var config = require('../../configs');

var http = require('http');

/**
 *The AlexaSkill Module that has the AlexaSkill prototype and helper functions
 */

var apiKey=config.getAPIKey();
var i=0,j=0;
/**
 * This function will return the train status as a string.
 * Sample output : Train departed from KARUKKUTTY(KUC) and late by 24 minutes.
 */
exports.getJsonLiveStatus= function (train_no,doj,eventCallback){

    var url =config.getBaseUrl()+"live/train/"+train_no+"/doj/"+doj+"/apikey/"+apiKey+"/";
    var state="";
    var status= "";
    http.get(url, function(res) {
        var body = '';

        res.on('data', function (chunk) {
            body += chunk;
        });

        res.on('end', function () {
            var stringResult = JSON.parse(body);
            var status=stringResult.position;
            if(stringResult.response_code!='200')
                    status="There was an error processing your request.";
            var result={speech:status,status:stringResult.position,heading:'Train Number: '+train_no};
            eventCallback(result);

        });
    }).on('error', function (e) {
        console.log("Got error: ", e);
        status= "Sorry, we could not process your request.";

        var result={speech:status,status:status,heading:null};
        eventCallback(result);
    });
   return status;
}

/**
 * This function will return the stations in a route as a string.
 * Sample output : KANYAKUMARI,Source,14:10,1,  NAGARCOIL JN,14:30,14:35,1,  KULITTHURAI,15:14,15:15,1,  TRIVANDRUM CNTL,15:55,16:00,1,  KOLLAM JN,17:00,17:05,1,  KAYANKULAM,17:34,17:36,1 (Station     **name,Arriavl time,Departure time,Day of arrival.)
 */
exports.getJsonTrainRoute=function (train_no,eventCallback){

    var url =config.getBaseUrl()+'route/train/'+train_no+'/apikey/'+ apiKey+'/';
       var station_string="";
    var state = "";
    http.get(url, function(res) {
        var body = '';

        res.on('data', function (chunk) {
            body += chunk;
        });

        res.on('end', function () {
            var stringResult = JSON.parse(body);
            var station_names=[];
            var station_arrival=[];
            var station_dep=[];
            var day=[];
            for (i=0; i<stringResult.route.length; i++){
                station_names[i]=stringResult.route[i].fullname;
                station_arrival[i]=stringResult.route[i].scharr;
                station_dep[i]=stringResult.route[i].schdep;
                day[i]=stringResult.route[i].day;
                }
             var m=0;
            for (j=0; j<i; j++){
               m=j+1;
               station_string = station_string + "Station"+m+ '. ';
               station_string=station_string+station_names[j]+",Arrives at : "+station_arrival[j]+",Departs at :"+station_dep[j]+",on day "+day[j] +".  ";

               }
               if(stringResult.response_code!='200')
                    station_string="There was an error processing your request.";
            var result={speech:station_string,status:station_string,heading:"Route of Train Number: "+train_no};
            eventCallback(result);

        });
    }).on('error', function (e) {
        console.log("Got error: ", e);
        status= "Sorry, we could not process your request.";
        var result={speech:status,status:status,heading:null};
        eventCallback(result);
    });
}

/**
 * This function will return the seat availability as string.
 * Sample output : AVAILABLE 11

*/
exports.getJsonSeatAvailability = function (train_no, source, dest, date, _class, quota,eventCallback){
    var url =config.getBaseUrl() +'/check_seat/train/'+train_no+'/source/'+ source +'/dest/'+dest+'/date/'+ date+'/class/'+_class+'/quota/'+quota+'/apikey/'+ apiKey+'/';
    var status = "";
      http.get(url, function(res) {
        var body = '';

        res.on('data', function (chunk) {
            body += chunk;
        });

        res.on('end', function () {
            var stringResult = JSON.parse(body);
            var index3=0;
            var index4=0;
            var status=stringResult.availability[0].status;
            if(status.charAt(0)=='G'&&status.charAt(1)=='N'&&status.charAt(2)=='W')
               {
                 index3=status.indexOf("/WL");
                 index4=status.indexOf("\n",index3);
                 var waiting=status.substring(index3+3,index3+8);
                 var status=waiting+" seats are in waiting list.";
                }
                if(stringResult.response_code!='200')
                    status="There was an error processing your request.";
             var result={speech:status,status:status,heading:"Seat availability of Train: "+train_no};
             eventCallback(result);
        });
    }).on('error', function (e) {
             var result={speech:"Sorry, we could not process your request.",status:"Sorry, we could not process your request.",heading:null};
             eventCallback(result);
    });
}

/**
 * This function will return the train numbers of trains between stations as a string.
 * Sample output : 12617  16382  16346  19577  16334

*/
exports.getJsonTrainBtw =function (source, dest, date, eventCallback){
         var url=config.getBaseUrl()+"between/source/"+source+"/dest/"+dest+"/date/"+date+"/apikey/"+apiKey+"/";
         var stat="";
         var train_string="";
        http.get(url, function(res) {
        var body = '';

        res.on('data', function (chunk) {
            body += chunk;
        });

        res.on('end', function () {
            var stringResult = JSON.parse(body);
            var train_no=[];
            var src_departure_time=[];
            var dest_arrival_time=[];
            var days=[];
            var index=0;
            for(i=0; i<stringResult["train"].length; i++){
                days[i]="";
                train_no[i]=stringResult["train"][i].number;
                src_departure_time[i]=stringResult["train"][i].src_departure_time;
                dest_arrival_time[i]=stringResult["train"][i].dest_arrival_time;
                for  (j=0; j<stringResult["train"][i]["days"].length; j++){
                        if(stringResult["train"][i]["days"][j].runs==="Y")
                          {
                            switch(j){
                               case 0: days[i]=days[i]+"monday, ";
                                       break;
                               case 1: days[i]=days[i]+"tuesday, ";
                                       break;
                               case 2: days[i]=days[i]+"wednesday, ";
                                       break;
                               case 3: days[i]=days[i]+"thursday, ";
                                       break;
                               case 4: days[i]=days[i]+"friday, ";
                                       break;
                               case 5: days[i]=days[i]+"saturday, ";
                                       break;
                               case 6: days[i]=days[i]+"sunday, ";
                                       break;
                              }
                            }
                     }
              }
             stat= stat+ "Total "+i+" trains are between "+source+ " and "+dest+"";
             var m=0;
            for (j=0; j<i; j++){
               m=j+1;
               if(m<=3)
                 {
                  stat = stat + "Train "+m+ '.';
                   stat = stat+" Source departure time :"+src_departure_time[j]+", Destination arrival time "+dest_arrival_time[j]+", Days of run "+days[j]+".";
                   }
               train_string = train_string + "Train "+m+ '\n';
               train_string =train_string+" Source departure time :"+src_departure_time[j]+"\n Destination arrival time "+dest_arrival_time[j]+"\n Days of run "+days[j]+"\n";

               }

               if(stringResult.response_code!='200'){
                    train_string="There was an error processing your request.";
                    stat =train_string;}
             stat=stat+ "For details of all other trains see the result card."
             var result={speech:stat,status:train_string,heading:"Trains running between "+source+" and "+dest};
             eventCallback(result);

        });
    }).on('error', function (e) {
        console.log("Got error: ", e);
        train_string = "sorry, we could not process your request.";
        var result={speech:train_string,status:train_string,heading:null};
        eventCallback(result);
    });
}

exports.getJsonPNRstatus=function (pnr_no, eventCallback){
    var result="";
    var stat="";
      var train_name="Hi";
     var url =config.getBaseUrl() +'pnr_status/pnr/'+pnr_no+'/apikey/'+ apiKey+'/';
    http.get(url, function(res) {
        var body = '';

        res.on('data', function (chunk) {
            body += chunk;
        });

        res.on('end', function () {
           var train_no;
            var doj="";
            var Class="";
            var chart_prepared="";
            var total_passengers;
            var booking_status=[];
            var current_status=[];
            var coach_position =[];
            var stringResult = JSON.parse(body);

            train_no= stringResult.train_no;
            doj= stringResult.doj;
            Class= stringResult.class;
            chart_prepared= stringResult.chart_prepared;
            total_passengers= stringResult.total_passengers;
           result= result+"Starting date is "+doj+"\n Class is "+Class+"\n Chart prepared "+chart_prepared+"\n Total number of passengers "+total_passengers+"\n Details of each passengers\n";
           result= result+"Starting date is "+doj+". Class is "+Class+". Chart prepared "+chart_prepared+". Total number of passengers "+total_passengers+". Details of each passengers.";

            for ( i=0; i<stringResult["passengers"].length; i++){
               booking_status[i]=stringResult["passengers"][i].booking_status;
               current_status[i]=stringResult["passengers"][i].current_status;
               coach_position[i]=stringResult["passengers"][i].coach_position;

              }
             var m=0;
            for (j=0; j<i; j++){
               m=j+1;
               result = result + "passenger "+m+ '\n';
               result=result+" Booking status "+booking_status[j]+"\n Current status "+current_status[j]+"\n Coach position "+coach_position[j]+"\n";
               stat=stat+" passenger "+m+", current status is "+current_status[j]+".";
               }

               stat=stat+" For further details see the result card.";
                if(stringResult.response_code=='410'){
                    result="PNR does not exist.";
                    stat=result;
                   }
            var result1={speech:stat,status:result,heading:"PNR status of: "+pnr_no};

            eventCallback(result1);

        });
    }).on('error', function (e) {
        console.log("Got error: ", e);
         result = "sorry, we could not process your request.";
        var result1={speech:result,status:result,heading:null};
        eventCallback(result1);
    });
}

exports.getJsonTrainArrivals=function (station_code,hrs, eventCallback){
    var result="";
    var url =config.getBaseUrl() +'arrivals/station/'+station_code+'/hours/'+hrs+'/apikey/'+ apiKey+'/';

    http.get(url, function(res) {
        var body = '';

        res.on('data', function (chunk) {
            body += chunk;
        });

        res.on('end', function () {
           var train_no=[];
            var actdep=[];
            var delaydep=[];
            var scharr=[];
            var delayarr=[];
            var schdep =[];
            var stringResult = JSON.parse(body);
             for (i=0; i<stringResult["train"].length; i++){
                train_no[i]=stringResult["train"][i].number;
                scharr[i]=stringResult["train"][i].scharr;
                delayarr[i]=stringResult["train"][i].delayarr;
                schdep[i]=stringResult["train"][i].schdep;
                actdep[i]=stringResult["train"][i].actdep;
                delaydep[i]=stringResult["train"][i].delaydep;

              }
             var m=0;
            for ( j=0; j<i; j++){
               m=j+1;
               result = result + "Train "+m+ '.';
               result=result+", Scheduled arrival "+scharr[j]+", Delayed arrival "+delayarr[j]+", Scheduled departure "+schdep[j]+", actual departure "+actdep[j]+", delayed departure "+delaydep[j]+".";

               }
               if(stringResult.response_code!='200')
                    result="There was an error processing your request.";
            var result1={speech:result,status:result,heading:"Train arrivals at station: "+station_code};
            eventCallback(result1);

        });
    }).on('error', function (e) {
        console.log("Got error: ", e);
         result = "sorry, we could not process your request.";
         var result1={speech:result,status:result,heading:null};
         eventCallback(result1);
    });
}

/*exports.getJsonTrainName = function (train_no){
    var state="";
    var result="";
    var url =baseUrl +'/name_number/train/'+train_no+'/apikey/'+ apikey+'/';
    http.get(url, function(res) {
        var body = '';

        res.on('data', function (chunk) {
            body += chunk;
        });

        res.on('end', function () {
            var stringResult = JSON.stringify(body);
            var index=0;
            var index2=0
            var train_numbers=[];
            var train_string="";
            var index=stringResult.indexOf("name")
            var index1=stringResult.indexOf("}",index)
            var name =stringResult.substring(index+10,index1-8);
            result=name;
            if(stringResult.indexOf("error") > -1) {
                 state = "error";
                }
            else
                 state = "success";

        });
    }).on('error', function (e) {
        state = "error";
    });
}

function numberToName(train_no,eventCallback){
    var baseURL="http://api.railwayapi.com";
    var apiKey="bkxel1825";
    var state="";
    var result="";
    var url =baseURL +'/name_number/train/'+train_no+'/apikey/'+ apiKey+'/';
    http.get(url, function(res) {
        var body = '';

        res.on('data', function (chunk) {
            body += chunk;
        });

        res.on('end', function () {
            var stringResult = JSON.parse(body);
            eventCallback(stringResult.train.name);

        });
    }).on('error', function (e) {
        state = "error";
    });
}
*/

<|endoftext|>


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

import GeoJSONFormat from 'ol/format/GeoJSON';
import WFSFormat from 'ol/format/WFS';

import * as ol_filters from 'ol/format/filter';
import * as proj from 'ol/proj';

import {jsonToFeature} from '../../../util';

function chainFilters(operator, filters) {
    let chained_filters = null;
    if(filters.length > 1) {
        chained_filters = operator(filters[0], filters[1]);
        for(let i = 2, ii = filters.length; i < ii; i++) {
            chained_filters = operator(chained_filters, filters[i]);
        }
    } else {
        chained_filters = filters[0];
    }
    return chained_filters;
}

// map the functions from OpenLayers to the internal
//  types
const FILTER_MAPPING = {
    'like': ol_filters.like,
    'ilike': function(name, value) {
        return ol_filters.like(name, value, '%', '_', '\\', false);
    },
    'eq': ol_filters.equalTo,
    'ge': ol_filters.greaterThanOrEqualTo,
    'gt': ol_filters.greaterThan,
    'le': ol_filters.lessThanOrEqualTo,
    'lt': ol_filters.lessThan
};

const FILTER_OPERATORS = {
    'and': ol_filters.and,
    'or': ol_filters.or,
};

const mapFilters = fields => {
    return fields.map(field => {
        if (Array.isArray(field)) {
            return chainFilters(FILTER_OPERATORS[field[0]], mapFilters(field.slice(1)));
        } else {
            return FILTER_MAPPING[field.comparitor](field.name, field.value);
        }
    });
};

const getTypeName = mapSource => {
    let typename = mapSource.params.typename;
    if (mapSource.config && mapSource.config.typename) {
        typename = mapSource.config.typename;
    }
    return typename;
}

const getGeometryName = mapSource => (
    (mapSource.config && mapSource.config['geometry-name']) ?
        mapSource.config['geometry-name'] : 'geom'
);

export function buildWfsQuery(query, mapSource, mapProjection, outputFormat) {
    const geom_field = getGeometryName(mapSource);

    // the internal storage mechanism requires features
    //  returned from the query be stored in 4326 and then
    //  reprojected on render.
    let query_projection = mapProjection;
    if(mapSource.wgs84Hack) {
        query_projection = new proj.get('EPSG:4326');
    }

    const filters = [];
    if(query.selection && query.selection.length > 0) {
        const geoFilters = query.selection.map(selectionFeature => {
            // convert the geojson geometry into a ol geometry.
            const ol_geom = (new GeoJSONFormat()).readGeometry(selectionFeature.geometry);
            // convert the geometry to the query projection
            ol_geom.transform(mapProjection, query_projection);

            // add the intersection filter to the filter stack.
            return ol_filters.intersects(geom_field, ol_geom);
        });

        if (geoFilters.length === 1) {
            filters.push(geoFilters[0]);
        } else {
            filters.push(chainFilters(FILTER_OPERATORS.or, geoFilters));
        }
    }

    mapFilters(query.fields).forEach(f => filters.push(f));

    // when multiple filters are set then they need to be
    //  chained together to create the compound filter.
    let chained_filters = null;
    if(filters.length > 1) {
        chained_filters = chainFilters(FILTER_OPERATORS.and, filters);
    } else {
        chained_filters = filters[0];
    }

    // the OL formatter requires that the typename and the schema be
    //  broken apart in order to properly format the request.
    const typename = getTypeName(mapSource);

    const type_parts = typename.split(':');
    const format_options = {
        srsName: query_projection.getCode(),
        featurePrefix: type_parts[0],
        featureTypes: [type_parts[1]],
        outputFormat,
        filter: chained_filters,
    };

    const feature_request = new WFSFormat().writeGetFeature(format_options);

    return (new XMLSerializer().serializeToString(feature_request));
}

export function wfsSaveFeatures(mapSource, mapProjection, inFeatures) {
    const format = new WFSFormat();
    const typename = getTypeName(mapSource);
    const typeParts = typename.split(':');
    const options = {
        featureType: typeParts[1],
        featureNS: 'http://geomoose.org',
        featurePrefix: typeParts[0],
        srsName: 'EPSG:3857',
    };

    const features = inFeatures.map(f => jsonToFeature(f));

    const geomFieldName = getGeometryName(mapSource);
    features.forEach(f => f.setGeometryName(geomFieldName));

    // writeTransaction (inserts, updates, deletes, options)
    const transaction = format.writeTransaction([], features, [], options);
    return (new XMLSerializer().serializeToString(transaction));
}
<commit_msg>Support alternate projections for WFS-T posts.<commit_after>/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2019-present Dan "Ducky" Little
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

import GeoJSONFormat from 'ol/format/GeoJSON';
import WFSFormat from 'ol/format/WFS';

import * as ol_filters from 'ol/format/filter';
import * as proj from 'ol/proj';

import {jsonToFeature} from '../../../util';

function chainFilters(operator, filters) {
    let chained_filters = null;
    if(filters.length > 1) {
        chained_filters = operator(filters[0], filters[1]);
        for(let i = 2, ii = filters.length; i < ii; i++) {
            chained_filters = operator(chained_filters, filters[i]);
        }
    } else {
        chained_filters = filters[0];
    }
    return chained_filters;
}

// map the functions from OpenLayers to the internal
//  types
const FILTER_MAPPING = {
    'like': ol_filters.like,
    'ilike': function(name, value) {
        return ol_filters.like(name, value, '%', '_', '\\', false);
    },
    'eq': ol_filters.equalTo,
    'ge': ol_filters.greaterThanOrEqualTo,
    'gt': ol_filters.greaterThan,
    'le': ol_filters.lessThanOrEqualTo,
    'lt': ol_filters.lessThan
};

const FILTER_OPERATORS = {
    'and': ol_filters.and,
    'or': ol_filters.or,
};

const mapFilters = fields => {
    return fields.map(field => {
        if (Array.isArray(field)) {
            return chainFilters(FILTER_OPERATORS[field[0]], mapFilters(field.slice(1)));
        } else {
            return FILTER_MAPPING[field.comparitor](field.name, field.value);
        }
    });
};

const getTypeName = mapSource => {
    let typename = mapSource.params.typename;
    if (mapSource.config && mapSource.config.typename) {
        typename = mapSource.config.typename;
    }
    return typename;
}

const getGeometryName = mapSource => (
    (mapSource.config && mapSource.config['geometry-name']) ?
        mapSource.config['geometry-name'] : 'geom'
);

export function buildWfsQuery(query, mapSource, mapProjection, outputFormat) {
    const geom_field = getGeometryName(mapSource);

    // the internal storage mechanism requires features
    //  returned from the query be stored in 4326 and then
    //  reprojected on render.
    let query_projection = mapProjection;
    if(mapSource.wgs84Hack) {
        query_projection = new proj.get('EPSG:4326');
    }

    const filters = [];
    if(query.selection && query.selection.length > 0) {
        const geoFilters = query.selection.map(selectionFeature => {
            // convert the geojson geometry into a ol geometry.
            const ol_geom = (new GeoJSONFormat()).readGeometry(selectionFeature.geometry);
            // convert the geometry to the query projection
            ol_geom.transform(mapProjection, query_projection);

            // add the intersection filter to the filter stack.
            return ol_filters.intersects(geom_field, ol_geom);
        });

        if (geoFilters.length === 1) {
            filters.push(geoFilters[0]);
        } else {
            filters.push(chainFilters(FILTER_OPERATORS.or, geoFilters));
        }
    }

    mapFilters(query.fields).forEach(f => filters.push(f));

    // when multiple filters are set then they need to be
    //  chained together to create the compound filter.
    let chained_filters = null;
    if(filters.length > 1) {
        chained_filters = chainFilters(FILTER_OPERATORS.and, filters);
    } else {
        chained_filters = filters[0];
    }

    // the OL formatter requires that the typename and the schema be
    //  broken apart in order to properly format the request.
    const typename = getTypeName(mapSource);

    const type_parts = typename.split(':');
    const format_options = {
        srsName: query_projection.getCode(),
        featurePrefix: type_parts[0],
        featureTypes: [type_parts[1]],
        outputFormat,
        filter: chained_filters,
    };

    const feature_request = new WFSFormat().writeGetFeature(format_options);

    return (new XMLSerializer().serializeToString(feature_request));
}

export function wfsSaveFeatures(mapSource, mapProjection, inFeatures) {
    const format = new WFSFormat();
    const config = mapSource.config || {};
    const typename = getTypeName(mapSource);
    const typeParts = typename.split(':');
    const options = {
        featurePrefix: typeParts[0],
        featureType: typeParts[1],
        srsName: config.srs || 'EPSG:3857',
    };

    if (config['namespace-uri']) {
        options.featureNS = config['namespace-uri'];
    }

    const features = inFeatures.map(f => jsonToFeature(f));
    // reproject the features to the layers native SRS
    if (options.srsName !== 'EPSG:3857') {
        features.forEach(f => f.getGeometry().transform('EPSG:3857', options.srsName));
    }

    const geomFieldName = getGeometryName(mapSource);
    features.forEach(f => f.setGeometryName(geomFieldName));

    // writeTransaction (inserts, updates, deletes, options)
    const transaction = format.writeTransaction([], features, [], options);
    return (new XMLSerializer().serializeToString(transaction));
}
<|endoftext|>


<commit_before>import axios from 'axios'
import convert from 'xml-js'
import path from 'path'

export default {
  loadProject: async ({
    dispatch
  }) => {
    // Clear any images in the current vuex state.
    await dispatch('image/clearImages', null, { root: true })

    // Sync
    await dispatch('app/synchroniseAnnotationAndOSDCanvas', null, { root: true })

    // Check for project file, if found then load that project
    try {
      const ext = path.extname(state.projectFilePath)
      const filePath = state.projectFilePath.split(ext)[0]
      const dataLocation = location.origin + '/annotations/' + filePath + '.json'
      const response = await window.fetch(dataLocation)
      const project = await response.json()

      // Load project annotation
      if (project.hasOwnProperty('annotation')) {
        dispatch('annotation/loadAnnotation', project.annotation, { root: true })
      }

      // Load project images
      if (project.hasOwnProperty('images')) {
        project.images.forEach(image => {
          if (image.source.indexOf('.dzi') > -1) {
            dispatch('image/addOSDImage', {
              name: image.name,
              fileType: 'dzi',
              source: image.source,
              function: 'project',
              opacity: 1
            }, { root: true })

            // dispatch('getProjectImageProperties')
          } else {
            dispatch('image/addOSDImage', {
              name: image.name,
              fileType: 'simple',
              source: image.source,
              function: 'project',
              opacity: 1
            }, { root: true })
          }
        })
      }

      // Load project editor configuration
      if (project.hasOwnProperty('editor')) {
        dispatch('app/loadEditorConfig', project.editor, { root: true })
      }
    } catch (err) {
      dispatch('getAnnotation')
      dispatch('getProjectImage')
    }
  },

  async getArrayOfImages ({
    commit
  }) {
    try {
      await axios.post(location.origin + '/checkForImages')
      const pathToImagesFile = location.origin + '/data/images.json'
      const imagesFile = await axios.get(pathToImagesFile)
      commit('setAvailableImages', imagesFile.data)
    } catch (err) {
      console.log(err)
    }
  },

  async saveProject ({
    dispatch,
    rootState
  }) {
    try {
      // Ensure all the paperJS items are included in the vuex state
      await dispatch('annotation/refreshAnnotationState', null, { root: true })

      // Post API request.
      const postUrl = location.origin + '/save'
      await axios.post(
        postUrl,
        {
          editor: {
            activeChannel: rootState.image.activeChannelIndex,
            activeStep: rootState.app.activeStep,
            activeLayer: rootState.app.activeLayer,
            type: rootState.app.type,
            steps: rootState.app.setps
          },
          annotation: rootState.annotation.project,
          images: rootState.image.images
        }
      )

      // Activate notification
      dispatch('app/activateSnackbar', {
        text: 'Saved annotation data',
        color: 'success'
      }, { root: true })

      // Set annotation save state
      dispatch('annotation/setSaveState', {
        changesSaved: true,
        lastSaveTimeStamp: new Date()
      }, { root: true })

    // Handle errors
    } catch (err) {
      try {
        // Save to local browser storage
        window.localStorage.setItem('annotation', JSON.stringify(rootState.annotation.project))

        // Activate notification
        dispatch('app/activateSnackbar', {
          text: 'Could not reach server, data saved in browser',
          color: 'warning'
        }, { root: true })

        // Set annotation save state
        dispatch('annotation/setSaveState', {
          changesSaved: true,
          lastSaveTimeStamp: new Date()
        }, { root: true })
      } catch (err) {
        dispatch('app/activateSnackbar', {
          text: 'Annotation data could not be saved',
          color: 'error'
        }, { root: true })
      }
    }
  },

  async saveAnnotation ({
    state,
    dispatch,
    rootState
  }) {
    try {
      // Ensure all the paperJS items are included in the vuex state
      await dispatch('annotation/refreshAnnotationState', null, { root: true })

      // Post API request.
      const postUrl = location.origin + '/save'
      await axios.post(
        postUrl,
        {
          projectFilePath: state.projectFilePath,
          annotationData: rootState.annotation.project
        }
      )

      // Activate notification
      dispatch('app/activateSnackbar', {
        text: 'Saved annotation data',
        color: 'success'
      }, { root: true })

      // Set annotation save state
      dispatch('annotation/setSaveState', {
        changesSaved: true,
        lastSaveTimeStamp: new Date()
      }, { root: true })

    // Handle errors
    } catch (err) {
      try {
        // Save to local browser storage
        window.localStorage.setItem('annotation', JSON.stringify(rootState.annotation.project))

        // Activate notification
        dispatch('app/activateSnackbar', {
          text: 'Could not reach server, data saved in browser',
          color: 'warning'
        }, { root: true })

        // Set annotation save state
        dispatch('annotation/setSaveState', {
          changesSaved: true,
          lastSaveTimeStamp: new Date()
        }, { root: true })
      } catch (err) {
        dispatch('app/activateSnackbar', {
          text: 'Annotation data could not be saved',
          color: 'error'
        }, { root: true })
      }
    }
  },

  async getAnnotation ({
    state,
    dispatch
  }) {
    const ext = path.extname(state.projectFilePath)
    const filePath = state.projectFilePath.split(ext)[0]
    const dataLocation = location.origin + '/annotations/' + filePath + '.json'
    try {
      const response = await axios.get(dataLocation)
      dispatch('annotation/loadAnnotation', response.data, { root: true })
    } catch (err) {
      console.log('Annotation data either could not be found or could not be loaded')
      console.log('Loading the default empty project')
      dispatch('annotation/loadAnnotation', null, { root: true })
    }
  },

  setProjectFilePath ({
    commit
  }, payload) {
    commit('setProjectFilePath', payload)
  },

  async getProjectImage ({
    state,
    rootState,
    dispatch
  }) {
    // Clear any images in the current vuex state.
    await dispatch('image/clearImages', null, { root: true })

    // Set image type by checking for .dzi in the fileName
    if (state.projectFilePath.indexOf('.dzi') > -1) {
      dispatch('image/addOSDImage', {
        name: rootState.image.projectImageName,
        fileType: 'dzi',
        source: location.origin + '/images/' + state.projectFilePath,
        function: 'project',
        opacity: 1
      }, { root: true })

      // dispatch('getProjectImageProperties')
    } else {
      dispatch('image/addOSDImage', {
        name: rootState.image.projectImageName,
        fileType: 'simple',
        source: location.origin + '/images/' + state.projectFilePath,
        function: 'project',
        opacity: 1
      }, { root: true })
    }
  },

  // Check for a properites file. These files can be created by libvips when
  // building deepZoom images and contain useful metadata about the image.
  async getProjectImageProperties ({
    state,
    dispatch
  }) {
    // Construct path to file
    const propsLocation = location.origin + '/images/' +
                      state.projectFilePath.slice(0, -4) +
                      '_files/vips-properties.xml'

    // Fetch data and convert to jsObject
    const propertiesFile = await fetch(propsLocation)
    const data = JSON.parse(convert.xml2json(
      await propertiesFile.text(),
      { ignoreDeclaration: true,
        compact: true,
        ignoreComment: true,
        alwaysChildren: true,
        attributesKey: 'attributes',
        textKey: 'text'
      }
    ))

    // Extract relevant metrics
    const mppX = Number(data.image.properties.property.find(ele => {
      return ele.name.text === 'openslide.mpp-x'
    }).value.text)

    const mppY = Number(data.image.properties.property.find(ele => {
      return ele.name.text === 'openslide.mpp-y'
    }).value.text)

    // Dispatch methods for saving the scale
    dispatch('image/setPixelScaleFactor', { mppX, mppY }, { root: true })
  }
}
<commit_msg>Enable local backend to define JSON projects, rather than annotation and image separately<commit_after>import axios from 'axios'
import convert from 'xml-js'
import path from 'path'

export default {
  loadProject: async ({
    state,
    dispatch
  }) => {
    // Clear any images in the current vuex state.
    await dispatch('image/clearImages', null, { root: true })

    // Sync
    await dispatch('app/synchroniseAnnotationAndOSDCanvas', null, { root: true })

    // Check for project file, if found then load that project
    try {
      const ext = path.extname(state.projectFilePath)
      const filePath = state.projectFilePath.split(ext)[0]
      const dataLocation = location.origin + '/projects/' + filePath + '.json'
      const response = await window.fetch(dataLocation)
      const project = await response.json()

      // Load project annotation
      if (project.hasOwnProperty('annotation')) {
        dispatch('annotation/loadAnnotation', project.annotation, { root: true })
      }

      // Load project images
      if (project.hasOwnProperty('images')) {
        project.images.forEach(image => {
          if (image.source.indexOf('.dzi') > -1) {
            dispatch('image/addOSDImage', {
              name: image.name,
              fileType: 'dzi',
              source: image.source,
              function: 'project',
              opacity: 1
            }, { root: true })

            // dispatch('getProjectImageProperties')
          } else {
            dispatch('image/addOSDImage', {
              name: image.name,
              fileType: 'simple',
              source: image.source,
              function: 'project',
              opacity: 1
            }, { root: true })
          }
        })
      }

      // Load project editor configuration
      if (project.hasOwnProperty('editor')) {
        dispatch('app/loadEditorConfig', project.editor, { root: true })
      }
    } catch (err) {
      dispatch('getAnnotation')
      dispatch('getProjectImage')
    }
  },

  async getArrayOfImages ({
    commit
  }) {
    try {
      await axios.post(location.origin + '/checkForImages')
      const pathToImagesFile = location.origin + '/data/images.json'
      const imagesFile = await axios.get(pathToImagesFile)
      commit('setAvailableImages', imagesFile.data)
    } catch (err) {
      console.log(err)
    }
  },

  async saveProject ({
    dispatch,
    rootState
  }) {
    try {
      // Ensure all the paperJS items are included in the vuex state
      await dispatch('annotation/refreshAnnotationState', null, { root: true })

      // Post API request.
      const postUrl = location.origin + '/save'
      await axios.post(
        postUrl,
        {
          editor: {
            activeChannel: rootState.image.activeChannelIndex,
            activeStep: rootState.app.activeStep,
            activeLayer: rootState.app.activeLayer,
            type: rootState.app.type,
            steps: rootState.app.setps
          },
          annotation: rootState.annotation.project,
          images: rootState.image.images
        }
      )

      // Activate notification
      dispatch('app/activateSnackbar', {
        text: 'Saved annotation data',
        color: 'success'
      }, { root: true })

      // Set annotation save state
      dispatch('annotation/setSaveState', {
        changesSaved: true,
        lastSaveTimeStamp: new Date()
      }, { root: true })

    // Handle errors
    } catch (err) {
      try {
        // Save to local browser storage
        window.localStorage.setItem('annotation', JSON.stringify(rootState.annotation.project))

        // Activate notification
        dispatch('app/activateSnackbar', {
          text: 'Could not reach server, data saved in browser',
          color: 'warning'
        }, { root: true })

        // Set annotation save state
        dispatch('annotation/setSaveState', {
          changesSaved: true,
          lastSaveTimeStamp: new Date()
        }, { root: true })
      } catch (err) {
        dispatch('app/activateSnackbar', {
          text: 'Annotation data could not be saved',
          color: 'error'
        }, { root: true })
      }
    }
  },

  async saveAnnotation ({
    state,
    dispatch,
    rootState
  }) {
    try {
      // Ensure all the paperJS items are included in the vuex state
      await dispatch('annotation/refreshAnnotationState', null, { root: true })

      // Post API request.
      const postUrl = location.origin + '/save'
      await axios.post(
        postUrl,
        {
          projectFilePath: state.projectFilePath,
          annotationData: rootState.annotation.project
        }
      )

      // Activate notification
      dispatch('app/activateSnackbar', {
        text: 'Saved annotation data',
        color: 'success'
      }, { root: true })

      // Set annotation save state
      dispatch('annotation/setSaveState', {
        changesSaved: true,
        lastSaveTimeStamp: new Date()
      }, { root: true })

    // Handle errors
    } catch (err) {
      try {
        // Save to local browser storage
        window.localStorage.setItem('annotation', JSON.stringify(rootState.annotation.project))

        // Activate notification
        dispatch('app/activateSnackbar', {
          text: 'Could not reach server, data saved in browser',
          color: 'warning'
        }, { root: true })

        // Set annotation save state
        dispatch('annotation/setSaveState', {
          changesSaved: true,
          lastSaveTimeStamp: new Date()
        }, { root: true })
      } catch (err) {
        dispatch('app/activateSnackbar', {
          text: 'Annotation data could not be saved',
          color: 'error'
        }, { root: true })
      }
    }
  },

  async getAnnotation ({
    state,
    dispatch
  }) {
    const ext = path.extname(state.projectFilePath)
    const filePath = state.projectFilePath.split(ext)[0]
    const dataLocation = location.origin + '/annotations/' + filePath + '.json'
    try {
      const response = await axios.get(dataLocation)
      dispatch('annotation/loadAnnotation', response.data, { root: true })
    } catch (err) {
      console.log('Annotation data either could not be found or could not be loaded')
      console.log('Loading the default empty project')
      dispatch('annotation/loadAnnotation', null, { root: true })
    }
  },

  setProjectFilePath ({
    commit
  }, payload) {
    commit('setProjectFilePath', payload)
  },

  async getProjectImage ({
    state,
    rootState,
    dispatch
  }) {
    // Clear any images in the current vuex state.
    await dispatch('image/clearImages', null, { root: true })

    // Set image type by checking for .dzi in the fileName
    if (state.projectFilePath.indexOf('.dzi') > -1) {
      dispatch('image/addOSDImage', {
        name: rootState.image.projectImageName,
        fileType: 'dzi',
        source: location.origin + '/images/' + state.projectFilePath,
        function: 'project',
        opacity: 1
      }, { root: true })

      // dispatch('getProjectImageProperties')
    } else {
      dispatch('image/addOSDImage', {
        name: rootState.image.projectImageName,
        fileType: 'simple',
        source: location.origin + '/images/' + state.projectFilePath,
        function: 'project',
        opacity: 1
      }, { root: true })
    }
  },

  // Check for a properites file. These files can be created by libvips when
  // building deepZoom images and contain useful metadata about the image.
  async getProjectImageProperties ({
    state,
    dispatch
  }) {
    // Construct path to file
    const propsLocation = location.origin + '/images/' +
                      state.projectFilePath.slice(0, -4) +
                      '_files/vips-properties.xml'

    // Fetch data and convert to jsObject
    const propertiesFile = await fetch(propsLocation)
    const data = JSON.parse(convert.xml2json(
      await propertiesFile.text(),
      { ignoreDeclaration: true,
        compact: true,
        ignoreComment: true,
        alwaysChildren: true,
        attributesKey: 'attributes',
        textKey: 'text'
      }
    ))

    // Extract relevant metrics
    const mppX = Number(data.image.properties.property.find(ele => {
      return ele.name.text === 'openslide.mpp-x'
    }).value.text)

    const mppY = Number(data.image.properties.property.find(ele => {
      return ele.name.text === 'openslide.mpp-y'
    }).value.text)

    // Dispatch methods for saving the scale
    dispatch('image/setPixelScaleFactor', { mppX, mppY }, { root: true })
  }
}
<|endoftext|>


<commit_before>/*
Copyright 2019 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import AutocompleteWrapperModel from "./autocomplete";
import Avatar from "../Avatar";

class BasePart {
    constructor(text = "") {
        this._text = text;
    }

    acceptsInsertion(chr, offset, inputType) {
        return true;
    }

    acceptsRemoval(position, chr) {
        return true;
    }

    merge(part) {
        return false;
    }

    split(offset) {
        const splitText = this.text.substr(offset);
        this._text = this.text.substr(0, offset);
        return new PlainPart(splitText);
    }

    // removes len chars, or returns the plain text this part should be replaced with
    // if the part would become invalid if it removed everything.
    remove(offset, len) {
        // validate
        const strWithRemoval = this.text.substr(0, offset) + this.text.substr(offset + len);
        for (let i = offset; i < (len + offset); ++i) {
            const chr = this.text.charAt(i);
            if (!this.acceptsRemoval(i, chr)) {
                return strWithRemoval;
            }
        }
        this._text = strWithRemoval;
    }

    // append str, returns the remaining string if a character was rejected.
    appendUntilRejected(str, inputType) {
        const offset = this.text.length;
        for (let i = 0; i < str.length; ++i) {
            const chr = str.charAt(i);
            if (!this.acceptsInsertion(chr, offset + i, inputType)) {
                this._text = this._text + str.substr(0, i);
                return str.substr(i);
            }
        }
        this._text = this._text + str;
    }

    // inserts str at offset if all the characters in str were accepted, otherwise don't do anything
    // return whether the str was accepted or not.
    validateAndInsert(offset, str, inputType) {
        for (let i = 0; i < str.length; ++i) {
            const chr = str.charAt(i);
            if (!this.acceptsInsertion(chr, offset + i, inputType)) {
                return false;
            }
        }
        const beforeInsert = this._text.substr(0, offset);
        const afterInsert = this._text.substr(offset);
        this._text = beforeInsert + str + afterInsert;
        return true;
    }

    createAutoComplete() {}

    trim(len) {
        const remaining = this._text.substr(len);
        this._text = this._text.substr(0, len);
        return remaining;
    }

    get text() {
        return this._text;
    }

    get canEdit() {
        return true;
    }

    toString() {
        return `${this.type}(${this.text})`;
    }

    serialize() {
        return {type: this.type, text: this.text};
    }
}

// exported for unit tests, should otherwise only be used through PartCreator
export class PlainPart extends BasePart {
    acceptsInsertion(chr, offset, inputType) {
        if (chr === "\n") {
            return false;
        }
        // when not pasting or dropping text, reject characters that should start a pill candidate
        if (inputType !== "insertFromPaste" && inputType !== "insertFromDrop") {
            return chr !== "@" && chr !== "#" && chr !== ":";
        }
        return true;
    }

    toDOMNode() {
        return document.createTextNode(this.text);
    }

    merge(part) {
        if (part.type === this.type) {
            this._text = this.text + part.text;
            return true;
        }
        return false;
    }

    get type() {
        return "plain";
    }

    updateDOMNode(node) {
        if (node.textContent !== this.text) {
            node.textContent = this.text;
        }
    }

    canUpdateDOMNode(node) {
        return node.nodeType === Node.TEXT_NODE;
    }
}

class PillPart extends BasePart {
    constructor(resourceId, label) {
        super(label);
        this.resourceId = resourceId;
    }

    acceptsInsertion(chr) {
        return chr !== " ";
    }

    acceptsRemoval(position, chr) {
        return position !== 0;  //if you remove initial # or @, pill should become plain
    }

    toDOMNode() {
        const container = document.createElement("span");
        container.className = this.className;
        container.appendChild(document.createTextNode(this.text));
        this.setAvatar(container);
        return container;
    }

    updateDOMNode(node) {
        const textNode = node.childNodes[0];
        if (textNode.textContent !== this.text) {
            textNode.textContent = this.text;
        }
        if (node.className !== this.className) {
            node.className = this.className;
        }
        this.setAvatar(node);
    }

    canUpdateDOMNode(node) {
        return node.nodeType === Node.ELEMENT_NODE &&
               node.nodeName === "SPAN" &&
               node.childNodes.length === 1 &&
               node.childNodes[0].nodeType === Node.TEXT_NODE;
    }

    // helper method for subclasses
    _setAvatarVars(node, avatarUrl, initialLetter) {
        const avatarBackground = `url('${avatarUrl}')`;
        const avatarLetter = `'${initialLetter}'`;
        // check if the value is changing,
        // otherwise the avatars flicker on every keystroke while updating.
        if (node.style.getPropertyValue("--avatar-background") !== avatarBackground) {
            node.style.setProperty("--avatar-background", avatarBackground);
        }
        if (node.style.getPropertyValue("--avatar-letter") !== avatarLetter) {
            node.style.setProperty("--avatar-letter", avatarLetter);
        }
    }

    get canEdit() {
        return false;
    }
}

class NewlinePart extends BasePart {
    acceptsInsertion(chr, offset) {
        return offset === 0 && chr === "\n";
    }

    acceptsRemoval(position, chr) {
        return true;
    }

    toDOMNode() {
        return document.createElement("br");
    }

    merge() {
        return false;
    }

    updateDOMNode() {}

    canUpdateDOMNode(node) {
        return node.tagName === "BR";
    }

    get type() {
        return "newline";
    }

    // this makes the cursor skip this part when it is inserted
    // rather than trying to append to it, which is what we want.
    // As a newline can also be only one character, it makes sense
    // as it can only be one character long. This caused #9741.
    get canEdit() {
        return false;
    }
}

class RoomPillPart extends PillPart {
    constructor(displayAlias, room) {
        super(displayAlias, displayAlias);
        this._room = room;
    }

    setAvatar(node) {
        let initialLetter = "";
        let avatarUrl = Avatar.avatarUrlForRoom(this._room, 16 * window.devicePixelRatio, 16 * window.devicePixelRatio);
        if (!avatarUrl) {
            initialLetter = Avatar.getInitialLetter(this._room.name);
            avatarUrl = `../../${Avatar.defaultAvatarUrlForString(this._room.roomId)}`;
        }
        this._setAvatarVars(node, avatarUrl, initialLetter);
    }

    get type() {
        return "room-pill";
    }

    get className() {
        return "mx_RoomPill mx_Pill";
    }
}

class AtRoomPillPart extends RoomPillPart {
    get type() {
        return "at-room-pill";
    }
}

class UserPillPart extends PillPart {
    constructor(userId, displayName, member) {
        super(userId, displayName);
        this._member = member;
    }

    setAvatar(node) {
        if (!this._member) {
            return;
        }
        const name = this._member.name || this._member.userId;
        const defaultAvatarUrl = Avatar.defaultAvatarUrlForString(this._member.userId);
        let avatarUrl = Avatar.avatarUrlForMember(
            this._member,
            16 * window.devicePixelRatio,
            16 * window.devicePixelRatio);
        let initialLetter = "";
        if (avatarUrl === defaultAvatarUrl) {
            // the url from defaultAvatarUrlForString is meant to go in an img element,
            // which has the base of the document. we're using it in css,
            // which has the base of the theme css file, two levels deeper than the document,
            // so go up to the level of the document.
            avatarUrl = `../../${avatarUrl}`;
            initialLetter = Avatar.getInitialLetter(name);
        }
        this._setAvatarVars(node, avatarUrl, initialLetter);
    }

    get type() {
        return "user-pill";
    }

    get className() {
        return "mx_UserPill mx_Pill";
    }

    serialize() {
        const obj = super.serialize();
        obj.resourceId = this.resourceId;
        return obj;
    }
}

class PillCandidatePart extends PlainPart {
    constructor(text, autoCompleteCreator) {
        super(text);
        this._autoCompleteCreator = autoCompleteCreator;
    }

    createAutoComplete(updateCallback) {
        return this._autoCompleteCreator.create(updateCallback);
    }

    acceptsInsertion(chr, offset, inputType) {
        if (offset === 0) {
            return true;
        } else {
            return super.acceptsInsertion(chr, offset, inputType);
        }
    }

    merge() {
        return false;
    }

    acceptsRemoval(position, chr) {
        return true;
    }

    get type() {
        return "pill-candidate";
    }
}

export function autoCompleteCreator(getAutocompleterComponent, updateQuery) {
    return (partCreator) => {
        return (updateCallback) => {
            return new AutocompleteWrapperModel(
                updateCallback,
                getAutocompleterComponent,
                updateQuery,
                partCreator,
            );
        };
    };
}

export class PartCreator {
    constructor(room, client, autoCompleteCreator = null) {
        this._room = room;
        this._client = client;
        // pre-create the creator as an object even without callback so it can already be passed
        // to PillCandidatePart (e.g. while deserializing) and set later on
        this._autoCompleteCreator = {create: autoCompleteCreator && autoCompleteCreator(this)};
    }

    setAutoCompleteCreator(autoCompleteCreator) {
        this._autoCompleteCreator.create = autoCompleteCreator(this);
    }

    createPartForInput(input) {
        switch (input[0]) {
            case "#":
            case "@":
            case ":":
                return this.pillCandidate("");
            case "\n":
                return new NewlinePart();
            default:
                return new PlainPart();
        }
    }

    createDefaultPart(text) {
        return this.plain(text);
    }

    deserializePart(part) {
        switch (part.type) {
            case "plain":
                return this.plain(part.text);
            case "newline":
                return this.newline();
            case "at-room-pill":
                return this.atRoomPill(part.text);
            case "pill-candidate":
                return this.pillCandidate(part.text);
            case "room-pill":
                return this.roomPill(part.text);
            case "user-pill":
                return this.userPill(part.text, part.resourceId);
        }
    }

    plain(text) {
        return new PlainPart(text);
    }

    newline() {
        return new NewlinePart("\n");
    }

    pillCandidate(text) {
        return new PillCandidatePart(text, this._autoCompleteCreator);
    }

    roomPill(alias) {
        let room;
        if (alias[0] === '#') {
            room = this._client.getRooms().find((r) => {
                return r.getAliases().includes(alias);
            });
        } else {
            room = this._client.getRoom(alias);
        }
        return new RoomPillPart(alias, room);
    }

    atRoomPill(text) {
        return new AtRoomPillPart(text, this._room);
    }

    userPill(displayName, userId) {
        const member = this._room.getMember(userId);
        return new UserPillPart(userId, displayName, member);
    }
}

// part creator that support auto complete for /commands,
// used in SendMessageComposer
export class CommandPartCreator extends PartCreator {
    createPartForInput(text, partIndex) {
        // at beginning and starts with /? create
        if (partIndex === 0 && text[0] === "/") {
            return new CommandPart("", this._autoCompleteCreator);
        } else {
            return super.createPartForInput(text, partIndex);
        }
    }

    deserializePart(part) {
        if (part.type === "command") {
            return new CommandPart(part.text, this._autoCompleteCreator);
        } else {
            return super.deserializePart(part);
        }
    }
}

class CommandPart extends PillCandidatePart {
    get type() {
        return "command";
    }
}
<commit_msg>disable spell check for pills in the new composer<commit_after>/*
Copyright 2019 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import AutocompleteWrapperModel from "./autocomplete";
import Avatar from "../Avatar";

class BasePart {
    constructor(text = "") {
        this._text = text;
    }

    acceptsInsertion(chr, offset, inputType) {
        return true;
    }

    acceptsRemoval(position, chr) {
        return true;
    }

    merge(part) {
        return false;
    }

    split(offset) {
        const splitText = this.text.substr(offset);
        this._text = this.text.substr(0, offset);
        return new PlainPart(splitText);
    }

    // removes len chars, or returns the plain text this part should be replaced with
    // if the part would become invalid if it removed everything.
    remove(offset, len) {
        // validate
        const strWithRemoval = this.text.substr(0, offset) + this.text.substr(offset + len);
        for (let i = offset; i < (len + offset); ++i) {
            const chr = this.text.charAt(i);
            if (!this.acceptsRemoval(i, chr)) {
                return strWithRemoval;
            }
        }
        this._text = strWithRemoval;
    }

    // append str, returns the remaining string if a character was rejected.
    appendUntilRejected(str, inputType) {
        const offset = this.text.length;
        for (let i = 0; i < str.length; ++i) {
            const chr = str.charAt(i);
            if (!this.acceptsInsertion(chr, offset + i, inputType)) {
                this._text = this._text + str.substr(0, i);
                return str.substr(i);
            }
        }
        this._text = this._text + str;
    }

    // inserts str at offset if all the characters in str were accepted, otherwise don't do anything
    // return whether the str was accepted or not.
    validateAndInsert(offset, str, inputType) {
        for (let i = 0; i < str.length; ++i) {
            const chr = str.charAt(i);
            if (!this.acceptsInsertion(chr, offset + i, inputType)) {
                return false;
            }
        }
        const beforeInsert = this._text.substr(0, offset);
        const afterInsert = this._text.substr(offset);
        this._text = beforeInsert + str + afterInsert;
        return true;
    }

    createAutoComplete() {}

    trim(len) {
        const remaining = this._text.substr(len);
        this._text = this._text.substr(0, len);
        return remaining;
    }

    get text() {
        return this._text;
    }

    get canEdit() {
        return true;
    }

    toString() {
        return `${this.type}(${this.text})`;
    }

    serialize() {
        return {type: this.type, text: this.text};
    }
}

// exported for unit tests, should otherwise only be used through PartCreator
export class PlainPart extends BasePart {
    acceptsInsertion(chr, offset, inputType) {
        if (chr === "\n") {
            return false;
        }
        // when not pasting or dropping text, reject characters that should start a pill candidate
        if (inputType !== "insertFromPaste" && inputType !== "insertFromDrop") {
            return chr !== "@" && chr !== "#" && chr !== ":";
        }
        return true;
    }

    toDOMNode() {
        return document.createTextNode(this.text);
    }

    merge(part) {
        if (part.type === this.type) {
            this._text = this.text + part.text;
            return true;
        }
        return false;
    }

    get type() {
        return "plain";
    }

    updateDOMNode(node) {
        if (node.textContent !== this.text) {
            node.textContent = this.text;
        }
    }

    canUpdateDOMNode(node) {
        return node.nodeType === Node.TEXT_NODE;
    }
}

class PillPart extends BasePart {
    constructor(resourceId, label) {
        super(label);
        this.resourceId = resourceId;
    }

    acceptsInsertion(chr) {
        return chr !== " ";
    }

    acceptsRemoval(position, chr) {
        return position !== 0;  //if you remove initial # or @, pill should become plain
    }

    toDOMNode() {
        const container = document.createElement("span");
        container.setAttribute("spellcheck", "false");
        container.className = this.className;
        container.appendChild(document.createTextNode(this.text));
        this.setAvatar(container);
        return container;
    }

    updateDOMNode(node) {
        const textNode = node.childNodes[0];
        if (textNode.textContent !== this.text) {
            textNode.textContent = this.text;
        }
        if (node.className !== this.className) {
            node.className = this.className;
        }
        this.setAvatar(node);
    }

    canUpdateDOMNode(node) {
        return node.nodeType === Node.ELEMENT_NODE &&
               node.nodeName === "SPAN" &&
               node.childNodes.length === 1 &&
               node.childNodes[0].nodeType === Node.TEXT_NODE;
    }

    // helper method for subclasses
    _setAvatarVars(node, avatarUrl, initialLetter) {
        const avatarBackground = `url('${avatarUrl}')`;
        const avatarLetter = `'${initialLetter}'`;
        // check if the value is changing,
        // otherwise the avatars flicker on every keystroke while updating.
        if (node.style.getPropertyValue("--avatar-background") !== avatarBackground) {
            node.style.setProperty("--avatar-background", avatarBackground);
        }
        if (node.style.getPropertyValue("--avatar-letter") !== avatarLetter) {
            node.style.setProperty("--avatar-letter", avatarLetter);
        }
    }

    get canEdit() {
        return false;
    }
}

class NewlinePart extends BasePart {
    acceptsInsertion(chr, offset) {
        return offset === 0 && chr === "\n";
    }

    acceptsRemoval(position, chr) {
        return true;
    }

    toDOMNode() {
        return document.createElement("br");
    }

    merge() {
        return false;
    }

    updateDOMNode() {}

    canUpdateDOMNode(node) {
        return node.tagName === "BR";
    }

    get type() {
        return "newline";
    }

    // this makes the cursor skip this part when it is inserted
    // rather than trying to append to it, which is what we want.
    // As a newline can also be only one character, it makes sense
    // as it can only be one character long. This caused #9741.
    get canEdit() {
        return false;
    }
}

class RoomPillPart extends PillPart {
    constructor(displayAlias, room) {
        super(displayAlias, displayAlias);
        this._room = room;
    }

    setAvatar(node) {
        let initialLetter = "";
        let avatarUrl = Avatar.avatarUrlForRoom(this._room, 16 * window.devicePixelRatio, 16 * window.devicePixelRatio);
        if (!avatarUrl) {
            initialLetter = Avatar.getInitialLetter(this._room.name);
            avatarUrl = `../../${Avatar.defaultAvatarUrlForString(this._room.roomId)}`;
        }
        this._setAvatarVars(node, avatarUrl, initialLetter);
    }

    get type() {
        return "room-pill";
    }

    get className() {
        return "mx_RoomPill mx_Pill";
    }
}

class AtRoomPillPart extends RoomPillPart {
    get type() {
        return "at-room-pill";
    }
}

class UserPillPart extends PillPart {
    constructor(userId, displayName, member) {
        super(userId, displayName);
        this._member = member;
    }

    setAvatar(node) {
        if (!this._member) {
            return;
        }
        const name = this._member.name || this._member.userId;
        const defaultAvatarUrl = Avatar.defaultAvatarUrlForString(this._member.userId);
        let avatarUrl = Avatar.avatarUrlForMember(
            this._member,
            16 * window.devicePixelRatio,
            16 * window.devicePixelRatio);
        let initialLetter = "";
        if (avatarUrl === defaultAvatarUrl) {
            // the url from defaultAvatarUrlForString is meant to go in an img element,
            // which has the base of the document. we're using it in css,
            // which has the base of the theme css file, two levels deeper than the document,
            // so go up to the level of the document.
            avatarUrl = `../../${avatarUrl}`;
            initialLetter = Avatar.getInitialLetter(name);
        }
        this._setAvatarVars(node, avatarUrl, initialLetter);
    }

    get type() {
        return "user-pill";
    }

    get className() {
        return "mx_UserPill mx_Pill";
    }

    serialize() {
        const obj = super.serialize();
        obj.resourceId = this.resourceId;
        return obj;
    }
}

class PillCandidatePart extends PlainPart {
    constructor(text, autoCompleteCreator) {
        super(text);
        this._autoCompleteCreator = autoCompleteCreator;
    }

    createAutoComplete(updateCallback) {
        return this._autoCompleteCreator.create(updateCallback);
    }

    acceptsInsertion(chr, offset, inputType) {
        if (offset === 0) {
            return true;
        } else {
            return super.acceptsInsertion(chr, offset, inputType);
        }
    }

    merge() {
        return false;
    }

    acceptsRemoval(position, chr) {
        return true;
    }

    get type() {
        return "pill-candidate";
    }
}

export function autoCompleteCreator(getAutocompleterComponent, updateQuery) {
    return (partCreator) => {
        return (updateCallback) => {
            return new AutocompleteWrapperModel(
                updateCallback,
                getAutocompleterComponent,
                updateQuery,
                partCreator,
            );
        };
    };
}

export class PartCreator {
    constructor(room, client, autoCompleteCreator = null) {
        this._room = room;
        this._client = client;
        // pre-create the creator as an object even without callback so it can already be passed
        // to PillCandidatePart (e.g. while deserializing) and set later on
        this._autoCompleteCreator = {create: autoCompleteCreator && autoCompleteCreator(this)};
    }

    setAutoCompleteCreator(autoCompleteCreator) {
        this._autoCompleteCreator.create = autoCompleteCreator(this);
    }

    createPartForInput(input) {
        switch (input[0]) {
            case "#":
            case "@":
            case ":":
                return this.pillCandidate("");
            case "\n":
                return new NewlinePart();
            default:
                return new PlainPart();
        }
    }

    createDefaultPart(text) {
        return this.plain(text);
    }

    deserializePart(part) {
        switch (part.type) {
            case "plain":
                return this.plain(part.text);
            case "newline":
                return this.newline();
            case "at-room-pill":
                return this.atRoomPill(part.text);
            case "pill-candidate":
                return this.pillCandidate(part.text);
            case "room-pill":
                return this.roomPill(part.text);
            case "user-pill":
                return this.userPill(part.text, part.resourceId);
        }
    }

    plain(text) {
        return new PlainPart(text);
    }

    newline() {
        return new NewlinePart("\n");
    }

    pillCandidate(text) {
        return new PillCandidatePart(text, this._autoCompleteCreator);
    }

    roomPill(alias) {
        let room;
        if (alias[0] === '#') {
            room = this._client.getRooms().find((r) => {
                return r.getAliases().includes(alias);
            });
        } else {
            room = this._client.getRoom(alias);
        }
        return new RoomPillPart(alias, room);
    }

    atRoomPill(text) {
        return new AtRoomPillPart(text, this._room);
    }

    userPill(displayName, userId) {
        const member = this._room.getMember(userId);
        return new UserPillPart(userId, displayName, member);
    }
}

// part creator that support auto complete for /commands,
// used in SendMessageComposer
export class CommandPartCreator extends PartCreator {
    createPartForInput(text, partIndex) {
        // at beginning and starts with /? create
        if (partIndex === 0 && text[0] === "/") {
            return new CommandPart("", this._autoCompleteCreator);
        } else {
            return super.createPartForInput(text, partIndex);
        }
    }

    deserializePart(part) {
        if (part.type === "command") {
            return new CommandPart(part.text, this._autoCompleteCreator);
        } else {
            return super.deserializePart(part);
        }
    }
}

class CommandPart extends PillCandidatePart {
    get type() {
        return "command";
    }
}
<|endoftext|>


<commit_before>import lazyLoading from './lazyLoading'

export default {
  name: 'Statistics',
  meta: {
    expanded: false,
    title: 'menu.statistics',
    iconClass: 'vuestic-icon vuestic-icon-statistics'
  },

  children: [
    {
      name: 'menu.charts',
      path: '/statistics/charts',
      component: lazyLoading('statistics/charts/Charts'),
      meta: {
        iconClass: 'entypo entypo-flashlight',
        title: 'menu.charts'
      }
    },
    {
      name: 'menu.progressBars',
      path: '/statistics/progress-bars',
      component: lazyLoading('statistics/progress-bars/ProgressBars'),
      meta: {
        title: 'menu.progressBars'
      }
    }
  ]
}
<commit_msg>Update statistics.js<commit_after>import lazyLoading from './lazyLoading'

export default {
  name: 'Statistics',
  meta: {
    expanded: false,
    title: 'menu.statistics',
    iconClass: 'vuestic-icon vuestic-icon-statistics'
  },

  children: [
    {
      name: 'menu.charts',
      path: '/statistics/charts',
      component: lazyLoading('statistics/charts/Charts'),
      meta: {
        title: 'menu.charts'
      }
    },
    {
      name: 'menu.progressBars',
      path: '/statistics/progress-bars',
      component: lazyLoading('statistics/progress-bars/ProgressBars'),
      meta: {
        title: 'menu.progressBars'
      }
    }
  ]
}
<|endoftext|>