<commit_before>const builtInTypes = {
  'A': '[A-Z]',
  '9': '[0-9]',
  '*': '.',
  '+': '.*',
}

export default class Coerce {
  constructor({pattern, extend}) {
    this.pattern = pattern;
    this.extend = extend;
    // Merge built in identifiers with custom ones
    this.specialTypes = {
      ...builtInTypes,
      ...extend,
    }
    this.value = null;
  }

  static string = ({value, pattern, extend}) => {
    const specialTypes = {
      ...builtInTypes,
      ...extend,
    }
    return stringResult(value, pattern, specialTypes);
  }

  string(value) {
    this.value = stringResult(value, this.pattern, this.specialTypes, this.value);
    return this.value;
  }
}

function stringResult(value, pattern, specialTypes, prevValue) {
  // Set up an array to push characters to
  let chars = [];

  // Set up an array to push in whether the character was added because of a match or not
  let matches = [];

  let string = value;

  let patternArray = pattern.split('');

  // Here we loop through all of the characters in the pattern
  for(let i = 0; i < patternArray.length; i++) {
    const a = patternArray[i];
    if(a in specialTypes) {

      /*
        If the pattern character is a "special type",
        meaning it has regex in the builtInTypes or the extend.
        In this case it will try to find a match starting at
        the beginning of the string. If a match is found, it will
        push the match to chars (if the regex was conditional, and
        there is no character, it still returns a match, but will push '').
        It then stores whether it was an actual matched character or not to
        matches. And finally if it was an actual match, it removes the first
        character of the string (since it's successfully been added to chars[])
      */
      const regexp = new RegExp('^'+specialTypes[a], 'i');
      const match = string.match(regexp);
      chars.push(match !== null ? match[0] : null);
      //chars.push(match);
      //console.log('Match: ', match, 'Chars: ', chars, 'Chars Join: ', chars.join(''), 'String: ', string, 'a: ', a);
      if(match && match[0] !== '') {
        matches.push(true);
        string = string.slice(match.length);
      } else {
        matches.push(false);
      }

      // Everything below is dealing with cases where there's no regex for this character
    } else if(a !== string[0]) {
      if(string[0] !== '' && !!string[0]){
        /*
          ^ if we're currently not at the end of the input string, and the next
          character in the string does not equal the next character in the pattern,
          cycle through the rest of the characters in the string. So long as none are
          equal to the current pattern, put them at the end of the string, if one matches
          push it to our chars[], remove it from our reference string, mark match as true,
          and stop cycling through the reference string.
        */
        let match = false;
        for(let b = 0; b < string.length; b++) {
          if(a !== string[0]) {
            string = string.slice(1) + string[0];
          } else {
            chars.push(string[0]);
            matches.push(true);
            string = string.slice(1);
            match = true;
            break;
          }
        }
        /*
          This next block makes it such that if there's a bunch of characters
          in sequence that aren't regex, they don't need to be manually added by the user/source
          if a string is provided that reaches this point, it will automatcially
          add the characters so long as it doesn't hit another specialType
        */
        let shouldPush;
        if(patternArray[i-1] in specialTypes) {
          shouldPush = chars[chars.length - 1] || chars[chars.length - 1] === '';
        } else {
          shouldPush = true;
        }
        /*
          So if the for loop above didn't find any matches to this character, and the
          previous character is not a specialType, then push it, marking this push as a
          false match (since it's not in our reference string). Otherwise break out of the
          containing for loop.
        */
        if(!match && shouldPush) {
            chars.push(a);
            matches.push(false);
        } else if(!shouldPush) {
          break;
        }
      } else {
        /*
          This section is where those extra characters that were added to the end of our
          reference string get put back in instead of being lost because they didn't match.
          If we're not at the first character (i-1 exists) and we are the last, then push (with
          a false match since it wasn't a character match). Within the class we also track the
          value, so it it's called again (presumably someone typing), if they remove a character
          rather than add on, we won't auto-append the characters (which would make backspacing
          impossible)
        */
        const isDelete = prevValue && prevValue.length > value.length;
        const lastItemWasValid = patternArray[i-1] === chars[chars.length-1] || patternArray[i-1] in specialTypes && matches[matches.length-1];
        if(!isDelete && patternArray[i-1] && lastItemWasValid) {
          chars.push(a);
          matches.push(false);
        } else if(i === 0 && string === '') {
          chars.push(a);
          matches.push(false);
          prevValue = '';
        }
      }
    } else {
      /*
        Here we know that there was a match, push to char[] and remove the character
        from the reference string
      */
      chars.push(string[0]);
      matches.push(true);
      string = string.slice(1);
    }
  }
  return chars.join('');
}<commit_msg>changed handling of empty string after delete<commit_after>const builtInTypes = {
  'A': '[A-Z]',
  '9': '[0-9]',
  '*': '.',
  '+': '.*',
}

export default class Coerce {
  constructor({pattern, extend}) {
    this.pattern = pattern;
    this.extend = extend;
    // Merge built in identifiers with custom ones
    this.specialTypes = {
      ...builtInTypes,
      ...extend,
    }
    this.value = null;
  }

  static string = ({value, pattern, extend}) => {
    const specialTypes = {
      ...builtInTypes,
      ...extend,
    }
    return stringResult(value, pattern, specialTypes);
  }

  string(value) {
    this.value = stringResult(value, this.pattern, this.specialTypes, this.value);
    return this.value;
  }
}

function stringResult(value, pattern, specialTypes, prevValue) {
  // Set up an array to push characters to
  let chars = [];

  // Set up an array to push in whether the character was added because of a match or not
  let matches = [];

  let string = value;

  let patternArray = pattern.split('');

  // Here we loop through all of the characters in the pattern
  for(let i = 0; i < patternArray.length; i++) {
    const a = patternArray[i];
    if(a in specialTypes) {

      /*
        If the pattern character is a "special type",
        meaning it has regex in the builtInTypes or the extend.
        In this case it will try to find a match starting at
        the beginning of the string. If a match is found, it will
        push the match to chars (if the regex was conditional, and
        there is no character, it still returns a match, but will push '').
        It then stores whether it was an actual matched character or not to
        matches. And finally if it was an actual match, it removes the first
        character of the string (since it's successfully been added to chars[])
      */
      const regexp = new RegExp('^'+specialTypes[a], 'i');
      const match = string.match(regexp);
      chars.push(match !== null ? match[0] : null);
      //chars.push(match);
      if(match && match[0] !== '') {
        matches.push(true);
        string = string.slice(match.length);
      } else {
        matches.push(false);
      }

      // Everything below is dealing with cases where there's no regex for this character
    } else if(a !== string[0]) {
      if(string[0] !== '' && !!string[0]){
        /*
          ^ if we're currently not at the end of the input string, and the next
          character in the string does not equal the next character in the pattern,
          cycle through the rest of the characters in the string. So long as none are
          equal to the current pattern, put them at the end of the string, if one matches
          push it to our chars[], remove it from our reference string, mark match as true,
          and stop cycling through the reference string.
        */
        let match = false;
        for(let b = 0; b < string.length; b++) {
          if(a !== string[0]) {
            string = string.slice(1) + string[0];
          } else {
            chars.push(string[0]);
            matches.push(true);
            string = string.slice(1);
            match = true;
            break;
          }
        }
        /*
          This next block makes it such that if there's a bunch of characters
          in sequence that aren't regex, they don't need to be manually added by the user/source
          if a string is provided that reaches this point, it will automatcially
          add the characters so long as it doesn't hit another specialType
        */
        let shouldPush;
        if(patternArray[i-1] in specialTypes) {
          shouldPush = chars[chars.length - 1] || chars[chars.length - 1] === '';
        } else {
          shouldPush = true;
        }
        /*
          So if the for loop above didn't find any matches to this character, and the
          previous character is not a specialType, then push it, marking this push as a
          false match (since it's not in our reference string). Otherwise break out of the
          containing for loop.
        */
        if(!match && shouldPush) {
            chars.push(a);
            matches.push(false);
        } else if(!shouldPush) {
          break;
        }
      } else {
        /*
          This section is where those extra characters that were added to the end of our
          reference string get put back in instead of being lost because they didn't match.
          If we're not at the first character (i-1 exists) and we are the last, then push (with
          a false match since it wasn't a character match). Within the class we also track the
          value, so it it's called again (presumably someone typing), if they remove a character
          rather than add on, we won't auto-append the characters (which would make backspacing
          impossible)
        */
        const isDelete = prevValue && prevValue.length > value.length;
        const lastItemWasValid = patternArray[i-1] === chars[chars.length-1] || patternArray[i-1] in specialTypes && matches[matches.length-1];
        if(!isDelete && patternArray[i-1] && lastItemWasValid) {
          chars.push(a);
          matches.push(false);
        } else if(i === 0 && string === '') {
          chars.push(a);
          matches.push(false);
          prevValue = '';
        }
      }
    } else {
      /*
        Here we know that there was a match, push to char[] and remove the character
        from the reference string
      */
      chars.push(string[0]);
      matches.push(true);
      string = string.slice(1);
    }
  }
  return chars.join('');
}<|endoftext|>


<commit_before>/**
 * @name		Init Module
 * @author		Tobias Reich
 * @copyright	2014 by Tobias Reich
 */

