<commit_before>/* @flow */
/* eslint-disable */
import type { ThemeType } from '../../types';
import { BORDER_COLOR, BRAND_COLOR } from '../../styles';
import cssEmojis from './cssEmojis';

const defaultTheme = `
html {
  -webkit-user-select: none; /* Safari 3.1+ */
  -moz-user-select: none; /* Firefox 2+ */
  -ms-user-select: none; /* IE 10+ */
  user-select: none; /* Standard syntax */
  -khtml-user-select: none;
  -webkit-touch-callout: none;
}
body {
  font-family: sans-serif;
  line-height: 1.4;
  margin: 0;
  width: 100%;
  max-width: 100%;
  font-size: 15px;
}
a {
  color: #08c;
}
p {
  margin: 0;
}
code {
  font-size: .857em;
  white-space: pre-wrap;
  padding: 0 0.25em;
}
pre {
  padding: 0.5em;
  margin: 0.5em 0;
  font-size: 0.75em;
  white-space: pre;
  overflow-x: auto;
  word-wrap: normal;
}
code, pre {
  border-radius: 3px;
  border: 1px solid rgba(127, 127, 127, 0.25);
  background-color: rgba(127, 127, 127, 0.125);
  font-family: Monaco, Menlo, Consolas, "Courier New", monospace;
}
table {
  border-collapse: collapse;
  width: 100%;
}
table, th, td {
  border: 1px solid rgba(127, 127, 127, 0.25);
}
thead {
  background: rgba(127, 127, 127, 0.1);
}
th, td {
  align: center;
  padding: 0.25em 0.5em;
}
hr {
  margin: 1em 0;
  border: 0;
  border-top: 1px solid rgba(127, 127, 127, 0.5);
}
.highlight {
  background-color: hsl(51, 94%, 74%);
}
.subheader {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  margin-bottom: 0.25em;
}
.timerow {
  text-align: center;
  color: #999;
  display: flex;
  align-items: center;
  padding: 0.5em 0;
}
.timerow-left,
.timerow-right {
  flex: 1;
  height: 1px;
  margin: 0.5em;
}
.timerow-left {
  background: -webkit-linear-gradient(left, transparent 10%, #999 100%);
}
.timerow-right {
  background: -webkit-linear-gradient(left, #999 0%, transparent 90%);
}
.timestamp {
  color: #999;
  font-size: 14px;
  line-height: 1;
  white-space: nowrap;
}
.message {
  display: flex;
  word-wrap: break-word;
  padding: 1em;
}
.message-brief {
  padding: 0 1em 1em 4em;
}
.avatar {
  min-width: 2em;
  width: 2em;
  height: 2em;
  margin-right: 1em;
}
.avatar img {
  width: 100%;
  border-radius: 3px;
}
.content {
  width: 100%;
  max-width: 100%;
  overflow: hidden;
}
.username {
  font-weight: bold;
  line-height: 1;
  text-overflow: ellipsis;
  white-space: nowrap;
  overflow: hidden;
}
.user-group-mention,
.user-mention {
  white-space: nowrap;
  background-color: rgba(0, 0, 0, 0.1);
  border-radius: 3px;
  padding: 0 .2em;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.2);
}
.header-wrapper {
  position: -webkit-sticky;
  position: sticky;
  top: -1px;
  padding: 0.5em;
  z-index: 100;
}
.avatar,
.header-wrapper {
  cursor: pointer;
}
.stream-header {
  padding: 0;
  display: flex;
  flex-direction: row;
}
.stream-text,
.topic-text,
.private-header {
  padding: 0 0.5em;
  line-height: 2;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.topic-text {
  flex: 1;
  padding-left: 0.5em;
  background: #ccc;
  min-width: 30%;
}
[data-mentioned="true"], [data-wildcard_mentioned="true"] {
  background: rgba(255, 0, 0, 0.05);
}
.message:not([data-read="true"]) {
}
.arrow-right {
  width: 0;
  height: 0;
  border-top: 1em solid transparent;
  border-bottom: 1em solid transparent;
  border-left: 1em solid green;
}
.private-header {
  background: #444;
  color: white;
}
.loading-avatar {
  min-width: 2em;
  width: 2em;
  height: 2em;
  margin-right: 1em;
  border-radius: 4px;
  background: rgba(127, 127, 127, 0.9);
}
.loading-content {
  width: 100%;
}
.loading-subheader {
  display: flex;
  justify-content: space-between;
}
.loading-content .block {
  background: linear-gradient(
    to right,
    rgba(127, 127, 127, 0.5) 0%,
    rgba(127, 127, 127, 0.5) 40%,
    rgba(127, 127, 127, 0.25) 51%,
    rgba(127, 127, 127, 0.5) 60%,
    rgba(127, 127, 127, 0.5) 100%
  );
  background-size: 200% 200%;
	animation: gradient-scroll 1s linear infinite;

  border-radius: 10px;
  height: 8px;
  margin-bottom: 10px;
}
@keyframes gradient-scroll {
	0% { background-position: 100% 50% }
	100% { background-position: 0 50% }
}
.loading-subheader .name {
  width: 10em;
  background-color: rgba(127, 127, 127, 0.9);
}
.loading-subheader .timestamp {
  width: 5em;
}
@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}
.loading-spinner, .outbox-spinner {
  width: 2em;
  height: 2em;
  border-radius: 50%;
  margin: 1em auto;
  font-size: 10px;
  border: 3px solid rgba(82, 194, 175, 0.25);
  border-left: 3px solid rgba(82, 194, 175, 0.75);
  animation: spin 1s linear infinite;
}
.outbox-spinner {
  margin: -16px 0 0 0;
  border-width: 2px;
  width: 12px;
  height: 12px;
  float: right;
}
.message_inline_image {
  text-align: center;
}
.message_inline_image img,
.twitter-image img {
  width: 100%;
  height: 30vh;
  object-fit: contain;
}
blockquote {
  padding-left: 0.5em;
  margin: 0.5em 0 0.5em 0;
  border-left: 3px solid rgba(127, 127, 127, 0.5);
}
ul {
  padding-left: 1em;
  margin: 0.5em 0;
}
.codehilite .gi { color: #00a000; }
.codehilite .gd { color: #a00000; }
.codehilite .k { color: #008000; font-weight: bold; }
.codehilite .kd { color: #008000; font-weight: bold; }
.codehilite .nf { color: #00f; }
.codehilite .s2 { color: #ba2121; }
.codehilite .cp { color: #bc7a00; }
.codehilite .kt { color: #b00040; }
.codehilite .nc { color: #00f; font-weight: bold; }
.codehilite .nb { color: #008000; }
.codehilite .s1 { color: #ba2121; }
.twitter-tweet {
  border: 2px solid rgba(29, 161, 242, 0.5);
  background: rgba(29, 161, 242, 0.1);
  border-radius: 0.25em;
  padding: 0.5em 1em;
  margin: 0.5em 0;
}
.twitter-avatar {
  border-radius: 0.25em;
  margin: 0.4em 0.4em 0.2em 0;
  float: left;
  width: 2.2em;
  height: 2.2em;
}
.twitter-image {
  text-align: center;
  margin: 0.5em auto;
}
.message-tags {
  text-align: right;
  margin: 0.25em 0;
  font-size: 10px;
}
.message-tag {
  padding: 0.25em 0.5em;
  margin-left: 4;
  border-radius: 2px;
  color: rgba(127, 127, 127, 0.75);
  background: rgba(0, 0, 0, 0.1);
}
.reaction-list {
  margin: 0.5em 0;
}
.reaction {
  color: rgba(127, 127, 127, 1);
  display: inline-block;
  padding: 5px 6.5px;
  border-radius: 4px;
  border: 1px solid rgba(127, 127, 127, 0.75);
  line-height: 1em;
  margin: .25em 0.5em .25em 0;
}
.realm-reaction {
  pointer-events: none;
  cursor: default;
  height: "auto";
  width: 1.25em;
  max-height: 1.25em;
  vertical-align: top;
}
.self-voted {
  color: ${BRAND_COLOR};
  border: 1px solid ${BRAND_COLOR};
  background: rgba(36, 202, 194, 0.1);
}
.hidden {
  display: none;
}
.emoji {
  display: inline-block;
  height: 18px;
  width: 18px;
  white-space: nowrap;
  color: transparent;
  vertical-align: text-top;
}
.emoji:before {
  color: white;
}
#typing {
  display: flex;
  padding-left: 1em;
}
#typing .content {
  padding-top: 0.5em;
  padding-bottom: 1em;
}
#typing span {
  display: inline-block;
  background-color: #B6B5BA;
  width: 0.5em;
  height: 0.5em;
  border-radius: 100%;
  margin-right: 5px;
  animation: bob 2s infinite;
}
#typing span:nth-child(2) {
  animation-delay: 0.15s;
}
#typing span:nth-child(3) {
  animation-delay: 0.3s;
  margin-right: 0;
}
@keyframes bob {
  10% {
    transform: translateY(-10px);
    background-color: #9E9DA2;
  }
  50% {
    transform: translateY(0);
    background-color: #B6B5BA;
  }
}
.typing-list {
  background: green;
  height: 20px;
}
#message-loading {
  position: fixed;
  width: 100%;
  opacity: 0.25;
}
#js-error {
  position: fixed;
  width: 100%;
  background: red;
  color: white;
  font-size: 10px;
}
#scroll-bottom {
  position: fixed;
  z-index: 200;
  right: 5px;
  bottom: 15px;
}
#scroll-bottom a {
  display: block;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: rgba(82, 194, 175, 0.5);
}
#scroll-bottom svg {
  width: 32px;
  height: 32px;
  fill: rgba(255, 255, 255, 0.75);
}
`;

const darkTheme = `
body {
  color: #d5d9dd;
  background: #212D3B;
}
.topic-text {
  background: #54606E;
}
.highlight {
  background-color: hsla(51, 100%, 64%, 0.42);
}
`;

export default (theme: ThemeType, highlightUnreadMessages: boolean) => `
<style>
${defaultTheme}
${theme === 'night' ? darkTheme : ''}
${cssEmojis}
${highlightUnreadMessages ? '.message:not([data-read="true"]) { background: red; }' : ''}
</style>
`;
<commit_msg>webview: Add 1em of spacing between paragraphs in the same message.<commit_after>/* @flow */
/* eslint-disable */
import type { ThemeType } from '../../types';
import { BORDER_COLOR, BRAND_COLOR } from '../../styles';
import cssEmojis from './cssEmojis';

