<commit_before>import React, { Component } from 'react';
import {
  StyleSheet,
  Image,
  Navigator,
  Dimensions,
  TouchableOpacity,
  AlertIOS,
  Alert,
  AsyncStorage
} from 'react-native';
import { createAnimatableComponent, View, Text } from 'react-native-animatable';

import CommonNav from '../common/CommonNav';
import RightButtonNav from '../common/RightButtonNav';
import TextPingFang from '../common/TextPingFang';
import {HOST} from '../util/config';
import HttpUtils from '../util/HttpUtils';
import Platform from 'Platform';
import AlertBox from '../common/AlertBox';

const WIDTH = Dimensions.get("window").width;
const INNERWIDTH = WIDTH - 16;
const HEIGHT = Dimensions.get("window").height;
const URL1 = HOST + 'users/update';
const URL2 = HOST + 'users/close_connect';

export default class SettingPage extends Component {
  static defaultProps = {}
  constructor(props) {
    super(props);
    this.state = {
      user_sex: this.props.user.user_sex,
      user_name: this.props.user.user_name,
      user_state: this.props.user.user_other_id,
      isDialogVisible: false,
      data: {}
    };
  }
  onPost() {
    HttpUtils.post(URL1, {
      uid: this.props.user.uid,
      timestamp: this.props.user.timestamp,
      token: this.props.user.token,
      user_name: this.state.user_name,
      user_sex: this.state.user_sex,
      user_other_id: this.user_state
    }).then((res)=> {
      if (res.status == 0) {
        this.showDialog()
        this.setState({
          data: {
            user_name: this.state.user_name,
            user_sex: this.state.user_sex,
            user_other_id: this.state.user_state
          }
        })
      }
    }).catch((error)=> {
      console.log(error);
    })
  }
  changeName() {
    if(Platform.OS === 'ios'){
      AlertIOS.prompt('请输入新的昵称','',[
        {text:'取消', onPress:this.userCanceled},
        {text:'确定', onPress:(name)=>{this.setState({user_name: name})}}
      ]);
    } else {
      Alert.alert('小提醒', '对不起，安卓用户暂时不支持更改昵称。。');
    }
  }

  showDialog(){
    this.setState({isDialogVisible:true});
  }

  hideDialog(){
    this.setState({isDialogVisible:false});
  }
  changeSex() {
    Alert.alert('是否更改性别？','',[
      {text:'取消', onPress:this.userCanceled},
      {text:'确定', onPress:(sex)=>{
        this.state.user_sex == 1? this.setState({user_sex: 0}):this.setState({user_sex: 1})
      }}])
  }
  changeConnectState() {
    Alert.alert('是否关闭匹配功能？如果关闭，任何人都将无法再匹配到您，并会断绝现有契约。','',[
      {text:'取消', onPress:this.userCanceled},
      {text:'确定', onPress:(user_state)=>{
        if(this.state.user_state !== -404) {
          HttpUtils.post(URL2, {
            uid: this.props.user.uid,
            token: this.props.user.token,
            timestamp: this.props.user.timestamp,
            user_other_id: this.state.user_state
          }).then((res)=>{
            if (res.status == 0) {
              this.setState({user_state: -404});
              Alert.alert('小提醒', '您已成功关闭了匹配功能');
              let data = {
                user_name: this.state.user_name,
                user_sex: this.state.user_sex,
                user_other_id: this.state.user_state
              }
              this.props.onCallBack(data);
            } else {
              Alert.alert('小提醒', '网络故障QAQ');
            }
          })
        } else {
          Alert.alert('小提醒', '您已成功开启了匹配功能，快去寻找另一半吧！');
          this.setState({user_state: -1})
          let data = {
            user_name: this.state.user_name,
            user_sex: this.state.user_sex,
            user_other_id: this.state.user_state
          }
          this.props.onCallBack(data);
        }
      }}])
  }
  render() {
    return (
      <View style={styles.container}>
        <AlertBox
          _dialogVisible={this.state.isDialogVisible}
          _dialogLeftBtnAction={()=> {
            AsyncStorage.setItem('user_info', JSON.stringify(this.state.data), (error, result)=>{
              if (!error) {
                console.log(JSON.stringify(this.state.data));
                this.props.onCallBack(this.state.data);
                this.props.navigator.pop();
              }
            })
          }}
          _dialogRightBtnAction={()=>{this.hideDialog()}}
          _dialogContent={'个人信息更改成功'}
          />
        <Image
          style={styles.bg}
          source={this.state.user_sex==0?require("../../res/images/about_bg.png"):require("../../res/images/about_bg1.png")}>
          <RightButtonNav
            title={"设置"}
            navigator={this.props.navigator}
            rightOnPress={()=>{
              this.onPost();
            }}
          />
          <Image
            style={styles.avatar_round}
            source={require("../../res/images/avatar_round.png")}>
            <Image
              style={styles.avatar}
              source={this.state.user_sex==0?require("../../res/images/avatar.png"):require("../../res/images/avatar2.png")} />
          </Image>
          <TextPingFang style={styles.avatar_font}>{this.props.user.user_code}</TextPingFang>
        </Image>
        <TouchableOpacity
            onPress={()=>{
              this.changeName();
            }}>
          <View style={styles.online_name} delay={100} animation="bounceInRight">
            <Text
              style={styles.online_font}>
              {this.state.user_name}
            </Text>
          </View>
        </TouchableOpacity>
        <TouchableOpacity
            onPress={()=>{
              this.changeSex();
            }}>
          <View style={styles.online_sex} delay={150} animation="bounceInRight">
            <Text
              style={styles.online_font}>
              {this.state.user_sex==0?'男':'女'}
            </Text>
          </View>
        </TouchableOpacity>
        <TouchableOpacity
            onPress={()=>{
              this.changeConnectState();
            }}>
          <View style={styles.online_state} delay={200} animation="bounceInRight">
            <Text
              style={styles.online_font}>
              {this.state.user_state==-404?'拒绝任何匹配':'开放匹配'}
            </Text>
          </View>
        </TouchableOpacity>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    height: HEIGHT,
    width: WIDTH,
    alignItems: 'center',
    backgroundColor: "rgb(242,246,250)"
  },
  bg: {
    width: WIDTH,
    alignItems: 'center',
  },
  opacity0:{
    backgroundColor: "rgba(0,0,0,0)"
  },
  avatar_round: {
    justifyContent: 'center',
    alignItems: 'center',
    marginTop: 48
  },
  avatar: {
  },
  avatar_font: {
    color:"#666666",
    fontSize:17,
    backgroundColor:"rgba(0,0,0,0)",
    marginTop:15,
    fontWeight:"600"
  },
  online_name: {
    marginTop: 52,
    backgroundColor: "white",
    alignItems: "center",
    justifyContent: "center",
    width: 150 / 375 * WIDTH,
    height: 44 / 667 * HEIGHT,
    borderRadius: 22 / 667 * HEIGHT
  },
  online_sex:{
    marginTop: 20,
    backgroundColor: "white",
    alignItems: "center",
    justifyContent: "center",
    width: 150 / 375 * WIDTH,
    height: 44 / 667 * HEIGHT,
    borderRadius: 22 / 667 * HEIGHT
  },
  online_state:{
    marginTop: 20,
    backgroundColor: "white",
    alignItems: "center",
    justifyContent: "center",
    width: 150 / 375 * WIDTH,
    height: 44 / 667 * HEIGHT,
    borderRadius: 22 / 667 * HEIGHT
  },
  online_font: {
    fontSize: 14,
    fontWeight: "600",
    color: "#666666",
  }
});<commit_msg>1.头像上传功能（头像显示的布局仍需微调）<commit_after>import React, { Component } from 'react';
import {
  StyleSheet,
  Image,
  Navigator,
  Dimensions,
  TouchableOpacity,
  AlertIOS,
  Alert,
  AsyncStorage
} from 'react-native';
import { createAnimatableComponent, View, Text } from 'react-native-animatable';

import CommonNav from '../common/CommonNav';
import RightButtonNav from '../common/RightButtonNav';
import TextPingFang from '../common/TextPingFang';
import {HOST, QINIU_UPHOST} from '../util/config';
import HttpUtils from '../util/HttpUtils';
import Platform from 'Platform';
import AlertBox from '../common/AlertBox';
import ImagePicker from 'react-native-image-picker';
import ImageResizer from 'react-native-image-resizer';

