<commit_before>// @flow

import { SC_ATTR, SC_ATTR_ACTIVE, SC_VERSION_ATTR, SC_VERSION } from '../constants';
import { getIdForGroup, setGroupForId } from './GroupIDAllocator';
import { getSheet } from './dom';
import type { Sheet } from './Sheet';

const SELECTOR = `style[${SC_ATTR}][${SC_VERSION_ATTR}="${SC_VERSION}"]`;
const MARKER_RE = new RegExp(`^${SC_ATTR}\\.g(\\d+)\\[id="([\\w\\d-]+)"\\]`, 'g');

export const outputSheet = (sheet: Sheet) => {
  const tag = sheet.getTag();
  const {length} = tag;

  let css = '';
  for (let group = 0; group < length; group++) {
    const id = getIdForGroup(group);
    if (id === undefined) continue;

    const names = sheet.names.get(id);
    if (names === undefined) continue;

    const rules = tag.getGroup(group);
    const selector = `${SC_ATTR}.g${group}[id="${id}"]`;

    let content = '';
    if (names !== undefined) {
      names.forEach(name => {
        if (name.length > 0) {
          content += `${name  },`;
        }
      });
    }

    // NOTE: It's easier to collect rules and have the marker
    // after the actual rules to simplify the rehydration
    css += rules + selector;
    if (content.length > 0) {
      css += `{content:"${content}"}\n`;
    } else {
      css += '{}\n';
    }
  }

  return css;
};

export const rehydrateSheet = (sheet: Sheet) => {
  const nodes = document.querySelectorAll(SELECTOR);

  for (let i = 0, l = nodes.length; i < l; i++) {
    const node = ((nodes[i]: any): HTMLStyleElement);
    if (node && node.getAttribute(SC_ATTR) !== SC_ATTR_ACTIVE) {
      rehydrateSheetFromTag(sheet, node);

      if (node.parentNode) {
        node.parentNode.removeChild(node);
      }
    }
  }
};

const rehydrateNamesFromContent = (sheet: Sheet, id: string, content: string) => {
  const names = content.slice(1, -1).split(',');
  for (let i = 0, l = names.length; i < l; i++) {
    const name = names[i];
    if (name.length > 0) {
      sheet.registerName(id, name);
    }
  }
};

const rehydrateSheetFromTag = (sheet: Sheet, style: HTMLStyleElement) => {
  const { cssRules } = getSheet(style);
  const rules: string[] = [];

  for (let i = 0, l = cssRules.length; i < l; i++) {
    const cssRule = (cssRules[i]: any);
    const marker = MARKER_RE.exec(cssRule.selectorText);

    if (marker !== null) {
      const group = parseInt(marker[1], 10) | 0;
      const id = marker[2];
      const { content } = cssRule.style;

      if (group !== 0) {
        // Rehydrate componentId to group index mapping
        setGroupForId(id, group);
        // Rehydrate names and rules
        rehydrateNamesFromContent(sheet, id, content);
        sheet.getTag().insertRules(group, rules);
      }

      rules.length = 0;
    } else {
      rules.push(cssRule.cssText);
    }
  }
};
<commit_msg>Fix regex matching in rehydration<commit_after>// @flow

import { SC_ATTR, SC_ATTR_ACTIVE, SC_VERSION_ATTR, SC_VERSION } from '../constants';
import { getIdForGroup, setGroupForId } from './GroupIDAllocator';
import { getSheet } from './dom';
import type { Sheet } from './Sheet';

const PLAIN_RULE_TYPE = 1;
const SELECTOR = `style[${SC_ATTR}][${SC_VERSION_ATTR}="${SC_VERSION}"]`;
const MARKER_RE = new RegExp(`^${SC_ATTR}\\.g(\\d+)\\[id="([\\w\\d-]+)"\\]`);

export const outputSheet = (sheet: Sheet) => {
  const tag = sheet.getTag();
  const {length} = tag;

  let css = '';
  for (let group = 0; group < length; group++) {
    const id = getIdForGroup(group);
    if (id === undefined) continue;

    const names = sheet.names.get(id);
    if (names === undefined) continue;

    const rules = tag.getGroup(group);
    const selector = `${SC_ATTR}.g${group}[id="${id}"]`;

    let content = '';
    if (names !== undefined) {
      names.forEach(name => {
        if (name.length > 0) {
          content += `${name  },`;
        }
      });
    }

    // NOTE: It's easier to collect rules and have the marker
    // after the actual rules to simplify the rehydration
    css += rules + selector;
    if (content.length > 0) {
      css += `{content:"${content}"}\n`;
    } else {
      css += '{}\n';
    }
  }

  return css;
};

export const rehydrateSheet = (sheet: Sheet) => {
  const nodes = document.querySelectorAll(SELECTOR);

  for (let i = 0, l = nodes.length; i < l; i++) {
    const node = ((nodes[i]: any): HTMLStyleElement);
    if (node && node.getAttribute(SC_ATTR) !== SC_ATTR_ACTIVE) {
      rehydrateSheetFromTag(sheet, node);

      if (node.parentNode) {
        node.parentNode.removeChild(node);
      }
    }
  }
};

const rehydrateNamesFromContent = (sheet: Sheet, id: string, content: string) => {
  const names = content.slice(1, -1).split(',');
  for (let i = 0, l = names.length; i < l; i++) {
    const name = names[i];
    if (name.length > 0) {
      sheet.registerName(id, name);
    }
  }
};

const rehydrateSheetFromTag = (sheet: Sheet, style: HTMLStyleElement) => {
  const { cssRules } = getSheet(style);
  const rules: string[] = [];

  for (let i = 0, l = cssRules.length; i < l; i++) {
    const cssRule = (cssRules[i]: any);

    if (cssRule.type !== PLAIN_RULE_TYPE) {
      rules.push(cssRule.cssText);
    } else {
      const marker = cssRule.selectorText.match(MARKER_RE);

      if (marker !== null) {
        const group = parseInt(marker[1], 10) | 0;
        const id = marker[2];
        const { content } = cssRule.style;

        if (group !== 0) {
          // Rehydrate componentId to group index mapping
          setGroupForId(id, group);
          // Rehydrate names and rules
          rehydrateNamesFromContent(sheet, id, content);
          sheet.getTag().insertRules(group, rules);
        }

        rules.length = 0;
      } else {
        rules.push(cssRule.cssText);
      }
    }
  }
};
<|endoftext|>


<commit_before>/* -------------------------------------------------------------------------- */
/* Copyright 2002-2020, OpenNebula Project, OpenNebula Systems                */
/*                                                                            */
/* Licensed under the Apache License, Version 2.0 (the "License"); you may    */
/* not use this file except in compliance with the License. You may obtain    */
/* a copy of the License at                                                   */
/*                                                                            */
/* http://www.apache.org/licenses/LICENSE-2.0                                 */
/*                                                                            */
/* Unless required by applicable law or agreed to in writing, software        */
/* distributed under the License is distributed on an "AS IS" BASIS,          */
/* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   */
/* See the License for the specific language governing permissions and        */
/* limitations under the License.                                             */
/* -------------------------------------------------------------------------- */

define(function(require) {
  /*
    DEPENDENCIES
   */

  var OpenNebulaVM = require('opennebula/vm');
  var StateActions = require('../utils/state-actions');

  /*
    CONSTANTS
   */

  var TAB_ID = require('../tabId');
  var RESOURCE = "VM";
  var XML_ROOT = "VM";

  /*
    FUNCTION DEFINITIONS
   */

  function _pre(info, contextTabId) {
    var element = info[XML_ROOT];

    // Enable only action buttons for the current state
    StateActions.disableAllStateActions();
    StateActions.enableStateActions(element.STATE, element.LCM_STATE);

    var isVNCSupported = OpenNebulaVM.isVNCSupported(element),
      isSPICESupported = OpenNebulaVM.isSPICESupported(element),
      isWFileSupported = OpenNebulaVM.isWFileSupported(element),
      isRDPSupported = OpenNebulaVM.isRDPSupported(element);

    if (isVNCSupported) {
      $(".vnc-sunstone-info").show();
      $(".spice-sunstone-info").hide();
    }
    else if (isSPICESupported) {
      $(".spice-sunstone-info").show();
      $(".vnc-sunstone-info").hide();
    }
    else {
      $(".spice-sunstone-info").hide();
      $(".vnc-sunstone-info").hide();
    }

    // Enable / disable virt-viewer button
    isWFileSupported ? $(".vv-sunstone-info").show() : $(".vv-sunstone-info").hide();

    // Enable / disable rdp button
    isRDPSupported ? $(".rdp-sunstone-info").show() : $(".rdp-sunstone-info").hide();

    // All remote buttons are disabled
    if (!isVNCSupported && !isSPICESupported && !isWFileSupported && !isRDPSupported) {
      $("#vmsremote_buttons").hide()
    }

    if(config &&
      config["system_config"] &&
      config["system_config"]["allow_vnc_federation"] &&
      config["system_config"]["allow_vnc_federation"] === 'no' &&
      config["id_own_federation"] &&
      config["zone_id"] &&
      config["id_own_federation"] !== config["zone_id"])
    {
      $(".vnc-sunstone-info").hide();
    }
  }

  function _post(info, contextTabId) {
  }

  return {
    'pre': _pre,
    'post': _post
  };
});<commit_msg>B #4948: Fix remote buttons after state switches (#39)<commit_after>/* -------------------------------------------------------------------------- */
/* Copyright 2002-2020, OpenNebula Project, OpenNebula Systems                */
/*                                                                            */
/* Licensed under the Apache License, Version 2.0 (the "License"); you may    */
/* not use this file except in compliance with the License. You may obtain    */
/* a copy of the License at                                                   */
/*                                                                            */
/* http://www.apache.org/licenses/LICENSE-2.0                                 */
/*                                                                            */
/* Unless required by applicable law or agreed to in writing, software        */
/* distributed under the License is distributed on an "AS IS" BASIS,          */
/* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   */
/* See the License for the specific language governing permissions and        */
/* limitations under the License.                                             */
/* -------------------------------------------------------------------------- */

define(function(require) {
  /*
    DEPENDENCIES
   */

  var OpenNebulaVM = require('opennebula/vm');
  var StateActions = require('../utils/state-actions');

  /*
    CONSTANTS
   */

  var TAB_ID = require('../tabId');
  var RESOURCE = "VM";
  var XML_ROOT = "VM";

  /*
    FUNCTION DEFINITIONS
   */

  function _pre(info, contextTabId) {
    var element = info[XML_ROOT];

    // Enable only action buttons for the current state
    StateActions.disableAllStateActions();
    StateActions.enableStateActions(element.STATE, element.LCM_STATE);

    var isVNCSupported = OpenNebulaVM.isVNCSupported(element),
      isSPICESupported = OpenNebulaVM.isSPICESupported(element),
      isWFileSupported = OpenNebulaVM.isWFileSupported(element),
      isRDPSupported = OpenNebulaVM.isRDPSupported(element);

    // All remote buttons are disabled
    var allDisabled = (!isVNCSupported && !isSPICESupported && !isWFileSupported && !isRDPSupported);
    $("#vmsremote_buttons").toggle(!allDisabled);

    if (isVNCSupported) {
      $(".vnc-sunstone-info").show();
      $(".spice-sunstone-info").hide();
    }
    else if (isSPICESupported) {
      $(".spice-sunstone-info").show();
      $(".vnc-sunstone-info").hide();
    }
    else {
      $(".spice-sunstone-info").hide();
      $(".vnc-sunstone-info").hide();
    }

    // Show / hide virt-viewer button
    $(".vv-sunstone-info").toggle(isWFileSupported);

    // Show / hide rdp button
    $(".rdp-sunstone-info").toggle(isRDPSupported);

    if(config &&
      config["system_config"] &&
      config["system_config"]["allow_vnc_federation"] &&
      config["system_config"]["allow_vnc_federation"] === 'no' &&
      config["id_own_federation"] &&
      config["zone_id"] &&
      config["id_own_federation"] !== config["zone_id"])
    {
      $(".vnc-sunstone-info").hide();
    }
  }

  function _post(info, contextTabId) {
  }

  return {
    'pre': _pre,
    'post': _post
  };
});<|endoftext|>