const defaultTheme = `
html {
  -webkit-user-select: none; /* Safari 3.1+ */
  -moz-user-select: none; /* Firefox 2+ */
  -ms-user-select: none; /* IE 10+ */
  user-select: none; /* Standard syntax */
  -khtml-user-select: none;
  -webkit-touch-callout: none;
}
body {
  font-family: sans-serif;
  line-height: 1.4;
  margin: 0;
  width: 100%;
  max-width: 100%;
  font-size: 15px;
}
a {
  color: #08c;
}
p {
  margin: 0;
}
code {
  font-size: .857em;
  white-space: pre-wrap;
  padding: 0 0.25em;
}
pre {
  padding: 0.5em;
  margin: 0.5em 0;
  font-size: 0.75em;
  white-space: pre;
  overflow-x: auto;
  word-wrap: normal;
}
code, pre {
  border-radius: 3px;
  border: 1px solid rgba(127, 127, 127, 0.25);
  background-color: rgba(127, 127, 127, 0.125);
  font-family: Monaco, Menlo, Consolas, "Courier New", monospace;
}
table {
  border-collapse: collapse;
  width: 100%;
}
table, th, td {
  border: 1px solid rgba(127, 127, 127, 0.25);
}
thead {
  background: rgba(127, 127, 127, 0.1);
}
th, td {
  align: center;
  padding: 0.25em 0.5em;
}
hr {
  margin: 1em 0;
  border: 0;
  border-top: 1px solid rgba(127, 127, 127, 0.5);
}
.highlight {
  background-color: hsl(51, 94%, 74%);
}
.subheader {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  margin-bottom: 0.25em;
}
.timerow {
  text-align: center;
  color: #999;
  display: flex;
  align-items: center;
  padding: 0.5em 0;
}
.timerow-left,
.timerow-right {
  flex: 1;
  height: 1px;
  margin: 0.5em;
}
.timerow-left {
  background: -webkit-linear-gradient(left, transparent 10%, #999 100%);
}
.timerow-right {
  background: -webkit-linear-gradient(left, #999 0%, transparent 90%);
}
.timestamp {
  color: #999;
  font-size: 14px;
  line-height: 1;
  white-space: nowrap;
}
.message {
  display: flex;
  word-wrap: break-word;
  padding: 1em;
}
.message-brief {
  padding: 0 1em 1em 4em;
}
.message p + p {
  margin-top: 1em;
}
.avatar {
  min-width: 2em;
  width: 2em;
  height: 2em;
  margin-right: 1em;
}
.avatar img {
  width: 100%;
  border-radius: 3px;
}
.content {
  width: 100%;
  max-width: 100%;
  overflow: hidden;
}
.username {
  font-weight: bold;
  line-height: 1;
  text-overflow: ellipsis;
  white-space: nowrap;
  overflow: hidden;
}
.user-group-mention,
.user-mention {
  white-space: nowrap;
  background-color: rgba(0, 0, 0, 0.1);
  border-radius: 3px;
  padding: 0 .2em;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.2);
}
.header-wrapper {
  position: -webkit-sticky;
  position: sticky;
  top: -1px;
  padding: 0.5em;
  z-index: 100;
}
.avatar,
.header-wrapper {
  cursor: pointer;
}
.stream-header {
  padding: 0;
  display: flex;
  flex-direction: row;
}
.stream-text,
.topic-text,
.private-header {
  padding: 0 0.5em;
  line-height: 2;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.topic-text {
  flex: 1;
  padding-left: 0.5em;
  background: #ccc;
  min-width: 30%;
}
[data-mentioned="true"], [data-wildcard_mentioned="true"] {
  background: rgba(255, 0, 0, 0.05);
}
.message:not([data-read="true"]) {
}
.arrow-right {
  width: 0;
  height: 0;
  border-top: 1em solid transparent;
  border-bottom: 1em solid transparent;
  border-left: 1em solid green;
}
.private-header {
  background: #444;
  color: white;
}
.loading-avatar {
  min-width: 2em;
  width: 2em;
  height: 2em;
  margin-right: 1em;
  border-radius: 4px;
  background: rgba(127, 127, 127, 0.9);
}
.loading-content {
  width: 100%;
}
.loading-subheader {
  display: flex;
  justify-content: space-between;
}
.loading-content .block {
  background: linear-gradient(
    to right,
    rgba(127, 127, 127, 0.5) 0%,
    rgba(127, 127, 127, 0.5) 40%,
    rgba(127, 127, 127, 0.25) 51%,
    rgba(127, 127, 127, 0.5) 60%,
    rgba(127, 127, 127, 0.5) 100%
  );
  background-size: 200% 200%;
	animation: gradient-scroll 1s linear infinite;

  border-radius: 10px;
  height: 8px;
  margin-bottom: 10px;
}
@keyframes gradient-scroll {
	0% { background-position: 100% 50% }
	100% { background-position: 0 50% }
}
.loading-subheader .name {
  width: 10em;
  background-color: rgba(127, 127, 127, 0.9);
}
.loading-subheader .timestamp {
  width: 5em;
}
@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}
.loading-spinner, .outbox-spinner {
  width: 2em;
  height: 2em;
  border-radius: 50%;
  margin: 1em auto;
  font-size: 10px;
  border: 3px solid rgba(82, 194, 175, 0.25);
  border-left: 3px solid rgba(82, 194, 175, 0.75);
  animation: spin 1s linear infinite;
}
.outbox-spinner {
  margin: -16px 0 0 0;
  border-width: 2px;
  width: 12px;
  height: 12px;
  float: right;
}
.message_inline_image {
  text-align: center;
}
.message_inline_image img,
.twitter-image img {
  width: 100%;
  height: 30vh;
  object-fit: contain;
}
blockquote {
  padding-left: 0.5em;
  margin: 0.5em 0 0.5em 0;
  border-left: 3px solid rgba(127, 127, 127, 0.5);
}
ul {
  padding-left: 1em;
  margin: 0.5em 0;
}
.codehilite .gi { color: #00a000; }
.codehilite .gd { color: #a00000; }
.codehilite .k { color: #008000; font-weight: bold; }
.codehilite .kd { color: #008000; font-weight: bold; }
.codehilite .nf { color: #00f; }
.codehilite .s2 { color: #ba2121; }
.codehilite .cp { color: #bc7a00; }
.codehilite .kt { color: #b00040; }
.codehilite .nc { color: #00f; font-weight: bold; }
.codehilite .nb { color: #008000; }
.codehilite .s1 { color: #ba2121; }
.twitter-tweet {
  border: 2px solid rgba(29, 161, 242, 0.5);
  background: rgba(29, 161, 242, 0.1);
  border-radius: 0.25em;
  padding: 0.5em 1em;
  margin: 0.5em 0;
}
.twitter-avatar {
  border-radius: 0.25em;
  margin: 0.4em 0.4em 0.2em 0;
  float: left;
  width: 2.2em;
  height: 2.2em;
}
.twitter-image {
  text-align: center;
  margin: 0.5em auto;
}
.message-tags {
  text-align: right;
  margin: 0.25em 0;
  font-size: 10px;
}
.message-tag {
  padding: 0.25em 0.5em;
  margin-left: 4;
  border-radius: 2px;
  color: rgba(127, 127, 127, 0.75);
  background: rgba(0, 0, 0, 0.1);
}
.reaction-list {
  margin: 0.5em 0;
}
.reaction {
  color: rgba(127, 127, 127, 1);
  display: inline-block;
  padding: 5px 6.5px;
  border-radius: 4px;
  border: 1px solid rgba(127, 127, 127, 0.75);
  line-height: 1em;
  margin: .25em 0.5em .25em 0;
}
.realm-reaction {
  pointer-events: none;
  cursor: default;
  height: "auto";
  width: 1.25em;
  max-height: 1.25em;
  vertical-align: top;
}
.self-voted {
  color: ${BRAND_COLOR};
  border: 1px solid ${BRAND_COLOR};
  background: rgba(36, 202, 194, 0.1);
}
.hidden {
  display: none;
}
.emoji {
  display: inline-block;
  height: 18px;
  width: 18px;
  white-space: nowrap;
  color: transparent;
  vertical-align: text-top;
}
.emoji:before {
  color: white;
}
#typing {
  display: flex;
  padding-left: 1em;
}
#typing .content {
  padding-top: 0.5em;
  padding-bottom: 1em;
}
#typing span {
  display: inline-block;
  background-color: #B6B5BA;
  width: 0.5em;
  height: 0.5em;
  border-radius: 100%;
  margin-right: 5px;
  animation: bob 2s infinite;
}
#typing span:nth-child(2) {
  animation-delay: 0.15s;
}
#typing span:nth-child(3) {
  animation-delay: 0.3s;
  margin-right: 0;
}
@keyframes bob {
  10% {
    transform: translateY(-10px);
    background-color: #9E9DA2;
  }
  50% {
    transform: translateY(0);
    background-color: #B6B5BA;
  }
}
.typing-list {
  background: green;
  height: 20px;
}
#message-loading {
  position: fixed;
  width: 100%;
  opacity: 0.25;
}
#js-error {
  position: fixed;
  width: 100%;
  background: red;
  color: white;
  font-size: 10px;
}
#scroll-bottom {
  position: fixed;
  z-index: 200;
  right: 5px;
  bottom: 15px;
}
#scroll-bottom a {
  display: block;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: rgba(82, 194, 175, 0.5);
}
#scroll-bottom svg {
  width: 32px;
  height: 32px;
  fill: rgba(255, 255, 255, 0.75);
}
`;

const darkTheme = `
body {
  color: #d5d9dd;
  background: #212D3B;
}
.topic-text {
  background: #54606E;
}
.highlight {
  background-color: hsla(51, 100%, 64%, 0.42);
}
`;

export default (theme: ThemeType, highlightUnreadMessages: boolean) => `
<style>
${defaultTheme}
${theme === 'night' ? darkTheme : ''}
${cssEmojis}
${highlightUnreadMessages ? '.message:not([data-read="true"]) { background: red; }' : ''}
</style>
`;
<|endoftext|>


<commit_before>window.fakeStorage = {
  _data: {},

  setItem: function (id, val) {
    return this._data[id] = String(val);
  },

  getItem: function (id) {
    return this._data.hasOwnProperty(id) ? this._data[id] : undefined;
  },

  removeItem: function (id) {
    return delete this._data[id];
  },

  clear: function () {
    return this._data = {};
  }
};

function LocalStorageManager() {
  this.bestScoreKey     = "bestScore";
  this.gameStateKey     = "gameState";

  var supported = this.localStorageSupported();
  this.storage = supported ? window.localStorage : window.fakeStorage;
  alert(supported + this.storage + this.getBestScore());
}

LocalStorageManager.prototype.localStorageSupported = function () {
  var testKey = "test";
  var storage = window.localStorage;

  try {
    storage.setItem(testKey, "1");
    storage.removeItem(testKey);
    return true;
  } catch (error) {
    return false;
  }
};

// Best score getters/setters
LocalStorageManager.prototype.getBestScore = function () {
  return this.storage.getItem(this.bestScoreKey) || 0;
};

LocalStorageManager.prototype.setBestScore = function (score) {
  this.storage.setItem(this.bestScoreKey, score);
};

// Game state getters/setters and clearing
LocalStorageManager.prototype.getGameState = function () {
  var stateJSON = this.storage.getItem(this.gameStateKey);
  return stateJSON ? JSON.parse(stateJSON) : null;
};

LocalStorageManager.prototype.setGameState = function (gameState) {
  this.storage.setItem(this.gameStateKey, JSON.stringify(gameState));
};

LocalStorageManager.prototype.clearGameState = function () {
  this.storage.removeItem(this.gameStateKey);
};
<commit_msg>debug localstorage<commit_after>window.fakeStorage = {
  _data: {},

  setItem: function (id, val) {
    return this._data[id] = String(val);
  },

  getItem: function (id) {
    return this._data.hasOwnProperty(id) ? this._data[id] : undefined;
  },

  removeItem: function (id) {
    return delete this._data[id];
  },

  clear: function () {
    return this._data = {};
  }
};

function LocalStorageManager() {
  this.bestScoreKey     = "bestScore";
  this.gameStateKey     = "gameState";

  var supported = this.localStorageSupported();
  this.storage = supported ? window.localStorage : window.fakeStorage;
  //alert(supported + this.storage + this.getBestScore());
}

LocalStorageManager.prototype.localStorageSupported = function () {
  var testKey = "test";
  var storage = window.localStorage;

  try {
    storage.setItem(testKey, "1");
    storage.removeItem(testKey);
    return true;
  } catch (error) {
    return false;
  }
};

// Best score getters/setters
LocalStorageManager.prototype.getBestScore = function () {
  return this.storage.getItem(this.bestScoreKey) || 0;
};

LocalStorageManager.prototype.setBestScore = function (score) {
  this.storage.setItem(this.bestScoreKey, score);
};

// Game state getters/setters and clearing
LocalStorageManager.prototype.getGameState = function () {
  var stateJSON = this.storage.getItem(this.gameStateKey);
  return stateJSON ? JSON.parse(stateJSON) : null;
};

LocalStorageManager.prototype.setGameState = function (gameState) {
  this.storage.setItem(this.gameStateKey, JSON.stringify(gameState));
};

LocalStorageManager.prototype.clearGameState = function () {
  this.storage.removeItem(this.gameStateKey);
};
<|endoftext|>


