<commit_before>import THREE from 'three';

import Submesh from './submesh';
import M2Material from './material';
import AnimationManager from './animation-manager';
import WorkerPool from '../worker/pool';

class M2 extends THREE.Group {

  static cache = {};

  constructor(path, data, skinData, instanceOpts) {
    super();

    const instance = instanceOpts || null;

    this.name = path.split('\\').slice(-1).pop();

    this.path = path;
    this.data = data;
    this.skinData = skinData;

    // Instanceable M2s can share geometry and texture units.
    this.canInstance = data.canInstance;

    this.animated = data.animated;
    this.animations = new AnimationManager(this, data.animations);
    this.billboards = [];

    // Keep track of whether or not to use skinning. If the M2 has bone animations, useSkinning is
    // set to true, and all meshes and materials used in the M2 will be skinning enabled. Otherwise,
    // skinning will not be enabled. Skinning has a very significant impact on the render loop in
    // three.js.
    this.useSkinning = false;

    this.mesh = null;
    this.submeshes = [];
    this.parts = new Map();

    this.geometry = null;
    this.submeshGeometries = new Map();

    this.skeleton = null;
    this.bones = [];
    this.rootBones = [];

    this.createSkeleton(data.bones);

    // Instanced M2s can share geometries and texture units.
    if (instance) {
      this.textureUnits = instance.textureUnits;
      this.geometry = instance.geometry;
      this.submeshGeometries = instance.submeshGeometries;
    } else {
      this.createTextureUnits(data, skinData);
      this.createGeometry(data.vertices);
    }

    this.createMesh(this.geometry, this.skeleton, this.rootBones);
    this.createSubmeshes(data, skinData);
  }

  createSkeleton(boneDefs) {
    const rootBones = [];
    const bones = [];
    const billboards = [];

    for (let boneIndex = 0, len = boneDefs.length; boneIndex < len; ++boneIndex) {
      const boneDef = boneDefs[boneIndex];
      const bone = new THREE.Bone();

      bones.push(bone);

      // M2 bone positioning seems to be inverted on X and Y
      const { pivotPoint } = boneDef;
      const correctedPosition = new THREE.Vector3(-pivotPoint.x, -pivotPoint.y, pivotPoint.z);
      bone.position.copy(correctedPosition);

      if (boneDef.parentID > -1) {
        const parent = bones[boneDef.parentID];
        parent.add(bone);

        // Correct bone positioning relative to parent
        let up = bone;
        while (up = up.parent) {
          bone.position.sub(up.position);
        }
      } else {
        bone.userData.isRoot = true;
        rootBones.push(bone);
      }

      // Enable skinning support on this M2 if we have bone animations.
      if (boneDef.animated) {
        this.useSkinning = true;
      }

      // Flag billboarded bones
      if (boneDef.billboarded) {
        bone.userData.billboarded = true;
        billboards.push(bone);
      }

      // Bone translation animation block
      if (boneDef.translation.animated) {
        this.animations.registerTrack({
          target: bone,
          property: 'position',
          animationBlock: boneDef.translation,
          trackType: 'VectorKeyframeTrack',

          valueTransform: function(value) {
            const translation = new THREE.Vector3(-value.x, -value.y, value.z);
            return bone.position.clone().add(translation);
          }
        });
      }

      // Bone rotation animation block
      if (boneDef.rotation.animated) {
        this.animations.registerTrack({
          target: bone,
          property: 'quaternion',
          animationBlock: boneDef.rotation,
          trackType: 'QuaternionKeyframeTrack',

          valueTransform: function(value) {
            return new THREE.Quaternion(value.x, value.y, -value.z, value.w).inverse();
          }
        });
      }

      // Bone scaling animation block
      if (boneDef.scaling.animated) {
        this.animations.registerTrack({
          target: bone,
          property: 'scale',
          animationBlock: boneDef.scaling,
          trackType: 'VectorKeyframeTrack',

          valueTransform: function(value) {
            return new THREE.Vector3(value.x, value.y, value.z);
          }
        });
      }
    }

    // Preserve the bones
    this.bones = bones;
    this.rootBones = rootBones;
    this.billboards = billboards;

    // Assemble the skeleton
    this.skeleton = new THREE.Skeleton(bones);
  }

  // Returns a map of M2Materials indexed by submesh. Each material represents a texture unit,
  // to be rendered in the order of appearance in the map's entry for the submesh index.
  createTextureUnits(data, skinData) {
    const textureUnits = new Map();

    const textureUnitDefs = skinData.textureUnits;

    const { textureLookups, textures, renderFlags } = data;
    const { transparencyLookups, transparencies, colors } = data;

    const tuLen = textureUnitDefs.length;
    for (let tuIndex = 0; tuIndex < tuLen; ++tuIndex) {
      const textureUnit = textureUnitDefs[tuIndex];
      const { submeshIndex, textureUnitNumber, opCount } = textureUnit;

      if (!textureUnits.has(submeshIndex)) {
        textureUnits.set(submeshIndex, []);
      }

      // Array that will contain materials matching each texture unit.
      const submeshTextureUnits = textureUnits.get(submeshIndex);

      const materialDef = {
        shaderID: null,
        opCount: textureUnit.opCount,
        renderFlags: null,
        blendingMode: null,
        color: null,
        textures: [],
        transparencies: []
      };

      // Shader ID (needs to be unmasked to get actual shader ID)
      materialDef.shaderID = textureUnit.shaderID;

      // Render flags and blending mode
      const renderFlagsIndex = textureUnit.renderFlagsIndex;
      materialDef.renderFlags = renderFlags[renderFlagsIndex].flags;
      materialDef.blendingMode = renderFlags[renderFlagsIndex].blendingMode;

      // Vertex color animation block
      if (textureUnit.colorIndex > -1) {
        materialDef.color = colors[textureUnit.colorIndex];
      }

      for (let opIndex = 0; opIndex < opCount; ++opIndex) {
        // Texture
        const textureLookup = textureUnit.textureIndex + opIndex;
        const textureIndex = textureLookups[textureLookup];
        const texture = textures[textureIndex];
        materialDef.textures[opIndex] = texture;

        // Texture transparency animation block
        const transparencyLookup = textureUnit.transparencyIndex + opIndex;
        const transparencyIndex = transparencyLookups[transparencyLookup];
        const transparency = transparencies[transparencyIndex];
        if (transparency) {
          materialDef.transparencies[opIndex] = transparency;
        }
      }

      // Observe the M2's skinning flag in the M2Material.
      materialDef.useSkinning = this.useSkinning;

      const tuMaterial = new M2Material(materialDef);

      submeshTextureUnits[textureUnitNumber] = tuMaterial;
    }

    this.textureUnits = textureUnits;
  }

  createGeometry(vertices) {
    const geometry = new THREE.Geometry();

    for (let vertexIndex = 0, len = vertices.length; vertexIndex < len; ++vertexIndex) {
      const vertex = vertices[vertexIndex];

      const { position } = vertex;

      geometry.vertices.push(
        // Provided as (X, Z, -Y)
        new THREE.Vector3(position[0], position[2], -position[1])
      );

      geometry.skinIndices.push(
        new THREE.Vector4(...vertex.boneIndices)
      );

      geometry.skinWeights.push(
        new THREE.Vector4(...vertex.boneWeights)
      );
    }

    // Mirror geometry over X and Y axes and rotate
    const matrix = new THREE.Matrix4();
    matrix.makeScale(-1, -1, 1);
    geometry.applyMatrix(matrix);
    geometry.rotateX(-Math.PI / 2);

    // Preserve the geometry
    this.geometry = geometry;
  }

  createMesh(geometry, skeleton, rootBones) {
    let mesh;

    if (this.useSkinning) {
      mesh = new THREE.SkinnedMesh(geometry);

      // Assign root bones to mesh
      rootBones.forEach((bone) => {
        mesh.add(bone);
        bone.skin = mesh;
      });

      // Bind mesh to skeleton
      mesh.bind(skeleton);
    } else {
      mesh = new THREE.Mesh(geometry);
    }

    // Add mesh to the group
    this.add(mesh);

    // Assign as root mesh
    this.mesh = mesh;
  }

  createSubmeshes(data, skinData) {
    const { vertices } = data;
    const { submeshes, indices, triangles } = skinData;

    const subLen = submeshes.length;

    for (let submeshIndex = 0; submeshIndex < subLen; ++submeshIndex) {
      const submeshDef = submeshes[submeshIndex];

      // Bring up relevant texture units and geometry.
      const submeshTextureUnits = this.textureUnits.get(submeshIndex);
      const submeshGeometry = this.submeshGeometries.get(submeshIndex) ||
        this.createSubmeshGeometry(submeshDef, indices, triangles, vertices);

      const submesh = this.createSubmesh(submeshDef, submeshGeometry, submeshTextureUnits);

      this.parts.set(submesh.userData.partID, submesh);
      this.submeshes.push(submesh);

      this.submeshGeometries.set(submeshIndex, submeshGeometry);

      this.mesh.add(submesh);
    }
  }

  createSubmeshGeometry(submeshDef, indices, triangles, vertices) {
    const geometry = this.geometry.clone();

    // TODO: Figure out why this isn't cloned by the line above
    geometry.skinIndices = Array.from(this.geometry.skinIndices);
    geometry.skinWeights = Array.from(this.geometry.skinWeights);

    const uvs = [];

    const { startTriangle: start, triangleCount: count } = submeshDef;
    for (let i = start, faceIndex = 0; i < start + count; i += 3, ++faceIndex) {
      const vindices = [
        indices[triangles[i]],
        indices[triangles[i + 1]],
        indices[triangles[i + 2]]
      ];

      const face = new THREE.Face3(vindices[0], vindices[1], vindices[2]);

      geometry.faces.push(face);

      uvs[faceIndex] = [];
      for (let vinIndex = 0, vinLen = vindices.length; vinIndex < vinLen; ++vinIndex) {
        const index = vindices[vinIndex];

        const { textureCoords, normal } = vertices[index];

        uvs[faceIndex].push(new THREE.Vector2(textureCoords[0], textureCoords[1]));

        face.vertexNormals.push(new THREE.Vector3(normal[0], normal[1], normal[2]));
      }
    }

    geometry.faceVertexUvs = [uvs];

    const bufferGeometry = new THREE.BufferGeometry().fromGeometry(geometry);

    return bufferGeometry;
  }

