<commit_before>import { AbstractTheme } from '../theme'

export var bootstrap4Theme = AbstractTheme.extend({
  /* Theme config options that allows changing various aspects of the output */
  options: {
    'disable_theme_rules': false
  },
  /* Custom stylesheet rules. format: "selector" : "CSS rules" */
  rules: {
    'div[data-schemaid="root"]:after': 'position:relative;color:red;margin:10px 0;font-weight:600;display:block;width:100%;text-align:center;content:"This is an old JSON-Editor 1.x Theme and might not display elements correctly when used with the 2.x version"'
  },

  getSelectInput: function (options, multiple) {
    var el = this._super(options)
    el.classList.add('form-control')
    // el.style.width = 'auto';
    return el
  },

  setGridColumnSize: function (el, size, offset) {
    el.classList.add('col-md-' + size)

    if (offset) {
      el.classList.add('offset-md-' + offset)
    }
  },

  afterInputReady: function (input) {
    if (input.controlgroup) return

    input.controlgroup = this.closest(input, '.form-group')

    // TODO: use bootstrap slider
  },

  getTextareaInput: function () {
    var el = document.createElement('textarea')
    el.classList.add('form-control')
    return el
  },

  getRangeInput: function (min, max, step) {
    // TODO: use better slider
    return this._super(min, max, step)
  },

  getFormInputField: function (type) {
    var el = this._super(type)
    if (type !== 'checkbox' && type !== 'radio') {
      el.classList.add('form-control')
    }
    return el
  },

  getFormControl: function (label, input, description) {
    var group = document.createElement('div')
    group.classList.add('form-group')

    if (label && (input.type === 'checkbox' || input.type === 'radio')) {
      var check = document.createElement('div')
      check.classList.add('form-check')

      input.classList.add('form-check-input')
      label.classList.add('form-check-label')

      // todo: nest input under label? not bootstrap default, can we set an unique ID here?
      input.id = input.name // no name yet available?
      label.for = input.id

      check.appendChild(input)
      check.appendChild(label)

      group.appendChild(check)
    } else {
      if (label) {
        group.appendChild(label)
      }

      group.appendChild(input)
    }

    if (description) {
      group.appendChild(description)
    }

    return group
  },

  /**
   * Multiple checkboxes in a row.
   *
   */
  getMultiCheckboxHolder: function (controls, label, description, infoText) {
    var el = document.createElement('div')
    el.classList.add('form-group')

    if (label) {
      label.style.display = 'block'
      el.appendChild(label)

      if (infoText) {
        label.appendChild(infoText)
      }
    }

    for (var i in controls) {
      if (!controls.hasOwnProperty(i)) {
        continue
      }

      // controls are already parsed by getFormControl() so they have an .form-group
      // wrapper we need to get rid of...
      var ctrl = controls[i].firstChild

      ctrl.classList.add('form-check-inline')
      el.appendChild(ctrl)
    }

    if (description) el.appendChild(description)

    return el
  },

  /**
   * Single radio element
   */
  getFormRadio: function (attributes) {
    var el = this.getFormInputField('radio')

    for (var key in attributes) {
      el.setAttribute(key, attributes[key])
    }

    el.classList.add('form-check-input')

    return el
  },

  /**
   * Add the  for the single radio from getFormRadio()
   *
   */
  getFormRadioLabel: function (text, req) {
    var el = document.createElement('label')
    el.classList.add('form-check-label')
    el.appendChild(document.createTextNode(text))
    return el
  },

  /**
   * Stack the radios from getFormRadio()/getFormRadioLabel()
   *
   */
  getFormRadioControl: function (label, input, compact) {
    var el = document.createElement('div')
    el.classList.add('form-check')

    el.appendChild(input)
    el.appendChild(label)

    if (compact) {
      el.classList.add('form-check-inline')
    }

    return el
  },

  getIndentedPanel: function () {

    var el = document.createElement('div')
    el.classList.add('card', 'card-body', 'bg-light', 'mb-3')

    // for better twbs card styling we should be able to return a nested div

    return el
  },

  getFormInputDescription: function (text) {
    var el = document.createElement('small')
    el.classList.add('form-text')

    if (window.DOMPurify) {
      el.innerHTML = window.DOMPurify.sanitize(text)
    } else {
      el.textContent = this.cleanText(text)
    }

    return el
  },

  getHeader: function (text) {
    // var cardHeader = document.createElement('div')
    // cardHeader.classList.add('card-header')

    var el = document.createElement('h3')
    el.classList.add('card-title')

    if (typeof text === 'string') {
      el.textContent = text
    } else {
      text.classList.add('pr-3')
      el.appendChild(text)
    }

    // cardHeader.appendChild(el)

    return el
  },
  getHeaderButtonHolder: function () {
    var el = this.getButtonHolder()

    return el
  },
  getButtonHolder: function () {
    // todo: we dont want to use div.btn-group until we can wrap all buttons inside it
    // single button inside a group get's it's corners cut off
    var el = document.createElement('span')
    // el.classList.add('btn-group')
    // el.classList.add('ml-2')
    return el
  },
  getButton: function (text, icon, title) {
    var el = this._super(text, icon, title)
    el.classList.add('btn', 'btn-secondary', 'btn-sm', 'mr-2')
    return el
  },

  getTable: function () {
    var el = document.createElement('table')
    el.classList.add('table-bordered', 'table-sm')
    el.style.width = 'auto'
    el.style.maxWidth = 'none'
    return el
  },

  /**
   * input validation on <input>
   */
  addInputError: function (input, text) {
    if (!input.controlgroup) return

    input.classList.add('is-invalid')

    if (!input.errmsg) {
      input.errmsg = document.createElement('p')
      input.errmsg.classList.add('invalid-feedback')
      input.controlgroup.appendChild(input.errmsg)
    } else {
      input.errmsg.style.display = ''
    }

    input.errmsg.textContent = text
  },
  removeInputError: function (input) {
    if (!input.errmsg) return
    input.errmsg.style.display = 'none'
    input.classList.remove('is-invalid')
  },

  getTabHolder: function (propertyName) {
    var el = document.createElement('div')
    var pName = (typeof propertyName === 'undefined') ? '' : propertyName
    el.innerHTML = "

"
    el.classList.add('row')
    return el
  },
  addTab: function (holder, tab) {
    holder.children[0].children[0].appendChild(tab)
  },
  getTopTabHolder: function (propertyName) {
    var pName = (typeof propertyName === 'undefined') ? '' : propertyName

    var el = document.createElement('div')
    el.classList.add('card')
    el.innerHTML = "

"

    return el
  },
  getTab: function (text, tabId) {
    var liel = document.createElement('li')
    liel.classList.add('nav-item')
    var ael = document.createElement('a')
    ael.classList.add('nav-link')
    ael.setAttribute('style', 'padding:10px;')
    ael.setAttribute('href', '#' + tabId)
    ael.setAttribute('data-toggle', 'tab')
    ael.appendChild(text)
    liel.appendChild(ael)
    return liel
  },
  getTopTab: function (text, tabId) {
    var el = document.createElement('li')
    el.classList.add('nav-item')

    var a = document.createElement('a')
    a.classList.add('nav-link')
    a.setAttribute('href', '#' + tabId)
    a.setAttribute('data-toggle', 'tab')
    a.appendChild(text)

    el.appendChild(a)

    return el
  },
  getTabContent: function () {
    var el = document.createElement('div')
    el.classList.add('tab-pane')
    el.setAttribute('role', 'tabpanel')
    return el
  },
  getTopTabContent: function () {
    var el = document.createElement('div')
    el.classList.add('tab-pane')
    el.setAttribute('role', 'tabpanel')
    return el
  },
  markTabActive: function (row) {
    row.tab.firstChild.classList.add('active')

    if (typeof row.rowPane !== 'undefined') {
      row.rowPane.classList.add('active')
    } else {
      row.container.classList.add('active')
    }
  },
  markTabInactive: function (row) {
    row.tab.firstChild.classList.remove('active')

    if (typeof row.rowPane !== 'undefined') {
      row.rowPane.classList.remove('active')
    } else {
      row.container.classList.remove('active')
    }
  },

  addTopTab: function (holder, tab) {
    holder.children[0].children[0].appendChild(tab)
  },

  getTopTabContentHolder: function (tabHolder) {
    return tabHolder.children[1].children[0]
  },

  getProgressBar: function () {
    var min = 0
    var max = 100
    var start = 0

    var container = document.createElement('div')
    container.classList.add('progress')

    var bar = document.createElement('div')
    bar.classList.add('progress-bar')
    bar.setAttribute('role', 'progressbar')
    bar.setAttribute('aria-valuenow', start)
    bar.setAttribute('aria-valuemin', min)
    bar.setAttribute('aria-valuenax', max)
    bar.innerHTML = start + '%'
    container.appendChild(bar)

    return container
  },
  updateProgressBar: function (progressBar, progress) {
    if (!progressBar) return

    var bar = progressBar.firstChild
    var percentage = progress + '%'
    bar.setAttribute('aria-valuenow', progress)
    bar.style.width = percentage
    bar.innerHTML = percentage
  },
  updateProgressBarUnknown: function (progressBar) {
    if (!progressBar) return

    var bar = progressBar.firstChild
    progressBar.classList.add('progress', 'progress-striped', 'active')
    bar.removeAttribute('aria-valuenow')
    bar.style.width = '100%'
    bar.innerHTML = ''
  },
  getInputGroup: function (input, buttons) {
    if (!input) return

    var inputGroupContainer = document.createElement('div')
    inputGroupContainer.classList.add('input-group')
    inputGroupContainer.appendChild(input)

    var inputGroup = document.createElement('div')
    inputGroup.classList.add('input-group-prepend')
    inputGroupContainer.appendChild(inputGroup)

    for (var i = 0; i < buttons.length; i++) {
      inputGroup.appendChild(buttons[i])
    }

    return inputGroupContainer
  }
})
<commit_msg>table styles + tweaking for form-group in cells<commit_after>import { AbstractTheme } from '../theme'