<commit_before>define(function(require) {
  var Constants = require("constants");
  var SYNC_OPERATION_DELETE = require("constants").SYNC_OPERATION_DELETE;
  var Path = Bramble.Filer.Path;
  var Buffer = Bramble.Filer.Buffer;
  var fs = Bramble.getFileSystem();

  // Installs a tarball (arraybuffer) containing the project's files/folders.
  function installTarball(config, tarball, callback) {
    var untarWorker;
    var pending = null;
    var sh = new fs.Shell();
    var root = config.root;
    var pendingOperations = config.syncQueue.pending;

    function extract(path, data, callback) {
      var basedir = Path.dirname(path);

      sh.mkdirp(basedir, function(err) {
        if(err) {
          return callback(err);
        }

        fs.writeFile(path, new Buffer(data), {encoding: null}, callback);
      });
    }

    // If there are pending delete operations to be run in the SyncQueue for a
    // given path in the project (e.g., user deleted a file locally, but closed
    // before the change could be synced to the server), we can safely ignore
    // the initial extract, since we'll just delete the file soon anyway.
    function maybeExtract(path, data, callback) {
      path = Path.join(root, path);

      if(pendingOperations[path] === SYNC_OPERATION_DELETE) {
        callback();
        return;
      }

      extract(path, data, callback);
    }

    function finish(err) {
      untarWorker.terminate();
      untarWorker = null;

      callback(err);
    }

    function writeCallback(err) {
      if(err) {
        console.error("[Thimble error] couldn't extract file for tar", err);
      }

      pending--;
      if(pending === 0) {
        finish(err);
      }
    }

    untarWorker = new Worker("/resources/scripts/vendor/bitjs-untar-worker.min.js");
    untarWorker.addEventListener("message", function(e) {
      var data = e.data;

      if(data.type === "progress" && pending === null) {
        // Set the total number of files we need to deal with so we know when we're done
        pending = data.totalFilesInArchive;
      } else if(data.type === "extract") {
        maybeExtract(data.unarchivedFile.filename, data.unarchivedFile.fileData, writeCallback);
      } else if(data.type === "error") {
        finish(new Error("[Thimble error]: " + data.msg));
      }
    });

    untarWorker.postMessage({file: tarball});
  }

  function loadTarball(config, callback) {
    // jQuery doesn't seem to support getting the arraybuffer type
    var url = config.host + "/projects";
    if (config.remixId || config.id) {
      url += "/" + (config.remixId || config.id);
    }
    url += "/files/data?cacheBust=" + (new Date()).toISOString();

    var xhr = new XMLHttpRequest();
    xhr.open("GET", url, true);
    xhr.responseType = "arraybuffer";
    xhr.onload = function() {
      if(this.status !== 200) {
        return callback(new Error("[Thimble error] unable to get tarball, status was:", this.status));
      }

      installTarball(config, this.response, callback);
    };
    xhr.send();
  }

  function upgradeAnonymousProject(config, callback) {
    var shell = new fs.Shell();
    var oldRoot = Path.join(Constants.ANONYMOUS_USER_FOLDER, config.anonymousId.toString());
    var newRoot = config.root;
    var pathUpdatesCache = [];

    function upgradeFile(path, next) {
      if (path.match(/\/$/)) {
        return next();
      }

      fs.readFile(path, function(err, data) {
        if (err) {
          return next(err);
        }

        var relativePath = Path.relative(oldRoot, path);
        var newPath = Path.join(newRoot, relativePath);
        var parent = Path.dirname(newPath);

        shell.mkdirp(parent, function(err) {
          if (err) {
            return next(err);
          }

          fs.writeFile(newPath, data, {encoding: null}, function(err) {
            if (err) {
              return next(err);
            }

            // Track this as a file to be persisted to publish
            pathUpdatesCache.push(newPath);
            next();
          });
        });
      });
    }

    shell.find(oldRoot, {exec: upgradeFile}, function(err) {
      if (err) {
        console.error("[Thimble Error] unable to process path while upgrading anonymous project", err);
        return callback(err);
      }

      shell.rm(oldRoot, {recursive: true}, function(err) {
        if(err) {
          console.error("[Thimble Error] unable to remove path while upgrading anonymous project", err);
          return callback(err);
        }

        // Send back the list of paths to be updated
        callback(null, pathUpdatesCache);
      });
    });
  }

  function loadProject(config, callback) {
    if (config.user) {
      if (config.anonymousId) {
        return upgradeAnonymousProject(config, callback);
      }

      return loadTarball(config, callback);
    }

    // First, check if this anonymous project already exists by checking the
    // root. If it exists, we've done this before and no loading is required
    fs.stat(config.root, function(err) {
      if (err) {
        if (err.code !== "ENOENT") {
          return callback(err);
        }

        // Anonymous project does not exist
        (new fs.Shell()).mkdirp(config.root, function(err) {
          if (err) {
            return callback(err);
          }

          loadTarball(config, callback);
        });
      } else {
        callback();
      }
    });
  }

  return {
    loadProject: loadProject
  };
});
<commit_msg>Fix #1287 - Files occasionally don't save<commit_after>define(function(require) {
  var Constants = require("constants");
  var Path = Bramble.Filer.Path;
  var Buffer = Bramble.Filer.Buffer;
  var fs = Bramble.getFileSystem();

  // Installs a tarball (arraybuffer) containing the project's files/folders.
  function installTarball(config, tarball, callback) {
    var untarWorker;
    var pending = null;
    var sh = new fs.Shell();
    var root = config.root;
    var pendingOperations = config.syncQueue.pending;

    function extract(path, data, callback) {
      var basedir = Path.dirname(path);

      sh.mkdirp(basedir, function(err) {
        if(err) {
          return callback(err);
        }

        fs.writeFile(path, new Buffer(data), {encoding: null}, callback);
      });
    }

    // If there are pending operations to be run in the SyncQueue for a
    // given path in the project (e.g., user deleted a file locally, but closed
    // before the change could be synced to the server), we can safely ignore
    // the initial extract, since we'll just be modifying the file anyway.
    function maybeExtract(path, data, callback) {
      path = Path.join(root, path);

      if(pendingOperations[path]) {
        callback();
        return;
      }

      extract(path, data, callback);
    }

    function finish(err) {
      untarWorker.terminate();
      untarWorker = null;

      callback(err);
    }

    function writeCallback(err) {
      if(err) {
        console.error("[Thimble error] couldn't extract file for tar", err);
      }

      pending--;
      if(pending === 0) {
        finish(err);
      }
    }

    untarWorker = new Worker("/resources/scripts/vendor/bitjs-untar-worker.min.js");
    untarWorker.addEventListener("message", function(e) {
      var data = e.data;

      if(data.type === "progress" && pending === null) {
        // Set the total number of files we need to deal with so we know when we're done
        pending = data.totalFilesInArchive;
      } else if(data.type === "extract") {
        maybeExtract(data.unarchivedFile.filename, data.unarchivedFile.fileData, writeCallback);
      } else if(data.type === "error") {
        finish(new Error("[Thimble error]: " + data.msg));
      }
    });

    untarWorker.postMessage({file: tarball});
  }

  function loadTarball(config, callback) {
    // jQuery doesn't seem to support getting the arraybuffer type
    var url = config.host + "/projects";
    if (config.remixId || config.id) {
      url += "/" + (config.remixId || config.id);
    }
    url += "/files/data?cacheBust=" + (new Date()).toISOString();

    var xhr = new XMLHttpRequest();
    xhr.open("GET", url, true);
    xhr.responseType = "arraybuffer";
    xhr.onload = function() {
      if(this.status !== 200) {
        return callback(new Error("[Thimble error] unable to get tarball, status was:", this.status));
      }

      installTarball(config, this.response, callback);
    };
    xhr.send();
  }

  function upgradeAnonymousProject(config, callback) {
    var shell = new fs.Shell();
    var oldRoot = Path.join(Constants.ANONYMOUS_USER_FOLDER, config.anonymousId.toString());
    var newRoot = config.root;
    var pathUpdatesCache = [];

    function upgradeFile(path, next) {
      if (path.match(/\/$/)) {
        return next();
      }

      fs.readFile(path, function(err, data) {
        if (err) {
          return next(err);
        }

        var relativePath = Path.relative(oldRoot, path);
        var newPath = Path.join(newRoot, relativePath);
        var parent = Path.dirname(newPath);

        shell.mkdirp(parent, function(err) {
          if (err) {
            return next(err);
          }

          fs.writeFile(newPath, data, {encoding: null}, function(err) {
            if (err) {
              return next(err);
            }

            // Track this as a file to be persisted to publish
            pathUpdatesCache.push(newPath);
            next();
          });
        });
      });
    }

    shell.find(oldRoot, {exec: upgradeFile}, function(err) {
      if (err) {
        console.error("[Thimble Error] unable to process path while upgrading anonymous project", err);
        return callback(err);
      }

      shell.rm(oldRoot, {recursive: true}, function(err) {
        if(err) {
          console.error("[Thimble Error] unable to remove path while upgrading anonymous project", err);
          return callback(err);
        }

        // Send back the list of paths to be updated
        callback(null, pathUpdatesCache);
      });
    });
  }

  function loadProject(config, callback) {
    if (config.user) {
      if (config.anonymousId) {
        return upgradeAnonymousProject(config, callback);
      }

      return loadTarball(config, callback);
    }

    // First, check if this anonymous project already exists by checking the
    // root. If it exists, we've done this before and no loading is required
    fs.stat(config.root, function(err) {
      if (err) {
        if (err.code !== "ENOENT") {
          return callback(err);
        }

        // Anonymous project does not exist
        (new fs.Shell()).mkdirp(config.root, function(err) {
          if (err) {
            return callback(err);
          }

          loadTarball(config, callback);
        });
      } else {
        callback();
      }
    });
  }

  return {
    loadProject: loadProject
  };
});
<|endoftext|>


<commit_before>/*
 * variables
 */

// general DOM elements
var g_formAvatar=null;
var g_sectionActivities=null;
var g_sectionUser=null;
var g_sectionResponse=null;
var g_activitiesFilter=null;
var g_activitiesSorter=null;

// input-field keys
var g_idFieldAvatar="idFieldAvatar";

// buttons
var g_btnUploadAvatar=null;

// others
var g_userId=null;

// Assistant Callback Functions
function onUploadAvatarFormSubmission(formEvt){

	formEvt.preventDefault(); // prevent default action.

	var formObj = $(this);
	var formData = new FormData(this);

	// append an user token for identity
	var token = $.cookie(g_keyToken);
	formData.append(g_keyToken, token);

	$.ajax({
		method: "POST",
		url: "/user/avatar/upload",
		data: formData,
		mimeType: "mutltipart/form-data",
		contentType: false,
		processData: false,
		success: function(data, status, xhr){
			g_sectionResponse.html("Uploaded");
		},
		error: function(xhr, status, err){
			g_sectionResponse.html("Failed");
		}
	});

}

function onQueryActivitiesSuccess(data, status, xhr){

	var jsonResponse = JSON.parse(data);
	if(jsonResponse == null) return;
	var count = Object.keys(jsonResponse).length;
	if(count <= 0) return;

	var oldStartingIndex = g_sectionActivities.data(g_keyStartingIndex);
	var oldEndingIndex = g_sectionActivities.data(g_keyEndingIndex);

	// clean target section
	g_sectionActivities.empty();
	var idx=0;
	// display contents
	for(var key in jsonResponse){
		var activityJson = jsonResponse[key];
		var activityId = activityJson[g_keyId];
		if(idx == 0)	g_sectionActivities.data(g_keyStartingIndex, activityId);
		if(idx == count-1)	g_sectionActivities.data(g_keyEndingIndex, activityId);
		var cell = generateActivityCell(activityJson);
		g_sectionActivities.append(cell);
		++idx;
	}

	var pageIndex = g_sectionActivities.data(g_keyPageIndex);
	var order = g_activitiesSorter.val();
	var newStartingIndex = g_sectionActivities.data(g_keyStartingIndex);
	var newEndingIndex = g_sectionActivities.data(g_keyEndingIndex);
	if(order == +1 && newStartingIndex > oldEndingIndex) ++pageIndex;
	if(order == +1 && newEndingIndex < oldStartingIndex) --pageIndex;
	if(order == -1 && newStartingIndex < oldEndingIndex) ++pageIndex;
	if(order == -1 && newEndingIndex > oldStartingIndex) --pageIndex;
	g_sectionActivities.data(g_keyPageIndex, pageIndex);

}

function onQueryActivitiesError(xhr, status, err) {

}

// Event Handlers
function onBtnUploadAvatarClicked(evt){

	var file = document.getElementById(g_keyAvatar);
	if(validateImage(file)==false){
		return;
	}

	// set callback function of form submission
	g_formAvatar.submit(onUploadAvatarFormSubmission);
	// invoke submission
	g_formAvatar.submit();
}