  createSubmesh(submeshDef, geometry, textureUnits) {
    const rootBone = this.bones[submeshDef.rootBone];

    const opts = {
      skeleton: this.skeleton,
      geometry: geometry,
      rootBone: rootBone,
      useSkinning: this.useSkinning
    };

    const submesh = new Submesh(opts);

    submesh.applyTextureUnits(textureUnits);

    submesh.userData.partID = submeshDef.id;

    return submesh;
  }

  applyBillboards(camera) {
    for (let i = 0, len = this.billboards.length; i < len; ++i) {
      const bone = this.billboards[i];
      this.applyBillboard(camera, bone);
    }
  }

  applyBillboard(camera, bone) {
    const boneRoot = bone.skin;

    if (!boneRoot) {
      return;
    }

    const camPos = this.worldToLocal(camera.position.clone());

    const modelForward = new THREE.Vector3(camPos.x, camPos.y, camPos.z);
    modelForward.normalize();

    const modelVmEl = boneRoot.modelViewMatrix.elements;
    const modelRight = new THREE.Vector3(modelVmEl[0], modelVmEl[4], modelVmEl[8]);
    modelRight.multiplyScalar(-1);

    const modelUp = new THREE.Vector3();
    modelUp.crossVectors(modelForward, modelRight);
    modelUp.normalize();

    const rotateMatrix = new THREE.Matrix4();

    rotateMatrix.set(
      modelForward.x,   modelRight.x,   modelUp.x,  0,
      modelForward.y,   modelRight.y,   modelUp.y,  0,
      modelForward.z,   modelRight.z,   modelUp.z,  0,
      0,                0,              0,          1
    );

    bone.rotation.setFromRotationMatrix(rotateMatrix);
  }

  set displayInfo(displayInfo) {
    for (let i = 0, len = this.submeshes.length; i < len; ++i) {
      this.submeshes[i].displayInfo = displayInfo;
    }
  }

  clone() {
    let instance = {};

    if (this.canInstance) {
      instance.geometry = this.geometry;
      instance.submeshGeometries = this.submeshGeometries;
      instance.textureUnits = this.textureUnits;
    } else {
      instance = null;
    }

    return new this.constructor(this.path, this.data, this.skinData, instance);
  }

  static load(path) {
    path = path.replace(/\.md(x|l)/i, '.m2');
    if (!(path in this.cache)) {
      this.cache[path] = WorkerPool.enqueue('M2', path).then((args) => {
        const [data, skinData] = args;
        return new this(path, data, skinData, null);
      });
    }
    return this.cache[path].then((m2) => {
      return m2.clone();
    });
  }

}

export default M2;
<commit_msg>Tweaked argument default<commit_after>import THREE from 'three';

import Submesh from './submesh';
import M2Material from './material';
import AnimationManager from './animation-manager';
import WorkerPool from '../worker/pool';

class M2 extends THREE.Group {

  static cache = {};

  constructor(path, data, skinData, instance = null) {
    super();

    this.name = path.split('\\').slice(-1).pop();

    this.path = path;
    this.data = data;
    this.skinData = skinData;

    // Instanceable M2s can share geometry and texture units.
    this.canInstance = data.canInstance;

    this.animated = data.animated;
    this.animations = new AnimationManager(this, data.animations);
    this.billboards = [];

    // Keep track of whether or not to use skinning. If the M2 has bone animations, useSkinning is
    // set to true, and all meshes and materials used in the M2 will be skinning enabled. Otherwise,
    // skinning will not be enabled. Skinning has a very significant impact on the render loop in
    // three.js.
    this.useSkinning = false;

    this.mesh = null;
    this.submeshes = [];
    this.parts = new Map();

    this.geometry = null;
    this.submeshGeometries = new Map();

    this.skeleton = null;
    this.bones = [];
    this.rootBones = [];

    this.createSkeleton(data.bones);

    // Instanced M2s can share geometries and texture units.
    if (instance) {
      this.textureUnits = instance.textureUnits;
      this.geometry = instance.geometry;
      this.submeshGeometries = instance.submeshGeometries;
    } else {
      this.createTextureUnits(data, skinData);
      this.createGeometry(data.vertices);
    }

    this.createMesh(this.geometry, this.skeleton, this.rootBones);
    this.createSubmeshes(data, skinData);
  }

  createSkeleton(boneDefs) {
    const rootBones = [];
    const bones = [];
    const billboards = [];

    for (let boneIndex = 0, len = boneDefs.length; boneIndex < len; ++boneIndex) {
      const boneDef = boneDefs[boneIndex];
      const bone = new THREE.Bone();

      bones.push(bone);

      // M2 bone positioning seems to be inverted on X and Y
      const { pivotPoint } = boneDef;
      const correctedPosition = new THREE.Vector3(-pivotPoint.x, -pivotPoint.y, pivotPoint.z);
      bone.position.copy(correctedPosition);

      if (boneDef.parentID > -1) {
        const parent = bones[boneDef.parentID];
        parent.add(bone);

        // Correct bone positioning relative to parent
        let up = bone;
        while (up = up.parent) {
          bone.position.sub(up.position);
        }
      } else {
        bone.userData.isRoot = true;
        rootBones.push(bone);
      }

      // Enable skinning support on this M2 if we have bone animations.
      if (boneDef.animated) {
        this.useSkinning = true;
      }

      // Flag billboarded bones
      if (boneDef.billboarded) {
        bone.userData.billboarded = true;
        billboards.push(bone);
      }

      // Bone translation animation block
      if (boneDef.translation.animated) {
        this.animations.registerTrack({
          target: bone,
          property: 'position',
          animationBlock: boneDef.translation,
          trackType: 'VectorKeyframeTrack',

          valueTransform: function(value) {
            const translation = new THREE.Vector3(-value.x, -value.y, value.z);
            return bone.position.clone().add(translation);
          }
        });
      }

      // Bone rotation animation block
      if (boneDef.rotation.animated) {
        this.animations.registerTrack({
          target: bone,
          property: 'quaternion',
          animationBlock: boneDef.rotation,
          trackType: 'QuaternionKeyframeTrack',

          valueTransform: function(value) {
            return new THREE.Quaternion(value.x, value.y, -value.z, value.w).inverse();
          }
        });
      }

      // Bone scaling animation block
      if (boneDef.scaling.animated) {
        this.animations.registerTrack({
          target: bone,
          property: 'scale',
          animationBlock: boneDef.scaling,
          trackType: 'VectorKeyframeTrack',

          valueTransform: function(value) {
            return new THREE.Vector3(value.x, value.y, value.z);
          }
        });
      }
    }

    // Preserve the bones
    this.bones = bones;
    this.rootBones = rootBones;
    this.billboards = billboards;

    // Assemble the skeleton
    this.skeleton = new THREE.Skeleton(bones);
  }

  // Returns a map of M2Materials indexed by submesh. Each material represents a texture unit,
  // to be rendered in the order of appearance in the map's entry for the submesh index.
  createTextureUnits(data, skinData) {
    const textureUnits = new Map();

    const textureUnitDefs = skinData.textureUnits;

    const { textureLookups, textures, renderFlags } = data;
    const { transparencyLookups, transparencies, colors } = data;

    const tuLen = textureUnitDefs.length;
    for (let tuIndex = 0; tuIndex < tuLen; ++tuIndex) {
      const textureUnit = textureUnitDefs[tuIndex];
      const { submeshIndex, textureUnitNumber, opCount } = textureUnit;

      if (!textureUnits.has(submeshIndex)) {
        textureUnits.set(submeshIndex, []);
      }

      // Array that will contain materials matching each texture unit.
      const submeshTextureUnits = textureUnits.get(submeshIndex);

      const materialDef = {
        shaderID: null,
        opCount: textureUnit.opCount,
        renderFlags: null,
        blendingMode: null,
        color: null,
        textures: [],
        transparencies: []
      };

      // Shader ID (needs to be unmasked to get actual shader ID)
      materialDef.shaderID = textureUnit.shaderID;

      // Render flags and blending mode
      const renderFlagsIndex = textureUnit.renderFlagsIndex;
      materialDef.renderFlags = renderFlags[renderFlagsIndex].flags;
      materialDef.blendingMode = renderFlags[renderFlagsIndex].blendingMode;

      // Vertex color animation block
      if (textureUnit.colorIndex > -1) {
        materialDef.color = colors[textureUnit.colorIndex];
      }

      for (let opIndex = 0; opIndex < opCount; ++opIndex) {
        // Texture
        const textureLookup = textureUnit.textureIndex + opIndex;
        const textureIndex = textureLookups[textureLookup];
        const texture = textures[textureIndex];
        materialDef.textures[opIndex] = texture;

        // Texture transparency animation block
        const transparencyLookup = textureUnit.transparencyIndex + opIndex;
        const transparencyIndex = transparencyLookups[transparencyLookup];
        const transparency = transparencies[transparencyIndex];
        if (transparency) {
          materialDef.transparencies[opIndex] = transparency;
        }
      }

      // Observe the M2's skinning flag in the M2Material.
      materialDef.useSkinning = this.useSkinning;

      const tuMaterial = new M2Material(materialDef);

      submeshTextureUnits[textureUnitNumber] = tuMaterial;
    }

    this.textureUnits = textureUnits;
  }

  createGeometry(vertices) {
    const geometry = new THREE.Geometry();

    for (let vertexIndex = 0, len = vertices.length; vertexIndex < len; ++vertexIndex) {
      const vertex = vertices[vertexIndex];

      const { position } = vertex;

      geometry.vertices.push(
        // Provided as (X, Z, -Y)
        new THREE.Vector3(position[0], position[2], -position[1])
      );

      geometry.skinIndices.push(
        new THREE.Vector4(...vertex.boneIndices)
      );

      geometry.skinWeights.push(
        new THREE.Vector4(...vertex.boneWeights)
      );
    }

    // Mirror geometry over X and Y axes and rotate
    const matrix = new THREE.Matrix4();
    matrix.makeScale(-1, -1, 1);
    geometry.applyMatrix(matrix);
    geometry.rotateX(-Math.PI / 2);

    // Preserve the geometry
    this.geometry = geometry;
  }

  createMesh(geometry, skeleton, rootBones) {
    let mesh;

    if (this.useSkinning) {
      mesh = new THREE.SkinnedMesh(geometry);

      // Assign root bones to mesh
      rootBones.forEach((bone) => {
        mesh.add(bone);
        bone.skin = mesh;
      });

      // Bind mesh to skeleton
      mesh.bind(skeleton);
    } else {
      mesh = new THREE.Mesh(geometry);
    }

    // Add mesh to the group
    this.add(mesh);

    // Assign as root mesh
    this.mesh = mesh;
  }

