<commit_before>/* eslint-disable import/first, max-len, no-restricted-properties */
jest.mock('cookie-cutter', () => {
  return {
    set: jest.fn(),
    get: jest.fn(),
  };
});

import React from 'react';
import { mount } from 'enzyme';
import SmartBanner from '../SmartBanner';

describe('SmartBanner', function () { // eslint-disable-line func-names
  let cookie;

  beforeEach(() => {
    jest.resetModules();

    cookie = require('cookie-cutter'); // eslint-disable-line global-require

    document.querySelector('head').innerHTML = `
      <meta name="apple-itunes-app" content="app-id=284882215">
      <meta name="google-play-app" content="app-id=com.facebook.katana">
      <meta name="kindle-fire-app" content="app-id=B0094BB4TW">
      <meta name="msApplication-ID" content="82a23635-5bd9-df11-a844-00237de2db9e">
      <meta name="msApplication-PackageFamilyName" content="facebook_9wzdncrfhv5g">`;
    document.body.innerHTML = '

';

    this.params = {
      force: '',
      title: '',
      author: '',
      url: '',
    };

    this.makeSubject = (_props = {}) => {
      const props = {
        ...this.params,
        ..._props,
      };

      return mount(
        <SmartBanner
          { ...props }
        />,
        {
          attachTo: document.getElementById('root'),
        },
      );
    };
  });

  it('should be rendered', () => {
    const subject = this.makeSubject();

    expect(subject.length).toBeTruthy();
  });

  describe('type snapshots', () => {
    it('should be matched the snapshot (no type)', () => {
      const subject = this.makeSubject();

      expect(subject.html()).toMatchSnapshot();
    });

    it('should be matched the snapshot (android)', () => {
      const subject = this.makeSubject({
        force: 'android',
      });

      expect(subject.state('type')).toBe('android');
      expect(subject.html()).toMatchSnapshot();
    });

    it('should be matched the snapshot (ios)', () => {
      const subject = this.makeSubject({
        force: 'ios',
      });

      expect(subject.state('type')).toBe('ios');
      expect(subject.html()).toMatchSnapshot();
    });

    it('should be matched the snapshot (kindle)', () => {
      const subject = this.makeSubject({
        force: 'kindle',
      });

      expect(subject.state('type')).toBe('kindle');
      expect(subject.html()).toMatchSnapshot();
    });

    it('should be matched the snapshot (windows)', () => {
      const subject = this.makeSubject({
        force: 'windows',
      });

      expect(subject.state('type')).toBe('windows');
      expect(subject.html()).toMatchSnapshot();
    });
  });

  describe('close smartbanner', () => {
    it('should change html classList and set cookie after click the close button', () => {
      const spy = jest.fn();
      const subject = this.makeSubject({
        force: 'android',
        onClose: spy,
      });

      subject.find('.smartbanner-close').simulate('click');

      expect(window.document.querySelector('html').classList).not.toContain('smartbanner-show');
      expect(cookie.set).toBeCalledWith('smartbanner-closed', 'true', {
        path: '/',
        expires: 'Fri, 26 May 2017 00:00:00 GMT',
      });
      expect(spy).toHaveBeenCalled();
    });
  });

  describe('click install on smartbanner', () => {
    it('should change html classList and set cookie after click the install button', () => {
      const spy = jest.fn();
      const subject = this.makeSubject({
        force: 'android',
        onInstall: spy,
      });

      subject.find('.smartbanner-button').simulate('click');

      expect(window.document.querySelector('html').classList).not.toContain('smartbanner-show');
      expect(cookie.set).toBeCalledWith('smartbanner-installed', 'true', {
        path: '/',
        expires: 'Thu, 24 Aug 2017 00:00:00 GMT',
      });
      expect(spy).toHaveBeenCalled();
    });
  });

  it('should only render div in SERVER env', () => {
    global.window = undefined;
    document.querySelector('head').innerHTML = '';
    const subject = this.makeSubject();

    expect(subject.html()).toBe('

');
  });

  it('should change type in state after receiving new force props', () => {
    const subject = this.makeSubject({
      force: 'android',
    });

    expect(subject.state('type')).toBe('android');

    subject.setProps({
      force: 'ios',
    });

    expect(subject.state('type')).toBe('ios');
  });

  describe('userAgent', () => {
    it('should change type to "ios" if we set iOS user agent', () => {
      window.navigator.__defineGetter__('userAgent', () => {
        return 'OperaMobile/12.02 (iPad; CPU OS 9_0 like Mac OS X) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.132 Mobile Safari/537.36';
      });

      const subject = this.makeSubject();

      expect(subject.state('type')).toBe('ios');
    });

    it('should change type to "android" if we set android user agent', () => {
      window.navigator.__defineGetter__('userAgent', () => {
        return 'Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19';
      });

      const subject = this.makeSubject();

      expect(subject.state('type')).toBe('android');
    });

    it('should change type to "windows" if we set windows phone user agent', () => {
      window.navigator.__defineGetter__('userAgent', () => {
        return 'Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; Trident/6.0; IEMobile/10.0; ARM; Touch; NOKIA; Lumia 810)';
      });

      const subject = this.makeSubject();

      expect(subject.state('type')).toBe('windows');
    });

    it('should change type to "kindle" if we set kindle user agent', () => {
      window.navigator.__defineGetter__('userAgent', () => {
        return 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_3; en-us; Silk/1.1.0-80) AppleWebKit/533.16 (KHTML, like Gecko) Version/5.0 Safari/533.16 Silk-Accelerated=true';
      });

      const subject = this.makeSubject();

      expect(subject.state('type')).toBe('kindle');
    });
  });
});
<commit_msg>Fixed test cases<commit_after>/* eslint-disable import/first, max-len, no-restricted-properties */
jest.mock('cookie-cutter', () => {
  return {
    set: jest.fn(),
    get: jest.fn(),
  };
});

import React from 'react';
import { mount } from 'enzyme';
import SmartBanner from '../SmartBanner';

describe('SmartBanner', function () { // eslint-disable-line func-names
  let cookie;

  beforeEach(() => {
    jest.resetModules();

    cookie = require('cookie-cutter'); // eslint-disable-line global-require

    document.querySelector('head').innerHTML = `
      <meta name="apple-itunes-app" content="app-id=284882215">
      <meta name="google-play-app" content="app-id=com.facebook.katana">
      <meta name="kindle-fire-app" content="app-id=B0094BB4TW">
      <meta name="msApplication-ID" content="82a23635-5bd9-df11-a844-00237de2db9e">
      <meta name="msApplication-PackageFamilyName" content="facebook_9wzdncrfhv5g">`;
    document.body.innerHTML = '

';

    this.params = {
      force: '',
      title: '',
      author: '',
      url: '',
    };

    this.makeSubject = (_props = {}) => {
      const props = {
        ...this.params,
        ..._props,
      };

      return mount(
        <SmartBanner
          { ...props }
        />,
        {
          attachTo: document.getElementById('root'),
        },
      );
    };
  });

  it('should be rendered', () => {
    const subject = this.makeSubject();

    expect(subject.length).toBeTruthy();
  });

  describe('type snapshots', () => {
    it('should be matched the snapshot (no type)', () => {
      const subject = this.makeSubject();

      expect(subject.html()).toMatchSnapshot();
    });

    it('should be matched the snapshot (android)', () => {
      const subject = this.makeSubject({
        force: 'android',
      });

      expect(subject.state('type')).toBe('android');
      expect(subject.html()).toMatchSnapshot();
    });

    it('should be matched the snapshot (ios)', () => {
      const subject = this.makeSubject({
        force: 'ios',
      });

      expect(subject.state('type')).toBe('ios');
      expect(subject.html()).toMatchSnapshot();
    });

    it('should be matched the snapshot (kindle)', () => {
      const subject = this.makeSubject({
        force: 'kindle',
      });

      expect(subject.state('type')).toBe('kindle');
      expect(subject.html()).toMatchSnapshot();
    });

    it('should be matched the snapshot (windows)', () => {
      const subject = this.makeSubject({
        force: 'windows',
      });

      expect(subject.state('type')).toBe('windows');
      expect(subject.html()).toMatchSnapshot();
    });
  });

  describe('close smartbanner', () => {
    it('should change html classList and set cookie after invoke the close function', () => {
      const spy = jest.fn();
      const subject = this.makeSubject({
        force: 'android',
        onClose: spy,
      });

      subject.instance().close();

      expect(window.document.querySelector('html').classList).not.toContain('smartbanner-show');
      expect(cookie.set).toBeCalledWith('smartbanner-closed', 'true', {
        path: '/',
        expires: 'Fri, 26 May 2017 00:00:00 GMT',
      });
      expect(spy).toHaveBeenCalled();
    });
  });

  describe('click install on smartbanner', () => {
    it('should change html classList and set cookie after invoke the install function', () => {
      const spy = jest.fn();
      const subject = this.makeSubject({
        force: 'android',
        onInstall: spy,
      });

      subject.instance().install();

      expect(window.document.querySelector('html').classList).not.toContain('smartbanner-show');
      expect(cookie.set).toBeCalledWith('smartbanner-installed', 'true', {
        path: '/',
        expires: 'Thu, 24 Aug 2017 00:00:00 GMT',
      });
      expect(spy).toHaveBeenCalled();
    });
  });

  it('should only render div in SERVER env', () => {
    global.window = undefined;
    document.querySelector('head').innerHTML = '';
    const subject = this.makeSubject();

    expect(subject.html()).toBe('

');
  });

  it('should change type in state after receiving new force props', () => {
    const subject = this.makeSubject({
      force: 'android',
    });

    expect(subject.state('type')).toBe('android');

    subject.setProps({
      force: 'ios',
    });

    expect(subject.state('type')).toBe('ios');
  });

  describe('userAgent', () => {
    it('should change type to "ios" if we set iOS user agent', () => {
      window.navigator.__defineGetter__('userAgent', () => {
        return 'OperaMobile/12.02 (iPad; CPU OS 9_0 like Mac OS X) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.132 Mobile Safari/537.36';
      });

      const subject = this.makeSubject();

      expect(subject.state('type')).toBe('ios');
    });

    it('should change type to "android" if we set android user agent', () => {
      window.navigator.__defineGetter__('userAgent', () => {
        return 'Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19';
      });

      const subject = this.makeSubject();

      expect(subject.state('type')).toBe('android');
    });

    it('should change type to "windows" if we set windows phone user agent', () => {
      window.navigator.__defineGetter__('userAgent', () => {
        return 'Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; Trident/6.0; IEMobile/10.0; ARM; Touch; NOKIA; Lumia 810)';
      });

      const subject = this.makeSubject();

      expect(subject.state('type')).toBe('windows');
    });

    it('should change type to "kindle" if we set kindle user agent', () => {
      window.navigator.__defineGetter__('userAgent', () => {
        return 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_3; en-us; Silk/1.1.0-80) AppleWebKit/533.16 (KHTML, like Gecko) Version/5.0 Safari/533.16 Silk-Accelerated=true';
      });

      const subject = this.makeSubject();

      expect(subject.state('type')).toBe('kindle');
    });
  });
});
<|endoftext|>


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

