<commit_before>define([
    'knockout',
    'jquery',
    'viewmodels/widget',
    'arches',
], function(ko, $, WidgetViewModel, arches) {
    var NAME_LOOKUP = {};

    var ConceptSelectViewModel = function(params) {
        var self = this;

        params.configKeys = ['placeholder', 'defaultValue'];

        this.multiple = params.multiple || false;
        this.allowClear = true;
        this.displayName = ko.observable('');

        WidgetViewModel.apply(this, [params]);

        this.valueList = ko.computed(function() {
            var valueList = self.value();
            self.displayName();

            if (!self.multiple && valueList) {
                valueList = [valueList];
            }
            if (Array.isArray(valueList)) {
                return valueList;
            }
            return [];
        });

        this.valueObjects = ko.computed(function() {
            self.displayName();
            return self.valueList().map(function(value) {
                return {
                    id: value,
                    name: NAME_LOOKUP[value]
                };
            }).filter(function(item) {
                return item.name;
            });
        });

        this.displayValue = ko.computed(function() {
            var val = self.value();
            var name = self.displayName();
            var displayVal = null;

            if (val) {
                displayVal = name;
            }

            return displayVal;
        });

        this.setNames = function() {
            var names = [];
            self.valueList().forEach(function(val) {
                if (val) {
                    if (NAME_LOOKUP[val]) {
                        names.push(NAME_LOOKUP[val]);
                        self.displayName(names.join(', '));
                    } else {
                        $.ajax(arches.urls.get_pref_label + '?valueid=' + val, {
                            dataType: "json"
                        }).done(function(data) {
                            NAME_LOOKUP[val] = data.value;
                            names.push(data.value);
                            self.displayName(names.join(', '));
                        });
                    }
                }
            });
        };
        this.setNames();

        this.value.subscribe(function() {
            this.setNames();
        });

        this.select2Config = {
            value: self.value,
            clickBubble: true,
            multiple: self.multiple,
            closeOnSlect: false,
            placeholder: self.placeholder,
            allowClear: true,
            ajax: {
                url: arches.urls.paged_dropdown,
                dataType: 'json',
                quietMillis: 250,
                data: function(term, page) {
                    return {
                        conceptid: ko.unwrap(params.node.config.rdmCollection),
                        query: term,
                        page: page
                    };
                },
                results: function(data) {
                    data.results.forEach(function(result) {
                        if (result.collector) {
                            delete result.id;
                        }
                    });
                    return {
                        results: data.results,
                        more: data.more
                    };
                }
            },
            id: function(item) {
                return item.id;
            },
            formatResult: function(item) {
                var indentation = '';
                for (var i = 0; i < item.depth-1; i++) {
                    indentation += '    ';
                }
                return indentation + item.text;
            },
            formatSelection: function(item) {
                return item.text;
            },
            clear: function() {
                self.value('');
            },
            isEmpty: ko.computed(function() {
                return self.value() === '' || !self.value();
            }),
            initSelection: function(el, callback) {
                var valueList = self.valueList();

                var setSelectionData = function(data) {
                    var valueData;

                    if (self.multiple) {
                        if (!(data instanceof Array)) { data = [data]; }

                        valueData = data.map(function(valueId) {
                            return {
                                id: valueId,
                                text: NAME_LOOKUP[valueId],
                            };
                        });

                        /* add the rest of the previously selected values */
                        valueList.forEach(function(value) {
                            if (value !== valueData[0].id) {
                                valueData.push({
                                    id: value,
                                    text: NAME_LOOKUP[value],
                                });
                            }
                        });

                        /* keeps valueData obeying valueList as ordering source of truth */
                        if (valueData[0].id !== valueList[0]) {
                            valueData.reverse();
                        }

                    }
                    else {
                        valueData = {
                            id: data,
                            text: NAME_LOOKUP[data],
                        };
                    }

                    if (valueData) { callback(valueData); }
                };

                valueList.forEach(function(value) {
                    if (value) {
                        if (NAME_LOOKUP[value]) {
                            setSelectionData(value);
                        } else {
                            $.ajax(arches.urls.concept_value + '?valueid=' + value, {
                                dataType: "json"
                            }).done(function(data) {
                                NAME_LOOKUP[value] = data.value;
                                setSelectionData(value);
                            });
                        }
                    }
                });

            }
        };
    };

    return ConceptSelectViewModel;
});
<commit_msg>Fix a concept selector error in workflow, #7317<commit_after>define([
    'knockout',
    'jquery',
    'viewmodels/widget',
    'arches',
], function(ko, $, WidgetViewModel, arches) {
    var NAME_LOOKUP = {};

    var ConceptSelectViewModel = function(params) {
        var self = this;

        params.configKeys = ['placeholder', 'defaultValue'];

        this.multiple = params.multiple || false;
        this.allowClear = true;
        this.displayName = ko.observable('');

        WidgetViewModel.apply(this, [params]);

        this.valueList = ko.computed(function() {
            var valueList = self.value();
            self.displayName();

            if (!self.multiple && valueList) {
                valueList = [valueList];
            }
            if (Array.isArray(valueList)) {
                return valueList;
            }
            return [];
        });

        this.valueObjects = ko.computed(function() {
            self.displayName();
            return self.valueList().map(function(value) {
                return {
                    id: value,
                    name: NAME_LOOKUP[value]
                };
            }).filter(function(item) {
                return item.name;
            });
        });

        this.displayValue = ko.computed(function() {
            var val = self.value();
            var name = self.displayName();
            var displayVal = null;

            if (val) {
                displayVal = name;
            }

            return displayVal;
        });

        this.setNames = function() {
            var names = [];
            self.valueList().forEach(function(val) {
                if (val) {
                    if (NAME_LOOKUP[val]) {
                        names.push(NAME_LOOKUP[val]);
                        self.displayName(names.join(', '));
                    } else {
                        $.ajax(arches.urls.get_pref_label + '?valueid=' + val, {
                            dataType: "json"
                        }).done(function(data) {
                            NAME_LOOKUP[val] = data.value;
                            names.push(data.value);
                            self.displayName(names.join(', '));
                        });
                    }
                }
            });
        };
        this.setNames();

        this.value.subscribe(function() {
            self.setNames();
        });

        this.select2Config = {
            value: self.value,
            clickBubble: true,
            multiple: self.multiple,
            closeOnSlect: false,
            placeholder: self.placeholder,
            allowClear: true,
            ajax: {
                url: arches.urls.paged_dropdown,
                dataType: 'json',
                quietMillis: 250,
                data: function(term, page) {
                    return {
                        conceptid: ko.unwrap(params.node.config.rdmCollection),
                        query: term,
                        page: page
                    };
                },
                results: function(data) {
                    data.results.forEach(function(result) {
                        if (result.collector) {
                            delete result.id;
                        }
                    });
                    return {
                        results: data.results,
                        more: data.more
                    };
                }
            },
            id: function(item) {
                return item.id;
            },
            formatResult: function(item) {
                var indentation = '';
                for (var i = 0; i < item.depth-1; i++) {
                    indentation += '    ';
                }
                return indentation + item.text;
            },
            formatSelection: function(item) {
                return item.text;
            },
            clear: function() {
                self.value('');
            },
            isEmpty: ko.computed(function() {
                return self.value() === '' || !self.value();
            }),
            initSelection: function(el, callback) {
                var valueList = self.valueList();

                var setSelectionData = function(data) {
                    var valueData;

                    if (self.multiple) {
                        if (!(data instanceof Array)) { data = [data]; }

                        valueData = data.map(function(valueId) {
                            return {
                                id: valueId,
                                text: NAME_LOOKUP[valueId],
                            };
                        });

                        /* add the rest of the previously selected values */
                        valueList.forEach(function(value) {
                            if (value !== valueData[0].id) {
                                valueData.push({
                                    id: value,
                                    text: NAME_LOOKUP[value],
                                });
                            }
                        });

                        /* keeps valueData obeying valueList as ordering source of truth */
                        if (valueData[0].id !== valueList[0]) {
                            valueData.reverse();
                        }

                    }
                    else {
                        valueData = {
                            id: data,
                            text: NAME_LOOKUP[data],
                        };
                    }

                    if (valueData) { callback(valueData); }
                };

                valueList.forEach(function(value) {
                    if (value) {
                        if (NAME_LOOKUP[value]) {
                            setSelectionData(value);
                        } else {
                            $.ajax(arches.urls.concept_value + '?valueid=' + value, {
                                dataType: "json"
                            }).done(function(data) {
                                NAME_LOOKUP[value] = data.value;
                                setSelectionData(value);
                            });
                        }
                    }
                });

            }
        };
    };

    return ConceptSelectViewModel;
});
<|endoftext|>


<commit_before>const fs = require('fs')
const path = require('path')
const tus = require('tus-js-client')
const uuid = require('uuid')
const createTailReadStream = require('@uppy/fs-tail-stream')
const emitter = require('./emitter')
const request = require('request')
const serializeError = require('serialize-error')
const { jsonStringify, hasMatch } = require('./helpers/utils')
const logger = require('./logger')
const validator = require('validator')
const headerSanitize = require('./header-blacklist')

class Uploader {
  /**
   * @typedef {object} UploaderOptions
   * @property {string} endpoint
   * @property {string=} uploadUrl
   * @property {string} protocol
   * @property {number} size
   * @property {string=} fieldname
   * @property {string} pathPrefix
   * @property {string=} path
   * @property {any=} s3
   * @property {any} metadata
   * @property {any} uppyOptions
   * @property {any=} storage
   * @property {any=} headers
   *
   * @param {UploaderOptions} options
   */
  constructor (options) {
    if (!this.validateOptions(options)) {
      logger.debug(this._errRespMessage, 'uploader.validator.fail')
      return
    }

    this.options = options
    this.token = uuid.v4()
    this.options.path = `${this.options.pathPrefix}/${Uploader.FILE_NAME_PREFIX}-${this.token}`
    this.writer = fs.createWriteStream(this.options.path, { mode: 0o666 }) // no executable files
      .on('error', (err) => logger.error(`${this.shortToken} ${err}`, 'uploader.write.error'))
    /** @type {number} */
    this.emittedProgress = 0
    this.storage = options.storage
  }

  /**
   * Validate the options passed down to the uplaoder
   *
   * @param {UploaderOptions} options
   * @returns {boolean}
   */
  validateOptions (options) {
    if (!options.endpoint && !options.uploadUrl) {
      this._errRespMessage = 'No destination specified'
      return false
    }

    const validatorOpts = { require_protocol: true, require_tld: !options.uppyOptions.debug }
    return [options.endpoint, options.uploadUrl].every((url) => {
      if (url && !validator.isURL(url, validatorOpts)) {
        this._errRespMessage = 'Invalid destination url'
        return false
      }

      const allowedUrls = options.uppyOptions.uploadUrls
      if (allowedUrls && url && !hasMatch(url, allowedUrls)) {
        this._errRespMessage = 'upload destination does not match any allowed destinations'
        return false
      }

      return true
    })
  }

  /**
   * returns a substring of the token
   */
  get shortToken () {
    return this.token.substring(0, 8)
  }

  /**
   *
   * @param {function} callback
   */
  onSocketReady (callback) {
    emitter().once(`connection:${this.token}`, () => callback())
    logger.debug(`${this.shortToken} waiting for connection`, 'uploader.socket.wait')
  }