  createSubmeshes(data, skinData) {
    const { vertices } = data;
    const { submeshes, indices, triangles } = skinData;

    const subLen = submeshes.length;

    for (let submeshIndex = 0; submeshIndex < subLen; ++submeshIndex) {
      const submeshDef = submeshes[submeshIndex];

      // Bring up relevant texture units and geometry.
      const submeshTextureUnits = this.textureUnits.get(submeshIndex);
      const submeshGeometry = this.submeshGeometries.get(submeshIndex) ||
        this.createSubmeshGeometry(submeshDef, indices, triangles, vertices);

      const submesh = this.createSubmesh(submeshDef, submeshGeometry, submeshTextureUnits);

      this.parts.set(submesh.userData.partID, submesh);
      this.submeshes.push(submesh);

      this.submeshGeometries.set(submeshIndex, submeshGeometry);

      this.mesh.add(submesh);
    }
  }

  createSubmeshGeometry(submeshDef, indices, triangles, vertices) {
    const geometry = this.geometry.clone();

    // TODO: Figure out why this isn't cloned by the line above
    geometry.skinIndices = Array.from(this.geometry.skinIndices);
    geometry.skinWeights = Array.from(this.geometry.skinWeights);

    const uvs = [];

    const { startTriangle: start, triangleCount: count } = submeshDef;
    for (let i = start, faceIndex = 0; i < start + count; i += 3, ++faceIndex) {
      const vindices = [
        indices[triangles[i]],
        indices[triangles[i + 1]],
        indices[triangles[i + 2]]
      ];

      const face = new THREE.Face3(vindices[0], vindices[1], vindices[2]);

      geometry.faces.push(face);

      uvs[faceIndex] = [];
      for (let vinIndex = 0, vinLen = vindices.length; vinIndex < vinLen; ++vinIndex) {
        const index = vindices[vinIndex];

        const { textureCoords, normal } = vertices[index];

        uvs[faceIndex].push(new THREE.Vector2(textureCoords[0], textureCoords[1]));

        face.vertexNormals.push(new THREE.Vector3(normal[0], normal[1], normal[2]));
      }
    }

    geometry.faceVertexUvs = [uvs];

    const bufferGeometry = new THREE.BufferGeometry().fromGeometry(geometry);

    return bufferGeometry;
  }

  createSubmesh(submeshDef, geometry, textureUnits) {
    const rootBone = this.bones[submeshDef.rootBone];

    const opts = {
      skeleton: this.skeleton,
      geometry: geometry,
      rootBone: rootBone,
      useSkinning: this.useSkinning
    };

    const submesh = new Submesh(opts);

    submesh.applyTextureUnits(textureUnits);

    submesh.userData.partID = submeshDef.id;

    return submesh;
  }

  applyBillboards(camera) {
    for (let i = 0, len = this.billboards.length; i < len; ++i) {
      const bone = this.billboards[i];
      this.applyBillboard(camera, bone);
    }
  }

  applyBillboard(camera, bone) {
    const boneRoot = bone.skin;

    if (!boneRoot) {
      return;
    }

    const camPos = this.worldToLocal(camera.position.clone());

    const modelForward = new THREE.Vector3(camPos.x, camPos.y, camPos.z);
    modelForward.normalize();

    const modelVmEl = boneRoot.modelViewMatrix.elements;
    const modelRight = new THREE.Vector3(modelVmEl[0], modelVmEl[4], modelVmEl[8]);
    modelRight.multiplyScalar(-1);

    const modelUp = new THREE.Vector3();
    modelUp.crossVectors(modelForward, modelRight);
    modelUp.normalize();

    const rotateMatrix = new THREE.Matrix4();

    rotateMatrix.set(
      modelForward.x,   modelRight.x,   modelUp.x,  0,
      modelForward.y,   modelRight.y,   modelUp.y,  0,
      modelForward.z,   modelRight.z,   modelUp.z,  0,
      0,                0,              0,          1
    );

    bone.rotation.setFromRotationMatrix(rotateMatrix);
  }

  set displayInfo(displayInfo) {
    for (let i = 0, len = this.submeshes.length; i < len; ++i) {
      this.submeshes[i].displayInfo = displayInfo;
    }
  }

  clone() {
    let instance = {};

    if (this.canInstance) {
      instance.geometry = this.geometry;
      instance.submeshGeometries = this.submeshGeometries;
      instance.textureUnits = this.textureUnits;
    } else {
      instance = null;
    }

    return new this.constructor(this.path, this.data, this.skinData, instance);
  }

  static load(path) {
    path = path.replace(/\.md(x|l)/i, '.m2');
    if (!(path in this.cache)) {
      this.cache[path] = WorkerPool.enqueue('M2', path).then((args) => {
        const [data, skinData] = args;
        return new this(path, data, skinData, null);
      });
    }
    return this.cache[path].then((m2) => {
      return m2.clone();
    });
  }

}

export default M2;
<|endoftext|>


<commit_before>/**
 * Created by long on 15/11/23.
 */

flax.ButtonGroup = flax.Class.extend({
    buttons:null,
    selectedButton:null,
    onSelected:null,
    _destroyed:false,
    ctor:function()
    {
        this.buttons = [];
        this.onSelected = new signals.Signal();
    },
    destroy: function () {
        if(this._destroyed) return;
        this._destroyed = true;
        for(var i = 0; i < this.buttons.length; i++) {
            this.buttons[i].group = null;
        }
        this.selectedButton = null;
        this.onSelected.removeAll();
        this.onSelected = null;
    },
    addButton:function(buttons)
    {
        if(!(buttons instanceof  Array)) {
            buttons = Array.prototype.slice.call(arguments);
        }
        for(var i = 0; i < buttons.length; i++){
            var btn = buttons[i];
            if(!flax.isButton(btn)) {
                throw "The element added to a ButtonGroup must be a flax button!"
                continue;
            }            if(this.buttons.indexOf(btn) > -1) continue;
            this.buttons.push(btn);
            btn.group = this;
        }
    },
    removeButton:function(button)
    {
        var i = this.buttons.indexOf(button);
        if(i > -1){
            this.buttons.splice(i, 1);
            button.group = null;
            if(this.selectedButton == button){
//                this.selectedButton = this.buttons[0];
//                if(this.selectedButton) this.selectedButton.setState(ButtonState.SELECTED);
            }
        }
        if(this.buttons.length == 0){
            this.onSelected.removeAll();
            this.onSelected = null;
        }
    },
    updateButtons:function(newSelected)
    {
        for(var i = 0; i < this.buttons.length; i++){
            var btn = this.buttons[i];
            if(btn != newSelected && btn.isMouseEnabled() && !btn.isLocked()){
                btn.setState(ButtonState.UP);
            }
        }
        this.selectedButton = newSelected;
        //If touched or just call setSelected
        var ifTouch = flax.mousePos && flax.ifTouched(newSelected, flax.mousePos);
        this.onSelected.dispatch(newSelected, ifTouch);
    }
});<commit_msg>Fixed destroy bug of ButtonGroup<commit_after>/**
 * Created by long on 15/11/23.
 */

flax.ButtonGroup = flax.Class.extend({
    buttons:null,
    selectedButton:null,
    onSelected:null,
    ctor:function()
    {
        this.buttons = [];
        this.onSelected = new signals.Signal();
    },
    addButton:function(buttons)
    {
        if(!(buttons instanceof  Array)) {
            buttons = Array.prototype.slice.call(arguments);
        }
        for(var i = 0; i < buttons.length; i++){
            var btn = buttons[i];
            if(!flax.isButton(btn)) {
                throw "The element added to a ButtonGroup must be a flax button!"
                continue;
            }            if(this.buttons.indexOf(btn) > -1) continue;
            this.buttons.push(btn);
            btn.group = this;
        }
    },
    removeButton:function(button)
    {
        var i = this.buttons.indexOf(button);
        if(i > -1){
            this.buttons.splice(i, 1);
            button.group = null;
            if(this.selectedButton == button){
                this.selectedButton = null;
//                this.selectedButton = this.buttons[0];
//                if(this.selectedButton) this.selectedButton.setState(ButtonState.SELECTED);
            }
        }
        if(this.buttons.length == 0){
            this.onSelected.removeAll();
            this.onSelected = null;
        }
    },
    updateButtons:function(newSelected)
    {
        for(var i = 0; i < this.buttons.length; i++){
            var btn = this.buttons[i];
            if(btn != newSelected && btn.isMouseEnabled() && !btn.isLocked()){
                btn.setState(ButtonState.UP);
            }
        }
        this.selectedButton = newSelected;
        //If touched or just call setSelected
        var ifTouch = flax.mousePos && flax.ifTouched(newSelected, flax.mousePos);
        this.onSelected.dispatch(newSelected, ifTouch);
    }
});<|endoftext|>


<commit_before>
export function makeSimpleTagRegex({ open, close = open }) {
  return new RegExp(`[${open}]([^${close}]+)[${close}]`, 'gi');
}

export default {
  makeSimpleTagRegex
};
<commit_msg>update tag regex<commit_after>// makeSimpleTagRegex MUST match text  surrounded by the provided open and close tags
// this text MAY span more than one line and include white space
// open and close tags MAY contain multiple characters and not be identical to each other
// Text surrounded by multiple instances of the same tag type MUST only match one copy of the tag
// eg text is here would only return text is here as a token
//
// regex based on https://stackoverflow.com/questions/11592033/regex-match-text-between-tags
// a simpler expression for matching single character delimters was originally used. See below
// new RegExp(`[${open}]([^${close}]+)[${close}]`, 'gi');
export function makeSimpleTagRegex({ open, close = open }) {
  return new RegExp(`\\${open}(?!\\${open})(.+?)\\${close}`, 'gi');
}

export default {
  makeSimpleTagRegex
};
<|endoftext|>


<commit_before>foam.CLASS({
  package: 'foam.u2',
  name: 'ViewSpecWithJava',
  extends: 'foam.u2.ViewSpec',
  properties: [
    ['view', { class: 'foam.u2.view.MapView' }],
    ['type', 'foam.lib.json.UnknownFObject'],
    ['javaInfoType', 'foam.core.AbstractFObjectPropertyInfo'],
    ['javaJSONParser', 'new foam.lib.json.UnknownFObjectParser()'],
    // TODO: remove next line when permanently fixed in ViewSpec
    ['fromJSON', function fromJSON(value, ctx, prop, json) {
      return value;
    }]
  ]
});