cr.define('print_preview', function() {
  'use strict';

  // TODO(rltoscano): Maybe clear print ticket when destination changes. Or
  // better yet, carry over any print ticket state that is possible. I.e. if
  // destination changes, the new destination might not support duplex anymore,
  // so we should clear the ticket's isDuplexEnabled state.

  /**
   * Storage of the print ticket and document statistics. Dispatches events when
   * the contents of the print ticket or document statistics change. Also
   * handles validation of the print ticket against destination capabilities and
   * against the document.
   * @param {!print_preview.DestinationStore} destinationStore Used to
   *     understand which printer is selected.
   * @param {!print_preview.AppState} appState Print preview application state.
   * @param {!print_preview.DocumentInfo} documentInfo Document data model.
   * @constructor
   * @extends {cr.EventTarget}
   */
  function PrintTicketStore(destinationStore, appState, documentInfo) {
    cr.EventTarget.call(this);

    /**
     * Destination store used to understand which printer is selected.
     * @type {!print_preview.DestinationStore}
     * @private
     */
    this.destinationStore_ = destinationStore;

    /**
     * App state used to persist and load ticket values.
     * @type {!print_preview.AppState}
     * @private
     */
    this.appState_ = appState;

    /**
     * Information about the document to print.
     * @type {!print_preview.DocumentInfo}
     * @private
     */
    this.documentInfo_ = documentInfo;

    /**
     * Printing capabilities of Chromium and the currently selected destination.
     * @type {!print_preview.CapabilitiesHolder}
     * @private
     */
    this.capabilitiesHolder_ = new print_preview.CapabilitiesHolder();

    /**
     * Current measurement system. Used to work with margin measurements.
     * @type {!print_preview.MeasurementSystem}
     * @private
     */
    this.measurementSystem_ = new print_preview.MeasurementSystem(
        ',', '.', print_preview.MeasurementSystem.UnitType.IMPERIAL);

    /**
     * Collate ticket item.
     * @type {!print_preview.ticket_items.Collate}
     * @private
     */
    this.collate_ = new print_preview.ticket_items.Collate(
        this.appState_, this.destinationStore_);

    /**
     * Color ticket item.
     * @type {!print_preview.ticket_items.Color}
     * @private
     */
    this.color_ = new print_preview.ticket_items.Color(
        this.appState_, this.destinationStore_);

    /**
     * Copies ticket item.
     * @type {!print_preview.ticket_items.Copies}
     * @private
     */
    this.copies_ =
        new print_preview.ticket_items.Copies(this.destinationStore_);

    /**
     * Duplex ticket item.
     * @type {!print_preview.ticket_items.Duplex}
     * @private
     */
    this.duplex_ = new print_preview.ticket_items.Duplex(
        this.appState_, this.destinationStore_);

    /**
     * Page range ticket item.
     * @type {!print_preview.ticket_items.PageRange}
     * @private
     */
    this.pageRange_ =
        new print_preview.ticket_items.PageRange(this.documentInfo_);

    /**
     * Custom margins ticket item.
     * @type {!print_preview.ticket_items.CustomMargins}
     * @private
     */
    this.customMargins_ = new print_preview.ticket_items.CustomMargins(
        this.appState_, this.documentInfo_);

    /**
     * Margins type ticket item.
     * @type {!print_preview.ticket_items.MarginsType}
     * @private
     */
    this.marginsType_ = new print_preview.ticket_items.MarginsType(
        this.appState_, this.documentInfo_, this.customMargins_);

    /**
     * Landscape ticket item.
     * @type {!print_preview.ticket_items.Landscape}
     * @private
     */
    this.landscape_ = new print_preview.ticket_items.Landscape(
        this.appState_, this.destinationStore_, this.documentInfo_,
        this.marginsType_, this.customMargins_);

    /**
     * Header-footer ticket item.
     * @type {!print_preview.ticket_items.HeaderFooter}
     * @private
     */
    this.headerFooter_ = new print_preview.ticket_items.HeaderFooter(
        this.appState_, this.documentInfo_, this.marginsType_,
        this.customMargins_);

    /**
     * Fit-to-page ticket item.
     * @type {!print_preview.ticket_items.FitToPage}
     * @private
     */
    this.fitToPage_ = new print_preview.ticket_items.FitToPage(
        this.documentInfo_, this.destinationStore_);

    /**
     * Print CSS backgrounds ticket item.
     * @type {!print_preview.ticket_items.CssBackground}
     * @private
     */
    this.cssBackground_ = new print_preview.ticket_items.CssBackground(
        this.appState_, this.documentInfo_);

    /**
     * Print selection only ticket item.
     * @type {!print_preview.ticket_items.SelectionOnly}
     * @private
     */
    this.selectionOnly_ =
        new print_preview.ticket_items.SelectionOnly(this.documentInfo_);

    /**
     * Keeps track of event listeners for the print ticket store.
     * @type {!EventTracker}
     * @private
     */
    this.tracker_ = new EventTracker();

    /**
     * Whether the print preview has been initialized.
     * @type {boolean}
     * @private
     */
    this.isInitialized_ = false;

    this.addEventListeners_();
  };

  /**
   * Event types dispatched by the print ticket store.
   * @enum {string}
   */
  PrintTicketStore.EventType = {
    CAPABILITIES_CHANGE: 'print_preview.PrintTicketStore.CAPABILITIES_CHANGE',
    DOCUMENT_CHANGE: 'print_preview.PrintTicketStore.DOCUMENT_CHANGE',
    INITIALIZE: 'print_preview.PrintTicketStore.INITIALIZE',
    TICKET_CHANGE: 'print_preview.PrintTicketStore.TICKET_CHANGE'
  };

  PrintTicketStore.prototype = {
    __proto__: cr.EventTarget.prototype,

    /**
     * Whether the print preview has been initialized.
     * @type {boolean}
     */
    get isInitialized() {
      return this.isInitialized_;
    },

    get collate() {
      return this.collate_;
    },

    get color() {
      return this.color_;
    },

    get copies() {
      return this.copies_;
    },

    get cssBackground() {
      return this.cssBackground_;
    },

    get customMargins() {
      return this.customMargins_;
    },

    get duplex() {
      return this.duplex_;
    },

    get fitToPage() {
      return this.fitToPage_;
    },

    get headerFooter() {
      return this.headerFooter_;
    },

    get landscape() {
      return this.landscape_;
    },

    get marginsType() {
      return this.marginsType_;
    },

    get pageRange() {
      return this.pageRange_;
    },

    get selectionOnly() {
      return this.selectionOnly_;
    },

    /**
     * @return {!print_preview.MeasurementSystem} Measurement system of the
     *     local system.
     */
    get measurementSystem() {
      return this.measurementSystem_;
    },

    /**
     * Initializes the print ticket store. Dispatches an INITIALIZE event.
     * @param {string} thousandsDelimeter Delimeter of the thousands place.
     * @param {string} decimalDelimeter Delimeter of the decimal point.
     * @param {!print_preview.MeasurementSystem.UnitType} unitType Type of unit
     *     of the local measurement system.
     * @param {boolean} selectionOnly Whether only selected content should be
     *     printed.
     */
    init: function(
        thousandsDelimeter, decimalDelimeter, unitType, selectionOnly) {
      this.measurementSystem_.setSystem(thousandsDelimeter, decimalDelimeter,
                                        unitType);
      this.selectionOnly_.updateValue(selectionOnly);

      // Initialize ticket with user's previous values.
      if (this.appState_.hasField(
          print_preview.AppState.Field.IS_COLOR_ENABLED)) {
        this.color_.updateValue(this.appState_.getField(
            print_preview.AppState.Field.IS_COLOR_ENABLED));
      }
      if (this.appState_.hasField(
          print_preview.AppState.Field.IS_DUPLEX_ENABLED)) {
        this.duplex_.updateValue(this.appState_.getField(
            print_preview.AppState.Field.IS_DUPLEX_ENABLED));
      }
      if (this.appState_.hasField(
          print_preview.AppState.Field.IS_LANDSCAPE_ENABLED)) {
        this.landscape_.updateValue(this.appState_.getField(
            print_preview.AppState.Field.IS_LANDSCAPE_ENABLED));
      }
      // Initialize margins after landscape because landscape may reset margins.
      if (this.appState_.hasField(print_preview.AppState.Field.MARGINS_TYPE)) {
        this.marginsType_.updateValue(
            this.appState_.getField(print_preview.AppState.Field.MARGINS_TYPE));
      }
      if (this.appState_.hasField(
          print_preview.AppState.Field.CUSTOM_MARGINS)) {
        this.customMargins_.updateValue(this.appState_.getField(
            print_preview.AppState.Field.CUSTOM_MARGINS));
      }
      if (this.appState_.hasField(
          print_preview.AppState.Field.IS_HEADER_FOOTER_ENABLED)) {
        this.headerFooter_.updateValue(this.appState_.getField(
            print_preview.AppState.Field.IS_HEADER_FOOTER_ENABLED));
      }
      if (this.appState_.hasField(
          print_preview.AppState.Field.IS_COLLATE_ENABLED)) {
        this.collate_.updateValue(this.appState_.getField(
            print_preview.AppState.Field.IS_COLLATE_ENABLED));
      }
      if (this.appState_.hasField(
          print_preview.AppState.Field.IS_CSS_BACKGROUND_ENABLED)) {
        this.cssBackground_.updateValue(this.appState_.getField(
            print_preview.AppState.Field.IS_CSS_BACKGROUND_ENABLED));
      }
    },

    /**
     * @return {boolean} {@code true} if the stored print ticket is valid,
     *     {@code false} otherwise.
     */
    isTicketValid: function() {
      return this.isTicketValidForPreview() &&
          (!this.pageRange_.isCapabilityAvailable() ||
              this.pageRange_.isValid());
    },

    /** @return {boolean} Whether the ticket is valid for preview generation. */
    isTicketValidForPreview: function() {
      return (!this.copies.isCapabilityAvailable() || this.copies.isValid()) &&
          (!this.marginsType_.isCapabilityAvailable() ||
              !this.marginsType_.isValueEqual(
                  print_preview.ticket_items.MarginsType.Value.CUSTOM) ||
              this.customMargins_.isValid());
    },

    /**
     * Creates an object that represents a Google Cloud Print print ticket.
     * @param {!print_preview.Destination} destination Destination to print to.
     * @return {!Object} Google Cloud Print print ticket.
     */
    createPrintTicket: function(destination) {
      assert(!destination.isLocal || destination.isPrivet,
             'Trying to create a Google Cloud Print print ticket for a local ' +
                 ' non-privet destination');

      assert(destination.capabilities,
             'Trying to create a Google Cloud Print print ticket for a ' +
                 'destination with no print capabilities');
      var cjt = {
        version: '1.0',
        print: {}
      };
      if (this.collate.isCapabilityAvailable() && this.collate.isUserEdited()) {
        cjt.print.collate = {collate: this.collate.getValue() == 'true'};
      }
      if (this.color.isCapabilityAvailable() && this.color.isUserEdited()) {
        var colorType = this.color.getValue() ?
            'STANDARD_COLOR' : 'STANDARD_MONOCHROME';
        // Find option with this colorType to read its vendor_id.
        var selectedOptions = destination.capabilities.printer.color.option.
            filter(function(option) {
              return option.type == colorType;
            });
        if (selectedOptions.length == 0) {
          console.error('Could not find correct color option');
        } else {
          cjt.print.color = {type: colorType};
          if (selectedOptions[0].hasOwnProperty('vendor_id')) {
            cjt.print.color.vendor_id = selectedOptions[0].vendor_id;
          }
        }
      }
      if (this.copies.isCapabilityAvailable() && this.copies.isUserEdited()) {
        cjt.print.copies = {copies: this.copies.getValueAsNumber()};
      }
      if (this.duplex.isCapabilityAvailable() && this.duplex.isUserEdited()) {
        cjt.print.duplex =
            {type: this.duplex.getValue() ? 'LONG_EDGE' : 'NO_DUPLEX'};
      }
      if (this.landscape.isCapabilityAvailable() &&
          this.landscape.isUserEdited()) {
        cjt.print.page_orientation =
            {type: this.landscape.getValue() ? 'LANDSCAPE' : 'PORTRAIT'};
      }
      return JSON.stringify(cjt);
    },

    /**
     * Adds event listeners for the print ticket store.
     * @private
     */
    addEventListeners_: function() {
      this.tracker_.add(
          this.destinationStore_,
          print_preview.DestinationStore.EventType.
              SELECTED_DESTINATION_CAPABILITIES_READY,
          this.onSelectedDestinationCapabilitiesReady_.bind(this));

      this.tracker_.add(
          this.destinationStore_,
          print_preview.DestinationStore.EventType.
              CACHED_SELECTED_DESTINATION_INFO_READY,
          this.onSelectedDestinationCapabilitiesReady_.bind(this));

      // TODO(rltoscano): Print ticket store shouldn't be re-dispatching these
      // events, the consumers of the print ticket store events should listen
      // for the events from document info instead. Will move this when
      // consumers are all migrated.
      this.tracker_.add(
          this.documentInfo_,
          print_preview.DocumentInfo.EventType.CHANGE,
          this.onDocumentInfoChange_.bind(this));
    },

    /**
     * Called when the capabilities of the selected destination are ready.
     * @private
     */
    onSelectedDestinationCapabilitiesReady_: function() {
      var caps = this.destinationStore_.selectedDestination.capabilities;
      var isFirstUpdate = this.capabilitiesHolder_.get() == null;
      this.capabilitiesHolder_.set(caps);
      if (isFirstUpdate) {
        this.isInitialized_ = true;
        cr.dispatchSimpleEvent(this, PrintTicketStore.EventType.INITIALIZE);
      } else {
        // Reset user selection for certain ticket items.
        this.customMargins_.updateValue(null);

        if (this.marginsType_.getValue() ==
            print_preview.ticket_items.MarginsType.Value.CUSTOM) {
          this.marginsType_.updateValue(
              print_preview.ticket_items.MarginsType.Value.DEFAULT);
        }
        cr.dispatchSimpleEvent(
            this, PrintTicketStore.EventType.CAPABILITIES_CHANGE);
      }
    },

    /**
     * Called when document data model has changed. Dispatches a print ticket
     * store event.
     * @private
     */
    onDocumentInfoChange_: function() {
      cr.dispatchSimpleEvent(this, PrintTicketStore.EventType.DOCUMENT_CHANGE);
    },
  };

  // Export
  return {
    PrintTicketStore: PrintTicketStore
  };
});
<commit_msg>Reset margins when destination selected, not when capabilities are ready.<commit_after>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