$(document).ready(function(){

	/* Event Name */
	var event_name = (mobileBrowser()) ? "touchend" : "click";

	/* Disable ContextMenu */
	$(document).bind("contextmenu", function(e) { e.preventDefault() });

	/* Tooltips */
	if (!mobileBrowser()) $(".tools").tipsy({gravity: 'n', fade: false, delayIn: 0, opacity: 1});

	/* Multiselect */
	$("#content").on("mousedown", multiselect.show);
	$(document).on("mouseup", multiselect.getSelection);

	/* Header */
	$("#hostedwith").on(event_name, function() { window.open(lychee.website) });
	$("#button_signin").on(event_name, lychee.loginDialog);
	$("#button_settings").on("click", contextMenu.settings);
	$("#button_share").on(event_name, function(e) {
		if (photo.json.public==1||photo.json.public==2) contextMenu.sharePhoto(photo.getID(), e);
		else photo.setPublic(photo.getID(), e);
	});
	$("#button_share_album").on(event_name, function(e) {
		if (album.json.public==1) contextMenu.shareAlbum(album.getID(), e);
		else album.setPublic(album.getID(), e);
	});
	$("#button_more").on(event_name, function(e) { contextMenu.photoMore(photo.getID(), e) });
	$("#button_trash_album").on(event_name, function() { album.delete([album.getID()]) });
	$("#button_move").on(event_name, function(e) { contextMenu.move([photo.getID()], e) });
	$("#button_trash").on(event_name, function() { photo.delete([photo.getID()]) });
	$("#button_info_album").on(event_name, function() { view.infobox.show() });
	$("#button_info").on(event_name, function() { view.infobox.show() });
	$("#button_archive").on(event_name, function() { album.getArchive(album.getID()) });
	$("#button_star").on(event_name, function() { photo.setStar([photo.getID()]) });

	/* Search */
	$("#search").on("keyup click", function() { search.find($(this).val()) });

	/* Clear Search */
	$("#clearSearch").on(event_name, function () {
		$("#search").focus();
		search.reset();
	});

	/* Back Buttons */
	$("#button_back_home").on(event_name, function() { lychee.goto("") });
	$("#button_back").on(event_name, function() { lychee.goto(album.getID()) });

	/* Image View */
	lychee.imageview
		.on(event_name, ".arrow_wrapper.previous", photo.previous)
		.on(event_name, ".arrow_wrapper.next", photo.next);

	/* Infobox */
	$("#infobox")
		.on(event_name, ".header a", function() { view.infobox.hide() })
		.on(event_name, "#edit_title_album", function() { album.setTitle([album.getID()]) })
		.on(event_name, "#edit_description_album", function() { album.setDescription(album.getID()) })
		.on(event_name, "#edit_title", function() { photo.setTitle([photo.getID()]) })
		.on(event_name, "#edit_description", function() { photo.setDescription(photo.getID()) })
		.on(event_name, "#edit_tags", function() { photo.editTags([photo.getID()]) })
		.on(event_name, "#tags .tag span", function() { photo.deleteTag(photo.getID(), $(this).data('index')) });

	/* Keyboard */
	Mousetrap
		.bind('left', function() { if (visible.photo()) $("#imageview a#previous").click() })
		.bind('right', function() { if (visible.photo()) $("#imageview a#next").click() })
		.bind(['u', 'ctrl+u'], function() { $("#upload_files").click() })
		.bind(['s', 'ctrl+s', 'f', 'ctrl+f'], function(e) {
			if (visible.photo()) {
				$("#button_star").click();
			} else if (visible.albums()) {
				e.preventDefault();
				$("#search").focus();
			}
		})
		.bind(['r', 'ctrl+r'], function(e) {
			e.preventDefault();
			if (visible.album()) album.setTitle(album.getID());
			else if (visible.photo()) photo.setTitle([photo.getID()]);
		})
		.bind(['d', 'ctrl+d'], function(e) {
			e.preventDefault();
			if (visible.photo()) photo.setDescription(photo.getID());
			else if (visible.album()) album.setDescription(album.getID());
		})
		.bind(['t', 'ctrl+t'], function(e) {
			if (visible.photo()) {
				e.preventDefault();
				photo.editTags([photo.getID()]);
			}
		})
		.bind(['i', 'ctrl+i'], function() {
			if (visible.infobox()) view.infobox.hide();
			else if (visible.infoboxbutton()) view.infobox.show();
		})
		.bind(['command+backspace', 'ctrl+backspace'], function() {
			if (visible.photo()&&!visible.message()) photo.delete([photo.getID()]);
			else if (visible.album()&&!visible.message()) album.delete([album.getID()]);
		});

	Mousetrap.bindGlobal('enter', function() {
		if ($(".message .button.active").length) $(".message .button.active").addClass("pressed").click()
	});

	Mousetrap.bindGlobal(['esc', 'command+up'], function(e) {
		e.preventDefault();
		if (visible.message()&&$(".message .close").length>0) modal.close();
		else if (visible.contextMenu()) contextMenu.close();
		else if (visible.infobox()) view.infobox.hide();
		else if (visible.photo()) lychee.goto(album.getID());
		else if (visible.album()) lychee.goto("");
		else if (visible.albums()&&$("#search").val().length!==0) search.reset();
	});

	if (mobileBrowser()) {

		$(document)

			/* Fullscreen on mobile */
			.on('touchend', '#image', function(e) {
				if (swipe.obj===null||(swipe.offset>=-5&&swipe.offset<=5)) {
					if (visible.controls()) view.header.hide(e, 0);
					else view.header.show();
				}
			})

			/* Swipe on mobile */
			.swipe().on('swipeStart', function() { if (visible.photo()) swipe.start($("#image")) })
			.swipe().on('swipeMove', function(e) { if (visible.photo()) swipe.move(e.swipe) })
			.swipe().on('swipeEnd', function(e) { if (visible.photo()) swipe.stop(e.swipe, photo.previous, photo.next) });

	}

	/* Document */
	$(document)

		/* Login */
		.on("keyup", "#password", function() { if ($(this).val().length>0) $(this).removeClass("error") })

		/* Header */
		.on(event_name, "#title.editable", function() {
			if (visible.photo()) photo.setTitle([photo.getID()]);
			else album.setTitle([album.getID()]);
		})

		/* Navigation */
		.on("click", ".album", function() { lychee.goto($(this).attr("data-id")) })
		.on("click", ".photo", function() { lychee.goto(album.getID() + "/" + $(this).attr("data-id")) })

		/* Modal */
		.on(event_name, ".message .close", modal.close)
		.on(event_name, ".message .button:first", function() { if (modal.fns!==null) modal.fns[0](); if (!visible.signin()) modal.close() })
		.on(event_name, ".message .button:last", function() { if (modal.fns!==null) modal.fns[1](); if (!visible.signin()) modal.close() })

		/* Add Dialog */
		.on(event_name, ".button_add", function(e) { contextMenu.add(e) })

		/* Upload */
		.on("change", "#upload_files", function() { modal.close(); upload.start.local(this.files) })

		/* Context Menu */
		.on("contextmenu", ".photo", function(e) { contextMenu.photo(photo.getID(), e) })
		.on("contextmenu", ".album", function(e) { contextMenu.album(album.getID(), e) })
		.on(event_name, ".contextmenu_bg", contextMenu.close)
		.on("contextmenu", ".contextmenu_bg", contextMenu.close)

		/* Infobox */
		.on(event_name, "#infobox_overlay", view.infobox.hide)

		/* Upload */
		.on("dragover", function(e) { e.preventDefault(); }, false)
		.on("drop", function(e) {
			e.stopPropagation();
			e.preventDefault();
			if (e.originalEvent.dataTransfer.files.length>0) upload.start.local(e.originalEvent.dataTransfer.files);
			else if (e.originalEvent.dataTransfer.getData('Text').length>3) upload.start.url(e.originalEvent.dataTransfer.getData('Text'));
			return true;
		});

	/* Init */
	lychee.init();

});<commit_msg>Close upload when clicking the close button<commit_after>/**
 * @name		Init Module
 * @author		Tobias Reich
 * @copyright	2014 by Tobias Reich
 */

$(document).ready(function(){

	/* Event Name */
	var event_name = (mobileBrowser()) ? "touchend" : "click";

	/* Disable ContextMenu */
	$(document).bind("contextmenu", function(e) { e.preventDefault() });

	/* Tooltips */
	if (!mobileBrowser()) $(".tools").tipsy({gravity: 'n', fade: false, delayIn: 0, opacity: 1});

	/* Multiselect */
	$("#content").on("mousedown", multiselect.show);
	$(document).on("mouseup", multiselect.getSelection);

	/* Header */
	$("#hostedwith").on(event_name, function() { window.open(lychee.website) });
	$("#button_signin").on(event_name, lychee.loginDialog);
	$("#button_settings").on("click", contextMenu.settings);
	$("#button_share").on(event_name, function(e) {
		if (photo.json.public==1||photo.json.public==2) contextMenu.sharePhoto(photo.getID(), e);
		else photo.setPublic(photo.getID(), e);
	});
	$("#button_share_album").on(event_name, function(e) {
		if (album.json.public==1) contextMenu.shareAlbum(album.getID(), e);
		else album.setPublic(album.getID(), e);
	});
	$("#button_more").on(event_name, function(e) { contextMenu.photoMore(photo.getID(), e) });
	$("#button_trash_album").on(event_name, function() { album.delete([album.getID()]) });
	$("#button_move").on(event_name, function(e) { contextMenu.move([photo.getID()], e) });
	$("#button_trash").on(event_name, function() { photo.delete([photo.getID()]) });
	$("#button_info_album").on(event_name, function() { view.infobox.show() });
	$("#button_info").on(event_name, function() { view.infobox.show() });
	$("#button_archive").on(event_name, function() { album.getArchive(album.getID()) });
	$("#button_star").on(event_name, function() { photo.setStar([photo.getID()]) });

	/* Search */
	$("#search").on("keyup click", function() { search.find($(this).val()) });

	/* Clear Search */
	$("#clearSearch").on(event_name, function () {
		$("#search").focus();
		search.reset();
	});

	/* Back Buttons */
	$("#button_back_home").on(event_name, function() { lychee.goto("") });
	$("#button_back").on(event_name, function() { lychee.goto(album.getID()) });

	/* Image View */
	lychee.imageview
		.on(event_name, ".arrow_wrapper.previous", photo.previous)
		.on(event_name, ".arrow_wrapper.next", photo.next);

	/* Infobox */
	$("#infobox")
		.on(event_name, ".header a", function() { view.infobox.hide() })
		.on(event_name, "#edit_title_album", function() { album.setTitle([album.getID()]) })
		.on(event_name, "#edit_description_album", function() { album.setDescription(album.getID()) })
		.on(event_name, "#edit_title", function() { photo.setTitle([photo.getID()]) })
		.on(event_name, "#edit_description", function() { photo.setDescription(photo.getID()) })
		.on(event_name, "#edit_tags", function() { photo.editTags([photo.getID()]) })
		.on(event_name, "#tags .tag span", function() { photo.deleteTag(photo.getID(), $(this).data('index')) });

	/* Keyboard */
	Mousetrap
		.bind('left', function() { if (visible.photo()) $("#imageview a#previous").click() })
		.bind('right', function() { if (visible.photo()) $("#imageview a#next").click() })
		.bind(['u', 'ctrl+u'], function() { $("#upload_files").click() })
		.bind(['s', 'ctrl+s', 'f', 'ctrl+f'], function(e) {
			if (visible.photo()) {
				$("#button_star").click();
			} else if (visible.albums()) {
				e.preventDefault();
				$("#search").focus();
			}
		})
		.bind(['r', 'ctrl+r'], function(e) {
			e.preventDefault();
			if (visible.album()) album.setTitle(album.getID());
			else if (visible.photo()) photo.setTitle([photo.getID()]);
		})
		.bind(['d', 'ctrl+d'], function(e) {
			e.preventDefault();
			if (visible.photo()) photo.setDescription(photo.getID());
			else if (visible.album()) album.setDescription(album.getID());
		})
		.bind(['t', 'ctrl+t'], function(e) {
			if (visible.photo()) {
				e.preventDefault();
				photo.editTags([photo.getID()]);
			}
		})
		.bind(['i', 'ctrl+i'], function() {
			if (visible.infobox()) view.infobox.hide();
			else if (visible.infoboxbutton()) view.infobox.show();
		})
		.bind(['command+backspace', 'ctrl+backspace'], function() {
			if (visible.photo()&&!visible.message()) photo.delete([photo.getID()]);
			else if (visible.album()&&!visible.message()) album.delete([album.getID()]);
		});

	Mousetrap.bindGlobal('enter', function() {
		if ($(".message .button.active").length) $(".message .button.active").addClass("pressed").click()
	});

	Mousetrap.bindGlobal(['esc', 'command+up'], function(e) {
		e.preventDefault();
		if (visible.message()&&$(".message .close").length>0) modal.close();
		else if (visible.contextMenu()) contextMenu.close();
		else if (visible.infobox()) view.infobox.hide();
		else if (visible.photo()) lychee.goto(album.getID());
		else if (visible.album()) lychee.goto("");
		else if (visible.albums()&&$("#search").val().length!==0) search.reset();
	});

	if (mobileBrowser()) {

		$(document)

			/* Fullscreen on mobile */
			.on('touchend', '#image', function(e) {
				if (swipe.obj===null||(swipe.offset>=-5&&swipe.offset<=5)) {
					if (visible.controls()) view.header.hide(e, 0);
					else view.header.show();
				}
			})

			/* Swipe on mobile */
			.swipe().on('swipeStart', function() { if (visible.photo()) swipe.start($("#image")) })
			.swipe().on('swipeMove', function(e) { if (visible.photo()) swipe.move(e.swipe) })
			.swipe().on('swipeEnd', function(e) { if (visible.photo()) swipe.stop(e.swipe, photo.previous, photo.next) });

	}

	/* Document */
	$(document)

		/* Login */
		.on("keyup", "#password", function() { if ($(this).val().length>0) $(this).removeClass("error") })

		/* Header */
		.on(event_name, "#title.editable", function() {
			if (visible.photo()) photo.setTitle([photo.getID()]);
			else album.setTitle([album.getID()]);
		})

		/* Navigation */
		.on("click", ".album", function() { lychee.goto($(this).attr("data-id")) })
		.on("click", ".photo", function() { lychee.goto(album.getID() + "/" + $(this).attr("data-id")) })

		/* Modal */
		.on(event_name, ".message .close", modal.close)
		.on(event_name, ".message .button:first", function() { if (modal.fns!==null) modal.fns[0](); if (!visible.signin()) modal.close() })
		.on(event_name, ".message .button:last", function() { if (modal.fns!==null) modal.fns[1](); if (!visible.signin()) modal.close() })

		/* Add Dialog */
		.on(event_name, ".button_add", function(e) { contextMenu.add(e) })

		/* Context Menu */
		.on("contextmenu", ".photo", function(e) { contextMenu.photo(photo.getID(), e) })
		.on("contextmenu", ".album", function(e) { contextMenu.album(album.getID(), e) })
		.on(event_name, ".contextmenu_bg", contextMenu.close)
		.on("contextmenu", ".contextmenu_bg", contextMenu.close)

		/* Infobox */
		.on(event_name, "#infobox_overlay", view.infobox.hide)

		/* Upload */
		.on("change", "#upload_files", function() { modal.close(); upload.start.local(this.files) })
		.on(event_name, ".upload_message a.close", upload.close)
		.on("dragover", function(e) { e.preventDefault(); }, false)
		.on("drop", function(e) {
			e.stopPropagation();
			e.preventDefault();
			if (e.originalEvent.dataTransfer.files.length>0)				upload.start.local(e.originalEvent.dataTransfer.files);
			else if (e.originalEvent.dataTransfer.getData('Text').length>3)	upload.start.url(e.originalEvent.dataTransfer.getData('Text'));
			return true;
		});

	/* Init */
	lychee.init();

});<|endoftext|>


