<commit_before>let React = require("react")
let frigInput = require("./input.js")
let propsClosure = require("../higher_order_functions/props_closure.js")
let {entries} = require("../util.js")

/*
 * A JSX-compatible React DOM Component.
 * FrigForm should be used as the top level component for Frig forms in JSX.
 * In coffeescript FrigForm is called by FrigDSL.
 */
export default class FrigForm extends React.Component {
  static propTypes = {
    data: React.PropTypes.object,
    form: React.PropTypes.func.isRequired,
    theme: React.PropTypes.object.isRequired,
    typeMapping: React.PropTypes.objectOf(React.PropTypes.string),
    // Callbacks
    onChange: React.PropTypes.func,
    onValidChange: React.PropTypes.func,
    onSubmit: React.PropTypes.func,
  }

  static defaultProps = {
    theme: undefined,
    typeMapping: {},
    onChange() {},
    onValidChange() {},
    onSubmit() {},
  }

  _frigChanges = {}
  _frigFormData = {}
  _frigValidFormData = {}

  /*
   * =========================================================================
   * Public Functions
   * =========================================================================
   */

  validate() {
    let valid = true
    for (let [key, input] of entries(this.refs)) {
      if (key.match(/Input$/) != null && input.validate != null) {
        valid &= input.validate()
      }
    }
    return valid
  }

  getData() {
    return this._frigFormData
  }

  getValidData() {
    return this._frigValidFormData
  }

  /*
   * =========================================================================
   * React Lifecycle + Render
   * =========================================================================
   */

  componentWillMount() {
    // Create a factory for the theme the first time the component mounts
    this._themedForm = React.createFactory(this.props.theme.component("form"))
  }

  render() {
    return this._themedForm(this._themedFormProps(), this._friggingChildren())
  }

  /*
   * =========================================================================
   * Private functions
   * =========================================================================
   */

  _themedFormProps() {
    let formProps = Object.assign({}, this.props)
    formProps.formHtml = Object.assign({}, formProps.formHtml || {}, {
      ref: "form",
      onSubmit: this._onSubmit.bind(this),
    })
    return formProps
  }

  // Generate the type mapping for an input component
  _typeMapping() {
    return Object.assign(
      {},
      require("../type_mapping.js"),
      this.props.theme.type_mapping,
    )
  }

  /*
   * Returns a value link-type object regardles of whether the data property is
   * a value or value link
   */
  _dataLink() {
    if (this.props.data.requestChange) {
      return this.props.data
    }
    else {
      return {
        value: this.props.data,
        requestChange: () => {},
      }
    }
  }

  _data() {
    return this._dataLink().value
  }

  // Generates React DOM elements to pass to the themed form component as
  // child components.
  _friggingChildren() {
    return this.props.form(this._componentClasses())
  }

  _onFriggingChildInit(k, v) {
    this._frigFormData[k] = v
    this._frigValidFormData[k] = v
  }

  _onFriggingChildChange(k, v, valid) {
    this._frigFormData[k] = v
    if (valid) {
      this._frigValidFormData[k] = v
    }
    else {
      delete this._frigValidFormData[k]
    }
    // clone the form data object to avoid the situation where subsequent form
    // updates unexpectedly mutate the data object
    let formData = Object.assign({}, this._frigFormData)
    let reactLinkData = Object.assign({}, this._data(), formData)
    // Notify the event listeners
    this.props.onChange(formData)
    if (valid) this.props.onValidChange(formData)
    // Update the ReactLink with the merged combination of form data and the
    // initial values passed in to the form (allowing non-form data to be
    // persisted)
    this._dataLink().requestChange(reactLinkData)
  }

  _onSubmit(e) {
    e.preventDefault()
    if (!this.validate()) return
    this.props.onSubmit(e, this._frigFormData)
  }

  /*
   * =========================================================================
   * DSL
   * =========================================================================
   */

  /*
   * Component classes for children
   */
  _componentClasses() {
    if (this._componentClassesCache != null) return this._componentClassesCache
    return this._componentClassesCache = {
      errors: this._errorsComponentClass(),
      input: this._inputComponentClass(),
      submit: this._submitComponentClass(),
    }
  }

  _errorsComponentClass() {
    // Returning a input component modified with this form's defaults and
    // overrides
    let mapping = this._typeMapping().errors
    let component = this._getThemedInputComponent({}, mapping.component)
    return propsClosure(component, {
      defaults: {key: "errors"},
      overrides: this._errorsOverrides.bind(this),
    })
  }

  _errorsOverrides() {
    return {
      type: "errors",
      errors: this.props.errors,
    }
  }

  // Create a submit button
  // value: [STRING] The label text for the submit button
  // props: [OBJECT] properties to send to the React Component (see input props)
  _submitComponentClass() {
    // Returning a input component modified with this form's defaults and
    // overrides
    let mapping = this._typeMapping().submit
    let component = this._getThemedInputComponent({}, mapping.component)
    return propsClosure(component, {
      defaults: {key: "submit"},
      overrides: this._submitOverrides.bind(this),
    })
  }

  _submitOverrides(submitProps) {
    return {
      type: "submit",
      inputHtml: {
        defaultValue: submitProps.value,
      },
    }
  }

  // Creates a form field
  // key: [STRING] the name of the field in the data
  // props:
  //   required: (default: true)
  //   inputHtml: specify html attributes for the input React DOM element
  //   labelHtml: specify html attributes for the label React DOM element
  //   placeholder:
  //     true: (default) adds a placeholder with a name based on a humanization
  //           of the key
  //     false: disables the placeholder
  //     [STRING]: sets the placeholder to the given string
  //  label:
  //     true: (default) adds a label with a name based on a humanization
  //           of the key
  //     false: disables the label
  //     [STRING]: sets the label to the given string
  _inputComponentClass() {
    // Returning a input component modified with this form's defaults and
    // overrides
    return propsClosure(frigInput, {
      defaults: this._inputDefaults.bind(this),
      overrides: this._inputOverrides.bind(this),
    })
  }

  _inputDefaults({name}) {
    return {
      name,
      key: `${name}Input`,
      valueLink: {
        value: this._data()[name],
        requestChange: this._onFriggingChildChange.bind(this, [name]),
      },
      onFriggingChildInit: this._onFriggingChildInit.bind(this, [name]),
    }
  }

  _inputOverrides(inputProps) {
    let name = inputProps.name
    // Guessing the type and using it to lookup the template
    let type = this._frigGuessInputType(inputProps)
    // looking up the template name with the type mappings and the type
    let mapping = this._typeMapping()[type]
    let component = this._getThemedInputComponent(inputProps, mapping.component)
    let inputHtmlDefaults = {type: mapping.htmlInputType}
    // Generating the overrides object
    return Object.assign({}, inputProps, {
      type,
      component,
      ref: `${name}Input`,
      inputHtml: Object.assign(inputHtmlDefaults, inputProps.inputHtml),
    })
  }

  _frigGuessInputType(inputProps) {
    let jsType = typeof inputProps.valueLink.value
    if (inputProps.type != null) {
      return inputProps.type
    }
    else if (inputProps.multiple || Array.isArray(inputProps.initialValue)) {
      return "multiselect"
    }
    else if (inputProps.options != null) {
      return "select"
    }
    else if (jsType === "boolean") {
      return "boolean"
    }
    else if (jsType === "number") {
      return "float"
    }
    else if (inputProps.name.match(/[pP]assword^/)) {
      return "password"
    }
    else {
      return "string"
    }
  }

  _getThemedInputComponent(props, componentName) {
    if (componentName == null) {
      throw `${props.name}: No type mapping found for type ${props.type}`
    }
    let component = this.props.theme.component(componentName)
    if (component == null) {
      throw `${props.name}: No ${componentName} component found in theme`
    }
    return component
  }

}
<commit_msg>Fix propsClosure Not Being Found In Form.js<commit_after>let React = require("react")
let frigInput = require("./input.js")
let propsClosure = require("../higher_order_components/props_closure.js")
let {entries} = require("../util.js")

/*
 * A JSX-compatible React DOM Component.
 * FrigForm should be used as the top level component for Frig forms in JSX.
 * In coffeescript FrigForm is called by FrigDSL.
 */
export default class FrigForm extends React.Component {
  static propTypes = {
    data: React.PropTypes.object,
    form: React.PropTypes.func.isRequired,
    theme: React.PropTypes.object.isRequired,
    typeMapping: React.PropTypes.objectOf(React.PropTypes.string),
    // Callbacks
    onChange: React.PropTypes.func,
    onValidChange: React.PropTypes.func,
    onSubmit: React.PropTypes.func,
  }

  static defaultProps = {
    theme: undefined,
    typeMapping: {},
    onChange() {},
    onValidChange() {},
    onSubmit() {},
  }

  _frigChanges = {}
  _frigFormData = {}
  _frigValidFormData = {}

  /*
   * =========================================================================
   * Public Functions
   * =========================================================================
   */

  validate() {
    let valid = true
    for (let [key, input] of entries(this.refs)) {
      if (key.match(/Input$/) != null && input.validate != null) {
        valid &= input.validate()
      }
    }
    return valid
  }

  getData() {
    return this._frigFormData
  }

  getValidData() {
    return this._frigValidFormData
  }

  /*
   * =========================================================================
   * React Lifecycle + Render
   * =========================================================================
   */

  componentWillMount() {
    // Create a factory for the theme the first time the component mounts
    this._themedForm = React.createFactory(this.props.theme.component("form"))
  }

  render() {
    return this._themedForm(this._themedFormProps(), this._friggingChildren())
  }

  /*
   * =========================================================================
   * Private functions
   * =========================================================================
   */

  _themedFormProps() {
    let formProps = Object.assign({}, this.props)
    formProps.formHtml = Object.assign({}, formProps.formHtml || {}, {
      ref: "form",
      onSubmit: this._onSubmit.bind(this),
    })
    return formProps
  }

  // Generate the type mapping for an input component
  _typeMapping() {
    return Object.assign(
      {},
      require("../type_mapping.js"),
      this.props.theme.type_mapping,
    )
  }

  /*
   * Returns a value link-type object regardles of whether the data property is
   * a value or value link
   */
  _dataLink() {
    if (this.props.data.requestChange) {
      return this.props.data
    }
    else {
      return {
        value: this.props.data,
        requestChange: () => {},
      }
    }
  }

  _data() {
    return this._dataLink().value
  }

  // Generates React DOM elements to pass to the themed form component as
  // child components.
  _friggingChildren() {
    return this.props.form(this._componentClasses())
  }

  _onFriggingChildInit(k, v) {
    this._frigFormData[k] = v
    this._frigValidFormData[k] = v
  }

  _onFriggingChildChange(k, v, valid) {
    this._frigFormData[k] = v
    if (valid) {
      this._frigValidFormData[k] = v
    }
    else {
      delete this._frigValidFormData[k]
    }
    // clone the form data object to avoid the situation where subsequent form
    // updates unexpectedly mutate the data object
    let formData = Object.assign({}, this._frigFormData)
    let reactLinkData = Object.assign({}, this._data(), formData)
    // Notify the event listeners
    this.props.onChange(formData)
    if (valid) this.props.onValidChange(formData)
    // Update the ReactLink with the merged combination of form data and the
    // initial values passed in to the form (allowing non-form data to be
    // persisted)
    this._dataLink().requestChange(reactLinkData)
  }

