<commit_before>import {
  printException,
  collectUrlParams,
  formatUrl,
  openImages,
} from './Utils';

const makeResultParams = ({ format, name }) => {
  return {
    protocol: 'https:',
    host: 'pbs.twimg.com',
    pathname: '/media/hogefuga123',
    format,
    name: name || null,
  };
}

describe('Utils', () => {
  it('エラーメッセージの表示(予期せぬ状況の確認)', () => {
    window = Object.create(window);
    Object.defineProperty(window, 'location', {
      value: {
        href: 'http://example.com/',
      },
    });
    console.log = jest.fn();
    printException('exception message');
    expect(console.log.mock.calls[0][0]).toBe('tooi: exception message at: http://example.com/');
  });

  // URLの変換のテストの場合たち
  const cases = [
    {
      title: '何もなし',
      url: 'https://pbs.twimg.com/media/hogefuga123',
      params: { format: 'jpg' },
    },
    {
      title: '.jpg',
      url: 'https://pbs.twimg.com/media/hogefuga123.jpg',
      params: { format: 'jpg' },
    },
    {
      title: '.png',
      url: 'https://pbs.twimg.com/media/hogefuga123.png',
      params: { format: 'png' },
    },
    {
      title: '.jpg:orig',
      url: 'https://pbs.twimg.com/media/hogefuga123.jpg:orig',
      params: { format: 'jpg', name: 'orig' },
    },
    {
      title: '.jpg:large',
      url: 'https://pbs.twimg.com/media/hogefuga123.jpg:large',
      params: { format: 'jpg', name: 'large' },
    },
    {
      title: '?format=jpg',
      url: 'https://pbs.twimg.com/media/hogefuga123?format=jpg',
      params: { format: 'jpg' },
    },
    {
      title: '?format=png',
      url: 'https://pbs.twimg.com/media/hogefuga123?format=png',
      params: { format: 'png' },
    },
    {
      title: '.jpg?format=jpg',
      url: 'https://pbs.twimg.com/media/hogefuga123.jpg?format=jpg',
      params: { format: 'jpg' },
    },
    {
      title: '.jpg?format=png',
      url: 'https://pbs.twimg.com/media/hogefuga123.jpg?format=png',
      params: { format: 'jpg' },
    },
    {
      title: '.png?format=jpg',
      url: 'https://pbs.twimg.com/media/hogefuga123.png?format=jpg',
      params: { format: 'png' },
    },
    {
      title: '.jpg?name=large',
      url: 'https://pbs.twimg.com/media/hogefuga123.jpg?name=large',
      params: { format: 'jpg', name: 'large' },
    },
    {
      title: '?format=jpg&name=large',
      url: 'https://pbs.twimg.com/media/hogefuga123?format=jpg&name=large',
      params: { format: 'jpg', name: 'large' },
    },
    {
      title: '.png?format=jpg&name=orig',
      url: 'https://pbs.twimg.com/media/hogefuga123.png?format=jpg&name=orig',
      params: { format: 'png', name: 'orig' },
    },
  ];

  describe('画像urlの要素を集める', () => {
    cases.forEach(singleCase => {
      const { title, url, params } = singleCase;
      it(title, () => {
        expect(collectUrlParams(url)).toStrictEqual(makeResultParams(params));
      });
    });

    it('twitterの画像URLでないときnull', () => {
      expect(collectUrlParams('https://twitter.com/tos')).toBe(null);
    });
  });

  describe('画像URLを https～?format=〜&name=orig に揃える', () => {
    cases.forEach(singleCase => {
      const { title, url, params } = singleCase;
      it(title, () => {
        expect(formatUrl(url)).toBe(`https://pbs.twimg.com/media/hogefuga123?format=${params.format}&name=orig`);
      });
    });

    it('twitterの画像URLでないときそのまま', () => {
      expect(formatUrl('https://twitter.com/tos')).toBe('https://twitter.com/tos');
    });
  });

  describe('画像を開く', () => {
    it('画像URLを1つ渡したとき開く', () => {
      window.open = jest.fn();
      openImages(['https://pbs.twimg.com/media/1st?format=jpg&name=orig']);
      expect(window.open.mock.calls.length).toBe(1);
      expect(window.open.mock.calls[0][0]).toBe('https://pbs.twimg.com/media/1st?format=jpg&name=orig');
    });
    it('画像URLを2つ渡したとき逆順に開く', () => {
      window.open = jest.fn();
      openImages([
        'https://pbs.twimg.com/media/1st?format=jpg&name=orig',
        'https://pbs.twimg.com/media/2nd?format=jpg&name=orig',
      ]);
      expect(window.open.mock.calls.length).toBe(2);
      expect(window.open.mock.calls[0][0]).toBe('https://pbs.twimg.com/media/2nd?format=jpg&name=orig');
      expect(window.open.mock.calls[1][0]).toBe('https://pbs.twimg.com/media/1st?format=jpg&name=orig');
    });
    it('画像URLを4つ渡したとき逆順に開く', () => {
      window.open = jest.fn();
      openImages([
        'https://pbs.twimg.com/media/1st?format=jpg&name=orig',
        'https://pbs.twimg.com/media/2nd?format=jpg&name=orig',
        'https://pbs.twimg.com/media/3rd?format=jpg&name=orig',
        'https://pbs.twimg.com/media/4th?format=jpg&name=orig',
      ]);
      expect(window.open.mock.calls.length).toBe(4);
      expect(window.open.mock.calls[0][0]).toBe('https://pbs.twimg.com/media/4th?format=jpg&name=orig');
      expect(window.open.mock.calls[1][0]).toBe('https://pbs.twimg.com/media/3rd?format=jpg&name=orig');
      expect(window.open.mock.calls[2][0]).toBe('https://pbs.twimg.com/media/2nd?format=jpg&name=orig');
      expect(window.open.mock.calls[3][0]).toBe('https://pbs.twimg.com/media/1st?format=jpg&name=orig');
    });
    it('画像URLでないURLを1つ渡したときもそのまま開く', () => {
      window.open = jest.fn();
      openImages(['https://twitter.com/tos']);
      expect(window.open.mock.calls.length).toBe(1);
      expect(window.open.mock.calls[0][0]).toBe('https://twitter.com/tos');
    });
    it('undefinedを1つ渡したとき開かない', () => {
      window.open = jest.fn();
      openImages([undefined]);
      expect(window.open.mock.calls.length).toBe(0);
    });
    it('URLとundefinedを混ぜたときURLだけ開いてundefinedは開かない', () => {
      window.open = jest.fn();
      openImages([
        'https://pbs.twimg.com/media/1st?format=jpg&name=orig',
        undefined,
        'https://twitter.com/tos',
        'https://pbs.twimg.com/media/2nd?format=jpg&name=orig',
      ]);
      expect(window.open.mock.calls.length).toBe(3);
      expect(window.open.mock.calls[0][0]).toBe('https://pbs.twimg.com/media/2nd?format=jpg&name=orig');
      expect(window.open.mock.calls[1][0]).toBe('https://twitter.com/tos');
      expect(window.open.mock.calls[2][0]).toBe('https://pbs.twimg.com/media/1st?format=jpg&name=orig');
    });
  });
});
<commit_msg>write test for getOptions of Utils<commit_after>import { INITIAL_OPTIONS, isTrue, isFalse } from './Constants';
import {
  printException,
  collectUrlParams,
  formatUrl,
  openImages,
  getOptions,
} from './Utils';

const makeResultParams = ({ format, name }) => {
  return {
    protocol: 'https:',
    host: 'pbs.twimg.com',
    pathname: '/media/hogefuga123',
    format,
    name: name || null,
  };
}

describe('Utils', () => {
  it('エラーメッセージの表示(予期せぬ状況の確認)', () => {
    window = Object.create(window);
    Object.defineProperty(window, 'location', {
      value: {
        href: 'http://example.com/',
      },
    });
    console.log = jest.fn();
    printException('exception message');
    expect(console.log.mock.calls[0][0]).toBe('tooi: exception message at: http://example.com/');
  });

  // URLの変換のテストの場合たち
  const cases = [
    {
      title: '何もなし',
      url: 'https://pbs.twimg.com/media/hogefuga123',
      params: { format: 'jpg' },
    },
    {
      title: '.jpg',
      url: 'https://pbs.twimg.com/media/hogefuga123.jpg',
      params: { format: 'jpg' },
    },
    {
      title: '.png',
      url: 'https://pbs.twimg.com/media/hogefuga123.png',
      params: { format: 'png' },
    },
    {
      title: '.jpg:orig',
      url: 'https://pbs.twimg.com/media/hogefuga123.jpg:orig',
      params: { format: 'jpg', name: 'orig' },
    },
    {
      title: '.jpg:large',
      url: 'https://pbs.twimg.com/media/hogefuga123.jpg:large',
      params: { format: 'jpg', name: 'large' },
    },
    {
      title: '?format=jpg',
      url: 'https://pbs.twimg.com/media/hogefuga123?format=jpg',
      params: { format: 'jpg' },
    },
    {
      title: '?format=png',
      url: 'https://pbs.twimg.com/media/hogefuga123?format=png',
      params: { format: 'png' },
    },
    {
      title: '.jpg?format=jpg',
      url: 'https://pbs.twimg.com/media/hogefuga123.jpg?format=jpg',
      params: { format: 'jpg' },
    },
    {
      title: '.jpg?format=png',
      url: 'https://pbs.twimg.com/media/hogefuga123.jpg?format=png',
      params: { format: 'jpg' },
    },
    {
      title: '.png?format=jpg',
      url: 'https://pbs.twimg.com/media/hogefuga123.png?format=jpg',
      params: { format: 'png' },
    },
    {
      title: '.jpg?name=large',
      url: 'https://pbs.twimg.com/media/hogefuga123.jpg?name=large',
      params: { format: 'jpg', name: 'large' },
    },
    {
      title: '?format=jpg&name=large',
      url: 'https://pbs.twimg.com/media/hogefuga123?format=jpg&name=large',
      params: { format: 'jpg', name: 'large' },
    },
    {
      title: '.png?format=jpg&name=orig',
      url: 'https://pbs.twimg.com/media/hogefuga123.png?format=jpg&name=orig',
      params: { format: 'png', name: 'orig' },
    },
  ];

  describe('画像urlの要素を集める', () => {
    cases.forEach(singleCase => {
      const { title, url, params } = singleCase;
      it(title, () => {
        expect(collectUrlParams(url)).toStrictEqual(makeResultParams(params));
      });
    });

    it('twitterの画像URLでないときnull', () => {
      expect(collectUrlParams('https://twitter.com/tos')).toBe(null);
    });
  });

  describe('画像URLを https～?format=〜&name=orig に揃える', () => {
    cases.forEach(singleCase => {
      const { title, url, params } = singleCase;
      it(title, () => {
        expect(formatUrl(url)).toBe(`https://pbs.twimg.com/media/hogefuga123?format=${params.format}&name=orig`);
      });
    });

    it('twitterの画像URLでないときそのまま', () => {
      expect(formatUrl('https://twitter.com/tos')).toBe('https://twitter.com/tos');
    });
  });

  describe('画像を開く', () => {
    it('画像URLを1つ渡したとき開く', () => {
      window.open = jest.fn();
      openImages(['https://pbs.twimg.com/media/1st?format=jpg&name=orig']);
      expect(window.open.mock.calls.length).toBe(1);
      expect(window.open.mock.calls[0][0]).toBe('https://pbs.twimg.com/media/1st?format=jpg&name=orig');
    });
    it('画像URLを2つ渡したとき逆順に開く', () => {
      window.open = jest.fn();
      openImages([
        'https://pbs.twimg.com/media/1st?format=jpg&name=orig',
        'https://pbs.twimg.com/media/2nd?format=jpg&name=orig',
      ]);
      expect(window.open.mock.calls.length).toBe(2);
      expect(window.open.mock.calls[0][0]).toBe('https://pbs.twimg.com/media/2nd?format=jpg&name=orig');
      expect(window.open.mock.calls[1][0]).toBe('https://pbs.twimg.com/media/1st?format=jpg&name=orig');
    });
    it('画像URLを4つ渡したとき逆順に開く', () => {
      window.open = jest.fn();
      openImages([
        'https://pbs.twimg.com/media/1st?format=jpg&name=orig',
        'https://pbs.twimg.com/media/2nd?format=jpg&name=orig',
        'https://pbs.twimg.com/media/3rd?format=jpg&name=orig',
        'https://pbs.twimg.com/media/4th?format=jpg&name=orig',
      ]);
      expect(window.open.mock.calls.length).toBe(4);
      expect(window.open.mock.calls[0][0]).toBe('https://pbs.twimg.com/media/4th?format=jpg&name=orig');
      expect(window.open.mock.calls[1][0]).toBe('https://pbs.twimg.com/media/3rd?format=jpg&name=orig');
      expect(window.open.mock.calls[2][0]).toBe('https://pbs.twimg.com/media/2nd?format=jpg&name=orig');
      expect(window.open.mock.calls[3][0]).toBe('https://pbs.twimg.com/media/1st?format=jpg&name=orig');
    });
    it('画像URLでないURLを1つ渡したときもそのまま開く', () => {
      window.open = jest.fn();
      openImages(['https://twitter.com/tos']);
      expect(window.open.mock.calls.length).toBe(1);
      expect(window.open.mock.calls[0][0]).toBe('https://twitter.com/tos');
    });
    it('undefinedを1つ渡したとき開かない', () => {
      window.open = jest.fn();
      openImages([undefined]);
      expect(window.open.mock.calls.length).toBe(0);
    });
    it('URLとundefinedを混ぜたときURLだけ開いてundefinedは開かない', () => {
      window.open = jest.fn();
      openImages([
        'https://pbs.twimg.com/media/1st?format=jpg&name=orig',
        undefined,
        'https://twitter.com/tos',
        'https://pbs.twimg.com/media/2nd?format=jpg&name=orig',
      ]);
      expect(window.open.mock.calls.length).toBe(3);
      expect(window.open.mock.calls[0][0]).toBe('https://pbs.twimg.com/media/2nd?format=jpg&name=orig');
      expect(window.open.mock.calls[1][0]).toBe('https://twitter.com/tos');
      expect(window.open.mock.calls[2][0]).toBe('https://pbs.twimg.com/media/1st?format=jpg&name=orig');
    });
  });

  describe('設定を取得', () => {
    it('初期設定を取得できる', () => {
      const expected = {};
      Object.keys(INITIAL_OPTIONS).forEach((key) => {
        expected[key] = isTrue;
      });
      window.chrome = {
        runtime: {
          sendMessage: jest.fn((_, callback) => callback({ data: {} })),
        },
      };
      getOptions().then((options) => {
        expect(options).toStrictEqual(expected);
      });
    });
    it('設定した値を取得できる', () => {
      const expected = {};
      Object.keys(INITIAL_OPTIONS).forEach((key, i) => {
        expected[key] = i % 2 === 0 ? isTrue : isFalse;
      });
      window.chrome = {
        runtime: {
          sendMessage: jest.fn((_, callback) => callback({ data: expected })),
        },
      };
      return getOptions().then((options) => {
        return expect(options).toStrictEqual(expected);
      });
    });
  });
});
<|endoftext|>


