<commit_before>import React, { Component } from 'react'
import {
  View,
  Text,
  StyleSheet,
  TouchableOpacity,
  Image,
  Dimensions,
  TouchableHighlight,
  AsyncStorage,
  ScrollView,
  Alert
} from 'react-native'
import RightButtonNav from '../components/RightButtonNav'
import BookCollectItem from '../components/BookCollectTitle'
import BookCollectAddPage from './BookCollectAdd'
import HttpUtils from '../network/HttpUtils'
import { LISTS } from '../network/Urls'
import { INNERWIDTH, HEIGHT } from '../common/styles'
import { Actions } from 'react-native-router-flux'
import { SCENE_BOOK_COLLECT_ADD } from '../constants/scene'
import Toast from 'antd-mobile/lib/toast'
import Storage from '../common/storage'

const URL_SHOW = LISTS.show_list
const URL_ADD_BOOK = LISTS.collect_book
const URL_RM_LIST = LISTS.remove_list

export default class BookCollectPage extends Component {
  constructor(props) {
    super(props)
    this.state = {
      lists: [],
      choosed: []
    }
  }
  static defaultProps = {
    book: {
      book_title: '美洲小宇宙',
      book_content:
      '一本书带你深度探访中南美洲腹地，身未动，心已远。沿着旧地图，走不到新大陆，毕淑敏带你走出自助旅行新路线: “世界最美岛屿”加拉帕戈斯群岛，“热带雾林王冠上的宝石”哥斯达黎加蒙特维德雾林，古印第安人的太阳、月亮金字塔与亡灵大道，全球银饰之都塔斯科，切•格瓦拉故居……太多秘密，等你探索，太多奇迹，等你发现。',
      book_cover:
      'https://imgsa.baidu.com/baike/c0%3Dbaike272%2C5%2C5%2C272%2C90/sign=b52bcf617f094b36cf9f13bfc2a517bc/9c16fdfaaf51f3de300988da9deef01f3b2979d0.jpg',
      book_key: 'TB47-05-/9',
      book_place: '中文文科库(418)',
      book_author: '毕淑敏',
      book_publish: '湖南文艺出版社',
      book_rate: 9.3,
      detail_data: [
        {
          detail_place: '中文文科库418',
          detail_id: 11,
          detail_key: 1,
          detail_in: true
        },
        {
          detail_place: '中文文科库418',
          detail_id: 11,
          detail_key: 2,
          detail_in: true
        },
        {
          detail_place: '中文文科库418',
          detail_id: 11,
          detail_key: 3,
          detail_in: false
        }
      ],
      book_last_number: 2,
      book_subscribe: true
    }
  }

  async componentDidMount() {
    let result = (await HttpUtils.post(URL_SHOW)) || {}
    let lists = result.data || []
    this.setState({ lists })
  }

  rightOnPress = async () => {
    if (this.props.title === '我的书单' || this.state.choosed.length == 0) {
      Actions.pop()
    }

    const choosed = this.state.choosed[0]
    let {
      list_id,
      book_list
    } = this.state.lists.filter(list => list.list_name === choosed)[0]

    book_list = book_list.split(',')
    book_list.push(this.props.book.book_id + '')
    book_list = [...new Set(book_list)]
    book_list = book_list.join(',')

    const params = {
      list_id,
      book_list: book_list
    }

    await HttpUtils.post(URL_ADD_BOOK, params)

    Toast.success('收藏成功！', 1)
    Actions.pop()
  }

  onPressButton(select, item) {
    let choosed = this.state.choosed
    if (select) {
      choosed = [...new Set([...choosed, item.list_name])]
    } else {
      choosed.splice(choosed.indexOf(item.list_name), 1)
    }
    this.setState({ choosed: choosed })
  }

  async onDelete(title) {
    let array = this.state.lists
    array.some(async (d, i) => {
      if (d.list_name === title) {
        array.splice(i, 1)
      }
      let params = {
        list_id: d.list_id
      }
      let result = (await HttpUtils.post(URL_RM_LIST, params)) || {}
      if (result.msg === '请求成功') {
        await this.setState({ lists: array })
        Toast.success('删除书单成功!', 1)
        await Storage.set('book_list', array)
        return true
      } else {
        Toast.offline(result.msg, 1)
      }
    })
  }

  onConfirm(title) {
    Alert.alert('确认删除', '您真的要删除这个书单吗?', [
      { text: '确认', onPress: this.onDelete.bind(this, title) },
      { text: '取消' }
    ])
  }

  render() {
    let lists = [
      { title: '专业书籍', des: '反正就是很专业的啦' },
      { title: '经典书籍', des: '反正就是很经典的啦' }
    ]
    return (
      <View style={styles.container}>
        <RightButtonNav
          title={this.props.title}
          rightOnPress={() => this.rightOnPress()}
        />
        <TouchableOpacity
          style={styles.add}
          onPress={() => {
            let params = {
              onCallBack: async () => {
                let array = await Storage.get('book_list', [])
                this.setState({ lists: array })
              }
            }
            Actions[SCENE_BOOK_COLLECT_ADD](params)
          }}
        >
          <Image source={require('../../res/images/icon_add.png')} />
        </TouchableOpacity>
        <ScrollView style={styles.list}>
          {this.state.lists.map((item, i) => {
            return (
              <BookCollectItem
                item={item}
                big_title={this.props.title}
                onPress={(select, data) => this.onPressButton(select, data)}
                onDelete={title => {
                  this.onConfirm(title)
                }}
                key={item.list_id}
                data={item}
              />
            )
          })}
        </ScrollView>
      </View>
    )
  }
}

const styles = StyleSheet.create({
  container: {
    backgroundColor: 'rgb(242,246,250)',
    height: HEIGHT
  },
  add: {
    width: INNERWIDTH,
    borderRadius: 8,
    marginLeft: 8,
    height: 48,
    marginTop: 8,
    alignItems: 'center',
    justifyContent: 'center',
    backgroundColor: 'white'
  },
  list: {
    marginTop: 20
  }
})
<commit_msg>fix(collect): 收藏时，不能一次性收藏到多个书单<commit_after>import React, { Component } from 'react'
import {
  View,
  Text,
  StyleSheet,
  TouchableOpacity,
  Image,
  Dimensions,
  TouchableHighlight,
  AsyncStorage,
  ScrollView,
  Alert
} from 'react-native'
import RightButtonNav from '../components/RightButtonNav'
import BookCollectItem from '../components/BookCollectTitle'
import BookCollectAddPage from './BookCollectAdd'
import HttpUtils from '../network/HttpUtils'
import { LISTS } from '../network/Urls'
import { INNERWIDTH, HEIGHT } from '../common/styles'
import { Actions } from 'react-native-router-flux'
import { SCENE_BOOK_COLLECT_ADD } from '../constants/scene'
import Toast from 'antd-mobile/lib/toast'
import Storage from '../common/storage'

const URL_SHOW = LISTS.show_list
const URL_ADD_BOOK = LISTS.collect_book
const URL_RM_LIST = LISTS.remove_list

export default class BookCollectPage extends Component {
  constructor(props) {
    super(props)
    this.state = {
      lists: [],
      choosed: []
    }
  }
  static defaultProps = {
    book: {
      book_title: '美洲小宇宙',
      book_content:
      '一本书带你深度探访中南美洲腹地，身未动，心已远。沿着旧地图，走不到新大陆，毕淑敏带你走出自助旅行新路线: “世界最美岛屿”加拉帕戈斯群岛，“热带雾林王冠上的宝石”哥斯达黎加蒙特维德雾林，古印第安人的太阳、月亮金字塔与亡灵大道，全球银饰之都塔斯科，切•格瓦拉故居……太多秘密，等你探索，太多奇迹，等你发现。',
      book_cover:
      'https://imgsa.baidu.com/baike/c0%3Dbaike272%2C5%2C5%2C272%2C90/sign=b52bcf617f094b36cf9f13bfc2a517bc/9c16fdfaaf51f3de300988da9deef01f3b2979d0.jpg',
      book_key: 'TB47-05-/9',
      book_place: '中文文科库(418)',
      book_author: '毕淑敏',
      book_publish: '湖南文艺出版社',
      book_rate: 9.3,
      detail_data: [
        {
          detail_place: '中文文科库418',
          detail_id: 11,
          detail_key: 1,
          detail_in: true
        },
        {
          detail_place: '中文文科库418',
          detail_id: 11,
          detail_key: 2,
          detail_in: true
        },
        {
          detail_place: '中文文科库418',
          detail_id: 11,
          detail_key: 3,
          detail_in: false
        }
      ],
      book_last_number: 2,
      book_subscribe: true
    }
  }

  async componentDidMount() {
    let result = (await HttpUtils.post(URL_SHOW)) || {}
    let lists = result.data || []
    this.setState({ lists })
  }

  rightOnPress = async () => {
    if (this.props.title === '我的书单' || this.state.choosed.length == 0) {
      Actions.pop()
    }

    const tasks = []

    this.state.choosed.forEach((choosed) => {
      let {
        list_id,
        book_list
      } = this.state.lists.filter(list => list.list_name === choosed)[0]

      book_list = book_list.split(',')
      book_list.push(this.props.book.book_id + '')
      book_list = [...new Set(book_list)]
      book_list = book_list.join(',')

      const params = {
        list_id,
        book_list: book_list
      }

      tasks.push(HttpUtils.post(URL_ADD_BOOK, params))
    })

    await Promise.all(tasks)

    Toast.success('收藏成功！', 1)
    Actions.pop()
  }

  onPressButton(select, item) {
    let choosed = this.state.choosed
    if (select) {
      choosed = [...new Set([...choosed, item.list_name])]
    } else {
      choosed.splice(choosed.indexOf(item.list_name), 1)
    }
    this.setState({ choosed: choosed })
  }

  async onDelete(title) {
    let array = this.state.lists
    array.some(async (d, i) => {
      if (d.list_name === title) {
        array.splice(i, 1)
      }
      let params = {
        list_id: d.list_id
      }
      let result = (await HttpUtils.post(URL_RM_LIST, params)) || {}
      if (result.msg === '请求成功') {
        await this.setState({ lists: array })
        Toast.success('删除书单成功!', 1)
        await Storage.set('book_list', array)
        return true
      } else {
        Toast.offline(result.msg, 1)
      }
    })
  }

  onConfirm(title) {
    Alert.alert('确认删除', '您真的要删除这个书单吗?', [
      { text: '确认', onPress: this.onDelete.bind(this, title) },
      { text: '取消' }
    ])
  }

  render() {
    let lists = [
      { title: '专业书籍', des: '反正就是很专业的啦' },
      { title: '经典书籍', des: '反正就是很经典的啦' }
    ]
    return (
      <View style={styles.container}>
        <RightButtonNav
          title={this.props.title}
          rightOnPress={() => this.rightOnPress()}
        />
        <TouchableOpacity
          style={styles.add}
          onPress={() => {
            let params = {
              onCallBack: async () => {
                let array = await Storage.get('book_list', [])
                this.setState({ lists: array })
              }
            }
            Actions[SCENE_BOOK_COLLECT_ADD](params)
          }}
        >
          <Image source={require('../../res/images/icon_add.png')} />
        </TouchableOpacity>
        <ScrollView style={styles.list}>
          {this.state.lists.map((item, i) => {
            return (
              <BookCollectItem
                item={item}
                big_title={this.props.title}
                onPress={(select, data) => this.onPressButton(select, data)}
                onDelete={title => {
                  this.onConfirm(title)
                }}
                key={item.list_id}
                data={item}
              />
            )
          })}
        </ScrollView>
      </View>
    )
  }
}