<commit_before>/*
 * Copyright (c) 2016, Globo.com (https://github.com/globocom)
 *
 * License: MIT
 */

import React from "react";
import ReactDOM from "react-dom";

import Overview from "../../docs/overview.md";
import ReactMarkdown from "react-markdown";

const DOCS = {
  overview: Overview
};

export default class Docs extends React.Component {

  componentDidMount() {
    this.highlightCode();
  }

  componentDidUpdate() {
    this.highlightCode();
  }

  highlightCode () {
    const domNode = ReactDOM.findDOMNode(this);
    const nodes = domNode.querySelectorAll('pre code');
    if (nodes.length > 0) {
      for (let i = 0; i < nodes.length; i=i+1) {
        hljs.highlightBlock(nodes[i]);
      }
    }
  }

  render() {
    const {doc} = this.props.params;
    return (

        <ReactMarkdown source={DOCS[doc]} />

    );
  }
}
<commit_msg>Fix lint<commit_after>/*
 * Copyright (c) 2016, Globo.com (https://github.com/globocom)
 *
 * License: MIT
 */

import React from "react";
import ReactDOM from "react-dom";

import Overview from "../../docs/overview.md";
import ReactMarkdown from "react-markdown";

const DOCS = {
  overview: Overview
};

export default class Docs extends React.Component {

  componentDidMount() {
    this.highlightCode();
  }

  componentDidUpdate() {
    this.highlightCode();
  }

  highlightCode () {
    const domNode = ReactDOM.findDOMNode(this);
    const nodes = domNode.querySelectorAll("pre code");
    if (nodes.length > 0) {
      for (let i = 0; i < nodes.length; i=i+1) {
        /* global hljs */
        hljs.highlightBlock(nodes[i]);
      }
    }
  }

  render() {
    const {doc} = this.props.params;
    return (

        <ReactMarkdown source={DOCS[doc]} />

    );
  }
}
<|endoftext|>


<commit_before>var finish_utc;
$(document).ready(function(){
	if ($('#countdown-finish-time').length != 0) {
	  var utc_iso8601 = $('#countdown-finish-time').html();
	  finish_utc = new Date(utc_iso8601);
	  update_countdown();
	}
});

function update_countdown() {
  var current_utc = new Date().getTime();
  var time_diff = finish_utc - current_utc;
  var seconds = Math.floor(time_diff / 1000);
  var minutes = Math.floor(seconds / 60);
  var hours = Math.floor(minutes / 60);
  var days = Math.floor(hours / 24);
  var content;

  hours %= 24;
  minutes %= 60;
  seconds %= 60;

  if (time_diff < 0) {
      $('.countdown').remove();
  }

  $('.countdown-days').text(days);
  $('.countdown-hours').text(hours);
  $('.countdown-minutes').text(minutes);
  $('.countdown-seconds').text(seconds);
  setTimeout('update_countdown()', 1000);
}
<commit_msg>Don't keep updating countdown when finished<commit_after>var finish_utc;
$(document).ready(function(){
	if ($('#countdown-finish-time').length != 0) {
	  var utc_iso8601 = $('#countdown-finish-time').html();
	  finish_utc = new Date(utc_iso8601);
	  update_countdown();
	}
});

function update_countdown() {
  var current_utc = new Date().getTime();
  var time_diff = finish_utc - current_utc;
  var seconds = Math.floor(time_diff / 1000);
  var minutes = Math.floor(seconds / 60);
  var hours = Math.floor(minutes / 60);
  var days = Math.floor(hours / 24);
  var content;

  hours %= 24;
  minutes %= 60;
  seconds %= 60;

  if (time_diff < 0) {
      $('.countdown').remove();
      return;
  }

  $('.countdown-days').text(days);
  $('.countdown-hours').text(hours);
  $('.countdown-minutes').text(minutes);
  $('.countdown-seconds').text(seconds);
  setTimeout('update_countdown()', 1000);
}
<|endoftext|>


<commit_before>/*global suite,test*/
require('source-map-support').install()

var fs = require("fs")
var path = require("path")

var _ = require("lodash")
var chai = require("chai")
var devUtils = require("../../tacoscript-dev-utils")
chai.use(devUtils.chaiHelper)
var expect = chai.expect
var render = require("tacoscript-cst-utils").render
var saveAst = devUtils.saveAst

var horchata = require("horchata")
var plugin = require("../lib/horchata/index")

// TODO: should load parser and lexer from ../lib/index
horchata.registerPluginModule("logicalAssign", require("../lib/horchata/parser"), require("../lib/horchata/lexer"));

// TODO: rewrite mocha-fixtures-generic to be more generic, w.r.t directory structure

var fixtureRootBase = path.join(__dirname, "fixtures")
var fixtureRootDirs = fs.readdirSync(fixtureRootBase)

suite("tacoscript-logical-assign", function () {
  _.forEach(fixtureRootDirs, function(fixtureRootDir) { suite(fixtureRootDir, function () {

    var fixtureDirs = fs.readdirSync(path.join(fixtureRootBase, fixtureRootDir))
    .filter(function(fixtureDir) { return fs.statSync(path.join(fixtureRootBase, fixtureRootDir, fixtureDir)).isDirectory() })

    // var optionsPath = path.join(fixtureRootBase, fixtureRootDir, "options.json")
    var options = {plugins: {"logicalAssign": true}};

    _.forEach(fixtureDirs, function(fixtureDir) { test(fixtureDir, function () {

      var fixtureBase = path.join(fixtureRootBase, fixtureRootDir, fixtureDir)
      var fixtureAstPath = path.join(fixtureBase, "ast.json")
      var fixtureAst; try { fixtureAst = require(fixtureAstPath) } catch(e) {}
      var expectedErr; try { expectedErr = require(path.join(fixtureBase, "error.json")) } catch(e) {}
      var source = fs.readFileSync(path.join(fixtureBase, "source.taco"), "utf-8")

      if (expectedErr) {
        expect(horchata.parse.bind(horchata, source, options)).to.throw(expectedErr.message)
      } else {
        var ast = horchata.parse(source, options)
        if (fixtureAst) {
          expect(ast).matches(fixtureAst)
          // expect(render(ast)).to.equal(source)
        } else {
          saveAst(fixtureAstPath, ast)
        }
      }

    }) /* end test */ }) /* end forEach fixtureDirs */
  }) /* end suite */ }) /* end forEach fixtureRootDirs */
})
<commit_msg>Fix broken require for logical-assign<commit_after>/*global suite,test*/
require('source-map-support').install()

var fs = require("fs")
var path = require("path")

var _ = require("lodash")
var chai = require("chai")
var devUtils = require("../../tacoscript-dev-utils")
chai.use(devUtils.chaiHelper)
var expect = chai.expect
var render = require("tacoscript-cst-utils").render
var saveAst = devUtils.saveAst

var horchata = require("horchata")

// TODO: should load parser and lexer from ../lib/index
horchata.registerPluginModule("logicalAssign", require("../lib/horchata/parser"), require("../lib/horchata/lexer"));

// TODO: rewrite mocha-fixtures-generic to be more generic, w.r.t directory structure

var fixtureRootBase = path.join(__dirname, "fixtures")
var fixtureRootDirs = fs.readdirSync(fixtureRootBase)

suite("tacoscript-logical-assign", function () {
  _.forEach(fixtureRootDirs, function(fixtureRootDir) { suite(fixtureRootDir, function () {

    var fixtureDirs = fs.readdirSync(path.join(fixtureRootBase, fixtureRootDir))
    .filter(function(fixtureDir) { return fs.statSync(path.join(fixtureRootBase, fixtureRootDir, fixtureDir)).isDirectory() })

    // var optionsPath = path.join(fixtureRootBase, fixtureRootDir, "options.json")
    var options = {plugins: {"logicalAssign": true}};

    _.forEach(fixtureDirs, function(fixtureDir) { test(fixtureDir, function () {

      var fixtureBase = path.join(fixtureRootBase, fixtureRootDir, fixtureDir)
      var fixtureAstPath = path.join(fixtureBase, "ast.json")
      var fixtureAst; try { fixtureAst = require(fixtureAstPath) } catch(e) {}
      var expectedErr; try { expectedErr = require(path.join(fixtureBase, "error.json")) } catch(e) {}
      var source = fs.readFileSync(path.join(fixtureBase, "source.taco"), "utf-8")

      if (expectedErr) {
        expect(horchata.parse.bind(horchata, source, options)).to.throw(expectedErr.message)
      } else {
        var ast = horchata.parse(source, options)
        if (fixtureAst) {
          expect(ast).matches(fixtureAst)
          // expect(render(ast)).to.equal(source)
        } else {
          saveAst(fixtureAstPath, ast)
        }
      }

    }) /* end test */ }) /* end forEach fixtureDirs */
  }) /* end suite */ }) /* end forEach fixtureRootDirs */
})
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// @flow
import * as React from 'react';
import { render } from '@testing-library/react';
import { AppLocalizationProvider } from 'firefox-profiler/components/app/AppLocalizationProvider';
import { blankStore } from '../fixtures/stores';
import { Provider } from 'react-redux';
import {
  getL10nFetchingPhase,
  getLocalization,
  getPrimaryLocale,
  getDirection,
} from 'firefox-profiler/selectors/l10n';
import { lazilyParsedBundles } from 'firefox-profiler/app-logic/l10n';
import { requestL10n, receiveL10n } from 'firefox-profiler/actions/l10n';
import { ReactLocalization, Localized } from '@fluent/react';
import { coerceMatchingShape } from '../../utils/flow';