cr.define('print_preview', function() {
  'use strict';

  // TODO(rltoscano): Maybe clear print ticket when destination changes. Or
  // better yet, carry over any print ticket state that is possible. I.e. if
  // destination changes, the new destination might not support duplex anymore,
  // so we should clear the ticket's isDuplexEnabled state.

  /**
   * Storage of the print ticket and document statistics. Dispatches events when
   * the contents of the print ticket or document statistics change. Also
   * handles validation of the print ticket against destination capabilities and
   * against the document.
   * @param {!print_preview.DestinationStore} destinationStore Used to
   *     understand which printer is selected.
   * @param {!print_preview.AppState} appState Print preview application state.
   * @param {!print_preview.DocumentInfo} documentInfo Document data model.
   * @constructor
   * @extends {cr.EventTarget}
   */
  function PrintTicketStore(destinationStore, appState, documentInfo) {
    cr.EventTarget.call(this);

    /**
     * Destination store used to understand which printer is selected.
     * @type {!print_preview.DestinationStore}
     * @private
     */
    this.destinationStore_ = destinationStore;

    /**
     * App state used to persist and load ticket values.
     * @type {!print_preview.AppState}
     * @private
     */
    this.appState_ = appState;

    /**
     * Information about the document to print.
     * @type {!print_preview.DocumentInfo}
     * @private
     */
    this.documentInfo_ = documentInfo;

    /**
     * Printing capabilities of Chromium and the currently selected destination.
     * @type {!print_preview.CapabilitiesHolder}
     * @private
     */
    this.capabilitiesHolder_ = new print_preview.CapabilitiesHolder();

    /**
     * Current measurement system. Used to work with margin measurements.
     * @type {!print_preview.MeasurementSystem}
     * @private
     */
    this.measurementSystem_ = new print_preview.MeasurementSystem(
        ',', '.', print_preview.MeasurementSystem.UnitType.IMPERIAL);

    /**
     * Collate ticket item.
     * @type {!print_preview.ticket_items.Collate}
     * @private
     */
    this.collate_ = new print_preview.ticket_items.Collate(
        this.appState_, this.destinationStore_);

    /**
     * Color ticket item.
     * @type {!print_preview.ticket_items.Color}
     * @private
     */
    this.color_ = new print_preview.ticket_items.Color(
        this.appState_, this.destinationStore_);

    /**
     * Copies ticket item.
     * @type {!print_preview.ticket_items.Copies}
     * @private
     */
    this.copies_ =
        new print_preview.ticket_items.Copies(this.destinationStore_);

    /**
     * Duplex ticket item.
     * @type {!print_preview.ticket_items.Duplex}
     * @private
     */
    this.duplex_ = new print_preview.ticket_items.Duplex(
        this.appState_, this.destinationStore_);

    /**
     * Page range ticket item.
     * @type {!print_preview.ticket_items.PageRange}
     * @private
     */
    this.pageRange_ =
        new print_preview.ticket_items.PageRange(this.documentInfo_);

    /**
     * Custom margins ticket item.
     * @type {!print_preview.ticket_items.CustomMargins}
     * @private
     */
    this.customMargins_ = new print_preview.ticket_items.CustomMargins(
        this.appState_, this.documentInfo_);

    /**
     * Margins type ticket item.
     * @type {!print_preview.ticket_items.MarginsType}
     * @private
     */
    this.marginsType_ = new print_preview.ticket_items.MarginsType(
        this.appState_, this.documentInfo_, this.customMargins_);

    /**
     * Landscape ticket item.
     * @type {!print_preview.ticket_items.Landscape}
     * @private
     */
    this.landscape_ = new print_preview.ticket_items.Landscape(
        this.appState_, this.destinationStore_, this.documentInfo_,
        this.marginsType_, this.customMargins_);

    /**
     * Header-footer ticket item.
     * @type {!print_preview.ticket_items.HeaderFooter}
     * @private
     */
    this.headerFooter_ = new print_preview.ticket_items.HeaderFooter(
        this.appState_, this.documentInfo_, this.marginsType_,
        this.customMargins_);

    /**
     * Fit-to-page ticket item.
     * @type {!print_preview.ticket_items.FitToPage}
     * @private
     */
    this.fitToPage_ = new print_preview.ticket_items.FitToPage(
        this.documentInfo_, this.destinationStore_);

    /**
     * Print CSS backgrounds ticket item.
     * @type {!print_preview.ticket_items.CssBackground}
     * @private
     */
    this.cssBackground_ = new print_preview.ticket_items.CssBackground(
        this.appState_, this.documentInfo_);

    /**
     * Print selection only ticket item.
     * @type {!print_preview.ticket_items.SelectionOnly}
     * @private
     */
    this.selectionOnly_ =
        new print_preview.ticket_items.SelectionOnly(this.documentInfo_);

    /**
     * Keeps track of event listeners for the print ticket store.
     * @type {!EventTracker}
     * @private
     */
    this.tracker_ = new EventTracker();

    /**
     * Whether the print preview has been initialized.
     * @type {boolean}
     * @private
     */
    this.isInitialized_ = false;

    this.addEventListeners_();
  };

  /**
   * Event types dispatched by the print ticket store.
   * @enum {string}
   */
  PrintTicketStore.EventType = {
    CAPABILITIES_CHANGE: 'print_preview.PrintTicketStore.CAPABILITIES_CHANGE',
    DOCUMENT_CHANGE: 'print_preview.PrintTicketStore.DOCUMENT_CHANGE',
    INITIALIZE: 'print_preview.PrintTicketStore.INITIALIZE',
    TICKET_CHANGE: 'print_preview.PrintTicketStore.TICKET_CHANGE'
  };

  PrintTicketStore.prototype = {
    __proto__: cr.EventTarget.prototype,

    /**
     * Whether the print preview has been initialized.
     * @type {boolean}
     */
    get isInitialized() {
      return this.isInitialized_;
    },

    get collate() {
      return this.collate_;
    },

    get color() {
      return this.color_;
    },

    get copies() {
      return this.copies_;
    },

    get cssBackground() {
      return this.cssBackground_;
    },

    get customMargins() {
      return this.customMargins_;
    },

    get duplex() {
      return this.duplex_;
    },

    get fitToPage() {
      return this.fitToPage_;
    },

    get headerFooter() {
      return this.headerFooter_;
    },

    get landscape() {
      return this.landscape_;
    },

    get marginsType() {
      return this.marginsType_;
    },

    get pageRange() {
      return this.pageRange_;
    },

    get selectionOnly() {
      return this.selectionOnly_;
    },

    /**
     * @return {!print_preview.MeasurementSystem} Measurement system of the
     *     local system.
     */
    get measurementSystem() {
      return this.measurementSystem_;
    },

    /**
     * Initializes the print ticket store. Dispatches an INITIALIZE event.
     * @param {string} thousandsDelimeter Delimeter of the thousands place.
     * @param {string} decimalDelimeter Delimeter of the decimal point.
     * @param {!print_preview.MeasurementSystem.UnitType} unitType Type of unit
     *     of the local measurement system.
     * @param {boolean} selectionOnly Whether only selected content should be
     *     printed.
     */
    init: function(
        thousandsDelimeter, decimalDelimeter, unitType, selectionOnly) {
      this.measurementSystem_.setSystem(thousandsDelimeter, decimalDelimeter,
                                        unitType);
      this.selectionOnly_.updateValue(selectionOnly);

      // Initialize ticket with user's previous values.
      if (this.appState_.hasField(
          print_preview.AppState.Field.IS_COLOR_ENABLED)) {
        this.color_.updateValue(this.appState_.getField(
            print_preview.AppState.Field.IS_COLOR_ENABLED));
      }
      if (this.appState_.hasField(
          print_preview.AppState.Field.IS_DUPLEX_ENABLED)) {
        this.duplex_.updateValue(this.appState_.getField(
            print_preview.AppState.Field.IS_DUPLEX_ENABLED));
      }
      if (this.appState_.hasField(
          print_preview.AppState.Field.IS_LANDSCAPE_ENABLED)) {
        this.landscape_.updateValue(this.appState_.getField(
            print_preview.AppState.Field.IS_LANDSCAPE_ENABLED));
      }
      // Initialize margins after landscape because landscape may reset margins.
      if (this.appState_.hasField(print_preview.AppState.Field.MARGINS_TYPE)) {
        this.marginsType_.updateValue(
            this.appState_.getField(print_preview.AppState.Field.MARGINS_TYPE));
      }
      if (this.appState_.hasField(
          print_preview.AppState.Field.CUSTOM_MARGINS)) {
        this.customMargins_.updateValue(this.appState_.getField(
            print_preview.AppState.Field.CUSTOM_MARGINS));
      }
      if (this.appState_.hasField(
          print_preview.AppState.Field.IS_HEADER_FOOTER_ENABLED)) {
        this.headerFooter_.updateValue(this.appState_.getField(
            print_preview.AppState.Field.IS_HEADER_FOOTER_ENABLED));
      }
      if (this.appState_.hasField(
          print_preview.AppState.Field.IS_COLLATE_ENABLED)) {
        this.collate_.updateValue(this.appState_.getField(
            print_preview.AppState.Field.IS_COLLATE_ENABLED));
      }
      if (this.appState_.hasField(
          print_preview.AppState.Field.IS_CSS_BACKGROUND_ENABLED)) {
        this.cssBackground_.updateValue(this.appState_.getField(
            print_preview.AppState.Field.IS_CSS_BACKGROUND_ENABLED));
      }
    },

    /**
     * @return {boolean} {@code true} if the stored print ticket is valid,
     *     {@code false} otherwise.
     */
    isTicketValid: function() {
      return this.isTicketValidForPreview() &&
          (!this.pageRange_.isCapabilityAvailable() ||
              this.pageRange_.isValid());
    },

    /** @return {boolean} Whether the ticket is valid for preview generation. */
    isTicketValidForPreview: function() {
      return (!this.copies.isCapabilityAvailable() || this.copies.isValid()) &&
          (!this.marginsType_.isCapabilityAvailable() ||
              !this.marginsType_.isValueEqual(
                  print_preview.ticket_items.MarginsType.Value.CUSTOM) ||
              this.customMargins_.isValid());
    },

    /**
     * Creates an object that represents a Google Cloud Print print ticket.
     * @param {!print_preview.Destination} destination Destination to print to.
     * @return {!Object} Google Cloud Print print ticket.
     */
    createPrintTicket: function(destination) {
      assert(!destination.isLocal || destination.isPrivet,
             'Trying to create a Google Cloud Print print ticket for a local ' +
                 ' non-privet destination');

      assert(destination.capabilities,
             'Trying to create a Google Cloud Print print ticket for a ' +
                 'destination with no print capabilities');
      var cjt = {
        version: '1.0',
        print: {}
      };
      if (this.collate.isCapabilityAvailable() && this.collate.isUserEdited()) {
        cjt.print.collate = {collate: this.collate.getValue() == 'true'};
      }
      if (this.color.isCapabilityAvailable() && this.color.isUserEdited()) {
        var colorType = this.color.getValue() ?
            'STANDARD_COLOR' : 'STANDARD_MONOCHROME';
        // Find option with this colorType to read its vendor_id.
        var selectedOptions = destination.capabilities.printer.color.option.
            filter(function(option) {
              return option.type == colorType;
            });
        if (selectedOptions.length == 0) {
          console.error('Could not find correct color option');
        } else {
          cjt.print.color = {type: colorType};
          if (selectedOptions[0].hasOwnProperty('vendor_id')) {
            cjt.print.color.vendor_id = selectedOptions[0].vendor_id;
          }
        }
      }
      if (this.copies.isCapabilityAvailable() && this.copies.isUserEdited()) {
        cjt.print.copies = {copies: this.copies.getValueAsNumber()};
      }
      if (this.duplex.isCapabilityAvailable() && this.duplex.isUserEdited()) {
        cjt.print.duplex =
            {type: this.duplex.getValue() ? 'LONG_EDGE' : 'NO_DUPLEX'};
      }
      if (this.landscape.isCapabilityAvailable() &&
          this.landscape.isUserEdited()) {
        cjt.print.page_orientation =
            {type: this.landscape.getValue() ? 'LANDSCAPE' : 'PORTRAIT'};
      }
      return JSON.stringify(cjt);
    },

    /**
     * Adds event listeners for the print ticket store.
     * @private
     */
    addEventListeners_: function() {
      this.tracker_.add(
          this.destinationStore_,
          print_preview.DestinationStore.EventType.DESTINATION_SELECT,
          this.onDestinationSelect_.bind(this));

      this.tracker_.add(
          this.destinationStore_,
          print_preview.DestinationStore.EventType.
              SELECTED_DESTINATION_CAPABILITIES_READY,
          this.onSelectedDestinationCapabilitiesReady_.bind(this));

      this.tracker_.add(
          this.destinationStore_,
          print_preview.DestinationStore.EventType.
              CACHED_SELECTED_DESTINATION_INFO_READY,
          this.onSelectedDestinationCapabilitiesReady_.bind(this));

      // TODO(rltoscano): Print ticket store shouldn't be re-dispatching these
      // events, the consumers of the print ticket store events should listen
      // for the events from document info instead. Will move this when
      // consumers are all migrated.
      this.tracker_.add(
          this.documentInfo_,
          print_preview.DocumentInfo.EventType.CHANGE,
          this.onDocumentInfoChange_.bind(this));
    },

    /**
     * Called when the destination selected.
     * @private
     */
    onDestinationSelect_: function() {
      // Reset user selection for certain ticket items.
      if (this.capabilitiesHolder_.get() != null) {
        this.customMargins_.updateValue(null);
        if (this.marginsType_.getValue() ==
            print_preview.ticket_items.MarginsType.Value.CUSTOM) {
          this.marginsType_.updateValue(
              print_preview.ticket_items.MarginsType.Value.DEFAULT);
        }
      }
    },

    /**
     * Called when the capabilities of the selected destination are ready.
     * @private
     */
    onSelectedDestinationCapabilitiesReady_: function() {
      var caps = this.destinationStore_.selectedDestination.capabilities;
      var isFirstUpdate = this.capabilitiesHolder_.get() == null;
      this.capabilitiesHolder_.set(caps);
      if (isFirstUpdate) {
        this.isInitialized_ = true;
        cr.dispatchSimpleEvent(this, PrintTicketStore.EventType.INITIALIZE);
      } else {
        cr.dispatchSimpleEvent(
            this, PrintTicketStore.EventType.CAPABILITIES_CHANGE);
      }
    },

    /**
     * Called when document data model has changed. Dispatches a print ticket
     * store event.
     * @private
     */
    onDocumentInfoChange_: function() {
      cr.dispatchSimpleEvent(this, PrintTicketStore.EventType.DOCUMENT_CHANGE);
    },
  };

  // Export
  return {
    PrintTicketStore: PrintTicketStore
  };
});
<|endoftext|>