const styles = StyleSheet.create({
  container: {
    backgroundColor: 'rgb(242,246,250)',
    height: HEIGHT
  },
  add: {
    width: INNERWIDTH,
    borderRadius: 8,
    marginLeft: 8,
    height: 48,
    marginTop: 8,
    alignItems: 'center',
    justifyContent: 'center',
    backgroundColor: 'white'
  },
  list: {
    marginTop: 20
  }
})
<|endoftext|>


<commit_before>let createNewItem = '
';
createNewItem += '
ADD NEW ITEMS
';
createNewItem += '
';
createNewItem += '
Item name
 ';
createNewItem += '               <input type="text" id="newItemName" maxlength="15">';
createNewItem += '
';
createNewItem += '
';
createNewItem += '
Store or Market
';
createNewItem += '               <select id="storeName">';
createNewItem += '                   <option value="Aldi">Aldi</option>';
createNewItem += '                   <option value="Coles">Coles</option>';
createNewItem += '                   <option value="Fruit_Market">Fruit Market</option>';
createNewItem += '                   <option value="other">other</option>';
createNewItem += '               </select>';
createNewItem += '
';
createNewItem += '
';
createNewItem += '
Item Category
';
createNewItem += '               <select id="categoryType">';
createNewItem += '                   <option value="produce">Produce </option>';
createNewItem += '                   <option value="fresh">Fresh </option>';
createNewItem += '                   <option value="canned">Canned </option>';
createNewItem += '                   <option value="cleaning">Cleaning </option>';
createNewItem += '               </select>';
createNewItem += '
';
createNewItem += '
';
createNewItem += '
Add to choices
';
createNewItem += '               <button onclick="handlers.addNewItem()" class="btn btn-success">Press</button>';
createNewItem += '
';
createNewItem += '
';
createNewItem += '          <button onclick="handlers.removeMakeNewItems()" class="btn btn-success">Hide this section</button>';
createNewItem += '
';
createNewItem += '
';<commit_msg>add single item functionality<commit_after>let createNewItem = '
';
createNewItem += '
ADD NEW ITEMS
';
createNewItem += '
';
createNewItem += '
Item name
 ';
createNewItem += '               <input type="text" id="newItemName" maxlength="15">';
createNewItem += '
';
createNewItem += '
';
createNewItem += '
Store or Market
';
createNewItem += '               <select id="storeName">';
createNewItem += '                   <option value="Aldi">Aldi</option>';
createNewItem += '                   <option value="Coles">Coles</option>';
createNewItem += '                   <option value="Fruit_Market">Fruit Market</option>';
createNewItem += '                   <option value="Other">Other</option>';
createNewItem += '               </select>';
createNewItem += '
';
createNewItem += '
';
createNewItem += '
Item Category
';
createNewItem += '               <select id="categoryType">';
createNewItem += '                   <option value="produce">Produce </option>';
createNewItem += '                   <option value="fresh">Fresh </option>';
createNewItem += '                   <option value="canned">Canned </option>';
createNewItem += '                   <option value="cleaning">Cleaning </option>';
createNewItem += '               </select>';
createNewItem += '
';
createNewItem += '
';
createNewItem += '
Add to choices
';
createNewItem += '               <button onclick="handlers.addNewItem()" class="btn btn-success">Press</button>';
createNewItem += '
';
createNewItem += '
';
createNewItem += '          <button onclick="handlers.removeMakeNewItems()" class="btn btn-success">Hide this section</button>';
createNewItem += '
';
createNewItem += '
';<|endoftext|>


<commit_before>'use strict';

function generate(){
    settings_save();

    var charlist = '';
    var ids = {
      'latin-lowercase': 'abcdefghijklmnopqrstuvwxyz',
      'latin-uppercase': 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
      'numbers': '0123456789',
      'other-lowercase': settings_settings['other-lowercase-touse'],
      'other-uppercase': settings_settings['other-uppercase-touse'],
      'symbols': settings_settings['symbols-touse'],
    };
    for(var id in ids){
        if(settings_settings[id]){
            charlist += ids[id];
        }
    }
    if(charlist.length <= 0){
        document.getElementById('passwords').innerHTML = 'You must select at least one option.';
        return;
    }

    // Generate passwords.
    var loopcounter = settings_settings['number-of-passwords'] - 1;
    var passwords = '';
    do{
        passwords += string_format_html({
          'string': random_string(
            settings_settings['length'] - 1,
            charlist
          ),
        }) + '
';
    }while(loopcounter--);
    document.getElementById('passwords').innerHTML = passwords;
}

window.onload = function(e){
    input_init(
      {
        13: {
          'todo': generate,
        },
      }
    );
    settings_init(
      'PasswordGenerator.htm-',
      {
        'latin-lowercase': true,
        'latin-uppercase': true,
        'length': 15,
        'number-of-passwords': 1,
        'numbers': true,
        'other-lowercase': true,
        'other-lowercase-touse': 'áčďéěíňóřšťúůýž',
        'other-uppercase': true,
        'other-uppercase-touse': 'ÁČĎÉĚÍŇÓŘŠŤÚŮÝŽ',
        'symbols': true,
        'symbols-touse': '~!@#$%^&*()-_=+[{]}\\|;:\'\",<.>/?',
      }
    );

    settings_update();
    generate();

    document.getElementById('generate').onclick = generate;
    document.getElementById('settings-reset').onclick = settings_reset;
};
<commit_msg>switched settings.js to args<commit_after>'use strict';

function generate(){
    settings_save();

    var charlist = '';
    var ids = {
      'latin-lowercase': 'abcdefghijklmnopqrstuvwxyz',
      'latin-uppercase': 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
      'numbers': '0123456789',
      'other-lowercase': settings_settings['other-lowercase-touse'],
      'other-uppercase': settings_settings['other-uppercase-touse'],
      'symbols': settings_settings['symbols-touse'],
    };
    for(var id in ids){
        if(settings_settings[id]){
            charlist += ids[id];
        }
    }
    if(charlist.length <= 0){
        document.getElementById('passwords').innerHTML = 'You must select at least one option.';
        return;
    }

    // Generate passwords.
    var loopcounter = settings_settings['number-of-passwords'] - 1;
    var passwords = '';
    do{
        passwords += string_format_html({
          'string': random_string(
            settings_settings['length'] - 1,
            charlist
          ),
        }) + '
';
    }while(loopcounter--);
    document.getElementById('passwords').innerHTML = passwords;
}

window.onload = function(e){
    input_init(
      {
        13: {
          'todo': generate,
        },
      }
    );
    settings_init({
      'prefix': 'PasswordGenerator.htm-',
      'settings': {
        'latin-lowercase': true,
        'latin-uppercase': true,
        'length': 15,
        'number-of-passwords': 1,
        'numbers': true,
        'other-lowercase': true,
        'other-lowercase-touse': 'áčďéěíňóřšťúůýž',
        'other-uppercase': true,
        'other-uppercase-touse': 'ÁČĎÉĚÍŇÓŘŠŤÚŮÝŽ',
        'symbols': true,
        'symbols-touse': '~!@#$%^&*()-_=+[{]}\\|;:\'\",<.>/?',
      },
    });

    settings_update();
    generate();

    document.getElementById('generate').onclick = generate;
    document.getElementById('settings-reset').onclick = settings_reset;
};
<|endoftext|>


<commit_before>/**
 * 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.
 */

'use strict';

//This code was borrowed from the Google Drive realtime-tasks sample.

zr.service('realtime', ['$q', '$rootScope', '$routeParams', 'config',
	/**
	 * Handles document creation & loading for the app. Keeps only
	 * one document loaded at a time.
	 *
	 * @param $q
	 * @param $rootScope
	 * @param config
	 */
	function ($q, $rootScope, $routeParams, config) {
		//Parameters for new project
		this.ideGraphical = false;
		this.gameId = null;

		this.id = null;
		this.document = null;
		var PROJ_INIT_TEXT = 'void init() {\n\t\n}\n\nvoid loop() {\n\t\n}\n';

		/**
		 * Close the current document.
		 */
		this.closeDocument = function () {
			Blockly.Realtime.model_ = null;
			Blockly.Realtime.blocksMap_ = null;
			Blockly.Realtime.topBlocks_ = null;
			this.document.close();
			this.document = null;
			this.id = null;
		};

		/**
		 * Ensure the document is loaded.
		 *
		 * @param id
		 * @returns {angular.$q.promise}
		 */
		this.getDocument = function (id, dontReplace) {
			if (this.id === id) {
				return $q.when(this.document);
			} else if (this.document && !dontReplace) {
				this.closeDocument();
			}
			return this.load(id, dontReplace);
		};

		/**
		 * Creates a new document.
		 *
		 * @param title
		 * @returns {angular.$q.promise}
		 */
		this.createDocument = function (title, folder) {
			var deferred = $q.defer();
			var onComplete = function (result) {
				if (result && !result.error) {
					deferred.resolve(result);
				} else {
					deferred.reject(result);
				}
			};

			if(!folder) {
				folder = [];
			}

			gapi.client.request({
				'path': '/drive/v2/files',
				'method': 'POST',
				'body': JSON.stringify({
					title: title,
					mimeType: 'application/vnd.google-apps.drive-sdk',
					parents: folder
				})
			}).execute(onComplete);
			return deferred.promise;
		};

		/**
		 * Checks to make sure the user is currently authorized and the access
		 * token hasn't expired.
		 *
		 * @param immediateMode
		 * @param userId
		 * @returns {angular.$q.promise}
		 */
		this.requireAuth = function(immediate) {
			/* jshint camelCase: false */
			var token = gapi.auth.getToken();
			var now = Date.now() / 1000;
			if (token && ((token.expires_at - now) > (60))) {
				return $q.when(token);
			} else {
				var params = {
					'client_id': config.clientId,
					'scope': config.scopes,
					'immediate': immediate !== false
				};
				var deferred = $q.defer();
				gapi.auth.authorize(params, function (result) {
					if (result && !result.error) {
						deferred.resolve(result);
						$rootScope.$digest();
					}
					else if(typeof immediate === 'boolean') {
						deferred.resolve(result);
					}
					else {
						//Try again w/popup
						params['immediate'] = false;
						gapi.auth.authorize(params, function() {
							deferred.resolve(result);
							$rootScope.$digest();
						});
					}
				});
				return deferred.promise;
			}
		};

		var this_ = this;
		/**
		 * Actually load a document. If the document is new, initializes
		 * the model with an empty list of todos.
		 *
		 * @param id
		 * @returns {angular.$q.promise}
		 */
		this.load = function (id, dontReplace) {
			var deferred = $q.defer();
			this.requireAuth().then(function() {
				var initialize = function (model) {
					var root = model.getRoot();
					var pages = model.createMap();
					root.set('pages', pages);
					root.set('gameId', this_.gameId);
					//Initialize the main pages
					if(!this_.ideGraphical) {
						root.set('graphical', false);
						pages.set('main', model.createString(PROJ_INIT_TEXT));
					}
					else {
						root.set('graphical', true);
						root.set('cglobals', model.createMap());
						root.set('procedures', model.createMap());
						var pageRoot = model.createMap();
						pages.set('main', pageRoot);
						//This code copied from blockly/core/realtime.js
						pageRoot.set('blocks', model.createMap());
						pageRoot.set('topBlocks', model.createList());
						pageRoot.set('type','loop');
						var pageRoot2 = model.createMap();
						pages.set('init', pageRoot2);
						pageRoot2.set('type','init');
						pageRoot2.set('blocks', model.createMap());
						pageRoot2.set('topBlocks', model.createList());
					}
					var log = model.createMap();
					root.set('log', log);
					//Log entries are identified by timestamp, plus some random digits to avoid collisions
					var key = String(new Date().getTime() + Math.random());
					log.set(key, {
						user: 'Ethan DiNinno',
						title: 'Project created',
						content: ''
					});
				};
				var onLoad = function (document) {
					//dontReplace is for loading the opponent
					if(!dontReplace) {
						this_.setDocument(id, document);
						var model = document.getModel();
						Blockly.Realtime.model_ = model;
						Blockly.zr_cpp.C_GLOBAL_VARS = model.getRoot().get('cglobals');
						Blockly.zr_cpp.procedures = model.getRoot().get('procedures');
					}
					deferred.resolve(document);
					$rootScope.$digest();
				}.bind(this);
				var onError = function (error) {
					if (error.type === gapi.drive.realtime.ErrorType.TOKEN_REFRESH_REQUIRED) {
						$rootScope.$emit('todos.token_refresh_required');
					} else if (error.type === gapi.drive.realtime.ErrorType.CLIENT_ERROR) {
						$rootScope.$emit('todos.client_error');
					} else if (error.type === gapi.drive.realtime.ErrorType.NOT_FOUND) {
						deferred.reject(error);
						$rootScope.$emit('todos.not_found', id);
					}
					$rootScope.$digest();
				};
				gapi.drive.realtime.load(id, onLoad, initialize, onError);
			});

			return deferred.promise;
		};

		/**
		 * Watches the model for any remote changes to force a digest cycle
		 *
		 * @param event
		 */
		this.changeListener = function (event) {
			if (!event.isLocal) {
				$rootScope.$digest();
			}
			window.lastEvent = event;
		};

		window.lastEvent = null;

		this.setDocument = function (id, document) {
			document.getModel().getRoot().addEventListener(
				gapi.drive.realtime.EventType.OBJECT_CHANGED,
				this.changeListener);
			this.document = document;
			this.id = id;
		};

		this.getDocAsString = function(modelRoot, countLines) {
			var str = '';
			var pages = modelRoot.get('pages');
			var keys = pages.keys().sort();
			var len = keys.length;
			var startLines = [];
			if(!modelRoot.get('graphical')) {
				for(var i = 0; i < len; i++) {
					if(countLines) {
						//Magic number: 25 is the offset needed to make line numbers match up with the zr.cpp template; needs to be changed if zr.cpp changes
						startLines.push({
							'name': keys[i],
							'line': str.split(/\r\n|\r|\n/).length + 25
						});
					}
					str = str + '//Begin page ' + keys[i] + '\n' + pages.get(keys[i]).getText() + '\n//End page ' + keys[i] + '\n';
				}
			}
			else {
				//Save the globals
				var oldGlobals = Blockly.zr_cpp.C_GLOBAL_VARS;
				//Load the target doc's globals
				Blockly.zr_cpp.C_GLOBAL_VARS = modelRoot.get('cglobals');
				for(var i = 0; i < len; i++) {
					var topBlocks = pages.get(keys[i]).get('topBlocks').asArray();
					if(topBlocks.length === 0) {
						//This will happen when the init page has not been initialized; it will be dealt with in finishFull
						continue;
					}
					var domText = topBlocks[0].xmlDom;
					var domObj = Blockly.Xml.textToDom(domText);
					var block = Blockly.Xml.domToSoloBlock(domObj);
					var code = Blockly.zr_cpp.blockToCode(block);
					if(countLines) {
						//Magic number: 25 is the offset needed to make line numbers match up with the zr.cpp template; needs to be changed if zr.cpp changes
						startLines.push({
							'name': keys[i],
							'line': str.split(/\r\n|\r|\n/).length + 25
						});
					}
					str = str + '//Begin page ' + keys[i] + '\n' + code + '\n//End page ' + keys[i] + '\n\n';
				}
				//Add globals and adjust line numbers
				if(countLines) {
					lengthbefore = str.split(/\r\n|\r|\n/).length;
					str = Blockly.zr_cpp.finishFull(str);
					diff = str.split(/\r\n|\r|\n/).length - lengthbefore;
					for(var i = startLines.length; i--;) {
						startLines[i].line += diff;
					}
				}
				else {
					str = Blockly.zr_cpp.finishFull(str);
				}
			}
			//Restore globals
			Blockly.zr_cpp.C_GLOBAL_VARS = oldGlobals;
			if(countLines) {
				return [str, startLines];
			}
			return str;
		};
	}]
);
<commit_msg>Adding parsing of compile errors<commit_after>/**
 * 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.
 */