describe('AppLocalizationProvider', () => {
  afterEach(function() {
    delete window.fetch;
  });

  function setup(language = 'en-US') {
    const store = blankStore();
    const { dispatch, getState } = store;

    const translatedText = `This is ${language} Text`;

    jest
      .spyOn(window.navigator, 'languages', 'get')
      .mockReturnValue([language]);

    const fetch = jest.fn().mockImplementation((fetchUrl: string) => {
      if (fetchUrl === `/locales/${language}/app.ftl`) {
        const response = (({
          ok: true,
          status: 200,
          headers: {
            get: () => 'text/plain',
          },
          text: () => Promise.resolve(`test-id = ${translatedText}`),
        }: any): Response);

        return Promise.resolve(response);
      }
      return Promise.reject(
        coerceMatchingShape<Response>({
          ok: false,
          status: 404,
          statusText: 'Not found',
        })
      );
    });
    (window: any).fetch = fetch;
    return { store, dispatch, getState, translatedText };
  }

  it('changes the state accordingly when the actions are dispatched', () => {
    const { dispatch, getState } = setup();
    expect(getL10nFetchingPhase(getState())).toBe('not-fetching');
    expect(getLocalization(getState())).toEqual(new ReactLocalization([]));
    dispatch(requestL10n());
    expect(getL10nFetchingPhase(getState())).toBe('fetching-ftl');
    const resource = [
      'locale',
      'data-testid = content-test-ApplocalizationProvider',
    ];
    const bundles = lazilyParsedBundles([resource]);
    const testLocalization = new ReactLocalization(bundles);
    dispatch(receiveL10n(testLocalization, 'en-US', 'ltr'));
    expect(getL10nFetchingPhase(getState())).toBe('done-fetching');
    expect(getLocalization(getState())).toEqual(testLocalization);
    expect(getPrimaryLocale(getState())).toEqual('en-US');
    expect(getDirection(getState())).toEqual('ltr');
  });

  it('renders its children', async () => {
    const { store } = setup();
    const { getByTestId } = render(
      <Provider store={store}>
        <AppLocalizationProvider>

        </AppLocalizationProvider>
      </Provider>
    );

    expect(
      getByTestId('content-test-AppLocalizationProvider')
    ).toBeInTheDocument();
  });

  it('fetches the en-US FTL strings and renders them', async () => {
    const { store, translatedText } = setup();
    const { findByText } = render(
      <Provider store={store}>
        <AppLocalizationProvider>
          <Localized id="test-id">
            Fallback String
          </Localized>
        </AppLocalizationProvider>
      </Provider>
    );

    expect(await findByText(translatedText)).toBeTruthy();
  });
});
<commit_msg>Add new tests for the direction and pseudo localization<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// @flow
import * as React from 'react';
import { render } from '@testing-library/react';
import { AppLocalizationProvider } from 'firefox-profiler/components/app/AppLocalizationProvider';
import { blankStore } from '../fixtures/stores';
import { Provider } from 'react-redux';
import {
  getL10nFetchingPhase,
  getLocalization,
  getPrimaryLocale,
  getDirection,
} from 'firefox-profiler/selectors/l10n';
import { lazilyParsedBundles } from 'firefox-profiler/app-logic/l10n';
import {
  requestL10n,
  receiveL10n,
  setupLocalization,
} from 'firefox-profiler/actions/l10n';
import { ReactLocalization, Localized } from '@fluent/react';
import { coerceMatchingShape } from '../../utils/flow';

describe('AppLocalizationProvider', () => {
  afterEach(function() {
    delete window.fetch;
  });

  function setup(language = 'en-US') {
    const store = blankStore();
    const { dispatch, getState } = store;

    const translatedText = `This is ${language} Text`;

    jest
      .spyOn(window.navigator, 'languages', 'get')
      .mockReturnValue([language]);

    const fetch = jest.fn().mockImplementation((fetchUrl: string) => {
      if (fetchUrl === `/locales/${language}/app.ftl`) {
        const response = (({
          ok: true,
          status: 200,
          headers: {
            get: () => 'text/plain',
          },
          text: () => Promise.resolve(`test-id = ${translatedText}`),
        }: any): Response);

        return Promise.resolve(response);
      }
      return Promise.reject(
        coerceMatchingShape<Response>({
          ok: false,
          status: 404,
          statusText: 'Not found',
        })
      );
    });
    (window: any).fetch = fetch;
    return { store, dispatch, getState, translatedText };
  }

  it('changes the state accordingly when the actions are dispatched', () => {
    const { dispatch, getState } = setup();
    expect(getL10nFetchingPhase(getState())).toBe('not-fetching');
    expect(getLocalization(getState())).toEqual(new ReactLocalization([]));
    dispatch(requestL10n());
    expect(getL10nFetchingPhase(getState())).toBe('fetching-ftl');
    const resource = [
      'locale',
      'data-testid = content-test-ApplocalizationProvider',
    ];
    const bundles = lazilyParsedBundles([resource]);
    const testLocalization = new ReactLocalization(bundles);
    dispatch(receiveL10n(testLocalization, 'en-US', 'ltr'));
    expect(getL10nFetchingPhase(getState())).toBe('done-fetching');
    expect(getLocalization(getState())).toEqual(testLocalization);
    expect(getPrimaryLocale(getState())).toEqual('en-US');
    expect(getDirection(getState())).toEqual('ltr');
  });

  it('renders its children', async () => {
    const { store } = setup();
    const { getByTestId } = render(
      <Provider store={store}>
        <AppLocalizationProvider>

        </AppLocalizationProvider>
      </Provider>
    );

    expect(
      getByTestId('content-test-AppLocalizationProvider')
    ).toBeInTheDocument();
  });

  it('fetches the en-US FTL strings and renders them', async () => {
    const { store, dispatch, translatedText } = setup();
    const { findByText } = render(
      <Provider store={store}>
        <AppLocalizationProvider>
          <Localized id="test-id">
            Fallback String
          </Localized>
        </AppLocalizationProvider>
      </Provider>
    );

    expect(await findByText(translatedText)).toBeTruthy();
    expect(document.documentElement).toHaveAttribute('lang', 'en-US');
    // $FlowExpectError Our version of flow doesn't know about document.dir
    expect(document.dir).toBe('ltr');

    // Now we're testing the LTR pseudo-localization.
    dispatch(setupLocalization(navigator.languages, 'accented'));
    expect(await findByText('Ŧħīş īş ḗḗƞ-ŬŞ Ŧḗḗẋŧ')).toBeInTheDocument();
    expect(document.documentElement).toHaveAttribute('lang', 'en-US');
    // $FlowExpectError Our version of flow doesn't know about document.dir
    expect(document.dir).toBe('ltr');

    // And now the RTL pseudo-localization.
    dispatch(setupLocalization(navigator.languages, 'bidi'));
    expect(await findByText(/⊥ɥıs ıs ǝu-∩S ⊥ǝxʇ/)).toBeInTheDocument();
    expect(document.documentElement).toHaveAttribute('lang', 'en-US');
    // $FlowExpectError Our version of flow doesn't know about document.dir
    expect(document.dir).toBe('rtl');
  });
});
<|endoftext|>


<commit_before>import globalThemeState from './globalThemeState';

export default function trackMediaType() {
  const listener = isPrint => {
    globalThemeState.mediaType = isPrint ? 'print' : null;
  };

  try {
    // All supported browsers should have `matchMedia`
    const mediaQueryList = window.matchMedia('print');

    // Listen for print
    mediaQueryList.addListener(e => listener(e.matches));
    listener(mediaQueryList.matches);
  } catch (e) {
    // Fallback
    window.addEventListener('beforeprint', () => listener(true));
    window.addEventListener('afterprint', () => listener(false));
  }
}
<commit_msg>Always add before/after print event handlers<commit_after>import globalThemeState from './globalThemeState';

export default function trackMediaType() {
  const listener = isPrint => {
    globalThemeState.mediaType = isPrint ? 'print' : null;
  };

  if (window.matchMedia) {
    // All supported browsers should have `matchMedia`
    const mediaQueryList = window.matchMedia('print');

    // Listen for print
    mediaQueryList.addListener(e => listener(e.matches));
    listener(mediaQueryList.matches);
  }

  // Fallback
  window.addEventListener('beforeprint', () => listener(true));
  window.addEventListener('afterprint', () => listener(false));
}
<|endoftext|>


<commit_before>'use babel'

export default [
  {
    name: 'color',
    description: 'Sets the text color',
    auto: '#333333',
    strength: 100
  },
  {
    name: 'margin',
    description: 'Spacing around an element',
    auto: 10,
    strength: 100
  },
  {
    name: 'maxHeight',
    description: 'Maximum height of element',
    auto: 100,
    strength: 100
  },
  {
    name: 'maxWidth',
    description: 'Maximum width of element',
    auto: 100,
    strength: 100
  },
  {
    name: 'minHeight',
    description: 'Minimum height of element',
    auto: 100,
    strength: 100
  },
  {
    name: 'minWidth',
    description: 'Minimum width of element',
    auto: 100,
    strength: 100
  },
  {
    name: 'flexDirection',
    description: 'Row or Column, sets direction element flows in',
    options: ['row', 'col'],
    auto: 'row',
    strength: 80
  },
  {
    name: 'transition',
    description: 'Transition values for element',
    auto: 'all ease-in 300ms',
    strength: 100
  },
  {
    name: 'height',
    description: 'Height of the element',
    options: [100, '100%'],
    auto: 100,
    strength: 80
  },
  {
    name: 'width',
    description: 'Width of the element',
    options: [100, '100%'],
    auto: 100,
    strength: 80
  },
  {
    name: 'border',
    description: 'Border of the element',
    auto: '1px solid #333',
    strength: 100
  },
  {
    name: 'borderRadius',
    description: 'Border radius of the element',
    auto: 10,
    strength: 100
  },
  {
    name: 'background',
    description: 'Background of the element',
    auto: '#eee',
    strength: 100
  },
  {
    name: 'backgroundColor',
    description: 'Background color of the element',
    auto: '#fff',
    strength: 80
  },
  {
    name: 'textAlign',
    description: 'Alignment of the element',
    options: ['left', 'center', 'right'],
    auto: 'center',
    strength: 80
  },
  {
    name: 'float',
    description: 'Adds a float to an item',
    auto: 'left',
    options: ['left', 'right', 'none'],
    strength: 100
  },
  {
    name: 'font',
    description: 'Sets font of text',
    auto: '14px/1.4rem Helvetica, Arial, sans-serif',
    strength: 100
  },
  {
    name: 'fontFamily',
    description: 'Sets font family of text',
    auto: 'Helvetica, Arial, sans-serif',
    strength: 80
  },
  {
    name: 'fontWeight',
    description: 'Sets weight of text',
    auto: 400,
    strength: 80
  },
  {
    name: 'fontSize',
    description: 'Sets size of text',
    auto: 16,
    strength: 100
  },
  {
    name: 'position',
    description: 'Positioning strategy',
    options: ['relative', 'fixed', 'absolute'],
    auto: 'absolute',
    strength: 100
  },
  {
    name: 'top',
    description: 'Positioning from top',
    auto: 0,
    strength: 100
  },
  {
    name: 'left',
    description: 'Positioning from left',
    auto: 0,
    strength: 100
  },
  {
    name: 'bottom',
    description: 'Positioning from bottom',
    auto: 0,
    strength: 100
  },
  {
    name: 'right',
    description: 'Positioning from right',
    auto: 0,
    strength: 100
  },
  {
    name: 'padding',
    description: 'Spacing inside an element',
    auto: 10,
    strength: 100
  },
  {
    name: 'whiteSpace',
    description: 'How to treat white space of text',
    options: ['wrap', 'nowrap', 'break-word'],
    auto: 'nowrap',
    strength: 100
  },
  {
    name: 'zIndex',
    description: 'Layer index in document',
    auto: 1,
    strength: 100
  },
]
<commit_msg>add a few more styles<commit_after>'use babel'