export var bootstrap4Theme = AbstractTheme.extend({
  /* Theme config options that allows changing various aspects of the output */
  options: {
    'disable_theme_rules': false
  },
  /* Custom stylesheet rules. format: "selector" : "CSS rules" */
  rules: {
    'td>.form-group': 'margin-bottom: 0',
    'div[data-schemaid="root"]:after': 'position:relative;color:red;margin:10px 0;font-weight:600;display:block;width:100%;text-align:center;content:"This is an old JSON-Editor 1.x Theme and might not display elements correctly when used with the 2.x version"'
  },

  getSelectInput: function (options, multiple) {
    var el = this._super(options)
    el.classList.add('form-control')
    // el.style.width = 'auto';
    return el
  },

  setGridColumnSize: function (el, size, offset) {
    el.classList.add('col-md-' + size)

    if (offset) {
      el.classList.add('offset-md-' + offset)
    }
  },

  afterInputReady: function (input) {
    if (input.controlgroup) return

    input.controlgroup = this.closest(input, '.form-group')

    // TODO: use bootstrap slider
  },

  getTextareaInput: function () {
    var el = document.createElement('textarea')
    el.classList.add('form-control')
    return el
  },

  getRangeInput: function (min, max, step) {
    // TODO: use better slider
    return this._super(min, max, step)
  },

  getFormInputField: function (type) {
    var el = this._super(type)
    if (type !== 'checkbox' && type !== 'radio') {
      el.classList.add('form-control')
    }
    return el
  },

  getFormControl: function (label, input, description) {
    var group = document.createElement('div')
    group.classList.add('form-group')

    if (label && (input.type === 'checkbox' || input.type === 'radio')) {
      var check = document.createElement('div')
      check.classList.add('form-check')

      input.classList.add('form-check-input')
      label.classList.add('form-check-label')

      // todo: nest input under label? not bootstrap default, can we set an unique ID here?
      input.id = input.name // no name yet available?
      label.for = input.id

      check.appendChild(input)
      check.appendChild(label)

      group.appendChild(check)
    } else {
      if (label) {
        group.appendChild(label)
      }

      group.appendChild(input)
    }

    if (description) {
      group.appendChild(description)
    }

    return group
  },

  /**
   * Multiple checkboxes in a row.
   *
   */
  getMultiCheckboxHolder: function (controls, label, description, infoText) {
    var el = document.createElement('div')
    el.classList.add('form-group')

    if (label) {
      label.style.display = 'block'
      el.appendChild(label)

      if (infoText) {
        label.appendChild(infoText)
      }
    }

    for (var i in controls) {
      if (!controls.hasOwnProperty(i)) {
        continue
      }

      // controls are already parsed by getFormControl() so they have an .form-group
      // wrapper we need to get rid of...
      var ctrl = controls[i].firstChild

      ctrl.classList.add('form-check-inline')
      el.appendChild(ctrl)
    }

    if (description) el.appendChild(description)

    return el
  },

  /**
   * Single radio element
   */
  getFormRadio: function (attributes) {
    var el = this.getFormInputField('radio')

    for (var key in attributes) {
      el.setAttribute(key, attributes[key])
    }

    el.classList.add('form-check-input')

    return el
  },

  /**
   * Add the  for the single radio from getFormRadio()
   *
   */
  getFormRadioLabel: function (text, req) {
    var el = document.createElement('label')
    el.classList.add('form-check-label')
    el.appendChild(document.createTextNode(text))
    return el
  },

  /**
   * Stack the radios from getFormRadio()/getFormRadioLabel()
   *
   */
  getFormRadioControl: function (label, input, compact) {
    var el = document.createElement('div')
    el.classList.add('form-check')

    el.appendChild(input)
    el.appendChild(label)

    if (compact) {
      el.classList.add('form-check-inline')
    }

    return el
  },

  getIndentedPanel: function () {

    var el = document.createElement('div')
    el.classList.add('card', 'card-body', 'bg-light', 'mb-3')

    // for better twbs card styling we should be able to return a nested div

    return el
  },

  getFormInputDescription: function (text) {
    var el = document.createElement('small')
    el.classList.add('form-text')

    if (window.DOMPurify) {
      el.innerHTML = window.DOMPurify.sanitize(text)
    } else {
      el.textContent = this.cleanText(text)
    }

    return el
  },

  getHeader: function (text) {
    // var cardHeader = document.createElement('div')
    // cardHeader.classList.add('card-header')

    var el = document.createElement('h3')
    el.classList.add('card-title')

    if (typeof text === 'string') {
      el.textContent = text
    } else {
      text.classList.add('pr-3')
      el.appendChild(text)
    }

    // cardHeader.appendChild(el)

    return el
  },
  getHeaderButtonHolder: function () {
    var el = this.getButtonHolder()

    return el
  },
  getButtonHolder: function () {
    // todo: we dont want to use div.btn-group until we can wrap all buttons inside it
    // single button inside a group get's it's corners cut off
    var el = document.createElement('span')
    // el.classList.add('btn-group')
    // el.classList.add('ml-2')
    return el
  },
  getButton: function (text, icon, title) {
    var el = this._super(text, icon, title)
    el.classList.add('btn', 'btn-secondary', 'btn-sm', 'mr-2')
    return el
  },

  getTable: function () {
    var el = document.createElement('table')
    el.classList.add('table', 'table-bordered', 'table-sm')
    return el
  },

  /**
   * input validation on <input>
   */
  addInputError: function (input, text) {
    if (!input.controlgroup) return

    input.classList.add('is-invalid')

    if (!input.errmsg) {
      input.errmsg = document.createElement('p')
      input.errmsg.classList.add('invalid-feedback')
      input.controlgroup.appendChild(input.errmsg)
    } else {
      input.errmsg.style.display = ''
    }

    input.errmsg.textContent = text
  },
  removeInputError: function (input) {
    if (!input.errmsg) return
    input.errmsg.style.display = 'none'
    input.classList.remove('is-invalid')
  },

  getTabHolder: function (propertyName) {
    var el = document.createElement('div')
    var pName = (typeof propertyName === 'undefined') ? '' : propertyName
    el.innerHTML = "

"
    el.classList.add('row')
    return el
  },
  addTab: function (holder, tab) {
    holder.children[0].children[0].appendChild(tab)
  },
  getTopTabHolder: function (propertyName) {
    var pName = (typeof propertyName === 'undefined') ? '' : propertyName

    var el = document.createElement('div')
    el.classList.add('card')
    el.innerHTML = "

"

    return el
  },
  getTab: function (text, tabId) {
    var liel = document.createElement('li')
    liel.classList.add('nav-item')
    var ael = document.createElement('a')
    ael.classList.add('nav-link')
    ael.setAttribute('style', 'padding:10px;')
    ael.setAttribute('href', '#' + tabId)
    ael.setAttribute('data-toggle', 'tab')
    ael.appendChild(text)
    liel.appendChild(ael)
    return liel
  },
  getTopTab: function (text, tabId) {
    var el = document.createElement('li')
    el.classList.add('nav-item')

    var a = document.createElement('a')
    a.classList.add('nav-link')
    a.setAttribute('href', '#' + tabId)
    a.setAttribute('data-toggle', 'tab')
    a.appendChild(text)

    el.appendChild(a)

    return el
  },
  getTabContent: function () {
    var el = document.createElement('div')
    el.classList.add('tab-pane')
    el.setAttribute('role', 'tabpanel')
    return el
  },
  getTopTabContent: function () {
    var el = document.createElement('div')
    el.classList.add('tab-pane')
    el.setAttribute('role', 'tabpanel')
    return el
  },
  markTabActive: function (row) {
    row.tab.firstChild.classList.add('active')

    if (typeof row.rowPane !== 'undefined') {
      row.rowPane.classList.add('active')
    } else {
      row.container.classList.add('active')
    }
  },
  markTabInactive: function (row) {
    row.tab.firstChild.classList.remove('active')

    if (typeof row.rowPane !== 'undefined') {
      row.rowPane.classList.remove('active')
    } else {
      row.container.classList.remove('active')
    }
  },

  addTopTab: function (holder, tab) {
    holder.children[0].children[0].appendChild(tab)
  },

  getTopTabContentHolder: function (tabHolder) {
    return tabHolder.children[1].children[0]
  },

  getProgressBar: function () {
    var min = 0
    var max = 100
    var start = 0

    var container = document.createElement('div')
    container.classList.add('progress')

    var bar = document.createElement('div')
    bar.classList.add('progress-bar')
    bar.setAttribute('role', 'progressbar')
    bar.setAttribute('aria-valuenow', start)
    bar.setAttribute('aria-valuemin', min)
    bar.setAttribute('aria-valuenax', max)
    bar.innerHTML = start + '%'
    container.appendChild(bar)

    return container
  },
  updateProgressBar: function (progressBar, progress) {
    if (!progressBar) return

    var bar = progressBar.firstChild
    var percentage = progress + '%'
    bar.setAttribute('aria-valuenow', progress)
    bar.style.width = percentage
    bar.innerHTML = percentage
  },
  updateProgressBarUnknown: function (progressBar) {
    if (!progressBar) return

    var bar = progressBar.firstChild
    progressBar.classList.add('progress', 'progress-striped', 'active')
    bar.removeAttribute('aria-valuenow')
    bar.style.width = '100%'
    bar.innerHTML = ''
  },
  getInputGroup: function (input, buttons) {
    if (!input) return

    var inputGroupContainer = document.createElement('div')
    inputGroupContainer.classList.add('input-group')
    inputGroupContainer.appendChild(input)

    var inputGroup = document.createElement('div')
    inputGroup.classList.add('input-group-prepend')
    inputGroupContainer.appendChild(inputGroup)

    for (var i = 0; i < buttons.length; i++) {
      inputGroup.appendChild(buttons[i])
    }

    return inputGroupContainer
  }
})
<|endoftext|>