foam.CLASS({
  package: 'foam.comics.v2',
  name: 'NamedView',
  properties: [
    {
      class: 'String',
      name: 'name'
    },
    {
      class: 'foam.u2.ViewSpecWithJava',
      name: 'view'
    },
    {
      class: 'String',
      name: 'enabledIcon'
    },
    {
      class: 'String',
      name: 'disabledIcon'
    }
  ]
});

foam.CLASS({
  package: 'foam.comics.v2',
  name: 'CannedQuery',
  properties: [
    {
      class: 'String',
      name: 'name'
    },
    {
      class: 'FObjectProperty',
      of: 'foam.mlang.predicate.Predicate',
      name: 'predicate'
    }
  ]
});

foam.CLASS({
  package: 'foam.comics.v2',
  name: 'DAOControllerConfig', // EasyDAOController?
  properties: [
    {
      class: 'String',
      name: 'daoKey'
    },
    {
      class: 'foam.dao.DAOProperty',
      name: 'dao',
      hidden: true,
      expression: function(daoKey) {
        return this.__context__[daoKey] || foam.dao.NullDAO.create({of: foam.core.FObject});
      }
    },
    {
      class: 'Class',
      name: 'of',
      expression: function(dao$of) { return dao$of; }
    },
    {
      class: 'String',
      name: 'browseTitle',
      expression: function(of) { return of.name; }
    },
    {
      class: 'foam.u2.ViewSpecWithJava',
      name: 'browseBorder',
      expression: function() {
        // Can't use a value here because java tries to generate a HasMap
        // for it which doesn't jive with the AbstractFObjectPropertyInfo.
        return { class: 'foam.u2.borders.NullBorder' };
      }
    },
    {
      // TODO: add an enabled and disabled icon
      // we should always have a table as the first index
      class: 'FObjectArray',
      of: 'foam.comics.v2.NamedView',
      name: 'browseViews',
      factory: function() {
        return [
          {
            name: 'Table',
            view: { class: 'foam.u2.view.ScrollTableView' },
            enabledIcon: 'images/list-view-enabled.svg',
            disabledIcon: 'images/list-view-disabled.svg'
          },
          // {
          //   name: 'Tree',
          //   view: { class: 'foam.u2.view.ScrollTableView' },
          //   enabledIcon: 'images/account-structure-enabled.svg',
          //   disabledIcon: 'images/account-structure-disabled.svg'
          // }
        ];
      }
    },
    {
      class: 'FObjectArray',
      of: 'foam.comics.v2.CannedQuery',
      name: 'cannedQueries',
      // ! These are just for testing purposes until we wire up the cannedQueries with the models
      factory: function(){
        return [
          {
            name: 'All',
            predicate: foam.mlang.predicate.True.create()
          },
          {
            name: 'None',
            predicate: foam.mlang.predicate.False.create()
          }
        ]
      }
    },
    {
      class: 'foam.u2.ViewSpecWithJava',
      name: 'viewBorder',
      expression: function() {
        // Can't use a value here because java tries to generate a HasMap
        // for it which doesn't jive with the AbstractFObjectPropertyInfo.
        return { class: 'foam.u2.borders.NullBorder' };
      }
    },
    {
      class: 'FObjectArray',
      of: 'foam.comics.v2.NamedView',
      name: 'viewViews',
      factory: function() {
        return [
          {
            name: 'SDV',
            view: { class: 'foam.u2.detail.SectionedDetailView' },
            // TODO: Add a disabled icon later when we have more viewViews
            enabledIcon: 'images/sdv-icon.svg',
            disabledIcon: 'images/sdv-icon.svg'
          }
        ];
      }
    }
  ]
});

foam.CLASS({
  package: 'foam.comics.v2',
  name: 'DAOBrowseControllerView',
  extends: 'foam.u2.View',
  imports: [
    'stack'
  ],
  requires: [
    'foam.comics.v2.DAOBrowserView',
    'foam.u2.layout.Cols',
    'foam.u2.layout.Rows',
    'foam.u2.borders.CardBorder'
  ],

  css: `
    ^container {
      padding: 32px;
    }

    ^inner-table {
      padding: 0px 24px;
    }

    ^inner-table .foam-u2-view-TableView th {
      background: #ffffff
    }

    ^ .foam-u2-view-ScrollTableView-table {
      width: 100%;
    }
  `,

  properties: [
    {
      class: 'FObjectProperty',
      of: 'foam.comics.v2.DAOControllerConfig',
      name: 'data'
    }
  ],
  actions: [
    {
      name: 'create',
      code: function() {
        alert('TODO');
      }
    }
  ],
  methods: [
    function initE() {
    this.SUPER();

    var self = this;

      this.addClass(this.myClass())
      .add(this.slot(function(data, data$browseBorder) {
        return self.E()
          .start(self.Rows).addClass(this.myClass('container'))
            .start(self.Cols).style({'align-items': 'center'})
              .start('h1').add(data.browseTitle$).end()
              .startContext({data: self}).add(self.CREATE).endContext()
            .end()
            .start(this.CardBorder)
              .start(data$browseBorder).addClass(this.myClass('inner-table'))
                .tag(self.DAOBrowserView, { data: data })
              .end()
            .end()
          .end();
      }));
    }
  ]
});

foam.CLASS({
  package: 'foam.comics.v2',
  name: 'DAOBrowserView',
  extends: 'foam.u2.View',
  requires: [
    'foam.u2.ScrollTableView',
    'foam.u2.layout.Cols',
    'foam.u2.layout.Rows',
    'foam.u2.layout.Item',
    'foam.u2.search.Toolbar',
    'foam.u2.ActionView'
  ],

  css: `
    ^ .foam-u2-ActionView-export {
      margin-left: 16px;
    }

    ^ .foam-u2-ActionView img {
      margin-right: 0;
    }

    ^top-bar {
      padding: 20px 0px;
      border-bottom: solid 1px #e7eaec;
    }

    ^query-bar {
      margin-top: 32px;
      margin-bottom: 24px;
    }
  `,

  imports: [
    'stack?'
  ],
  exports: [
    'dblclick'
  ],
  properties: [
    {
      class: 'FObjectProperty',
      of: 'foam.comics.v2.DAOControllerConfig',
      name: 'data'
    },
    {
      class: 'FObjectProperty',
      of: 'foam.mlang.predicate.Predicate',
      name: 'predicate',
      factory: function() {
        // showing all by default if no cannedQueries are specified, see below in the initE
        return foam.mlang.predicate.True.create();
      }
    },
    {
      class: 'foam.dao.DAOProperty',
      name: 'predicatedDAO',
      expression: function(data, predicate) {
        return data.dao$proxy.where(predicate);
      }
    },
    {
      class: 'foam.u2.ViewSpecWithJava',
      name: 'browseView',
      expression: function(data$browseViews) {
        return data$browseViews[0].view;
      }
    }
  ],
  actions: [
    {
      name: 'export',
      label: '',
      icon: 'images/export-arrow-icon.svg',
      code: function() {
        alert('TODO');
      }
    },
    {
      name: 'toggleCannedQuery',
      code: function(cannedQueryPredicate) {
        this.predicate = cannedQueryPredicate;
      }
    }
  ],
  methods: [
    function dblclick(obj) {
      if ( ! this.stack ) return;
      this.stack.push({
        class: 'foam.comics.v2.DAOUpdateView',
        data$: this.data$,
        obj: obj
      });
    },
    function initE() {
      var self = this;
      this.addClass(this.myClass());
      this.SUPER();
      this
        .add(self.slot(function(data$cannedQueries, data$browseViews) {
          return self.E()
            .start(self.Rows)
              .callIf(data$cannedQueries.length > 1 || data$browseViews > 1, function() {
                this.start(self.Cols).addClass(self.myClass('top-bar'))
                  .start(self.Cols)
                    .callIf(data$cannedQueries.length > 1, function() {
                        this.tag( foam.u2.view.TabChoiceView, {
                          choices: data$cannedQueries.map(o => [o.predicate, o.name]),
                          data$: self.predicate$,
                        }
                      )
                    })
                    .callIf(data$cannedQueries.length === 1, function() {
                        self.predicate = data$cannedQueries[0].predicate;
                      }
                    )
                    /**
                     * otherwise if no cannedQueries are specified then the default
                     * will show all entries so that we do not break history code
                     */
                  .end()
                  .start(self.Cols)
                    .callIf(data$browseViews.length > 1, function() {
                      this.tag( foam.u2.view.IconChoiceView, {
                          choices: data$browseViews.map(o => [o.view, o.enabledIcon]),
                          data$: self.browseView$,
                        }
                      )
                    })
                  .end()
                .end()
              })
              .start(self.Cols).addClass(this.myClass('query-bar')).style({ 'align-items': 'center'})
                .start(self.Item)
                  .style({'flex-grow': 1 })
                    .tag(self.Toolbar, { data$: self.predicate$ })
                  .end()
                .startContext({data: self}).tag(self.EXPORT, {
                  buttonStyle: foam.u2.ButtonStyle.SECONDARY
                })
                .endContext()
              .end()
              .start(self.Item)
                .style({ margin: 'auto' })
                .add(self.slot(function(browseView) {
                  return self.E().tag(browseView, {
                    data: self.predicatedDAO$proxy
                  });
                }))
              .end()
            .end();
        }));
    }
  ]
});