<commit_before>/**
 * FORGE.Hotspot3D
 * Abstract base class for projeted views. Should be subclassed for every supported projection / view type.
 *
 * @constructor FORGE.Hotspot3D
 * @param {FORGE.Viewer} viewer - viewer reference
 * @param {HotspotConfig} config - hostspot configuration
 * @extends {FORGE.Object3D}
 *
 * @todo Review/refactor all the raycasting and click part
 */
FORGE.Hotspot3D = function(viewer, config)
{
    /**
     * Hotspot configuration
     * @name  FORGE.Hotspot3D#_config
     * @type {HotspotConfig}
     * @private
     */
    this._config = config;

    /**
     * Name
     * @name  FORGE.Hotspot3D#_name
     * @type {string}
     * @private
     */
    this._name = "";

    /**
     * HotspotTransform object for the 3D object.
     * @name  FORGE.Hotspot3D#_transform
     * @type {FORGE.HotspotTransform}
     * @private
     */
    this._transform = null;

    /**
     * Material object for the 3D object.
     * @name  FORGE.Hotspot3D#_material
     * @type {FORGE.HotspotMaterial}
     * @private
     */
    this._material = null;

    /**
     * Color based on 3D Object id used for picking.
     * @name FORGE.Hotspot3D#_pickingColor
     * @type {THREE.Color}
     * @private
     */
    this._pickingColor = null;

    /**
     * Sound object for the 3D object.
     * @name  FORGE.Hotspot3D#_sound
     * @type {FORGE.HotspotSound}
     * @private
     */
    this._sound = null;

    /**
     * Animation object for the 3D object.
     * @name FORGE.Hotspot3D#_animation
     * @type {FORGE.HotspotAnimation}
     * @private
     */
    this._animation = null;

    /**
     * Does the hotspot is facing the camera ? Useful for a flat hotspot we want
     * to always be facing to the camera.
     * @type {boolean}
     * @private
     */
    this._facingCenter = false;

    /**
     * Before render bound callback.
     * @name FORGE.Hotspot3D#_onBeforeRenderBound
     * @type {?function(this:THREE.Object3D,?THREE.WebGLRenderer,?THREE.Scene,?THREE.Camera,?THREE.Geometry,?THREE.Material,?THREE.Group)}
     * @private
     */
    this._onBeforeRenderBound = null;

    /**
     * After render bound callback.
     * @name FORGE.Hotspot3D#_onAfterRenderBound
     * @type {?function(this:THREE.Object3D,?THREE.WebGLRenderer,?THREE.Scene,?THREE.Camera,?THREE.Geometry,?THREE.Material,?THREE.Group)}
     * @private
     */
    this._onAfterRenderBound = null;

    FORGE.Object3D.call(this, viewer, "Hotspot3D");
};

FORGE.Hotspot3D.prototype = Object.create(FORGE.Object3D.prototype);
FORGE.Hotspot3D.prototype.constructor = FORGE.Hotspot3D;

/**
 * Boot sequence.

 * Call superclass boot when objects are created as it will trigger parse config
 * @private
 */
FORGE.Hotspot3D.prototype._boot = function()
{
    FORGE.Object3D.prototype._boot.call(this);

    this._transform = new FORGE.HotspotTransform();
    this._animation = new FORGE.HotspotAnimation(this._viewer, this._transform);
    this._material = new FORGE.HotspotMaterial(this._viewer);

    if (typeof this._config !== "undefined" && this._config !== null)
    {
        this._parseConfig(this._config);
    }

    this._onBeforeRenderBound = this._onBeforeRender.bind(this);
    this._onAfterRenderBound = this._onAfterRender.bind(this);
};

/**
 * Parse the config object.
 * @method FORGE.Hotspot3D#_parseConfig
 * @param {HotspotConfig} config - The hotspot config to parse.
 * @private
 */
FORGE.Hotspot3D.prototype._parseConfig = function(config)
{
    this._uid = config.uid;
    this._tags = config.tags;
    this._register();

    this._type = config.type;
    this._name = config.name;
    this._visible = config.visible;

    this._facingCenter = config.facingCenter || false;

    if (typeof config.transform !== "undefined")
    {
        this._transform.load(config.transform);
    }

    if (typeof config.animation !== "undefined")
    {
        this._animation.load(config.animation);
        this._animation.onProgress.add(this._updatePosition, this);
    }

    this._material.onReady.add(this._materialReadyHandler, this);

    /** @type {HotspotMaterialConfig} */
    var materialConfig;

    if (typeof config.material !== "undefined")
    {
        materialConfig = config.material;
    }
    else
    {
        materialConfig = FORGE.HotspotMaterial.presets.TRANSPARENT;
    }

    if (this._debug === true)
    {
        materialConfig = /** @type {HotspotMaterialConfig} */ (FORGE.Utils.extendMultipleObjects(materialConfig, FORGE.HotspotMaterial.presets.DEBUG));
    }

    this._material.load(materialConfig);

    if (typeof config.sound !== "undefined")
    {
        this._sound = new FORGE.HotspotSound(this._viewer);
        this._sound.load(config.sound, config.transform);
    }

    if (typeof config.fx === "string")
    {
        this._fx = config.fx;
    }

    if (typeof config.events === "object")
    {
        this._createEvents(config.events);
    }
};

/**
 * Before render handler
 * @method FORGE.Hotspot3D#_onBeforeRender
 * @private
 */
FORGE.Hotspot3D.prototype._onBeforeRender = function(renderer, scene, camera, geometry, material, group)
{
    //Logs these value for jscs check (if not warn parameter is not used)
    this.log(group);

    var gl = this._viewer.renderer.webGLRenderer.getContext();

    this._viewer.renderer.view.updateUniforms(material.uniforms);

    // Check what is the current render pass looking at the material: Hotspot or Picking Material
    if (material.name === "HotspotMaterial")
    {
        if (this._material.type === FORGE.HotspotMaterial.types.GRAPHICS)
        {
            material.uniforms.tColor.value = new THREE.Color(this._material.color);
        }
        else
        {
            material.uniforms.tTexture.value = this._material._texture;
        }
    }
    else if (material.name === "PickingMaterial")
    {
        // As picking material is the same for all spots renderer in this pass, material uniforms won't be refreshed
        // Setting material.uniforms.tColor value will be useless, set direct value by acceding program uniforms map
        // Call useProgram first to avoid WebGL warning if material.program is not the current program
        // Set also material uniform to avoid both settings will collide on first object
        if (material.program)
        {
            var color = this._pickingColor;
            gl.useProgram(material.program.program);
            material.program.getUniforms().map.tColor.setValue(gl, color);
            material.uniforms.tColor.value = color;
        }
    }
};

/**
 * After render handler
 * @method FORGE.Hotspot3D#_onAfterRender
 * @private
 */
FORGE.Hotspot3D.prototype._onAfterRender = function()
{};

/**
 * Event handler for material ready. Triggers the creation of the hotspot3D.
 * @method FORGE.Hotspot3D#_materialReadyHandler
 * @private
 */
FORGE.Hotspot3D.prototype._materialReadyHandler = function()
{
    this._mesh.material = this._material.material;

    this._createHotspot3D();

    this._pickingColor = FORGE.PickingDrawPass.colorFrom3DObject(this._mesh);

    this._mesh.onBeforeRender = /** @type {function(this:THREE.Object3D,?THREE.WebGLRenderer,?THREE.Scene,?THREE.Camera,?THREE.Geometry,?THREE.Material,?THREE.Group)} */ (this._onBeforeRenderBound);
    this._mesh.onAfterRender = /** @type {function(this:THREE.Object3D,?THREE.WebGLRenderer,?THREE.Scene,?THREE.Camera,?THREE.Geometry,?THREE.Material,?THREE.Group)} */ (this._onAfterRenderBound);

    if (this._animation.autoPlay === true)
    {
        this._animation.play();
    }
};

/**
 * Final init step once setup is done.
 * @method FORGE.Hotspot3D#_setupDoneCallback
 * @private
 */
FORGE.Hotspot3D.prototype._createHotspot3D = function()
{
    if (typeof this._config.geometry !== "undefined" && typeof this._config.geometry.type === "string")
    {
        var options = this._config.geometry.options;

        switch (this._config.geometry.type)
        {
            case FORGE.HotspotGeometryType.BOX:
                this._mesh.geometry = FORGE.HotspotGeometry.BOX(options);
                break;
            case FORGE.HotspotGeometryType.SPHERE:
                this._mesh.geometry = FORGE.HotspotGeometry.SPHERE(options);
                break;
            case FORGE.HotspotGeometryType.CYLINDER:
                this._mesh.geometry = FORGE.HotspotGeometry.CYLINDER(options);
                break;
            case FORGE.HotspotGeometryType.PLANE:
                this._mesh.geometry = FORGE.HotspotGeometry.PLANE(options);
                break;
            default:
                this._mesh.geometry = FORGE.HotspotGeometry.PLANE();
                break;
        }
    }
    else
    {
        this._mesh.geometry = FORGE.HotspotGeometry.PLANE();
    }

    this._mesh.geometry.scale(this._transform.scale.x, this._transform.scale.y, this._transform.scale.z);
    this._mesh.material = this._material.material;
    this._mesh.userData = this._config;

    // Only enable frustum culling when view is rectilinear and frustum makes sense
    this._mesh.frustumCulled = this._viewer.renderer.view instanceof FORGE.ViewRectilinear;

    this._updatePosition();

    this._ready = true;

    if (this._onReady !== null)
    {
        this._onReady.dispatch();
    }
};

/**
 * Setup hotspot spatial position.
 * @method FORGE.Hotspot3D#_setupPosition
 * @private
 */