<commit_before>import { darken, lighten, adjust, invert } from 'khroma';
import { mkBorder } from './theme-helpers';
class Theme {
  constructor() {
    /** # Base variables */
    /** * background - used to know what the background color is of the diagram. This is used for deducing colors for istance line color. Defaulr value is #f4f4f4. */
    this.background = '#f4f4f4';
    this.darkMode = false;

    // this.background = '#0c0c0c';
    // this.darkMode = true;
    this.primaryColor = '#fff4dd';
    // this.background = '#0c0c0c';
    // this.primaryColor = '#1f1f00';

    this.noteBkgColor = '#fff5ad';
    this.noteTextColor = '#333';

    // dark

    // this.primaryColor = '#034694';
    // this.primaryColor = '#f2ee7e';
    // this.primaryColor = '#9f33be';
    // this.primaryColor = '#f0fff0';
    // this.primaryColor = '#fa255e';
    // this.primaryColor = '#ECECFF';

    // this.secondaryColor = '#c39ea0';
    // this.tertiaryColor = '#f8e5e5';

    // this.secondaryColor = '#dfdfde';
    // this.tertiaryColor = '#CCCCFF';

    this.fontFamily = '"trebuchet ms", verdana, arial';
    this.fontSize = '16px';
    // this.updateColors();
  }
  updateColors() {
    // The || is to make sure that if the variable has been defiend by a user override that value is to be used

    /* Main */
    this.primaryTextColor = this.primaryTextColor || (this.darkMode ? '#ddd' : '#333'); // invert(this.primaryColor);
    this.secondaryColor = this.secondaryColor || adjust(this.primaryColor, { h: -120 });
    this.tertiaryColor = this.tertiaryColor || adjust(this.primaryColor, { h: 180, l: 5 });

    this.primaryBorderColor = this.primaryBorderColor || mkBorder(this.primaryColor, this.darkMode);
    this.secondaryBorderColor =
      this.secondaryBorderColor || mkBorder(this.secondaryColor, this.darkMode);
    this.tertiaryBorderColor =
      this.tertiaryBorderColor || mkBorder(this.tertiaryColor, this.darkMode);
    this.noteBorderColor = this.noteBorderColor || mkBorder(this.noteBkgColor, this.darkMode);

    this.secondaryTextColor = this.secondaryTextColor || invert(this.secondaryColor);
    this.tertiaryTextColor = this.tertiaryTextColor || invert(this.tertiaryColor);
    this.lineColor = this.lineColor || invert(this.background);
    this.textColor = this.textColor || this.primaryTextColor;

    /* Flowchart variables */
    this.nodeBkg = this.nodeBkg || this.primaryColor;
    this.mainBkg = this.mainBkg || this.primaryColor;
    this.nodeBorder = this.nodeBorder || this.primaryBorderColor;
    this.clusterBkg = this.clusterBkg || this.tertiaryColor;
    this.clusterBorder = this.clusterBorder || this.tertiaryBorderColor;
    this.defaultLinkColor = this.defaultLinkColor || this.lineColor;
    this.titleColor = this.titleColor || this.tertiaryTextColor;
    this.edgeLabelBackground = this.edgeLabelBackground ||
        (this.darkMode ? darken(this.secondaryColor, 30) : this.secondaryColor);
    this.nodeTextColor = this.nodeTextColor || this.primaryTextColor;
    /* Sequence Diagram variables */

    // this.actorBorder = lighten(this.border1, 0.5);
    this.actorBorder = this.actorBorder || this.primaryBorderColor;
    this.actorBkg = this.actorBkg || this.mainBkg;
    this.actorTextColor = this.actorTextColor || this.primaryTextColor;
    this.actorLineColor = this.actorLineColor || 'grey';
    this.labelBoxBkgColor = this.labelBoxBkgColor || this.actorBkg;
    this.signalColor = this.signalColor || this.textColor;
    this.signalTextColor = this.signalTextColor || this.textColor;
    this.labelBoxBorderColor = this.labelBoxBorderColor || this.actorBorder;
    this.labelTextColor = this.labelTextColor || this.actorTextColor;
    this.loopTextColor = this.loopTextColor || this.actorTextColor;
    this.activationBorderColor = this.activationBorderColor || darken(this.secondaryColor, 10);
    this.activationBkgColor = this.activationBkgColor || this.secondaryColor;
    this.sequenceNumberColor = this.sequenceNumberColor || invert(this.lineColor);

    /* Gantt chart variables */

    this.sectionBkgColor = this.sectionBkgColor || this.tertiaryColor;
    this.altSectionBkgColor = this.altSectionBkgColor || 'white';
    this.sectionBkgColor = this.sectionBkgColor || this.secondaryColor;
    this.sectionBkgColor2 = this.sectionBkgColor2 || this.primaryColor;
    this.taskBorderColor = this.taskBorderColor || this.primaryBorderColor;
    this.taskBkgColor = this.taskBkgColor || this.primaryColor;
    this.activeTaskBorderColor = this.activeTaskBorderColor || this.primaryColor;
    this.activeTaskBkgColor = this.activeTaskBkgColor || lighten(this.primaryColor, 23);
    this.gridColor = this.gridColor || 'lightgrey';
    this.doneTaskBkgColor = this.doneTaskBkgColor || 'lightgrey';
    this.doneTaskBorderColor = this.doneTaskBorderColor || 'grey';
    this.critBorderColor = this.critBorderColor || '#ff8888';
    this.critBkgColor = this.critBkgColor || 'red';
    this.todayLineColor = this.todayLineColor || 'red';
    this.taskTextColor = this.taskTextColor || this.textColor;
    this.taskTextOutsideColor = this.taskTextOutsideColor || this.textColor;
    this.taskTextLightColor = this.taskTextLightColor || this.textColor;
    this.taskTextColor = this.taskTextColor || this.primaryTextColor;
    this.taskTextDarkColor = this.taskTextDarkColor || this.textColor;
    this.taskTextClickableColor = this.taskTextClickableColor || '#003163';

    /* state colors */
    this.labelColor = this.labelColor || this.primaryTextColor;
    this.altBackground = this.altBackground || this.tertiaryColor;
    this.errorBkgColor = this.errorBkgColor || this.tertiaryColor;
    this.errorTextColor = this.errorTextColor || this.tertiaryTextColor;

    /* class */
    this.classText = this.classText || this.textColor;

    /* user-journey */
    this.fillType0 = this.fillType0 || this.primaryColor;
    this.fillType1 = this.fillType1 || this.secondaryColor;
    this.fillType2 = this.fillType2 || adjust(this.primaryColor, { h: 64 });
    this.fillType3 = this.fillType3 || adjust(this.secondaryColor, { h: 64 });
    this.fillType4 = this.fillType4 || adjust(this.primaryColor, { h: -64 });
    this.fillType5 = this.fillType5 || adjust(this.secondaryColor, { h: -64 });
    this.fillType6 = this.fillType6 || adjust(this.primaryColor, { h: 128 });
    this.fillType7 = this.fillType7 || adjust(this.secondaryColor, { h: 128 });
  }
  calculate(overrides) {
    if (typeof overrides !== 'object') {
      // Calculate colors form base colors
      this.updateColors();
      return;
    }

    const keys = Object.keys(overrides);

    // Copy values from overrides, this is mainly for base colors
    keys.forEach(k => {
      this[k] = overrides[k];
    });

    // Calculate colors form base colors
    this.updateColors();
    // Copy values from overrides again in case of an override of derived value
    keys.forEach(k => {
      this[k] = overrides[k];
    });
  }
}

export const getThemeVariables = userOverrides => {
  const theme = new Theme();
  theme.calculate(userOverrides);
  return theme;
};
<commit_msg>Update theme-base.js<commit_after>import { darken, lighten, adjust, invert } from 'khroma';
import { mkBorder } from './theme-helpers';
class Theme {
  constructor() {
    /** # Base variables */
    /** * background - used to know what the background color is of the diagram. This is used for deducing colors for istance line color. Defaulr value is #f4f4f4. */
    this.background = '#f4f4f4';
    this.darkMode = false;

    // this.background = '#0c0c0c';
    // this.darkMode = true;
    this.primaryColor = '#fff4dd';
    // this.background = '#0c0c0c';
    // this.primaryColor = '#1f1f00';

    this.noteBkgColor = '#fff5ad';
    this.noteTextColor = '#333';

    // dark

    // this.primaryColor = '#034694';
    // this.primaryColor = '#f2ee7e';
    // this.primaryColor = '#9f33be';
    // this.primaryColor = '#f0fff0';
    // this.primaryColor = '#fa255e';
    // this.primaryColor = '#ECECFF';

    // this.secondaryColor = '#c39ea0';
    // this.tertiaryColor = '#f8e5e5';

    // this.secondaryColor = '#dfdfde';
    // this.tertiaryColor = '#CCCCFF';

    this.fontFamily = '"trebuchet ms", verdana, arial';
    this.fontSize = '16px';
    // this.updateColors();
  }
  updateColors() {
    // The || is to make sure that if the variable has been defiend by a user override that value is to be used

    /* Main */
    this.primaryTextColor = this.primaryTextColor || (this.darkMode ? '#ddd' : '#333'); // invert(this.primaryColor);
    this.secondaryColor = this.secondaryColor || adjust(this.primaryColor, { h: -120 });
    this.tertiaryColor = this.tertiaryColor || adjust(this.primaryColor, { h: 180, l: 5 });

    this.primaryBorderColor = this.primaryBorderColor || mkBorder(this.primaryColor, this.darkMode);
    this.secondaryBorderColor =
      this.secondaryBorderColor || mkBorder(this.secondaryColor, this.darkMode);
    this.tertiaryBorderColor =
      this.tertiaryBorderColor || mkBorder(this.tertiaryColor, this.darkMode);
    this.noteBorderColor = this.noteBorderColor || mkBorder(this.noteBkgColor, this.darkMode);

    this.secondaryTextColor = this.secondaryTextColor || invert(this.secondaryColor);
    this.tertiaryTextColor = this.tertiaryTextColor || invert(this.tertiaryColor);
    this.lineColor = this.lineColor || invert(this.background);
    this.textColor = this.textColor || this.primaryTextColor;

    /* Flowchart variables */
    this.nodeBkg = this.nodeBkg || this.primaryColor;
    this.mainBkg = this.mainBkg || this.primaryColor;
    this.nodeBorder = this.nodeBorder || this.primaryBorderColor;
    this.clusterBkg = this.clusterBkg || this.tertiaryColor;
    this.clusterBorder = this.clusterBorder || this.tertiaryBorderColor;
    this.defaultLinkColor = this.defaultLinkColor || this.lineColor;
    this.titleColor = this.titleColor || this.tertiaryTextColor;
    this.edgeLabelBackground =
      this.edgeLabelBackground || (this.darkMode ? darken(this.secondaryColor, 30) : this.secondaryColor);
    this.nodeTextColor = this.nodeTextColor || this.primaryTextColor;
    /* Sequence Diagram variables */

    // this.actorBorder = lighten(this.border1, 0.5);
    this.actorBorder = this.actorBorder || this.primaryBorderColor;
    this.actorBkg = this.actorBkg || this.mainBkg;
    this.actorTextColor = this.actorTextColor || this.primaryTextColor;
    this.actorLineColor = this.actorLineColor || 'grey';
    this.labelBoxBkgColor = this.labelBoxBkgColor || this.actorBkg;
    this.signalColor = this.signalColor || this.textColor;
    this.signalTextColor = this.signalTextColor || this.textColor;
    this.labelBoxBorderColor = this.labelBoxBorderColor || this.actorBorder;
    this.labelTextColor = this.labelTextColor || this.actorTextColor;
    this.loopTextColor = this.loopTextColor || this.actorTextColor;
    this.activationBorderColor = this.activationBorderColor || darken(this.secondaryColor, 10);
    this.activationBkgColor = this.activationBkgColor || this.secondaryColor;
    this.sequenceNumberColor = this.sequenceNumberColor || invert(this.lineColor);

    /* Gantt chart variables */

    this.sectionBkgColor = this.sectionBkgColor || this.tertiaryColor;
    this.altSectionBkgColor = this.altSectionBkgColor || 'white';
    this.sectionBkgColor = this.sectionBkgColor || this.secondaryColor;
    this.sectionBkgColor2 = this.sectionBkgColor2 || this.primaryColor;
    this.taskBorderColor = this.taskBorderColor || this.primaryBorderColor;
    this.taskBkgColor = this.taskBkgColor || this.primaryColor;
    this.activeTaskBorderColor = this.activeTaskBorderColor || this.primaryColor;
    this.activeTaskBkgColor = this.activeTaskBkgColor || lighten(this.primaryColor, 23);
    this.gridColor = this.gridColor || 'lightgrey';
    this.doneTaskBkgColor = this.doneTaskBkgColor || 'lightgrey';
    this.doneTaskBorderColor = this.doneTaskBorderColor || 'grey';
    this.critBorderColor = this.critBorderColor || '#ff8888';
    this.critBkgColor = this.critBkgColor || 'red';
    this.todayLineColor = this.todayLineColor || 'red';
    this.taskTextColor = this.taskTextColor || this.textColor;
    this.taskTextOutsideColor = this.taskTextOutsideColor || this.textColor;
    this.taskTextLightColor = this.taskTextLightColor || this.textColor;
    this.taskTextColor = this.taskTextColor || this.primaryTextColor;
    this.taskTextDarkColor = this.taskTextDarkColor || this.textColor;
    this.taskTextClickableColor = this.taskTextClickableColor || '#003163';

    /* state colors */
    this.labelColor = this.labelColor || this.primaryTextColor;
    this.altBackground = this.altBackground || this.tertiaryColor;
    this.errorBkgColor = this.errorBkgColor || this.tertiaryColor;
    this.errorTextColor = this.errorTextColor || this.tertiaryTextColor;

    /* class */
    this.classText = this.classText || this.textColor;

    /* user-journey */
    this.fillType0 = this.fillType0 || this.primaryColor;
    this.fillType1 = this.fillType1 || this.secondaryColor;
    this.fillType2 = this.fillType2 || adjust(this.primaryColor, { h: 64 });
    this.fillType3 = this.fillType3 || adjust(this.secondaryColor, { h: 64 });
    this.fillType4 = this.fillType4 || adjust(this.primaryColor, { h: -64 });
    this.fillType5 = this.fillType5 || adjust(this.secondaryColor, { h: -64 });
    this.fillType6 = this.fillType6 || adjust(this.primaryColor, { h: 128 });
    this.fillType7 = this.fillType7 || adjust(this.secondaryColor, { h: 128 });
  }
  calculate(overrides) {
    if (typeof overrides !== 'object') {
      // Calculate colors form base colors
      this.updateColors();
      return;
    }

    const keys = Object.keys(overrides);

    // Copy values from overrides, this is mainly for base colors
    keys.forEach(k => {
      this[k] = overrides[k];
    });

    // Calculate colors form base colors
    this.updateColors();
    // Copy values from overrides again in case of an override of derived value
    keys.forEach(k => {
      this[k] = overrides[k];
    });
  }
}