'use strict';

//This code was borrowed from the Google Drive realtime-tasks sample.

zr.service('realtime', ['$q', '$rootScope', '$routeParams', 'config',
	/**
	 * Handles document creation & loading for the app. Keeps only
	 * one document loaded at a time.
	 *
	 * @param $q
	 * @param $rootScope
	 * @param config
	 */
	function ($q, $rootScope, $routeParams, config) {
		//Parameters for new project
		this.ideGraphical = false;
		this.gameId = null;

		this.id = null;
		this.document = null;
		var PROJ_INIT_TEXT = 'void init() {\n\t\n}\n\nvoid loop() {\n\t\n}\n';

		/**
		 * Close the current document.
		 */
		this.closeDocument = function () {
			Blockly.Realtime.model_ = null;
			Blockly.Realtime.blocksMap_ = null;
			Blockly.Realtime.topBlocks_ = null;
			this.document.close();
			this.document = null;
			this.id = null;
		};

		/**
		 * Ensure the document is loaded.
		 *
		 * @param id
		 * @returns {angular.$q.promise}
		 */
		this.getDocument = function (id, dontReplace) {
			if (this.id === id) {
				return $q.when(this.document);
			} else if (this.document && !dontReplace) {
				this.closeDocument();
			}
			return this.load(id, dontReplace);
		};

		/**
		 * Creates a new document.
		 *
		 * @param title
		 * @returns {angular.$q.promise}
		 */
		this.createDocument = function (title, folder) {
			var deferred = $q.defer();
			var onComplete = function (result) {
				if (result && !result.error) {
					deferred.resolve(result);
				} else {
					deferred.reject(result);
				}
			};

			if(!folder) {
				folder = [];
			}

			gapi.client.request({
				'path': '/drive/v2/files',
				'method': 'POST',
				'body': JSON.stringify({
					title: title,
					mimeType: 'application/vnd.google-apps.drive-sdk',
					parents: folder
				})
			}).execute(onComplete);
			return deferred.promise;
		};

		/**
		 * Checks to make sure the user is currently authorized and the access
		 * token hasn't expired.
		 *
		 * @param immediateMode
		 * @param userId
		 * @returns {angular.$q.promise}
		 */
		this.requireAuth = function(immediate) {
			/* jshint camelCase: false */
			var token = gapi.auth.getToken();
			var now = Date.now() / 1000;
			if (token && ((token.expires_at - now) > (60))) {
				return $q.when(token);
			} else {
				var params = {
					'client_id': config.clientId,
					'scope': config.scopes,
					'immediate': immediate !== false
				};
				var deferred = $q.defer();
				gapi.auth.authorize(params, function (result) {
					if (result && !result.error) {
						deferred.resolve(result);
						$rootScope.$digest();
					}
					else if(typeof immediate === 'boolean') {
						deferred.resolve(result);
					}
					else {
						//Try again w/popup
						params['immediate'] = false;
						gapi.auth.authorize(params, function() {
							deferred.resolve(result);
							$rootScope.$digest();
						});
					}
				});
				return deferred.promise;
			}
		};

		var this_ = this;
		/**
		 * Actually load a document. If the document is new, initializes
		 * the model with an empty list of todos.
		 *
		 * @param id
		 * @returns {angular.$q.promise}
		 */
		this.load = function (id, dontReplace) {
			var deferred = $q.defer();
			this.requireAuth().then(function() {
				var initialize = function (model) {
					var root = model.getRoot();
					var pages = model.createMap();
					root.set('pages', pages);
					root.set('gameId', this_.gameId);
					//Initialize the main pages
					if(!this_.ideGraphical) {
						root.set('graphical', false);
						pages.set('main', model.createString(PROJ_INIT_TEXT));
					}
					else {
						root.set('graphical', true);
						root.set('cglobals', model.createMap());
						root.set('procedures', model.createMap());
						var pageRoot = model.createMap();
						pages.set('main', pageRoot);
						//This code copied from blockly/core/realtime.js
						pageRoot.set('blocks', model.createMap());
						pageRoot.set('topBlocks', model.createList());
						pageRoot.set('type','loop');
						var pageRoot2 = model.createMap();
						pages.set('init', pageRoot2);
						pageRoot2.set('type','init');
						pageRoot2.set('blocks', model.createMap());
						pageRoot2.set('topBlocks', model.createList());
					}
					var log = model.createMap();
					root.set('log', log);
					//Log entries are identified by timestamp, plus some random digits to avoid collisions
					var key = String(new Date().getTime() + Math.random());
					log.set(key, {
						user: 'Ethan DiNinno',
						title: 'Project created',
						content: ''
					});
				};
				var onLoad = function (document) {
					//dontReplace is for loading the opponent
					if(!dontReplace) {
						this_.setDocument(id, document);
						var model = document.getModel();
						Blockly.Realtime.model_ = model;
						Blockly.zr_cpp.C_GLOBAL_VARS = model.getRoot().get('cglobals');
						Blockly.zr_cpp.procedures = model.getRoot().get('procedures');
					}
					deferred.resolve(document);
					$rootScope.$digest();
				}.bind(this);
				var onError = function (error) {
					if (error.type === gapi.drive.realtime.ErrorType.TOKEN_REFRESH_REQUIRED) {
						$rootScope.$emit('todos.token_refresh_required');
					} else if (error.type === gapi.drive.realtime.ErrorType.CLIENT_ERROR) {
						$rootScope.$emit('todos.client_error');
					} else if (error.type === gapi.drive.realtime.ErrorType.NOT_FOUND) {
						deferred.reject(error);
						$rootScope.$emit('todos.not_found', id);
					}
					$rootScope.$digest();
				};
				gapi.drive.realtime.load(id, onLoad, initialize, onError);
			});

			return deferred.promise;
		};

		/**
		 * Watches the model for any remote changes to force a digest cycle
		 *
		 * @param event
		 */
		this.changeListener = function (event) {
			if (!event.isLocal) {
				$rootScope.$digest();
			}
			window.lastEvent = event;
		};

		window.lastEvent = null;

		this.setDocument = function (id, document) {
			document.getModel().getRoot().addEventListener(
				gapi.drive.realtime.EventType.OBJECT_CHANGED,
				this.changeListener);
			this.document = document;
			this.id = id;
		};

		this.getDocAsString = function(modelRoot, countLines) {
			var str = '';
			var pages = modelRoot.get('pages');
			var keys = pages.keys().sort();
			var len = keys.length;
			var startLines = [];
			if(!modelRoot.get('graphical')) {
				for(var i = 0; i < len; i++) {
					if(countLines) {
						//Magic number: 24 is the offset needed to make line numbers match up with the zr.cpp template; needs to be changed if zr.cpp changes
						startLines.push({
							'name': keys[i],
							'line': str.split(/\r\n|\r|\n/).length + 24
						});
					}
					str = str + '//Begin page ' + keys[i] + '\n' + pages.get(keys[i]).getText() + '\n//End page ' + keys[i] + '\n';
				}
			}
			else {
				//Save the globals
				var oldGlobals = Blockly.zr_cpp.C_GLOBAL_VARS;
				//Load the target doc's globals
				Blockly.zr_cpp.C_GLOBAL_VARS = modelRoot.get('cglobals');
				for(var i = 0; i < len; i++) {
					var topBlocks = pages.get(keys[i]).get('topBlocks').asArray();
					if(topBlocks.length === 0) {
						//This will happen when the init page has not been initialized; it will be dealt with in finishFull
						continue;
					}
					var domText = topBlocks[0].xmlDom;
					var domObj = Blockly.Xml.textToDom(domText);
					var block = Blockly.Xml.domToSoloBlock(domObj);
					var code = Blockly.zr_cpp.blockToCode(block);
					if(countLines) {
						//Magic number: 24 is the offset needed to make line numbers match up with the zr.cpp template; needs to be changed if zr.cpp changes
						startLines.push({
							'name': keys[i],
							'line': str.split(/\r\n|\r|\n/).length + 24
						});
					}
					str = str + '//Begin page ' + keys[i] + '\n' + code + '\n//End page ' + keys[i] + '\n\n';
				}
				//Add globals and adjust line numbers
				if(countLines) {
					lengthbefore = str.split(/\r\n|\r|\n/).length;
					str = Blockly.zr_cpp.finishFull(str);
					diff = str.split(/\r\n|\r|\n/).length - lengthbefore;
					for(var i = startLines.length; i--;) {
						startLines[i].line += diff;
					}
				}
				else {
					str = Blockly.zr_cpp.finishFull(str);
				}
			}
			//Restore globals
			Blockly.zr_cpp.C_GLOBAL_VARS = oldGlobals;
			if(countLines) {
				return [str, startLines];
			}
			return str;
		};
	}]
);
<|endoftext|>