<commit_before>class TTNode {
    constructor(leftValue, rightValue, left, center, right) {
        if (arguments.length) {
            this.leftValue = leftValue;
            this.rightValue = rightValue;
            this.left = left;
            this.center = center;
            this.right = right;
        } else {
            this.leftValue = this.rightValue = this.left = this.center = this.right = null;
        }
    }

    isLeaf() {
        return this.left == null;
    }

    add(it) {
        if (this.rightValue == null) {
            if (this.leftValue <= it.leftValue) {
                this.rightValue = it.leftValue;
                this.right = it.center;
                this.center = it.left;
            } else {
                this.rightValue = this.leftValue;
                this.right = this.center;
                this.leftValue = it.leftValue;
                this.center = it.center;
                this.left = it.left;
            }

            return this;
        } else if (this.leftValue > it.leftValue) {
            var newNode = new TTNode(this.leftValue, null, it, this, null);
            this.leftValue = this.rightValue;
            this.rightValue = null;
            this.left = this.center;
            this.center = this.right;
            this.right = null;

            return newNode;
        } else if (this.rightValue <= it.leftValue) {
            newNode = new TTNode(this.rightValue, null, this, it, null);
            this.rightValue = null;
            this.right = null;

            return newNode;
        } else {
            newNode = new TTNode(this.rightValue, null, it.center, this.right, null);
            this.center = it.left;
            this.rightValue = null;
            this.right = null;
            it.left = this;
            it.center = newNode;

            return it;
        }
    }

    delete(child) {
        if (child === this.left) {
            if (this.center.rightValue == null) {
                this.center.rightValue = this.center.leftValue;
                this.center.leftValue = this.leftValue;
                this.leftValue = this.rightValue;
                this.rightValue = null;
                this.center.right = this.center.center;
                this.center.center = this.center.left;
                this.center.left = child.left;
                child.left = null;
                this.left = this.center;
                this.center = this.right;
                this.right = null;
            } else {
                child.leftValue = this.leftValue;
                this.leftValue = this.center.leftValue;
                this.center.leftValue = this.center.rightValue;
                this.center.rightValue = null;
                child.center = this.center.left;
                this.center.left = this.center.center;
                this.center.center = this.center.right;
                this.center.right = null;
            }
        } else if (child === this.center) {
            if (this.left.rightValue == null) {
                this.left.rightValue = this.leftValue;
                this.leftValue = this.rightValue;
                this.rightValue = null;
                this.left.right = child.left;
                child.left = null;
                this.center = this.right;
                this.right = null;
            } else {
                child.leftValue = this.leftValue;
                this.leftValue = this.left.rightValue;
                this.left.rightValue = null;
                child.center = child.left;
                child.left = this.left.right;
                this.left.right = null;
            }
        } else {
            if (this.center.rightValue == null) {
                this.center.rightValue = this.rightValue;
                this.rightValue = null;
                this.center.right = child.left;
                child.left = null;
                this.right = null;
            } else {
                child.leftValue = this.rightValue;
                this.rightValue = this.center.rightValue;
                this.center.rightValue = null;
                child.center = child.left;
                child.left = this.center.right;
                this.center.right = null;
            }
        }
    }

}