export default [
  {
    name: 'color',
    description: 'Sets the text color',
    auto: '#333333',
    strength: 100
  },
  {
    name: 'margin',
    description: 'Spacing around an element',
    auto: 10,
    strength: 100
  },
  {
    name: 'maxHeight',
    description: 'Maximum height of element',
    auto: 100,
    strength: 100
  },
  {
    name: 'maxWidth',
    description: 'Maximum width of element',
    auto: 100,
    strength: 100
  },
  {
    name: 'minHeight',
    description: 'Minimum height of element',
    auto: 100,
    strength: 100
  },
  {
    name: 'minWidth',
    description: 'Minimum width of element',
    auto: 100,
    strength: 100
  },
  {
    name: 'flexDirection',
    description: 'Row or Column, sets direction element flows in',
    options: ['row', 'col'],
    auto: 'row',
    strength: 80
  },
  {
    name: 'transition',
    description: 'Transition values for element',
    auto: 'all ease-in 300ms',
    strength: 100
  },
  {
    name: 'height',
    description: 'Height of the element',
    options: [100, '100%'],
    auto: 100,
    strength: 80
  },
  {
    name: 'width',
    description: 'Width of the element',
    options: [100, '100%'],
    auto: 100,
    strength: 80
  },
  {
    name: 'border',
    description: 'Border of the element',
    auto: '1px solid #333',
    strength: 100
  },
  {
    name: 'borderRadius',
    description: 'Border radius of the element',
    auto: 10,
    strength: 100
  },
  {
    name: 'background',
    description: 'Background of the element',
    auto: '#eee',
    strength: 100
  },
  {
    name: 'backgroundColor',
    description: 'Background color of the element',
    auto: '#fff',
    strength: 80
  },
  {
    name: 'textAlign',
    description: 'Alignment of the element',
    options: ['left', 'center', 'right'],
    auto: 'center',
    strength: 80
  },
  {
    name: 'float',
    description: 'Adds a float to an item',
    auto: 'left',
    options: ['left', 'right', 'none'],
    strength: 100
  },
  {
    name: 'font',
    description: 'Sets font of text',
    auto: '14px/1.4rem Helvetica, Arial, sans-serif',
    strength: 100
  },
  {
    name: 'fontFamily',
    description: 'Sets font family of text',
    auto: 'Helvetica, Arial, sans-serif',
    strength: 80
  },
  {
    name: 'fontWeight',
    description: 'Sets weight of text',
    auto: 400,
    strength: 80
  },
  {
    name: 'fontSize',
    description: 'Sets size of text',
    auto: 16,
    strength: 100
  },
  {
    name: 'position',
    description: 'Positioning strategy',
    options: ['relative', 'fixed', 'absolute'],
    auto: 'absolute',
    strength: 100
  },
  {
    name: 'top',
    description: 'Positioning from top',
    auto: 0,
    strength: 100
  },
  {
    name: 'left',
    description: 'Positioning from left',
    auto: 0,
    strength: 100
  },
  {
    name: 'bottom',
    description: 'Positioning from bottom',
    auto: 0,
    strength: 100
  },
  {
    name: 'right',
    description: 'Positioning from right',
    auto: 0,
    strength: 100
  },
  {
    name: 'padding',
    description: 'Spacing inside an element',
    auto: 10,
    strength: 100
  },
  {
    name: 'whiteSpace',
    description: 'How to treat white space of text',
    options: ['wrap', 'nowrap', 'break-word'],
    auto: 'nowrap',
    strength: 100
  },
  {
    name: 'zIndex',
    description: 'Layer index in document',
    auto: 1,
    strength: 100
  },
  {
    name: 'flexGrow',
    description: 'Flexbox stretch weight',
    auto: 1,
    strength: 100
  },
  {
    name: 'flexShrink',
    description: 'Flexbox shrink weight',
    auto: 1,
    strength: 100
  },
  {
    name: 'cursor',
    description: 'Layer index in document',
    options: ['auto', 'default', 'none', 'pointer', 'text', 'crosshair', 'zoom-in', 'zoom-out'],
    auto: 'pointer',
    strength: 100
  },
  {
    name: 'outline',
    description: 'Outline on form and link elements',
    auto: 'none',
    strength: 100
  },
]
<|endoftext|>


<commit_before>import 'babel-polyfill'; // fixme

import React from 'react';
import ReactDOM from 'react-dom';
// import { Sagas } from 'react-redux-saga'; // fixme -- need to fork this and make compatible with react16
import { Provider } from 'react-redux';
import { object, arrayOf } from 'prop-types';

import { modules } from 'veritone-redux-common';
const { user: userModule, config: configModule } = modules;

import appConfig from '../../config.json';
import configureStore from '../redux/configureStore';

export default class VeritoneApp {
  _store = configureStore();
  _containerEl = null;
  _token = null;
  _OAuthURI = null;

  constructor(widgets, { OAuthURI } = {}) {
    this._widgets = widgets;
    this._OAuthURI = OAuthURI;

    this._store.dispatch(configModule.setConfig(appConfig));
  }

  // auth can follow two paths:
  // 1. internal apps, make /current-user api call with appInstance.login().
  //    this can happen automatically.
  // 2. oauth apps, popup window flow. must be initiated by user action (clicking a button).
  //    oauth apps must add an OAuthLoginButtonWidget and do not need to call login()
  login({ token } = {}) {
    // new VeritoneApp(...widgets)
    //   .login() // try to use an existing cookie
    // or
    //   .login({ token }); // use provided token in header

    // todo: handle promise result
    // make sure it rejects on bad auth
    if (token) {
      this._token = token;
      return this._store
        .dispatch(userModule.fetchUser({ token }))
        .then(this._handleLoginResponse);
    } else {
      return this._store
        .dispatch(userModule.fetchUser())
        .then(this._handleLoginResponse);
    }
  }

  _handleLoginResponse(action) {
    return action.error ? Promise.reject(action) : action;
  }

  mount() {
    const existingApp = document.getElementById('veritone-react-app');
    if (existingApp) {
      // todo: should this be an error?
      return console.warn(
        'The DOM element from a VeritoneApp instance already exists on this page. ' +
          'Destroy it before mounting a new one.'
      );
    }

    this._containerEl = document.createElement('div');
    this._containerEl.setAttribute('id', 'veritone-react-app');
    document.body.appendChild(this._containerEl);

    this._widgets.forEach(w => w.init());
    this._renderReactApp();

    return this;
  }

  destroy() {
    if (this._containerEl) {
      ReactDOM.unmountComponentAtNode(this._containerEl);
      try {
        document.body.removeChild(this._containerEl);
      } catch (e) {
        // ignore
      }
    }
  }

  _renderReactApp() {
    ReactDOM.render(
      <Provider store={this._store}>
        <VeritoneRootComponent store={this._store} widgets={this._widgets} />
      </Provider>,
      this._containerEl
    );
  }
}

// todo:
// @connect VeritoneRootComponent to provide auth info/dispatch auth/boot actions.
function VeritoneRootComponent({ widgets }) {
  return (

      {widgets.map(w =>
        ReactDOM.createPortal(<w.Component {...w.props} />, w.el)
      )}

  );
}

VeritoneRootComponent.propTypes = {
  /* eslint-disable react/forbid-prop-types */
  store: object.isRequired, // redux store
  widgets: arrayOf(object)
};
<commit_msg>remove unused param:<commit_after>import 'babel-polyfill'; // fixme

import React from 'react';
import ReactDOM from 'react-dom';
// import { Sagas } from 'react-redux-saga'; // fixme -- need to fork this and make compatible with react16
import { Provider } from 'react-redux';
import { object, arrayOf } from 'prop-types';

import { modules } from 'veritone-redux-common';
const { user: userModule, config: configModule } = modules;

import appConfig from '../../config.json';
import configureStore from '../redux/configureStore';

export default class VeritoneApp {
  _store = configureStore();
  _containerEl = null;
  _token = null;

  constructor(widgets) {
    this._widgets = widgets;

    this._store.dispatch(configModule.setConfig(appConfig));
  }

  // auth can follow two paths:
  // 1. internal apps, make /current-user api call with appInstance.login().
  //    this can happen automatically.
  // 2. oauth apps, popup window flow. must be initiated by user action (clicking a button).
  //    oauth apps must add an OAuthLoginButtonWidget and do not need to call login()
  login({ token } = {}) {
    // new VeritoneApp(...widgets)
    //   .login() // try to use an existing cookie
    // or
    //   .login({ token }); // use provided token in header

    // todo: handle promise result
    // make sure it rejects on bad auth
    if (token) {
      this._token = token;
      return this._store
        .dispatch(userModule.fetchUser({ token }))
        .then(this._handleLoginResponse);
    } else {
      return this._store
        .dispatch(userModule.fetchUser())
        .then(this._handleLoginResponse);
    }
  }

  _handleLoginResponse(action) {
    return action.error ? Promise.reject(action) : action;
  }

  mount() {
    const existingApp = document.getElementById('veritone-react-app');
    if (existingApp) {
      // todo: should this be an error?
      return console.warn(
        'The DOM element from a VeritoneApp instance already exists on this page. ' +
          'Destroy it before mounting a new one.'
      );
    }

    this._containerEl = document.createElement('div');
    this._containerEl.setAttribute('id', 'veritone-react-app');
    document.body.appendChild(this._containerEl);

    this._widgets.forEach(w => w.init());
    this._renderReactApp();

    return this;
  }

  destroy() {
    if (this._containerEl) {
      ReactDOM.unmountComponentAtNode(this._containerEl);
      try {
        document.body.removeChild(this._containerEl);
      } catch (e) {
        // ignore
      }
    }
  }

  _renderReactApp() {
    ReactDOM.render(
      <Provider store={this._store}>
        <VeritoneRootComponent store={this._store} widgets={this._widgets} />
      </Provider>,
      this._containerEl
    );
  }
}

// todo:
// @connect VeritoneRootComponent to provide auth info/dispatch auth/boot actions.
function VeritoneRootComponent({ widgets }) {
  return (

      {widgets.map(w =>
        ReactDOM.createPortal(<w.Component {...w.props} />, w.el)
      )}

  );
}

VeritoneRootComponent.propTypes = {
  /* eslint-disable react/forbid-prop-types */
  store: object.isRequired, // redux store
  widgets: arrayOf(object)
};
<|endoftext|>


<commit_before>wax = wax || {};