  cleanUp () {
    fs.unlink(this.options.path, (err) => {
      if (err) {
        logger.error(`cleanup failed for: ${this.options.path} err: ${err}`, 'uploader.cleanup.error')
      }
    })
    emitter().removeAllListeners(`pause:${this.token}`)
    emitter().removeAllListeners(`resume:${this.token}`)
  }

  /**
   *
   * @param {Buffer | Buffer[]} chunk
   */
  handleChunk (chunk) {
    logger.debug(`${this.shortToken} ${this.writer.bytesWritten} bytes`, 'uploader.download.progress')

    const protocol = this.options.protocol || 'multipart'

    // The download has completed; close the file and start an upload if necessary.
    if (chunk === null) {
      if (this.options.endpoint && protocol === 'multipart') {
        this.writer.on('finish', () => {
          this.uploadMultipart()
        })
      }
      return this.writer.end()
    }

    this.writer.write(chunk, () => {
      if (protocol === 's3-multipart' && !this.s3Upload) {
        return this.uploadS3Streaming()
      }
      if (!this.options.endpoint) return

      if (protocol === 'tus' && !this.tus) {
        return this.uploadTus()
      }
    })
  }

  /**
   *
   * @param {object} resp
   */
  handleResponse (resp) {
    resp.pipe(this.writer)

    const protocol = this.options.protocol || 'multipart'

    this.writer.on('finish', () => {
      if (protocol === 's3-multipart') {
        this.uploadS3Full()
      }

      if (!this.options.endpoint) return

      if (protocol === 'tus') {
        this.uploadTus()
      }
      if (protocol === 'multipart') {
        this.uploadMultipart()
      }
    })
  }

  getResponse () {
    if (this._errRespMessage) {
      return { body: this._errRespMessage, status: 400 }
    }
    return { body: { token: this.token }, status: 200 }
  }

  /**
   * @typedef {{action: string, payload: object}} State
   * @param {State} state
   */
  saveState (state) {
    if (!this.storage) return
    this.storage.set(`${Uploader.STORAGE_PREFIX}:${this.token}`, jsonStringify(state))
  }

  /**
   *
   * @param {number} bytesUploaded
   * @param {number | null} bytesTotal
   */
  emitProgress (bytesUploaded, bytesTotal) {
    bytesTotal = bytesTotal || this.options.size
    const percentage = (bytesUploaded / bytesTotal * 100)
    const formatPercentage = percentage.toFixed(2)
    logger.debug(
      `${this.shortToken} ${bytesUploaded} ${bytesTotal} ${formatPercentage}%`,
      'uploader.upload.progress'
    )

    const dataToEmit = {
      action: 'progress',
      payload: { progress: formatPercentage, bytesUploaded, bytesTotal }
    }
    this.saveState(dataToEmit)

    // avoid flooding the client with progress events.
    const roundedPercentage = Math.floor(percentage)
    if (this.emittedProgress !== roundedPercentage) {
      this.emittedProgress = roundedPercentage
      emitter().emit(this.token, dataToEmit)
    }
  }

  /**
   *
   * @param {string} url
   * @param {object} extraData
   */
  emitSuccess (url, extraData = {}) {
    const emitData = {
      action: 'success',
      payload: Object.assign(extraData, { complete: true, url })
    }
    this.saveState(emitData)
    emitter().emit(this.token, emitData)
  }

  /**
   *
   * @param {Error} err
   * @param {object=} extraData
   */
  emitError (err, extraData = {}) {
    const dataToEmit = {
      action: 'error',
      // TODO: consider removing the stack property
      payload: Object.assign(extraData, { error: serializeError(err) })
    }
    this.saveState(dataToEmit)
    emitter().emit(this.token, dataToEmit)
  }

  uploadTus () {
    const fname = path.basename(this.options.path)
    const ftype = this.options.metadata.type
    const metadata = Object.assign({ filename: fname, filetype: ftype }, this.options.metadata || {})
    const file = fs.createReadStream(this.options.path)
    const uploader = this

    // @ts-ignore
    this.tus = new tus.Upload(file, {
      endpoint: this.options.endpoint,
      uploadUrl: this.options.uploadUrl,
      resume: true,
      uploadSize: this.options.size || fs.statSync(this.options.path).size,
      metadata,
      chunkSize: this.writer.bytesWritten,
      /**
       *
       * @param {Error} error
       */
      onError (error) {
        logger.error(error, 'uploader.tus.error')
        uploader.emitError(error)
      },
      /**
       *
       * @param {number} bytesUploaded
       * @param {number} bytesTotal
       */
      onProgress (bytesUploaded, bytesTotal) {
        uploader.emitProgress(bytesUploaded, bytesTotal)
      },
      /**
       *
       * @param {number} chunkSize
       * @param {number} bytesUploaded
       * @param {number} bytesTotal
       */
      onChunkComplete (chunkSize, bytesUploaded, bytesTotal) {
        uploader.tus.options.chunkSize = uploader.writer.bytesWritten - bytesUploaded
      },
      onSuccess () {
        uploader.emitSuccess(uploader.tus.url)
        uploader.cleanUp()
      }
    })

    this.tus.start()

    emitter().on(`pause:${this.token}`, () => {
      this.tus.abort()
    })

    emitter().on(`resume:${this.token}`, () => {
      this.tus.start()
    })
  }

  uploadMultipart () {
    const file = fs.createReadStream(this.options.path)

    // upload progress
    let bytesUploaded = 0
    file.on('data', (data) => {
      bytesUploaded += data.length
      this.emitProgress(bytesUploaded, null)
    })

    const formData = Object.assign(
      {},
      this.options.metadata,
      { [this.options.fieldname]: file }
    )
    const headers = headerSanitize(this.options.headers)
    request.post({ url: this.options.endpoint, headers, formData, encoding: null }, (error, response, body) => {
      if (error) {
        logger.error(error, 'upload.multipart.error')
        this.emitError(error)
        return
      }
      const headers = response.headers
      // remove browser forbidden headers
      delete headers['set-cookie']
      delete headers['set-cookie2']

      const respObj = {
        responseText: body.toString(),
        status: response.statusCode,
        statusText: response.statusMessage,
        headers
      }

      if (response.statusCode >= 400) {
        logger.error(`upload failed with status: ${response.statusCode}`, 'upload.multipar.error')
        this.emitError(new Error(response.statusMessage), respObj)
      } else {
        this.emitSuccess(null, { response: respObj })
      }

      this.cleanUp()
    })
  }

  /**
   * Upload the file to S3 while it is still being downloaded.
   */
  uploadS3Streaming () {
    const file = createTailReadStream(this.options.path, {
      tail: true
    })

    this.writer.on('finish', () => {
      file.close()
    })

    return this._uploadS3(file)
  }

  /**
   * Upload the file to S3 after it has been fully downloaded.
   */
  uploadS3Full () {
    const file = fs.createReadStream(this.options.path)
    return this._uploadS3(file)
  }

  /**
   * Upload a stream to S3.
   */
  _uploadS3 (stream) {
    if (!this.options.s3) {
      this.emitError(new Error('The S3 client is not configured on this companion instance.'))
      return
    }

    const filename = this.options.metadata.filename || path.basename(this.options.path)
    const { client, options } = this.options.s3

    const upload = client.upload({
      Bucket: options.bucket,
      Key: options.getKey(null, filename),
      ACL: options.acl,
      ContentType: this.options.metadata.type,
      Body: stream
    })

    this.s3Upload = upload

    upload.on('httpUploadProgress', ({ loaded, total }) => {
      this.emitProgress(loaded, total)
    })

    upload.send((error, data) => {
      this.s3Upload = null
      if (error) {
        this.emitError(error)
      } else {
        this.emitSuccess(null, {
          response: {
            responseText: JSON.stringify(data),
            headers: {
              'content-type': 'application/json'
            }
          }
        })
      }
      this.cleanUp()
    })
  }
}

Uploader.FILE_NAME_PREFIX = 'uppy-file'
Uploader.STORAGE_PREFIX = 'companion'

module.exports = Uploader
<commit_msg>companion: bypass upload destination validation for s3<commit_after>const fs = require('fs')
const path = require('path')
const tus = require('tus-js-client')
const uuid = require('uuid')
const createTailReadStream = require('@uppy/fs-tail-stream')
const emitter = require('./emitter')
const request = require('request')
const serializeError = require('serialize-error')
const { jsonStringify, hasMatch } = require('./helpers/utils')
const logger = require('./logger')
const validator = require('validator')
const headerSanitize = require('./header-blacklist')

class Uploader {
  /**
   * @typedef {object} UploaderOptions
   * @property {string} endpoint
   * @property {string=} uploadUrl
   * @property {string} protocol
   * @property {number} size
   * @property {string=} fieldname
   * @property {string} pathPrefix
   * @property {string=} path
   * @property {any=} s3
   * @property {any} metadata
   * @property {any} uppyOptions
   * @property {any=} storage
   * @property {any=} headers
   *
   * @param {UploaderOptions} options
   */
  constructor (options) {
    if (!this.validateOptions(options)) {
      logger.debug(this._errRespMessage, 'uploader.validator.fail')
      return
    }

    this.options = options
    this.token = uuid.v4()
    this.options.path = `${this.options.pathPrefix}/${Uploader.FILE_NAME_PREFIX}-${this.token}`
    this.writer = fs.createWriteStream(this.options.path, { mode: 0o666 }) // no executable files
      .on('error', (err) => logger.error(`${this.shortToken} ${err}`, 'uploader.write.error'))
    /** @type {number} */
    this.emittedProgress = 0
    this.storage = options.storage
  }

  /**
   * Validate the options passed down to the uplaoder
   *
   * @param {UploaderOptions} options
   * @returns {boolean}
   */
  validateOptions (options) {
    // s3 uploads don't require upload destination
    // validation, because the destination is determined
    // by the server's s3 config
    if (options.protocol === 's3-multipart') {
      return true
    }

    if (!options.endpoint && !options.uploadUrl) {
      this._errRespMessage = 'No destination specified'
      return false
    }

    const validatorOpts = { require_protocol: true, require_tld: !options.uppyOptions.debug }
    return [options.endpoint, options.uploadUrl].every((url) => {
      if (url && !validator.isURL(url, validatorOpts)) {
        this._errRespMessage = 'Invalid destination url'
        return false
      }

      const allowedUrls = options.uppyOptions.uploadUrls
      if (allowedUrls && url && !hasMatch(url, allowedUrls)) {
        this._errRespMessage = 'upload destination does not match any allowed destinations'
        return false
      }

      return true
    })
  }

  /**
   * returns a substring of the token
   */
  get shortToken () {
    return this.token.substring(0, 8)
  }

  /**
   *
   * @param {function} callback
   */
  onSocketReady (callback) {
    emitter().once(`connection:${this.token}`, () => callback())
    logger.debug(`${this.shortToken} waiting for connection`, 'uploader.socket.wait')
  }

  cleanUp () {
    fs.unlink(this.options.path, (err) => {
      if (err) {
        logger.error(`cleanup failed for: ${this.options.path} err: ${err}`, 'uploader.cleanup.error')
      }
    })
    emitter().removeAllListeners(`pause:${this.token}`)
    emitter().removeAllListeners(`resume:${this.token}`)
  }

