<commit_before>/*
 *  Copyright (c) 2012, 2013 Jochen S. Klar <jklar@aip.de>,
 *                           Adrian M. Partl <apartl@aip.de>,
 *                           AIP E-Science (www.aip.de)
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  See the NOTICE file distributed with this work for additional
 *  information regarding copyright ownership. 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.
 */

/*
 * daiquiri_plot - a plugin fo jquery and flot
 *
 * (c) Jochen Klar and AIP E-Science 2012
 *
 */

var _daiquiri_plot = {
    defaults: {

    },
    items: {}
}

function Daiquiri_Plot(container, opt) {
    // set state
    this.container = container;
    this.opt = opt;

    this.ajaxError = function (jqXHR, textStatus, errorThrown) {
        var message = 'Error with ajax call';
        console.log(message);
        console.log({
            'status': jqXHR.status,
            'statusText': jqXHR.statusText,
            'responseText': jqXHR.responseText
        });
    };

    this.displayForm = function () {
        var self = this;

        // get colums by ajax call
        $.ajax({
            url: self.opt.colsurl,
            type: 'GET',
            dataType: 'json',
            headers: {
                'Accept': 'application/json'
            },
            data: {
                'db': self.opt.params.db,
                'table': self.opt.params.table
            },
            error: self.ajaxError,
            success: function (json) {
                var selectOptions = '';
                for (var i = 0; i < json.cols.length; i++) {
                    var col = json.cols[i].name;
                    selectOptions += '<option label="' + col + '"value="'+ col +'">' + col + '</option>'
                }

                var html = '
Select Colums for plot
';
                html += '
';
                html += 'Column for X axis:'
                html += '<select id="xCol" name="xCol" value="1">';
                html += selectOptions;
                html += '</select>';
                html += 'Column for Y axis:'
                html += '<select id="yCol" name="yCol" value="2">';
                html += selectOptions;
                html += '</select>';
                html += 'Number of rows to plot:'
                html += '<input type="text" name="number" id="number" value="1000">'
                html += '

';
                html += '<button id="submitplot" value="Create Plot" class="btn btn-primary">Create Plot</button>'
                html += '
'

                $('
',{
                    'html' : html
                }).appendTo(self.opt.form);

                if (json.cols.length > 2) {
                    $("#xCol option[value='" + json.cols[1].name + "']").attr('selected',true);
                    $("#yCol option[value='" + json.cols[2].name + "']").attr('selected',true);
                } else if (json.cols.length == 2) {
                    $("#yCol option[value='" + json.cols[1].name + "']").attr('selected',true);
                }

                $('#submitplot').click(function(){
                    self.displayPlot($('#xCol').val(),$('#yCol').val(),$('#number').val());
                });
            }
        });
    };
    this.displayPlot = function (xCol, yCol, number) {
        var self = this;
        // get colums by ajax call
        $.ajax({
            url: self.opt.rowsurl,
            type: 'GET',
            dataType: 'json',
            headers: {
                'Accept': 'application/json'
            },
            data: {
                'db': self.opt.params.db,
                'table': self.opt.params.table,
                'nrows': number,
                'cols': xCol + ',' + yCol
            },
            error: self.ajaxError,
            success: function (json) {
                var x,y;
                var plotData = [];

                // gather data for plot
                for (var i = 0; i < json.rows.length; i++) {
                    x = json.rows[i][0];
                    if (json.rows[i].length > 1) {
                        y = json.rows[i][1];
                    } else {
                        y = x;
                    }
                    plotData.push([x,y]);
                }

                var series = {
                    color: "#900",
                    data: plotData
                }

                var opt = {
                    color: "#900",
                    lines: {
                        show: false
                    },
                    points: {
                        radius: 0.5,
                        show: true,
                        fill: true,
                        fillColor: "#900"

                    },
                    shadowSize: 0,
                    xaxis: {
                        tickFormatter: self.tickFormatter
                    },
                    yaxis: {
                        tickFormatter: self.tickFormatter
                    }
                };

                // draw plot
                $.plot(self.container, [series], opt);
            }
        });
    };

    /**
     * Tick formatter to format ticks.
     */
    this.tickFormatter = function(val, axis) {
        if (val > 1000) {
            var exp = Math.floor(Math.log(val) / Math.LN10);
            var man = val / Math.pow(10,exp);
            return (man).toFixed(3) + "E" + exp;
        } else {
            return val.toFixed(axis.tickDecimals);
        }
    }

    /**
     * Resets the container to the inital state
     */
    this.reset = function() {
        this.container.children().remove();
        this.opt.form.children().remove();
    };
}

// main plugin
(function($){
    $.fn.extend({
        daiquiri_plot: function(opt) {

            // apply default options
            opt = $.extend(_daiquiri_plot.defaults, opt);

            return this.each(function() {
                var id = $(this).attr('id');

                // check if plot is already set
                if (_daiquiri_plot.items[id] == undefined) {
                    _daiquiri_plot.items[id] = new Daiquiri_Plot($(this),opt);
                } else {
                    _daiquiri_plot.items[id].reset();
                    _daiquiri_plot.items[id].opt = opt;
                }

                _daiquiri_plot.items[id].displayForm();
            });
        }
    });
})(jQuery);
<commit_msg>fix for plot<commit_after>/*
 *  Copyright (c) 2012, 2013 Jochen S. Klar <jklar@aip.de>,
 *                           Adrian M. Partl <apartl@aip.de>,
 *                           AIP E-Science (www.aip.de)
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  See the NOTICE file distributed with this work for additional
 *  information regarding copyright ownership. 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.
 */

/*
 * daiquiri_plot - a plugin fo jquery and flot
 *
 * (c) Jochen Klar and AIP E-Science 2012
 *
 */

var _daiquiri_plot = {
    defaults: {

    },
    items: {}
}

function Daiquiri_Plot(container, opt) {
    // set state
    this.container = container;
    this.opt = opt;

    this.ajaxError = function (jqXHR, textStatus, errorThrown) {
        var message = 'Error with ajax call';
        console.log(message);
        console.log({
            'status': jqXHR.status,
            'statusText': jqXHR.statusText,
            'responseText': jqXHR.responseText
        });
    };

    this.displayForm = function () {
        var self = this;

        // get colums by ajax call
        $.ajax({
            url: self.opt.colsurl,
            type: 'GET',
            dataType: 'json',
            headers: {
                'Accept': 'application/json'
            },
            data: {
                'db': self.opt.params.db,
                'table': self.opt.params.table
            },
            error: self.ajaxError,
            success: function (json) {
                var selectOptions = '';
                for (var i = 0; i < json.cols.length; i++) {
                    var col = json.cols[i].name;
                    selectOptions += '<option label="' + col + '"value="'+ col +'">' + col + '</option>'
                }

                var html = '
Select Colums for plot
';
                html += '
';
                html += 'Column for X axis:'
                html += '<select id="xCol" name="xCol" value="1">';
                html += selectOptions;
                html += '</select>';
                html += 'Column for Y axis:'
                html += '<select id="yCol" name="yCol" value="2">';
                html += selectOptions;
                html += '</select>';
                html += 'Number of rows to plot:'
                html += '<input type="text" name="number" id="number" value="1000">'
                html += '

';
                html += '<button id="submitplot" value="Create Plot" class="btn btn-primary">Create Plot</button>'
                html += '
'

                $('
',{
                    'html' : html
                }).appendTo(self.opt.form);

                if (json.cols.length > 2) {
                    $("#xCol option[value='" + json.cols[1].name + "']").attr('selected',true);
                    $("#yCol option[value='" + json.cols[2].name + "']").attr('selected',true);
                } else if (json.cols.length == 2) {
                    $("#yCol option[value='" + json.cols[1].name + "']").attr('selected',true);
                }

                $('#submitplot').click(function(){
                    self.displayPlot($('#xCol').val(),$('#yCol').val(),$('#number').val());
                });
            }
        });
    };
    this.displayPlot = function (xCol, yCol, number) {
        var self = this;
        // get colums by ajax call
        $.ajax({
            url: self.opt.rowsurl,
            type: 'GET',
            dataType: 'json',
            headers: {
                'Accept': 'application/json'
            },
            data: {
                'db': self.opt.params.db,
                'table': self.opt.params.table,
                'nrows': number,
                'cols': xCol + ',' + yCol
            },
            error: self.ajaxError,
            success: function (json) {
                var x,y;
                var plotData = [];

                // gather data for plot
                for (var i = 0; i < json.rows.length; i++) {
                    x = json.rows[i]['cell'][0];
                    if (json.rows[i]['cell'].length > 1) {
                        y = json.rows[i]['cell'][1];
                    } else {
                        y = x;
                    }
                    plotData.push([x,y]);
                }

                var series = {
                    color: "#900",
                    data: plotData
                }

                var opt = {
                    color: "#900",
                    lines: {
                        show: false
                    },
                    points: {
                        radius: 0.5,
                        show: true,
                        fill: true,
                        fillColor: "#900"

                    },
                    shadowSize: 0,
                    xaxis: {
                        tickFormatter: self.tickFormatter
                    },
                    yaxis: {
                        tickFormatter: self.tickFormatter
                    }
                };

                // draw plot
                $.plot(self.container, [series], opt);
            }
        });
    };

    /**
     * Tick formatter to format ticks.
     */
    this.tickFormatter = function(val, axis) {
        if (val > 1000) {
            var exp = Math.floor(Math.log(val) / Math.LN10);
            var man = val / Math.pow(10,exp);
            return (man).toFixed(3) + "E" + exp;
        } else {
            return val.toFixed(axis.tickDecimals);
        }
    }

    /**
     * Resets the container to the inital state
     */
    this.reset = function() {
        this.container.children().remove();
        this.opt.form.children().remove();
    };
}