const WIDTH = Dimensions.get("window").width;
const INNERWIDTH = WIDTH - 16;
const HEIGHT = Dimensions.get("window").height;
const URL1 = HOST + 'users/update';
const URL2 = HOST + 'users/close_connect';
const URL_TOKEN = HOST + "util/qiniu_token";//qiniu_token

export default class SettingPage extends Component {
  static defaultProps = {}
  constructor(props) {
    super(props);
    this.state = {
      user_sex: this.props.user.user_sex,
      user_name: this.props.user.user_name,
      user_state: this.props.user.user_other_id,
      isDialogVisible: false,
      data: {},
      file:{}
    };
  }
  onPost() {
    HttpUtils.post(URL1, {
      uid: this.props.user.uid,
      timestamp: this.props.user.timestamp,
      token: this.props.user.token,
      user_name: this.state.user_name,
      user_sex: this.state.user_sex,
      user_other_id: this.user_state
    }).then((res)=> {
      if (res.status == 0) {
        this.resizeFile(this.state.file, ()=>{
          this.uploadFile(this.state.file, ()=>{
            console.log('uploadFile success');
            this.showDialog()
            this.setState({
              data: {
                user_name: this.state.user_name,
                user_sex: this.state.user_sex,
                user_other_id: this.state.user_state
              }
            })
          });
        })
      }
    }).catch((error)=> {
      console.log(error);
    })
  }

  resizeFile(file, complete) {
    if(!file.name) {
      Alert.alert("小提示","图片没有名称哦~");
      return ;
    }
    if(!file.uri) {
      Alert.alert("小提示","图片没有内容哦~");
      return ;
    }

    ImageResizer.createResizedImage(file.uri, file.width, file.height, 'JPEG', 80)
    .then((resizedImageUri) => {
      file.resizedUri = resizedImageUri;
      complete(file);
    }).catch((err) => {
      Alert.alert("小提示","压缩图片失败哦~");
      return ;
    });
  }

  uploadFile(file, complete) {
    if(!file.name) {
      Alert.alert("小提示","图片没有名称哦~");
      return ;
    }
    if(!file.uri) {
      Alert.alert("小提示","图片没有内容哦~");
      return ;
    }

    HttpUtils.post(URL_TOKEN,{
      token: this.props.user.token,
      uid: this.props.user.uid,
      timestamp: this.props.timestamp,
      filename: file.name//"image/twolife/" + this.state.file.name,
    }).then((response)=>{
      if(response.status== 0) {
        file.token = response.qiniu_token;

        var formData = new FormData();
        formData.append('file', {uri: file.resizedUri, type:'application/octet-stream', name: file.name});
        formData.append('key', file.name);
        formData.append('token', file.token);

        return fetch(QINIU_UPHOST, {
          method: 'post',
          headers: {
            'Accept': 'application/json',
            'Content-Type': 'application/octet-stream'
          },
          body: formData
          }).then((response) => {
            complete();
        }).catch((error) => {
          Alert.alert("小提示", '网络故障:(');
        });

      }
    }).catch((error)=>{
        Alert.alert("小提示", '网络故障:(');
    })
  }

  changeName() {
    if(Platform.OS === 'ios'){
      AlertIOS.prompt('请输入新的昵称','',[
        {text:'取消', onPress:this.userCanceled},
        {text:'确定', onPress:(name)=>{this.setState({user_name: name})}}
      ]);
    } else {
      Alert.alert('小提醒', '对不起，安卓用户暂时不支持更改昵称。。');
    }
  }

  showDialog(){
    this.setState({isDialogVisible:true});
  }

  hideDialog(){
    this.setState({isDialogVisible:false});
  }
  changeSex() {
    Alert.alert('是否更改性别？','',[
      {text:'取消', onPress:this.userCanceled},
      {text:'确定', onPress:(sex)=>{
        this.state.user_sex == 1? this.setState({user_sex: 0}):this.setState({user_sex: 1})
      }}])
  }
  changeConnectState() {
    Alert.alert('是否关闭匹配功能？如果关闭，任何人都将无法再匹配到您，并会断绝现有契约。','',[
      {text:'取消', onPress:this.userCanceled},
      {text:'确定', onPress:(user_state)=>{
        if(this.state.user_state !== -404) {
          HttpUtils.post(URL2, {
            uid: this.props.user.uid,
            token: this.props.user.token,
            timestamp: this.props.user.timestamp,
            user_other_id: this.state.user_state
          }).then((res)=>{
            if (res.status == 0) {
              this.setState({user_state: -404});
              Alert.alert('小提醒', '您已成功关闭了匹配功能');
              let data = {
                user_name: this.state.user_name,
                user_sex: this.state.user_sex,
                user_other_id: this.state.user_state
              }
              this.props.onCallBack(data);
            } else {
              Alert.alert('小提醒', '网络故障QAQ');
            }
          })
        } else {
          Alert.alert('小提醒', '您已成功开启了匹配功能，快去寻找另一半吧！');
          this.setState({user_state: -1})
          let data = {
            user_name: this.state.user_name,
            user_sex: this.state.user_sex,
            user_other_id: this.state.user_state
          }
          this.props.onCallBack(data);
        }
      }}])
  }
  render() {
    var options = {
      title: 'Select File',
      customButtons: [
      ],
      storageOptions: {
        skipBackup: true,
        path: 'images'
      }
    };

    var avatar = null;
    if (this.state.file.uri) {
      avatar = <Image style={styles.avatar} source={{uri:this.state.file.uri}}/>
    } else {
      avatar = <Image style={styles.avatar} source={this.state.user_sex==0?require("../../res/images/avatar.png"):require("../../res/images/avatar2.png")}/>
    }

    return (
      <View style={styles.container}>
        <AlertBox
          _dialogVisible={this.state.isDialogVisible}
          _dialogLeftBtnAction={()=> {
            AsyncStorage.setItem('user_info', JSON.stringify(this.state.data), (error, result)=>{
              if (!error) {
                console.log(JSON.stringify(this.state.data));
                this.props.onCallBack(this.state.data);
                this.props.navigator.pop();
              }
            })
          }}
          _dialogRightBtnAction={()=>{this.hideDialog()}}
          _dialogContent={'个人信息更改成功'}
          />
        <Image
          style={styles.bg}
          source={this.state.user_sex==0?require("../../res/images/about_bg.png"):require("../../res/images/about_bg1.png")}>
          <RightButtonNav
            title={"设置"}
            navigator={this.props.navigator}
            rightOnPress={()=>{
              this.onPost();
            }}
          />

          <TouchableOpacity
              onPress={()=>{
                ImagePicker.showImagePicker(options, (response) => {
                  console.log('Response = ', response);

                  if (response.didCancel) {
                    console.log('User cancelled image picker');
                  }
                  else if (response.error) {
                    console.log('ImagePicker Error: ', response.error);
                  }
                  else if (response.customButton) {
                    console.log('User tapped custom button: ', response.customButton);
                  }
                  else {
                    let file = {uri: response.uri, height:response.height, width:response.width, name: 'image/twolife/' + this.props.user.uid + '/' + response.fileName};
                    this.setState({
                      file: file
                    })
                  }
                });
              }}>
              <Image
                style={styles.avatar_round}
                source={require("../../res/images/avatar_round.png")}>
                {avatar}
              </Image>
          </TouchableOpacity>

          <TextPingFang style={styles.avatar_font}>{this.props.user.user_code}</TextPingFang>
        </Image>
        <TouchableOpacity
            onPress={()=>{
              this.changeName();
            }}>
          <View style={styles.online_name} delay={100} animation="bounceInRight">
            <Text
              style={styles.online_font}>
              {this.state.user_name}
            </Text>
          </View>
        </TouchableOpacity>
        <TouchableOpacity
            onPress={()=>{
              this.changeSex();
            }}>
          <View style={styles.online_sex} delay={150} animation="bounceInRight">
            <Text
              style={styles.online_font}>
              {this.state.user_sex==0?'男':'女'}
            </Text>
          </View>
        </TouchableOpacity>
        <TouchableOpacity
            onPress={()=>{
              this.changeConnectState();
            }}>
          <View style={styles.online_state} delay={200} animation="bounceInRight">
            <Text
              style={styles.online_font}>
              {this.state.user_state==-404?'拒绝任何匹配':'开放匹配'}
            </Text>
          </View>
        </TouchableOpacity>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    height: HEIGHT,
    width: WIDTH,
    alignItems: 'center',
    backgroundColor: "rgb(242,246,250)"
  },
  bg: {
    width: WIDTH,
    alignItems: 'center',
  },
  opacity0:{
    backgroundColor: "rgba(0,0,0,0)"
  },
  avatar_round: {
    justifyContent: 'center',
    alignItems: 'center',
    marginTop: 48
  },
  avatar: {
    //头像布局需要调整
    width:40,
    height:40
  },
  avatar_font: {
    color:"#666666",
    fontSize:17,
    backgroundColor:"rgba(0,0,0,0)",
    marginTop:15,
    fontWeight:"600"
  },
  online_name: {
    marginTop: 52,
    backgroundColor: "white",
    alignItems: "center",
    justifyContent: "center",
    width: 150 / 375 * WIDTH,
    height: 44 / 667 * HEIGHT,
    borderRadius: 22 / 667 * HEIGHT
  },
  online_sex:{
    marginTop: 20,
    backgroundColor: "white",
    alignItems: "center",
    justifyContent: "center",
    width: 150 / 375 * WIDTH,
    height: 44 / 667 * HEIGHT,
    borderRadius: 22 / 667 * HEIGHT
  },
  online_state:{
    marginTop: 20,
    backgroundColor: "white",
    alignItems: "center",
    justifyContent: "center",
    width: 150 / 375 * WIDTH,
    height: 44 / 667 * HEIGHT,
    borderRadius: 22 / 667 * HEIGHT
  },
  online_font: {
    fontSize: 14,
    fontWeight: "600",
    color: "#666666",
  }
});
<|endoftext|>