<commit_before>const Util = require('../../util');
const TIME_OUT = 16;
let timer = null;

class State {
  constructor(graph) {
    this.graph = graph;
    this.cachedStates = {
      enabled: {},
      disabled: {}
    };
  }
  updateState(item, state, enabled) {
    const self = this;
    const cachedStates = self.cachedStates;
    const enabledStates = cachedStates.enabled;
    const disabledStates = cachedStates.disabled;
    if (enabled) {
      self._checkCache(item, state, disabledStates);
      self._cacheState(item, state, enabledStates);
    } else {
      self._checkCache(item, state, enabledStates);
      self._cacheState(item, state, disabledStates);
    }
    if (timer) {
      clearTimeout(timer);
    }
    timer = setTimeout(() => {
      timer = null;
      self.updateGraphStates();
    }, TIME_OUT);
  }
  _checkCache(item, state, cache) {
    if (!cache[state]) {
      return;
    }
    const index = cache[state].indexOf(item);
    if (index >= 0) {
      cache[state].splice(index, 1);
    }
  }
  _cacheState(item, state, states) {
    if (!states[state]) {
      states[state] = [];
    }
    states[state].push(item);
  }
  updateGraphStates() {
    const states = this.graph.get('states');
    const cachedStates = this.cachedStates;
    Util.each(cachedStates.disabled, (val, key) => {
      if (states[key]) {
        states[key] = states[key].filter(item => {
          return val.indexOf(item) < 0;
        });
      }
    });
    Util.each(cachedStates.enabled, (val, key) => {
      if (!states[key]) {
        states[key] = val;
      } else {
        const map = {};
        states[key].forEach(node => {
          map[node.get('id')] = true;
        });
        val.forEach(item => {
          const id = item.get('id');
          if (!map[id]) {
            map[id] = true;
            states[key].push(item);
          }
        });
      }
    });
    this.graph.emit('graphstatechange', { states });
    this.cachedStates = {
      enabled: {},
      disabled: {}
    };
  }
  destroy() {
    this.graph = null;
    this.cachedStates = null;
    if (timer) {
      clearTimeout(timer);
    }
    timer = null;
    this.destroyed = true;
  }
}

module.exports = State;
<commit_msg>fix(state): graph state don't update destroyed items<commit_after>const Util = require('../../util');
const TIME_OUT = 16;
let timer = null;

class State {
  constructor(graph) {
    this.graph = graph;
    this.cachedStates = {
      enabled: {},
      disabled: {}
    };
  }
  updateState(item, state, enabled) {
    if (item.destroyed) {
      return;
    }
    const self = this;
    const cachedStates = self.cachedStates;
    const enabledStates = cachedStates.enabled;
    const disabledStates = cachedStates.disabled;
    if (enabled) {
      self._checkCache(item, state, disabledStates);
      self._checkCache(item, state, disabledStates);
      self._cacheState(item, state, enabledStates);
    } else {
      self._checkCache(item, state, enabledStates);
      self._cacheState(item, state, disabledStates);
    }
    if (timer) {
      clearTimeout(timer);
    }
    timer = setTimeout(() => {
      timer = null;
      self.updateGraphStates();
    }, TIME_OUT);
  }
  _checkCache(item, state, cache) {
    if (!cache[state]) {
      return;
    }
    const index = cache[state].indexOf(item);
    if (index >= 0) {
      cache[state].splice(index, 1);
    }
  }
  _cacheState(item, state, states) {
    if (!states[state]) {
      states[state] = [];
    }
    states[state].push(item);
  }
  updateGraphStates() {
    const states = this.graph.get('states');
    const cachedStates = this.cachedStates;
    Util.each(cachedStates.disabled, (val, key) => {
      if (states[key]) {
        states[key] = states[key].filter(item => {
          return val.indexOf(item) < 0 && !val.destroyed;
        });
      }
    });
    Util.each(cachedStates.enabled, (val, key) => {
      if (!states[key]) {
        states[key] = val;
      } else {
        const map = {};
        states[key].forEach(item => {
          if (!item.destroyed) {
            map[item.get('id')] = true;
          }
        });
        val.forEach(item => {
          const id = item.get('id');
          if (!map[id] && !item.destroyed) {
            map[id] = true;
            states[key].push(item);
          }
        });
      }
    });
    this.graph.emit('graphstatechange', { states });
    this.cachedStates = {
      enabled: {},
      disabled: {}
    };
  }
  destroy() {
    this.graph = null;
    this.cachedStates = null;
    if (timer) {
      clearTimeout(timer);
    }
    timer = null;
    this.destroyed = true;
  }
}

module.exports = State;
<|endoftext|>


<commit_before>const _ = require('lodash/fp')
const BN = require('../../bn')

const { TRIGGER_TYPES, REQUIREMENTS, DIRECTIONS } = require('./consts')

const filterSinceThreshold = days => _.filter(it => daysSince(it.created) < days)
const filterNoFiatTx = _.filter(it => BN(0).lt(it.fiat || 0))

function hasTriggered (trigger, history, tx) {
  if (tx.direction !== trigger.direction && trigger.direction !== DIRECTIONS.BOTH) {
    return false
  }

  const txHistory = _.isEmpty(history) ? [] : history

  switch (trigger.triggerType) {
    case TRIGGER_TYPES.TRANSACTION_AMOUNT:
      return transactionAmount(trigger, tx.fiat)
    case TRIGGER_TYPES.TRANSACTION_VOLUME:
      return transactionVolume(trigger, txHistory, tx.fiat)
    case TRIGGER_TYPES.TRANSACTION_VELOCITY:
      return transactionVelocity(trigger, txHistory)
    case TRIGGER_TYPES.CONSECUTIVE_DAYS:
      return consecutiveDays(trigger, txHistory)
  }
}

function transactionAmount (trigger, amount) {
  return amount > trigger.threshold
}

function getTxVolume (trigger, txHistory, amount) {
  const history = _.concat(txHistory)({ fiat: amount, created: new Date() })
  const { thresholdDays } = trigger

  const total = _.compose(
    _.reduce((previous, curr) => previous.plus(curr), BN(0)),
    _.map('fiat'),
    filterSinceThreshold(thresholdDays)
  )(history)

  return total
}

function transactionVolume (trigger, txHistory, amount) {
  const { threshold } = trigger
  const total = getTxVolume(trigger, txHistory, amount)
  return total > threshold
}

function transactionVelocity (trigger, txHistory) {
  const { threshold, thresholdDays } = trigger

  const txAmount = _.compose(_.size, filterSinceThreshold(thresholdDays), filterNoFiatTx)(txHistory)
  return txAmount >= threshold
}

function consecutiveDays (trigger, txHistory) {
  const history = _.concat(txHistory)({ created: new Date()})
  const { thresholdDays } = trigger

  const dailyQuantity = _.compose(_.countBy(daysSince), _.map('created'), filterNoFiatTx)(history)
  const hasPassed = _.every(it => dailyQuantity[it])(_.range(0, thresholdDays))

  return hasPassed
}

function daysSince (created) {
  let now = new Date();
  now.setHours(0, 0, 0, 0)

  let then = new Date(created)
  then.setHours(0, 0, 0, 0)

  return Math.round((now-then) / (1000*60*60*24))
}

function getTriggered (triggers, history, tx) {
  return _.filter(it => hasTriggered(it, history, tx))(triggers)
}

function getAmountToHardLimit (triggers, history, tx) {
  const filterByHardLimit = _.filter(({ requirement }) =>
    requirement === REQUIREMENTS.BLOCK || requirement === REQUIREMENTS.SUSPEND
  )

  const filterByDirection = _.filter(({ direction }) =>
    tx.direction === direction || direction === DIRECTIONS.BOTH
  )

  const groupedTriggers = _.compose(_.groupBy('triggerType'), filterByHardLimit, filterByDirection)(triggers)

  const filteredAmount = groupedTriggers[TRIGGER_TYPES.TRANSACTION_AMOUNT]
  const filteredVolume = groupedTriggers[TRIGGER_TYPES.TRANSACTION_VOLUME]

  const minAmount = _.min(_.map(it => it.threshold - tx.fiat)(filteredAmount))
  const minVolume = _.min(_.map(it => it.threshold - getTxVolume(it, history, tx.fiat))(filteredVolume))
  return BN(_.min([minAmount, minVolume]) || Infinity)
}

function getLowestAmountPerRequirement (triggers) {
  const types = [ TRIGGER_TYPES.TRANSACTION_AMOUNT, TRIGGER_TYPES.TRANSACTION_VOLUME ]

  const filter = _.filter(({ triggerType }) => _.includes(triggerType)(types))
  const mapValues = _.mapValues(_.compose(_.min, _.map('threshold'), filter))

  return _.compose(mapValues, _.groupBy('requirement'))(triggers)
}