export default class TTTree {
    constructor() {
        this.root = null;
        this.MAX_HEIGHT = 3;
    }

    getRoot() {
        return this.root;
    }

    appendHelp(root, value) {
        var returnRoot = null;
        if (root == null)
            return new TTNode(value, null, null, null, null);
        if (root.isLeaf())
            return root.add(new TTNode(value, null, null, null, null));
        if (value < root.leftValue) {
            returnRoot = this.appendHelp(root.left, value);
            if (returnRoot === root.left)
                return root;
            else
                return root.add(returnRoot);
        } else if ((root.rightValue == null) || (value < root.rightValue)) {
            returnRoot = this.appendHelp(root.center, value);
            if (returnRoot === root.center)
                return root;
            else
                return root.add(returnRoot);
        } else {
            returnRoot = this.appendHelp(root.right, value);
            if (returnRoot === root.right)
                return root;
            else
                return root.add(returnRoot);
        }
    }

    findHelp(root, value) {
        if (root == null)
            return false;
        else if (root.leftValue > value)
            return this.findHelp(root.left, value);
        else if ((root.leftValue < value) && ((root.rightValue == null) || (root.rightValue > value)))
            return this.findHelp(root.center, value);
        else if ((root.rightValue != null) && (root.rightValue < value))
            return this.findHelp(root.right, value);
        else
            return true;
    }

    getParent(child) {
        var node = [];
        node.push(this.root);
        for (var i = 0; i < node.length; i++) {
            if (node[i] != null) {
                if ((node[i].left === child) || (node[i].center === child) || (node[i].right === child)) {
                    return node[i];
                }
                if (!node[i].isLeaf()) {
                    node.push(node[i].left);
                    node.push(node[i].center);
                    node.push(node[i].right);
                }
            }
        }
    }

    deletemin(root, deleteNode, sign) {
        if (root.left == null) {
            if (!sign)
                deleteNode.leftValue = root.leftValue;
            else
                deleteNode.rightValue = root.leftValue;
            root.leftValue = root.rightValue;
            root.rightValue = null;
        } else {
            this.deletemin(root.left, deleteNode, sign);
        }
        if (root.leftValue == null) {
            this.adjust(root);
        }
    }

    deleteHelp(root, value) {
        if (root == null)
            return;
        if (value < root.leftValue)
            this.deleteHelp(root.left, value);
        else if ((value > root.leftValue) && ((root.rightValue == null) || (root.rightValue > value)))
            this.deleteHelp(root.center, value);
        else if ((root.rightValue != null) && (value > root.rightValue))
            this.deleteHelp(root.right, value);
        else if (value === root.leftValue) {
            if (root.left == null) {
                root.leftValue = root.rightValue;
                root.rightValue = null;
            }
            else {
                this.deletemin(root.center, root, 0);
            }
        }
        else {
            if (root.left == null) {
                root.rightValue = null;
            }
            else
                this.deletemin(root.right, root, 1);
        }
        if (root.leftValue == null)
            this.adjust(root);
    }

    adjust(root) {
        if (root === this.root) {
            this.root = this.root.left;
            return;
        }
        var parent = this.getParent(root);
        parent.delete(root);
    }

    getHeightHelp(root) {
        if (root == null)
            return 0;
        return 1 + this.getHeightHelp(root.left);
    }

    copyTTTree() {
        if (root == null)
            return null;
        var node = [];
        node.push(this.root);
        var copy = [];
        var nodeLength = (Math.pow(3, this.getHeight()) - 1) / 2;
        node.push(this.root);
        for (var i = 0; i < nodeLength; i++) {
            if (node[i] != null) {
                if (3 * i + 1 < nodeLength)
                    node[3 * i + 1] = node[i].left;
                if (3 * i + 2 < nodeLength)
                    node[3 * i + 2] = node[i].center;
                if (3 * i + 3 < nodeLength)
                    node[3 * i + 3] = node[i].right;
            } else {
                if (3 * i + 1 < nodeLength)
                    node[3 * i + 1] = null;
                if (3 * i + 2 < nodeLength)
                    node[3 * i + 2] = null;
                if (3 * i + 3 < nodeLength)
                    node[3 * i + 3] = null;
            }
        }

        var newNode = null;
        for (i = 0; i < node.length; i++) {
            if (node[i] != null)
                newNode = new TTNode(node[i].leftValue, node[i].rightValue, null, null, null);
            else
                newNode = null;
            copy.push(newNode);
        }
        for (i = 0; i < copy.length; i++) {
            if ((copy[i] != null)) {
                if (3 * i + 3 < copy.length) {
                    copy[i].left = copy[3 * i + 1];
                    copy[i].center = copy[3 * i + 2];
                    copy[i].right = copy[3 * i + 3];
                }
            }
        }
        return copy[0];
    }

    getNode(position) {
        if (this.root == null)
            return null;
        var node = [];
        var nodeLength = (Math.pow(3, this.getHeight()) - 1) / 2;
        node.push(this.root);
        for (var i = 0; (i < nodeLength) && (i !== position + 1); i++) {
            if (node[i] != null) {
                if (3 * i + 1 < nodeLength)
                    node[3 * i + 1] = node[i].left;
                if (3 * i + 2 < nodeLength)
                    node[3 * i + 2] = node[i].center;
                if (3 * i + 3 < nodeLength)
                    node[3 * i + 3] = node[i].right;
            } else {
                if (3 * i + 1 < nodeLength)
                    node[3 * i + 1] = null;
                if (3 * i + 2 < nodeLength)
                    node[3 * i + 2] = null;
                if (3 * i + 3 < nodeLength)
                    node[3 * i + 3] = null;
            }
        }
        return node[position];
    }

    append(value) {
        var oldRoot = this.copyTTTree();
        this.root = this.appendHelp(this.root, value);
        if (this.getHeight() > this.MAX_HEIGHT) {
            console.log('树高超过限高' + this.MAX_HEIGHT + '层!');
            console.log('本次append ' + value + ' 取消');
            this.root = oldRoot;
        }
    }

    find(value) {
        return this.findHelp(this.root, value);
    }

    // 该方法按值来删除，不适合演示
    // delete(position) {
    //     var value = this.get(position);
    //     if (value == null)
    //         return;
    //     this.deleteHelp(this.root, value);
    // }
    delete(position) {
        var deleteNode = this.getNode(Math.floor(position / 2));
        var value = this.get(position);
        if (deleteNode == null)
            return;
        if (value === deleteNode.leftValue) {
            if (deleteNode.left == null) {
                deleteNode.leftValue = deleteNode.rightValue;
                deleteNode.rightValue = null;
            }
            else {
                this.deletemin(deleteNode.center, deleteNode, 0);
            }
        }
        else {
            if (deleteNode.left == null) {
                deleteNode.rightValue = null;
            }
            else
                this.deletemin(deleteNode.right, deleteNode, 1);
        }
        while ((deleteNode !== this.root) && (deleteNode.leftValue == null)) {
            this.adjust(deleteNode);
            deleteNode = this.getParent(deleteNode);
        }
        if (this.root.leftValue == null)
            this.adjust(this.root);
    }

    get(position) {
        if (this.root == null)
            return null;
        var node = [];
        var value = [];
        var nodeLength = (Math.pow(3, this.getHeight()) - 1) / 2;
        node.push(this.root);
        for (var i = 0, j = 0; (i < nodeLength) && (j !== position + 1); i++ , j += 2) {
            if (node[i] != null) {
                value[j] = node[i].leftValue;
                value[j + 1] = node[i].rightValue;
                if (3 * i + 1 < nodeLength)
                    node[3 * i + 1] = node[i].left;
                if (3 * i + 2 < nodeLength)
                    node[3 * i + 2] = node[i].center;
                if (3 * i + 3 < nodeLength)
                    node[3 * i + 3] = node[i].right;
            } else {
                value[j] = null;
                value[j + 1] = null;
                if (3 * i + 1 < nodeLength)
                    node[3 * i + 1] = null;
                if (3 * i + 2 < nodeLength)
                    node[3 * i + 2] = null;
                if (3 * i + 3 < nodeLength)
                    node[3 * i + 3] = null;
            }
        }
        return value[position];

    }

    getRandomInteger(max, min) {
        return Math.round(Math.random() * (max - min) + min);
    }

    generateTTTree(nodeCount) {
        for (var i = 0; i < nodeCount; i++) {
            var num = this.getRandomInteger(9, 0);
            this.append(num);
        }
        console.log("随机生成的二三树：")
        this.print();
    }

    getHeight() {
        return this.getHeightHelp(this.root);
    }

    print() {
        var valueLength = Math.pow(3, this.getHeight()) - 1;
        if (!valueLength) {
            console.log("[]");
            return;
        }
        var result = "[";
        for (var i = 0; i < valueLength - 1; i++)
            result += this.get(i) + ",";
        result += this.get(i) + "]";
        console.log(result);
    }

    randomAdd() {
        var num = this.getRandomInteger(9, 0)
        console.log("随机添加: " + num);
        this.append(num);
    }

    remove(position) {
        return this.delete(position);
    }

}