// main plugin
(function($){
    $.fn.extend({
        daiquiri_plot: function(opt) {

            // apply default options
            opt = $.extend(_daiquiri_plot.defaults, opt);

            return this.each(function() {
                var id = $(this).attr('id');

                // check if plot is already set
                if (_daiquiri_plot.items[id] == undefined) {
                    _daiquiri_plot.items[id] = new Daiquiri_Plot($(this),opt);
                } else {
                    _daiquiri_plot.items[id].reset();
                    _daiquiri_plot.items[id].opt = opt;
                }

                _daiquiri_plot.items[id].displayForm();
            });
        }
    });
})(jQuery);
<|endoftext|>


<commit_before>/* eslint-disable react/forbid-prop-types */
/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

import React, { useCallback } from 'react';
import PropTypes from 'prop-types';
import { View } from 'react-native';
import { connect } from 'react-redux';

import { getItemLayout, getPageDispatchers } from './dataTableUtilities';

import { DataTable, DataTableHeaderRow, DataTableRow } from '../widgets/DataTable';

import globalStyles from '../globalStyles';
import { useSyncListener } from '../hooks';

import { DataTablePageView, SearchBar } from '../widgets';

import { ItemDetails } from '../widgets/modals/ItemDetails';

import { ROUTES } from '../navigation/constants';

/**
 * Renders a mSupply mobile page with Items and their stock levels.
 *
 * State:
 * Uses a reducer to manage state with `backingData` being a realm results
 * of items to display. `data` is a plain JS array of realm objects. data is
 * hydrated from the `backingData` for displaying in the interface.
 * i.e: When filtering, data is populated from filtered items of `backingData`.
 *
 * dataState is a simple map of objects corresponding to a row being displayed,
 * holding the state of a given row. Each object has the shape :
 * { isSelected, isFocused, isDisabled },
 */
export const Stock = ({
  data,
  dataState,
  sortKey,
  isAscending,
  selectedRow,
  searchTerm,
  keyExtractor,
  columns,
  refreshData,
  onSelectRow,
  onDeselectRow,
  onFilterData,
  onSortColumn,
}) => {
  //  Refresh data on retrieving item or itembatch records from sync.
  useSyncListener(refreshData, ['Item', 'ItemBatch']);

  const renderRow = useCallback(
    listItem => {
      const { item, index } = listItem;
      const rowKey = keyExtractor(item);
      return (
        <DataTableRow
          rowData={data[index]}
          rowState={dataState.get(rowKey)}
          rowKey={rowKey}
          columns={columns}
          rowIndex={index}
          onPress={onSelectRow}
        />
      );
    },
    [data, dataState]
  );

  const renderHeader = () => (
    <DataTableHeaderRow
      columns={columns}
      onPress={onSortColumn}
      isAscending={isAscending}
      sortKey={sortKey}
    />
  );

  const { pageTopSectionContainer } = globalStyles;
  return (
    <DataTablePageView captureUncaughtGestures={false}>
      <View style={pageTopSectionContainer}>
        <SearchBar
          onChangeText={onFilterData}
          value={searchTerm}
          onFocusOrBlur={selectedRow && onDeselectRow}
        />
      </View>

      <DataTable
        data={data}
        extraData={dataState}
        renderRow={renderRow}
        renderHeader={renderHeader}
        keyExtractor={keyExtractor}
        getItemLayout={getItemLayout}
        columns={columns}
      />

      <ItemDetails isOpen={!!selectedRow} item={selectedRow} onClose={onDeselectRow} />
    </DataTablePageView>
  );
};

const mapDispatchToProps = (dispatch, ownProps) =>
  getPageDispatchers(dispatch, ownProps, '', ROUTES.STOCK);

const mapStateToProps = state => {
  const { pages } = state;

  const { stock } = pages;
  return stock;
};

export const StockPage = connect(mapStateToProps, mapDispatchToProps)(Stock);

Stock.defaultProps = {
  selectedRow: null,
};

Stock.propTypes = {
  data: PropTypes.array.isRequired,
  dataState: PropTypes.object.isRequired,
  sortKey: PropTypes.string.isRequired,
  isAscending: PropTypes.bool.isRequired,
  selectedRow: PropTypes.object,
  searchTerm: PropTypes.string.isRequired,
  keyExtractor: PropTypes.func.isRequired,
  columns: PropTypes.array.isRequired,
  refreshData: PropTypes.func.isRequired,
  onSelectRow: PropTypes.func.isRequired,
  onDeselectRow: PropTypes.func.isRequired,
  onFilterData: PropTypes.func.isRequired,
  onSortColumn: PropTypes.func.isRequired,
};
<commit_msg>add sorting key to searchbar<commit_after>/* eslint-disable react/forbid-prop-types */
/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

import React, { useCallback } from 'react';
import PropTypes from 'prop-types';
import { View } from 'react-native';
import { connect } from 'react-redux';

import { getItemLayout, getPageDispatchers } from './dataTableUtilities';

import { DataTable, DataTableHeaderRow, DataTableRow } from '../widgets/DataTable';

import globalStyles from '../globalStyles';
import { useSyncListener } from '../hooks';

import { DataTablePageView, SearchBar } from '../widgets';

import { ItemDetails } from '../widgets/modals/ItemDetails';

import { ROUTES } from '../navigation/constants';
import { generalStrings, tableStrings } from '../localization';

/**
 * Renders a mSupply mobile page with Items and their stock levels.
 *
 * State:
 * Uses a reducer to manage state with `backingData` being a realm results
 * of items to display. `data` is a plain JS array of realm objects. data is
 * hydrated from the `backingData` for displaying in the interface.
 * i.e: When filtering, data is populated from filtered items of `backingData`.
 *
 * dataState is a simple map of objects corresponding to a row being displayed,
 * holding the state of a given row. Each object has the shape :
 * { isSelected, isFocused, isDisabled },
 */
export const Stock = ({
  data,
  dataState,
  sortKey,
  isAscending,
  selectedRow,
  searchTerm,
  keyExtractor,
  columns,
  refreshData,
  onSelectRow,
  onDeselectRow,
  onFilterData,
  onSortColumn,
}) => {
  //  Refresh data on retrieving item or itembatch records from sync.
  useSyncListener(refreshData, ['Item', 'ItemBatch']);

  const renderRow = useCallback(
    listItem => {
      const { item, index } = listItem;
      const rowKey = keyExtractor(item);
      return (
        <DataTableRow
          rowData={data[index]}
          rowState={dataState.get(rowKey)}
          rowKey={rowKey}
          columns={columns}
          rowIndex={index}
          onPress={onSelectRow}
        />
      );
    },
    [data, dataState]
  );

  const renderHeader = () => (
    <DataTableHeaderRow
      columns={columns}
      onPress={onSortColumn}
      isAscending={isAscending}
      sortKey={sortKey}
    />
  );

  const { pageTopSectionContainer } = globalStyles;
  return (
    <DataTablePageView captureUncaughtGestures={false}>
      <View style={pageTopSectionContainer}>
        <SearchBar
          onChangeText={onFilterData}
          value={searchTerm}
          onFocusOrBlur={selectedRow && onDeselectRow}
          placeholder={`${generalStrings.searchBar} ${tableStrings.name || tableStrings.item_code}`}
        />
      </View>

      <DataTable
        data={data}
        extraData={dataState}
        renderRow={renderRow}
        renderHeader={renderHeader}
        keyExtractor={keyExtractor}
        getItemLayout={getItemLayout}
        columns={columns}
      />

      <ItemDetails isOpen={!!selectedRow} item={selectedRow} onClose={onDeselectRow} />
    </DataTablePageView>
  );
};

const mapDispatchToProps = (dispatch, ownProps) =>
  getPageDispatchers(dispatch, ownProps, '', ROUTES.STOCK);

const mapStateToProps = state => {
  const { pages } = state;

  const { stock } = pages;
  return stock;
};

export const StockPage = connect(mapStateToProps, mapDispatchToProps)(Stock);

Stock.defaultProps = {
  selectedRow: null,
};

Stock.propTypes = {
  data: PropTypes.array.isRequired,
  dataState: PropTypes.object.isRequired,
  sortKey: PropTypes.string.isRequired,
  isAscending: PropTypes.bool.isRequired,
  selectedRow: PropTypes.object,
  searchTerm: PropTypes.string.isRequired,
  keyExtractor: PropTypes.func.isRequired,
  columns: PropTypes.array.isRequired,
  refreshData: PropTypes.func.isRequired,
  onSelectRow: PropTypes.func.isRequired,
  onDeselectRow: PropTypes.func.isRequired,
  onFilterData: PropTypes.func.isRequired,
  onSortColumn: PropTypes.func.isRequired,
};
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2014 Google Inc. All Rights Reserved.
 *
 * 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.
 */