export const getThemeVariables = userOverrides => {
  const theme = new Theme();
  theme.calculate(userOverrides);
  return theme;
};
<|endoftext|>


<commit_before>/**
 * PowerTip TooltipController
 *
 * @fileoverview  TooltipController object that manages tips for an instance.
 * @link          https://stevenbenner.github.io/jquery-powertip/
 * @author        Steven Benner (http://stevenbenner.com/)
 * @requires      jQuery 1.7+
 */

/**
 * Creates a new tooltip controller.
 * @private
 * @constructor
 * @param {Object} options Options object containing settings.
 */
function TooltipController(options) {
	var placementCalculator = new PlacementCalculator(),
		tipElement = $('#' + options.popupId);

	// build and append tooltip div if it does not already exist
	if (tipElement.length === 0) {
		tipElement = $('<div/>', { id: options.popupId });
		// grab body element if it was not populated when the script loaded
		// note: this hack exists solely for jsfiddle support
		if ($body.length === 0) {
			$body = $('body');
		}
		$body.append(tipElement);
		// remember the tooltip elements that the plugin has created
		session.tooltips = session.tooltips ? session.tooltips.add(tipElement) : tipElement;
	}

	// hook mousemove for cursor follow tooltips
	if (options.followMouse) {
		// only one positionTipOnCursor hook per tooltip element, please
		if (!tipElement.data(DATA_HASMOUSEMOVE)) {
			$document.on('mousemove' + EVENT_NAMESPACE, positionTipOnCursor);
			$window.on('scroll' + EVENT_NAMESPACE, positionTipOnCursor);
			tipElement.data(DATA_HASMOUSEMOVE, true);
		}
	}

	/**
	 * Gives the specified element the active-hover state and queues up the
	 * showTip function.
	 * @private
	 * @param {jQuery} element The element that the tooltip should target.
	 */
	function beginShowTip(element) {
		element.data(DATA_HASACTIVEHOVER, true);
		// show tooltip, asap
		tipElement.queue(function queueTipInit(next) {
			showTip(element);
			next();
		});
	}

	/**
	 * Shows the tooltip, as soon as possible.
	 * @private
	 * @param {jQuery} element The element that the tooltip should target.
	 */
	function showTip(element) {
		var tipContent;

		// it is possible, especially with keyboard navigation, to move on to
		// another element with a tooltip during the queue to get to this point
		// in the code. if that happens then we need to not proceed or we may
		// have the fadeout callback for the last tooltip execute immediately
		// after this code runs, causing bugs.
		if (!element.data(DATA_HASACTIVEHOVER)) {
			return;
		}

		// if the tooltip is open and we got asked to open another one then the
		// old one is still in its fadeOut cycle, so wait and try again
		if (session.isTipOpen) {
			if (!session.isClosing) {
				hideTip(session.activeHover);
			}
			tipElement.delay(100).queue(function queueTipAgain(next) {
				showTip(element);
				next();
			});
			return;
		}

		// trigger powerTipPreRender event
		element.trigger('powerTipPreRender');

		// set tooltip content
		tipContent = getTooltipContent(element);
		if (tipContent) {
			tipElement.empty().append(tipContent);
		} else {
			// we have no content to display, give up
			return;
		}

		// trigger powerTipRender event
		element.trigger('powerTipRender');

		session.activeHover = element;
		session.isTipOpen = true;

		tipElement.data(DATA_MOUSEONTOTIP, options.mouseOnToPopup);

		// add custom class to tooltip element
		tipElement.addClass(options.popupClass);

		// set tooltip position
		// revert to static placement when the "force open" flag was set because
		// that flag means that we do not have accurate mouse position info
		if (!options.followMouse || element.data(DATA_FORCEDOPEN)) {
			positionTipOnElement(element);
			session.isFixedTipOpen = true;
		} else {
			positionTipOnCursor();
		}

		// close tooltip when clicking anywhere on the page, with the exception
		// of the tooltip's trigger element and any elements that are within a
		// tooltip that has 'mouseOnToPopup' option enabled
		// always enable this feature when the "force open" flag is set on a
		// followMouse tooltip because we reverted to static placement above
		if (!element.data(DATA_FORCEDOPEN) && !options.followMouse) {
			$document.on('click' + EVENT_NAMESPACE, function documentClick(event) {
				var target = event.target;
				if (target !== element[0]) {
					if (options.mouseOnToPopup) {
						if (target !== tipElement[0] && !$.contains(tipElement[0], target)) {
							$.powerTip.hide();
						}
					} else {
						$.powerTip.hide();
					}
				}
			});
		}

		// if we want to be able to mouse on to the tooltip then we need to
		// attach hover events to the tooltip that will cancel a close request
		// on mouseenter and start a new close request on mouseleave
		// only hook these listeners if we're not in manual mode
		if (options.mouseOnToPopup && !options.manual) {
			tipElement.on('mouseenter' + EVENT_NAMESPACE, function tipMouseEnter() {
				// check activeHover in case the mouse cursor entered the
				// tooltip during the fadeOut and close cycle
				if (session.activeHover) {
					session.activeHover.data(DATA_DISPLAYCONTROLLER).cancel();
				}
			});
			tipElement.on('mouseleave' + EVENT_NAMESPACE, function tipMouseLeave() {
				// check activeHover in case the mouse cursor left the tooltip
				// during the fadeOut and close cycle
				if (session.activeHover) {
					session.activeHover.data(DATA_DISPLAYCONTROLLER).hide();
				}
			});
		}

		// fadein
		tipElement.fadeIn(options.fadeInTime, function fadeInCallback() {
			// start desync polling
			if (!session.desyncTimeout) {
				session.desyncTimeout = setInterval(closeDesyncedTip, 500);
			}

			// trigger powerTipOpen event
			element.trigger('powerTipOpen');
		});
	}

	/**
	 * Hides the tooltip.
	 * @private
	 * @param {jQuery} element The element that the tooltip should target.
	 */
	function hideTip(element) {
		// reset session
		session.isClosing = true;
		session.isTipOpen = false;

		// stop desync polling
		session.desyncTimeout = clearInterval(session.desyncTimeout);

		// reset element state
		element.data(DATA_HASACTIVEHOVER, false);
		element.data(DATA_FORCEDOPEN, false);

		// remove document click handler
		$document.off('click' + EVENT_NAMESPACE);

		// unbind the mouseOnToPopup events if they were set
		tipElement.off(EVENT_NAMESPACE);

		// fade out
		tipElement.fadeOut(options.fadeOutTime, function fadeOutCallback() {
			var coords = new CSSCoordinates();

			// reset session and tooltip element
			session.activeHover = null;
			session.isClosing = false;
			session.isFixedTipOpen = false;
			tipElement.removeClass();

			// support mouse-follow and fixed position tips at the same time by
			// moving the tooltip to the last cursor location after it is hidden
			coords.set('top', session.currentY + options.offset);
			coords.set('left', session.currentX + options.offset);
			tipElement.css(coords);

			// trigger powerTipClose event
			element.trigger('powerTipClose');
		});
	}

	/**
	 * Moves the tooltip to the users mouse cursor.
	 * @private
	 */
	function positionTipOnCursor() {
		var tipWidth,
			tipHeight,
			coords,
			collisions,
			collisionCount;

		// to support having fixed tooltips on the same page as cursor tooltips,
		// where both instances are referencing the same tooltip element, we
		// need to keep track of the mouse position constantly, but we should
		// only set the tip location if a fixed tip is not currently open, a tip
		// open is imminent or active, and the tooltip element in question does
		// have a mouse-follow using it.
		if (!session.isFixedTipOpen && (session.isTipOpen || (session.tipOpenImminent && tipElement.data(DATA_HASMOUSEMOVE)))) {
			// grab measurements
			tipWidth = tipElement.outerWidth();
			tipHeight = tipElement.outerHeight();
			coords = new CSSCoordinates();

			// grab collisions
			coords.set('top', session.currentY + options.offset);
			coords.set('left', session.currentX + options.offset);
			collisions = getViewportCollisions(
				coords,
				tipWidth,
				tipHeight
			);

			// handle tooltip view port collisions
			if (collisions !== Collision.none) {
				collisionCount = countFlags(collisions);
				if (collisionCount === 1) {
					// if there is only one collision (bottom or right) then
					// simply constrain the tooltip to the view port
					if (collisions === Collision.right) {
						coords.set('left', session.scrollLeft + session.windowWidth - tipWidth);
					} else if (collisions === Collision.bottom) {
						coords.set('top', session.scrollTop + session.windowHeight - tipHeight);
					}
				} else {
					// if the tooltip has more than one collision then it is
					// trapped in the corner and should be flipped to get it out
					// of the users way
					coords.set('left', session.currentX - tipWidth - options.offset);
					coords.set('top', session.currentY - tipHeight - options.offset);
				}
			}

			// position the tooltip
			tipElement.css(coords);
		}
	}

	/**
	 * Sets the tooltip to the correct position relative to the specified target
	 * element. Based on options settings.
	 * @private
	 * @param {jQuery} element The element that the tooltip should target.
	 */
	function positionTipOnElement(element) {
		var priorityList,
			finalPlacement;

		// when the followMouse option is enabled and the "force open" flag is
		// set we revert to static positioning. since the developer may not have
		// considered this scenario we should use smart placement
		if (options.smartPlacement || (options.followMouse && element.data(DATA_FORCEDOPEN))) {
			priorityList = $.fn.powerTip.smartPlacementLists[options.placement];

			// iterate over the priority list and use the first placement option
			// that does not collide with the view port. if they all collide
			// then the last placement in the list will be used.
			$.each(priorityList, function(idx, pos) {
				// place tooltip and find collisions
				var collisions = getViewportCollisions(
					placeTooltip(element, pos),
					tipElement.outerWidth(),
					tipElement.outerHeight()
				);

				// update the final placement variable
				finalPlacement = pos;

				// break if there were no collisions
				return collisions !== Collision.none;
			});
		} else {
			// if we're not going to use the smart placement feature then just
			// compute the coordinates and do it
			placeTooltip(element, options.placement);
			finalPlacement = options.placement;
		}

		// add placement as class for CSS arrows
		tipElement.removeClass('w nw sw e ne se n s w se-alt sw-alt ne-alt nw-alt');
		tipElement.addClass(finalPlacement);
	}

	/**
	 * Sets the tooltip position to the appropriate values to show the tip at
	 * the specified placement. This function will iterate and test the tooltip
	 * to support elastic tooltips.
	 * @private
	 * @param {jQuery} element The element that the tooltip should target.
	 * @param {string} placement The placement for the tooltip.
	 * @return {CSSCoordinates} A CSSCoordinates object with the top, left, and
	 *     right position values.
	 */
	function placeTooltip(element, placement) {
		var iterationCount = 0,
			tipWidth,
			tipHeight,
			coords = new CSSCoordinates();

		// set the tip to 0,0 to get the full expanded width
		coords.set('top', 0);
		coords.set('left', 0);
		tipElement.css(coords);

		// to support elastic tooltips we need to check for a change in the
		// rendered dimensions after the tooltip has been positioned
		do {
			// grab the current tip dimensions
			tipWidth = tipElement.outerWidth();
			tipHeight = tipElement.outerHeight();

			// get placement coordinates
			coords = placementCalculator.compute(
				element,
				placement,
				tipWidth,
				tipHeight,
				options.offset
			);

			// place the tooltip
			tipElement.css(coords);
		} while (
			// sanity check: limit to 5 iterations, and...
			++iterationCount <= 5 &&
			// try again if the dimensions changed after placement
			(tipWidth !== tipElement.outerWidth() || tipHeight !== tipElement.outerHeight())
		);

		return coords;
	}

	/**
	 * Checks for a tooltip desync and closes the tooltip if one occurs.
	 * @private
	 */
	function closeDesyncedTip() {
		var isDesynced = false;
		// It is possible for the mouse cursor to leave an element without
		// firing the mouseleave or blur event. This most commonly happens when
		// the element is disabled under mouse cursor. If this happens it will
		// result in a desynced tooltip because the tooltip was never asked to
		// close. So we should periodically check for a desync situation and
		// close the tip if such a situation arises.
		if (session.isTipOpen && !session.isClosing && !session.delayInProgress && ($.inArray('mouseleave', options.closeEvents) > -1 || $.inArray('mouseout', options.closeEvents) > -1 || $.inArray('blur', options.closeEvents) > -1 || $.inArray('focusout', options.closeEvents) > -1)) {
			// user moused onto another tip or active hover is disabled
			if (session.activeHover.data(DATA_HASACTIVEHOVER) === false || session.activeHover.is(':disabled')) {
				isDesynced = true;
			} else {
				// hanging tip - have to test if mouse position is not over the
				// active hover and not over a tooltip set to let the user
				// interact with it.
				// for keyboard navigation: this only counts if the element does
				// not have focus.
				// for tooltips opened via the api: we need to check if it has
				// the forcedOpen flag.
				if (!isMouseOver(session.activeHover) && !session.activeHover.is(':focus') && !session.activeHover.data(DATA_FORCEDOPEN)) {
					if (tipElement.data(DATA_MOUSEONTOTIP)) {
						if (!isMouseOver(tipElement)) {
							isDesynced = true;
						}
					} else {
						isDesynced = true;
					}
				}
			}

			if (isDesynced) {
				// close the desynced tip
				hideTip(session.activeHover);
			}
		}
	}

	// expose methods
	this.showTip = beginShowTip;
	this.hideTip = hideTip;
	this.resetPosition = positionTipOnElement;
}
<commit_msg>Refactor closeDesyncedTip() to improve readability<commit_after>/**
 * PowerTip TooltipController
 *
 * @fileoverview  TooltipController object that manages tips for an instance.
 * @link          https://stevenbenner.github.io/jquery-powertip/
 * @author        Steven Benner (http://stevenbenner.com/)
 * @requires      jQuery 1.7+
 */