<commit_before>(function() {
    "use strict";

    var app = angular.module("TendrlModule");

    app.controller("hostController", hostController);

    /*@ngInject*/
    function hostController($scope, $rootScope, $state, $interval, utils, config) {
        var vm = this, clusterObj, associatedHosts = [];

        vm.isDataLoading = true;

        init();

        function init() {
            utils.getObjectList("Node").then(function(list) {
                vm.isDataLoading = false;
                vm.hostList = [];
                if(list !== null) {
                    if(typeof $scope.clusterId !== "undefined") {
                        associatedHosts = utils.getAssociatedHosts(list.nodes,$scope.clusterId);
                        vm.hostList = setupHostListData(associatedHosts);
                    } else {
                        vm.hostList = setupHostListData(list.nodes);
                    }
                }
            });
        }

        /* Trigger this function when we have cluster data */
        $scope.$on("GotClusterData", function (event, data) {
            /* Forward to home view if we don't have any cluster */
            if($rootScope.clusterData === null || $rootScope.clusterData.clusters.length === 0){
                $state.go("home");
            }else {
                init();
            }
        });

        function setupHostListData(list) {
            var i, length = list.length, hostList=[], host, stats;

            for (i = 0; i < length; i++) {
                host={};
                clusterObj = {};
                host.cluster_name = "Unassigned";
                host.id = list[i].node_id;
                host.status = list[i].status;
                host.name = list[i].fqdn;
                host.role = list[i].role;
                clusterObj = utils.getClusterDetails(list[i].tendrl_context.cluster_id);
                if(typeof clusterObj !== "undefined" && typeof clusterObj.tendrl_context !== "undefined") {
                    host.cluster_name = clusterObj.tendrl_context.sds_name;
                }
                if(typeof list[i].stats !== "undefined") {
                    list[i].stats = list[i].stats.replace(/'/g, '"');
                    stats = JSON.parse(list[i].stats);
                    host.storage = stats.storage;
                    host.cpu = stats.cpu;
                    host.memory = stats.memory;
                    host.alert_count = stats.alert_cnt;
                }else {
                    host.alert_count = "NA";
                }

                hostList.push(host);
            }
            return hostList;
        }

        /*Refreshing list after each 30 second interval*/
        var timer = $interval(function () {
          init();
        }, 1000 * config.refreshIntervalTime );

        /*Cancelling interval when scope is destroy*/
        $scope.$on('$destroy', function() {
            $interval.cancel(timer);
        });
    }

})();<commit_msg>Changes for host list view according to new api-resonse<commit_after>(function() {
    "use strict";

    var app = angular.module("TendrlModule");

    app.controller("hostController", hostController);

    /*@ngInject*/
    function hostController($scope, $rootScope, $state, $interval, utils, config) {
        var vm = this, clusterObj, associatedHosts = [];

        vm.isDataLoading = true;

        init();

        function init() {
            utils.getObjectList("Node").then(function(list) {
                vm.isDataLoading = false;
                vm.hostList = [];
                if(list !== null) {
                    if(typeof $scope.clusterId !== "undefined") {
                        associatedHosts = utils.getAssociatedHosts(list.nodes,$scope.clusterId);
                        vm.hostList = setupHostListData(associatedHosts);
                    } else {
                        vm.hostList = setupHostListData(list.nodes,list.clusters);

                    }
                }
            });
        }

        /* Trigger this function when we have cluster data */
        $scope.$on("GotClusterData", function (event, data) {
            /* Forward to home view if we don't have any cluster */
            if($rootScope.clusterData === null || $rootScope.clusterData.clusters.length === 0){
                $state.go("home");
            }else {
                init();
            }
        });

        function setupHostListData(list,clusters) {
            var i, j, length = list.length, hostList=[], host, stats;
            for (i = 0; i < length; i++) {
                host={};
                clusterObj = {};
                host.cluster_name = "Unassigned";
                host.id = list[i].node_id;
                host.status = list[i].status;
                host.name = list[i].fqdn;
                host.role = list[i].role;
                for (j = 0; j < clusters.length; j++) {
                    if(list[i].detectedcluster.detected_cluster_id === clusters[i].cluster_id){
                        host.cluster_name = clusters[i].sds_name
                    }
                }
                // clusterObj = utils.getClusterDetails(list[i].detectedcluster.detected_cluster_id);
                // if(typeof clusterObj !== "undefined" && typeof clusterObj.tendrl_context !== "undefined") {
                //     host.cluster_name = clusterObj.tendrl_context.sds_name;
                // }
                if(typeof list[i].stats !== "undefined") {
                    list[i].stats = list[i].stats.replace(/'/g, '"');
                    stats = JSON.parse(list[i].stats);
                    host.storage = stats.storage;
                    host.cpu = stats.cpu;
                    host.memory = stats.memory;
                    host.alert_count = stats.alert_cnt;
                }else {
                    host.alert_count = "NA";
                }

                hostList.push(host);
            }
            return hostList;
        }

        /*Refreshing list after each 30 second interval*/
        var timer = $interval(function () {
          init();
        }, 1000 * config.refreshIntervalTime );

        /*Cancelling interval when scope is destroy*/
        $scope.$on('$destroy', function() {
            $interval.cancel(timer);
        });
    }

})();<|endoftext|>


<commit_before>define(["pex/core/Edge"], function(Edge) {
  function Geometry() {

  }

  Geometry.MAX_VERTICES = 65536;

  //We should be more inteligent here and split by faces and then
  //recompute edges or sth like that. For now Ijust assume that if
  //geometry has faces it doesn't edges.
  Geometry.prototype.split = function(maxVertices) {
    maxVertices = maxVertices || Geometry.MAX_VERTICES;

    var geometries = [];
    var attributes = {"vertices":"", "texCoords":"", "normals":"", "colors":""};

    if (this.faces) {
      console.log("Geometry.split ERROR : Only edge meshes are supported at the moment");
      return;
    }
    else if (this.edges) {
      console.log("Geometry.split edges");
      var geometry = null;
      var numVertices = 0;
      for(var i=0; i<this.edges.length; i++) {
        if (geometry == null) {
          console.log("Geometry.split geometries.length : " + (geometries.length + 1));
          geometry = new Geometry();
          geometry.edges = [];
          for(var attribName in attributes) {
            if (!this[attribName]) continue;
            geometry[attribName] = [];
          }
          geometries.push(geometry);
          numVertices = 0;
        }

        var edge = this.edges[i];

        var full = false;
        for(var attribName in attributes) {
          if (!this[attribName]) continue;
          geometry[attribName].push(this[attribName][edge.a]);
          geometry[attribName].push(this[attribName][edge.b]);
          if (geometry[attribName].length >= Geometry.MAX_VERTICES - 2) {
            full = true;
          }
        }

        geometry.edges.push(new Edge(numVertices, numVertices+1));
        numVertices += 2;

        if (full) {
          //console.log(geometry.edges);
          //console.log(geometry.vertices.length);
          geometry = null;
          //return geometries;
        }
      }
    }
    else {
      //we gonna draw unindexed array so no problem and no need to split
      geometries.push(this);
    }

    return geometries;
  }

  return Geometry;
});<commit_msg>Geometry.computeEdges<commit_after>define(["pex/core/Edge", "pex/core/Face3", "pex/core/Face4"], function(Edge, Face3, Face4) {
  function Geometry() {

  }

  Geometry.MAX_VERTICES = 65536;

  //We should be more inteligent here and split by faces and then
  //recompute edges or sth like that. For now Ijust assume that if
  //geometry has faces it doesn't edges.
  Geometry.prototype.split = function(maxVertices) {
    maxVertices = maxVertices || Geometry.MAX_VERTICES;

    var geometries = [];
    var attributes = {"vertices":"", "texCoords":"", "normals":"", "colors":""};

    if (this.faces) {
      console.log("Geometry.split ERROR : Only edge meshes are supported at the moment");
      return;
    }
    else if (this.edges) {
      console.log("Geometry.split edges");
      var geometry = null;
      var numVertices = 0;
      for(var i=0; i<this.edges.length; i++) {
        if (geometry == null) {
          console.log("Geometry.split geometries.length : " + (geometries.length + 1));
          geometry = new Geometry();
          geometry.edges = [];
          for(var attribName in attributes) {
            if (!this[attribName]) continue;
            geometry[attribName] = [];
          }
          geometries.push(geometry);
          numVertices = 0;
        }

        var edge = this.edges[i];

        var full = false;
        for(var attribName in attributes) {
          if (!this[attribName]) continue;
          geometry[attribName].push(this[attribName][edge.a]);
          geometry[attribName].push(this[attribName][edge.b]);
          if (geometry[attribName].length >= Geometry.MAX_VERTICES - 2) {
            full = true;
          }
        }

        geometry.edges.push(new Edge(numVertices, numVertices+1));
        numVertices += 2;

        if (full) {
          //console.log(geometry.edges);
          //console.log(geometry.vertices.length);
          geometry = null;
          //return geometries;
        }
      }
    }
    else {
      //we gonna draw unindexed array so no problem and no need to split
      geometries.push(this);
    }

    return geometries;
  }

  Geometry.prototype.computeEdges = function() {
    this.edges = [];
    for(var i=0; i<this.faces.length; i++) {
      var face = this.faces[i];
      if (face instanceof Face3) {
        this.edges.push(new Edge(face.a, face.b));
        this.edges.push(new Edge(face.b, face.c));
        this.edges.push(new Edge(face.c, face.a));
      }
      else if (face instanceof Face4) {
        this.edges.push(new Edge(face.a, face.b));
        this.edges.push(new Edge(face.b, face.c));
        this.edges.push(new Edge(face.c, face.d));
        this.edges.push(new Edge(face.d, face.a));
      }
    }

    //remove duplicates
    for(var i=0; i<this.edges.length; i++) {
      var edgeI = this.edges[i];
      for(var j=i+1; j<this.edges.length; j++) {
        var edgeJ = this.edges[j];
        if ((edgeI.a == edgeJ.a && edgeI.b == edgeJ.b) || (edgeI.a == edgeJ.b && edgeI.b == edgeJ.a)) {
          this.edges.splice(j, 1);
          j--;
        }
      }
    }
  }

  return Geometry;
});<|endoftext|>


<commit_before>import React from 'react';
import styles from './Checkbox.less';

class Checkbox extends React.Component {
    render() {
        return (

 {
                if (!this.props.disabled) {
                    this.props.onClick();
                }
            }}>

                    {this.props.checked &&

}

                {this.props.label || this.props.children}

        );
    }
}

Checkbox.propTypes = {
    checked: React.PropTypes.bool.isRequired,
    disabled: React.PropTypes.bool,
    onClick: React.PropTypes.func,
    label: React.PropTypes.node,
    children: React.PropTypes.node
};

export default Checkbox;
<commit_msg>fix an error when no onClick prop is given<commit_after>import React from 'react';
import styles from './Checkbox.less';

class Checkbox extends React.Component {
    render() {
        return (

 {
                if (!this.props.disabled) {
                    this.props.onClick();
                }
            }}>

                    {this.props.checked &&

}

                {this.props.label || this.props.children}

        );
    }
}