/*
 TODO:
   Use MementoMgr.
   Browser history support.
*/
MODEL({
  name: 'StackView',
  traits: ['PositionedDOMViewTrait'],
  extendsModel: 'View',
  properties: [
    {
      model_: 'ArrayProperty',
      name: 'stack',
      factory: function() { return []; }
    },
    {
      name: 'redo',
      factory: function() { return []; }
    },
    {
      name: 'slider',
      factory: function() { return this.X.ViewSlider.create(); }
    },
    {
      name: 'overlaySlider',
      factory: function() { return this.X.OverlaySlider.create(); },
      postSet: function(old, v) {
        if ( old ) old.unsubscribe(['click'], this.overlayBack);
        v.subscribe(['click'], this.overlayBack);
      }
    },
    { model_: 'BooleanProperty', name: 'sliderOpen', defaultValue: false },
    'slideLatch'
  ],
  methods: {
    init: function() {
      this.SUPER();
      var self = this;
      this.X.dynamic(function() { self.width; self.height; self.sliderOpen }, this.layout);
    },
    setPreview: function(){ console.warn('Preview removed from stack view, do it yourself.'); },
    pushView: function(view, opt_label, opt_back, opt_transition) {
      if ( ! opt_back ) {
        var prev = this.stack[this.stack.length];
        if ( prev ) prev.destroy();
        this.redo.length = 0;
        this.propertyChange('redo', this.redo, this.redo);
      }

      if ( ! view.model_.Z ) view = FloatingView.create({ view: view });

      this.stack.push(view);
      this.propertyChange('stack', this.stack, this.stack);

      if ( opt_transition === 'none' ) {
        this.slider.setView(view);
        return;
      }

      window.setTimeout(function() {
        this.slider.reverse = opt_transition === 'fromLeft';
        this.slider.slideView(view, undefined, undefined, 100);
      }.bind(this), 100)
    },
    setTopView: function(view) {
      if ( this.stack.length > 0 ) {
        this.stack[this.stack.length - 1].destroy();
      }
      this.stack = [];
      this.pushView(view, undefined, undefined, 'none');
    },
    slideView: function(view, opt_label, opt_side, opt_delay) {
      if ( ! view.model_.Z ) view = FloatingView.create({ view: view });

      if ( this.slideLatch ) {
        this.slideLatch();
        this.slideLatch = '';
      }

      if ( ! Number.isFinite(opt_delay) ) opt_delay = 100;

      this.sliderOpen = true;
      this.overlaySlider.view = view;

      var self = this;
      window.setTimeout(function() {
        self.slideLatch = Movement.animate(
          300,
          function() { self.overlaySlider.slideAmount = 1 },
          undefined,
          function() {
            self.slideLatch = '';
          })();
      }, opt_delay);
    }
  },
  listeners: [
    {
      name: 'layout',
      code: function() {
        this.overlaySlider.x = 0;
        this.overlaySlider.y = 0;
        this.overlaySlider.z = this.sliderOpen ? 1 : 0;
        this.overlaySlider.width = this.width;
        this.overlaySlider.height = this.height;

        this.slider.x = 0;
        this.slider.y = 0;
        this.slider.width = this.width;
        this.slider.height = this.height;
      }
    },
    {
      name: 'overlayBack',
      code: function() {
        if ( this.sliderOpen ) this.back();
      }
    }
  ],
  templates: [
    function toInnerHTML() {/* %%overlaySlider %%slider */}
  ],
  actions: [
    {
      name:  'back',
      label: '<',
      help:  'Go to previous view',

      isEnabled: function() { return this.stack.length > 1 || this.sliderOpen; },
      action: function() {
        if ( this.sliderOpen ) {
          if ( this.slideLatch ) {
            this.slideLatch();
            this.slideLatch = '';
          }

          var self = this;
          this.sliderOpen = false;
          this.slideLatch = Movement.animate(
            300,
            function() { self.overlaySlider.slideAmount = 0; },
            undefined,
            function() {
              self.slideLatch = '';
              self.overlaySlider.view = '';
            })();
        } else {
          var v = this.stack.pop();
          v.destroy();
          this.redo.push(v);
          this.pushView(this.stack.pop(), undefined, true, 'fromLeft');
          this.propertyChange('stack', this.stack, this.stack);
        }
      }
    },
    {
      name:  'forth',
      label: '>',
      help:  'Undo the previous back.',
      isEnabled: function() { return this.redo.length > 0; },
      action: function() {
        this.pushView(this.redo.pop());
        this.propertyChange('stack', this.redo, this.redo);
      }
    }
  ]
});
<commit_msg>StackView: Have menus ease in and out.<commit_after>/**
 * @license
 * Copyright 2014 Google Inc. All Rights Reserved.
 *
 * 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.
 */

/*
 TODO:
   Use MementoMgr.
   Browser history support.
*/
MODEL({
  name: 'StackView',
  traits: ['PositionedDOMViewTrait'],
  extendsModel: 'View',
  properties: [
    {
      model_: 'ArrayProperty',
      name: 'stack',
      factory: function() { return []; }
    },
    {
      name: 'redo',
      factory: function() { return []; }
    },
    {
      name: 'slider',
      factory: function() { return this.X.ViewSlider.create(); }
    },
    {
      name: 'overlaySlider',
      factory: function() { return this.X.OverlaySlider.create(); },
      postSet: function(old, v) {
        if ( old ) old.unsubscribe(['click'], this.overlayBack);
        v.subscribe(['click'], this.overlayBack);
      }
    },
    { model_: 'BooleanProperty', name: 'sliderOpen', defaultValue: false },
    'slideLatch'
  ],
  constants: {
    EASE_ACCELERATION: 0.9
  },
  methods: {
    init: function() {
      this.SUPER();
      var self = this;
      this.X.dynamic(function() { self.width; self.height; self.sliderOpen }, this.layout);
    },
    setPreview: function(){ console.warn('Preview removed from stack view, do it yourself.'); },
    pushView: function(view, opt_label, opt_back, opt_transition) {
      if ( ! opt_back ) {
        var prev = this.stack[this.stack.length];
        if ( prev ) prev.destroy();
        this.redo.length = 0;
        this.propertyChange('redo', this.redo, this.redo);
      }

      if ( ! view.model_.Z ) view = FloatingView.create({ view: view });

      this.stack.push(view);
      this.propertyChange('stack', this.stack, this.stack);

      if ( opt_transition === 'none' ) {
        this.slider.setView(view);
        return;
      }

      window.setTimeout(function() {
        this.slider.reverse = opt_transition === 'fromLeft';
        this.slider.slideView(view, undefined, undefined, 100);
      }.bind(this), 100)
    },
    setTopView: function(view) {
      if ( this.stack.length > 0 ) {
        this.stack[this.stack.length - 1].destroy();
      }
      this.stack = [];
      this.pushView(view, undefined, undefined, 'none');
    },
    slideView: function(view, opt_label, opt_side, opt_delay) {
      if ( ! view.model_.Z ) view = FloatingView.create({ view: view });

      if ( this.slideLatch ) {
        this.slideLatch();
        this.slideLatch = '';
      }

      if ( ! Number.isFinite(opt_delay) ) opt_delay = 100;

      this.sliderOpen = true;
      this.overlaySlider.view = view;

      var self = this;
      window.setTimeout(function() {
        self.slideLatch = Movement.animate(
          300,
          function() { self.overlaySlider.slideAmount = 1 },
          Movement.easeOut(self.EASE_ACCELERATION),
          function() {
            self.slideLatch = '';
          })();
      }, opt_delay);
    }
  },
  listeners: [
    {
      name: 'layout',
      code: function() {
        this.overlaySlider.x = 0;
        this.overlaySlider.y = 0;
        this.overlaySlider.z = this.sliderOpen ? 1 : 0;
        this.overlaySlider.width = this.width;
        this.overlaySlider.height = this.height;

        this.slider.x = 0;
        this.slider.y = 0;
        this.slider.width = this.width;
        this.slider.height = this.height;
      }
    },
    {
      name: 'overlayBack',
      code: function() {
        if ( this.sliderOpen ) this.back();
      }
    }
  ],
  templates: [
    function toInnerHTML() {/* %%overlaySlider %%slider */}
  ],
  actions: [
    {
      name:  'back',
      label: '<',
      help:  'Go to previous view',

      isEnabled: function() { return this.stack.length > 1 || this.sliderOpen; },
      action: function() {
        if ( this.sliderOpen ) {
          if ( this.slideLatch ) {
            this.slideLatch();
            this.slideLatch = '';
          }

          var self = this;
          this.sliderOpen = false;
          this.slideLatch = Movement.animate(
            300,
            function() { self.overlaySlider.slideAmount = 0; },
            Movement.easeIn(this.EASE_ACCELERATION),
            function() {
              self.slideLatch = '';
              self.overlaySlider.view = '';
            })();
        } else {
          var v = this.stack.pop();
          v.destroy();
          this.redo.push(v);
          this.pushView(this.stack.pop(), undefined, true, 'fromLeft');
          this.propertyChange('stack', this.stack, this.stack);
        }
      }
    },
    {
      name:  'forth',
      label: '>',
      help:  'Undo the previous back.',
      isEnabled: function() { return this.redo.length > 0; },
      action: function() {
        this.pushView(this.redo.pop());
        this.propertyChange('stack', this.redo, this.redo);
      }
    }
  ]
});
<|endoftext|>