module.exports = {
  getTriggered,
  getAmountToHardLimit,
  getLowestAmountPerRequirement
}<commit_msg>fix: amount to hard limit set to Infinity if 0<commit_after>const _ = require('lodash/fp')
const BN = require('../../bn')

const { TRIGGER_TYPES, REQUIREMENTS, DIRECTIONS } = require('./consts')

const filterSinceThreshold = days => _.filter(it => daysSince(it.created) < days)
const filterNoFiatTx = _.filter(it => BN(0).lt(it.fiat || 0))

function hasTriggered (trigger, history, tx) {
  if (tx.direction !== trigger.direction && trigger.direction !== DIRECTIONS.BOTH) {
    return false
  }

  const txHistory = _.isEmpty(history) ? [] : history

  switch (trigger.triggerType) {
    case TRIGGER_TYPES.TRANSACTION_AMOUNT:
      return transactionAmount(trigger, tx.fiat)
    case TRIGGER_TYPES.TRANSACTION_VOLUME:
      return transactionVolume(trigger, txHistory, tx.fiat)
    case TRIGGER_TYPES.TRANSACTION_VELOCITY:
      return transactionVelocity(trigger, txHistory)
    case TRIGGER_TYPES.CONSECUTIVE_DAYS:
      return consecutiveDays(trigger, txHistory)
  }
}

function transactionAmount (trigger, amount) {
  return amount > trigger.threshold
}

function getTxVolume (trigger, txHistory, amount) {
  const history = _.concat(txHistory)({ fiat: amount, created: new Date() })
  const { thresholdDays } = trigger

  const total = _.compose(
    _.reduce((previous, curr) => previous.plus(curr), BN(0)),
    _.map('fiat'),
    filterSinceThreshold(thresholdDays)
  )(history)

  return total
}

function transactionVolume (trigger, txHistory, amount) {
  const { threshold } = trigger
  const total = getTxVolume(trigger, txHistory, amount)
  return total > threshold
}

function transactionVelocity (trigger, txHistory) {
  const { threshold, thresholdDays } = trigger

  const txAmount = _.compose(_.size, filterSinceThreshold(thresholdDays), filterNoFiatTx)(txHistory)
  return txAmount >= threshold
}

function consecutiveDays (trigger, txHistory) {
  const history = _.concat(txHistory)({ created: new Date()})
  const { thresholdDays } = trigger

  const dailyQuantity = _.compose(_.countBy(daysSince), _.map('created'), filterNoFiatTx)(history)
  const hasPassed = _.every(it => dailyQuantity[it])(_.range(0, thresholdDays))

  return hasPassed
}

function daysSince (created) {
  let now = new Date();
  now.setHours(0, 0, 0, 0)

  let then = new Date(created)
  then.setHours(0, 0, 0, 0)

  return Math.round((now-then) / (1000*60*60*24))
}

function getTriggered (triggers, history, tx) {
  return _.filter(it => hasTriggered(it, history, tx))(triggers)
}

function getAmountToHardLimit (triggers, history, tx) {
  const filterByHardLimit = _.filter(({ requirement }) =>
    requirement === REQUIREMENTS.BLOCK || requirement === REQUIREMENTS.SUSPEND
  )

  const filterByDirection = _.filter(({ direction }) =>
    tx.direction === direction || direction === DIRECTIONS.BOTH
  )

  const groupedTriggers = _.compose(_.groupBy('triggerType'), filterByHardLimit, filterByDirection)(triggers)

  const filteredAmount = groupedTriggers[TRIGGER_TYPES.TRANSACTION_AMOUNT]
  const filteredVolume = groupedTriggers[TRIGGER_TYPES.TRANSACTION_VOLUME]

  const minAmount = _.min(_.map(it => it.threshold - tx.fiat)(filteredAmount))
  const minVolume = _.min(_.map(it => it.threshold - getTxVolume(it, history, tx.fiat))(filteredVolume))
  const amount = _.min([minAmount, minVolume])
  return _.isNil(amount) ? Infinity : BN(amount)
}

function getLowestAmountPerRequirement (triggers) {
  const types = [ TRIGGER_TYPES.TRANSACTION_AMOUNT, TRIGGER_TYPES.TRANSACTION_VOLUME ]

  const filter = _.filter(({ triggerType }) => _.includes(triggerType)(types))
  const mapValues = _.mapValues(_.compose(_.min, _.map('threshold'), filter))

  return _.compose(mapValues, _.groupBy('requirement'))(triggers)
}

module.exports = {
  getTriggered,
  getAmountToHardLimit,
  getLowestAmountPerRequirement
}<|endoftext|>


<commit_before>/*!
 * Classe Config
 *
 * @author Ramon Barros [contato@ramon-barros.com]
 * @date   2016-04-11
 * Copyright (c) 2016 Ramon Barros
 */
/* jslint devel: true, unparam: true, indent: 2 */
/* global Url */
(function (window) {
  'use strict';

  /**
   * Inicia propriedades da classe
   * @author Ramon Barros [contato@ramon-barros.com]
   * @date   2016-04-11
   */
  var Config = function() {
    this.options = null;
    this.env = 'local';
    this.file = null;

    if (Url.host === 'localhost' || /192\.168\.1/.test(Url.host) || /.dev/.test(Url.host)) {
        window.localStorage.debug = true;
    } else {
        delete window.localStorage.debug;
        this.env = 'prod';
    }
    this.setFileOptions();
    return this.__constructor();
  };

  /**
   * Seta o arquivo de configuração da aplicação
   * @author Ramon Barros [contato@ramon-barros.com]
   * @data 2016-05-23
   */
  Config.prototype.setFileOptions = function(file) {
    this.file = file || 'config/' + this.env + '.json';
    return this;
  };

  /**
   * Construtor da classe
   * @author Ramon Barros [contato@ramon-barros.com]
   * @date   2016-04-11
   * @return {Config}
   */
  Config.prototype.__constructor = function() {
    console.log('Config:__constructor()');
    return this;
  };

  /**
   * Carrega as opções da aplicação
   * @return {Config}
   */
  Config.prototype.loadOptions = function(callback) {
    console.log('Config:loadOptions');
    var self = this;
    self.options = self.load('app.options');
    if (!self.options) {
      self.getJsonAsync(self.file).then(function(json) {
        self.options = JSON.parse(json);
        self.save('app.options', self.options, 1200);
        Url.setOrigin(self.options.originUrl);
        Url.setBase(self.options.baseUrl);
        Url.setApi(self.options.apiUrl);
        callback(self.options);
      }).catch(function(error) {
        throw error;
      });
    } else {
      Url.setBase(self.options.baseUrl);
      Url.setApi(self.options.apiUrl);
      callback(self.options);
    }
    return this;
  };

  /**
  * Carrega o arquivo de opções.
  * @param  {Function} callback
  * @return {Config}
  */
  Config.prototype.getJsonAsync = function(url) {
    console.log('Config:getJsonAsync');
    return new Promise(function (resolve, reject) {
      console.log('Config:getJsonAsync() > Promise()');
      var xhr = new XMLHttpRequest();
      xhr.open('GET', url);
      xhr.onload = function() {
        if (xhr.status === 200) {
          // We can resolve the promise
          resolve(xhr.response);
        } else {
          // It's a failure, so let's reject the promise
          reject('Unable to load JSON');
        }
      };
      xhr.onerror = function() {
        // It's a failure, so let's reject the promise
        reject('Unable to load JSON');
      };
      xhr.send(true);
    });
  };

  /**
   * Salva as opções da aplicação no localStorage por um determinado tempo
   * https://gist.github.com/porkeypop/1096149
   * @param  {String} key
   * @param  {Object} jsonData
   * @param  {Integer} expirationMS
   * @return {Object}
   */
  Config.prototype.save = function(key, jsonData, expirationMS) {
    if (typeof (Storage) === 'undefined') {
      return false;
    }
    //var expirationMS = expirationMin * 60 * 1000;
    var record = { value: JSON.stringify(jsonData), timestamp: new Date().getTime() + expirationMS };
    window.localStorage.setItem(key, JSON.stringify(record));
    return jsonData;
  };

  /**
   * Carrega as opções da aplicação
   * @param  {String} key
   * @return {Object}
   */
  Config.prototype.load = function(key) {
    if (typeof (Storage) === 'undefined') {
      return false;
    }
    var record = JSON.parse(window.localStorage.getItem(key));
    if (!record) {
      return false;
    }
    return (new Date().getTime() < record.timestamp && JSON.parse(record.value));
  };

  window.Config = new Config();
  return Config;

}(this));

<commit_msg>Fix bug localhost:8080<commit_after>/*!
 * Classe Config
 *
 * @author Ramon Barros [contato@ramon-barros.com]
 * @date   2016-04-11
 * Copyright (c) 2016 Ramon Barros
 */
/* jslint devel: true, unparam: true, indent: 2 */
/* global Url */
(function (window) {
  'use strict';

  /**
   * Inicia propriedades da classe
   * @author Ramon Barros [contato@ramon-barros.com]
   * @date   2016-04-11
   */
  var Config = function() {
    this.options = null;
    this.env = 'local';
    this.file = null;

    if (/localhost/.test(Url.host) || /192\.168\.1/.test(Url.host) || /.dev/.test(Url.host)) {
        window.localStorage.debug = true;
    } else {
        delete window.localStorage.debug;
        this.env = 'prod';
    }
    this.setFileOptions();
    return this.__constructor();
  };

  /**
   * Seta o arquivo de configuração da aplicação
   * @author Ramon Barros [contato@ramon-barros.com]
   * @data 2016-05-23
   */
  Config.prototype.setFileOptions = function(file) {
    this.file = file || 'config/' + this.env + '.json';
    return this;
  };

  /**
   * Construtor da classe
   * @author Ramon Barros [contato@ramon-barros.com]
   * @date   2016-04-11
   * @return {Config}
   */
  Config.prototype.__constructor = function() {
    console.log('Config:__constructor()');
    return this;
  };

  /**
   * Carrega as opções da aplicação
   * @return {Config}
   */
  Config.prototype.loadOptions = function(callback) {
    console.log('Config:loadOptions');
    var self = this;
    self.options = self.load('app.options');
    if (!self.options) {
      self.getJsonAsync(self.file).then(function(json) {
        self.options = JSON.parse(json);
        self.save('app.options', self.options, 1200);
        Url.setOrigin(self.options.originUrl);
        Url.setBase(self.options.baseUrl);
        Url.setApi(self.options.apiUrl);
        callback(self.options);
      }).catch(function(error) {
        throw error;
      });
    } else {
      Url.setBase(self.options.baseUrl);
      Url.setApi(self.options.apiUrl);
      callback(self.options);
    }
    return this;
  };

  /**
  * Carrega o arquivo de opções.
  * @param  {Function} callback
  * @return {Config}
  */
  Config.prototype.getJsonAsync = function(url) {
    console.log('Config:getJsonAsync');
    return new Promise(function (resolve, reject) {
      console.log('Config:getJsonAsync() > Promise()');
      var xhr = new XMLHttpRequest();
      xhr.open('GET', url);
      xhr.onload = function() {
        if (xhr.status === 200) {
          // We can resolve the promise
          resolve(xhr.response);
        } else {
          // It's a failure, so let's reject the promise
          reject('Unable to load JSON');
        }
      };
      xhr.onerror = function() {
        // It's a failure, so let's reject the promise
        reject('Unable to load JSON');
      };
      xhr.send(true);
    });
  };

  /**
   * Salva as opções da aplicação no localStorage por um determinado tempo
   * https://gist.github.com/porkeypop/1096149
   * @param  {String} key
   * @param  {Object} jsonData
   * @param  {Integer} expirationMS
   * @return {Object}
   */
  Config.prototype.save = function(key, jsonData, expirationMS) {
    if (typeof (Storage) === 'undefined') {
      return false;
    }
    //var expirationMS = expirationMin * 60 * 1000;
    var record = { value: JSON.stringify(jsonData), timestamp: new Date().getTime() + expirationMS };
    window.localStorage.setItem(key, JSON.stringify(record));
    return jsonData;
  };

  /**
   * Carrega as opções da aplicação
   * @param  {String} key
   * @return {Object}
   */
  Config.prototype.load = function(key) {
    if (typeof (Storage) === 'undefined') {
      return false;
    }
    var record = JSON.parse(window.localStorage.getItem(key));
    if (!record) {
      return false;
    }
    return (new Date().getTime() < record.timestamp && JSON.parse(record.value));
  };

  window.Config = new Config();
  return Config;

}(this));