FORGE.Hotspot3D.prototype._updatePosition = function()
{
    this._mesh.position.x = this._transform.position.x;
    this._mesh.position.y = this._transform.position.y;
    this._mesh.position.z = this._transform.position.z;

    if (this._facingCenter === true)
    {
        var spherical = new THREE.Spherical().setFromVector3(new THREE.Vector3(this._transform.position.x, this._transform.position.y, this._transform.position.z));

        this._mesh.rotation.set(-spherical.phi + Math.PI / 2, spherical.theta + Math.PI, 0, "YXZ");

        // Apply rotation
        this._mesh.rotation.x += -FORGE.Math.degToRad(this._transform.rotation.x); // pitch
        this._mesh.rotation.y += FORGE.Math.degToRad(this._transform.rotation.y); // yaw
        this._mesh.rotation.z += FORGE.Math.degToRad(this._transform.rotation.z);
    }
    else
    {
        // Apply rotation
        var rx = -FORGE.Math.degToRad(this._transform.rotation.x); // pitch
        var ry = FORGE.Math.degToRad(this._transform.rotation.y); // yaw
        var rz = FORGE.Math.degToRad(this._transform.rotation.z);

        this._mesh.rotation.set(rx, ry, rz, "YXZ");
    }

    // Scale
    this._mesh.scale.x = FORGE.Math.clamp(this._transform.scale.x, 0.000001, 100000);
    this._mesh.scale.y = FORGE.Math.clamp(this._transform.scale.y, 0.000001, 100000);
    this._mesh.scale.z = FORGE.Math.clamp(this._transform.scale.z, 0.000001, 100000);
};

/**
 * Update hotspot content
 * @method FORGE.Hotspot3D#update
 */
FORGE.Hotspot3D.prototype.update = function()
{
    if (this._material !== null)
    {
        this._material.update();
    }

    if (this._sound !== null)
    {
        this._sound.update();
    }
};

/**
 * Destroy routine
 * @method FORGE.Hotspot3D#destroy
 */
FORGE.Hotspot3D.prototype.destroy = function()
{
    this._material.onReady.remove(this._materialReadyHandler, this);

    this._onBeforeRenderBound = null;
    this._onAfterRenderBound = null;

    if (this._transform !== null)
    {
        this._transform.destroy();
        this._transform = null;
    }

    if (this._animation !== null)
    {
        this._animation.destroy();
        this._animation = null;
    }

    if (this._material !== null)
    {
        this._material.destroy();
        this._material = null;
    }

    if (this._sound !== null)
    {
        this._sound.destroy();
        this._sound = null;
    }

    FORGE.Object3D.prototype.destroy.call(this);
};

/**
 * Hotspot type accessor
 * @name FORGE.Hotspot3D#visible
 * @readonly
 * @type {string}
 */
Object.defineProperty(FORGE.Hotspot3D.prototype, "type",
{
    /** @this {FORGE.Hotspot3D} */
    get: function()
    {
        return this._type;
    }
});

/**
 * Hotspot name accessor
 * @name FORGE.Hotspot3D#name
 * @readonly
 * @type {string}
 */
Object.defineProperty(FORGE.Hotspot3D.prototype, "name",
{
    /** @this {FORGE.Hotspot3D} */
    get: function()
    {
        return this._name;
    }
});

/**
 * Hotspot animation accessor
 * @name FORGE.Hotspot3D#animation
 * @readonly
 * @type {FORGE.HotspotAnimation}
 */
Object.defineProperty(FORGE.Hotspot3D.prototype, "animation",
{
    /** @this {FORGE.Hotspot3D} */
    get: function()
    {
        return this._animation;
    }
});<commit_msg>Fix sequence problem in Hotspot3D Exception with mesh onBeforeRender and onAfterRender are now assigned before parsing the config<commit_after>/**
 * FORGE.Hotspot3D
 * Abstract base class for projeted views. Should be subclassed for every supported projection / view type.
 *
 * @constructor FORGE.Hotspot3D
 * @param {FORGE.Viewer} viewer - viewer reference
 * @param {HotspotConfig} config - hostspot configuration
 * @extends {FORGE.Object3D}
 *
 * @todo Review/refactor all the raycasting and click part
 */
FORGE.Hotspot3D = function(viewer, config)
{
    /**
     * Hotspot configuration
     * @name  FORGE.Hotspot3D#_config
     * @type {HotspotConfig}
     * @private
     */
    this._config = config;

    /**
     * Name
     * @name  FORGE.Hotspot3D#_name
     * @type {string}
     * @private
     */
    this._name = "";

    /**
     * HotspotTransform object for the 3D object.
     * @name  FORGE.Hotspot3D#_transform
     * @type {FORGE.HotspotTransform}
     * @private
     */
    this._transform = null;

    /**
     * Material object for the 3D object.
     * @name  FORGE.Hotspot3D#_material
     * @type {FORGE.HotspotMaterial}
     * @private
     */
    this._material = null;

    /**
     * Color based on 3D Object id used for picking.
     * @name FORGE.Hotspot3D#_pickingColor
     * @type {THREE.Color}
     * @private
     */
    this._pickingColor = null;

    /**
     * Sound object for the 3D object.
     * @name  FORGE.Hotspot3D#_sound
     * @type {FORGE.HotspotSound}
     * @private
     */
    this._sound = null;

    /**
     * Animation object for the 3D object.
     * @name FORGE.Hotspot3D#_animation
     * @type {FORGE.HotspotAnimation}
     * @private
     */
    this._animation = null;

    /**
     * Does the hotspot is facing the camera ? Useful for a flat hotspot we want
     * to always be facing to the camera.
     * @type {boolean}
     * @private
     */
    this._facingCenter = false;

    /**
     * Before render bound callback.
     * @name FORGE.Hotspot3D#_onBeforeRenderBound
     * @type {?function(this:THREE.Object3D,?THREE.WebGLRenderer,?THREE.Scene,?THREE.Camera,?THREE.Geometry,?THREE.Material,?THREE.Group)}
     * @private
     */
    this._onBeforeRenderBound = null;

    /**
     * After render bound callback.
     * @name FORGE.Hotspot3D#_onAfterRenderBound
     * @type {?function(this:THREE.Object3D,?THREE.WebGLRenderer,?THREE.Scene,?THREE.Camera,?THREE.Geometry,?THREE.Material,?THREE.Group)}
     * @private
     */
    this._onAfterRenderBound = null;

    FORGE.Object3D.call(this, viewer, "Hotspot3D");
};

FORGE.Hotspot3D.prototype = Object.create(FORGE.Object3D.prototype);
FORGE.Hotspot3D.prototype.constructor = FORGE.Hotspot3D;

/**
 * Boot sequence.

 * Call superclass boot when objects are created as it will trigger parse config
 * @private
 */
FORGE.Hotspot3D.prototype._boot = function()
{
    FORGE.Object3D.prototype._boot.call(this);

    this._transform = new FORGE.HotspotTransform();
    this._animation = new FORGE.HotspotAnimation(this._viewer, this._transform);
    this._material = new FORGE.HotspotMaterial(this._viewer);

    this._onBeforeRenderBound = this._onBeforeRender.bind(this);
    this._onAfterRenderBound = this._onAfterRender.bind(this);

    if (typeof this._config !== "undefined" && this._config !== null)
    {
        this._parseConfig(this._config);
    }
};

/**
 * Parse the config object.
 * @method FORGE.Hotspot3D#_parseConfig
 * @param {HotspotConfig} config - The hotspot config to parse.
 * @private
 */
FORGE.Hotspot3D.prototype._parseConfig = function(config)
{
    this._uid = config.uid;
    this._tags = config.tags;
    this._register();

    this._type = config.type;
    this._name = config.name;
    this._visible = config.visible;

    this._mesh.name = "mesh-" + this._uid;

    this._facingCenter = config.facingCenter || false;

    if (typeof config.transform !== "undefined")
    {
        this._transform.load(config.transform);
    }

    if (typeof config.animation !== "undefined")
    {
        this._animation.load(config.animation);
        this._animation.onProgress.add(this._updatePosition, this);
    }

    this._material.onReady.add(this._materialReadyHandler, this);

    /** @type {HotspotMaterialConfig} */
    var materialConfig;

    if (typeof config.material !== "undefined")
    {
        materialConfig = config.material;
    }
    else
    {
        materialConfig = FORGE.HotspotMaterial.presets.TRANSPARENT;
    }

    if (this._debug === true)
    {
        materialConfig = /** @type {HotspotMaterialConfig} */ (FORGE.Utils.extendMultipleObjects(materialConfig, FORGE.HotspotMaterial.presets.DEBUG));
    }

    this._material.load(materialConfig);

    if (typeof config.sound !== "undefined")
    {
        this._sound = new FORGE.HotspotSound(this._viewer);
        this._sound.load(config.sound, config.transform);
    }

    if (typeof config.fx === "string")
    {
        this._fx = config.fx;
    }

    if (typeof config.events === "object")
    {
        this._createEvents(config.events);
    }
};

/**
 * Before render handler
 * @method FORGE.Hotspot3D#_onBeforeRender
 * @private
 */
FORGE.Hotspot3D.prototype._onBeforeRender = function(renderer, scene, camera, geometry, material, group)
{
    //Logs these value for jscs check (if not warn parameter is not used)
    this.log(group);

    var gl = this._viewer.renderer.webGLRenderer.getContext();

    this._viewer.renderer.view.updateUniforms(material.uniforms);

    // Check what is the current render pass looking at the material: Hotspot or Picking Material
    if (material.name === "HotspotMaterial")
    {
        if (this._material.type === FORGE.HotspotMaterial.types.GRAPHICS)
        {
            material.uniforms.tColor.value = new THREE.Color(this._material.color);
        }
        else
        {
            material.uniforms.tTexture.value = this._material._texture;
        }
    }
    else if (material.name === "PickingMaterial")
    {
        // As picking material is the same for all spots renderer in this pass, material uniforms won't be refreshed
        // Setting material.uniforms.tColor value will be useless, set direct value by acceding program uniforms map
        // Call useProgram first to avoid WebGL warning if material.program is not the current program
        // Set also material uniform to avoid both settings will collide on first object
        if (material.program)
        {
            var color = this._pickingColor;
            gl.useProgram(material.program.program);
            material.program.getUniforms().map.tColor.setValue(gl, color);
            material.uniforms.tColor.value = color;
        }
    }
};

/**
 * After render handler
 * @method FORGE.Hotspot3D#_onAfterRender
 * @private
 */
FORGE.Hotspot3D.prototype._onAfterRender = function()
{};

/**
 * Event handler for material ready. Triggers the creation of the hotspot3D.
 * @method FORGE.Hotspot3D#_materialReadyHandler
 * @private
 */
FORGE.Hotspot3D.prototype._materialReadyHandler = function()
{
    this._mesh.material = this._material.material;

    this._createHotspot3D();

    this._pickingColor = FORGE.PickingDrawPass.colorFrom3DObject(this._mesh);

    this._mesh.onBeforeRender = /** @type {function(this:THREE.Object3D,?THREE.WebGLRenderer,?THREE.Scene,?THREE.Camera,?THREE.Geometry,?THREE.Material,?THREE.Group)} */ (this._onBeforeRenderBound);
    this._mesh.onAfterRender = /** @type {function(this:THREE.Object3D,?THREE.WebGLRenderer,?THREE.Scene,?THREE.Camera,?THREE.Geometry,?THREE.Material,?THREE.Group)} */ (this._onAfterRenderBound);

    if (this._animation.autoPlay === true)
    {
        this._animation.play();
    }
};

/**
 * Final init step once setup is done.
 * @method FORGE.Hotspot3D#_setupDoneCallback
 * @private
 */
FORGE.Hotspot3D.prototype._createHotspot3D = function()
{
    if (typeof this._config.geometry !== "undefined" && typeof this._config.geometry.type === "string")
    {
        var options = this._config.geometry.options;

        switch (this._config.geometry.type)
        {
            case FORGE.HotspotGeometryType.BOX:
                this._mesh.geometry = FORGE.HotspotGeometry.BOX(options);
                break;
            case FORGE.HotspotGeometryType.SPHERE:
                this._mesh.geometry = FORGE.HotspotGeometry.SPHERE(options);
                break;
            case FORGE.HotspotGeometryType.CYLINDER:
                this._mesh.geometry = FORGE.HotspotGeometry.CYLINDER(options);
                break;
            case FORGE.HotspotGeometryType.PLANE:
                this._mesh.geometry = FORGE.HotspotGeometry.PLANE(options);
                break;
            default:
                this._mesh.geometry = FORGE.HotspotGeometry.PLANE();
                break;
        }
    }
    else
    {
        this._mesh.geometry = FORGE.HotspotGeometry.PLANE();
    }

    this._mesh.geometry.scale(this._transform.scale.x, this._transform.scale.y, this._transform.scale.z);
    this._mesh.material = this._material.material;
    this._mesh.userData = this._config;

    // Only enable frustum culling when view is rectilinear and frustum makes sense
    this._mesh.frustumCulled = this._viewer.renderer.view instanceof FORGE.ViewRectilinear;

    this._updatePosition();

    this._ready = true;

    if (this._onReady !== null)
    {
        this._onReady.dispatch();
    }
};