function onBtnPreviousPageClicked(evt){

	var pageIndex=g_sectionActivities.data(g_keyPageIndex);
	var startingIndex=g_sectionActivities.data(g_keyStartingIndex);

	var relation = g_activitiesFilter.val();
	var order = g_activitiesSorter.val();
	queryActivities(startingIndex, g_numItemsPerPage, order, g_directionBackward, g_userId, relation, null, onQueryActivitiesSuccess, onQueryActivitiesError);

}

function onBtnNextPageClicked(evt){
	var pageIndex=g_sectionActivities.data(g_keyPageIndex);
	var endingIndex=g_sectionActivities.data(g_keyEndingIndex);
	var relation = g_activitiesFilter.val();
	var order = g_activitiesSorter.val();
	queryActivities(endingIndex, g_numItemsPerPage, order, g_directionForward, g_userId, relation, null, onQueryActivitiesSuccess, onQueryActivitiesError);
}

function onSectionActivitiesScrolled(evt){
	if( $(this).scrollTop() + $(this).height() >= $(document).height() ){
		evt.preventDefault();
	}
}

function refreshOnEnter(){
	g_formAvatar.hide();
	queryUserDetail();
}

function refreshOnLoggedIn(){
	g_formAvatar.show();
	queryUserDetail();
}

function queryUserDetail(){
	var params={};
	params[g_keyUserId]=g_userId;
	var token=$.cookie(g_keyToken);
	if(token!=null) params[g_keyToken]=token;
	$.ajax({
		type: "GET",
		url: "/user/detail",
		data: params,
		success: function(data, status, xhr){
			var userJson=JSON.parse(data);
			var username=userJson[g_keyName];
			var prefix=$("<span>", {
				text: "You are viewing the profile of ",
				style: "color: black"
			}).appendTo(g_sectionUser);
			var sectionUser=$("<span>", {
				text: username,
				style: "color: blue"
			}).appendTo(g_sectionUser);
		}
	});
}

function validateImage(file){
	var fileName = file.value;
	var ext = fileName.split('.').pop().toLowerCase();
	if($.inArray(ext, ['gif','png','jpg','jpeg']) == -1) {
	    alert('invalid extension!');
	    return false;
	}
	return true;
}
<commit_msg>UI fix in profile page.<commit_after>/*
 * variables
 */

// general DOM elements
var g_formAvatar=null;
var g_sectionActivities=null;
var g_sectionUser=null;
var g_sectionResponse=null;
var g_activitiesFilter=null;
var g_activitiesSorter=null;

// input-field keys
var g_idFieldAvatar="idFieldAvatar";

// buttons
var g_btnUploadAvatar=null;

// others
var g_userId=null;

// Assistant Callback Functions
function onUploadAvatarFormSubmission(formEvt){

	formEvt.preventDefault(); // prevent default action.

	var formObj = $(this);
	var formData = new FormData(this);

	// append an user token for identity
	var token = $.cookie(g_keyToken);
	formData.append(g_keyToken, token);

	$.ajax({
		method: "POST",
		url: "/user/avatar/upload",
		data: formData,
		mimeType: "mutltipart/form-data",
		contentType: false,
		processData: false,
		success: function(data, status, xhr){
			g_sectionResponse.html("Uploaded");
		},
		error: function(xhr, status, err){
			g_sectionResponse.html("Failed");
		}
	});

}

function onQueryActivitiesSuccess(data, status, xhr){

	var jsonResponse = JSON.parse(data);
	if(jsonResponse == null) return;
	var count = Object.keys(jsonResponse).length;
	if(count <= 0) return;

	var oldStartingIndex = g_sectionActivities.data(g_keyStartingIndex);
	var oldEndingIndex = g_sectionActivities.data(g_keyEndingIndex);

	// clean target section
	g_sectionActivities.empty();
	var idx=0;
	// display contents
	for(var key in jsonResponse){
		var activityJson = jsonResponse[key];
		var activityId = activityJson[g_keyId];
		if(idx == 0)	g_sectionActivities.data(g_keyStartingIndex, activityId);
		if(idx == count-1)	g_sectionActivities.data(g_keyEndingIndex, activityId);
		var cell = generateActivityCell(activityJson);
		g_sectionActivities.append(cell);
		++idx;
	}

	var pageIndex = g_sectionActivities.data(g_keyPageIndex);
	var order = g_activitiesSorter.val();
	var newStartingIndex = g_sectionActivities.data(g_keyStartingIndex);
	var newEndingIndex = g_sectionActivities.data(g_keyEndingIndex);
	if(order == +1 && newStartingIndex > oldEndingIndex) ++pageIndex;
	if(order == +1 && newEndingIndex < oldStartingIndex) --pageIndex;
	if(order == -1 && newStartingIndex < oldEndingIndex) ++pageIndex;
	if(order == -1 && newEndingIndex > oldStartingIndex) --pageIndex;
	g_sectionActivities.data(g_keyPageIndex, pageIndex);

}

function onQueryActivitiesError(xhr, status, err) {

}

// Event Handlers
function onBtnUploadAvatarClicked(evt){

	var file = document.getElementById(g_keyAvatar);
	if(validateImage(file)==false){
		return;
	}

	// set callback function of form submission
	g_formAvatar.submit(onUploadAvatarFormSubmission);
	// invoke submission
	g_formAvatar.submit();
}

function onBtnPreviousPageClicked(evt){

	var pageIndex=g_sectionActivities.data(g_keyPageIndex);
	var startingIndex=g_sectionActivities.data(g_keyStartingIndex);

	var relation = g_activitiesFilter.val();
	var order = g_activitiesSorter.val();
	queryActivities(startingIndex, g_numItemsPerPage, order, g_directionBackward, g_userId, relation, null, onQueryActivitiesSuccess, onQueryActivitiesError);

}

function onBtnNextPageClicked(evt){
	var pageIndex=g_sectionActivities.data(g_keyPageIndex);
	var endingIndex=g_sectionActivities.data(g_keyEndingIndex);
	var relation = g_activitiesFilter.val();
	var order = g_activitiesSorter.val();
	queryActivities(endingIndex, g_numItemsPerPage, order, g_directionForward, g_userId, relation, null, onQueryActivitiesSuccess, onQueryActivitiesError);
}

function onSectionActivitiesScrolled(evt){
	if( $(this).scrollTop() + $(this).height() >= $(document).height() ){
		evt.preventDefault();
	}
}

function refreshOnEnter(){
	g_formAvatar.hide();
	queryUserDetail();
}

function refreshOnLoggedIn(){
	g_formAvatar.show();
	queryUserDetail();
}

function queryUserDetail(){
	var params={};
	params[g_keyUserId]=g_userId;
	var token=$.cookie(g_keyToken);
	if(token!=null) params[g_keyToken]=token;
	$.ajax({
		type: "GET",
		url: "/user/detail",
		data: params,
		success: function(data, status, xhr){
			if(g_sectionUser == null) return;
			g_sectionUser.empty();
			var userJson=JSON.parse(data);
			var username=userJson[g_keyName];
			var prefix=$("<span>", {
				text: "You are viewing the profile of ",
				style: "color: black"
			}).appendTo(g_sectionUser);
			var sectionUser=$("<span>", {
				text: username,
				style: "color: blue"
			}).appendTo(g_sectionUser);
		}
	});
}

function validateImage(file){
	var fileName = file.value;
	var ext = fileName.split('.').pop().toLowerCase();
	if($.inArray(ext, ['gif','png','jpg','jpeg']) == -1) {
	    alert('invalid extension!');
	    return false;
	}
	return true;
}
<|endoftext|>


<commit_before>/**
 * Ext.ux
 * RelationField / JoinedRelationField / BindedField
 *
 * @TODO: move out of extux into Garp
 */

Ext.ns('Ext.ux');

Ext.ux.RelationField = Ext.extend(Ext.form.ComboBox,{
	/**
	 * @cfg model
	 */
	model: null,
	allowBlank: false,
	autoLoad: true,
	editable: false,
	triggerAction: 'all',
	typeAhead: false,
	mode: 'remote',
	valueField: 'id',
	displayField: 'name',
	disableCreate: false,
	emptyText: __('(empty)'),
	trigger1Class: 'relation-open-button',
	cls: 'relation-field',
	allQuery: '',
	lastQuery: '',
	baseParams: {
		start: 0,
		limit: Garp.pageSize
	},

	/**
	 * Gets called when the RelationPickerWindow is closed;
	 * @param {Object} selected
	 */
	selectCallback: function(selected){
		var v = this.getValue();
		if (selected && selected.selected) {

			// set it to the value retrieved from the modelpicker:
			this.setValue(selected.selected.get(this.displayField) || selected.selected.get('id'));
			this.disable();
			// ...then reload to get the 'real' value from the server:
			// the reason we reload, is that the displayfield might not get passed from the picker (it just might not be there)

			this.store.on({
				load: function(){
					this.setValue(selected.selected.get('id'));
					this.enable();
					if (this.assertValue) {
						this.assertValue();
					}
					this.el.focus(true);
					this.collapse.defer(200, this);
				},
				single: true,
				scope: this
			});
			this.store.load();
		} else {
			this.setValue(null);
		}
		this.originalValue = v;
		this.win.destroy();

		this.fireEvent('select', selected);
	},

	triggerFn: function(){
		this.win = new Garp.ModelPickerWindow({
			model: this.model,
			query: this.allQuery || null,
			allowBlank: this.allowBlank
		});
		this.win.on('select', this.selectCallback, this);
		this.win.on('close', this.selectCallback, this);
		this.win.show();
	},

	createRelationUrl: function(){
		if (this.getValue()) {
			return BASE + 'admin?' +
			Ext.urlEncode({
				model: this.model,
				id: this.getValue()
			});
		}
		return false;
	},

	onRelationOpenTriggerClick: function(){
		if (this.tip) {
			if (this.tip.isVisible()) {
				this.tip.hide();
			} else {
				this.tip.show();
			}
		} else {
			var url = this.createRelationUrl();
			if (url) {
				var win = window.open(url);
			}
		}
	},

	// override, to allow for null values (or "unrelate", so to say)
	getValue: function(){
		var value = Ext.ux.RelationField.superclass.getValue.call(this);
		if(value === '' || value == this.emptyText){
			value = null;
		}
		return value;
	},

	setValue: function(v){
		this.el.removeClass('x-form-invalid');
		if (v) {
			if (this.store.find('id', v) < 0) {
				this.disable();
				this.el.addClass('loading');
				this.store.load({
					add: true,
					single: true,
					params: {
						query: {
							'id': v
						}
					},
					callback: function(data, opts, success){
						this.enable();
						this.el.removeClass('loading');
						if (data && data.length) {
							this.assertValue();
							this.collapse();
						} else {
							// value not found! DB Integrity issue!
							this.store.clearFilter();
							this.el.addClass('x-form-invalid');
						}
					},
					scope: this
				});
			}
		}
		if(v){
			this.getTrigger(0).removeClass('no-relation');
		} else {
			this.getTrigger(0).addClass('no-relation');
		}
		Ext.ux.RelationField.superclass.setValue.call(this, v);
	},

	initComponent: function(){
		this.store = new Ext.data.DirectStore({
			autoLoad: false,
			autoSave: false,
			pruneModifiedRecords: true,
			remoteSort: true,
			restful: true,
			autoDestroy: true,
			root: 'rows',
			idProperty: 'id',
			fields: Garp.dataTypes[this.model] ? Garp.dataTypes[this.model].getStoreFieldsFromColumnModel() : [],
			totalProperty: 'total',
			sortInfo: Garp.dataTypes[this.model] && Garp.dataTypes[this.model].sortInfo ? Garp.dataTypes[this.model].sortInfo : null,
			baseParams: this.baseParams,
			api: {
				create: Ext.emptyFn,
				read: Garp[this.model].fetch || Ext.emptyFn,
				update: Ext.emptyFn,
				destroy: Ext.emptyFn
			}
		});

		if (this.triggerFn) {
			this.onTriggerClick = this.triggerFn;
		}

		this.store.on({
			'load': {
				single: true,
				scope: this,
				fn: function(){
					var clear = {
						id: 0
					};
					clear[this.displayField] = this.emptyText;
					var rec = new this.store.recordType(clear, 0);
					this.store.add(rec);
					this.assertValue();
				}
			}
		});
		Ext.ux.RelationField.superclass.initComponent.call(this);
		this.triggerConfig = {
			tag: 'span',
			cls: 'x-form-twin-triggers',
			cn: [{
				tag: "img",
				src: Ext.BLANK_IMAGE_URL,
				alt: '',
				title: __('Open in new window'),
				cls: "x-form-trigger " + this.trigger1Class
			}, {
				tag: "img",
				src: Ext.BLANK_IMAGE_URL,
				alt: "",
				cls: "x-form-trigger " + this.trigger2Class
			}]
		};
		if (!Garp.dataTypes[this.model]) {
			this.disable();
		}

	},

	getTrigger : function(index){
        return this.triggers[index];
    },

    afterRender: function(){
		Ext.form.TwinTriggerField.superclass.afterRender.call(this);
		var triggers = this.triggers, i = 0, len = triggers.length;

		for (; i < len; ++i) {
			if (this['hideTrigger' + (i + 1)]) {
				triggers[i].hide();
			}
		}

		if (Garp.dataTypes[this.model].previewItems && Garp.dataTypes[this.model].previewItems.length) {
			this.tip = new Ext.ToolTip({
				target: this.el,
				html: '',
				anchor: 'top',
				anchorOffset: 5,
				closable: true,
				showDelay: 1000,
				hideDelay: 3500,
				listeners: {
					'show': function(tip){
						var idx = this.store.find('id', this.getValue());
						var items = Garp.dataTypes[this.model].previewItems;
						if (idx > -1) {
							var data = this.store.getAt(idx).data;
							var str = '
';
							Ext.each(items, function(item){
								var value = data[item] || '';
								str += '
' + __(item) + ': ' + value + '
';
							});
							str += '
';
							str += '' + __('Open in new window') + '';
							tip.update(str);
						} else {
							tip.hide();
						}
					},
					scope: this
				},
				scope: this
			});
		}
	},

    initTrigger : function(){
        var ts = this.trigger.select('.x-form-trigger', true),
            triggerField = this;

        ts.each(function(t, all, index){
            var triggerIndex = 'Trigger'+(index+1);
            t.hide = function(){
                var w = triggerField.wrap.getWidth();
                this.dom.style.display = 'none';
                triggerField.el.setWidth(w-triggerField.trigger.getWidth());
                triggerField['hidden' + triggerIndex] = true;
            };
            t.show = function(){
                var w = triggerField.wrap.getWidth();
                this.dom.style.display = '';
                triggerField.el.setWidth(w-triggerField.trigger.getWidth());
                triggerField['hidden' + triggerIndex] = false;
            };
			if (index === 0) {
				this.mon(t, 'click', this.onRelationOpenTriggerClick, this, {
					preventDefault: true
				});
			} else {
				this.mon(t, 'click', this.onTriggerClick, this, {
					preventDefault: true
				});
			}
            t.addClassOnOver('x-form-trigger-over');
            t.addClassOnClick('x-form-trigger-click');
        }, this);
        this.triggers = ts.elements;
    },

    getTriggerWidth: function(){
        var tw = 0;
        Ext.each(this.triggers, function(t, index){
            var triggerIndex = 'Trigger' + (index + 1),
                w = t.getWidth();
            if(w === 0 && !this['hidden' + triggerIndex]){
                tw += this.defaultTriggerWidth;
            }else{
                tw += w;
            }
        }, this);
        return tw;
    },

    // private
    onDestroy : function() {
        Ext.destroy(this.triggers);
        Ext.form.TwinTriggerField.superclass.onDestroy.call(this);
    },

	/**
     * The function that should handle the trigger's click event.  This method does nothing by default
     * until overridden by an implementing function. See {@link Ext.form.TriggerField#onTriggerClick}
     * for additional information.
     * @method
     * @param {EventObject} e
     */
    onTrigger1Click : Ext.emptyFn,
    /**
     * The function that should handle the trigger's click event.  This method does nothing by default
     * until overridden by an implementing function. See {@link Ext.form.TriggerField#onTriggerClick}
     * for additional information.
     * @method
     * @param {EventObject} e
     */
    onTrigger2Click : Ext.emptyFn

});