  /**
   *
   * @param {Buffer | Buffer[]} chunk
   */
  handleChunk (chunk) {
    logger.debug(`${this.shortToken} ${this.writer.bytesWritten} bytes`, 'uploader.download.progress')

    const protocol = this.options.protocol || 'multipart'

    // The download has completed; close the file and start an upload if necessary.
    if (chunk === null) {
      if (this.options.endpoint && protocol === 'multipart') {
        this.writer.on('finish', () => {
          this.uploadMultipart()
        })
      }
      return this.writer.end()
    }

    this.writer.write(chunk, () => {
      if (protocol === 's3-multipart' && !this.s3Upload) {
        return this.uploadS3Streaming()
      }
      if (!this.options.endpoint) return

      if (protocol === 'tus' && !this.tus) {
        return this.uploadTus()
      }
    })
  }

  /**
   *
   * @param {object} resp
   */
  handleResponse (resp) {
    resp.pipe(this.writer)

    const protocol = this.options.protocol || 'multipart'

    this.writer.on('finish', () => {
      if (protocol === 's3-multipart') {
        this.uploadS3Full()
      }

      if (!this.options.endpoint) return

      if (protocol === 'tus') {
        this.uploadTus()
      }
      if (protocol === 'multipart') {
        this.uploadMultipart()
      }
    })
  }

  getResponse () {
    if (this._errRespMessage) {
      return { body: this._errRespMessage, status: 400 }
    }
    return { body: { token: this.token }, status: 200 }
  }

  /**
   * @typedef {{action: string, payload: object}} State
   * @param {State} state
   */
  saveState (state) {
    if (!this.storage) return
    this.storage.set(`${Uploader.STORAGE_PREFIX}:${this.token}`, jsonStringify(state))
  }

  /**
   *
   * @param {number} bytesUploaded
   * @param {number | null} bytesTotal
   */
  emitProgress (bytesUploaded, bytesTotal) {
    bytesTotal = bytesTotal || this.options.size
    const percentage = (bytesUploaded / bytesTotal * 100)
    const formatPercentage = percentage.toFixed(2)
    logger.debug(
      `${this.shortToken} ${bytesUploaded} ${bytesTotal} ${formatPercentage}%`,
      'uploader.upload.progress'
    )

    const dataToEmit = {
      action: 'progress',
      payload: { progress: formatPercentage, bytesUploaded, bytesTotal }
    }
    this.saveState(dataToEmit)

    // avoid flooding the client with progress events.
    const roundedPercentage = Math.floor(percentage)
    if (this.emittedProgress !== roundedPercentage) {
      this.emittedProgress = roundedPercentage
      emitter().emit(this.token, dataToEmit)
    }
  }

  /**
   *
   * @param {string} url
   * @param {object} extraData
   */
  emitSuccess (url, extraData = {}) {
    const emitData = {
      action: 'success',
      payload: Object.assign(extraData, { complete: true, url })
    }
    this.saveState(emitData)
    emitter().emit(this.token, emitData)
  }

  /**
   *
   * @param {Error} err
   * @param {object=} extraData
   */
  emitError (err, extraData = {}) {
    const dataToEmit = {
      action: 'error',
      // TODO: consider removing the stack property
      payload: Object.assign(extraData, { error: serializeError(err) })
    }
    this.saveState(dataToEmit)
    emitter().emit(this.token, dataToEmit)
  }

  uploadTus () {
    const fname = path.basename(this.options.path)
    const ftype = this.options.metadata.type
    const metadata = Object.assign({ filename: fname, filetype: ftype }, this.options.metadata || {})
    const file = fs.createReadStream(this.options.path)
    const uploader = this

    // @ts-ignore
    this.tus = new tus.Upload(file, {
      endpoint: this.options.endpoint,
      uploadUrl: this.options.uploadUrl,
      resume: true,
      uploadSize: this.options.size || fs.statSync(this.options.path).size,
      metadata,
      chunkSize: this.writer.bytesWritten,
      /**
       *
       * @param {Error} error
       */
      onError (error) {
        logger.error(error, 'uploader.tus.error')
        uploader.emitError(error)
      },
      /**
       *
       * @param {number} bytesUploaded
       * @param {number} bytesTotal
       */
      onProgress (bytesUploaded, bytesTotal) {
        uploader.emitProgress(bytesUploaded, bytesTotal)
      },
      /**
       *
       * @param {number} chunkSize
       * @param {number} bytesUploaded
       * @param {number} bytesTotal
       */
      onChunkComplete (chunkSize, bytesUploaded, bytesTotal) {
        uploader.tus.options.chunkSize = uploader.writer.bytesWritten - bytesUploaded
      },
      onSuccess () {
        uploader.emitSuccess(uploader.tus.url)
        uploader.cleanUp()
      }
    })

    this.tus.start()

    emitter().on(`pause:${this.token}`, () => {
      this.tus.abort()
    })

    emitter().on(`resume:${this.token}`, () => {
      this.tus.start()
    })
  }

  uploadMultipart () {
    const file = fs.createReadStream(this.options.path)

    // upload progress
    let bytesUploaded = 0
    file.on('data', (data) => {
      bytesUploaded += data.length
      this.emitProgress(bytesUploaded, null)
    })

    const formData = Object.assign(
      {},
      this.options.metadata,
      { [this.options.fieldname]: file }
    )
    const headers = headerSanitize(this.options.headers)
    request.post({ url: this.options.endpoint, headers, formData, encoding: null }, (error, response, body) => {
      if (error) {
        logger.error(error, 'upload.multipart.error')
        this.emitError(error)
        return
      }
      const headers = response.headers
      // remove browser forbidden headers
      delete headers['set-cookie']
      delete headers['set-cookie2']

      const respObj = {
        responseText: body.toString(),
        status: response.statusCode,
        statusText: response.statusMessage,
        headers
      }

      if (response.statusCode >= 400) {
        logger.error(`upload failed with status: ${response.statusCode}`, 'upload.multipar.error')
        this.emitError(new Error(response.statusMessage), respObj)
      } else {
        this.emitSuccess(null, { response: respObj })
      }

      this.cleanUp()
    })
  }

  /**
   * Upload the file to S3 while it is still being downloaded.
   */
  uploadS3Streaming () {
    const file = createTailReadStream(this.options.path, {
      tail: true
    })

    this.writer.on('finish', () => {
      file.close()
    })

    return this._uploadS3(file)
  }

  /**
   * Upload the file to S3 after it has been fully downloaded.
   */
  uploadS3Full () {
    const file = fs.createReadStream(this.options.path)
    return this._uploadS3(file)
  }

  /**
   * Upload a stream to S3.
   */
  _uploadS3 (stream) {
    if (!this.options.s3) {
      this.emitError(new Error('The S3 client is not configured on this companion instance.'))
      return
    }

    const filename = this.options.metadata.filename || path.basename(this.options.path)
    const { client, options } = this.options.s3

    const upload = client.upload({
      Bucket: options.bucket,
      Key: options.getKey(null, filename),
      ACL: options.acl,
      ContentType: this.options.metadata.type,
      Body: stream
    })

    this.s3Upload = upload

    upload.on('httpUploadProgress', ({ loaded, total }) => {
      this.emitProgress(loaded, total)
    })

    upload.send((error, data) => {
      this.s3Upload = null
      if (error) {
        this.emitError(error)
      } else {
        this.emitSuccess(null, {
          response: {
            responseText: JSON.stringify(data),
            headers: {
              'content-type': 'application/json'
            }
          }
        })
      }
      this.cleanUp()
    })
  }
}

Uploader.FILE_NAME_PREFIX = 'uppy-file'
Uploader.STORAGE_PREFIX = 'companion'

module.exports = Uploader
<|endoftext|>


<commit_before>/**
 * The AnalysisToolPresenter class for the AnalysisToolView.
 *
 * @return AnalysisToolPresenter class.
 */
