<commit_before>/**********************************************************************
    Freeciv-web - the web version of Freeciv. http://play.freeciv.org/
    Copyright (C) 2009-2016  The Freeciv-web project

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

***********************************************************************/

var webgl_textures = {};
var webgl_models_xml_strings = {};
var webgl_models = {};
var start_preload = 0;
var total_model_count = 0;
var load_count = 0;
var webgl_materials = {};

/****************************************************************************
  Preload textures and models
****************************************************************************/
function webgl_preload()
{
  $.blockUI({ message: "
Downloading textures and models...
" });
  start_preload = new Date().getTime();

  var loadingManager = new THREE.LoadingManager();
  loadingManager.onLoad = function () {
    webgl_preload_models();

  };

  var textureLoader = new THREE.ImageLoader( loadingManager );

  /* Preload water overlay texture. */
  var water_texture = new THREE.Texture();
  webgl_textures["water_overlay"] = water_texture;
  var filename = is_small_screen() ? '/textures/small/water_overlay_texture.png'
                                   : '/textures/large/water_overlay_texture.png';
  textureLoader.load( filename, function ( image ) {
      water_texture.image = image;
      water_texture.wrapS = THREE.RepeatWrapping;
      water_texture.wrapT = THREE.RepeatWrapping;
      water_texture.repeat.set( 5, 5);
      water_texture.needsUpdate = true;
  } );

  /* Preload tree sprite. */
  var tree_sprite = new THREE.Texture();
  webgl_textures["tree_1"] = tree_sprite;
  textureLoader.load( '/textures/tree_1.png', function ( image ) {
      tree_sprite.image = image;
      tree_sprite.needsUpdate = true;
  } );

  var jungle_sprite = new THREE.Texture();
  webgl_textures["jungle_1"] = jungle_sprite;
  textureLoader.load( '/textures/jungle_1.png', function ( image ) {
      jungle_sprite.image = image;
      jungle_sprite.needsUpdate = true;
  } );

  var road_sprite = new THREE.Texture();
  webgl_textures["road_1"] = road_sprite;
  textureLoader.load( '/textures/road_1.png', function ( image ) {
      road_sprite.image = image;
      road_sprite.needsUpdate = true;
      roadMaterial = new THREE.MeshBasicMaterial( { map: road_sprite, side:THREE.DoubleSide } );
      roadMaterial.transparent = true;
      webgl_materials['road_1'] = roadMaterial;
  } );

  var rail_sprite = new THREE.Texture();
  webgl_textures["rail_1"] = rail_sprite;
  textureLoader.load( '/textures/rail_1.png', function ( image ) {
      rail_sprite.image = image;
      rail_sprite.needsUpdate = true;
      railMaterial = new THREE.MeshBasicMaterial( { map: rail_sprite, side:THREE.DoubleSide } );
      railMaterial.transparent = true;
      webgl_materials['rail_1'] = railMaterial;
  } );

  /* Preload a texture for each map tile type. */
  for (var i = 0; i < tiletype_terrains.length; i++) {
    var imgurl = is_small_screen() ? "/textures/small/" + tiletype_terrains[i] + ".png"
                                   : "/textures/large/" + tiletype_terrains[i] + ".png" ;
    textureLoader.load(imgurl, (function (url, index) {
            return function (image, i) {
                webgl_textures[tiletype_terrains[index]] = new THREE.Texture();
                webgl_textures[tiletype_terrains[index]].image = image;
                webgl_textures[tiletype_terrains[index]].wrapS = THREE.RepeatWrapping;
                webgl_textures[tiletype_terrains[index]].wrapT = THREE.RepeatWrapping;
                webgl_textures[tiletype_terrains[index]].needsUpdate = true;
            }
      })(imgurl, i)
    );
  }

}

/****************************************************************************
  Preload all models.
****************************************************************************/
function webgl_preload_models()
{
  var url = '/3d-models/models.zip';
  zip.createReader(new zip.HttpReader(url), function(zipReader){
     $.blockUI({ message: "
Downloading 3D models...
" });
     zipReader.getEntries(function(entries){
       total_model_count = entries.length;
       for (var i = 0; i < entries.length; i++) {
         var file = entries[i];
         handle_zip_file(file['filename'].replace(".dae", ""), file);
       }
     });
  }, function(message){
    swal("Unable to download models. Please reload the page and try again.");
    console.error("Problem downloading models.zip. Error message: " + message);
  });

}

/****************************************************************************
  Handle each unzipped file, call load_model on each file.
****************************************************************************/
function handle_zip_file(filename, file)
{
  file.getData(new zip.TextWriter(), function(text) {
    load_count++;

    webgl_models_xml_strings[filename] = text;
    if (load_count == total_model_count) {
      webgl_preload_complete();
      console.log("WebGL preloading took: " + (new Date().getTime() - start_preload) + " ms.");
    }
  });

}

/****************************************************************************
 Loads a single model file.
****************************************************************************/
function webgl_get_model(filename)
{
  if (webgl_models[filename] != null) return webgl_models[filename].clone();;
  if (webgl_models_xml_strings[filename] == null) {
    return null;
  }

  var colladaloader = new THREE.ColladaLoader();
  colladaloader.options.convertUpAxis = true;

  var collada = colladaloader.parse( webgl_models_xml_strings[filename]);
  var dae = collada.scene;
  dae.updateMatrix();
  dae.scale.x = dae.scale.y = dae.scale.z = 11;
  webgl_models[filename] = dae;
  webgl_models_xml_strings[filename] = ""; // clear xml string for this model.

  return dae.clone();

}

/****************************************************************************
 Returns a flag mesh
****************************************************************************/
function get_flag_mesh(key)
{
  if (meshes[key] != null) return meshes[key].clone();
  if (sprites[key] == null || key.substring(0,2) != "f.") {
    console.log("Invalid flag key: " + key);
    return null;
  }

  var flagGeometry = new THREE.PlaneBufferGeometry( 14, 10 );
  /* resize flag to 32x16, since that is required by Three.js*/
  var fcanvas = document.createElement("canvas");
  fcanvas.width = 32;
  fcanvas.height = 16;
  var fcontext = fcanvas.getContext("2d");
  fcontext.drawImage(sprites[key], 0, 0,
                sprites[key].width, sprites[key].height,
                0,0,32,16);

  var texture = new THREE.CanvasTexture(fcanvas);
  var material = new THREE.MeshBasicMaterial( { map: texture, overdraw: 0.5 } );
  meshes[key] = new THREE.Mesh(flagGeometry, material);
  return meshes[key].clone();

}<commit_msg>User-facing unit flags<commit_after>/**********************************************************************
    Freeciv-web - the web version of Freeciv. http://play.freeciv.org/
    Copyright (C) 2009-2016  The Freeciv-web project

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

***********************************************************************/

var webgl_textures = {};
var webgl_models_xml_strings = {};
var webgl_models = {};
var start_preload = 0;
var total_model_count = 0;
var load_count = 0;
var webgl_materials = {};

/****************************************************************************
  Preload textures and models
****************************************************************************/
function webgl_preload()
{
  $.blockUI({ message: "
Downloading textures and models...
" });
  start_preload = new Date().getTime();

  var loadingManager = new THREE.LoadingManager();
  loadingManager.onLoad = function () {
    webgl_preload_models();

  };

  var textureLoader = new THREE.ImageLoader( loadingManager );

  /* Preload water overlay texture. */
  var water_texture = new THREE.Texture();
  webgl_textures["water_overlay"] = water_texture;
  var filename = is_small_screen() ? '/textures/small/water_overlay_texture.png'
                                   : '/textures/large/water_overlay_texture.png';
  textureLoader.load( filename, function ( image ) {
      water_texture.image = image;
      water_texture.wrapS = THREE.RepeatWrapping;
      water_texture.wrapT = THREE.RepeatWrapping;
      water_texture.repeat.set( 5, 5);
      water_texture.needsUpdate = true;
  } );

  /* Preload tree sprite. */
  var tree_sprite = new THREE.Texture();
  webgl_textures["tree_1"] = tree_sprite;
  textureLoader.load( '/textures/tree_1.png', function ( image ) {
      tree_sprite.image = image;
      tree_sprite.needsUpdate = true;
  } );

  var jungle_sprite = new THREE.Texture();
  webgl_textures["jungle_1"] = jungle_sprite;
  textureLoader.load( '/textures/jungle_1.png', function ( image ) {
      jungle_sprite.image = image;
      jungle_sprite.needsUpdate = true;
  } );

  var road_sprite = new THREE.Texture();
  webgl_textures["road_1"] = road_sprite;
  textureLoader.load( '/textures/road_1.png', function ( image ) {
      road_sprite.image = image;
      road_sprite.needsUpdate = true;
      roadMaterial = new THREE.MeshBasicMaterial( { map: road_sprite, side:THREE.DoubleSide } );
      roadMaterial.transparent = true;
      webgl_materials['road_1'] = roadMaterial;
  } );

  var rail_sprite = new THREE.Texture();
  webgl_textures["rail_1"] = rail_sprite;
  textureLoader.load( '/textures/rail_1.png', function ( image ) {
      rail_sprite.image = image;
      rail_sprite.needsUpdate = true;
      railMaterial = new THREE.MeshBasicMaterial( { map: rail_sprite, side:THREE.DoubleSide } );
      railMaterial.transparent = true;
      webgl_materials['rail_1'] = railMaterial;
  } );

  /* Preload a texture for each map tile type. */
  for (var i = 0; i < tiletype_terrains.length; i++) {
    var imgurl = is_small_screen() ? "/textures/small/" + tiletype_terrains[i] + ".png"
                                   : "/textures/large/" + tiletype_terrains[i] + ".png" ;
    textureLoader.load(imgurl, (function (url, index) {
            return function (image, i) {
                webgl_textures[tiletype_terrains[index]] = new THREE.Texture();
                webgl_textures[tiletype_terrains[index]].image = image;
                webgl_textures[tiletype_terrains[index]].wrapS = THREE.RepeatWrapping;
                webgl_textures[tiletype_terrains[index]].wrapT = THREE.RepeatWrapping;
                webgl_textures[tiletype_terrains[index]].needsUpdate = true;
            }
      })(imgurl, i)
    );
  }

}

/****************************************************************************
  Preload all models.
****************************************************************************/
function webgl_preload_models()
{
  var url = '/3d-models/models.zip';
  zip.createReader(new zip.HttpReader(url), function(zipReader){
     $.blockUI({ message: "
Downloading 3D models...
" });
     zipReader.getEntries(function(entries){
       total_model_count = entries.length;
       for (var i = 0; i < entries.length; i++) {
         var file = entries[i];
         handle_zip_file(file['filename'].replace(".dae", ""), file);
       }
     });
  }, function(message){
    swal("Unable to download models. Please reload the page and try again.");
    console.error("Problem downloading models.zip. Error message: " + message);
  });

}

/****************************************************************************
  Handle each unzipped file, call load_model on each file.
****************************************************************************/
function handle_zip_file(filename, file)
{
  file.getData(new zip.TextWriter(), function(text) {
    load_count++;

    webgl_models_xml_strings[filename] = text;
    if (load_count == total_model_count) {
      webgl_preload_complete();
      console.log("WebGL preloading took: " + (new Date().getTime() - start_preload) + " ms.");
    }
  });

}

/****************************************************************************
 Loads a single model file.
****************************************************************************/
function webgl_get_model(filename)
{
  if (webgl_models[filename] != null) return webgl_models[filename].clone();;
  if (webgl_models_xml_strings[filename] == null) {
    return null;
  }

  var colladaloader = new THREE.ColladaLoader();
  colladaloader.options.convertUpAxis = true;

  var collada = colladaloader.parse( webgl_models_xml_strings[filename]);
  var dae = collada.scene;
  dae.updateMatrix();
  dae.scale.x = dae.scale.y = dae.scale.z = 11;
  webgl_models[filename] = dae;
  webgl_models_xml_strings[filename] = ""; // clear xml string for this model.

  return dae.clone();

}

/****************************************************************************
 Returns a flag mesh
****************************************************************************/
function get_flag_mesh(key)
{
  if (meshes[key] != null) return meshes[key].clone();
  if (sprites[key] == null || key.substring(0,2) != "f.") {
    console.log("Invalid flag key: " + key);
    return null;
  }

  var flagGeometry = new THREE.PlaneBufferGeometry( 14, 10 );
  /* resize flag to 32x16, since that is required by Three.js*/
  var fcanvas = document.createElement("canvas");
  fcanvas.width = 32;
  fcanvas.height = 16;
  var fcontext = fcanvas.getContext("2d");
  fcontext.drawImage(sprites[key], 0, 0,
                sprites[key].width, sprites[key].height,
                0,0,32,16);

  meshes[key] = canvas_to_user_facing_mesh(fcanvas, 32, false)
  return meshes[key].clone();

}
<|endoftext|>


<commit_before>// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// @ts-nocheck This file is not checked by TypeScript as it has a lot of legacy code.
import * as Platform from '../../core/platform/platform.js';
import * as TestRunner from './TestRunner.js';

export {
  TestRunner,
};

self.Platform = self.Platform || {};
self.Platform.StringUtilities = Platform.StringUtilities;

/**
 * @param {!SDK.Target} target
 */
function _setupTestHelpers(target) {
  self.TestRunner.BrowserAgent = target.browserAgent();
  self.TestRunner.CSSAgent = target.cssAgent();
  self.TestRunner.DeviceOrientationAgent = target.deviceOrientationAgent();
  self.TestRunner.DOMAgent = target.domAgent();
  self.TestRunner.DOMDebuggerAgent = target.domdebuggerAgent();
  self.TestRunner.DebuggerAgent = target.debuggerAgent();
  self.TestRunner.EmulationAgent = target.emulationAgent();
  self.TestRunner.HeapProfilerAgent = target.heapProfilerAgent();
  self.TestRunner.InputAgent = target.inputAgent();
  self.TestRunner.InspectorAgent = target.inspectorAgent();
  self.TestRunner.NetworkAgent = target.networkAgent();
  self.TestRunner.OverlayAgent = target.overlayAgent();
  self.TestRunner.PageAgent = target.pageAgent();
  self.TestRunner.ProfilerAgent = target.profilerAgent();
  self.TestRunner.RuntimeAgent = target.runtimeAgent();
  self.TestRunner.TargetAgent = target.targetAgent();

  self.TestRunner.networkManager = target.model(SDK.NetworkManager);
  self.TestRunner.securityOriginManager = target.model(SDK.SecurityOriginManager);
  self.TestRunner.storageKeyManager = target.model(SDK.StorageKeyManager);
  self.TestRunner.resourceTreeModel = target.model(SDK.ResourceTreeModel);
  self.TestRunner.debuggerModel = target.model(SDK.DebuggerModel);
  self.TestRunner.runtimeModel = target.model(SDK.RuntimeModel);
  self.TestRunner.domModel = target.model(SDK.DOMModel);
  self.TestRunner.domDebuggerModel = target.model(SDK.DOMDebuggerModel);
  self.TestRunner.cssModel = target.model(SDK.CSSModel);
  self.TestRunner.cpuProfilerModel = target.model(SDK.CPUProfilerModel);
  self.TestRunner.overlayModel = target.model(SDK.OverlayModel);
  self.TestRunner.serviceWorkerManager = target.model(SDK.ServiceWorkerManager);
  self.TestRunner.tracingManager = target.model(SDK.TracingManager);
  self.TestRunner.mainTarget = target;
}

export async function _executeTestScript() {
  const testScriptURL = /** @type {string} */ (Root.Runtime.queryParam('test'));
  if (TestRunner.isDebugTest()) {
    /* eslint-disable no-console */
    TestRunner.setInnerResult(console.log);
    TestRunner.setInnerCompleteTest(() => console.log('Test completed'));
    /* eslint-enable no-console */

    // Auto-start unit tests
    self.test = async function() {
      await import(testScriptURL);
    };
    return;
  }

  try {
    await import(testScriptURL);
  } catch (err) {
    TestRunner.addResult('TEST ENDED EARLY DUE TO UNCAUGHT ERROR:');
    TestRunner.addResult(err && err.stack || err);
    TestRunner.addResult('=== DO NOT COMMIT THIS INTO -expected.txt ===');
    TestRunner.completeTest();
  }
}

/** @type {boolean} */
let _startedTest = false;

/**
 * @implements {SDK.TargetManager.Observer}
 */
export class _TestObserver {
  /**
   * @param {!SDK.Target} target
   * @override
   */
  targetAdded(target) {
    if (target.id() === 'main') {
      _setupTestHelpers(target);
    }
    if (_startedTest) {
      return;
    }
    _startedTest = true;
    TestRunner
        .loadHTML(`
      <head>
        <base href="${TestRunner.url()}">
      </head>
      <body>
      </body>
    `).then(() => _executeTestScript());
  }

  /**
   * @param {!SDK.Target} target
   * @override
   */
  targetRemoved(target) {
  }
}

SDK.targetManager.observeTargets(new _TestObserver());
<commit_msg>Handling tab target in the legacy layout test runner.<commit_after>// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// @ts-nocheck This file is not checked by TypeScript as it has a lot of legacy code.
import * as Platform from '../../core/platform/platform.js';
import * as TestRunner from './TestRunner.js';

export {
  TestRunner,
};

self.Platform = self.Platform || {};
self.Platform.StringUtilities = Platform.StringUtilities;

/**
 * @param {!SDK.Target} target
 */
function _setupTestHelpers(target) {
  self.TestRunner.BrowserAgent = target.browserAgent();
  self.TestRunner.CSSAgent = target.cssAgent();
  self.TestRunner.DeviceOrientationAgent = target.deviceOrientationAgent();
  self.TestRunner.DOMAgent = target.domAgent();
  self.TestRunner.DOMDebuggerAgent = target.domdebuggerAgent();
  self.TestRunner.DebuggerAgent = target.debuggerAgent();
  self.TestRunner.EmulationAgent = target.emulationAgent();
  self.TestRunner.HeapProfilerAgent = target.heapProfilerAgent();
  self.TestRunner.InputAgent = target.inputAgent();
  self.TestRunner.InspectorAgent = target.inspectorAgent();
  self.TestRunner.NetworkAgent = target.networkAgent();
  self.TestRunner.OverlayAgent = target.overlayAgent();
  self.TestRunner.PageAgent = target.pageAgent();
  self.TestRunner.ProfilerAgent = target.profilerAgent();
  self.TestRunner.RuntimeAgent = target.runtimeAgent();
  self.TestRunner.TargetAgent = target.targetAgent();

  self.TestRunner.networkManager = target.model(SDK.NetworkManager);
  self.TestRunner.securityOriginManager = target.model(SDK.SecurityOriginManager);
  self.TestRunner.storageKeyManager = target.model(SDK.StorageKeyManager);
  self.TestRunner.resourceTreeModel = target.model(SDK.ResourceTreeModel);
  self.TestRunner.debuggerModel = target.model(SDK.DebuggerModel);
  self.TestRunner.runtimeModel = target.model(SDK.RuntimeModel);
  self.TestRunner.domModel = target.model(SDK.DOMModel);
  self.TestRunner.domDebuggerModel = target.model(SDK.DOMDebuggerModel);
  self.TestRunner.cssModel = target.model(SDK.CSSModel);
  self.TestRunner.cpuProfilerModel = target.model(SDK.CPUProfilerModel);
  self.TestRunner.overlayModel = target.model(SDK.OverlayModel);
  self.TestRunner.serviceWorkerManager = target.model(SDK.ServiceWorkerManager);
  self.TestRunner.tracingManager = target.model(SDK.TracingManager);
  self.TestRunner.mainTarget = target;
}

export async function _executeTestScript() {
  const testScriptURL = /** @type {string} */ (Root.Runtime.queryParam('test'));
  if (TestRunner.isDebugTest()) {
    /* eslint-disable no-console */
    TestRunner.setInnerResult(console.log);
    TestRunner.setInnerCompleteTest(() => console.log('Test completed'));
    /* eslint-enable no-console */

    // Auto-start unit tests
    self.test = async function() {
      await import(testScriptURL);
    };
    return;
  }

  try {
    await import(testScriptURL);
  } catch (err) {
    TestRunner.addResult('TEST ENDED EARLY DUE TO UNCAUGHT ERROR:');
    TestRunner.addResult(err && err.stack || err);
    TestRunner.addResult('=== DO NOT COMMIT THIS INTO -expected.txt ===');
    TestRunner.completeTest();
  }
}

/** @type {boolean} */
let _startedTest = false;

/**
 * @implements {SDK.TargetManager.Observer}
 */
export class _TestObserver {
  /**
   * @param {!SDK.Target} target
   * @override
   */
  targetAdded(target) {
    if (target.id() === 'main' && target.type() === 'frame' ||
        target.parentTarget()?.type() === 'tab' && target.type() === 'frame' && !target.targetInfo()?.subtype?.length) {
      _setupTestHelpers(target);
      if (_startedTest) {
        return;
      }
      _startedTest = true;
      TestRunner
          .loadHTML(`
        <head>
          <base href="${TestRunner.url()}">
        </head>
        <body>
        </body>
      `).then(() => _executeTestScript());
    }
  }

  /**
   * @param {!SDK.Target} target
   * @override
   */
  targetRemoved(target) {
  }
}

SDK.targetManager.observeTargets(new _TestObserver());
<|endoftext|>


<commit_before>/*======================================================
************   Material Text Inputs   ************
======================================================*/
app.initPageMaterialInputs = function (pageContainer) {
    pageContainer = $(pageContainer);
    var textareas = pageContainer.find('textarea.resizable');
    pageContainer.find('.item-input').each(function () {
        var itemInput = $(this);
        var notInputs = ['checkbox', 'button', 'submit', 'range', 'radio', 'image'];
        itemInput.find('input, select, textarea').each(function () {
            var input = $(this);
            if (notInputs.indexOf(input.attr('type')) < 0) {
                itemInput.addClass('item-input-field');
                if (input.val().trim() !== '') {
                    input.parents('.item-input, .input-field').add(input.parents('.item-inner')).addClass('not-empty-state');
                }
            }
        });
        if (itemInput.parents('.input-item, .inputs-list').length > 0) return;
        itemInput.parents('.list-block').eq(0).addClass('inputs-list');
    });
};
/*======================================================
************   Material Focus Inputs   ************
======================================================*/
app.initMaterialWatchInputs = function () {
    var notInputs = ['checkbox', 'button', 'submit', 'range', 'radio', 'image'];
    function addFocusState(e) {
        /*jshint validthis:true*/
        var i = $(this);
        var type = i.attr('type');
        if (notInputs.indexOf(type) >= 0) return;
        var els = i.add(i.parents('.item-input, .input-field')).add(i.parents('.item-inner').eq(0));
        els.addClass('focus-state');
    }
    function removeFocusState(e) {
        /*jshint validthis:true*/
        var i = $(this), value = i.val();
        var type = i.attr('type');
        if (notInputs.indexOf(type) >= 0) return;
        var els = i.add(i.parents('.item-input, .input-field')).add(i.parents('.item-inner').eq(0));
        els.removeClass('focus-state');
        if (value && value.trim() !== '') {
            els.addClass('not-empty-state');
        }
        else {
            els.removeClass('not-empty-state');
        }
    }
    $(document).on('focus', '.item-input input, .item-input select, .item-input textarea, input, textarea, select', addFocusState, true);
    $(document).on('blur', '.item-input input, .item-input select, .item-input textarea, input, textarea, select', removeFocusState, true);
};<commit_msg>Watch for change event<commit_after>/*======================================================
************   Material Text Inputs   ************
======================================================*/
app.initPageMaterialInputs = function (pageContainer) {
    pageContainer = $(pageContainer);
    var textareas = pageContainer.find('textarea.resizable');
    pageContainer.find('.item-input').each(function () {
        var itemInput = $(this);
        var notInputs = ['checkbox', 'button', 'submit', 'range', 'radio', 'image'];
        itemInput.find('input, select, textarea').each(function () {
            var input = $(this);
            if (notInputs.indexOf(input.attr('type')) < 0) {
                itemInput.addClass('item-input-field');
                if (input.val().trim() !== '') {
                    input.parents('.item-input, .input-field').add(input.parents('.item-inner')).addClass('not-empty-state');
                }
            }
        });
        if (itemInput.parents('.input-item, .inputs-list').length > 0) return;
        itemInput.parents('.list-block').eq(0).addClass('inputs-list');
    });
};
/*======================================================
************   Material Focus Inputs   ************
======================================================*/
app.initMaterialWatchInputs = function () {
    var notInputs = ['checkbox', 'button', 'submit', 'range', 'radio', 'image'];
    function addFocusState(e) {
        /*jshint validthis:true*/
        var i = $(this);
        var type = i.attr('type');
        if (notInputs.indexOf(type) >= 0) return;
        var els = i.add(i.parents('.item-input, .input-field')).add(i.parents('.item-inner').eq(0));
        els.addClass('focus-state');
    }
    function removeFocusState(e) {
        /*jshint validthis:true*/
        var i = $(this), value = i.val();
        var type = i.attr('type');
        if (notInputs.indexOf(type) >= 0) return;
        var els = i.add(i.parents('.item-input, .input-field')).add(i.parents('.item-inner').eq(0));
        els.removeClass('focus-state');
        if (value && value.trim() !== '') {
            els.addClass('not-empty-state');
        }
        else {
            els.removeClass('not-empty-state');
        }
    }
    function watchChangeState(e) {
        /*jshint validthis:true*/
        var i = $(this), value = i.val();
        var type = i.attr('type');
        if (notInputs.indexOf(type) >= 0) return;
        var els = i.add(i.parents('.item-input, .input-field')).add(i.parents('.item-inner').eq(0));
        if (value && value.trim() !== '') {
            els.addClass('not-empty-state');
        }
        else {
            els.removeClass('not-empty-state');
        }
    }
    $(document).on('change', '.item-input input, .item-input select, .item-input textarea, input, textarea, select', watchChangeState, true);
    $(document).on('focus', '.item-input input, .item-input select, .item-input textarea, input, textarea, select', addFocusState, true);
    $(document).on('blur', '.item-input input, .item-input select, .item-input textarea, input, textarea, select', removeFocusState, true);
};<|endoftext|>


<commit_before>var kana = require('./kana'),
    rows;

module.exports = Backbone.Model.extend({
    defaults: function(){
        return getQuestion();
    },
    isCorrect: function(choice){
        var correct = this.get('choices')[choice] === this.get('answer');

        updateScore(this.get('kana'), correct);

        if(correct){
            unlockNextRow();
        }

        return correct;
    },
    nextQuestion: function(){
        this.set(getQuestion());
    }
});

function initRow(row){
    _.forEach(row, function(q){
        q.score = {correct: 0, incorrect: 0};
    });
};

function getAvailable(rows){
    return _.filter(_.flatten(rows), function(kana){
        return kana.score;
    });
}

function updateScore(kana, correct){
    kana.score[correct ? 'correct' : 'incorrect'] += 1;
}

function getQuestion(){
    var available = getAvailable(rows);

    var fields = Math.random() > 0.5 ? ['sound', 'kana'] : ['kana', 'sound'],
        questionField = fields[0], answerField = fields[1];

    var questionKana = _.sample(available);

    question = {
        kana: questionKana,
        question:questionKana[questionField],
        answer: questionKana[answerField]
    }

    var choices = _.chain(available)
                   .filter(function(kana){
                       return kana[answerField] !== question.answer &&
                              kana[questionField] !== question.question;
                    })
                   .sample(3)
                   .pluck(answerField)
                   .value();

    choices.push(question.answer);

    question.choices = _.shuffle(choices);

    return question;
};

function unlockNextRow(){
    var nextToUnlock = _.find(rows, function(row){
        return !row[0].score;
    });

    var allLearned = _.every(getAvailable(rows), function(kana){
            var attempts = kana.score.correct + kana.score.incorrect,
                errorRatio = attempts > 0 ? kana.score.incorrect / attempts : 1;
            return errorRatio < 0.5;
        });

    if (nextToUnlock && allLearned){
        initRow(nextToUnlock);
    }
}

function init(){
    rows = kana.hiragana.concat(kana.katakana);
    initRow(rows[0]);
}

init();
<commit_msg>Adds offline storage of game progress<commit_after>var kana = require('./kana'),
    game;

module.exports = Backbone.Model.extend({
    defaults: function(){
        return getQuestion();
    },
    isCorrect: function(choice){
        var correct = this.get('choices')[choice] === this.get('answer');

        updateScore(this.get('kana'), correct);

        if(correct){
            unlockNextRow();
            saveGame();
        }

        return correct;
    },
    nextQuestion: function(){
        this.set(getQuestion());
    }
});

function initRow(row){
    _.forEach(row, function(q){
        q.score = {correct: 0, incorrect: 0};
    });
}

function getAvailable(rows){
    return _.filter(_.flatten(rows), function(kana){
        return kana.score;
    });
}

function updateScore(kana, correct){
    kana.score[correct ? 'correct' : 'incorrect'] += 1;
}

function getQuestion(){
    var available = getAvailable(game);

    var fields = Math.random() > 0.5 ? ['sound', 'kana'] : ['kana', 'sound'],
        questionField = fields[0], answerField = fields[1];

    var questionKana = _.sample(available);

    question = {
        kana: questionKana,
        question:questionKana[questionField],
        answer: questionKana[answerField]
    };

    var choices = _.chain(available)
                   .filter(function(kana){
                       return kana[answerField] !== question.answer &&
                              kana[questionField] !== question.question;
                    })
                   .sample(3)
                   .pluck(answerField)
                   .value();

    choices.push(question.answer);

    question.choices = _.shuffle(choices);

    return question;
}

function unlockNextRow(){
    var nextToUnlock = _.find(game, function(row){
        return !row[0].score;
    });

    var allLearned = _.every(getAvailable(game), function(kana){
            var attempts = kana.score.correct + kana.score.incorrect,
                errorRatio = attempts > 0 ? kana.score.incorrect / attempts : 1;
            return errorRatio < 0.5;
        });

    if (nextToUnlock && allLearned){
        initRow(nextToUnlock);
    }
}

function saveGame(){
    localStorage && localStorage.setItem("game", JSON.stringify(game));
}

function loadGame(){
    return (localStorage && JSON.parse(localStorage.getItem("game"))) ||
           kana.hiragana.concat(kana.katakana);
}

function newGame(){
    game = loadGame();
    initRow(game[0]);
}

newGame();
<|endoftext|>


<commit_before>(function($) {

    $.fn.offCanvasMenu = function(options) {

        var settings = $.extend({

            // These are the defaults.
            container : $(this),
            containerActiveAffix: "active",
            globalName: "js-offCanvasMenu",
            activeState: "is-active",
            effect: "push",
            buttonName: "button",
            overlayName: "overlay",
            iconOpen : "fa-navicon",
            textOpen : "Menu",
            iconClose : "fa-times",
            textClose : "Sluit"

        }, options),

        htmlClasses = {
            containerActive: settings.globalName + "--" + settings.containerActiveAffix,
            button: settings.globalName + "-" + settings.buttonName,
            buttonActive: settings.activeState,
            overlay: settings.globalName + "-" + settings.overlayName,
            effect: settings.globalName + "--effect--" + settings.effect
        },

        cssClasses = {
            containerActive: "." + htmlClasses.containerActiveAffix,
            button: "." + htmlClasses.button,
            buttonText: "." + htmlClasses.button + "-text",
            buttonIcon: "." + htmlClasses.button + "-icon",
            overlay: "." + htmlClasses.overlay,
            effect: "." + htmlClasses.effect
        },

        elements = {
            button: $(cssClasses.button),
            buttonText: $(cssClasses.buttonText),
            buttonIcon: $(cssClasses.buttonIcon),
            container: settings.container,
            overlay: $(cssClasses.overlay)
        },

        toggle = function(elem, effect) {

            var time,
                checkIdenticalEffects = function(){
                        var elementEffect = elem.data("effect"),
                            containerEffect = elements.container.data("effect");

                        if (containerEffect === elementEffect) {
                            return true;
                        } else {
                            return false;
                        }
                    },
                toggleClasses = function(){
                    if (settings.container.hasClass(htmlClasses.containerActive)) {
                        //elem.removeClass(htmlClasses.activeCssClass);
                        elements.container.removeClass(htmlClasses.containerActive);
                        buttonSettings.icon.removeClass(settings.iconClose).addClass(settings.iconOpen);
                        elements.buttonText.textfi(settings.textOpen);
                    } else {
                        //elem.addClass(htmlClasses.activeCssClass);
                        elements.container.addClass(htmlClasses.containerActive);
                        buttonSettings.icon.removeClass(settings.iconOpen).addClass(settings.iconClose);
                        elements.buttonText.text(settings.textClose);
                    }
                };

            if (checkIdenticalEffects()) {
                time = 0;
            } else {
                time = 400;
            }

            setTimeout( function(time){
                toggleClasses();
            }, time);

            //Update effect data and class
            elements.container.removeClass(function () {
                return (settings.globalName + "--effect--" + $(this).data("effect"));
            });

            elements.container.addClass(settings.globalName + "--effect--" + checkEffect(elem));
            elements.container.data("effect", checkEffect(elem));
        },

        checkEffect = function(elem) {
            if (elem.data("effect").length > 0){
                return elem.data("effect");
            } else {
                return settings.effect;
            }
        };

        // Click event
        // =======================================================

        elements.button.click(function(e) {
            e.preventDefault();
            toggle( $(this) , checkEffect($(this)) );
        });

        //Refactor for use of overlay
        elements.overlay.click(function(e) {
            e.preventDefault();
            $(this).data("effect", checkEffect(settings.container));
            toggle( $(this), checkEffect(settings.container) );
        });

        // On load
        // Provide the correct effect class to the container.
        // =======================================================
        elements.container.addClass(htmlClasses.effect);
        elements.container.data("effect", settings.effect);
    };

}(jQuery));
<commit_msg>Update off-canvas-menu.js<commit_after>(function($) {

    $.fn.offCanvasMenu = function(options) {

        var settings = $.extend({

            // These are the defaults.
            container : $(this),
            containerActiveAffix: "active",
            globalName: "js-offCanvasMenu",
            activeState: "is-active",
            effect: "push",
            buttonName: "button",
            overlayName: "overlay",
            iconOpen : "fa-navicon",
            textOpen : "Menu",
            iconClose : "fa-times",
            textClose : "Sluit"

        }, options),

        htmlClasses = {
            containerActive: settings.globalName + "--" + settings.containerActiveAffix,
            button: settings.globalName + "-" + settings.buttonName,
            buttonActive: settings.activeState,
            overlay: settings.globalName + "-" + settings.overlayName,
            effect: settings.globalName + "--effect--" + settings.effect
        },

        cssClasses = {
            containerActive: "." + htmlClasses.containerActiveAffix,
            button: "." + htmlClasses.button,
            buttonText: "." + htmlClasses.button + "-text",
            buttonIcon: "." + htmlClasses.button + "-icon",
            overlay: "." + htmlClasses.overlay,
            effect: "." + htmlClasses.effect
        },

        elements = {
            button: $(cssClasses.button),
            buttonText: $(cssClasses.buttonText),
            buttonIcon: $(cssClasses.buttonIcon),
            container: settings.container,
            overlay: $(cssClasses.overlay)
        },

        toggle = function(elem, effect) {

            var time,
                checkIdenticalEffects = function(){
                        var elementEffect = elem.data("effect"),
                            containerEffect = elements.container.data("effect");

                        if (containerEffect === elementEffect) {
                            return true;
                        } else {
                            return false;
                        }
                    },
                toggleClasses = function(){
                    if (settings.container.hasClass(htmlClasses.containerActive)) {
                        //elem.removeClass(htmlClasses.activeCssClass);
                        elements.container.removeClass(htmlClasses.containerActive);
                        elements.buttonIcon.removeClass(settings.iconClose).addClass(settings.iconOpen);
                        elements.buttonText.text(settings.textOpen);
                    } else {
                        //elem.addClass(htmlClasses.activeCssClass);
                        elements.container.addClass(htmlClasses.containerActive);
                        elements.buttonIcon.removeClass(settings.iconOpen).addClass(settings.iconClose);
                        elements.buttonText.text(settings.textClose);
                    }
                };

            if (checkIdenticalEffects()) {
                time = 0;
            } else {
                time = 400;
            }

            setTimeout( function(time){
                toggleClasses();
            }, time);

            //Update effect data and class
            elements.container.removeClass(function () {
                return (settings.globalName + "--effect--" + $(this).data("effect"));
            });

            elements.container.addClass(settings.globalName + "--effect--" + checkEffect(elem));
            elements.container.data("effect", checkEffect(elem));
        },

        checkEffect = function(elem) {
            if (elem.data("effect").length > 0){
                return elem.data("effect");
            } else {
                return settings.effect;
            }
        };

        // Click event
        // =======================================================

        elements.button.click(function(e) {
            e.preventDefault();
            toggle( $(this) , checkEffect($(this)) );
        });

        //Refactor for use of overlay
        elements.overlay.click(function(e) {
            e.preventDefault();
            $(this).data("effect", checkEffect(settings.container));
            toggle( $(this), checkEffect(settings.container) );
        });

        // On load
        // Provide the correct effect class to the container.
        // =======================================================
        elements.container.addClass(htmlClasses.effect);
        elements.container.data("effect", settings.effect);
    };

}(jQuery));
<|endoftext|>


<commit_before>/*
File: workset_samples.js
URL: /static/js/workset_samples.js
Powers /workset/[workset] - template is run_dir/design/workset_samples.html
*/

// Get data attributes
var workset_name= $('#workset-js').attr('data-workset');

//globals are on purpose
var wsdata={};
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var margin_left=144;
var margin_top=112;
var interval=90;
var max_x=1144;
var max_y=742;

setupPlate();

$.getJSON("/api/v1/workset/"+workset_name, function(data) {

    // Fill in the main table with summary information
    var content="";
    wsdata= data[workset_name];
    //reused, needed as global
    lims_step=wsdata['id'];
    date_run=wsdata['date_run'];
    $('#date_run').html(date_run);
    $('#span_lims_step').html(''+lims_step+'');
    load_running_notes();
    load_links();

    if(wsdata && wsdata.hasOwnProperty("projects")){
        $.each(wsdata.projects, function(project_id, project_data){
            content+='
Project '+project_id+'
';
            content+='
 \

 \
                     Project name	 \
                     Application	 \
                     Library method	 \

 ';
            var samplesnb=Object.keys(project_data['samples']).length;
            content+="
 \
                    "+project_data['name']+"	\
                    "+project_data['application']+"	\
                    "+project_data['library']+"

";
        content+="
Samples
";
        content+='
 \

 \
                     Sample name	 \
                     Submitter Name	 \
                     Reception Control	 \
                     Library	 \
                     Sequencing	 \
                     Location	 \

 ';
        $.each(project_data.samples, function(sample_id, sample_data){
        updateSample(sample_id,sample_data)
        content+="
"+sample_id+"	 \
                  "+sample_data['customer_name']+"	 \
                  "+auto_format(sample_data['rec_ctrl']['status'])+"	 \
                ";
            $.each(sample_data.library, function(lib_id, lib_data){
                lims_id=lib_id.split("-")[1];
                content+=""+lib_id+' '+lib_data['date']+" "+auto_format(lib_data['status'])+"
";
            });
                content+="	";
            $.each(sample_data.sequencing, function(seq_id, seq_data){
                lims_id=seq_id.split("-")[1];
                content+=""+seq_id+' '+seq_data['date']+" "+auto_format(seq_data['status'])+"
";
            });
                content+='	'+sample_data['location']+"	 \

";

        });
        content+="

";
    });
    }
    $("#workset_tables").html(content);

    // Remove the loading text
    $('#loading_spinner').hide();
    $('#page_content').show();

    // console.log(data)
}).error(function(){
    // workset not found - probably
    $('#page_content').html('
Error - Workset Not Found

Oops! Sorry about that, we can\'t find the Workset '+workset_name+'
');

    // Remove the loading text
    $('#loading_spinner').hide();
    $('#page_content').show();
});

function setupPlate(){
    ctx.fillStyle = 'black';
    ctx.font = '12pt Calibri';
    ctx.textAlign = 'center';
    for (var i = margin_left; i <= max_x; i+=interval){
        ctx.fillText((i-margin_left)/interval+1, i, margin_top/2);
    }
    for (var i = margin_top; i <= max_y; i+=interval){
        ctx.fillText(String.fromCharCode((i-margin_top)/interval+65), margin_left/2, i);
    }

    for (var i = margin_left; i <= max_x; i+=interval){
        for (var j=margin_top; j<=max_y; j+=interval){
            drawWell(i, j, 'Unknown');
        }
    }
}
function drawWell(centerX, centerY, sid, stat ){
    ctx.beginPath();
    ctx.arc(centerX,centerY,32,0,2*Math.PI);

    if (stat == 'FAILED'){
        ctx.fillStyle="#f2dede";
        ctx.strokeStyle="#DDC4C4";
        ctx.fill()
        ctx.fillStyle="#a94442";
    }else if (stat == 'PASSED'){
        ctx.fillStyle="#dff0d8";
        ctx.strokeStyle="#B4DA95";
        ctx.fill()
        ctx.fillStyle="#3c763d";
    }else{
        ctx.fillStyle="white";
        ctx.strokeStyle="#CCCCCC";
        ctx.fill()
        ctx.fillStyle="#666666";
    }
    ctx.font = '10pt Calibri';
    ctx.textAlign = 'center';
    ctx.fillText(sid, centerX, centerY+4);

    ctx.stroke();
}
function updateSample(sample_id,sample_data, level){
    var positions=sample_data['location'].split(':');
    var positionX=parseInt(positions[1]-1)*interval+margin_left;
    var positionY=(positions[0].charCodeAt(0)-65)*interval+margin_top;
    if(level == 'seq'){
        drawWell(positionX, positionY, sample_id, sample_data['sequencing_status']);
    }else if (level == 'rec_ctrl'){
        drawWell(positionX, positionY, sample_id, sample_data['rec_ctrl']['status']);
    }else{
        drawWell(positionX, positionY, sample_id, sample_data['library_status']);
    }
}

$(document).ready(function(){
    $('.plate-type-button').click(function(){
        $(this).parent().siblings('li').removeClass('active');
        $(this).parent().addClass('active');
        var target = $(this).attr('href').substr(1);
        updatePlate(target);
    });
});

function updatePlate(level){
    setupPlate();
    if(wsdata.hasOwnProperty("projects")){
        $.each(wsdata.projects, function(project_id, project_data){
            $.each(project_data.samples, function(sample_id, sample_data){
                updateSample(sample_id, sample_data, level);
            });
        });
    }
}
<commit_msg>added more data to libs<commit_after>/*
File: workset_samples.js
URL: /static/js/workset_samples.js
Powers /workset/[workset] - template is run_dir/design/workset_samples.html
*/

// Get data attributes
var workset_name= $('#workset-js').attr('data-workset');

//globals are on purpose
var wsdata={};
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var margin_left=144;
var margin_top=112;
var interval=90;
var max_x=1144;
var max_y=742;

setupPlate();

$.getJSON("/api/v1/workset/"+workset_name, function(data) {

    // Fill in the main table with summary information
    var content="";
    wsdata= data[workset_name];
    //reused, needed as global
    lims_step=wsdata['id'];
    date_run=wsdata['date_run'];
    $('#date_run').html(date_run);
    $('#span_lims_step').html(''+lims_step+'');
    load_running_notes();
    load_links();

    if(wsdata && wsdata.hasOwnProperty("projects")){
        $.each(wsdata.projects, function(project_id, project_data){
            content+='
Project '+project_id+'
';
            content+='
 \

 \
                     Project name	 \
                     Application	 \
                     Library method	 \

 ';
            var samplesnb=Object.keys(project_data['samples']).length;
            content+="
 \
                    "+project_data['name']+"	\
                    "+project_data['application']+"	\
                    "+project_data['library']+"

";
        content+="
Samples
";
        content+='
 \

 \
                     Sample name	 \
                     Submitter Name	 \
                     Reception Control	 \
                     Library	 \
                     Sequencing	 \
                     Location	 \

 ';
        $.each(project_data.samples, function(sample_id, sample_data){
        updateSample(sample_id,sample_data)
        content+="
"+sample_id+"	 \
                  "+sample_data['customer_name']+"	 \
                  "+auto_format(sample_data['rec_ctrl']['status'])+"	 \
                ";
            $.each(sample_data.library, function(lib_id, lib_data){
                lims_id=lib_id.split("-")[1];
                content+="" ;
                content+=lib_data['date']+" "
                content+=auto_format(lib_data['status']);
                content+=" "+lib_data['concentration']+""
                content+=" "+lib_data['size']+"bp"
                content+="
";
            });
                content+="	";
            $.each(sample_data.sequencing, function(seq_id, seq_data){
                lims_id=seq_id.split("-")[1];
                content+=""+seq_id+' '+seq_data['date']+" "+auto_format(seq_data['status'])+"
";
            });
                content+='	'+sample_data['location']+"	 \

";

        });
        content+="

";
    });
    }
    $("#workset_tables").html(content);

    // Remove the loading text
    $('#loading_spinner').hide();
    $('#page_content').show();

    // console.log(data)
}).error(function(){
    // workset not found - probably
    $('#page_content').html('
Error - Workset Not Found

Oops! Sorry about that, we can\'t find the Workset '+workset_name+'
');

    // Remove the loading text
    $('#loading_spinner').hide();
    $('#page_content').show();
});

function setupPlate(){
    ctx.fillStyle = 'black';
    ctx.font = '12pt Calibri';
    ctx.textAlign = 'center';
    for (var i = margin_left; i <= max_x; i+=interval){
        ctx.fillText((i-margin_left)/interval+1, i, margin_top/2);
    }
    for (var i = margin_top; i <= max_y; i+=interval){
        ctx.fillText(String.fromCharCode((i-margin_top)/interval+65), margin_left/2, i);
    }

    for (var i = margin_left; i <= max_x; i+=interval){
        for (var j=margin_top; j<=max_y; j+=interval){
            drawWell(i, j, 'Unknown');
        }
    }
}
function drawWell(centerX, centerY, sid, stat ){
    ctx.beginPath();
    ctx.arc(centerX,centerY,32,0,2*Math.PI);

    if (stat == 'FAILED'){
        ctx.fillStyle="#f2dede";
        ctx.strokeStyle="#DDC4C4";
        ctx.fill()
        ctx.fillStyle="#a94442";
    }else if (stat == 'PASSED'){
        ctx.fillStyle="#dff0d8";
        ctx.strokeStyle="#B4DA95";
        ctx.fill()
        ctx.fillStyle="#3c763d";
    }else{
        ctx.fillStyle="white";
        ctx.strokeStyle="#CCCCCC";
        ctx.fill()
        ctx.fillStyle="#666666";
    }
    ctx.font = '10pt Calibri';
    ctx.textAlign = 'center';
    ctx.fillText(sid, centerX, centerY+4);

    ctx.stroke();
}
function updateSample(sample_id,sample_data, level){
    var positions=sample_data['location'].split(':');
    var positionX=parseInt(positions[1]-1)*interval+margin_left;
    var positionY=(positions[0].charCodeAt(0)-65)*interval+margin_top;
    if(level == 'seq'){
        drawWell(positionX, positionY, sample_id, sample_data['sequencing_status']);
    }else if (level == 'rec_ctrl'){
        drawWell(positionX, positionY, sample_id, sample_data['rec_ctrl']['status']);
    }else{
        drawWell(positionX, positionY, sample_id, sample_data['library_status']);
    }
}

$(document).ready(function(){
    $('.plate-type-button').click(function(){
        $(this).parent().siblings('li').removeClass('active');
        $(this).parent().addClass('active');
        var target = $(this).attr('href').substr(1);
        updatePlate(target);
    });
});

function updatePlate(level){
    setupPlate();
    if(wsdata.hasOwnProperty("projects")){
        $.each(wsdata.projects, function(project_id, project_data){
            $.each(project_data.samples, function(sample_id, sample_data){
                updateSample(sample_id, sample_data, level);
            });
        });
    }
}
<|endoftext|>


<commit_before>import Vue from "vue";
import $ from "jquery";
import { sprintf, vsprintf } from "sprintf-js";

function Subtitle(id, state, secondaryLang) {
  this.id = id;
  this.state = state;
  this.state.loaded = false;
  this.users = {};
  this.users[me.id] = {
    username: me.username,
    roles: me.roles,
  };
  this.secondaryLang = secondaryLang;
}

Subtitle.prototype.wsOpen = function() {
  if (this.state.loadedOnce) {
    Toasts.info.fire("Reconectado al servidor de traducción");
  }

  this.loadSequences();
  this.loadComments();
};

Subtitle.prototype.wsError = function() {
  this.state.loaded = false;
};

Subtitle.prototype.wsMessage = function(event) {
  try {
    let data = JSON.parse(event.data);
    switch (data.type) {
      case "seq-open":
        this.openSeq(data.num, data.user, data.openLockID);
        break;

      case "seq-close":
        this.closeSeq(data.num);
        break;

      case "seq-change":
        this.changeSeq(data.num, Number(data.nid), data.user, data.ntext, data.ntstart, data.ntend);
        break;

      case "seq-lock":
        this.lockSeq(data.id, data.status);
        break;

      case "seq-del":
        this.deleteSeq(data.id);
        break;

      case "com-new":
        this.addComment(data.id, this.getUserObject(data.user), data.time, data.text);
        if (data.user != me.id) {
          Toasts.info.fire(
            sprintf("%s ha publicado un comentario", this.getUsername(data.user))
          );
        }
        break;

      case "com-del":
        this.deleteComment(data.id);
        break;

      case "com-pin":
        this.setCommentPin(data.id, data.pinned);
        break;

      case "uinfo":
        this.users[data.id] = {
          username: data.username,
          roles: data.roles,
        };

        break;
    }
  } catch (e) {
    console.error(e);
  }
};

Subtitle.prototype.getUserObject = function(uid) {
  return {
    id: uid,
    username: this.getUsername(uid),
    roles: this.getRoles(uid),
  };
};

Subtitle.prototype.getRoles = function(uid) {
  return this.users[uid] ? this.users[uid].roles : [""];
};

Subtitle.prototype.getUsername = function(uid) {
  return this.users[uid] ? this.users[uid].username : "u#" + uid;
};

Subtitle.prototype.loadSequences = function () {
  function decode(s) {
    return s.replace(/[a-zA-Z]/g, function (c) { return String.fromCharCode((c <= "Z" ? 90 : 122) >= (c = c.charCodeAt(0) + 13) ? c : c - 26); })
  }

  $.ajax({
    url: "/subtitles/" + subID + "/translate/load",
    method: "GET",
    data: {
      secondaryLang: this.secondaryLang,
    },
  }).done(reply => {
    let sequenceList = reply.sequences;
    let userList = reply.users;
    let isFirstLoad = !this.state.loadedOnce;

    this.state.loaded = true;
    this.state.loadedOnce = true;
    Object.keys(sequenceList).forEach(k => {
      let seq = sequenceList[k];
      seq.text = decode(seq.text);
      seq.secondary_text = decode(seq.secondary_text);

      let idx = this.findSeqIdxByNum(seq.number);
      if (idx == -1) {
        this.state.sequences.push(seq);
      } else {
        let existingSeq = this.state.sequences[idx];
        if (seq.id != existingSeq.id) {
          existingSeq.id = seq.id;
          existingSeq.text = seq.text;
          existingSeq.author = seq.author;
          existingSeq.openInfo = seq.openInfo;
        }
      }
    });

    Object.keys(userList).forEach(uid => {
      if (!this.users[uid]) {
        this.users[uid] = userList[uid];
      }
    });

    if (isFirstLoad) {
      this.state.curPage = 1;
    }

    if (window.location.hash) {
      this.state.jumpToUrlSequence();
    }
  });
};

Subtitle.prototype.loadComments = function() {
  $.ajax({
    url: "/subtitles/" + subID + "/translate/comments",
    method: "GET",
  })
    .done(reply => {
      this.state.comments = reply;
    })
    .fail(function() {
      Toasts.error.fire("Ha ocurrido un error tratando de cargar los comentarios");
    });
};

Subtitle.prototype.findSeqIdxByID = function(seqID) {
  let key = -1;
  Object.keys(this.state.sequences).forEach(k => {
    if (this.state.sequences[k].id == seqID) {
      key = k;
      return;
    }
  });

  return key;
};

Subtitle.prototype.findSeqIdxByNum = function(seqNum) {
  let key = -1;
  Object.keys(this.state.sequences).forEach(k => {
    if (this.state.sequences[k].number == seqNum) {
      key = k;
      return;
    }
  });

  return key;
};

Subtitle.prototype.openSeq = function(seqNum, by, openLockID) {
  let idx = this.findSeqIdxByNum(seqNum);
  if (idx < 0) {
    console.error("Could not open sequence " + seqNum + " (not found)");
    return;
  }

  let openInfo = {
    lockID: openLockID,
    by: by,
    since: new Date().toISOString(),
  };

  this.state.sequences[idx].openInfo = openInfo;
};

Subtitle.prototype.closeSeq = function(seqNum) {
  let idx = this.findSeqIdxByNum(seqNum);
  if (idx < 0) {
    console.log("Could not close sequence " + seqNum + " (not found)");
    return;
  }

  this.state.sequences[idx].openInfo = null;
};

Subtitle.prototype.lockSeq = function(seqID, status) {
  let idx = this.findSeqIdxByID(seqID);
  if (idx < 0) {
    console.log("Could not set lock status for sequence " + seqID + " (not found)");
    return;
  }

  this.state.sequences[idx].locked = status;
};

Subtitle.prototype.deleteSeq = function(seqID, status) {
  const removeFn = (s, hkey, replaceMain) => {
    if (hkey >= 0) {
      if (replaceMain && hkey == s.history.length - 1) {
        // We also have to replace the actual sequence
        let ns = s.history[hkey];
        s.id = ns.id;
        s.tstart = ns.tstart;
        s.tend = ns.tend;
        s.text = ns.text;
        s.author = ns.author;
      }

      s.history.splice(hkey, 1);
    } else {
      s.id = 0;
      s.author = 0;
      s.text = "";
    }
  };

  Object.keys(this.state.sequences).forEach(k => {
    let s = this.state.sequences[k];
    if (s.id == seqID) {
      removeFn(s, s.history ? s.history.length - 1 : -1, true);
    } else if (s.history && s.history.length > 0) {
      s.history.forEach((hs, hkey) => {
        if (hs.id == seqID) {
          removeFn(s, hkey, false);
        }
      });
    }
  });
};

Subtitle.prototype.changeSeq = function(
  seqNum,
  newSeqID,
  newAuthorID,
  newText,
  newStartTime,
  newEndTime
) {
  let idx = this.findSeqIdxByNum(seqNum);
  if (idx < 0) {
    console.log("Could not update sequence text for " + seqNum + " (not found)");
    return;
  }

  let seq = this.state.sequences[idx];

  if (seq.id) {
    if (seq.id == newSeqID) {
      // We already did this change!
      console.log("Ignoring update, no change for " + seqNum);
      return;
    }

    // Since we're overwriting a sequence, save current to history
    if (!seq.history) {
      seq.history = [];
    }

    seq.history.push({
      id: seq.id,
      author: seq.author,
      text: seq.text,
      tstart: seq.tstart,
      tend: seq.tend,
    });
  }

  seq.id = newSeqID;
  seq.text = newText;
  seq.author = newAuthorID;
  seq.tstart = newStartTime;
  seq.tend = newEndTime;
  seq.openInfo = null;
};

Subtitle.prototype.getDataByNum = function(seqNum) {
  let idx = this.findSeqIdxByNum(seqNum);
  if (idx < 0) {
    console.error("Could not find sequence " + seqNum + " to retrieve its data");
    return;
  }

  return this.state.sequences[idx];
};

Subtitle.prototype.addComment = function(id, user, published_at, text) {
  let exists = this.state.comments.some(c => {
    return c.id == id;
  });

  if (!exists) {
    this.state.comments.push({
      id: id,
      user: user,
      published_at: published_at,
      text: text,
      pinned: false,
    });

    this.sortComments();
  }
};

Subtitle.prototype.sortComments = function() {
  this.state.comments.sort((a, b) => {
    if (a.published_at  b.published_at) {
      if (!a.pinned && b.pinned) {
        // Pinned ones have preference
        return 1;
      }
      return -1;
    }

    return 0;
  });
};

Subtitle.prototype.deleteComment = function(id) {
  let idx = this.state.comments.findIndex(c => {
    return c.id == id;
  });

  if (idx != -1) {
    this.state.comments.splice(idx, 1);
  }
};

Subtitle.prototype.setCommentPin = function(id, pinned) {
  let idx = this.state.comments.findIndex(c => {
    return c.id == id;
  });

  if (idx != -1) {
    this.state.comments[idx].pinned = pinned;
    this.sortComments();
  }
};

module.exports = Subtitle;
<commit_msg>Fix missing space when publishing a comment<commit_after>import Vue from "vue";
import $ from "jquery";
import { sprintf, vsprintf } from "sprintf-js";

function Subtitle(id, state, secondaryLang) {
  this.id = id;
  this.state = state;
  this.state.loaded = false;
  this.users = {};
  this.users[me.id] = {
    username: me.username,
    roles: me.roles,
  };
  this.secondaryLang = secondaryLang;
}

Subtitle.prototype.wsOpen = function() {
  if (this.state.loadedOnce) {
    Toasts.info.fire("Reconectado al servidor de traducción");
  }

  this.loadSequences();
  this.loadComments();
};

Subtitle.prototype.wsError = function() {
  this.state.loaded = false;
};

Subtitle.prototype.wsMessage = function(event) {
  try {
    let data = JSON.parse(event.data);
    switch (data.type) {
      case "seq-open":
        this.openSeq(data.num, data.user, data.openLockID);
        break;

      case "seq-close":
        this.closeSeq(data.num);
        break;

      case "seq-change":
        this.changeSeq(data.num, Number(data.nid), data.user, data.ntext, data.ntstart, data.ntend);
        break;

      case "seq-lock":
        this.lockSeq(data.id, data.status);
        break;

      case "seq-del":
        this.deleteSeq(data.id);
        break;

      case "com-new":
        this.addComment(data.id, this.getUserObject(data.user), data.time, data.text);
        if (data.user != me.id) {
          Toasts.info.fire(
            sprintf("%s ha publicado un comentario", this.getUsername(data.user))
          );
        }
        break;

      case "com-del":
        this.deleteComment(data.id);
        break;

      case "com-pin":
        this.setCommentPin(data.id, data.pinned);
        break;

      case "uinfo":
        this.users[data.id] = {
          username: data.username,
          roles: data.roles,
        };

        break;
    }
  } catch (e) {
    console.error(e);
  }
};

Subtitle.prototype.getUserObject = function(uid) {
  return {
    id: uid,
    username: this.getUsername(uid),
    roles: this.getRoles(uid),
  };
};

Subtitle.prototype.getRoles = function(uid) {
  return this.users[uid] ? this.users[uid].roles : [""];
};

Subtitle.prototype.getUsername = function(uid) {
  return this.users[uid] ? this.users[uid].username : "u#" + uid;
};

Subtitle.prototype.loadSequences = function () {
  function decode(s) {
    return s.replace(/[a-zA-Z]/g, function (c) { return String.fromCharCode((c <= "Z" ? 90 : 122) >= (c = c.charCodeAt(0) + 13) ? c : c - 26); })
  }

  $.ajax({
    url: "/subtitles/" + subID + "/translate/load",
    method: "GET",
    data: {
      secondaryLang: this.secondaryLang,
    },
  }).done(reply => {
    let sequenceList = reply.sequences;
    let userList = reply.users;
    let isFirstLoad = !this.state.loadedOnce;

    this.state.loaded = true;
    this.state.loadedOnce = true;
    Object.keys(sequenceList).forEach(k => {
      let seq = sequenceList[k];
      seq.text = decode(seq.text);
      seq.secondary_text = decode(seq.secondary_text);

      let idx = this.findSeqIdxByNum(seq.number);
      if (idx == -1) {
        this.state.sequences.push(seq);
      } else {
        let existingSeq = this.state.sequences[idx];
        if (seq.id != existingSeq.id) {
          existingSeq.id = seq.id;
          existingSeq.text = seq.text;
          existingSeq.author = seq.author;
          existingSeq.openInfo = seq.openInfo;
        }
      }
    });

    Object.keys(userList).forEach(uid => {
      if (!this.users[uid]) {
        this.users[uid] = userList[uid];
      }
    });

    if (isFirstLoad) {
      this.state.curPage = 1;
    }

    if (window.location.hash) {
      this.state.jumpToUrlSequence();
    }
  });
};

Subtitle.prototype.loadComments = function() {
  $.ajax({
    url: "/subtitles/" + subID + "/translate/comments",
    method: "GET",
  })
    .done(reply => {
      this.state.comments = reply;
    })
    .fail(function() {
      Toasts.error.fire("Ha ocurrido un error tratando de cargar los comentarios");
    });
};

Subtitle.prototype.findSeqIdxByID = function(seqID) {
  let key = -1;
  Object.keys(this.state.sequences).forEach(k => {
    if (this.state.sequences[k].id == seqID) {
      key = k;
      return;
    }
  });

  return key;
};

Subtitle.prototype.findSeqIdxByNum = function(seqNum) {
  let key = -1;
  Object.keys(this.state.sequences).forEach(k => {
    if (this.state.sequences[k].number == seqNum) {
      key = k;
      return;
    }
  });

  return key;
};

Subtitle.prototype.openSeq = function(seqNum, by, openLockID) {
  let idx = this.findSeqIdxByNum(seqNum);
  if (idx < 0) {
    console.error("Could not open sequence " + seqNum + " (not found)");
    return;
  }

  let openInfo = {
    lockID: openLockID,
    by: by,
    since: new Date().toISOString(),
  };

  this.state.sequences[idx].openInfo = openInfo;
};

Subtitle.prototype.closeSeq = function(seqNum) {
  let idx = this.findSeqIdxByNum(seqNum);
  if (idx < 0) {
    console.log("Could not close sequence " + seqNum + " (not found)");
    return;
  }

  this.state.sequences[idx].openInfo = null;
};

Subtitle.prototype.lockSeq = function(seqID, status) {
  let idx = this.findSeqIdxByID(seqID);
  if (idx < 0) {
    console.log("Could not set lock status for sequence " + seqID + " (not found)");
    return;
  }

  this.state.sequences[idx].locked = status;
};

Subtitle.prototype.deleteSeq = function(seqID, status) {
  const removeFn = (s, hkey, replaceMain) => {
    if (hkey >= 0) {
      if (replaceMain && hkey == s.history.length - 1) {
        // We also have to replace the actual sequence
        let ns = s.history[hkey];
        s.id = ns.id;
        s.tstart = ns.tstart;
        s.tend = ns.tend;
        s.text = ns.text;
        s.author = ns.author;
      }

      s.history.splice(hkey, 1);
    } else {
      s.id = 0;
      s.author = 0;
      s.text = "";
    }
  };

  Object.keys(this.state.sequences).forEach(k => {
    let s = this.state.sequences[k];
    if (s.id == seqID) {
      removeFn(s, s.history ? s.history.length - 1 : -1, true);
    } else if (s.history && s.history.length > 0) {
      s.history.forEach((hs, hkey) => {
        if (hs.id == seqID) {
          removeFn(s, hkey, false);
        }
      });
    }
  });
};

Subtitle.prototype.changeSeq = function(
  seqNum,
  newSeqID,
  newAuthorID,
  newText,
  newStartTime,
  newEndTime
) {
  let idx = this.findSeqIdxByNum(seqNum);
  if (idx < 0) {
    console.log("Could not update sequence text for " + seqNum + " (not found)");
    return;
  }

  let seq = this.state.sequences[idx];

  if (seq.id) {
    if (seq.id == newSeqID) {
      // We already did this change!
      console.log("Ignoring update, no change for " + seqNum);
      return;
    }

    // Since we're overwriting a sequence, save current to history
    if (!seq.history) {
      seq.history = [];
    }

    seq.history.push({
      id: seq.id,
      author: seq.author,
      text: seq.text,
      tstart: seq.tstart,
      tend: seq.tend,
    });
  }

  seq.id = newSeqID;
  seq.text = newText;
  seq.author = newAuthorID;
  seq.tstart = newStartTime;
  seq.tend = newEndTime;
  seq.openInfo = null;
};

Subtitle.prototype.getDataByNum = function(seqNum) {
  let idx = this.findSeqIdxByNum(seqNum);
  if (idx < 0) {
    console.error("Could not find sequence " + seqNum + " to retrieve its data");
    return;
  }

  return this.state.sequences[idx];
};

Subtitle.prototype.addComment = function(id, user, published_at, text) {
  let exists = this.state.comments.some(c => {
    return c.id == id;
  });

  if (!exists) {
    this.state.comments.push({
      id: id,
      user: user,
      published_at: published_at,
      text: text,
      pinned: false,
    });

    this.sortComments();
  }
};

Subtitle.prototype.sortComments = function() {
  this.state.comments.sort((a, b) => {
    if (a.published_at  b.published_at) {
      if (!a.pinned && b.pinned) {
        // Pinned ones have preference
        return 1;
      }
      return -1;
    }

    return 0;
  });
};

Subtitle.prototype.deleteComment = function(id) {
  let idx = this.state.comments.findIndex(c => {
    return c.id == id;
  });

  if (idx != -1) {
    this.state.comments.splice(idx, 1);
  }
};

Subtitle.prototype.setCommentPin = function(id, pinned) {
  let idx = this.state.comments.findIndex(c => {
    return c.id == id;
  });

  if (idx != -1) {
    this.state.comments[idx].pinned = pinned;
    this.sortComments();
  }
};

module.exports = Subtitle;
<|endoftext|>


<commit_before>if (!window.Q) { // You can remove this part after you've run install.php
	document.getElementsByTagName('body')[0].innerHTML = "
Please run MyApp/scripts/Q/install.php --all
";
	throw "Q is not defined";
}

var MyApp = (function (Q, $) {

	// Here is some example code to get you started

	var MyApp = {
		userContextual: function (item) {
			var action = $(item).attr('data-action');
			if (MyApp.actions[action]) {
				Q.handle(MyApp.actions[action], MyApp, [item]);
			}
		},
		actions: {
			logout: Q.Users.logout,
			setIdentifier: Q.Users.setIdentifier
		}
	};

	// The following code is for all pages.
	// For specific pages, see web/js/pages directory.
	Q.page('', function () {

		$('.MyApp_login').on(Q.Pointer.click, function () {
			Q.Users.login();
			return false;
		});

		Q.addScript("{{Q}}/js/QTools.js", function () {
			$('#dashboard .Users_avatar_tool').plugin('Q/contextual', {
				elements: $('#dashboard_user_contextual .Q_listing').clone().children(),
				defaultHandler: MyApp.userContextual
			});
		});

		// For example, we can hide notices when the user clicks/taps on them
		$('#notices li').on(Q.Pointer.fastclick, true, function () {
			var $this = $(this), key;
			$this.css('min-height', 0)
			.slideUp(300, function () {
				$(this).remove();
				if (!$('#notices li').length) {
					$('#notices_slot').empty();
				}
				Q.layout();
			});
			if (key = $this.attr('data-key')) {
				Q.req('Q/notice', 'data', null, {
					method: 'delete',
					fields: {key: key}
				});
			}
		}).css('cursor', 'pointer');

	}, 'MyApp');

	// example stream
	Q.Streams.define("MyApp/cool", "js/streams/cool.js");

	// example tool
	Q.Tool.define("MyApp/cool", "js/tools/cool.js");

	// tell Q.handle to load pages using AJAX - much smoother
	Q.handle.options.loadUsingAjax = true;

	// make the app feel more native on touch devices
	Q.Pointer.preventRubberBand({
		direction: 'vertical'
	});
	Q.Pointer.startBlurringOnTouch();

	// set some options
	if (Q.info.isTouchscreen) {
		Q.Tool.jQuery.options("Q/clickable", {
			press: { size: 1.5 },
			release: { size: 3 },
			shadow: null
		});
	}

	Q.extend(Q.Tool.jQuery.loadAtStart, [
		'Q/clickfocus',
		'Q/contextual',
		'Q/scrollIndicators',
		'Q/iScroll',
		'Q/scroller',
		'Q/touchscroll'
	]);

	return MyApp;

})(Q, jQuery);<commit_msg>Added to MyApp.js code related to iOS cordova applications.<commit_after>if (!window.Q) { // You can remove this part after you've run install.php
	document.getElementsByTagName('body')[0].innerHTML = "
Please run MyApp/scripts/Q/install.php --all
";
	throw "Q is not defined";
}

var MyApp = (function (Q, $) {

	// Here is some example code to get you started

	var MyApp = {
		userContextual: function (item) {
			var action = $(item).attr('data-action');
			if (MyApp.actions[action]) {
				Q.handle(MyApp.actions[action], MyApp, [item]);
			}
		},
		actions: {
			logout: Q.Users.logout,
			setIdentifier: Q.Users.setIdentifier
		}
	};

	// The following code is for all pages.
	// For specific pages, see web/js/pages directory.
	Q.page('', function () {

		$('.MyApp_login').on(Q.Pointer.click, function () {
			Q.Users.login();
			return false;
		});

		Q.addScript("{{Q}}/js/QTools.js", function () {
			$('#dashboard .Users_avatar_tool').plugin('Q/contextual', {
				elements: $('#dashboard_user_contextual .Q_listing').clone().children(),
				defaultHandler: MyApp.userContextual
			});
		});

		// For example, we can hide notices when the user clicks/taps on them
		$('#notices li').on(Q.Pointer.fastclick, true, function () {
			var $this = $(this), key;
			$this.css('min-height', 0)
			.slideUp(300, function () {
				$(this).remove();
				if (!$('#notices li').length) {
					$('#notices_slot').empty();
				}
				Q.layout();
			});
			if (key = $this.attr('data-key')) {
				Q.req('Q/notice', 'data', null, {
					method: 'delete',
					fields: {key: key}
				});
			}
		}).css('cursor', 'pointer');

		// iOS related code
		if (navigator.splashscreen) {
			navigator.splashscreen.hide();
		}
	}, 'MyApp');

	// example stream
	Q.Streams.define("MyApp/cool", "js/streams/cool.js");

	// example tool
	Q.Tool.define("MyApp/cool", "js/tools/cool.js");

	// tell Q.handle to load pages using AJAX - much smoother
	Q.handle.options.loadUsingAjax = true;

	// make the app feel more native on touch devices
	Q.Pointer.preventRubberBand({
		direction: 'vertical'
	});
	Q.Pointer.startBlurringOnTouch();

	// set some options
	if (Q.info.isTouchscreen) {
		Q.Tool.jQuery.options("Q/clickable", {
			press: { size: 1.5 },
			release: { size: 3 },
			shadow: null
		});
	}

	Q.extend(Q.Tool.jQuery.loadAtStart, [
		'Q/clickfocus',
		'Q/contextual',
		'Q/scrollIndicators',
		'Q/iScroll',
		'Q/scroller',
		'Q/touchscroll'
	]);

	return MyApp;

})(Q, jQuery);<|endoftext|>


<commit_before>import React from 'react';

import app from '../Firebase/';
import Button from 'react-bootstrap/Button';

import * as DB from '../../constants/database.js';

const db = app.firestore();

/**
 * Component used to delete a Trip.
 *
 *
 * @param {Object} props These are the props for this component:
 * - tripId: Document ID for the current Trip document.
 * - refreshTripsContainer: Handler that refreshes the TripsContainer
 *        component upon trip creation (Remove when fix Issue #62).
 */
const DeleteTripsButton = (props) => {
  /**
   * Deletes documents in query with a batch delete.
   */
  async function deleteQueryBatch(db, query, resolve) {
    const snapshot = await query.get();

    const batchSize = snapshot.size;
    if (batchSize === 0) {
      // When there are no documents left, we are done
      resolve();
      return;
    }

    // Delete documents in a batch
    const batch = db.batch();
    snapshot.docs.forEach((doc) => {
      batch.delete(doc.ref);
    });
    await batch.commit();

    // Recurse on the next process tick, to avoid
    // exploding the stack.
    process.nextTick(() => {
      deleteQueryBatch(db, query, resolve);
    });
  }

  /**
   * Deletes a trips subcollection of activities corrsponding to the
   * `tripId` prop.
   *
   * TODO(Issue #81): Consider deleting data with callabable cloud function
   * https://firebase.google.com/docs/firestore/solutions/delete-collections
   */
  async function deleteTripActivities() {
    const query = db.collection(DB.COLLECTION_TRIPS)
        .doc(props.tripId)
        .collection(DB.COLLECTION_ACTIVITIES)
        .orderBy(DB.ACTIVITIES_TITLE)
        .limit(3);

    return new Promise((resolve, reject) => {
      deleteQueryBatch(db, query, resolve).catch(reject);
    });
  }

  /**
   * Deletes a trip and its subcollection of activities corrsponding to the
   * `tripId` prop and then refreshes the TripsContainer component.
   * TODO(Issue #62): Remove refreshTripsContainer.
   */
  async function deleteTrip() {
    await deleteTripActivities();

    db.collection(DB.COLLECTION_TRIPS)
        .doc(props.tripId)
        .delete()
        .then(() => {
          console.log("Document successfully deleted with id: ", props.tripId);
        }).catch(error => {
          console.error("Error removing document: ", error);
        });

    props.refreshTripsContainer();
  }

  return (
    <Button
      type='button'
      variant='primary'
      onClick={deleteTrip} >
      Delete Trip
    </Button>
  );
}

export default DeleteTripsButton;
<commit_msg>Add documentation in delete-trip-button.<commit_after>import React from 'react';

import app from '../Firebase/';
import Button from 'react-bootstrap/Button';

import * as DB from '../../constants/database.js';

const db = app.firestore();

/**
 * Component used to delete a Trip.
 *
 *
 * @param {Object} props These are the props for this component:
 * - tripId: Document ID for the current Trip document.
 * - refreshTripsContainer: Handler that refreshes the TripsContainer
 *        component upon trip creation (Remove when fix Issue #62).
 */
const DeleteTripsButton = (props) => {
   /**
   * Deletes documents in query with a batch delete.
   *
   * This was derived from the delete collection snippets in the documentation
   * at https://firebase.google.com/docs/firestore/manage-data/delete-data.
    *
    * @param {firebase.firestore.Firestore} db Firestore database instance.
    * @param {firebase.firestore.Query} query Query containing documents from
    *     the activities subcollection of a trip documents.
    * @param {Function} resolve Resolve function that returns a void Promise.
    */
  async function deleteQueryBatch(db, query, resolve) {
    const snapshot = await query.get();

    const batchSize = snapshot.size;
    if (batchSize === 0) {
      // When there are no documents left, we are done
      resolve();
      return;
    }

    // Delete documents in a batch
    const batch = db.batch();
    snapshot.docs.forEach((doc) => {
      batch.delete(doc.ref);
    });
    await batch.commit();

    // Recurse on the next process tick, to avoid
    // exploding the stack.
    process.nextTick(() => {
      deleteQueryBatch(db, query, resolve);
    });
  }

  /**
   * Deletes a trips subcollection of activities corrsponding to the
   * `tripId` prop.
   *
   * This was derived from the delete collection snippets in the documentation
   * at https://firebase.google.com/docs/firestore/manage-data/delete-data.
   *
   * TODO(Issue #81): Consider deleting data with callabable cloud function
   * https://firebase.google.com/docs/firestore/solutions/delete-collections
   */
  async function deleteTripActivities() {
    const query = db.collection(DB.COLLECTION_TRIPS)
        .doc(props.tripId)
        .collection(DB.COLLECTION_ACTIVITIES)
        .orderBy(DB.ACTIVITIES_TITLE)
        .limit(3);

    return new Promise((resolve, reject) => {
      deleteQueryBatch(db, query, resolve).catch(reject);
    });
  }

  /**
   * Deletes a trip and its subcollection of activities corrsponding to the
   * `tripId` prop and then refreshes the TripsContainer component.
   * TODO(Issue #62): Remove refreshTripsContainer.
   */
  async function deleteTrip() {
    await deleteTripActivities();

    db.collection(DB.COLLECTION_TRIPS)
        .doc(props.tripId)
        .delete()
        .then(() => {
          console.log("Document successfully deleted with id: ", props.tripId);
        }).catch(error => {
          console.error("Error removing document: ", error);
        });

    props.refreshTripsContainer();
  }

  return (
    <Button
      type='button'
      variant='primary'
      onClick={deleteTrip} >
      Delete Trip
    </Button>
  );
}

export default DeleteTripsButton;
<|endoftext|>


<commit_before>/*
Copyright 2019 The Matrix.org Foundation C.I.C.

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 Modal from './Modal';
import * as sdk from './index';
import {MatrixClientPeg} from './MatrixClientPeg';
import { deriveKey } from 'matrix-js-sdk/src/crypto/key_passphrase';
import { decodeRecoveryKey } from 'matrix-js-sdk/src/crypto/recoverykey';
import { _t } from './languageHandler';
import SettingsStore from './settings/SettingsStore';

// This stores the secret storage private keys in memory for the JS SDK. This is
// only meant to act as a cache to avoid prompting the user multiple times
// during the same single operation. Use `accessSecretStorage` below to scope a
// single secret storage operation, as it will clear the cached keys once the
// operation ends.
let secretStorageKeys = {};
let secretStorageBeingAccessed = false;

function isCachingAllowed() {
    return (
        secretStorageBeingAccessed ||
        SettingsStore.getValue("keepSecretStoragePassphraseForSession")
    );
}

export class AccessCancelledError extends Error {
    constructor() {
        super("Secret storage access canceled");
    }
}

async function getSecretStorageKey({ keys: keyInfos }) {
    const keyInfoEntries = Object.entries(keyInfos);
    if (keyInfoEntries.length > 1) {
        throw new Error("Multiple storage key requests not implemented");
    }
    const [name, info] = keyInfoEntries[0];

    // Check the in-memory cache
    if (isCachingAllowed() && secretStorageKeys[name]) {
        return [name, secretStorageKeys[name]];
    }

    const inputToKey = async ({ passphrase, recoveryKey }) => {
        if (passphrase) {
            return deriveKey(
                passphrase,
                info.passphrase.salt,
                info.passphrase.iterations,
            );
        } else {
            return decodeRecoveryKey(recoveryKey);
        }
    };
    const AccessSecretStorageDialog =
        sdk.getComponent("dialogs.secretstorage.AccessSecretStorageDialog");
    const { finished } = Modal.createTrackedDialog("Access Secret Storage dialog", "",
        AccessSecretStorageDialog,
        {
            keyInfo: info,
            checkPrivateKey: async (input) => {
                const key = await inputToKey(input);
                return MatrixClientPeg.get().checkSecretStoragePrivateKey(key, info.pubkey);
            },
        },
    );
    const [input] = await finished;
    if (!input) {
        throw new AccessCancelledError();
    }
    const key = await inputToKey(input);

    // Save to cache to avoid future prompts in the current session
    if (isCachingAllowed()) {
        secretStorageKeys[name] = key;
    }

    return [name, key];
}

export const crossSigningCallbacks = {
    getSecretStorageKey,
};

/**
 * This helper should be used whenever you need to access secret storage. It
 * ensures that secret storage (and also cross-signing since they each depend on
 * each other in a cycle of sorts) have been bootstrapped before running the
 * provided function.
 *
 * Bootstrapping secret storage may take one of these paths:
 * 1. Create secret storage from a passphrase and store cross-signing keys
 *    in secret storage.
 * 2. Access existing secret storage by requesting passphrase and accessing
 *    cross-signing keys as needed.
 * 3. All keys are loaded and there's nothing to do.
 *
 * Additionally, the secret storage keys are cached during the scope of this function
 * to ensure the user is prompted only once for their secret storage
 * passphrase. The cache is then cleared once the provided function completes.
 *
 * @param {Function} [func] An operation to perform once secret storage has been
 * bootstrapped. Optional.
 */
export async function accessSecretStorage(func = async () => { }) {
    const cli = MatrixClientPeg.get();
    secretStorageBeingAccessed = true;

    try {
        if (!await cli.hasSecretStorageKey()) {
            // This dialog calls bootstrap itself after guiding the user through
            // passphrase creation.
            const { finished } = Modal.createTrackedDialogAsync('Create Secret Storage dialog', '',
                import("./async-components/views/dialogs/secretstorage/CreateSecretStorageDialog"),
                null, null, /* priority = */ false, /* static = */ true,
            );
            const [confirmed] = await finished;
            if (!confirmed) {
                throw new Error("Secret storage creation canceled");
            }
        } else {
            const InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog");
            await cli.bootstrapSecretStorage({
                authUploadDeviceSigningKeys: async (makeRequest) => {
                    const { finished } = Modal.createTrackedDialog(
                        'Cross-signing keys dialog', '', InteractiveAuthDialog,
                        {
                            title: _t("Setting up keys"),
                            matrixClient: MatrixClientPeg.get(),
                            makeRequest,
                        },
                    );
                    const [confirmed] = await finished;
                    if (!confirmed) {
                        throw new Error("Cross-signing key upload auth canceled");
                    }
                },
            });
        }

        // `return await` needed here to ensure `finally` block runs after the
        // inner operation completes.
        return await func();
    } finally {
        // Clear secret storage key cache now that work is complete
        secretStorageBeingAccessed = false;
        if (!isCachingAllowed()) {
            secretStorageKeys = {};
        }
    }
}
<commit_msg>confirm to close the passphrase dialog if it was done by backgroundClick<commit_after>/*
Copyright 2019 The Matrix.org Foundation C.I.C.

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 Modal from './Modal';
import * as sdk from './index';
import {MatrixClientPeg} from './MatrixClientPeg';
import { deriveKey } from 'matrix-js-sdk/src/crypto/key_passphrase';
import { decodeRecoveryKey } from 'matrix-js-sdk/src/crypto/recoverykey';
import { _t } from './languageHandler';
import SettingsStore from './settings/SettingsStore';

// This stores the secret storage private keys in memory for the JS SDK. This is
// only meant to act as a cache to avoid prompting the user multiple times
// during the same single operation. Use `accessSecretStorage` below to scope a
// single secret storage operation, as it will clear the cached keys once the
// operation ends.
let secretStorageKeys = {};
let secretStorageBeingAccessed = false;

function isCachingAllowed() {
    return (
        secretStorageBeingAccessed ||
        SettingsStore.getValue("keepSecretStoragePassphraseForSession")
    );
}

export class AccessCancelledError extends Error {
    constructor() {
        super("Secret storage access canceled");
    }
}

async function getSecretStorageKey({ keys: keyInfos }) {
    const keyInfoEntries = Object.entries(keyInfos);
    if (keyInfoEntries.length > 1) {
        throw new Error("Multiple storage key requests not implemented");
    }
    const [name, info] = keyInfoEntries[0];

    // Check the in-memory cache
    if (isCachingAllowed() && secretStorageKeys[name]) {
        return [name, secretStorageKeys[name]];
    }

    const inputToKey = async ({ passphrase, recoveryKey }) => {
        if (passphrase) {
            return deriveKey(
                passphrase,
                info.passphrase.salt,
                info.passphrase.iterations,
            );
        } else {
            return decodeRecoveryKey(recoveryKey);
        }
    };
    const AccessSecretStorageDialog =
        sdk.getComponent("dialogs.secretstorage.AccessSecretStorageDialog");
    const { finished } = Modal.createTrackedDialog("Access Secret Storage dialog", "",
        AccessSecretStorageDialog,
        /* props= */
        {
            keyInfo: info,
            checkPrivateKey: async (input) => {
                const key = await inputToKey(input);
                return MatrixClientPeg.get().checkSecretStoragePrivateKey(key, info.pubkey);
            },
        },
        /* className= */ null,
        /* isPriorityModal= */ false,
        /* isStaticModal= */ false,
        /* options= */ {
            onBeforeClose: async (reason) => {
                if (reason !== "backgroundClick") {
                    return true;
                }
                const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
                const [sure] = await Modal.createDialog(QuestionDialog, {
                    title: _t("Cancel entering passphrase?"),
                    description: _t("If you cancel now, you won't complete your SSSS operation!"),
                }).finished;
                return sure;
            },
        },
    );
    const [input] = await finished;
    if (!input) {
        throw new AccessCancelledError();
    }
    const key = await inputToKey(input);

    // Save to cache to avoid future prompts in the current session
    if (isCachingAllowed()) {
        secretStorageKeys[name] = key;
    }

    return [name, key];
}

export const crossSigningCallbacks = {
    getSecretStorageKey,
};

/**
 * This helper should be used whenever you need to access secret storage. It
 * ensures that secret storage (and also cross-signing since they each depend on
 * each other in a cycle of sorts) have been bootstrapped before running the
 * provided function.
 *
 * Bootstrapping secret storage may take one of these paths:
 * 1. Create secret storage from a passphrase and store cross-signing keys
 *    in secret storage.
 * 2. Access existing secret storage by requesting passphrase and accessing
 *    cross-signing keys as needed.
 * 3. All keys are loaded and there's nothing to do.
 *
 * Additionally, the secret storage keys are cached during the scope of this function
 * to ensure the user is prompted only once for their secret storage
 * passphrase. The cache is then cleared once the provided function completes.
 *
 * @param {Function} [func] An operation to perform once secret storage has been
 * bootstrapped. Optional.
 */
export async function accessSecretStorage(func = async () => { }) {
    const cli = MatrixClientPeg.get();
    secretStorageBeingAccessed = true;

    try {
        if (!await cli.hasSecretStorageKey()) {
            // This dialog calls bootstrap itself after guiding the user through
            // passphrase creation.
            const { finished } = Modal.createTrackedDialogAsync('Create Secret Storage dialog', '',
                import("./async-components/views/dialogs/secretstorage/CreateSecretStorageDialog"),
                null, null, /* priority = */ false, /* static = */ true,
            );
            const [confirmed] = await finished;
            if (!confirmed) {
                throw new Error("Secret storage creation canceled");
            }
        } else {
            const InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog");
            await cli.bootstrapSecretStorage({
                authUploadDeviceSigningKeys: async (makeRequest) => {
                    const { finished } = Modal.createTrackedDialog(
                        'Cross-signing keys dialog', '', InteractiveAuthDialog,
                        {
                            title: _t("Setting up keys"),
                            matrixClient: MatrixClientPeg.get(),
                            makeRequest,
                        },
                    );
                    const [confirmed] = await finished;
                    if (!confirmed) {
                        throw new Error("Cross-signing key upload auth canceled");
                    }
                },
            });
        }

        // `return await` needed here to ensure `finally` block runs after the
        // inner operation completes.
        return await func();
    } finally {
        // Clear secret storage key cache now that work is complete
        secretStorageBeingAccessed = false;
        if (!isCachingAllowed()) {
            secretStorageKeys = {};
        }
    }
}
<|endoftext|>


<commit_before>var gulp         = require('gulp')
var render       = require('gulp-nunjucks-render')
var rename       = require('gulp-rename')
var handleErrors = require('../../lib/handleErrors')
var gutil        = require('gulp-util')

module.exports = function(config) {
  return function(glyphs, options) {
    gutil.log(gutil.colors.blue('Generating ' + config.sassDest + '/' + config.sassOutputName))
    render.nunjucks.configure(config.nunjucks, {watch: false })
    return gulp.src(config.template)
      .pipe(render({
        icons: glyphs.map(function(glyph) {
          gutil.log(gutil.colors.green('+ adding ' + glyph.name + ' glyph'))
          return {
            name: glyph.name,
            code: glyph.unicode[0].charCodeAt(0).toString(16).toUpperCase()
          }
        }),

        fontName: config.options.fontName,
        fontPath: config.fontPath,
        className: config.className,
        comment: '// DO NOT EDIT DIRECTLY!\n  //Generated by gulpfile.js/tasks/iconFont.js\n  //from ' + config.template
      }))
    .on('error', handleErrors)
    .pipe(rename(config.sassOutputName))
    .pipe(gulp.dest(config.sassDest))
  }
}
<commit_msg>Update iconFont sass generator for use with nunjucks-render 2.0.0<commit_after>var gulp         = require('gulp')
var render       = require('gulp-nunjucks-render')
var rename       = require('gulp-rename')
var handleErrors = require('../../lib/handleErrors')
var gutil        = require('gulp-util')
var data         = require('gulp-data')

module.exports = function(config) {
  return function(glyphs, options) {
    gutil.log(gutil.colors.blue('Generating ' + config.sassDest + '/' + config.sassOutputName))
    render.nunjucks.configure(config.nunjucks, {watch: false })
    return gulp.src(config.template)
      .pipe(data({
        icons: glyphs.map(function(glyph) {
          gutil.log(gutil.colors.green('+ adding ' + glyph.name + ' glyph'))
          return {
            name: glyph.name,
            code: glyph.unicode[0].charCodeAt(0).toString(16).toUpperCase()
          }
        }),

        fontName: config.options.fontName,
        fontPath: config.fontPath,
        className: config.className,
        comment: '// DO NOT EDIT DIRECTLY!\n  //Generated by gulpfile.js/tasks/iconFont.js\n  //from ' + config.template
      }))
      .pipe(render({
        path: config.template
      }))
    .on('error', handleErrors)
    .pipe(rename(config.sassOutputName))
    .pipe(gulp.dest(config.sassDest))
  }
}
<|endoftext|>