<commit_before>/*
*  Directives
*/
// Sidenav
angular.module('app.directives', [])
    .directive('siteSidenav', function () {
        var SidenavController = function ($scope, $mdSidenav) {
            console.info('Successfully loaded!');

            $scope.main = [
                { url: 'https://chan4077.github.io', title: 'Main', icon: 'home' },
                { url: 'https://chan4077.github.io/about', title: 'About Me', icon: 'account-box' },
                { url: 'https://chan4077.github.io/blog', title: 'Blog', icon: 'blogger' },
                { url: 'https://chan4077.github.io/media', title: 'Social Media', icon: 'google-plus' },
                { url: 'https://chan4077.github.io/angular', title: 'Angular', icon: 'angular' },
            ];
            $scope.other = [
                { url: 'https://github.com/Chan4077/chan4077.github.io', title: 'Project Page', icon: 'github-circle' },
                { url: 'https://chan4077.github.io/help', title: 'Get Help', icon: 'help-circle' },
                { url: 'https://chan4077.github.io/beta', title: 'Beta', icon: 'flask' },
                { url: 'https://chan4077.github.io/beta/blog.html', title: 'Beta Blog', icon: 'flask' },
                { url: 'https://github.com/Chan4077/chan4077.github.io/wiki/Sites', title: 'Sitemap', icon: 'sitemap' }
            ];
            $scope.projects = [
                { url: 'https://chan4077.github.io/projects', title: 'Projects', icon: 'code-tags' },
                { url: 'https://chan4077.github.io/MyFirstApp', title: 'MyFirstApp', icon: 'code-tags' },
                { url: 'https://chan4077.github.io/icons', title: 'Icons', icon: 'code-tags' }
            ];
            // Toggle left sidenav
            $scope.toggleLeftMenu = function () {
                $mdSidenav('left').toggle();
            };
            $scope.toggleAllLinks = function () {
                if ($scope.toggleLinks && $scope.toggleOtherLinks && $scope.toggleProjectLinks) {
                    $scope.toggledAllLinks = true;
                    console.log('All are expanded.');
                } else {
                    $scope.toggledAllLinks = false;
                    console.log('They are not all expanded.');
                }
            };
            $scope.toggleLinks = function() {
                $scope.toggledLinks = !$scope.toggledLinks;
                console.log('Toggled links: '+$scope.toggledLinks);
            }
            $scope.toggleOtherLinks = function() {
                $scope.toggledOtherLinks = !$scope.toggledOtherLinks;
                console.log('Toggled other links: '+$scope.toggledOtherLinks);
            }
            $scope.toggleProjectLinks = function() {
                $scope.toggledProjectLinks = !$scope.toggledProjectLinks;
                console.log('Toggled project links: '+$scope.toggledProjectLinks);
            }
        };
        return {
            restrict: 'E',
            templateUrl: '/templates/sidenav.html',
            transclude: true,
            controller: SidenavController
        };
    })
    .directive('siteToolbar', function () {
        return {
            restrict: 'E',
            templateUrl: '/templates/toolbar.html',
            transclude: true,
            controller: function ToolbarController($scope, $location, $mdDialog, $mdToast) {
                console.info('Loaded!');
                $scope.data = [{
                    "url": "/",
                    "name": "Home"
                }, {
                    "url": "beta",
                    "name": "Beta"
                }, {
                    "url": "cookies",
                    "name": "Cookies"
                }, {
                    "url": "projects",
                    "name": "Projects"
                }, {
                    "url": "help",
                    "name": "Help"
                }, {
                    "url": "about",
                    "name": "About"
                }, {
                    "url": "angular",
                    "name": "Angular"
                }]
                var urls = ['/', 'beta', 'cookies', 'projects', 'help', 'about', 'angular']
                angular.forEach($scope.data, function (value, key) {
                    // Based on http://plnkr.co/edit/WeQhCb?p=preview
                    if (urls.indexOf(value.url) !== -1) {
                        $scope[value.url] = value.name;
                    } else {
                        console.error('Invalid value');
                    }

                })
                console.log('url visited: ' + $location.path());
                $scope.viewGithub = function () {
                    window.location.href = "https://github.com/Chan4077/chan4077.github.io";
                };
                $scope.refreshPage = function () {
                    window.location.reload(true);
                };
                // $mdDialog (start)
                $scope.whats_new = function (ev) {
                    $mdDialog.show({
                        controller: WhatNewController,
                        templateUrl: '/templates/whats_new_tmpl.html',
                        parent: angular.element(document.body),
                        targetEvent: ev,
                        clickOutsideToClose: true,
                    });
                };
                $scope.settings = function (ev) {
                    $mdDialog.show({
                        controller: SettingsController,
                        templateUrl: '/templates/settings_tmpl.html',
                        parent: angular.element(document.body),
                        targetEvent: ev,
                        clickOutsideToClose: true,
                    });
                };
                $scope.sendFeedback = function () {
                    // Head to Github -> Create new issue
                    var toast = $mdToast.simple()
                        .textContent('Send feedback here: ')
                        .action('GO TO GITHUB')
                        .highlightAction(true)
                        .position("bottom left");

                    $mdToast.show(toast).then(function (response) {
                        if (response == 'ok') {
                            alert('Redirecting to Github...');
                            window.location.href = 'https://github.com/Chan4077/chan4077.github.io/issues/new';
                        }
                    })
                };
                // What's New
                function WhatNewController($scope, $mdDialog, $mdToast) {
                    // Hide button
                    $scope.hide = function () {
                        // Hides $mdDialog
                        $mdDialog.hide();
                    };
                    // Cancel button
                    $scope.cancel = function () {
                        // Cancels $mdDialog
                        $mdDialog.cancel();
                    };
                    // Help button
                    $scope.help = function () {
                        // Redirect to help site
                        window.location.href = "https://chan4077.github.io/help";
                        console.info('Redirecting to Help site...');
                    }
                    // More Info button
                    $scope.moreInfo = function () {
                        // Note that this is percent-encoded
                        // More information: https://en.wikipedia.org/wiki/Percent-encoding
                        window.location.href = "https://github.com/Chan4077/chan4077.github.io/wiki/What%27s-New";
                    }
                };
                // Feedback
                function FeedbackController($scope, $mdDialog, $mdToast) {
                    $scope.hide = function () {
                        $mdDialog.hide();
                    };
                    $scope.cancel = function () {
                        $mdDialog.cancel();
                        $mdToast.show(
                            $mdToast.simple()
                                .textContent('Feedback cancelled')
                                .position('bottom left')
                                .hideDelay(3000)
                        );
                    };
                    $scope.help = function () {
                        window.location.href = "https://chan4077.github.io/help/about_feedback.html";
                        console.info('Redirecting to Help site...');
                    }
                    // $scope.oriFeedback = {
                    //     name: "",
                    //     email: "",
                    //     sendFeedback: ""
                    // };
                    // // Declare a variable called oriFeedback
                    // var oriFeedback = angular.copy($scope.oriFeedback);

                    // // On initial, set feedback to original
                    // $scope.feedback = angular.copy(oriFeedback);

                    // // Reset form
                    // $scope.reset = function () {
                    //     $scope.feedback = angular.copy(oriFeedback);
                    //     // Reset form
                    //     $scope.feedbackhelp.$setPristine();
                    //     // Reset validation errors
                    //     $scope.feedbackhelp.$setUntouched();
                    // };
                    $scope.postOnGithub = function () {
                        $mdDialog.cancel();
                        console.debug('Redirecting to Github...');
                        window.location.href = "https://github.com/Chan4077/chan4077.github.io/issues/new";
                    };
                };
                // Settings
                function SettingsController($scope, $mdDialog) {
                    $scope.hide = function () {
                        $mdDialog.hide();
                    };
                    $scope.cancel = function () {
                        $mdDialog.cancel();
                    };
                    $scope.help = function () {
                        window.location.href = "https://chan4077.github.io/help";
                        console.info('Redirecting to Help site...');
                    }
                    $scope.general_settings = {
                        proBar: true,
                        enableSound: true
                    };
                    $scope.developer_settings = {
                        consoleLog: false,
                        showExtraOptions: false
                    };
                    $scope.extra_settings = {
                        showCredits: true,
                        enableOffline: true
                    };
                    // $mdDialog (end)

                    $scope.about_site = function () {
                        window.location.href = "https://chan4077.github.io/about#angular";
                    };
                };
            }
        };
    })
    // Speed Dial
    .directive('siteSpeedDial', function () {
        return {
            restrict: 'E',
            templateUrl: '/templates/speeddial.html',
            transclude: true
        };
    })
    // Controllers
    .controller('SideNavController', function ($scope, $mdSidenav) {

    })