Checkbox.propTypes = {
    checked: React.PropTypes.bool.isRequired,
    disabled: React.PropTypes.bool,
    onClick: React.PropTypes.func.isRequired,
    label: React.PropTypes.node,
    children: React.PropTypes.node
};

Checkbox.defaultProps = {
    onClick: () => {
    }
};

export default Checkbox;
<|endoftext|>


<commit_before>import webpack from 'webpack';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import ExtractTextPlugin from 'extract-text-webpack-plugin';
import CopyWebpackPlugin from 'copy-webpack-plugin';
import jeet from 'jeet';
import rupture from 'rupture';
import nib from 'nib';
import _svgStylus from 'svg-stylus/svg';
import rework from 'rework';

function svgStylus(options) {
  return function callback(style) {
    this.on('end', (err, css) => {
      const cssObj = rework(css);
      cssObj.use(_svgStylus(options));

      return cssObj.toString();
    });
  };
};

const vendorModules = /(node_modules|bower_components)/;

const htmlMinifyOptions = process.env.NODE_ENV === 'production' ? {
  removeComments: true,
  removeCommentsFromCDATA: true,
  collapseWhitespace: true,
  conservativeCollapse: false,
  collapseBooleanAttributes: true,
  removeAttributeQuotes: true,
  removeRedundantAttributes: true,
  preventAttributesEscaping: true,
  useShortDoctype: true,
  removeEmptyAttributes: true,
  removeScriptTypeAttributes: true,
  removeStyleLinkTypeAttributes: true,
} : false;

//export default
module.exports = {
  target: "web",
  entry: {
    app: "./app/index.js",
    debug: "./app/debug.js",
    layout: "./app/layout.js",
    logic: "./app/logic.js",
    vendor: require("../app/vendor.js"),
  },

  output: {
    path: "./build",
    filename: "[name]-[hash].js",
    pathinfo: true,
    publicPath: "",
  },

  module: {
    preLoaders: [
      {test: /\.js$/, loader: "eslint-loader", exclude: vendorModules},
    ],
    loaders: [
      {
        test: /\.(json)$/,
        loader: "json",
      },
      {
        test: /\.(png|jpg|svg|ttf|eot|woff|woff2)$/,
        loader: 'url?name=[path][name].[ext]',
      },
      {
        test: /\.js$/,
        exclude: vendorModules,
        loader: "babel",
        query: {
          plugins: ['transform-function-bind'],
        },
      },
    ],
  },

  stylus: {
    use: [svgStylus('./app'), jeet(), rupture(), nib()],
  },

  plugins: [
    new webpack.optimize.CommonsChunkPlugin(
      'vendor', 'vendor-[hash].js', Infinity
    ),
    new ExtractTextPlugin('[name]-[contenthash].css', {allChunks: true}),
    new HtmlWebpackPlugin({
      title: 'KV Diagram Editor',
      minify: htmlMinifyOptions,
      chunks: ['app', 'vendor'],
      template: './app/index.html',
      favicon: './app/favicon.ico',
    }),
    new HtmlWebpackPlugin({
      title: 'Logic Parser',
      minify: htmlMinifyOptions,
      chunks: ['logic', 'vendor'],
      template: './app/index.html',
      favicon: './app/favicon.ico',
    }),
    new HtmlWebpackPlugin({
      title: 'Debug',
      minify: htmlMinifyOptions,
      chunks: ['debug', 'vendor'],
      template: './app/index.html',
      filename: 'debug.html',
      favicon: './app/favicon.ico',
    }),
    new HtmlWebpackPlugin({
      title: 'Layout',
      minify: htmlMinifyOptions,
      chunks: ['layout', 'vendor'],
      template: './app/index.html',
      filename: 'layout.html',
      favicon: './app/favicon.ico',
    }),
    new CopyWebpackPlugin([
      {from: 'app/static'},
    ]),
  ],
};
<commit_msg>fix file name<commit_after>import webpack from 'webpack';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import ExtractTextPlugin from 'extract-text-webpack-plugin';
import CopyWebpackPlugin from 'copy-webpack-plugin';
import jeet from 'jeet';
import rupture from 'rupture';
import nib from 'nib';
import _svgStylus from 'svg-stylus/svg';
import rework from 'rework';

function svgStylus(options) {
  return function callback(style) {
    this.on('end', (err, css) => {
      const cssObj = rework(css);
      cssObj.use(_svgStylus(options));

      return cssObj.toString();
    });
  };
};

const vendorModules = /(node_modules|bower_components)/;