<commit_before>// @flow
import React from 'react'
import Document, { Html, Head, Main, NextScript } from 'next/document'
import { readFileSync } from 'fs'
import { join } from 'path'

class InlineStylesHead extends Head {
  getCssLinks() {
    return this.__getInlineStyles()
  }

  __getInlineStyles() {
    const { assetPrefix, files } = this.context
    if (!files || files.length === 0) return null

    return files
      .filter((file) => /\.css$/.test(file))
      .map((file) => (


<commit_before>
const RG = require('./rg');

const MEM_NO_ACTORS = Object.freeze([]);

/* Memory is used by the actor to hold information about enemies, items etc.
 * It's a separate object from decision-making brain.*/
const Memory = function() {

    this._actors = {};
    this._enemyTypes = {}; // List of enemy types for this actor
    this._communications = [];
    this._lastAttackedID = null;
    // TODO add memory of player closing a door/using stairs
};

/* Adds a generic enemy type. */
Memory.prototype.addEnemyType = function(type) {
    this._enemyTypes[type] = true;
};

/* Removes a generic enemy type. */
Memory.prototype.removeEnemyType = function(type) {
    if (this._enemyTypes[type]) {
        delete this._enemyTypes[type];
    }
};

/* Checks if given actor is an enemy. */
Memory.prototype.isEnemy = function(actor) {
    if (this._actors.hasOwnProperty('enemies')) {
        const index = this._actors.enemies.indexOf(actor);
        if (index >= 0) {return true;}
    }
    if (!this.isFriend(actor)) {
        if (this._enemyTypes[actor.getType()]) {
            return true;
        }
        if (!actor.isPlayer) {
            const json = JSON.stringify(actor);
            RG.err('Memory', 'isEnemy',
                'Actor has not isPlayer() ' + json);
        }
        if (actor.isPlayer()) {
            return this._enemyTypes.player;
        }
    }
    return false;
};

/* Checks if actor is a friend. */
Memory.prototype.isFriend = function(actor) {
    if (this._actors.hasOwnProperty('friends')) {
        const index = this._actors.friends.indexOf(actor);
        return index >= 0;
    }
    return false;
};

/* Adds an actor friend. */
Memory.prototype.addFriend = function(actor) {
    if (this.isEnemy(actor)) {
        this.removeEnemy(actor);
    }
    if (!this._actors.hasOwnProperty('friends')) {
        this._actors.friends = [];
    }
    if (!this.isFriend(actor)) {
        this._actors.friends.push(actor);
    }
};

Memory.prototype.addEnemySeenCell = function(actor) {
    if (!this._actors.seen) {this._actors.seen = {};}
    this._actors.seen[actor.getID()] = {x: actor.getX(), y: actor.getY(),
        level: actor.getLevel().getID()};
};

Memory.prototype.hasSeen = function(actor) {
    if (this._actors.seen && this._actors.seen[actor.getID()]) {
        return true;
    }
    return false;
};

Memory.prototype.getLastSeen = function(actor) {
    if (this._actors.seen[actor.getID()]) {
        return this._actors.seen[actor.getID()];
    }
    return null;
};

/* Adds given actor as (personal) enemy. */
Memory.prototype.addEnemy = function(actor) {
    if (!RG.isActor(actor)) {
        const json = JSON.stringify(actor);
        RG.err('Memory', 'addEnemy',
            'Only actors can be added. Got: ' + json);
    }
    if (!this.isEnemy(actor)) {
        if (this.isFriend(actor)) {
            this.removeFriend(actor);
        }
        if (!this._actors.hasOwnProperty('enemies')) {
            this._actors.enemies = [];
        }
        this._actors.enemies.push(actor);
        if (this._communications.length > 0) {
            this._communications = []; // Invalidate communications
        }
    }
};

Memory.prototype.removeEnemy = function(actor) {
    if (this._actors.hasOwnProperty('enemies')) {
        const index = this._actors.enemies.indexOf(actor);
        if (index >= 0) {
            this._actors.enemies.splice(index, 1);
        }
    }
};

Memory.prototype.removeFriend = function(actor) {
    if (this._actors.hasOwnProperty('friends')) {
        const index = this._actors.friends.indexOf(actor);
        if (index >= 0) {
            this._actors.friends.splice(index, 1);
        }
    }
};

Memory.prototype.getEnemies = function() {
    return this._actors.enemies || MEM_NO_ACTORS;
};

Memory.prototype.getFriends = function() {
    return this._actors.friends || MEM_NO_ACTORS;
};

/* Adds a communication with given actor. */
Memory.prototype.addCommunicationWith = function(actor) {
    if (!this.hasCommunicatedWith(actor)) {
        this._communications.push(actor);
    }
};

Memory.prototype.wasLastAttacked = function(actor) {
    return this._lastAttackedID === actor.getID();
};

/* Sets last attacked actor. This is used to prevent actor from switching
 * target between attacks (which is ineffective to kill anything). */
Memory.prototype.setLastAttacked = function(actor) {
    if (actor) {
        this._lastAttackedID = actor.getID();
    }
    else {
        // When restoring game, actor can be null (ie it was killed), but
        // this actor does not know it
        this._lastAttackedID = null;
    }
};

/* Returns true if has communicated with given actor.*/
Memory.prototype.hasCommunicatedWith = function(actor) {
    const index = this._communications.indexOf(actor);
    return index !== -1;
};

Memory.prototype.toJSON = function() {
    const obj = {
        enemyTypes: Object.keys(this._enemyTypes)
    };
    if (this._actors.hasOwnProperty('enemies')) {
        obj.enemies = this._actors.enemies.map(enemy => enemy.getID());
    }
    if (this._actors.hasOwnProperty('friends')) {
        obj.friends = this._actors.friends.map(enemy => enemy.getID());
    }
    if (this._lastAttackedID) {
        obj.lastAttackedID = this._lastAttackedID;
    }
    if (this._actors.hasOwnProperty('seen')) {
        obj.seen = this._actors.seen;
    }
    return obj;
};

module.exports = Memory;
<commit_msg>Added copyMemoryFrom(). Useful for summoners.<commit_after>
const RG = require('./rg');

const MEM_NO_ACTORS = Object.freeze([]);

/* Memory is used by the actor to hold information about enemies, items etc.
 * It's a separate object from decision-making brain.*/
const Memory = function() {

    this._actors = {};
    this._enemyTypes = {}; // List of enemy types for this actor
    this._communications = [];
    this._lastAttackedID = null;
    // TODO add memory of player closing a door/using stairs
};

/* Adds a generic enemy type. */
Memory.prototype.addEnemyType = function(type) {
    this._enemyTypes[type] = true;
};

/* Removes a generic enemy type. */
Memory.prototype.removeEnemyType = function(type) {
    if (this._enemyTypes[type]) {
        delete this._enemyTypes[type];
    }
};

/* Checks if given actor is an enemy. */
Memory.prototype.isEnemy = function(actor) {
    if (this._actors.hasOwnProperty('enemies')) {
        const index = this._actors.enemies.indexOf(actor);
        if (index >= 0) {return true;}
    }
    if (!this.isFriend(actor)) {
        if (this._enemyTypes[actor.getType()]) {
            return true;
        }
        if (!actor.isPlayer) {
            const json = JSON.stringify(actor);
            RG.err('Memory', 'isEnemy',
                'Actor has not isPlayer() ' + json);
        }
        if (actor.isPlayer()) {
            return this._enemyTypes.player;
        }
    }
    return false;
};

/* Checks if actor is a friend. */
Memory.prototype.isFriend = function(actor) {
    if (this._actors.hasOwnProperty('friends')) {
        const index = this._actors.friends.indexOf(actor);
        return index >= 0;
    }
    return false;
};

/* Adds an actor friend. */
Memory.prototype.addFriend = function(actor) {
    if (this.isEnemy(actor)) {
        this.removeEnemy(actor);
    }
    if (!this._actors.hasOwnProperty('friends')) {
        this._actors.friends = [];
    }
    if (!this.isFriend(actor)) {
        this._actors.friends.push(actor);
    }
};

Memory.prototype.addEnemySeenCell = function(actor) {
    if (!this._actors.seen) {this._actors.seen = {};}
    this._actors.seen[actor.getID()] = {x: actor.getX(), y: actor.getY(),
        level: actor.getLevel().getID()};
};

Memory.prototype.hasSeen = function(actor) {
    if (this._actors.seen && this._actors.seen[actor.getID()]) {
        return true;
    }
    return false;
};

Memory.prototype.getLastSeen = function(actor) {
    if (this._actors.seen[actor.getID()]) {
        return this._actors.seen[actor.getID()];
    }
    return null;
};

/* Adds given actor as (personal) enemy. */
Memory.prototype.addEnemy = function(actor) {
    if (!RG.isActor(actor)) {
        const json = JSON.stringify(actor);
        RG.err('Memory', 'addEnemy',
            'Only actors can be added. Got: ' + json);
    }
    if (!this.isEnemy(actor)) {
        if (this.isFriend(actor)) {
            this.removeFriend(actor);
        }
        if (!this._actors.hasOwnProperty('enemies')) {
            this._actors.enemies = [];
        }
        this._actors.enemies.push(actor);
        if (this._communications.length > 0) {
            this._communications = []; // Invalidate communications
        }
    }
};

Memory.prototype.removeEnemy = function(actor) {
    if (this._actors.hasOwnProperty('enemies')) {
        const index = this._actors.enemies.indexOf(actor);
        if (index >= 0) {
            this._actors.enemies.splice(index, 1);
        }
    }
};

Memory.prototype.removeFriend = function(actor) {
    if (this._actors.hasOwnProperty('friends')) {
        const index = this._actors.friends.indexOf(actor);
        if (index >= 0) {
            this._actors.friends.splice(index, 1);
        }
    }
};

Memory.prototype.getEnemies = function() {
    return this._actors.enemies || MEM_NO_ACTORS;
};

Memory.prototype.getFriends = function() {
    return this._actors.friends || MEM_NO_ACTORS;
};

/* Copies memory (mainly friends/enemies) from one actor. Used when another
 * actor is summoned to copy summoner's enemies. */
Memory.prototype.copyMemoryFrom = function(actor) {
    const memory = actor.getBrain().getMemory();
    const enemies = memory.getEnemies().slice();
    this._actors.enemies = enemies;
    const friends = memory.getFriends().slice();
    this._actors.friends = friends;
    this._enemyTypes = Object.assign({}, memory._enemyTypes);
};

/* Adds a communication with given actor. */
Memory.prototype.addCommunicationWith = function(actor) {
    if (!this.hasCommunicatedWith(actor)) {
        this._communications.push(actor);
    }
};

Memory.prototype.wasLastAttacked = function(actor) {
    return this._lastAttackedID === actor.getID();
};

/* Sets last attacked actor. This is used to prevent actor from switching
 * target between attacks (which is ineffective to kill anything). */
Memory.prototype.setLastAttacked = function(actor) {
    if (actor) {
        this._lastAttackedID = actor.getID();
    }
    else {
        // When restoring game, actor can be null (ie it was killed), but
        // this actor does not know it
        this._lastAttackedID = null;
    }
};

/* Returns true if has communicated with given actor.*/
Memory.prototype.hasCommunicatedWith = function(actor) {
    const index = this._communications.indexOf(actor);
    return index !== -1;
};

Memory.prototype.toJSON = function() {
    const obj = {
        enemyTypes: Object.keys(this._enemyTypes)
    };
    if (this._actors.hasOwnProperty('enemies')) {
        obj.enemies = this._actors.enemies.map(enemy => enemy.getID());
    }
    if (this._actors.hasOwnProperty('friends')) {
        obj.friends = this._actors.friends.map(enemy => enemy.getID());
    }
    if (this._lastAttackedID) {
        obj.lastAttackedID = this._lastAttackedID;
    }
    if (this._actors.hasOwnProperty('seen')) {
        obj.seen = this._actors.seen;
    }
    return obj;
};

module.exports = Memory;
<|endoftext|>


<commit_before>var TableView2 = FOAM({
  model_: 'Model',

  extendsModel: 'AbstractView',

  name: 'TableView2',
  label: 'Table View',

  properties: [
    {
      name:  'model',
      type:  'Model'
    },
    {
      model_: StringArrayProperty,
      name:  'properties',
      preSet: function(a) { return ! a || a.length == 0 ? null : a; },
      postSet: function() { this.repaint_(); },
      defaultValue: null
    },
    {
      name:  'hardSelection',
      type:  'Value',
      valueFactory: function() { return SimpleValue.create(); }
    },
    {
      name:  'selection',
      type:  'Value',
      valueFactory: function() { return SimpleValue.create(); }
    },
    {
      name:  'children',
      type:  'Array[View]',
      valueFactory: function() { return []; }
    },
    {
      name:  'sortOrder',
      type:  'Comparator',
      postSet: function() { this.repaint_(); },
      defaultValue: undefined
    },
    {
      name:  'dao',
      label: 'DAO',
      type: 'DAO',
      required: true,
      hidden: true,
      postSet: function(oldValue, val) {
        if ( oldValue && this.listener ) oldValue.unlisten(this.listener);
        this.listener && val && val.listen(this.listener);
        this.repaint_ && this.repaint_();
      }
    },
    {
      name: 'rows',
      type:  'Integer',
      defaultValue: 30,
      postSet: function() {
        this.repaint();
      }
    },
    {
      model_: 'IntegerProperty',
      name: 'height'
    },
    {
      model_: 'BooleanProperty',
      name: 'scrollEnabled',
      defaultValue: false
    },
    {
      model_: 'BooleanProperty',
      name: 'editColumnsEnabled',
      defaultValue: false
    },
    {
      name: 'scrollbar',
      type: 'ScrollCView',
      valueFactory: function() {
        var sb = ScrollCView.create({height:800, width: 20, x: 2, y: 2, size: 200, extent: 10});

        if ( this.dao ) this.dao.select(COUNT())(function(c) { sb.size = c.count; });

        return sb;
      }
    }
  ],

  listeners: [
    {
      model_: 'Method',

      name: 'onEditColumns',
      code: function(evt) {
        var v = EditColumnsView.create({
          model:               this.model,
          properties:          this.properties || this.model.tableProperties,
          availableProperties: this.model.properties
        });

        v.addPropertyListener('properties', function() {
          v.close();
          this.properties = v.properties;
          v.removePropertyListener('properties', arguments.callee);
          this.repaint();
        }.bind(this));

        this.$.insertAdjacentHTML('beforebegin', v.toHTML());
        v.initHTML();
      }
    },
    {
      model_: 'Method',

      name: 'layout',
      animate: true,
      code: function() {
        if ( ! this.$ ) {
          console.warn('Attempt to layout() $-less TableView.');
          return;
        }

        var style;
        try {
          style = window.getComputedStyle(this.$);
        } catch (x) {
          return;
        }

        console.log('height ', this.height, 'width', this.width);
        console.log('style height ', style.height, 'width', style.width);
        var height = toNum(style.height);
        this.scrollbar.height = height-150;
        this.scrollbar.parent.height = height-150;

        console.log('scrollbar height ', this.scrollbar.height, 'width', this.scrollbar.width);

        /*
        var top = 47;
        var height = 20;
        var rows = $$("tr-" + this.getID());

        // TODO: investigate how this is called on startup, it seems suspicious
        if ( rows.length > 1 ) {
          var row = rows[1];
          var style = window.getComputedStyle(row);
          // If the row is selected its height is less, so if we select two rows
        // we're sure to get one that isn't selected.
          height = Math.max(row.clientHeight, rows[0].clientHeight)+1;
          top = rows[0].offsetTop + rows[0].offsetParent.offsetTop;
        }

        this.rows = Math.floor((toNum(parent.height) - top) / height);
        */
      }
    }

  ],

  methods: {

    init: function() {
      this.SUPER();

      var self = this;
      this.repaint_ = EventService.animate(this.repaint.bind(this));

      this.listener = {
        put: self.repaint_,
        remove: self.repaint_
      };
    },

    // Not actually a method, but still works
    // TODO: add 'Constants' to Model
    DOUBLE_CLICK: "double-click", // event topic

    toHTML: function() {
      return '
' +
        '' +
        this.tableToHTML() +
        '' +
        '' +
        '' +
        this.scrollbar.toHTML() +
        '' +
        '
';
    },

    initHTML: function() {
      this.scrollbar.initHTML();
      this.scrollbar.paint();
      this.repaint();

      (this.window || window).addEventListener('resize', this.layout, false);
    },

    tableToHTML: function() {
      var model = this.model;

      if ( this.callbacks_ ) {
        // console.log('Warning: TableView.tableToHTML called twice without initHTML');
        delete this['callbacks_'];
        this.children = [];
      }

      var str = [];
      var props = [];

      str.push('
');

      //str += '


<commit_before>// [VexFlow](http://vexflow.com) - Copyright (c) Mohit Muthanna 2010.
//
// ## Description
//
// This file implements an abstract interface for notes and chords that
// are rendered on a stave. Notes have some common properties: All of them
// have a value (e.g., pitch, fret, etc.) and a duration (quarter, half, etc.)
//
// Some notes have stems, heads, dots, etc. Most notational elements that
// surround a note are called *modifiers*, and every note has an associated
// array of them. All notes also have a rendering context and belong to a stave.

import { Vex } from './vex';
import { Flow } from './tables';
import { Tickable } from './tickable';

export class Note extends Tickable {
  static get CATEGORY() { return 'note'; }
  static get STAVEPADDING() { return 12; }

  // Debug helper. Displays various note metrics for the given
  // note.
  static plotMetrics(ctx, note, yPos) {
    const metrics = note.getMetrics();
    const xStart = note.getAbsoluteX() - metrics.modLeftPx - metrics.extraLeftPx;
    const xPre1 = note.getAbsoluteX() - metrics.extraLeftPx;
    const xAbs = note.getAbsoluteX();
    const xPost1 = note.getAbsoluteX() + metrics.noteWidth;
    const xPost2 = note.getAbsoluteX() + metrics.noteWidth + metrics.extraRightPx;
    const xEnd = note.getAbsoluteX()
      + metrics.noteWidth
      + metrics.extraRightPx
      + metrics.modRightPx;
    const xFreedomRight = xEnd + note.getFormatterMetrics().freedom.right;

    const xWidth = xEnd - xStart;
    ctx.save();
    ctx.setFont('Arial', 8, '');
    ctx.fillText(Math.round(xWidth) + 'px', xStart + note.getXShift(), yPos);

    const y = (yPos + 7);
    function stroke(x1, x2, color, yy = y) {
      ctx.beginPath();
      ctx.setStrokeStyle(color);
      ctx.setFillStyle(color);
      ctx.setLineWidth(3);
      ctx.moveTo(x1 + note.getXShift(), yy);
      ctx.lineTo(x2 + note.getXShift(), yy);
      ctx.stroke();
    }

    stroke(xStart, xPre1, 'red');
    stroke(xPre1, xAbs, '#999');
    stroke(xAbs, xPost1, 'green');
    stroke(xPost1, xPost2, '#999');
    stroke(xPost2, xEnd, 'red');
    stroke(xEnd, xFreedomRight, '#DD0');
    stroke(xStart - note.getXShift(), xStart, '#BBB'); // Shift
    Vex.drawDot(ctx, xAbs + note.getXShift(), y, 'blue');

    const formatterMetrics = note.getFormatterMetrics();
    if (formatterMetrics.iterations > 0) {
      const spaceDeviation = formatterMetrics.space.deviation;
      const prefix = spaceDeviation >= 0 ? '+' : '';
      ctx.setFillStyle('red');
      ctx.fillText(prefix + Math.round(spaceDeviation),
        xAbs + note.getXShift(), yPos - 10);
    }
    ctx.restore();
  }

  static parseDuration(durationString) {
    if (typeof (durationString) !== 'string') {
      return null;
    }

    const regexp = /(\d*\/?\d+|[a-z])(d*)([nrhms]|$)/;

    const result = regexp.exec(durationString);
    if (!result) {
      return null;
    }

    const duration = result[1];
    const dots = result[2].length;
    let type = result[3];

    if (type.length === 0) {
      type = 'n';
    }

    return {
      duration,
      dots,
      type,
    };
  }

  static parseNoteStruct(noteStruct) {
    const durationString = noteStruct.duration;
    const customTypes = [];

    // Preserve backwards-compatibility
    const durationProps = Note.parseDuration(durationString);
    if (!durationProps) { return null; }

    // If specified type is invalid, return null
    let type = noteStruct.type;
    if (type && !Flow.getGlyphProps.validTypes[type]) { return null; }

    // If no type specified, check duration or custom types
    if (!type) {
      type = durationProps.type || 'n';

      // If we have keys, try and check if we've got a custom glyph
      if (noteStruct.keys !== undefined) {
        noteStruct.keys.forEach((k, i) => {
          const result = k.split('/');
          // We have a custom glyph specified after the note eg. /X2
          customTypes[i] = (result && result.length === 3) ? result[2] : type;
        });
      }
    }

    // Calculate the tick duration of the note
    let ticks = Flow.durationToTicks(durationProps.duration);
    if (ticks == null) { return null; }

    // Are there any dots?
    const dots = noteStruct.dots ? noteStruct.dots : durationProps.dots;
    if (typeof (dots) !== 'number') { return null; }

    // Add ticks as necessary depending on the numbr of dots
    let currentTicks = ticks;
    for (let i = 0; i < dots; i++) {
      if (currentTicks <= 1) return null;

      currentTicks = currentTicks / 2;
      ticks += currentTicks;
    }

    return {
      duration: durationProps.duration,
      type,
      customTypes,
      dots,
      ticks,
    };
  }

  // Every note is a tickable, i.e., it can be mutated by the `Formatter` class for
  // positioning and layout.
  // To create a new note you need to provide a `noteStruct`, which consists
  // of the following fields:
  //
  // `type`: The note type (e.g., `r` for rest, `s` for slash notes, etc.)
  // `dots`: The number of dots, which affects the duration.
  // `duration`: The time length (e.g., `q` for quarter, `h` for half, `8` for eighth etc.)
  //
  // The range of values for these parameters are available in `src/tables.js`.
  constructor(noteStruct) {
    super();
    this.setAttribute('type', 'Note');

    if (!noteStruct) {
      throw new Vex.RuntimeError(
        'BadArguments', 'Note must have valid initialization data to identify duration and type.'
      );
    }

    // Parse `noteStruct` and get note properties.
    const initStruct = Note.parseNoteStruct(noteStruct);
    if (!initStruct) {
      throw new Vex.RuntimeError(
        'BadArguments', `Invalid note initialization object: ${JSON.stringify(noteStruct)}`
      );
    }

    // Set note properties from parameters.
    this.duration = initStruct.duration;
    this.dots = initStruct.dots;
    this.noteType = initStruct.type;
    this.customTypes = initStruct.customTypes;

    if (noteStruct.duration_override) {
      // Custom duration
      this.setDuration(noteStruct.duration_override);
    } else {
      // Default duration
      this.setIntrinsicTicks(initStruct.ticks);
    }

    this.modifiers = [];

    // Get the glyph code for this note from the font.
    this.glyph = Flow.getGlyphProps(this.duration, this.noteType);
    this.customGlyphs = this.customTypes.map(t => Flow.getGlyphProps(this.duration, t));

    if (this.positions && (typeof (this.positions) !== 'object' || !this.positions.length)) {
      throw new Vex.RuntimeError('BadArguments', 'Note keys must be array type.');
    }

    // Note to play for audio players.
    this.playNote = null;

    // Positioning contexts used by the Formatter.
    this.tickContext = null;    // The current tick context.
    this.modifierContext = null;
    this.ignore_ticks = false;

    // Positioning variables
    this.width = 0;             // Width in pixels calculated after preFormat
    this.extraLeftPx = 0;       // Extra room on left for offset note head
    this.extraRightPx = 0;      // Extra room on right for offset note head
    this.x_shift = 0;           // X shift from tick context X
    this.left_modPx = 0;        // Max width of left modifiers
    this.right_modPx = 0;       // Max width of right modifiers
    this.voice = null;          // The voice that this note is in
    this.preFormatted = false;  // Is this note preFormatted?
    this.ys = [];               // list of y coordinates for each note
    // we need to hold on to these for ties and beams.

    if (noteStruct.align_center) {
      this.setCenterAlignment(noteStruct.align_center);
    }

    // The render surface.
    this.stave = null;
    this.render_options = {
      annotation_spacing: 5,
      stave_padding: Note.STAVEPADDING,
    };
  }

  // Get and set the play note, which is arbitrary data that can be used by an
  // audio player.
  getPlayNote() { return this.playNote; }
  setPlayNote(note) { this.playNote = note; return this; }

  // Don't play notes by default, call them rests. This is also used by things like
  // beams and dots for positioning.
  isRest() { return false; }

  // TODO(0xfe): Why is this method here?
  addStroke(index, stroke) {
    stroke.setNote(this);
    stroke.setIndex(index);
    this.modifiers.push(stroke);
    this.setPreFormatted(false);
    return this;
  }

  // Get and set the target stave.
  getStave() { return this.stave; }
  setStave(stave) {
    this.stave = stave;
    this.setYs([stave.getYForLine(0)]); // Update Y values if the stave is changed.
    this.context = this.stave.context;
    return this;
  }

  // `Note` is not really a modifier, but is used in
  // a `ModifierContext`.
  getCategory() { return Note.CATEGORY; }

  // Set the rendering context for the note.
  setContext(context) { this.context = context; return this; }

  // Get and set spacing to the left and right of the notes.
  getExtraLeftPx() { return this.extraLeftPx; }
  getExtraRightPx() { return this.extraRightPx; }
  setExtraLeftPx(x) { this.extraLeftPx = x; return this; }
  setExtraRightPx(x) { this.extraRightPx = x; return this; }

  // Returns true if this note has no duration (e.g., bar notes, spacers, etc.)
  shouldIgnoreTicks() { return this.ignore_ticks; }

  // Get the stave line number for the note.
  getLineNumber() { return 0; }

  // Get the stave line number for rest.
  getLineForRest() { return 0; }

  // Get the glyph associated with this note.
  getGlyph() { return this.glyph; }

  getGlyphWidth() {
    return this.glyph.getWidth(this.render_options.glyph_font_scale);
  }

  // Set and get Y positions for this note. Each Y value is associated with
  // an individual pitch/key within the note/chord.
  setYs(ys) { this.ys = ys; return this; }
  getYs() {
    if (this.ys.length === 0) {
      throw new Vex.RERR('NoYValues', 'No Y-values calculated for this note.');
    }

    return this.ys;
  }

  // Get the Y position of the space above the stave onto which text can
  // be rendered.
  getYForTopText(text_line) {
    if (!this.stave) {
      throw new Vex.RERR('NoStave', 'No stave attached to this note.');
    }

    return this.stave.getYForTopText(text_line);
  }

  // Get a `BoundingBox` for this note.
  getBoundingBox() { return null; }

  // Returns the voice that this note belongs in.
  getVoice() {
    if (!this.voice) throw new Vex.RERR('NoVoice', 'Note has no voice.');
    return this.voice;
  }

  // Attach this note to `voice`.
  setVoice(voice) {
    this.voice = voice;
    this.preFormatted = false;
    return this;
  }

  // Get and set the `TickContext` for this note.
  getTickContext() { return this.tickContext; }
  setTickContext(tc) {
    this.tickContext = tc;
    this.preFormatted = false;
    return this;
  }

  // Accessors for the note type.
  getDuration() { return this.duration; }
  isDotted() { return (this.dots > 0); }
  hasStem() { return false; }
  getDots() { return this.dots; }
  getNoteType() { return this.noteType; }
  setBeam() { return this; } // ignore parameters

  // Attach this note to a modifier context.
  setModifierContext(mc) { this.modifierContext = mc; return this; }

  // Attach a modifier to this note.
  addModifier(modifier, index = 0) {
    modifier.setNote(this);
    modifier.setIndex(index);
    this.modifiers.push(modifier);
    this.setPreFormatted(false);
    return this;
  }

  // Get the coordinates for where modifiers begin.
  getModifierStartXY() {
    if (!this.preFormatted) {
      throw new Vex.RERR('UnformattedNote', "Can't call GetModifierStartXY on an unformatted note");
    }

    return {
      x: this.getAbsoluteX(),
      y: this.ys[0],
    };
  }

  // Get bounds and metrics for this note.
  //
  // Returns a struct with fields:
  // `width`: The total width of the note (including modifiers.)
  // `noteWidth`: The width of the note head only.
  // `left_shift`: The horizontal displacement of the note.
  // `modLeftPx`: Start `X` for left modifiers.
  // `modRightPx`: Start `X` for right modifiers.
  // `extraLeftPx`: Extra space on left of note.
  // `extraRightPx`: Extra space on right of note.
  getMetrics() {
    if (!this.preFormatted) {
      throw new Vex.RERR('UnformattedNote', "Can't call getMetrics on an unformatted note.");
    }

    let modLeftPx = 0;
    let modRightPx = 0;
    if (this.modifierContext != null) {
      modLeftPx = this.modifierContext.state.left_shift;
      modRightPx = this.modifierContext.state.right_shift;
    }

    const width = this.getWidth();
    return {
      width,
      noteWidth: width - modLeftPx - modRightPx - this.extraLeftPx - this.extraRightPx,
      left_shift: this.x_shift, // TODO(0xfe): Make style consistent

      // Modifiers, accidentals etc.
      modLeftPx,
      modRightPx,

      // Displaced note head on left or right.
      extraLeftPx: this.extraLeftPx,
      extraRightPx: this.extraRightPx,
    };
  }

  // Get and set width of note. Used by the formatter for positioning.
  setWidth(width) { this.width = width; }
  getWidth() {
    if (!this.preFormatted) {
      throw new Vex.RERR('UnformattedNote', "Can't call GetWidth on an unformatted note.");
    }

    return this.width + (this.modifierContext ? this.modifierContext.getWidth() : 0);
  }

  // Displace note by `x` pixels. Used by the formatter.
  setXShift(x) { this.x_shift = x; return this; }
  getXShift() { return this.x_shift; }

  // Get `X` position of this tick context.
  getX() {
    if (!this.tickContext) {
      throw new Vex.RERR('NoTickContext', 'Note needs a TickContext assigned for an X-Value');
    }

    return this.tickContext.getX() + this.x_shift;
  }

  // Get the absolute `X` position of this note's tick context. This
  // excludes x_shift, so you'll need to factor it in if you're
  // looking for the post-formatted x-position.
  getAbsoluteX() {
    if (!this.tickContext) {
      throw new Vex.RERR('NoTickContext', 'Note needs a TickContext assigned for an X-Value');
    }

    // Position note to left edge of tick context.
    let x = this.tickContext.getX();
    if (this.stave) {
      x += this.stave.getNoteStartX() + this.render_options.stave_padding;
    }

    if (this.isCenterAligned()) {
      x += this.getCenterXShift();
    }

    return x;
  }
  setPreFormatted(value) {
    this.preFormatted = value;

    // Maintain the width of left and right modifiers in pixels.
    if (this.preFormatted) {
      const extra = this.tickContext.getExtraPx();
      this.left_modPx = Math.max(this.left_modPx, extra.left);
      this.right_modPx = Math.max(this.right_modPx, extra.right);
    }
  }
}
<commit_msg>Formatting fixes.<commit_after>// [VexFlow](http://vexflow.com) - Copyright (c) Mohit Muthanna 2010.
//
// ## Description
//
// This file implements an abstract interface for notes and chords that
// are rendered on a stave. Notes have some common properties: All of them
// have a value (e.g., pitch, fret, etc.) and a duration (quarter, half, etc.)
//
// Some notes have stems, heads, dots, etc. Most notational elements that
// surround a note are called *modifiers*, and every note has an associated
// array of them. All notes also have a rendering context and belong to a stave.

import { Vex } from './vex';
import { Flow } from './tables';
import { Tickable } from './tickable';

export class Note extends Tickable {
  static get CATEGORY() { return 'note'; }
  static get STAVEPADDING() { return 12; }

  // Debug helper. Displays various note metrics for the given
  // note.
  static plotMetrics(ctx, note, yPos) {
    const metrics = note.getMetrics();
    const xStart = note.getAbsoluteX() - metrics.modLeftPx - metrics.extraLeftPx;
    const xPre1 = note.getAbsoluteX() - metrics.extraLeftPx;
    const xAbs = note.getAbsoluteX();
    const xPost1 = note.getAbsoluteX() + metrics.noteWidth;
    const xPost2 = note.getAbsoluteX() + metrics.noteWidth + metrics.extraRightPx;
    const xEnd = note.getAbsoluteX()
      + metrics.noteWidth
      + metrics.extraRightPx
      + metrics.modRightPx;
    const xFreedomRight = xEnd + note.getFormatterMetrics().freedom.right;

    const xWidth = xEnd - xStart;
    ctx.save();
    ctx.setFont('Arial', 8, '');
    ctx.fillText(Math.round(xWidth) + 'px', xStart + note.getXShift(), yPos);

    const y = (yPos + 7);
    function stroke(x1, x2, color, yy = y) {
      ctx.beginPath();
      ctx.setStrokeStyle(color);
      ctx.setFillStyle(color);
      ctx.setLineWidth(3);
      ctx.moveTo(x1 + note.getXShift(), yy);
      ctx.lineTo(x2 + note.getXShift(), yy);
      ctx.stroke();
    }

    stroke(xStart, xPre1, 'red');
    stroke(xPre1, xAbs, '#999');
    stroke(xAbs, xPost1, 'green');
    stroke(xPost1, xPost2, '#999');
    stroke(xPost2, xEnd, 'red');
    stroke(xEnd, xFreedomRight, '#DD0');
    stroke(xStart - note.getXShift(), xStart, '#BBB'); // Shift
    Vex.drawDot(ctx, xAbs + note.getXShift(), y, 'blue');

    const formatterMetrics = note.getFormatterMetrics();
    if (formatterMetrics.iterations > 0) {
      const spaceDeviation = formatterMetrics.space.deviation;
      const prefix = spaceDeviation >= 0 ? '+' : '';
      ctx.setFillStyle('red');
      ctx.fillText(prefix + Math.round(spaceDeviation),
        xAbs + note.getXShift(), yPos - 10);
    }
    ctx.restore();
  }

  static parseDuration(durationString) {
    if (typeof (durationString) !== 'string') { return null; }

    const regexp = /(\d*\/?\d+|[a-z])(d*)([nrhms]|$)/;
    const result = regexp.exec(durationString);
    if (!result) { return null; }

    const duration = result[1];
    const dots = result[2].length;
    const type = result[3] || 'n';

    return { duration, dots, type };
  }

  static parseNoteStruct(noteStruct) {
    const durationString = noteStruct.duration;
    const customTypes = [];

    // Preserve backwards-compatibility
    const durationProps = Note.parseDuration(durationString);
    if (!durationProps) { return null; }

    // If specified type is invalid, return null
    let type = noteStruct.type;
    if (type && !Flow.getGlyphProps.validTypes[type]) { return null; }

    // If no type specified, check duration or custom types
    if (!type) {
      type = durationProps.type || 'n';

      // If we have keys, try and check if we've got a custom glyph
      if (noteStruct.keys !== undefined) {
        noteStruct.keys.forEach((k, i) => {
          const result = k.split('/');
          // We have a custom glyph specified after the note eg. /X2
          customTypes[i] = (result && result.length === 3) ? result[2] : type;
        });
      }
    }

    // Calculate the tick duration of the note
    let ticks = Flow.durationToTicks(durationProps.duration);
    if (ticks == null) { return null; }

    // Are there any dots?
    const dots = noteStruct.dots ? noteStruct.dots : durationProps.dots;
    if (typeof (dots) !== 'number') { return null; }

    // Add ticks as necessary depending on the numbr of dots
    let currentTicks = ticks;
    for (let i = 0; i < dots; i++) {
      if (currentTicks <= 1) return null;

      currentTicks = currentTicks / 2;
      ticks += currentTicks;
    }

    return {
      duration: durationProps.duration,
      type,
      customTypes,
      dots,
      ticks,
    };
  }

  // Every note is a tickable, i.e., it can be mutated by the `Formatter` class for
  // positioning and layout.
  // To create a new note you need to provide a `noteStruct`, which consists
  // of the following fields:
  //
  // `type`: The note type (e.g., `r` for rest, `s` for slash notes, etc.)
  // `dots`: The number of dots, which affects the duration.
  // `duration`: The time length (e.g., `q` for quarter, `h` for half, `8` for eighth etc.)
  //
  // The range of values for these parameters are available in `src/tables.js`.
  constructor(noteStruct) {
    super();
    this.setAttribute('type', 'Note');

    if (!noteStruct) {
      throw new Vex.RuntimeError(
        'BadArguments', 'Note must have valid initialization data to identify duration and type.'
      );
    }

    // Parse `noteStruct` and get note properties.
    const initStruct = Note.parseNoteStruct(noteStruct);
    if (!initStruct) {
      throw new Vex.RuntimeError(
        'BadArguments', `Invalid note initialization object: ${JSON.stringify(noteStruct)}`
      );
    }

    // Set note properties from parameters.
    this.duration = initStruct.duration;
    this.dots = initStruct.dots;
    this.noteType = initStruct.type;
    this.customTypes = initStruct.customTypes;

    if (noteStruct.duration_override) {
      // Custom duration
      this.setDuration(noteStruct.duration_override);
    } else {
      // Default duration
      this.setIntrinsicTicks(initStruct.ticks);
    }

    this.modifiers = [];

    // Get the glyph code for this note from the font.
    this.glyph = Flow.getGlyphProps(this.duration, this.noteType);
    this.customGlyphs = this.customTypes.map(t => Flow.getGlyphProps(this.duration, t));

    if (this.positions && (typeof (this.positions) !== 'object' || !this.positions.length)) {
      throw new Vex.RuntimeError('BadArguments', 'Note keys must be array type.');
    }

    // Note to play for audio players.
    this.playNote = null;

    // Positioning contexts used by the Formatter.
    this.tickContext = null;    // The current tick context.
    this.modifierContext = null;
    this.ignore_ticks = false;

    // Positioning variables
    this.width = 0;             // Width in pixels calculated after preFormat
    this.extraLeftPx = 0;       // Extra room on left for offset note head
    this.extraRightPx = 0;      // Extra room on right for offset note head
    this.x_shift = 0;           // X shift from tick context X
    this.left_modPx = 0;        // Max width of left modifiers
    this.right_modPx = 0;       // Max width of right modifiers
    this.voice = null;          // The voice that this note is in
    this.preFormatted = false;  // Is this note preFormatted?
    this.ys = [];               // list of y coordinates for each note
    // we need to hold on to these for ties and beams.

    if (noteStruct.align_center) {
      this.setCenterAlignment(noteStruct.align_center);
    }

    // The render surface.
    this.stave = null;
    this.render_options = {
      annotation_spacing: 5,
      stave_padding: Note.STAVEPADDING,
    };
  }

  // Get and set the play note, which is arbitrary data that can be used by an
  // audio player.
  getPlayNote() { return this.playNote; }
  setPlayNote(note) { this.playNote = note; return this; }

  // Don't play notes by default, call them rests. This is also used by things like
  // beams and dots for positioning.
  isRest() { return false; }

  // TODO(0xfe): Why is this method here?
  addStroke(index, stroke) {
    stroke.setNote(this);
    stroke.setIndex(index);
    this.modifiers.push(stroke);
    this.setPreFormatted(false);
    return this;
  }

  // Get and set the target stave.
  getStave() { return this.stave; }
  setStave(stave) {
    this.stave = stave;
    this.setYs([stave.getYForLine(0)]); // Update Y values if the stave is changed.
    this.context = this.stave.context;
    return this;
  }

  // `Note` is not really a modifier, but is used in
  // a `ModifierContext`.
  getCategory() { return Note.CATEGORY; }

  // Set the rendering context for the note.
  setContext(context) { this.context = context; return this; }

  // Get and set spacing to the left and right of the notes.
  getExtraLeftPx() { return this.extraLeftPx; }
  getExtraRightPx() { return this.extraRightPx; }
  setExtraLeftPx(x) { this.extraLeftPx = x; return this; }
  setExtraRightPx(x) { this.extraRightPx = x; return this; }

  // Returns true if this note has no duration (e.g., bar notes, spacers, etc.)
  shouldIgnoreTicks() { return this.ignore_ticks; }

  // Get the stave line number for the note.
  getLineNumber() { return 0; }

  // Get the stave line number for rest.
  getLineForRest() { return 0; }

  // Get the glyph associated with this note.
  getGlyph() { return this.glyph; }

  getGlyphWidth() {
    return this.glyph.getWidth(this.render_options.glyph_font_scale);
  }

  // Set and get Y positions for this note. Each Y value is associated with
  // an individual pitch/key within the note/chord.
  setYs(ys) { this.ys = ys; return this; }
  getYs() {
    if (this.ys.length === 0) {
      throw new Vex.RERR('NoYValues', 'No Y-values calculated for this note.');
    }

    return this.ys;
  }

  // Get the Y position of the space above the stave onto which text can
  // be rendered.
  getYForTopText(text_line) {
    if (!this.stave) {
      throw new Vex.RERR('NoStave', 'No stave attached to this note.');
    }

    return this.stave.getYForTopText(text_line);
  }

  // Get a `BoundingBox` for this note.
  getBoundingBox() { return null; }

  // Returns the voice that this note belongs in.
  getVoice() {
    if (!this.voice) throw new Vex.RERR('NoVoice', 'Note has no voice.');
    return this.voice;
  }

  // Attach this note to `voice`.
  setVoice(voice) {
    this.voice = voice;
    this.preFormatted = false;
    return this;
  }

  // Get and set the `TickContext` for this note.
  getTickContext() { return this.tickContext; }
  setTickContext(tc) {
    this.tickContext = tc;
    this.preFormatted = false;
    return this;
  }

  // Accessors for the note type.
  getDuration() { return this.duration; }
  isDotted() { return (this.dots > 0); }
  hasStem() { return false; }
  getDots() { return this.dots; }
  getNoteType() { return this.noteType; }
  setBeam() { return this; } // ignore parameters

  // Attach this note to a modifier context.
  setModifierContext(mc) { this.modifierContext = mc; return this; }

  // Attach a modifier to this note.
  addModifier(modifier, index = 0) {
    modifier.setNote(this);
    modifier.setIndex(index);
    this.modifiers.push(modifier);
    this.setPreFormatted(false);
    return this;
  }

  // Get the coordinates for where modifiers begin.
  getModifierStartXY() {
    if (!this.preFormatted) {
      throw new Vex.RERR('UnformattedNote', "Can't call GetModifierStartXY on an unformatted note");
    }

    return {
      x: this.getAbsoluteX(),
      y: this.ys[0],
    };
  }

  // Get bounds and metrics for this note.
  //
  // Returns a struct with fields:
  // `width`: The total width of the note (including modifiers.)
  // `noteWidth`: The width of the note head only.
  // `left_shift`: The horizontal displacement of the note.
  // `modLeftPx`: Start `X` for left modifiers.
  // `modRightPx`: Start `X` for right modifiers.
  // `extraLeftPx`: Extra space on left of note.
  // `extraRightPx`: Extra space on right of note.
  getMetrics() {
    if (!this.preFormatted) {
      throw new Vex.RERR('UnformattedNote', "Can't call getMetrics on an unformatted note.");
    }

    let modLeftPx = 0;
    let modRightPx = 0;
    if (this.modifierContext != null) {
      modLeftPx = this.modifierContext.state.left_shift;
      modRightPx = this.modifierContext.state.right_shift;
    }

    const width = this.getWidth();
    return {
      width,
      noteWidth: width - modLeftPx - modRightPx - this.extraLeftPx - this.extraRightPx,
      left_shift: this.x_shift, // TODO(0xfe): Make style consistent

      // Modifiers, accidentals etc.
      modLeftPx,
      modRightPx,

      // Displaced note head on left or right.
      extraLeftPx: this.extraLeftPx,
      extraRightPx: this.extraRightPx,
    };
  }

  // Get and set width of note. Used by the formatter for positioning.
  setWidth(width) { this.width = width; }
  getWidth() {
    if (!this.preFormatted) {
      throw new Vex.RERR('UnformattedNote', "Can't call GetWidth on an unformatted note.");
    }

    return this.width + (this.modifierContext ? this.modifierContext.getWidth() : 0);
  }

  // Displace note by `x` pixels. Used by the formatter.
  setXShift(x) { this.x_shift = x; return this; }
  getXShift() { return this.x_shift; }

  // Get `X` position of this tick context.
  getX() {
    if (!this.tickContext) {
      throw new Vex.RERR('NoTickContext', 'Note needs a TickContext assigned for an X-Value');
    }

    return this.tickContext.getX() + this.x_shift;
  }

  // Get the absolute `X` position of this note's tick context. This
  // excludes x_shift, so you'll need to factor it in if you're
  // looking for the post-formatted x-position.
  getAbsoluteX() {
    if (!this.tickContext) {
      throw new Vex.RERR('NoTickContext', 'Note needs a TickContext assigned for an X-Value');
    }

    // Position note to left edge of tick context.
    let x = this.tickContext.getX();
    if (this.stave) {
      x += this.stave.getNoteStartX() + this.render_options.stave_padding;
    }

    if (this.isCenterAligned()) {
      x += this.getCenterXShift();
    }

    return x;
  }
  setPreFormatted(value) {
    this.preFormatted = value;

    // Maintain the width of left and right modifiers in pixels.
    if (this.preFormatted) {
      const extra = this.tickContext.getExtraPx();
      this.left_modPx = Math.max(this.left_modPx, extra.left);
      this.right_modPx = Math.max(this.right_modPx, extra.right);
    }
  }
}
<|endoftext|>