<commit_before>/* eslint-disable no-magic-numbers */
/* eslint-disable guard-for-in */
import * as THREE from 'three';
import vertexShader from './Uber_vert.glsl';
import fragmentShader from './Uber_frag.glsl';
import capabilities from '../capabilities';
import noise from '../noiseTexture';

// Length of _samplesKernel is used in Uber_frag.glsl
// If you want to change length of _samplesKernel, please, remember change it in Uber_frag.glsl too.
// You can easy find places for replace using word:_samplesKernel
const _samplesKernel = [
  new THREE.Vector2(-0.541978, 0.840393),
  new THREE.Vector2(0.125533, -0.992089),
  new THREE.Vector2(0.374329, 0.927296),
  new THREE.Vector2(-0.105475, 0.994422),
];

//  var INSTANCED_SPRITE_OVERSCALE = 1.3;

const defaultUniforms = THREE.UniformsUtils.merge([

  THREE.UniformsLib.common, // is it needed?
  THREE.UniformsLib.fog,
  THREE.UniformsLib.lights,

  {
    specular: { type: 'c', value: new THREE.Color(0x111111) },
    shininess: { type: 'f', value: 30 },
    fixedColor: { type: 'c', value: new THREE.Color(0xffffff) },
    zOffset: { type: 'f', value: 0.0 },
    zClipValue: { type: 'f', value: 0.0 },
    clipPlaneValue: { type: 'f', value: 0.0 },
    invModelViewMatrix: { type: '4fv', value: new THREE.Matrix4() },
    world2colorMatrix: { type: '4fv', value: new THREE.Matrix4() },
    dashedLineSize: { type: 'f', value: 0.1 },
    dashedLinePeriod: { type: 'f', value: 0.2 },
    projMatrixInv: { type: '4fv', value: new THREE.Matrix4() },
    viewport: { type: 'v2', value: new THREE.Vector2() },
    lineWidth: { type: 'f', value: 2.0 },
    // default value must be the same as settings
    fogAlpha: { type: 'f', value: 1.0 },
    samplesKernel: { type: 'v2v', value: null },
    noiseTex: { type: 't', value: null },
    noiseTexelSize: { type: 'v2', value: null },
    srcTexelSize: { type: 'v2', value: null },
  },

]);

const uberOptionNames = [
  'shininess',
  'opacity',
  'zOffset',
  'diffuse',
  'specular',
  'fixedColor',
  'zClipCoef',
  'zClipValue',
  'clipPlaneValue',
  'world2colorMatrix',
  'dashedLineSize',
  'dashedLinePeriod',
  'projMatrixInv',
  'viewport',
  'lineWidth',
  'fogAlpha',
  'samplesKernel',
  'noiseTex',
  'noiseTexelSize',
  'srcTexelSize',
];

function UberMaterial(params) {
  THREE.RawShaderMaterial.call(this);

  // add fog
  this.fog = true;
  // used for instanced geometry
  this.instancedPos = false;
  this.instancedMatrix = false;
  // atoms and links color
  this.attrColor = false;
  // second link color for cylinders
  this.attrColor2 = false;
  //
  this.attrAlphaColor = false;
  // overrides color for all vertices (used in selection)
  this.overrideColor = false;
  // zsrpites
  this.sphereSprite = false;
  this.cylinderSprite = false;
  // clip Surfs individually
  this.zClip = false;
  // clip scene with global clip plane
  this.clipPlane = false;
  // enable fake (chess-like) opacity
  this.fakeOpacity = false;
  // render only depth, don't take care about the pixel color (used for transparency depth prepass)
  this.prepassTransparancy = false;
  // used to render pixel positions
  this.colorFromPos = false;
  // used to render shadowmap
  this.shadowmap = false;
  // used to describe shadowmap type
  this.shadowmapType = 'random';
  // used to render pixel view deph
  this.colorFromDepth = false;
  // used to render dashed line
  this.dashedLine = false;
  // mark as transparent
  this.transparent = true;
  // mark as thick lines
  this.thickLine = false;
  // makes fog begin transparency (required for transparent background)
  this.fogTransparent = false;
  // used to render surface normals to G buffer for ssao effect
  this.normalsToGBuffer = false;
  // used for toon material
  this.toonShading = false;

  // uber options of "root" materials are inherited from single uber-options object that resides in prototype
  this.uberOptions = Object.create(UberMaterial.prototype.uberOptions);

  // set default values
  THREE.RawShaderMaterial.prototype.setValues.call(this, {
    uniforms: THREE.UniformsUtils.clone(defaultUniforms),
    vertexShader: this.precisionString() + vertexShader,
    fragmentShader: this.precisionString() + fragmentShader,
    lights: true,
    fog: true,
    side: THREE.DoubleSide,
  });

  this.setValues(params);
}

UberMaterial.prototype = Object.create(THREE.RawShaderMaterial.prototype);
UberMaterial.prototype.constructor = UberMaterial;

UberMaterial.prototype.precisionString = function () {
  const { precision } = capabilities;
  const str = `precision ${precision} float;\n`
    + `precision ${precision} int;\n\n`;
  return str;
};

// properties that convert to uniforms
UberMaterial.prototype.uberOptions = {
  diffuse: new THREE.Color(0xffffff), // used in phong lighting
  specular: new THREE.Color(0x111111), // used in phong lighting
  shininess: 30, // used in phong lighting
  opacity: 1, // set mesh opacity
  fixedColor: new THREE.Color(0xffffff), // color to override (see OVERRIDE_COLOR)
  zOffset: 0.0, // used fo zsprites (see SPHERE_SPRITE CYLINDER_SPRITE)
  zClipCoef: 2.0, // use for Surfs clipping (mesh param, isn't used in shader)  FIXME move to representation param
  zClipValue: 0.0, //  value to clip Surfs in shader  (see ZCLIP)
  clipPlaneValue: 0.0, // value to clip scene globally (see CLIPPLANE)
  world2colorMatrix: new THREE.Matrix4(),
  dashedLineSize: 0.1,
  dashedLinePeriod: 0.3,
  projMatrixInv: new THREE.Matrix4(),
  viewport: new THREE.Vector2(800, 600),
  lineWidth: 2.0,
  fogAlpha: 1.0,
  samplesKernel: _samplesKernel,
  noiseTex: noise.noiseTexture,
  noiseTexelSize: new THREE.Vector2(1.0 / noise.noiseWidth, 1.0 / noise.noiseHeight),
  srcTexelSize: new THREE.Vector2(1.0 / 800.0, 1.0 / 600.0),

  copy(source) {
    this.diffuse.copy(source.diffuse);
    this.specular.copy(source.specular);
    this.shininess = source.shininess;
    this.opacity = source.opacity;
    this.fixedColor.copy(source.fixedColor);
    this.zOffset = source.zOffset;
    this.zClipCoef = source.zClipCoef;
    this.zClipValue = source.zClipValue;
    this.clipPlaneValue = source.clipPlaneValue;
    this.world2colorMatrix.copy(source.world2colorMatrix);
    this.dashedLineSize = source.dashedLineSize;
    this.dashedLinePeriod = source.dashedLinePeriod;
    this.projMatrixInv = source.projMatrixInv;
    this.viewport = source.viewport;
    this.lineWidth = source.lineWidth; // used for thick lines only
    this.toonShading = source.toonShading;
    this.fogAlpha = source.fogAlpha;
    this.samplesKernel = source.samplesKernel;
    this.noiseTex = source.noiseTex;
    this.noiseTexelSize = source.noiseTexelSize;
    this.srcTexelSize = source.srcTexelSize;
  },
};

UberMaterial.prototype.copy = function (source) {
  THREE.RawShaderMaterial.prototype.copy.call(this, source);

  this.fragmentShader = source.fragmentShader;
  this.vertexShader = source.vertexShader;

  this.uniforms = THREE.UniformsUtils.clone(source.uniforms);
  this.defines = Object.assign({}, source.defines);
  this.extensions = source.extensions;

  this.fog = source.fog;
  this.instancedPos = source.instancedPos;
  this.instancedMatrix = source.instancedMatrix;
  this.attrColor = source.attrColor;
  this.attrColor2 = source.attrColor2;
  this.attrAlphaColor = source.attrAlphaColor;
  this.overrideColor = source.overrideColor;
  this.sphereSprite = source.sphereSprite;
  this.cylinderSprite = source.cylinderSprite;
  this.zClip = source.zClip;
  this.clipPlane = source.clipPlane;
  this.fakeOpacity = source.fakeOpacity;
  this.colorFromPos = source.colorFromPos;
  this.shadowmap = source.shadowmap;
  this.shadowmapType = source.shadowmapType;
  this.colorFromDepth = source.colorFromDepth;
  this.prepassTransparancy = source.prepassTransparancy;
  this.dashedLine = source.dashedLine;
  this.thickLine = source.thickLine;
  this.fogTransparent = source.fogTransparent;
  this.normalsToGBuffer = source.normalsToGBuffer;
  this.toonShading = source.toonShading;

  this.uberOptions.copy(source.uberOptions);

  return this;
};

// create copy of this material
// its options are prototyped after this material's options
UberMaterial.prototype.createInstance = function () {
  const inst = new UberMaterial();
  inst.copy(this);
  inst.uberOptions = Object.create(this.uberOptions);
  return inst;
};

UberMaterial.prototype.setValues = function (values) {
  if (typeof values === 'undefined') {
    return;
  }

  // set direct values
  THREE.RawShaderMaterial.prototype.setValues.call(this, values);

  const defines = {};
  const extensions = {};

  if (this.fog) {
    defines.USE_FOG = 1;
  }
  if (this.instancedPos) {
    defines.INSTANCED_POS = 1;
  }
  if (this.instancedMatrix) {
    defines.INSTANCED_MATRIX = 1;
  }
  if (this.attrColor) {
    defines.ATTR_COLOR = 1;
  }
  if (this.attrColor2) {
    defines.ATTR_COLOR2 = 1;
  }
  if (this.attrAlphaColor) {
    defines.ATTR_ALPHA_COLOR = 1;
  }
  if (this.overrideColor) {
    defines.OVERRIDE_COLOR = 1;
  }
  if (this.sphereSprite) {
    defines.SPHERE_SPRITE = 1;
    extensions.fragDepth = 1;
  }
  if (this.cylinderSprite) {
    defines.CYLINDER_SPRITE = 1;
    extensions.fragDepth = 1;
  }
  if (this.zClip) {
    defines.ZCLIP = 1;
  }
  if (this.clipPlane) {
    defines.CLIP_PLANE = 1;
  }
  if (this.fakeOpacity) {
    defines.FAKE_OPACITY = 1;
  }
  if (this.lights) {
    defines.USE_LIGHTS = 1;
  }
  if (this.colorFromPos) {
    defines.COLOR_FROM_POS = 1;
  }
  if (this.shadowmap) {
    defines.SHADOWMAP = 1;
    if (this.shadowmapType === 'pcf') {
      defines.SHADOWMAP_PCF_SHARP = 1;
    } else if (this.shadowmapType === 'random') {
      defines.SHADOWMAP_PCF_RAND = 1;
    } else {
      defines.SHADOWMAP_BASIC = 1;
    }
  }
  if (this.colorFromDepth) {
    defines.COLOR_FROM_DEPTH = 1;
  }
  if (this.prepassTransparancy) {
    defines.PREPASS_TRANSP = 1;
  }
  if (this.dashedLine) {
    defines.DASHED_LINE = 1;
  }
  if (this.thickLine) {
    defines.THICK_LINE = 1;
  }
  if (this.fogTransparent) {
    defines.FOG_TRANSPARENT = 1;
  }
  if (this.normalsToGBuffer) {
    extensions.drawBuffers = 1;
    defines.NORMALS_TO_G_BUFFER = 1;
  }
  if (this.toonShading) {
    defines.TOON_SHADING = 1;
  }
  // set dependent values
  this.defines = defines;
  this.extensions = extensions;
};