<commit_msg>bug fixed<commit_after>class TTNode {
    constructor(leftValue, rightValue, left, center, right) {
        if (arguments.length) {
            this.leftValue = leftValue;
            this.rightValue = rightValue;
            this.left = left;
            this.center = center;
            this.right = right;
        } else {
            this.leftValue = this.rightValue = this.left = this.center = this.right = null;
        }
    }

    isLeaf() {
        return this.left == null;
    }

    add(it) {
        if (this.rightValue == null) {
            if (this.leftValue <= it.leftValue) {
                this.rightValue = it.leftValue;
                this.right = it.center;
                this.center = it.left;
            } else {
                this.rightValue = this.leftValue;
                this.right = this.center;
                this.leftValue = it.leftValue;
                this.center = it.center;
                this.left = it.left;
            }

            return this;
        } else if (this.leftValue > it.leftValue) {
            var newNode = new TTNode(this.leftValue, null, it, this, null);
            this.leftValue = this.rightValue;
            this.rightValue = null;
            this.left = this.center;
            this.center = this.right;
            this.right = null;

            return newNode;
        } else if (this.rightValue <= it.leftValue) {
            newNode = new TTNode(this.rightValue, null, this, it, null);
            this.rightValue = null;
            this.right = null;

            return newNode;
        } else {
            newNode = new TTNode(this.rightValue, null, it.center, this.right, null);
            this.center = it.left;
            this.rightValue = null;
            this.right = null;
            it.left = this;
            it.center = newNode;

            return it;
        }
    }

    delete(child) {
        if (child === this.left) {
            if (this.center.rightValue == null) {
                this.center.rightValue = this.center.leftValue;
                this.center.leftValue = this.leftValue;
                this.leftValue = this.rightValue;
                this.rightValue = null;
                this.center.right = this.center.center;
                this.center.center = this.center.left;
                this.center.left = child.left;
                child.left = null;
                this.left = this.center;
                this.center = this.right;
                this.right = null;
            } else {
                child.leftValue = this.leftValue;
                this.leftValue = this.center.leftValue;
                this.center.leftValue = this.center.rightValue;
                this.center.rightValue = null;
                child.center = this.center.left;
                this.center.left = this.center.center;
                this.center.center = this.center.right;
                this.center.right = null;
            }
        } else if (child === this.center) {
            if (this.left.rightValue == null) {
                this.left.rightValue = this.leftValue;
                this.leftValue = this.rightValue;
                this.rightValue = null;
                this.left.right = child.left;
                child.left = null;
                this.center = this.right;
                this.right = null;
            } else {
                child.leftValue = this.leftValue;
                this.leftValue = this.left.rightValue;
                this.left.rightValue = null;
                child.center = child.left;
                child.left = this.left.right;
                this.left.right = null;
            }
        } else {
            if (this.center.rightValue == null) {
                this.center.rightValue = this.rightValue;
                this.rightValue = null;
                this.center.right = child.left;
                child.left = null;
                this.right = null;
            } else {
                child.leftValue = this.rightValue;
                this.rightValue = this.center.rightValue;
                this.center.rightValue = null;
                child.center = child.left;
                child.left = this.center.right;
                this.center.right = null;
            }
        }
    }

}

export default class TTTree {
    constructor() {
        this.root = null;
        this.MAX_HEIGHT = 3;
    }

    getRoot() {
        return this.root;
    }

    appendHelp(root, value) {
        var returnRoot = null;
        if (root == null)
            return new TTNode(value, null, null, null, null);
        if (root.isLeaf())
            return root.add(new TTNode(value, null, null, null, null));
        if (value < root.leftValue) {
            returnRoot = this.appendHelp(root.left, value);
            if (returnRoot === root.left)
                return root;
            else
                return root.add(returnRoot);
        } else if ((root.rightValue == null) || (value < root.rightValue)) {
            returnRoot = this.appendHelp(root.center, value);
            if (returnRoot === root.center)
                return root;
            else
                return root.add(returnRoot);
        } else {
            returnRoot = this.appendHelp(root.right, value);
            if (returnRoot === root.right)
                return root;
            else
                return root.add(returnRoot);
        }
    }

    findHelp(root, value) {
        if (root == null)
            return false;
        else if (root.leftValue > value)
            return this.findHelp(root.left, value);
        else if ((root.leftValue < value) && ((root.rightValue == null) || (root.rightValue > value)))
            return this.findHelp(root.center, value);
        else if ((root.rightValue != null) && (root.rightValue < value))
            return this.findHelp(root.right, value);
        else
            return true;
    }

    getParent(child) {
        var node = [];
        node.push(this.root);
        for (var i = 0; i < node.length; i++) {
            if (node[i] != null) {
                if ((node[i].left === child) || (node[i].center === child) || (node[i].right === child)) {
                    return node[i];
                }
                if (!node[i].isLeaf()) {
                    node.push(node[i].left);
                    node.push(node[i].center);
                    node.push(node[i].right);
                }
            }
        }
    }

    deletemin(root, deleteNode, sign) {
        if (root.left == null) {
            if (!sign)
                deleteNode.leftValue = root.leftValue;
            else
                deleteNode.rightValue = root.leftValue;
            root.leftValue = root.rightValue;
            root.rightValue = null;
        } else {
            this.deletemin(root.left, deleteNode, sign);
        }
        if (root.leftValue == null) {
            this.adjust(root);
        }
    }

    deleteHelp(root, value) {
        if (root == null)
            return;
        if (value < root.leftValue)
            this.deleteHelp(root.left, value);
        else if ((value > root.leftValue) && ((root.rightValue == null) || (root.rightValue > value)))
            this.deleteHelp(root.center, value);
        else if ((root.rightValue != null) && (value > root.rightValue))
            this.deleteHelp(root.right, value);
        else if (value === root.leftValue) {
            if (root.left == null) {
                root.leftValue = root.rightValue;
                root.rightValue = null;
            }
            else {
                this.deletemin(root.center, root, 0);
            }
        }
        else {
            if (root.left == null) {
                root.rightValue = null;
            }
            else
                this.deletemin(root.right, root, 1);
        }
        if (root.leftValue == null)
            this.adjust(root);
    }

    adjust(root) {
        if (root === this.root) {
            this.root = this.root.left;
            return;
        }
        var parent = this.getParent(root);
        parent.delete(root);
    }

    getHeightHelp(root) {
        if (root == null)
            return 0;
        return 1 + this.getHeightHelp(root.left);
    }

    copyTTTree() {
        if (root == null)
            return null;
        var node = [];
        node.push(this.root);
        var copy = [];
        var nodeLength = (Math.pow(3, this.getHeight()) - 1) / 2;
        node.push(this.root);
        for (var i = 0; i < nodeLength; i++) {
            if (node[i] != null) {
                if (3 * i + 1 < nodeLength)
                    node[3 * i + 1] = node[i].left;
                if (3 * i + 2 < nodeLength)
                    node[3 * i + 2] = node[i].center;
                if (3 * i + 3 < nodeLength)
                    node[3 * i + 3] = node[i].right;
            } else {
                if (3 * i + 1 < nodeLength)
                    node[3 * i + 1] = null;
                if (3 * i + 2 < nodeLength)
                    node[3 * i + 2] = null;
                if (3 * i + 3 < nodeLength)
                    node[3 * i + 3] = null;
            }
        }

        var newNode = null;
        for (i = 0; i < node.length; i++) {
            if (node[i] != null)
                newNode = new TTNode(node[i].leftValue, node[i].rightValue, null, null, null);
            else
                newNode = null;
            copy.push(newNode);
        }
        for (i = 0; i < copy.length; i++) {
            if ((copy[i] != null)) {
                if (3 * i + 3 < copy.length) {
                    copy[i].left = copy[3 * i + 1];
                    copy[i].center = copy[3 * i + 2];
                    copy[i].right = copy[3 * i + 3];
                }
            }
        }
        return copy[0];
    }

    getNode(position) {
        if (this.root == null)
            return null;
        var node = [];
        var nodeLength = (Math.pow(3, this.getHeight()) - 1) / 2;
        node.push(this.root);
        for (var i = 0; (i < nodeLength) && (i !== position + 1); i++) {
            if (node[i] != null) {
                if (3 * i + 1 < nodeLength)
                    node[3 * i + 1] = node[i].left;
                if (3 * i + 2 < nodeLength)
                    node[3 * i + 2] = node[i].center;
                if (3 * i + 3 < nodeLength)
                    node[3 * i + 3] = node[i].right;
            } else {
                if (3 * i + 1 < nodeLength)
                    node[3 * i + 1] = null;
                if (3 * i + 2 < nodeLength)
                    node[3 * i + 2] = null;
                if (3 * i + 3 < nodeLength)
                    node[3 * i + 3] = null;
            }
        }
        return node[position];
    }

    append(value) {
        var oldRoot = this.copyTTTree();
        this.root = this.appendHelp(this.root, value);
        if (this.getHeight() > this.MAX_HEIGHT) {
            console.log('树高超过限高' + this.MAX_HEIGHT + '层!');
            console.log('本次append ' + value + ' 取消');
            this.root = oldRoot;
        }
    }

    find(value) {
        return this.findHelp(this.root, value);
    }

    // 该方法按值来删除，不适合演示
    // delete(position) {
    //     var value = this.get(position);
    //     if (value == null)
    //         return;
    //     this.deleteHelp(this.root, value);
    // }
    delete(position) {
        var deleteNodePosition = Math.floor(position / 2);
        var deleteNode = this.getNode(deleteNodePosition);
        var value = this.get(position);
        if (deleteNode == null)
            return;
        if (value === deleteNode.leftValue) {
            if (deleteNode.left == null) {
                deleteNode.leftValue = deleteNode.rightValue;
                deleteNode.rightValue = null;
            }
            else {
                this.deletemin(deleteNode.center, deleteNode, 0);
            }
        }
        else {
            if (deleteNode.left == null) {
                deleteNode.rightValue = null;
            }
            else
                this.deletemin(deleteNode.right, deleteNode, 1);
        }

        while ((deleteNode !== this.root) && (deleteNode.leftValue == null)) {
            this.adjust(deleteNode);
            deleteNodePosition = Math.floor((deleteNodePosition - 1) / 3);
            deleteNode = this.getNode(deleteNodePosition);
        }
        if (this.root.leftValue == null)
            this.adjust(this.root);
    }

    get(position) {
        if (this.root == null)
            return null;
        var node = [];
        var value = [];
        var nodeLength = (Math.pow(3, this.getHeight()) - 1) / 2;
        node.push(this.root);
        for (var i = 0, j = 0; (i < nodeLength) && (j !== position + 1); i++ , j += 2) {
            if (node[i] != null) {
                value[j] = node[i].leftValue;
                value[j + 1] = node[i].rightValue;
                if (3 * i + 1 < nodeLength)
                    node[3 * i + 1] = node[i].left;
                if (3 * i + 2 < nodeLength)
                    node[3 * i + 2] = node[i].center;
                if (3 * i + 3 < nodeLength)
                    node[3 * i + 3] = node[i].right;
            } else {
                value[j] = null;
                value[j + 1] = null;
                if (3 * i + 1 < nodeLength)
                    node[3 * i + 1] = null;
                if (3 * i + 2 < nodeLength)
                    node[3 * i + 2] = null;
                if (3 * i + 3 < nodeLength)
                    node[3 * i + 3] = null;
            }
        }
        return value[position];

    }