foam.CLASS({
  package: 'foam.comics.v2',
  name: 'DAOUpdateView',
  extends: 'foam.u2.View',
  requires: [
    'foam.u2.layout.Cols',
    'foam.u2.layout.Rows',
    'foam.u2.ControllerMode',
    'foam.u2.layout.Item'
  ],
  imports: [
    'stack'
  ],
  exports: [
    'controllerMode'
  ],
  properties: [
    {
      class: 'FObjectProperty',
      of: 'foam.comics.v2.DAOControllerConfig',
      name: 'data'
    },
    {
      name: 'controllerMode',
      factory: function() {
        return this.ControllerMode.VIEW;
      }
    },
    {
      class: 'FObjectProperty',
      name: 'obj'
    },
    {
      class: 'foam.u2.ViewSpecWithJava',
      name: 'viewView',
      expression: function(data$viewViews) {
        return data$viewViews[0].view;
      }
    }
  ],
  actions: [
    {
      name: 'primary',
      code: function() {
        alert('TODO');
      }
    },
    {
      name: 'edit',
      code: function() {
        this.controllerMode = this.ControllerMode.EDIT;
      }
    },
    {
      name: 'delete',
      code: function() {
        alert('TODO');
      }
    }
  ],
  methods: [
    function initE() {
      var self = this;
      this.SUPER();
      this
        .add(self.slot(function(obj, data$viewBorder, data$viewViews) {
          return self.E()
            .start(self.Rows)
              .start(self.Cols)
                .start(self.Rows)
                  .startContext({ data: self.stack }).add(self.stack.BACK).endContext()
                  .start('h1').add(obj.toSummary()).end()
                .end()
                .startContext({data: self}).add(self.PRIMARY).endContext()
              .end()

              .start(self.Cols)
                .startContext({data: self}).add(self.EDIT).endContext()
                .startContext({data: self}).add(self.DELETE).endContext()
              .end()

              .start(data$viewBorder)
                .start(self.Cols)
                  .forEach(data$viewViews, function(o) {
                    // TODO: make these do something.
                    // TODO: make these icons.
                    this.add(o.name);
                  })
                .end()
                .start(self.Item)
                  .style({ margin: 'auto' })
                  .add(self.slot(function(viewView) {
                    return self.E().tag(viewView, {
                      data: obj
                    });
                  }))
                .end()
              .end()
            .end();
        }));
    }
  ]
}); <commit_msg>Added in an extra comment<commit_after>foam.CLASS({
  package: 'foam.u2',
  name: 'ViewSpecWithJava',
  extends: 'foam.u2.ViewSpec',
  properties: [
    ['view', { class: 'foam.u2.view.MapView' }],
    ['type', 'foam.lib.json.UnknownFObject'],
    ['javaInfoType', 'foam.core.AbstractFObjectPropertyInfo'],
    ['javaJSONParser', 'new foam.lib.json.UnknownFObjectParser()'],
    // TODO: remove next line when permanently fixed in ViewSpec
    ['fromJSON', function fromJSON(value, ctx, prop, json) {
      return value;
    }]
  ]
});

foam.CLASS({
  package: 'foam.comics.v2',
  name: 'NamedView',
  properties: [
    {
      class: 'String',
      name: 'name'
    },
    {
      class: 'foam.u2.ViewSpecWithJava',
      name: 'view'
    },
    {
      class: 'String',
      name: 'enabledIcon'
    },
    {
      class: 'String',
      name: 'disabledIcon'
    }
  ]
});

foam.CLASS({
  package: 'foam.comics.v2',
  name: 'CannedQuery',
  properties: [
    {
      class: 'String',
      name: 'name'
    },
    {
      class: 'FObjectProperty',
      of: 'foam.mlang.predicate.Predicate',
      name: 'predicate'
    }
  ]
});

foam.CLASS({
  package: 'foam.comics.v2',
  name: 'DAOControllerConfig', // EasyDAOController?
  properties: [
    {
      class: 'String',
      name: 'daoKey'
    },
    {
      class: 'foam.dao.DAOProperty',
      name: 'dao',
      hidden: true,
      expression: function(daoKey) {
        return this.__context__[daoKey] || foam.dao.NullDAO.create({of: foam.core.FObject});
      }
    },
    {
      class: 'Class',
      name: 'of',
      expression: function(dao$of) { return dao$of; }
    },
    {
      class: 'String',
      name: 'browseTitle',
      expression: function(of) { return of.name; }
    },
    {
      class: 'foam.u2.ViewSpecWithJava',
      name: 'browseBorder',
      expression: function() {
        // Can't use a value here because java tries to generate a HasMap
        // for it which doesn't jive with the AbstractFObjectPropertyInfo.
        return { class: 'foam.u2.borders.NullBorder' };
      }
    },
    {
      // TODO: add an enabled and disabled icon
      // we should always have a table as the first index
      class: 'FObjectArray',
      of: 'foam.comics.v2.NamedView',
      name: 'browseViews',
      factory: function() {
        return [
          {
            name: 'Table',
            view: { class: 'foam.u2.view.ScrollTableView' },
            enabledIcon: 'images/list-view-enabled.svg',
            disabledIcon: 'images/list-view-disabled.svg'
          },
          // {
          //   name: 'Tree',
          //   view: { class: 'foam.u2.view.ScrollTableView' },
          //   enabledIcon: 'images/account-structure-enabled.svg',
          //   disabledIcon: 'images/account-structure-disabled.svg'
          // }
        ];
      }
    },
    {
      class: 'FObjectArray',
      of: 'foam.comics.v2.CannedQuery',
      name: 'cannedQueries',
      // ! These are just for testing purposes until we wire up the cannedQueries with the models
      factory: function(){
        return [
          {
            name: 'All',
            predicate: foam.mlang.predicate.True.create()
          },
          {
            name: 'None',
            predicate: foam.mlang.predicate.False.create()
          }
        ]
      }
    },
    {
      class: 'foam.u2.ViewSpecWithJava',
      name: 'viewBorder',
      expression: function() {
        // Can't use a value here because java tries to generate a HasMap
        // for it which doesn't jive with the AbstractFObjectPropertyInfo.
        return { class: 'foam.u2.borders.NullBorder' };
      }
    },
    {
      class: 'FObjectArray',
      of: 'foam.comics.v2.NamedView',
      name: 'viewViews',
      factory: function() {
        return [
          {
            name: 'SDV',
            view: { class: 'foam.u2.detail.SectionedDetailView' },
            // TODO: Add a disabled icon later when we have more viewViews
            enabledIcon: 'images/sdv-icon.svg',
            disabledIcon: 'images/sdv-icon.svg'
          }
        ];
      }
    }
  ]
});

foam.CLASS({
  package: 'foam.comics.v2',
  name: 'DAOBrowseControllerView',
  extends: 'foam.u2.View',
  imports: [
    'stack'
  ],
  requires: [
    'foam.comics.v2.DAOBrowserView',
    'foam.u2.layout.Cols',
    'foam.u2.layout.Rows',
    'foam.u2.borders.CardBorder'
  ],

  css: `
    ^container {
      padding: 32px;
    }

    ^inner-table {
      padding: 0px 24px;
    }

    ^inner-table .foam-u2-view-TableView th {
      background: #ffffff
    }

    ^ .foam-u2-view-ScrollTableView-table {
      width: 100%;
    }
  `,

  properties: [
    {
      class: 'FObjectProperty',
      of: 'foam.comics.v2.DAOControllerConfig',
      name: 'data'
    }
  ],
  actions: [
    {
      name: 'create',
      code: function() {
        alert('TODO');
      }
    }
  ],
  methods: [
    function initE() {
    this.SUPER();

    var self = this;

      this.addClass(this.myClass())
      .add(this.slot(function(data, data$browseBorder) {
        return self.E()
          .start(self.Rows).addClass(this.myClass('container'))
            .start(self.Cols).style({'align-items': 'center'})
              .start('h1').add(data.browseTitle$).end()
              .startContext({data: self}).add(self.CREATE).endContext()
            .end()
            .start(this.CardBorder)
              .start(data$browseBorder).addClass(this.myClass('inner-table'))
                .tag(self.DAOBrowserView, { data: data })
              .end()
            .end()
          .end();
      }));
    }
  ]
});

foam.CLASS({
  package: 'foam.comics.v2',
  name: 'DAOBrowserView',
  extends: 'foam.u2.View',
  requires: [
    'foam.u2.ScrollTableView',
    'foam.u2.layout.Cols',
    'foam.u2.layout.Rows',
    'foam.u2.layout.Item',
    'foam.u2.search.Toolbar',
    'foam.u2.ActionView'
  ],

  css: `
    ^ .foam-u2-ActionView-export {
      margin-left: 16px;
    }

    ^ .foam-u2-ActionView img {
      margin-right: 0;
    }

    ^top-bar {
      padding: 20px 0px;
      border-bottom: solid 1px #e7eaec;
    }

    ^query-bar {
      margin-top: 32px;
      margin-bottom: 24px;
    }
  `,

  imports: [
    'stack?'
  ],
  exports: [
    'dblclick'
  ],
  properties: [
    {
      class: 'FObjectProperty',
      of: 'foam.comics.v2.DAOControllerConfig',
      name: 'data'
    },
    {
      class: 'FObjectProperty',
      of: 'foam.mlang.predicate.Predicate',
      name: 'predicate',
      factory: function() {
        // showing all by default if no cannedQueries are specified, see below in the initE
        return foam.mlang.predicate.True.create();
      }
    },
    {
      class: 'foam.dao.DAOProperty',
      name: 'predicatedDAO',
      expression: function(data, predicate) {
        return data.dao$proxy.where(predicate);
      }
    },
    {
      class: 'foam.u2.ViewSpecWithJava',
      name: 'browseView',
      expression: function(data$browseViews) {
        return data$browseViews[0].view;
      }
    }
  ],
  actions: [
    {
      name: 'export',
      label: '',
      icon: 'images/export-arrow-icon.svg',
      code: function() {
        alert('TODO');
      }
    },
    {
      name: 'toggleCannedQuery',
      code: function(cannedQueryPredicate) {
        this.predicate = cannedQueryPredicate;
      }
    }
  ],
  methods: [
    function dblclick(obj) {
      if ( ! this.stack ) return;
      this.stack.push({
        class: 'foam.comics.v2.DAOUpdateView',
        data$: this.data$,
        obj: obj
      });
    },
    function initE() {
      var self = this;
      this.addClass(this.myClass());
      this.SUPER();
      this
        .add(self.slot(function(data$cannedQueries, data$browseViews) {
          return self.E()
            .start(self.Rows)
              .callIf(data$cannedQueries.length > 1 || data$browseViews > 1, function() {
                this.start(self.Cols).addClass(self.myClass('top-bar'))
                  .start(self.Cols)
                    .callIf(data$cannedQueries.length > 1, function() {
                        this.tag( foam.u2.view.TabChoiceView, {
                          choices: data$cannedQueries.map(o => [o.predicate, o.name]),
                          data$: self.predicate$,
                        }
                      )
                    })
                    .callIf(data$cannedQueries.length === 1, function() {
                        self.predicate = data$cannedQueries[0].predicate;
                      }
                    )
                    /**
                     * otherwise if no cannedQueries are specified then the default
                     * will show all entries so that we do not break history code
                     */
                  .end()
                  .start(self.Cols)
                    .callIf(data$browseViews.length > 1, function() {
                      this.tag( foam.u2.view.IconChoiceView, {
                          choices: data$browseViews.map(o => [o.view, o.enabledIcon]),
                          data$: self.browseView$,
                        }
                      )
                    })
                    /**
                     * By default the browseView should always be a table view
                     */
                  .end()
                .end()
              })
              .start(self.Cols).addClass(this.myClass('query-bar')).style({ 'align-items': 'center'})
                .start(self.Item)
                  .style({'flex-grow': 1 })
                    .tag(self.Toolbar, { data$: self.predicate$ })
                  .end()
                .startContext({data: self}).tag(self.EXPORT, {
                  buttonStyle: foam.u2.ButtonStyle.SECONDARY
                })
                .endContext()
              .end()
              .start(self.Item)
                .style({ margin: 'auto' })
                .add(self.slot(function(browseView) {
                  return self.E().tag(browseView, {
                    data: self.predicatedDAO$proxy
                  });
                }))
              .end()
            .end();
        }));
    }
  ]
});