const htmlMinifyOptions = process.env.NODE_ENV === 'production' ? {
  removeComments: true,
  removeCommentsFromCDATA: true,
  collapseWhitespace: true,
  conservativeCollapse: false,
  collapseBooleanAttributes: true,
  removeAttributeQuotes: true,
  removeRedundantAttributes: true,
  preventAttributesEscaping: true,
  useShortDoctype: true,
  removeEmptyAttributes: true,
  removeScriptTypeAttributes: true,
  removeStyleLinkTypeAttributes: true,
} : false;

//export default
module.exports = {
  target: "web",
  entry: {
    app: "./app/index.js",
    debug: "./app/debug.js",
    layout: "./app/layout.js",
    logic: "./app/logic.js",
    vendor: require("../app/vendor.js"),
  },

  output: {
    path: "./build",
    filename: "[name]-[hash].js",
    pathinfo: true,
    publicPath: "",
  },

  module: {
    preLoaders: [
      {test: /\.js$/, loader: "eslint-loader", exclude: vendorModules},
    ],
    loaders: [
      {
        test: /\.(json)$/,
        loader: "json",
      },
      {
        test: /\.(png|jpg|svg|ttf|eot|woff|woff2)$/,
        loader: 'url?name=[path][name].[ext]',
      },
      {
        test: /\.js$/,
        exclude: vendorModules,
        loader: "babel",
        query: {
          plugins: ['transform-function-bind'],
        },
      },
    ],
  },

  stylus: {
    use: [svgStylus('./app'), jeet(), rupture(), nib()],
  },

  plugins: [
    new webpack.optimize.CommonsChunkPlugin(
      'vendor', 'vendor-[hash].js', Infinity
    ),
    new ExtractTextPlugin('[name]-[contenthash].css', {allChunks: true}),
    new HtmlWebpackPlugin({
      title: 'KV Diagram Editor',
      minify: htmlMinifyOptions,
      chunks: ['app', 'vendor'],
      template: './app/index.html',
      favicon: './app/favicon.ico',
    }),
    new HtmlWebpackPlugin({
      title: 'Logic Parser',
      minify: htmlMinifyOptions,
      chunks: ['logic', 'vendor'],
      template: './app/index.html',
      filename: 'logic.html',
      favicon: './app/favicon.ico',
    }),
    new HtmlWebpackPlugin({
      title: 'Debug',
      minify: htmlMinifyOptions,
      chunks: ['debug', 'vendor'],
      template: './app/index.html',
      filename: 'debug.html',
      favicon: './app/favicon.ico',
    }),
    new HtmlWebpackPlugin({
      title: 'Layout',
      minify: htmlMinifyOptions,
      chunks: ['layout', 'vendor'],
      template: './app/index.html',
      filename: 'layout.html',
      favicon: './app/favicon.ico',
    }),
    new CopyWebpackPlugin([
      {from: 'app/static'},
    ]),
  ],
};
<|endoftext|>


<commit_before>"use strict";
var express = require("express");
var bodyParser = require('body-parser');
var morgan = require("morgan");
var util = require("util");
var EventEmitter = require("events").EventEmitter;

/**
 * Construct a new application service.
 * @constructor
 * @param {Object} config Configuration for this service.
 * @param {String} config.homeserverToken The incoming HS token to expect. Must
 * be set prior to calling listen(port).
 * @param {Number} config.httpMaxSizeBytes The max number of bytes allowed on an
 * incoming HTTP request. Default: 5000000.
 * @throws If a homeserver token is not supplied.
 */
function AppService(config) {
    var self = this;
    this.config = config || {};
    this.app = express();
    this.app.use(morgan("combined", {
        stream: {
            write: function(str) {
                /**
                 * An HTTP log line.
                 * @event AppService#http-log
                 * @type {String}
                 * @example
                 * appService.on("http-log", function(logLine) {
                 *   console.log(logLine);
                 * });
                 */
                self.emit("http-log", str);
            }
        }
    }));

    this.app.use(bodyParser.json({
        limit: this.config.httpMaxSizeBytes || 5000000  // 5MB
    }));

    function isInvalidToken(req, res) {
        var hsToken = req.query.access_token;
        if (hsToken !== self.config.homeserverToken) {
            res.send({
                errcode: "M_FORBIDDEN",
                error: "Bad token supplied,"
            });
            return true;
        }
        return false;
    }

    this.app.get("/users/:userId", function(req, res) {
        if (isInvalidToken(req, res)) {
            return;
        }
        var possiblePromise = self.onUserQuery(req.params.userId, function() {
            res.send({});
        });
        if (possiblePromise) {
            possiblePromise.done(function() {
                res.send({});
            }, function(e) {
                res.send({
                    errcode: "M_UNKNOWN",
                    error: e ? e.message : ""
                });
            });
        }
    });

    this.app.get("/rooms/:alias", function(req, res) {
        if (isInvalidToken(req, res)) {
            return;
        }
        var possiblePromise = self.onAliasQuery(req.params.alias, function() {
            res.send({});
        });
        if (possiblePromise) {
            possiblePromise.done(function() {
                res.send({});
            }, function(e) {
                res.send({
                    errcode: "M_UNKNOWN",
                    error: e ? e.message : ""
                });
            });
        }
    });

    var lastProcessedTxnId;
    this.app.put("/transactions/:txnId", function(req, res) {
        if (isInvalidToken(req, res)) {
            return;
        }

        var txnId = req.params.txnId;
        if (!txnId) {
            res.send("Missing transaction ID.");
            return;
        }
        if (!req.body || !req.body.events) {
            res.send("Missing events body.");
            return;
        }
        var events = req.body.events;

        if (lastProcessedTxnId === txnId) {
            res.send({}); // duplicate
            return;
        }
        for (var i = 0; i < events.length; i++) {
            /**
             * An incoming Matrix JSON event.
             * @event AppService#event
             * @type {Object}
             * @example
             * appService.on("event", function(ev) {
             *   console.log("ID: %s", ev.event_id);
             * });
             */
            self.emit("event", events[i]);
            if (events[i].type) {
                /**
                 * An incoming Matrix JSON event, filtered by event.type
                 * @event AppService#type:event
                 * @type {Object}
                 * @example
                 * appService.on("type:m.room.message", function(ev) {
                 *   console.log("Body: %s", ev.content.body);
                 * });
                 */
                self.emit("type:" + events[i].type, events[i]);
            }
        }
        lastProcessedTxnId = txnId;
        res.send({});
    });
}
util.inherits(AppService, EventEmitter);

/**
 * Override this method to handle alias queries.
 * @param {string} alias The queried room alias
 * @param {Function} callback The callback to invoke when complete.
 * @return {Promise} A promise to resolve when complete (if callback isn't supplied)
 */
AppService.prototype.onAliasQuery = function(alias, callback) {
    callback(); // stub impl
    return null;
};

/**
 * Override this method to handle user queries.
 * @param {string} userId The queried user ID.
 * @param {Function} callback The callback to invoke when complete.
 * @return {Promise} A promise to resolve when complete (if callback isn't supplied)
 */
AppService.prototype.onUserQuery = function(userId, callback) {
    callback(); // stub impl
    return null;
};

/**
 * Set the token that should be used to verify incoming events.
 * @param {string} hsToken The home server token
 */
AppService.prototype.setHomeserverToken = function(hsToken) {
    this.config.homeserverToken = hsToken;
};

/**
 * Begin listening on the specified port.
 * @param {Number} port The port to listen on.
 */
AppService.prototype.listen = function(port) {
    this.app.listen(port);
};

/** The application service class */
module.exports = AppService;
<commit_msg>Pass through additional HTTP options to `listen` method<commit_after>"use strict";
var express = require("express");
var bodyParser = require('body-parser');
var morgan = require("morgan");
var util = require("util");
var EventEmitter = require("events").EventEmitter;

/**
 * Construct a new application service.
 * @constructor
 * @param {Object} config Configuration for this service.
 * @param {String} config.homeserverToken The incoming HS token to expect. Must
 * be set prior to calling listen(port).
 * @param {Number} config.httpMaxSizeBytes The max number of bytes allowed on an
 * incoming HTTP request. Default: 5000000.
 * @throws If a homeserver token is not supplied.
 */