    getRandomInteger(max, min) {
        return Math.round(Math.random() * (max - min) + min);
    }

    generateTTTree(nodeCount) {
        for (var i = 0; i < nodeCount; i++) {
            var num = this.getRandomInteger(9, 0);
            this.append(num);
        }
        console.log("随机生成的二三树：")
        this.print();
    }

    getHeight() {
        return this.getHeightHelp(this.root);
    }

    print() {
        var valueLength = Math.pow(3, this.getHeight()) - 1;
        if (!valueLength) {
            console.log("[]");
            return;
        }
        var result = "[";
        for (var i = 0; i < valueLength - 1; i++)
            result += this.get(i) + ",";
        result += this.get(i) + "]";
        console.log(result);
    }

    randomAdd() {
        var num = this.getRandomInteger(9, 0)
        console.log("随机增加 ： " + num);
        this.append(num);
    }

    remove(position) {
        return this.delete(position);
    }

}
<|endoftext|>


<commit_before>function personInput(thisItem) {
    var val = $(thisItem).val();
    previewHtml = "";
    $("#qperson .controls .button").css("visibility", "hidden");
    $("#qperson .preview").css("display", "none");
    if (val.length > 2) {  // don't fire on short input to prevent enormous lists
        var re = new RegExp(val, "i");
        personNames = Object.keys(persons)
        var includePersons = personNames.filter(function(val) {
            return re.test(val);
        });
        previewHtml = "";
        for(var p in includePersons) {
            previewHtml += '
 '+ ' ' + includePersons[p] + '
\n';
        }
        $("#qperson .preview .persons").html(previewHtml);
        if(includePersons.length) {
            $("#qperson .controls .button").css("visibility", "visible");
            $("#qperson .preview").css("display", "block");
        }
    }

    $(".preview .persons").html(previewHtml);
}

function placeInput(thisItem) {
    var val = $(thisItem).val();
    previewHtml = "";
    $("#qplace .controls .button").css("visibility", "hidden");
    $("#qplace .preview").css("display", "none");
    if (val.length > 2) {  // dont fire on short input to prevent enormous lists
        var re = new RegExp(val, "i");
        var includePlaces = [];
        for (var p in places) {
            if(re.test(places[p])) {
                includePlaces.push(places[p]);
            }
        };
        previewHtml = "";
        for(var iP in includePlaces) {
            previewHtml += '
 '+ ' ' + includePlaces[iP] + '
\n';
        }
        $("#qplace .preview .places").html(previewHtml);
        if(includePlaces.length) {
            $("#qplace .controls .button").css("visibility", "visible");
            $("#qplace .preview").css("display", "block");
        }
    }

    $("#qplace .preview .places").html(previewHtml);
}

function subjectInput(thisItem) {
    var val = $(thisItem).val();
    previewHtml = "";
    $("#qsubject .controls .button").css("visibility", "hidden");
    $("#qsubject .preview").css("display", "none");
    if (val.length > 2) {  // dont fire on short input to prevent enormous lists
        var re = new RegExp(val, "i");
        var includeSubjects = [];
        for (var p in subjects) {
            if(re.test(subjects[p])) {
                includeSubjects.push(subjects[p]);
            }
        };
        previewHtml = "";
        for(var iP in includeSubjects) {
            previewHtml += '
 '+ ' ' + includeSubjects[iP] + '
\n';
        }
        $("#qsubject .preview .subjects").html(previewHtml);
        if(includeSubjects.length) {
            $("#qsubject .controls .button").css("visibility", "visible");
            $("#qsubject .preview").css("display", "block");
        }
    }

    $("#qsubject .preview .subjects").html(previewHtml);
}

function genreInput(thisItem) {
    var val = $(thisItem).val();
    previewHtml = "";
    $("#qgenre .controls .button").css("visibility", "hidden");
    $("#qgenre .preview").css("display", "none");
    if (val.length > 2) {  // dont fire on short input to prevent enormous lists
        var re = new RegExp(val, "i");
        var includeGenres = [];
        for (var p in genres) {
            if(re.test(genres[p])) {
                includeGenres.push(genres[p]);
            }
        };
        previewHtml = "";
        for(var iP in includeGenres) {
            previewHtml += '
 '+ ' ' + includeGenres[iP] + '
\n';
        }
        $("#qgenre .preview .genres").html(previewHtml);
        if(includeGenres.length) {
            $("#qgenre .controls .button").css("visibility", "visible");
            $("#qgenre .preview").css("display", "block");
        }
    }

    $("#qgenre .preview .genres").html(previewHtml);
}

function dateInput(element) {
    $("#datesIncludedHeader").css("display", "none");
    var datetype = $("#datetype").val();
    var datetypestr = $("#datetype :selected").html();
    var daterelation = $("#daterelation").val();
    var daterelationstr = $("#daterelation :selected").html();
    var date = $("#date").val();
    datestr = "Dates: " + datetypestr + " " + daterelationstr + " " + date;
    var includeDates = $("#datesInQuery").html();
    includeDates += '
' + datestr + '
';
    $("#datesInQuery").html(includeDates);
    if(includeDates) {
        $("#datesIncludedHeader").css("display", "block");
    }
    $("#date").val("");

}

function expandItems(thisItem) {
    if($(thisItem).find("i").hasClass("fa-plus-square-o")) {
        // we need to expand the items
        $(thisItem).children(".works").slideDown("fast");
        // and change the icon to afford contracting rather than expanding
        $(thisItem).find("i").removeClass("fa-plus-square-o");
        $(thisItem).find("i").addClass("fa-minus-square-o");
    } else {
        // we need to contract the items
        $(thisItem).children(".works").slideUp("fast");
        // and change the icon to afford expanding rather than contracting
        $(thisItem).find("i").removeClass("fa-minus-square-o");
        $(thisItem).find("i").addClass("fa-plus-square-o");
    }
}

function toggleSelect(element) {
    $(element).parent().toggleClass("selected")
}

function toggleListExclusion(element) {
    if($(element).hasClass("fa-eye-slash")) {
        // unlist this item
        $(element).removeClass("fa-eye-slash");
        $(element).addClass("fa-eye");
        $(element).parent().addClass("unlisted");
    } else {
        // relist this item
        $(element).removeClass("fa-eye");
        $(element).addClass("fa-eye-slash");
        $(element).parent().removeClass("unlisted");
    }
}

function includeSelectedPersons(element) {
    // include all selected persons in our query
    includePersons(element, "#qperson .preview .persons .pperson.selected");
}

function includeListedPersons(element) {
    // include all listed persons in our query
    includePersons(element, "#qperson .preview .persons .pperson:not(.unlisted)");
}

function includePersons(element, selector) {
    $("#personsIncludedHeader").css("display", "none");
    includedPersons = $("#personsInQuery").html();
    alreadyIncluded = [];
    $("#personsInQuery div").each(function() {
        alreadyIncluded.push($(this).attr("title"));
    });
    $(selector).each(function() {
        if(alreadyIncluded.indexOf($(this).attr("title")) < 0) {
            // only if not already included
            includedPersons += '
 Works by: ' + $(this).children("span").html() + '
\n';
        }
    });
    $("#personsInQuery").html(includedPersons);
    if(includedPersons) {
        $("#personsIncludedHeader").css("display", "block");
    }
}

function includeSelectedPlaces(element) {
    // include all selected places in our query
    includePlaces(element, "#qplace .preview .places .pplace.selected");
}

function includeListedPlaces(element) {
    // include all listed places in our query
    includePlaces(element, "#qplace .preview .places .pplace:not(.unlisted)");
}

function includePlaces(element, selector) {
    $("#placesIncludedHeader").css("display", "none");
    includedPlaces = $("#placesInQuery").html();
    alreadyIncluded = [];
    $("#placesInQuery div").each(function() {
        alreadyIncluded.push($(this).attr("title"));
    });
    $(selector).each(function() {
        if(alreadyIncluded.indexOf($(this).attr("title")) < 0) {
            // only if not already included
            includedPlaces += '
 Publication place: ' + $(this).children("span").html() + '
\n';
        }
    });
    if(includedPlaces) {
        $("#placesIncludedHeader").css("display", "block");
    }
    $("#placesInQuery").html(includedPlaces);

}

function includeSelectedSubjects(element) {
    // include all selected subjects in our query
    includeSubjects(element, "#qsubject .preview .subjects .psubject.selected");
}

function includeListedSubjects(element) {
    // include all listed subjects in our query
    includeSubjects(element, "#qsubject .preview .subjects .psubject:not(.unlisted)");
}

function includeSubjects(element, selector) {
    $("#subjectsIncludedHeader").css("display", "none");
    includedSubjects = $("#subjectsInQuery").html();
    alreadyIncluded = [];
    $("#subjectsInQuery div").each(function() {
        alreadyIncluded.push($(this).attr("title"));
    });
    $(selector).each(function() {
        if(alreadyIncluded.indexOf($(this).attr("title")) < 0) {
            // only if not already included
            includedSubjects += '
 Publication subject: ' + $(this).children("span").html() + '
\n';
        }
    });
    if(includedSubjects) {
        $("#subjectsIncludedHeader").css("display", "block");
    }
    $("#subjectsInQuery").html(includedSubjects);

}

function includeSelectedGenres(element) {
    // include all selected genres in our query
    includeGenres(element, "#qgenre .preview .genres .pgenre.selected");
}

function includeListedGenres(element) {
    // include all listed genres in our query
    includeGenres(element, "#qgenre .preview .genres .pgenre:not(.unlisted)");
}

function includeGenres(element, selector) {
    $("#genresIncludedHeader").css("display", "none");
    includedGenres = $("#genresInQuery").html();
    alreadyIncluded = [];
    $("#genresInQuery div").each(function() {
        alreadyIncluded.push($(this).attr("title"));
    });
    $(selector).each(function() {
        if(alreadyIncluded.indexOf($(this).attr("title")) < 0) {
            // only if not already included
            includedGenres += '
 Publication genre: ' + $(this).children("span").html() + '
\n';
        }
    });
    if(includedGenres) {
        $("#genresIncludedHeader").css("display", "block");
    }
    $("#genresInQuery").html(includedGenres);

}

function removeFromQuery(element) {
    if(confirm("Removing from workset\n" + $(element).html() + "\nAre you sure?")) {
        $(element).remove();
    }
}