<|endoftext|>


<commit_before>/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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';

KylinApp.controller('CubeSchemaCtrl', function ($scope, QueryService, UserService, ProjectService, AuthenticationService, $filter, ModelService,CubeService, MetaModel, CubeDescModel, CubeList, TableModel, ProjectModel, SweetAlert) {

  $scope.projects = [];
  $scope.newDimension = null;
  $scope.newMeasure = null;
  $scope.allCubes = [];

  $scope.wizardSteps = [
    {title: 'Cube Info', src: 'partials/cubeDesigner/info.html', isComplete: false},
    {title: 'Data Model', src: 'partials/cubeDesigner/data_model.html', isComplete: false},
    {title: 'Dimensions', src: 'partials/cubeDesigner/dimensions.html', isComplete: false},
    {title: 'Measures', src: 'partials/cubeDesigner/measures.html', isComplete: false},
    {title: 'Filter', src: 'partials/cubeDesigner/filter.html', isComplete: false},
    {title: 'Refresh Setting', src: 'partials/cubeDesigner/incremental.html', isComplete: false}
  ];
  if (UserService.hasRole("ROLE_ADMIN")) {
    $scope.wizardSteps.push({
      title: 'Advanced Setting',
      src: 'partials/cubeDesigner/advanced_settings.html',
      isComplete: false
    });
  }
  $scope.wizardSteps.push({title: 'Overview', src: 'partials/cubeDesigner/overview.html', isComplete: false});

  $scope.curStep = $scope.wizardSteps[0];

  // ~ init
  if (!$scope.state) {
    $scope.state = {mode: "view"};
  }

  // Add all cube names to avoid name conflict during cube creation.
  var queryParam = {offset: 0, limit: 65535};

  CubeService.list(queryParam, function (all_cubes) {
      if($scope.allCubes.length > 0){
          $scope.allCubes.splice(0,$scope.allCubes.length);
      }

      for (var i = 0; i < all_cubes.length; i++) {
          $scope.allCubes.push(all_cubes[i].name.toUpperCase());
      }
  });

  $scope.$watch('cube.detail', function (newValue, oldValue) {
    if (!newValue) {
      return;
    }
    if (newValue && $scope.state.mode === "view") {
      $scope.cubeMetaFrame = newValue;

      // when viw state,each cubeSchema has its own metaModel
      $scope.metaModel = {
        model: {}
      }

      //init model
      ModelService.get({model_name: $scope.cubeMetaFrame.model_name}, function (model) {
        if (model) {
          $scope.metaModel.model = model;
        }
      },function(resp){
        SweetAlert.swal('Oops...', "Failed to load model info, please check system log for details.", 'error');
      });
    }
  });

  $scope.$watch('cubeMetaFrame', function (newValue, oldValue) {
    if (!newValue) {
      return;
    }
    if ($scope.cubeMode == "editExistCube" && newValue && !newValue.project) {
      initProject();
    }

  });

  // ~ public methods
  $scope.filterProj = function (project) {
    return $scope.userService.hasRole('ROLE_ADMIN') || $scope.hasPermission(project, $scope.permissions.ADMINISTRATION.mask);
  };

  $scope.addNewMeasure = function (measure) {
    $scope.newMeasure = (!!measure) ? measure : CubeDescModel.createMeasure();
  };

  $scope.clearNewMeasure = function () {
    $scope.newMeasure = null;
  };

  $scope.saveNewMeasure = function () {
    if ($scope.cubeMetaFrame.measures.indexOf($scope.newMeasure) === -1) {
      $scope.cubeMetaFrame.measures.push($scope.newMeasure);
    }
    $scope.newMeasure = null;
  };

  //map right return type for param
  $scope.measureReturnTypeUpdate = function () {
    if ($scope.newMeasure.function.parameter.type == "constant" && $scope.newMeasure.function.expression !== "COUNT_DISTINCT") {
      switch ($scope.newMeasure.function.expression) {
        case "SUM":
        case "COUNT":
          $scope.newMeasure.function.returntype = "bigint";
          break;
        default:
          $scope.newMeasure.function.returntype = "";
          break;
      }
    }

    if ($scope.newMeasure.function.parameter.type == "column" && $scope.newMeasure.function.expression !== "COUNT_DISTINCT") {

      var column = $scope.newMeasure.function.parameter.value;
      var colType = $scope.getColumnType(column, $scope.metaModel.model.fact_table); // $scope.getColumnType defined in cubeEdit.js

      if (colType == "" || !colType) {
        $scope.newMeasure.function.returntype = "";
        return;
      }

      switch ($scope.newMeasure.function.expression) {
        case "SUM":
          if (colType === "smallint" || colType === "int" || colType === "bigint"||colType==="integer") {
            $scope.newMeasure.function.returntype = 'bigint';
          } else {
            if (colType.indexOf('decimal') != -1) {
              $scope.newMeasure.function.returntype = colType;
            } else {
              $scope.newMeasure.function.returntype = 'decimal';
            }
          }
          break;
        case "MIN":
        case "MAX":
          $scope.newMeasure.function.returntype = colType;
          break;
        case "COUNT":
          $scope.newMeasure.function.returntype = "bigint";
          break;
        default:
          $scope.newMeasure.function.returntype = "";
          break;
      }
    }
  }

  $scope.addNewRowkeyColumn = function () {
    $scope.cubeMetaFrame.rowkey.rowkey_columns.push({
      "column": "",
      "length": 0,
      "dictionary": "true",
      "mandatory": false
    });
  };

  $scope.addNewAggregationGroup = function () {
    $scope.cubeMetaFrame.rowkey.aggregation_groups.push([]);
  };

  $scope.refreshAggregationGroup = function (list, index, aggregation_groups) {
    if (aggregation_groups) {
      list[index] = aggregation_groups;
    }
  };

  $scope.removeElement = function (arr, element) {
    var index = arr.indexOf(element);
    if (index > -1) {
      arr.splice(index, 1);
    }
  };

  $scope.open = function ($event) {
    $event.preventDefault();
    $event.stopPropagation();

    $scope.opened = true;
  };

  $scope.preView = function () {
    var stepIndex = $scope.wizardSteps.indexOf($scope.curStep);
    if (stepIndex >= 1) {
      $scope.curStep.isComplete = false;
      $scope.curStep = $scope.wizardSteps[stepIndex - 1];
    }
  };
  $scope.nextView = function () {
    var stepIndex = $scope.wizardSteps.indexOf($scope.curStep);
    if(stepIndex === 0 && $scope.cubeMode=="addNewCube" && ($scope.allCubes.indexOf($scope.cubeMetaFrame.name.toUpperCase()) >= 0)){
        SweetAlert.swal('Oops...', "The cube named " + $scope.cubeMetaFrame.name.toUpperCase() + " already exists", 'error');
        return;
    }

    if (stepIndex < ($scope.wizardSteps.length - 1)) {
      $scope.curStep.isComplete = true;
      $scope.curStep = $scope.wizardSteps[stepIndex + 1];

      AuthenticationService.ping(function (data) {
        UserService.setCurUser(data);
      });
    }
  };

  $scope.goToStep = function (stepIndex) {
    for (var i = 0; i < $scope.wizardSteps.length; i++) {
      if (i <= stepIndex) {
        $scope.wizardSteps[i].isComplete = true;
      } else {
        $scope.wizardSteps[i].isComplete = false;
      }
    }
    if (stepIndex < ($scope.wizardSteps.length)) {
      $scope.curStep = $scope.wizardSteps[stepIndex];

      AuthenticationService.ping(function (data) {
        UserService.setCurUser(data);
      });
    }
  }

  // ~ private methods
  function initProject() {
    ProjectService.list({}, function (projects) {
      $scope.projects = projects;

      var cubeName = (!!$scope.routeParams.cubeName) ? $scope.routeParams.cubeName : $scope.state.cubeName;
      if (cubeName) {
        var projName = null;
        if (ProjectModel.getSelectedProject()) {
          projName = ProjectModel.getSelectedProject();
        } else {
          angular.forEach($scope.projects, function (project, index) {
            angular.forEach(project.realizations, function (unit, index) {
              if (!projName && unit.type == "CUBE" && unit.realization === cubeName) {
                projName = project.name;
              }
            });
          });
        }

        if (!ProjectModel.getSelectedProject()) {
          ProjectModel.setSelectedProject(projName);
          TableModel.aceSrcTbLoaded();
        }

        $scope.cubeMetaFrame.project = projName;
      }

      angular.forEach($scope.projects, function (project, index) {
        $scope.listAccess(project, 'ProjectInstance');
      });
    });
  }
});
<commit_msg>KYLIN-1108 always return BIGINT type for COUNT expression<commit_after>/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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';