wax.interaction = function() {
    var gm = wax.gm(),
        interaction = {},
        _downLock = false,
        _clickTimeout = null,
        // Active feature
        // Down event
        _d,
        // Touch tolerance
        tol = 4,
        grid,
        attach,
        detach,
        parent,
        map,
        tileGrid;

    var defaultEvents = {
        mousemove: onMove,
        touchstart: onDown,
        mousedown: onDown
    };

    var touchEnds = {
        touchend: onUp,
        touchmove: onUp,
        touchcancel: touchCancel
    };

    var pointerEnds = {
        MSPointerUp: onUp,
        MSPointerMove: onUp,
        MSPointerCancel: touchCancel
    };

    // Abstract getTile method. Depends on a tilegrid with
    // grid[ [x, y, tile] ] structure.
    function getTile(e) {
        var g = grid();
        var regExp = new RegExp(gm.tileRegexp());
        for (var i = 0; i < g.length; i++) {
            if (e) {
                var isInside = ((g[i][0] <= e.y) &&
                     ((g[i][0] + 256) > e.y) &&
                      (g[i][1] <= e.x) &&
                     ((g[i][1] + 256) > e.x));
                if(isInside && regExp.exec(g[i][2].src)) {
                    return g[i][2];
                }
            }
        }
        return false;
    }

    // Clear the double-click timeout to prevent double-clicks from
    // triggering popups.
    function killTimeout() {
        if (_clickTimeout) {
            window.clearTimeout(_clickTimeout);
            _clickTimeout = null;
            return true;
        } else {
            return false;
        }
    }

    function onMove(e) {
        // If the user is actually dragging the map, exit early
        // to avoid performance hits.
        if (_downLock) return;

        var _e = (e.type != "MSPointerMove" ? e : e.originalEvent);
        var pos = wax.u.eventoffset(_e);

        interaction.screen_feature(pos, function(feature) {
            if (feature) {
                bean.fire(interaction, 'on', {
                    parent: parent(),
                    data: feature,
                    formatter: gm.formatter().format,
                    e: e
                });
            } else {
                bean.fire(interaction, 'off');
            }
        });
    }

    // A handler for 'down' events - which means `mousedown` and `touchstart`
    function onDown(e) {

        // Prevent interaction offset calculations happening while
        // the user is dragging the map.
        //
        // Store this event so that we can compare it to the
        // up event
        _downLock = true;
        var _e = (e.type != "MSPointerDown" ? e : e.originalEvent);
        _d = wax.u.eventoffset(_e);
        if (e.type === 'mousedown') {
            bean.add(document.body, 'click', onUp);
            // track mouse up to remove lockDown when the drags end
            bean.add(document.body, 'mouseup', dragEnd);

        // Only track single-touches. Double-touches will not affect this
        // control
        } else if (e.type === 'touchstart' && e.touches.length === 1) {
            // Don't make the user click close if they hit another tooltip
            bean.fire(interaction, 'off');
            // Touch moves invalidate touches
            bean.add(parent(), touchEnds);
        } else if (e.originalEvent.type === "MSPointerDown" && e.originalEvent.touches && e.originalEvent.touches.length === 1) {
          // Don't make the user click close if they hit another tooltip
            bean.fire(interaction, 'off');
            // Touch moves invalidate touches
            bean.add(parent(), pointerEnds);
        }

    }

    function dragEnd() {
        _downLock = false;
    }

    function touchCancel() {
        bean.remove(parent(), touchEnds);
        bean.remove(parent(), pointerEnds);
        _downLock = false;
    }

    function onUp(e) {
        var evt = {},
            _e = (e.type != "MSPointerMove" && e.type != "MSPointerUp" ? e : e.originalEvent),
            pos = wax.u.eventoffset(_e);
        _downLock = false;

        for (var key in _e) {
          evt[key] = _e[key];
        }

        // for (var key in e) {
        //   evt[key] = e[key];
        // }

        evt.changedTouches = [];

        bean.remove(document.body, 'mouseup', onUp);
        bean.remove(parent(), touchEnds);
        bean.remove(parent(), pointerEnds);

        if (e.type === 'touchend') {
            // If this was a touch and it survived, there's no need to avoid a double-tap
            // but also wax.u.eventoffset will have failed, since this touch
            // event doesn't have coordinates
            interaction.click(e, _d);
        } else if (evt.type === "MSPointerMove" || evt.type === "MSPointerUp") {
            interaction.click(evt, pos);
        } else if (Math.round(pos.y / tol) === Math.round(_d.y / tol) &&
            Math.round(pos.x / tol) === Math.round(_d.x / tol)) {
            // Contain the event data in a closure.
            // Ignore double-clicks by ignoring clicks within 300ms of
            // each other.
            if(!_clickTimeout) {
              _clickTimeout = window.setTimeout(function() {
                  _clickTimeout = null;
                  interaction.click(evt, pos);
              }, 150);
            } else {
              killTimeout();
            }
        }
        return onUp;
    }

    // Handle a click event. Takes a second
    interaction.click = function(e, pos) {
        interaction.screen_feature(pos, function(feature) {
            if (feature) bean.fire(interaction, 'on', {
                parent: parent(),
                data: feature,
                formatter: gm.formatter().format,
                e: e
            });
        });
    };

    interaction.screen_feature = function(pos, callback) {
        var tile = getTile(pos);
        if (!tile) callback(null);
        gm.getGrid(tile.src, function(err, g) {
            if (err || !g) return callback(null);
            var feature = g.tileFeature(pos.x, pos.y, tile);
            callback(feature);
        });
    };

    // set an attach function that should be
    // called when maps are set
    interaction.attach = function(x) {
        if (!arguments.length) return attach;
        attach = x;
        return interaction;
    };

    interaction.detach = function(x) {
        if (!arguments.length) return detach;
        detach = x;
        return interaction;
    };

    // Attach listeners to the map
    interaction.map = function(x) {
        if (!arguments.length) return map;
        map = x;
        if (attach) attach(map);
        bean.add(parent(), defaultEvents);
        bean.add(parent(), 'touchstart', onDown);
        bean.add(parent(), 'MSPointerDown', onDown);
        return interaction;
    };

    // set a grid getter for this control
    interaction.grid = function(x) {
        if (!arguments.length) return grid;
        grid = x;
        return interaction;
    };

    // detach this and its events from the map cleanly
    interaction.remove = function(x) {
        if (detach) detach(map);
        bean.remove(parent(), defaultEvents);
        bean.fire(interaction, 'remove');
        return interaction;
    };

    // get or set a tilejson chunk of json
    interaction.tilejson = function(x) {
        if (!arguments.length) return gm.tilejson();
        gm.tilejson(x);
        return interaction;
    };

    // return the formatter, which has an exposed .format
    // function
    interaction.formatter = function() {
        return gm.formatter();
    };

    // ev can be 'on', 'off', fn is the handler
    interaction.on = function(ev, fn) {
        bean.add(interaction, ev, fn);
        return interaction;
    };

    // ev can be 'on', 'off', fn is the handler
    interaction.off = function(ev, fn) {
        bean.remove(interaction, ev, fn);
        return interaction;
    };

    // Return or set the gridmanager implementation
    interaction.gridmanager = function(x) {
        if (!arguments.length) return gm;
        gm = x;
        return interaction;
    };

    // parent should be a function that returns
    // the parent element of the map
    interaction.parent  = function(x) {
        parent = x;
        return interaction;
    };

    return interaction;
};
<commit_msg>Fixed interactivity error when there are more than touch at the same time and try to get event of the offset<commit_after>wax = wax || {};

wax.interaction = function() {
    var gm = wax.gm(),
        interaction = {},
        _downLock = false,
        _clickTimeout = null,
        // Active feature
        // Down event
        _d,
        // Touch tolerance
        tol = 4,
        grid,
        attach,
        detach,
        parent,
        map,
        tileGrid;

    var defaultEvents = {
        mousemove: onMove,
        touchstart: onDown,
        mousedown: onDown
    };

    var touchEnds = {
        touchend: onUp,
        touchmove: onUp,
        touchcancel: touchCancel
    };

    var pointerEnds = {
        MSPointerUp: onUp,
        MSPointerMove: onUp,
        MSPointerCancel: touchCancel
    };

    // Abstract getTile method. Depends on a tilegrid with
    // grid[ [x, y, tile] ] structure.
    function getTile(e) {
        var g = grid();
        var regExp = new RegExp(gm.tileRegexp());
        for (var i = 0; i < g.length; i++) {
            if (e) {
                var isInside = ((g[i][0] <= e.y) &&
                     ((g[i][0] + 256) > e.y) &&
                      (g[i][1] <= e.x) &&
                     ((g[i][1] + 256) > e.x));
                if(isInside && regExp.exec(g[i][2].src)) {
                    return g[i][2];
                }
            }
        }
        return false;
    }

    // Clear the double-click timeout to prevent double-clicks from
    // triggering popups.
    function killTimeout() {
        if (_clickTimeout) {
            window.clearTimeout(_clickTimeout);
            _clickTimeout = null;
            return true;
        } else {
            return false;
        }
    }

    function onMove(e) {
        // If the user is actually dragging the map, exit early
        // to avoid performance hits.
        if (_downLock) return;

        var _e = (e.type != "MSPointerMove" ? e : e.originalEvent);
        var pos = wax.u.eventoffset(_e);

        interaction.screen_feature(pos, function(feature) {
            if (feature) {
                bean.fire(interaction, 'on', {
                    parent: parent(),
                    data: feature,
                    formatter: gm.formatter().format,
                    e: e
                });
            } else {
                bean.fire(interaction, 'off');
            }
        });
    }

    // A handler for 'down' events - which means `mousedown` and `touchstart`
    function onDown(e) {

        // Prevent interaction offset calculations happening while
        // the user is dragging the map.
        //
        // Store this event so that we can compare it to the
        // up event
        _downLock = true;
        var _e = (e.type != "MSPointerDown" ? e : e.originalEvent);
        _d = wax.u.eventoffset(_e);
        if (e.type === 'mousedown') {
            bean.add(document.body, 'click', onUp);
            // track mouse up to remove lockDown when the drags end
            bean.add(document.body, 'mouseup', dragEnd);

        // Only track single-touches. Double-touches will not affect this
        // control
        } else if (e.type === 'touchstart' && e.touches.length === 1) {
            // Don't make the user click close if they hit another tooltip
            bean.fire(interaction, 'off');
            // Touch moves invalidate touches
            bean.add(parent(), touchEnds);
        } else if (e.originalEvent.type === "MSPointerDown" && e.originalEvent.touches && e.originalEvent.touches.length === 1) {
          // Don't make the user click close if they hit another tooltip
            bean.fire(interaction, 'off');
            // Touch moves invalidate touches
            bean.add(parent(), pointerEnds);
        }

    }

    function dragEnd() {
        _downLock = false;
    }

    function touchCancel() {
        bean.remove(parent(), touchEnds);
        bean.remove(parent(), pointerEnds);
        _downLock = false;
    }

    function onUp(e) {
        var evt = {},
            _e = (e.type != "MSPointerMove" && e.type != "MSPointerUp" ? e : e.originalEvent),
            pos = wax.u.eventoffset(_e);
        _downLock = false;

        for (var key in _e) {
          evt[key] = _e[key];
        }

        // for (var key in e) {
        //   evt[key] = e[key];
        // }

        evt.changedTouches = [];

        bean.remove(document.body, 'mouseup', onUp);
        bean.remove(parent(), touchEnds);
        bean.remove(parent(), pointerEnds);

        if (e.type === 'touchend') {
            // If this was a touch and it survived, there's no need to avoid a double-tap
            // but also wax.u.eventoffset will have failed, since this touch
            // event doesn't have coordinates
            interaction.click(e, _d);
        } else if (pos) {
          // If pos is not defined means wax can't calculate event position,
          // So next cases aren't possible.

          if (evt.type === "MSPointerMove" || evt.type === "MSPointerUp") {
            interaction.click(evt, pos);
          } else if (Math.round(pos.y / tol) === Math.round(_d.y / tol) &&
            Math.round(pos.x / tol) === Math.round(_d.x / tol)) {
            // Contain the event data in a closure.
            // Ignore double-clicks by ignoring clicks within 300ms of
            // each other.
            if(!_clickTimeout) {
              _clickTimeout = window.setTimeout(function() {
                  _clickTimeout = null;
                  interaction.click(evt, pos);
              }, 150);
            } else {
              killTimeout();
            }
          }

        }

        return onUp;
    }

    // Handle a click event. Takes a second
    interaction.click = function(e, pos) {
        interaction.screen_feature(pos, function(feature) {
            if (feature) bean.fire(interaction, 'on', {
                parent: parent(),
                data: feature,
                formatter: gm.formatter().format,
                e: e
            });
        });
    };

    interaction.screen_feature = function(pos, callback) {
        var tile = getTile(pos);
        if (!tile) callback(null);
        gm.getGrid(tile.src, function(err, g) {
            if (err || !g) return callback(null);
            var feature = g.tileFeature(pos.x, pos.y, tile);
            callback(feature);
        });
    };

    // set an attach function that should be
    // called when maps are set
    interaction.attach = function(x) {
        if (!arguments.length) return attach;
        attach = x;
        return interaction;
    };

    interaction.detach = function(x) {
        if (!arguments.length) return detach;
        detach = x;
        return interaction;
    };

    // Attach listeners to the map
    interaction.map = function(x) {
        if (!arguments.length) return map;
        map = x;
        if (attach) attach(map);
        bean.add(parent(), defaultEvents);
        bean.add(parent(), 'touchstart', onDown);
        bean.add(parent(), 'MSPointerDown', onDown);
        return interaction;
    };

    // set a grid getter for this control
    interaction.grid = function(x) {
        if (!arguments.length) return grid;
        grid = x;
        return interaction;
    };

    // detach this and its events from the map cleanly
    interaction.remove = function(x) {
        if (detach) detach(map);
        bean.remove(parent(), defaultEvents);
        bean.fire(interaction, 'remove');
        return interaction;
    };

    // get or set a tilejson chunk of json
    interaction.tilejson = function(x) {
        if (!arguments.length) return gm.tilejson();
        gm.tilejson(x);
        return interaction;
    };

    // return the formatter, which has an exposed .format
    // function
    interaction.formatter = function() {
        return gm.formatter();
    };

    // ev can be 'on', 'off', fn is the handler
    interaction.on = function(ev, fn) {
        bean.add(interaction, ev, fn);
        return interaction;
    };

    // ev can be 'on', 'off', fn is the handler
    interaction.off = function(ev, fn) {
        bean.remove(interaction, ev, fn);
        return interaction;
    };

    // Return or set the gridmanager implementation
    interaction.gridmanager = function(x) {
        if (!arguments.length) return gm;
        gm = x;
        return interaction;
    };

    // parent should be a function that returns
    // the parent element of the map
    interaction.parent  = function(x) {
        parent = x;
        return interaction;
    };

    return interaction;
};
<|endoftext|>