$(document).ready(function() {
    $("#person").val("");
    $("#place").val("");
    $("#subject").val("");
    $("#genre").val("");
    $("#date").val("");
    $("#workset-title").val("");
    $("#workset-abstract").val("");
    $("#personsIncludedHeader").css("display", "none");
    $("#placesIncludedHeader").css("display", "none");
    $("#subjectsIncludedHeader").css("display", "none");
    $("#genresIncludedHeader").css("display", "none");
    $("#datesIncludedHeader").css("display", "none");
    $(".preview").css("display", "none");
});
<commit_msg>Bugfix: only show headers in workset parameters in constructor when input has actually happened for that category<commit_after>function personInput(thisItem) {
    var val = $(thisItem).val();
    previewHtml = "";
    $("#qperson .controls .button").css("visibility", "hidden");
    $("#qperson .preview").css("display", "none");
    if (val.length > 2) {  // don't fire on short input to prevent enormous lists
        var re = new RegExp(val, "i");
        personNames = Object.keys(persons)
        var includePersons = personNames.filter(function(val) {
            return re.test(val);
        });
        previewHtml = "";
        for(var p in includePersons) {
            previewHtml += '
 '+ ' ' + includePersons[p] + '
\n';
        }
        $("#qperson .preview .persons").html(previewHtml);
        if(includePersons.length) {
            $("#qperson .controls .button").css("visibility", "visible");
            $("#qperson .preview").css("display", "block");
        }
    }

    $(".preview .persons").html(previewHtml);
}

function placeInput(thisItem) {
    var val = $(thisItem).val();
    previewHtml = "";
    $("#qplace .controls .button").css("visibility", "hidden");
    $("#qplace .preview").css("display", "none");
    if (val.length > 2) {  // dont fire on short input to prevent enormous lists
        var re = new RegExp(val, "i");
        var includePlaces = [];
        for (var p in places) {
            if(re.test(places[p])) {
                includePlaces.push(places[p]);
            }
        };
        previewHtml = "";
        for(var iP in includePlaces) {
            previewHtml += '
 '+ ' ' + includePlaces[iP] + '
\n';
        }
        $("#qplace .preview .places").html(previewHtml);
        if(includePlaces.length) {
            $("#qplace .controls .button").css("visibility", "visible");
            $("#qplace .preview").css("display", "block");
        }
    }

    $("#qplace .preview .places").html(previewHtml);
}

function subjectInput(thisItem) {
    var val = $(thisItem).val();
    previewHtml = "";
    $("#qsubject .controls .button").css("visibility", "hidden");
    $("#qsubject .preview").css("display", "none");
    if (val.length > 2) {  // dont fire on short input to prevent enormous lists
        var re = new RegExp(val, "i");
        var includeSubjects = [];
        for (var p in subjects) {
            if(re.test(subjects[p])) {
                includeSubjects.push(subjects[p]);
            }
        };
        previewHtml = "";
        for(var iP in includeSubjects) {
            previewHtml += '
 '+ ' ' + includeSubjects[iP] + '
\n';
        }
        $("#qsubject .preview .subjects").html(previewHtml);
        if(includeSubjects.length) {
            $("#qsubject .controls .button").css("visibility", "visible");
            $("#qsubject .preview").css("display", "block");
        }
    }

    $("#qsubject .preview .subjects").html(previewHtml);
}

function genreInput(thisItem) {
    var val = $(thisItem).val();
    previewHtml = "";
    $("#qgenre .controls .button").css("visibility", "hidden");
    $("#qgenre .preview").css("display", "none");
    if (val.length > 2) {  // dont fire on short input to prevent enormous lists
        var re = new RegExp(val, "i");
        var includeGenres = [];
        for (var p in genres) {
            if(re.test(genres[p])) {
                includeGenres.push(genres[p]);
            }
        };
        previewHtml = "";
        for(var iP in includeGenres) {
            previewHtml += '
 '+ ' ' + includeGenres[iP] + '
\n';
        }
        $("#qgenre .preview .genres").html(previewHtml);
        if(includeGenres.length) {
            $("#qgenre .controls .button").css("visibility", "visible");
            $("#qgenre .preview").css("display", "block");
        }
    }

    $("#qgenre .preview .genres").html(previewHtml);
}

function dateInput(element) {
    $("#datesIncludedHeader").css("display", "none");
    var datetype = $("#datetype").val();
    var datetypestr = $("#datetype :selected").html();
    var daterelation = $("#daterelation").val();
    var daterelationstr = $("#daterelation :selected").html();
    var date = $("#date").val();
    if(!date) {
        return;
    }
    datestr = "Dates: " + datetypestr + " " + daterelationstr + " " + date;
    var includeDates = $("#datesInQuery").html();
    includeDates += '
' + datestr + '
';
    $("#datesInQuery").html(includeDates);
    if(includeDates !== " ") {
        $("#datesIncludedHeader").css("display", "block");
    }
    $("#date").val("");

}

function expandItems(thisItem) {
    if($(thisItem).find("i").hasClass("fa-plus-square-o")) {
        // we need to expand the items
        $(thisItem).children(".works").slideDown("fast");
        // and change the icon to afford contracting rather than expanding
        $(thisItem).find("i").removeClass("fa-plus-square-o");
        $(thisItem).find("i").addClass("fa-minus-square-o");
    } else {
        // we need to contract the items
        $(thisItem).children(".works").slideUp("fast");
        // and change the icon to afford expanding rather than contracting
        $(thisItem).find("i").removeClass("fa-minus-square-o");
        $(thisItem).find("i").addClass("fa-plus-square-o");
    }
}

function toggleSelect(element) {
    $(element).parent().toggleClass("selected")
}

function toggleListExclusion(element) {
    if($(element).hasClass("fa-eye-slash")) {
        // unlist this item
        $(element).removeClass("fa-eye-slash");
        $(element).addClass("fa-eye");
        $(element).parent().addClass("unlisted");
    } else {
        // relist this item
        $(element).removeClass("fa-eye");
        $(element).addClass("fa-eye-slash");
        $(element).parent().removeClass("unlisted");
    }
}

function includeSelectedPersons(element) {
    // include all selected persons in our query
    includePersons(element, "#qperson .preview .persons .pperson.selected");
}

function includeListedPersons(element) {
    // include all listed persons in our query
    includePersons(element, "#qperson .preview .persons .pperson:not(.unlisted)");
}

function includePersons(element, selector) {
    $("#personsIncludedHeader").css("display", "none");
    includedPersons = $("#personsInQuery").html();
    alreadyIncluded = [];
    $("#personsInQuery div").each(function() {
        alreadyIncluded.push($(this).attr("title"));
    });
    $(selector).each(function() {
        if(alreadyIncluded.indexOf($(this).attr("title")) < 0) {
            // only if not already included
            includedPersons += '
 Works by: ' + $(this).children("span").html() + '
\n';
        }
    });
    $("#personsInQuery").html(includedPersons);
    if(includedPersons !== " ") {
        $("#personsIncludedHeader").css("display", "block");
    }
}

function includeSelectedPlaces(element) {
    // include all selected places in our query
    includePlaces(element, "#qplace .preview .places .pplace.selected");
}

function includeListedPlaces(element) {
    // include all listed places in our query
    includePlaces(element, "#qplace .preview .places .pplace:not(.unlisted)");
}

function includePlaces(element, selector) {
    $("#placesIncludedHeader").css("display", "none");
    includedPlaces = $("#placesInQuery").html();
    alreadyIncluded = [];
    $("#placesInQuery div").each(function() {
        alreadyIncluded.push($(this).attr("title"));
    });
    $(selector).each(function() {
        if(alreadyIncluded.indexOf($(this).attr("title")) < 0) {
            // only if not already included
            includedPlaces += '
 Publication place: ' + $(this).children("span").html() + '
\n';
        }
    });
    if(includedPlaces !== " ") {
        $("#placesIncludedHeader").css("display", "block");
    }
    $("#placesInQuery").html(includedPlaces);

}

function includeSelectedSubjects(element) {
    // include all selected subjects in our query
    includeSubjects(element, "#qsubject .preview .subjects .psubject.selected");
}

function includeListedSubjects(element) {
    // include all listed subjects in our query
    includeSubjects(element, "#qsubject .preview .subjects .psubject:not(.unlisted)");
}

function includeSubjects(element, selector) {
    $("#subjectsIncludedHeader").css("display", "none");
    includedSubjects = $("#subjectsInQuery").html();
    alreadyIncluded = [];
    $("#subjectsInQuery div").each(function() {
        alreadyIncluded.push($(this).attr("title"));
    });
    $(selector).each(function() {
        if(alreadyIncluded.indexOf($(this).attr("title")) < 0) {
            // only if not already included
            includedSubjects += '
 Publication subject: ' + $(this).children("span").html() + '
\n';
        }
    });
    if(includedSubjects !== " ") {
        $("#subjectsIncludedHeader").css("display", "block");
    }
    $("#subjectsInQuery").html(includedSubjects);

}

function includeSelectedGenres(element) {
    // include all selected genres in our query
    includeGenres(element, "#qgenre .preview .genres .pgenre.selected");
}

function includeListedGenres(element) {
    // include all listed genres in our query
    includeGenres(element, "#qgenre .preview .genres .pgenre:not(.unlisted)");
}

function includeGenres(element, selector) {
    $("#genresIncludedHeader").css("display", "none");
    includedGenres = $("#genresInQuery").html();
    alreadyIncluded = [];
    $("#genresInQuery div").each(function() {
        alreadyIncluded.push($(this).attr("title"));
    });
    $(selector).each(function() {
        if(alreadyIncluded.indexOf($(this).attr("title")) < 0) {
            // only if not already included
            includedGenres += '
 Publication genre: ' + $(this).children("span").html() + '
\n';
        }
    });
    if(includedGenres !== " ") {
        $("#genresIncludedHeader").css("display", "block");
    }
    $("#genresInQuery").html(includedGenres);

}

function removeFromQuery(element) {
    if(confirm("Removing from workset\n" + $(element).html() + "\nAre you sure?")) {
        if($(element).siblings("div").size() == 0) {
            // removed the last item of this type
            // so hide the header
            $(element).parent().prev("span").css("display", "none");
        }
        $(element).remove();
    }
}

$(document).ready(function() {
    $("#person").val("");
    $("#place").val("");
    $("#subject").val("");
    $("#genre").val("");
    $("#date").val("");
    $("#workset-title").val("");
    $("#workset-abstract").val("");
    $("#personsIncludedHeader").css("display", "none");
    $("#placesIncludedHeader").css("display", "none");
    $("#subjectsIncludedHeader").css("display", "none");
    $("#genresIncludedHeader").css("display", "none");
    $("#datesIncludedHeader").css("display", "none");
    $(".preview").css("display", "none");
});
<|endoftext|>