define([
  'Class',
  'underscore',
  'backbone',
  'mps',
  'topojson',
  'services/CountryService',
  'services/RegionService'
], function(Class, _, Backbone, mps, topojson, countryService, regionService) {

  'use strict';

  var AnalysisToolPresenter = Class.extend({

    datasets: {
      'umd_tree_loss_gain': 'umd-loss-gain',
      'forma': 'forma-alerts',
      'imazon': 'imazon-alerts',
      'fires': 'nasa-active-fires',
      'modis': 'quicc-alerts'
    },

    init: function(view) {
      this.view = view;

      this.status = new (Backbone.Model.extend())({
        baselayer: null,
        analysis: null, // analysis resource
        currentDate: null,
        threshold: null,
        overlay: null, // google.maps.Polygon (user draw)
        polygon: null, // geojson (user polygons)
        multipolygon: null // geojson (countries and regions)
      });

      this._subscribe();
      mps.publish('Place/register', [this]);
    },

    /**
     * Subscribe to application events.
     */
    _subscribe: function() {
      mps.subscribe('LayerNav/change', _.bind(function(layerSpec) {
        this._setBaselayer(layerSpec);
        this._checkUnavailable();
      }, this));

      mps.subscribe('Place/go', _.bind(function(place) {
        var p = place.params;
        this._setBaselayer(place.layerSpec);
        this._setCurrentDate([p.begin, p.end]);
        this._drawFromUrl(p.iso, p.geojson);
        this.status.set('threshold', p.threshold);
      }, this));

      mps.subscribe('AnalysisTool/update-analysis', _.bind(function() {
        if (this.status.get('analysis')) {
          this._publishAnalysis(this.status.get('analysis'));
        }
      }, this));

      mps.subscribe('AnalysisResults/delete-analysis', _.bind(function() {
        this.deleteGeom();
      }, this));

      mps.subscribe('Timeline/date-change', _.bind(function(layerSlug, date) {
        this._setCurrentDate(date);
      }, this));

      mps.subscribe('Threshold/changed', _.bind(function(threshold) {
        this.status.set('threshold', threshold);
      }, this));

      mps.subscribe('MapView/click-protected', _.bind(function(wdpaid) {
        this._getProtectedAreaPolygon(wdpaid.wdpaid);
        this._publishAnalysis({wdpaid: wdpaid});
      }, this));
    },

    _setCurrentDate: function(date) {
      if (date[0] && date[1]) {
        this.status.set('currentDate', date);
      }
    },

    /**
     * Set current baselayer from any layer change.
     */
    _setBaselayer: function(layerSpec) {
      var baselayers = layerSpec.getBaselayers();

      var baselayer = baselayers[_.first(_.intersection(
        _.pluck(baselayers, 'slug'),
        _.keys(this.datasets)))];

      this.status.set('baselayer', baselayer);
      this.view.model.set('hidden', !!!baselayer);
    },

    _getProtectedAreaPolygon: function(id) {
      var self = this;
       $.getJSON('http://wri-01.cartodb.com/api/v2/sql/?q=SELECT ST_AsGeoJSON(the_geom) from wdpa_all where wdpaid ='+id, function(data) {
          self._drawFromUrl('wdpa', JSON.parse(data.rows[0].st_asgeojson))
        });
    },

    _drawFromUrl: function(iso, geojson) {
      var resource = null;

      // Draw country
      if (iso.country && iso.country !== 'ALL' && !iso.region) {
        resource = {iso: iso.country};
        countryService.execute(iso.country, _.bind(function(results) {
          var geojson = topojson.feature(results.topojson, results.topojson.objects[0]);
          console.log(geojson)
          this.view.drawMultipolygon(geojson);
          this._publishAnalysis(resource);
        },this));
      // Draw region
      } else if (iso.country !== 'ALL' && iso.region) {
        resource = {iso: iso.country, id1: iso.region};
        regionService.execute(resource, _.bind(function(results) {
          var geojson = results.features[0];
          console.log(geojson, resource)
          this.view.drawMultipolygon(geojson);
          this._publishAnalysis(resource);
        },this));
      } else if (iso === 'wdpa') {
          this.view.drawMultipolygon({
            geometry: geojson,
            properties: {},
            type: 'Feature'
          });
      // Draw user polygon
      } else if (geojson) {
        resource = {geojson: JSON.stringify(geojson)};
        this.status.set('polygon', geojson);
        this.view.drawPaths(this._geojsonToPath(geojson));
        this._publishAnalysis(resource);
      }

      // Append resource to analysis before the analysis resource is
      // created, this way the url doesnt blink until the topojsons
      // are loaded. We can find another way of doing this on the PlaceService.
      this.status.set('analysis', resource);
    },

    _checkUnavailable: function() {
      if (!this.status.get('baselayer') && this.status.get('analysis')) {
        mps.publish('AnalysisService/results', [{unavailable: true}]);
      }
    },

    /**
     * Publish an analysis from a resource.
     * Resources should be stringified.
     *
     * @param  {Object} resource geojson/iso/wdpaid
     */
    _publishAnalysis: function(resource) {
      // If there are no baselayer render unsupported layer msg.
      if (!this.status.get('baselayer')) {
        mps.publish('AnalysisResults/unavailable', []);
        return;
      }

      if (this.status.get('baselayer').slug === 'umd_tree_loss_gain') {
        resource.thresh = '?thresh=' + this.status.get('threshold');
      } else {
        delete resource.thresh;
      }

      var date = this.status.get('currentDate');
      resource.dataset = this.datasets[this.status.get('baselayer').slug];

      if (!resource.wdpaid) {
        resource.period = '{0},{1}'.format(date[0].format('YYYY-MM-DD'), date[1].format('YYYY-MM-DD'));
      } else {
        resource.wdpaid = resource.wdpaid.wdpaid;
      }

      this.status.set('analysis', resource);
      mps.publish('Place/update', [{go: false}]);
      mps.publish('AnalysisService/get', [resource]);
    },

    onOverlayComplete: function(e) {
      e.overlay.type = e.type;
      e.overlay.setEditable(true);
      this.setOverlay(e.overlay);
    },

    setOverlay: function(overlay) {
      this.status.set('overlay', overlay);
    },

    setMultipolygon: function(multipolygon, geojson) {
      this.status.set('multipolygon', multipolygon);
      mps.publish('AnalysisTool/iso-drawn', [geojson.geometry]);
    },

    startDrawing: function() {
      mps.publish('AnalysisTool/start-drawing', []);
    },

    stopDrawing: function() {
      mps.publish('AnalysisTool/stop-drawing', []);
    },

    doneDrawing: function() {
      var overlay = this.status.get('overlay');
      var paths = this.status.get('overlay').getPath().getArray();
      var geojson = this._pathToGeojson(paths);
      this.status.set('polygon', geojson);
      this.view.setEditable(overlay, false);
      this._publishAnalysis({geojson: JSON.stringify(geojson)});
    },

    /**
     * Deletes the current geometry from the map. This is triggered
     * when the users cancel a drawing or when a analysis is removed.
     */
    deleteGeom: function() {
      this.view.deleteGeom({
        overlay: this.status.get('overlay'),
        multipolygon: this.status.get('multipolygon')
      });

      // Reset status
      this.status.set('analysis', null);
      this.status.set('overlay', null);
      this.status.set('polygon', null);
      this.status.set('multipolygon', null);
    },

    /**
     * Generates a GEOJSON form a path.
     *
     * @param  {Array} path Array of google.maps.LatLng objects
     * @return {string} A GeoJSON string representing the path
     */
    _pathToGeojson: function(path) {
      var coordinates = null;

      coordinates = _.map(path, function(latlng) {
        return [
          _.toNumber(latlng.lng().toFixed(4)),
          _.toNumber(latlng.lat().toFixed(4))];
      });

      // First and last coordinate should be the same
      coordinates.push(_.first(coordinates));

      return {
        'type': 'Polygon',
        'coordinates': [coordinates]
      };
    },

    /**
     * Generates a path from a Geojson.
     *
     * @param  {object} geojson
     * @return {array} paths
     */
    _geojsonToPath: function(geojson) {
      debugger
      var coords = geojson.coordinates[0];
      return _.map(coords, function(g) {
        return new google.maps.LatLng(g[1], g[0]);
      });
    },

    /**
     * Used by PlaceService to get the current iso/geom params.
     *
     * @return {object} iso/geom params
     */
    getPlaceParams: function() {
      var analysis = this.status.get('analysis');
      if (!analysis) {return;}
      var p = {};

      if (analysis.iso) {
        p.iso = {};
        p.iso.country = analysis.iso;
        p.iso.region = analysis.id1 ? analysis.id1 : null;
      } else if (analysis.geojson) {
        p.geojson = encodeURIComponent(analysis.geojson);
      }

      return p;
    }

  });

  return AnalysisToolPresenter;

});

<commit_msg>clean debugger<commit_after>/**
 * The AnalysisToolPresenter class for the AnalysisToolView.
 *
 * @return AnalysisToolPresenter class.
 */
define([
  'Class',
  'underscore',
  'backbone',
  'mps',
  'topojson',
  'services/CountryService',
  'services/RegionService'
], function(Class, _, Backbone, mps, topojson, countryService, regionService) {

  'use strict';

  var AnalysisToolPresenter = Class.extend({

    datasets: {
      'umd_tree_loss_gain': 'umd-loss-gain',
      'forma': 'forma-alerts',
      'imazon': 'imazon-alerts',
      'fires': 'nasa-active-fires',
      'modis': 'quicc-alerts'
    },

    init: function(view) {
      this.view = view;

      this.status = new (Backbone.Model.extend())({
        baselayer: null,
        analysis: null, // analysis resource
        currentDate: null,
        threshold: null,
        overlay: null, // google.maps.Polygon (user draw)
        polygon: null, // geojson (user polygons)
        multipolygon: null // geojson (countries and regions)
      });

      this._subscribe();
      mps.publish('Place/register', [this]);
    },

    /**
     * Subscribe to application events.
     */
    _subscribe: function() {
      mps.subscribe('LayerNav/change', _.bind(function(layerSpec) {
        this._setBaselayer(layerSpec);
        this._checkUnavailable();
      }, this));

      mps.subscribe('Place/go', _.bind(function(place) {
        var p = place.params;
        this._setBaselayer(place.layerSpec);
        this._setCurrentDate([p.begin, p.end]);
        this._drawFromUrl(p.iso, p.geojson);
        this.status.set('threshold', p.threshold);
      }, this));

      mps.subscribe('AnalysisTool/update-analysis', _.bind(function() {
        if (this.status.get('analysis')) {
          this._publishAnalysis(this.status.get('analysis'));
        }
      }, this));

      mps.subscribe('AnalysisResults/delete-analysis', _.bind(function() {
        this.deleteGeom();
      }, this));

      mps.subscribe('Timeline/date-change', _.bind(function(layerSlug, date) {
        this._setCurrentDate(date);
      }, this));

      mps.subscribe('Threshold/changed', _.bind(function(threshold) {
        this.status.set('threshold', threshold);
      }, this));

      mps.subscribe('MapView/click-protected', _.bind(function(wdpaid) {
        this._getProtectedAreaPolygon(wdpaid.wdpaid);
        this._publishAnalysis({wdpaid: wdpaid});
      }, this));
    },

    _setCurrentDate: function(date) {
      if (date[0] && date[1]) {
        this.status.set('currentDate', date);
      }
    },

    /**
     * Set current baselayer from any layer change.
     */
    _setBaselayer: function(layerSpec) {
      var baselayers = layerSpec.getBaselayers();

      var baselayer = baselayers[_.first(_.intersection(
        _.pluck(baselayers, 'slug'),
        _.keys(this.datasets)))];

      this.status.set('baselayer', baselayer);
      this.view.model.set('hidden', !!!baselayer);
    },

    _getProtectedAreaPolygon: function(id) {
      var self = this;
       $.getJSON('http://wri-01.cartodb.com/api/v2/sql/?q=SELECT ST_AsGeoJSON(the_geom) from wdpa_all where wdpaid ='+id, function(data) {
          self._drawFromUrl('wdpa', JSON.parse(data.rows[0].st_asgeojson))
        });
    },

    _drawFromUrl: function(iso, geojson) {
      var resource = null;

      // Draw country
      if (iso.country && iso.country !== 'ALL' && !iso.region) {
        resource = {iso: iso.country};
        countryService.execute(iso.country, _.bind(function(results) {
          var geojson = topojson.feature(results.topojson, results.topojson.objects[0]);
          console.log(geojson)
          this.view.drawMultipolygon(geojson);
          this._publishAnalysis(resource);
        },this));
      // Draw region
      } else if (iso.country !== 'ALL' && iso.region) {
        resource = {iso: iso.country, id1: iso.region};
        regionService.execute(resource, _.bind(function(results) {
          var geojson = results.features[0];
          console.log(geojson, resource)
          this.view.drawMultipolygon(geojson);
          this._publishAnalysis(resource);
        },this));
      } else if (iso === 'wdpa') {
          this.view.drawMultipolygon({
            geometry: geojson,
            properties: {},
            type: 'Feature'
          });
      // Draw user polygon
      } else if (geojson) {
        resource = {geojson: JSON.stringify(geojson)};
        this.status.set('polygon', geojson);
        this.view.drawPaths(this._geojsonToPath(geojson));
        this._publishAnalysis(resource);
      }

      // Append resource to analysis before the analysis resource is
      // created, this way the url doesnt blink until the topojsons
      // are loaded. We can find another way of doing this on the PlaceService.
      this.status.set('analysis', resource);
    },

    _checkUnavailable: function() {
      if (!this.status.get('baselayer') && this.status.get('analysis')) {
        mps.publish('AnalysisService/results', [{unavailable: true}]);
      }
    },

    /**
     * Publish an analysis from a resource.
     * Resources should be stringified.
     *
     * @param  {Object} resource geojson/iso/wdpaid
     */
    _publishAnalysis: function(resource) {
      // If there are no baselayer render unsupported layer msg.
      if (!this.status.get('baselayer')) {
        mps.publish('AnalysisResults/unavailable', []);
        return;
      }

      if (this.status.get('baselayer').slug === 'umd_tree_loss_gain') {
        resource.thresh = '?thresh=' + this.status.get('threshold');
      } else {
        delete resource.thresh;
      }

      var date = this.status.get('currentDate');
      resource.dataset = this.datasets[this.status.get('baselayer').slug];

      if (!resource.wdpaid) {
        resource.period = '{0},{1}'.format(date[0].format('YYYY-MM-DD'), date[1].format('YYYY-MM-DD'));
      } else {
        resource.wdpaid = resource.wdpaid.wdpaid;
      }

      this.status.set('analysis', resource);
      mps.publish('Place/update', [{go: false}]);
      mps.publish('AnalysisService/get', [resource]);
    },

    onOverlayComplete: function(e) {
      e.overlay.type = e.type;
      e.overlay.setEditable(true);
      this.setOverlay(e.overlay);
    },

    setOverlay: function(overlay) {
      this.status.set('overlay', overlay);
    },

    setMultipolygon: function(multipolygon, geojson) {
      this.status.set('multipolygon', multipolygon);
      mps.publish('AnalysisTool/iso-drawn', [geojson.geometry]);
    },

    startDrawing: function() {
      mps.publish('AnalysisTool/start-drawing', []);
    },

    stopDrawing: function() {
      mps.publish('AnalysisTool/stop-drawing', []);
    },

    doneDrawing: function() {
      var overlay = this.status.get('overlay');
      var paths = this.status.get('overlay').getPath().getArray();
      var geojson = this._pathToGeojson(paths);
      this.status.set('polygon', geojson);
      this.view.setEditable(overlay, false);
      this._publishAnalysis({geojson: JSON.stringify(geojson)});
    },

    /**
     * Deletes the current geometry from the map. This is triggered
     * when the users cancel a drawing or when a analysis is removed.
     */
    deleteGeom: function() {
      this.view.deleteGeom({
        overlay: this.status.get('overlay'),
        multipolygon: this.status.get('multipolygon')
      });

      // Reset status
      this.status.set('analysis', null);
      this.status.set('overlay', null);
      this.status.set('polygon', null);
      this.status.set('multipolygon', null);
    },

    /**
     * Generates a GEOJSON form a path.
     *
     * @param  {Array} path Array of google.maps.LatLng objects
     * @return {string} A GeoJSON string representing the path
     */
    _pathToGeojson: function(path) {
      var coordinates = null;

      coordinates = _.map(path, function(latlng) {
        return [
          _.toNumber(latlng.lng().toFixed(4)),
          _.toNumber(latlng.lat().toFixed(4))];
      });

      // First and last coordinate should be the same
      coordinates.push(_.first(coordinates));

      return {
        'type': 'Polygon',
        'coordinates': [coordinates]
      };
    },

    /**
     * Generates a path from a Geojson.
     *
     * @param  {object} geojson
     * @return {array} paths
     */
    _geojsonToPath: function(geojson) {
      var coords = geojson.coordinates[0];
      return _.map(coords, function(g) {
        return new google.maps.LatLng(g[1], g[0]);
      });
    },

    /**
     * Used by PlaceService to get the current iso/geom params.
     *
     * @return {object} iso/geom params
     */
    getPlaceParams: function() {
      var analysis = this.status.get('analysis');
      if (!analysis) {return;}
      var p = {};

      if (analysis.iso) {
        p.iso = {};
        p.iso.country = analysis.iso;
        p.iso.region = analysis.id1 ? analysis.id1 : null;
      } else if (analysis.geojson) {
        p.geojson = encodeURIComponent(analysis.geojson);
      }

      return p;
    }

  });

  return AnalysisToolPresenter;

});