function AppService(config) {
    var self = this;
    this.config = config || {};
    this.app = express();
    this.app.use(morgan("combined", {
        stream: {
            write: function(str) {
                /**
                 * An HTTP log line.
                 * @event AppService#http-log
                 * @type {String}
                 * @example
                 * appService.on("http-log", function(logLine) {
                 *   console.log(logLine);
                 * });
                 */
                self.emit("http-log", str);
            }
        }
    }));

    this.app.use(bodyParser.json({
        limit: this.config.httpMaxSizeBytes || 5000000  // 5MB
    }));

    function isInvalidToken(req, res) {
        var hsToken = req.query.access_token;
        if (hsToken !== self.config.homeserverToken) {
            res.send({
                errcode: "M_FORBIDDEN",
                error: "Bad token supplied,"
            });
            return true;
        }
        return false;
    }

    this.app.get("/users/:userId", function(req, res) {
        if (isInvalidToken(req, res)) {
            return;
        }
        var possiblePromise = self.onUserQuery(req.params.userId, function() {
            res.send({});
        });
        if (possiblePromise) {
            possiblePromise.done(function() {
                res.send({});
            }, function(e) {
                res.send({
                    errcode: "M_UNKNOWN",
                    error: e ? e.message : ""
                });
            });
        }
    });

    this.app.get("/rooms/:alias", function(req, res) {
        if (isInvalidToken(req, res)) {
            return;
        }
        var possiblePromise = self.onAliasQuery(req.params.alias, function() {
            res.send({});
        });
        if (possiblePromise) {
            possiblePromise.done(function() {
                res.send({});
            }, function(e) {
                res.send({
                    errcode: "M_UNKNOWN",
                    error: e ? e.message : ""
                });
            });
        }
    });

    var lastProcessedTxnId;
    this.app.put("/transactions/:txnId", function(req, res) {
        if (isInvalidToken(req, res)) {
            return;
        }

        var txnId = req.params.txnId;
        if (!txnId) {
            res.send("Missing transaction ID.");
            return;
        }
        if (!req.body || !req.body.events) {
            res.send("Missing events body.");
            return;
        }
        var events = req.body.events;

        if (lastProcessedTxnId === txnId) {
            res.send({}); // duplicate
            return;
        }
        for (var i = 0; i < events.length; i++) {
            /**
             * An incoming Matrix JSON event.
             * @event AppService#event
             * @type {Object}
             * @example
             * appService.on("event", function(ev) {
             *   console.log("ID: %s", ev.event_id);
             * });
             */
            self.emit("event", events[i]);
            if (events[i].type) {
                /**
                 * An incoming Matrix JSON event, filtered by event.type
                 * @event AppService#type:event
                 * @type {Object}
                 * @example
                 * appService.on("type:m.room.message", function(ev) {
                 *   console.log("Body: %s", ev.content.body);
                 * });
                 */
                self.emit("type:" + events[i].type, events[i]);
            }
        }
        lastProcessedTxnId = txnId;
        res.send({});
    });
}
util.inherits(AppService, EventEmitter);

/**
 * Override this method to handle alias queries.
 * @param {string} alias The queried room alias
 * @param {Function} callback The callback to invoke when complete.
 * @return {Promise} A promise to resolve when complete (if callback isn't supplied)
 */
AppService.prototype.onAliasQuery = function(alias, callback) {
    callback(); // stub impl
    return null;
};

/**
 * Override this method to handle user queries.
 * @param {string} userId The queried user ID.
 * @param {Function} callback The callback to invoke when complete.
 * @return {Promise} A promise to resolve when complete (if callback isn't supplied)
 */
AppService.prototype.onUserQuery = function(userId, callback) {
    callback(); // stub impl
    return null;
};

/**
 * Set the token that should be used to verify incoming events.
 * @param {string} hsToken The home server token
 */
AppService.prototype.setHomeserverToken = function(hsToken) {
    this.config.homeserverToken = hsToken;
};

/**
 * Begin listening on the specified port.
 * @param {Number} port The port to listen on.
 * @param {String} hostname Optional hostname to listen on
 * @param {Number} backlog Maximum length of the queue of pending connections
 * @param {Function} callback The callback for the "listening" event
 */
AppService.prototype.listen = function(port, hostname, backlog, callback) {
    this.app.listen(port, hostname, backlog, callback);
};

/** The application service class */
module.exports = AppService;
<|endoftext|>


<commit_before>/**
    GroupRemoteModel
    Utility class for common code that has remote entities grouped
    by a value but accesed all the time individually.
    It has local copy and cache.
    Difference with GroupListRemoteModel its that the content is NOT
    a list, but an entity.
**/
'use strict';

var ko = require('knockout');
var CacheControl = require('./CacheControl');

function required(val, msg) {
    if (val === null || typeof(val) === 'undefined') throw new Error(msg || 'Required parameter');
    else return val;
}