  _onSubmit(e) {
    e.preventDefault()
    if (!this.validate()) return
    this.props.onSubmit(e, this._frigFormData)
  }

  /*
   * =========================================================================
   * DSL
   * =========================================================================
   */

  /*
   * Component classes for children
   */
  _componentClasses() {
    if (this._componentClassesCache != null) return this._componentClassesCache
    return this._componentClassesCache = {
      errors: this._errorsComponentClass(),
      input: this._inputComponentClass(),
      submit: this._submitComponentClass(),
    }
  }

  _errorsComponentClass() {
    // Returning a input component modified with this form's defaults and
    // overrides
    let mapping = this._typeMapping().errors
    let component = this._getThemedInputComponent({}, mapping.component)
    return propsClosure(component, {
      defaults: {key: "errors"},
      overrides: this._errorsOverrides.bind(this),
    })
  }

  _errorsOverrides() {
    return {
      type: "errors",
      errors: this.props.errors,
    }
  }

  // Create a submit button
  // value: [STRING] The label text for the submit button
  // props: [OBJECT] properties to send to the React Component (see input props)
  _submitComponentClass() {
    // Returning a input component modified with this form's defaults and
    // overrides
    let mapping = this._typeMapping().submit
    let component = this._getThemedInputComponent({}, mapping.component)
    return propsClosure(component, {
      defaults: {key: "submit"},
      overrides: this._submitOverrides.bind(this),
    })
  }

  _submitOverrides(submitProps) {
    return {
      type: "submit",
      inputHtml: {
        defaultValue: submitProps.value,
      },
    }
  }

  // Creates a form field
  // key: [STRING] the name of the field in the data
  // props:
  //   required: (default: true)
  //   inputHtml: specify html attributes for the input React DOM element
  //   labelHtml: specify html attributes for the label React DOM element
  //   placeholder:
  //     true: (default) adds a placeholder with a name based on a humanization
  //           of the key
  //     false: disables the placeholder
  //     [STRING]: sets the placeholder to the given string
  //  label:
  //     true: (default) adds a label with a name based on a humanization
  //           of the key
  //     false: disables the label
  //     [STRING]: sets the label to the given string
  _inputComponentClass() {
    // Returning a input component modified with this form's defaults and
    // overrides
    return propsClosure(frigInput, {
      defaults: this._inputDefaults.bind(this),
      overrides: this._inputOverrides.bind(this),
    })
  }

  _inputDefaults({name}) {
    return {
      name,
      key: `${name}Input`,
      valueLink: {
        value: this._data()[name],
        requestChange: this._onFriggingChildChange.bind(this, [name]),
      },
      onFriggingChildInit: this._onFriggingChildInit.bind(this, [name]),
    }
  }

  _inputOverrides(inputProps) {
    let name = inputProps.name
    // Guessing the type and using it to lookup the template
    let type = this._frigGuessInputType(inputProps)
    // looking up the template name with the type mappings and the type
    let mapping = this._typeMapping()[type]
    let component = this._getThemedInputComponent(inputProps, mapping.component)
    let inputHtmlDefaults = {type: mapping.htmlInputType}
    // Generating the overrides object
    return Object.assign({}, inputProps, {
      type,
      component,
      ref: `${name}Input`,
      inputHtml: Object.assign(inputHtmlDefaults, inputProps.inputHtml),
    })
  }

  _frigGuessInputType(inputProps) {
    let jsType = typeof inputProps.valueLink.value
    if (inputProps.type != null) {
      return inputProps.type
    }
    else if (inputProps.multiple || Array.isArray(inputProps.initialValue)) {
      return "multiselect"
    }
    else if (inputProps.options != null) {
      return "select"
    }
    else if (jsType === "boolean") {
      return "boolean"
    }
    else if (jsType === "number") {
      return "float"
    }
    else if (inputProps.name.match(/[pP]assword^/)) {
      return "password"
    }
    else {
      return "string"
    }
  }

  _getThemedInputComponent(props, componentName) {
    if (componentName == null) {
      throw `${props.name}: No type mapping found for type ${props.type}`
    }
    let component = this.props.theme.component(componentName)
    if (component == null) {
      throw `${props.name}: No ${componentName} component found in theme`
    }
    return component
  }

}
<|endoftext|>


<commit_before>module.exports = function(opts) {
  var pickOptions = {
    mimetype: 'image/*',
    multiple: false,
    services: ['COMPUTER', 'FACEBOOK', 'WEBCAM', 'GOOGLE_DRIVE', 'DROPBOX', 'INSTAGRAM', 'FLICKR', 'URL'],
    folders: false
  };

  var storeOptions = {
    location: 'S3',
    container: hyloEnv.s3.bucket,
    path: 'orig/',
    access: 'public'
  };

  var convert = function(blobs) {
    var blob = blobs[0],
      convertStoreOptions = $.extend(storeOptions, {path: opts.path + blob.key.substring(5)});

    filepicker.convert(blob, opts.convert, convertStoreOptions, function(newBlob) {
      opts.success(hyloEnv.s3.cloudfrontHost + '/' + newBlob.key);
    }, opts.failure)
  };

  filepicker.pickAndStore(pickOptions, storeOptions, convert, opts.failure);
};
<commit_msg>fix two bugs with filepicker uploads<commit_after>var path = require('path');

module.exports = function(opts) {
  var pickOptions = {
    mimetype: 'image/*',
    multiple: false,
    services: ['COMPUTER', 'FACEBOOK', 'WEBCAM', 'GOOGLE_DRIVE', 'DROPBOX', 'INSTAGRAM', 'FLICKR', 'URL'],
    folders: false
  };

  var storeOptions = {
    location: 'S3',
    container: hyloEnv.s3.bucket,
    path: 'orig/',
    access: 'public'
  };

  var convert = function(blobs) {
    var blob = blobs[0],
      // trim the old path "orig/" and replace spaces
      filename = blob.key.substring(5).replace(/ /g, '_'),
      convertStoreOptions = $.extend(storeOptions, {
        path: path.join(opts.path, filename)
      });

    filepicker.convert(blob, opts.convert, convertStoreOptions, function(newBlob) {
      opts.success(hyloEnv.s3.cloudfrontHost + '/' + newBlob.key);
    }, opts.failure)
  };

  filepicker.pickAndStore(pickOptions, storeOptions, convert, opts.failure);
};
<|endoftext|>


<commit_before>import sampleCombine from 'xstream/extra/sampleCombine'
import isolate from '@cycle/isolate'
import { i, p, div, br, label, input, code, table, tr, td, b, h2, button, textarea, a, ul, li, span, th, thead, tbody, h3 } from '@cycle/dom';
import { clone, equals, merge, isEmpty } from 'ramda';
import xs from 'xstream';
import { logThis, log } from '../utils/logger'
import { ENTER_KEYCODE } from '../utils/keycodes.js'
import dropRepeats from 'xstream/extra/dropRepeats'
import delay from 'xstream/extra/delay'
import { loggerFactory } from '../utils/logger'
import { stringify } from 'querystring';
import { GeneAnnotationQuery } from './GeneAnnotationQuery'
import { absGene } from '../utils/utils'
import { busyUiReducer, dirtyWrapperStream } from "../utils/ui"

const emptyData = {
    body: {
        result: {
            data: []
        }
    }
}

const signatureLens = {
    get: state => ({ core: state.form.signature, settings: state.settings,
        ui: (state.ui??{}).signature ?? {dirty: false}, // Get state.ui.signature in a safe way or else get a default
     }),
    set: (state, childState) => ({ ...state, form: { ...state.form, signature: childState.core } })
};

function model(newInput$, request$, data$) {

        // Initialization
        const defaultReducer$ = xs.of(prevState => ({ ...prevState, core: { input: '' } }))
        // Add input to state
        const inputReducer$ = newInput$.map(i => prevState => ({ ...prevState, core: { ...prevState.core, input: i } }))
        // Add request body to state
        const requestReducer$ = request$.map(req => prevState => ({ ...prevState, core: { ...prevState.core, request: req } }))
        // Add data from API to state, update output key when relevant
        const dataReducer$ = data$.map(newData => prevState => ({ ...prevState, core: { ...prevState.core, data: newData, output: newData.join(" ") } }))
        // Logic and reducer stream that monitors if this component is busy
        const busyReducer$ = busyUiReducer(newInput$, data$)

    return xs.merge(
        defaultReducer$,
        inputReducer$,
        dataReducer$,
        requestReducer$,
        busyReducer$,
    )
}

function view(state$, request$, response$, geneAnnotationQuery) {

    const validSignature$ = response$
        .map(r => r.body.result.join(" "))
        .filter(s => s != '')

    const invalidSignature$ = response$
        .map(r => r.body.result.join(" "))
        .filter(s => s == '')

    const signature$ = xs.merge(validSignature$, invalidSignature$).remember()

    const geneStyle = {
        style: {
            'border-style': 'solid',
            'border-width': '1px',
            'margin': '2px 2px 2px 2px'
        }
    }

    const showGene = (thisGene) =>
    div('#' + absGene(thisGene) + '.col.orange.lighten-4.genePopup', geneStyle, [
        thisGene
    ])

    const validVdom$ = xs.combine(validSignature$, geneAnnotationQuery.DOM)
        .map(([s, annotation]) => div('.card .orange .lighten-3', [
            div('.card-content .orange-text .text-darken-4', [
                span('.card-title', 'Signature:'),
                div('.row', { style: { fontSize: "16px", fontStyle: 'bold' } },
                    s.split(" ").map(gene => showGene(gene))),
                annotation
            ])
        ]))
        .startWith(div('.card .orange .lighten-3', []))

    const invalidVdom$ = invalidSignature$
        .map(_ => div('.card .orange .lighten-3', [
            div('.card-content .red-text .text-darken-1', [
                div('.row', { style: { fontSize: "16px", fontStyle: 'bold' } }, [
                    p('.center', { style: { fontSize: "26px" } }, "The resulting signature is empty, please check the sample selection!")
                ])
            ])
        ]))
        .startWith(div('.card .orange .lighten-3', []))

    const loadingVdom$ = request$.compose(sampleCombine(state$))
        .mapTo(
        div('.card .orange .lighten-3', [
            div('.card-content .orange-text .text-darken-4', [
            span('.card-title', 'Signature:'),
            div('.progress.orange.lighten-3.yellow-text', { style: { margin: '2px 0px 2px 0px'} }, [
                div('.indeterminate', {style : { "background-color" : 'orange' }})
            ])
            ])
        ]))
        .startWith(div('.card .orange .lighten-3', []))
        .remember()

    // Wrap component vdom with an extra div that handles being dirty
    const vdom$ = dirtyWrapperStream( state$, xs.merge(loadingVdom$, invalidVdom$, validVdom$) )

    return {
        vdom$: vdom$,
        signature$: signature$
    }
}