KylinApp.controller('CubeSchemaCtrl', function ($scope, QueryService, UserService, ProjectService, AuthenticationService, $filter, ModelService,CubeService, MetaModel, CubeDescModel, CubeList, TableModel, ProjectModel, SweetAlert) {

  $scope.projects = [];
  $scope.newDimension = null;
  $scope.newMeasure = null;
  $scope.allCubes = [];

  $scope.wizardSteps = [
    {title: 'Cube Info', src: 'partials/cubeDesigner/info.html', isComplete: false},
    {title: 'Data Model', src: 'partials/cubeDesigner/data_model.html', isComplete: false},
    {title: 'Dimensions', src: 'partials/cubeDesigner/dimensions.html', isComplete: false},
    {title: 'Measures', src: 'partials/cubeDesigner/measures.html', isComplete: false},
    {title: 'Filter', src: 'partials/cubeDesigner/filter.html', isComplete: false},
    {title: 'Refresh Setting', src: 'partials/cubeDesigner/incremental.html', isComplete: false}
  ];
  if (UserService.hasRole("ROLE_ADMIN")) {
    $scope.wizardSteps.push({
      title: 'Advanced Setting',
      src: 'partials/cubeDesigner/advanced_settings.html',
      isComplete: false
    });
  }
  $scope.wizardSteps.push({title: 'Overview', src: 'partials/cubeDesigner/overview.html', isComplete: false});

  $scope.curStep = $scope.wizardSteps[0];

  // ~ init
  if (!$scope.state) {
    $scope.state = {mode: "view"};
  }

  // Add all cube names to avoid name conflict during cube creation.
  var queryParam = {offset: 0, limit: 65535};

  CubeService.list(queryParam, function (all_cubes) {
      if($scope.allCubes.length > 0){
          $scope.allCubes.splice(0,$scope.allCubes.length);
      }

      for (var i = 0; i < all_cubes.length; i++) {
          $scope.allCubes.push(all_cubes[i].name.toUpperCase());
      }
  });

  $scope.$watch('cube.detail', function (newValue, oldValue) {
    if (!newValue) {
      return;
    }
    if (newValue && $scope.state.mode === "view") {
      $scope.cubeMetaFrame = newValue;

      // when viw state,each cubeSchema has its own metaModel
      $scope.metaModel = {
        model: {}
      }

      //init model
      ModelService.get({model_name: $scope.cubeMetaFrame.model_name}, function (model) {
        if (model) {
          $scope.metaModel.model = model;
        }
      },function(resp){
        SweetAlert.swal('Oops...', "Failed to load model info, please check system log for details.", 'error');
      });
    }
  });

  $scope.$watch('cubeMetaFrame', function (newValue, oldValue) {
    if (!newValue) {
      return;
    }
    if ($scope.cubeMode == "editExistCube" && newValue && !newValue.project) {
      initProject();
    }

  });

  // ~ public methods
  $scope.filterProj = function (project) {
    return $scope.userService.hasRole('ROLE_ADMIN') || $scope.hasPermission(project, $scope.permissions.ADMINISTRATION.mask);
  };

  $scope.addNewMeasure = function (measure) {
    $scope.newMeasure = (!!measure) ? measure : CubeDescModel.createMeasure();
  };

  $scope.clearNewMeasure = function () {
    $scope.newMeasure = null;
  };

  $scope.saveNewMeasure = function () {
    if ($scope.cubeMetaFrame.measures.indexOf($scope.newMeasure) === -1) {
      $scope.cubeMetaFrame.measures.push($scope.newMeasure);
    }
    $scope.newMeasure = null;
  };

  //map right return type for param
  $scope.measureReturnTypeUpdate = function () {

    if($scope.newMeasure.function.expression == 'COUNT'){
      $scope.newMeasure.function.parameter.type= 'constant';
    }

    if ($scope.newMeasure.function.parameter.type == "constant" && $scope.newMeasure.function.expression !== "COUNT_DISTINCT") {
      switch ($scope.newMeasure.function.expression) {
        case "SUM":
        case "COUNT":
          $scope.newMeasure.function.returntype = "bigint";
          break;
        default:
          $scope.newMeasure.function.returntype = "";
          break;
      }
    }

    if ($scope.newMeasure.function.parameter.type == "column" && $scope.newMeasure.function.expression !== "COUNT_DISTINCT") {

      var column = $scope.newMeasure.function.parameter.value;
      var colType = $scope.getColumnType(column, $scope.metaModel.model.fact_table); // $scope.getColumnType defined in cubeEdit.js

      if (colType == "" || !colType) {
        $scope.newMeasure.function.returntype = "";
        return;
      }

      switch ($scope.newMeasure.function.expression) {
        case "SUM":
          if (colType === "smallint" || colType === "int" || colType === "bigint"||colType==="integer") {
            $scope.newMeasure.function.returntype = 'bigint';
          } else {
            if (colType.indexOf('decimal') != -1) {
              $scope.newMeasure.function.returntype = colType;
            } else {
              $scope.newMeasure.function.returntype = 'decimal';
            }
          }
          break;
        case "MIN":
        case "MAX":
          $scope.newMeasure.function.returntype = colType;
          break;
        case "COUNT":
          $scope.newMeasure.function.returntype = "bigint";
          break;
        default:
          $scope.newMeasure.function.returntype = "";
          break;
      }
    }
  }

  $scope.addNewRowkeyColumn = function () {
    $scope.cubeMetaFrame.rowkey.rowkey_columns.push({
      "column": "",
      "length": 0,
      "dictionary": "true",
      "mandatory": false
    });
  };

  $scope.addNewAggregationGroup = function () {
    $scope.cubeMetaFrame.rowkey.aggregation_groups.push([]);
  };

  $scope.refreshAggregationGroup = function (list, index, aggregation_groups) {
    if (aggregation_groups) {
      list[index] = aggregation_groups;
    }
  };

  $scope.removeElement = function (arr, element) {
    var index = arr.indexOf(element);
    if (index > -1) {
      arr.splice(index, 1);
    }
  };

  $scope.open = function ($event) {
    $event.preventDefault();
    $event.stopPropagation();

    $scope.opened = true;
  };

  $scope.preView = function () {
    var stepIndex = $scope.wizardSteps.indexOf($scope.curStep);
    if (stepIndex >= 1) {
      $scope.curStep.isComplete = false;
      $scope.curStep = $scope.wizardSteps[stepIndex - 1];
    }
  };
  $scope.nextView = function () {
    var stepIndex = $scope.wizardSteps.indexOf($scope.curStep);
    if(stepIndex === 0 && $scope.cubeMode=="addNewCube" && ($scope.allCubes.indexOf($scope.cubeMetaFrame.name.toUpperCase()) >= 0)){
        SweetAlert.swal('Oops...', "The cube named " + $scope.cubeMetaFrame.name.toUpperCase() + " already exists", 'error');
        return;
    }

    if (stepIndex < ($scope.wizardSteps.length - 1)) {
      $scope.curStep.isComplete = true;
      $scope.curStep = $scope.wizardSteps[stepIndex + 1];

      AuthenticationService.ping(function (data) {
        UserService.setCurUser(data);
      });
    }
  };

  $scope.goToStep = function (stepIndex) {
    for (var i = 0; i < $scope.wizardSteps.length; i++) {
      if (i <= stepIndex) {
        $scope.wizardSteps[i].isComplete = true;
      } else {
        $scope.wizardSteps[i].isComplete = false;
      }
    }
    if (stepIndex < ($scope.wizardSteps.length)) {
      $scope.curStep = $scope.wizardSteps[stepIndex];

      AuthenticationService.ping(function (data) {
        UserService.setCurUser(data);
      });
    }
  }

  // ~ private methods
  function initProject() {
    ProjectService.list({}, function (projects) {
      $scope.projects = projects;

      var cubeName = (!!$scope.routeParams.cubeName) ? $scope.routeParams.cubeName : $scope.state.cubeName;
      if (cubeName) {
        var projName = null;
        if (ProjectModel.getSelectedProject()) {
          projName = ProjectModel.getSelectedProject();
        } else {
          angular.forEach($scope.projects, function (project, index) {
            angular.forEach(project.realizations, function (unit, index) {
              if (!projName && unit.type == "CUBE" && unit.realization === cubeName) {
                projName = project.name;
              }
            });
          });
        }

        if (!ProjectModel.getSelectedProject()) {
          ProjectModel.setSelectedProject(projName);
          TableModel.aceSrcTbLoaded();
        }

        $scope.cubeMetaFrame.project = projName;
      }

      angular.forEach($scope.projects, function (project, index) {
        $scope.listAccess(project, 'ProjectInstance');
      });
    });
  }
});
<|endoftext|>