/**
 * Joined RelationFields are pre-filled relations sent from the server. If we want to change it,
 * we need to update the bindedField referenced field. The server then joins again.
 * Untill then, we use the referenced model's displayFieldRederer
 */
Ext.ux.JoinedRelationField = Ext.extend(Ext.ux.RelationField, {

	bindedField: null,

	selectCallback: function(selected){
		if(selected && selected.selected){
			var val = selected.selected.get('id');
			var disp = Garp.dataTypes[this.model].displayFieldRenderer(selected.selected);
			this.form.findField(this.bindedField).setValue(val);
			this.setValue(disp);
			this.fireEvent('select', selected);
		}
	},

	// joinedRelation saves data in it's bindedField. The field itself must not get send!
	isDirty: function(){
		return false;
	},

	// joinedRelation saves data in it's bindedField. The field itself must not get send!
	getValue: function(){
		return null;
	},

	createRelationUrl: function(){
		var id = this.form.findField(this.bindedField).getValue();
		if (id) {
			return BASE + 'admin?' +
			Ext.urlEncode({
				model: this.model,
				id: id
			});
		}
		return false;
	},

	setValue: function(v){
		Ext.ux.RelationField.superclass.setValue.call(this, v);
		return this;
	}
});

/**
 * Glue component; this field holds the ID value for a joinedRelationField
 */
Ext.ux.BindedField = Ext.extend(Ext.form.TextField, {
	bindedField: null,
	initComponent: function(v){
		this.hidden = true;
		this.hideFieldLabel = true;
		Ext.ux.BindedField.superclass.initComponent.call(this, v);
	}
});

// xtypes
Ext.reg('relationfield', Ext.ux.RelationField);
Ext.reg('joinedrelationfield', Ext.ux.JoinedRelationField);
Ext.reg('bindedfield', Ext.ux.BindedField);
<commit_msg>Ticket #881 Video relationfield selection clear<commit_after>/**
 * Ext.ux
 * RelationField / JoinedRelationField / BindedField
 *
 * @TODO: move out of extux into Garp
 */

Ext.ns('Ext.ux');

Ext.ux.RelationField = Ext.extend(Ext.form.ComboBox,{
	/**
	 * @cfg model
	 */
	model: null,
	allowBlank: false,
	autoLoad: true,
	editable: false,
	triggerAction: 'all',
	typeAhead: false,
	mode: 'remote',
	valueField: 'id',
	displayField: 'name',
	disableCreate: false,
	emptyText: __('(empty)'),
	trigger1Class: 'relation-open-button',
	cls: 'relation-field',
	allQuery: '',
	lastQuery: '',
	baseParams: {
		start: 0,
		limit: Garp.pageSize
	},

	/**
	 * Gets called when the RelationPickerWindow is closed;
	 * @param {Object} selected
	 */
	selectCallback: function(selected){
		var v = this.getValue();
		if (selected && selected.selected) {

			// set it to the value retrieved from the modelpicker:
			this.setValue(selected.selected.get(this.displayField) || selected.selected.get('id'));
			this.disable();
			// ...then reload to get the 'real' value from the server:
			// the reason we reload, is that the displayfield might not get passed from the picker (it just might not be there)

			this.store.on({
				load: function(){
					this.setValue(selected.selected.get('id'));
					this.enable();
					if (this.assertValue) {
						this.assertValue();
					}
					this.el.focus(true);
					this.collapse.defer(200, this);
				},
				single: true,
				scope: this
			});
			this.store.load();
		} else {
			this.setValue(null);
		}
		this.originalValue = v;
		this.win.destroy();

		this.fireEvent('select', selected);
	},

	triggerFn: function(){
		this.win = new Garp.ModelPickerWindow({
			model: this.model,
			query: this.allQuery || null,
			allowBlank: this.allowBlank
		});
		this.win.on('select', this.selectCallback, this);
		this.win.on('close', this.selectCallback, this);
		this.win.show();
	},

	createRelationUrl: function(){
		if (this.getValue()) {
			return BASE + 'admin?' +
			Ext.urlEncode({
				model: this.model,
				id: this.getValue()
			});
		}
		return false;
	},

	onRelationOpenTriggerClick: function(){
		if (this.tip) {
			if (this.tip.isVisible()) {
				this.tip.hide();
			} else {
				this.tip.show();
			}
		} else {
			var url = this.createRelationUrl();
			if (url) {
				var win = window.open(url);
			}
		}
	},

	// override, to allow for null values (or "unrelate", so to say)
	getValue: function(){
		var value = Ext.ux.RelationField.superclass.getValue.call(this);
		if(value === '' || value == this.emptyText){
			value = null;
		}
		return value;
	},

	setValue: function(v){
		this.el.removeClass('x-form-invalid');
		if (v) {
			if (this.store.find('id', v) < 0) {
				this.disable();
				this.el.addClass('loading');
				this.store.load({
					add: true,
					single: true,
					params: {
						query: {
							'id': v
						}
					},
					callback: function(data, opts, success){
						this.enable();
						this.el.removeClass('loading');
						if (data && data.length) {
							this.assertValue();
							this.collapse();
						} else {
							// value not found! DB Integrity issue!
							this.store.clearFilter();
							this.el.addClass('x-form-invalid');
						}
					},
					scope: this
				});
			}
		}
		if(v){
			this.getTrigger(0).removeClass('no-relation');
		} else {
			this.getTrigger(0).addClass('no-relation');
		}
		Ext.ux.RelationField.superclass.setValue.call(this, v);
	},

	initComponent: function(){
		this.store = new Ext.data.DirectStore({
			autoLoad: false,
			autoSave: false,
			pruneModifiedRecords: true,
			remoteSort: true,
			restful: true,
			autoDestroy: true,
			root: 'rows',
			idProperty: 'id',
			fields: Garp.dataTypes[this.model] ? Garp.dataTypes[this.model].getStoreFieldsFromColumnModel() : [],
			totalProperty: 'total',
			sortInfo: Garp.dataTypes[this.model] && Garp.dataTypes[this.model].sortInfo ? Garp.dataTypes[this.model].sortInfo : null,
			baseParams: this.baseParams,
			api: {
				create: Ext.emptyFn,
				read: Garp[this.model].fetch || Ext.emptyFn,
				update: Ext.emptyFn,
				destroy: Ext.emptyFn
			}
		});

		if (this.triggerFn) {
			this.onTriggerClick = this.triggerFn;
		}

		this.store.on({
			'load': {
				single: true,
				scope: this,
				fn: function(){
					var clear = {
						id: 0
					};
					clear[this.displayField] = this.emptyText;
					var rec = new this.store.recordType(clear, 0);
					this.store.add(rec);
					this.assertValue();
				}
			}
		});
		Ext.ux.RelationField.superclass.initComponent.call(this);
		this.triggerConfig = {
			tag: 'span',
			cls: 'x-form-twin-triggers',
			cn: [{
				tag: "img",
				src: Ext.BLANK_IMAGE_URL,
				alt: '',
				title: __('Open in new window'),
				cls: "x-form-trigger " + this.trigger1Class
			}, {
				tag: "img",
				src: Ext.BLANK_IMAGE_URL,
				alt: "",
				cls: "x-form-trigger " + this.trigger2Class
			}]
		};
		if (!Garp.dataTypes[this.model]) {
			this.disable();
		}

	},

	getTrigger : function(index){
        return this.triggers[index];
    },

    afterRender: function(){
		Ext.form.TwinTriggerField.superclass.afterRender.call(this);
		var triggers = this.triggers, i = 0, len = triggers.length;

		for (; i < len; ++i) {
			if (this['hideTrigger' + (i + 1)]) {
				triggers[i].hide();
			}
		}

		if (Garp.dataTypes[this.model].previewItems && Garp.dataTypes[this.model].previewItems.length) {
			this.tip = new Ext.ToolTip({
				target: this.el,
				html: '',
				anchor: 'top',
				anchorOffset: 5,
				closable: true,
				showDelay: 1000,
				hideDelay: 3500,
				listeners: {
					'show': function(tip){
						var idx = this.store.find('id', this.getValue());
						var items = Garp.dataTypes[this.model].previewItems;
						if (idx > -1) {
							var data = this.store.getAt(idx).data;
							var str = '
';
							Ext.each(items, function(item){
								var value = data[item] || '';
								str += '
' + __(item) + ': ' + value + '
';
							});
							str += '
';
							str += '' + __('Open in new window') + '';
							tip.update(str);
						} else {
							tip.hide();
						}
					},
					scope: this
				},
				scope: this
			});
		}
	},

    initTrigger : function(){
        var ts = this.trigger.select('.x-form-trigger', true),
            triggerField = this;

        ts.each(function(t, all, index){
            var triggerIndex = 'Trigger'+(index+1);
            t.hide = function(){
                var w = triggerField.wrap.getWidth();
                this.dom.style.display = 'none';
                triggerField.el.setWidth(w-triggerField.trigger.getWidth());
                triggerField['hidden' + triggerIndex] = true;
            };
            t.show = function(){
                var w = triggerField.wrap.getWidth();
                this.dom.style.display = '';
                triggerField.el.setWidth(w-triggerField.trigger.getWidth());
                triggerField['hidden' + triggerIndex] = false;
            };
			if (index === 0) {
				this.mon(t, 'click', this.onRelationOpenTriggerClick, this, {
					preventDefault: true
				});
			} else {
				this.mon(t, 'click', this.onTriggerClick, this, {
					preventDefault: true
				});
			}
            t.addClassOnOver('x-form-trigger-over');
            t.addClassOnClick('x-form-trigger-click');
        }, this);
        this.triggers = ts.elements;
    },

    getTriggerWidth: function(){
        var tw = 0;
        Ext.each(this.triggers, function(t, index){
            var triggerIndex = 'Trigger' + (index + 1),
                w = t.getWidth();
            if(w === 0 && !this['hidden' + triggerIndex]){
                tw += this.defaultTriggerWidth;
            }else{
                tw += w;
            }
        }, this);
        return tw;
    },

    // private
    onDestroy : function() {
        Ext.destroy(this.triggers);
        Ext.form.TwinTriggerField.superclass.onDestroy.call(this);
    },

	/**
     * The function that should handle the trigger's click event.  This method does nothing by default
     * until overridden by an implementing function. See {@link Ext.form.TriggerField#onTriggerClick}
     * for additional information.
     * @method
     * @param {EventObject} e
     */
    onTrigger1Click : Ext.emptyFn,
    /**
     * The function that should handle the trigger's click event.  This method does nothing by default
     * until overridden by an implementing function. See {@link Ext.form.TriggerField#onTriggerClick}
     * for additional information.
     * @method
     * @param {EventObject} e
     */
    onTrigger2Click : Ext.emptyFn

});