<|endoftext|>


<commit_before>/**
 * Copyright (c) 2014 TopCoder, Inc. All rights reserved.
 */
'use strict';

//Setting up route
angular.module('mean.challenges').config(['$stateProvider',
  function($stateProvider) {
    // Check if the user is connected
    var checkLoggedin = function($q, $timeout, $http, $location) {
      // Initialize a new promise
      var deferred = $q.defer();

      // Make an AJAX call to check if the user is logged in
      $http.get('/loggedin').success(function(user) {
        // Authenticated
        if (user !== '0') $timeout(deferred.resolve);

        // Not Authenticated
        else {
          $timeout(deferred.reject);
          $location.url('/login');
        }
      });

      return deferred.promise;
    };

    // states for my app
    $stateProvider
      .state('all challenges', {
        url: '/challenges',
        templateUrl: 'challenges/views/list.html',
        resolve: {
          loggedin: checkLoggedin
        }
      })
      .state('create challenge', {
        url: '/challenges/create',
        templateUrl: 'challenges/views/create.html',
        resolve: {
          loggedin: checkLoggedin
        }
      })
      .state('edit challenge', {
        url: '/challenges/:challengeId/edit',
        templateUrl: 'challenges/views/edit.html',
        resolve: {
          loggedin: checkLoggedin
        }
      })
      .state('challenge by id', {
        url: '/challenges/:challengeId',
        templateUrl: 'challenges/views/view.html',
        resolve: {
          loggedin: checkLoggedin
        }
      });
  }
]);<commit_msg>Add route for multiview<commit_after>/**
 * Copyright (c) 2014 TopCoder, Inc. All rights reserved.
 */
'use strict';

//Setting up route
angular.module('mean.challenges').config(['$stateProvider',
  function($stateProvider) {
    // Check if the user is connected
    var checkLoggedin = function($q, $timeout, $http, $location) {
      // Initialize a new promise
      var deferred = $q.defer();

      // Make an AJAX call to check if the user is logged in
      $http.get('/loggedin').success(function(user) {
        // Authenticated
        if (user !== '0') $timeout(deferred.resolve);

        // Not Authenticated
        else {
          $timeout(deferred.reject);
          $location.url('/login');
        }
      });

      return deferred.promise;
    };

    // states for my app
    $stateProvider
      .state('all challenges', {
        url: '/challenges',
        templateUrl: 'challenges/views/list.html',
        resolve: {
          loggedin: checkLoggedin
        }
      })
      .state('all challenges multi', {
        url: '/challenges-multi',
        templateUrl: 'challenges/views/listmultiview.html',
        resolve: {
          loggedin: checkLoggedin
        }
      })
      .state('create challenge', {
        url: '/challenges/create',
        templateUrl: 'challenges/views/create.html',
        resolve: {
          loggedin: checkLoggedin
        }
      })
      .state('edit challenge', {
        url: '/challenges/:challengeId/edit',
        templateUrl: 'challenges/views/edit.html',
        resolve: {
          loggedin: checkLoggedin
        }
      })
      .state('challenge by id', {
        url: '/challenges/:challengeId',
        templateUrl: 'challenges/views/view.html',
        resolve: {
          loggedin: checkLoggedin
        }
      });
  }
]);<|endoftext|>


<commit_before>/* eslint-disable comma-dangle, object-shorthand, no-param-reassign, camelcase, no-nested-ternary, no-continue, max-len */

import Vue from 'vue';
import Cookies from 'js-cookie';