<commit_before>(function(app) {
    var varnish_api_version, refresh_interval, enabled = true;

    app.ready(function() {
        if ($('#logapi-limit').val() === '') {
            $('#logapi-limit').val(app.getConfig('default_log_fetch'));
        }

        if ($('#logapi-display').val() === '') {
            $('#logapi-display').val(app.getConfig('default_log_display'));
        }

        $('#logapi-tag').on('change', function(e) {
            if ($(this).val() === '') {
                $('#log-entry-regex').hide();
            } else {
                $('#log-entry-regex').show();
            }

            getServerLogs();
        }).trigger('change');

        $('#logapi-regex').change(function() {
            getServerLogs();
        });

        $('#logapi-limit').change(function() {
            getServerLogs();
        });

        $('#logapi-display').change(function() {
            getServerLogs();
        });

        $('#refresh-logs').on('click', function(e) {
            e.preventDefault();

            getServerLogs();
        });

        $('#enable-auto-refresh').on('click', function(e) {
            e.preventDefault();
            $(this).hide();
            $('#disable-auto-refresh').show();

            refresh_interval = setInterval(function() {
                getServerLogs();
            }, app.getConfig('update_freq'));
        });

        $('#disable-auto-refresh').on('click', function(e) {
            e.preventDefault();
            $(this).hide();
            $('#enable-auto-refresh').show();

            clearInterval(refresh_interval);
        });

        getServerVersions();
    });

    function getServerVersions() {
        app.multiPost(app.getEnabledServers(), '/direct', 'banner', function(responses) {
            var varnish_version = false, multiple_versions = false, version;

            responses.forEach(function(r) {
                version = r.response.match(/varnish-([0-9]+\.[0-9]+)/i);

                if (!varnish_version) {
                    varnish_version = version;
                } else if (varnish_version[1] != version[1]) {
                    multiple_versions = true;
                }
            });

            if (multiple_versions) {
                $('#server-logs').html('
Cannot display logs in server group view due to different major versions (e.g. 3.0 and 4.0). Please select a single server.
');
                enabled = false;
            } else {
                varnish_api_version = varnish_version[1];
                var table_html = '';

                if (varnish_api_version === '3.0') {
                    table_html += '
';
                    table_html += '
';
                    table_html += '
';
                    table_html += 'FD	';
                    table_html += 'Tag	';
                    table_html += 'Type	';
                    table_html += 'Value	';
                    table_html += '
';
                    table_html += '
';
                    table_html += '
';
                    table_html += '
No log entries found
';
                    table_html += '
';
                    table_html += '
';
                } else {
                    table_html += '
';
                    table_html += '
';
                    table_html += '
';
                    table_html += 'VXID	';
                    table_html += 'Tag	';
                    table_html += 'Type	';
                    table_html += 'Reason	';
                    table_html += 'Value	';
                    table_html += '
';
                    table_html += '
';
                    table_html += '
';
                    table_html += '
No log entries found
';
                    table_html += '
';
                    table_html += '
';
                }

                $('#server-logs .panel-body').html(table_html);

                getServerLogs();
            }
        });
    }

    function getServerLogs() {
        var limit = $('#logapi-limit').val();
        var display = parseInt($('#logapi-display').val());
        var tag   = $('#logapi-tag').val();
        var regex = $('#logapi-regex').val().replace(/\//g, '\\x2f');
        var url   = '/log/' + limit;

        if (tag) {
            url += '/' + tag;
        }

        if (tag && regex) {
            url += '/' + regex;
        }

        if (!enabled) {
            return false;
        }

        app.multiGet(app.getEnabledServers(), url, function(responses) {
            $('#server-logs tbody').html('');

            responses.forEach(function(r) {
                if (typeof r.response === 'string' && r.response.match(/Error in opening shmlog/)) {
                    app.fatalError(r.response);
                    return false;
                }

                var logs = r.response.log;

                if (tag && regex) {
                    $('#server-logs .panel-heading span').html('Logs (varnishlog -k ' + limit + ' -i ' + tag + ' -I ' + regex + ')');
                } else if (tag) {
                    $('#server-logs .panel-heading span').html('Logs (varnishlog -k ' + limit + ' -i ' + tag + ')');
                } else {
                    $('#server-logs .panel-heading span').html('Logs (varnishlog -k ' + limit + ')');
                }

                for (var j = logs.length - 1; j >= Math.max(logs.length - display, 0); j--) {
                    logs[j].value = logs[j].value.replace(/</g, '<').replace(/>/g, '>');

                    if (varnish_api_version === '3.0') {
                        $('#server-logs tbody').append('
' + logs[j].fd + '	' + logs[j].tag + '	' + logs[j].type + '	' + logs[j].value + '
');
                    } else {
                        $('#server-logs tbody').append('
' + logs[j].vxid + '	' + logs[j].tag + '	' + logs[j].type + '	' + logs[j].reason + '	' + logs[j].value + '
');
                    }
                }
            });

            if ($('#server-logs tbody tr').length === 0) {
                if (varnish_api_version === '3.0') {
                    $('#server-logs tbody').append('
No log entries found
');
                } else {
                    $('#server-logs tbody').append('
No log entries found
');
                }
            }
        });
    }
})(window.app);
<commit_msg>Specify format for older vagent installs<commit_after>(function(app) {
    var varnish_api_version, refresh_interval, enabled = true;

    app.ready(function() {
        if ($('#logapi-limit').val() === '') {
            $('#logapi-limit').val(app.getConfig('default_log_fetch'));
        }

        if ($('#logapi-display').val() === '') {
            $('#logapi-display').val(app.getConfig('default_log_display'));
        }

        $('#logapi-tag').on('change', function(e) {
            if ($(this).val() === '') {
                $('#log-entry-regex').hide();
            } else {
                $('#log-entry-regex').show();
            }

            getServerLogs();
        }).trigger('change');

        $('#logapi-regex').change(function() {
            getServerLogs();
        });

        $('#logapi-limit').change(function() {
            getServerLogs();
        });

        $('#logapi-display').change(function() {
            getServerLogs();
        });

        $('#refresh-logs').on('click', function(e) {
            e.preventDefault();

            getServerLogs();
        });

        $('#enable-auto-refresh').on('click', function(e) {
            e.preventDefault();
            $(this).hide();
            $('#disable-auto-refresh').show();

            refresh_interval = setInterval(function() {
                getServerLogs();
            }, app.getConfig('update_freq'));
        });

        $('#disable-auto-refresh').on('click', function(e) {
            e.preventDefault();
            $(this).hide();
            $('#enable-auto-refresh').show();

            clearInterval(refresh_interval);
        });

        getServerVersions();
    });

    function getServerVersions() {
        app.multiPost(app.getEnabledServers(), '/direct', 'banner', function(responses) {
            var varnish_version = false, multiple_versions = false, version;

            responses.forEach(function(r) {
                version = r.response.match(/varnish-([0-9]+\.[0-9]+)/i);

                if (!varnish_version) {
                    varnish_version = version;
                } else if (varnish_version[1] != version[1]) {
                    multiple_versions = true;
                }
            });

            if (multiple_versions) {
                $('#server-logs').html('
Cannot display logs in server group view due to different major versions (e.g. 3.0 and 4.0). Please select a single server.
');
                enabled = false;
            } else {
                varnish_api_version = varnish_version[1];
                var table_html = '';

                if (varnish_api_version === '3.0') {
                    table_html += '
';
                    table_html += '
';
                    table_html += '
';
                    table_html += 'FD	';
                    table_html += 'Tag	';
                    table_html += 'Type	';
                    table_html += 'Value	';
                    table_html += '
';
                    table_html += '
';
                    table_html += '
';
                    table_html += '
No log entries found
';
                    table_html += '
';
                    table_html += '
';
                } else {
                    table_html += '
';
                    table_html += '
';
                    table_html += '
';
                    table_html += 'VXID	';
                    table_html += 'Tag	';
                    table_html += 'Type	';
                    table_html += 'Reason	';
                    table_html += 'Value	';
                    table_html += '
';
                    table_html += '
';
                    table_html += '
';
                    table_html += '
No log entries found
';
                    table_html += '
';
                    table_html += '
';
                }

                $('#server-logs .panel-body').html(table_html);

                getServerLogs();
            }
        });
    }

    function getServerLogs() {
        var limit = $('#logapi-limit').val();
        var display = parseInt($('#logapi-display').val());
        var tag   = $('#logapi-tag').val();
        var regex = $('#logapi-regex').val().replace(/\//g, '\\x2f');
        var url   = '/log/' + limit;

        if (tag) {
            url += '/' + tag;
        }

        if (tag && regex) {
            url += '/' + regex;
        }

        if (!enabled) {
            return false;
        }

        app.multiGet(app.getEnabledServers(), url, function(responses) {
            $('#server-logs tbody').html('');

            responses.forEach(function(r) {
                if (typeof r.response === 'string' && r.response.match(/Error in opening shmlog/)) {
                    app.fatalError(r.response);
                    return false;
                }

                var logs = r.response.log;

                if (tag && regex) {
                    $('#server-logs .panel-heading span').html('Logs (varnishlog -k ' + limit + ' -i ' + tag + ' -I ' + regex + ')');
                } else if (tag) {
                    $('#server-logs .panel-heading span').html('Logs (varnishlog -k ' + limit + ' -i ' + tag + ')');
                } else {
                    $('#server-logs .panel-heading span').html('Logs (varnishlog -k ' + limit + ')');
                }

                for (var j = logs.length - 1; j >= Math.max(logs.length - display, 0); j--) {
                    logs[j].value = logs[j].value.replace(/</g, '<').replace(/>/g, '>');

                    if (varnish_api_version === '3.0') {
                        $('#server-logs tbody').append('
' + logs[j].fd + '	' + logs[j].tag + '	' + logs[j].type + '	' + logs[j].value + '
');
                    } else {
                        $('#server-logs tbody').append('
' + logs[j].vxid + '	' + logs[j].tag + '	' + logs[j].type + '	' + logs[j].reason + '	' + logs[j].value + '
');
                    }
                }
            });

            if ($('#server-logs tbody tr').length === 0) {
                if (varnish_api_version === '3.0') {
                    $('#server-logs tbody').append('
No log entries found
');
                } else {
                    $('#server-logs tbody').append('
No log entries found
');
                }
            }
        }, 'json');
    }
})(window.app);
<|endoftext|>


<commit_before>// InstaClick is so broken that I had to do this :(

// Loader is the one for the current in-progress request Promises
const loader = {};

// Then they go to the cache. This only stores serializable data.
const cache = lru();

// Cache for 100 seconds (or browser refresh)
cache.expire = 10000;

cache.notify = true;
cache.onchange = (event, serializedCache) => {
  if (event !== 'remove' && event !== 'set') return;
  setLinks();
};

// These are already executed and should not be executed again
const loaded = {};

const setLinks = () => {
  const links = Object.keys(cache.cache);
  document.querySelectorAll('a').forEach(link => {
    // console.log(links, link.href);
    if (links.includes(link.href)) {
      if (link.getAttribute('data-cached')) return;
      return link.setAttribute('data-cached', true);
    } else {
      if (!link.getAttribute('data-cached')) return;
      link.removeAttribute('data-cached');
    }
  });
};

const bar = {
  min: 300,
  init: 0,
  show: () => {
    bar.init = new Date();
    document.querySelector('html').classList.add('loading');
  },
  hide: () => {
    const timeout = Math.max(bar.min, bar.min - (new Date() - bar.init));
    setTimeout(() => {
      document.querySelector('html').classList.remove('loading');
    }, timeout);
  }
};

// Inject the old, unevaluated script in the new document
const inject = (scr, body) => new Promise((resolve, reject) => {
  // console.log('Loaded scripts:', loaded[scr.src], scr.src, Object.keys(loaded));
  if (loaded[scr.src]) {
    // console.log(`Already loaded: ${scr.src}`);
    return resolve();
  }

  const script = document.createElement("script");

  // https://www.danielcrabtree.com/blog/25/gotchas-with-dynamically-adding-script-tags-to-html
  if (scr.src) {
    script.addEventListener('load', resolve);
    script.addEventListener('error', reject);
    // Cannot wrap this unfortunately ¯\_(ツ)_/¯
    script.src = scr.src;
    return body.appendChild(script);
  }

  // Script just has a string
  // Wrap it in a new context to avoid globals being re-assigned
  const wrapped = `(() => {${scr.innerHTML}})();`;
  script.appendChild(document.createTextNode(wrapped));
  body.appendChild(script);
  resolve();
});

// Fetch either a URL or an <a> element corresponding HTML
const preload = (ref) => {
  const href = ref.href || ref;
  // Ignore the totally external links since they cannot be loaded anyway
  // if (link.host !== location.host) return;

  // Already cached
  if (loader[href]) return loader[href];
  if (cache.get(href)) return Promise.resolve(cache.get(href));

  // Add to cache
  // console.log('Preloading:', href);
  loader[href] = fetch(href).then(res => res.text()).then(html => {
    cache.set(href, { href, html });
    setLinks();
    delete loader[href];
    return cache.get(href);
  });

  setLinks();
  return loader[href];
};

// Load either a URL or a <a> element referenced HTML into the body
const load = async ref => {
  const href = ref.href || ref;

  // TODO: make sure that the url is not the current one with a fragment

  // If anything fails, manually load the new page
  try {

    // Loading indicator
    bar.show();

    // Already loaded! Yay!
    if (cache.get(href)) return await replaceContent(cache.get(href));

    // The URL is loading; load the replaceContent later on
    if (loader[href]) return await loader[href].then(replaceContent);

    // Manually trigger a link that was not preloaded (preload it then load it)
    await preload(href).then(replaceContent);
  } catch (error) {
    location.href = href;
  }
};

// Set the new content to whatever it is passed
const replaceContent = ({ href, html }) => {
  // URL & History
  // This has to be before the DOM manipulations, otherwise the browser might
  //   think that the data is the old one
  history.pushState({ href }, "", href);

  // Generate a "virtual dom" (no, not your React virtual dom)
  const dom = document.createElement("html");
  dom.innerHTML = html;

  // Body replacement
  const body = document.querySelector('body')
  body.innerHTML = '';
  [...dom.querySelector('body').children].forEach(node => {
    body.appendChild(node);
  });

  document.querySelector('title').innerText = dom.querySelector('title').innerText;
  // const head = document.querySelector('head');
  // head.innerHTML = '';
  // [...dom.querySelector('head').children].forEach(node => {
  //   head.appendChild(node);
  // });

  // Load scripts
  const scripts = [...document.querySelectorAll('script')];

  // Recursive iteration over the scripts when they have finished loading
  function flipScript ([current, ...scripts]) {
    if (current) return inject(current, body).then(() => flipScript(scripts));
    return Promise.resolve();
  }

  flipScript(scripts).then(() => {
    // Reattach the links
    attach();

    // Set the webpage link attr to reflect the current website
    setLinks();

    // Hide the loading bar
    bar.hide();
  });
};

const attach = (links = 'a') => {
  document.querySelectorAll('script[data-once]').forEach(scr => loaded[scr.src] = true);

  document.querySelectorAll(links).forEach(link => {
    // Ignore the totally external links since they cannot be loaded anyway
    if (link.host !== location.host) return;

    link.addEventListener('mouseover', e => preload(e.currentTarget));
    link.addEventListener('touchstart', e => preload(e.currentTarget));
    link.addEventListener('click', e => {
      e.preventDefault();
      return load(e.currentTarget);
    });
  });
};

const pwa = () => {
  document.querySelectorAll('a').forEach(preload);
};

window.onpopstate = function(e) {
  const href = e.currentTarget.location.href;
  load(href);
};

// without jQuery (doesn't work in older IEs)
document.addEventListener('DOMContentLoaded', function(){

  // This needs to be here so that any script loaded a posteriori is also added
  cache.set(window.location.href, {
    href: window.location.href,
    html: document.querySelector('html').outerHTML
  });

  attach();
}, false);
<commit_msg>Fixed preload initial<commit_after>// InstaClick is so broken that I had to do this :(

// Loader is the one for the current in-progress request Promises
const loader = {};

// Then they go to the cache. This only stores serializable data.
const cache = lru();

// Cache for 100 seconds (or browser refresh)
cache.expire = 10000;

cache.notify = true;
cache.onchange = (event, serializedCache) => {
  if (event !== 'remove' && event !== 'set') return;
  setLinks();
};

// These are already executed and should not be executed again
const loaded = {};

const setLinks = () => {
  const links = Object.keys(cache.cache);
  document.querySelectorAll('a').forEach(link => {
    // console.log(links, link.href);
    if (links.includes(link.href)) {
      if (link.getAttribute('data-cached')) return;
      return link.setAttribute('data-cached', true);
    } else {
      if (!link.getAttribute('data-cached')) return;
      link.removeAttribute('data-cached');
    }
  });
};

const bar = {
  min: 300,
  init: 0,
  show: () => {
    bar.init = new Date();
    document.querySelector('html').classList.add('loading');
  },
  hide: () => {
    const timeout = Math.max(bar.min, bar.min - (new Date() - bar.init));
    setTimeout(() => {
      document.querySelector('html').classList.remove('loading');
    }, timeout);
  }
};

// Inject the old, unevaluated script in the new document
const inject = (scr, body) => new Promise((resolve, reject) => {
  // console.log('Loaded scripts:', loaded[scr.src], scr.src, Object.keys(loaded));
  if (loaded[scr.src]) {
    // console.log(`Already loaded: ${scr.src}`);
    return resolve();
  }

  const script = document.createElement("script");

  // https://www.danielcrabtree.com/blog/25/gotchas-with-dynamically-adding-script-tags-to-html
  if (scr.src) {
    script.addEventListener('load', resolve);
    script.addEventListener('error', reject);
    // Cannot wrap this unfortunately ¯\_(ツ)_/¯
    script.src = scr.src;
    return body.appendChild(script);
  }

  // Script just has a string
  // Wrap it in a new context to avoid globals being re-assigned
  const wrapped = `(() => {${scr.innerHTML}})();`;
  script.appendChild(document.createTextNode(wrapped));
  body.appendChild(script);
  resolve();
});

// Fetch either a URL or an <a> element corresponding HTML
const preload = (ref) => {
  const href = ref.href || ref;
  // Ignore the totally external links since they cannot be loaded anyway
  // if (link.host !== location.host) return;

  // Already cached
  if (loader[href]) return loader[href];
  if (cache.get(href)) return Promise.resolve(cache.get(href));

  // Add to cache
  // console.log('Preloading:', href);
  loader[href] = fetch(href).then(res => res.text()).then(html => {
    cache.set(href, { href, html });
    setLinks();
    delete loader[href];
    return cache.get(href);
  });

  setLinks();
  return loader[href];
};

// Load either a URL or a <a> element referenced HTML into the body
const load = async ref => {
  const href = ref.href || ref;

  // TODO: make sure that the url is not the current one with a fragment

  // If anything fails, manually load the new page
  try {

    // Loading indicator
    bar.show();

    // Already loaded! Yay!
    if (cache.get(href)) return await replaceContent(cache.get(href));

    // The URL is loading; load the replaceContent later on
    if (loader[href]) return await loader[href].then(replaceContent);

    // Manually trigger a link that was not preloaded (preload it then load it)
    await preload(href).then(replaceContent);
  } catch (error) {
    location.href = href;
  }
};

const exists = (head, el) =>  !![...head.children].find(n => n.isEqualNode(el));

const diffHead = (head, updated) => {
  // updated.children.map(parse(head)).forEach(console.log);

  [...head.children].filter(node => !exists(updated, node)).forEach(node => {
    if (node.nodeName.toLowerCase() === 'title') return;
    head.removeChild(node);
  });

  [...updated.children].forEach(node => {
    if (exists(head, node)) return;

    // The title is a bit special; it should be ONCE and it replaces the html
    if (node.nodeName.toLowerCase() === 'title') {
      head.querySelector('title').innerText = node.innerText;
      return;
    }

    // Everything else can be multiple times and just appended in the end
    console.log(node, node.outerHTML);
    head.appendChild(node);
  });
};

// Set the new content to whatever it is passed
const replaceContent = ({ href, html }) => {
  // URL & History
  // This has to be before the DOM manipulations, otherwise the browser might
  //   think that the data is the old one
  history.pushState({ href }, "", href);

  // Generate a "virtual dom" (no, not your React virtual dom)
  const dom = document.createElement("html");
  dom.innerHTML = html;

  // Body replacement
  const body = document.querySelector('body')
  body.innerHTML = '';
  [...dom.querySelector('body').children].forEach(node => {
    body.appendChild(node);
  });

  // Head replacement
  diffHead(document.querySelector('head'), dom.querySelector('head'));

  // document.querySelector('title').innerText = dom.querySelector('title').innerText;
  // const head = document.querySelector('head');
  // head.innerHTML = '';
  // [...dom.querySelector('head').children].forEach(node => {
  //   head.appendChild(node);
  // });

  // Load scripts
  const scripts = [...document.querySelectorAll('script')];

  // Recursive iteration over the scripts when they have finished loading
  function flipScript ([current, ...scripts]) {
    if (!current) return Promise.resolve();
    return inject(current, body).then(() => flipScript(scripts));
  }

  flipScript(scripts).then(() => {
    // Reattach the links
    attach();

    // Set the webpage link attr to reflect the current website
    setLinks();

    // Hide the loading bar
    bar.hide();
  });
};

const attach = (links = 'a') => {
  document.querySelectorAll('script[data-once]').forEach(scr => loaded[scr.src] = true);

  document.querySelectorAll(links).forEach(link => {
    // Ignore the totally external links since they cannot be loaded anyway
    if (link.host !== location.host) return;

    link.addEventListener('mouseover', e => preload(e.currentTarget));
    link.addEventListener('touchstart', e => preload(e.currentTarget));
    link.addEventListener('click', e => {
      e.preventDefault();
      return load(e.currentTarget);
    });
  });
};

const pwa = () => {
  document.querySelectorAll('a').forEach(preload);
};

window.onpopstate = function(e) {
  const href = e.currentTarget.location.href;
  load(href);
};

// without jQuery (doesn't work in older IEs)
document.addEventListener('DOMContentLoaded', function(){

  // This needs to be here so that any script loaded a posteriori is also added
  cache.set(window.location.href, {
    href: window.location.href,
    html: document.querySelector('html').outerHTML
  });

  attach();
}, false);
<|endoftext|>


<commit_before>exports = {

    // Base game dimension
    width: 576,
    height: 1024,

    // Set maximum delta time value
    max_delta: 100,

    board: {
        columns: 10,
        rows: 15,

        radius: 15,
        angle: 30,
        isEven: false, // start layout
        baseWidth: 576,
        keepPosition: 9 // Keep min and max height of grid on this row
    },

    ball: {
        ballSpeed: 0.6,
        dropSpeed: 0.2,
    },

    cannon: {
        ballWindowYoffset: 27,
        shootDelay: 380,
        barrel: {
            autoStart: true,
            width: 70, //130,
            height: 118, //220,
            frameRate: 10,
            defaultAnimation: "idle",
            url: "resources/images/cannon/barrel",
        },
        base: {
            image: "resources/images/cannon/base.png",
            width: 70,
            height: 29
        }
    },

    ballPool: {
        initCount: 200,
        initOpts: {
            width: 25,
            height: 25
        },
        types: ['blue', 'green', 'red', 'purpure', 'yellow']
    },

    balls: {
        "blue": {
            image: 'resources/images/balls/ball_blue2.png',
            score: 10
        },
        "green": {
            image: 'resources/images/balls/ball_green.png',
            score: 15
        },
        "red": {
            image: 'resources/images/balls/ball_red.png',
            score: 20
        },
        "purpure": {
            image: 'resources/images/balls/ball_purpure.png',
            score: 25
        },
        "yellow": {
            image: 'resources/images/balls/ball_yellow.png',
            score: 30
        },
    },

    // Background image
    background: {
        image: 'resources/images/ui/bg1_center.png',
        width: 768,
        height: 1024
    },

    head: {
        image: 'resources/images/ui/bg1_header.png',
        layout: 'box',
        horizontalAlign: 'center',
        verticalAlign: 'top',
        height: 128,
    },

    bg_left: {
        image: 'resources/images/ui/bg1_left_corner.png',
        width: 260,
        height: 208
    },

    bg_right: {
        image: 'resources/images/ui/bg1_right_corner.png',
        width: 260,
        height: 212
    },

    levels: [
		'resources/levels/1.json'
	]

};
<commit_msg>debug<commit_after>exports = {

    // Base game dimension
    width: 576,
    height: 1024,

    // Set maximum delta time value
    max_delta: 100,

    board: {
        columns: 10,
        rows: 15,

        radius: 15,
        angle: 30,
        isEven: false, // start layout
        baseWidth: 576,
        keepPosition: 9 // Keep min and max height of grid on this row
    },

    ball: {
        ballSpeed: 1.6,
        dropSpeed: 0.2,
    },

    cannon: {
        ballWindowYoffset: 27,
        shootDelay: 380,
        barrel: {
            autoStart: true,
            width: 70, //130,
            height: 118, //220,
            frameRate: 10,
            defaultAnimation: "idle",
            url: "resources/images/cannon/barrel",
        },
        base: {
            image: "resources/images/cannon/base.png",
            width: 70,
            height: 29
        }
    },

    ballPool: {
        initCount: 200,
        initOpts: {
            width: 25,
            height: 25
        },
        types: ['blue', 'green', 'red', 'purpure', 'yellow']
    },

    balls: {
        "blue": {
            image: 'resources/images/balls/ball_blue2.png',
            score: 10
        },
        "green": {
            image: 'resources/images/balls/ball_green.png',
            score: 15
        },
        "red": {
            image: 'resources/images/balls/ball_red.png',
            score: 20
        },
        "purpure": {
            image: 'resources/images/balls/ball_purpure.png',
            score: 25
        },
        "yellow": {
            image: 'resources/images/balls/ball_yellow.png',
            score: 30
        },
    },

    // Background image
    background: {
        image: 'resources/images/ui/bg1_center.png',
        width: 768,
        height: 1024
    },

    head: {
        image: 'resources/images/ui/bg1_header.png',
        layout: 'box',
        horizontalAlign: 'center',
        verticalAlign: 'top',
        height: 128,
    },

    bg_left: {
        image: 'resources/images/ui/bg1_left_corner.png',
        width: 260,
        height: 208
    },

    bg_right: {
        image: 'resources/images/ui/bg1_right_corner.png',
        width: 260,
        height: 212
    },

    levels: [
		'resources/levels/1.json'
	]

};
<|endoftext|>