/**
 * Joined RelationFields are pre-filled relations sent from the server. If we want to change it,
 * we need to update the bindedField referenced field. The server then joins again.
 * Untill then, we use the referenced model's displayFieldRederer
 */
Ext.ux.JoinedRelationField = Ext.extend(Ext.ux.RelationField, {

	bindedField: null,

	selectCallback: function(selected){
		var val, disp;
		if(selected && typeof selected.selected !== 'undefined'){
			if (selected.selected) {
				val = selected.selected.get('id');
				disp = Garp.dataTypes[this.model].displayFieldRenderer(selected.selected);
			} else{
				val = null;
				disp = null;
			}
			this.form.findField(this.bindedField).setValue(val);
			this.setValue(disp);
			this.fireEvent('select', selected);
		}
	},

	// joinedRelation saves data in it's bindedField. The field itself must not get send!
	isDirty: function(){
		return false;
	},

	// joinedRelation saves data in it's bindedField. The field itself must not get send!
	getValue: function(){
		return null;
	},

	createRelationUrl: function(){
		var id = this.form.findField(this.bindedField).getValue();
		if (id) {
			return BASE + 'admin?' +
			Ext.urlEncode({
				model: this.model,
				id: id
			});
		}
		return false;
	},

	setValue: function(v){
		Ext.ux.RelationField.superclass.setValue.call(this, v);
		return this;
	}
});

/**
 * Glue component; this field holds the ID value for a joinedRelationField
 */
Ext.ux.BindedField = Ext.extend(Ext.form.TextField, {
	bindedField: null,
	initComponent: function(v){
		this.hidden = true;
		this.hideFieldLabel = true;
		Ext.ux.BindedField.superclass.initComponent.call(this, v);
	}
});

// xtypes
Ext.reg('relationfield', Ext.ux.RelationField);
Ext.reg('joinedrelationfield', Ext.ux.JoinedRelationField);
Ext.reg('bindedfield', Ext.ux.BindedField);
<|endoftext|>


<commit_before>'use strict';
var MACROUTILS = require( 'osg/Utils' );
var Object = require( 'osg/Object' );
var BoundingBox = require( 'osg/BoundingBox' );
var BoundingSphere = require( 'osg/BoundingSphere' );
var StateSet = require( 'osg/StateSet' );
var NodeVisitor = require( 'osg/NodeVisitor' );
var Notify = require( 'osg/Notify' );
var Matrix = require( 'osg/Matrix' );
var MatrixMemoryPool = require( 'osg/MatrixMemoryPool' );
var ComputeMatrixFromNodePath = require( 'osg/ComputeMatrixFromNodePath' );
var TransformEnums = require( 'osg/TransformEnums' );

/**
 *  Node that can contains child node
 *  @class Node
 */
var Node = function () {
    Object.call( this );

    this.children = [];
    this._parents = [];
    /*jshint bitwise: false */
    this.nodeMask = ~0;
    /*jshint bitwise: true */

    this._boundingSphere = new BoundingSphere();
    this._boundingSphereComputed = false;

    this._boundingBox = new BoundingBox();
    this._boundingBoxComputed = false;

    this._updateCallbacks = [];
    this._cullCallback = undefined;
    this._cullingActive = true;
    this._numChildrenWithCullingDisabled = 0;

    this._numChildrenRequiringUpdateTraversal = 0;

    // it's a tmp object for internal use, do not use
    this._tmpBox = new BoundingBox();
};

Node._reservedMatrixStack = new MatrixMemoryPool();
var nodeGetMat = function () {
    var mat = Node._reservedMatrixStack.get.bind( Node._reservedMatrixStack );
    return Matrix.makeIdentity( mat );
};