/**
 * Setup hotspot spatial position.
 * @method FORGE.Hotspot3D#_setupPosition
 * @private
 */
FORGE.Hotspot3D.prototype._updatePosition = function()
{
    this._mesh.position.x = this._transform.position.x;
    this._mesh.position.y = this._transform.position.y;
    this._mesh.position.z = this._transform.position.z;

    if (this._facingCenter === true)
    {
        var spherical = new THREE.Spherical().setFromVector3(new THREE.Vector3(this._transform.position.x, this._transform.position.y, this._transform.position.z));

        this._mesh.rotation.set(-spherical.phi + Math.PI / 2, spherical.theta + Math.PI, 0, "YXZ");

        // Apply rotation
        this._mesh.rotation.x += -FORGE.Math.degToRad(this._transform.rotation.x); // pitch
        this._mesh.rotation.y += FORGE.Math.degToRad(this._transform.rotation.y); // yaw
        this._mesh.rotation.z += FORGE.Math.degToRad(this._transform.rotation.z);
    }
    else
    {
        // Apply rotation
        var rx = -FORGE.Math.degToRad(this._transform.rotation.x); // pitch
        var ry = FORGE.Math.degToRad(this._transform.rotation.y); // yaw
        var rz = FORGE.Math.degToRad(this._transform.rotation.z);

        this._mesh.rotation.set(rx, ry, rz, "YXZ");
    }

    // Scale
    this._mesh.scale.x = FORGE.Math.clamp(this._transform.scale.x, 0.000001, 100000);
    this._mesh.scale.y = FORGE.Math.clamp(this._transform.scale.y, 0.000001, 100000);
    this._mesh.scale.z = FORGE.Math.clamp(this._transform.scale.z, 0.000001, 100000);
};

/**
 * Update hotspot content
 * @method FORGE.Hotspot3D#update
 */
FORGE.Hotspot3D.prototype.update = function()
{
    if (this._material !== null)
    {
        this._material.update();
    }

    if (this._sound !== null)
    {
        this._sound.update();
    }
};

/**
 * Destroy routine
 * @method FORGE.Hotspot3D#destroy
 */
FORGE.Hotspot3D.prototype.destroy = function()
{
    this._material.onReady.remove(this._materialReadyHandler, this);

    this._onBeforeRenderBound = null;
    this._onAfterRenderBound = null;

    if (this._transform !== null)
    {
        this._transform.destroy();
        this._transform = null;
    }

    if (this._animation !== null)
    {
        this._animation.destroy();
        this._animation = null;
    }

    if (this._material !== null)
    {
        this._material.destroy();
        this._material = null;
    }

    if (this._sound !== null)
    {
        this._sound.destroy();
        this._sound = null;
    }

    FORGE.Object3D.prototype.destroy.call(this);
};

/**
 * Hotspot type accessor
 * @name FORGE.Hotspot3D#visible
 * @readonly
 * @type {string}
 */
Object.defineProperty(FORGE.Hotspot3D.prototype, "type",
{
    /** @this {FORGE.Hotspot3D} */
    get: function()
    {
        return this._type;
    }
});

/**
 * Hotspot name accessor
 * @name FORGE.Hotspot3D#name
 * @readonly
 * @type {string}
 */
Object.defineProperty(FORGE.Hotspot3D.prototype, "name",
{
    /** @this {FORGE.Hotspot3D} */
    get: function()
    {
        return this._name;
    }
});

/**
 * Hotspot animation accessor
 * @name FORGE.Hotspot3D#animation
 * @readonly
 * @type {FORGE.HotspotAnimation}
 */
Object.defineProperty(FORGE.Hotspot3D.prototype, "animation",
{
    /** @this {FORGE.Hotspot3D} */
    get: function()
    {
        return this._animation;
    }
});<|endoftext|>


<commit_before>/* eslint-env mocha */

import expect from 'expect';

import generateRanges from '../generate-ranges';

describe('generateRanges()', () => {
  it('should generate ranges', () => {
    var stats = {
      min: 1.99,
      max: 4999.98,
      avg: 243.349,
      sum: 2433490.0
    };
    var expected = [
      {to: 1},
      {from: 1, to: 80},
      {from: 80, to: 160},
      {from: 160, to: 240},
      {from: 240, to: 1820},
      {from: 1820, to: 3400},
      {from: 3400, to: 4980},
      {from: 4980}
    ];
    expect(generateRanges(stats)).toEqual(expected);
  });

  it('should generate small ranges', () => {
    var stats = {min: 20, max: 50, avg: 35, sum: 70};
    var expected = [
      {to: 20},
      {from: 20, to: 25},
      {from: 25, to: 30},
      {from: 30, to: 35},
      {from: 35, to: 40},
      {from: 40, to: 45},
      {from: 45}
    ];
    expect(generateRanges(stats)).toEqual(expected);
  });

  it('should not do an infinite loop', () => {
    var stats = {min: 99.99, max: 149.99, avg: 124.99, sum: 249.98};
    generateRanges(stats);
  });

  it('should not generate ranges', () => {
    var stats = {min: 20, max: 20, avg: 20, sum: 20};
    expect(generateRanges(stats)).toEqual([]);
  });
});
<commit_msg>test(ranges): add an expect call<commit_after>/* eslint-env mocha */

import expect from 'expect';

import generateRanges from '../generate-ranges';

describe('generateRanges()', () => {
  it('should generate ranges', () => {
    var stats = {
      min: 1.99,
      max: 4999.98,
      avg: 243.349,
      sum: 2433490.0
    };
    var expected = [
      {to: 1},
      {from: 1, to: 80},
      {from: 80, to: 160},
      {from: 160, to: 240},
      {from: 240, to: 1820},
      {from: 1820, to: 3400},
      {from: 3400, to: 4980},
      {from: 4980}
    ];
    expect(generateRanges(stats)).toEqual(expected);
  });

  it('should generate small ranges', () => {
    var stats = {min: 20, max: 50, avg: 35, sum: 70};
    var expected = [
      {to: 20},
      {from: 20, to: 25},
      {from: 25, to: 30},
      {from: 30, to: 35},
      {from: 35, to: 40},
      {from: 40, to: 45},
      {from: 45}
    ];
    expect(generateRanges(stats)).toEqual(expected);
  });

  it('should not do an infinite loop', () => {
    var stats = {min: 99.99, max: 149.99, avg: 124.99, sum: 249.98};
    var expected = [
      {to: 100},
      {from: 100, to: 110},
      {from: 110, to: 120},
      {from: 120, to: 130},
      {from: 130, to: 131},
      {from: 131, to: 132},
      {from: 132}
    ];
    expect(generateRanges(stats)).toEqual(expected);
  });

  it('should not generate ranges', () => {
    var stats = {min: 20, max: 20, avg: 20, sum: 20};
    expect(generateRanges(stats)).toEqual([]);
  });
});
<|endoftext|>


<commit_before>import React from 'react'
import PropTypes from 'prop-types'
import {Link} from 'react-router'
import _ from 'lodash'

import ConfirmButton from 'src/shared/components/ConfirmButton'
import {parseAlertNodeList} from 'src/shared/parsing/parseHandlersFromRule'
import {TASKS_TABLE} from 'src/kapacitor/constants/tableSizing'
const {
  colName,
  colTrigger,
  colMessage,
  colAlerts,
  colEnabled,
  colActions,
} = TASKS_TABLE

const KapacitorRulesTable = ({rules, source, onDelete, onChangeRuleStatus}) =>

        Name
        Rule Type
        Message
        Alert Handlers

          Task Enabled

      {_.sortBy(rules, r => r.name.toLowerCase()).map(rule => {
        return (
          <RuleRow
            key={rule.id}
            rule={rule}
            source={source}
            onDelete={onDelete}
            onChangeRuleStatus={onChangeRuleStatus}
          />
        )
      })}

const handleDelete = (rule, onDelete) => onDelete(rule)

const RuleRow = ({rule, source, onDelete, onChangeRuleStatus}) =>

      <Link to={`/sources/${source.id}/alert-rules/${rule.id}`}>
        {rule.name}
      </Link>

      {rule.trigger}

      {rule.message}

      {parseAlertNodeList(rule)}

        <input
          id={`kapacitor-enabled ${rule.id}`}
          className="form-control-static"
          type="checkbox"
          defaultChecked={rule.status === 'enabled'}
          onClick={onChangeRuleStatus(rule)}
        />
        <label htmlFor={`kapacitor-enabled ${rule.id}`} />

      <ConfirmButton
        text="Delete"
        type="btn-danger"
        size="btn-xs"
        customClass="table--show-on-row-hover"
        confirmAction={handleDelete(rule, onDelete)}
      />

const {arrayOf, func, shape, string} = PropTypes

KapacitorRulesTable.propTypes = {
  rules: arrayOf(shape()),
  onChangeRuleStatus: func,
  onDelete: func,
  source: shape({
    id: string.isRequired,
  }).isRequired,
}

RuleRow.propTypes = {
  rule: shape(),
  source: shape(),
  onChangeRuleStatus: func,
  onDelete: func,
}

export default KapacitorRulesTable
<commit_msg>Fix KapacitorRulesTable to use UUIDv4 for tr keys<commit_after>import React from 'react'
import PropTypes from 'prop-types'
import {Link} from 'react-router'
import _ from 'lodash'
import uuid from 'uuid'

import ConfirmButton from 'src/shared/components/ConfirmButton'
import {parseAlertNodeList} from 'src/shared/parsing/parseHandlersFromRule'
import {TASKS_TABLE} from 'src/kapacitor/constants/tableSizing'
const {
  colName,
  colTrigger,
  colMessage,
  colAlerts,
  colEnabled,
  colActions,
} = TASKS_TABLE

const KapacitorRulesTable = ({rules, source, onDelete, onChangeRuleStatus}) =>

        Name
        Rule Type
        Message
        Alert Handlers

          Task Enabled

      {_.sortBy(rules, r => r.name.toLowerCase()).map(rule => {
        return (
          <RuleRow
            key={uuid.v4()}
            rule={rule}
            source={source}
            onDelete={onDelete}
            onChangeRuleStatus={onChangeRuleStatus}
          />
        )
      })}

const handleDelete = (rule, onDelete) => onDelete(rule)

const RuleRow = ({rule, source, onDelete, onChangeRuleStatus}) =>

      <Link to={`/sources/${source.id}/alert-rules/${rule.id}`}>
        {rule.name}
      </Link>

      {rule.trigger}

      {rule.message}

      {parseAlertNodeList(rule)}

        <input
          id={`kapacitor-enabled ${rule.id}`}
          className="form-control-static"
          type="checkbox"
          defaultChecked={rule.status === 'enabled'}
          onClick={onChangeRuleStatus(rule)}
        />
        <label htmlFor={`kapacitor-enabled ${rule.id}`} />

      <ConfirmButton
        text="Delete"
        type="btn-danger"
        size="btn-xs"
        customClass="table--show-on-row-hover"
        confirmAction={handleDelete(rule, onDelete)}
      />

const {arrayOf, func, shape, string} = PropTypes

KapacitorRulesTable.propTypes = {
  rules: arrayOf(shape()),
  onChangeRuleStatus: func,
  onDelete: func,
  source: shape({
    id: string.isRequired,
  }).isRequired,
}

RuleRow.propTypes = {
  rule: shape(),
  source: shape(),
  onChangeRuleStatus: func,
  onDelete: func,
}

export default KapacitorRulesTable
<|endoftext|>