foam.CLASS({
  package: 'foam.comics.v2',
  name: 'DAOUpdateView',
  extends: 'foam.u2.View',
  requires: [
    'foam.u2.layout.Cols',
    'foam.u2.layout.Rows',
    'foam.u2.ControllerMode',
    'foam.u2.layout.Item'
  ],
  imports: [
    'stack'
  ],
  exports: [
    'controllerMode'
  ],
  properties: [
    {
      class: 'FObjectProperty',
      of: 'foam.comics.v2.DAOControllerConfig',
      name: 'data'
    },
    {
      name: 'controllerMode',
      factory: function() {
        return this.ControllerMode.VIEW;
      }
    },
    {
      class: 'FObjectProperty',
      name: 'obj'
    },
    {
      class: 'foam.u2.ViewSpecWithJava',
      name: 'viewView',
      expression: function(data$viewViews) {
        return data$viewViews[0].view;
      }
    }
  ],
  actions: [
    {
      name: 'primary',
      code: function() {
        alert('TODO');
      }
    },
    {
      name: 'edit',
      code: function() {
        this.controllerMode = this.ControllerMode.EDIT;
      }
    },
    {
      name: 'delete',
      code: function() {
        alert('TODO');
      }
    }
  ],
  methods: [
    function initE() {
      var self = this;
      this.SUPER();
      this
        .add(self.slot(function(obj, data$viewBorder, data$viewViews) {
          return self.E()
            .start(self.Rows)
              .start(self.Cols)
                .start(self.Rows)
                  .startContext({ data: self.stack }).add(self.stack.BACK).endContext()
                  .start('h1').add(obj.toSummary()).end()
                .end()
                .startContext({data: self}).add(self.PRIMARY).endContext()
              .end()

              .start(self.Cols)
                .startContext({data: self}).add(self.EDIT).endContext()
                .startContext({data: self}).add(self.DELETE).endContext()
              .end()

              .start(data$viewBorder)
                .start(self.Cols)
                  .forEach(data$viewViews, function(o) {
                    // TODO: make these do something.
                    // TODO: make these icons.
                    this.add(o.name);
                  })
                .end()
                .start(self.Item)
                  .style({ margin: 'auto' })
                  .add(self.slot(function(viewView) {
                    return self.E().tag(viewView, {
                      data: obj
                    });
                  }))
                .end()
              .end()
            .end();
        }));
    }
  ]
}); <|endoftext|>


<commit_before>/**
 * Shopware 4.0
 * Copyright © 2012 shopware AG
 *
 * According to our dual licensing model, this program can be used either
 * under the terms of the GNU Affero General Public License, version 3,
 * or under a proprietary license.
 *
 * The texts of the GNU Affero General Public License with an additional
 * permission and of our proprietary license can be found at and
 * in the LICENSE file you have received along with this program.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * "Shopware" is a registered trademark of shopware AG.
 * The licensing of the program under the AGPLv3 does not imply a
 * trademark license. Therefore any rights, title and interest in
 * our trademarks remain entirely with us.
 *
 * @category   Shopware
 * @package    Emotion
 * @subpackage View
 * @copyright  Copyright (c) 2012, shopware AG (http://www.shopware.de)
 * @version    $Id$
 * @author shopware AG
 */