/** @lends Node.prototype */
Node.prototype = MACROUTILS.objectLibraryClass( MACROUTILS.objectInherit( Object.prototype, {

    /**
      Return StateSet and create it if it does not exist yet
      @type StateSet
   */
    getOrCreateStateSet: function () {
        if ( !this.stateset ) this.setStateSet( new StateSet() );
        return this.stateset;
    },

    getStateSet: function () {
        return this.stateset;
    },

    accept: function ( nv ) {
        if ( nv.validNodeMask( this ) ) {
            nv.pushOntoNodePath( this );
            nv.apply( this );
            nv.popFromNodePath();
        }
    },

    dirtyBound: function () {
        if ( this._boundingSphereComputed === true || this._boundingBoxComputed === true ) {
            this._boundingSphereComputed = false;
            this._boundingBoxComputed = false;
            var parents = this._parents;
            for ( var i = 0, l = parents.length; i < l; i++ ) {
                parents[ i ].dirtyBound();
            }
        }
    },

    setNodeMask: function ( mask ) {
        this.nodeMask = mask;
    },

    getNodeMask: function () {
        return this.nodeMask;
    },

    setStateSet: function ( stateSet ) {

        if ( this.stateset === stateSet ) return;

        var deltaUpdate = 0;

        if ( this.stateset ) {
            deltaUpdate--;
            this.stateset.removeParent( this );
        }

        if ( stateSet ) {
            stateSet.addParent( this );
            if ( stateSet.requiresUpdateTraversal() ) ++deltaUpdate;
        }

        if ( deltaUpdate !== 0 )
            this.setNumChildrenRequiringUpdateTraversal( this.getNumChildrenRequiringUpdateTraversal() + deltaUpdate );

        this.stateset = stateSet;
    },

    _updateNumChildrenRequeringUpdateTraversal: function ( delta ) {

        if ( this._numChildrenRequiringUpdateTraversal === 0 && this._parents.length ) {
            // the number of callbacks has changed, need to pass this
            // on to parents so they know whether app traversal is
            // required on this subgraph.
            for ( var i = 0, l = this._parents.length; i < l; i++ ) {
                var parent = this._parents[ i ];
                var num = parent.getNumChildrenRequiringUpdateTraversal();
                parent.setNumChildrenRequiringUpdateTraversal( num + delta );
            }
        }
    },

    setNumChildrenRequiringUpdateTraversal: function ( num ) {

        // if no changes just return.
        if ( this._numChildrenRequiringUpdateTraversal === num ) return;

        // note, if _updateCallback is set then the
        // parents won't be affected by any changes to
        // _numChildrenRequiringUpdateTraversal so no need to inform them.
        if ( !this._updateCallbacks.length && this._parents.length ) {

            // need to pass on changes to parents.
            var delta = 0;
            if ( this._numChildrenRequiringUpdateTraversal > 0 ) --delta;
            if ( num > 0 ) ++delta;

            if ( delta !== 0 ) {
                // the number of callbacks has changed, need to pass this
                // on to parents so they know whether app traversal is
                // required on this subgraph.
                var parents = this._parents;
                for ( var i = 0, l = parents.length; i < l; i++ ) {
                    var parent = parents[ i ];
                    var parentNum = parent.getNumChildrenRequiringUpdateTraversal();
                    parent.setNumChildrenRequiringUpdateTraversal( parentNum + delta );
                }
            }
        }

        // finally update this objects value.
        this._numChildrenRequiringUpdateTraversal = num;

    },

    getNumChildrenRequiringUpdateTraversal: function () {
        return this._numChildrenRequiringUpdateTraversal;
    },

    /**

      Set update node callback, called during update traversal.
      The Object must have the following method
      update(node, nodeVisitor) {}
      note, callback is responsible for scenegraph traversal so
      they must call traverse(node,nv) to ensure that the
      scene graph subtree (and associated callbacks) are traversed.

      Here a dummy UpdateCallback example

      @example
      var DummyUpdateCallback = function() {};
      DummyUpdateCallback.prototype = {
          update: function(node, nodeVisitor) {
              return true;
          }
      };

      @param Oject callback
   */
    setUpdateCallback: function ( cb ) {
        Notify.warn( 'deprecated use instead addUpdateCallback/removeUpdateCallback' );
        if ( cb === this._updateCallbacks[ 0 ] || !cb ) return;

        var hasExistingCallback = Boolean( this._updateCallbacks.length );
        if ( !this._updateCallbacks.length )
            this.addUpdateCallback( cb );
        else
            this._updateCallbacks[ 0 ] = cb;

        if ( !hasExistingCallback )
            this._updateNumChildrenRequeringUpdateTraversal( 1 );
    },

    /** Get update node callback, called during update traversal.
      @type Oject
   */
    getUpdateCallback: function () {
        return this._updateCallbacks[ 0 ];
    },

    addUpdateCallback: function ( cb ) {
        var hasExistingCallback = Boolean( this._updateCallbacks.length );
        this._updateCallbacks.push( cb );

        // send the signal on first add
        if ( !hasExistingCallback )
            this._updateNumChildrenRequeringUpdateTraversal( 1 );
    },

    removeUpdateCallback: function ( cb ) {
        var arrayIdx = this._updateCallbacks.indexOf( cb );
        if ( arrayIdx === -1 ) return;
        this._updateCallbacks.splice( arrayIdx, 1 );

        // send the signal when no more callback
        if ( !this._updateCallbacks.length )
            this._updateNumChildrenRequeringUpdateTraversal( -1 );

    },
    getUpdateCallbackList: function () {
        return this._updateCallbacks;
    },

    /**

      Set cull node callback, called during cull traversal.
      The Object must have the following method
      cull(node, nodeVisitor) {}
      note, callback is responsible for scenegraph traversal so
      they must return true to traverse.

      Here a dummy CullCallback example

      @example
      var DummyCullCallback = function() {};
      DummyCullCallback.prototype = {
          cull: function(node, nodeVisitor) {
              return true;
          }
      };

      @param Oject callback
   */
    setCullCallback: function ( cb ) {
        this._cullCallback = cb;
    },
    getCullCallback: function () {
        return this._cullCallback;
    },

    hasChild: function ( child ) {
        for ( var i = 0, l = this.children.length; i < l; i++ ) {
            if ( this.children[ i ] === child ) {
                return true;
            }
        }
        return false;
    },

    addChild: function ( child ) {

        if ( this.children.indexOf( child ) !== -1 ) return undefined;

        this.children.push( child );
        child.addParent( this );
        this.dirtyBound();

        // could now require app traversal thanks to the new subgraph,
        // so need to check and update if required.
        if ( child.getNumChildrenRequiringUpdateTraversal() > 0 ||
            child.getUpdateCallbackList().length ) {
            this.setNumChildrenRequiringUpdateTraversal(
                this.getNumChildrenRequiringUpdateTraversal() + 1
            );
        }

        return child;
    },

    getChildren: function () {
        return this.children;
    },
    getNumChildren: function () {
        return this.children.length;
    },
    getChild: function ( num ) {
        return this.children[ num ];
    },
    getParents: function () {
        return this._parents;
    },

    addParent: function ( parent ) {
        this._parents.push( parent );
    },

    removeParent: function ( parent ) {
        var idx = this._parents.indexOf( parent );
        if ( idx === -1 ) return;
        this._parents.splice( idx, 1 );
    },

    removeChildren: function () {
        var children = this.children;
        var nbChildren = children.length;
        if ( !nbChildren ) return;

        var updateCallbackRemoved = 0;

        for ( var i = 0; i < nbChildren; i++ ) {
            var child = children[ i ];
            child.removeParent( this );
            if ( child.getNumChildrenRequiringUpdateTraversal() > 0 || child.getUpdateCallbackList().length ) ++updateCallbackRemoved;
        }

        children.length = 0;
        if ( updateCallbackRemoved )
            this.setNumChildrenRequiringUpdateTraversal( this.getNumChildrenRequiringUpdateTraversal() - updateCallbackRemoved );

        this.dirtyBound();
    },

    // preserve order
    removeChild: function ( child ) {

        var children = this.children;
        for ( var i = 0, l = children.length; i < l; i++ ) {
            if ( children[ i ] === child ) {
                child.removeParent( this );
                children.splice( i, 1 );
                i--;
                l--;

                if ( child.getNumChildrenRequiringUpdateTraversal() > 0 || child.getUpdateCallbackList().length )
                    this.setNumChildrenRequiringUpdateTraversal( this.getNumChildrenRequiringUpdateTraversal() - 1 );

                return;
            }
        }

    },

    traverse: function ( visitor ) {
        var children = this.children;
        for ( var i = 0, l = children.length; i < l; i++ ) {
            var child = children[ i ];
            child.accept( visitor );
        }
    },

    ascend: function ( visitor ) {
        var parents = this._parents;
        for ( var i = 0, l = parents.length; i < l; i++ ) {
            var parent = parents[ i ];
            parent.accept( visitor );
        }
    },

    getBoundingBox: function () {
        if ( !this._boundingBoxComputed ) {
            this.computeBoundingBox( this._boundingBox );
            this._boundingBoxComputed = true;
        }
        return this._boundingBox;
    },

    computeBoundingBox: function ( bbox ) {

        // circular dependency... not sure if the global visitor instance should be instancied here
        var ComputeBoundsVisitor = require( 'osg/ComputeBoundsVisitor' );
        var cbv = ComputeBoundsVisitor.instance = ComputeBoundsVisitor.instance || new ComputeBoundsVisitor();
        cbv.setNodeMaskOverride( ~0x0 ); // traverse everything to be consistent with computeBoundingSphere
        cbv.reset();

        cbv.apply( this );
        bbox.copy( cbv.getBoundingBox() );
        return bbox;
    },

    getBoundingSphere: function () {
        return this.getBound();
    },

    getBound: function () {
        if ( !this._boundingSphereComputed ) {
            this.computeBoundingSphere( this._boundingSphere );
            this._boundingSphereComputed = true;
        }
        return this._boundingSphere;
    },

    computeBoundingSphere: function ( bSphere ) {

        var children = this.children;
        var l = children.length;

        bSphere.init();
        if ( l === 0 ) return bSphere;

        var cc, i;
        var bb = this._tmpBox;
        bb.init();
        for ( i = 0; i < l; i++ ) {
            cc = children[ i ];
            if ( cc.referenceFrame !== TransformEnums.ABSOLUTE_RF ) {
                bb.expandByBoundingSphere( cc.getBound() );
            }
        }
        if ( !bb.valid() ) return bSphere;

        bSphere.set( bb.center( bSphere.center() ), 0.0 );
        for ( i = 0; i < l; i++ ) {
            cc = children[ i ];
            if ( cc.referenceFrame !== TransformEnums.ABSOLUTE_RF ) {
                bSphere.expandRadiusBySphere( cc.getBound() );
            }
        }
        return bSphere;
    },

    // matrixCreate allow user handling of garbage collection of matrices
    getWorldMatrices: ( function () {
        var CollectParentPaths = function () {
            this.nodePaths = [];
            this.halt = undefined;
            NodeVisitor.call( this, NodeVisitor.TRAVERSE_PARENTS );
        };
        CollectParentPaths.prototype = MACROUTILS.objectInherit( NodeVisitor.prototype, {
            reset: function () {
                this.nodePath.length = 0;
                this.nodePaths.length = 0;
            },
            apply: function ( node ) {
                if ( node._parents.length === 0 || node === this.halt || node.referenceFrame === TransformEnums.ABSOLUTE_RF ) {
                    // copy
                    this.nodePaths.push( this.nodePath.slice( 0 ) );
                } else {
                    this.traverse( node );
                }
            }
        } );
        var collected = new CollectParentPaths();
        collected.setNodeMaskOverride( ~0x0 ); // traverse everything

        return function computeLocalToWorldList( halt, matrixCreate ) {
            collected.reset();
            collected.halt = halt;

            this.accept( collected );
            var matrixList = [];

            var matrixGenerator = matrixCreate || Matrix.create;
            for ( var i = 0, l = collected.nodePaths.length; i < l; i++ ) {
                var np = collected.nodePaths[ i ];
                var m = matrixGenerator();
                if ( np.length !== 0 ) {
                    ComputeMatrixFromNodePath.computeLocalToWorld( np, true, m );
                }
                matrixList.push( m );
            }

            return matrixList;
        };

    } )(),

    // same as getWorldMatrices GC: Perf WIN
    getWorldMatrix: function ( halt, matrix ) {

        // pass allocator on master
        var matrixList = this.getWorldMatrices( halt, nodeGetMat );

        if ( matrixList.length === 0 ) {

            Matrix.makeIdentity( matrix );

        } else {

            Matrix.copy( matrixList[ 0 ], matrix );

        }

        Node._reservedMatrixStack.reset();
        return matrix;

    },

    setCullingActive: function ( value ) {
        if ( this._cullingActive === value ) return;
        if ( this._numChildrenWithCullingDisabled === 0 && this._parents.length > 0 ) {
            var delta = 0;
            if ( !this._cullingActive ) --delta;
            if ( !value ) ++delta;
            if ( delta !== 0 ) {
                for ( var i = 0, k = this._parents.length; i < k; i++ ) {
                    this._parents[ i ].setNumChildrenWithCullingDisabled( this._parents[ i ].getNumChildrenWithCullingDisabled() + delta );
                }
            }
        }
        this._cullingActive = value;
    },

    getCullingActive: function () {
        return this._cullingActive;
    },

    isCullingActive: function () {
        return this._numChildrenWithCullingDisabled === 0 && this._cullingActive && this.getBound().valid();
    },

    setNumChildrenWithCullingDisabled: function ( num ) {
        if ( this._numChildrenWithCullingDisabled === num ) return;
        if ( this._cullingActive && this._parents.length > 0 ) {
            var delta = 0;
            if ( this._numChildrenWithCullingDisabled > 0 ) --delta;
            if ( num > 0 ) ++delta;
            if ( delta !== 0 ) {
                for ( var i = 0, k = this._parents.length; i < k; i++ ) {
                    this._parents[ i ].setNumChildrenWithCullingDisabled( this._parents[ i ].getNumChildrenWithCullingDisabled() + delta );
                }
            }
        }
        this._numChildrenWithCullingDisabled = num;
    },

    getNumChildrenWithCullingDisabled: function () {
        return this._numChildrenWithCullingDisabled;
    },

    releaseGLObjects: function () {
        if ( this.stateset !== undefined ) this.stateset.releaseGLObjects();
    }

} ), 'osg', 'Node' );
MACROUTILS.setTypeID( Node );

module.exports = Node;
<commit_msg>Fixes cache update when setting new stateset<commit_after>'use strict';
var MACROUTILS = require( 'osg/Utils' );
var Object = require( 'osg/Object' );
var BoundingBox = require( 'osg/BoundingBox' );
var BoundingSphere = require( 'osg/BoundingSphere' );
var StateSet = require( 'osg/StateSet' );
var NodeVisitor = require( 'osg/NodeVisitor' );
var Notify = require( 'osg/Notify' );
var Matrix = require( 'osg/Matrix' );
var MatrixMemoryPool = require( 'osg/MatrixMemoryPool' );
var ComputeMatrixFromNodePath = require( 'osg/ComputeMatrixFromNodePath' );
var TransformEnums = require( 'osg/TransformEnums' );

/**
 *  Node that can contains child node
 *  @class Node
 */
var Node = function () {
    Object.call( this );

    this.children = [];
    this._parents = [];
    /*jshint bitwise: false */
    this.nodeMask = ~0;
    /*jshint bitwise: true */

    this._boundingSphere = new BoundingSphere();
    this._boundingSphereComputed = false;

    this._boundingBox = new BoundingBox();
    this._boundingBoxComputed = false;

    this._updateCallbacks = [];
    this._cullCallback = undefined;
    this._cullingActive = true;
    this._numChildrenWithCullingDisabled = 0;

    this._numChildrenRequiringUpdateTraversal = 0;

    // it's a tmp object for internal use, do not use
    this._tmpBox = new BoundingBox();
};

Node._reservedMatrixStack = new MatrixMemoryPool();
var nodeGetMat = function () {
    var mat = Node._reservedMatrixStack.get.bind( Node._reservedMatrixStack );
    return Matrix.makeIdentity( mat );
};