UberMaterial.prototype.setUberOptions = function (values) {
  if (typeof values === 'undefined') {
    return;
  }

  for (const key in values) {
    if (!values.hasOwnProperty(key)) {
      continue;
    }

    if (this.uberOptions[key] instanceof THREE.Color) {
      this.uberOptions[key] = values[key].clone();
    } else {
      this.uberOptions[key] = values[key];
    }
  }
};

UberMaterial.prototype.clone = function (shallow) {
  if (!shallow) {
    return THREE.Material.prototype.clone.call(this);
  }
  return this.createInstance();
};

UberMaterial.prototype.updateUniforms = function () {
  const self = this;

  uberOptionNames.forEach((p) => {
    if (self.uniforms.hasOwnProperty(p)) {
      if (self.uberOptions[p] instanceof THREE.Color
            || self.uberOptions[p] instanceof THREE.Matrix4) {
        self.uniforms[p].value = self.uberOptions[p].clone();
      } else {
        self.uniforms[p].value = self.uberOptions[p];
      }
    }
  });
};

export default UberMaterial;
<commit_msg>Remove obsolete<commit_after>/* eslint-disable no-magic-numbers */
/* eslint-disable guard-for-in */
import * as THREE from 'three';
import vertexShader from './Uber_vert.glsl';
import fragmentShader from './Uber_frag.glsl';
import capabilities from '../capabilities';
import noise from '../noiseTexture';

// Length of _samplesKernel is used in Uber_frag.glsl
// If you want to change length of _samplesKernel, please, remember change it in Uber_frag.glsl too.
// You can easy find places for replace using word:_samplesKernel
const _samplesKernel = [
  new THREE.Vector2(-0.541978, 0.840393),
  new THREE.Vector2(0.125533, -0.992089),
  new THREE.Vector2(0.374329, 0.927296),
  new THREE.Vector2(-0.105475, 0.994422),
];

//  var INSTANCED_SPRITE_OVERSCALE = 1.3;

const defaultUniforms = THREE.UniformsUtils.merge([

  THREE.UniformsLib.fog,
  THREE.UniformsLib.lights,

  {
    // are updated automatically by three.js (see THREE.ShaderLib.common)
    diffuse: { value: new THREE.Color(0xeeeeee) },
    opacity: { value: 1.0 },

    specular: { type: 'c', value: new THREE.Color(0x111111) },
    shininess: { type: 'f', value: 30 },
    fixedColor: { type: 'c', value: new THREE.Color(0xffffff) },
    zOffset: { type: 'f', value: 0.0 },
    zClipValue: { type: 'f', value: 0.0 },
    clipPlaneValue: { type: 'f', value: 0.0 },
    invModelViewMatrix: { type: '4fv', value: new THREE.Matrix4() },
    world2colorMatrix: { type: '4fv', value: new THREE.Matrix4() },
    dashedLineSize: { type: 'f', value: 0.1 },
    dashedLinePeriod: { type: 'f', value: 0.2 },
    projMatrixInv: { type: '4fv', value: new THREE.Matrix4() },
    viewport: { type: 'v2', value: new THREE.Vector2() },
    lineWidth: { type: 'f', value: 2.0 },
    // default value must be the same as settings
    fogAlpha: { type: 'f', value: 1.0 },
    samplesKernel: { type: 'v2v', value: null },
    noiseTex: { type: 't', value: null },
    noiseTexelSize: { type: 'v2', value: null },
    srcTexelSize: { type: 'v2', value: null },
  },

]);

const uberOptionNames = [
  'shininess',
  'opacity',
  'zOffset',
  'diffuse',
  'specular',
  'fixedColor',
  'zClipCoef',
  'zClipValue',
  'clipPlaneValue',
  'world2colorMatrix',
  'dashedLineSize',
  'dashedLinePeriod',
  'projMatrixInv',
  'viewport',
  'lineWidth',
  'fogAlpha',
  'samplesKernel',
  'noiseTex',
  'noiseTexelSize',
  'srcTexelSize',
];

function UberMaterial(params) {
  THREE.RawShaderMaterial.call(this);

  // add fog
  this.fog = true;
  // used for instanced geometry
  this.instancedPos = false;
  this.instancedMatrix = false;
  // atoms and links color
  this.attrColor = false;
  // second link color for cylinders
  this.attrColor2 = false;
  //
  this.attrAlphaColor = false;
  // overrides color for all vertices (used in selection)
  this.overrideColor = false;
  // zsrpites
  this.sphereSprite = false;
  this.cylinderSprite = false;
  // clip Surfs individually
  this.zClip = false;
  // clip scene with global clip plane
  this.clipPlane = false;
  // enable fake (chess-like) opacity
  this.fakeOpacity = false;
  // render only depth, don't take care about the pixel color (used for transparency depth prepass)
  this.prepassTransparancy = false;
  // used to render pixel positions
  this.colorFromPos = false;
  // used to render shadowmap
  this.shadowmap = false;
  // used to describe shadowmap type
  this.shadowmapType = 'random';
  // used to render pixel view deph
  this.colorFromDepth = false;
  // used to render dashed line
  this.dashedLine = false;
  // mark as transparent
  this.transparent = true;
  // mark as thick lines
  this.thickLine = false;
  // makes fog begin transparency (required for transparent background)
  this.fogTransparent = false;
  // used to render surface normals to G buffer for ssao effect
  this.normalsToGBuffer = false;
  // used for toon material
  this.toonShading = false;

  // uber options of "root" materials are inherited from single uber-options object that resides in prototype
  this.uberOptions = Object.create(UberMaterial.prototype.uberOptions);

  // set default values
  THREE.RawShaderMaterial.prototype.setValues.call(this, {
    uniforms: THREE.UniformsUtils.clone(defaultUniforms),
    vertexShader: this.precisionString() + vertexShader,
    fragmentShader: this.precisionString() + fragmentShader,
    lights: true,
    fog: true,
    side: THREE.DoubleSide,
  });

  this.setValues(params);
}

UberMaterial.prototype = Object.create(THREE.RawShaderMaterial.prototype);
UberMaterial.prototype.constructor = UberMaterial;

UberMaterial.prototype.precisionString = function () {
  const { precision } = capabilities;
  const str = `precision ${precision} float;\n`
    + `precision ${precision} int;\n\n`;
  return str;
};

// properties that convert to uniforms
UberMaterial.prototype.uberOptions = {
  diffuse: new THREE.Color(0xffffff), // used in phong lighting
  specular: new THREE.Color(0x111111), // used in phong lighting
  shininess: 30, // used in phong lighting
  opacity: 1, // set mesh opacity
  fixedColor: new THREE.Color(0xffffff), // color to override (see OVERRIDE_COLOR)
  zOffset: 0.0, // used fo zsprites (see SPHERE_SPRITE CYLINDER_SPRITE)
  zClipCoef: 2.0, // use for Surfs clipping (mesh param, isn't used in shader)  FIXME move to representation param
  zClipValue: 0.0, //  value to clip Surfs in shader  (see ZCLIP)
  clipPlaneValue: 0.0, // value to clip scene globally (see CLIPPLANE)
  world2colorMatrix: new THREE.Matrix4(),
  dashedLineSize: 0.1,
  dashedLinePeriod: 0.3,
  projMatrixInv: new THREE.Matrix4(),
  viewport: new THREE.Vector2(800, 600),
  lineWidth: 2.0,
  fogAlpha: 1.0,
  samplesKernel: _samplesKernel,
  noiseTex: noise.noiseTexture,
  noiseTexelSize: new THREE.Vector2(1.0 / noise.noiseWidth, 1.0 / noise.noiseHeight),
  srcTexelSize: new THREE.Vector2(1.0 / 800.0, 1.0 / 600.0),

  copy(source) {
    this.diffuse.copy(source.diffuse);
    this.specular.copy(source.specular);
    this.shininess = source.shininess;
    this.opacity = source.opacity;
    this.fixedColor.copy(source.fixedColor);
    this.zOffset = source.zOffset;
    this.zClipCoef = source.zClipCoef;
    this.zClipValue = source.zClipValue;
    this.clipPlaneValue = source.clipPlaneValue;
    this.world2colorMatrix.copy(source.world2colorMatrix);
    this.dashedLineSize = source.dashedLineSize;
    this.dashedLinePeriod = source.dashedLinePeriod;
    this.projMatrixInv = source.projMatrixInv;
    this.viewport = source.viewport;
    this.lineWidth = source.lineWidth; // used for thick lines only
    this.toonShading = source.toonShading;
    this.fogAlpha = source.fogAlpha;
    this.samplesKernel = source.samplesKernel;
    this.noiseTex = source.noiseTex;
    this.noiseTexelSize = source.noiseTexelSize;
    this.srcTexelSize = source.srcTexelSize;
  },
};

UberMaterial.prototype.copy = function (source) {
  THREE.RawShaderMaterial.prototype.copy.call(this, source);

  this.fragmentShader = source.fragmentShader;
  this.vertexShader = source.vertexShader;

  this.uniforms = THREE.UniformsUtils.clone(source.uniforms);
  this.defines = Object.assign({}, source.defines);
  this.extensions = source.extensions;

  this.fog = source.fog;
  this.instancedPos = source.instancedPos;
  this.instancedMatrix = source.instancedMatrix;
  this.attrColor = source.attrColor;
  this.attrColor2 = source.attrColor2;
  this.attrAlphaColor = source.attrAlphaColor;
  this.overrideColor = source.overrideColor;
  this.sphereSprite = source.sphereSprite;
  this.cylinderSprite = source.cylinderSprite;
  this.zClip = source.zClip;
  this.clipPlane = source.clipPlane;
  this.fakeOpacity = source.fakeOpacity;
  this.colorFromPos = source.colorFromPos;
  this.shadowmap = source.shadowmap;
  this.shadowmapType = source.shadowmapType;
  this.colorFromDepth = source.colorFromDepth;
  this.prepassTransparancy = source.prepassTransparancy;
  this.dashedLine = source.dashedLine;
  this.thickLine = source.thickLine;
  this.fogTransparent = source.fogTransparent;
  this.normalsToGBuffer = source.normalsToGBuffer;
  this.toonShading = source.toonShading;

  this.uberOptions.copy(source.uberOptions);

  return this;
};