function GroupRemoteModel(settings) {

    settings = settings || {};
    settings.ttl = required(settings.ttl, 'ttl is required');
    settings.itemIdField = required(settings.itemIdField, 'itemIdField is required');
    settings.Model = required(settings.Model, 'Model is required');
    // Required for API additions
    this.settings = settings;

    this.state = {
        isLoading: ko.observable(false),
        isSyncing: ko.observable(false),
        isSaving: ko.observable(false),
        isDeleting: ko.observable(false)
    };

    var cache = {
        // 'indexID': CacheControl
    };
    var getCacheItem = function(id) {
        var c = cache[id];
        if (!c) {
            c = cache[id] = new CacheControl({
                ttl: settings.ttl
            });
        }
        return c;
    };
    var setCacheItem = function(id, rawData) {
        var c = getCacheItem(id);
        if (c.data)
            c.data.model.updateWith(rawData, true);
        else
            c.data = new settings.Model(rawData);

        c.touch();
        return c;
    };
    this.clearCache = function() {
        cache = {};
    };

    this.state.isLocked = ko.pureComputed(function() {
        return this.isLoading() || this.isSaving() || this.isDeleting();
    }, this.state);

    /** Data Stores Management: implementation must be replaced, with custom code or using
        the helpers added to the class (see addXxSupport prototype methods).
    **/
    function notImplemented() { throw new Error('Not Implemented'); }
    this.fetchItemFromLocal = notImplemented;
    this.fetchItemFromRemote = notImplemented;
    this.pushItemToLocal = notImplemented;
    this.pushItemToRemote = notImplemented;
    this.removeItemFromLocal = notImplemented;
    this.removeItemFromRemote = notImplemented;

    /** API definition **/
    var api = this;

    api.getItem = function getItem(itemID, forceRemoteLoad) {
        //jshint maxcomplexity:9
        var cacheEntry = getCacheItem(itemID);

        // Concurrent requests: First, check if there is a running/pending request already for this
        if (cacheEntry.request) {
            return cacheEntry.request;
        }
        else if (forceRemoteLoad || cacheEntry.mustRevalidate()) {
            // No cache data, is first load, try from local
            if (!cacheEntry.data) {
                api.state.isLoading(true);
                // From local
                cacheEntry.request = this.fetchItemFromLocal(itemID)
                .then(function(data) {
                    // launch remote for sync
                    api.state.isSyncing(true);
                    var remotePromise = this.fetchItemFromRemote(itemID)
                    .then(function(serverData) {
                        var cached = setCacheItem(itemID, serverData);
                        this.pushItemToLocal(itemID, serverData);
                        api.state.isSyncing(false);
                        return cached.data;
                    }.bind(this));
                    // Remote fallback: If no local, wait for remote
                    return data ? data : remotePromise;
                }.bind(this))
                .then(function(data) {
                    var cached = setCacheItem(itemID, data);
                    api.state.isLoading(false);
                    cacheEntry.request = null;
                    return cached.data;
                }.bind(this))
                .catch(function(err) {
                    api.state.isLoading(false);
                    api.state.isSyncing(false);
                    cacheEntry.request = null;
                    // rethrow error
                    throw err;
                });
                return cacheEntry.request;
            } else {
                api.state.isSyncing(true);
                // From remote
                cacheEntry.request = this.fetchItemFromRemote(itemID)
                .then(function(data) {
                    var cached = setCacheItem(itemID, data);
                    this.pushItemToLocal(itemID, data);
                    api.state.isLoading(false);
                    api.state.isSyncing(false);
                    cacheEntry.request = null;
                    return cached.data;
                }.bind(this))
                .catch(function(err) {
                    api.state.isLoading(false);
                    api.state.isSyncing(false);
                    cacheEntry.request = null;
                    // rethrow error
                    throw err;
                });
                return cacheEntry.request;
            }
        }
        else {
            // From cache
            return Promise.resolve(cacheEntry.data);
        }
    };

    /**
        Save an item in cache, local and remote.
        Can be new or updated.
        The IDs goes with all the other data, being
        itemID required for updates
        but falsy for insertions.
        @param data:object Plain object
    **/
    api.setItem = function setItem(data) {

        // Get plain data if is model
        data = data && data.model && data.model.toPlainObject(true) || data;

        api.state.isSaving(true);
        // Send to remote first
        return this.pushItemToRemote(data[settings.itemIdField], data)
        .then(function(serverData) {
            var cached;
            // Success! update local copy with returned data
            // IMPORTANT: to use server data here so we get values set
            // by the server, as updates dates and itemID when creating
            // a new item.
            if (serverData) {
                var itemID = serverData[settings.itemIdField];
                // Save in cache
                cached = setCacheItem(serverData[settings.itemIdField], serverData);
                // Save in local storage
                // In local need to be saved all the grouped data, not just
                // the item; since we have the cache list updated, use that
                // full list to save local
                this.pushItemToLocal(itemID, cached.data.model.toPlainObject(true));
            }
            api.state.isSaving(false);

            return cached && cached.data;
        }.bind(this))
        .catch(function(err) {
            api.state.isSaving(false);
            // Rethrow error
            throw err;
        });
    };

    api.delItem = function delItem(itemID) {

        api.state.isDeleting(true);

        // Remove in remote first
        return this.removeItemFromRemote(itemID)
        .then(function(removedData) {
            // Update cache
            delete cache[itemID];
            // Update local storage
            this.removeItemFromLocal(itemID);

            api.state.isDeleting(false);

            return removedData;
        }.bind(this))
        .catch(function(err) {
            api.state.isDeleting(false);
            // Rethrow error
            throw err;
        });
    };

    /** Some Utils **/
    var ModelVersion = require('../utils/ModelVersion');
    /**
        Gets immediately an object based on ModelVersion
        with the original and version being the cached version
        if any or an empty Model (instance without data).
        If there is no data, it triggers a load automatically
        setting its state flag isLoading to true until ends.
        If there are data but is obsolete, it triggers a
        load automatically settings its state  flag isSyncing
        to true until ends.
        It adds a load method to trigger loading of the original
        from remote, and it triggers ModelVersion.sync on finishing
        (isSyncing is flagged); returns Promise
        It adds a save method to trigger saving the version
        to remote, and it push the changes to the original on success
        (isSaving is flagged); returns Promise.
    **/
    api.getItemVersion = function getItemVersion(itemID) {

        var cached = cache[itemID],
            initialModel = cached && cached.data || new settings.Model(),
            version = new ModelVersion(initialModel);

        // Extend with state flags
        version.state = {
            isLoading: ko.observable(false),
            isSyncing: ko.observable(false),
            isSaving: ko.observable(false),
            // Not just a flag, it contains any error
            // on loading/saving. It no value/null, no error
            lastError: ko.observable(null)
            //isDeleting: ko.observable(false)
        };

        // First time load, if needed:
        if (itemID && (!cached || cached.mustRevalidate())) {
            version.state.isLoading(true);
            api.getItem(itemID)
            .then(function(model) {
                version.state.lastError(null);
                version.state.isLoading(false);
                version.original.model.updateWith(model, true);
                version.version.model.updateWith(model, true);
            })
            .catch(function(err) {
                version.state.lastError(err);
                version.state.isLoading(false);
            });
        }

        version.load = function load(newItemID, forceRemoteLoad) {
            version.state.isSyncing(true);
            // Use the new itemID, OR use the one at the original model.
            // Why not the closure itemID?
            // The ID could get updated in a save process,
            // because autogenerating one for a new item.
            var oItemID = newItemID || version.original[settings.itemIdField]();
            return api.getItem(oItemID, forceRemoteLoad)
            .then(function(model) {
                version.state.lastError(null);
                version.state.isSyncing(false);
                version.original.model.updateWith(model, true);
                //// Try sync, if original is newer will replace version
                //version.sync();
                // pull changes from the new original
                version.pull({ eventIfNewer: true });
                return model;
            })
            .catch(function(err) {
                version.state.lastError(err);
                version.state.isSyncing(false);
                // rethrow error
                throw err;
            });
        };

        version.reset = function reset() {
            version.original.model.reset();
            version.version.model.reset();
        };

        version.save = function save() {
            version.state.isSaving(true);
            return api.setItem(version.version)
            .then(function(model) {
                version.state.lastError(null);
                version.state.isSaving(false);
                version.version.model.updateWith(model, true);
                // push changes to the original
                version.push({ evenIfObsolete: true });
                return model;
            })
            .catch(function(err) {
                version.state.lastError(err);
                version.state.isSaving(false);
                // rethrow error
                throw err;
            });
        };

        return version;
    };

    api.newItemVersion = function newItemVersion(values) {
        var version = api.getItemVersion();
        // Initial data
        if (values)
            version.version.model.updateWith(values, true);
        // To be sure that the version appear as something 'new', unsaved,
        // we update its timestamp to be different to the original.
        version.version.model.touch();
        return version;
    };
}

module.exports = GroupRemoteModel;

GroupRemoteModel.prototype.addLocalforageSupport = function addLocalforageSupport(baseName) {
    var localforage = require('localforage');

    this.fetchItemFromLocal = function fetchFromLocal(itemID) {
        return localforage.getItem(baseName + itemID);
    };
    this.pushItemToLocal = function pushToLocal(itemID, data) {
        return localforage.setItem(baseName + itemID, data);
    };
    this.remoteItemFromLocal = function remoteFromLocal(itemID) {
        return localforage.removeItem(baseName + itemID);
    };
};

GroupRemoteModel.prototype.addRestSupport = function addRestSupport(restClient, baseUrl, suffixUrl) {
    suffixUrl = suffixUrl || '';
    this.fetchItemFromRemote = function fetchFromRemote(itemID) {
        return restClient.get(baseUrl + itemID + suffixUrl);
    };
    this.pushItemToRemote = function pushToRemote(itemID, data) {

        var method = itemID ? 'put' : 'post';

        var url = baseUrl + (
            itemID ? '/' + itemID : ''
        ) + suffixUrl;
        return restClient[method](url, data);
    };
    this.removeItemFromRemote = function removeItemFromRemote(itemID) {
        return restClient.delete(baseUrl + itemID + suffixUrl);
    };
};
<commit_msg>fixed typo in option argument for ModelVersion method in GroupRemoteModel to fix servicesOverview reload bug for #115<commit_after>/**
    GroupRemoteModel
    Utility class for common code that has remote entities grouped
    by a value but accesed all the time individually.
    It has local copy and cache.
    Difference with GroupListRemoteModel its that the content is NOT
    a list, but an entity.
**/
'use strict';

var ko = require('knockout');
var CacheControl = require('./CacheControl');

function required(val, msg) {
    if (val === null || typeof(val) === 'undefined') throw new Error(msg || 'Required parameter');
    else return val;
}