/**
 * Creates a new tooltip controller.
 * @private
 * @constructor
 * @param {Object} options Options object containing settings.
 */
function TooltipController(options) {
	var placementCalculator = new PlacementCalculator(),
		tipElement = $('#' + options.popupId);

	// build and append tooltip div if it does not already exist
	if (tipElement.length === 0) {
		tipElement = $('<div/>', { id: options.popupId });
		// grab body element if it was not populated when the script loaded
		// note: this hack exists solely for jsfiddle support
		if ($body.length === 0) {
			$body = $('body');
		}
		$body.append(tipElement);
		// remember the tooltip elements that the plugin has created
		session.tooltips = session.tooltips ? session.tooltips.add(tipElement) : tipElement;
	}

	// hook mousemove for cursor follow tooltips
	if (options.followMouse) {
		// only one positionTipOnCursor hook per tooltip element, please
		if (!tipElement.data(DATA_HASMOUSEMOVE)) {
			$document.on('mousemove' + EVENT_NAMESPACE, positionTipOnCursor);
			$window.on('scroll' + EVENT_NAMESPACE, positionTipOnCursor);
			tipElement.data(DATA_HASMOUSEMOVE, true);
		}
	}

	/**
	 * Gives the specified element the active-hover state and queues up the
	 * showTip function.
	 * @private
	 * @param {jQuery} element The element that the tooltip should target.
	 */
	function beginShowTip(element) {
		element.data(DATA_HASACTIVEHOVER, true);
		// show tooltip, asap
		tipElement.queue(function queueTipInit(next) {
			showTip(element);
			next();
		});
	}

	/**
	 * Shows the tooltip, as soon as possible.
	 * @private
	 * @param {jQuery} element The element that the tooltip should target.
	 */
	function showTip(element) {
		var tipContent;

		// it is possible, especially with keyboard navigation, to move on to
		// another element with a tooltip during the queue to get to this point
		// in the code. if that happens then we need to not proceed or we may
		// have the fadeout callback for the last tooltip execute immediately
		// after this code runs, causing bugs.
		if (!element.data(DATA_HASACTIVEHOVER)) {
			return;
		}

		// if the tooltip is open and we got asked to open another one then the
		// old one is still in its fadeOut cycle, so wait and try again
		if (session.isTipOpen) {
			if (!session.isClosing) {
				hideTip(session.activeHover);
			}
			tipElement.delay(100).queue(function queueTipAgain(next) {
				showTip(element);
				next();
			});
			return;
		}

		// trigger powerTipPreRender event
		element.trigger('powerTipPreRender');

		// set tooltip content
		tipContent = getTooltipContent(element);
		if (tipContent) {
			tipElement.empty().append(tipContent);
		} else {
			// we have no content to display, give up
			return;
		}

		// trigger powerTipRender event
		element.trigger('powerTipRender');

		session.activeHover = element;
		session.isTipOpen = true;

		tipElement.data(DATA_MOUSEONTOTIP, options.mouseOnToPopup);

		// add custom class to tooltip element
		tipElement.addClass(options.popupClass);

		// set tooltip position
		// revert to static placement when the "force open" flag was set because
		// that flag means that we do not have accurate mouse position info
		if (!options.followMouse || element.data(DATA_FORCEDOPEN)) {
			positionTipOnElement(element);
			session.isFixedTipOpen = true;
		} else {
			positionTipOnCursor();
		}

		// close tooltip when clicking anywhere on the page, with the exception
		// of the tooltip's trigger element and any elements that are within a
		// tooltip that has 'mouseOnToPopup' option enabled
		// always enable this feature when the "force open" flag is set on a
		// followMouse tooltip because we reverted to static placement above
		if (!element.data(DATA_FORCEDOPEN) && !options.followMouse) {
			$document.on('click' + EVENT_NAMESPACE, function documentClick(event) {
				var target = event.target;
				if (target !== element[0]) {
					if (options.mouseOnToPopup) {
						if (target !== tipElement[0] && !$.contains(tipElement[0], target)) {
							$.powerTip.hide();
						}
					} else {
						$.powerTip.hide();
					}
				}
			});
		}

		// if we want to be able to mouse on to the tooltip then we need to
		// attach hover events to the tooltip that will cancel a close request
		// on mouseenter and start a new close request on mouseleave
		// only hook these listeners if we're not in manual mode
		if (options.mouseOnToPopup && !options.manual) {
			tipElement.on('mouseenter' + EVENT_NAMESPACE, function tipMouseEnter() {
				// check activeHover in case the mouse cursor entered the
				// tooltip during the fadeOut and close cycle
				if (session.activeHover) {
					session.activeHover.data(DATA_DISPLAYCONTROLLER).cancel();
				}
			});
			tipElement.on('mouseleave' + EVENT_NAMESPACE, function tipMouseLeave() {
				// check activeHover in case the mouse cursor left the tooltip
				// during the fadeOut and close cycle
				if (session.activeHover) {
					session.activeHover.data(DATA_DISPLAYCONTROLLER).hide();
				}
			});
		}

		// fadein
		tipElement.fadeIn(options.fadeInTime, function fadeInCallback() {
			// start desync polling
			if (!session.desyncTimeout) {
				session.desyncTimeout = setInterval(closeDesyncedTip, 500);
			}

			// trigger powerTipOpen event
			element.trigger('powerTipOpen');
		});
	}

	/**
	 * Hides the tooltip.
	 * @private
	 * @param {jQuery} element The element that the tooltip should target.
	 */
	function hideTip(element) {
		// reset session
		session.isClosing = true;
		session.isTipOpen = false;

		// stop desync polling
		session.desyncTimeout = clearInterval(session.desyncTimeout);

		// reset element state
		element.data(DATA_HASACTIVEHOVER, false);
		element.data(DATA_FORCEDOPEN, false);

		// remove document click handler
		$document.off('click' + EVENT_NAMESPACE);

		// unbind the mouseOnToPopup events if they were set
		tipElement.off(EVENT_NAMESPACE);

		// fade out
		tipElement.fadeOut(options.fadeOutTime, function fadeOutCallback() {
			var coords = new CSSCoordinates();

			// reset session and tooltip element
			session.activeHover = null;
			session.isClosing = false;
			session.isFixedTipOpen = false;
			tipElement.removeClass();

			// support mouse-follow and fixed position tips at the same time by
			// moving the tooltip to the last cursor location after it is hidden
			coords.set('top', session.currentY + options.offset);
			coords.set('left', session.currentX + options.offset);
			tipElement.css(coords);

			// trigger powerTipClose event
			element.trigger('powerTipClose');
		});
	}

	/**
	 * Moves the tooltip to the users mouse cursor.
	 * @private
	 */
	function positionTipOnCursor() {
		var tipWidth,
			tipHeight,
			coords,
			collisions,
			collisionCount;

		// to support having fixed tooltips on the same page as cursor tooltips,
		// where both instances are referencing the same tooltip element, we
		// need to keep track of the mouse position constantly, but we should
		// only set the tip location if a fixed tip is not currently open, a tip
		// open is imminent or active, and the tooltip element in question does
		// have a mouse-follow using it.
		if (!session.isFixedTipOpen && (session.isTipOpen || (session.tipOpenImminent && tipElement.data(DATA_HASMOUSEMOVE)))) {
			// grab measurements
			tipWidth = tipElement.outerWidth();
			tipHeight = tipElement.outerHeight();
			coords = new CSSCoordinates();

			// grab collisions
			coords.set('top', session.currentY + options.offset);
			coords.set('left', session.currentX + options.offset);
			collisions = getViewportCollisions(
				coords,
				tipWidth,
				tipHeight
			);

			// handle tooltip view port collisions
			if (collisions !== Collision.none) {
				collisionCount = countFlags(collisions);
				if (collisionCount === 1) {
					// if there is only one collision (bottom or right) then
					// simply constrain the tooltip to the view port
					if (collisions === Collision.right) {
						coords.set('left', session.scrollLeft + session.windowWidth - tipWidth);
					} else if (collisions === Collision.bottom) {
						coords.set('top', session.scrollTop + session.windowHeight - tipHeight);
					}
				} else {
					// if the tooltip has more than one collision then it is
					// trapped in the corner and should be flipped to get it out
					// of the users way
					coords.set('left', session.currentX - tipWidth - options.offset);
					coords.set('top', session.currentY - tipHeight - options.offset);
				}
			}

			// position the tooltip
			tipElement.css(coords);
		}
	}

	/**
	 * Sets the tooltip to the correct position relative to the specified target
	 * element. Based on options settings.
	 * @private
	 * @param {jQuery} element The element that the tooltip should target.
	 */
	function positionTipOnElement(element) {
		var priorityList,
			finalPlacement;

		// when the followMouse option is enabled and the "force open" flag is
		// set we revert to static positioning. since the developer may not have
		// considered this scenario we should use smart placement
		if (options.smartPlacement || (options.followMouse && element.data(DATA_FORCEDOPEN))) {
			priorityList = $.fn.powerTip.smartPlacementLists[options.placement];

			// iterate over the priority list and use the first placement option
			// that does not collide with the view port. if they all collide
			// then the last placement in the list will be used.
			$.each(priorityList, function(idx, pos) {
				// place tooltip and find collisions
				var collisions = getViewportCollisions(
					placeTooltip(element, pos),
					tipElement.outerWidth(),
					tipElement.outerHeight()
				);

				// update the final placement variable
				finalPlacement = pos;

				// break if there were no collisions
				return collisions !== Collision.none;
			});
		} else {
			// if we're not going to use the smart placement feature then just
			// compute the coordinates and do it
			placeTooltip(element, options.placement);
			finalPlacement = options.placement;
		}

		// add placement as class for CSS arrows
		tipElement.removeClass('w nw sw e ne se n s w se-alt sw-alt ne-alt nw-alt');
		tipElement.addClass(finalPlacement);
	}

	/**
	 * Sets the tooltip position to the appropriate values to show the tip at
	 * the specified placement. This function will iterate and test the tooltip
	 * to support elastic tooltips.
	 * @private
	 * @param {jQuery} element The element that the tooltip should target.
	 * @param {string} placement The placement for the tooltip.
	 * @return {CSSCoordinates} A CSSCoordinates object with the top, left, and
	 *     right position values.
	 */
	function placeTooltip(element, placement) {
		var iterationCount = 0,
			tipWidth,
			tipHeight,
			coords = new CSSCoordinates();

		// set the tip to 0,0 to get the full expanded width
		coords.set('top', 0);
		coords.set('left', 0);
		tipElement.css(coords);

		// to support elastic tooltips we need to check for a change in the
		// rendered dimensions after the tooltip has been positioned
		do {
			// grab the current tip dimensions
			tipWidth = tipElement.outerWidth();
			tipHeight = tipElement.outerHeight();

			// get placement coordinates
			coords = placementCalculator.compute(
				element,
				placement,
				tipWidth,
				tipHeight,
				options.offset
			);

			// place the tooltip
			tipElement.css(coords);
		} while (
			// sanity check: limit to 5 iterations, and...
			++iterationCount <= 5 &&
			// try again if the dimensions changed after placement
			(tipWidth !== tipElement.outerWidth() || tipHeight !== tipElement.outerHeight())
		);

		return coords;
	}

	/**
	 * Checks for a tooltip desync and closes the tooltip if one occurs.
	 * @private
	 */
	function closeDesyncedTip() {
		var isDesynced = false,
			hasDesyncableCloseEvent = $.grep(
				[ 'mouseleave', 'mouseout', 'blur', 'focusout' ],
				function(eventType) {
					return $.inArray(options.closeEvents, eventType) !== -1;
				}
			).length > 0;

		// It is possible for the mouse cursor to leave an element without
		// firing the mouseleave or blur event. This most commonly happens when
		// the element is disabled under mouse cursor. If this happens it will
		// result in a desynced tooltip because the tooltip was never asked to
		// close. So we should periodically check for a desync situation and
		// close the tip if such a situation arises.
		if (session.isTipOpen && !session.isClosing && !session.delayInProgress && hasDesyncableCloseEvent) {
			if (session.activeHover.data(DATA_HASACTIVEHOVER) === false || session.activeHover.is(':disabled')) {
				// user moused onto another tip or active hover is disabled
				isDesynced = true;
			} else if (!isMouseOver(session.activeHover) && !session.activeHover.is(':focus') && !session.activeHover.data(DATA_FORCEDOPEN)) {
				// hanging tip - have to test if mouse position is not over the
				// active hover and not over a tooltip set to let the user
				// interact with it.
				// for keyboard navigation: this only counts if the element does
				// not have focus.
				// for tooltips opened via the api: we need to check if it has
				// the forcedOpen flag.
				if (tipElement.data(DATA_MOUSEONTOTIP)) {
					if (!isMouseOver(tipElement)) {
						isDesynced = true;
					}
				} else {
					isDesynced = true;
				}
			}

			if (isDesynced) {
				// close the desynced tip
				hideTip(session.activeHover);
			}
		}
	}

	// expose methods
	this.showTip = beginShowTip;
	this.hideTip = hideTip;
	this.resetPosition = positionTipOnElement;
}
<|endoftext|>