((global) => {
  global.mergeConflicts = global.mergeConflicts || {};

  const diffViewType = Cookies.get('diff_view');
  const HEAD_HEADER_TEXT = 'HEAD//our changes';
  const ORIGIN_HEADER_TEXT = 'origin//their changes';
  const HEAD_BUTTON_TITLE = 'Use ours';
  const ORIGIN_BUTTON_TITLE = 'Use theirs';
  const INTERACTIVE_RESOLVE_MODE = 'interactive';
  const EDIT_RESOLVE_MODE = 'edit';
  const DEFAULT_RESOLVE_MODE = INTERACTIVE_RESOLVE_MODE;
  const VIEW_TYPES = {
    INLINE: 'inline',
    PARALLEL: 'parallel'
  };
  const CONFLICT_TYPES = {
    TEXT: 'text',
    TEXT_EDITOR: 'text-editor'
  };

  global.mergeConflicts.mergeConflictsStore = {
    state: {
      isLoading: true,
      hasError: false,
      isSubmitting: false,
      isParallel: diffViewType === VIEW_TYPES.PARALLEL,
      diffViewType: diffViewType,
      conflictsData: {}
    },

    setConflictsData(data) {
      this.decorateFiles(data.files);

      this.state.conflictsData = {
        files: data.files,
        commitMessage: data.commit_message,
        sourceBranch: data.source_branch,
        targetBranch: data.target_branch,
        shortCommitSha: data.commit_sha.slice(0, 7),
      };
    },

    decorateFiles(files) {
      files.forEach((file) => {
        file.content = '';
        file.resolutionData = {};
        file.promptDiscardConfirmation = false;
        file.resolveMode = DEFAULT_RESOLVE_MODE;
        file.filePath = this.getFilePath(file);
        file.iconClass = `fa-${file.blob_icon}`;
        file.blobPath = file.blob_path;

        if (file.type === CONFLICT_TYPES.TEXT) {
          file.showEditor = false;
          file.loadEditor = false;

          this.setInlineLine(file);
          this.setParallelLine(file);
        } else if (file.type === CONFLICT_TYPES.TEXT_EDITOR) {
          file.showEditor = true;
          file.loadEditor = true;
        }
      });
    },

    setInlineLine(file) {
      file.inlineLines = [];

      file.sections.forEach((section) => {
        let currentLineType = 'new';
        const { conflict, lines, id } = section;

        if (conflict) {
          file.inlineLines.push(this.getHeadHeaderLine(id));
        }

        lines.forEach((line) => {
          const { type } = line;

          if ((type === 'new' || type === 'old') && currentLineType !== type) {
            currentLineType = type;
            file.inlineLines.push({ lineType: 'emptyLine', richText: '' });
          }

          this.decorateLineForInlineView(line, id, conflict);
          file.inlineLines.push(line);
        });

        if (conflict) {
          file.inlineLines.push(this.getOriginHeaderLine(id));
        }
      });
    },

    setParallelLine(file) {
      file.parallelLines = [];
      const linesObj = { left: [], right: [] };

      file.sections.forEach((section) => {
        const { conflict, lines, id } = section;

        if (conflict) {
          linesObj.left.push(this.getOriginHeaderLine(id));
          linesObj.right.push(this.getHeadHeaderLine(id));
        }

        lines.forEach((line) => {
          const { type } = line;

          if (conflict) {
            if (type === 'old') {
              linesObj.left.push(this.getLineForParallelView(line, id, 'conflict'));
            } else if (type === 'new') {
              linesObj.right.push(this.getLineForParallelView(line, id, 'conflict', true));
            }
          } else {
            const lineType = type || 'context';

            linesObj.left.push(this.getLineForParallelView(line, id, lineType));
            linesObj.right.push(this.getLineForParallelView(line, id, lineType, true));
          }
        });

        this.checkLineLengths(linesObj);
      });

      for (let i = 0, len = linesObj.left.length; i < len; i += 1) {
        file.parallelLines.push([
          linesObj.right[i],
          linesObj.left[i]
        ]);
      }
    },

    setLoadingState(state) {
      this.state.isLoading = state;
    },

    setErrorState(state) {
      this.state.hasError = state;
    },

    setFailedRequest(message) {
      this.state.hasError = true;
      this.state.conflictsData.errorMessage = message;
    },

    getConflictsCount() {
      if (!this.state.conflictsData.files.length) {
        return 0;
      }

      const files = this.state.conflictsData.files;
      let count = 0;

      files.forEach((file) => {
        if (file.type === CONFLICT_TYPES.TEXT) {
          file.sections.forEach((section) => {
            if (section.conflict) {
              count += 1;
            }
          });
        } else {
          count += 1;
        }
      });

      return count;
    },

    getConflictsCountText() {
      const count = this.getConflictsCount();
      const text = count ? 'conflicts' : 'conflict';

      return `${count} ${text}`;
    },

    setViewType(viewType) {
      this.state.diffView = viewType;
      this.state.isParallel = viewType === VIEW_TYPES.PARALLEL;

      Cookies.set('diff_view', viewType);
    },

    getHeadHeaderLine(id) {
      return {
        id: id,
        richText: HEAD_HEADER_TEXT,
        buttonTitle: HEAD_BUTTON_TITLE,
        type: 'new',
        section: 'head',
        isHeader: true,
        isHead: true,
        isSelected: false,
        isUnselected: false
      };
    },

    decorateLineForInlineView(line, id, conflict) {
      const { type } = line;
      line.id = id;
      line.hasConflict = conflict;
      line.isHead = type === 'new';
      line.isOrigin = type === 'old';
      line.hasMatch = type === 'match';
      line.richText = line.rich_text;
      line.isSelected = false;
      line.isUnselected = false;
    },

    getLineForParallelView(line, id, lineType, isHead) {
      const { old_line, new_line, rich_text } = line;
      const hasConflict = lineType === 'conflict';

      return {
        id,
        lineType,
        hasConflict,
        isHead: hasConflict && isHead,
        isOrigin: hasConflict && !isHead,
        hasMatch: lineType === 'match',
        lineNumber: isHead ? new_line : old_line,
        section: isHead ? 'head' : 'origin',
        richText: rich_text,
        isSelected: false,
        isUnselected: false
      };
    },

    getOriginHeaderLine(id) {
      return {
        id: id,
        richText: ORIGIN_HEADER_TEXT,
        buttonTitle: ORIGIN_BUTTON_TITLE,
        type: 'old',
        section: 'origin',
        isHeader: true,
        isOrigin: true,
        isSelected: false,
        isUnselected: false
      };
    },

    getFilePath(file) {
      const { old_path, new_path } = file;
      return old_path === new_path ? new_path : `${old_path} → ${new_path}`;
    },

    checkLineLengths(linesObj) {
      const { left, right } = linesObj;

      if (left.length !== right.length) {
        if (left.length > right.length) {
          const diff = left.length - right.length;
          for (let i = 0; i < diff; i += 1) {
            right.push({ lineType: 'emptyLine', richText: '' });
          }
        } else {
          const diff = right.length - left.length;
          for (let i = 0; i < diff; i += 1) {
            left.push({ lineType: 'emptyLine', richText: '' });
          }
        }
      }
    },

    setPromptConfirmationState(file, state) {
      file.promptDiscardConfirmation = state;
    },

    setFileResolveMode(file, mode) {
      if (mode === INTERACTIVE_RESOLVE_MODE) {
        file.showEditor = false;
      } else if (mode === EDIT_RESOLVE_MODE) {
        // Restore Interactive mode when switching to Edit mode
        file.showEditor = true;
        file.loadEditor = true;
        file.resolutionData = {};

        this.restoreFileLinesState(file);
      }

      file.resolveMode = mode;
    },

    restoreFileLinesState(file) {
      file.inlineLines.forEach((line) => {
        if (line.hasConflict || line.isHeader) {
          line.isSelected = false;
          line.isUnselected = false;
        }
      });

      file.parallelLines.forEach((lines) => {
        const left = lines[0];
        const right = lines[1];
        const isLeftMatch = left.hasConflict || left.isHeader;
        const isRightMatch = right.hasConflict || right.isHeader;

        if (isLeftMatch || isRightMatch) {
          left.isSelected = false;
          left.isUnselected = false;
          right.isSelected = false;
          right.isUnselected = false;
        }
      });
    },

    isReadyToCommit() {
      const files = this.state.conflictsData.files;
      const hasCommitMessage = $.trim(this.state.conflictsData.commitMessage).length;
      let unresolved = 0;

      for (let i = 0, l = files.length; i < l; i += 1) {
        const file = files[i];

        if (file.resolveMode === INTERACTIVE_RESOLVE_MODE) {
          let numberConflicts = 0;
          const resolvedConflicts = Object.keys(file.resolutionData).length;

          // We only check for conflicts type 'text'
          // since conflicts `text_editor` can´t be resolved in interactive mode
          if (file.type === CONFLICT_TYPES.TEXT) {
            for (let j = 0, k = file.sections.length; j < k; j += 1) {
              if (file.sections[j].conflict) {
                numberConflicts += 1;
              }
            }

            if (resolvedConflicts !== numberConflicts) {
              unresolved += 1;
            }
          }
        } else if (file.resolveMode === EDIT_RESOLVE_MODE) {
          // Unlikely to happen since switching to Edit mode saves content automatically.
          // Checking anyway in case the save strategy changes in the future
          if (!file.content) {
            unresolved += 1;
            continue;
          }
        }
      }

      return !this.state.isSubmitting && hasCommitMessage && !unresolved;
    },

    getCommitButtonText() {
      const initial = 'Commit conflict resolution';
      const inProgress = 'Committing...';

      return this.state ? this.state.isSubmitting ? inProgress : initial : initial;
    },

    getCommitData() {
      let commitData = {};

      commitData = {
        commit_message: this.state.conflictsData.commitMessage,
        files: []
      };

      this.state.conflictsData.files.forEach((file) => {
        const addFile = {
          old_path: file.old_path,
          new_path: file.new_path
        };

        if (file.type === CONFLICT_TYPES.TEXT) {
          // Submit only one data for type of editing
          if (file.resolveMode === INTERACTIVE_RESOLVE_MODE) {
            addFile.sections = file.resolutionData;
          } else if (file.resolveMode === EDIT_RESOLVE_MODE) {
            addFile.content = file.content;
          }
        } else if (file.type === CONFLICT_TYPES.TEXT_EDITOR) {
          addFile.content = file.content;
        }

        commitData.files.push(addFile);
      });

      return commitData;
    },

    handleSelected(file, sectionId, selection) {
      Vue.set(file.resolutionData, sectionId, selection);

      file.inlineLines.forEach((line) => {
        if (line.id === sectionId && (line.hasConflict || line.isHeader)) {
          this.markLine(line, selection);
        }
      });

      file.parallelLines.forEach((lines) => {
        const left = lines[0];
        const right = lines[1];
        const hasSameId = right.id === sectionId || left.id === sectionId;
        const isLeftMatch = left.hasConflict || left.isHeader;
        const isRightMatch = right.hasConflict || right.isHeader;

        if (hasSameId && (isLeftMatch || isRightMatch)) {
          this.markLine(left, selection);
          this.markLine(right, selection);
        }
      });
    },

    markLine(line, selection) {
      if (selection === 'head' && line.isHead) {
        line.isSelected = true;
        line.isUnselected = false;
      } else if (selection === 'origin' && line.isOrigin) {
        line.isSelected = true;
        line.isUnselected = false;
      } else {
        line.isSelected = false;
        line.isUnselected = true;
      }
    },

    setSubmitState(state) {
      this.state.isSubmitting = state;
    },

    fileTextTypePresent() {
      return this.state.conflictsData.files.some(f => f.type === CONFLICT_TYPES.TEXT);
    }
  };
})(window.gl || (window.gl = {}));
<commit_msg>fix conflict pluralized<commit_after>/* eslint-disable comma-dangle, object-shorthand, no-param-reassign, camelcase, no-nested-ternary, no-continue, max-len */

import Vue from 'vue';
import Cookies from 'js-cookie';