function GroupRemoteModel(settings) {

    settings = settings || {};
    settings.ttl = required(settings.ttl, 'ttl is required');
    settings.itemIdField = required(settings.itemIdField, 'itemIdField is required');
    settings.Model = required(settings.Model, 'Model is required');
    // Required for API additions
    this.settings = settings;

    this.state = {
        isLoading: ko.observable(false),
        isSyncing: ko.observable(false),
        isSaving: ko.observable(false),
        isDeleting: ko.observable(false)
    };

    var cache = {
        // 'indexID': CacheControl
    };
    var getCacheItem = function(id) {
        var c = cache[id];
        if (!c) {
            c = cache[id] = new CacheControl({
                ttl: settings.ttl
            });
        }
        return c;
    };
    var setCacheItem = function(id, rawData) {
        var c = getCacheItem(id);
        if (c.data)
            c.data.model.updateWith(rawData, true);
        else
            c.data = new settings.Model(rawData);

        c.touch();
        return c;
    };
    this.clearCache = function() {
        cache = {};
    };

    this.state.isLocked = ko.pureComputed(function() {
        return this.isLoading() || this.isSaving() || this.isDeleting();
    }, this.state);

    /** Data Stores Management: implementation must be replaced, with custom code or using
        the helpers added to the class (see addXxSupport prototype methods).
    **/
    function notImplemented() { throw new Error('Not Implemented'); }
    this.fetchItemFromLocal = notImplemented;
    this.fetchItemFromRemote = notImplemented;
    this.pushItemToLocal = notImplemented;
    this.pushItemToRemote = notImplemented;
    this.removeItemFromLocal = notImplemented;
    this.removeItemFromRemote = notImplemented;

    /** API definition **/
    var api = this;

    api.getItem = function getItem(itemID, forceRemoteLoad) {
        //jshint maxcomplexity:9
        var cacheEntry = getCacheItem(itemID);

        // Concurrent requests: First, check if there is a running/pending request already for this
        if (cacheEntry.request) {
            return cacheEntry.request;
        }
        else if (forceRemoteLoad || cacheEntry.mustRevalidate()) {
            // No cache data, is first load, try from local
            if (!cacheEntry.data) {
                api.state.isLoading(true);
                // From local
                cacheEntry.request = this.fetchItemFromLocal(itemID)
                .then(function(data) {
                    // launch remote for sync
                    api.state.isSyncing(true);
                    var remotePromise = this.fetchItemFromRemote(itemID)
                    .then(function(serverData) {
                        var cached = setCacheItem(itemID, serverData);
                        this.pushItemToLocal(itemID, serverData);
                        api.state.isSyncing(false);
                        return cached.data;
                    }.bind(this));
                    // Remote fallback: If no local, wait for remote
                    return data ? data : remotePromise;
                }.bind(this))
                .then(function(data) {
                    var cached = setCacheItem(itemID, data);
                    api.state.isLoading(false);
                    cacheEntry.request = null;
                    return cached.data;
                }.bind(this))
                .catch(function(err) {
                    api.state.isLoading(false);
                    api.state.isSyncing(false);
                    cacheEntry.request = null;
                    // rethrow error
                    throw err;
                });
                return cacheEntry.request;
            } else {
                api.state.isSyncing(true);
                // From remote
                cacheEntry.request = this.fetchItemFromRemote(itemID)
                .then(function(data) {
                    var cached = setCacheItem(itemID, data);
                    this.pushItemToLocal(itemID, data);
                    api.state.isLoading(false);
                    api.state.isSyncing(false);
                    cacheEntry.request = null;
                    return cached.data;
                }.bind(this))
                .catch(function(err) {
                    api.state.isLoading(false);
                    api.state.isSyncing(false);
                    cacheEntry.request = null;
                    // rethrow error
                    throw err;
                });
                return cacheEntry.request;
            }
        }
        else {
            // From cache
            return Promise.resolve(cacheEntry.data);
        }
    };

    /**
        Save an item in cache, local and remote.
        Can be new or updated.
        The IDs goes with all the other data, being
        itemID required for updates
        but falsy for insertions.
        @param data:object Plain object
    **/
    api.setItem = function setItem(data) {

        // Get plain data if is model
        data = data && data.model && data.model.toPlainObject(true) || data;

        api.state.isSaving(true);
        // Send to remote first
        return this.pushItemToRemote(data[settings.itemIdField], data)
        .then(function(serverData) {
            var cached;
            // Success! update local copy with returned data
            // IMPORTANT: to use server data here so we get values set
            // by the server, as updates dates and itemID when creating
            // a new item.
            if (serverData) {
                var itemID = serverData[settings.itemIdField];
                // Save in cache
                cached = setCacheItem(serverData[settings.itemIdField], serverData);
                // Save in local storage
                // In local need to be saved all the grouped data, not just
                // the item; since we have the cache list updated, use that
                // full list to save local
                this.pushItemToLocal(itemID, cached.data.model.toPlainObject(true));
            }
            api.state.isSaving(false);

            return cached && cached.data;
        }.bind(this))
        .catch(function(err) {
            api.state.isSaving(false);
            // Rethrow error
            throw err;
        });
    };

    api.delItem = function delItem(itemID) {

        api.state.isDeleting(true);

        // Remove in remote first
        return this.removeItemFromRemote(itemID)
        .then(function(removedData) {
            // Update cache
            delete cache[itemID];
            // Update local storage
            this.removeItemFromLocal(itemID);

            api.state.isDeleting(false);

            return removedData;
        }.bind(this))
        .catch(function(err) {
            api.state.isDeleting(false);
            // Rethrow error
            throw err;
        });
    };

    /** Some Utils **/
    var ModelVersion = require('../utils/ModelVersion');
    /**
        Gets immediately an object based on ModelVersion
        with the original and version being the cached version
        if any or an empty Model (instance without data).
        If there is no data, it triggers a load automatically
        setting its state flag isLoading to true until ends.
        If there are data but is obsolete, it triggers a
        load automatically settings its state  flag isSyncing
        to true until ends.
        It adds a load method to trigger loading of the original
        from remote, and it triggers ModelVersion.sync on finishing
        (isSyncing is flagged); returns Promise
        It adds a save method to trigger saving the version
        to remote, and it push the changes to the original on success
        (isSaving is flagged); returns Promise.
    **/
    api.getItemVersion = function getItemVersion(itemID) {

        var cached = cache[itemID],
            initialModel = cached && cached.data || new settings.Model(),
            version = new ModelVersion(initialModel);

        // Extend with state flags
        version.state = {
            isLoading: ko.observable(false),
            isSyncing: ko.observable(false),
            isSaving: ko.observable(false),
            // Not just a flag, it contains any error
            // on loading/saving. It no value/null, no error
            lastError: ko.observable(null)
            //isDeleting: ko.observable(false)
        };

        // First time load, if needed:
        if (itemID && (!cached || cached.mustRevalidate())) {
            version.state.isLoading(true);
            api.getItem(itemID)
            .then(function(model) {
                version.state.lastError(null);
                version.state.isLoading(false);
                version.original.model.updateWith(model, true);
                version.version.model.updateWith(model, true);
            })
            .catch(function(err) {
                version.state.lastError(err);
                version.state.isLoading(false);
            });
        }

        version.load = function load(newItemID, forceRemoteLoad) {
            version.state.isSyncing(true);
            // Use the new itemID, OR use the one at the original model.
            // Why not the closure itemID?
            // The ID could get updated in a save process,
            // because autogenerating one for a new item.
            var oItemID = newItemID || version.original[settings.itemIdField]();
            return api.getItem(oItemID, forceRemoteLoad)
            .then(function(model) {
                version.state.lastError(null);
                version.state.isSyncing(false);
                version.original.model.updateWith(model, true);
                //// Try sync, if original is newer will replace version
                //version.sync();
                // pull changes from the new original
                version.pull({ evenIfNewer: true });
                return model;
            })
            .catch(function(err) {
                version.state.lastError(err);
                version.state.isSyncing(false);
                // rethrow error
                throw err;
            });
        };

        version.reset = function reset() {
            version.original.model.reset();
            version.version.model.reset();
        };

        version.save = function save() {
            version.state.isSaving(true);
            return api.setItem(version.version)
            .then(function(model) {
                version.state.lastError(null);
                version.state.isSaving(false);
                version.version.model.updateWith(model, true);
                // push changes to the original
                version.push({ evenIfObsolete: true });
                return model;
            })
            .catch(function(err) {
                version.state.lastError(err);
                version.state.isSaving(false);
                // rethrow error
                throw err;
            });
        };

        return version;
    };

    api.newItemVersion = function newItemVersion(values) {
        var version = api.getItemVersion();
        // Initial data
        if (values)
            version.version.model.updateWith(values, true);
        // To be sure that the version appear as something 'new', unsaved,
        // we update its timestamp to be different to the original.
        version.version.model.touch();
        return version;
    };
}

module.exports = GroupRemoteModel;

GroupRemoteModel.prototype.addLocalforageSupport = function addLocalforageSupport(baseName) {
    var localforage = require('localforage');

    this.fetchItemFromLocal = function fetchFromLocal(itemID) {
        return localforage.getItem(baseName + itemID);
    };
    this.pushItemToLocal = function pushToLocal(itemID, data) {
        return localforage.setItem(baseName + itemID, data);
    };
    this.remoteItemFromLocal = function remoteFromLocal(itemID) {
        return localforage.removeItem(baseName + itemID);
    };
};

GroupRemoteModel.prototype.addRestSupport = function addRestSupport(restClient, baseUrl, suffixUrl) {
    suffixUrl = suffixUrl || '';
    this.fetchItemFromRemote = function fetchFromRemote(itemID) {
        return restClient.get(baseUrl + itemID + suffixUrl);
    };
    this.pushItemToRemote = function pushToRemote(itemID, data) {

        var method = itemID ? 'put' : 'post';

        var url = baseUrl + (
            itemID ? '/' + itemID : ''
        ) + suffixUrl;
        return restClient[method](url, data);
    };
    this.removeItemFromRemote = function removeItemFromRemote(itemID) {
        return restClient.delete(baseUrl + itemID + suffixUrl);
    };
};
<|endoftext|>