// create copy of this material
// its options are prototyped after this material's options
UberMaterial.prototype.createInstance = function () {
  const inst = new UberMaterial();
  inst.copy(this);
  inst.uberOptions = Object.create(this.uberOptions);
  return inst;
};

UberMaterial.prototype.setValues = function (values) {
  if (typeof values === 'undefined') {
    return;
  }

  // set direct values
  THREE.RawShaderMaterial.prototype.setValues.call(this, values);

  const defines = {};
  const extensions = {};

  if (this.fog) {
    defines.USE_FOG = 1;
  }
  if (this.instancedPos) {
    defines.INSTANCED_POS = 1;
  }
  if (this.instancedMatrix) {
    defines.INSTANCED_MATRIX = 1;
  }
  if (this.attrColor) {
    defines.ATTR_COLOR = 1;
  }
  if (this.attrColor2) {
    defines.ATTR_COLOR2 = 1;
  }
  if (this.attrAlphaColor) {
    defines.ATTR_ALPHA_COLOR = 1;
  }
  if (this.overrideColor) {
    defines.OVERRIDE_COLOR = 1;
  }
  if (this.sphereSprite) {
    defines.SPHERE_SPRITE = 1;
    extensions.fragDepth = 1;
  }
  if (this.cylinderSprite) {
    defines.CYLINDER_SPRITE = 1;
    extensions.fragDepth = 1;
  }
  if (this.zClip) {
    defines.ZCLIP = 1;
  }
  if (this.clipPlane) {
    defines.CLIP_PLANE = 1;
  }
  if (this.fakeOpacity) {
    defines.FAKE_OPACITY = 1;
  }
  if (this.lights) {
    defines.USE_LIGHTS = 1;
  }
  if (this.colorFromPos) {
    defines.COLOR_FROM_POS = 1;
  }
  if (this.shadowmap) {
    defines.SHADOWMAP = 1;
    if (this.shadowmapType === 'pcf') {
      defines.SHADOWMAP_PCF_SHARP = 1;
    } else if (this.shadowmapType === 'random') {
      defines.SHADOWMAP_PCF_RAND = 1;
    } else {
      defines.SHADOWMAP_BASIC = 1;
    }
  }
  if (this.colorFromDepth) {
    defines.COLOR_FROM_DEPTH = 1;
  }
  if (this.prepassTransparancy) {
    defines.PREPASS_TRANSP = 1;
  }
  if (this.dashedLine) {
    defines.DASHED_LINE = 1;
  }
  if (this.thickLine) {
    defines.THICK_LINE = 1;
  }
  if (this.fogTransparent) {
    defines.FOG_TRANSPARENT = 1;
  }
  if (this.normalsToGBuffer) {
    extensions.drawBuffers = 1;
    defines.NORMALS_TO_G_BUFFER = 1;
  }
  if (this.toonShading) {
    defines.TOON_SHADING = 1;
  }
  // set dependent values
  this.defines = defines;
  this.extensions = extensions;
};

UberMaterial.prototype.setUberOptions = function (values) {
  if (typeof values === 'undefined') {
    return;
  }

  for (const key in values) {
    if (!values.hasOwnProperty(key)) {
      continue;
    }

    if (this.uberOptions[key] instanceof THREE.Color) {
      this.uberOptions[key] = values[key].clone();
    } else {
      this.uberOptions[key] = values[key];
    }
  }
};

UberMaterial.prototype.clone = function (shallow) {
  if (!shallow) {
    return THREE.Material.prototype.clone.call(this);
  }
  return this.createInstance();
};

UberMaterial.prototype.updateUniforms = function () {
  const self = this;

  uberOptionNames.forEach((p) => {
    if (self.uniforms.hasOwnProperty(p)) {
      if (self.uberOptions[p] instanceof THREE.Color
            || self.uberOptions[p] instanceof THREE.Matrix4) {
        self.uniforms[p].value = self.uberOptions[p].clone();
      } else {
        self.uniforms[p].value = self.uberOptions[p];
      }
    }
  });
};

export default UberMaterial;
<|endoftext|>


<commit_before>// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
// MIT License. See license.txt

// new re-factored Listing object
// removed rarely used functionality
//
// opts:
//   parent

//   method (method to call on server)
//   args (additional args to method)
//   get_args (method to return args as dict)

//   show_filters [false]
//   doctype
//   filter_fields (if given, this list is rendered, else built from doctype)

//   query or get_query (will be deprecated)
//   query_max
//   buttons_in_frame

//   no_result_message ("No result")

//   page_length (20)
//   hide_refresh (False)
//   no_toolbar
//   new_doctype
//   [function] render_row(parent, data)
//   [function] onrun
//   no_loading (no ajax indicator)