<commit_before>(function(){

	var positionStore = {};

	core.Module("unify.ui.core.Util", {
		getWidgetLocation : function(widget) {
			return lowland.bom.Element.getLocation(widget.getElement());
		},

		domElementToRootLevel : function(widget) {
			var hash = widget.getHash();
			var posOverride = positionStore[hash];

			var e = widget.getElement();

			if (!posOverride) {
				var pos = unify.ui.core.Util.getWidgetLocation(widget);
				var origPos = widget.getPosition();

				posOverride = positionStore[hash] = {
					originalPosition : {
						left: origPos[0],
						top: origPos[1],
						zIndex: lowland.bom.Style.get(e, "zIndex")
					},
					newPosition : {
						left: pos.left,
						top: pos.top,
						zIndex: 20000
					}
				};
			}

			var rootElement = unify.core.Init.getApplication().getRoot().getElement();

			if (rootElement === e.parentNode) {
				// Element is in root, so nothing to do
				return;
			}

			widget.setUserData("domElementPositionOverride", {left: posOverride.newPosition.left, top: posOverride.newPosition.top});

			lowland.bom.Style.set(e, {
				left: posOverride.newPosition.left + "px",
				top:  posOverride.newPosition.top + "px",
				zIndex: 20000
			});

			rootElement.appendChild(e);
		},

		domElementToTreeLevel : function(widget) {
			var hash = widget.getHash();
			var e = widget.getElement();

			var posOverride = positionStore[hash];

			if (!posOverride) {
				return;
			}

			var parentElement = widget.getParentBox().getContentElement();
			if (e.parentNode == parentElement) {
				// Element is in tree, so nothing to do
				return;
			}

			var origPos = posOverride.originalPosition;

			lowland.bom.Style.set(e, {
				left: origPos.left + "px",
				top: origPos.top + "px",
				zIndex: origPos.zIndex
			});
			parentElement.appendChild(e);

			widget.setUserData("domElementPositionOverride", null);
		}
	});

})();<commit_msg>Make widget position detection relative to viewport element<commit_after>(function(){

	var positionStore = {};
  var viewportRoot = null;

	core.Module("unify.ui.core.Util", {
		getWidgetLocation : function(widget) {
      if (!viewportRoot) {
        viewportRoot = unify.core.Init.getApplication().getRoot().getViewportElement();
      }

			return lowland.bom.Element.getLocation(widget.getElement(), viewportRoot);
		},

		domElementToRootLevel : function(widget) {
			var hash = widget.getHash();
			var posOverride = positionStore[hash];

			var e = widget.getElement();

			if (!posOverride) {
				var pos = unify.ui.core.Util.getWidgetLocation(widget);
				var origPos = widget.getPosition();

				posOverride = positionStore[hash] = {
					originalPosition : {
						left: origPos[0],
						top: origPos[1],
						zIndex: lowland.bom.Style.get(e, "zIndex")
					},
					newPosition : {
						left: pos.left,
						top: pos.top,
						zIndex: 20000
					}
				};
			}

			var rootElement = unify.core.Init.getApplication().getRoot().getElement();

			if (rootElement === e.parentNode) {
				// Element is in root, so nothing to do
				return;
			}

			widget.setUserData("domElementPositionOverride", {left: posOverride.newPosition.left, top: posOverride.newPosition.top});

			lowland.bom.Style.set(e, {
				left: posOverride.newPosition.left + "px",
				top:  posOverride.newPosition.top + "px",
				zIndex: 20000
			});

			rootElement.appendChild(e);
		},

		domElementToTreeLevel : function(widget) {
			var hash = widget.getHash();
			var e = widget.getElement();

			var posOverride = positionStore[hash];

			if (!posOverride) {
				return;
			}

			var parentElement = widget.getParentBox().getContentElement();
			if (e.parentNode == parentElement) {
				// Element is in tree, so nothing to do
				return;
			}

			var origPos = posOverride.originalPosition;

			lowland.bom.Style.set(e, {
				left: origPos.left + "px",
				top: origPos.top + "px",
				zIndex: origPos.zIndex
			});
			parentElement.appendChild(e);

			widget.setUserData("domElementPositionOverride", null);
		}
	});

})();<|endoftext|>


<commit_before>import React, { Component } from 'react';
import { Text, View, Image, TextInput, ListView, TouchableHighlight, TouchableOpacity } from 'react-native';
import helpers from '../helpers/helpers.js';
import Recipe from './Recipe.ios.js';
import styles from '../styles.ios.js';
import NutrientComparisons from './NutrientsComparison.ios.js';
export default class Recipes extends Component {
  constructor(props) {
    super(props);
    const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
    this.state = {
      foodDataSource: ds.cloneWithRows(props.recipes),
      comparing: false,
      compareArray: [],
      titles: [],
      images: []
    }
  }
  selectRecipe(id, title, image) {
    if (this.state.comparing) {
      console.log('in comparing');
      this.comparing(id, title, image);
    } else {
      helpers.recipes.getRecipe(id)
        .then( resp => {
          this.props.navigator.push({
            component: Recipe,
            passProps: { store: this.props.store, recipe: resp.data, food: title, image: image  }
          })
        })
    }
  }
  goBack() {
    this.props.navigator.pop();
  }
  compareRecipe() {
    this.setState({
      comparing: !this.state.comparing
    })
    this.forceUpdate();
  }
  comparing(id, title, image) {
    this.state.compareArray.push(id);
    this.state.titles.push(title);
    this.state.images.push(image);
    if (this.state.compareArray.length === 2) {
      helpers.recipes.compareRecipes(this.state.compareArray)
        .then( resp => {
          this.props.navigator.push({
            component: NutrientComparisons,
            passProps: {results: resp.data, ids: this.state.compareArray, titles: this.state.titles, images: this.state.images, store: this.props.store}
          })
        })
    }
      // this.props.navigator.push({
      //   component: NutrientComparisons,
      //   passProps: {results: fakeData, ids: this.state.compareArray, titles: this.state.titles, images: this.state.images}
      // })
    }
  }
  render() {
    if (this.props.recipes.length === 0) {
      return (
        <View style={[styles.container]}>
          <View style={styles.resultsTitle}>
            <TouchableOpacity style={styles.backButton} onPress={this.goBack.bind(this)}>
              <Image style={styles.backButtonImage} source={{uri: 'https://cdn0.iconfinder.com/data/icons/vector-basic-tab-bar-icons/48/back_button-128.png'}} />
            </TouchableOpacity>
            <Text style={styles.resultsTitleText}>We are unable to find any pairings for the selected item at this time</Text>
          </View>
        </View>
      )
    } else if (this.state.comparing) {
      return (
        <View style={styles.resultsList}>
          <View style={styles.resultsTitle}>
            <TouchableOpacity style={styles.backButton} onPress={this.goBack.bind(this)}>
              <Image style={styles.backButtonImage} source={{uri: 'https://cdn0.iconfinder.com/data/icons/vector-basic-tab-bar-icons/48/back_button-128.png'}} />
            </TouchableOpacity>
            <Text style={styles.resultsTitleText}>Comparing Recipes:</Text>
            <TouchableOpacity onPress={this.compareRecipe.bind(this)}>
              <Image source = {{uri: 'https://tavaga.com/images/compare-button.png'}} style = {styles.shareButton} />
            </TouchableOpacity>
          </View>
          <ListView
            dataSource={this.state.foodDataSource}
            renderRow={(recipe, i) => (
              <TouchableHighlight
                key={i}
                style={styles.listItem}
                underlayColor="blue"
                style={styles.resultsList}
                onPress={ this.comparing.bind(this, recipe.id, recipe.title, recipe.image)}
                >
                <TouchableHighlight onPress={ this.comparing.bind(this, recipe.id, recipe.title, recipe.image)}>
                  <View style={styles.listItem}>
                    <Image style={styles.resultsPicture} source={{uri: recipe.image}} />
                    <View style={styles.listItemContainer}>
                      <View style={styles.listItemTitle}>
                        <Text style={styles.recipeTitle}>{recipe.title}</Text>
                      </View>
                      <View style={styles.listItemText}>
                        <Text style={styles.recipeDescription}>Missing ingredients: {recipe.missedIngredientCount}</Text>
                        <Text style={styles.recipeDescription}>Likes: {recipe.likes}</Text>
                      </View>
                    </View>
                  </View>
                </TouchableHighlight>
              </TouchableHighlight>
              )}
          />
        </View>
      )
    } else {
      return (
        <View style={styles.resultsList}>
          <View style={styles.resultsTitle}>
            <TouchableOpacity style={styles.backButton} onPress={this.goBack.bind(this)}>
              <Image style={styles.backButtonImage} source={{uri: 'https://cdn0.iconfinder.com/data/icons/vector-basic-tab-bar-icons/48/back_button-128.png'}} />
            </TouchableOpacity>
            <Text style={styles.resultsTitleText}>Recipes:</Text>
            <TouchableOpacity onPress={this.compareRecipe.bind(this)}>
              <Image source = {{uri: 'https://tavaga.com/images/compare-button.png'}} style = {styles.shareButton} />
            </TouchableOpacity>
          </View>
          <ListView
            dataSource={this.state.foodDataSource}
            renderRow={(recipe, i) => (
              <TouchableHighlight
                key={i}
                style={styles.listItem}
                underlayColor="grey"
                style={styles.resultsList}
                >
                <TouchableHighlight onPress={this.selectRecipe.bind(this, recipe.id, recipe.title, recipe.image)}>
                  <View style={styles.listItem}>
                    <Image style={styles.resultsPicture} source={{uri: recipe.image}} />
                    <View style={styles.listItemContainer}>
                      <View style={styles.listItemTitle}>
                        <Text style={styles.recipeTitle}>{recipe.title}</Text>
                      </View>
                      <View style={styles.listItemText}>
                        <Text style={styles.recipeDescription}>Missing ingredients: {recipe.missedIngredientCount}</Text>
                        <Text style={styles.recipeDescription}>Likes: {recipe.likes}</Text>
                      </View>
                    </View>
                  </View>
                </TouchableHighlight>
              </TouchableHighlight>
              )}
          />
        </View>
      )
    }
  }
}
<commit_msg>updated recipes page to put button in the center<commit_after>import React, { Component } from 'react';
import { Text, View, Image, TextInput, ListView, TouchableHighlight, TouchableOpacity } from 'react-native';
import helpers from '../helpers/helpers.js';
import Recipe from './Recipe.ios.js';
import styles from '../styles.ios.js';
import NutrientComparisons from './NutrientsComparison.ios.js';
export default class Recipes extends Component {
  constructor(props) {
    super(props);
    const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
    this.state = {
      foodDataSource: ds.cloneWithRows(props.recipes),
      comparing: false,
      compareArray: [],
      titles: [],
      images: []
    }
  }
  selectRecipe(id, title, image) {
    if (this.state.comparing) {
      console.log('in comparing');
      this.comparing(id, title, image);
    } else {
      helpers.recipes.getRecipe(id)
        .then( resp => {
          this.props.navigator.push({
            component: Recipe,
            passProps: { store: this.props.store, recipe: resp.data, food: title, image: image  }
          })
        })
    }
  }
  goBack() {
    this.props.navigator.pop();
  }
  compareRecipe() {
    this.setState({
      comparing: !this.state.comparing
    })
    this.forceUpdate();
  }
  comparing(id, title, image) {
    this.state.compareArray.push(id);
    this.state.titles.push(title);
    this.state.images.push(image);
    if (this.state.compareArray.length === 2) {
      helpers.recipes.compareRecipes(this.state.compareArray)
        .then( resp => {
          this.props.navigator.push({
            component: NutrientComparisons,
            passProps: {results: resp.data, ids: this.state.compareArray, titles: this.state.titles, images: this.state.images, store: this.props.store}
          })
        })
    }
      // this.props.navigator.push({
      //   component: NutrientComparisons,
      //   passProps: {results: fakeData, ids: this.state.compareArray, titles: this.state.titles, images: this.state.images}
      // })
  }
  render() {
    if (this.props.recipes.length === 0) {
      return (
        <View style={[styles.container]}>
          <View style={styles.resultsTitle}>
            <TouchableOpacity style={styles.backButton} onPress={this.goBack.bind(this)}>
              <Image style={styles.backButtonImage} source={{uri: 'https://cdn0.iconfinder.com/data/icons/vector-basic-tab-bar-icons/48/back_button-128.png'}} />
            </TouchableOpacity>
            <Text style={styles.resultsTitleText}>We are unable to find any pairings for the selected item at this time</Text>
          </View>
        </View>
      )
    } else if (this.state.comparing) {
      return (
        <View style={styles.resultsList}>
          <View style={styles.resultsTitle}>
            <TouchableOpacity style={styles.backButton} onPress={this.goBack.bind(this)}>
              <Image style={styles.backButtonImage} source={{uri: 'https://cdn0.iconfinder.com/data/icons/vector-basic-tab-bar-icons/48/back_button-128.png'}} />
            </TouchableOpacity>
            <Text style={styles.resultsTitleText}>Comparing Recipes:</Text>
            <TouchableOpacity onPress={this.compareRecipe.bind(this)}>
              <Image source = {{uri: 'https://tavaga.com/images/compare-button.png'}} style = {styles.shareButton} />
            </TouchableOpacity>
          </View>
          <ListView
            dataSource={this.state.foodDataSource}
            renderRow={(recipe, i) => (
              <TouchableHighlight
                key={i}
                style={styles.listItem}
                underlayColor="blue"
                style={styles.resultsList}
                onPress={ this.comparing.bind(this, recipe.id, recipe.title, recipe.image)}
                >
                <TouchableHighlight onPress={ this.comparing.bind(this, recipe.id, recipe.title, recipe.image)}>
                  <View style={styles.listItem}>
                    <Image style={styles.resultsPicture} source={{uri: recipe.image}} />
                    <View style={styles.listItemContainer}>
                      <View style={styles.listItemTitle}>
                        <Text style={styles.recipeTitle}>{recipe.title}</Text>
                      </View>
                      <View style={styles.listItemText}>
                        <Text style={styles.recipeDescription}>Missing ingredients: {recipe.missedIngredientCount}</Text>
                        <Text style={styles.recipeDescription}>Likes: {recipe.likes}</Text>
                      </View>
                    </View>
                  </View>
                </TouchableHighlight>
              </TouchableHighlight>
              )}
          />
        </View>
      )
    } else {
      return (
        <View style={styles.resultsList}>
          <View style={styles.resultsTitle}>
            <TouchableOpacity style={styles.backButton} onPress={this.goBack.bind(this)}>
              <Image style={styles.backButtonImage} source={{uri: 'https://cdn0.iconfinder.com/data/icons/vector-basic-tab-bar-icons/48/back_button-128.png'}} />
            </TouchableOpacity>
            <Text style={styles.resultsTitleText}>Recipes:</Text>
            <TouchableOpacity onPress={this.compareRecipe.bind(this)}>
              <Image source = {{uri: 'https://tavaga.com/images/compare-button.png'}} style = {styles.shareButton} />
            </TouchableOpacity>
          </View>
          <ListView
            dataSource={this.state.foodDataSource}
            renderRow={(recipe, i) => (
              <TouchableHighlight
                key={i}
                style={styles.listItem}
                underlayColor="grey"
                style={styles.resultsList}
                >
                <TouchableHighlight onPress={this.selectRecipe.bind(this, recipe.id, recipe.title, recipe.image)}>
                  <View style={styles.listItem}>
                    <Image style={styles.resultsPicture} source={{uri: recipe.image}} />
                    <View style={styles.listItemContainer}>
                      <View style={styles.listItemTitle}>
                        <Text style={styles.recipeTitle}>{recipe.title}</Text>
                      </View>
                      <View style={styles.listItemText}>
                        <Text style={styles.recipeDescription}>Missing ingredients: {recipe.missedIngredientCount}</Text>
                        <Text style={styles.recipeDescription}>Likes: {recipe.likes}</Text>
                      </View>
                    </View>
                  </View>
                </TouchableHighlight>
              </TouchableHighlight>
              )}
          />
        </View>
      )
    }
  }
}
<|endoftext|>