((global) => {
  global.mergeConflicts = global.mergeConflicts || {};

  const diffViewType = Cookies.get('diff_view');
  const HEAD_HEADER_TEXT = 'HEAD//our changes';
  const ORIGIN_HEADER_TEXT = 'origin//their changes';
  const HEAD_BUTTON_TITLE = 'Use ours';
  const ORIGIN_BUTTON_TITLE = 'Use theirs';
  const INTERACTIVE_RESOLVE_MODE = 'interactive';
  const EDIT_RESOLVE_MODE = 'edit';
  const DEFAULT_RESOLVE_MODE = INTERACTIVE_RESOLVE_MODE;
  const VIEW_TYPES = {
    INLINE: 'inline',
    PARALLEL: 'parallel'
  };
  const CONFLICT_TYPES = {
    TEXT: 'text',
    TEXT_EDITOR: 'text-editor'
  };

  global.mergeConflicts.mergeConflictsStore = {
    state: {
      isLoading: true,
      hasError: false,
      isSubmitting: false,
      isParallel: diffViewType === VIEW_TYPES.PARALLEL,
      diffViewType: diffViewType,
      conflictsData: {}
    },

    setConflictsData(data) {
      this.decorateFiles(data.files);

      this.state.conflictsData = {
        files: data.files,
        commitMessage: data.commit_message,
        sourceBranch: data.source_branch,
        targetBranch: data.target_branch,
        shortCommitSha: data.commit_sha.slice(0, 7),
      };
    },

    decorateFiles(files) {
      files.forEach((file) => {
        file.content = '';
        file.resolutionData = {};
        file.promptDiscardConfirmation = false;
        file.resolveMode = DEFAULT_RESOLVE_MODE;
        file.filePath = this.getFilePath(file);
        file.iconClass = `fa-${file.blob_icon}`;
        file.blobPath = file.blob_path;

        if (file.type === CONFLICT_TYPES.TEXT) {
          file.showEditor = false;
          file.loadEditor = false;

          this.setInlineLine(file);
          this.setParallelLine(file);
        } else if (file.type === CONFLICT_TYPES.TEXT_EDITOR) {
          file.showEditor = true;
          file.loadEditor = true;
        }
      });
    },

    setInlineLine(file) {
      file.inlineLines = [];

      file.sections.forEach((section) => {
        let currentLineType = 'new';
        const { conflict, lines, id } = section;

        if (conflict) {
          file.inlineLines.push(this.getHeadHeaderLine(id));
        }

        lines.forEach((line) => {
          const { type } = line;

          if ((type === 'new' || type === 'old') && currentLineType !== type) {
            currentLineType = type;
            file.inlineLines.push({ lineType: 'emptyLine', richText: '' });
          }

          this.decorateLineForInlineView(line, id, conflict);
          file.inlineLines.push(line);
        });

        if (conflict) {
          file.inlineLines.push(this.getOriginHeaderLine(id));
        }
      });
    },

    setParallelLine(file) {
      file.parallelLines = [];
      const linesObj = { left: [], right: [] };

      file.sections.forEach((section) => {
        const { conflict, lines, id } = section;

        if (conflict) {
          linesObj.left.push(this.getOriginHeaderLine(id));
          linesObj.right.push(this.getHeadHeaderLine(id));
        }

        lines.forEach((line) => {
          const { type } = line;

          if (conflict) {
            if (type === 'old') {
              linesObj.left.push(this.getLineForParallelView(line, id, 'conflict'));
            } else if (type === 'new') {
              linesObj.right.push(this.getLineForParallelView(line, id, 'conflict', true));
            }
          } else {
            const lineType = type || 'context';

            linesObj.left.push(this.getLineForParallelView(line, id, lineType));
            linesObj.right.push(this.getLineForParallelView(line, id, lineType, true));
          }
        });

        this.checkLineLengths(linesObj);
      });

      for (let i = 0, len = linesObj.left.length; i < len; i += 1) {
        file.parallelLines.push([
          linesObj.right[i],
          linesObj.left[i]
        ]);
      }
    },

    setLoadingState(state) {
      this.state.isLoading = state;
    },

    setErrorState(state) {
      this.state.hasError = state;
    },

    setFailedRequest(message) {
      this.state.hasError = true;
      this.state.conflictsData.errorMessage = message;
    },

    getConflictsCount() {
      if (!this.state.conflictsData.files.length) {
        return 0;
      }

      const files = this.state.conflictsData.files;
      let count = 0;

      files.forEach((file) => {
        if (file.type === CONFLICT_TYPES.TEXT) {
          file.sections.forEach((section) => {
            if (section.conflict) {
              count += 1;
            }
          });
        } else {
          count += 1;
        }
      });

      return count;
    },

    getConflictsCountText() {
      const count = this.getConflictsCount();
      const text = count > 1 ? 'conflicts' : 'conflict';

      return `${count} ${text}`;
    },

    setViewType(viewType) {
      this.state.diffView = viewType;
      this.state.isParallel = viewType === VIEW_TYPES.PARALLEL;

      Cookies.set('diff_view', viewType);
    },

    getHeadHeaderLine(id) {
      return {
        id: id,
        richText: HEAD_HEADER_TEXT,
        buttonTitle: HEAD_BUTTON_TITLE,
        type: 'new',
        section: 'head',
        isHeader: true,
        isHead: true,
        isSelected: false,
        isUnselected: false
      };
    },

    decorateLineForInlineView(line, id, conflict) {
      const { type } = line;
      line.id = id;
      line.hasConflict = conflict;
      line.isHead = type === 'new';
      line.isOrigin = type === 'old';
      line.hasMatch = type === 'match';
      line.richText = line.rich_text;
      line.isSelected = false;
      line.isUnselected = false;
    },

    getLineForParallelView(line, id, lineType, isHead) {
      const { old_line, new_line, rich_text } = line;
      const hasConflict = lineType === 'conflict';

      return {
        id,
        lineType,
        hasConflict,
        isHead: hasConflict && isHead,
        isOrigin: hasConflict && !isHead,
        hasMatch: lineType === 'match',
        lineNumber: isHead ? new_line : old_line,
        section: isHead ? 'head' : 'origin',
        richText: rich_text,
        isSelected: false,
        isUnselected: false
      };
    },

    getOriginHeaderLine(id) {
      return {
        id: id,
        richText: ORIGIN_HEADER_TEXT,
        buttonTitle: ORIGIN_BUTTON_TITLE,
        type: 'old',
        section: 'origin',
        isHeader: true,
        isOrigin: true,
        isSelected: false,
        isUnselected: false
      };
    },

    getFilePath(file) {
      const { old_path, new_path } = file;
      return old_path === new_path ? new_path : `${old_path} → ${new_path}`;
    },

    checkLineLengths(linesObj) {
      const { left, right } = linesObj;

      if (left.length !== right.length) {
        if (left.length > right.length) {
          const diff = left.length - right.length;
          for (let i = 0; i < diff; i += 1) {
            right.push({ lineType: 'emptyLine', richText: '' });
          }
        } else {
          const diff = right.length - left.length;
          for (let i = 0; i < diff; i += 1) {
            left.push({ lineType: 'emptyLine', richText: '' });
          }
        }
      }
    },

    setPromptConfirmationState(file, state) {
      file.promptDiscardConfirmation = state;
    },

    setFileResolveMode(file, mode) {
      if (mode === INTERACTIVE_RESOLVE_MODE) {
        file.showEditor = false;
      } else if (mode === EDIT_RESOLVE_MODE) {
        // Restore Interactive mode when switching to Edit mode
        file.showEditor = true;
        file.loadEditor = true;
        file.resolutionData = {};

        this.restoreFileLinesState(file);
      }

      file.resolveMode = mode;
    },

    restoreFileLinesState(file) {
      file.inlineLines.forEach((line) => {
        if (line.hasConflict || line.isHeader) {
          line.isSelected = false;
          line.isUnselected = false;
        }
      });

      file.parallelLines.forEach((lines) => {
        const left = lines[0];
        const right = lines[1];
        const isLeftMatch = left.hasConflict || left.isHeader;
        const isRightMatch = right.hasConflict || right.isHeader;

        if (isLeftMatch || isRightMatch) {
          left.isSelected = false;
          left.isUnselected = false;
          right.isSelected = false;
          right.isUnselected = false;
        }
      });
    },

    isReadyToCommit() {
      const files = this.state.conflictsData.files;
      const hasCommitMessage = $.trim(this.state.conflictsData.commitMessage).length;
      let unresolved = 0;

      for (let i = 0, l = files.length; i < l; i += 1) {
        const file = files[i];

        if (file.resolveMode === INTERACTIVE_RESOLVE_MODE) {
          let numberConflicts = 0;
          const resolvedConflicts = Object.keys(file.resolutionData).length;

          // We only check for conflicts type 'text'
          // since conflicts `text_editor` can´t be resolved in interactive mode
          if (file.type === CONFLICT_TYPES.TEXT) {
            for (let j = 0, k = file.sections.length; j < k; j += 1) {
              if (file.sections[j].conflict) {
                numberConflicts += 1;
              }
            }

            if (resolvedConflicts !== numberConflicts) {
              unresolved += 1;
            }
          }
        } else if (file.resolveMode === EDIT_RESOLVE_MODE) {
          // Unlikely to happen since switching to Edit mode saves content automatically.
          // Checking anyway in case the save strategy changes in the future
          if (!file.content) {
            unresolved += 1;
            continue;
          }
        }
      }

      return !this.state.isSubmitting && hasCommitMessage && !unresolved;
    },

    getCommitButtonText() {
      const initial = 'Commit conflict resolution';
      const inProgress = 'Committing...';

      return this.state ? this.state.isSubmitting ? inProgress : initial : initial;
    },

    getCommitData() {
      let commitData = {};

      commitData = {
        commit_message: this.state.conflictsData.commitMessage,
        files: []
      };

      this.state.conflictsData.files.forEach((file) => {
        const addFile = {
          old_path: file.old_path,
          new_path: file.new_path
        };

        if (file.type === CONFLICT_TYPES.TEXT) {
          // Submit only one data for type of editing
          if (file.resolveMode === INTERACTIVE_RESOLVE_MODE) {
            addFile.sections = file.resolutionData;
          } else if (file.resolveMode === EDIT_RESOLVE_MODE) {
            addFile.content = file.content;
          }
        } else if (file.type === CONFLICT_TYPES.TEXT_EDITOR) {
          addFile.content = file.content;
        }

        commitData.files.push(addFile);
      });

      return commitData;
    },

    handleSelected(file, sectionId, selection) {
      Vue.set(file.resolutionData, sectionId, selection);

      file.inlineLines.forEach((line) => {
        if (line.id === sectionId && (line.hasConflict || line.isHeader)) {
          this.markLine(line, selection);
        }
      });

      file.parallelLines.forEach((lines) => {
        const left = lines[0];
        const right = lines[1];
        const hasSameId = right.id === sectionId || left.id === sectionId;
        const isLeftMatch = left.hasConflict || left.isHeader;
        const isRightMatch = right.hasConflict || right.isHeader;

        if (hasSameId && (isLeftMatch || isRightMatch)) {
          this.markLine(left, selection);
          this.markLine(right, selection);
        }
      });
    },

    markLine(line, selection) {
      if (selection === 'head' && line.isHead) {
        line.isSelected = true;
        line.isUnselected = false;
      } else if (selection === 'origin' && line.isOrigin) {
        line.isSelected = true;
        line.isUnselected = false;
      } else {
        line.isSelected = false;
        line.isUnselected = true;
      }
    },

    setSubmitState(state) {
      this.state.isSubmitting = state;
    },

    fileTextTypePresent() {
      return this.state.conflictsData.files.some(f => f.type === CONFLICT_TYPES.TEXT);
    }
  };
})(window.gl || (window.gl = {}));
<|endoftext|>


<commit_before>/**
 * WordPress dependencies
 */
import { __ } from '@wordpress/i18n';
import { Dropdown, IconButton, Button } from '@wordpress/components';
import { Component } from '@wordpress/element';
import { withSelect, withDispatch } from '@wordpress/data';
import { createBlock } from '@wordpress/blocks';
import { compose } from '@wordpress/compose';

/**
 * Internal dependencies
 */
import { BlockPreview } from '../';
import pageIcon from '../../../images/add-page-inserter.svg';
import addTemplateIcon from '../../../images/add-template.svg';
import './edit.css';
import { createSkeletonTemplate } from '../../helpers';

const storyPageBlockName = 'amp/amp-story-page';

class TemplateInserter extends Component {
	constructor() {
		super( ...arguments );

		this.onToggle = this.onToggle.bind( this );

		this.state = {
			reusableBlocks: [],
		};
	}

	componentDidMount() {
		this.props.fetchReusableBlocks();
	}

	componentDidUpdate( prevProps ) {
		// This check is needed to make sure that the blocks are loaded in time.
		if ( prevProps.reusableBlocks !== this.props.reusableBlocks || prevProps.allBlocks !== this.props.allBlocks ) {
			this.setState( {
				reusableBlocks: this.props.reusableBlocks,
			} );
		}
	}

	onToggle( isOpen ) {
		const { onToggle } = this.props;

		// Surface toggle callback to parent component
		if ( onToggle ) {
			onToggle( isOpen );
		}
	}

	render() {
		const { insertBlock, getBlock } = this.props;
		return (
			<Dropdown
				className="editor-inserter block-editor-inserter"
				contentClassName="amp-stories__template-inserter__popover is-from-top is-bottom editor-inserter__popover"
				onToggle={ this.onToggle }
				expandOnMobile
				renderToggle={ ( { onToggle, isOpen } ) => (
					<IconButton
						icon={ addTemplateIcon( { width: 16, height: 16 } ) }
						label={ __( 'Insert Template', 'amp' ) }
						onClick={ onToggle }
						className="editor-inserter__amp-inserter"
						aria-haspopup="true"
						aria-expanded={ isOpen }
					/>
				) }
				renderContent={ ( { onClose } ) => {
					const isStoryBlock = ( clientId ) => {
						const block = getBlock( clientId );
						return block && storyPageBlockName === block.name;
					};

					const onSelect = ( item ) => {
						const block = ! item ? createBlock( storyPageBlockName ) : getBlock( item.clientId );
						onClose();

						insertBlock( createSkeletonTemplate( block ) );
					};

					const storyTemplates = this.state.reusableBlocks.filter( ( { clientId } ) => isStoryBlock( clientId ) );

					return (

										<IconButton
											icon={ pageIcon( { width: 86, height: 96 } ) }
											label={ __( 'Blank Page', 'amp' ) }
											onClick={ () => {
												onSelect( null );
											} }
											className="amp-stories__blank-page-inserter editor-block-preview__content block-editor-block-preview__content editor-styles-wrapper"
										/>

									{ storyTemplates && storyTemplates.map( ( item ) => (
										<Button
											key={ `template-preview-${ item.id }` }
											onClick={ () => {
												onSelect( item );
											} }
											className="components-button block-editor-block-preview"
										>
											<BlockPreview
												name="core/block"
												attributes={ { ref: item.id } }
											/>
										</Button>
									) ) }

					);
				} }
			/>
		);
	}
}