//function intent() {}

/**
 * Generate a signature from a list of samples.
 *
 * Input: List of samples (array)
 * Output: Signature (can be empty!)
 *
 * Genes can be annotated (if Brutus is running). But the app is robust against Brutus not being online.
 *
 * TODO: A lot of cleanup and rework is still required:
 *
 * - Configure Brutus endpoint
 * - Isolate gene as a component
 */
function SignatureGenerator(sources) {

    // Plug-in annotations for genes
    const geneAnnotationQuery = GeneAnnotationQuery(sources)

    const logger = loggerFactory('signatureGenerator', sources.onion.state$, 'settings.form.debug')

    const state$ = sources.onion.state$

    const input$ = sources.input

    const newInput$ = xs.combine(
        input$,
        state$
    )
        .map(([newinput, state]) => ({ ...state, core: { ...state.core, input: newinput } }))
        .compose(dropRepeats((x, y) => equals(x.core.input, y.core.input)))

    const triggerRequest$ = newInput$

    const request$ = triggerRequest$
        .map(state => {
            return {
                url: state.settings.api.url + '&classPath=com.dataintuitive.luciusapi.generateSignature',
                method: 'POST',
                send: {
                    version: 'v2',
                    samples: state.core.input.join(" "),
                    pvalue: state.settings.common.pvalue
                },
                'category': 'generateSignature'
            }
        })

    const response$ = sources.HTTP
        .select('generateSignature')
        .map((response$) =>
            response$.replaceError(() => xs.of(emptyData))
        )
        .flatten()

    const data$ = response$
        .map(r => r.body.result)

    const reducers$ = model(newInput$, request$, data$)

    const views = view(state$, request$, response$, geneAnnotationQuery)

    return {
        log: xs.merge(
            logger(state$, 'state$'),
            geneAnnotationQuery.log
        ),
        DOM: views.vdom$,
        output: views.signature$,
        HTTP: xs.merge(
            request$,
            geneAnnotationQuery.HTTP
        ),
        onion: reducers$,
        modal: geneAnnotationQuery.modal
    }

}