<commit_before>/* global Vue, global document, global location*/
'use strict';

var vm = new Vue({
  el: '#addInstance',

  ready : function () {

    var self = this;
    var publicDomain;

    self.$http.get('/-/v3/config.js').then(function (result) {
      self.$set('publicDomain', result.data.publicDomain);
      publicDomain = result.data.publicDomain;
    }, console.error);
  },

  methods : {

    displayFormAddInstance : function (event) {
      document.getElementById('modal-add-instance').style.display = 'block';
    },

    cancelAddInstance : function (event) {
      document.getElementById('modal-add-instance').style.display = 'none';
    },

    addInstance : function (event) {
      this.longName = document.getElementById('inputLongName').value;
      this.project = document.getElementById('inputProject').value;
      this.study = document.getElementById('inputStudy').value;
      this.version = document.getElementById('inputVersion').value;

      document.getElementById('save').style.display = 'none';
      document.getElementById('close_modal').style.display = 'none';
      document.getElementById('loader').style.display = 'block';

      if (this.longName == '') { this.longName = 'Free comment of '+this.technicalName; }

      var data = {
        'longName' : this.longName,
        'project' : this.project,
        'version' : this.version,
        'study': this.study,
        'technicalName' :  this.technicalName,
        'app' : document.getElementById('app').value
      };
      this.$http.post('/-/v1/instances', data).then(function (result) {
        if (result.status == 200) { location.reload(); }
      }, function (error) {
        if (error.status == 400) {
          this.codeErrorPull = error.status;
          this.messageErrorPull = error.data;
          document.getElementById('loader').style.display = 'none';
          document.getElementById('errorLoader').style.display = 'block';
        }
      });
    },

    tryAgain : function (event) {
      location.reload();
    }
  },
  data : {
    longName : '',
    project: '',
    version : '',
    study : '',
    technicalName : '',
    urlPreview : '',
    messageErrorPull : '',
    codeErrorPull : ''
  }
});

module.exports = vm;

// Manage field color changes in the addInstance form.

vm.$watch('longName', function(data) {

  var value = document.forms['Form']['inputLongName'].value;

  if (value == '')
    document.getElementById('inputLongName').style.backgroundColor='#FFCDD2';
  else
    document.getElementById('inputLongName').style.backgroundColor='#C5E1A5';

});

vm.$watch('study', function(data) {

  var value = document.forms['Form']['inputStudy'].value;

  if (/^[a-z0-9]+$/.test(value)==false || value == '')
    document.getElementById('inputStudy').style.backgroundColor='#FFCDD2';
  else
    document.getElementById('inputStudy').style.backgroundColor='#C5E1A5';

});

vm.$watch('project', function(data) {

  var value = document.forms['Form']['inputProject'].value;

  if (/^[a-z0-9]+$/.test(value)==false || value == '')
    document.getElementById('inputProject').style.backgroundColor='#FFCDD2';
  else
    document.getElementById('inputProject').style.backgroundColor='#C5E1A5';

});

vm.$watch('project', function(data) {

  var self = this;
  var publicDomain;

    self.$http.get('/-/v3/config.js').then(function (result) {
      publicDomain = result.data.publicDomain;
    }, console.error);
  },

  this.project = data;

  if (this.version == '') { this.technicalName = this.project + '-' + this.study; }
  else { this.technicalName = this.project + '-' + this.study + '-' + this.version; }

  this.urlPreview = 'http://'+this.technicalName+'.'+publicDomain;
  verif(this.technicalName);
});

vm.$watch('study', function (data) {
  this.study = data;

  if (this.version == '') { this.technicalName = this.project + '-' + this.study; }
  else { this.technicalName = this.project + '-' + this.study + '-' + this.version; }

  this.urlPreview = 'http://'+this.technicalName+'.'+publicDomain;
  verif(this.technicalName);
});

vm.$watch('version', function (data) {
  this.version = data;

  if (this.version == '') { this.technicalName = this.project + '-' + this.study; }
  else { this.technicalName = this.project + '-' + this.study + '-' + this.version; }

  this.urlPreview = 'http://'+this.technicalName+'.'+publicDomain;
  verif(this.technicalName);
});

function verif (tn) {
  var data = {
    'technicalName' : tn
  };
  vm.$http.get('/-/v1/instances/verif', data).then(function (result) {
    if (result.status == 200) {
      document.getElementById('technicalNameExists').style.display = 'none';
      document.getElementById('save').style.display = 'block';
      document.getElementById('saveTechnicalExists').style.display = 'none';
    }
  }, function (error) {
    if (error.status == 409) {
      document.getElementById('technicalNameExists').style.display = 'block';
      document.getElementById('save').style.display = 'none';
      document.getElementById('saveTechnicalExists').style.display = 'block';
    }
  });
}<commit_msg>fix addInstance.js<commit_after>/* global Vue, global document, global location*/
'use strict';

var vm = new Vue({
  el: '#addInstance',

  ready : function () {

    var self = this;
    var publicDomain;

    self.$http.get('/-/v3/config.js').then(function (result) {
      self.$set('publicDomain', result.data.publicDomain);
      publicDomain = result.data.publicDomain;
    }, console.error);
  },

  methods : {

    displayFormAddInstance : function (event) {
      document.getElementById('modal-add-instance').style.display = 'block';
    },

    cancelAddInstance : function (event) {
      document.getElementById('modal-add-instance').style.display = 'none';
    },

    addInstance : function (event) {
      this.longName = document.getElementById('inputLongName').value;
      this.project = document.getElementById('inputProject').value;
      this.study = document.getElementById('inputStudy').value;
      this.version = document.getElementById('inputVersion').value;

      document.getElementById('save').style.display = 'none';
      document.getElementById('close_modal').style.display = 'none';
      document.getElementById('loader').style.display = 'block';

      if (this.longName == '') { this.longName = 'Free comment of '+this.technicalName; }

      var data = {
        'longName' : this.longName,
        'project' : this.project,
        'version' : this.version,
        'study': this.study,
        'technicalName' :  this.technicalName,
        'app' : document.getElementById('app').value
      };
      this.$http.post('/-/v1/instances', data).then(function (result) {
        if (result.status == 200) { location.reload(); }
      }, function (error) {
        if (error.status == 400) {
          this.codeErrorPull = error.status;
          this.messageErrorPull = error.data;
          document.getElementById('loader').style.display = 'none';
          document.getElementById('errorLoader').style.display = 'block';
        }
      });
    },

    tryAgain : function (event) {
      location.reload();
    }
  },
  data : {
    longName : '',
    project: '',
    version : '',
    study : '',
    technicalName : '',
    urlPreview : '',
    messageErrorPull : '',
    codeErrorPull : ''
  }
});

module.exports = vm;

// Manage field color changes in the addInstance form.

vm.$watch('longName', function(data) {

  var value = document.forms['Form']['inputLongName'].value;

  if (value == '')
    document.getElementById('inputLongName').style.backgroundColor='#FFCDD2';
  else
    document.getElementById('inputLongName').style.backgroundColor='#C5E1A5';

});

vm.$watch('study', function(data) {

  var value = document.forms['Form']['inputStudy'].value;

  if (/^[a-z0-9]+$/.test(value)==false || value == '')
    document.getElementById('inputStudy').style.backgroundColor='#FFCDD2';
  else
    document.getElementById('inputStudy').style.backgroundColor='#C5E1A5';

});

vm.$watch('project', function(data) {

  var value = document.forms['Form']['inputProject'].value;

  if (/^[a-z0-9]+$/.test(value)==false || value == '')
    document.getElementById('inputProject').style.backgroundColor='#FFCDD2';
  else
    document.getElementById('inputProject').style.backgroundColor='#C5E1A5';

});

vm.$watch('project', function(data) {

  var self = this;
  var publicDomain;

    self.$http.get('/-/v3/config.js').then(function (result) {
      publicDomain = result.data.publicDomain;
    }, console.error);

  this.project = data;

  if (this.version == '') { this.technicalName = this.project + '-' + this.study; }
  else { this.technicalName = this.project + '-' + this.study + '-' + this.version; }

  this.urlPreview = 'http://'+this.technicalName+'.'+publicDomain;
  verif(this.technicalName);
});

vm.$watch('study', function (data) {
  this.study = data;

  if (this.version == '') { this.technicalName = this.project + '-' + this.study; }
  else { this.technicalName = this.project + '-' + this.study + '-' + this.version; }

  this.urlPreview = 'http://'+this.technicalName+'.'+publicDomain;
  verif(this.technicalName);
});

vm.$watch('version', function (data) {
  this.version = data;

  if (this.version == '') { this.technicalName = this.project + '-' + this.study; }
  else { this.technicalName = this.project + '-' + this.study + '-' + this.version; }

  this.urlPreview = 'http://'+this.technicalName+'.'+publicDomain;
  verif(this.technicalName);
});