frappe.provide('frappe.ui');
frappe.ui.Listing = Class.extend({
	init: function(opts) {
		this.opts = opts || {};
		this.page_length = 20;
		this.start = 0;
		this.data = [];
		if(opts) {
			this.make();
		}
	},
	prepare_opts: function() {
		if(this.opts.new_doctype) {
			if(frappe.boot.profile.can_create.indexOf(this.opts.new_doctype)==-1) {
				this.opts.new_doctype = null;
			} else {
				this.opts.new_doctype = this.opts.new_doctype;
			}
		}
		if(!this.opts.no_result_message) {
			this.opts.no_result_message = frappe._('Nothing to show');
		}
		if(!this.opts.page_length) {
			this.opts.page_length = 20;
		}
		this.opts._more = frappe._("More");
	},
	make: function(opts) {
		if(opts) {
			this.opts = opts;
		}
		this.prepare_opts();
		$.extend(this, this.opts);

		$(this.parent).html(repl('\

\

%(title)s
\
				\

\

\

\

\
							<button class="btn btn-info search-btn">\
								 \
								Search</button>\
							<button class="btn btn-default add-filter-btn">\
								 \
								Add Filter</button>\

\

\

\
				\

\

\

\

\

\

\

\
					%(no_result_message)s\

\

\

\

\

\

\

\
							<button class="btn btn-default btn-more">%(_more)s...</button>\

\

\

\
								<button type="button" class="btn btn-default btn-small btn-info" data-value="20">20</button>\
								<button type="button" class="btn btn-default btn-small" data-value="100">100</button>\
								<button type="button" class="btn btn-default btn-small" data-value="500">500</button>\

\

\

\

\

\
		', this.opts));
		this.$w = $(this.parent).find('.frappe-list');
		this.set_events();

		if(this.appframe) {
			this.$w.find('.list-toolbar-wrapper').toggle(false);
		}

		if(this.show_filters) {
			this.make_filters();
		}
	},
	add_button: function(label, click, icon) {
		if(this.appframe) {
			return this.appframe.add_button(label, click, icon)
		} else {
			$button = $('<button class="btn btn-default"></button>')
				.appendTo(this.$w.find('.list-toolbar'))
				.html((icon ? (" ") : "") + label)
				.click(click);
			return $button
		}
	},
	show_view: function($btn, $div, $btn_unsel, $div_unsel) {
		$btn_unsel.removeClass('btn-info');
		$btn_unsel.find('i').removeClass('icon-white');
		$div_unsel.toggle(false);

		$btn.addClass('btn-info');
		$btn.find('i').addClass('icon-white');
		$div.toggle(true);
	},
	set_events: function() {
		var me = this;

		// next page
		this.$w.find('.btn-more').click(function() {
			me.run({append: true });
		});

		this.$w.find(".btn-group-paging .btn").click(function() {
			me.page_length = cint($(this).attr("data-value"));
			me.run({append: true});
		})

		// title
		if(this.title) {
			this.$w.find('h3').html(this.title).toggle(true);
		}

		// hide-refresh
		if(!(this.hide_refresh || this.no_refresh)) {
			this.add_button('Refresh', function() {
				me.run();
			}, 'icon-refresh');

		}

		// new
		if(this.new_doctype) {
			if(this.appframe) {
				this.appframe.set_title_right(" " + frappe._('New') + ", function() {
					(me.custom_new_doc || me.make_new_doc).apply(me, [me.new_doctype]); });
			}
			this.add_button(frappe._('New'), function() {
				(me.custom_new_doc || me.make_new_doc).apply(me, [me.new_doctype]);
			}, 'icon-plus');
		}

		// hide-filter
		if(me.show_filters) {
			this.add_button(frappe._('Filter'), function() {
				me.filter_list.show_filters();
			}, 'icon-search').addClass('btn-filter');
		}

		if(me.no_toolbar || me.hide_toolbar) {
			me.$w.find('.list-toolbar-wrapper').toggle(false);
		}
	},

	make_new_doc: function(doctype) {
		var me = this;
		frappe.model.with_doctype(doctype, function() {
			var doc = frappe.model.get_new_doc(doctype);
			if(me.filter_list) {
				$.each(me.filter_list.get_filters(), function(i, f) {
					if(f[0]===doctype && f[2]==="=" && f[1]!=="name")
						doc[f[1]]=f[3];
				})
			}
			frappe.set_route("Form", doctype, doc.name);
		});
	},

	make_filters: function() {
		this.filter_list = new frappe.ui.FilterList({
			listobj: this,
			$parent: this.$w.find('.list-filters').toggle(true),
			doctype: this.doctype,
			filter_fields: this.filter_fields
		});
	},

	clear: function() {
		this.data = [];
		this.$w.find('.result-list').empty();
		this.$w.find('.result').toggle(true);
		this.$w.find('.no-result').toggle(false);
		this.start = 0;
	},
	run: function(more) {
		var me = this;
		if(!more) {
			this.start = 0;
			if(this.onreset) this.onreset();
		}

		if(!me.opts.no_loading)
			me.set_working(true);

		return frappe.call({
			method: this.opts.method || 'frappe.widgets.query_builder.runquery',
			type: "GET",
			args: this.get_call_args(),
			callback: function(r) {
				if(!me.opts.no_loading)
					me.set_working(false);
				me.dirty = false;
				me.render_results(r);
			},
			no_spinner: this.opts.no_loading
		});
	},
	set_working: function(flag) {
		this.$w.find('.img-load').toggle(flag);
	},
	get_call_args: function() {
		// load query
		if(!this.method) {
			var query = this.get_query ? this.get_query() : this.query;
			query = this.add_limits(query);
			var args={
				query_max: this.query_max,
				as_dict: 1
			}
			args.simple_query = query;
		} else {
			var args = {
				limit_start: this.start,
				limit_page_length: this.page_length
			}
		}

		// append user-defined arguments
		if(this.args)
			$.extend(args, this.args)

		if(this.get_args) {
			$.extend(args, this.get_args());
		}
		return args;
	},
	render_results: function(r) {
		if(this.start===0) this.clear();

		this.$w.find('.paging-button').toggle(false);

		if(r.message) {
			r.values = this.get_values_from_response(r.message);
		}

		if(r.values && r.values.length) {
			this.data = this.data.concat(r.values);
			this.render_list(r.values);
			this.update_paging(r.values);
		} else {
			if(this.start===0) {
				this.$w.find('.result').toggle(false);

				var msg = this.get_no_result_message
					? this.get_no_result_message()
					: (this.no_result_message
						? this.no_result_message
						: frappe._("Nothing to show"));

				this.$w.find('.no-result')
					.html(msg)
					.toggle(true);
			}
		}

		// callbacks
		if(this.onrun) this.onrun();
		if(this.callback) this.callback(r);
		this.$w.trigger("render-complete");
	},

	get_values_from_response: function(data) {
		// make dictionaries from keys and values
		if(data.keys) {
			return frappe.utils.dict(data.keys, data.values);
		} else {
			return data;
		}
	},

	render_list: function(values) {
		var m = Math.min(values.length, this.page_length);
		this.data = values;
		if(this.filter_list)
			this.filter_values = this.filter_list.get_filters();

		// render the rows
		for(var i=0; i < m; i++) {
			this.render_row(this.add_row(), values[i], this, i);
		}
	},
	update_paging: function(values) {
		if(values.length >= this.page_length) {
			this.$w.find('.paging-button').toggle(true);
			this.start += this.page_length;
		}
	},
	add_row: function() {
		return $('
').appendTo(this.$w.find('.result-list')).get(0);
	},
	refresh: function() {
		this.run();
	},
	add_limits: function(query) {
		query += ' LIMIT ' + this.start + ',' + (this.page_length+1);
		return query
	},
	set_filter: function(fieldname, label, doctype) {
		if(!doctype) doctype = this.doctype;
		var filter = this.filter_list.get_filter(fieldname);
		if(filter) {
			var v = filter.field.get_parsed_value();
			if(v.indexOf(label)!=-1) {
				// already set
				return this;
			} else {
				// second filter set for this field
				if(fieldname=='_user_tags') {
					// and for tags
					this.filter_list.add_filter(doctype, fieldname,
						'like', '%' + label);
				} else {
					// or for rest using "in"
					filter.set_values(doctype, fieldname, 'in', v + ', ' + label);
				}
			}
		} else {
			// no filter for this item,
			// setup one
			if(['_user_tags', '_comments'].indexOf(fieldname)!==-1) {
				this.filter_list.add_filter(doctype, fieldname,
					'like', '%' + label);
			} else {
				this.filter_list.add_filter(doctype, fieldname, '=', label);
			}
		}
		return this;
	}
});
<commit_msg>fix unterminated string literal<commit_after>// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
// MIT License. See license.txt

// new re-factored Listing object
// removed rarely used functionality
//
// opts:
//   parent

//   method (method to call on server)
//   args (additional args to method)
//   get_args (method to return args as dict)

//   show_filters [false]
//   doctype
//   filter_fields (if given, this list is rendered, else built from doctype)

//   query or get_query (will be deprecated)
//   query_max
//   buttons_in_frame

//   no_result_message ("No result")

//   page_length (20)
//   hide_refresh (False)
//   no_toolbar
//   new_doctype
//   [function] render_row(parent, data)
//   [function] onrun
//   no_loading (no ajax indicator)

frappe.provide('frappe.ui');
frappe.ui.Listing = Class.extend({
	init: function(opts) {
		this.opts = opts || {};
		this.page_length = 20;
		this.start = 0;
		this.data = [];
		if(opts) {
			this.make();
		}
	},
	prepare_opts: function() {
		if(this.opts.new_doctype) {
			if(frappe.boot.profile.can_create.indexOf(this.opts.new_doctype)==-1) {
				this.opts.new_doctype = null;
			} else {
				this.opts.new_doctype = this.opts.new_doctype;
			}
		}
		if(!this.opts.no_result_message) {
			this.opts.no_result_message = frappe._('Nothing to show');
		}
		if(!this.opts.page_length) {
			this.opts.page_length = 20;
		}
		this.opts._more = frappe._("More");
	},
	make: function(opts) {
		if(opts) {
			this.opts = opts;
		}
		this.prepare_opts();
		$.extend(this, this.opts);

		$(this.parent).html(repl('\

\

%(title)s
\
				\

\

\

\

\
							<button class="btn btn-info search-btn">\
								 \
								Search</button>\
							<button class="btn btn-default add-filter-btn">\
								 \
								Add Filter</button>\

\

\

\
				\

\

\

\

\

\

\

\
					%(no_result_message)s\

\

\

\

\

\

\

\
							<button class="btn btn-default btn-more">%(_more)s...</button>\

\

\

\
								<button type="button" class="btn btn-default btn-small btn-info" data-value="20">20</button>\
								<button type="button" class="btn btn-default btn-small" data-value="100">100</button>\
								<button type="button" class="btn btn-default btn-small" data-value="500">500</button>\

\

\

\

\

\
		', this.opts));
		this.$w = $(this.parent).find('.frappe-list');
		this.set_events();

		if(this.appframe) {
			this.$w.find('.list-toolbar-wrapper').toggle(false);
		}

		if(this.show_filters) {
			this.make_filters();
		}
	},
	add_button: function(label, click, icon) {
		if(this.appframe) {
			return this.appframe.add_button(label, click, icon)
		} else {
			$button = $('<button class="btn btn-default"></button>')
				.appendTo(this.$w.find('.list-toolbar'))
				.html((icon ? (" ") : "") + label)
				.click(click);
			return $button
		}
	},
	show_view: function($btn, $div, $btn_unsel, $div_unsel) {
		$btn_unsel.removeClass('btn-info');
		$btn_unsel.find('i').removeClass('icon-white');
		$div_unsel.toggle(false);

		$btn.addClass('btn-info');
		$btn.find('i').addClass('icon-white');
		$div.toggle(true);
	},
	set_events: function() {
		var me = this;

		// next page
		this.$w.find('.btn-more').click(function() {
			me.run({append: true });
		});

		this.$w.find(".btn-group-paging .btn").click(function() {
			me.page_length = cint($(this).attr("data-value"));
			me.run({append: true});
		})

		// title
		if(this.title) {
			this.$w.find('h3').html(this.title).toggle(true);
		}

		// hide-refresh
		if(!(this.hide_refresh || this.no_refresh)) {
			this.add_button('Refresh', function() {
				me.run();
			}, 'icon-refresh');

		}

		// new
		if(this.new_doctype) {
			if(this.appframe) {
				this.appframe.set_title_right(" " + frappe._('New'), function() {
					(me.custom_new_doc || me.make_new_doc).apply(me, [me.new_doctype]); });
			}
			this.add_button(frappe._('New'), function() {
				(me.custom_new_doc || me.make_new_doc).apply(me, [me.new_doctype]);
			}, 'icon-plus');
		}

		// hide-filter
		if(me.show_filters) {
			this.add_button(frappe._('Filter'), function() {
				me.filter_list.show_filters();
			}, 'icon-search').addClass('btn-filter');
		}

		if(me.no_toolbar || me.hide_toolbar) {
			me.$w.find('.list-toolbar-wrapper').toggle(false);
		}
	},

	make_new_doc: function(doctype) {
		var me = this;
		frappe.model.with_doctype(doctype, function() {
			var doc = frappe.model.get_new_doc(doctype);
			if(me.filter_list) {
				$.each(me.filter_list.get_filters(), function(i, f) {
					if(f[0]===doctype && f[2]==="=" && f[1]!=="name")
						doc[f[1]]=f[3];
				})
			}
			frappe.set_route("Form", doctype, doc.name);
		});
	},

	make_filters: function() {
		this.filter_list = new frappe.ui.FilterList({
			listobj: this,
			$parent: this.$w.find('.list-filters').toggle(true),
			doctype: this.doctype,
			filter_fields: this.filter_fields
		});
	},

	clear: function() {
		this.data = [];
		this.$w.find('.result-list').empty();
		this.$w.find('.result').toggle(true);
		this.$w.find('.no-result').toggle(false);
		this.start = 0;
	},
	run: function(more) {
		var me = this;
		if(!more) {
			this.start = 0;
			if(this.onreset) this.onreset();
		}

		if(!me.opts.no_loading)
			me.set_working(true);

		return frappe.call({
			method: this.opts.method || 'frappe.widgets.query_builder.runquery',
			type: "GET",
			args: this.get_call_args(),
			callback: function(r) {
				if(!me.opts.no_loading)
					me.set_working(false);
				me.dirty = false;
				me.render_results(r);
			},
			no_spinner: this.opts.no_loading
		});
	},
	set_working: function(flag) {
		this.$w.find('.img-load').toggle(flag);
	},
	get_call_args: function() {
		// load query
		if(!this.method) {
			var query = this.get_query ? this.get_query() : this.query;
			query = this.add_limits(query);
			var args={
				query_max: this.query_max,
				as_dict: 1
			}
			args.simple_query = query;
		} else {
			var args = {
				limit_start: this.start,
				limit_page_length: this.page_length
			}
		}

		// append user-defined arguments
		if(this.args)
			$.extend(args, this.args)

		if(this.get_args) {
			$.extend(args, this.get_args());
		}
		return args;
	},
	render_results: function(r) {
		if(this.start===0) this.clear();

		this.$w.find('.paging-button').toggle(false);

		if(r.message) {
			r.values = this.get_values_from_response(r.message);
		}

		if(r.values && r.values.length) {
			this.data = this.data.concat(r.values);
			this.render_list(r.values);
			this.update_paging(r.values);
		} else {
			if(this.start===0) {
				this.$w.find('.result').toggle(false);

				var msg = this.get_no_result_message
					? this.get_no_result_message()
					: (this.no_result_message
						? this.no_result_message
						: frappe._("Nothing to show"));

				this.$w.find('.no-result')
					.html(msg)
					.toggle(true);
			}
		}

		// callbacks
		if(this.onrun) this.onrun();
		if(this.callback) this.callback(r);
		this.$w.trigger("render-complete");
	},

	get_values_from_response: function(data) {
		// make dictionaries from keys and values
		if(data.keys) {
			return frappe.utils.dict(data.keys, data.values);
		} else {
			return data;
		}
	},

	render_list: function(values) {
		var m = Math.min(values.length, this.page_length);
		this.data = values;
		if(this.filter_list)
			this.filter_values = this.filter_list.get_filters();

		// render the rows
		for(var i=0; i < m; i++) {
			this.render_row(this.add_row(), values[i], this, i);
		}
	},
	update_paging: function(values) {
		if(values.length >= this.page_length) {
			this.$w.find('.paging-button').toggle(true);
			this.start += this.page_length;
		}
	},
	add_row: function() {
		return $('
').appendTo(this.$w.find('.result-list')).get(0);
	},
	refresh: function() {
		this.run();
	},
	add_limits: function(query) {
		query += ' LIMIT ' + this.start + ',' + (this.page_length+1);
		return query
	},
	set_filter: function(fieldname, label, doctype) {
		if(!doctype) doctype = this.doctype;
		var filter = this.filter_list.get_filter(fieldname);
		if(filter) {
			var v = filter.field.get_parsed_value();
			if(v.indexOf(label)!=-1) {
				// already set
				return this;
			} else {
				// second filter set for this field
				if(fieldname=='_user_tags') {
					// and for tags
					this.filter_list.add_filter(doctype, fieldname,
						'like', '%' + label);
				} else {
					// or for rest using "in"
					filter.set_values(doctype, fieldname, 'in', v + ', ' + label);
				}
			}
		} else {
			// no filter for this item,
			// setup one
			if(['_user_tags', '_comments'].indexOf(fieldname)!==-1) {
				this.filter_list.add_filter(doctype, fieldname,
					'like', '%' + label);
			} else {
				this.filter_list.add_filter(doctype, fieldname, '=', label);
			}
		}
		return this;
	}
});
<|endoftext|>


<commit_before>define([
	"dojo/_base/array", // array.forEach
	"dojo/_base/declare", // declare
	"dojo/dom-attr", // domAttr.set
	"dojo/dom-style", // domStyle.get
	"dojo/_base/lang", // lang.hitch lang.isArray
	"dojo/mouse", // mouse.isLeft
	"dojo/on",
	"dojo/sniff", // has("webkit")
	"dojo/window", // winUtils.scrollIntoView
	"../a11y"	// a11y.hasDefaultTabStop
], function(array, declare, domAttr, domStyle, lang, mouse, on, has, winUtils, a11y){

// module:
//		dijit/form/_FormWidgetMixin

return declare("dijit.form._FormWidgetMixin", null, {
	// summary:
	//		Mixin for widgets corresponding to native HTML elements such as `<checkbox>` or `<button>`,
	//		which can be children of a `<form>` node or a `dijit/form/Form` widget.
	//
	// description:
	//		Represents a single HTML element.
	//		All these widgets should have these attributes just like native HTML input elements.
	//		You can set them during widget construction or afterwards, via `dijit/_WidgetBase.set()`.
	//
	//		They also share some common methods.

	// name: [const] String
	//		Name used when submitting form; same as "name" attribute or plain HTML elements
	name: "",

	// alt: String
	//		Corresponds to the native HTML `<input>` element's attribute.
	alt: "",

	// value: String
	//		Corresponds to the native HTML `<input>` element's attribute.
	value: "",

	// type: [const] String
	//		Corresponds to the native HTML `<input>` element's attribute.
	type: "text",

	// type: String
	//		Apply aria-label in markup to the widget's focusNode
	"aria-label": "focusNode",

	// tabIndex: String
	//		Order fields are traversed when user hits the tab key
	tabIndex: "0",
	_setTabIndexAttr: "focusNode",	// force copy even when tabIndex default value, needed since Button is

	// disabled: Boolean
	//		Should this widget respond to user input?
	//		In markup, this is specified as "disabled='disabled'", or just "disabled".
	disabled: false,

	// intermediateChanges: Boolean
	//		Fires onChange for each value change or only on demand
	intermediateChanges: false,

	// scrollOnFocus: Boolean
	//		On focus, should this widget scroll into view?
	scrollOnFocus: true,

	// Override _WidgetBase mapping id to this.domNode, needs to be on focusNode so  etc.
	// works with screen reader
	_setIdAttr: "focusNode",

	_setDisabledAttr: function(/*Boolean*/ value){
		this._set("disabled", value);
		domAttr.set(this.focusNode, 'disabled', value);
		if(this.valueNode){
			domAttr.set(this.valueNode, 'disabled', value);
		}
		this.focusNode.setAttribute("aria-disabled", value ? "true" : "false");

		if(value){
			// reset these, because after the domNode is disabled, we can no longer receive
			// mouse related events, see #4200
			this._set("hovering", false);
			this._set("active", false);

			// clear tab stop(s) on this widget's focusable node(s)  (ComboBox has two focusable nodes)
			var attachPointNames = "tabIndex" in this.attributeMap ? this.attributeMap.tabIndex :
				("_setTabIndexAttr" in this) ? this._setTabIndexAttr : "focusNode";
			array.forEach(lang.isArray(attachPointNames) ? attachPointNames : [attachPointNames], function(attachPointName){
				var node = this[attachPointName];
				// complex code because tabIndex=-1 on a
 doesn't work on FF
				if(has("webkit") || a11y.hasDefaultTabStop(node)){	// see #11064 about webkit bug
					node.setAttribute('tabIndex', "-1");
				}else{
					node.removeAttribute('tabIndex');
				}
			}, this);
		}else{
			if(this.tabIndex != ""){
				this.set('tabIndex', this.tabIndex);
			}
		}
	},

	_onFocus: function(/*String*/ by){
		// If user clicks on the widget, even if the mouse is released outside of it,
		// this widget's focusNode should get focus (to mimic native browser behavior).
		// Browsers often need help to make sure the focus via mouse actually gets to the focusNode.
		// TODO: consider removing all of this for 2.0 or sooner, see #16622 etc.
		if(by == "mouse" && this.isFocusable()){
			// IE exhibits strange scrolling behavior when refocusing a node so only do it when !focused.
			var focusHandle = this.own(on(this.focusNode, "focus", function(){
				mouseUpHandle.remove();
				focusHandle.remove();
			}))[0];
			// Set a global event to handle mouseup, so it fires properly
			// even if the cursor leaves this.domNode before the mouse up event.
			var mouseUpHandle = this.own(on(this.ownerDocumentBody, "mouseup, touchend", lang.hitch(this, function(){
				mouseUpHandle.remove();
				focusHandle.remove();
				// if here, then the mousedown did not focus the focusNode as the default action
				if(this.focused){
					this.defer("focus");
				}
			})))[0];
		}
		if(this.scrollOnFocus){
			this.defer(function(){ winUtils.scrollIntoView(this.domNode); }); // without defer, the input caret position can change on mouse click
		}
		this.inherited(arguments);
	},

	isFocusable: function(){
		// summary:
		//		Tells if this widget is focusable or not.  Used internally by dijit.
		// tags:
		//		protected
		return !this.disabled && this.focusNode && (domStyle.get(this.domNode, "display") != "none");
	},

	focus: function(){
		// summary:
		//		Put focus on this widget
		if(!this.disabled && this.focusNode.focus){
			try{ this.focusNode.focus(); }catch(e){}/*squelch errors from hidden nodes*/
		}
	},

	compare: function(/*anything*/ val1, /*anything*/ val2){
		// summary:
		//		Compare 2 values (as returned by get('value') for this widget).
		// tags:
		//		protected
		if(typeof val1 == "number" && typeof val2 == "number"){
			return (isNaN(val1) && isNaN(val2)) ? 0 : val1 - val2;
		}else if(val1 > val2){
			return 1;
		}else if(val1 < val2){
			return -1;
		}else{
			return 0;
		}
	},

	onChange: function(/*===== newValue =====*/){
		// summary:
		//		Callback when this widget's value is changed.
		// tags:
		//		callback
	},

	// _onChangeActive: [private] Boolean
	//		Indicates that changes to the value should call onChange() callback.
	//		This is false during widget initialization, to avoid calling onChange()
	//		when the initial value is set.
	_onChangeActive: false,

	_handleOnChange: function(/*anything*/ newValue, /*Boolean?*/ priorityChange){
		// summary:
		//		Called when the value of the widget is set.  Calls onChange() if appropriate
		// newValue:
		//		the new value
		// priorityChange:
		//		For a slider, for example, dragging the slider is priorityChange==false,
		//		but on mouse up, it's priorityChange==true.  If intermediateChanges==false,
		//		onChange is only called form priorityChange=true events.
		// tags:
		//		private
		if(this._lastValueReported == undefined && (priorityChange === null || !this._onChangeActive)){
			// this block executes not for a change, but during initialization,
			// and is used to store away the original value (or for ToggleButton, the original checked state)
			this._resetValue = this._lastValueReported = newValue;
		}
		this._pendingOnChange = this._pendingOnChange
			|| (typeof newValue != typeof this._lastValueReported)
			|| (this.compare(newValue, this._lastValueReported) != 0);
		if((this.intermediateChanges || priorityChange || priorityChange === undefined) && this._pendingOnChange){
			this._lastValueReported = newValue;
			this._pendingOnChange = false;
			if(this._onChangeActive){
				if(this._onChangeHandle){
					this._onChangeHandle.remove();
				}
				// defer allows hidden value processing to run and
				// also the onChange handler can safely adjust focus, etc
				this._onChangeHandle = this.defer(
					function(){
						this._onChangeHandle = null;
						this.onChange(newValue);
					}); // try to collapse multiple onChange's fired faster than can be processed
			}
		}
	},

	create: function(){
		// Overrides _Widget.create()
		this.inherited(arguments);
		this._onChangeActive = true;
	},

	destroy: function(){
		if(this._onChangeHandle){ // destroy called before last onChange has fired
			this._onChangeHandle.remove();
			this.onChange(this._lastValueReported);
		}
		this.inherited(arguments);
	}
});

});
<commit_msg>Fixes #16725.  Defer the alternate-focusNode-focus() for touchend only so that we're not fighting native focus events.<commit_after>define([
	"dojo/_base/array", // array.forEach
	"dojo/_base/declare", // declare
	"dojo/dom-attr", // domAttr.set
	"dojo/dom-style", // domStyle.get
	"dojo/_base/lang", // lang.hitch lang.isArray
	"dojo/mouse", // mouse.isLeft
	"dojo/on",
	"dojo/sniff", // has("webkit")
	"dojo/window", // winUtils.scrollIntoView
	"../a11y"	// a11y.hasDefaultTabStop
], function(array, declare, domAttr, domStyle, lang, mouse, on, has, winUtils, a11y){

// module:
//		dijit/form/_FormWidgetMixin

return declare("dijit.form._FormWidgetMixin", null, {
	// summary:
	//		Mixin for widgets corresponding to native HTML elements such as `<checkbox>` or `<button>`,
	//		which can be children of a `<form>` node or a `dijit/form/Form` widget.
	//
	// description:
	//		Represents a single HTML element.
	//		All these widgets should have these attributes just like native HTML input elements.
	//		You can set them during widget construction or afterwards, via `dijit/_WidgetBase.set()`.
	//
	//		They also share some common methods.

	// name: [const] String
	//		Name used when submitting form; same as "name" attribute or plain HTML elements
	name: "",

	// alt: String
	//		Corresponds to the native HTML `<input>` element's attribute.
	alt: "",

	// value: String
	//		Corresponds to the native HTML `<input>` element's attribute.
	value: "",

	// type: [const] String
	//		Corresponds to the native HTML `<input>` element's attribute.
	type: "text",

	// type: String
	//		Apply aria-label in markup to the widget's focusNode
	"aria-label": "focusNode",

	// tabIndex: String
	//		Order fields are traversed when user hits the tab key
	tabIndex: "0",
	_setTabIndexAttr: "focusNode",	// force copy even when tabIndex default value, needed since Button is

	// disabled: Boolean
	//		Should this widget respond to user input?
	//		In markup, this is specified as "disabled='disabled'", or just "disabled".
	disabled: false,

	// intermediateChanges: Boolean
	//		Fires onChange for each value change or only on demand
	intermediateChanges: false,

	// scrollOnFocus: Boolean
	//		On focus, should this widget scroll into view?
	scrollOnFocus: true,

	// Override _WidgetBase mapping id to this.domNode, needs to be on focusNode so  etc.
	// works with screen reader
	_setIdAttr: "focusNode",

	_setDisabledAttr: function(/*Boolean*/ value){
		this._set("disabled", value);
		domAttr.set(this.focusNode, 'disabled', value);
		if(this.valueNode){
			domAttr.set(this.valueNode, 'disabled', value);
		}
		this.focusNode.setAttribute("aria-disabled", value ? "true" : "false");

		if(value){
			// reset these, because after the domNode is disabled, we can no longer receive
			// mouse related events, see #4200
			this._set("hovering", false);
			this._set("active", false);

			// clear tab stop(s) on this widget's focusable node(s)  (ComboBox has two focusable nodes)
			var attachPointNames = "tabIndex" in this.attributeMap ? this.attributeMap.tabIndex :
				("_setTabIndexAttr" in this) ? this._setTabIndexAttr : "focusNode";
			array.forEach(lang.isArray(attachPointNames) ? attachPointNames : [attachPointNames], function(attachPointName){
				var node = this[attachPointName];
				// complex code because tabIndex=-1 on a
 doesn't work on FF
				if(has("webkit") || a11y.hasDefaultTabStop(node)){	// see #11064 about webkit bug
					node.setAttribute('tabIndex', "-1");
				}else{
					node.removeAttribute('tabIndex');
				}
			}, this);
		}else{
			if(this.tabIndex != ""){
				this.set('tabIndex', this.tabIndex);
			}
		}
	},

	_onFocus: function(/*String*/ by){
		// If user clicks on the widget, even if the mouse is released outside of it,
		// this widget's focusNode should get focus (to mimic native browser behavior).
		// Browsers often need help to make sure the focus via mouse actually gets to the focusNode.
		// TODO: consider removing all of this for 2.0 or sooner, see #16622 etc.
		if(by == "mouse" && this.isFocusable()){
			// IE exhibits strange scrolling behavior when refocusing a node so only do it when !focused.
			var focusHandle = this.own(on(this.focusNode, "focus", function(){
				mouseUpHandle.remove();
				focusHandle.remove();
			}))[0];
			// Set a global event to handle mouseup, so it fires properly
			// even if the cursor leaves this.domNode before the mouse up event.
			var mouseUpHandle = this.own(on(this.ownerDocumentBody, "mouseup, touchend", lang.hitch(this, function(evt){
				mouseUpHandle.remove();
				focusHandle.remove();
				// if here, then the mousedown did not focus the focusNode as the default action
				if(this.focused){
					if(evt.type == "touchend"){
						this.defer("focus"); // native focus hasn't occurred yet
					}else{
						this.focus(); // native focus already occurred on mousedown
					}
				}
			})))[0];
		}
		if(this.scrollOnFocus){
			this.defer(function(){ winUtils.scrollIntoView(this.domNode); }); // without defer, the input caret position can change on mouse click
		}
		this.inherited(arguments);
	},

	isFocusable: function(){
		// summary:
		//		Tells if this widget is focusable or not.  Used internally by dijit.
		// tags:
		//		protected
		return !this.disabled && this.focusNode && (domStyle.get(this.domNode, "display") != "none");
	},

	focus: function(){
		// summary:
		//		Put focus on this widget
		if(!this.disabled && this.focusNode.focus){
			try{ this.focusNode.focus(); }catch(e){}/*squelch errors from hidden nodes*/
		}
	},

	compare: function(/*anything*/ val1, /*anything*/ val2){
		// summary:
		//		Compare 2 values (as returned by get('value') for this widget).
		// tags:
		//		protected
		if(typeof val1 == "number" && typeof val2 == "number"){
			return (isNaN(val1) && isNaN(val2)) ? 0 : val1 - val2;
		}else if(val1 > val2){
			return 1;
		}else if(val1 < val2){
			return -1;
		}else{
			return 0;
		}
	},

	onChange: function(/*===== newValue =====*/){
		// summary:
		//		Callback when this widget's value is changed.
		// tags:
		//		callback
	},

	// _onChangeActive: [private] Boolean
	//		Indicates that changes to the value should call onChange() callback.
	//		This is false during widget initialization, to avoid calling onChange()
	//		when the initial value is set.
	_onChangeActive: false,

	_handleOnChange: function(/*anything*/ newValue, /*Boolean?*/ priorityChange){
		// summary:
		//		Called when the value of the widget is set.  Calls onChange() if appropriate
		// newValue:
		//		the new value
		// priorityChange:
		//		For a slider, for example, dragging the slider is priorityChange==false,
		//		but on mouse up, it's priorityChange==true.  If intermediateChanges==false,
		//		onChange is only called form priorityChange=true events.
		// tags:
		//		private
		if(this._lastValueReported == undefined && (priorityChange === null || !this._onChangeActive)){
			// this block executes not for a change, but during initialization,
			// and is used to store away the original value (or for ToggleButton, the original checked state)
			this._resetValue = this._lastValueReported = newValue;
		}
		this._pendingOnChange = this._pendingOnChange
			|| (typeof newValue != typeof this._lastValueReported)
			|| (this.compare(newValue, this._lastValueReported) != 0);
		if((this.intermediateChanges || priorityChange || priorityChange === undefined) && this._pendingOnChange){
			this._lastValueReported = newValue;
			this._pendingOnChange = false;
			if(this._onChangeActive){
				if(this._onChangeHandle){
					this._onChangeHandle.remove();
				}
				// defer allows hidden value processing to run and
				// also the onChange handler can safely adjust focus, etc
				this._onChangeHandle = this.defer(
					function(){
						this._onChangeHandle = null;
						this.onChange(newValue);
					}); // try to collapse multiple onChange's fired faster than can be processed
			}
		}
	},

	create: function(){
		// Overrides _Widget.create()
		this.inherited(arguments);
		this._onChangeActive = true;
	},

	destroy: function(){
		if(this._onChangeHandle){ // destroy called before last onChange has fired
			this._onChangeHandle.remove();
			this.onChange(this._lastValueReported);
		}
		this.inherited(arguments);
	}
});

});
<|endoftext|>


<commit_before>/* Copyright (C) 2017 Canonical Ltd. */

'use strict';

/** Status React component used to display Juju status. */
class Status extends React.Component {
  /**
    Return an element class name suitable for the given value.
    @param {String} value The provided value.
    @returns {String} The class name ('ok', 'error' or '').
  */
  _getClass(value) {
    switch (value) {
      case 'active':
      case 'idle':
      case 'started':
        return 'ok';
      case 'blocked':
      case 'down':
      case 'error':
        return 'error';
    }
    return '';
  }

  /**
    Sort by the key attribute.
    @param {Object} a The first value.
    @param {Object} b The second value.
    @returns {Array} The sorted array.
  */
  _byKey(a, b) {
    if (a.key  b.key)
      return 1;
    return 0;
  }

  /**
    Generate the current model status.
    @returns {Object} The resulting element.
  */
  _generateStatus() {
    const elements = [];
    const db = this.props.db;
    const model = this.props.model;
    if (!model.environmentName) {
      // No need to go further: we are not connected to a model.
      return 'Cannot show the status: the GUI is not connected to a model.';
    }
    elements.push(this._generateModel(model));
    if (db.remoteServices.size()) {
      elements.push(this._generateRemoteApplications(db.remoteServices));
    }
    if (db.services.size()) {
      elements.push(
        this._generateApplications(db.services),
        this._generateUnits(db.services)
      );
    }
    if (db.machines.size()) {
      elements.push(this._generateMachines(db.machines));
    }
    if (db.relations.size()) {
      elements.push(this._generateRelations(db.relations));
    }
    return elements;
  }

  /**
    Generate the model fragment of the status.
    @param {Object} model The model attributes.
    @returns {Object} The resulting element.
  */
  _generateModel(model) {
    return (
      <juju.components.BasicTable
        headers={[{
          content: 'Model',
          columnSize: 3
        }, {
          content: 'Cloud/Region',
          columnSize: 3
        }, {
          content: 'Version',
          columnSize: 3
        }, {
          content: 'SLA',
          columnSize: 3
        }]}
        key="model"
        rows={[{
          columns: [{
            columnSize: 3,
            content: model.environmentName
          }, {
            columnSize: 3,
            content: `${model.cloud}/${model.region}`
          }, {
            columnSize: 3,
            content: model.version
          }, {
            columnSize: 3,
            content: model.sla
          }],
          key: 'model'
        }]} />);
  }

  /**
    Navigate to the chosen application.
    @param appId {String} The id of the application to display.
  */
  _navigateToApplication(appId) {
    // Navigate to the app in the inspector, clearing the state so that the
    // app overview is shown.
    this.props.changeState({
      gui: {
        inspector: {
          id: appId,
          activeComponent: undefined,
          unit: null,
          unitStatus: null
        }
      }
    });
  }

  /**
    Navigate to the chosen charm.
    @param charmURL {String} The id of the charm to display.
  */
  _navigateToCharm(charmURL) {
    this.props.changeState({store: charmURL});
  }

  /**
    Navigate to the chosen unit.
    @param unitId {String} The id of the unit to display in the format
      'app-id/unit-id'.
  */
  _navigateToUnit(unitId) {
    const unitParts = unitId.split('/');
    this.props.changeState({
      gui: {
        inspector: {
          id: unitParts[0],
          unit: unitParts[1],
          activeComponent: 'unit'
        }
      }
    });
  }

  /**
    Navigate to the chosen machine.
    @param machineId {String} The id of the machine to display.
  */
  _navigateToMachine(machineId) {
    this.props.changeState({
      gui: {
        machines: machineId,
        status: null
      }
    });
  }

  /**
    Generate the remote applications fragment of the status.
    @param {Object} remoteApplications The remote applications as included in
      the GUI db.
    @returns {Object} The resulting element.
  */
  _generateRemoteApplications(remoteApplications) {
    const rows = remoteApplications.map(application => {
      const app = application.getAttrs();
      const urlParts = app.url.split(':');
      return {
        columns: [{
          columnSize: 3,
          content: app.service
        }, {
          columnSize: 3,
          content: app.status.current
        }, {
          columnSize: 3,
          content: urlParts[0]
        }, {
          columnSize: 3,
          content: urlParts[1]
        }],
        key: app.url
      };
    });
    return (
      <juju.components.BasicTable
        headers={[{
          content: 'SAAS',
          columnSize: 3
        }, {
          content: 'Status',
          columnSize: 3
        }, {
          content: 'Store',
          columnSize: 3
        }, {
          content: 'URL',
          columnSize: 3
        }]}
        key="remote-applications"
        rows={rows}
        sort={this._byKey} />);
  }

  /**
    Generate the applications fragment of the status.
    @param {Object} applications The applications as included in the GUI db.
    @returns {Object} The resulting element.
  */
  _generateApplications(applications) {
    const urllib = this.props.urllib;
    const rows = applications.map((application, i) => {
      const app = application.getAttrs();
      const charm = urllib.fromLegacyString(app.charm);
      const store = charm.schema === 'cs' ? 'jujucharms' : 'local';
      const revision = charm.revision;
      const charmId = charm.path();
      // Set the revision to null so that it's not included when calling
      // charm.path() below.
      charm.revision = null;
      return {
        columns:[{
          columnSize: 2,
          content: (

              {app.name}
            )
        }, {
          columnSize: 2,
          content: app.workloadVersion
        }, {
          columnSize: 2,
          content: (

              {app.status.current}
            )
        }, {
          columnSize: 1,
          content: app.units.size()
        }, {
          columnSize: 2,
          content: (

              {charm.path()}
            )
        }, {
          columnSize: 2,
          content: store
        }, {
          columnSize: 1,
          content: revision
        }],
        key: app.name
      };
    });
    return (
      <juju.components.BasicTable
        headers={[{
          content: 'Application',
          columnSize: 2
        }, {
          content: 'Version',
          columnSize: 2
        }, {
          content: 'Status',
          columnSize: 2
        }, {
          content: 'Scale',
          columnSize: 1
        }, {
          content: 'Charm',
          columnSize: 2
        }, {
          content: 'Store',
          columnSize: 2
        }, {
          content: 'Rev',
          columnSize: 1
        }]}
        key="applications"
        rows={rows}
        sort={this._byKey} />);
  }

  /**
    Generate the units fragment of the status.
    @param {Object} applications The applications as included in the GUI db.
    @returns {Object} The resulting element.
  */
  _generateUnits(applications) {
    const formatPorts = ranges => {
      if (!ranges) {
        return '';
      }
      return ranges.map(range => {
        if (range.from === range.to) {
          return `${range.from}/${range.protocol}`;
        }
        return `${range.from}-${range.to}/${range.protocol}`;
      }).join(', ');
    };
    const rows = [];
    applications.each(application => {
      application.get('units').each((unit, i) => {
        rows.push({
          columns: [{
            columnSize: 2,
            content: (

                {unit.displayName}
              )
          }, {
            columnSize: 2,
            content: (

                {unit.workloadStatus}
              )
          }, {
            columnSize: 2,
            content: (

                {unit.agentStatus}
              )
          }, {
            columnSize: 1,
            content: (

                {unit.machine}
              )
          }, {
            columnSize: 2,
            content: unit.public_address
          }, {
            columnSize: 1,
            content: formatPorts(unit.portRanges)
          }, {
            columnSize: 2,
            content: unit.workloadStatusMessage
          }],
          key: unit.id
        });
      });
    });
    if (!rows.length) {
      return null;
    }
    return (
      <juju.components.BasicTable
        headers={[{
          content: 'Unit',
          columnSize: 2
        }, {
          content: 'Workload',
          columnSize: 2
        }, {
          content: 'Agent',
          columnSize: 2
        }, {
          content: 'Machine',
          columnSize: 1
        }, {
          content: 'Public address',
          columnSize: 2
        }, {
          content: 'Ports',
          columnSize: 1
        }, {
          content: 'Message',
          columnSize: 2
        }]}
        key="units"
        rows={rows.sort(this._byKey.bind(this, 0))}
        sort={this._byKey} />);
  }

  /**
    Generate the machines fragment of the status.
    @param {Object} machines The machines as included in the GUI db.
    @returns {Object} The resulting element.
  */
  _generateMachines(machines) {
    const rows = machines.map((machine, i) => {
      return {
        columns: [{
          columnSize: 2,
          content: (

              {machine.displayName}
            )
        }, {
          columnSize: 2,
          content: (

              {machine.agent_state}
            )
        }, {
          columnSize: 2,
          content: machine.public_address
        }, {
          columnSize: 2,
          content: machine.instance_id
        }, {
          columnSize: 2,
          content: machine.series
        }, {
          columnSize: 2,
          content: machine.agent_state_info
        }],
        key: machine.id
      };
    });
    return (
      <juju.components.BasicTable
        headers={[{
          content: 'Machine',
          columnSize: 2
        }, {
          content: 'State',
          columnSize: 2
        }, {
          content: 'DNS',
          columnSize: 2
        }, {
          content: 'Instance ID',
          columnSize: 2
        }, {
          content: 'Series',
          columnSize: 2
        }, {
          content: 'Message',
          columnSize: 2
        }]}
        key="machines"
        rows={rows.sort(this._byKey.bind(this, 0))}
        sort={this._byKey} />);
  }

  /**
    Generate the relations fragment of the status.
    @param {Array} relations The relations as included in the GUI db.
    @returns {Object} The resulting element.
  */
  _generateRelations(relations) {
    const rows = relations.map(relation => {
      const rel = relation.getAttrs();
      let name = '';
      let provides = '';
      let consumes = '';
      let scope = '';
      rel.endpoints.forEach(endpoint => {
        const application = endpoint[0];
        const ep = endpoint[1];
        switch (ep.role) {
          case 'peer':
            name = ep.name;
            provides = application;
            consumes = application;
            scope = 'peer';
            return;
          case 'provider':
            name = ep.name;
            consumes = application;
            scope = 'regular';
            break;
          case 'requirer':
            provides = application;
            break;
        }
      });
      return {
        columns: [{
          columnSize: 3,
          content: name
        }, {
          columnSize: 3,
          content: (

              {provides}
            )
        }, {
          columnSize: 3,
          content: (

              {consumes}
            )
        }, {
          columnSize: 3,
          content: scope
        }],
        key: rel.id
      };
    });
    return (
      <juju.components.BasicTable
        headers={[{
          content: 'Relation',
          columnSize: 3
        }, {
          content: 'Provides',
          columnSize: 3
        }, {
          content: 'Consumes',
          columnSize: 3
        }, {
          content: 'Type',
          columnSize: 3
        }]}
        key="relations"
        rows={rows.sort(this._byKey.bind(this, 0))}
        sort={this._byKey} />);
  }

  render() {
    return (
      <juju.components.Panel
        instanceName="status-view"
        visible={true}>

          {this._generateStatus()}

      </juju.components.Panel>
    );
  }
};

Status.propTypes = {
  changeState: PropTypes.func.isRequired,
  db: shapeup.shape({
    machines: shapeup.shape({
      map: PropTypes.func.isRequired,
      size: PropTypes.func.isRequired
    }).isRequired,
    relations: shapeup.shape({
      map: PropTypes.func.isRequired,
      size: PropTypes.func.isRequired
    }).isRequired,
    remoteServices: shapeup.shape({
      map: PropTypes.func.isRequired,
      size: PropTypes.func.isRequired
    }).isRequired,
    services: shapeup.shape({
      each: PropTypes.func.isRequired,
      map: PropTypes.func.isRequired,
      size: PropTypes.func.isRequired
    }).isRequired
  }).frozen.isRequired,
  model: shapeup.shape({
    cloud: PropTypes.string,
    environmentName: PropTypes.string,
    region: PropTypes.string,
    sla: PropTypes.string,
    version: PropTypes.string
  }).frozen.isRequired,
  urllib: shapeup.shape({
    fromLegacyString: PropTypes.func.isRequired
  }).frozen.isRequired
};

YUI.add('status', function() {
  juju.components.Status = Status;
}, '', {
  requires: [
    'basic-table',
    'panel-component'
  ]
});
<commit_msg>Make unit variable name more helpful.<commit_after>/* Copyright (C) 2017 Canonical Ltd. */

'use strict';

/** Status React component used to display Juju status. */
class Status extends React.Component {
  /**
    Return an element class name suitable for the given value.
    @param {String} value The provided value.
    @returns {String} The class name ('ok', 'error' or '').
  */
  _getClass(value) {
    switch (value) {
      case 'active':
      case 'idle':
      case 'started':
        return 'ok';
      case 'blocked':
      case 'down':
      case 'error':
        return 'error';
    }
    return '';
  }

  /**
    Sort by the key attribute.
    @param {Object} a The first value.
    @param {Object} b The second value.
    @returns {Array} The sorted array.
  */
  _byKey(a, b) {
    if (a.key  b.key)
      return 1;
    return 0;
  }

  /**
    Generate the current model status.
    @returns {Object} The resulting element.
  */
  _generateStatus() {
    const elements = [];
    const db = this.props.db;
    const model = this.props.model;
    if (!model.environmentName) {
      // No need to go further: we are not connected to a model.
      return 'Cannot show the status: the GUI is not connected to a model.';
    }
    elements.push(this._generateModel(model));
    if (db.remoteServices.size()) {
      elements.push(this._generateRemoteApplications(db.remoteServices));
    }
    if (db.services.size()) {
      elements.push(
        this._generateApplications(db.services),
        this._generateUnits(db.services)
      );
    }
    if (db.machines.size()) {
      elements.push(this._generateMachines(db.machines));
    }
    if (db.relations.size()) {
      elements.push(this._generateRelations(db.relations));
    }
    return elements;
  }

  /**
    Generate the model fragment of the status.
    @param {Object} model The model attributes.
    @returns {Object} The resulting element.
  */
  _generateModel(model) {
    return (
      <juju.components.BasicTable
        headers={[{
          content: 'Model',
          columnSize: 3
        }, {
          content: 'Cloud/Region',
          columnSize: 3
        }, {
          content: 'Version',
          columnSize: 3
        }, {
          content: 'SLA',
          columnSize: 3
        }]}
        key="model"
        rows={[{
          columns: [{
            columnSize: 3,
            content: model.environmentName
          }, {
            columnSize: 3,
            content: `${model.cloud}/${model.region}`
          }, {
            columnSize: 3,
            content: model.version
          }, {
            columnSize: 3,
            content: model.sla
          }],
          key: 'model'
        }]} />);
  }

  /**
    Navigate to the chosen application.
    @param appId {String} The id of the application to display.
  */
  _navigateToApplication(appId) {
    // Navigate to the app in the inspector, clearing the state so that the
    // app overview is shown.
    this.props.changeState({
      gui: {
        inspector: {
          id: appId,
          activeComponent: undefined,
          unit: null,
          unitStatus: null
        }
      }
    });
  }

  /**
    Navigate to the chosen charm.
    @param charmURL {String} The id of the charm to display.
  */
  _navigateToCharm(charmURL) {
    this.props.changeState({store: charmURL});
  }

  /**
    Navigate to the chosen unit.
    @param unitName {String} The name of the unit to display in the format
      'app-id/unit-number'.
  */
  _navigateToUnit(unitName) {
    const unitParts = unitName.split('/');
    this.props.changeState({
      gui: {
        inspector: {
          id: unitParts[0],
          unit: unitParts[1],
          activeComponent: 'unit'
        }
      }
    });
  }

  /**
    Navigate to the chosen machine.
    @param machineId {String} The id of the machine to display.
  */
  _navigateToMachine(machineId) {
    this.props.changeState({
      gui: {
        machines: machineId,
        status: null
      }
    });
  }

  /**
    Generate the remote applications fragment of the status.
    @param {Object} remoteApplications The remote applications as included in
      the GUI db.
    @returns {Object} The resulting element.
  */
  _generateRemoteApplications(remoteApplications) {
    const rows = remoteApplications.map(application => {
      const app = application.getAttrs();
      const urlParts = app.url.split(':');
      return {
        columns: [{
          columnSize: 3,
          content: app.service
        }, {
          columnSize: 3,
          content: app.status.current
        }, {
          columnSize: 3,
          content: urlParts[0]
        }, {
          columnSize: 3,
          content: urlParts[1]
        }],
        key: app.url
      };
    });
    return (
      <juju.components.BasicTable
        headers={[{
          content: 'SAAS',
          columnSize: 3
        }, {
          content: 'Status',
          columnSize: 3
        }, {
          content: 'Store',
          columnSize: 3
        }, {
          content: 'URL',
          columnSize: 3
        }]}
        key="remote-applications"
        rows={rows}
        sort={this._byKey} />);
  }

  /**
    Generate the applications fragment of the status.
    @param {Object} applications The applications as included in the GUI db.
    @returns {Object} The resulting element.
  */
  _generateApplications(applications) {
    const urllib = this.props.urllib;
    const rows = applications.map((application, i) => {
      const app = application.getAttrs();
      const charm = urllib.fromLegacyString(app.charm);
      const store = charm.schema === 'cs' ? 'jujucharms' : 'local';
      const revision = charm.revision;
      const charmId = charm.path();
      // Set the revision to null so that it's not included when calling
      // charm.path() below.
      charm.revision = null;
      return {
        columns:[{
          columnSize: 2,
          content: (

              {app.name}
            )
        }, {
          columnSize: 2,
          content: app.workloadVersion
        }, {
          columnSize: 2,
          content: (

              {app.status.current}
            )
        }, {
          columnSize: 1,
          content: app.units.size()
        }, {
          columnSize: 2,
          content: (

              {charm.path()}
            )
        }, {
          columnSize: 2,
          content: store
        }, {
          columnSize: 1,
          content: revision
        }],
        key: app.name
      };
    });
    return (
      <juju.components.BasicTable
        headers={[{
          content: 'Application',
          columnSize: 2
        }, {
          content: 'Version',
          columnSize: 2
        }, {
          content: 'Status',
          columnSize: 2
        }, {
          content: 'Scale',
          columnSize: 1
        }, {
          content: 'Charm',
          columnSize: 2
        }, {
          content: 'Store',
          columnSize: 2
        }, {
          content: 'Rev',
          columnSize: 1
        }]}
        key="applications"
        rows={rows}
        sort={this._byKey} />);
  }

  /**
    Generate the units fragment of the status.
    @param {Object} applications The applications as included in the GUI db.
    @returns {Object} The resulting element.
  */
  _generateUnits(applications) {
    const formatPorts = ranges => {
      if (!ranges) {
        return '';
      }
      return ranges.map(range => {
        if (range.from === range.to) {
          return `${range.from}/${range.protocol}`;
        }
        return `${range.from}-${range.to}/${range.protocol}`;
      }).join(', ');
    };
    const rows = [];
    applications.each(application => {
      application.get('units').each((unit, i) => {
        rows.push({
          columns: [{
            columnSize: 2,
            content: (

                {unit.displayName}
              )
          }, {
            columnSize: 2,
            content: (

                {unit.workloadStatus}
              )
          }, {
            columnSize: 2,
            content: (

                {unit.agentStatus}
              )
          }, {
            columnSize: 1,
            content: (

                {unit.machine}
              )
          }, {
            columnSize: 2,
            content: unit.public_address
          }, {
            columnSize: 1,
            content: formatPorts(unit.portRanges)
          }, {
            columnSize: 2,
            content: unit.workloadStatusMessage
          }],
          key: unit.id
        });
      });
    });
    if (!rows.length) {
      return null;
    }
    return (
      <juju.components.BasicTable
        headers={[{
          content: 'Unit',
          columnSize: 2
        }, {
          content: 'Workload',
          columnSize: 2
        }, {
          content: 'Agent',
          columnSize: 2
        }, {
          content: 'Machine',
          columnSize: 1
        }, {
          content: 'Public address',
          columnSize: 2
        }, {
          content: 'Ports',
          columnSize: 1
        }, {
          content: 'Message',
          columnSize: 2
        }]}
        key="units"
        rows={rows.sort(this._byKey.bind(this, 0))}
        sort={this._byKey} />);
  }

  /**
    Generate the machines fragment of the status.
    @param {Object} machines The machines as included in the GUI db.
    @returns {Object} The resulting element.
  */
  _generateMachines(machines) {
    const rows = machines.map((machine, i) => {
      return {
        columns: [{
          columnSize: 2,
          content: (

              {machine.displayName}
            )
        }, {
          columnSize: 2,
          content: (

              {machine.agent_state}
            )
        }, {
          columnSize: 2,
          content: machine.public_address
        }, {
          columnSize: 2,
          content: machine.instance_id
        }, {
          columnSize: 2,
          content: machine.series
        }, {
          columnSize: 2,
          content: machine.agent_state_info
        }],
        key: machine.id
      };
    });
    return (
      <juju.components.BasicTable
        headers={[{
          content: 'Machine',
          columnSize: 2
        }, {
          content: 'State',
          columnSize: 2
        }, {
          content: 'DNS',
          columnSize: 2
        }, {
          content: 'Instance ID',
          columnSize: 2
        }, {
          content: 'Series',
          columnSize: 2
        }, {
          content: 'Message',
          columnSize: 2
        }]}
        key="machines"
        rows={rows.sort(this._byKey.bind(this, 0))}
        sort={this._byKey} />);
  }

  /**
    Generate the relations fragment of the status.
    @param {Array} relations The relations as included in the GUI db.
    @returns {Object} The resulting element.
  */
  _generateRelations(relations) {
    const rows = relations.map(relation => {
      const rel = relation.getAttrs();
      let name = '';
      let provides = '';
      let consumes = '';
      let scope = '';
      rel.endpoints.forEach(endpoint => {
        const application = endpoint[0];
        const ep = endpoint[1];
        switch (ep.role) {
          case 'peer':
            name = ep.name;
            provides = application;
            consumes = application;
            scope = 'peer';
            return;
          case 'provider':
            name = ep.name;
            consumes = application;
            scope = 'regular';
            break;
          case 'requirer':
            provides = application;
            break;
        }
      });
      return {
        columns: [{
          columnSize: 3,
          content: name
        }, {
          columnSize: 3,
          content: (

              {provides}
            )
        }, {
          columnSize: 3,
          content: (

              {consumes}
            )
        }, {
          columnSize: 3,
          content: scope
        }],
        key: rel.id
      };
    });
    return (
      <juju.components.BasicTable
        headers={[{
          content: 'Relation',
          columnSize: 3
        }, {
          content: 'Provides',
          columnSize: 3
        }, {
          content: 'Consumes',
          columnSize: 3
        }, {
          content: 'Type',
          columnSize: 3
        }]}
        key="relations"
        rows={rows.sort(this._byKey.bind(this, 0))}
        sort={this._byKey} />);
  }

  render() {
    return (
      <juju.components.Panel
        instanceName="status-view"
        visible={true}>

          {this._generateStatus()}

      </juju.components.Panel>
    );
  }
};

Status.propTypes = {
  changeState: PropTypes.func.isRequired,
  db: shapeup.shape({
    machines: shapeup.shape({
      map: PropTypes.func.isRequired,
      size: PropTypes.func.isRequired
    }).isRequired,
    relations: shapeup.shape({
      map: PropTypes.func.isRequired,
      size: PropTypes.func.isRequired
    }).isRequired,
    remoteServices: shapeup.shape({
      map: PropTypes.func.isRequired,
      size: PropTypes.func.isRequired
    }).isRequired,
    services: shapeup.shape({
      each: PropTypes.func.isRequired,
      map: PropTypes.func.isRequired,
      size: PropTypes.func.isRequired
    }).isRequired
  }).frozen.isRequired,
  model: shapeup.shape({
    cloud: PropTypes.string,
    environmentName: PropTypes.string,
    region: PropTypes.string,
    sla: PropTypes.string,
    version: PropTypes.string
  }).frozen.isRequired,
  urllib: shapeup.shape({
    fromLegacyString: PropTypes.func.isRequired
  }).frozen.isRequired
};

YUI.add('status', function() {
  juju.components.Status = Status;
}, '', {
  requires: [
    'basic-table',
    'panel-component'
  ]
});
<|endoftext|>


<commit_before>var os = require('os'),
    p = require('path'),
    exec = require('child_process').exec,
    pkg = '';

console.log('Linking background service utilities...');

switch (os.platform().toLowerCase()){
  case 'win32':
    pkg = 'node-windows';
    break;
  case 'darwin':
    pkg = 'node-mac';
    break;
  case 'linux':
    pkg = 'node-linux';
    break;
}

exec('npm config get prefix',function(err,prefix){
  require('fs').symlink(p.join(out,'node_modules',pkg),p.join(out,'node_modules','ngn','node_modules',pkg),'dir',function(){
    console.log('Daemon functionality mapped to NGN.');
  });
});<commit_msg>Typo fix in installer<commit_after>var os = require('os'),
    p = require('path'),
    exec = require('child_process').exec,
    pkg = '';

console.log('Linking background service utilities...');

switch (os.platform().toLowerCase()){
  case 'win32':
    pkg = 'node-windows';
    break;
  case 'darwin':
    pkg = 'node-mac';
    break;
  case 'linux':
    pkg = 'node-linux';
    break;
}

exec('npm config get prefix',function(err,prefix){
  require('fs').symlink(p.join(prefix,'node_modules',pkg),p.join(prefix,'node_modules','ngn','node_modules',pkg),'dir',function(){
    console.log('Daemon functionality mapped to NGN.');
  });
});<|endoftext|>