export default compose(
	withSelect( ( select ) => {
		const {
			__experimentalGetReusableBlocks: getReusableBlocks,
		} = select( 'core/editor' );

		const {
			getBlock,
			getBlocks,
		} = select( 'core/block-editor' );

		return {
			reusableBlocks: getReusableBlocks(),
			getBlock,
			allBlocks: getBlocks(),
		};
	} ),
	withDispatch( ( dispatch ) => {
		const {
			__experimentalFetchReusableBlocks: fetchReusableBlocks,
		} = dispatch( 'core/editor' );

		const { insertBlock } = dispatch( 'core/block-editor' );

		return {
			fetchReusableBlocks,
			insertBlock,
		};
	} )
)( TemplateInserter );
<commit_msg>Change <Button> to , to work around a console warning (#2206)<commit_after>
/**
 * External dependencies
 */
import { includes } from 'lodash';

/**
 * WordPress dependencies
 */
import { __ } from '@wordpress/i18n';
import { Dropdown, IconButton } from '@wordpress/components';
import { Component } from '@wordpress/element';
import { withSelect, withDispatch } from '@wordpress/data';
import { createBlock } from '@wordpress/blocks';
import { compose } from '@wordpress/compose';
import { ENTER, SPACE } from '@wordpress/keycodes';

/**
 * Internal dependencies
 */
import { BlockPreview } from '../';
import pageIcon from '../../../images/add-page-inserter.svg';
import addTemplateIcon from '../../../images/add-template.svg';
import './edit.css';
import { createSkeletonTemplate } from '../../helpers';

const storyPageBlockName = 'amp/amp-story-page';

class TemplateInserter extends Component {
	constructor() {
		super( ...arguments );

		this.onToggle = this.onToggle.bind( this );

		this.state = {
			reusableBlocks: [],
		};
	}

	componentDidMount() {
		this.props.fetchReusableBlocks();
	}

	componentDidUpdate( prevProps ) {
		// This check is needed to make sure that the blocks are loaded in time.
		if ( prevProps.reusableBlocks !== this.props.reusableBlocks || prevProps.allBlocks !== this.props.allBlocks ) {
			this.setState( {
				reusableBlocks: this.props.reusableBlocks,
			} );
		}
	}

	onToggle( isOpen ) {
		const { onToggle } = this.props;

		// Surface toggle callback to parent component
		if ( onToggle ) {
			onToggle( isOpen );
		}
	}

	render() {
		const { insertBlock, getBlock } = this.props;
		return (
			<Dropdown
				className="editor-inserter block-editor-inserter"
				contentClassName="amp-stories__template-inserter__popover is-from-top is-bottom editor-inserter__popover"
				onToggle={ this.onToggle }
				expandOnMobile
				renderToggle={ ( { onToggle, isOpen } ) => (
					<IconButton
						icon={ addTemplateIcon( { width: 16, height: 16 } ) }
						label={ __( 'Insert Template', 'amp' ) }
						onClick={ onToggle }
						className="editor-inserter__amp-inserter"
						aria-haspopup="true"
						aria-expanded={ isOpen }
					/>
				) }
				renderContent={ ( { onClose } ) => {
					const isStoryBlock = ( clientId ) => {
						const block = getBlock( clientId );
						return block && storyPageBlockName === block.name;
					};

					const onSelect = ( item ) => {
						const block = ! item ? createBlock( storyPageBlockName ) : getBlock( item.clientId );
						onClose();

						insertBlock( createSkeletonTemplate( block ) );
					};

					const storyTemplates = this.state.reusableBlocks.filter( ( { clientId } ) => isStoryBlock( clientId ) );

					return (

										<IconButton
											icon={ pageIcon( { width: 86, height: 96 } ) }
											label={ __( 'Blank Page', 'amp' ) }
											onClick={ () => {
												onSelect( null );
											} }
											className="amp-stories__blank-page-inserter editor-block-preview__content block-editor-block-preview__content editor-styles-wrapper"
										/>

									{ storyTemplates && storyTemplates.map( ( item ) => (
										<a // eslint-disable-line jsx-a11y/anchor-is-valid, see https://github.com/ampproject/amp-wp/issues/2165
											key={ `template-preview-${ item.id }` }
											role="button"
											tabIndex="0"
											onClick={ () => {
												onSelect( item );
											} }
											onKeyDown={ ( event ) => {
												if ( includes( [ ENTER, SPACE ], event.keyCode ) ) {
													onSelect( item );
												}
											} }
											className="components-button block-editor-block-preview"
										>
											<BlockPreview
												name="core/block"
												attributes={ { ref: item.id } }
											/>

									) ) }

					);
				} }
			/>
		);
	}
}

export default compose(
	withSelect( ( select ) => {
		const {
			__experimentalGetReusableBlocks: getReusableBlocks,
		} = select( 'core/editor' );

		const {
			getBlock,
			getBlocks,
		} = select( 'core/block-editor' );

		return {
			reusableBlocks: getReusableBlocks(),
			getBlock,
			allBlocks: getBlocks(),
		};
	} ),
	withDispatch( ( dispatch ) => {
		const {
			__experimentalFetchReusableBlocks: fetchReusableBlocks,
		} = dispatch( 'core/editor' );

		const { insertBlock } = dispatch( 'core/block-editor' );

		return {
			fetchReusableBlocks,
			insertBlock,
		};
	} )
)( TemplateInserter );
<|endoftext|>


<commit_before>const React = require('react');
const styled = require('styled-components').default;

const Code = require('../code');

module.exports = CodePane;

function CodePane(props) {
	return (
		<StyledCodePane className={props.className} hermit={props.hermit}>
			<StyledScrollbox>
				<Code block language="html">
					{props.source}
				</Code>
			</StyledScrollbox>
		</StyledCodePane
	);
}

const BORDER_RADIUS = 10;

const StyledCodePane = styled.div`
	position: relative;
	height: 100%;
	width: 100%;
	box-sizing: border-box;
	&::before {
		content: '';
		position: absolute;
		z-index: 1;
		top: 0;
		right: 0;
		bottom: 0;
		left: 0;
		border-radius: ${props => props.hermit ? `${BORDER_RADIUS}px` : `0 ${BORDER_RADIUS}px ${BORDER_RADIUS}px 0`};
		background: ${props => props.theme.background};
	}
`;

const StyledScrollbox = styled.div`
	position: relative;
	z-index: 2;
	height: 100%;
	width: 100%;
	box-sizing: border-box;
	overflow: scroll;
	-webkit-overflow-scrolling: touch;
`;
<commit_msg>fix: remove test fault<commit_after>const React = require('react');
const styled = require('styled-components').default;

const Code = require('../code');

module.exports = CodePane;

function CodePane(props) {
	return (
		<StyledCodePane className={props.className} hermit={props.hermit}>
			<StyledScrollbox>
				<Code block language="html">
					{props.source}
				</Code>
			</StyledScrollbox>
		</StyledCodePane>
	);
}

const BORDER_RADIUS = 10;

const StyledCodePane = styled.div`
	position: relative;
	height: 100%;
	width: 100%;
	box-sizing: border-box;
	&::before {
		content: '';
		position: absolute;
		z-index: 1;
		top: 0;
		right: 0;
		bottom: 0;
		left: 0;
		border-radius: ${props => props.hermit ? `${BORDER_RADIUS}px` : `0 ${BORDER_RADIUS}px ${BORDER_RADIUS}px 0`};
		background: ${props => props.theme.background};
	}
`;

const StyledScrollbox = styled.div`
	position: relative;
	z-index: 2;
	height: 100%;
	width: 100%;
	box-sizing: border-box;
	overflow: scroll;
	-webkit-overflow-scrolling: touch;
`;
<|endoftext|>


<commit_before>/**
 * @typedef {{status: string, test_results: [{status: string, title: string}]}} SubmissionResult
 */

/**
 * @typedef {{solution: object, result?: SubmissionResult}} Submission
 */

var mumuki = mumuki || {};

(function (mumuki) {
  var lastSubmission = {};

  function Laboratory(exerciseId){
    this.exerciseId = exerciseId;
  }

  function asString(json){
    return JSON.stringify(json);
  }

  function sameAsLastSolution(newSolution){
    return asString(lastSubmission.content) === asString(newSolution);
  }

  function lastSubmissionFinishedSuccessfully(){
    return lastSubmission.result && lastSubmission.result.status !== 'aborted';
  }

  function sendNewSolution(solution){
    var token = new mumuki.CsrfToken();
    var request = token.newRequest({
      type: 'POST',
      url: window.location.origin + window.location.pathname + '/solutions' + window.location.search,
      data: solution
    });

    return $.ajax(request).then(preRenderResult).done(function (result) {
      lastSubmission = { content: solution, result: result };
    });
  }

  /**
   * Pre-renders some html parts of submission UI
   * */
  function preRenderResult(result) {
    result.class_for_progress_list_item = mumuki.renderers.progressListItemClassForStatus(result.status, true)
    return result;
  }

  mumuki.load(function () {
    lastSubmission = {};
  });

  Laboratory.prototype = {

    // ==========
    // Public API
    // ==========

    /**
     * Runs tests for the current exercise using the given submission
     * content.
     *
     * @param {object} content the content object
     * */
    runTests: function(content) {
      return this._submitSolution({ solution: content });
    },

    // ===========
    // Private API
    // ===========

    /**
     * Sends a solution object
     *
     * @param {Submission} submission the submission object
     */
    _submitSolution: function (solution) {
      if(lastSubmissionFinishedSuccessfully() && sameAsLastSolution(solution)){
        return $.Deferred().resolve(lastSubmission.result);
      } else {
        return sendNewSolution(solution);
      }
    }
  };

  mumuki.bridge = {
    Laboratory: Laboratory
  };

}(mumuki));
<commit_msg>Actually passing result information<commit_after>/**
 * @typedef {{status: string, test_results: [{status: string, title: string}]}} SubmissionResult
 */

/**
 * @typedef {{solution: object, result?: SubmissionResult}} Submission
 */

var mumuki = mumuki || {};

(function (mumuki) {
  var lastSubmission = {};

  function Laboratory(exerciseId){
    this.exerciseId = exerciseId;
  }

  function asString(json){
    return JSON.stringify(json);
  }

  function sameAsLastSolution(newSolution){
    return asString(lastSubmission.content) === asString(newSolution);
  }

  function lastSubmissionFinishedSuccessfully(){
    return lastSubmission.result && lastSubmission.result.status !== 'aborted';
  }

  function sendNewSolution(submission){
    var token = new mumuki.CsrfToken();
    var request = token.newRequest({
      type: 'POST',
      url: window.location.origin + window.location.pathname + '/solutions' + window.location.search,
      data: submission
    });

    return $.ajax(request).then(preRenderResult).done(function (result) {
      lastSubmission = { content: {solution: submission.solution}, result: result };
    });
  }

  /**
   * Pre-renders some html parts of submission UI
   * */
  function preRenderResult(result) {
    result.class_for_progress_list_item = mumuki.renderers.progressListItemClassForStatus(result.status, true)
    return result;
  }

  mumuki.load(function () {
    lastSubmission = {};
  });

  Laboratory.prototype = {

    // ==========
    // Public API
    // ==========

    /**
     * Runs tests for the current exercise using the given submission
     * content.
     *
     * @param {object} content the content object
     * */
    runTests: function(content) {
      return this._submitSolution({ solution: content });
    },

    // ===========
    // Private API
    // ===========

    /**
     * Sends a solution object
     *
     * @param {Submission} submission the submission object
     */
    _submitSolution: function (submission) {
      if(lastSubmissionFinishedSuccessfully() && sameAsLastSolution(submission)){
        return $.Deferred().resolve(lastSubmission.result);
      } else {
        return sendNewSolution(submission);
      }
    }
  };

  mumuki.bridge = {
    Laboratory: Laboratory
  };

}(mumuki));
<|endoftext|>