function verif (tn) {
  var data = {
    'technicalName' : tn
  };
  vm.$http.get('/-/v1/instances/verif', data).then(function (result) {
    if (result.status == 200) {
      document.getElementById('technicalNameExists').style.display = 'none';
      document.getElementById('save').style.display = 'block';
      document.getElementById('saveTechnicalExists').style.display = 'none';
    }
  }, function (error) {
    if (error.status == 409) {
      document.getElementById('technicalNameExists').style.display = 'block';
      document.getElementById('save').style.display = 'none';
      document.getElementById('saveTechnicalExists').style.display = 'block';
    }
  });
}<|endoftext|>


<commit_before>import { Component } from 'react';
import styles from './Pricing.module.css';
import apps from '../../lib/pro';

class Pricing extends Component {
  render() {
    return

Get it now

Join other developers using Dracula PRO every day to build elite apps.

Launch Promo

$79$49

one-time payment

                <svg className={styles.icon} fill="#50fa7b" width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><title>ionicons-v5-checkmark-circle</title><path d="M256,48C141.31,48,48,141.31,48,256s93.31,208,208,208,208-93.31,208-208S370.69,48,256,48ZM364.25,186.29l-134.4,160a16,16,0,0,1-12,5.71h-.27a16,16,0,0,1-11.89-5.3l-57.6-64a16,16,0,1,1,23.78-21.4l45.29,50.32L339.75,165.71a16,16,0,0,1,24.5,20.58Z"/></svg>
                10 Themes

                <svg className={styles.icon} fill="#50fa7b" width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><title>ionicons-v5-checkmark-circle</title><path d="M256,48C141.31,48,48,141.31,48,256s93.31,208,208,208,208-93.31,208-208S370.69,48,256,48ZM364.25,186.29l-134.4,160a16,16,0,0,1-12,5.71h-.27a16,16,0,0,1-11.89-5.3l-57.6-64a16,16,0,1,1,23.78-21.4l45.29,50.32L339.75,165.71a16,16,0,0,1,24.5,20.58Z"/></svg>
                6 Variants

                <svg className={styles.icon} fill="#50fa7b" width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><title>ionicons-v5-checkmark-circle</title><path d="M256,48C141.31,48,48,141.31,48,256s93.31,208,208,208,208-93.31,208-208S370.69,48,256,48ZM364.25,186.29l-134.4,160a16,16,0,0,1-12,5.71h-.27a16,16,0,0,1-11.89-5.3l-57.6-64a16,16,0,1,1,23.78-21.4l45.29,50.32L339.75,165.71a16,16,0,0,1,24.5,20.58Z"/></svg>
                4 Hand-picked Fonts

                <svg className={styles.icon} fill="#50fa7b" width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><title>ionicons-v5-checkmark-circle</title><path d="M256,48C141.31,48,48,141.31,48,256s93.31,208,208,208,208-93.31,208-208S370.69,48,256,48ZM364.25,186.29l-134.4,160a16,16,0,0,1-12,5.71h-.27a16,16,0,0,1-11.89-5.3l-57.6-64a16,16,0,1,1,23.78-21.4l45.29,50.32L339.75,165.71a16,16,0,0,1,24.5,20.58Z"/></svg>
                1 Productivy E-book

                <svg className={styles.icon} fill="#50fa7b" width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><title>ionicons-v5-checkmark-circle</title><path d="M256,48C141.31,48,48,141.31,48,256s93.31,208,208,208,208-93.31,208-208S370.69,48,256,48ZM364.25,186.29l-134.4,160a16,16,0,0,1-12,5.71h-.27a16,16,0,0,1-11.89-5.3l-57.6-64a16,16,0,1,1,23.78-21.4l45.29,50.32L339.75,165.71a16,16,0,0,1,24.5,20.58Z"/></svg>
                1 Bonus Screencast

                <svg className={styles.icon} fill="#50fa7b" width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><title>ionicons-v5-checkmark-circle</title><path d="M256,48C141.31,48,48,141.31,48,256s93.31,208,208,208,208-93.31,208-208S370.69,48,256,48ZM364.25,186.29l-134.4,160a16,16,0,0,1-12,5.71h-.27a16,16,0,0,1-11.89-5.3l-57.6-64a16,16,0,1,1,23.78-21.4l45.29,50.32L339.75,165.71a16,16,0,0,1,24.5,20.58Z"/></svg>
                Constant updates

                <svg className={styles.icon} fill="#50fa7b" width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><title>ionicons-v5-checkmark-circle</title><path d="M256,48C141.31,48,48,141.31,48,256s93.31,208,208,208,208-93.31,208-208S370.69,48,256,48ZM364.25,186.29l-134.4,160a16,16,0,0,1-12,5.71h-.27a16,16,0,0,1-11.89-5.3l-57.6-64a16,16,0,1,1,23.78-21.4l45.29,50.32L339.75,165.71a16,16,0,0,1,24.5,20.58Z"/></svg>
                Support included

                <svg className={styles.icon} fill="#50fa7b" width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><title>ionicons-v5-checkmark-circle</title><path d="M256,48C141.31,48,48,141.31,48,256s93.31,208,208,208,208-93.31,208-208S370.69,48,256,48ZM364.25,186.29l-134.4,160a16,16,0,0,1-12,5.71h-.27a16,16,0,0,1-11.89-5.3l-57.6-64a16,16,0,1,1,23.78-21.4l45.29,50.32L339.75,165.71a16,16,0,0,1,24.5,20.58Z"/></svg>
                License for 3 computers

              Buy Dracula PRO

14 days refund (no Q/A)

Contact me to get a Team License to share with your team.

  }
}

export default Pricing;<commit_msg>Fix typo<commit_after>import { Component } from 'react';
import styles from './Pricing.module.css';
import apps from '../../lib/pro';

class Pricing extends Component {
  render() {
    return

Get it now

Join other developers using Dracula PRO every day to build elite apps.

Launch Promo

$79$49

one-time payment

                <svg className={styles.icon} fill="#50fa7b" width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><title>ionicons-v5-checkmark-circle</title><path d="M256,48C141.31,48,48,141.31,48,256s93.31,208,208,208,208-93.31,208-208S370.69,48,256,48ZM364.25,186.29l-134.4,160a16,16,0,0,1-12,5.71h-.27a16,16,0,0,1-11.89-5.3l-57.6-64a16,16,0,1,1,23.78-21.4l45.29,50.32L339.75,165.71a16,16,0,0,1,24.5,20.58Z"/></svg>
                10 Themes

                <svg className={styles.icon} fill="#50fa7b" width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><title>ionicons-v5-checkmark-circle</title><path d="M256,48C141.31,48,48,141.31,48,256s93.31,208,208,208,208-93.31,208-208S370.69,48,256,48ZM364.25,186.29l-134.4,160a16,16,0,0,1-12,5.71h-.27a16,16,0,0,1-11.89-5.3l-57.6-64a16,16,0,1,1,23.78-21.4l45.29,50.32L339.75,165.71a16,16,0,0,1,24.5,20.58Z"/></svg>
                6 Variants

                <svg className={styles.icon} fill="#50fa7b" width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><title>ionicons-v5-checkmark-circle</title><path d="M256,48C141.31,48,48,141.31,48,256s93.31,208,208,208,208-93.31,208-208S370.69,48,256,48ZM364.25,186.29l-134.4,160a16,16,0,0,1-12,5.71h-.27a16,16,0,0,1-11.89-5.3l-57.6-64a16,16,0,1,1,23.78-21.4l45.29,50.32L339.75,165.71a16,16,0,0,1,24.5,20.58Z"/></svg>
                4 Hand-picked Fonts

                <svg className={styles.icon} fill="#50fa7b" width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><title>ionicons-v5-checkmark-circle</title><path d="M256,48C141.31,48,48,141.31,48,256s93.31,208,208,208,208-93.31,208-208S370.69,48,256,48ZM364.25,186.29l-134.4,160a16,16,0,0,1-12,5.71h-.27a16,16,0,0,1-11.89-5.3l-57.6-64a16,16,0,1,1,23.78-21.4l45.29,50.32L339.75,165.71a16,16,0,0,1,24.5,20.58Z"/></svg>
                1 Productivity E-book

                <svg className={styles.icon} fill="#50fa7b" width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><title>ionicons-v5-checkmark-circle</title><path d="M256,48C141.31,48,48,141.31,48,256s93.31,208,208,208,208-93.31,208-208S370.69,48,256,48ZM364.25,186.29l-134.4,160a16,16,0,0,1-12,5.71h-.27a16,16,0,0,1-11.89-5.3l-57.6-64a16,16,0,1,1,23.78-21.4l45.29,50.32L339.75,165.71a16,16,0,0,1,24.5,20.58Z"/></svg>
                1 Bonus Screencast

                <svg className={styles.icon} fill="#50fa7b" width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><title>ionicons-v5-checkmark-circle</title><path d="M256,48C141.31,48,48,141.31,48,256s93.31,208,208,208,208-93.31,208-208S370.69,48,256,48ZM364.25,186.29l-134.4,160a16,16,0,0,1-12,5.71h-.27a16,16,0,0,1-11.89-5.3l-57.6-64a16,16,0,1,1,23.78-21.4l45.29,50.32L339.75,165.71a16,16,0,0,1,24.5,20.58Z"/></svg>
                Constant updates

                <svg className={styles.icon} fill="#50fa7b" width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><title>ionicons-v5-checkmark-circle</title><path d="M256,48C141.31,48,48,141.31,48,256s93.31,208,208,208,208-93.31,208-208S370.69,48,256,48ZM364.25,186.29l-134.4,160a16,16,0,0,1-12,5.71h-.27a16,16,0,0,1-11.89-5.3l-57.6-64a16,16,0,1,1,23.78-21.4l45.29,50.32L339.75,165.71a16,16,0,0,1,24.5,20.58Z"/></svg>
                Support included

                <svg className={styles.icon} fill="#50fa7b" width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><title>ionicons-v5-checkmark-circle</title><path d="M256,48C141.31,48,48,141.31,48,256s93.31,208,208,208,208-93.31,208-208S370.69,48,256,48ZM364.25,186.29l-134.4,160a16,16,0,0,1-12,5.71h-.27a16,16,0,0,1-11.89-5.3l-57.6-64a16,16,0,1,1,23.78-21.4l45.29,50.32L339.75,165.71a16,16,0,0,1,24.5,20.58Z"/></svg>
                License for 3 computers

              Buy Dracula PRO

14 days refund (no Q/A)

Contact me to get a Team License to share with your team.

  }
}

export default Pricing;<|endoftext|>


