<commit_before>// Copyright (c) 2015 - 2017 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import {Layer, log, createIterable} from '@deck.gl/core';
import GL from '@luma.gl/constants';
import {Model, CylinderGeometry, fp64, PhongMaterial} from '@luma.gl/core';
const {fp64LowPart} = fp64;
const defaultMaterial = new PhongMaterial();

import vs from './column-layer-vertex.glsl';
import fs from './column-layer-fragment.glsl';

const DEFAULT_COLOR = [255, 0, 255, 255];

const defaultProps = {
  diskResolution: {type: 'number', min: 4, value: 20},
  vertices: null,
  radius: {type: 'number', min: 0, value: 1000},
  angle: {type: 'number', value: 0},
  offset: {type: 'array', value: [0, 0]},
  coverage: {type: 'number', min: 0, max: 1, value: 1},
  elevationScale: {type: 'number', min: 0, value: 1},
  extruded: true,
  fp64: false,

  getPosition: {type: 'accessor', value: x => x.position},
  getColor: {type: 'accessor', value: DEFAULT_COLOR},
  getElevation: {type: 'accessor', value: 1000},

  material: defaultMaterial
};

export default class ColumnLayer extends Layer {
  getShaders() {
    const projectModule = this.use64bitProjection() ? 'project64' : 'project32';
    return {vs, fs, modules: [projectModule, 'gouraud-lighting', 'picking']};
  }

  /**
   * DeckGL calls initializeState when GL context is available
   * Essentially a deferred constructor
   */
  initializeState() {
    const attributeManager = this.getAttributeManager();
    /* eslint-disable max-len */
    attributeManager.addInstanced({
      instancePositions: {
        size: 3,
        transition: true,
        accessor: 'getPosition'
      },
      instanceElevations: {
        size: 1,
        transition: true,
        accessor: 'getElevation'
      },
      instancePositions64xyLow: {
        size: 2,
        accessor: 'getPosition',
        update: this.calculateInstancePositions64xyLow
      },
      instanceColors: {
        size: 4,
        type: GL.UNSIGNED_BYTE,
        transition: true,
        accessor: 'getColor',
        defaultValue: DEFAULT_COLOR
      }
    });
    /* eslint-enable max-len */
  }

  updateState({props, oldProps, changeFlags}) {
    super.updateState({props, oldProps, changeFlags});
    if (props.fp64 !== oldProps.fp64 || props.diskResolution !== oldProps.diskResolution) {
      const {gl} = this.context;
      if (this.state.model) {
        this.state.model.delete();
      }
      this.setState({model: this._getModel(gl)});
      this.getAttributeManager().invalidateAll();
    }

    if (props.vertices !== oldProps.vertices) {
      this._updateVertices(props.vertices);
    }
  }

  getGeometry(diskResolution) {
    return new CylinderGeometry({
      radius: 1,
      topCap: false,
      bottomCap: true,
      height: 2,
      verticalAxis: 'z',
      nradial: diskResolution,
      nvertical: 1
    });
  }

  _getModel(gl) {
    return new Model(
      gl,
      Object.assign({}, this.getShaders(), {
        id: this.props.id,
        geometry: this.getGeometry(this.props.diskResolution),
        isInstanced: true,
        shaderCache: this.context.shaderCache
      })
    );
  }

  _updateVertices(vertices) {
    if (!vertices) {
      return;
    }

    const {diskResolution} = this.props;
    log.assert(vertices.length >= diskResolution);

    const {model} = this.state;
    const geometry = this.getGeometry(this.props.diskResolution);
    const positions = geometry.attributes.POSITION;
    let i = 0;
    for (let loopIndex = 0; loopIndex < 3; loopIndex++) {
      for (let j = 0; j <= diskResolution; j++) {
        const p = vertices[j] || vertices[0]; // auto close loop
        // replace x and y in geometry
        positions.value[i++] = p[0];
        positions.value[i++] = p[1];
        i++;
      }
    }
    model.setProps({geometry});
  }

  draw({uniforms}) {
    const {elevationScale, extruded, offset, coverage, radius, angle} = this.props;

    this.state.model
      .setUniforms(
        Object.assign({}, uniforms, {
          radius,
          angle: (angle / 180) * Math.PI,
          offset,
          extruded,
          coverage,
          elevationScale
        })
      )
      .draw();
  }

  calculateInstancePositions64xyLow(attribute) {
    const isFP64 = this.use64bitPositions();
    attribute.constant = !isFP64;

    if (!isFP64) {
      attribute.value = new Float32Array(2);
      return;
    }

    const {data, getPosition} = this.props;
    const {value} = attribute;
    let i = 0;
    const {iterable, objectInfo} = createIterable(data);
    for (const object of iterable) {
      objectInfo.index++;
      const position = getPosition(object, objectInfo);
      value[i++] = fp64LowPart(position[0]);
      value[i++] = fp64LowPart(position[1]);
    }
  }
}