/** @lends Node.prototype */
Node.prototype = MACROUTILS.objectLibraryClass( MACROUTILS.objectInherit( Object.prototype, {

    /**
      Return StateSet and create it if it does not exist yet
      @type StateSet
   */
    getOrCreateStateSet: function () {
        if ( !this.stateset ) this.setStateSet( new StateSet() );
        return this.stateset;
    },

    getStateSet: function () {
        return this.stateset;
    },

    accept: function ( nv ) {
        if ( nv.validNodeMask( this ) ) {
            nv.pushOntoNodePath( this );
            nv.apply( this );
            nv.popFromNodePath();
        }
    },

    dirtyBound: function () {
        if ( this._boundingSphereComputed === true || this._boundingBoxComputed === true ) {
            this._boundingSphereComputed = false;
            this._boundingBoxComputed = false;
            var parents = this._parents;
            for ( var i = 0, l = parents.length; i < l; i++ ) {
                parents[ i ].dirtyBound();
            }
        }
    },

    setNodeMask: function ( mask ) {
        this.nodeMask = mask;
    },

    getNodeMask: function () {
        return this.nodeMask;
    },

    setStateSet: function ( stateSet ) {

        if ( this.stateset === stateSet ) return;

        var deltaUpdate = 0;

        if ( this.stateset ) {
            if ( this.stateset.requiresUpdateTraversal() ) deltaUpdate--;
            this.stateset.removeParent( this );
        }

        if ( stateSet ) {
            stateSet.addParent( this );
            if ( stateSet.requiresUpdateTraversal() ) ++deltaUpdate;
        }

        if ( deltaUpdate !== 0 )
            this.setNumChildrenRequiringUpdateTraversal( this.getNumChildrenRequiringUpdateTraversal() + deltaUpdate );

        this.stateset = stateSet;
    },

    _updateNumChildrenRequeringUpdateTraversal: function ( delta ) {

        if ( this._numChildrenRequiringUpdateTraversal === 0 && this._parents.length ) {
            // the number of callbacks has changed, need to pass this
            // on to parents so they know whether app traversal is
            // required on this subgraph.
            for ( var i = 0, l = this._parents.length; i < l; i++ ) {
                var parent = this._parents[ i ];
                var num = parent.getNumChildrenRequiringUpdateTraversal();
                parent.setNumChildrenRequiringUpdateTraversal( num + delta );
            }
        }
    },

    setNumChildrenRequiringUpdateTraversal: function ( num ) {

        // if no changes just return.
        if ( this._numChildrenRequiringUpdateTraversal === num ) return;

        // note, if _updateCallback is set then the
        // parents won't be affected by any changes to
        // _numChildrenRequiringUpdateTraversal so no need to inform them.
        if ( !this._updateCallbacks.length && this._parents.length ) {

            // need to pass on changes to parents.
            var delta = 0;
            if ( this._numChildrenRequiringUpdateTraversal > 0 ) --delta;
            if ( num > 0 ) ++delta;

            if ( delta !== 0 ) {
                // the number of callbacks has changed, need to pass this
                // on to parents so they know whether app traversal is
                // required on this subgraph.
                var parents = this._parents;
                for ( var i = 0, l = parents.length; i < l; i++ ) {
                    var parent = parents[ i ];
                    var parentNum = parent.getNumChildrenRequiringUpdateTraversal();
                    parent.setNumChildrenRequiringUpdateTraversal( parentNum + delta );
                }
            }
        }

        // finally update this objects value.
        this._numChildrenRequiringUpdateTraversal = num;

    },

    getNumChildrenRequiringUpdateTraversal: function () {
        return this._numChildrenRequiringUpdateTraversal;
    },

    /**

      Set update node callback, called during update traversal.
      The Object must have the following method
      update(node, nodeVisitor) {}
      note, callback is responsible for scenegraph traversal so
      they must call traverse(node,nv) to ensure that the
      scene graph subtree (and associated callbacks) are traversed.

      Here a dummy UpdateCallback example

      @example
      var DummyUpdateCallback = function() {};
      DummyUpdateCallback.prototype = {
          update: function(node, nodeVisitor) {
              return true;
          }
      };

      @param Oject callback
   */
    setUpdateCallback: function ( cb ) {
        Notify.warn( 'deprecated use instead addUpdateCallback/removeUpdateCallback' );
        if ( cb === this._updateCallbacks[ 0 ] || !cb ) return;

        var hasExistingCallback = Boolean( this._updateCallbacks.length );
        if ( !this._updateCallbacks.length )
            this.addUpdateCallback( cb );
        else
            this._updateCallbacks[ 0 ] = cb;

        if ( !hasExistingCallback )
            this._updateNumChildrenRequeringUpdateTraversal( 1 );
    },

    /** Get update node callback, called during update traversal.
      @type Oject
   */
    getUpdateCallback: function () {
        return this._updateCallbacks[ 0 ];
    },

    addUpdateCallback: function ( cb ) {
        var hasExistingCallback = Boolean( this._updateCallbacks.length );
        this._updateCallbacks.push( cb );

        // send the signal on first add
        if ( !hasExistingCallback )
            this._updateNumChildrenRequeringUpdateTraversal( 1 );
    },

    removeUpdateCallback: function ( cb ) {
        var arrayIdx = this._updateCallbacks.indexOf( cb );
        if ( arrayIdx === -1 ) return;
        this._updateCallbacks.splice( arrayIdx, 1 );

        // send the signal when no more callback
        if ( !this._updateCallbacks.length )
            this._updateNumChildrenRequeringUpdateTraversal( -1 );

    },
    getUpdateCallbackList: function () {
        return this._updateCallbacks;
    },

    /**

      Set cull node callback, called during cull traversal.
      The Object must have the following method
      cull(node, nodeVisitor) {}
      note, callback is responsible for scenegraph traversal so
      they must return true to traverse.

      Here a dummy CullCallback example

      @example
      var DummyCullCallback = function() {};
      DummyCullCallback.prototype = {
          cull: function(node, nodeVisitor) {
              return true;
          }
      };

      @param Oject callback
   */
    setCullCallback: function ( cb ) {
        this._cullCallback = cb;
    },
    getCullCallback: function () {
        return this._cullCallback;
    },

    hasChild: function ( child ) {
        for ( var i = 0, l = this.children.length; i < l; i++ ) {
            if ( this.children[ i ] === child ) {
                return true;
            }
        }
        return false;
    },

    addChild: function ( child ) {

        if ( this.children.indexOf( child ) !== -1 ) return undefined;

        this.children.push( child );
        child.addParent( this );
        this.dirtyBound();

        // could now require app traversal thanks to the new subgraph,
        // so need to check and update if required.
        if ( child.getNumChildrenRequiringUpdateTraversal() > 0 ||
            child.getUpdateCallbackList().length ) {
            this.setNumChildrenRequiringUpdateTraversal(
                this.getNumChildrenRequiringUpdateTraversal() + 1
            );
        }

        return child;
    },

    getChildren: function () {
        return this.children;
    },
    getNumChildren: function () {
        return this.children.length;
    },
    getChild: function ( num ) {
        return this.children[ num ];
    },
    getParents: function () {
        return this._parents;
    },

    addParent: function ( parent ) {
        this._parents.push( parent );
    },

    removeParent: function ( parent ) {
        var idx = this._parents.indexOf( parent );
        if ( idx === -1 ) return;
        this._parents.splice( idx, 1 );
    },

    removeChildren: function () {
        var children = this.children;
        var nbChildren = children.length;
        if ( !nbChildren ) return;

        var updateCallbackRemoved = 0;

        for ( var i = 0; i < nbChildren; i++ ) {
            var child = children[ i ];
            child.removeParent( this );
            if ( child.getNumChildrenRequiringUpdateTraversal() > 0 || child.getUpdateCallbackList().length ) ++updateCallbackRemoved;
        }

        children.length = 0;
        if ( updateCallbackRemoved )
            this.setNumChildrenRequiringUpdateTraversal( this.getNumChildrenRequiringUpdateTraversal() - updateCallbackRemoved );

        this.dirtyBound();
    },

    // preserve order
    removeChild: function ( child ) {

        var children = this.children;
        var id = children.indexOf( child );
        if ( id === -1 ) return;

        child.removeParent( this );
        children.splice( id, 1 );

        if ( child.getNumChildrenRequiringUpdateTraversal() > 0 || child.getUpdateCallbackList().length )
            this.setNumChildrenRequiringUpdateTraversal( this.getNumChildrenRequiringUpdateTraversal() - 1 );

    },

    traverse: function ( visitor ) {
        var children = this.children;
        for ( var i = 0, l = children.length; i < l; i++ ) {
            var child = children[ i ];
            child.accept( visitor );
        }
    },

    ascend: function ( visitor ) {
        var parents = this._parents;
        for ( var i = 0, l = parents.length; i < l; i++ ) {
            var parent = parents[ i ];
            parent.accept( visitor );
        }
    },

    getBoundingBox: function () {
        if ( !this._boundingBoxComputed ) {
            this.computeBoundingBox( this._boundingBox );
            this._boundingBoxComputed = true;
        }
        return this._boundingBox;
    },

    computeBoundingBox: function ( bbox ) {

        // circular dependency... not sure if the global visitor instance should be instancied here
        var ComputeBoundsVisitor = require( 'osg/ComputeBoundsVisitor' );
        var cbv = ComputeBoundsVisitor.instance = ComputeBoundsVisitor.instance || new ComputeBoundsVisitor();
        cbv.setNodeMaskOverride( ~0x0 ); // traverse everything to be consistent with computeBoundingSphere
        cbv.reset();

        cbv.apply( this );
        bbox.copy( cbv.getBoundingBox() );
        return bbox;
    },

    getBoundingSphere: function () {
        return this.getBound();
    },

    getBound: function () {
        if ( !this._boundingSphereComputed ) {
            this.computeBoundingSphere( this._boundingSphere );
            this._boundingSphereComputed = true;
        }
        return this._boundingSphere;
    },

    computeBoundingSphere: function ( bSphere ) {

        var children = this.children;
        var l = children.length;

        bSphere.init();
        if ( l === 0 ) return bSphere;

        var cc, i;
        var bb = this._tmpBox;
        bb.init();
        for ( i = 0; i < l; i++ ) {
            cc = children[ i ];
            if ( cc.referenceFrame !== TransformEnums.ABSOLUTE_RF ) {
                bb.expandByBoundingSphere( cc.getBound() );
            }
        }
        if ( !bb.valid() ) return bSphere;

        bSphere.set( bb.center( bSphere.center() ), 0.0 );
        for ( i = 0; i < l; i++ ) {
            cc = children[ i ];
            if ( cc.referenceFrame !== TransformEnums.ABSOLUTE_RF ) {
                bSphere.expandRadiusBySphere( cc.getBound() );
            }
        }
        return bSphere;
    },

    // matrixCreate allow user handling of garbage collection of matrices
    getWorldMatrices: ( function () {
        var CollectParentPaths = function () {
            this.nodePaths = [];
            this.halt = undefined;
            NodeVisitor.call( this, NodeVisitor.TRAVERSE_PARENTS );
        };
        CollectParentPaths.prototype = MACROUTILS.objectInherit( NodeVisitor.prototype, {
            reset: function () {
                this.nodePath.length = 0;
                this.nodePaths.length = 0;
            },
            apply: function ( node ) {
                if ( node._parents.length === 0 || node === this.halt || node.referenceFrame === TransformEnums.ABSOLUTE_RF ) {
                    // copy
                    this.nodePaths.push( this.nodePath.slice( 0 ) );
                } else {
                    this.traverse( node );
                }
            }
        } );
        var collected = new CollectParentPaths();
        collected.setNodeMaskOverride( ~0x0 ); // traverse everything

        return function computeLocalToWorldList( halt, matrixCreate ) {
            collected.reset();
            collected.halt = halt;

            this.accept( collected );
            var matrixList = [];

            var matrixGenerator = matrixCreate || Matrix.create;
            for ( var i = 0, l = collected.nodePaths.length; i < l; i++ ) {
                var np = collected.nodePaths[ i ];
                var m = matrixGenerator();
                if ( np.length !== 0 ) {
                    ComputeMatrixFromNodePath.computeLocalToWorld( np, true, m );
                }
                matrixList.push( m );
            }

            return matrixList;
        };

    } )(),

    // same as getWorldMatrices GC: Perf WIN
    getWorldMatrix: function ( halt, matrix ) {

        // pass allocator on master
        var matrixList = this.getWorldMatrices( halt, nodeGetMat );

        if ( matrixList.length === 0 ) {

            Matrix.makeIdentity( matrix );

        } else {

            Matrix.copy( matrixList[ 0 ], matrix );

        }

        Node._reservedMatrixStack.reset();
        return matrix;

    },

    setCullingActive: function ( value ) {
        if ( this._cullingActive === value ) return;
        if ( this._numChildrenWithCullingDisabled === 0 && this._parents.length > 0 ) {
            var delta = 0;
            if ( !this._cullingActive ) --delta;
            if ( !value ) ++delta;
            if ( delta !== 0 ) {
                for ( var i = 0, k = this._parents.length; i < k; i++ ) {
                    this._parents[ i ].setNumChildrenWithCullingDisabled( this._parents[ i ].getNumChildrenWithCullingDisabled() + delta );
                }
            }
        }
        this._cullingActive = value;
    },

    getCullingActive: function () {
        return this._cullingActive;
    },

    isCullingActive: function () {
        return this._numChildrenWithCullingDisabled === 0 && this._cullingActive && this.getBound().valid();
    },

    setNumChildrenWithCullingDisabled: function ( num ) {
        if ( this._numChildrenWithCullingDisabled === num ) return;
        if ( this._cullingActive && this._parents.length > 0 ) {
            var delta = 0;
            if ( this._numChildrenWithCullingDisabled > 0 ) --delta;
            if ( num > 0 ) ++delta;
            if ( delta !== 0 ) {
                for ( var i = 0, k = this._parents.length; i < k; i++ ) {
                    this._parents[ i ].setNumChildrenWithCullingDisabled( this._parents[ i ].getNumChildrenWithCullingDisabled() + delta );
                }
            }
        }
        this._numChildrenWithCullingDisabled = num;
    },

    getNumChildrenWithCullingDisabled: function () {
        return this._numChildrenWithCullingDisabled;
    },

    releaseGLObjects: function () {
        if ( this.stateset !== undefined ) this.stateset.releaseGLObjects();
    }

} ), 'osg', 'Node' );
MACROUTILS.setTypeID( Node );

module.exports = Node;
<|endoftext|>