// App config
angular.module('app.config', [])
    .config(function ($mdIconProvider, $mdToastProvider, $interpolateProvider) {
        $mdIconProvider
            .defaultIconSet('/img/mdi.svg')
            .icon('website-logo', '/img/website.svg');
        console.info('Successfully initialized!');
        $interpolateProvider.startSymbol('{(');
        $interpolateProvider.endSymbol(')}');
    });<commit_msg>fix(sidenav-toggle) - See if this works...<commit_after>/*
*  Directives
*/
// Sidenav
angular.module('app.directives', [])
    .directive('siteSidenav', function () {
        var SidenavController = function ($scope, $mdSidenav) {
            console.info('Successfully loaded!');

            $scope.main = [
                { url: 'https://chan4077.github.io', title: 'Main', icon: 'home' },
                { url: 'https://chan4077.github.io/about', title: 'About Me', icon: 'account-box' },
                { url: 'https://chan4077.github.io/blog', title: 'Blog', icon: 'blogger' },
                { url: 'https://chan4077.github.io/media', title: 'Social Media', icon: 'google-plus' },
                { url: 'https://chan4077.github.io/angular', title: 'Angular', icon: 'angular' },
            ];
            $scope.other = [
                { url: 'https://github.com/Chan4077/chan4077.github.io', title: 'Project Page', icon: 'github-circle' },
                { url: 'https://chan4077.github.io/help', title: 'Get Help', icon: 'help-circle' },
                { url: 'https://chan4077.github.io/beta', title: 'Beta', icon: 'flask' },
                { url: 'https://chan4077.github.io/beta/blog.html', title: 'Beta Blog', icon: 'flask' },
                { url: 'https://github.com/Chan4077/chan4077.github.io/wiki/Sites', title: 'Sitemap', icon: 'sitemap' }
            ];
            $scope.projects = [
                { url: 'https://chan4077.github.io/projects', title: 'Projects', icon: 'code-tags' },
                { url: 'https://chan4077.github.io/MyFirstApp', title: 'MyFirstApp', icon: 'code-tags' },
                { url: 'https://chan4077.github.io/icons', title: 'Icons', icon: 'code-tags' }
            ];
            // Toggle left sidenav
            $scope.toggleLeftMenu = function () {
                $mdSidenav('left').toggle();
            };
            $scope.toggleAllLinks = function () {
                if (!$scope.toggleLinks && !$scope.toggleOtherLinks && !$scope.toggleProjectLinks) {
                    $scope.toggledAllLinks = true;
                    $scope.toggledLinks = true;
                    $scope.toggledProjectLinks = true;
                    $scope.toggledOtherLinks = true;
                    console.log('All are expanded.');
                } else {
                    $scope.toggledAllLinks = false;
                    $scope.toggledLinks = false;
                    $scope.toggledProjectLinks = false;
                    $scope.toggledOtherLinks = false;
                    console.log('They are collapsed.');
                }
            };
            $scope.toggleLinks = function() {
                $scope.toggledLinks = !$scope.toggledLinks;
                console.log('Toggled links: '+$scope.toggledLinks);
            }
            $scope.toggleOtherLinks = function() {
                $scope.toggledOtherLinks = !$scope.toggledOtherLinks;
                console.log('Toggled other links: '+$scope.toggledOtherLinks);
            }
            $scope.toggleProjectLinks = function() {
                $scope.toggledProjectLinks = !$scope.toggledProjectLinks;
                console.log('Toggled project links: '+$scope.toggledProjectLinks);
            }
        };
        return {
            restrict: 'E',
            templateUrl: '/templates/sidenav.html',
            transclude: true,
            controller: SidenavController
        };
    })
    .directive('siteToolbar', function () {
        return {
            restrict: 'E',
            templateUrl: '/templates/toolbar.html',
            transclude: true,
            controller: function ToolbarController($scope, $location, $mdDialog, $mdToast) {
                console.info('Loaded!');
                $scope.data = [{
                    "url": "/",
                    "name": "Home"
                }, {
                    "url": "beta",
                    "name": "Beta"
                }, {
                    "url": "cookies",
                    "name": "Cookies"
                }, {
                    "url": "projects",
                    "name": "Projects"
                }, {
                    "url": "help",
                    "name": "Help"
                }, {
                    "url": "about",
                    "name": "About"
                }, {
                    "url": "angular",
                    "name": "Angular"
                }]
                var urls = ['/', 'beta', 'cookies', 'projects', 'help', 'about', 'angular']
                angular.forEach($scope.data, function (value, key) {
                    // Based on http://plnkr.co/edit/WeQhCb?p=preview
                    if (urls.indexOf(value.url) !== -1) {
                        $scope[value.url] = value.name;
                    } else {
                        console.error('Invalid value');
                    }

                })
                console.log('url visited: ' + $location.path());
                $scope.viewGithub = function () {
                    window.location.href = "https://github.com/Chan4077/chan4077.github.io";
                };
                $scope.refreshPage = function () {
                    window.location.reload(true);
                };
                // $mdDialog (start)
                $scope.whats_new = function (ev) {
                    $mdDialog.show({
                        controller: WhatNewController,
                        templateUrl: '/templates/whats_new_tmpl.html',
                        parent: angular.element(document.body),
                        targetEvent: ev,
                        clickOutsideToClose: true,
                    });
                };
                $scope.settings = function (ev) {
                    $mdDialog.show({
                        controller: SettingsController,
                        templateUrl: '/templates/settings_tmpl.html',
                        parent: angular.element(document.body),
                        targetEvent: ev,
                        clickOutsideToClose: true,
                    });
                };
                $scope.sendFeedback = function () {
                    // Head to Github -> Create new issue
                    var toast = $mdToast.simple()
                        .textContent('Send feedback here: ')
                        .action('GO TO GITHUB')
                        .highlightAction(true)
                        .position("bottom left");

                    $mdToast.show(toast).then(function (response) {
                        if (response == 'ok') {
                            alert('Redirecting to Github...');
                            window.location.href = 'https://github.com/Chan4077/chan4077.github.io/issues/new';
                        }
                    })
                };
                // What's New
                function WhatNewController($scope, $mdDialog, $mdToast) {
                    // Hide button
                    $scope.hide = function () {
                        // Hides $mdDialog
                        $mdDialog.hide();
                    };
                    // Cancel button
                    $scope.cancel = function () {
                        // Cancels $mdDialog
                        $mdDialog.cancel();
                    };
                    // Help button
                    $scope.help = function () {
                        // Redirect to help site
                        window.location.href = "https://chan4077.github.io/help";
                        console.info('Redirecting to Help site...');
                    }
                    // More Info button
                    $scope.moreInfo = function () {
                        // Note that this is percent-encoded
                        // More information: https://en.wikipedia.org/wiki/Percent-encoding
                        window.location.href = "https://github.com/Chan4077/chan4077.github.io/wiki/What%27s-New";
                    }
                };
                // Feedback
                function FeedbackController($scope, $mdDialog, $mdToast) {
                    $scope.hide = function () {
                        $mdDialog.hide();
                    };
                    $scope.cancel = function () {
                        $mdDialog.cancel();
                        $mdToast.show(
                            $mdToast.simple()
                                .textContent('Feedback cancelled')
                                .position('bottom left')
                                .hideDelay(3000)
                        );
                    };
                    $scope.help = function () {
                        window.location.href = "https://chan4077.github.io/help/about_feedback.html";
                        console.info('Redirecting to Help site...');
                    }
                    // $scope.oriFeedback = {
                    //     name: "",
                    //     email: "",
                    //     sendFeedback: ""
                    // };
                    // // Declare a variable called oriFeedback
                    // var oriFeedback = angular.copy($scope.oriFeedback);

                    // // On initial, set feedback to original
                    // $scope.feedback = angular.copy(oriFeedback);

                    // // Reset form
                    // $scope.reset = function () {
                    //     $scope.feedback = angular.copy(oriFeedback);
                    //     // Reset form
                    //     $scope.feedbackhelp.$setPristine();
                    //     // Reset validation errors
                    //     $scope.feedbackhelp.$setUntouched();
                    // };
                    $scope.postOnGithub = function () {
                        $mdDialog.cancel();
                        console.debug('Redirecting to Github...');
                        window.location.href = "https://github.com/Chan4077/chan4077.github.io/issues/new";
                    };
                };
                // Settings
                function SettingsController($scope, $mdDialog) {
                    $scope.hide = function () {
                        $mdDialog.hide();
                    };
                    $scope.cancel = function () {
                        $mdDialog.cancel();
                    };
                    $scope.help = function () {
                        window.location.href = "https://chan4077.github.io/help";
                        console.info('Redirecting to Help site...');
                    }
                    $scope.general_settings = {
                        proBar: true,
                        enableSound: true
                    };
                    $scope.developer_settings = {
                        consoleLog: false,
                        showExtraOptions: false
                    };
                    $scope.extra_settings = {
                        showCredits: true,
                        enableOffline: true
                    };
                    // $mdDialog (end)

                    $scope.about_site = function () {
                        window.location.href = "https://chan4077.github.io/about#angular";
                    };
                };
            }
        };
    })
    // Speed Dial
    .directive('siteSpeedDial', function () {
        return {
            restrict: 'E',
            templateUrl: '/templates/speeddial.html',
            transclude: true
        };
    })
    // Controllers
    .controller('SideNavController', function ($scope, $mdSidenav) {

    })
// App config
angular.module('app.config', [])
    .config(function ($mdIconProvider, $mdToastProvider, $interpolateProvider) {
        $mdIconProvider
            .defaultIconSet('/img/mdi.svg')
            .icon('website-logo', '/img/website.svg');
        console.info('Successfully initialized!');
        $interpolateProvider.startSymbol('{(');
        $interpolateProvider.endSymbol(')}');
    });<|endoftext|>