<commit_before>/**
  * @constructor
  */

var UIWindowBase = new function()
{
  var self = this;
  var id_count = 1;
  var ids = [];

  var getId = function()
  {
    return 'ui-window-' + (id_count++).toString();
  }

  this.default_width = 500;
  this.default_height = 300;
  this.default_top = 100;
  this.default_left = 100;

  var
  min_width = 200,
  min_height = 100,
  min_z_index = 100;

  var viewport = null;

  this.init = function(view_id, left, top, width, height)
  {
    var view = window.views[view_id];
    var has_statusbar =
      typeof view.window_statusbar == 'boolean' ? view.window_statusbar : true;
    ids[ids.length] = this.id = getId();
    this.view_id = view_id;
    this.top = top || view && view.window_top || this.default_top;
    this.left = left || view && view.window_left || this.default_left;
    this.width = width || view && view.window_width || this.default_width;
    this.height = height || view && view.window_height || this.default_height;
    this.min_top = 6;
    this.min_visible_height = 80;
    this.min_visible_width = 150;
    this.container = new WindowContainer(this);
    this.is_resizable =
      typeof view.window_resizable == 'boolean' ? view.window_resizable : true;
    if (has_statusbar)
      this.statusbar = new WindowStatusbar(this);
    if(toolbars[view_id])
    {
      this.toolbar = new WindowToolbar(this, toolbars[view_id].buttons, toolbars[view_id].filters );
    }
    if(!window.ui_windows)
    {
      window.ui_windows = {};
    }
    window.ui_windows[this.id] = this;
    this.render();
  }

  this.render = function(resizable)
  {
    var win = viewport.render(templates._window(this));

    if( this.toolbar )
    {
      this.toolbar.setDimensions();
      this.toolbar.setup(this.view_id);
    }

    this.container.setup(this.view_id);

    if (this.statusbar)
      this.statusbar.setup(this.view_id);

    this.setZIndex();
    win.style.zIndex = 200;
  }

  this._delete = function(id)
  {
    var
    _id = '',
    i = 0;
    for( ; ( _id = ids[i] ) && _id != id; i++);
    if(_id)
    {
      delete window['ui-windows'][id];
      ids.splice(i, 1);
      return true;
    }
    return false;
  }

  this.setZIndex = function()
  {
    var win = null, id = '', i = 0, z = 0;
    for( ; id = ids[i]; i++)
    {
      win = document.getElementById( id );
      if( win )
      {
        if( ( z = parseInt( win.style.zIndex ) ) && z > min_z_index )
        win.style.zIndex = z - 1;
      }
    }
  }

  this.getWindowByViewId = function(view_id)
  {
    var win = null, id = '', i = 0;
    for( ; id = ids[i]; i++)
    {
      if( ui_windows[id].view_id == view_id )
      {
        return ui_windows[id];
      }
    }
    return null;
  }

  this.update = function()
  {
    this.container.setDimensions();
  }

  this.showWindow = function(view_id, top, left, width, height)
  {
    var win = this.getWindowByViewId(view_id);

    if(win)
    {
      if (!win.is_resizable)
      {
        if (typeof width == 'number')
          win.width = width;
        if (typeof height == 'number')
          win.height = height;
      }

      if(document.getElementById(win.id))
      {
        self.setZIndex();
        document.getElementById(win.id).style.zIndex = 200;
      }
      else
      {
        win.render();
      }
    }
    else
    {
      win = new UIWindow(view_id, top, left, width, height);
    }
    return win;

  }

 /* event handling */

  var
  handlers =
  {
    'window-scale-top-left': true,
    'window-scale-top': true,
    'window-scale-top-right': true,
    'window-move': true,
    'window-scale-right': true,
    'window-scale-left': true,
    'window-scale-bottom': true,
    'window-scale-bottom-right': true,
    'window-scale-bottom-left': true
  },
  set = {},
  update = {},
  click_handlers = {},
  current_target = null,
  __event = null,
  interval = 0,
  current_style = null,
  update_handler = null,
  left_delta = 0,
  top_delta = 0,
  bottom_delta = 0,
  right_delta = 0,
  focus_catcher = null,
  window_shadows = null,

  store_event = function(event)
  {
    __event = event;
    focus_catcher.focus();
  },

  force_reflow_shadows = function()
  {
    if(window_shadows)
    {
      window_shadows.innerHTML = "" +
        "<window-shadow class='top-left'></window-shadow>" +
        "<window-shadow class='top'></window-shadow>" +
        "<window-shadow class='top-right'></window-shadow>" +
        "<window-shadow class='left'></window-shadow>" +
        "<window-shadow class='right'></window-shadow>" +
        "<window-shadow class='bottom-left'></window-shadow>" +
        "<window-shadow class='bottom'></window-shadow>" +
        "<window-shadow class='bottom-right'></window-shadow>";
    }
  },

  mousedown = function(event)
  {
    var handler = event.target.getAttribute('handler');
    {
      if( handler in handlers )
      {
        if( interval )
        {
          interval = clearInterval( interval );
        }
        if(!focus_catcher)
        {
          focus_catcher = UIBase.getFocusCatcher();
        }
        window_shadows = event.target.parentNode.getElementsByTagName('window-shadows')[0];
        current_style = event.target.parentNode.style;
        self.setZIndex();
        current_style.zIndex = 200;
        interval = setInterval( update[handler], 30 );
        update_handler = update[handler];
        set[handler](event);
        current_target = ui_windows[event.target.parentNode.id] || {};
        document.addEventListener('mousemove', store_event, false);
        document.addEventListener('mouseup', mouseup, false);
      }
    }
  },

  mouseclick = function(event)
  {
    var handler = event.target.getAttribute('handler');
    {
      if( handler in click_handlers )
      {
        click_handlers[handler](event);
      }
      else
      {
        var parent = event.target;
        while( parent )
        {
          if( /^window$/i.test(parent.nodeName) )
          {
            self.setZIndex();
            parent.style.zIndex = 200;
            break;
          }
          parent = parent.parentElement;
        }
      }
    }
  },

  mouseup = function(event)
  {
    document.removeEventListener('mousemove', store_event, false);
    document.removeEventListener('mouseup', mouseup, false);
    document.onselectstart = null;
    interval = clearInterval( interval );
    update_handler();
    window_shadows = current_target = current_style = __event = update_handler = null;
  },

  verify_left = function(win, inner_width, left)
  {
    if (left > inner_width - win.min_visible_width)
      left = inner_width - win.min_visible_width;
    if (left < win.min_visible_width - win.width)
      left = win.min_visible_width - win.width;
    return left;
  },

  verify_top = function(win, inner_height, top)
  {
    if (top > inner_height - win.min_visible_height)
      top = inner_height - win.min_visible_height;
    if (top < win.min_top)
      top = win.min_top;
    return top
  };

  set['window-move'] = function(event)
  {
    left_delta = event.pageX - event.target.parentNode.offsetLeft;
    top_delta = event.pageY - event.target.parentNode.offsetTop;
  }

  update['window-move'] = function()
  {

    if(__event)
    {
      var inner_height = window.innerHeight, inner_width = window.innerWidth;
      if( __event.pageX < inner_width && __event.pageX > 0 )
      {
        current_target.left = verify_left(current_target, inner_width,
                                          __event.pageX - left_delta);
        current_style.left = current_target.left + 'px';
      }
      if( __event.pageY < inner_height && __event.pageY > 0 )
      {
        current_target.top = verify_top(current_target, inner_height,
                                        __event.pageY - top_delta)
        current_style.top = current_target.top + 'px';
      }
      focus_catcher.focus();
    }
  }

  set['window-scale-top'] = function(event)
  {
    top_delta = event.pageY - event.target.parentNode.offsetTop;
    bottom_delta = event.target.parentNode.offsetTop + event.target.parentNode.offsetHeight - 2;
  }

  update['window-scale-top'] = function(event)
  {
    if( __event )
    {
      var top = __event.pageY - top_delta;
      var height =  bottom_delta - top;
      if( height > min_width )
      {
        current_style.height = ( current_target.height = height ) + 'px';
        current_style.top = ( current_target.top = top ) + 'px';
        current_target.update();
        focus_catcher.focus();
      }
    }
  }

  set['window-scale-right'] = function(event)
  {
    left_delta = event.pageX - ( event.target.parentNode.offsetWidth - 2 );
  }

  update['window-scale-right'] = function()
  {
    if(__event)
    {
      var width = __event.pageX - left_delta;
      if( width > min_width )
      {
        current_style.width = ( current_target.width = width ) + 'px';
        current_target.update();
        focus_catcher.focus();
      }
    }
  }

  set['window-scale-left'] = function(event)
  {
    left_delta = event.pageX - event.target.parentNode.offsetLeft;
    right_delta = event.target.parentNode.offsetLeft + event.target.parentNode.offsetWidth - 2;
  }

  update['window-scale-left'] = function(event)
  {
    if( __event )
    {
      var left = __event.pageX - left_delta;
      var width =  right_delta - left;
      if( width > min_width )
      {
        current_style.width = ( current_target.width = width ) + 'px';
        current_style.left = ( current_target.left = left ) + 'px';
        current_target.update();
        focus_catcher.focus();
      }
    }
  }

  set['window-scale-bottom'] = function(event)
  {
    top_delta = event.pageY - event.target.parentNode.offsetHeight;
  }

  update['window-scale-bottom'] = function()
  {
    if( __event )
    {
      var height = __event.pageY - top_delta;
      if( height > min_height )
      {
        current_style.height = ( current_target.height = height ) + 'px';
        current_target.update();
        focus_catcher.focus();
        force_reflow_shadows();
      }
    }
  }

  set['window-scale-top-left'] = function(event)
  {
    set['window-scale-left'](event);
    set['window-scale-top'](event);
  }

  update['window-scale-top-left'] = function()
  {
    update['window-scale-left']();
    update['window-scale-top']();
  }

  set['window-scale-top-right'] = function(event)
  {
    set['window-scale-right'](event);
    set['window-scale-top'](event);
  }

  update['window-scale-top-right'] = function()
  {
    update['window-scale-right']();
    update['window-scale-top']();
  }

  set['window-scale-bottom-right'] = function(event)
  {
    set['window-scale-right'](event);
    set['window-scale-bottom'](event);
  }

  update['window-scale-bottom-right'] = function()
  {
    update['window-scale-right']();
    update['window-scale-bottom']();
  }

  set['window-scale-bottom-left'] = function(event)
  {
    set['window-scale-left'](event);
    set['window-scale-bottom'](event);
  }

  update['window-scale-bottom-left'] = function()
  {
    update['window-scale-left']();
    update['window-scale-bottom']();
  }

  click_handlers['window-close'] = function(event)
  {
    var win = event.target.parentElement.parentElement;
    if(win)
    {
      ref_obj = ui_windows[win.id];
      ref_obj.container.onclose();
      messages.post("hide-view", {id: win.getAttribute('view_id')});
      win.parentElement.removeChild(win);
    }
  }

  var resize = function()
  {
    if (window.ui_windows)
    {
      var
      id = '',
      inner_height = window.innerHeight,
      inner_width = window.innerWidth,
      current_target = null,
      current_style = null,
      top = 0,
      left = 0;

      for (id in window.ui_windows)
      {
        current_target = window.ui_windows[id];
        if (current_style = (document.getElementById(id) &&
                             document.getElementById(id).style))
        {
          left = verify_left(current_target, inner_width, current_target.left);
          if (left != current_target.left)
            current_style.left = (current_target.left = left) + 'px';
          top = verify_left(current_target, inner_height, current_target.top);
          if (top != current_target.top)
            current_style.top = (current_target.top = top) + 'px';
        }
      }
    }
  }

  var init = function(event)
  {
    document.removeEventListener('load', arguments.callee, false);
    viewport = document.getElementsByTagName(defaults.window_container)[0];
    if(!viewport)
    {
      self.showWindow = function(){};
      opera.postError(ui_strings.DRAGONFLY_INFO_MESSAGE +
        'missing view port in init in windows');
    }
  }

  document.addEventListener('mousedown', mousedown, false);
  document.addEventListener('click', mouseclick, false);
  window.addEventListener('load', init, false);
  window.addEventListener('resize', resize, false);

}