//{block name="backend/emotion/view/components/article_slider"}
//{namespace name=backend/emotion/view/components/article_slider}
Ext.define('Shopware.apps.Emotion.view.components.ArticleSlider', {
    extend: 'Shopware.apps.Emotion.view.components.Base',
    alias: 'widget.emotion-components-article-slider',

    /**
     * Snippets for the component.
     * @object
     */
    snippets: {
        'select_article': '{s name=select_article}Select article(s){/s}',
        'article_administration': '{s name=article_administration}Article administration{/s}',
        'name': '{s name=name}Article name{/s}',
        'ordernumber': '{s name=ordernumber}Ordernumber{/s}',
        'actions': '{s name=actions}Action(s){/s}'
    },

    /**
     * Initiliaze the component.
     *
     * @public
     * @return void
     */
    initComponent: function() {
        var me = this;
        me.callParent(arguments);
        me.add(me.createArticleFieldset());
        me.setDefaultValues();
        me.getGridData();

        me.articleType = me.down('emotion-components-fields-article-slider-type');
        if(!me.articleType.getValue()) {
            me.maxCountField.hide();
            me.articleFieldset.hide();
        }
        if(me.articleType.getValue() === 'selected_article') {
            me.maxCountField.hide()
            me.articleFieldset.show();
            me.rotateSpeed.show().enable();
            me.rotation.show().enable();
        } else {
            me.maxCountField.show()
            me.articleFieldset.hide();
            me.rotateSpeed.hide().disable();
            me.rotation.hide().disable();
        }
        me.articleType.on('change', me.onChange, me);

        me.refreshHiddenValue();
    },

    onChange: function(field, newValue) {
        var me = this;

        if(newValue !== 'selected_article') {
            me.maxCountField.show()
            me.articleFieldset.hide();
            me.rotateSpeed.hide().disable();
            me.rotation.hide().disable();
        } else {
            me.maxCountField.hide()
            me.articleFieldset.show();
            me.rotateSpeed.show().enable();
            me.rotation.show().enable();
        }
    },

    /**
     * Sets default values if the article slider
     * wasn't saved previously.
     *
     * @public
     * @return void
     */
    setDefaultValues: function() {
        var me = this,
            numberfields =  me.query('numberfield'),
            checkboxes = me.query('checkbox');

        Ext.each(numberfields, function(field) {
            if(!field.getValue()) {
                field.setValue(500);
            }
            if(field.getName() === 'article_slider_max_number') {
                me.maxCountField = field;
                field.setValue(25);
            }

            if(field.getName() === 'article_slider_rotatespeed') {
                me.rotateSpeed = field;
            }
        });

        Ext.each(checkboxes, function(field) {
            if(field.getName() === 'article_slider_rotation') {
                me.rotation = field;
            }
        });
    },

    /**
     * Creates the fieldset which holds the article administration. The method
     * also creates the article store and registers the drag and drop plugin
     * for the grid.
     *
     * @public
     * @return [object] Ext.form.FieldSet
     */
    createArticleFieldset: function() {
        var me = this;

        me.articleSearch = Ext.create('Shopware.form.field.ArticleSearch', {
            layout: 'anchor',
            anchor: '100%',
            multiSelect: false,
            returnValue: 'name',
            hiddenReturnValue: 'number',
            listeners: {
                scope: me,
                valueselect: me.onAddArticleToGrid
            }
        });

        me.articleStore = Ext.create('Ext.data.Store', {
            fields: [ 'position', 'name', 'ordernumber', 'articleId' ]
        });

        me.ddGridPlugin = Ext.create('Ext.grid.plugin.DragDrop');

        me.articleGrid = Ext.create('Ext.grid.Panel', {
            columns: me.createColumns(),
            autoScroll: true,
            store: me.articleStore,
            height: 300,
            viewConfig: {
                plugins: [ me.ddGridPlugin ],
                listeners: {
                    scope: me,
                    drop: me.onRepositionArticle
                }
            }
        });

        return me.articleFieldset = Ext.create('Ext.form.FieldSet', {
            title: me.snippets.article_administration,
            layout:  'anchor',
            defaults: { anchor: '100%' },
            items: [ me.articleSearch, me.articleGrid ]
        });
    },

    /**
     * Helper method which creates the column model
     * for the article administration grid panel.
     *
     * @public
     * @return [array] computed columns
     */
    createColumns: function() {
        var me = this, snippets = me.snippets;

        return [{
            header: '⚌',
            width: 24,
            hideable: false,
            renderer : me.renderSorthandleColumn
        }, {
            dataIndex: 'name',
            header: snippets.name,
            flex: 1
        }, {
            dataIndex: 'ordernumber',
            header: snippets.ordernumber,
            flex: 1
        }, {
            xtype: 'actioncolumn',
            header: snippets.actions,
            width: 60,
            items: [{
                iconCls: 'sprite-minus-circle',
                action: 'delete-article',
                scope: me,
                handler: me.onDeleteArticle
            }]
        }];
    },

    /**
     * Event listener method which will be triggered when one (or more)
     * article are added to the article slider.
     *
     * Creates new models based on the selected articles and
     * assigns them to the article store.
     *
     * @public
     * @event selectMedia
     * @param [object] field - Shopware.MediaManager.MediaSelection
     * @param [array] records - array of the selected media
     */
    onAddArticleToGrid: function(field, returnVal, hiddenVal, record) {
        var me = this, store = me.articleStore;

        var model = Ext.create('Shopware.apps.Emotion.model.ArticleSlider', {
            position: store.getCount(),
            name: returnVal,
            ordernumber: hiddenVal,
            articleId: record.get('id')
        });
        store.add(model);

        field.searchField.setValue();
        me.refreshHiddenValue();

    },

    /**
     * Event listener method which will be triggered when the user
     * deletes a article from article administration grid panel.
     *
     * Removes the article from the article store.
     *
     * @event click#actioncolumn
     * @param [object] grid - Ext.grid.Panel
     * @param [integer] rowIndex - Index of the clicked row
     * @param [integer] colIndex - Index of the clicked column
     * @param [object] item - DOM node of the clicked row
     * @param [object] eOpts - additional event parameters
     * @param [object] record - Associated model of the clicked row
     */
    onDeleteArticle: function(grid, rowIndex, colIndex, item, eOpts, record) {
        var me = this;
        var store = grid.getStore();
        store.remove(record);
        me.refreshHiddenValue();
    },

    /**
     * Event listener method which will be fired when the user
     * repositions a article through drag and drop.
     *
     * Sets the new position of the article in the article store
     * and saves the data to an hidden field.
     *
     * @public
     * @event drop
     * @return void
     */
    onRepositionArticle: function() {
        var me = this;

        var i = 0;
        me.articleStore.each(function(item) {
            item.set('position', i);
            i++;
        });
        me.refreshHiddenValue();
    },

    /**
     * Refreshes the mapping field in the model
     * which contains all articles in the grid.
     *
     * @public
     * @return void
     */
    refreshHiddenValue: function() {
        var me = this,
            store = me.articleStore,
            cache = [];

        store.each(function(item) {
            cache.push(item.data);
        });
        var record = me.getSettings('record');
        record.set('mapping', cache);
    },

    /**
     * Refactor sthe mapping field in the global record
     * which contains all article in the grid.
     *
     * Adds all articles to the article administration grid
     * when the user opens the component.
     *
     * @return void
     */
    getGridData: function() {
        var me = this,
            elementStore = me.getSettings('record').get('data'), articleSlider;

        Ext.each(elementStore, function(element) {
            if(element.key === 'selected_articles') {
                articleSlider = element;
                return false;
            }
        });

        if(articleSlider && articleSlider.value) {
            Ext.each(articleSlider.value, function(item) {
                me.articleStore.add(Ext.create('Shopware.apps.Emotion.model.ArticleSlider', item));
            });
        }
    },

    /**
     * Renderer for sorthandle-column
     *
     * @param [string] value
     */
    renderSorthandleColumn: function() {
        return '
⚌
';
    }
});
//{/block}<commit_msg>SW-6618 fix backend emotion article-slider component<commit_after>/**
 * Shopware 4.0
 * Copyright © 2012 shopware AG
 *
 * According to our dual licensing model, this program can be used either
 * under the terms of the GNU Affero General Public License, version 3,
 * or under a proprietary license.
 *
 * The texts of the GNU Affero General Public License with an additional
 * permission and of our proprietary license can be found at and
 * in the LICENSE file you have received along with this program.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * "Shopware" is a registered trademark of shopware AG.
 * The licensing of the program under the AGPLv3 does not imply a
 * trademark license. Therefore any rights, title and interest in
 * our trademarks remain entirely with us.
 *
 * @category   Shopware
 * @package    Emotion
 * @subpackage View
 * @copyright  Copyright (c) 2012, shopware AG (http://www.shopware.de)
 * @version    $Id$
 * @author shopware AG
 */
//{block name="backend/emotion/view/components/article_slider"}
//{namespace name=backend/emotion/view/components/article_slider}
Ext.define('Shopware.apps.Emotion.view.components.ArticleSlider', {
    extend: 'Shopware.apps.Emotion.view.components.Base',
    alias: 'widget.emotion-components-article-slider',

    /**
     * Snippets for the component.
     * @object
     */
    snippets: {
        'select_article': '{s name=select_article}Select article(s){/s}',
        'article_administration': '{s name=article_administration}Article administration{/s}',
        'name': '{s name=name}Article name{/s}',
        'ordernumber': '{s name=ordernumber}Ordernumber{/s}',
        'actions': '{s name=actions}Action(s){/s}'
    },

    /**
     * Initiliaze the component.
     *
     * @public
     * @return void
     */
    initComponent: function() {
        var me = this;
        me.callParent(arguments);
        me.add(me.createArticleFieldset());
        me.setDefaultValues();
        me.getGridData();

        me.articleType = me.down('emotion-components-fields-article-slider-type');
        if(!me.articleType.getValue()) {
            me.maxCountField.hide();
            me.articleFieldset.hide();
        }
        if(me.articleType.getValue() === 'selected_article') {
            me.maxCountField.hide();
            me.articleFieldset.show();
            me.rotateSpeed.show().enable();
            me.rotation.show().enable();
        } else {
            me.maxCountField.show();
            me.articleFieldset.hide();
            me.rotateSpeed.hide().disable();
            me.rotation.hide().disable();
        }
        me.articleType.on('change', me.onChange, me);

        me.refreshHiddenValue();
    },

    onChange: function(field, newValue) {
        var me = this;

        if(newValue !== 'selected_article') {
            me.maxCountField.show();
            me.articleFieldset.hide();
            me.rotateSpeed.hide().disable();
            me.rotation.hide().disable();
        } else {
            me.maxCountField.hide();
            me.articleFieldset.show();
            me.rotateSpeed.show().enable();
            me.rotation.show().enable();
        }
    },

    /**
     * Sets default values if the article slider
     * wasn't saved previously.
     *
     * @public
     * @return void
     */
    setDefaultValues: function() {
        var me = this,
            numberfields =  me.query('numberfield'),
            checkboxes = me.query('checkbox');

        Ext.each(numberfields, function(field) {
            if(field.getName() === 'article_slider_max_number') {
                me.maxCountField = field;
                if(!field.getValue()) {
                    field.setValue(25);
                }
            }

            if(field.getName() === 'article_slider_rotatespeed') {
                me.rotateSpeed = field;
            }

            if(!field.getValue()) {
                field.setValue(500);
            }
        });

        Ext.each(checkboxes, function(field) {
            if(field.getName() === 'article_slider_rotation') {
                me.rotation = field;
            }
        });
    },

    /**
     * Creates the fieldset which holds the article administration. The method
     * also creates the article store and registers the drag and drop plugin
     * for the grid.
     *
     * @public
     * @return [object] Ext.form.FieldSet
     */
    createArticleFieldset: function() {
        var me = this;

        me.articleSearch = Ext.create('Shopware.form.field.ArticleSearch', {
            layout: 'anchor',
            anchor: '100%',
            multiSelect: false,
            returnValue: 'name',
            hiddenReturnValue: 'number',
            listeners: {
                scope: me,
                valueselect: me.onAddArticleToGrid
            }
        });

        me.articleStore = Ext.create('Ext.data.Store', {
            fields: [ 'position', 'name', 'ordernumber', 'articleId' ]
        });

        me.ddGridPlugin = Ext.create('Ext.grid.plugin.DragDrop');

        me.articleGrid = Ext.create('Ext.grid.Panel', {
            columns: me.createColumns(),
            autoScroll: true,
            store: me.articleStore,
            height: 300,
            viewConfig: {
                plugins: [ me.ddGridPlugin ],
                listeners: {
                    scope: me,
                    drop: me.onRepositionArticle
                }
            }
        });

        return me.articleFieldset = Ext.create('Ext.form.FieldSet', {
            title: me.snippets.article_administration,
            layout:  'anchor',
            defaults: { anchor: '100%' },
            items: [ me.articleSearch, me.articleGrid ]
        });
    },

    /**
     * Helper method which creates the column model
     * for the article administration grid panel.
     *
     * @public
     * @return [array] computed columns
     */
    createColumns: function() {
        var me = this, snippets = me.snippets;

        return [{
            header: '⚌',
            width: 24,
            hideable: false,
            renderer : me.renderSorthandleColumn
        }, {
            dataIndex: 'name',
            header: snippets.name,
            flex: 1
        }, {
            dataIndex: 'ordernumber',
            header: snippets.ordernumber,
            flex: 1
        }, {
            xtype: 'actioncolumn',
            header: snippets.actions,
            width: 60,
            items: [{
                iconCls: 'sprite-minus-circle',
                action: 'delete-article',
                scope: me,
                handler: me.onDeleteArticle
            }]
        }];
    },

    /**
     * Event listener method which will be triggered when one (or more)
     * article are added to the article slider.
     *
     * Creates new models based on the selected articles and
     * assigns them to the article store.
     *
     * @public
     * @event selectMedia
     * @param [object] field - Shopware.MediaManager.MediaSelection
     * @param [array] records - array of the selected media
     */
    onAddArticleToGrid: function(field, returnVal, hiddenVal, record) {
        var me = this, store = me.articleStore;

        var model = Ext.create('Shopware.apps.Emotion.model.ArticleSlider', {
            position: store.getCount(),
            name: returnVal,
            ordernumber: hiddenVal,
            articleId: record.get('id')
        });
        store.add(model);

        field.searchField.setValue();
        me.refreshHiddenValue();

    },

    /**
     * Event listener method which will be triggered when the user
     * deletes a article from article administration grid panel.
     *
     * Removes the article from the article store.
     *
     * @event click#actioncolumn
     * @param [object] grid - Ext.grid.Panel
     * @param [integer] rowIndex - Index of the clicked row
     * @param [integer] colIndex - Index of the clicked column
     * @param [object] item - DOM node of the clicked row
     * @param [object] eOpts - additional event parameters
     * @param [object] record - Associated model of the clicked row
     */
    onDeleteArticle: function(grid, rowIndex, colIndex, item, eOpts, record) {
        var me = this;
        var store = grid.getStore();
        store.remove(record);
        me.refreshHiddenValue();
    },

    /**
     * Event listener method which will be fired when the user
     * repositions a article through drag and drop.
     *
     * Sets the new position of the article in the article store
     * and saves the data to an hidden field.
     *
     * @public
     * @event drop
     * @return void
     */
    onRepositionArticle: function() {
        var me = this;

        var i = 0;
        me.articleStore.each(function(item) {
            item.set('position', i);
            i++;
        });
        me.refreshHiddenValue();
    },

    /**
     * Refreshes the mapping field in the model
     * which contains all articles in the grid.
     *
     * @public
     * @return void
     */
    refreshHiddenValue: function() {
        var me = this,
            store = me.articleStore,
            cache = [];

        store.each(function(item) {
            cache.push(item.data);
        });
        var record = me.getSettings('record');
        record.set('mapping', cache);
    },

    /**
     * Refactor sthe mapping field in the global record
     * which contains all article in the grid.
     *
     * Adds all articles to the article administration grid
     * when the user opens the component.
     *
     * @return void
     */
    getGridData: function() {
        var me = this,
            elementStore = me.getSettings('record').get('data'), articleSlider;

        Ext.each(elementStore, function(element) {
            if(element.key === 'selected_articles') {
                articleSlider = element;
                return false;
            }
        });

        if(articleSlider && articleSlider.value) {
            Ext.each(articleSlider.value, function(item) {
                me.articleStore.add(Ext.create('Shopware.apps.Emotion.model.ArticleSlider', item));
            });
        }
    },

    /**
     * Renderer for sorthandle-column
     *
     * @param [string] value
     */
    renderSorthandleColumn: function() {
        return '
⚌
';
    }
});
//{/block}<|endoftext|>


<commit_before>var fs = require('fs')

var contents = fs.readFileSync(process.argv[2])
var lines = contents.toString().split('\n').length - 1
console.log(lines)

// メモ：'utf8'を'readFileSync'に二つ目の関数として渡すと'.toString'なくて
// も動いていますよ！
// fs.readFileSync(process.argv[2], 'utf8').split('\n').length - 1<commit_msg>Update Japanese Message on My_First_Io<commit_after>var fs = require('fs')

var contents = fs.readFileSync(process.argv[2])
var lines = contents.toString().split('\n').length - 1
console.log(lines)

// 注：'readFileSync' の二つ目の引数に 'utf8' を渡すと、
// '.toString' を使わずに文字列を受け取ることが出来ます！
// fs.readFileSync(process.argv[2], 'utf8').split('\n').length - 1
<|endoftext|>


<commit_before>
define([
    'flight/lib/component',
    './withVertexPopover',
    'util/formatters',
    'service/vertex',
    'data'
], function(
    defineComponent,
    withVertexPopover,
    formatters,
    VertexService,
    appData) {
    'use strict';

    return defineComponent(FindPathPopover, withVertexPopover);

    function FindPathPopover() {

        this.vertexService = new VertexService();

        this.defaultAttrs({
            buttonSelector: 'button',
            findPathButtonSelector: '.find-path-form button',
            findPathHopsButtonSelector: '.popover-title .dropdown-menu a'
        });

        this.after('teardown', function() {
            if (this.findPathRequest && this.findPathRequest.abort) {
                this.findPathRequest.abort();
            }
        });

        this.before('initialize', function(node, config) {
            config.hops = config.connectionData && config.connectionData.hops || 1;
            config.template = 'findPathPopover';
        });

        this.after('initialize', function() {
            this.on('click', {
                findPathButtonSelector: this.onFindPathButton
            });

            this.on('popoverInitialize', this.onFindPath);
        });

        this.onFindPath = function() {
            this.trigger('defocusPaths');

            var self = this,
                cy = this.attr.cy,
                title = this.popover.find('.popover-title').hide().find('.title').text(''),
                text = this.popover.find('span.path').text('Finding paths...'),
                button = this.popover.find('button').hide().text('Add Vertices').attr('disabled', true).focus();

            this.select('findPathHopsButtonSelector').off('click').on('click', this.onFindPathHopsButton.bind(this));

            this.positionDialog();

            var src = this.attr.sourceVertexId,
                dest = this.attr.targetVertexId;

            this.findPathRequest = this.findPath(src, dest)
                .fail(function() {
                    self.popover.find('span.path').text('Server returned an error finding paths');
                    self.positionDialog();
                })
                .done(function(result) {

                    var paths = result.paths,
                        vertices = result.uniqueVertices,
                        verticesNotSourceDest = vertices.filter(function(v) {
                            return v.id !== src && v.id !== dest;
                        }),
                        notInWorkspace = vertices.filter(function(v) {
                            return !appData.workspaceVertices[v.id];
                        }),
                        pathsFoundText = formatters.string.plural(paths.length, 'path') + ' found';

                    if (paths.length) {
                        if (notInWorkspace.length) {
                            var vertexText = formatters.string.plural(notInWorkspace.length, 'vertex', 'vertices'),
                                suffix = notInWorkspace.length === 1 ? ' isn\'t' : ' aren\'t';
                            text.text(vertexText + suffix + ' already in workspace');
                            button.text('Add ' + vertexText).removeAttr('disabled').show();

                            var index, map = {};
                            for (var i = 0; i < notInWorkspace.length; i++) {
                                path_loop: for (var j = 0; j < paths.length; j++) {
                                    for (var x = 0; x < paths[j].length; x++) {
                                        if (paths[j][x].id === notInWorkspace[i].id) {
                                            map[notInWorkspace[i].id] = {
                                                sourceId: paths[j][x-1].id,
                                                targetId: paths[j][x+1].id
                                            };
                                            break path_loop;
                                        }
                                    }
                                }
                            }

                            self.verticesToAdd = notInWorkspace;
                            self.verticesToAddLayoutMap = map;
                        } else {
                            text.text('all vertices are already added to workspace');
                        }

                        cy.$('.temp').remove();
                        self.trigger('focusPaths', { paths:paths, sourceId:src, targetId:dest });
                    } else text.text('Searching up to ' + formatters.string.plural(self.attr.hops, 'hop'));

                    title.text(pathsFoundText).closest('.popover-title').show();
                    self.positionDialog();
                });
        }

        this.findPath = function(source, dest) {
            var parameters = {
                sourceGraphVertexId: source,
                destGraphVertexId: dest,
                depth: 5,
                hops: this.attr.hops
            };

            return this.vertexService.findPath(parameters)
                        .then(function (data) {
                            var vertices = [], added = {};
                            data.paths.forEach(function (path) {
                                path.forEach(function (vertex) {
                                    if (!added[vertex.id]) {
                                        vertices.push(vertex);
                                        added[vertex.id] = true;
                                    }
                                });
                            });

                            return {
                                paths: data.paths,
                                uniqueVertices: vertices
                            };
                        });
        };

        this.onFindPathButton = function(e) {
            var vertices = this.verticesToAdd;

            this.trigger('finishedVertexConnection');
            this.trigger('addVertices', {
                vertices: vertices,
                options: {
                    layout: {
                        type: 'path',
                        map: this.verticesToAddLayoutMap
                    }
                }
            });
            this.trigger('selectObjects', { vertices:vertices });
        };

        this.onFindPathHopsButton = function(e) {
            var $target = $(e.target),
                newHops = $target.data('hops');

            if ($target.closest('.disabled').length) return;

            var list = $target.closest('ul')

            list.siblings('.dropdown-toggle').html($target.data('displayText') + ' ');
            list.find('.disabled').removeClass('disabled');
            $target.closest('li').addClass('disabled');

            this.attr.hops = newHops;
            this.onFindPath();
        };
    }
});
<commit_msg>changed default hop for findPath to be 2<commit_after>
define([
    'flight/lib/component',
    './withVertexPopover',
    'util/formatters',
    'service/vertex',
    'data'
], function(
    defineComponent,
    withVertexPopover,
    formatters,
    VertexService,
    appData) {
    'use strict';

    return defineComponent(FindPathPopover, withVertexPopover);

    function FindPathPopover() {

        this.vertexService = new VertexService();

        this.defaultAttrs({
            buttonSelector: 'button',
            findPathButtonSelector: '.find-path-form button',
            findPathHopsButtonSelector: '.popover-title .dropdown-menu a'
        });

        this.after('teardown', function() {
            if (this.findPathRequest && this.findPathRequest.abort) {
                this.findPathRequest.abort();
            }
        });

        this.before('initialize', function(node, config) {
            config.hops = config.connectionData && config.connectionData.hops || 2;
            config.template = 'findPathPopover';
        });

        this.after('initialize', function() {
            this.on('click', {
                findPathButtonSelector: this.onFindPathButton
            });

            this.on('popoverInitialize', this.onFindPath);
        });

        this.onFindPath = function() {
            this.trigger('defocusPaths');

            var self = this,
                cy = this.attr.cy,
                title = this.popover.find('.popover-title').hide().find('.title').text(''),
                text = this.popover.find('span.path').text('Finding paths...'),
                button = this.popover.find('button').hide().text('Add Vertices').attr('disabled', true).focus();

            this.select('findPathHopsButtonSelector').off('click').on('click', this.onFindPathHopsButton.bind(this));

            this.positionDialog();

            var src = this.attr.sourceVertexId,
                dest = this.attr.targetVertexId;

            this.findPathRequest = this.findPath(src, dest)
                .fail(function() {
                    self.popover.find('span.path').text('Server returned an error finding paths');
                    self.positionDialog();
                })
                .done(function(result) {

                    var paths = result.paths,
                        vertices = result.uniqueVertices,
                        verticesNotSourceDest = vertices.filter(function(v) {
                            return v.id !== src && v.id !== dest;
                        }),
                        notInWorkspace = vertices.filter(function(v) {
                            return !appData.workspaceVertices[v.id];
                        }),
                        pathsFoundText = formatters.string.plural(paths.length, 'path') + ' found';

                    if (paths.length) {
                        if (notInWorkspace.length) {
                            var vertexText = formatters.string.plural(notInWorkspace.length, 'vertex', 'vertices'),
                                suffix = notInWorkspace.length === 1 ? ' isn\'t' : ' aren\'t';
                            text.text(vertexText + suffix + ' already in workspace');
                            button.text('Add ' + vertexText).removeAttr('disabled').show();

                            var index, map = {};
                            for (var i = 0; i < notInWorkspace.length; i++) {
                                path_loop: for (var j = 0; j < paths.length; j++) {
                                    for (var x = 0; x < paths[j].length; x++) {
                                        if (paths[j][x].id === notInWorkspace[i].id) {
                                            map[notInWorkspace[i].id] = {
                                                sourceId: paths[j][x-1].id,
                                                targetId: paths[j][x+1].id
                                            };
                                            break path_loop;
                                        }
                                    }
                                }
                            }

                            self.verticesToAdd = notInWorkspace;
                            self.verticesToAddLayoutMap = map;
                        } else {
                            text.text('all vertices are already added to workspace');
                        }

                        cy.$('.temp').remove();
                        self.trigger('focusPaths', { paths:paths, sourceId:src, targetId:dest });
                    } else text.text('Searching up to ' + formatters.string.plural(self.attr.hops, 'hop'));

                    title.text(pathsFoundText).closest('.popover-title').show();
                    self.positionDialog();
                });
        }

        this.findPath = function(source, dest) {
            var parameters = {
                sourceGraphVertexId: source,
                destGraphVertexId: dest,
                depth: 5,
                hops: this.attr.hops
            };

            return this.vertexService.findPath(parameters)
                        .then(function (data) {
                            var vertices = [], added = {};
                            data.paths.forEach(function (path) {
                                path.forEach(function (vertex) {
                                    if (!added[vertex.id]) {
                                        vertices.push(vertex);
                                        added[vertex.id] = true;
                                    }
                                });
                            });

                            return {
                                paths: data.paths,
                                uniqueVertices: vertices
                            };
                        });
        };

        this.onFindPathButton = function(e) {
            var vertices = this.verticesToAdd;

            this.trigger('finishedVertexConnection');
            this.trigger('addVertices', {
                vertices: vertices,
                options: {
                    layout: {
                        type: 'path',
                        map: this.verticesToAddLayoutMap
                    }
                }
            });
            this.trigger('selectObjects', { vertices:vertices });
        };

        this.onFindPathHopsButton = function(e) {
            var $target = $(e.target),
                newHops = $target.data('hops');

            if ($target.closest('.disabled').length) return;

            var list = $target.closest('ul')

            list.siblings('.dropdown-toggle').html($target.data('displayText') + ' ');
            list.find('.disabled').removeClass('disabled');
            $target.closest('li').addClass('disabled');

            this.attr.hops = newHops;
            this.onFindPath();
        };
    }
});
<|endoftext|>