<commit_before>---
---
$(document).ready(function() {
  var groups = {};
  var WEDDING_BOT_URL = '{{ site.wedding-bot-url }}';

  var guestListRequest = new XMLHttpRequest();
  guestListRequest.onreadystatechange = function(response) {
    if (guestListRequest.readyState === XMLHttpRequest.DONE) {
      $(".loader-container").addClass("hidden");
      if (guestListRequest.status === 200) {
        var guests = JSON.parse(guestListRequest.responseText);
        // Group the results
        guests.forEach(function(item) {
          if (!groups[item.relationship]) {
            groups[item.relationship] = {
              count: 0,
              unrespondedCount: 0,
              unresponded: []
            };
          }
          groups[item.relationship].count += item.guests.length;
          if (item.needs_rsvp) {
          groups[item.relationship].unrespondedCount += item.guests.length;
            groups[item.relationship].unresponded.push(item);
          }
        });
        updateUI();
      }
    }
  };

  var updateUI = function() {
    console.log($(".email-list"));
    if ($(".email-list").length > 0) {
      console.log("email list");
      makeUnrespondedCsv();
    } else {
      console.log("lists");
      addUnrespondedLists();
    }
  }

  var makeUnrespondedCsv = function() {
    console.log("csv");
    var container = $(".email-list");
    text = "";
    for (var key in groups) {
      if (key != "null") {
        var group = groups[key];
        group.unresponded.forEach(function(item) {
          if (text != "") {
            text += "
"  ;
          }
          text += item.name+","+item.email+","+key;
        })
      }
    }
    container.html(text);
  }

  var addUnrespondedLists = function() {
    console.log('list');
    var container = $('#container');
    for (var key in groups) {
      if (key != "null") {
        var section = $("
", {class: "status-section"});
        var countText = groups[key].unrespondedCount + " / " + groups[key].count;
        section.append($("

", {class: "unstylized"}).text(key + " "+countText));
        var list = $("

", {style:"text-align:left"});
        groups[key].unresponded.forEach(function(item) {
          list.append($("

").text(item.name));
        })
        var listContainer = $("

", {class:"detail-container row split-row"})
        listContainer.append(list);
        section.append(listContainer);
        container.append(section);
      }
    }
  }

  guestListRequest.open('GET', WEDDING_BOT_URL+'/guests?includeRelationship=true&includeAddress=true', true);
  guestListRequest.send();

  $(".loader-container").removeClass("hidden");
});<commit_msg>escape string commas<commit_after>---
---
$(document).ready(function() {
  var groups = {};
  var WEDDING_BOT_URL = '{{ site.wedding-bot-url }}';

  var guestListRequest = new XMLHttpRequest();
  guestListRequest.onreadystatechange = function(response) {
    if (guestListRequest.readyState === XMLHttpRequest.DONE) {
      $(".loader-container").addClass("hidden");
      if (guestListRequest.status === 200) {
        var guests = JSON.parse(guestListRequest.responseText);
        // Group the results
        guests.forEach(function(item) {
          if (!groups[item.relationship]) {
            groups[item.relationship] = {
              count: 0,
              unrespondedCount: 0,
              unresponded: []
            };
          }
          groups[item.relationship].count += item.guests.length;
          if (item.needs_rsvp) {
          groups[item.relationship].unrespondedCount += item.guests.length;
            groups[item.relationship].unresponded.push(item);
          }
        });
        updateUI();
      }
    }
  };

  var updateUI = function() {
    console.log($(".email-list"));
    if ($(".email-list").length > 0) {
      console.log("email list");
      makeUnrespondedCsv();
    } else {
      console.log("lists");
      addUnrespondedLists();
    }
  }

  var makeUnrespondedCsv = function() {
    console.log("csv");
    var container = $(".email-list");
    text = "";
    for (var key in groups) {
      if (key != "null") {
        var group = groups[key];
        group.unresponded.forEach(function(item) {
          if (text != "") {
            text += "
"  ;
          }
          text += '"'+item.name+'","'+item.email+'","'+key+'"';
        })
      }
    }
    container.html(text);
  }

  var addUnrespondedLists = function() {
    console.log('list');
    var container = $('#container');
    for (var key in groups) {
      if (key != "null") {
        var section = $("
", {class: "status-section"});
        var countText = groups[key].unrespondedCount + " / " + groups[key].count;
        section.append($("

", {class: "unstylized"}).text(key + " "+countText));
        var list = $("

", {style:"text-align:left"});
        groups[key].unresponded.forEach(function(item) {
          list.append($("

").text(item.name));
        })
        var listContainer = $("

", {class:"detail-container row split-row"})
        listContainer.append(list);
        section.append(listContainer);
        container.append(section);
      }
    }
  }

  guestListRequest.open('GET', WEDDING_BOT_URL+'/guests?includeRelationship=true&includeAddress=true', true);
  guestListRequest.send();

  $(".loader-container").removeClass("hidden");
});<|endoftext|>


<commit_before>// Copyright 2019-2021, University of Colorado Boulder

/**
 * ReferenceIO uses reference identity for toStateObject/fromStateObject
 *
 * @author Sam Reid (PhET Interactive Simulations)
 * @author Chris Klusendorf (PhET Interactive Simulations)
 * @author Michael Kauzmann (PhET Interactive Simulations)
 */

import ValidatorDef from '../../../axon/js/ValidatorDef.js';
import CouldNotYetDeserializeError from '../CouldNotYetDeserializeError.js';
import tandemNamespace from '../tandemNamespace.js';
import IOType from './IOType.js';

// {Map.<cacheKey:string|*, IOType>} - Cache each parameterized ReferenceIO so that it is only created once
const cache = new Map();

/**
 * @param {IOType} parameterType
 * @returns {IOType}
 */
const ReferenceIO = parameterType => {
  assert && assert( parameterType, 'ReferenceIO needs parameterType' );

  const cacheKey = parameterType;

  if ( !cache.has( cacheKey ) ) {

    assert && assert( typeof parameterType.typeName === 'string', 'type name should be a string' );
    cache.set( cacheKey, new IOType( `ReferenceIO<${parameterType.typeName}>`, {
      isValidValue: value => ValidatorDef.isValueValid( value, parameterType.validator ),
      documentation: 'Uses reference identity for serializing and deserializing, and validates based on its parameter IO Type.',
      parameterTypes: [ parameterType ],

      /**
       * Return the json that ReferenceIO is wrapping.  This can be overridden by subclasses, or types can use ReferenceIO type
       * directly to use this implementation.
       */
      toStateObject( phetioObject ) {

        // NOTE: We cannot assert that phetioObject.phetioState === false here because sometimes ReferencIO is used statically like
        // ReferenceIO( Vector2IO ).toStateObject( myVector );
        return {
          phetioID: phetioObject.tandem.phetioID
        };
      },

      /**
       * Decodes the object from a state, used in PhetioStateEngine.setState.  This can be overridden by subclasses, or types can
       * use ReferenceIO type directly to use this implementation.
       * @param {{phetioID:string}} stateObject
       * @returns {PhetioObject}
       * @throws CouldNotYetDeserializeError
       */
      fromStateObject( stateObject ) {
        assert && assert( stateObject && typeof stateObject.phetioID === 'string', 'phetioID should be a string' );
        if ( phet.phetio.phetioEngine.hasPhetioObject( stateObject.phetioID ) ) {
          return phet.phetio.phetioEngine.getPhetioObject( stateObject.phetioID );
        }
        else {
          throw new CouldNotYetDeserializeError();
        }
      },

      /**
       * References should be using fromStateObject to get a copy of the PhET-iO element.
       */
      applyState( coreObject ) {
        assert && assert( false, `ReferenceIO is meant to be used as DataType serialization (see fromStateObject) for phetioID: ${coreObject.tandem.phetioID}` );
      }
    } ) );
  }

  return cache.get( cacheKey );
};

tandemNamespace.register( 'ReferenceIO', ReferenceIO );
export default ReferenceIO;<commit_msg>Fix typo, see https://github.com/phetsims/tandem/issues/241<commit_after>// Copyright 2019-2021, University of Colorado Boulder

/**
 * ReferenceIO uses reference identity for toStateObject/fromStateObject
 *
 * @author Sam Reid (PhET Interactive Simulations)
 * @author Chris Klusendorf (PhET Interactive Simulations)
 * @author Michael Kauzmann (PhET Interactive Simulations)
 */

import ValidatorDef from '../../../axon/js/ValidatorDef.js';
import CouldNotYetDeserializeError from '../CouldNotYetDeserializeError.js';
import tandemNamespace from '../tandemNamespace.js';
import IOType from './IOType.js';

// {Map.<cacheKey:string|*, IOType>} - Cache each parameterized ReferenceIO so that it is only created once
const cache = new Map();

/**
 * @param {IOType} parameterType
 * @returns {IOType}
 */
const ReferenceIO = parameterType => {
  assert && assert( parameterType, 'ReferenceIO needs parameterType' );

  const cacheKey = parameterType;

  if ( !cache.has( cacheKey ) ) {

    assert && assert( typeof parameterType.typeName === 'string', 'type name should be a string' );
    cache.set( cacheKey, new IOType( `ReferenceIO<${parameterType.typeName}>`, {
      isValidValue: value => ValidatorDef.isValueValid( value, parameterType.validator ),
      documentation: 'Uses reference identity for serializing and deserializing, and validates based on its parameter IO Type.',
      parameterTypes: [ parameterType ],

      /**
       * Return the json that ReferenceIO is wrapping.  This can be overridden by subclasses, or types can use ReferenceIO type
       * directly to use this implementation.
       */
      toStateObject( phetioObject ) {

        // NOTE: We cannot assert that phetioObject.phetioState === false here because sometimes ReferenceIO is used statically like
        // ReferenceIO( Vector2IO ).toStateObject( myVector );
        return {
          phetioID: phetioObject.tandem.phetioID
        };
      },

      /**
       * Decodes the object from a state, used in PhetioStateEngine.setState.  This can be overridden by subclasses, or types can
       * use ReferenceIO type directly to use this implementation.
       * @param {{phetioID:string}} stateObject
       * @returns {PhetioObject}
       * @throws CouldNotYetDeserializeError
       */
      fromStateObject( stateObject ) {
        assert && assert( stateObject && typeof stateObject.phetioID === 'string', 'phetioID should be a string' );
        if ( phet.phetio.phetioEngine.hasPhetioObject( stateObject.phetioID ) ) {
          return phet.phetio.phetioEngine.getPhetioObject( stateObject.phetioID );
        }
        else {
          throw new CouldNotYetDeserializeError();
        }
      },

      /**
       * References should be using fromStateObject to get a copy of the PhET-iO element.
       */
      applyState( coreObject ) {
        assert && assert( false, `ReferenceIO is meant to be used as DataType serialization (see fromStateObject) for phetioID: ${coreObject.tandem.phetioID}` );
      }
    } ) );
  }

  return cache.get( cacheKey );
};

tandemNamespace.register( 'ReferenceIO', ReferenceIO );
export default ReferenceIO;<|endoftext|>


<commit_before>import fetch from 'isomorphic-fetch'

const _getRequest = url =>
      fetch(url).then((response) => {
        if (response.ok) {
          if (response.status == 200) {
            return response.json()
          } else {
            return []
          }
        } throw Error(response.statusText)
      })

const _postJSONRequest = (url, data) =>
      fetch(url, {
        method: 'post',
        headers: {
          Accept: 'application/json',
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(data),
      })
      .then((response) => {
        if (response.ok) {
          if (response.status == 200) {
            return response.json()
          } else {
            return []
          }
        } throw Error(response.statusText)
      })

const addFeed = url => _postJSONRequest('/feeds/', { url })

const refreshFeedList = () =>
      _getRequest('/feeds/')
      .then(response => response["_embedded"]["feeds"])

const refreshFeed = feedId =>
      _postJSONRequest(`/feeds/${feedId}`)
      .then(response => response["_embedded"]["entries"])

const fetchFeedEntries = feedId =>
      _getRequest(`/feeds/${feedId}/entries`)
      .then(response => response["_embedded"]["entries"])

const changeEntryReadStatus = (feedId, entryId, newReadStatus) =>
      _postJSONRequest(`/feeds/${feedId}/entries/${entryId}`,
                       { read: newReadStatus })

module.exports = {
  addFeed,
  refreshFeedList,
  refreshFeed,
  fetchFeedEntries,
  changeEntryReadStatus,
}
<commit_msg>fetch should use absolute URLs<commit_after>import fetch from 'isomorphic-fetch'

const SERVER = 'http://127.0.0.1:5000'

const _getRequest = url =>
      fetch(url).then((response) => {
        if (response.ok) {
          if (response.status === 200) {
            return response.json()
          } return []
        } throw Error(response.statusText)
      })

const _postJSONRequest = (url, data) =>
      fetch(url, {
        method: 'post',
        headers: {
          Accept: 'application/json',
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(data),
      })
      .then((response) => {
        if (response.ok) {
          if (response.status === 200) {
            return response.json()
          } return []
        } throw Error(response.statusText)
      })

const addFeed = url => _postJSONRequest('/feeds/', { url })

const refreshFeedList = () =>
      _getRequest(`${SERVER}/feeds/`)
      .then(response => response._embedded.feeds)

const refreshFeed = feedId =>
      _postJSONRequest(`${SERVER}/feeds/${feedId}`)
      .then(response => response._embedded.entries)

const fetchFeedEntries = feedId =>
      _getRequest(`${SERVER}/feeds/${feedId}/entries`)
      .then(response => response._embedded.entries)

const changeEntryReadStatus = (feedId, entryId, newReadStatus) =>
      _postJSONRequest(`${SERVER}/feeds/${feedId}/entries/${entryId}`,
                       { read: newReadStatus })

module.exports = {
  addFeed,
  refreshFeedList,
  refreshFeed,
  fetchFeedEntries,
  changeEntryReadStatus,
}
<|endoftext|>


<commit_before>var Joi = require('joi');

module.exports = Joi.object().keys({
  // basaltab.js: actualized is optional, reflecting whether scheduled segment was delivered
  actualized: Joi.boolean(),
  basalSchedules: Joi.array().includes(Joi.object().keys({
    name: Joi.string().min(1).required(),
    value: Joi.array().includes(Joi.object().keys({
      rate: Joi.number().min(0).required(),
      // milliseconds per twenty-four hours
      start: Joi.number().integer().min(0).max(86400000).required()
    }))
  })).when('type', {
    is: 'settings',
    then: Joi.required()
  }),
  bgTarget: Joi.array().includes(Joi.object().keys({
    // pulling a min out of thin air a little bit, Animas pump allows min of 60
    amount: Joi.number().integer().min(50),
    // milliseconds per twenty-four hours
    start: Joi.number().integer().min(0).max(86400000).required()
  })).when('type', {
    is: 'settings',
    then: Joi.required()
  }),
  // preprocess removes wizards w/o joinKeys that match to a bolus
  // hence the requirement
  // no need to validate boluses internally since they are validated at the higher level
  bolus: Joi.object().when('type', {
    is: 'wizard',
    then: Joi.required()
  }),
  carbRatio: Joi.array().includes(Joi.object().keys({
    // some pumps allow this to be a non-integer
    amount: Joi.number().min(0).required(),
    // milliseconds per twenty-four hours
    start: Joi.number().integer().min(0).max(86400000).required()
  })).when('type', {
    is: 'settings',
    then: Joi.required()
  }),
  // not all wizard interaction involve a carb bolus
  // so this is never required
  carbs: Joi.object().keys({
    value: Joi.number().integer().min(0).required()
  }),
  // all basal rate segments require a deliveryType
  deliveryType: Joi.string().valid(['scheduled', 'suspend', 'temp']).when('type', {
    is: 'basal-rate-segment',
    then: Joi.required()
  }),
  // deviceTime is the raw, non-timezone-aware string, so won't validate as isoDate()
  deviceTime: Joi.string().regex(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/),
  // duration is optional in several cases
  // where it's required is specified below in an `with` clause
  duration: Joi.number().integer(),
  // extended is optional on boluses
  // required co-occurrence with extendedDelivery below in `and` clause
  extended: Joi.boolean(),
  // generate data for background fill requires a fillColor
  fillColor: Joi.string().valid(['darkest', 'darker', 'dark',
    'light', 'lighter', 'lightest']).when('type', {
      is: 'fill',
      then: Joi.required()
    }),
  // all IDs must be alphanumeric plus - or _ but no other special characters
  id: Joi.string().regex(/^[A-Za-z0-9\-\_]+$/).required(),
  initialDelivery: Joi.number().min(0),
  insulinSensitivity: Joi.array().includes(Joi.object().keys({
    amount: Joi.number().integer().min(0).required(),
    // milliseconds per twenty-four hours
    start: Joi.number().integer().min(0).max(86400000).required()
  })).when('type', {
    is: 'settings',
    then: Joi.required()
  }),
  extendedDelivery: Joi.number().min(0),
  // some types optionally have a joinKey, with the same characters requiredments as IDs
  // _ not included here because that only occurs in IDs not generated server-side
  joinKey: Joi.string().regex(/^[A-Za-z0-9\-]+$/),
  normalEnd: Joi.string().isoDate().when('type', {
    is: ['basal-rate-segment', 'basal-settings-segment', 'fill'],
    then: Joi.required()
  }),
  normalTime: Joi.string().isoDate(),
  parentMessage: Joi.alternatives().try(Joi.string().when('type', {
    is: 'message',
    then: Joi.required()
  }), Joi.any().valid(null, '')),
  programmed: Joi.number().min(0),
  recommended: Joi.number().min(0),
  // basaltab.js: schedule is required
  // identifies which basal schedule in the settings the segment belongs to
  schedule: Joi.string().min(1).when('type', {
    is: 'basal-settings-segment',
    then: Joi.required()
  }),
  // TODO: change to isoDate() as soon as merge browser compatibility branch
  // which adds Watsoning to suspendedAt field
  suspendedAt: Joi.string().regex(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/),
  type: Joi.string().valid([
    'basal-rate-segment',
    'basal-settings-segment',
    'bolus',
    'cbg',
    // NB: other than allowing the type, deviceMeta doesn't get validated
    // because it doesn't get visualized
    // only deviceMetas transformed into basal-rate-segments are visualized
    'deviceMeta',
    'fill',
    'message',
    'settings',
    'smbg',
    'wizard']).required(),
  // only messages are currently visualized using a true timezone-aware UTC datetime string
  utcTime: Joi.string().isoDate().when('type', {
    is: 'message',
    then: Joi.required()
  }),
  // can't verify that some fields (carbs, cbg, and smbg) are integer values
  // and still require the field only with certain types
  // not a big deal since carbs types is going away and
  // cbg and smbg will soon be coming in as mmol/L
  value: Joi.number().min(0).when('type', {
    is: ['basal-rate-segment', 'basal-settings-segment', 'bolus', 'carbs', 'cbg', 'smbg'],
    then: Joi.required()
  }),
}).and(['extended', 'extendedDelivery'])
  .with('extended', 'duration');<commit_msg>small fixes in response to feedback prior to splitting up schemas<commit_after>var Joi = require('joi');

module.exports = Joi.object().keys({
  basalSchedules: Joi.array().includes(Joi.object().keys({
    name: Joi.string().min(1).required(),
    value: Joi.array().includes(Joi.object().keys({
      rate: Joi.number().min(0).required(),
      // milliseconds per twenty-four hours
      start: Joi.number().integer().min(0).max(86400000).required()
    }))
  })).when('type', {
    is: 'settings',
    then: Joi.required()
  }),
  bgTarget: Joi.array().includes(Joi.object().keys({
    // pulling a min out of thin air a little bit, Animas pump allows min of 60
    amount: Joi.number().integer(),
    // milliseconds per twenty-four hours
    start: Joi.number().integer().min(0).max(86400000).required()
  })).when('type', {
    is: 'settings',
    then: Joi.required()
  }),
  // preprocess removes wizards w/o joinKeys that match to a bolus
  // hence the requirement
  // no need to validate boluses internally since they are validated at the higher level
  bolus: Joi.object().when('type', {
    is: 'wizard',
    then: Joi.required()
  }),
  carbRatio: Joi.array().includes(Joi.object().keys({
    // some pumps allow this to be a non-integer
    amount: Joi.number().min(0).required(),
    // milliseconds per twenty-four hours
    start: Joi.number().integer().min(0).max(86400000).required()
  })).when('type', {
    is: 'settings',
    then: Joi.required()
  }),
  // not all wizard interaction involve a carb bolus
  // so this is never required
  carbs: Joi.object().keys({
    value: Joi.number().integer().min(0).required()
  }),
  // all basal rate segments require a deliveryType
  deliveryType: Joi.string().valid(['scheduled', 'suspend', 'temp']).when('type', {
    is: 'basal-rate-segment',
    then: Joi.required()
  }),
  // deviceTime is the raw, non-timezone-aware string, so won't validate as isoDate()
  deviceTime: Joi.string().regex(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/),
  // duration is optional in several cases
  // where it's required is specified below in an `with` clause
  duration: Joi.number().integer(),
  // extended is optional on boluses
  // required co-occurrence with extendedDelivery below in `and` clause
  extended: Joi.boolean(),
  // all IDs must be alphanumeric plus - or _ but no other special characters
  id: Joi.string().regex(/^[A-Za-z0-9\-\_]+$/).required(),
  initialDelivery: Joi.number().min(0),
  insulinSensitivity: Joi.array().includes(Joi.object().keys({
    amount: Joi.number().integer().min(0).required(),
    // milliseconds per twenty-four hours
    start: Joi.number().integer().min(0).max(86400000).required()
  })).when('type', {
    is: 'settings',
    then: Joi.required()
  }),
  extendedDelivery: Joi.number().min(0),
  // some types optionally have a joinKey, with the same characters requirements as IDs
  // _ not included here because that only occurs in IDs not generated server-side
  joinKey: Joi.string().regex(/^[A-Za-z0-9\-]+$/),
  normalEnd: Joi.string().isoDate().when('type', {
    is: 'basal-rate-segment',
    then: Joi.required()
  }),
  normalTime: Joi.string().isoDate(),
  parentMessage: Joi.alternatives().try(Joi.string().when('type', {
    is: 'message',
    then: Joi.required()
  }), Joi.any().valid(null, '')),
  programmed: Joi.number().min(0),
  recommended: Joi.number().min(0),
  // TODO: change to isoDate() as soon as merge browser compatibility branch
  // which adds Watsoning to suspendedAt field
  suspendedAt: Joi.string().regex(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/),
  type: Joi.string().valid([
    'basal-rate-segment',
    'bolus',
    'cbg',
    // NB: other than allowing the type, deviceMeta doesn't get validated
    // because it doesn't get visualized
    // only deviceMetas transformed into basal-rate-segments are visualized
    'deviceMeta',
    'message',
    'settings',
    'smbg',
    'wizard']).invalid(['basal-settings-segment', 'fill']).required(),
  // only messages are currently visualized using a true timezone-aware UTC datetime string
  utcTime: Joi.string().isoDate().when('type', {
    is: 'message',
    then: Joi.required()
  }),
  // can't verify that some fields (carbs, cbg, and smbg) are integer values
  // and still require the field only with certain types
  // not a big deal since carbs types is going away and
  // cbg and smbg will soon be coming in as mmol/L
  value: Joi.number().min(0).when('type', {
    is: ['basal-rate-segment', 'bolus', 'carbs', 'cbg', 'smbg'],
    then: Joi.required()
  }),
}).and(['extended', 'extendedDelivery'])
  .with('extended', 'duration');<|endoftext|>


<commit_before>import * as I from 'immutable';

import * as freeWords  from '../fpgroups/freeWords';
import * as DS         from './delaney';
import * as properties from './properties';

const _other = (a, b, c) => a == c ? b : a;

const _glue = function _glue(ds, bnd, D, i) {
  const E = ds.s(i, D);

  return bnd.withMutations(function(map) {
    ds.indices()
      .filter(j => j != i && bnd.getIn([D, i, j]))
      .forEach(function(j) {
        const oppD = bnd.getIn([D, i, j]);
        const oppE = bnd.getIn([E, i, j]);
        const count = D == E ? oppD.count : oppD.count + oppE.count;
        map.setIn([oppD.chamber, oppD.index, _other(i, j, oppD.index)],
                  { chamber: oppE.chamber, index: oppE.index, count: count });
        map.setIn([oppE.chamber, oppE.index, _other(i, j, oppE.index)],
                  { chamber: oppD.chamber, index: oppD.index, count: count });
      });
    map.deleteIn([D, i]).deleteIn([E, i]);
  });
};

const _todoAfterGluing = function _todoAfterGluing(ds, bnd, D, i) {
  const onMirror = ds.s(i, D) == D;

  return I.List().withMutations(function(list) {
    ds.indices().forEach(function(j) {
      const opp = bnd.getIn([D, i, j]);

      if (opp) {
        const E = opp.chamber;
        const k = opp.index;
        if (onMirror == (ds.s(k, E) == E))
          list.push(I.List([E, k, _other(i, j, k)]));
      }
    });
  });
};

const _glueRecursively = function _glueRecursively(ds, bnd, facets) {
  let boundary = bnd;
  let todo = I.List(facets).map(I.List).asMutable();
  let glued = I.List();

  while (!todo.isEmpty()) {
    const next = todo.first();
    todo.shift();

    const D = next.get(0);
    const i = next.get(1);
    const j = next.get(2);
    const m = DS.m(ds, i, j, D) * (ds.s(i, D) == D ? 1 : 2);

    const opp = boundary.getIn(next);

    if (opp && (j == null || opp.count == m)) {
      for (const t of _todoAfterGluing(ds, boundary, D, i))
        todo.push(t);

      boundary = _glue(ds, boundary, D, i);
      glued = glued.push(next);
    }
  }

  return I.Map({ boundary: boundary, glued: glued });
};

const _spanningTree = function _spanningTree(ds) {
  const root = properties.traversal.root;
  let seen = I.Set();
  let todo = I.List();

  properties.traversal(ds, ds.indices(), ds.elements()).forEach(function(e) {
    const D = e[0];
    const i = e[1];
    const E = e[2];

    if (i != root && !seen.contains(E))
      todo = todo.push(I.List([D, i]));
    seen = seen.add(E);
  });

  return todo;
};

const _initialBoundary = function _initialBoundary(ds) {
  return I.Map().withMutations(map => {
    ds.elements().forEach(D => {
      ds.indices().forEach(i => {
        ds.indices().forEach(j => {
          if (i != j)
            map.setIn([D, i, j], { chamber: D, index: j, count: 1 });
        });
      });
    });
  });
};

const _traceWord = function _traceWord(ds, edge2word, i, j, D) {
  let E = ds.s(i, D);
  let k = j;
  const factors = [];

  while(true) {
    factors.push(edge2word.getIn([E, k]) || freeWords.empty);
    if (E == D && k ==i)
      break;

    E = ds.s(k, E) || E;
    k = _other(i, j, k);
  }

  return freeWords.product(factors);
};

const _updatedWordMap = function _updatedWordMap(ds, edge2word, D, i, gen, glued) {
  return edge2word.withMutations(function(e2w) {
    e2w.setIn([D, i], freeWords.word([gen]));
    e2w.setIn([ds.s(i, D), i], freeWords.inverse([gen]));
    glued.rest().forEach(function(e) {
      const D = e.get(0);
      const i = e.get(1);
      const j = e.get(2);
      const w = _traceWord(ds, e2w, i, j, D);

      if (!freeWords.empty.equals(w)) {
        e2w.setIn([D, i], freeWords.inverse(w));
        e2w.setIn([ds.s(i, D), i], w);
      }
    });
  });
};

const _findGenerators = function _findGenerators(ds) {
  let boundary = _initialBoundary(ds);
  const tree = _spanningTree(ds);

  boundary = _glueRecursively(ds, boundary, tree).get('boundary');

  let edge2word = I.Map();
  let gen2edge = I.Map();

  ds.elements().forEach(function(D) {
    ds.indices().forEach(function(i) {
      if (boundary.getIn([D, i])) {
        const tmp = _glueRecursively(ds, boundary, [[D, i]]);
        const glued = tmp.get('glued');
        const gen = gen2edge.size+1;

        boundary = tmp.get('boundary');
        gen2edge = gen2edge.set(gen, I.Map({ chamber: D, index: i }));
        edge2word = _updatedWordMap(ds, edge2word, D, i, gen, glued);
      }
    })
  });

  return I.Map({ edge2word: edge2word, gen2edge: gen2edge });
};

const FundamentalGroup = I.Record({
  nrGenerators: undefined,
  relators    : undefined,
  cones       : undefined,
  gen2edge    : undefined,
  edge2word   : undefined
});

export function innerEdges(ds) {
  return _glueRecursively(ds, _initialBoundary(ds), _spanningTree(ds))
    .get('glued')
    .map(a => a.slice(0, 2));
};

export function fundamentalGroup(ds) {
  const tmp = _findGenerators(ds);
  const edge2word = tmp.get('edge2word');
  const gen2edge = tmp.get('gen2edge');

  const orbits = [];
  for (const i of ds.indices()) {
    for (const j of ds.indices()) {
      if (j > i) {
        for (const D of properties.orbitReps(ds, [i, j])) {
          const w = _traceWord(ds, edge2word, i, j, D);
          const v = ds.v(i, j, D);
          if (v && w.size > 0)
            orbits.push([D, i, j, w, v]);
        }
      }
    }
  }

  const orbitRelators = orbits.map(orb => freeWords.raisedTo(orb[4], orb[3]));

  const mirrors = gen2edge.entrySeq()
    .filter(function(e) {
      const D = e[1].get('chamber');
      const i = e[1].get('index');
      return ds.s(i, D) == D;
    })
    .map(e => freeWords.word([e[0], e[0]]))
    .toArray();

  const cones = orbits
    .filter(orb => orb[4] > 1)
    .map(orb => orb.slice(3))
    .sort();

  const nGens = gen2edge.size;
  const rels  = I.Set(
    orbitRelators.concat(mirrors)
      .map(freeWords.relatorRepresentative))
    .sort();

  return FundamentalGroup({
    nrGenerators: nGens,
    relators    : rels,
    cones       : cones,
    gen2edge    : gen2edge,
    edge2word   : edge2word
  });
};

if (require.main == module) {
  const test = function test(ds) {
    console.log('ds = '+ds);
    console.log();

    console.log('    spanning tree: '+JSON.stringify(_spanningTree(ds)));
    console.log('    inner edges: '+JSON.stringify(innerEdges(ds)));
    console.log();

    const gens = _findGenerators(ds);

    console.log('    generators: '+gens.get('gen2edge'));
    console.log();

    console.log('    edge words: '+gens.get('edge2word'));
    console.log();

    const group = fundamentalGroup(ds);

    console.log('    relators: '+group.relators);
    console.log();

    console.log('    cones: '+group.cones);
    console.log();
    console.log();
  };

  test(DS.parse(
    '<1.1:24:' +
      '2 4 6 8 10 12 14 16 18 20 22 24,' +
      '16 3 5 7 9 11 13 15 24 19 21 23,' +
      '10 9 20 19 14 13 22 21 24 23 18 17:' +
      '8 4,3 3 3 3>'));

  test(DS.parse('<1.1:3:1 2 3,1 3,2 3:4 8,3>'));
  test(DS.parse('<1.1:2 3:2,1 2,1 2,2:6,3 2,6>'));
}
<commit_msg>get rid of function keyword in fundamental.js<commit_after>import * as I from 'immutable';

import * as freeWords  from '../fpgroups/freeWords';
import * as DS         from './delaney';
import * as properties from './properties';

const _other = (a, b, c) => a == c ? b : a;

const _glue = (ds, bnd, D, i) => {
  const E = ds.s(i, D);

  return bnd.withMutations(map => {
    ds.indices()
      .filter(j => j != i && bnd.getIn([D, i, j]))
      .forEach(j => {
        const oppD = bnd.getIn([D, i, j]);
        const oppE = bnd.getIn([E, i, j]);
        const count = D == E ? oppD.count : oppD.count + oppE.count;
        map.setIn([oppD.chamber, oppD.index, _other(i, j, oppD.index)],
                  { chamber: oppE.chamber, index: oppE.index, count: count });
        map.setIn([oppE.chamber, oppE.index, _other(i, j, oppE.index)],
                  { chamber: oppD.chamber, index: oppD.index, count: count });
      });
    map.deleteIn([D, i]).deleteIn([E, i]);
  });
};

const _todoAfterGluing = (ds, bnd, D, i) => {
  const onMirror = ds.s(i, D) == D;

  return I.List().withMutations(list => {
    ds.indices().forEach(j => {
      const opp = bnd.getIn([D, i, j]);

      if (opp) {
        const E = opp.chamber;
        const k = opp.index;
        if (onMirror == (ds.s(k, E) == E))
          list.push(I.List([E, k, _other(i, j, k)]));
      }
    });
  });
};

const _glueRecursively = (ds, bnd, facets) => {
  let boundary = bnd;
  let todo = I.List(facets).map(I.List).asMutable();
  let glued = I.List();

  while (!todo.isEmpty()) {
    const next = todo.first();
    todo.shift();

    const D = next.get(0);
    const i = next.get(1);
    const j = next.get(2);
    const m = DS.m(ds, i, j, D) * (ds.s(i, D) == D ? 1 : 2);

    const opp = boundary.getIn(next);

    if (opp && (j == null || opp.count == m)) {
      for (const t of _todoAfterGluing(ds, boundary, D, i))
        todo.push(t);

      boundary = _glue(ds, boundary, D, i);
      glued = glued.push(next);
    }
  }

  return I.Map({ boundary: boundary, glued: glued });
};

const _spanningTree = ds => {
  const root = properties.traversal.root;
  let seen = I.Set();
  let todo = I.List();

  properties.traversal(ds, ds.indices(), ds.elements()).forEach(e => {
    const D = e[0];
    const i = e[1];
    const E = e[2];

    if (i != root && !seen.contains(E))
      todo = todo.push(I.List([D, i]));
    seen = seen.add(E);
  });

  return todo;
};

const _initialBoundary = ds =>
  I.Map().withMutations(map => {
    ds.elements().forEach(D => {
      ds.indices().forEach(i => {
        ds.indices().forEach(j => {
          if (i != j)
            map.setIn([D, i, j], { chamber: D, index: j, count: 1 });
        });
      });
    });
  });

const _traceWord = (ds, edge2word, i, j, D) => {
  let E = ds.s(i, D);
  let k = j;
  const factors = [];

  while(true) {
    factors.push(edge2word.getIn([E, k]) || freeWords.empty);
    if (E == D && k ==i)
      break;

    E = ds.s(k, E) || E;
    k = _other(i, j, k);
  }

  return freeWords.product(factors);
};

const _updatedWordMap = (ds, edge2word, D, i, gen, glued) => {
  return edge2word.withMutations(e2w => {
    e2w.setIn([D, i], freeWords.word([gen]));
    e2w.setIn([ds.s(i, D), i], freeWords.inverse([gen]));
    glued.rest().forEach(e => {
      const D = e.get(0);
      const i = e.get(1);
      const j = e.get(2);
      const w = _traceWord(ds, e2w, i, j, D);

      if (!freeWords.empty.equals(w)) {
        e2w.setIn([D, i], freeWords.inverse(w));
        e2w.setIn([ds.s(i, D), i], w);
      }
    });
  });
};

const _findGenerators = ds => {
  let boundary = _initialBoundary(ds);
  const tree = _spanningTree(ds);

  boundary = _glueRecursively(ds, boundary, tree).get('boundary');

  let edge2word = I.Map();
  let gen2edge = I.Map();

  ds.elements().forEach(D => {
    ds.indices().forEach(i => {
      if (boundary.getIn([D, i])) {
        const tmp = _glueRecursively(ds, boundary, [[D, i]]);
        const glued = tmp.get('glued');
        const gen = gen2edge.size+1;

        boundary = tmp.get('boundary');
        gen2edge = gen2edge.set(gen, I.Map({ chamber: D, index: i }));
        edge2word = _updatedWordMap(ds, edge2word, D, i, gen, glued);
      }
    })
  });

  return I.Map({ edge2word: edge2word, gen2edge: gen2edge });
};

const FundamentalGroup = I.Record({
  nrGenerators: undefined,
  relators    : undefined,
  cones       : undefined,
  gen2edge    : undefined,
  edge2word   : undefined
});

const innerEdges = ds =>
  _glueRecursively(ds, _initialBoundary(ds), _spanningTree(ds))
  .get('glued')
  .map(a => a.slice(0, 2));

export const fundamentalGroup = ds => {
  const tmp = _findGenerators(ds);
  const edge2word = tmp.get('edge2word');
  const gen2edge = tmp.get('gen2edge');

  const orbits = [];
  for (const i of ds.indices()) {
    for (const j of ds.indices()) {
      if (j > i) {
        for (const D of properties.orbitReps(ds, [i, j])) {
          const w = _traceWord(ds, edge2word, i, j, D);
          const v = ds.v(i, j, D);
          if (v && w.size > 0)
            orbits.push([D, i, j, w, v]);
        }
      }
    }
  }

  const orbitRelators = orbits.map(orb => freeWords.raisedTo(orb[4], orb[3]));

  const mirrors = gen2edge.entrySeq()
    .filter(e => {
      const D = e[1].get('chamber');
      const i = e[1].get('index');
      return ds.s(i, D) == D;
    })
    .map(e => freeWords.word([e[0], e[0]]))
    .toArray();

  const cones = orbits
    .filter(orb => orb[4] > 1)
    .map(orb => orb.slice(3))
    .sort();

  const nGens = gen2edge.size;
  const rels  = I.Set(
    orbitRelators.concat(mirrors)
      .map(freeWords.relatorRepresentative))
    .sort();

  return FundamentalGroup({
    nrGenerators: nGens,
    relators    : rels,
    cones       : cones,
    gen2edge    : gen2edge,
    edge2word   : edge2word
  });
};

if (require.main == module) {
  const test = ds => {
    console.log('ds = '+ds);
    console.log();

    console.log('    spanning tree: '+JSON.stringify(_spanningTree(ds)));
    console.log('    inner edges: '+JSON.stringify(innerEdges(ds)));
    console.log();

    const gens = _findGenerators(ds);

    console.log('    generators: '+gens.get('gen2edge'));
    console.log();

    console.log('    edge words: '+gens.get('edge2word'));
    console.log();

    const group = fundamentalGroup(ds);

    console.log('    relators: '+group.relators);
    console.log();

    console.log('    cones: '+group.cones);
    console.log();
    console.log();
  };

  test(DS.parse(
    '<1.1:24:' +
      '2 4 6 8 10 12 14 16 18 20 22 24,' +
      '16 3 5 7 9 11 13 15 24 19 21 23,' +
      '10 9 20 19 14 13 22 21 24 23 18 17:' +
      '8 4,3 3 3 3>'));

  test(DS.parse('<1.1:3:1 2 3,1 3,2 3:4 8,3>'));
  test(DS.parse('<1.1:2 3:2,1 2,1 2,2:6,3 2,6>'));
}
<|endoftext|>