<commit_before>//
//  File: skin.js
//  Project: R7.Epsilon
//
//  Author: Roman M. Yagodin <roman.yagodin@gmail.com>
//
//  Copyright (c) 2015-2019 Roman M. Yagodin, R7.Labs
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Affero General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Affero General Public License for more details.
//
//  You should have received a copy of the GNU Affero General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.

import A11y from "./a11y";

window.skinGoogleTranslatePage = function (fromLang) {
    window.open ("http://translate.google.com/translate?hl=en&sl=" + fromLang + "&u=" + encodeURI (document.location));
};

window.skinOpenFeedback = function (button, $, feedbackModuleId) {
    const selection = encodeURIComponent (rangy.getSelection ().toString ().replace (/(\n|\r)/gm," ").replace (/\s+/g, " ").replace (/\"/g, "").trim ().substring (0,100));
    const baseFeedbackUrl = $(button).data ("feedback-url");
    const feedbackParams = "returntabid=" + epsilon.queryParams ["TabId"] + "&feedbackmid=" + feedbackModuleId;

    const feedbackSelection = ((!!selection)? "&feedbackselection=" + selection : "");

    if (epsilon.enablePopups && window.skinA11y.getPopupsDisabled () === false && $(button).data ("feedback-open-in-popup") === true) {
        const popupFeedbackUrl = baseFeedbackUrl + "/mid/" + feedbackModuleId + "?" + feedbackParams + "&popup=true" + feedbackSelection;
        dnnModal.show (popupFeedbackUrl, false, 550, 950, false, "");
    }
    else {
        const rawFeedbackUrl = baseFeedbackUrl + "?" + feedbackParams + "&" + feedbackSelection;
        window.open (rawFeedbackUrl, "_blank");
    }
};

(function ($, window, document) {

    function initBreadcrumb () {
        if (epsilon.breadCrumbsRemoveLastLink) {
            // assume new style breadcrumbs with schema.org markup (DNN 8+)
            var schemaOrg = true;
            var breadcrumb = $(".breadcrumb > span > span").first ();

            if (breadcrumb.length === 0) {
                // it looks like an old style breadcrumbs
                schemaOrg = false;
                breadcrumb = $(".breadcrumb > span").first ();
            }

            // remove last link (to the current page)
            if (breadcrumb.length > 0) {
                if (schemaOrg) {
                    breadcrumb.find ("a").last ().parent ().remove ();
                } else {
                    breadcrumb.find ("a").last ().remove ();
                }
            }
        }
    }

    function initUpButton (offset, duration) {
        $(window).scroll(function() {
            if ($(this).scrollTop() > offset) {
                $('.skin-float-button-up').fadeIn(duration);
            } else {
                $('.skin-float-button-up').fadeOut(duration);
            }
        });

        $('.skin-float-button-up').click(function(event) {
            event.preventDefault();
            $(this).tooltip ('hide');
            $('html, body').animate({scrollTop: 0}, duration);
            return false;
        });
    }

    function emptyLayoutRows () {
        $('.row').each (function () {
            if ($(this).children ().length ===
                $(this).children ('.DNNEmptyPane').not ('.dnnSortable').length) {
                $(this).addClass ('hidden');
            }
        });
    }

    function initLanguage () {
        $(".skin-languages .language-object a").each (function () {
            var lang = $(this).parent (".Language").attr ("title");
            var langCode = lang.substr (0, 2).toUpperCase ();
            $(this).addClass ("dropdown-item").html ("" + langCode + " " + lang);
        });
    }

    function initBootstrapTooltips () {
        if (typeof ($.fn.tooltip) !== "undefined") {
            $("[data-toggle='tooltip']").tooltip ();
        }
    }

    function initBootstrapPopovers () {
        if (typeof ($.fn.popover) !== "undefined") {
            $("[data-toggle='popover']").popover ();
        }
    }

    function alterLogin () {
        $(".skin-login li.userDisplayName > a").addClass ("dropdown-item");
        $(".skin-login div.loginGroup > a").addClass ("dropdown-item");
        $(".skin-login li.userMessages > a").addClass ("dropdown-item");
        $(".skin-login li.userNotifications > a").addClass ("dropdown-item");
        $(".skin-login li.userProfileImg > a").addClass ("dropdown-item");

        const loginGroup = $(".skin-login .loginGroup").first ().detach ();
        const divider = "

";

        const profileImg = $(".skin-login li.userProfileImg > a > img");
        if (profileImg.length > 0) {
            profileImg.attr ("src", profileImg.attr ("src").replace ("h=32&w=32", "h=64&w=64"));

            const profileLinkBlock = $(".skin-login li.userDisplayName").first ().detach ();
            const profileImgBlock = $(".skin-login li.userProfileImg").first ().detach ();

            $(".skin-login ul.buttonGroup")
                .prepend (divider)
                .prepend (profileLinkBlock)
                .prepend (profileImgBlock)
                .append (divider)
                .append (loginGroup);
        }
        else {
            $(".skin-login ul.buttonGroup")
                .append (divider)
                .append (loginGroup);
        }
    }

    $(function () {
        initBootstrapTooltips ();
        initBootstrapPopovers ();

        if (! epsilon.inPopup) {
            emptyLayoutRows ();
            initBreadcrumb ();
            initUpButton (320, 500);
            initLanguage ();
            alterLogin ();
            window.skinA11y = new A11y ().init ();
        }
    });

}) (jQuery, window, document);
<commit_msg>GH-111 Add icons for login and logout<commit_after>//
//  File: skin.js
//  Project: R7.Epsilon
//
//  Author: Roman M. Yagodin <roman.yagodin@gmail.com>
//
//  Copyright (c) 2015-2019 Roman M. Yagodin, R7.Labs
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Affero General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Affero General Public License for more details.
//
//  You should have received a copy of the GNU Affero General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.

import A11y from "./a11y";

window.skinGoogleTranslatePage = function (fromLang) {
    window.open ("http://translate.google.com/translate?hl=en&sl=" + fromLang + "&u=" + encodeURI (document.location));
};

window.skinOpenFeedback = function (button, $, feedbackModuleId) {
    const selection = encodeURIComponent (rangy.getSelection ().toString ().replace (/(\n|\r)/gm," ").replace (/\s+/g, " ").replace (/\"/g, "").trim ().substring (0,100));
    const baseFeedbackUrl = $(button).data ("feedback-url");
    const feedbackParams = "returntabid=" + epsilon.queryParams ["TabId"] + "&feedbackmid=" + feedbackModuleId;

    const feedbackSelection = ((!!selection)? "&feedbackselection=" + selection : "");

    if (epsilon.enablePopups && window.skinA11y.getPopupsDisabled () === false && $(button).data ("feedback-open-in-popup") === true) {
        const popupFeedbackUrl = baseFeedbackUrl + "/mid/" + feedbackModuleId + "?" + feedbackParams + "&popup=true" + feedbackSelection;
        dnnModal.show (popupFeedbackUrl, false, 550, 950, false, "");
    }
    else {
        const rawFeedbackUrl = baseFeedbackUrl + "?" + feedbackParams + "&" + feedbackSelection;
        window.open (rawFeedbackUrl, "_blank");
    }
};

(function ($, window, document) {

    function initBreadcrumb () {
        if (epsilon.breadCrumbsRemoveLastLink) {
            // assume new style breadcrumbs with schema.org markup (DNN 8+)
            var schemaOrg = true;
            var breadcrumb = $(".breadcrumb > span > span").first ();

            if (breadcrumb.length === 0) {
                // it looks like an old style breadcrumbs
                schemaOrg = false;
                breadcrumb = $(".breadcrumb > span").first ();
            }

            // remove last link (to the current page)
            if (breadcrumb.length > 0) {
                if (schemaOrg) {
                    breadcrumb.find ("a").last ().parent ().remove ();
                } else {
                    breadcrumb.find ("a").last ().remove ();
                }
            }
        }
    }

    function initUpButton (offset, duration) {
        $(window).scroll(function() {
            if ($(this).scrollTop() > offset) {
                $('.skin-float-button-up').fadeIn(duration);
            } else {
                $('.skin-float-button-up').fadeOut(duration);
            }
        });

        $('.skin-float-button-up').click(function(event) {
            event.preventDefault();
            $(this).tooltip ('hide');
            $('html, body').animate({scrollTop: 0}, duration);
            return false;
        });
    }

    function emptyLayoutRows () {
        $('.row').each (function () {
            if ($(this).children ().length ===
                $(this).children ('.DNNEmptyPane').not ('.dnnSortable').length) {
                $(this).addClass ('hidden');
            }
        });
    }

    function initLanguage () {
        $(".skin-languages .language-object a").each (function () {
            var lang = $(this).parent (".Language").attr ("title");
            var langCode = lang.substr (0, 2).toUpperCase ();
            $(this).addClass ("dropdown-item").html ("" + langCode + " " + lang);
        });
    }

    function initBootstrapTooltips () {
        if (typeof ($.fn.tooltip) !== "undefined") {
            $("[data-toggle='tooltip']").tooltip ();
        }
    }

    function initBootstrapPopovers () {
        if (typeof ($.fn.popover) !== "undefined") {
            $("[data-toggle='popover']").popover ();
        }
    }

    function alterLogin () {
        $(".skin-login li.userDisplayName > a").addClass ("dropdown-item");
        $(".skin-login div.loginGroup > a").addClass ("dropdown-item");
        $(".skin-login li.userMessages > a").addClass ("dropdown-item");
        $(".skin-login li.userNotifications > a").addClass ("dropdown-item");
        $(".skin-login li.userProfileImg > a").addClass ("dropdown-item");

        const loginGroup = $(".skin-login .loginGroup").first ().detach ();
        const divider = "

";

        const profileImg = $(".skin-login li.userProfileImg > a > img");
        if (profileImg.length > 0) {
            profileImg.attr ("src", profileImg.attr ("src").replace ("h=32&w=32", "h=64&w=64"));

            const profileLinkBlock = $(".skin-login li.userDisplayName").first ().detach ();
            const profileImgBlock = $(".skin-login li.userProfileImg").first ().detach ();

            $(".skin-login ul.buttonGroup")
                .prepend (divider)
                .prepend (profileLinkBlock)
                .prepend (profileImgBlock)
                .append (divider)
                .append (loginGroup.children ("a").prepend (" "));
        }
        else {
            $(".skin-login ul.buttonGroup")
                .append (divider)
                .append (loginGroup.children ("a").prepend (" "));
        }
    }

    $(function () {
        initBootstrapTooltips ();
        initBootstrapPopovers ();

        if (! epsilon.inPopup) {
            emptyLayoutRows ();
            initBreadcrumb ();
            initUpButton (320, 500);
            initLanguage ();
            alterLogin ();
            window.skinA11y = new A11y ().init ();
        }
    });

}) (jQuery, window, document);
<|endoftext|>


<commit_before>/*
 * Chrome-Last.fm-Scrobbler Slacker.com Connector
 *
 * Uses Jordan Perr's pandora connector as a template --- http://jperr.com --- jordan[at]jperr[dot]com
 *
 * Joe Crawford joe@artlung.com
 */

/********* Configuration: ***********/

// changes to the DOM in this container will trigger an update.
LFM_WATCHED_CONTAINER = "div#track-metadata";

function LFM_IS_A_SONG() {
	return !$("#player-track-name").hasClass("noClick");
}

// function that returns title of current song
function LFM_TRACK_TITLE() {
	return $("#player-track-name").text();
}

// function that returns artist of current song
function LFM_TRACK_ARTIST() {
	return $("#player-artist-name").text();
}

// function that returns duration of current song in seconds
// called at begining of song
function LFM_TRACK_DURATION() {
	durationArr = $('#total-length').text().split(":");
	return parseInt(durationArr[0], 10)*60 + parseInt(durationArr[1], 10);
}

/********* Connector: ***********/

var LFM_lastTrack = "";
var LFM_isWaiting = 0;

function LFM_updateNowPlaying(){
	// Acquire data from page
	title = LFM_TRACK_TITLE();
	artist = LFM_TRACK_ARTIST();
	duration = LFM_TRACK_DURATION();
	newTrack = title + " " + artist;
	isASong = LFM_IS_A_SONG();
	// Update scrobbler if necessary
	if (isASong && newTrack != " " && newTrack != LFM_lastTrack){
		if (duration === 0) {
			// Nasty workaround for delayed duration visiblity with skipped tracks.
			setTimeout(LFM_updateNowPlaying, 5000);
			return 0;
		}
		console.log("submitting a now playing request. artist: "+artist+", title: "+title+", duration: "+duration);
		LFM_lastTrack = newTrack;
		chrome.extension.sendRequest({type: 'validate', artist: artist, track: title}, function(response) {
			if (response !== false) {
				chrome.extension.sendRequest({type: 'nowPlaying', artist: artist, track: title, duration: duration});
			} else { // on failure send nowPlaying 'unknown song'
				chrome.extension.sendRequest({type: 'nowPlaying', duration: duration});
			}
		});
	}
	LFM_isWaiting = 0;
}

// Run at startup
$(function(){
	console.log("Slacker module starting up");

	$(LFM_WATCHED_CONTAINER).live('DOMSubtreeModified', function(e) {
		//console.log("Live watcher called");
		if ($(LFM_WATCHED_CONTAINER).length > 0) {
			if(LFM_isWaiting === 0){
				LFM_isWaiting = 1;
				setTimeout(LFM_updateNowPlaying, 10000);
			}
			return;
		}
	});

	$(window).unload(function() {
		chrome.extension.sendRequest({type: 'reset'});
		return true;
	});
});
<commit_msg>Adding album support for Slacker #150 #152<commit_after>/*
 * Chrome-Last.fm-Scrobbler Slacker.com Connector
 *
 * Uses Jordan Perr's pandora connector as a template --- http://jperr.com --- jordan[at]jperr[dot]com
 *
 * Joe Crawford joe@artlung.com
 */

/********* Configuration: ***********/

// changes to the DOM in this container will trigger an update.
LFM_WATCHED_CONTAINER = "div#track-metadata";

function LFM_IS_A_SONG() {
	return !$("#player-track-name").hasClass("noClick");
}

// function that returns title of current song
function LFM_TRACK_TITLE() {
	return $("#player-track-name").text();
}

// function that returns artist of current song
function LFM_TRACK_ARTIST() {
	return $("#player-artist-name").text();
}

// function that returns artist of current song
function LFM_TRACK_ALBUM() {
	return $("#player-album-name").text();
}

// function that returns duration of current song in seconds
// called at begining of song
function LFM_TRACK_DURATION() {
	durationArr = $('#total-length').text().split(":");
	return parseInt(durationArr[0], 10)*60 + parseInt(durationArr[1], 10);
}

/********* Connector: ***********/

var LFM_lastTrack = "";
var LFM_isWaiting = 0;

function LFM_updateNowPlaying(){
	// Acquire data from page
	title = LFM_TRACK_TITLE();
	artist = LFM_TRACK_ARTIST();
	duration = LFM_TRACK_DURATION();
	album = LFM_TRACK_ALBUM();
	newTrack = title + " " + artist;
	isASong = LFM_IS_A_SONG();
	// Update scrobbler if necessary
	if (isASong && newTrack != " " && newTrack != LFM_lastTrack){
		if (duration === 0) {
			// Nasty workaround for delayed duration visiblity with skipped tracks.
			setTimeout(LFM_updateNowPlaying, 5000);
			return 0;
		}
		console.log("submitting a now playing request. artist: "+artist+", title: "+title+", duration: "+duration + ", album: " + album);
		LFM_lastTrack = newTrack;
		chrome.extension.sendRequest({type: 'validate', artist: artist, track: title}, function(response) {
			if (response !== false) {
				chrome.extension.sendRequest({type: 'nowPlaying', artist: artist, track: title, duration: duration, album: album});
			} else { // on failure send nowPlaying 'unknown song'
				chrome.extension.sendRequest({type: 'nowPlaying', duration: duration});
			}
		});
	}
	LFM_isWaiting = 0;
}

// Run at startup
$(function(){
	console.log("Slacker module starting up");

	$(LFM_WATCHED_CONTAINER).live('DOMSubtreeModified', function(e) {
		//console.log("Live watcher called");
		if ($(LFM_WATCHED_CONTAINER).length > 0) {
			if(LFM_isWaiting === 0){
				LFM_isWaiting = 1;
				setTimeout(LFM_updateNowPlaying, 10000);
			}
			return;
		}
	});

	$(window).unload(function() {
		chrome.extension.sendRequest({type: 'reset'});
		return true;
	});
});
<|endoftext|>


<commit_before>angular.module('os.administrative.container.addedit', ['os.administrative.models'])
  .controller('ContainerAddEditCtrl', function(
    $scope, $state, $stateParams, $q, container, containerType,
    Site, Container, ContainerType, CollectionProtocol, PvManager, Util) {

    var allSpecimenTypes = undefined;
    var allowedCps = undefined;

    function init() {
      container.storageLocation = container.storageLocation || {};
      $scope.container = container;
      $scope.ctx = {createHierarchy: $stateParams.mode == 'createHierarchy'};

      /**
       * Some how the ui-select's multiple option is removing pre-selected items
       * when cp list is being loaded or not yet loaded...
       * Therefore we copy pre-selected cps and then use it when all CPs are loaded
       */
      allowedCps = angular.copy(container.allowedCollectionProtocols);

      $scope.cps = [];
      $scope.specimenTypes = [];
      loadPvs();

      $scope.specimenTypeSelectorOpts = {
        items: $scope.specimenTypes,
        selectedCategories: container.allowedSpecimenClasses,
        selectedCategoryItems: container.allowedSpecimenTypes,
        categoryAttr: 'parent',
        valueAttr: 'value',
        allowAll: undefined
      };

      if ($stateParams.parentContainerName && $stateParams.parentContainerId &&
          $stateParams.posOne && $stateParams.posTwo) {
        //
        // This happens when user adds container from container map
        //

        $scope.locationSelected = true;
        container.storageLocation = {
          name: $stateParams.parentContainerName,
          positionX: $stateParams.posOne,
          positionY: $stateParams.posTwo
        };
        restrictCpsAndSpecimenTypes();
      }

      loadContainerTypes();
      setContainerTypeProps(containerType);

      watchParentContainer();
    };

    function watchParentContainer() {
      $scope.$watch('container.storageLocation.name', function(newVal, oldVal) {
        if (newVal == oldVal) {
          return;
        }

        if (!newVal) {
          loadAllCpsAndSpecimenTypes();
        } else {
          restrictCpsAndSpecimenTypes();
        }
      });
    };

    function loadPvs() {
      $scope.positionLabelingSchemes = PvManager.getPvs('container-position-labeling-schemes');

      var op = !!$scope.container.id ? 'Update' : 'Create';
      $scope.sites = [];
      Site.listForContainers(op).then(function(sites) {
        $scope.sites = sites;
      });

      if ($scope.container.storageLocation.name) {
        restrictCpsAndSpecimenTypes();
      } else {
        loadAllCpsAndSpecimenTypes();
      }

    };

    function loadContainerTypes() {
      $scope.containerTypes = [];
      ContainerType.query().then(function(containerTypes) {
        $scope.containerTypes = containerTypes;
      });
    }

    function restrictCpsAndSpecimenTypes() {
      var parentName = $scope.container.storageLocation.name;
      Container.getByName(parentName).then(
        function(parentContainer) {
          restrictCps(parentContainer);
          restrictSpecimenTypes(parentContainer);
        }
      );
    };

    function loadAllCpsAndSpecimenTypes() {
      loadAllCps();
      loadAllSpecimenTypes();
    };

    function restrictCps(parentContainer) {
      var parentCps = parentContainer.calcAllowedCollectionProtocols;
      if (parentCps.length > 0) {
        $scope.cps = parentCps;
      } else {
        loadAllCps(parentContainer.siteName);
      }

      $scope.container.allowedCollectionProtocols = allowedCps;
    };

    function loadAllCps(siteName) {
      siteName = !siteName ? $scope.container.siteName : siteName;

      CollectionProtocol.query({repositoryName: siteName}).then(
        function(cps) {
          $scope.cps = cps.map(function(cp) { return cp.shortTitle; });

          // fix - pre-selected cps were getting cleared
          $scope.container.allowedCollectionProtocols = allowedCps;
        }
      );
    };

    function restrictSpecimenTypes(parentContainer) {
      if (allSpecimenTypes) {
        filterSpecimenTypes(parentContainer);
      } else {
        loadAllSpecimenTypes().then(
          function() {
            filterSpecimenTypes(parentContainer);
          }
        );
      }
    };

    function filterSpecimenTypes(parentContainer) {
      var allowedClasses = parentContainer.calcAllowedSpecimenClasses;
      var allowedTypes = parentContainer.calcAllowedSpecimenTypes;
      $scope.specimenTypeSelectorOpts.allowAll = allowedClasses;

      var filtered = allSpecimenTypes.filter(
        function(specimenType) {
          return allowedClasses.indexOf(specimenType.parent) >= 0 ||
                   allowedTypes.indexOf(specimenType.value) >= 0;
        }
      );
      Util.assign($scope.specimenTypes, filtered);
    };

    function loadAllSpecimenTypes() {
      if (allSpecimenTypes) {
        var d = $q.defer();
        d.resolve(allSpecimenTypes);
        return d.promise;
      }

      return PvManager.loadPvsByParent('specimen-class', undefined, true).then(
        function(specimenTypes) {
          allSpecimenTypes = specimenTypes;
          Util.assign($scope.specimenTypes, specimenTypes);
          return allSpecimenTypes;
        }
      );
    };

    function saveContainer() {
      var container = angular.copy($scope.container);
      container.$saveOrUpdate().then(
        function(result) {
          if (!$scope.locationSelected) {
            $state.go('container-detail.overview', {containerId: result.id});
          } else {
            $state.go('container-detail.locations', {containerId: $stateParams.parentContainerId});
          }
        }
      );
    };

    function createHierarchy() {
      Container.createHierarchy($scope.container).then(
        function(resp) {
          if (resp[0].storageLocation && resp[0].storageLocation.id) {
            //
            // hierarchy created under an existing container
            // go to that container detail
            //
            $state.go('container-detail.overview', {containerId: resp[0].storageLocation.id});
          } else {
            //
            // hierarchy created for top-level container. go to list view
            //
            $state.go('container-list');
          }
        }
      )
    };

    function setContainerTypeProps(containerType) {
      if (!containerType) {
        return;
      }

      $scope.container.typeId = containerType.id;
      $scope.container.typeName = containerType.name;
      $scope.container.noOfRows = containerType.noOfRows;
      $scope.container.noOfColumns = containerType.noOfColumns;
      $scope.container.rowLabelingScheme = containerType.rowLabelingScheme;
      $scope.container.columnLabelingScheme = containerType.columnLabelingScheme;
      $scope.container.temperature = containerType.temperature;
      $scope.container.storeSpecimensEnabled = containerType.storeSpecimenEnabled;
    };

    $scope.loadAllCps = loadAllCps;

    $scope.onSelectContainerType = setContainerTypeProps;

    $scope.save = function() {
      if ($scope.ctx.createHierarchy) {
        createHierarchy();
      } else {
        saveContainer();
      }
    }

    $scope.onCreateHierarchyClick = function(createHierarchy) {
      var attrsToDelete = createHierarchy ? ['name', 'barcode'] : ['numOfContainers'];
      attrsToDelete.forEach(function(attr) {
        delete $scope.container[attr];
      });
    }

    init();
  });
<commit_msg>OPSMN-2993 Unable to disassociate a container from its type<commit_after>angular.module('os.administrative.container.addedit', ['os.administrative.models'])
  .controller('ContainerAddEditCtrl', function(
    $scope, $state, $stateParams, $q, container, containerType,
    Site, Container, ContainerType, CollectionProtocol, PvManager, Util) {

    var allSpecimenTypes = undefined;
    var allowedCps = undefined;

    function init() {
      container.storageLocation = container.storageLocation || {};
      $scope.container = container;
      $scope.ctx = {createHierarchy: $stateParams.mode == 'createHierarchy'};

      /**
       * Some how the ui-select's multiple option is removing pre-selected items
       * when cp list is being loaded or not yet loaded...
       * Therefore we copy pre-selected cps and then use it when all CPs are loaded
       */
      allowedCps = angular.copy(container.allowedCollectionProtocols);

      $scope.cps = [];
      $scope.specimenTypes = [];
      loadPvs();

      $scope.specimenTypeSelectorOpts = {
        items: $scope.specimenTypes,
        selectedCategories: container.allowedSpecimenClasses,
        selectedCategoryItems: container.allowedSpecimenTypes,
        categoryAttr: 'parent',
        valueAttr: 'value',
        allowAll: undefined
      };

      if ($stateParams.parentContainerName && $stateParams.parentContainerId &&
          $stateParams.posOne && $stateParams.posTwo) {
        //
        // This happens when user adds container from container map
        //

        $scope.locationSelected = true;
        container.storageLocation = {
          name: $stateParams.parentContainerName,
          positionX: $stateParams.posOne,
          positionY: $stateParams.posTwo
        };
        restrictCpsAndSpecimenTypes();
      }

      loadContainerTypes();
      setContainerTypeProps(containerType);

      watchParentContainer();
    };

    function watchParentContainer() {
      $scope.$watch('container.storageLocation.name', function(newVal, oldVal) {
        if (newVal == oldVal) {
          return;
        }

        if (!newVal) {
          loadAllCpsAndSpecimenTypes();
        } else {
          restrictCpsAndSpecimenTypes();
        }
      });
    };

    function loadPvs() {
      $scope.positionLabelingSchemes = PvManager.getPvs('container-position-labeling-schemes');

      var op = !!$scope.container.id ? 'Update' : 'Create';
      $scope.sites = [];
      Site.listForContainers(op).then(function(sites) {
        $scope.sites = sites;
      });

      if ($scope.container.storageLocation.name) {
        restrictCpsAndSpecimenTypes();
      } else {
        loadAllCpsAndSpecimenTypes();
      }

    };

    function loadContainerTypes() {
      $scope.containerTypes = [];
      ContainerType.query().then(function(containerTypes) {
        $scope.containerTypes = containerTypes;
      });
    }

    function restrictCpsAndSpecimenTypes() {
      var parentName = $scope.container.storageLocation.name;
      Container.getByName(parentName).then(
        function(parentContainer) {
          restrictCps(parentContainer);
          restrictSpecimenTypes(parentContainer);
        }
      );
    };

    function loadAllCpsAndSpecimenTypes() {
      loadAllCps();
      loadAllSpecimenTypes();
    };

    function restrictCps(parentContainer) {
      var parentCps = parentContainer.calcAllowedCollectionProtocols;
      if (parentCps.length > 0) {
        $scope.cps = parentCps;
      } else {
        loadAllCps(parentContainer.siteName);
      }

      $scope.container.allowedCollectionProtocols = allowedCps;
    };

    function loadAllCps(siteName) {
      siteName = !siteName ? $scope.container.siteName : siteName;

      CollectionProtocol.query({repositoryName: siteName}).then(
        function(cps) {
          $scope.cps = cps.map(function(cp) { return cp.shortTitle; });

          // fix - pre-selected cps were getting cleared
          $scope.container.allowedCollectionProtocols = allowedCps;
        }
      );
    };

    function restrictSpecimenTypes(parentContainer) {
      if (allSpecimenTypes) {
        filterSpecimenTypes(parentContainer);
      } else {
        loadAllSpecimenTypes().then(
          function() {
            filterSpecimenTypes(parentContainer);
          }
        );
      }
    };

    function filterSpecimenTypes(parentContainer) {
      var allowedClasses = parentContainer.calcAllowedSpecimenClasses;
      var allowedTypes = parentContainer.calcAllowedSpecimenTypes;
      $scope.specimenTypeSelectorOpts.allowAll = allowedClasses;

      var filtered = allSpecimenTypes.filter(
        function(specimenType) {
          return allowedClasses.indexOf(specimenType.parent) >= 0 ||
                   allowedTypes.indexOf(specimenType.value) >= 0;
        }
      );
      Util.assign($scope.specimenTypes, filtered);
    };

    function loadAllSpecimenTypes() {
      if (allSpecimenTypes) {
        var d = $q.defer();
        d.resolve(allSpecimenTypes);
        return d.promise;
      }

      return PvManager.loadPvsByParent('specimen-class', undefined, true).then(
        function(specimenTypes) {
          allSpecimenTypes = specimenTypes;
          Util.assign($scope.specimenTypes, specimenTypes);
          return allSpecimenTypes;
        }
      );
    };

    function saveContainer() {
      var container = angular.copy($scope.container);
      container.$saveOrUpdate().then(
        function(result) {
          if (!$scope.locationSelected) {
            $state.go('container-detail.overview', {containerId: result.id});
          } else {
            $state.go('container-detail.locations', {containerId: $stateParams.parentContainerId});
          }
        }
      );
    };

    function createHierarchy() {
      Container.createHierarchy($scope.container).then(
        function(resp) {
          if (resp[0].storageLocation && resp[0].storageLocation.id) {
            //
            // hierarchy created under an existing container
            // go to that container detail
            //
            $state.go('container-detail.overview', {containerId: resp[0].storageLocation.id});
          } else {
            //
            // hierarchy created for top-level container. go to list view
            //
            $state.go('container-list');
          }
        }
      )
    };

    function setContainerTypeProps(containerType) {
      if (!containerType) {
        $scope.container.typeId = undefined;
        return;
      }

      $scope.container.typeId = containerType.id;
      $scope.container.typeName = containerType.name;
      $scope.container.noOfRows = containerType.noOfRows;
      $scope.container.noOfColumns = containerType.noOfColumns;
      $scope.container.rowLabelingScheme = containerType.rowLabelingScheme;
      $scope.container.columnLabelingScheme = containerType.columnLabelingScheme;
      $scope.container.temperature = containerType.temperature;
      $scope.container.storeSpecimensEnabled = containerType.storeSpecimenEnabled;
    };

    $scope.loadAllCps = loadAllCps;

    $scope.onSelectContainerType = setContainerTypeProps;

    $scope.save = function() {
      if ($scope.ctx.createHierarchy) {
        createHierarchy();
      } else {
        saveContainer();
      }
    }

    $scope.onCreateHierarchyClick = function(createHierarchy) {
      var attrsToDelete = createHierarchy ? ['name', 'barcode'] : ['numOfContainers'];
      attrsToDelete.forEach(function(attr) {
        delete $scope.container[attr];
      });
    }

    init();
  });
<|endoftext|>


<commit_before>import {Controller,Route} from "./Controller";
import models from "../db/models";

export default class Session extends Controller {
    @Route("Session:register")
    async register(msg, conn) {
        let user = await models.User.create({
            username: msg.username,
            slug: msg.username,
            language: msg.username,
            joined: Date.now(),
            gRole: 0
        });
        return user;
    }

    @Route("Session:login");
    async login(msg, conn) {
        let s = await models.User.findOne({where: {username: msg.username}});
        //TODO: password

        let s = await models.Auth.create({
            user_id: user.id,
            token: this.makeId(),
            created: Date.now(),
            last: Date.()
        });

        return {user: user, session: s};
    }

    @Route("Session:loginToken")
    async loginToken(msg, conn) {
        let s = await models.Auth.findOne({where: {token: msg.token}});
        let user = await models.Auth.findById(s.user_id);
        return {user: user, session: s};
    }

    @Route("Session:logout")
    async logout(msg, conn) {
        let s = await models.Auth.findOne({where: {token: msg.token}});
        await s.destroy();
        return true;
    }

    //http://stackoverflow.com/a/1349426
    makeid() {
        var text = "";
        var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

        for (var i = 0; i < 5; i++)
            text += possible.charAt(Math.floor(Math.random() * possible.length));

        return text;
    }

}<commit_msg>Another typo fix<commit_after>import {Controller,Route} from "./Controller";
import models from "../db/models";

export default class Session extends Controller {
    @Route("Session:register")
    async register(msg, conn) {
        let user = await models.User.create({
            username: msg.username,
            slug: msg.username,
            language: msg.username,
            joined: Date.now(),
            gRole: 0
        });
        return user;
    }

    @Route("Session:login");
    async login(msg, conn) {
        let s = await models.User.findOne({where: {username: msg.username}});
        //TODO: password

        let s = await models.Auth.create({
            user_id: user.id,
            token: this.makeId(),
            created: Date.now(),
            last: Date.now()
        });

        return {user: user, session: s};
    }

    @Route("Session:loginToken")
    async loginToken(msg, conn) {
        let s = await models.Auth.findOne({where: {token: msg.token}});
        let user = await models.Auth.findById(s.user_id);
        return {user: user, session: s};
    }

    @Route("Session:logout")
    async logout(msg, conn) {
        let s = await models.Auth.findOne({where: {token: msg.token}});
        await s.destroy();
        return true;
    }

    //http://stackoverflow.com/a/1349426
    makeid() {
        var text = "";
        var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

        for (var i = 0; i < 5; i++)
            text += possible.charAt(Math.floor(Math.random() * possible.length));

        return text;
    }

}<|endoftext|>


<commit_before>import _ from 'underscore';
import React from 'react';

import './svgExporter'; // create handlers for SVG and PNG download buttons

var Graphers = {};

export default function Grapher(Graph) {

    return class extends React.Component {
        constructor(props) {
            super(props);
            this.state = { collapsed: this.props.collapsed };
        }

        collapseId () {
            return Graph.collapseId(this.props);
        }

        render () {
            var cssClasses = Graph.className() + ' grapher';
            return (

                            { this.state.collapsed ?
                                this.plusIcon() : this.minusIcon() }

                            {Graph.name()}

                        { !this.state.collapsed && this.graphLinksJSX() }

                    { this.svgContainerJSX() }

            );
        }

        minusIcon () {
            return (

            );
        }

        plusIcon () {
            return (

            );
        }

        graphLinksJSX () {
            return (

                         SVG

                    |

                         PNG

            );
        }

        svgContainerJSX () {
            var cssClasses = Graph.className() + ' svg-container collapse';
            if (!this.state.collapsed) cssClasses += ' in';
            return (

            );
        }

        componentDidMount () {
            Graphers[this.collapseId()] = this;

            // Draw visualisation for the first time. Visualisations are
            // redrawn when browser window is resized.
            this.draw();
        }

        componentDidUpdate () {
            // Re-draw visualisation when the component change state.
            this.draw();
        }

        svgContainer () {
            return $(React.findDOMNode(this.refs.svgContainer));
        }

        draw () {
            // Clean slate.
            this.svgContainer().empty();
            this.graph = null;

            // Draw if uncollapsed.
            if (this.state.collapsed) { return }
            this.graph = new Graph(this.svgContainer(), this.props);
            this.svgContainer().find('svg').attr('data-name', Graph.dataName(this.props));
        }
    };
}

// Redraw if window resized.
$(window).resize(_.debounce(function () {
    _.each(Graphers, (grapher) => {
        grapher.draw();
    });
}, 125));

// Swap-icon and toggle .graph-links on collapse.
$('body').on('hidden.bs.collapse', ".collapse", function () {
    var component = Graphers[$(this).attr('id')];
    if (component) {
        component.setState({ collapsed: true });
    }
});
$('body').on('shown.bs.collapse', ".collapse", function () {
    var component = Graphers[$(this).attr('id')];
    if (component) {
        component.setState({ collapsed: false });
    }
});
<commit_msg>Start documenting Grapher and fix a few eslint offences<commit_after>import _ from 'underscore';
import React from 'react';

import './svgExporter'; // create handlers for SVG and PNG download buttons

// Each instance of Grapher is added to this object once the component has been
// mounted. This is so that grapher can be iterated over and redrawn on window
// resize event.
var Graphers = {};

// Grapher is a function that takes a Graph class and returns a React component.
// This React component provides HTML boilerplate to add heading, to make the
// graphs collapsible, to redraw graphs when window is resized, and SVG and PNG
// export buttons and functionality.
export default function Grapher(Graph) {

    return class extends React.Component {
        constructor(props) {
            super(props);
            this.state = { collapsed: this.props.collapsed };
        }

        collapseId () {
            return Graph.collapseId(this.props);
        }

        render () {
            var cssClasses = Graph.className() + ' grapher';
            return (

                            { this.state.collapsed ?
                                this.plusIcon() : this.minusIcon() }

                            {Graph.name()}

                        { !this.state.collapsed && this.graphLinksJSX() }

                    { this.svgContainerJSX() }

            );
        }

        minusIcon () {
            return (

            );
        }

        plusIcon () {
            return (

            );
        }

        graphLinksJSX () {
            return (

                         SVG

                    |

                         PNG

            );
        }

        svgContainerJSX () {
            var cssClasses = Graph.className() + ' svg-container collapse';
            if (!this.state.collapsed) cssClasses += ' in';
            return (

            );
        }

        componentDidMount () {
            Graphers[this.collapseId()] = this;

            // Draw visualisation for the first time. Visualisations are
            // redrawn when browser window is resized.
            this.draw();
        }

        componentDidUpdate () {
            // Re-draw visualisation when the component change state.
            this.draw();
        }

        svgContainer () {
            return $(React.findDOMNode(this.refs.svgContainer));
        }

        draw () {
            // Clean slate.
            this.svgContainer().empty();
            this.graph = null;

            // Draw if uncollapsed.
            if (this.state.collapsed) { return; }
            this.graph = new Graph(this.svgContainer(), this.props);
            this.svgContainer().find('svg').attr('data-name', Graph.dataName(this.props));
        }
    };
}

// Redraw if window resized.
$(window).resize(_.debounce(function () {
    _.each(Graphers, (grapher) => {
        grapher.draw();
    });
}, 125));

// Swap-icon and toggle .graph-links on collapse.
$('body').on('hidden.bs.collapse', '.collapse', function () {
    var component = Graphers[$(this).attr('id')];
    if (component) {
        component.setState({ collapsed: true });
    }
});
$('body').on('shown.bs.collapse', '.collapse', function () {
    var component = Graphers[$(this).attr('id')];
    if (component) {
        component.setState({ collapsed: false });
    }
});
<|endoftext|>


<commit_before>
const RG = require('./rg');
const FromJSON = require('./game.fromjson');
const debug = require('debug')('bitn:ChunkManager');

export const LOAD = Object.freeze(
    {EMPTY: 'EMPTY', LOADED: 'LOADED', JSON: 'JSON', ON_DISK: 'ON_DISK',
        LOADED2JSON: 'LOADED2JSON'});
export const CREATE = Object.freeze(
    {EMPTY: 'EMPTY', CREATED: 'CREATED', POPULATED: 'POPULATED'});

export function printTileConnections(msg, tileToConnect, id = -1) {
    console.log(msg);
    if (typeof tileToConnect.getLevel === 'function') {
        if (tileToConnect.getLevel().getID() === id || id === -1) {
            const conns0 = tileToConnect.getLevel().getConnections();
            conns0.forEach(c => {
                const targetLevel = c.getTargetLevel();
                if (Number.isInteger(targetLevel)) {
                    console.log(`\tTarget ID ${targetLevel} found`);
                }
                else {
                    console.log(`\tMap.Level ${targetLevel.getID()} found`);
                }
            });
        }
    }
    else {
        console.log('Skipping printTileConnections due to json input');

    }
}

/* Chunk manager handles loading/saving of world chunks (World.AreaTiles)
 * from/to memory/disk. It also keeps track of the state of each chunk.
 * */
export default class ChunkManager {

    constructor(game, area) {
        const [sizeX, sizeY] = [area.getSizeX(), area.getSizeY()];
        this.sizeX = sizeX;
        this.sizeY = sizeY;
        this.area = area;
        this.game = game;
        this.state = [];

        for (let x = 0; x < sizeX; x++) {
            this.state[x] = [];
            for (let y = 0; y < sizeY; y++) {
                if (area.isLoaded(x, y)) {
                    const chunkState = {loadState: LOAD.LOADED};
                    this.state[x].push(chunkState);
                }
                else {
                    const chunkState = {loadState: LOAD.JSON};
                    this.state[x].push(chunkState);
                }
            }
        }

        // From how far the levels are loaded
        this.loadDistX = 1;
        this.loadDistY = 1;

        this.onDiskDistX = sizeX;
        this.onDiskDistY = sizeY;
    }

    setPlayerTile(px, py, oldX, oldY) {
        const moveDir = this.getMoveDir(px, py, oldX, oldY);
        // printTileConnections('setPlayerTile XXX',
        // this.area.getTiles()[1][0], 4);
        const loadedTiles = [];
        for (let x = 0; x < this.sizeX; x++) {
            for (let y = 0; y < this.sizeY; y++) {
                if (this.inLoadRange(px, py, x, y)) {
                    if (!this.isLoaded(x, y)) {
                        // this.loadTile(px, py, x, y, moveDir);
                        loadedTiles.push([x, y]);
                    }
                }
                else if (this.isLoaded(x, y)) {
                    this.unloadTile(px, py, x, y, moveDir);
                }
            }
        }
        if (loadedTiles.length > 0) {
            this.loadTiles(px, py, loadedTiles, moveDir);
        }
    }

    isLoaded(x, y) {
        return this.state[x][y].loadState === LOAD.LOADED;
    }

    /* Returns true if given tile (tx,ty) is within load range from
     * player px,py .*/
    inLoadRange(px, py, tx, ty) {
        for (let x = px - this.loadDistX; x <= px + this.loadDistX; x++) {
            for (let y = py - this.loadDistY; y <= py + this.loadDistY; y++) {
                if (tx === x && ty === y) {return true;}
            }
        }
        return false;
    }

    loadAllTiles() {
        this.setLoadStateAll(LOAD.LOADED);
    }

    /* Returns number of tiles in given load state. */
    getNumInState(loadState) {
        let num = 0;
        for (let x = 0; x < this.sizeX; x++) {
            for (let y = 0; y < this.sizeY; y++) {
                if (this.state[x][y].loadState === loadState) {
                    ++num;
                }
            }
        }
        return num;
    }

    /* Loads the serialized/on-disk tile. */
    loadTiles(px, py, loadedTilesXY) {
        const areaTiles = this.area.getTiles();
        debug('loadTiles: ' + JSON.stringify(loadedTilesXY));
        const loadedAreaTiles = loadedTilesXY.map(
            xy => areaTiles[xy[0]][xy[1]]
        );

        this.createTiles(loadedAreaTiles);

        loadedTilesXY.forEach(xy => {
            debug(`ChunkManager load now tile ${xy}`);
            const [tx, ty] = xy;
            this.state[tx][ty].loadState = LOAD.LOADED;
            this.area.setLoaded(tx, ty);
        });
    }

    /* Unloads the tile from memory. */
    unloadTile(px, py, tx, ty, moveDir) {
        debug(`Unloading tile ${tx},${ty}`);
        const areaTiles = this.area.getTiles();
        this.state[tx][ty].loadState = LOAD.LOADED2JSON;
        this.area.setUnloaded(tx, ty);

        const levels = areaTiles[tx][ty].getLevels();
        this.game.removeLevels(levels);

        areaTiles[tx][ty] = areaTiles[tx][ty].toJSON();

        // Need to replace connections on adjacent tiles
        if (moveDir === 'WEST') {
            const newX = tx - 1;
            debug(`Removing connections from tile ${tx - 1},${ty}`);
            if (newX < this.area.getSizeX()) {
                this.removeConnections('EAST', areaTiles[tx - 1][ty]);
            }
        }
        else if (moveDir === 'EAST') {
            const newX = tx + 1;
            debug(`Removing connections from tile ${tx + 1},${ty}`);
            if (newX < this.area.getSizeX()) {
                this.removeConnections('WEST', areaTiles[tx + 1][ty]);
            }
        }
        else if (moveDir === 'NORTH') {
            const newY = ty - 1;
            debug(`Removing connections from tile ${tx},${ty - 1}`);
            if (newY >= 0) {
                this.removeConnections('SOUTH', areaTiles[tx][ty - 1]);
            }
        }
        else if (moveDir === 'SOUTH') {
            const newY = ty + 1;
            debug(`Removing connections from tile ${tx},${ty + 1}`);
            if (newY < this.area.getSizeY) {
                this.removeConnections('NORTH', areaTiles[tx][ty + 1]);
            }
        }
        this.state[tx][ty].loadState = LOAD.JSON;
    }

    getLoadState(x, y) {
        return this.state[x][y].loadState;
    }

    serializeArea() {
        this.setLoadStateAll(LOAD.JSON);
        const levels = this.area.getLevels();
        this.game.removeLevels(levels);
        this.levels = levels.map(l => l.toJSON());
        const tiles = this.area.getTiles();
        for (let x = 0; x < this.sizeX; x++) {
            for (let y = 0; y < this.sizeY; y++) {
                tiles[x][y] = tiles[x][y].toJSON();
            }
        }
    }

    toJSON() {
        return {
            state: this.state
        };
    }

    setLoadStateAll(state) {
        this.state.forEach((col, x) => {
            col.forEach((tile, y) => {
                this.state[x][y].loadState = state;
            });
        });
    }

    createTiles(tilesJSON) {
        const fromJSON = new FromJSON();
        fromJSON.setChunkMode(true);
        fromJSON.createTiles(this.game, tilesJSON);
    }

    addConnections(dir, tileToConnect, newTile) {
        // printTileConnections('XXX', tileToConnect);
        const oppositeDir = this.getOpposite(dir);
        const addedConns = this.getReplacedConnections(dir, tileToConnect);
        const newConns = this.getReplacedConnections(oppositeDir, newTile);
        const fromJSON = new FromJSON();
        const conns = addedConns.concat(newConns);
        const levels = [tileToConnect.getLevel(), newTile.getLevel()];
        fromJSON.connectTileLevels(levels, conns);
    }

    removeConnections(dir, tile) {
        const replacedConns = this.getReplacedConnections(dir, tile);
        replacedConns.forEach(conn => {
            const targetConn = conn.getTargetStairs();
            const connObj = {
                targetLevel: conn.getTargetLevel().getID(),
                targetStairs: {x: targetConn.getX(), y: targetConn.getY()}
            };

            conn.setConnObj(connObj);
        });
    }

    getReplacedConnections(dir, tile) {
        const level = tile.getLevel();
        const conns = level.getConnections();
        if (conns.length === 0) {
            RG.err('ChunkManager', 'getReplacedConnections',
                'No connections found.');
        }
        let replacedConns = [];
        if (dir === 'SOUTH') {
            replacedConns = conns.filter(conn => conn.getY() === tile.rows - 1);
        }
        else if (dir === 'NORTH') {
            replacedConns = conns.filter(conn => conn.getY() === 0);
        }
        else if (dir === 'EAST') {
            replacedConns = conns.filter(conn => conn.getX() === tile.cols - 1);
        }
        else if (dir === 'WEST') {
            replacedConns = conns.filter(conn => conn.getX() === 0);
        }
        return replacedConns;
    }

    getOpposite(dir) {
        switch (dir) {
            case 'NORTH': return 'SOUTH';
            case 'SOUTH': return 'NORTH';
            case 'EAST': return 'WEST';
            case 'WEST': return 'EAST';
            default: RG.err('ChunkManager', 'getOpposite',
                `Illegal dir ${dir} given.`);
        }
        return '';
    }

    /* Returns the player movement direction. */
    getMoveDir(px, py, oldX, oldY) {
        let [dx, dy] = [0, 0];
        let moveDir = '';
        if (!RG.isNullOrUndef([oldX, oldY])) {
            dx = px - oldX;
            dy = py - oldY;
            if (dx !== 0 && dy !== 0) {
                RG.err('ChunkManager', 'setPlayerTile',
                    'Diagonal move not supported');
            }
            if (dx > 0) {moveDir = 'EAST';}
            else if (dx < 0) {moveDir = 'WEST';}
            if (dy > 0) {moveDir = 'SOUTH';}
            else if (dy < 0) {moveDir = 'NORTH';}
        }
        return moveDir;
    }

    /* Prints the state in concise format. */
    debugPrint() {
        let result = '';
        for (let y = 0; y < this.sizeY; y++) {
            for (let x = 0; x < this.sizeX; x++) {
                result += ' ' + this.stateToChar(this.state[x][y]);
            }
            result += ` - ${y} \n`;
        }
        result += '\n\tNum loaded: ' + this.getNumInState(LOAD.LOADED);
        result += '\n\tNum serialized: ' + this.getNumInState(LOAD.JSON);
        result += '\n\tNum on disk: ' + this.getNumInState(LOAD.ON_DISK);
        return result;
    }

    /* Converts current state into a single char. */
    stateToChar(state) {
        switch (state.loadState) {
            case LOAD.LOADED: return 'L';
            case LOAD.JSON: return 'J';
            case LOAD.ON_DISK: return 'D';
            case LOAD.EMPTY: return 'E';
            case LOAD.LOADED2JSON: return '*';
            default: return '';
        }
    }
}

<commit_msg>Unloads also battles when a tile is unloaded.<commit_after>
const RG = require('./rg');
const FromJSON = require('./game.fromjson');
const debug = require('debug')('bitn:ChunkManager');

export const LOAD = Object.freeze(
    {EMPTY: 'EMPTY', LOADED: 'LOADED', JSON: 'JSON', ON_DISK: 'ON_DISK',
        LOADED2JSON: 'LOADED2JSON'});
export const CREATE = Object.freeze(
    {EMPTY: 'EMPTY', CREATED: 'CREATED', POPULATED: 'POPULATED'});

export function printTileConnections(msg, tileToConnect, id = -1) {
    console.log(msg);
    if (typeof tileToConnect.getLevel === 'function') {
        if (tileToConnect.getLevel().getID() === id || id === -1) {
            const conns0 = tileToConnect.getLevel().getConnections();
            conns0.forEach(c => {
                const targetLevel = c.getTargetLevel();
                if (Number.isInteger(targetLevel)) {
                    console.log(`\tTarget ID ${targetLevel} found`);
                }
                else {
                    console.log(`\tMap.Level ${targetLevel.getID()} found`);
                }
            });
        }
    }
    else {
        console.log('Skipping printTileConnections due to json input');

    }
}

/* Chunk manager handles loading/saving of world chunks (World.AreaTiles)
 * from/to memory/disk. It also keeps track of the state of each chunk.
 * */
export default class ChunkManager {

    constructor(game, area) {
        const [sizeX, sizeY] = [area.getSizeX(), area.getSizeY()];
        this.sizeX = sizeX;
        this.sizeY = sizeY;
        this.area = area;
        this.game = game;
        this.state = [];

        for (let x = 0; x < sizeX; x++) {
            this.state[x] = [];
            for (let y = 0; y < sizeY; y++) {
                if (area.isLoaded(x, y)) {
                    const chunkState = {loadState: LOAD.LOADED};
                    this.state[x].push(chunkState);
                }
                else {
                    const chunkState = {loadState: LOAD.JSON};
                    this.state[x].push(chunkState);
                }
            }
        }

        // From how far the levels are loaded
        this.loadDistX = 1;
        this.loadDistY = 1;

        this.onDiskDistX = sizeX;
        this.onDiskDistY = sizeY;
    }

    setPlayerTile(px, py, oldX, oldY) {
        const moveDir = this.getMoveDir(px, py, oldX, oldY);
        // printTileConnections('setPlayerTile XXX',
        // this.area.getTiles()[1][0], 4);
        const loadedTiles = [];
        for (let x = 0; x < this.sizeX; x++) {
            for (let y = 0; y < this.sizeY; y++) {
                if (this.inLoadRange(px, py, x, y)) {
                    if (!this.isLoaded(x, y)) {
                        // this.loadTile(px, py, x, y, moveDir);
                        loadedTiles.push([x, y]);
                    }
                }
                else if (this.isLoaded(x, y)) {
                    this.unloadTile(px, py, x, y, moveDir);
                }
            }
        }
        if (loadedTiles.length > 0) {
            this.loadTiles(px, py, loadedTiles, moveDir);
        }
    }

    isLoaded(x, y) {
        return this.state[x][y].loadState === LOAD.LOADED;
    }

    /* Returns true if given tile (tx,ty) is within load range from
     * player px,py .*/
    inLoadRange(px, py, tx, ty) {
        for (let x = px - this.loadDistX; x <= px + this.loadDistX; x++) {
            for (let y = py - this.loadDistY; y <= py + this.loadDistY; y++) {
                if (tx === x && ty === y) {return true;}
            }
        }
        return false;
    }

    loadAllTiles() {
        this.setLoadStateAll(LOAD.LOADED);
    }

    /* Returns number of tiles in given load state. */
    getNumInState(loadState) {
        let num = 0;
        for (let x = 0; x < this.sizeX; x++) {
            for (let y = 0; y < this.sizeY; y++) {
                if (this.state[x][y].loadState === loadState) {
                    ++num;
                }
            }
        }
        return num;
    }

    /* Loads the serialized/on-disk tile. */
    loadTiles(px, py, loadedTilesXY) {
        const areaTiles = this.area.getTiles();
        debug('loadTiles: ' + JSON.stringify(loadedTilesXY));
        const loadedAreaTiles = loadedTilesXY.map(
            xy => areaTiles[xy[0]][xy[1]]
        );

        this.createTiles(loadedAreaTiles);

        loadedTilesXY.forEach(xy => {
            debug(`ChunkManager load now tile ${xy}`);
            const [tx, ty] = xy;
            this.state[tx][ty].loadState = LOAD.LOADED;
            this.area.setLoaded(tx, ty);
        });
    }

    /* Unloads the tile from memory. */
    unloadTile(px, py, tx, ty, moveDir) {
        debug(`Unloading tile ${tx},${ty}`);
        const areaTiles = this.area.getTiles();
        this.state[tx][ty].loadState = LOAD.LOADED2JSON;
        this.area.setUnloaded(tx, ty);

        const levels = areaTiles[tx][ty].getLevels();
        this.game.removeLevels(levels);

        // Unload battles associated with this tile
        const battleLevel = areaTiles[tx][ty].getLevel();
        debug(`\tUnloading tile ${tx},${ty}, id: ${battleLevel.getID()}`);
        const gameMaster = this.game.getGameMaster();
        gameMaster.unloadBattles(battleLevel);

        areaTiles[tx][ty] = areaTiles[tx][ty].toJSON();

        // Need to replace connections on adjacent tiles
        if (moveDir === 'WEST') {
            const newX = tx - 1;
            debug(`Removing connections from tile ${tx - 1},${ty}`);
            if (newX < this.area.getSizeX()) {
                this.removeConnections('EAST', areaTiles[tx - 1][ty]);
            }
        }
        else if (moveDir === 'EAST') {
            const newX = tx + 1;
            debug(`Removing connections from tile ${tx + 1},${ty}`);
            if (newX < this.area.getSizeX()) {
                this.removeConnections('WEST', areaTiles[tx + 1][ty]);
            }
        }
        else if (moveDir === 'NORTH') {
            const newY = ty - 1;
            debug(`Removing connections from tile ${tx},${ty - 1}`);
            if (newY >= 0) {
                this.removeConnections('SOUTH', areaTiles[tx][ty - 1]);
            }
        }
        else if (moveDir === 'SOUTH') {
            const newY = ty + 1;
            debug(`Removing connections from tile ${tx},${ty + 1}`);
            if (newY < this.area.getSizeY) {
                this.removeConnections('NORTH', areaTiles[tx][ty + 1]);
            }
        }
        this.state[tx][ty].loadState = LOAD.JSON;
    }

    getLoadState(x, y) {
        return this.state[x][y].loadState;
    }

    serializeArea() {
        this.setLoadStateAll(LOAD.JSON);
        const levels = this.area.getLevels();
        this.game.removeLevels(levels);
        this.levels = levels.map(l => l.toJSON());
        const tiles = this.area.getTiles();
        for (let x = 0; x < this.sizeX; x++) {
            for (let y = 0; y < this.sizeY; y++) {
                tiles[x][y] = tiles[x][y].toJSON();
            }
        }
    }

    toJSON() {
        return {
            state: this.state
        };
    }

    setLoadStateAll(state) {
        this.state.forEach((col, x) => {
            col.forEach((tile, y) => {
                this.state[x][y].loadState = state;
            });
        });
    }

    createTiles(tilesJSON) {
        const fromJSON = new FromJSON();
        fromJSON.setChunkMode(true);
        fromJSON.createTiles(this.game, tilesJSON);
    }

    addConnections(dir, tileToConnect, newTile) {
        // printTileConnections('XXX', tileToConnect);
        const oppositeDir = this.getOpposite(dir);
        const addedConns = this.getReplacedConnections(dir, tileToConnect);
        const newConns = this.getReplacedConnections(oppositeDir, newTile);
        const fromJSON = new FromJSON();
        const conns = addedConns.concat(newConns);
        const levels = [tileToConnect.getLevel(), newTile.getLevel()];
        fromJSON.connectTileLevels(levels, conns);
    }

    removeConnections(dir, tile) {
        const replacedConns = this.getReplacedConnections(dir, tile);
        replacedConns.forEach(conn => {
            const targetConn = conn.getTargetStairs();
            const connObj = {
                targetLevel: conn.getTargetLevel().getID(),
                targetStairs: {x: targetConn.getX(), y: targetConn.getY()}
            };

            conn.setConnObj(connObj);
        });
    }

    getReplacedConnections(dir, tile) {
        const level = tile.getLevel();
        const conns = level.getConnections();
        if (conns.length === 0) {
            RG.err('ChunkManager', 'getReplacedConnections',
                'No connections found.');
        }
        let replacedConns = [];
        if (dir === 'SOUTH') {
            replacedConns = conns.filter(conn => conn.getY() === tile.rows - 1);
        }
        else if (dir === 'NORTH') {
            replacedConns = conns.filter(conn => conn.getY() === 0);
        }
        else if (dir === 'EAST') {
            replacedConns = conns.filter(conn => conn.getX() === tile.cols - 1);
        }
        else if (dir === 'WEST') {
            replacedConns = conns.filter(conn => conn.getX() === 0);
        }
        return replacedConns;
    }

    getOpposite(dir) {
        switch (dir) {
            case 'NORTH': return 'SOUTH';
            case 'SOUTH': return 'NORTH';
            case 'EAST': return 'WEST';
            case 'WEST': return 'EAST';
            default: RG.err('ChunkManager', 'getOpposite',
                `Illegal dir ${dir} given.`);
        }
        return '';
    }

    /* Returns the player movement direction. */
    getMoveDir(px, py, oldX, oldY) {
        let [dx, dy] = [0, 0];
        let moveDir = '';
        if (!RG.isNullOrUndef([oldX, oldY])) {
            dx = px - oldX;
            dy = py - oldY;
            if (dx !== 0 && dy !== 0) {
                RG.err('ChunkManager', 'setPlayerTile',
                    'Diagonal move not supported');
            }
            if (dx > 0) {moveDir = 'EAST';}
            else if (dx < 0) {moveDir = 'WEST';}
            if (dy > 0) {moveDir = 'SOUTH';}
            else if (dy < 0) {moveDir = 'NORTH';}
        }
        return moveDir;
    }

    /* Prints the state in concise format. */
    debugPrint() {
        let result = '';
        for (let y = 0; y < this.sizeY; y++) {
            for (let x = 0; x < this.sizeX; x++) {
                result += ' ' + this.stateToChar(this.state[x][y]);
            }
            result += ` - ${y} \n`;
        }
        result += '\n\tNum loaded: ' + this.getNumInState(LOAD.LOADED);
        result += '\n\tNum serialized: ' + this.getNumInState(LOAD.JSON);
        result += '\n\tNum on disk: ' + this.getNumInState(LOAD.ON_DISK);
        return result;
    }

    /* Converts current state into a single char. */
    stateToChar(state) {
        switch (state.loadState) {
            case LOAD.LOADED: return 'L';
            case LOAD.JSON: return 'J';
            case LOAD.ON_DISK: return 'D';
            case LOAD.EMPTY: return 'E';
            case LOAD.LOADED2JSON: return '*';
            default: return '';
        }
    }
}

<|endoftext|>


<commit_before>// -*- coding: utf-8 -*-
/*
 (c) 2014 Andreas Motl, Elmyra UG <andreas.motl@elmyra.de>
 derived from https://github.com/tavendo/AutobahnPython/blob/master/examples/twisted/wamp/pubsub/simple/example2/index.html
 */

// WAMP session object
var sess;
var wsuri;

window.onload = function() {

    if (window.location.protocol === "file:") {
        wsuri = "ws://localhost:9000";
    } else {
        wsuri = "ws://" + window.location.hostname + ":9000";
    }

    //ab.debug(true, true, true);

    // connect to WAMP server
    ab.connect(wsuri,

        // WAMP session was established
        function(session) {

            sess = session;
            console.log("Connected!");

            sess.prefix("registry", "http://ilaundry.useeds.de/registry#");
            sess.prefix("broadcast", "http://ilaundry.useeds.de/broadcast#");
            sess.prefix("dashboard", "http://ilaundry.useeds.de/dashboard#");

            sess.subscribe("broadcast:node-heartbeat", node_heartbeat);
            sess.subscribe("dashboard:update", dashboard_update);

            // trigger dashboard update
            sess.publish("dashboard:update", null, false);

        },

        // WAMP session is gone
        function(code, reason) {
            sess = null;
            //alert(reason);
        }
    );
};

function dump_event(topic, event) {
    console.log({'topic': topic, 'event': event});
}

function node_heartbeat(topic, node_id) {
    console.log({'topic': topic, 'node_id': node_id});
}

function dashboard_update(topic, event) {
    sess.call("registry:get_nodelist").then(function(nodelist) {
        ab.log('nodelist: ' + nodelist);
        var node_select = document.getElementById("node_id");
        node_select.options.length = 0;
        for (index in nodelist) {
            var node_id = nodelist[index];
            node_select.options[node_select.options.length] = new Option(node_id, node_id);
        }
    });
}

function sayText(node_id, message) {
    /*
    evt = {};
    evt.node_id = node_id;
    evt.message = message;
    evt.date = new Date();
    */

    if (!sess) {
        console.error('ERROR: No WAMP session with iLaundry master service');
        return;
    }

    var node_topic = 'http://ilaundry.useeds.de/node/' + node_id + '#';
    sess.prefix(node_id, node_topic);
    //sess.subscribe(node_topic, dump_event);
    var event_topic = node_id + ':say';
    sess.publish(event_topic, message);
}
<commit_msg>ui: listen for node-motion-on and node-motion-off events<commit_after>// -*- coding: utf-8 -*-
/*
 (c) 2014 Andreas Motl, Elmyra UG <andreas.motl@elmyra.de>
 derived from https://github.com/tavendo/AutobahnPython/blob/master/examples/twisted/wamp/pubsub/simple/example2/index.html
 */

// WAMP session object
var sess;
var wsuri;

window.onload = function() {

    if (window.location.protocol === "file:") {
        wsuri = "ws://localhost:9000";
    } else {
        wsuri = "ws://" + window.location.hostname + ":9000";
    }

    //ab.debug(true, true, true);

    // connect to WAMP server
    ab.connect(wsuri,

        // WAMP session was established
        function(session) {

            sess = session;
            console.log("Connected!");

            sess.prefix("registry", "http://ilaundry.useeds.de/registry#");
            sess.prefix("broadcast", "http://ilaundry.useeds.de/broadcast#");
            sess.prefix("dashboard", "http://ilaundry.useeds.de/dashboard#");

            sess.subscribe("broadcast:node-heartbeat", node_heartbeat);
            sess.subscribe("broadcast:node-motion-on", dump_event);
            sess.subscribe("broadcast:node-motion-off", dump_event);
            sess.subscribe("dashboard:update", dashboard_update);

            // trigger dashboard update
            sess.publish("dashboard:update", null, false);

        },

        // WAMP session is gone
        function(code, reason) {
            sess = null;
            //alert(reason);
        }
    );
};

function dump_event(topic, event) {
    console.log({'topic': topic, 'event': event});
}

function node_heartbeat(topic, node_id) {
    console.log({'topic': topic, 'node_id': node_id});
}

function dashboard_update(topic, event) {
    sess.call("registry:get_nodelist").then(function(nodelist) {
        ab.log('nodelist: ' + nodelist);
        var node_select = document.getElementById("node_id");
        node_select.options.length = 0;
        for (index in nodelist) {
            var node_id = nodelist[index];
            node_select.options[node_select.options.length] = new Option(node_id, node_id);
        }
    });
}

function sayText(node_id, message) {
    /*
    evt = {};
    evt.node_id = node_id;
    evt.message = message;
    evt.date = new Date();
    */

    if (!sess) {
        console.error('ERROR: No WAMP session with iLaundry master service');
        return;
    }

    var node_topic = 'http://ilaundry.useeds.de/node/' + node_id + '#';
    sess.prefix(node_id, node_topic);
    //sess.subscribe(node_topic, dump_event);
    var event_topic = node_id + ':say';
    sess.publish(event_topic, message);
}
<|endoftext|>


<commit_before>//
//  File: skin.js
//  Project: R7.Epsilon
//
//  Author: Roman M. Yagodin <roman.yagodin@gmail.com>
//
//  Copyright (c) 2014-2019 Roman M. Yagodin, R7.Labs
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Affero General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Affero General Public License for more details.
//
//  You should have received a copy of the GNU Affero General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.

//
//  File: skin.js
//  Project: R7.Epsilon
//
//  Author: Roman M. Yagodin <roman.yagodin@gmail.com>
//
//  Copyright (c) 2015-2019 Roman M. Yagodin, R7.Labs
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Affero General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Affero General Public License for more details.
//
//  You should have received a copy of the GNU Affero General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.

import A11y from "./a11y";

window.skinGoogleTranslatePage = function (fromLang) {
    window.open ("http://translate.google.com/translate?hl=en&sl=" + fromLang + "&u=" + encodeURI (document.location));
};

window.skinOpenFeedback = function (button, $, feedbackModuleId) {
    const selection = encodeURIComponent (rangy.getSelection ().toString ().replace (/(\n|\r)/gm," ").replace (/\s+/g, " ").replace (/\"/g, "").trim ().substring (0,100));
    const baseFeedbackUrl = $(button).data ("feedback-url");
    const feedbackParams = "returntabid=" + epsilon.queryParams ["TabId"] + "&feedbackmid=" + feedbackModuleId;

    const feedbackSelection = ((!!selection)? "&feedbackselection=" + selection : "");

    if (epsilon.enablePopups && window.skinA11y.getPopupsDisabled () === false && $(button).data ("feedback-open-in-popup") === true) {
        const popupFeedbackUrl = baseFeedbackUrl + "/mid/" + feedbackModuleId + "?" + feedbackParams + "&popup=true" + feedbackSelection;
        dnnModal.show (popupFeedbackUrl, false, 550, 950, false, "");
    }
    else {
        const rawFeedbackUrl = baseFeedbackUrl + "?" + feedbackParams + "&" + feedbackSelection;
        window.open (rawFeedbackUrl, "_blank");
    }
};

(function ($, window, document) {

    function initBreadcrumb () {
        if (epsilon.breadCrumbsRemoveLastLink) {
            // assume new style breadcrumbs with schema.org markup (DNN 8+)
            var schemaOrg = true;
            var breadcrumb = $(".breadcrumb > span > span").first ();

            if (breadcrumb.length === 0) {
                // it looks like an old style breadcrumbs
                schemaOrg = false;
                breadcrumb = $(".breadcrumb > span").first ();
            }

            // remove last link (to the current page)
            if (breadcrumb.length > 0) {
                if (schemaOrg) {
                    breadcrumb.find ("a").last ().parent ().remove ();
                } else {
                    breadcrumb.find ("a").last ().remove ();
                }
            }
        }
    }

    function initUpButton (offset, duration) {
        $(window).scroll(function() {
            if ($(this).scrollTop() > offset) {
                $('.skin-float-button-up').fadeIn(duration);
            } else {
                $('.skin-float-button-up').fadeOut(duration);
            }
        });

        $('.skin-float-button-up').click(function(event) {
            event.preventDefault();
            $(this).tooltip ('hide');
            $('html, body').animate({scrollTop: 0}, duration);
            return false;
        });
    }

    function emptyLayoutRows () {
        $('.row').each (function () {
            if ($(this).children ().length ===
                $(this).children ('.DNNEmptyPane').not ('.dnnSortable').length) {
                $(this).addClass ('hidden');
            }
        });
    }

    function alterLanguage () {
        $(".skin-languages .language-object a").each (function () {
            const lang = $(this).parent (".Language").attr ("title");
            const langCode = $(this).closest (".language-object").find ("option").filter (function () { return $(this).text() == lang; }).val ();
            $(this).addClass ("dropdown-item")
                .html ("" + langCode + " " + lang)
                .attr ("hreflang", langCode);
        });
    }

    function initBootstrapTooltips () {
        if (typeof ($.fn.tooltip) !== "undefined") {
            $("[data-toggle='tooltip']").tooltip ();
        }
    }

    function initBootstrapPopovers () {
        if (typeof ($.fn.popover) !== "undefined") {
            $("[data-toggle='popover']").popover ();
        }
    }

    function alterLogin () {
        $(".skin-login li.userDisplayName > a").addClass ("dropdown-item");
        $(".skin-login div.loginGroup > a").addClass ("dropdown-item");
        $(".skin-login li.userMessages > a").addClass ("dropdown-item");
        $(".skin-login li.userNotifications > a").addClass ("dropdown-item");
        $(".skin-login li.userProfileImg > a").addClass ("dropdown-item");

        const loginGroup = $(".skin-login .loginGroup").first ().detach ();
        const divider = "

";

        const profileImg = $(".skin-login li.userProfileImg > a > img");
        if (profileImg.length > 0) {
            profileImg.attr ("src", profileImg.attr ("src").replace ("h=32&w=32", "h=64&w=64"));

            const profileLinkBlock = $(".skin-login li.userDisplayName").first ().detach ();
            const profileImgBlock = $(".skin-login li.userProfileImg").first ().detach ();

            $(".skin-login ul.buttonGroup")
                .prepend (divider)
                .prepend (profileLinkBlock)
                .prepend (profileImgBlock)
                .append (divider)
                .append (loginGroup.children ("a").prepend (" "));

                $(".skin-login li.userMessages > a > span").addClass ("badge badge-primary");
                $(".skin-login li.userNotifications > a > span").addClass ("badge badge-secondary");
        }
        else {
            $(".skin-login ul.buttonGroup")
                .append (divider)
                .append (loginGroup.children ("a").prepend (" "));
        }
    }

    function initCustomContent () {
        // TODO: Also check for superusers and admins
        if (epsilon.isEditable) {
            $(".skin-custom-content").each (function () {
                $(this).prepend ("
"
                                + "
"
                                // TODO: Localize label
                                // TODO: Generate actual edit URL
                                + "

"
                                + "
"
                                + "
");
            });
        }
    }

    $(function () {
        initBootstrapTooltips ();
        initBootstrapPopovers ();

        if (! epsilon.inPopup) {
            emptyLayoutRows ();
            initBreadcrumb ();
            initUpButton (320, 500);
            initCustomContent ();
            alterLanguage ();
            alterLogin ();
            window.skinA11y = new A11y ().init ();
        }
    });

}) (jQuery, window, document);
<commit_msg>Duplicate header<commit_after>//
//  File: skin.js
//  Project: R7.Epsilon
//
//  Author: Roman M. Yagodin <roman.yagodin@gmail.com>
//
//  Copyright (c) 2015-2019 Roman M. Yagodin, R7.Labs
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Affero General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Affero General Public License for more details.
//
//  You should have received a copy of the GNU Affero General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.

import A11y from "./a11y";

window.skinGoogleTranslatePage = function (fromLang) {
    window.open ("http://translate.google.com/translate?hl=en&sl=" + fromLang + "&u=" + encodeURI (document.location));
};

window.skinOpenFeedback = function (button, $, feedbackModuleId) {
    const selection = encodeURIComponent (rangy.getSelection ().toString ().replace (/(\n|\r)/gm," ").replace (/\s+/g, " ").replace (/\"/g, "").trim ().substring (0,100));
    const baseFeedbackUrl = $(button).data ("feedback-url");
    const feedbackParams = "returntabid=" + epsilon.queryParams ["TabId"] + "&feedbackmid=" + feedbackModuleId;

    const feedbackSelection = ((!!selection)? "&feedbackselection=" + selection : "");

    if (epsilon.enablePopups && window.skinA11y.getPopupsDisabled () === false && $(button).data ("feedback-open-in-popup") === true) {
        const popupFeedbackUrl = baseFeedbackUrl + "/mid/" + feedbackModuleId + "?" + feedbackParams + "&popup=true" + feedbackSelection;
        dnnModal.show (popupFeedbackUrl, false, 550, 950, false, "");
    }
    else {
        const rawFeedbackUrl = baseFeedbackUrl + "?" + feedbackParams + "&" + feedbackSelection;
        window.open (rawFeedbackUrl, "_blank");
    }
};

(function ($, window, document) {

    function initBreadcrumb () {
        if (epsilon.breadCrumbsRemoveLastLink) {
            // assume new style breadcrumbs with schema.org markup (DNN 8+)
            var schemaOrg = true;
            var breadcrumb = $(".breadcrumb > span > span").first ();

            if (breadcrumb.length === 0) {
                // it looks like an old style breadcrumbs
                schemaOrg = false;
                breadcrumb = $(".breadcrumb > span").first ();
            }

            // remove last link (to the current page)
            if (breadcrumb.length > 0) {
                if (schemaOrg) {
                    breadcrumb.find ("a").last ().parent ().remove ();
                } else {
                    breadcrumb.find ("a").last ().remove ();
                }
            }
        }
    }

    function initUpButton (offset, duration) {
        $(window).scroll(function() {
            if ($(this).scrollTop() > offset) {
                $('.skin-float-button-up').fadeIn(duration);
            } else {
                $('.skin-float-button-up').fadeOut(duration);
            }
        });

        $('.skin-float-button-up').click(function(event) {
            event.preventDefault();
            $(this).tooltip ('hide');
            $('html, body').animate({scrollTop: 0}, duration);
            return false;
        });
    }

    function emptyLayoutRows () {
        $('.row').each (function () {
            if ($(this).children ().length ===
                $(this).children ('.DNNEmptyPane').not ('.dnnSortable').length) {
                $(this).addClass ('hidden');
            }
        });
    }

    function alterLanguage () {
        $(".skin-languages .language-object a").each (function () {
            const lang = $(this).parent (".Language").attr ("title");
            const langCode = $(this).closest (".language-object").find ("option").filter (function () { return $(this).text() == lang; }).val ();
            $(this).addClass ("dropdown-item")
                .html ("" + langCode + " " + lang)
                .attr ("hreflang", langCode);
        });
    }

    function initBootstrapTooltips () {
        if (typeof ($.fn.tooltip) !== "undefined") {
            $("[data-toggle='tooltip']").tooltip ();
        }
    }

    function initBootstrapPopovers () {
        if (typeof ($.fn.popover) !== "undefined") {
            $("[data-toggle='popover']").popover ();
        }
    }

    function alterLogin () {
        $(".skin-login li.userDisplayName > a").addClass ("dropdown-item");
        $(".skin-login div.loginGroup > a").addClass ("dropdown-item");
        $(".skin-login li.userMessages > a").addClass ("dropdown-item");
        $(".skin-login li.userNotifications > a").addClass ("dropdown-item");
        $(".skin-login li.userProfileImg > a").addClass ("dropdown-item");

        const loginGroup = $(".skin-login .loginGroup").first ().detach ();
        const divider = "

";

        const profileImg = $(".skin-login li.userProfileImg > a > img");
        if (profileImg.length > 0) {
            profileImg.attr ("src", profileImg.attr ("src").replace ("h=32&w=32", "h=64&w=64"));

            const profileLinkBlock = $(".skin-login li.userDisplayName").first ().detach ();
            const profileImgBlock = $(".skin-login li.userProfileImg").first ().detach ();

            $(".skin-login ul.buttonGroup")
                .prepend (divider)
                .prepend (profileLinkBlock)
                .prepend (profileImgBlock)
                .append (divider)
                .append (loginGroup.children ("a").prepend (" "));

                $(".skin-login li.userMessages > a > span").addClass ("badge badge-primary");
                $(".skin-login li.userNotifications > a > span").addClass ("badge badge-secondary");
        }
        else {
            $(".skin-login ul.buttonGroup")
                .append (divider)
                .append (loginGroup.children ("a").prepend (" "));
        }
    }

    function initCustomContent () {
        // TODO: Also check for superusers and admins
        if (epsilon.isEditable) {
            $(".skin-custom-content").each (function () {
                $(this).prepend ("
"
                                + "
"
                                // TODO: Localize label
                                // TODO: Generate actual edit URL
                                + "

"
                                + "
"
                                + "
");
            });
        }
    }

    $(function () {
        initBootstrapTooltips ();
        initBootstrapPopovers ();

        if (! epsilon.inPopup) {
            emptyLayoutRows ();
            initBreadcrumb ();
            initUpButton (320, 500);
            initCustomContent ();
            alterLanguage ();
            alterLogin ();
            window.skinA11y = new A11y ().init ();
        }
    });

}) (jQuery, window, document);
<|endoftext|>


<commit_before>import Component from 'ember-component'
import layout    from '../templates/components/ember-snip'

const { document }    = window
const LEFT_MOUSE      = 1
const preventDefault  = e => e.preventDefault()
const on              = (el, type, fun) => el.addEventListener(type, fun, false)
const off             = (el, type, fun) => el.removeEventListener(type, fun)
const { abs, min }    = Math
const EMPTY_RECTANGLE = new Rectangle(0, 0, 0, 0)
const ZERO_POINT      = new Point(0, 0)

const EmberSnip = Component.extend({
  layout,

  tagName: 'ember-snip',

  disabled: false,
  cancel: null,
  _distance: 0,

  snipee: 'ember-snipee',

  _moved: false,
  _offset: null,
  _dimensions: null,
  _startPoint: null,
  _currentPoint: null,
  _lastPoint: null,

  init() {
    this._super(...arguments)

    this._startPoint   = ZERO_POINT
    this._currentPoint = ZERO_POINT
    this._lastPoint    = ZERO_POINT

    this.move = this.move.bind(this)
    this.end  = this.end.bind(this)
  },

  _toggleMoveListeners(enable) {
    let fn = enable ? on : off

    fn(document, 'mouseup',    this.end)
    fn(document, 'touchend',   this.end)
    fn(document, 'mousemove',  this.move)
    fn(document, 'touchmove',  this.move)
    fn(document, 'mousewheel', preventDefault)
  },

  start(e) {
    if (this.get('disabled') || this._isCancelled(e)) {
      return
    }

    let $el   = this.$()
    let point = this._eventToPoint(this._eventDataForPoint(e))

    this._offset = $el.offset()

    if (this._clickedScrollbar(point)) {
      return
    }

    e.preventDefault()

    this._updateElementDimensions()
    this._setStartPoint(point)
    this._setCurrentPoint(this._startPoint)
    this._setLastPoint(ZERO_POINT)

    this._toggleMoveListeners(true)

    this.sendAction('on-start', e)
  },

  'on-end'(e, self) {
    self.hideSnipee()
  },

  end(e) {
    this._moved = false
    this._toggleMoveListeners(false)
    this.sendAction('on-end', e, this)
  },

  move(e) {
    let point = this._eventToPoint(this._eventDataForPoint(e))

    if (!this._moved && this._notMovedBeyondDistance(point)) {
      return
    }

    this._moved = true
    this._setLastPoint(this._currentPoint)
    this._setCurrentPoint(point)

    let rect = this._createRectangle(
      this._dimensions,
      this._startPoint,
      this._currentPoint
    )

    this._updateSnipee(rect)
    this.sendAction('on-move', rect)
  },

  mouseDown(e) {
    if (e.which === LEFT_MOUSE) {
      this.start(e)
    }
  },

  touchStart(e) {
    this.start(e)
  },

  hideSnipee() {
    this._updateSnipee(EMPTY_RECTANGLE)
  },

  _eventDataForPoint(e) {
    if (e.changedTouches) {
      return e.changedTouches[0]
    }

    return e
  },

  _eventToPoint(e) {
    return new Point(e.pageX, e.pageY)
  },

  _clickedScrollbar(point) {
    return point.x > this._offset.left + this.element.clientWidth ||
           point.y > this._offset.top  + this.element.clientHeight
  },

  _updateElementDimensions() {
    let $el  = this.$()
    let top  = this._offset.top  + ($el.outerHeight(false) - $el.innerHeight()) / 2
    let left = this._offset.left + ($el.outerWidth(false)  - $el.innerWidth())  / 2

    this._dimensions = new ElementDimension({
      top,
      left,
      width:      this.element.clientWidth,
      height:     this.element.clientHeight,
      scrollTop:  this.element.scrollTop,
      scrollLeft: this.element.scrollLeft
    })
  },

  _notMovedBeyondDistance(point) {
    if (!this._distance) {
      return false
    }

    return abs(this._startPoint.x - point.x) < this._distance &&
           abs(this._startPoint.y - point.y) < this._distance
  },

  _setCurrentPoint(point) {
    this._setPoint('_currentPoint', point)
  },

  _setStartPoint(point) {
    this._setPoint('_startPoint', point)
  },

  _setLastPoint(point) {
    this._setPoint('_lastPoint', point)
  },

  _setPoint(prop, point) {
    this[prop] = point
  },

  _isCancelled(e) {
    return this.get('cancel') && this.$(e.target).is(this.get('cancel'))
  },

  _createRectangle(dimensions, p1, p2) {
    let x1 = min(p1.x, p2.x) -
      dimensions.left +
      dimensions.scrollLeft

    let y1 = min(p1.y, p2.y) -
      dimensions.top +
      dimensions.scrollTop

    let x2 = x1 + abs(p1.x - p2.x)
    let y2 = y1 + abs(p1.y - p2.y)

    return new Rectangle(x1, y1, x2, y2)
  },

  _updateSnipee(rect) {
    let [ snipee ] = this.childViews

    if (snipee) {
      snipee.set('rectangle', rect)
    }
  }
})

function Point(x, y) {
  this.x = x | 0
  this.y = y | 0

  //Object.freeze(this)
}

function Rectangle(x1, y1, x2, y2) {
  this.x1 = x1 | 0
  this.y1 = y1 | 0
  this.x2 = x2 | 0
  this.y2 = y2 | 0

  //Object.freeze(this)
}

function ElementDimension(props) {
  this.top        = props.top        | 0
  this.left       = props.left       | 0
  this.width      = props.width      | 0
  this.height     = props.height     | 0
  this.scrollTop  = props.scrollTop  | 0
  this.scrollLeft = props.scrollLeft | 0

  //Object.freeze(this)
}

if (Symbol.toStringTag) {
  Point.prototype[Symbol.toStringTag] = 'Point'
  Rectangle.prototype[Symbol.toStringTag] = 'Rectangle'
  ElementDimension.prototype[Symbol.toStringTag] = 'ElementDimension'
}

export default EmberSnip
<commit_msg>Only expose hideSnipee in end event<commit_after>import Component from 'ember-component'
import layout    from '../templates/components/ember-snip'

const { document }    = window
const LEFT_MOUSE      = 1
const preventDefault  = e => e.preventDefault()
const on              = (el, type, fun) => el.addEventListener(type, fun, false)
const off             = (el, type, fun) => el.removeEventListener(type, fun)
const { abs, min }    = Math
const EMPTY_RECTANGLE = new Rectangle(0, 0, 0, 0)
const ZERO_POINT      = new Point(0, 0)

const EmberSnip = Component.extend({
  layout,

  tagName: 'ember-snip',

  disabled: false,
  cancel: null,
  _distance: 0,

  snipee: 'ember-snipee',

  _moved: false,
  _offset: null,
  _dimensions: null,
  _startPoint: null,
  _currentPoint: null,
  _lastPoint: null,

  init() {
    this._super(...arguments)

    this._startPoint   = ZERO_POINT
    this._currentPoint = ZERO_POINT
    this._lastPoint    = ZERO_POINT

    this.move = this.move.bind(this)
    this.end  = this.end.bind(this)
  },

  _toggleMoveListeners(enable) {
    let fn = enable ? on : off

    fn(document, 'mouseup',    this.end)
    fn(document, 'touchend',   this.end)
    fn(document, 'mousemove',  this.move)
    fn(document, 'touchmove',  this.move)
    fn(document, 'mousewheel', preventDefault)
  },

  start(e) {
    if (this.get('disabled') || this._isCancelled(e)) {
      return
    }

    let $el   = this.$()
    let point = this._eventToPoint(this._eventDataForPoint(e))

    this._offset = $el.offset()

    if (this._clickedScrollbar(point)) {
      return
    }

    e.preventDefault()

    this._updateElementDimensions()
    this._setStartPoint(point)
    this._setCurrentPoint(this._startPoint)
    this._setLastPoint(ZERO_POINT)

    this._toggleMoveListeners(true)

    this.sendAction('on-start', e)
  },

  'on-end'(e, self) {
    self.hideSnipee()
  },

  end(e) {
    this._moved = false
    this._toggleMoveListeners(false)
    this.sendAction('on-end', e, {
      hideSnipee: () => this.hideSnipee()
    })
  },

  move(e) {
    let point = this._eventToPoint(this._eventDataForPoint(e))

    if (!this._moved && this._notMovedBeyondDistance(point)) {
      return
    }

    this._moved = true
    this._setLastPoint(this._currentPoint)
    this._setCurrentPoint(point)

    let rect = this._createRectangle(
      this._dimensions,
      this._startPoint,
      this._currentPoint
    )

    this._updateSnipee(rect)
    this.sendAction('on-move', rect)
  },

  mouseDown(e) {
    if (e.which === LEFT_MOUSE) {
      this.start(e)
    }
  },

  touchStart(e) {
    this.start(e)
  },

  hideSnipee() {
    this._updateSnipee(EMPTY_RECTANGLE)
  },

  _eventDataForPoint(e) {
    if (e.changedTouches) {
      return e.changedTouches[0]
    }

    return e
  },

  _eventToPoint(e) {
    return new Point(e.pageX, e.pageY)
  },

  _clickedScrollbar(point) {
    return point.x > this._offset.left + this.element.clientWidth ||
           point.y > this._offset.top  + this.element.clientHeight
  },

  _updateElementDimensions() {
    let $el  = this.$()
    let top  = this._offset.top  + ($el.outerHeight(false) - $el.innerHeight()) / 2
    let left = this._offset.left + ($el.outerWidth(false)  - $el.innerWidth())  / 2

    this._dimensions = new ElementDimension({
      top,
      left,
      width:      this.element.clientWidth,
      height:     this.element.clientHeight,
      scrollTop:  this.element.scrollTop,
      scrollLeft: this.element.scrollLeft
    })
  },

  _notMovedBeyondDistance(point) {
    if (!this._distance) {
      return false
    }

    return abs(this._startPoint.x - point.x) < this._distance &&
           abs(this._startPoint.y - point.y) < this._distance
  },

  _setCurrentPoint(point) {
    this._setPoint('_currentPoint', point)
  },

  _setStartPoint(point) {
    this._setPoint('_startPoint', point)
  },

  _setLastPoint(point) {
    this._setPoint('_lastPoint', point)
  },

  _setPoint(prop, point) {
    this[prop] = point
  },

  _isCancelled(e) {
    return this.get('cancel') && this.$(e.target).is(this.get('cancel'))
  },

  _createRectangle(dimensions, p1, p2) {
    let x1 = min(p1.x, p2.x) -
      dimensions.left +
      dimensions.scrollLeft

    let y1 = min(p1.y, p2.y) -
      dimensions.top +
      dimensions.scrollTop

    let x2 = x1 + abs(p1.x - p2.x)
    let y2 = y1 + abs(p1.y - p2.y)

    return new Rectangle(x1, y1, x2, y2)
  },

  _updateSnipee(rect) {
    let [ snipee ] = this.childViews

    if (snipee) {
      snipee.set('rectangle', rect)
    }
  }
})

function Point(x, y) {
  this.x = x | 0
  this.y = y | 0

  //Object.freeze(this)
}

function Rectangle(x1, y1, x2, y2) {
  this.x1 = x1 | 0
  this.y1 = y1 | 0
  this.x2 = x2 | 0
  this.y2 = y2 | 0

  //Object.freeze(this)
}

function ElementDimension(props) {
  this.top        = props.top        | 0
  this.left       = props.left       | 0
  this.width      = props.width      | 0
  this.height     = props.height     | 0
  this.scrollTop  = props.scrollTop  | 0
  this.scrollLeft = props.scrollLeft | 0

  //Object.freeze(this)
}

if (Symbol.toStringTag) {
  Point.prototype[Symbol.toStringTag] = 'Point'
  Rectangle.prototype[Symbol.toStringTag] = 'Rectangle'
  ElementDimension.prototype[Symbol.toStringTag] = 'ElementDimension'
}

export default EmberSnip
<|endoftext|>


<commit_before>var gitrev

git = require('git-rev')

exports.get = function (request, response) {
  var responseModel

  responseModel = {
    links: {
      self: request.originalUrl
    }
  }

  git.long(function (githash) {
    var status, serverVersion

    serverVersion = process.env.npm_package_version

    responseModel.data = {
      version: serverVersion,
      commit: githash
    }

    status = 200
    response.status(status)
    response.json(responseModel)
  })
}
<commit_msg>Fix variable misnaming<commit_after>var gitrev

gitrev = require('git-rev')

exports.get = function (request, response) {
  var responseModel

  responseModel = {
    links: {
      self: request.originalUrl
    }
  }

  gitrev.long(function (githash) {
    var status, serverVersion

    serverVersion = process.env.npm_package_version

    responseModel.data = {
      version: serverVersion,
      commit: githash
    }

    status = 200
    response.status(status)
    response.json(responseModel)
  })
}
<|endoftext|>


<commit_before>import { Meteor } from 'meteor/meteor';
import { onPageLoad } from 'meteor/server-render';

import { createMetaName, createMetaProperty } from '/server/startup/metaTag/createMeta';
import { getCustomMetaTagByPathname } from '/server/startup/metaTag/getCustomMetaTagByPathname';

onPageLoad((sink) => {
  let metaTag = getCommonMetaTag();

  const { pathname } = sink.request.url;
  const customeMetaTag = getCustomMetaTagByPathname(pathname);
  if (customeMetaTag) {
    metaTag += customeMetaTag;
  }
  else {
    metaTag += getDefaultMetaTag();
  }

  sink.appendToHead(metaTag);
});

function getCommonMetaTag() {
  let metaTag = '';
  metaTag += createMetaName('twitter:card', 'summary');
  metaTag += createMetaProperty('og:image:width', 300);
  metaTag += createMetaProperty('og:image:height', 300);

  return metaTag;
}

function getDefaultMetaTag() {
  let metaTag = '';
  metaTag += createMetaProperty('og:title', Meteor.settings.public.websiteName);
  metaTag += createMetaProperty('og:image', 'https://acgn-stock.com/ms-icon-310x310.png');
  metaTag += createMetaProperty('og:image:url', 'https://acgn-stock.com/ms-icon-310x310.png');
  metaTag += createMetaProperty('og:description', '漲停!!');

  return metaTag;
}
<commit_msg>修改股市預覽時的預設簡介<commit_after>import { Meteor } from 'meteor/meteor';
import { onPageLoad } from 'meteor/server-render';

import { createMetaName, createMetaProperty } from '/server/startup/metaTag/createMeta';
import { getCustomMetaTagByPathname } from '/server/startup/metaTag/getCustomMetaTagByPathname';

onPageLoad((sink) => {
  let metaTag = getCommonMetaTag();

  const { pathname } = sink.request.url;
  const customeMetaTag = getCustomMetaTagByPathname(pathname);
  if (customeMetaTag) {
    metaTag += customeMetaTag;
  }
  else {
    metaTag += getDefaultMetaTag();
  }

  sink.appendToHead(metaTag);
});

function getCommonMetaTag() {
  let metaTag = '';
  metaTag += createMetaName('twitter:card', 'summary');
  metaTag += createMetaProperty('og:image:width', 300);
  metaTag += createMetaProperty('og:image:height', 300);

  return metaTag;
}

function getDefaultMetaTag() {
  let metaTag = '';
  metaTag += createMetaProperty('og:title', Meteor.settings.public.websiteName);
  metaTag += createMetaProperty('og:image', 'https://acgn-stock.com/ms-icon-310x310.png');
  metaTag += createMetaProperty('og:image:url', 'https://acgn-stock.com/ms-icon-310x310.png');
  metaTag += createMetaProperty('og:description', '｜ 尋找你的老婆？ \n｜ 喜歡嗎？那麼就入股吧！');

  return metaTag;
}
<|endoftext|>