<commit_before>'use strict';

const MenuItem = require('../../models/menu_item');

function getMenuData() {
  return [
    new MenuItem('About Us', null, [
      new MenuItem('Mission', '/mission'),
      new MenuItem('FAQ', '/faq'),
      new MenuItem('Contact', '/contact')
    ]),
    new MenuItem('Survey', 'http://www.transmentalhealthsurvey.org'),
    new MenuItem('Get Involved', null, [
      new MenuItem('Ways to Help', '/help'),
      new MenuItem('Volunteer', 'http://apply.translifeline.org'),
      new MenuItem('Host A Fundraiser', '/hostafundraiser'),
      new MenuItem('Host Resources', '/host-resources'),
    ]),
    new MenuItem('Resources', 'https://radremedy.org/'),
    new MenuItem('Donate', '/donate'),
    new MenuItem('Store', 'http://www.redbubble.com/people/translifeline/shop'),
    new MenuItem('Blog', 'https://medium.com/@Translifeline'),
    new MenuItem('Login', 'http://hotline.translifeline.org/login')
  ];
}

module.exports = getMenuData;
<commit_msg>Change volunteer link<commit_after>'use strict';

const MenuItem = require('../../models/menu_item');

function getMenuData() {
  return [
    new MenuItem('About Us', null, [
      new MenuItem('Mission', '/mission'),
      new MenuItem('FAQ', '/faq'),
      new MenuItem('Contact', '/contact')
    ]),
    new MenuItem('Survey', 'http://www.transmentalhealthsurvey.org'),
    new MenuItem('Get Involved', null, [
      new MenuItem('Ways to Help', '/help'),
      new MenuItem('Volunteer', 'https://jobs.lever.co/translifeline'),
      new MenuItem('Host A Fundraiser', '/hostafundraiser'),
      new MenuItem('Host Resources', '/host-resources'),
    ]),
    new MenuItem('Resources', 'https://radremedy.org/'),
    new MenuItem('Donate', '/donate'),
    new MenuItem('Store', 'http://www.redbubble.com/people/translifeline/shop'),
    new MenuItem('Blog', 'https://medium.com/@Translifeline'),
    new MenuItem('Login', 'http://hotline.translifeline.org/login')
  ];
}

module.exports = getMenuData;
<|endoftext|>


<commit_before>
var Class = require('../../utils/Class');
var GameObject = require('../GameObject');
var Sprite = require('../sprite/Sprite');
var NOOP = require('../../utils/NOOP');

//  A Pool

var Pool = new Class({

    Extends: GameObject,

    initialize:

    function Pool (state, children, config)
    {
        GameObject.call(this, state, 'Pool');

        this.live = [];
        this.pendingInsertion = [];
        this.pendingRemoval = [];
        this.dead = [];

        this.classType = Sprite;

        if (config)
        {
            this.createMultiple(config);
        }
    },

    add: function (child)
    {
        if (Array.isArray(child))
        {
            for (var i = 0; i < child.length; i++)
            {
                this.pendingInsertion.push(child[i]);
            }
        }
        else
        {
            this.pendingInsertion.push(child);
        }

        return this;
    },

    createMultiple: function ()
    {

    },

    preUpdate: function (time, delta)
    {
        //  Because a Group child may mess with the length of the Group during its update
        // var temp = this.children.entries.slice();

        // for (var i = 0; i < temp.length; i++)
        // {
        //     if (temp[i].update(time, delta) === false)
        //     {
        //         break;
        //     }
        // }
    },

    renderCanvas: NOOP,
    renderWebGL: NOOP,

    destroy: function ()
    {

    }

});

module.exports = Pool;
<commit_msg>Small pool updates<commit_after>
var Class = require('../../utils/Class');
var GameObject = require('../GameObject');
var Sprite = require('../sprite/Sprite');
var NOOP = require('../../utils/NOOP');

//  A Pool

var Pool = new Class({

    Extends: GameObject,

    initialize:

    function Pool (state, children, config)
    {
        GameObject.call(this, state, 'Pool');

        this.live = [];
        this.pendingInsertion = [];
        this.pendingRemoval = [];
        this.dead = [];

        this.classType = Sprite;

        if (config)
        {
            this.createMultiple(config);
        }
    },

    //  Allow them to add a Group too
    add: function (child)
    {
        if (Array.isArray(child))
        {
            for (var i = 0; i < child.length; i++)
            {
                this.pendingInsertion.push(child[i]);
            }
        }
        else
        {
            this.pendingInsertion.push(child);
        }

        return this;
    },

    createMultiple: function ()
    {
    },

    preUpdate: function (time, delta)
    {
        //  Because a Group child may mess with the length of the Group during its update
        // var temp = this.children.entries.slice();

        // for (var i = 0; i < temp.length; i++)
        // {
        //     if (temp[i].update(time, delta) === false)
        //     {
        //         break;
        //     }
        // }
    },

    renderCanvas: NOOP,
    renderWebGL: NOOP,

    destroy: function ()
    {

    }

});

module.exports = Pool;
<|endoftext|>