export { SignatureGenerator, signatureLens }
<commit_msg>Display a message how the signature was created or why no signature was made (#96)<commit_after>import sampleCombine from 'xstream/extra/sampleCombine'
import isolate from '@cycle/isolate'
import { i, p, div, br, label, input, code, table, tr, td, b, h2, button, textarea, a, ul, li, span, th, thead, tbody, h3 } from '@cycle/dom';
import { clone, equals, merge, isEmpty } from 'ramda';
import xs from 'xstream';
import { logThis, log } from '../utils/logger'
import { ENTER_KEYCODE } from '../utils/keycodes.js'
import dropRepeats from 'xstream/extra/dropRepeats'
import delay from 'xstream/extra/delay'
import { loggerFactory } from '../utils/logger'
import { stringify } from 'querystring';
import { GeneAnnotationQuery } from './GeneAnnotationQuery'
import { absGene } from '../utils/utils'
import { busyUiReducer, dirtyWrapperStream } from "../utils/ui"

const emptyData = {
    body: {
        result: {
            data: []
        }
    }
}

const signatureLens = {
    get: state => ({ core: state.form.signature, settings: state.settings,
        ui: (state.ui??{}).signature ?? {dirty: false}, // Get state.ui.signature in a safe way or else get a default
     }),
    set: (state, childState) => ({ ...state, form: { ...state.form, signature: childState.core } })
};

function model(newInput$, request$, data$) {

        // Initialization
        const defaultReducer$ = xs.of(prevState => ({ ...prevState, core: { input: '' } }))
        // Add input to state
        const inputReducer$ = newInput$.map(i => prevState => ({ ...prevState, core: { ...prevState.core, input: i.core.input } }))
        // Add request body to state
        const requestReducer$ = request$.map(req => prevState => ({ ...prevState, core: { ...prevState.core, request: req } }))
        // Add data from API to state, update output key when relevant
        const dataReducer$ = data$.map(newData => prevState => ({ ...prevState, core: { ...prevState.core, data: newData, output: newData.join(" ") } }))
        // Logic and reducer stream that monitors if this component is busy
        const busyReducer$ = busyUiReducer(newInput$, data$)

    return xs.merge(
        defaultReducer$,
        inputReducer$,
        dataReducer$,
        requestReducer$,
        busyReducer$,
    )
}

function view(state$, request$, response$, geneAnnotationQuery) {

    const validSignature$ = response$
        .map(r => r.body.result.join(" "))
        .filter(s => s != '')

    const invalidSignature$ = response$
        .map(r => r.body.result.join(" "))
        .filter(s => s == '')

    const amountOfInputs$ = state$.map((state) => (state.core?.input?.length)).compose(dropRepeats(equals)).remember()

    const signature$ = xs.merge(validSignature$, invalidSignature$).remember()

    const geneStyle = {
        style: {
            'border-style': 'solid',
            'border-width': '1px',
            'margin': '2px 2px 2px 2px'
        }
    }

    const showGene = (thisGene) =>
    div('#' + absGene(thisGene) + '.col.orange.lighten-4.genePopup', geneStyle, [
        thisGene
    ])

    const validVdom$ = xs.combine(validSignature$, geneAnnotationQuery.DOM, amountOfInputs$)
        .map(([s, annotation, amount]) => div('.card .orange .lighten-3', [
            div('.card-content .orange-text .text-darken-4', [
                span('.card-title', 'Signature:'),
                div('.row', { style: { fontSize: "16px", fontStyle: 'bold' } },
                    s.split(" ").map(gene => showGene(gene))),
                annotation,
                p( { style: { fontSize: "16px" } },
                    (() => {
                        const amountOfGenes = s.split(" ").length
                        if (amount == 1)
                            return amountOfGenes == 1 ?
                                "This is the 1 significant gene from the selected sample." :
                                "These are the " + amountOfGenes + " significant genes from the selected sample."
                        else if (amount > 1)
                            return amountOfGenes == 1 ?
                                "The intersection of the significant genes from the selected samples resulted in 1 gene." :
                                "The intersection of the significant genes from the selected samples resulted in " + amountOfGenes + " genes."
                        else
                            return "No samples were selected and yet a valid signature was generated. Please create a bug report!"
                    })()
                ),
            ])
        ]))
        .startWith(div('.card .orange .lighten-3', []))

    const invalidVdom$ = xs.combine(invalidSignature$, amountOfInputs$)
        .map(([_, amount]) => div('.card .orange .lighten-3', [
            div('.card-content .red-text .text-darken-1', [
                div('.row', { style: { fontSize: "16px", fontStyle: 'bold' } }, [
                    p('.center', { style: { fontSize: "26px" } },
                        (() => {
                            if (amount == 1)
                                return "The selected sample didn't return any significant genes from which to create a signature!"
                            else if (amount > 1)
                                return "The resulting signature is empty as there is no intersection of significant genes in the selected samples!"
                            else
                                return "No samples were selected from which to create a signature!"
                        })()
                    )
                ])
            ])
        ]))
        .startWith(div('.card .orange .lighten-3', []))

    const loadingVdom$ = request$.compose(sampleCombine(state$))
        .mapTo(
            div('.card .orange .lighten-3', [
                div('.card-content .orange-text .text-darken-4', [
                span('.card-title', 'Signature:'),
                div('.progress.orange.lighten-3.yellow-text', { style: { margin: '2px 0px 2px 0px'} }, [
                    div('.indeterminate', {style : { "background-color" : 'orange' }})
                ])
                ])
            ]))
        .startWith(div('.card .orange .lighten-3', []))
        .remember()

    // Wrap component vdom with an extra div that handles being dirty
    const vdom$ = dirtyWrapperStream( state$, xs.merge(loadingVdom$, invalidVdom$, validVdom$) )

    return {
        vdom$: vdom$,
        signature$: signature$
    }
}

//function intent() {}

/**
 * Generate a signature from a list of samples.
 *
 * Input: List of samples (array)
 * Output: Signature (can be empty!)
 *
 * Genes can be annotated (if Brutus is running). But the app is robust against Brutus not being online.
 *
 * TODO: A lot of cleanup and rework is still required:
 *
 * - Configure Brutus endpoint
 * - Isolate gene as a component
 */
function SignatureGenerator(sources) {

    // Plug-in annotations for genes
    const geneAnnotationQuery = GeneAnnotationQuery(sources)

    const logger = loggerFactory('signatureGenerator', sources.onion.state$, 'settings.form.debug')

    const state$ = sources.onion.state$

    const input$ = sources.input

    const newInput$ = xs.combine(
        input$,
        state$
    )
        .map(([newinput, state]) => ({ ...state, core: { ...state.core, input: newinput } }))
        .compose(dropRepeats((x, y) => equals(x.core.input, y.core.input)))

    const triggerRequest$ = newInput$

    const request$ = triggerRequest$
        .map(state => {
            return {
                url: state.settings.api.url + '&classPath=com.dataintuitive.luciusapi.generateSignature',
                method: 'POST',
                send: {
                    version: 'v2',
                    samples: state.core.input.join(" "),
                    pvalue: state.settings.common.pvalue
                },
                'category': 'generateSignature'
            }
        })

    const response$ = sources.HTTP
        .select('generateSignature')
        .map((response$) =>
            response$.replaceError(() => xs.of(emptyData))
        )
        .flatten()

    const data$ = response$
        .map(r => r.body.result)

    const reducers$ = model(newInput$, request$, data$)

    const views = view(state$, request$, response$, geneAnnotationQuery)

    return {
        log: xs.merge(
            logger(state$, 'state$'),
            geneAnnotationQuery.log
        ),
        DOM: views.vdom$,
        output: views.signature$,
        HTTP: xs.merge(
            request$,
            geneAnnotationQuery.HTTP
        ),
        onion: reducers$,
        modal: geneAnnotationQuery.modal
    }

}

export { SignatureGenerator, signatureLens }
<|endoftext|>


<commit_before>'use strict';

angular.module('copayApp.controllers').controller('preferencesGlobalController',
  function($scope, $rootScope, $log, configService, uxLanguage, platformInfo, pushNotificationsService, profileService, feeService) {

    var isCordova = platformInfo.isCordova;

    $scope.init = function() {
      var config = configService.getSync();
      $scope.unitName = config.wallet.settings.unitName;
      $scope.currentLanguageName = uxLanguage.getCurrentLanguageName();
      $scope.selectedAlternative = {
        name: config.wallet.settings.alternativeName,
        isoCode: config.wallet.settings.alternativeIsoCode
      };
      $scope.feeOpts = feeService.feeOpts;
      $scope.currentFeeLevel = feeService.getCurrentFeeLevel();
      $scope.usePushNotifications = isCordova && !platformInfo.isWP;
      $scope.PNEnabledByUser = true;
      $scope.isIOSApp = platformInfo.isIOS && isCordova;
      if ($scope.isIOSApp) {
        cordova.plugins.diagnostic.isRemoteNotificationsEnabled(function(isEnabled) {
          $scope.PNEnabledByUser = isEnabled;
        });
      }
      $scope.spendUnconfirmed = config.wallet.spendUnconfirmed;
      $scope.glideraEnabled = config.glidera.enabled;
      $scope.coinbaseEnabled = config.coinbase.enabled;
      $scope.pushNotifications = config.pushNotifications.enabled;
    };

    $scope.openSettings = function() {
      cordova.plugins.diagnostic.switchToSettings(function() {
        $log.debug('switched to settings');
      }, function(err) {
        $log.debug(err);
      });
    }

    $scope.spendUnconfirmedChange = function() {
      var opts = {
        wallet: {
          spendUnconfirmed: $scope.spendUnconfirmed
        }
      };
      configService.set(opts, function(err) {
        $rootScope.$emit('Local/SpendUnconfirmedUpdated', $scope.spendUnconfirmed);
        if (err) $log.debug(err);
      });
    };

    $scope.pushNotificationsChange = function() {
      var opts = {
        pushNotifications: {
          enabled: $scope.pushNotifications
        }
      };
      configService.set(opts, function(err) {
        if (opts.pushNotifications.enabled)
          pushNotificationsService.enableNotifications(profileService.walletClients);
        else
          pushNotificationsService.disableNotifications(profileService.walletClients);
        if (err) $log.debug(err);
      });
    };

    $scope.glideraChange = function() {
      var opts = {
        glidera: {
          enabled: $scope.glideraEnabled
        }
      };
      configService.set(opts, function(err) {
        $rootScope.$emit('Local/GlideraUpdated');
        if (err) $log.debug(err);
      });
    };

    $scope.coinbaseChange = function() {
      var opts = {
        coinbase: {
          enabled: $scope.coinbaseEnabled
        }
      };
      configService.set(opts, function(err) {
        $rootScope.$emit('Local/CoinbaseUpdated');
        if (err) $log.debug(err);
      });
    };
  });
<commit_msg>Refresh UI after check if notification is enabled<commit_after>'use strict';

angular.module('copayApp.controllers').controller('preferencesGlobalController',
  function($scope, $rootScope, $log, configService, uxLanguage, platformInfo, pushNotificationsService, profileService, feeService) {

    var isCordova = platformInfo.isCordova;

    $scope.init = function() {
      var config = configService.getSync();
      $scope.unitName = config.wallet.settings.unitName;
      $scope.currentLanguageName = uxLanguage.getCurrentLanguageName();
      $scope.selectedAlternative = {
        name: config.wallet.settings.alternativeName,
        isoCode: config.wallet.settings.alternativeIsoCode
      };
      $scope.feeOpts = feeService.feeOpts;
      $scope.currentFeeLevel = feeService.getCurrentFeeLevel();
      $scope.usePushNotifications = isCordova && !platformInfo.isWP;
      $scope.PNEnabledByUser = true;
      $scope.isIOSApp = platformInfo.isIOS && isCordova;
      if ($scope.isIOSApp) {
        cordova.plugins.diagnostic.isRemoteNotificationsEnabled(function(isEnabled) {
          $scope.PNEnabledByUser = isEnabled;
          $scope.$digest();
        });
      }
      $scope.spendUnconfirmed = config.wallet.spendUnconfirmed;
      $scope.glideraEnabled = config.glidera.enabled;
      $scope.coinbaseEnabled = config.coinbase.enabled;
      $scope.pushNotifications = config.pushNotifications.enabled;
    };

    $scope.openSettings = function() {
      cordova.plugins.diagnostic.switchToSettings(function() {
        $log.debug('switched to settings');
      }, function(err) {
        $log.debug(err);
      });
    }

    $scope.spendUnconfirmedChange = function() {
      var opts = {
        wallet: {
          spendUnconfirmed: $scope.spendUnconfirmed
        }
      };
      configService.set(opts, function(err) {
        $rootScope.$emit('Local/SpendUnconfirmedUpdated', $scope.spendUnconfirmed);
        if (err) $log.debug(err);
      });
    };

    $scope.pushNotificationsChange = function() {
      var opts = {
        pushNotifications: {
          enabled: $scope.pushNotifications
        }
      };
      configService.set(opts, function(err) {
        if (opts.pushNotifications.enabled)
          pushNotificationsService.enableNotifications(profileService.walletClients);
        else
          pushNotificationsService.disableNotifications(profileService.walletClients);
        if (err) $log.debug(err);
      });
    };

    $scope.glideraChange = function() {
      var opts = {
        glidera: {
          enabled: $scope.glideraEnabled
        }
      };
      configService.set(opts, function(err) {
        $rootScope.$emit('Local/GlideraUpdated');
        if (err) $log.debug(err);
      });
    };

    $scope.coinbaseChange = function() {
      var opts = {
        coinbase: {
          enabled: $scope.coinbaseEnabled
        }
      };
      configService.set(opts, function(err) {
        $rootScope.$emit('Local/CoinbaseUpdated');
        if (err) $log.debug(err);
      });
    };
  });
<|endoftext|>


<commit_before>import { config } from '../config';
import { listener } from '../util/passive-event-listener';

export { SlidehubMouseInteraction };

const scrolling = {
  vertical: {
    delta: 'deltaY'
  },
  horizontal: {
    delta: 'deltaX'
  }
};

/**
 * Mouse Interaction.
 */
class SlidehubMouseInteraction {
  constructor(slidehub) {
    this.slidehub = slidehub;
  }

  start() {
    this.enableModifier();
    this.initExistingDocuments();
  }

  initExistingDocuments() {
    const documents = Array.from(this.slidehub.node.querySelectorAll(config.selector.doc));
    documents.forEach(docNode => this.initMouseInteraction(docNode));
  }

  /**
   * @param {Element} docNode
   */
  initMouseInteraction(docNode) {
    docNode.addEventListener('wheel', this.handleWheelInteraction.bind(this), listener.active);
    docNode.addEventListener('click', this.handleClickSelect.bind(this), listener.passive);
    docNode.addEventListener('mousemove', this.handleMoveHover.bind(this), listener.passive);
  }

  /**
   * Handles wheel navigation.
   *
   * @param {WheelEvent} event
   */
  handleWheelInteraction(event) {
    // Don’t handle scrolling on elements that are not inside a document
    const doc = this.slidehub.documents.get(event.currentTarget.id);

    if (!doc.loaded) {
      return;
    }

    const ratio = Math.abs(event.deltaX / event.deltaY);
    const scrollingDirection = ratio < 1 ? scrolling.vertical : scrolling.horizontal;

    // When scrolling vertically, only trigger navigation when modifier is pressed
    if (scrollingDirection === scrolling.vertical && event.shiftKey) {
      // Prevent vertical scrolling
      event.preventDefault();

      const delta = event[scrollingDirection.delta];

      doc.navigateItem.by(Math.sign(delta));
    }
  }

  /**
   * Selects documents/items on click.
   *
   * @param {MouseEvent} event
   */
  handleClickSelect(event) {
    const doc = this.slidehub.documents.get(event.currentTarget.id);

    if (!doc.loaded) {
      return;
    }

    this.slidehub.selectDocument(doc);

    if (config.keepSelectedPageInFirstColumn) {
      return;
    }

    const item = event.target.closest(config.selector.item);
    if (item) {
      doc.selectItem(item);
    }
  }

  /**
   * Highlights documents/items on hover.
   *
   * @param {MouseEvent} event
   */
  handleMoveHover(event) {
    const doc = this.slidehub.documents.get(event.currentTarget.id);

    if (!doc.loaded) {
      return;
    }

    this.slidehub.hoverDocument(doc);

    const item = event.target.closest(config.selector.item);
    if (item) {
      doc.hoverItem(item);
    }
  }

  /**
   * Wrapper for enabling all event listeners related to modifier handling.
   */
  enableModifier() {
    document.addEventListener('keydown', this.onModifierDown.bind(this), listener.passive);
    document.addEventListener('keyup', this.onModifierUp.bind(this), listener.passive);
    window.addEventListener('blur', this.onModifierBlur.bind(this), listener.passive);
  }

  /**
   * Displays a special cursor when the modifier is pressed.
   *
   * @param {KeyboardEvent} event
   */
  onModifierDown(event) {
    const doc = this.slidehub.hoveredDocument;
    if (doc && event.keyCode === 16) {
      doc.node.style.setProperty('cursor', 'ew-resize');
    }
  }

  /**
   * Removes the special cursor when the modifier is no longer pressed.
   *
   * @param {KeyboardEvent} event
   */
  onModifierUp(event) {
    const doc = this.slidehub.hoveredDocument;
    if (doc && event.keyCode === 16) {
      doc.node.style.setProperty('cursor', 'auto');
    }
  }

  /**
   * Removes the special cursor when the user somehow leaves the page.
   */
  onModifierBlur() {
    const doc = this.slidehub.hoveredDocument;
    if (doc) {
      doc.node.style.setProperty('cursor', 'auto');
    }
  }
};
<commit_msg>Update hover targets on scroll, too.<commit_after>import { config } from '../config';
import { listener } from '../util/passive-event-listener';
import { debounce } from '../util/debounce';

export { SlidehubMouseInteraction };

const scrolling = {
  vertical: {
    delta: 'deltaY'
  },
  horizontal: {
    delta: 'deltaX'
  }
};

/**
 * Mouse Interaction.
 */
class SlidehubMouseInteraction {
  constructor(slidehub) {
    this.mouseX = window.innerWidth / 2;
    this.mouseY = window.innerHeight / 2;
    this.slidehub = slidehub;
  }

  start() {
    this.initStoreMousePosition();
    this.initHoverOnScroll();
    this.initExistingDocuments();
    this.initModifiers();
  }

  initStoreMousePosition() {
    document.addEventListener('mousemove', this.storeMousePosition.bind(this), listener.passive);
  }

  /**
   * The firing rate of the listener needs to be slowed down because the
   * `scroll` event is rapidly firing. Not doing so will decrease the
   * performance significantly.
   *
   * It’s sufficient to debounce the listener. This means the listener will
   * be triggered **once** after the event has stopped firing.
   */
  initHoverOnScroll() {
    document.addEventListener('scroll', debounce(this.handleScrollHover.bind(this), 25), listener.passive);
  }

  initExistingDocuments() {
    const documents = Array.from(this.slidehub.node.querySelectorAll(config.selector.doc));
    documents.forEach(docNode => this.initMouseInteraction(docNode));
  }

  /**
   * Wrapper for initializing all event listeners related to mouse interactions.
   *
   * @param {Element} docNode
   */
  initMouseInteraction(docNode) {
    docNode.addEventListener('wheel', this.handleWheelInteraction.bind(this), listener.active);
    docNode.addEventListener('click', this.handleClickSelect.bind(this), listener.passive);
    docNode.addEventListener('mousemove', this.handleMoveHover.bind(this), listener.passive);
  }

  /**
   * Stores the position of the mouse cursor.
   *
   * @param {MouseEvent} event
   */
  storeMousePosition(event) {
    this.mouseX = event.clientX;
    this.mouseY = event.clientY;
  }

  /**
   * Finds and hovers the document under the current mouse cursor.
   *
   * @param {UIEvent} event
   */
  handleScrollHover(event) {
    const targetElement = document.elementFromPoint(this.mouseX, this.mouseY);
    const docElement = targetElement.closest(config.selector.doc);

    if (!docElement) {
      return;
    }

    const doc = this.slidehub.documents.get(docElement.id);

    if (!doc.loaded) {
      return;
    }

    this.slidehub.hoverDocument(doc);

    const itemElement = targetElement.closest(config.selector.item);

    if (itemElement) {
      doc.hoverItem(itemElement);
    }
  }

  /**
   * Handles wheel navigation.
   *
   * @param {WheelEvent} event
   */
  handleWheelInteraction(event) {
    // Don’t handle scrolling on elements that are not inside a document
    const doc = this.slidehub.documents.get(event.currentTarget.id);

    if (!doc.loaded) {
      return;
    }

    const ratio = Math.abs(event.deltaX / event.deltaY);
    const scrollingDirection = ratio < 1 ? scrolling.vertical : scrolling.horizontal;

    // When scrolling vertically, only trigger navigation when modifier is pressed
    if (scrollingDirection === scrolling.vertical && event.shiftKey) {
      // Prevent vertical scrolling
      event.preventDefault();

      const delta = event[scrollingDirection.delta];

      doc.navigateItem.by(Math.sign(delta));
    }
  }

  /**
   * Selects documents/items on click.
   *
   * @param {MouseEvent} event
   */
  handleClickSelect(event) {
    const doc = this.slidehub.documents.get(event.currentTarget.id);

    if (!doc.loaded) {
      return;
    }

    this.slidehub.selectDocument(doc);

    if (config.keepSelectedPageInFirstColumn) {
      return;
    }

    const item = event.target.closest(config.selector.item);
    if (item) {
      doc.selectItem(item);
    }
  }

  /**
   * Highlights documents/items on hover.
   *
   * @param {MouseEvent} event
   */
  handleMoveHover(event) {
    const doc = this.slidehub.documents.get(event.currentTarget.id);

    if (!doc.loaded) {
      return;
    }

    this.slidehub.hoverDocument(doc);

    const item = event.target.closest(config.selector.item);
    if (item) {
      doc.hoverItem(item);
    }
  }

  /**
   * Wrapper for enabling all event listeners related to modifier handling.
   */
  initModifiers() {
    document.addEventListener('keydown', this.onModifierDown.bind(this), listener.passive);
    document.addEventListener('keyup', this.onModifierUp.bind(this), listener.passive);
    window.addEventListener('blur', this.onModifierBlur.bind(this), listener.passive);
  }

  /**
   * Displays a special cursor when the modifier is pressed.
   *
   * @param {KeyboardEvent} event
   */
  onModifierDown(event) {
    const doc = this.slidehub.hoveredDocument;
    if (doc && event.keyCode === 16) {
      doc.node.style.setProperty('cursor', 'ew-resize');
    }
  }

  /**
   * Removes the special cursor when the modifier is no longer pressed.
   *
   * @param {KeyboardEvent} event
   */
  onModifierUp(event) {
    const doc = this.slidehub.hoveredDocument;
    if (doc && event.keyCode === 16) {
      doc.node.style.setProperty('cursor', 'auto');
    }
  }

  /**
   * Removes the special cursor when the user somehow leaves the page.
   */
  onModifierBlur() {
    const doc = this.slidehub.hoveredDocument;
    if (doc) {
      doc.node.style.setProperty('cursor', 'auto');
    }
  }
};
<|endoftext|>


<commit_before>define([
	"dojo/io/script", // get
	"rishson/control/Response",	//constructor
	"rishson/control/Transport",	//mixin
	"rishson/util/ObjectValidator",	//validate
	"dojo/_base/declare", // declare
	"dojo/_base/lang", // mixin
	"dojo/_base/array", // indexOf
	"dojo/_base/xhr", // get, etc.
	"require" // context-sensitive require
], function (script, Response, Transport, ObjectValidator, declare, lang, arrayUtil, xhr, require) {

	/**
	 * @class
	 * @name rishson.control.MockTransport
	 * @description An implementation of rishson.control.Transport that mocks XHR calls to a server
	 * and returns canned data from a test directory instead
	 */
	return declare('rishson.control.MockTransport', Transport, {

		/**
		 * @field
		 * @name rishson.control.MockTransport.requestTimeout
		 * @type {number}
		 * @description the number of milliseconds that a rishson.control.Request can take before the call
		 * is aborted.
		 */
		requestTimeout: 5000, //defaults to 5 seconds

		/**
		 * @field
		 * @name rishson.control.MockTransport.delay
		 * @type {number}
		 * @description The number of milliseconds to delay a REST response for, useful in testing
		 * to fake a slow server call
		 */
		delay: 0,

		/**
		 * @field
		 * @name rishson.control.MockTransport.namespace
		 * @type {string}
		 * @description Namespace where the test .
		 */
		namespace: 'rishson/tests/data/',

		/**
		 * @constructor
		 * @param {object} params
		 */
		constructor: function (params) {
			lang.mixin(this, params);
		},

		/**
		 * @function
		 * @name rishson.control.MockTransport.setDelayAttr
		 * @param {number} delay The delay value in milliseconds
		 * @description Setter for this.delay
		 */
		setDelayAttr: function (delay) {
			this.delay = delay;
		},

		/**
		 * @function
		 * @name rishson.control.MockTransport.send
		 * @override Transport.send
		 * @param {rishson.control.Request} request to send to the server
		 * @description Issues the provided rishson.control.Request in an asynchronous manner
		 */
		send: function (request, appId) {
			var testFuncName = 'processRequest', //name of the function to call on the TestMethod module
				self = this, //maintain self-reference for inside require callback
				/*get the full namespace of the module to provide the response
				 The namespace is in the form test.data.[typeOfResponse].[request.url]
				 e.g. for a service request:
				 test.data.serviceResponses.someService.SomeMethod
				 for a rest service:
				 test.data.restResponses.someService.SomeEndpoint
				 */
				namespace = this.namespace,
				indexOfClassName,
				testMethodClass,
				methodParams,
				mockResponse,
				wrappedResponse;

			//if an app is specified in the send, then get the application specific url
			if (appId) {
				namespace = this.apps[appId];
			}

			if (request.declaredClass === 'rishson.control.ServiceRequest') {
				namespace += 'serviceResponses/' + request.toUrl();
			} else if (request.declaredClass === 'rishson.control.RestRequest') {
				namespace += 'restResponses/' + request.toUrl() + '/' + request.verb;
			} else {
				throw ('Unknown request type supplied: ' + request.declaredClass);
			}

			//capitalise the module name
			indexOfClassName = namespace.lastIndexOf('/') + 1;
			namespace = namespace.slice(0, indexOfClassName) +
				namespace.charAt(indexOfClassName).toUpperCase() + namespace.slice(indexOfClassName + 1);

			//get the TestModule
			require([namespace], lang.hitch(this, function (TestModule) {
				setTimeout(function () {
					testMethodClass = new TestModule(); //create an instance of the TestMethod class
					methodParams = self.createBasePostParams(request);
					mockResponse = testMethodClass[testFuncName](methodParams);	//call the test method

					wrappedResponse = new Response(mockResponse.payload, request.type === 'rest', mockResponse.ioArgs);
					if (arrayUtil.indexOf(wrappedResponse.mappedStatusCodes, mockResponse.ioArgs.xhr.status) === -1) {
						self.handleErrorFunc(request, wrappedResponse);
					} else {
						self.handleResponseFunc(request, wrappedResponse);
					}
				}, this.delay);
			}));
		}
	});
});<commit_msg>Added queryString check for REST requests<commit_after>define([
	"dojo/io/script", // get
	"rishson/control/Response",	//constructor
	"rishson/control/Transport",	//mixin
	"rishson/util/ObjectValidator",	//validate
	"dojo/_base/declare", // declare
	"dojo/_base/lang", // mixin
	"dojo/_base/array", // indexOf
	"dojo/_base/xhr", // get, etc.
	"require" // context-sensitive require
], function (script, Response, Transport, ObjectValidator, declare, lang, arrayUtil, xhr, require) {

	/**
	 * @class
	 * @name rishson.control.MockTransport
	 * @description An implementation of rishson.control.Transport that mocks XHR calls to a server
	 * and returns canned data from a test directory instead
	 */
	return declare('rishson.control.MockTransport', Transport, {

		/**
		 * @field
		 * @name rishson.control.MockTransport.requestTimeout
		 * @type {number}
		 * @description the number of milliseconds that a rishson.control.Request can take before the call
		 * is aborted.
		 */
		requestTimeout: 5000, //defaults to 5 seconds

		/**
		 * @field
		 * @name rishson.control.MockTransport.delay
		 * @type {number}
		 * @description The number of milliseconds to delay a REST response for, useful in testing
		 * to fake a slow server call
		 */
		delay: 0,

		/**
		 * @field
		 * @name rishson.control.MockTransport.namespace
		 * @type {string}
		 * @description Namespace where the test .
		 */
		namespace: 'rishson/tests/data/',

		/**
		 * @constructor
		 * @param {object} params
		 */
		constructor: function (params) {
			lang.mixin(this, params);
		},

		/**
		 * @function
		 * @name rishson.control.MockTransport.setDelayAttr
		 * @param {number} delay The delay value in milliseconds
		 * @description Setter for this.delay
		 */
		setDelayAttr: function (delay) {
			this.delay = delay;
		},

		/**
		 * @function
		 * @name rishson.control.MockTransport.send
		 * @override Transport.send
		 * @param {rishson.control.Request} request to send to the server
		 * @description Issues the provided rishson.control.Request in an asynchronous manner
		 */
		send: function (request, appId) {
			var testFuncName = 'processRequest', //name of the function to call on the TestMethod module
				self = this, //maintain self-reference for inside require callback
				/*get the full namespace of the module to provide the response
				 The namespace is in the form test.data.[typeOfResponse].[request.url]
				 e.g. for a service request:
				 test.data.serviceResponses.someService.SomeMethod
				 for a rest service:
				 test.data.restResponses.someService.SomeEndpoint
				 */
				namespace = this.namespace,
				indexOfClassName,
				testMethodClass,
				methodParams,
				mockResponse,
				wrappedResponse,
				queryString;

			//if an app is specified in the send, then get the application specific url
			if (appId) {
				namespace = this.apps[appId];
			}

			if (request.declaredClass === 'rishson.control.ServiceRequest') {
				namespace += 'serviceResponses/' + request.toUrl();
			} else if (request.declaredClass === 'rishson.control.RestRequest') {
				namespace += 'restResponses/' + request.toUrl() + '/' + request.verb;
				if (namespace.indexOf("?") !== -1) {
					namespace = namespace.split("?");
					queryString = namespace[1].split("/");
					namespace = namespace[0] + "/" + queryString[1] + "?" + queryString[0];
				}
			} else {
				throw ('Unknown request type supplied: ' + request.declaredClass);
			}

			//capitalise the module name
			indexOfClassName = namespace.lastIndexOf('/') + 1;
			namespace = namespace.slice(0, indexOfClassName) +
				namespace.charAt(indexOfClassName).toUpperCase() + namespace.slice(indexOfClassName + 1);

			//get the TestModule
			require([namespace], lang.hitch(this, function (TestModule) {
				setTimeout(function () {
					testMethodClass = new TestModule(); //create an instance of the TestMethod class
					methodParams = self.createBasePostParams(request);
					mockResponse = testMethodClass[testFuncName](methodParams);	//call the test method

					wrappedResponse = new Response(mockResponse.payload, request.type === 'rest', mockResponse.ioArgs);
					if (arrayUtil.indexOf(wrappedResponse.mappedStatusCodes, mockResponse.ioArgs.xhr.status) === -1) {
						self.handleErrorFunc(request, wrappedResponse);
					} else {
						self.handleResponseFunc(request, wrappedResponse);
					}
				}, this.delay);
			}));
		}
	});
});<|endoftext|>


<commit_before>const _background = '#FFF';
const _opacity = .4;
const _inputBackground = '#666';

var Dimensions = require('Dimensions')
var {width, height} = Dimensions.get('window');

module.exports = require('react-native').StyleSheet.create({
  center: {
    justifyContent: 'center'
  },
  bImage: {
    flex: 1,
    //resizeMode: 'stretch',
    //borderWidth: 2,
    margin: 0,
    width: null,
    height: null,
  },
  blur: {
    flex: 1,
    backgroundColor: _background,
    opacity: _opacity,
  },
  container: {
    flex: 1,
    // justifyContent: 'center',
    // alignItems: 'center',
    // flexDirection: 'column',
    //  borderWidth: 3,
    // margin: 0,
    // padding: 0,
    backgroundColor: '#9cff92',
  },
  innerContainer: {
    flex: 1,
    // borderWidth: 1,
    // borderColor: 'red',
    // make things inside inner container centered
    justifyContent: 'center',
    marginTop: 64, // need this because of the navigation bar
  },
  topMargin: {
      height:20,
  },
  input: {
    height: 40,
    marginBottom:0,
    borderRadius:5,
    borderColor: 'gray',
    borderWidth: 1,
    backgroundColor: _inputBackground,
    color: '#FFF',
    alignItems: 'center',
  },
  button: {
    borderRadius: 4,
    borderColor:'#bbb',
    backgroundColor: '#333',
    borderWidth: 1,
    padding: 5,
    flex: 1,
  },
  addButton: {
    height: 30,
    backgroundColor: '#114800',
    alignSelf: 'center',
    //marginTop: 10,
    padding: 12,
    borderWidth: 1,
    borderRadius: 9,
    justifyContent: 'center',
    marginLeft: 24,
    marginRight: 24,
    marginBottom: 12,
    marginTop: 12,
  },
  addButtonText: {
    color: '#77ed6b',
    fontSize: 14,
    alignSelf: 'center',
  },
  mainContent: {
    flex: 0.8,
    //borderWidth: 1,
    // make things inside inner container centered
    justifyContent: 'center',
  },
  pageHeading: {
    alignSelf: 'center',
    fontSize: 18,
    marginBottom: 48,
    backgroundColor: 'transparent',
    color: '#77ed6b',
    fontWeight: 'bold',
  },
  inputLabel: {
    fontWeight: 'bold',
    fontSize: 15,
    alignSelf: 'flex-start',
  },
  inputText: {
    color: 'rgb(239,239,239)',
    height: 40,
    //marginTop: 10,
    marginBottom: 12,
    marginLeft: 24,
    marginRight: 24,
    padding: 12,
    fontSize: 15,
    borderWidth: 1,
    borderRadius: 9,
    borderColor: '#114800',
    backgroundColor: 'rgba(51, 31, 14, 0.40)'//'#ff9c4a',
  },
  textArea: {
    height: 150,
  },
  submitButton: {
    height: 35,
    backgroundColor: '#114800',
    alignSelf: 'stretch',
    //marginTop: 10,
    padding: 12,
    borderWidth: 1,
    borderRadius: 9,
    justifyContent: 'center',
    marginLeft: 24,
    marginRight: 24,
    marginTop: 40,
    marginBottom: 12,
  },
  signupButton: {
    height: 35,
    backgroundColor: '#479030',
    alignSelf: 'stretch',
    //marginTop: 10,
    justifyContent: 'center',
  },
  buttonText: {
    color: '#77ed6b',
    fontSize: 18,
    alignSelf: 'center',
  },
  pageFooter: {
    //borderWidth: 2,
    flex: 0.2,
  },
  profileEntry: {
    //alignItems: 'center',
    //borderTopWidth: 1,
    borderBottomWidth: 1,
    // paddingLeft: 6,
    // paddingRight: 6,
    padding: 12,
    marginLeft: 12,
    marginRight: 12,
  },
  entryLabel: {
    fontSize: 18,
    fontWeight: 'bold',
  },
  entryText: {
    fontSize: 16,
    fontWeight: 'normal',
  },
  profileAvatar: {
    width: 128,
    height: 128,
    borderRadius: 128/2,
    borderWidth: 1,
    alignSelf: 'center',
    marginBottom: 12,
    marginTop: 0,
  },
  listEntry: {
    flexDirection: 'row',
    borderBottomWidth: 1,
    borderColor: 'rgb(140, 140, 140)',
    padding: 12,
    marginLeft: 12,
    marginRight: 12,
  },
  listEntryContent: {
    flexDirection: 'column',
    justifyContent: 'space-around',
  },
  entryAvatar: {
    width: 50,
    height: 50,
    marginRight: 10,
    borderRadius: 50/2,
  },
  // pickerArea: {
  //   // flex: 1,
  //   marginBottom: 12,
  //   marginLeft: 24,
  //   marginRight: 24,
  //   padding: 12,
  //   height: 75,
  //   borderWidth: 1,
  // },
  // picker: {
  //   flex: 0,
  //   //width: width * 0.5,
  //   //flexwrap: 'wrap',
  //   //justifyContent: '',
  //   //alignSelf: 'flex-end',
  //   //height: 50,
  //   borderWidth: 1,
  // },
  rowWithLeave:{
    flex: 8,
  },
  deleteButton:{
    backgroundColor: "red",
    flex: 2,
  },
  userProfileTop: {
    // alignSelf: 'center',
    alignItems: 'flex-start',
    flexDirection: 'column',
    //justifyContent: 'space-between',
    borderBottomWidth: 1,
    borderColor: '#E8E8E8',
    padding: 12,
    paddingRight: 0,
    marginLeft: 12,
    marginRight: 12,
  },
  dogList: {
    backgroundColor: 'rgb(239,239,239)',
    borderWidth: 1,
    borderColor: 'red',
    marginLeft: 12,
    marginRight: 12,
  },
  editProfileButton: {
    height: 30,
  },
  editProfileButtonText: {
    color: 'blue',
    fontSize: 14,
  },
  joinGroupText: {
    color: 'green',
    alignSelf: 'flex-start',
    fontWeight: 'bold'
  },
  leaveGroupText: {
    color: 'red',
    alignSelf: 'flex-end',
    fontWeight: 'bold'
  },
});
<commit_msg>Remove red borders from list views for demo<commit_after>const _background = '#FFF';
const _opacity = .4;
const _inputBackground = '#666';

var Dimensions = require('Dimensions')
var {width, height} = Dimensions.get('window');

module.exports = require('react-native').StyleSheet.create({
  center: {
    justifyContent: 'center'
  },
  bImage: {
    flex: 1,
    //resizeMode: 'stretch',
    //borderWidth: 2,
    margin: 0,
    width: null,
    height: null,
  },
  blur: {
    flex: 1,
    backgroundColor: _background,
    opacity: _opacity,
  },
  container: {
    flex: 1,
    // justifyContent: 'center',
    // alignItems: 'center',
    // flexDirection: 'column',
    //  borderWidth: 3,
    // margin: 0,
    // padding: 0,
    backgroundColor: '#9cff92',
  },
  innerContainer: {
    flex: 1,
    // borderWidth: 1,
    // borderColor: 'red',
    // make things inside inner container centered
    justifyContent: 'center',
    marginTop: 64, // need this because of the navigation bar
  },
  topMargin: {
      height:20,
  },
  input: {
    height: 40,
    marginBottom:0,
    borderRadius:5,
    borderColor: 'gray',
    borderWidth: 1,
    backgroundColor: _inputBackground,
    color: '#FFF',
    alignItems: 'center',
  },
  button: {
    borderRadius: 4,
    borderColor:'#bbb',
    backgroundColor: '#333',
    borderWidth: 1,
    padding: 5,
    flex: 1,
  },
  addButton: {
    height: 30,
    backgroundColor: '#114800',
    alignSelf: 'center',
    //marginTop: 10,
    padding: 12,
    borderWidth: 1,
    borderRadius: 9,
    justifyContent: 'center',
    marginLeft: 24,
    marginRight: 24,
    marginBottom: 12,
    marginTop: 12,
  },
  addButtonText: {
    color: '#77ed6b',
    fontSize: 14,
    alignSelf: 'center',
  },
  mainContent: {
    flex: 0.8,
    //borderWidth: 1,
    // make things inside inner container centered
    justifyContent: 'center',
  },
  pageHeading: {
    alignSelf: 'center',
    fontSize: 18,
    marginBottom: 48,
    backgroundColor: 'transparent',
    color: '#77ed6b',
    fontWeight: 'bold',
  },
  inputLabel: {
    fontWeight: 'bold',
    fontSize: 15,
    alignSelf: 'flex-start',
  },
  inputText: {
    color: 'rgb(239,239,239)',
    height: 40,
    //marginTop: 10,
    marginBottom: 12,
    marginLeft: 24,
    marginRight: 24,
    padding: 12,
    fontSize: 15,
    borderWidth: 1,
    borderRadius: 9,
    borderColor: '#114800',
    backgroundColor: 'rgba(51, 31, 14, 0.40)'//'#ff9c4a',
  },
  textArea: {
    height: 150,
  },
  submitButton: {
    height: 35,
    backgroundColor: '#114800',
    alignSelf: 'stretch',
    //marginTop: 10,
    padding: 12,
    borderWidth: 1,
    borderRadius: 9,
    justifyContent: 'center',
    marginLeft: 24,
    marginRight: 24,
    marginTop: 40,
    marginBottom: 12,
  },
  signupButton: {
    height: 35,
    backgroundColor: '#479030',
    alignSelf: 'stretch',
    //marginTop: 10,
    justifyContent: 'center',
  },
  buttonText: {
    color: '#77ed6b',
    fontSize: 18,
    alignSelf: 'center',
  },
  pageFooter: {
    //borderWidth: 2,
    flex: 0.2,
  },
  profileEntry: {
    //alignItems: 'center',
    //borderTopWidth: 1,
    borderBottomWidth: 1,
    // paddingLeft: 6,
    // paddingRight: 6,
    padding: 12,
    marginLeft: 12,
    marginRight: 12,
  },
  entryLabel: {
    fontSize: 18,
    fontWeight: 'bold',
  },
  entryText: {
    fontSize: 16,
    fontWeight: 'normal',
  },
  profileAvatar: {
    width: 128,
    height: 128,
    borderRadius: 128/2,
    borderWidth: 1,
    alignSelf: 'center',
    marginBottom: 12,
    marginTop: 0,
  },
  listEntry: {
    flexDirection: 'row',
    borderBottomWidth: 1,
    borderColor: 'rgb(140, 140, 140)',
    padding: 12,
    marginLeft: 12,
    marginRight: 12,
  },
  listEntryContent: {
    flexDirection: 'column',
    justifyContent: 'space-around',
  },
  entryAvatar: {
    width: 50,
    height: 50,
    marginRight: 10,
    borderRadius: 50/2,
  },
  // pickerArea: {
  //   // flex: 1,
  //   marginBottom: 12,
  //   marginLeft: 24,
  //   marginRight: 24,
  //   padding: 12,
  //   height: 75,
  //   borderWidth: 1,
  // },
  // picker: {
  //   flex: 0,
  //   //width: width * 0.5,
  //   //flexwrap: 'wrap',
  //   //justifyContent: '',
  //   //alignSelf: 'flex-end',
  //   //height: 50,
  //   borderWidth: 1,
  // },
  rowWithLeave:{
    flex: 8,
  },
  deleteButton:{
    backgroundColor: "red",
    flex: 2,
  },
  userProfileTop: {
    // alignSelf: 'center',
    alignItems: 'flex-start',
    flexDirection: 'column',
    //justifyContent: 'space-between',
    borderBottomWidth: 1,
    borderColor: '#E8E8E8',
    padding: 12,
    paddingRight: 0,
    marginLeft: 12,
    marginRight: 12,
  },
  dogList: {
    backgroundColor: 'rgb(239,239,239)',
    // borderWidth: 1,
    marginLeft: 12,
    marginRight: 12,
  },
  editProfileButton: {
    height: 30,
  },
  editProfileButtonText: {
    color: 'blue',
    fontSize: 14,
  },
  joinGroupText: {
    color: 'green',
    alignSelf: 'flex-start',
    fontWeight: 'bold'
  },
  leaveGroupText: {
    color: 'red',
    alignSelf: 'flex-end',
    fontWeight: 'bold'
  },
});
<|endoftext|>


<commit_before>/*
 * wysiwyg web editor
 *
 * suneditor.js
 * Copyright 2017 JiHong Lee.
 * MIT license.
 */
SUNEDITOR.plugin.fontSize = {
    add: function (_this, targetElement) {
        /** set submenu */
        var listDiv = eval(this.setSubmenu(_this.context.user));

        /** add event listeners */
        listDiv.getElementsByTagName('UL')[0].addEventListener('click', this.pickup.bind(_this));

        /** append html */
        targetElement.parentNode.appendChild(listDiv);
    },

    setSubmenu: function (user) {
        var listDiv = document.createElement('DIV');
        listDiv.className = 'layer_editor layer_size';
        listDiv.style.display = 'none';

        var sizeList = !user.fontSizeList ? [8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24, 26, 28, 36, 48, 72] : user.fontSizeList;

        var list = '
' +
            '
';
        for (var i = 0; i < sizeList.length; i++) {
            var size = sizeList[i];
            list += '
<button type="button" class="btn_edit" data-value="' + size + '" style="font-size:' + size + 'pt;">' + size + '</button>
';
        }
        list += '
' +
            '
';

        listDiv.innerHTML = list;

        return listDiv;
    },

    overlayLineInnerNodes: function (element, newInnerNode, validation) {
        (function recursionFunc(current, node) {
            var childNodes = current.childNodes;
            for (var i = 0, len = childNodes.length; i < len; i++) {
                var child = childNodes[i];
                var coverNode = node;
                if (validation(child)) {
                    var cloneNode = child.cloneNode(false);
                    node.appendChild(cloneNode);
                    if (child.nodeType === 1) coverNode = cloneNode;
                }
                recursionFunc(child, coverNode);
            }
        })(element, newInnerNode);

        element.innerHTML = '';
        element.appendChild(newInnerNode);
    },

    overlayLineNodesStart: function (element, newInnerNode, validation, startCon, startOff) {
        var pNode = document.createElement('P');
        var container = startCon;
        var offset = startOff;

        var passNode = false;
        var pCurrent;
        var newNode;
        var appendNode;

        (function recursionFunc(current, node) {
            var childNodes = current.childNodes;
            for (var i = 0, len = childNodes.length; i < len; i++) {
                var child = childNodes[i];
                var coverNode = node;

                if (passNode) {
                    newNode = node;
                    while (newNode.parentNode !== newInnerNode && newNode.parentNode !== pNode && newNode.parentNode !== null) {
                        newNode = newNode.parentNode;
                    }
                    if (newNode.parentNode === pNode) {
                        newInnerNode.appendChild(newNode);
                    }
                    if (node === pNode) {
                        node = newInnerNode;
                    }
                }

                // startContainer
                if (!passNode && child === container) {
                    var prevNode = document.createTextNode(container.substringData(0, offset));
                    var textNode = document.createTextNode(container.substringData(offset, (container.length - offset)));

                    if (prevNode.data.length > 0) {
                        node.appendChild(prevNode);
                    }

                    container = textNode;
                    offset = 0;

                    newNode = node;
                    pCurrent = [];
                    while (newNode.parentNode !== pNode && newNode !== null) {
                        if (validation(newNode, true) && newNode.nodeType === 1) {
                            pCurrent.push(newNode.cloneNode(false));
                        }
                        newNode = newNode.parentNode;
                    }

                    appendNode = newNode = pCurrent.pop() || newNode;
                    while (pCurrent.length > 0) {
                        newNode = pCurrent.pop();
                        appendNode.appendChild(newNode);
                    }

                    if (appendNode !== node) {
                        newInnerNode.appendChild(appendNode);
                        node = newNode;
                    } else {
                        node = newInnerNode;
                    }

                    pNode.appendChild(newInnerNode);
                    child = textNode;
                    passNode = true;
                }

                if (!passNode || validation(child)) {
                    var cloneNode = child.cloneNode(false);
                    node.appendChild(cloneNode);
                    if (child.nodeType === 1) coverNode = cloneNode;
                }

                recursionFunc(child, coverNode);
            }
        })(element, pNode);

        element.innerHTML = pNode.innerHTML;

        return {
            container: container,
            offset: offset
        };
    },

    overlayFullLineNodesEnd: function (element, nodeName, fontSize, endCon, endOff) {
        var pNode = document.createElement('P');
        var sNode = document.createElement(nodeName);
        sNode.style.fontSize = fontSize;
        pNode.appendChild(sNode);
        var container = endCon;
        var offset = endOff;

        var passNode = false;
        var newNode;

        var oNode = document.createElement('P');

        (function recursionFunc(current, node, originNode) {
            var childNodes = current.childNodes;
            for (var i = 0; i < childNodes.length; i++) {
                var child = childNodes[i];

                if (passNode) {
                    newNode = node;
                    while (newNode !== sNode && newNode !== pNode) {
                        newNode = newNode.parentNode;
                    }
                    if (newNode === sNode) {
                        if (originNode !== oNode) {
                            node = originNode;
                            while (originNode.parentNode !== oNode) {
                                originNode = originNode.parentNode;
                            }
                            pNode.appendChild(originNode);
                        }
                        node = originNode;
                    } else {
                        node = sNode;
                    }
                }

                // endContainer
                if (!passNode && child === container) {
                    var afterNode = document.createTextNode(container.substringData(offset, (container.length - offset)));
                    var textNode = document.createTextNode(container.substringData(0, offset));

                    container = textNode;
                    offset = textNode.data.length;

                    node.appendChild(textNode);

                    if (originNode !== oNode) {
                        node = newNode = originNode;
                        while (newNode.parentNode !== oNode) {
                            newNode = newNode.parentNode;
                        }
                        pNode.appendChild(newNode);
                    }

                    child = afterNode;
                    passNode = true;
                }

                if (passNode || child.nodeName !== nodeName) {
                    var cloneNode = child.cloneNode(false);
                    node.appendChild(cloneNode);
                    if (child.nodeType === 1) node = cloneNode;
                }

                if (child.nodeType === 1) {
                    cloneNode = child.cloneNode(false);
                    originNode.appendChild(cloneNode);
                    originNode = cloneNode;
                }

                recursionFunc(child, node, originNode);
            }
        })(element, sNode, oNode);

        element.innerHTML = pNode.innerHTML;

        return {
            container: container,
            offset: offset
        };
    },

    appendSpan: function (fontSize) {
        fontSize = fontSize + 'pt';

        var nativeRng = this.getRange();
        var startCon = nativeRng.startContainer;
        var startOff = nativeRng.startOffset;
        var endCon = nativeRng.endContainer;
        var endOff = nativeRng.endOffset;
        var commonCon = nativeRng.commonAncestorContainer;
        var start = {}, end = {};
        var newNode;

        /** Select within the same node */
        if (startCon === endCon) {
            var sNode;
            if (startOff === endOff) {
                sNode = document.createElement("SPAN");
                sNode.style.fontSize = fontSize;
                sNode.innerHTML = " ";
                startCon.parentNode.insertBefore(sNode, startCon.nextSibling);
            } else {
                var lineNode = startCon;
                while (!/^P$/i.test(lineNode.nodeName)) {
                    lineNode = lineNode.parentNode;
                }
                // sNode = SUNEDITOR.plugin.fontSize.overlayLineNodesStart(lineNode, 'SPAN', fontSize, startCon, startOff);
            }

            start.container = sNode;
            start.offset = 0;
            end.container = sNode;
            end.offset = 1;
        }
        /** Select multiple nodes */
        else {
            // get Nodes
            var lineNodes = SUNEDITOR.dom.getListChildren(commonCon, function (current) { return /^P$/i.test(current.nodeName) && current.childNodes.length > 0; });

            function checkFontSizeCss(vNode) {
                if (vNode.nodeType === 3) return true;

                var style = '';
                if (vNode.style.cssText.length > 0) {
                    style = vNode.style.cssText = vNode.style.cssText.replace(/font-size\s?:\s?.+?\s?(?:;|$|\s)/, '').trim();
                }

                return vNode.nodeName !== 'SPAN' || style.length > 0;
            }

            // startCon
            newNode = document.createElement('SPAN'); newNode.style.fontSize = fontSize;
            start = SUNEDITOR.plugin.fontSize.overlayLineNodesStart(lineNodes[0], newNode, checkFontSizeCss, startCon, startOff);
            // mid
            for (var i = 1; i < lineNodes.length - 1; i++) {
                newNode = document.createElement('SPAN'); newNode.style.fontSize = fontSize;
                SUNEDITOR.plugin.fontSize.overlayLineInnerNodes(lineNodes[i], newNode, checkFontSizeCss);
            }
            // endCon
            end = SUNEDITOR.plugin.fontSize.overlayFullLineNodesEnd(lineNodes[lineNodes.length - 1], 'SPAN', fontSize, endCon, endOff);
        }

        // set range
        this.setRange(start.container, start.offset, end.container, end.offset);
    },

    pickup: function (e) {
        e.preventDefault();
        e.stopPropagation();

        if (!/^BUTTON$/i.test(e.target.tagName)) {
            return false;
        }

        this.focus();
        SUNEDITOR.plugin.fontSize.appendSpan.call(this, e.target.getAttribute('data-value'));
        this.submenuOff();
    }
};<commit_msg>update: fontSize module (multiple nodes)<commit_after>/*
 * wysiwyg web editor
 *
 * suneditor.js
 * Copyright 2017 JiHong Lee.
 * MIT license.
 */
SUNEDITOR.plugin.fontSize = {
    add: function (_this, targetElement) {
        /** set submenu */
        var listDiv = eval(this.setSubmenu(_this.context.user));

        /** add event listeners */
        listDiv.getElementsByTagName('UL')[0].addEventListener('click', this.pickup.bind(_this));

        /** append html */
        targetElement.parentNode.appendChild(listDiv);
    },

    setSubmenu: function (user) {
        var listDiv = document.createElement('DIV');
        listDiv.className = 'layer_editor layer_size';
        listDiv.style.display = 'none';

        var sizeList = !user.fontSizeList ? [8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24, 26, 28, 36, 48, 72] : user.fontSizeList;

        var list = '
' +
            '
';
        for (var i = 0; i < sizeList.length; i++) {
            var size = sizeList[i];
            list += '
<button type="button" class="btn_edit" data-value="' + size + '" style="font-size:' + size + 'pt;">' + size + '</button>
';
        }
        list += '
' +
            '
';

        listDiv.innerHTML = list;

        return listDiv;
    },

    overlayLineInnerNodes: function (element, newInnerNode, validation) {
        (function recursionFunc(current, node) {
            var childNodes = current.childNodes;
            for (var i = 0, len = childNodes.length; i < len; i++) {
                var child = childNodes[i];
                var coverNode = node;
                if (validation(child)) {
                    var cloneNode = child.cloneNode(false);
                    node.appendChild(cloneNode);
                    if (child.nodeType === 1) coverNode = cloneNode;
                }
                recursionFunc(child, coverNode);
            }
        })(element, newInnerNode);

        element.innerHTML = '';
        element.appendChild(newInnerNode);
    },

    overlayLineNodesStart: function (element, newInnerNode, validation, startCon, startOff) {
        var el = element;
        var container = startCon;
        var offset = startOff;

        var pNode = document.createElement('P');
        var passNode = false;
        var pCurrent;
        var newNode;
        var appendNode;

        (function recursionFunc(current, node) {
            var childNodes = current.childNodes;
            for (var i = 0, len = childNodes.length; i < len; i++) {
                var child = childNodes[i];
                var coverNode = node;

                if (passNode) {
                    if (child.nodeType === 1) {
                        recursionFunc(child, child.cloneNode(false));
                        continue;
                    }

                    newNode = child;
                    pCurrent = [];
                    while (newNode.parentNode !== null && newNode !== el && newNode !== newInnerNode) {
                        if (validation(newNode) && newNode.nodeType === 1) {
                            pCurrent.push(newNode.cloneNode(false));
                        }
                        newNode = newNode.parentNode;
                    }

                    if (pCurrent.length > 0) {
                        appendNode = newNode = pCurrent.pop();
                        while (pCurrent.length > 0) {
                            newNode = pCurrent.pop();
                            appendNode.appendChild(newNode);
                        }
                        newInnerNode.appendChild(appendNode);
                        node = newNode;
                    } else {
                        node = newInnerNode
                    }
                }

                // startContainer
                if (!passNode && child === container) {
                    var prevNode = document.createTextNode(container.substringData(0, offset));
                    var textNode = document.createTextNode(container.substringData(offset, (container.length - offset)));

                    if (prevNode.data.length > 0) {
                        node.appendChild(prevNode);
                    }

                    newNode = node;
                    pCurrent = [];
                    while (newNode !== pNode && newNode !== null) {
                        if (validation(newNode) && newNode.nodeType === 1) {
                            pCurrent.push(newNode.cloneNode(false));
                        }
                        newNode = newNode.parentNode;
                    }

                    appendNode = newNode = pCurrent.pop() || node;
                    while (pCurrent.length > 0) {
                        newNode = pCurrent.pop();
                        appendNode.appendChild(newNode);
                    }

                    if (appendNode !== node) {
                        newInnerNode.appendChild(appendNode);
                        node = newNode;
                    } else {
                        node = newInnerNode;
                    }

                    pNode.appendChild(newInnerNode);
                    child = container = textNode;
                    offset = 0;
                    passNode = true;
                }

                if (!passNode || validation(child)) {
                    var cloneNode = child.cloneNode(false);
                    node.appendChild(cloneNode);
                    if (child.nodeType === 1) coverNode = cloneNode;
                }

                recursionFunc(child, coverNode);
            }
        })(element, pNode);

        element.innerHTML = pNode.innerHTML;

        return {
            container: container,
            offset: offset
        };
    },

    overlayFullLineNodesEnd: function (element, newInnerNode, validation, endCon, endOff) {
        var el = element;
        var container = endCon;
        var offset = endOff;

        var pNode = document.createElement('P');
        // pNode.appendChild(newInnerNode);
        var passNode = false;
        var pCurrent;
        var newNode;
        var appendNode;

        (function recursionFunc(current, node) {
            var childNodes = current.childNodes;
            for (var i = childNodes.length -1; 0 <= i; i--) {
                var child = childNodes[i];
                var coverNode = node;

                if (passNode) {
                    if (child.nodeType === 1) {
                        recursionFunc(child, child.cloneNode(false));
                        continue;
                    }

                    newNode = child;
                    pCurrent = [];
                    while (newNode.parentNode !== null && newNode !== el && newNode !== newInnerNode) {
                        if (validation(newNode) && newNode.nodeType === 1) {
                            pCurrent.push(newNode.cloneNode(false));
                        }
                        newNode = newNode.parentNode;
                    }

                    if (pCurrent.length > 0) {
                        appendNode = newNode = pCurrent.pop();
                        while (pCurrent.length > 0) {
                            newNode = pCurrent.pop();
                            appendNode.insertBefore(newNode, appendNode.firstChild);
                        }
                        newInnerNode.insertBefore(appendNode, newInnerNode.firstChild);
                        node = newNode;
                    } else {
                        node = newInnerNode
                    }
                }

                // endContainer
                if (!passNode && child === container) {
                    var afterNode = document.createTextNode(container.substringData(offset, (container.length - offset)));
                    var textNode = document.createTextNode(container.substringData(0, offset));

                    if (afterNode.data.length > 0) {
                        node.insertBefore(afterNode, node.firstChild);
                    }

                    newNode = node;
                    pCurrent = [];
                    while (newNode !== pNode && newNode !== null) {
                        if (validation(newNode) && newNode.nodeType === 1) {
                            pCurrent.push(newNode.cloneNode(false));
                        }
                        newNode = newNode.parentNode;
                    }

                    appendNode = newNode = pCurrent.pop() || node;
                    while (pCurrent.length > 0) {
                        newNode = pCurrent.pop();
                        appendNode.insertBefore(newNode, appendNode.firstChild);
                    }

                    if (appendNode !== node) {
                        newInnerNode.insertBefore(appendNode, newInnerNode.firstChild);
                        node = newNode;
                    } else {
                        node = newInnerNode;
                    }

                    pNode.insertBefore(newInnerNode, pNode.firstChild);
                    child = container = textNode;
                    offset = textNode.data.length;
                    passNode = true;
                }

                if (!passNode || validation(child)) {
                    var cloneNode = child.cloneNode(false);
                    node.insertBefore(cloneNode, node.firstChild);
                    if (child.nodeType === 1) coverNode = cloneNode;
                }

                recursionFunc(child, coverNode);
            }
        })(element, pNode);

        element.innerHTML = pNode.innerHTML;

        return {
            container: container,
            offset: offset
        };
    },

    appendSpan: function (fontSize) {
        fontSize = fontSize + 'pt';

        var nativeRng = this.getRange();
        var startCon = nativeRng.startContainer;
        var startOff = nativeRng.startOffset;
        var endCon = nativeRng.endContainer;
        var endOff = nativeRng.endOffset;
        var commonCon = nativeRng.commonAncestorContainer;
        var start = {}, end = {};
        var newNode;

        /** Select within the same node */
        if (startCon === endCon) {
            var sNode;
            if (startOff === endOff) {
                sNode = document.createElement("SPAN");
                sNode.style.fontSize = fontSize;
                sNode.innerHTML = " ";
                startCon.parentNode.insertBefore(sNode, startCon.nextSibling);
            } else {
                var lineNode = startCon;
                while (!/^P$/i.test(lineNode.nodeName)) {
                    lineNode = lineNode.parentNode;
                }
                // sNode = SUNEDITOR.plugin.fontSize.overlayLineNodesStart(lineNode, 'SPAN', fontSize, startCon, startOff);
            }

            start.container = sNode;
            start.offset = 0;
            end.container = sNode;
            end.offset = 1;
        }
        /** Select multiple nodes */
        else {
            // get Nodes
            var lineNodes = SUNEDITOR.dom.getListChildren(commonCon, function (current) { return /^P$/i.test(current.nodeName) && current.childNodes.length > 0; });

            function checkFontSizeCss(vNode) {
                if (vNode.nodeType === 3) return true;

                var style = '';
                if (vNode.style.cssText.length > 0) {
                    style = vNode.style.cssText.replace(/font-size\s?:\s?.+?\s?(?:;|$|\s)/, '').trim();
                }

                if (vNode.nodeName !== 'SPAN' || style.length > 0) {
                    if (vNode.style.cssText.length > 0) vNode.style.cssText = style;
                    return true;
                }

                return false;
            }

            // startCon
            newNode = document.createElement('SPAN'); newNode.style.fontSize = fontSize;
            start = SUNEDITOR.plugin.fontSize.overlayLineNodesStart(lineNodes[0], newNode, checkFontSizeCss, startCon, startOff);
            // mid
            for (var i = 1; i < lineNodes.length - 1; i++) {
                newNode = document.createElement('SPAN'); newNode.style.fontSize = fontSize;
                SUNEDITOR.plugin.fontSize.overlayLineInnerNodes(lineNodes[i], newNode, checkFontSizeCss);
            }
            // endCon
            newNode = document.createElement('SPAN'); newNode.style.fontSize = fontSize;
            end = SUNEDITOR.plugin.fontSize.overlayFullLineNodesEnd(lineNodes[lineNodes.length - 1], newNode, checkFontSizeCss, endCon, endOff);
        }

        // set range
        this.setRange(start.container, start.offset, end.container, end.offset);
    },

    pickup: function (e) {
        e.preventDefault();
        e.stopPropagation();

        if (!/^BUTTON$/i.test(e.target.tagName)) {
            return false;
        }

        this.focus();
        SUNEDITOR.plugin.fontSize.appendSpan.call(this, e.target.getAttribute('data-value'));
        this.submenuOff();
    }
};<|endoftext|>