/**
  * @constructor
  * @extends UIWindowBase
  */

var UIWindow = function(view_id, top, left, width, height)
{
  this.init(view_id, top, left, width, height);
}

UIWindow.prototype = UIWindowBase;
<commit_msg>Implemented closeWindow in UIWindoebase.<commit_after>/**
  * @constructor
  */

var UIWindowBase = new function()
{
  var self = this;
  var id_count = 1;
  var ids = [];

  var getId = function()
  {
    return 'ui-window-' + (id_count++).toString();
  }

  this.default_width = 500;
  this.default_height = 300;
  this.default_top = 100;
  this.default_left = 100;

  var
  min_width = 200,
  min_height = 100,
  min_z_index = 100;

  var viewport = null;

  this.init = function(view_id, left, top, width, height)
  {
    var view = window.views[view_id];
    var has_statusbar =
      typeof view.window_statusbar == 'boolean' ? view.window_statusbar : true;
    ids[ids.length] = this.id = getId();
    this.view_id = view_id;
    this.top = top || view && view.window_top || this.default_top;
    this.left = left || view && view.window_left || this.default_left;
    this.width = width || view && view.window_width || this.default_width;
    this.height = height || view && view.window_height || this.default_height;
    this.min_top = 6;
    this.min_visible_height = 80;
    this.min_visible_width = 150;
    this.container = new WindowContainer(this);
    this.is_resizable =
      typeof view.window_resizable == 'boolean' ? view.window_resizable : true;
    if (has_statusbar)
      this.statusbar = new WindowStatusbar(this);
    if(toolbars[view_id])
    {
      this.toolbar = new WindowToolbar(this, toolbars[view_id].buttons, toolbars[view_id].filters );
    }
    if(!window.ui_windows)
    {
      window.ui_windows = {};
    }
    window.ui_windows[this.id] = this;
    this.render();
  }

  this.render = function(resizable)
  {
    var win = viewport.render(templates._window(this));

    if( this.toolbar )
    {
      this.toolbar.setDimensions();
      this.toolbar.setup(this.view_id);
    }

    this.container.setup(this.view_id);

    if (this.statusbar)
      this.statusbar.setup(this.view_id);

    this.setZIndex();
    win.style.zIndex = 200;
  }

  this._delete = function(id)
  {
    var
    _id = '',
    i = 0;
    for( ; ( _id = ids[i] ) && _id != id; i++);
    if(_id)
    {
      delete window['ui-windows'][id];
      ids.splice(i, 1);
      return true;
    }
    return false;
  }

  this.setZIndex = function()
  {
    var win = null, id = '', i = 0, z = 0;
    for( ; id = ids[i]; i++)
    {
      win = document.getElementById( id );
      if( win )
      {
        if( ( z = parseInt( win.style.zIndex ) ) && z > min_z_index )
        win.style.zIndex = z - 1;
      }
    }
  }

  this.getWindowByViewId = function(view_id)
  {
    var win = null, id = '', i = 0;
    for( ; id = ids[i]; i++)
    {
      if( ui_windows[id].view_id == view_id )
      {
        return ui_windows[id];
      }
    }
    return null;
  }

  this.update = function()
  {
    this.container.setDimensions();
  }

  this.showWindow = function(view_id, top, left, width, height)
  {
    var win = this.getWindowByViewId(view_id);

    if(win)
    {
      if (!win.is_resizable)
      {
        if (typeof width == 'number')
          win.width = width;
        if (typeof height == 'number')
          win.height = height;
      }

      if(document.getElementById(win.id))
      {
        self.setZIndex();
        document.getElementById(win.id).style.zIndex = 200;
      }
      else
      {
        win.render();
      }
    }
    else
    {
      win = new UIWindow(view_id, top, left, width, height);
    }
    return win;

  }

  this.closeWindow = function(view_id)
  {
    var win = this.getWindowByViewId(view_id), win_ele = null;
    if(win && (win_ele = document.getElementById(win.id)))
    {
      win.container.onclose();
      win_ele.parentNode.removeChild(win_ele);
    }
  }

 /* event handling */

  var
  handlers =
  {
    'window-scale-top-left': true,
    'window-scale-top': true,
    'window-scale-top-right': true,
    'window-move': true,
    'window-scale-right': true,
    'window-scale-left': true,
    'window-scale-bottom': true,
    'window-scale-bottom-right': true,
    'window-scale-bottom-left': true
  },
  set = {},
  update = {},
  click_handlers = {},
  current_target = null,
  __event = null,
  interval = 0,
  current_style = null,
  update_handler = null,
  left_delta = 0,
  top_delta = 0,
  bottom_delta = 0,
  right_delta = 0,
  focus_catcher = null,
  window_shadows = null,

  store_event = function(event)
  {
    __event = event;
    focus_catcher.focus();
  },

  force_reflow_shadows = function()
  {
    if(window_shadows)
    {
      window_shadows.innerHTML = "" +
        "<window-shadow class='top-left'></window-shadow>" +
        "<window-shadow class='top'></window-shadow>" +
        "<window-shadow class='top-right'></window-shadow>" +
        "<window-shadow class='left'></window-shadow>" +
        "<window-shadow class='right'></window-shadow>" +
        "<window-shadow class='bottom-left'></window-shadow>" +
        "<window-shadow class='bottom'></window-shadow>" +
        "<window-shadow class='bottom-right'></window-shadow>";
    }
  },

  mousedown = function(event)
  {
    var handler = event.target.getAttribute('handler');
    {
      if( handler in handlers )
      {
        if( interval )
        {
          interval = clearInterval( interval );
        }
        if(!focus_catcher)
        {
          focus_catcher = UIBase.getFocusCatcher();
        }
        window_shadows = event.target.parentNode.getElementsByTagName('window-shadows')[0];
        current_style = event.target.parentNode.style;
        self.setZIndex();
        current_style.zIndex = 200;
        interval = setInterval( update[handler], 30 );
        update_handler = update[handler];
        set[handler](event);
        current_target = ui_windows[event.target.parentNode.id] || {};
        document.addEventListener('mousemove', store_event, false);
        document.addEventListener('mouseup', mouseup, false);
      }
    }
  },

  mouseclick = function(event)
  {
    var handler = event.target.getAttribute('handler');
    {
      if( handler in click_handlers )
      {
        click_handlers[handler](event);
      }
      else
      {
        var parent = event.target;
        while( parent )
        {
          if( /^window$/i.test(parent.nodeName) )
          {
            self.setZIndex();
            parent.style.zIndex = 200;
            break;
          }
          parent = parent.parentElement;
        }
      }
    }
  },

  mouseup = function(event)
  {
    document.removeEventListener('mousemove', store_event, false);
    document.removeEventListener('mouseup', mouseup, false);
    document.onselectstart = null;
    interval = clearInterval( interval );
    update_handler();
    window_shadows = current_target = current_style = __event = update_handler = null;
  },

  verify_left = function(win, inner_width, left)
  {
    if (left > inner_width - win.min_visible_width)
      left = inner_width - win.min_visible_width;
    if (left < win.min_visible_width - win.width)
      left = win.min_visible_width - win.width;
    return left;
  },

  verify_top = function(win, inner_height, top)
  {
    if (top > inner_height - win.min_visible_height)
      top = inner_height - win.min_visible_height;
    if (top < win.min_top)
      top = win.min_top;
    return top
  };

  set['window-move'] = function(event)
  {
    left_delta = event.pageX - event.target.parentNode.offsetLeft;
    top_delta = event.pageY - event.target.parentNode.offsetTop;
  }

  update['window-move'] = function()
  {

    if(__event)
    {
      var inner_height = window.innerHeight, inner_width = window.innerWidth;
      if( __event.pageX < inner_width && __event.pageX > 0 )
      {
        current_target.left = verify_left(current_target, inner_width,
                                          __event.pageX - left_delta);
        current_style.left = current_target.left + 'px';
      }
      if( __event.pageY < inner_height && __event.pageY > 0 )
      {
        current_target.top = verify_top(current_target, inner_height,
                                        __event.pageY - top_delta)
        current_style.top = current_target.top + 'px';
      }
      focus_catcher.focus();
    }
  }

  set['window-scale-top'] = function(event)
  {
    top_delta = event.pageY - event.target.parentNode.offsetTop;
    bottom_delta = event.target.parentNode.offsetTop + event.target.parentNode.offsetHeight - 2;
  }

  update['window-scale-top'] = function(event)
  {
    if( __event )
    {
      var top = __event.pageY - top_delta;
      var height =  bottom_delta - top;
      if( height > min_width )
      {
        current_style.height = ( current_target.height = height ) + 'px';
        current_style.top = ( current_target.top = top ) + 'px';
        current_target.update();
        focus_catcher.focus();
      }
    }
  }

  set['window-scale-right'] = function(event)
  {
    left_delta = event.pageX - ( event.target.parentNode.offsetWidth - 2 );
  }

  update['window-scale-right'] = function()
  {
    if(__event)
    {
      var width = __event.pageX - left_delta;
      if( width > min_width )
      {
        current_style.width = ( current_target.width = width ) + 'px';
        current_target.update();
        focus_catcher.focus();
      }
    }
  }

  set['window-scale-left'] = function(event)
  {
    left_delta = event.pageX - event.target.parentNode.offsetLeft;
    right_delta = event.target.parentNode.offsetLeft + event.target.parentNode.offsetWidth - 2;
  }

  update['window-scale-left'] = function(event)
  {
    if( __event )
    {
      var left = __event.pageX - left_delta;
      var width =  right_delta - left;
      if( width > min_width )
      {
        current_style.width = ( current_target.width = width ) + 'px';
        current_style.left = ( current_target.left = left ) + 'px';
        current_target.update();
        focus_catcher.focus();
      }
    }
  }

  set['window-scale-bottom'] = function(event)
  {
    top_delta = event.pageY - event.target.parentNode.offsetHeight;
  }

  update['window-scale-bottom'] = function()
  {
    if( __event )
    {
      var height = __event.pageY - top_delta;
      if( height > min_height )
      {
        current_style.height = ( current_target.height = height ) + 'px';
        current_target.update();
        focus_catcher.focus();
        force_reflow_shadows();
      }
    }
  }

  set['window-scale-top-left'] = function(event)
  {
    set['window-scale-left'](event);
    set['window-scale-top'](event);
  }

  update['window-scale-top-left'] = function()
  {
    update['window-scale-left']();
    update['window-scale-top']();
  }

  set['window-scale-top-right'] = function(event)
  {
    set['window-scale-right'](event);
    set['window-scale-top'](event);
  }

  update['window-scale-top-right'] = function()
  {
    update['window-scale-right']();
    update['window-scale-top']();
  }

  set['window-scale-bottom-right'] = function(event)
  {
    set['window-scale-right'](event);
    set['window-scale-bottom'](event);
  }

  update['window-scale-bottom-right'] = function()
  {
    update['window-scale-right']();
    update['window-scale-bottom']();
  }

  set['window-scale-bottom-left'] = function(event)
  {
    set['window-scale-left'](event);
    set['window-scale-bottom'](event);
  }

  update['window-scale-bottom-left'] = function()
  {
    update['window-scale-left']();
    update['window-scale-bottom']();
  }

  click_handlers['window-close'] = function(event)
  {
    var win = event.target.parentElement.parentElement;
    if(win)
    {
      ref_obj = ui_windows[win.id];
      ref_obj.container.onclose();
      messages.post("hide-view", {id: win.getAttribute('view_id')});
      win.parentElement.removeChild(win);
    }
  }

  var resize = function()
  {
    if (window.ui_windows)
    {
      var
      id = '',
      inner_height = window.innerHeight,
      inner_width = window.innerWidth,
      current_target = null,
      current_style = null,
      top = 0,
      left = 0;

      for (id in window.ui_windows)
      {
        current_target = window.ui_windows[id];
        if (current_style = (document.getElementById(id) &&
                             document.getElementById(id).style))
        {
          left = verify_left(current_target, inner_width, current_target.left);
          if (left != current_target.left)
            current_style.left = (current_target.left = left) + 'px';
          top = verify_left(current_target, inner_height, current_target.top);
          if (top != current_target.top)
            current_style.top = (current_target.top = top) + 'px';
        }
      }
    }
  }

  var init = function(event)
  {
    document.removeEventListener('load', arguments.callee, false);
    viewport = document.getElementsByTagName(defaults.window_container)[0];
    if(!viewport)
    {
      self.showWindow = function(){};
      opera.postError(ui_strings.DRAGONFLY_INFO_MESSAGE +
        'missing view port in init in windows');
    }
  }

  document.addEventListener('mousedown', mousedown, false);
  document.addEventListener('click', mouseclick, false);
  window.addEventListener('load', init, false);
  window.addEventListener('resize', resize, false);

}

/**
  * @constructor
  * @extends UIWindowBase
  */

var UIWindow = function(view_id, top, left, width, height)
{
  this.init(view_id, top, left, width, height);
}

UIWindow.prototype = UIWindowBase;
<|endoftext|>