<commit_before>/**
 * Copyright 2015-2019 OpenCB
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import {LitElement, html} from "lit";
import UtilsNew from "../../core/utilsNew.js";
import LitUtils from "./utils/lit-utils.js";
import "./filters/catalog-search-autocomplete.js";
import "./filters/catalog-distinct-autocomplete.js";
import "./forms/date-filter.js";
import "./opencga-facet-view.js";
import "./forms/text-field-filter.js";
import "./filters/somatic-filter.js";
import "./forms/section-filter.js";
import "./forms/select-token-filter-static.js";
import "../opencga/catalog/variableSets/opencga-annotation-filter-modal.js";

export default class OpencgaBrowserFilter extends LitElement {

    constructor() {
        super();

        this._init();

    }
    createRenderRoot() {
        return this;
    }

    static get properties() {
        return {
            opencgaSession: {
                type: Object
            },
            cellbaseClient: {
                type: Object
            },
            resource: {
                type: String
            },
            query: {
                type: Object
            },
            variableSets: {
                type: Array
            },
            variables: {
                type: Array
            },
            config: {
                type: Object
            }
        };
    }

    _init() {
        this._prefix = UtilsNew.randomString(8);

        this.annotationFilterConfig = {
            class: "small",
            buttonClass: "btn-sm",
            inputClass: "input-sm"
        };

        this.query = {};
        this.preparedQuery = {};
        this.searchButton = true;

        // Make sure some filters point the right RESOURCE
        this.filterToResource = {
            "fileIds": "FILE",
            "samples": "SAMPLE",
            "sample": "SAMPLE",
            "sampleIds": "SAMPLE",
            "individualId": "INDIVIDUAL",
            "members": "INDIVIDUAL",
            "family": "FAMILY",
            "jobId": "JOB",
            "input": "FILE",
            "output": "FILE",
        };

        // Select the right distinct field to be displayed
        this.filterToDistinctField = {
            "phenotypes": "phenotypes.name",
            "disorders": "disorders.name",
            "ethnicity": "ethnicity.id",
            "proband": "proband.id",
            "tool": "tool.id",
            "genes": "genes.id",
            "categories": "categories.name",
            "source": "source.name",
            "tags": "tags"
        };
    }

    connectedCallback() {
        super.connectedCallback();

        this.preparedQuery = {...this.query}; // propagates here the iva-app query object
    }

    firstUpdated(changedProperties) {
        super.firstUpdated(changedProperties);

        UtilsNew.initTooltip(this);
    }

    update(changedProperties) {
        if (changedProperties.has("query")) {
            this.queryObserver();
        }
        // if (changedProperties.has("variables")) {
        //     this.variablesChanged()
        // }
        super.update(changedProperties);
    }

    // TODO review, this is used only in case of Search button inside filter component.
    onSearch() {
        this.notifySearch(this.preparedQuery);
    }

    queryObserver() {
        this.preparedQuery = this.query || {};
        this.requestUpdate();
    }

    onFilterChange(key, value) {
        if (value && value !== "") {
            this.preparedQuery = {...this.preparedQuery, ...{[key]: value}};
        } else {
            delete this.preparedQuery[key];
            this.preparedQuery = {...this.preparedQuery};
        }
        this.notifyQuery(this.preparedQuery);
        this.requestUpdate();
    }

    onAnnotationChange(e) {
        if (e.detail.value) {
            this.preparedQuery.annotation = e.detail.value;
        } else {
            delete this.preparedQuery.annotation;
        }
        this.preparedQuery = {...this.preparedQuery};
        this.notifyQuery(this.preparedQuery);
        this.requestUpdate();
    }

    notifyQuery(query) {
        LitUtils.dispatchCustomEvent(this, "queryChange", null, {query: query});
    }

    notifySearch(query) {
        LitUtils.dispatchCustomEvent(this, "querySearch", null, {query: query});
    }

    _createSection(section) {
        const htmlFields = section.filters?.length ? section.filters.map(subsection => this._createSubSection(subsection)) : "";
        return this.config.sections.length > 1 ? html`<section-filter .config="${section}" .filters="${htmlFields}">` : htmlFields;
    }

    _createSubSection(subsection) {
        // Prevent empty filters section when a subsection is undefined
        if (!subsection?.id) {
            console.error("Filter definition error", subsection);
            return "";
        }

        let content = "";

        if (subsection.render) {
            content = subsection.render(this.onFilterChange, this.preparedQuery, this.opencgaSession);
        } else {
            const id = subsection.id === "priority"? `${this.resource.toLowerCase()}_${subsection.id}`: subsection.id;
            switch (id) {
                case "id":
                case "name":
                case "fileIds":
                case "father":
                case "mother":
                case "samples":
                case "sample":
                case "sampleIds":
                case "individualId":
                case "members":
                case "family":
                case "input":
                case "jobId":
                case "output":
                    content = html`
                        <catalog-search-autocomplete
                            .value="${this.preparedQuery[subsection.id]}"
                            .resource="${this.filterToResource[subsection.id] || this.resource}"
                            .opencgaSession="${this.opencgaSession}"
                            .config="${subsection}"
                            @filterChange="${e => this.onFilterChange(subsection.id, e.detail.value)}">
                        </catalog-search-autocomplete>
                    `;
                    break;
                case "directory": // Temporal Solution
                    content = html`
                        <catalog-search-autocomplete
                            .value="${this.preparedQuery[subsection.id]}"
                            resource="DIRECTORY"
                            .opencgaSession="${this.opencgaSession}"
                            .config="${subsection}"
                            @filterChange="${e => this.onFilterChange(subsection.id, e.detail.value)}">
                        </catalog-search-autocomplete>
                    `;
                    break;
                case "phenotypes":
                case "disorders":
                case "ethnicity":
                case "proband":
                case "tool":
                case "categories":
                case "genes":
                case "tags":
                case "source":
                    content = html`
                        <catalog-distinct-autocomplete
                            .value="${this.preparedQuery[subsection.id]}"
                            .queryField="${subsection.id}"
                            .distinctField="${this.filterToDistinctField[subsection.id]}"
                            .resource="${this.resource}"
                            .opencgaSession="${this.opencgaSession}"
                            .config="${subsection}"
                            @filterChange="${e => this.onFilterChange(subsection.id, e.detail.value)}">
                        </catalog-distinct-autocomplete>
                    `;
                    break;
                case "sex":
                case "type": // cohort, clinical
                case "karyotypicSex":
                case "affectationStatus":
                case "lifeStatus":
                case "format":
                case "bioformat":
                case "internal.index.status.name":
                case "internalStatus":
                case "visited":
                case "job_priority":
                    content = html`
                        <select-field-filter
                            .value="${this.preparedQuery[subsection.id]}"
                            .data="${subsection.allowedValues}"
                            ?multiple="${subsection?.multiple}"
                            @filterChange="${e => this.onFilterChange(subsection.id, e.detail.value)}">
                        </select-field-filter>
                    `;
                    break;
                case "path":
                    content = html`
                        <text-field-filter
                            placeholder="${subsection.placeholder}"
                            .value="${this.preparedQuery[subsection.id]}"
                            @filterChange="${e => this.onFilterChange(subsection.id, e.detail.value)}">
                        </text-field-filter>
                    `;
                    break;
                case "annotations":
                    content = html`
                        <opencga-annotation-filter-modal
                            .opencgaSession="${this.opencgaSession}"
                            .opencgaClient="${this.opencgaSession.opencgaClient}"
                            .resource="${this.resource}"
                            .config="${this.annotationFilterConfig}"
                            .selectedVariablesText="${this.preparedQuery.annotation}"
                            @annotationChange="${this.onAnnotationChange}">
                        </opencga-annotation-filter-modal>
                    `;
                    break;
                case "somatic":
                    content = html`
                        <somatic-filter
                            .value="${this.preparedQuery.somatic}"
                            @filterChange="${e => this.onFilterChange("somatic", e.detail.value)}">
                        </somatic-filter>
                    `;
                    break;
                case "clinical_analysis_priority":
                    content = html`
                        <clinical-priority-filter
                            .priority="${this.preparedQuery[subsection.id]}"
                            .priorities="${Object.values(this.opencgaSession.study.internal?.configuration?.clinical?.priorities ?? [])}"
                            @filterChange="${e => this.onFilterChange(subsection.id, e.detail.value)}">
                        </clinical-priority-filter>
                    `;
                    break;
                case "status":
                    content = html`
                        <clinical-status-filter
                            .status="${this.preparedQuery[subsection.id]}"
                            .statuses="${Object.values(this.opencgaSession.study.internal?.configuration?.clinical?.status)?.flat()}"
                            .multiple="${true}"
                            @filterChange="${e => this.onFilterChange(subsection.id, e.detail.value)}">
                        </clinical-status-filter>
                    `;
                    break;
                case "region":
                    content = html`
                        <region-filter
                            .cellbaseClient="${this.cellbaseClient}"
                            .region="${this.preparedQuery.region}"
                            @filterChange="${e => this.onFilterChange("regions", e.detail.value)}">
                        </region-filter>
                    `;
                    break;
                case "date":
                case "creationDate":
                    content = html`
                        <date-filter
                            .filterDate="${this.preparedQuery.creationDate}"
                            @filterChange="${e => this.onFilterChange("creationDate", e.detail.value)}">
                        </date-filter>
                    `;
                    break;
                case "dueDate":
                    content = html`
                    <date-filter
                        .filterDate="${this.preparedQuery.dueDate}"
                        @filterChange="${e => this.onFilterChange("dueDate", e.detail.value)}">
                    </date-filter>
                    `;
                    break;
                default:
                    console.error("Filter component not found: ", id);
            }
        }

        if (content) {
            return html`

                        ${subsection.name}
                        ${subsection.description ? html`

                        ` : null}

                        ${content}

            `;
        } else {
            return "";
        }
    }

    render() {
        return html`
            ${this.config?.searchButton ? html`

                    <button type="button" class="btn btn-primary ripple" @click="${this.onSearch}">
                         Search
                    </button>

            ` : null}

                ${this.config?.sections?.length ? this.config.sections.map(section => this._createSection(section)) : html`No filter has been configured.`}

        `;
    }

}

customElements.define("opencga-browser-filter", OpencgaBrowserFilter);
<commit_msg>wc: add disorders.id to the list of distinct fields in opencga-browser-filters #TASK-2082<commit_after>/**
 * Copyright 2015-2019 OpenCB
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import {LitElement, html} from "lit";
import UtilsNew from "../../core/utilsNew.js";
import LitUtils from "./utils/lit-utils.js";
import "./filters/catalog-search-autocomplete.js";
import "./filters/catalog-distinct-autocomplete.js";
import "./forms/date-filter.js";
import "./opencga-facet-view.js";
import "./forms/text-field-filter.js";
import "./filters/somatic-filter.js";
import "./forms/section-filter.js";
import "./forms/select-token-filter-static.js";
import "../opencga/catalog/variableSets/opencga-annotation-filter-modal.js";

export default class OpencgaBrowserFilter extends LitElement {

    constructor() {
        super();

        this._init();

    }
    createRenderRoot() {
        return this;
    }

    static get properties() {
        return {
            opencgaSession: {
                type: Object
            },
            cellbaseClient: {
                type: Object
            },
            resource: {
                type: String
            },
            query: {
                type: Object
            },
            variableSets: {
                type: Array
            },
            variables: {
                type: Array
            },
            config: {
                type: Object
            }
        };
    }

    _init() {
        this._prefix = UtilsNew.randomString(8);

        this.annotationFilterConfig = {
            class: "small",
            buttonClass: "btn-sm",
            inputClass: "input-sm"
        };

        this.query = {};
        this.preparedQuery = {};
        this.searchButton = true;

        // Make sure some filters point the right RESOURCE
        this.filterToResource = {
            "fileIds": "FILE",
            "samples": "SAMPLE",
            "sample": "SAMPLE",
            "sampleIds": "SAMPLE",
            "individualId": "INDIVIDUAL",
            "members": "INDIVIDUAL",
            "family": "FAMILY",
            "jobId": "JOB",
            "input": "FILE",
            "output": "FILE",
        };

        // Select the right distinct field to be displayed
        this.filterToDistinctField = {
            "phenotypes": "phenotypes.name",
            "disorders": "disorders.id,disorders.name",
            "ethnicity": "ethnicity.id",
            "proband": "proband.id",
            "tool": "tool.id",
            "genes": "genes.id",
            "categories": "categories.name",
            "source": "source.name",
            "tags": "tags"
        };
    }

    connectedCallback() {
        super.connectedCallback();

        this.preparedQuery = {...this.query}; // propagates here the iva-app query object
    }

    firstUpdated(changedProperties) {
        super.firstUpdated(changedProperties);

        UtilsNew.initTooltip(this);
    }

    update(changedProperties) {
        if (changedProperties.has("query")) {
            this.queryObserver();
        }
        // if (changedProperties.has("variables")) {
        //     this.variablesChanged()
        // }
        super.update(changedProperties);
    }

    // TODO review, this is used only in case of Search button inside filter component.
    onSearch() {
        this.notifySearch(this.preparedQuery);
    }

    queryObserver() {
        this.preparedQuery = this.query || {};
        this.requestUpdate();
    }

    onFilterChange(key, value) {
        if (value && value !== "") {
            this.preparedQuery = {...this.preparedQuery, ...{[key]: value}};
        } else {
            delete this.preparedQuery[key];
            this.preparedQuery = {...this.preparedQuery};
        }
        this.notifyQuery(this.preparedQuery);
        this.requestUpdate();
    }

    onAnnotationChange(e) {
        if (e.detail.value) {
            this.preparedQuery.annotation = e.detail.value;
        } else {
            delete this.preparedQuery.annotation;
        }
        this.preparedQuery = {...this.preparedQuery};
        this.notifyQuery(this.preparedQuery);
        this.requestUpdate();
    }

    notifyQuery(query) {
        LitUtils.dispatchCustomEvent(this, "queryChange", null, {query: query});
    }

    notifySearch(query) {
        LitUtils.dispatchCustomEvent(this, "querySearch", null, {query: query});
    }

    _createSection(section) {
        const htmlFields = section.filters?.length ? section.filters.map(subsection => this._createSubSection(subsection)) : "";
        return this.config.sections.length > 1 ? html`<section-filter .config="${section}" .filters="${htmlFields}">` : htmlFields;
    }

    _createSubSection(subsection) {
        // Prevent empty filters section when a subsection is undefined
        if (!subsection?.id) {
            console.error("Filter definition error", subsection);
            return "";
        }

        let content = "";

        if (subsection.render) {
            content = subsection.render(this.onFilterChange, this.preparedQuery, this.opencgaSession);
        } else {
            const id = subsection.id === "priority"? `${this.resource.toLowerCase()}_${subsection.id}`: subsection.id;
            switch (id) {
                case "id":
                case "name":
                case "fileIds":
                case "father":
                case "mother":
                case "samples":
                case "sample":
                case "sampleIds":
                case "individualId":
                case "members":
                case "family":
                case "input":
                case "jobId":
                case "output":
                    content = html`
                        <catalog-search-autocomplete
                            .value="${this.preparedQuery[subsection.id]}"
                            .resource="${this.filterToResource[subsection.id] || this.resource}"
                            .opencgaSession="${this.opencgaSession}"
                            .config="${subsection}"
                            @filterChange="${e => this.onFilterChange(subsection.id, e.detail.value)}">
                        </catalog-search-autocomplete>
                    `;
                    break;
                case "directory": // Temporal Solution
                    content = html`
                        <catalog-search-autocomplete
                            .value="${this.preparedQuery[subsection.id]}"
                            resource="DIRECTORY"
                            .opencgaSession="${this.opencgaSession}"
                            .config="${subsection}"
                            @filterChange="${e => this.onFilterChange(subsection.id, e.detail.value)}">
                        </catalog-search-autocomplete>
                    `;
                    break;
                case "phenotypes":
                case "disorders":
                case "ethnicity":
                case "proband":
                case "tool":
                case "categories":
                case "genes":
                case "tags":
                case "source":
                    content = html`
                        <catalog-distinct-autocomplete
                            .value="${this.preparedQuery[subsection.id]}"
                            .queryField="${subsection.id}"
                            .distinctFields="${this.filterToDistinctField[subsection.id]}"
                            .resource="${this.resource}"
                            .opencgaSession="${this.opencgaSession}"
                            .config="${subsection}"
                            @filterChange="${e => this.onFilterChange(subsection.id, e.detail.value)}">
                        </catalog-distinct-autocomplete>
                    `;
                    break;
                case "sex":
                case "type": // cohort, clinical
                case "karyotypicSex":
                case "affectationStatus":
                case "lifeStatus":
                case "format":
                case "bioformat":
                case "internal.index.status.name":
                case "internalStatus":
                case "visited":
                case "job_priority":
                    content = html`
                        <select-field-filter
                            .value="${this.preparedQuery[subsection.id]}"
                            .data="${subsection.allowedValues}"
                            ?multiple="${subsection?.multiple}"
                            @filterChange="${e => this.onFilterChange(subsection.id, e.detail.value)}">
                        </select-field-filter>
                    `;
                    break;
                case "path":
                    content = html`
                        <text-field-filter
                            placeholder="${subsection.placeholder}"
                            .value="${this.preparedQuery[subsection.id]}"
                            @filterChange="${e => this.onFilterChange(subsection.id, e.detail.value)}">
                        </text-field-filter>
                    `;
                    break;
                case "annotations":
                    content = html`
                        <opencga-annotation-filter-modal
                            .opencgaSession="${this.opencgaSession}"
                            .opencgaClient="${this.opencgaSession.opencgaClient}"
                            .resource="${this.resource}"
                            .config="${this.annotationFilterConfig}"
                            .selectedVariablesText="${this.preparedQuery.annotation}"
                            @annotationChange="${this.onAnnotationChange}">
                        </opencga-annotation-filter-modal>
                    `;
                    break;
                case "somatic":
                    content = html`
                        <somatic-filter
                            .value="${this.preparedQuery.somatic}"
                            @filterChange="${e => this.onFilterChange("somatic", e.detail.value)}">
                        </somatic-filter>
                    `;
                    break;
                case "clinical_analysis_priority":
                    content = html`
                        <clinical-priority-filter
                            .priority="${this.preparedQuery[subsection.id]}"
                            .priorities="${Object.values(this.opencgaSession.study.internal?.configuration?.clinical?.priorities ?? [])}"
                            @filterChange="${e => this.onFilterChange(subsection.id, e.detail.value)}">
                        </clinical-priority-filter>
                    `;
                    break;
                case "status":
                    content = html`
                        <clinical-status-filter
                            .status="${this.preparedQuery[subsection.id]}"
                            .statuses="${Object.values(this.opencgaSession.study.internal?.configuration?.clinical?.status)?.flat()}"
                            .multiple="${true}"
                            @filterChange="${e => this.onFilterChange(subsection.id, e.detail.value)}">
                        </clinical-status-filter>
                    `;
                    break;
                case "region":
                    content = html`
                        <region-filter
                            .cellbaseClient="${this.cellbaseClient}"
                            .region="${this.preparedQuery.region}"
                            @filterChange="${e => this.onFilterChange("regions", e.detail.value)}">
                        </region-filter>
                    `;
                    break;
                case "date":
                case "creationDate":
                    content = html`
                        <date-filter
                            .filterDate="${this.preparedQuery.creationDate}"
                            @filterChange="${e => this.onFilterChange("creationDate", e.detail.value)}">
                        </date-filter>
                    `;
                    break;
                case "dueDate":
                    content = html`
                    <date-filter
                        .filterDate="${this.preparedQuery.dueDate}"
                        @filterChange="${e => this.onFilterChange("dueDate", e.detail.value)}">
                    </date-filter>
                    `;
                    break;
                default:
                    console.error("Filter component not found: ", id);
            }
        }

        if (content) {
            return html`

                        ${subsection.name}
                        ${subsection.description ? html`

                        ` : null}

                        ${content}

            `;
        } else {
            return "";
        }
    }

    render() {
        return html`
            ${this.config?.searchButton ? html`

                    <button type="button" class="btn btn-primary ripple" @click="${this.onSearch}">
                         Search
                    </button>

            ` : null}

                ${this.config?.sections?.length ? this.config.sections.map(section => this._createSection(section)) : html`No filter has been configured.`}

        `;
    }

}

customElements.define("opencga-browser-filter", OpencgaBrowserFilter);
<|endoftext|>


<commit_before>var modes = (function () {

	var	container = $('#modes');

	var defaultIndex = 1;

	var	list = [
		{ name: 'html'      , doc: null, mime: 'text/html' },
		{ name: 'javascript', doc: null, mime: 'text/javascript' },
		{ name: 'css'       , doc: null, mime: 'text/css' },
		{ name: 'json'      , doc: null, mime: 'application/json' }
	];

	function clearAll() {

		// clear javascript first
		switchTo('javascript');
		aigua.codeMirror.setValue('');

		switchTo('html');
		aigua.codeMirror.setValue('');

		switchTo('css');
		aigua.codeMirror.setValue('');

		switchTo('json');
		aigua.codeMirror.setValue('');

	}

	function get(name) {
		return _.findWhere(list, {name: name});
	}

	function getCurrent() {
		return get($('h2[class*="active"]', container).text());
	}

	// TODO: get rid of noTab references
	function switchTo(name) {

		if (!name) {
			name = list[defaultIndex].name;
		}

		// no need to do most of this if we're switching to this same mode
		var currentMode = getCurrent();
		if (name != currentMode.name) {

			// pause aigua: disable code evals from happening on codemirror changes
			aigua.pause = true;

			// what mode are we switching to?
			var nextMode = get(name);

			// get the mode's doc, or create a new one if null
			var nextModeDoc = nextMode.doc || CodeMirror.Doc('', nextMode.mime);

			// swap docs
			currentMode.doc = aigua.codeMirror.swapDoc(nextModeDoc);

			// select the correct mode tab
			$('h2', container).not(":contains('" + name + "')").addClass('passive').removeClass('active');
			$("h2:contains('" + name + "')", container).addClass('active').removeClass('passive');

			// resume code execution
			aigua.pause = false;

		}

	}

	function storeIn(payload) {

		_.each(list, function(v) {

			// get the mode's name
			var name = v.name;

			// is this the current mode?
			if (getCurrent().name == name) {

				// save the mode's contents directly from codemirror
				payload[name] = aigua.codeMirror.getValue();

			} else {

				// since we're not on the current mode, the contents
				// will be saved on the mode object
				payload[name] = _.findWhere(list, {name: name}).doc.getValue();
			}

		});

	}

	function init() {

		// populate mode switcher
		_.each(list, function(mode, i) {

			var div = $("

");
			var h2 = $("

");
			div.append(h2);

			h2.addClass(i == 1 ? 'active' : 'passive');
			h2.text(mode.name);

			container.append(div);
		});

		// handle modes switcher
		$('.item h2', container).on('click', function(e) {
			switchTo($(this).text());
		});

	}

	return {
		init: init,
		getCurrent: getCurrent,
		get: get,
		storeIn: storeIn,
		switchTo: switchTo,
		clearAll: clearAll
	}

}());
<commit_msg>fixed bug<commit_after>var modes = (function () {

	var	container = $('#modes');

	var defaultIndex = 1;

	var	list = [
		{ name: 'html'      , doc: null, mime: 'text/html' },
		{ name: 'javascript', doc: null, mime: 'text/javascript' },
		{ name: 'css'       , doc: null, mime: 'text/css' },
		{ name: 'json'      , doc: null, mime: 'application/json' }
	];

	function clearAll() {

		// clear javascript first
		switchTo('javascript');
		aigua.codeMirror.setValue('');

		switchTo('html');
		aigua.codeMirror.setValue('');

		switchTo('css');
		aigua.codeMirror.setValue('');

		switchTo('json');
		aigua.codeMirror.setValue('');

	}

	function get(name) {
		return _.findWhere(list, {name: name});
	}

	function getCurrent() {
		return get($('h2[class*="active"]', container).text());
	}

	// TODO: get rid of noTab references
	function switchTo(name) {

		if (!name) {
			name = list[defaultIndex].name;
		}

		// no need to do most of this if we're switching to this same mode
		var currentMode = getCurrent();
		if (name != currentMode.name) {

			// pause aigua: disable code evals from happening on codemirror changes
			aigua.pause = true;

			// what mode are we switching to?
			var nextMode = get(name);

			// get the mode's doc, or create a new one if null
			var nextModeDoc = nextMode.doc || CodeMirror.Doc('', nextMode.mime);

			// swap docs
			currentMode.doc = aigua.codeMirror.swapDoc(nextModeDoc);

			// select the correct mode tab
			$('h2', container).not(":contains('" + name + "')").addClass('passive').removeClass('active');
			$("h2:contains('" + name + "')", container).addClass('active').removeClass('passive');

			// resume code execution
			aigua.pause = false;

		}

	}

	function storeIn(payload) {

		_.each(list, function(v) {

			// get the mode's name
			var name = v.name;

			// is this the current mode?
			if (getCurrent().name == name) {

				// save the mode's contents directly from codemirror
				payload[name] = aigua.codeMirror.getValue();

			} else {

				// since we're not on the current mode, the contents
				// will be saved on the mode object
				var modeDoc = _.findWhere(list, {name: name}).doc;
				payload[name] = modeDoc ? modeDoc.getValue() : '';
			}

		});

	}

	function init() {

		// populate mode switcher
		_.each(list, function(mode, i) {

			var div = $("

");
			var h2 = $("

");
			div.append(h2);

			h2.addClass(i == 1 ? 'active' : 'passive');
			h2.text(mode.name);

			container.append(div);
		});

		// handle modes switcher
		$('.item h2', container).on('click', function(e) {
			switchTo($(this).text());
		});

	}

	return {
		init: init,
		getCurrent: getCurrent,
		get: get,
		storeIn: storeIn,
		switchTo: switchTo,
		clearAll: clearAll
	}

}());
<|endoftext|>