<commit_before>(function(d3, fc) {
    'use strict';

    fc.series.ohlc = function(drawMethod) {

        // Configurable attributes
        var xScale = d3.time.scale(),
            yScale = d3.scale.linear(),
            tickWidth = fc.utilities.timeIntervalWidth(d3.time.day, 0.35);

        var yOpen = fc.utilities.valueAccessor('open'),
            yHigh = fc.utilities.valueAccessor('high'),
            yLow = fc.utilities.valueAccessor('low'),
            yClose = fc.utilities.valueAccessor('close');

        // Accessor functions
        var open = function(d) {
                return yScale(yOpen(d));
            },
            high = function(d) {
                return yScale(yHigh(d));
            },
            low = function(d) {
                return yScale(yLow(d));
            },
            close = function(d) {
                return yScale(yClose(d));
            },
            date = function(d) {
                return xScale(d.date);
            };

        // Up/down day logic
        var isUpDay = function(d) {
            return yClose(d) > yOpen(d);
        };
        var isDownDay = function(d) {
            return yClose(d) < yOpen(d);
        };
        var isStaticDay = function(d) {
            return yClose(d) === yOpen(d);
        };

        var makeBarPath = function(d) {
            var width = tickWidth(xScale),
                moveToLow = 'M' + date(d) + ',' + low(d),
                verticalToHigh = 'V' + high(d),
                openTick = 'M' + date(d) + ',' + open(d) + 'h' + (-width),
                closeTick = 'M' + date(d) + ',' + close(d) + 'h' + width;
            return moveToLow + verticalToHigh + openTick + closeTick;
        };

        var ohlc = function(selection) {
            selection.each(function(data) {
                // data-join in order to create the series container element
                var series = d3.select(this)
                    .selectAll('.ohlc-series')
                    .data([data]);

                series.enter()
                    .append('g')
                    .classed('ohlc-series', true);

                // create the bar paths
                var bars = series.selectAll('.bar')
                    // data-join, keying on the date (see #130)
                    .data(data, function(d) {
                        return d.date;
                    });

                bars.enter()
                    .append('path')
                    .classed('bar', true);

                bars.classed({
                    'up-day': isUpDay,
                    'down-day': isDownDay,
                    'static-day': isStaticDay
                });

                bars.attr('d', makeBarPath);

                bars.exit().remove();
            });
        };

        ohlc.xScale = function(value) {
            if (!arguments.length) {
                return xScale;
            }
            xScale = value;
            return ohlc;
        };

        ohlc.yScale = function(value) {
            if (!arguments.length) {
                return yScale;
            }
            yScale = value;
            return ohlc;
        };

        ohlc.tickWidth = function(value) {
            if (!arguments.length) {
                return tickWidth;
            }
            tickWidth = d3.functor(value);
            return ohlc;
        };

        ohlc.yOpen = function(value) {
            if (!arguments.length) {
                return yOpen;
            }
            yOpen = value;
            return ohlc;
        };

        ohlc.yHigh = function(value) {
            if (!arguments.length) {
                return yHigh;
            }
            yHigh = value;
            return ohlc;
        };

        ohlc.yLow = function(value) {
            if (!arguments.length) {
                return yLow;
            }
            yLow = value;
            return ohlc;
        };

        ohlc.yClose = function(value) {
            if (!arguments.length) {
                return yClose;
            }
            yClose = value;
            return ohlc;
        };

        return ohlc;
    };
}(d3, fc));<commit_msg>Added missing file<commit_after>(function(d3, fc) {
    'use strict';

    fc.series.ohlc = function(drawMethod) {

        // Configurable attributes
        var xScale = d3.time.scale(),
            yScale = d3.scale.linear(),
            tickWidth = fc.utilities.timeIntervalWidth(d3.time.day, 0.35);

        var yOpen = fc.utilities.valueAccessor('open'),
            yHigh = fc.utilities.valueAccessor('high'),
            yLow = fc.utilities.valueAccessor('low'),
            yClose = fc.utilities.valueAccessor('close');

        // Accessor functions
        var open = function(d) {
                return yScale(yOpen(d));
            },
            high = function(d) {
                return yScale(yHigh(d));
            },
            low = function(d) {
                return yScale(yLow(d));
            },
            close = function(d) {
                return yScale(yClose(d));
            },
            date = function(d) {
                return xScale(d.date);
            };

        // Up/down day logic
        var isUpDay = function(d) {
            return yClose(d) > yOpen(d);
        };
        var isDownDay = function(d) {
            return yClose(d) < yOpen(d);
        };
        var isStaticDay = function(d) {
            return yClose(d) === yOpen(d);
        };

        var makeBarPath = function(d) {
            var width = tickWidth(xScale),
                moveToLow = 'M' + date(d) + ',' + low(d),
                verticalToHigh = 'V' + high(d),
                openTick = 'M' + date(d) + ',' + open(d) + 'h' + (-width),
                closeTick = 'M' + date(d) + ',' + close(d) + 'h' + width;
            return moveToLow + verticalToHigh + openTick + closeTick;
        };

        var ohlc = function(selection) {
            selection.each(function(data) {
                // data-join in order to create the series container element
                var series = d3.select(this)
                    .selectAll('.ohlc-series')
                    .data([data]);

                series.enter()
                    .append('g')
                    .classed('ohlc-series', true);

                // create the bar paths
                var bars = series.selectAll('.bar')
                    // data-join, keying on the date (see #130)
                    .data(data, function(d) {
                        return d.date;
                    });

                bars.enter()
                    .append('path')
                    .classed('bar', true);

                bars.classed({
                    'up-day': isUpDay,
                    'down-day': isDownDay,
                    'static-day': isStaticDay
                });

                bars.attr('d', makeBarPath);

                bars.exit().remove();
            });
        };

        function fn(value, args, prop) {
            if (!args.length) {
                return prop;
            }
            prop = value;
            return prop;
        }

        ohlc.xScale = function(value) {
            return fn(value, arguments, xScale);
        };

        ohlc.yScale = function(value) {
            if (!arguments.length) {
                return yScale;
            }
            yScale = value;
            return ohlc;
        };

        ohlc.tickWidth = function(value) {
            if (!arguments.length) {
                return tickWidth;
            }
            tickWidth = d3.functor(value);
            return ohlc;
        };

        ohlc.yOpen = function(value) {
            if (!arguments.length) {
                return yOpen;
            }
            yOpen = value;
            return ohlc;
        };

        ohlc.yHigh = function(value) {
            if (!arguments.length) {
                return yHigh;
            }
            yHigh = value;
            return ohlc;
        };

        ohlc.yLow = function(value) {
            if (!arguments.length) {
                return yLow;
            }
            yLow = value;
            return ohlc;
        };

        ohlc.yClose = function(value) {
            if (!arguments.length) {
                return yClose;
            }
            yClose = value;
            return ohlc;
        };

        return ohlc;
    };
}(d3, fc));<|endoftext|>


<commit_before>import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import ActivableRenderer from '../hoc/ActivableRenderer';
import { Button, IconButton } from '../button';
import Portal from '../hoc/Portal';
import { IconCloseMediumOutline } from '@teamleader/ui-icons';
import theme from './theme.css';

const factory = (Button, IconButton) => {
  class Toast extends PureComponent {
    static propTypes = {
      action: PropTypes.string,
      active: PropTypes.bool,
      children: PropTypes.node,
      className: PropTypes.string,
      label: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
      onClick: PropTypes.func,
      onTimeout: PropTypes.func,
      timeout: PropTypes.number,
      type: PropTypes.oneOf(['accept', 'cancel', 'warning']),
    };

    componentDidMount() {
      if (this.props.active && this.props.timeout) {
        this._scheduleTimeout(this.props);
      }
    }

    componentWillReceiveProps(nextProps) {
      if (nextProps.active && nextProps.timeout) {
        this._scheduleTimeout(nextProps);
      }
    }

    componentWillUnmount() {
      clearTimeout(this.currentTimeout);
    }

    _scheduleTimeout = props => {
      const { onTimeout, timeout } = props;

      if (this.currentTimeout) {
        clearTimeout(this.currentTimeout);
      }

      this.currentTimeout = setTimeout(() => {
        if (onTimeout) {
          onTimeout();
        }

        this.currentTimeout = null;
      }, timeout);
    };

    render() {
      const { action, active, children, label, onClick, type } = this.props;

      const className = cx(
        [theme.toast, theme[type]],
        {
          [theme.active]: active,
        },
        this.props.className,
      );

      return (
        <Portal className={theme.portal}>

              {label}
              {children}

            {onClick ? (
              action ? (
                <Button className={theme.button} label={action} level="outline" onClick={onClick} size="small" />
              ) : (
                <IconButton className={theme.iconButton} icon={<IconCloseMediumOutline />} inverse onClick={onClick} />
              )
            ) : null}

        </Portal>
      );
    }
  }

  return ActivableRenderer()(Toast);
};

const Toast = factory(Button, IconButton);

export default Toast;
export { factory as toastFactory, Toast };
<commit_msg>Deleted unused classname, there was no styling present for .portal in theme.css<commit_after>import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import ActivableRenderer from '../hoc/ActivableRenderer';
import { Button, IconButton } from '../button';
import Portal from '../hoc/Portal';
import { IconCloseMediumOutline } from '@teamleader/ui-icons';
import theme from './theme.css';

const factory = (Button, IconButton) => {
  class Toast extends PureComponent {
    static propTypes = {
      action: PropTypes.string,
      active: PropTypes.bool,
      children: PropTypes.node,
      className: PropTypes.string,
      label: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
      onClick: PropTypes.func,
      onTimeout: PropTypes.func,
      timeout: PropTypes.number,
      type: PropTypes.oneOf(['accept', 'cancel', 'warning']),
    };

    componentDidMount() {
      if (this.props.active && this.props.timeout) {
        this._scheduleTimeout(this.props);
      }
    }

    componentWillReceiveProps(nextProps) {
      if (nextProps.active && nextProps.timeout) {
        this._scheduleTimeout(nextProps);
      }
    }

    componentWillUnmount() {
      clearTimeout(this.currentTimeout);
    }

    _scheduleTimeout = props => {
      const { onTimeout, timeout } = props;

      if (this.currentTimeout) {
        clearTimeout(this.currentTimeout);
      }

      this.currentTimeout = setTimeout(() => {
        if (onTimeout) {
          onTimeout();
        }

        this.currentTimeout = null;
      }, timeout);
    };

    render() {
      const { action, active, children, label, onClick, type } = this.props;

      const className = cx(
        [theme.toast, theme[type]],
        {
          [theme.active]: active,
        },
        this.props.className,
      );

      return (
        <Portal>

              {label}
              {children}

            {onClick ? (
              action ? (
                <Button className={theme.button} label={action} level="outline" onClick={onClick} size="small" />
              ) : (
                <IconButton className={theme.iconButton} icon={<IconCloseMediumOutline />} inverse onClick={onClick} />
              )
            ) : null}

        </Portal>
      );
    }
  }

  return ActivableRenderer()(Toast);
};

const Toast = factory(Button, IconButton);

export default Toast;
export { factory as toastFactory, Toast };
<|endoftext|>


<commit_before>import { MDCRippleFoundation } from '@material/ripple'
import { supportsCssVariables, getMatchesProperty } from '@material/ripple/util'

/* global HTMLElement */
const MATCHES = getMatchesProperty(HTMLElement.prototype)

export class RippleBase extends MDCRippleFoundation {
  constructor (vm, options) {
    super(Object.assign({
      browserSupportsCssVars: () => {
        return supportsCssVariables(window)
      },
      registerResizeHandler: (handler) => {
        return window.addEventListener('resize', handler)
      },
      deregisterResizeHandler: (handler) => {
        return window.removeEventListener('resize', handler)
      },
      getWindowPageOffset: () => {
        return ({x: window.pageXOffset, y: window.pageYOffset})
      },
      isUnbounded: () => {
        return false
      },
      computeBoundingRect: () => {
        return vm.$el.getBoundingClientRect()
      },
      isSurfaceActive: () => {
        return vm.$el[MATCHES](':active')
      },
      isSurfaceDisabled: () => {
        return vm.disabled
      },
      addClass (className) {
        vm.$set(vm.classes, className, true)
      },
      // assumes a data 'classes' property on the root element
      removeClass (className) {
        vm.$delete(vm.classes, className)
      },
      registerInteractionHandler: (evt, handler) => {
        vm.$el.addEventListener(evt, handler)
      },
      deregisterInteractionHandler: (evt, handler) => {
        vm.$el.removeEventListener(evt, handler)
      },
      updateCssVariable: (varName, value) => {
        vm.$set(vm.styles, varName, value)
      }
    }, options))
  }

  static isSurfaceActive (ref) {
    return ref[MATCHES](':active')
  }
}
<commit_msg>fix(ssr): avoid fetching matches property on server (#77)<commit_after>import { MDCRippleFoundation } from '@material/ripple'
import { supportsCssVariables, getMatchesProperty } from '@material/ripple/util'

/* global HTMLElement */
const MATCHES = HTMLElement && getMatchesProperty(HTMLElement.prototype)

export class RippleBase extends MDCRippleFoundation {
  constructor (vm, options) {

    super(Object.assign({
      browserSupportsCssVars: () => {
        return supportsCssVariables(window)
      },
      registerResizeHandler: (handler) => {
        return window.addEventListener('resize', handler)
      },
      deregisterResizeHandler: (handler) => {
        return window.removeEventListener('resize', handler)
      },
      getWindowPageOffset: () => {
        return ({x: window.pageXOffset, y: window.pageYOffset})
      },
      isUnbounded: () => {
        return false
      },
      computeBoundingRect: () => {
        return vm.$el.getBoundingClientRect()
      },
      isSurfaceActive: () => {
        return vm.$el[MATCHES](':active')
      },
      isSurfaceDisabled: () => {
        return vm.disabled
      },
      addClass (className) {
        vm.$set(vm.classes, className, true)
      },
      // assumes a data 'classes' property on the root element
      removeClass (className) {
        vm.$delete(vm.classes, className)
      },
      registerInteractionHandler: (evt, handler) => {
        vm.$el.addEventListener(evt, handler)
      },
      deregisterInteractionHandler: (evt, handler) => {
        vm.$el.removeEventListener(evt, handler)
      },
      updateCssVariable: (varName, value) => {
        vm.$set(vm.styles, varName, value)
      }
    }, options))
  }

  static isSurfaceActive (ref) {
    return ref[MATCHES](':active')
  }
}
<|endoftext|>