ColumnLayer.layerName = 'ColumnLayer';
ColumnLayer.defaultProps = defaultProps;
<commit_msg>Fix update vertices when regenerate models (#3078)<commit_after>// Copyright (c) 2015 - 2017 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import {Layer, log, createIterable} from '@deck.gl/core';
import GL from '@luma.gl/constants';
import {Model, CylinderGeometry, fp64, PhongMaterial} from '@luma.gl/core';
const {fp64LowPart} = fp64;
const defaultMaterial = new PhongMaterial();

import vs from './column-layer-vertex.glsl';
import fs from './column-layer-fragment.glsl';

const DEFAULT_COLOR = [255, 0, 255, 255];

const defaultProps = {
  diskResolution: {type: 'number', min: 4, value: 20},
  vertices: null,
  radius: {type: 'number', min: 0, value: 1000},
  angle: {type: 'number', value: 0},
  offset: {type: 'array', value: [0, 0]},
  coverage: {type: 'number', min: 0, max: 1, value: 1},
  elevationScale: {type: 'number', min: 0, value: 1},
  extruded: true,
  fp64: false,

  getPosition: {type: 'accessor', value: x => x.position},
  getColor: {type: 'accessor', value: DEFAULT_COLOR},
  getElevation: {type: 'accessor', value: 1000},

  material: defaultMaterial
};

export default class ColumnLayer extends Layer {
  getShaders() {
    const projectModule = this.use64bitProjection() ? 'project64' : 'project32';
    return {vs, fs, modules: [projectModule, 'gouraud-lighting', 'picking']};
  }

  /**
   * DeckGL calls initializeState when GL context is available
   * Essentially a deferred constructor
   */
  initializeState() {
    const attributeManager = this.getAttributeManager();
    /* eslint-disable max-len */
    attributeManager.addInstanced({
      instancePositions: {
        size: 3,
        transition: true,
        accessor: 'getPosition'
      },
      instanceElevations: {
        size: 1,
        transition: true,
        accessor: 'getElevation'
      },
      instancePositions64xyLow: {
        size: 2,
        accessor: 'getPosition',
        update: this.calculateInstancePositions64xyLow
      },
      instanceColors: {
        size: 4,
        type: GL.UNSIGNED_BYTE,
        transition: true,
        accessor: 'getColor',
        defaultValue: DEFAULT_COLOR
      }
    });
    /* eslint-enable max-len */
  }

  updateState({props, oldProps, changeFlags}) {
    super.updateState({props, oldProps, changeFlags});
    const regenerateModels =
      props.fp64 !== oldProps.fp64 || props.diskResolution !== oldProps.diskResolution;
    if (regenerateModels) {
      const {gl} = this.context;
      if (this.state.model) {
        this.state.model.delete();
      }
      this.setState({model: this._getModel(gl)});
      this.getAttributeManager().invalidateAll();
    }

    if (regenerateModels || props.vertices !== oldProps.vertices) {
      this._updateVertices(props.vertices);
    }
  }

  getGeometry(diskResolution) {
    return new CylinderGeometry({
      radius: 1,
      topCap: false,
      bottomCap: true,
      height: 2,
      verticalAxis: 'z',
      nradial: diskResolution,
      nvertical: 1
    });
  }

  _getModel(gl) {
    return new Model(
      gl,
      Object.assign({}, this.getShaders(), {
        id: this.props.id,
        geometry: this.getGeometry(this.props.diskResolution),
        isInstanced: true,
        shaderCache: this.context.shaderCache
      })
    );
  }

  _updateVertices(vertices) {
    if (!vertices) {
      return;
    }

    const {diskResolution} = this.props;
    log.assert(vertices.length >= diskResolution);

    const {model} = this.state;
    const geometry = this.getGeometry(this.props.diskResolution);
    const positions = geometry.attributes.POSITION;
    let i = 0;
    for (let loopIndex = 0; loopIndex < 3; loopIndex++) {
      for (let j = 0; j <= diskResolution; j++) {
        const p = vertices[j] || vertices[0]; // auto close loop
        // replace x and y in geometry
        positions.value[i++] = p[0];
        positions.value[i++] = p[1];
        i++;
      }
    }
    model.setProps({geometry});
  }

  draw({uniforms}) {
    const {elevationScale, extruded, offset, coverage, radius, angle} = this.props;

    this.state.model
      .setUniforms(
        Object.assign({}, uniforms, {
          radius,
          angle: (angle / 180) * Math.PI,
          offset,
          extruded,
          coverage,
          elevationScale
        })
      )
      .draw();
  }

  calculateInstancePositions64xyLow(attribute) {
    const isFP64 = this.use64bitPositions();
    attribute.constant = !isFP64;

    if (!isFP64) {
      attribute.value = new Float32Array(2);
      return;
    }

    const {data, getPosition} = this.props;
    const {value} = attribute;
    let i = 0;
    const {iterable, objectInfo} = createIterable(data);
    for (const object of iterable) {
      objectInfo.index++;
      const position = getPosition(object, objectInfo);
      value[i++] = fp64LowPart(position[0]);
      value[i++] = fp64LowPart(position[1]);
    }
  }
}

ColumnLayer.layerName = 'ColumnLayer';
ColumnLayer.defaultProps = defaultProps;
<|endoftext|>


<commit_before>'use strict'

module.exports = (ctx) => ({
  map: ctx.file.dirname.startsWith('docs') || ctx.file.dirname.includes('examples') ? false : {
    inline: false,
    annotation: true,
    sourcesContent: true
  },
  plugins: {
    autoprefixer: {}
  }
})
<commit_msg>Remove unused code from postcss.config.js.<commit_after>'use strict'

module.exports = (ctx) => ({
  map: ctx.file.dirname.includes('examples') ? false : {
    inline: false,
    annotation: true,
    sourcesContent: true
  },
  plugins: {
    autoprefixer: {}
  }
})
<|endoftext|>


<commit_before>// Copyright 2022, University of Colorado Boulder
const startTime = Date.now();

const execute = require( '../common/execute' );
const _ = require( 'lodash' ); // eslint-disable-line
const fs = require( 'fs' );

// constants
// Don't use getActiveRepos() since it cannot be run from the root
const contents = fs.readFileSync( 'perennial/data/active-repos', 'utf8' ).trim();
const repos = contents.split( '\n' ).map( sim => sim.trim() );

/**
 * Identify all repos with uncommitted changes, and run the precommit hooks on them.
 *
 * USAGE:
 * cd ${root containing all repos}
 * node perennial/js/scripts/precommit-hook-changed-repos.js
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */
( async () => {

  // Detect uncommitted changes in each repo:
  // https://stackoverflow.com/questions/3878624/how-do-i-programmatically-determine-if-there-are-uncommitted-changes
  // git update-index --refresh
  // git diff-index --quiet HEAD --

  const updateIndexPromises = repos.map( repo => execute( 'git', 'update-index --refresh'.split( ' ' ), `${repo}`, {

    // resolve errors so Promise.all doesn't fail on first repo that cannot pull/rebase
    errors: 'resolve'
  } ) );
  await Promise.all( updateIndexPromises );

  const diffIndexPromises = repos.map( repo => execute( 'git', 'diff-index --quiet HEAD --'.split( ' ' ), `${repo}`, {

    // resolve errors so Promise.all doesn't fail on first repo that cannot pull/rebase
    errors: 'resolve'
  } ) );
  const results = await Promise.all( diffIndexPromises );

  // Find out which repos have uncommitted changes
  const changedRepos = [];
  for ( let i = 0; i < results.length; i++ ) {
    const repo = repos[ i ];
    const result = results[ i ];

    if ( result.code === 0 ) {

      // was up-to-date
    }
    else {

      // needs to push
      changedRepos.push( repo );
    }
  }
  console.log( 'detected changed repos: ' + changedRepos.join( ', ' ) );

  // This is done sequentially so we don't spawn a bunch of uncached tsc at once, but in the future we may want to optimize
  // to run one sequentially then the rest in parallel
  for ( let i = 0; i < changedRepos.length; i++ ) {

    process.stdout.write( changedRepos[ i ] + ': ' );

    const result = await execute( 'node', [ '../perennial/js/scripts/hook-pre-commit.js' ], `${changedRepos[ i ]}`, {

      // resolve errors so Promise.all doesn't fail on first repo that cannot pull/rebase
      errors: 'resolve'
    } );
    if ( result.code === 0 ) {

      console.log( 'Success' );
    }
    else {
      console.log();
      result.stdout.trim().length > 0 && console.log( result.stdout.trim() );
      result.stderr.trim().length > 0 && console.log( result.stderr.trim() );
    }
  }

  const endTime = Date.now();
  console.log( 'Done in ' + ( endTime - startTime ) + 'ms' );
} )();<commit_msg>Move hook-pre-commit.js to chipper from perennial, see https://github.com/phetsims/perennial/issues/278<commit_after>// Copyright 2022, University of Colorado Boulder
const startTime = Date.now();

const execute = require( '../common/execute' );
const _ = require( 'lodash' ); // eslint-disable-line
const fs = require( 'fs' );

// constants
// Don't use getActiveRepos() since it cannot be run from the root
const contents = fs.readFileSync( 'perennial/data/active-repos', 'utf8' ).trim();
const repos = contents.split( '\n' ).map( sim => sim.trim() );

/**
 * Identify all repos with uncommitted changes, and run the precommit hooks on them.
 *
 * USAGE:
 * cd ${root containing all repos}
 * node perennial/js/scripts/precommit-hook-changed-repos.js
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */
( async () => {

  // Detect uncommitted changes in each repo:
  // https://stackoverflow.com/questions/3878624/how-do-i-programmatically-determine-if-there-are-uncommitted-changes
  // git update-index --refresh
  // git diff-index --quiet HEAD --

  const updateIndexPromises = repos.map( repo => execute( 'git', 'update-index --refresh'.split( ' ' ), `${repo}`, {

    // resolve errors so Promise.all doesn't fail on first repo that cannot pull/rebase
    errors: 'resolve'
  } ) );
  await Promise.all( updateIndexPromises );

  const diffIndexPromises = repos.map( repo => execute( 'git', 'diff-index --quiet HEAD --'.split( ' ' ), `${repo}`, {

    // resolve errors so Promise.all doesn't fail on first repo that cannot pull/rebase
    errors: 'resolve'
  } ) );
  const results = await Promise.all( diffIndexPromises );

  // Find out which repos have uncommitted changes
  const changedRepos = [];
  for ( let i = 0; i < results.length; i++ ) {
    const repo = repos[ i ];
    const result = results[ i ];

    if ( result.code === 0 ) {

      // was up-to-date
    }
    else {

      // needs to push
      changedRepos.push( repo );
    }
  }
  console.log( 'detected changed repos: ' + changedRepos.join( ', ' ) );

  // This is done sequentially so we don't spawn a bunch of uncached tsc at once, but in the future we may want to optimize
  // to run one sequentially then the rest in parallel
  for ( let i = 0; i < changedRepos.length; i++ ) {

    process.stdout.write( changedRepos[ i ] + ': ' );

    const result = await execute( 'node', [ '../chipper/js/scripts/hook-pre-commit.js' ], `${changedRepos[ i ]}`, {

      // resolve errors so Promise.all doesn't fail on first repo that cannot pull/rebase
      errors: 'resolve'
    } );
    if ( result.code === 0 ) {

      console.log( 'Success' );
    }
    else {
      console.log();
      result.stdout.trim().length > 0 && console.log( result.stdout.trim() );
      result.stderr.trim().length > 0 && console.log( result.stderr.trim() );
    }
  }

  const endTime = Date.now();
  console.log( 'Done in ' + ( endTime - startTime ) + 'ms' );
} )();<|endoftext|>


<commit_before>// @flow

import React from 'react'
import Async from 'react-code-splitting'
import { createRoutes, IndexRoute, Route } from 'react-router'
import Relay from 'react-relay'

import Chrome from '../../webapp/components/Chrome'

import Compendium from '../../units/urb-example-compendium/webapp/components/Compendium'

import Ensayo_List from '../../units/urb-example-ensayo/webapp/components/Ensayo_List'
import Ensayo_Screen from '../../units/urb-example-ensayo/webapp/components/Ensayo_Screen'
import Ensayo_PublicItem from '../../units/urb-example-ensayo/webapp/components/Ensayo_PublicItem'
import Ensayo_PublicListing from '../../units/urb-example-ensayo/webapp/components/Ensayo_PublicListing'

import ForceLogin from '../../units/urb-example-force-login/webapp/components/ForceLogin'

import HomeScreen from './components/HomeScreen'

import MUI_Icons from '../../units/urb-example-mui/webapp/components/MUI_Icons'
import MUI_Icons_CountryFlags from '../../units/urb-example-mui/webapp/components/MUI_Icons_CountryFlags'
import MUI_Icons_CreditCards from '../../units/urb-example-mui/webapp/components/MUI_Icons_CreditCards'
import MUI_Home from '../../units/urb-example-mui/webapp/components/MUI_Home'

import ToDo_List from '../../units/urb-example-todo/webapp/components/ToDo_List'
import ToDo_Screen from '../../units/urb-example-todo/webapp/components/ToDo_Screen'

// prettier-ignore
const Translaticiarum_Calendar = () => <Async load={ import( '../../units/urb-example-translaticiarum/webapp/components/Translaticiarum_Calendar' ) } />
import Translaticiarum_List from '../../units/urb-example-translaticiarum/webapp/components/Translaticiarum_List'
import Translaticiarum_Grid from '../../units/urb-example-translaticiarum/webapp/components/Translaticiarum_Grid'
import Translaticiarum_Screen from '../../units/urb-example-translaticiarum/webapp/components/Translaticiarum_Screen'

import User_Properties from '../../units/urb-account-management/webapp/components/User_Properties'
import User_UpdatePassword from '../../units/urb-account-management/webapp/components/User_UpdatePassword'

export const queries = {
  Viewer: () => Relay.QL`query { Viewer }`,
}

export default createRoutes(
  <Route path="/" component={Chrome} queries={queries}>

    <IndexRoute component={HomeScreen} queries={queries} />

    <Route path="compendium">
      <IndexRoute component={Compendium} queries={queries} />
    </Route>

    <Route path="ensayo">
      <IndexRoute component={Ensayo_PublicListing} queries={queries} />
      <Route path="item">
        <Route path=":id" component={Ensayo_PublicItem} queries={queries} />
      </Route>
      <Route path="edit" component={Ensayo_Screen} queries={queries}>
        <IndexRoute component={Ensayo_List} queries={queries} />
      </Route>
    </Route>

    <Route path="todo" component={ToDo_Screen} queries={queries}>
      <IndexRoute
        component={ToDo_List}
        queries={queries}
        prepareParams={() => ({ status: 'any' })}
      />
      <Route path=":status" component={ToDo_List} queries={queries} />
    </Route>

    <Route path="translaticiarum">
      <IndexRoute component={Translaticiarum_Grid} queries={queries} />
      <Route path="edit" component={Translaticiarum_Screen} queries={queries}>
        <IndexRoute component={Translaticiarum_List} queries={queries} />
      </Route>
      <Route path="calendar" component={Translaticiarum_Calendar} queries={queries} />
    </Route>

    <Route path="force_login">
      <IndexRoute component={ForceLogin} queries={queries} />
    </Route>

    <Route path="mui">
      <IndexRoute component={MUI_Home} queries={queries} />
      <Route path="icons" component={MUI_Icons} queries={queries} />
      <Route path="icons_country_flags" component={MUI_Icons_CountryFlags} queries={queries} />
      <Route path="icons_credit_cards" component={MUI_Icons_CreditCards} queries={queries} />
    </Route>

    <Route path="user">
      <IndexRoute component={User_Properties} queries={queries} />
      <Route path="update_password" component={User_UpdatePassword} queries={queries} />
    </Route>

  </Route>,
)
<commit_msg>Revert attempt at implementing code splitting for Calendar since issues with Relay werte encountered<commit_after>// @flow

import React from 'react'
import Async from 'react-code-splitting'
import { createRoutes, IndexRoute, Route } from 'react-router'
import Relay from 'react-relay'

import Chrome from '../../webapp/components/Chrome'

import Compendium from '../../units/urb-example-compendium/webapp/components/Compendium'

import Ensayo_List from '../../units/urb-example-ensayo/webapp/components/Ensayo_List'
import Ensayo_Screen from '../../units/urb-example-ensayo/webapp/components/Ensayo_Screen'
import Ensayo_PublicItem from '../../units/urb-example-ensayo/webapp/components/Ensayo_PublicItem'
import Ensayo_PublicListing from '../../units/urb-example-ensayo/webapp/components/Ensayo_PublicListing'

import ForceLogin from '../../units/urb-example-force-login/webapp/components/ForceLogin'

import HomeScreen from './components/HomeScreen'

import MUI_Icons from '../../units/urb-example-mui/webapp/components/MUI_Icons'
import MUI_Icons_CountryFlags from '../../units/urb-example-mui/webapp/components/MUI_Icons_CountryFlags'
import MUI_Icons_CreditCards from '../../units/urb-example-mui/webapp/components/MUI_Icons_CreditCards'
import MUI_Home from '../../units/urb-example-mui/webapp/components/MUI_Home'

import ToDo_List from '../../units/urb-example-todo/webapp/components/ToDo_List'
import ToDo_Screen from '../../units/urb-example-todo/webapp/components/ToDo_Screen'

// https://github.com/codefoundries/UniversalRelayBoilerplate/issues/307
// prettier-ignore
//const Translaticiarum_Calendar = () => <Async load={ import( '../../units/urb-example-translaticiarum/webapp/components/Translaticiarum_Calendar' ) } />
import Translaticiarum_Calendar from '../../units/urb-example-translaticiarum/webapp/components/Translaticiarum_Calendar'
import Translaticiarum_List from '../../units/urb-example-translaticiarum/webapp/components/Translaticiarum_List'
import Translaticiarum_Grid from '../../units/urb-example-translaticiarum/webapp/components/Translaticiarum_Grid'
import Translaticiarum_Screen from '../../units/urb-example-translaticiarum/webapp/components/Translaticiarum_Screen'

import User_Properties from '../../units/urb-account-management/webapp/components/User_Properties'
import User_UpdatePassword from '../../units/urb-account-management/webapp/components/User_UpdatePassword'

export const queries = {
  Viewer: () => Relay.QL`query { Viewer }`,
}

export default createRoutes(
  <Route path="/" component={Chrome} queries={queries}>

    <IndexRoute component={HomeScreen} queries={queries} />

    <Route path="compendium">
      <IndexRoute component={Compendium} queries={queries} />
    </Route>

    <Route path="ensayo">
      <IndexRoute component={Ensayo_PublicListing} queries={queries} />
      <Route path="item">
        <Route path=":id" component={Ensayo_PublicItem} queries={queries} />
      </Route>
      <Route path="edit" component={Ensayo_Screen} queries={queries}>
        <IndexRoute component={Ensayo_List} queries={queries} />
      </Route>
    </Route>

    <Route path="todo" component={ToDo_Screen} queries={queries}>
      <IndexRoute
        component={ToDo_List}
        queries={queries}
        prepareParams={() => ({ status: 'any' })}
      />
      <Route path=":status" component={ToDo_List} queries={queries} />
    </Route>

    <Route path="translaticiarum">
      <IndexRoute component={Translaticiarum_Grid} queries={queries} />
      <Route path="edit" component={Translaticiarum_Screen} queries={queries}>
        <IndexRoute component={Translaticiarum_List} queries={queries} />
      </Route>
      <Route path="calendar" component={Translaticiarum_Calendar} queries={queries} />
    </Route>

    <Route path="force_login">
      <IndexRoute component={ForceLogin} queries={queries} />
    </Route>

    <Route path="mui">
      <IndexRoute component={MUI_Home} queries={queries} />
      <Route path="icons" component={MUI_Icons} queries={queries} />
      <Route path="icons_country_flags" component={MUI_Icons_CountryFlags} queries={queries} />
      <Route path="icons_credit_cards" component={MUI_Icons_CreditCards} queries={queries} />
    </Route>

    <Route path="user">
      <IndexRoute component={User_Properties} queries={queries} />
      <Route path="update_password" component={User_UpdatePassword} queries={queries} />
    </Route>

  </Route>,
)
<|endoftext|>


<commit_before>define([
    'angular',
    'lodash',
    'app/core/utils/datemath',
    'moment',
    './directives',
    './query_ctrl'
],
       function (angular, _, dateMath) {
           'use strict';

           var module = angular.module('grafana.services');

           module.factory('RaspimonDatasource', function($q, backendSrv) {

               // the datasource object passed to constructor
               // is the same defined in config.js
               function RaspimonDatasource(datasource) {
                   this.name = datasource.name;
                   this.type = "raspimon";
                   this.url  = datasource.url;
                   this.topic_filters = datasource.topic_filters || [];
                   this.supportMetrics = true;
               }

               RaspimonDatasource.prototype._get = function(relativeUrl, params) {
                   return backendSrv.datasourceRequest({
                       method: 'GET',
                       url: this.url + relativeUrl,
                       params: params,
                   });
               };

               RaspimonDatasource.prototype._post = function(relativeUrl, params) {
                   return backendSrv.datasourceRequest({
                       method: 'POST',
                       url: this.url + relativeUrl,
                       params: params,
                   });
               };

               var transformToTimeSeries = function(query, data) {
                   var dps = [];
                   _.each(data, function(v) {
                       var mul = query.mul || 1.0;
                       var add = query.add || 0.0;
                       dps.push([v[0]*mul + add, Math.round(v[1] * 1000)]);
                   })
                   return {
                       target: query.alias || query.topic,
                       datapoints: dps,
                   };
               };

               RaspimonDatasource.prototype.query = function(options) {
                   console.log('options: ' + JSON.stringify(options));
                   // get from & to in seconds
                   var from = Math.floor(dateMath.parse(options.range.from) / 1000);
                   var to = Math.ceil(dateMath.parse(options.range.to) / 1000);
                   var maxDataPoints = options.maxDataPoints
                   var qs = [];

                   _.each(options.targets, function(target) {
                       if (target.topic && !target.hide) qs.push(target);
                   });

                   if (_.isEmpty(qs)) {
                       var d = $q.defer();
                       d.resolve({ data: [] });
                       return d.promise;
                   }

                   var buildQuery = function(topic, aggregator) {
                       return "/raspimon/api/aggregate/" + aggregator + "/" + topic + "/" + from + "/" + to + "/" + maxDataPoints;
                   };

                   // chain all promises, one per each element at qs (targets)
                   var self = this;
                   var promises = []
                   _.each(qs, function(q) {
                       promises.push( self._get(buildQuery(q.topic, q.aggregator || "last"))
                                      .then(function(response) {
                                          return transformToTimeSeries(q, response.data);
                                      }) );
                   });

                   return $q.all(promises).then(function(result) {
                       return { data: result };
                   });
               };

               RaspimonDatasource.prototype.testDatasource = function () {
                   return this._get('/raspimon/api/topics').then(function(topics) {
                       if (!topics) return { status: "error", message: "Unable to connect to data source" };
                       return { status: "success", message: "Data source is working", title: "Success" };
                   });
               };

               RaspimonDatasource.prototype.getTopicsList = function() {
                   var callback = function(result) {
                       if (result.data && _.isArray(result.data)) {
                           return result.data.sort();
                       }
                       return [];
                   };
                   if (this.topic_filters.length > 0) {
                       return this._post('/raspimon/api/topics/filtered',{topic_filters:this.topic_filters}).then(callback);
                   }
                   else {
                       return this._get('/raspimon/api/topics').then(callback);
                   }
               };

               RaspimonDatasource.prototype.getAggregatorsList = function() {
                   return this._get('/raspimon/api/aggregators').then(function(result) {
                       if (result.data && _.isArray(result.data)) {
                           return result.data.sort();
                       }
                       return [];
                   });
               };

               return RaspimonDatasource;
           });
       });
<commit_msg>Added filters logging<commit_after>define([
    'angular',
    'lodash',
    'app/core/utils/datemath',
    'moment',
    './directives',
    './query_ctrl'
],
       function (angular, _, dateMath) {
           'use strict';

           var module = angular.module('grafana.services');

           module.factory('RaspimonDatasource', function($q, backendSrv) {

               // the datasource object passed to constructor
               // is the same defined in config.js
               function RaspimonDatasource(datasource) {
                   this.name = datasource.name;
                   this.type = "raspimon";
                   this.url  = datasource.url;
                   this.topic_filters = datasource.topic_filters || [];
                   this.supportMetrics = true;
                   console.log('filters: ' + JSON.stringify(this.topic_filters));
               }

               RaspimonDatasource.prototype._get = function(relativeUrl, params) {
                   return backendSrv.datasourceRequest({
                       method: 'GET',
                       url: this.url + relativeUrl,
                       params: params,
                   });
               };

               RaspimonDatasource.prototype._post = function(relativeUrl, params) {
                   return backendSrv.datasourceRequest({
                       method: 'POST',
                       url: this.url + relativeUrl,
                       params: params,
                   });
               };

               var transformToTimeSeries = function(query, data) {
                   var dps = [];
                   _.each(data, function(v) {
                       var mul = query.mul || 1.0;
                       var add = query.add || 0.0;
                       dps.push([v[0]*mul + add, Math.round(v[1] * 1000)]);
                   })
                   return {
                       target: query.alias || query.topic,
                       datapoints: dps,
                   };
               };

               RaspimonDatasource.prototype.query = function(options) {
                   console.log('options: ' + JSON.stringify(options));
                   // get from & to in seconds
                   var from = Math.floor(dateMath.parse(options.range.from) / 1000);
                   var to = Math.ceil(dateMath.parse(options.range.to) / 1000);
                   var maxDataPoints = options.maxDataPoints
                   var qs = [];

                   _.each(options.targets, function(target) {
                       if (target.topic && !target.hide) qs.push(target);
                   });

                   if (_.isEmpty(qs)) {
                       var d = $q.defer();
                       d.resolve({ data: [] });
                       return d.promise;
                   }

                   var buildQuery = function(topic, aggregator) {
                       return "/raspimon/api/aggregate/" + aggregator + "/" + topic + "/" + from + "/" + to + "/" + maxDataPoints;
                   };

                   // chain all promises, one per each element at qs (targets)
                   var self = this;
                   var promises = []
                   _.each(qs, function(q) {
                       promises.push( self._get(buildQuery(q.topic, q.aggregator || "last"))
                                      .then(function(response) {
                                          return transformToTimeSeries(q, response.data);
                                      }) );
                   });

                   return $q.all(promises).then(function(result) {
                       return { data: result };
                   });
               };

               RaspimonDatasource.prototype.testDatasource = function () {
                   return this._get('/raspimon/api/topics').then(function(topics) {
                       if (!topics) return { status: "error", message: "Unable to connect to data source" };
                       return { status: "success", message: "Data source is working", title: "Success" };
                   });
               };

               RaspimonDatasource.prototype.getTopicsList = function() {
                   var callback = function(result) {
                       if (result.data && _.isArray(result.data)) {
                           return result.data.sort();
                       }
                       return [];
                   };
                   if (this.topic_filters.length > 0) {
                       return this._post('/raspimon/api/topics/filtered',{topic_filters:this.topic_filters}).then(callback);
                   }
                   else {
                       return this._get('/raspimon/api/topics').then(callback);
                   }
               };

               RaspimonDatasource.prototype.getAggregatorsList = function() {
                   return this._get('/raspimon/api/aggregators').then(function(result) {
                       if (result.data && _.isArray(result.data)) {
                           return result.data.sort();
                       }
                       return [];
                   });
               };

               return RaspimonDatasource;
           });
       });
<|endoftext|>


<commit_before>const expect = require('chai').expect
const sinon = require('sinon')
const VersionChecker = require('../app/utils/versionChecker')

describe('VersionChecker', () => {
  describe('latest', () => {
    let sandbox

    beforeEach(() => {
      sandbox = sinon.sandbox.create();
    });

    afterEach(() => {
      sandbox.restore()
    })

    it('fetches tag name', (done) => {
      const tagName = 'tag name'
      const body = 'body'
      sandbox.stub(JSON, 'parse').returns({ tag_name: tagName })
      const response = {
        text: sinon.stub().returns(Promise.resolve(body))
      }
      global.fetch = sinon.stub().returns(Promise.resolve(response))

      const checker = new VersionChecker()

      checker.latest()
        .then((result) => {
          expect(result).to.equal(tagName)

          sinon.assert.calledWithExactly(
            fetch,
            'https://api.github.com/repos/hovancik/stretchly/releases/latest',
            {
              method: 'GET',
              headers: {'User-Agent': 'hovancik/stretchly'},
              mode: 'cors',
              cache: 'default'
            })

          sinon.assert.called(response.text)
          sinon.assert.calledWithExactly(JSON.parse, body)

          done()
        })
    })
  })
})
<commit_msg>Removed semicolons.<commit_after>const expect = require('chai').expect
const sinon = require('sinon')
const VersionChecker = require('../app/utils/versionChecker')

describe('VersionChecker', () => {
  describe('latest', () => {
    let sandbox

    beforeEach(() => {
      sandbox = sinon.sandbox.create()
    })

    afterEach(() => {
      sandbox.restore()
    })

    it('fetches tag name', (done) => {
      const tagName = 'tag name'
      const body = 'body'
      sandbox.stub(JSON, 'parse').returns({ tag_name: tagName })
      const response = {
        text: sinon.stub().returns(Promise.resolve(body))
      }
      global.fetch = sinon.stub().returns(Promise.resolve(response))

      const checker = new VersionChecker()

      checker.latest()
        .then((result) => {
          expect(result).to.equal(tagName)

          sinon.assert.calledWithExactly(
            fetch,
            'https://api.github.com/repos/hovancik/stretchly/releases/latest',
            {
              method: 'GET',
              headers: {'User-Agent': 'hovancik/stretchly'},
              mode: 'cors',
              cache: 'default'
            })

          sinon.assert.called(response.text)
          sinon.assert.calledWithExactly(JSON.parse, body)

          done()
        })
    })
  })
})
<|endoftext|>


<commit_before>const cp = require('child_process')

const actionEmitter = require('../../action-emitter')

const lc = require('../../leds/led-control')
const LEDS = require('./led-addresses')

module.exports = { run }

function pulseUp (range, color) {
  return lc.lightUp({ range, color, type: 'pulse' })
}

function solidUp (range, color) {
  return lc.lightUp({ range, color, type: 'solid' })
}

function killAll () {
  console.log('LED: kill any hangovers')
  cp.execFile('/usr/bin/killall', ['leds'], err => {
    if (err) console.log('LEDS killall error:', err)
  })
}

function processBrain (event) {
  switch (event.action) {
    case 'billValidatorPending':
      return pulseUp(LEDS.validatorLeds, lc.COLORS.amazonite)
    case 'billValidatorAccepting':
      return pulseUp(LEDS.validatorLeds, lc.COLORS.orange)
    case 'billValidatorRejecting':
      return pulseUp(LEDS.validatorLeds, lc.COLORS.red)

    case 'scanBayLightOn':
      return solidUp(LEDS.scanBayLeds, lc.COLORS.dimmed)

    case 'billValidatorOff':
    case 'scanBayLightOff':
    case 'ledsOff':
    case 'forceOff':
      return lc.lightDown()
  }
}

function run () {
  console.log('DEBUG100')
  killAll()
  actionEmitter.on('brain', processBrain)
  return Promise.resolve()
}
<commit_msg>fix: remove error message on LEDS killall<commit_after>const cp = require('child_process')

const actionEmitter = require('../../action-emitter')

const lc = require('../../leds/led-control')
const LEDS = require('./led-addresses')

module.exports = { run }

function pulseUp (range, color) {
  return lc.lightUp({ range, color, type: 'pulse' })
}

function solidUp (range, color) {
  return lc.lightUp({ range, color, type: 'solid' })
}

function killAll () {
  console.log('LED: kill any hangovers')
  cp.execFile('/usr/bin/killall', ['leds'], () => {})
}

function processBrain (event) {
  switch (event.action) {
    case 'billValidatorPending':
      return pulseUp(LEDS.validatorLeds, lc.COLORS.amazonite)
    case 'billValidatorAccepting':
      return pulseUp(LEDS.validatorLeds, lc.COLORS.orange)
    case 'billValidatorRejecting':
      return pulseUp(LEDS.validatorLeds, lc.COLORS.red)

    case 'scanBayLightOn':
      return solidUp(LEDS.scanBayLeds, lc.COLORS.dimmed)

    case 'billValidatorOff':
    case 'scanBayLightOff':
    case 'ledsOff':
    case 'forceOff':
      return lc.lightDown()
  }
}

function run () {
  console.log('DEBUG100')
  killAll()
  actionEmitter.on('brain', processBrain)
  return Promise.resolve()
}
<|endoftext|>


<commit_before>/*jshint globalstrict:false, strict:false, globalstrict: true */
/*global describe, beforeEach, it, expect*/

////////////////////////////////////////////////////////////////////////////////
/// @brief Spec for foxx manager
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2014 ArangoDB GmbH, Cologne, Germany
///
/// 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.
///
/// Copyright holder is ArangoDB GmbH, Cologne, Germany
///
/// @author Michael Hackstein
/// @author Copyright 2014, ArangoDB GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////

'use strict';

var FoxxManager = require("org/arangodb/foxx/manager");
var arangodb = require("org/arangodb");
var errors = arangodb.errors;

describe("Foxx Manager", function() {

  beforeEach(function() {
    FoxxManager.update();
  });

  it("should fetch apps from the appstore", function() {
    var list = FoxxManager.availableJson();
    var i, app, l = list.length;
    expect(l).toBeGreaterThan(0);
    for (i = 0; i < l; ++i) {
      app = list[i];
      expect(app.name).toBeDefined("App has no name");
      expect(app.latestVersion).toBeDefined("App " + app.name + " has no latestVersion.");
      expect(app.author).toBeDefined("App " + app.name + " has no author.");
      expect(app.description).toBeDefined("App " + app.name + " has no description");
    }
  });

  it("should be able to install all apps from appstore", function() {
    var mount = "/unittest/testApps";
    try {
      FoxxManager.uninstall(mount, { force: true });
    } catch(e) {
    }
    var list = FoxxManager.availableJson();
    var i, app, l = list.length;
    for (i = 0; i < l; ++i) {
      app = list[i];
      if (app.name === "sessions-example-app") {
        // this app cannot be installed at the moment
        continue; // FIXME
        // Requires oauth2 to be installed
        try {
          FoxxManager.uninstall("/oauth2", { force: true });
        } catch(err) {
        }
        try {
          FoxxManager.install(app.name, mount);
          expect(true).toBeFalsy("Installing sessions-example-app should require oauth2");
        } catch(e) {
          expect(e.errorNum).toEqual(errors.ERROR_FAILED_TO_EXECUTE_SCRIPT.code);
          try {
            FoxxManager.install("oauth2", "/oauth2");
          } catch(err) {
            expect(e).toBeUndefined("Could not install oauth2");
            continue;
          }
          try {
            FoxxManager.install(app.name, mount);
            FoxxManager.uninstall(mount);
          } catch(err2) {
            expect(err2).toBeUndefined("Could not install " + app.name);
            try {
              FoxxManager.uninstall(mount, { force: true });
            } catch(err) {
            }
          }
          try {
            FoxxManager.uninstall("/oauth2", {force: true});
          } catch(err) {
          }
        }
      } else {
        try {
          FoxxManager.install(app.name, mount);
          FoxxManager.uninstall(mount);
        } catch(e) {
          expect(e).toBeUndefined("Could not install " + app.name + " - \n" + e.stack);
          try {
            FoxxManager.uninstall(mount, { force: true });
          } catch(err) {
          }
        }
      }
    }
  });

});

<commit_msg>fix jslint<commit_after>/*jshint globalstrict:false, strict:false, globalstrict: true */
/*global describe, beforeEach, it, expect*/

////////////////////////////////////////////////////////////////////////////////
/// @brief Spec for foxx manager
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2014 ArangoDB GmbH, Cologne, Germany
///
/// 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.
///
/// Copyright holder is ArangoDB GmbH, Cologne, Germany
///
/// @author Michael Hackstein
/// @author Copyright 2014, ArangoDB GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////

'use strict';

var FoxxManager = require("org/arangodb/foxx/manager");

describe("Foxx Manager", function() {

  beforeEach(function() {
    FoxxManager.update();
  });

  it("should fetch apps from the appstore", function() {
    var list = FoxxManager.availableJson();
    var i, app, l = list.length;
    expect(l).toBeGreaterThan(0);
    for (i = 0; i < l; ++i) {
      app = list[i];
      expect(app.name).toBeDefined("App has no name");
      expect(app.latestVersion).toBeDefined("App " + app.name + " has no latestVersion.");
      expect(app.author).toBeDefined("App " + app.name + " has no author.");
      expect(app.description).toBeDefined("App " + app.name + " has no description");
    }
  });

  it("should be able to install all apps from appstore", function() {
    var mount = "/unittest/testApps";
    try {
      FoxxManager.uninstall(mount, { force: true });
    } catch(e) {
    }
    var list = FoxxManager.availableJson();
    var i, app, l = list.length;
    for (i = 0; i < l; ++i) {
      app = list[i];
      if (app.name === "sessions-example-app") {
        // this app cannot be installed at the moment
        continue; // FIXME
        /*
        var arangodb = require("org/arangodb");
        var errors = arangodb.errors;
        // Requires oauth2 to be installed
        try {
          FoxxManager.uninstall("/oauth2", { force: true });
        } catch(err) {
        }
        try {
          FoxxManager.install(app.name, mount);
          expect(true).toBeFalsy("Installing sessions-example-app should require oauth2");
        } catch(e) {
          expect(e.errorNum).toEqual(errors.ERROR_FAILED_TO_EXECUTE_SCRIPT.code);
          try {
            FoxxManager.install("oauth2", "/oauth2");
          } catch(err) {
            expect(e).toBeUndefined("Could not install oauth2");
            continue;
          }
          try {
            FoxxManager.install(app.name, mount);
            FoxxManager.uninstall(mount);
          } catch(err2) {
            expect(err2).toBeUndefined("Could not install " + app.name);
            try {
              FoxxManager.uninstall(mount, { force: true });
            } catch(err) {
            }
          }
          try {
            FoxxManager.uninstall("/oauth2", {force: true});
          } catch(err) {
          }
        }
        */
      } else {
        try {
          FoxxManager.install(app.name, mount);
          FoxxManager.uninstall(mount);
        } catch(e) {
          expect(e).toBeUndefined("Could not install " + app.name + " - \n" + e.stack);
          try {
            FoxxManager.uninstall(mount, { force: true });
          } catch(err) {
          }
        }
      }
    }
  });

});

<|endoftext|>


<commit_before>import { cloneDeep } from 'lodash'

import { API_URL } from '../app/config'
import { showError, ERRORS } from '../app/errors'
import { trackEvent } from '../app/event_tracking'
import {
  checkIfEverythingIsLoaded,
  setServerContacted
} from '../app/initialization'
import { checkIfImagesLoaded } from '../app/load_resources'
import { t } from '../app/locale'
import { MODES, processMode, getMode, setMode } from '../app/mode'
import { goNewStreet } from '../app/routing'
import { showStatusMessage } from '../app/status_message'
import { infoBubble } from '../info_bubble/info_bubble'
import { app } from '../preinit/app_settings'
import { segmentsChanged } from '../segments/view'
import {
  getAuthHeader,
  getSignInData,
  isSignedIn
} from '../users/authentication'
import { propagateUnits } from '../users/localization'
import {
  confirmSaveStreetToServerInitial,
  saveSettingsToServer,
  getSettings,
  setSettings
} from '../users/settings'
import {
  isblockingAjaxRequestInProgress,
  newBlockingAjaxRequest
} from '../util/fetch_blocking'
import {
  newNonblockingAjaxRequest,
  getNonblockingAjaxRequestCount
} from '../util/fetch_nonblocking'
import { NEW_STREET_EMPTY, makeDefaultStreet } from './creation'
import {
  prepareEmptyStreet,
  prepareDefaultStreet,
  getStreet,
  trimStreetData,
  updateEverything,
  createDomFromData,
  DEFAULT_NAME,
  setStreet,
  updateToLatestSchemaVersion,
  setStreetCreatorId,
  setUpdateTimeToNow,
  setLastStreet
} from './data_model'
import { updateStreetName } from './name'
import {
  getRemixOnFirstEdit,
  setRemixOnFirstEdit,
  remixStreet,
  addRemixSuffixToName
} from './remix'
import {
  setUndoStack,
  setUndoPosition,
  getUndoStack,
  FLAG_SAVE_UNDO,
  getUndoPosition,
  unifyUndoStack,
  setIgnoreStreetChanges
} from './undo_stack'
import { resizeStreetWidth } from './width'
import store from '../store'
import { setMapState } from '../store/actions/map'

const SAVE_STREET_DELAY = 500

var saveStreetTimerId = -1
let saveStreetIncomplete = false

export function getSaveStreetIncomplete () {
  return saveStreetIncomplete
}

export function setSaveStreetIncomplete (value) {
  saveStreetIncomplete = value
}

var uniqueRequestId = 0

var latestRequestId
var latestVerificationStreet

function getUniqueRequestHeader () {
  uniqueRequestId++
  return uniqueRequestId
}

export function createNewStreetOnServer () {
  if (getSettings().newStreetPreference === NEW_STREET_EMPTY) {
    prepareEmptyStreet()
  } else {
    prepareDefaultStreet()
  }

  const options = {
    method: 'POST',
    body: packServerStreetData(),
    headers: {
      'Authorization': getAuthHeader(),
      'Content-Type': 'application/json'
    }
  }

  // TODO const url
  window.fetch(API_URL + 'v1/streets', options)
    .then(response => {
      if (!response.ok) {
        throw response
      }
      return response.json()
    })
    .then(receiveNewStreet)
    .catch(errorReceiveNewStreet)
}

function receiveNewStreet (data) {
  setStreetId(data.id, data.namespacedId)

  saveStreetToServer(true)
}

function errorReceiveNewStreet (data) {
  showError(ERRORS.NEW_STREET_SERVER_FAILURE, true)
}

export function getFetchStreetUrl () {
  // TODO const
  let url
  var street = getStreet()
  if (street.creatorId) {
    url = API_URL + 'v1/streets?namespacedId=' +
      encodeURIComponent(street.namespacedId) + '&creatorId=' +
      encodeURIComponent(street.creatorId)
  } else {
    url = API_URL + 'v1/streets?namespacedId=' +
      encodeURIComponent(street.namespacedId)
  }

  return url
}

export function fetchStreetFromServer () {
  var url = getFetchStreetUrl()

  window.fetch(url)
    .then(response => response.json())
    .catch(errorReceiveStreet)
    .then(receiveStreet)
}

function errorReceiveStreet (data) {
  let mode = getMode()
  if ((mode === MODES.CONTINUE) || (mode === MODES.USER_GALLERY) ||
    (mode === MODES.GLOBAL_GALLERY)) {
    goNewStreet()
  } else {
    if ((data.status === 404) || (data.status === 410)) {
      if (getStreet().creatorId) {
        if (data.status === 410) {
          setMode(MODES.STREET_410_BUT_LINK_TO_USER)
        } else {
          setMode(MODES.STREET_404_BUT_LINK_TO_USER)
        }
      } else {
        setMode(MODES.STREET_404)
      }
      // TODO swap for showError (here and elsewhere)
      processMode()
    } else {
      showError(ERRORS.NEW_STREET_SERVER_FAILURE, true)
    }
  }
}

export function saveStreetToServer (initial) {
  if (app.readOnly) {
    return
  }

  const transmission = packServerStreetData()
  const street = getStreet()
  const url = API_URL + 'v1/streets/' + street.id
  const options = {
    method: 'PUT',
    body: transmission,
    headers: {
      'Authorization': getAuthHeader(),
      'Content-Type': 'application/json'
    }
  }

  if (initial) {
    // blocking
    window.fetch(url, options)
      .then(confirmSaveStreetToServerInitial)
  } else {
    newNonblockingAjaxRequest(url, options, false)
  }
}

function clearScheduledSavingStreetToServer () {
  window.clearTimeout(saveStreetTimerId)
}

export function fetchStreetForVerification () {
  // Don’t do it with any network services pending
  if (getNonblockingAjaxRequestCount() || isblockingAjaxRequestInProgress() ||
    saveStreetIncomplete || store.getState().errors.abortEverything || getRemixOnFirstEdit()) {
    return
  }

  const url = getFetchStreetUrl()

  latestRequestId = getUniqueRequestHeader()
  latestVerificationStreet = trimStreetData(getStreet())

  const options = {
    headers: { 'X-Streetmix-Request-Id': latestRequestId }
  }

  window.fetch(url, options)
    .then(response => {
      if (!response.ok) {
        throw response
      }

      const requestId = parseInt(response.headers.get('X-Streetmix-Request-Id'), 10)

      if (requestId !== latestRequestId) {
        throw new Error('1')
      } else {
        return response.json()
      }
    })
    .then(transmission => {
      receiveStreetForVerification(transmission)
    })
    .catch(error => {
      // Early exit if requestId does not equal the latestRequestId
      if (error.message !== '1') return

      errorReceiveStreetForVerification(error)
    })
}

function receiveStreetForVerification (transmission) {
  const localStreetData = trimStreetData(latestVerificationStreet)
  const serverStreetData = trimStreetData(unpackStreetDataFromServerTransmission(transmission))

  if (JSON.stringify(localStreetData) !== JSON.stringify(serverStreetData)) {
    console.log('NOT EQUAL')
    console.log('-')
    console.log(JSON.stringify(localStreetData))
    console.log('-')
    console.log(JSON.stringify(serverStreetData))
    console.log('-')
    console.log(transmission)

    showStatusMessage(t('toast.reloaded'))

    infoBubble.suppress()

    unpackServerStreetData(transmission, null, null, false)
    updateEverything(true)

    trackEvent('EVENT', 'STREET_MODIFIED_ELSEWHERE', null, null, false)
  }
}

function errorReceiveStreetForVerification (data) {
  // 404 should never happen here, since 410 designates streets that have
  // been deleted (but remain hidden on the server)

  if (isSignedIn() && ((data.status === 404) || (data.status === 410))) {
    showError(ERRORS.STREET_DELETED_ELSEWHERE, true)
  }
}

function receiveStreet (transmission) {
  unpackServerStreetData(transmission, null, null, true)

  propagateUnits()

  // TODO this is stupid, only here to fill some structures
  // window.addEventListener('stmx:assets_loaded', () => {
  checkIfImagesLoaded().then(() => {
    createDomFromData()

    setServerContacted(true)

    // Legacy - remove once everything is Promise-based.
    checkIfEverythingIsLoaded()
  })
}

function unpackStreetDataFromServerTransmission (transmission) {
  // Catch a data error where a user's street might be retrieved
  // without any data in it (so-called error 9B)
  if (!transmission.data) {
    showError(ERRORS.STREET_DATA_FAILURE)
    return
  }

  var street = cloneDeep(transmission.data.street)
  street.creatorId = (transmission.creator && transmission.creator.id) || null
  street.originalStreetId = transmission.originalStreetId || null
  street.updatedAt = transmission.updatedAt || null
  street.name = transmission.name || DEFAULT_NAME
  street.location = transmission.data.street.location || null

  // If there is a location, update map state to that location
  if (street.location) {
    store.dispatch(setMapState({
      addressInformation: street.location.hierarchy,
      addressInformationLabel: street.location.label,
      markerLocation: street.location.latlng
    }))
  }

  // FIXME just read it and do 0 otherwise
  if (typeof transmission.data.street.editCount === 'undefined') {
    // console.log('editCount read is empty')
    street.editCount = null
  } else {
    street.editCount = transmission.data.street.editCount
  // console.log('editCount read is', street.editCount)
  }

  return street
}

export function unpackServerStreetData (transmission, id, namespacedId, checkIfNeedsToBeRemixed) {
  setStreet(unpackStreetDataFromServerTransmission(transmission))
  var street = getStreet()

  if (transmission.data.undoStack) {
    setUndoStack(cloneDeep(transmission.data.undoStack))
    setUndoPosition(transmission.data.undoPosition)
  } else {
    setUndoStack([])
    setUndoPosition(0)
  }

  var updatedSchema = updateToLatestSchemaVersion(street)
  var undoStack = getUndoStack()
  for (var i = 0; i < undoStack.length; i++) {
    if (updateToLatestSchemaVersion(undoStack[i])) {
      updatedSchema = true
    }
  }

  if (id) {
    setStreetId(id, namespacedId)
  } else {
    setStreetId(transmission.id, transmission.namespacedId)
  }

  if (checkIfNeedsToBeRemixed) {
    if (!isSignedIn() || (street.creatorId !== getSignInData().userId)) {
      setRemixOnFirstEdit(true)
    } else {
      setRemixOnFirstEdit(false)
    }

    if (updatedSchema && !getRemixOnFirstEdit()) {
      saveStreetToServer()
    }
  }
}

export function packServerStreetData () {
  var data = {}

  data.street = trimStreetData(getStreet())

  // Those go above data in the structure, so they need to be cleared here
  delete data.street.name
  delete data.street.originalStreetId
  delete data.street.updatedAt

  // This will be implied through authorization header
  delete data.street.creatorId

  if (FLAG_SAVE_UNDO) {
    data.undoStack = cloneDeep(getUndoStack())
    data.undoPosition = getUndoPosition()
  }

  var street = getStreet()
  var transmission = {
    name: street.name,
    originalStreetId: street.originalStreetId,
    data: data
  }

  return JSON.stringify(transmission)
}

export function setStreetId (newId, newNamespacedId) {
  var street = getStreet()
  street.id = newId
  street.namespacedId = newNamespacedId

  unifyUndoStack()

  updateLastStreetInfo()
}

export function updateLastStreetInfo () {
  const street = getStreet()
  setSettings({
    lastStreetId: street.id,
    lastStreetNamespacedId: street.namespacedId,
    lastStreetCreatorId: street.creatorId
  })
}

export function scheduleSavingStreetToServer () {
  saveStreetIncomplete = true

  clearScheduledSavingStreetToServer()

  if (getRemixOnFirstEdit()) {
    remixStreet()
  } else {
    saveStreetTimerId =
      window.setTimeout(function () { saveStreetToServer(false) }, SAVE_STREET_DELAY)
  }
}

export function fetchLastStreet () {
  newBlockingAjaxRequest(t('msg.loading'),
    {
      // TODO const
      url: API_URL + 'v1/streets/' + getSettings().priorLastStreetId,
      method: 'GET',
      headers: { 'Authorization': getAuthHeader() }
    }, receiveLastStreet, cancelReceiveLastStreet
  )
}

function cancelReceiveLastStreet () {
  document.querySelector('#new-street-default').checked = true
  makeDefaultStreet()
}

function receiveLastStreet (transmission) {
  setIgnoreStreetChanges(true)
  var street = getStreet()
  unpackServerStreetData(transmission, street.id, street.namespacedId, false)
  street.originalStreetId = getSettings().priorLastStreetId
  addRemixSuffixToName()

  if (isSignedIn()) {
    setStreetCreatorId(getSignInData().userId)
  } else {
    setStreetCreatorId(null)
  }
  setUpdateTimeToNow()
  street.editCount = 0
  // console.log('editCount = 0 on last street!')

  propagateUnits()

  unifyUndoStack()

  resizeStreetWidth()
  updateStreetName()
  createDomFromData()
  segmentsChanged()

  setIgnoreStreetChanges(false)
  setLastStreet(trimStreetData(street))

  saveStreetToServer(false)
}

export function sendDeleteStreetToServer (id) {
  // Prevents new street submenu from showing the last street
  const settings = getSettings()
  if (settings.lastStreetId === id) {
    setSettings({
      lastStreetId: null,
      lastStreetCreatorId: null,
      lastStreetNamespacedId: null
    })

    saveSettingsToServer()
  }

  // TODO const url
  newNonblockingAjaxRequest(API_URL + 'v1/streets/' + id, {
    method: 'DELETE',
    headers: { 'Authorization': getAuthHeader() }
  }, false)
}
<commit_msg>removed setting map state from unpackServer<commit_after>import { cloneDeep } from 'lodash'

import { API_URL } from '../app/config'
import { showError, ERRORS } from '../app/errors'
import { trackEvent } from '../app/event_tracking'
import {
  checkIfEverythingIsLoaded,
  setServerContacted
} from '../app/initialization'
import { checkIfImagesLoaded } from '../app/load_resources'
import { t } from '../app/locale'
import { MODES, processMode, getMode, setMode } from '../app/mode'
import { goNewStreet } from '../app/routing'
import { showStatusMessage } from '../app/status_message'
import { infoBubble } from '../info_bubble/info_bubble'
import { app } from '../preinit/app_settings'
import { segmentsChanged } from '../segments/view'
import {
  getAuthHeader,
  getSignInData,
  isSignedIn
} from '../users/authentication'
import { propagateUnits } from '../users/localization'
import {
  confirmSaveStreetToServerInitial,
  saveSettingsToServer,
  getSettings,
  setSettings
} from '../users/settings'
import {
  isblockingAjaxRequestInProgress,
  newBlockingAjaxRequest
} from '../util/fetch_blocking'
import {
  newNonblockingAjaxRequest,
  getNonblockingAjaxRequestCount
} from '../util/fetch_nonblocking'
import { NEW_STREET_EMPTY, makeDefaultStreet } from './creation'
import {
  prepareEmptyStreet,
  prepareDefaultStreet,
  getStreet,
  trimStreetData,
  updateEverything,
  createDomFromData,
  DEFAULT_NAME,
  setStreet,
  updateToLatestSchemaVersion,
  setStreetCreatorId,
  setUpdateTimeToNow,
  setLastStreet
} from './data_model'
import { updateStreetName } from './name'
import {
  getRemixOnFirstEdit,
  setRemixOnFirstEdit,
  remixStreet,
  addRemixSuffixToName
} from './remix'
import {
  setUndoStack,
  setUndoPosition,
  getUndoStack,
  FLAG_SAVE_UNDO,
  getUndoPosition,
  unifyUndoStack,
  setIgnoreStreetChanges
} from './undo_stack'
import { resizeStreetWidth } from './width'
import store from '../store'

const SAVE_STREET_DELAY = 500

var saveStreetTimerId = -1
let saveStreetIncomplete = false

export function getSaveStreetIncomplete () {
  return saveStreetIncomplete
}

export function setSaveStreetIncomplete (value) {
  saveStreetIncomplete = value
}

var uniqueRequestId = 0

var latestRequestId
var latestVerificationStreet

function getUniqueRequestHeader () {
  uniqueRequestId++
  return uniqueRequestId
}

export function createNewStreetOnServer () {
  if (getSettings().newStreetPreference === NEW_STREET_EMPTY) {
    prepareEmptyStreet()
  } else {
    prepareDefaultStreet()
  }

  const options = {
    method: 'POST',
    body: packServerStreetData(),
    headers: {
      'Authorization': getAuthHeader(),
      'Content-Type': 'application/json'
    }
  }

  // TODO const url
  window.fetch(API_URL + 'v1/streets', options)
    .then(response => {
      if (!response.ok) {
        throw response
      }
      return response.json()
    })
    .then(receiveNewStreet)
    .catch(errorReceiveNewStreet)
}

function receiveNewStreet (data) {
  setStreetId(data.id, data.namespacedId)

  saveStreetToServer(true)
}

function errorReceiveNewStreet (data) {
  showError(ERRORS.NEW_STREET_SERVER_FAILURE, true)
}

export function getFetchStreetUrl () {
  // TODO const
  let url
  var street = getStreet()
  if (street.creatorId) {
    url = API_URL + 'v1/streets?namespacedId=' +
      encodeURIComponent(street.namespacedId) + '&creatorId=' +
      encodeURIComponent(street.creatorId)
  } else {
    url = API_URL + 'v1/streets?namespacedId=' +
      encodeURIComponent(street.namespacedId)
  }

  return url
}

export function fetchStreetFromServer () {
  var url = getFetchStreetUrl()

  window.fetch(url)
    .then(response => response.json())
    .catch(errorReceiveStreet)
    .then(receiveStreet)
}

function errorReceiveStreet (data) {
  let mode = getMode()
  if ((mode === MODES.CONTINUE) || (mode === MODES.USER_GALLERY) ||
    (mode === MODES.GLOBAL_GALLERY)) {
    goNewStreet()
  } else {
    if ((data.status === 404) || (data.status === 410)) {
      if (getStreet().creatorId) {
        if (data.status === 410) {
          setMode(MODES.STREET_410_BUT_LINK_TO_USER)
        } else {
          setMode(MODES.STREET_404_BUT_LINK_TO_USER)
        }
      } else {
        setMode(MODES.STREET_404)
      }
      // TODO swap for showError (here and elsewhere)
      processMode()
    } else {
      showError(ERRORS.NEW_STREET_SERVER_FAILURE, true)
    }
  }
}

export function saveStreetToServer (initial) {
  if (app.readOnly) {
    return
  }

  const transmission = packServerStreetData()
  const street = getStreet()
  const url = API_URL + 'v1/streets/' + street.id
  const options = {
    method: 'PUT',
    body: transmission,
    headers: {
      'Authorization': getAuthHeader(),
      'Content-Type': 'application/json'
    }
  }

  if (initial) {
    // blocking
    window.fetch(url, options)
      .then(confirmSaveStreetToServerInitial)
  } else {
    newNonblockingAjaxRequest(url, options, false)
  }
}

function clearScheduledSavingStreetToServer () {
  window.clearTimeout(saveStreetTimerId)
}

export function fetchStreetForVerification () {
  // Don’t do it with any network services pending
  if (getNonblockingAjaxRequestCount() || isblockingAjaxRequestInProgress() ||
    saveStreetIncomplete || store.getState().errors.abortEverything || getRemixOnFirstEdit()) {
    return
  }

  const url = getFetchStreetUrl()

  latestRequestId = getUniqueRequestHeader()
  latestVerificationStreet = trimStreetData(getStreet())

  const options = {
    headers: { 'X-Streetmix-Request-Id': latestRequestId }
  }

  window.fetch(url, options)
    .then(response => {
      if (!response.ok) {
        throw response
      }

      const requestId = parseInt(response.headers.get('X-Streetmix-Request-Id'), 10)

      if (requestId !== latestRequestId) {
        throw new Error('1')
      } else {
        return response.json()
      }
    })
    .then(transmission => {
      receiveStreetForVerification(transmission)
    })
    .catch(error => {
      // Early exit if requestId does not equal the latestRequestId
      if (error.message !== '1') return

      errorReceiveStreetForVerification(error)
    })
}

function receiveStreetForVerification (transmission) {
  const localStreetData = trimStreetData(latestVerificationStreet)
  const serverStreetData = trimStreetData(unpackStreetDataFromServerTransmission(transmission))

  if (JSON.stringify(localStreetData) !== JSON.stringify(serverStreetData)) {
    console.log('NOT EQUAL')
    console.log('-')
    console.log(JSON.stringify(localStreetData))
    console.log('-')
    console.log(JSON.stringify(serverStreetData))
    console.log('-')
    console.log(transmission)

    showStatusMessage(t('toast.reloaded'))

    infoBubble.suppress()

    unpackServerStreetData(transmission, null, null, false)
    updateEverything(true)

    trackEvent('EVENT', 'STREET_MODIFIED_ELSEWHERE', null, null, false)
  }
}

function errorReceiveStreetForVerification (data) {
  // 404 should never happen here, since 410 designates streets that have
  // been deleted (but remain hidden on the server)

  if (isSignedIn() && ((data.status === 404) || (data.status === 410))) {
    showError(ERRORS.STREET_DELETED_ELSEWHERE, true)
  }
}

function receiveStreet (transmission) {
  unpackServerStreetData(transmission, null, null, true)

  propagateUnits()

  // TODO this is stupid, only here to fill some structures
  // window.addEventListener('stmx:assets_loaded', () => {
  checkIfImagesLoaded().then(() => {
    createDomFromData()

    setServerContacted(true)

    // Legacy - remove once everything is Promise-based.
    checkIfEverythingIsLoaded()
  })
}

function unpackStreetDataFromServerTransmission (transmission) {
  // Catch a data error where a user's street might be retrieved
  // without any data in it (so-called error 9B)
  if (!transmission.data) {
    showError(ERRORS.STREET_DATA_FAILURE)
    return
  }

  var street = cloneDeep(transmission.data.street)
  street.creatorId = (transmission.creator && transmission.creator.id) || null
  street.originalStreetId = transmission.originalStreetId || null
  street.updatedAt = transmission.updatedAt || null
  street.name = transmission.name || DEFAULT_NAME
  street.location = transmission.data.street.location || null

  // FIXME just read it and do 0 otherwise
  if (typeof transmission.data.street.editCount === 'undefined') {
    // console.log('editCount read is empty')
    street.editCount = null
  } else {
    street.editCount = transmission.data.street.editCount
  // console.log('editCount read is', street.editCount)
  }

  return street
}

export function unpackServerStreetData (transmission, id, namespacedId, checkIfNeedsToBeRemixed) {
  setStreet(unpackStreetDataFromServerTransmission(transmission))
  var street = getStreet()

  if (transmission.data.undoStack) {
    setUndoStack(cloneDeep(transmission.data.undoStack))
    setUndoPosition(transmission.data.undoPosition)
  } else {
    setUndoStack([])
    setUndoPosition(0)
  }

  var updatedSchema = updateToLatestSchemaVersion(street)
  var undoStack = getUndoStack()
  for (var i = 0; i < undoStack.length; i++) {
    if (updateToLatestSchemaVersion(undoStack[i])) {
      updatedSchema = true
    }
  }

  if (id) {
    setStreetId(id, namespacedId)
  } else {
    setStreetId(transmission.id, transmission.namespacedId)
  }

  if (checkIfNeedsToBeRemixed) {
    if (!isSignedIn() || (street.creatorId !== getSignInData().userId)) {
      setRemixOnFirstEdit(true)
    } else {
      setRemixOnFirstEdit(false)
    }

    if (updatedSchema && !getRemixOnFirstEdit()) {
      saveStreetToServer()
    }
  }
}

export function packServerStreetData () {
  var data = {}

  data.street = trimStreetData(getStreet())

  // Those go above data in the structure, so they need to be cleared here
  delete data.street.name
  delete data.street.originalStreetId
  delete data.street.updatedAt

  // This will be implied through authorization header
  delete data.street.creatorId

  if (FLAG_SAVE_UNDO) {
    data.undoStack = cloneDeep(getUndoStack())
    data.undoPosition = getUndoPosition()
  }

  var street = getStreet()
  var transmission = {
    name: street.name,
    originalStreetId: street.originalStreetId,
    data: data
  }

  return JSON.stringify(transmission)
}

export function setStreetId (newId, newNamespacedId) {
  var street = getStreet()
  street.id = newId
  street.namespacedId = newNamespacedId

  unifyUndoStack()

  updateLastStreetInfo()
}

export function updateLastStreetInfo () {
  const street = getStreet()
  setSettings({
    lastStreetId: street.id,
    lastStreetNamespacedId: street.namespacedId,
    lastStreetCreatorId: street.creatorId
  })
}

export function scheduleSavingStreetToServer () {
  saveStreetIncomplete = true

  clearScheduledSavingStreetToServer()

  if (getRemixOnFirstEdit()) {
    remixStreet()
  } else {
    saveStreetTimerId =
      window.setTimeout(function () { saveStreetToServer(false) }, SAVE_STREET_DELAY)
  }
}

export function fetchLastStreet () {
  newBlockingAjaxRequest(t('msg.loading'),
    {
      // TODO const
      url: API_URL + 'v1/streets/' + getSettings().priorLastStreetId,
      method: 'GET',
      headers: { 'Authorization': getAuthHeader() }
    }, receiveLastStreet, cancelReceiveLastStreet
  )
}

function cancelReceiveLastStreet () {
  document.querySelector('#new-street-default').checked = true
  makeDefaultStreet()
}

function receiveLastStreet (transmission) {
  setIgnoreStreetChanges(true)
  var street = getStreet()
  unpackServerStreetData(transmission, street.id, street.namespacedId, false)
  street.originalStreetId = getSettings().priorLastStreetId
  addRemixSuffixToName()

  if (isSignedIn()) {
    setStreetCreatorId(getSignInData().userId)
  } else {
    setStreetCreatorId(null)
  }
  setUpdateTimeToNow()
  street.editCount = 0
  // console.log('editCount = 0 on last street!')

  propagateUnits()

  unifyUndoStack()

  resizeStreetWidth()
  updateStreetName()
  createDomFromData()
  segmentsChanged()

  setIgnoreStreetChanges(false)
  setLastStreet(trimStreetData(street))

  saveStreetToServer(false)
}

export function sendDeleteStreetToServer (id) {
  // Prevents new street submenu from showing the last street
  const settings = getSettings()
  if (settings.lastStreetId === id) {
    setSettings({
      lastStreetId: null,
      lastStreetCreatorId: null,
      lastStreetNamespacedId: null
    })

    saveSettingsToServer()
  }

  // TODO const url
  newNonblockingAjaxRequest(API_URL + 'v1/streets/' + id, {
    method: 'DELETE',
    headers: { 'Authorization': getAuthHeader() }
  }, false)
}
<|endoftext|>


<commit_before>var sitedata_keyboards = {
    "English US": {
        windows: "english-us.html",
        mac: "english-us_mac.html",
        linux: "english-us.html",
    },
    "English UK": {
        windows: "english-uk.html",
        mac: "english-uk_mac.html",
        linux: "english-uk.html",
    },
    "Danish": {
        windows: "danish.html",
        mac: "danish_mac.html",
        linux: "danish.html",
    }
}
<commit_msg>Enable UK dvorak mac layout in chooser<commit_after>var sitedata_keyboards = {
    "English US": {
        windows: "english-us.html",
        mac: "english-us_mac.html",
        linux: "english-us.html",
    },
    "English UK": {
        windows: "english-uk.html",
        mac: "english-uk_mac.html",
        linux: "english-uk.html",
    },
    "English UK Dvorak": {
        mac: "english-uk-dvorak_mac.html",
    },
    "Danish": {
        windows: "danish.html",
        mac: "danish_mac.html",
        linux: "danish.html",
    }
}
<|endoftext|>