changeset 219:1bd03ac62ee5

Merge pull request #1 from piper-audio/feature/list-analysis-cards Feature/list analysis cards
author Lucas Thompson <LucasThompson@users.noreply.github.com>
date Thu, 30 Mar 2017 16:03:59 +0100
parents a0b13b82cd72 (current diff) e72e74453581 (diff)
children 4f0e5032a240
files
diffstat 19 files changed, 1064 insertions(+), 598 deletions(-) [+]
line wrap: on
line diff
--- a/package.json	Tue Mar 28 13:40:56 2017 +0100
+++ b/package.json	Thu Mar 30 16:03:59 2017 +0100
@@ -8,7 +8,8 @@
     "lint": "tslint \"src/**/*.ts\"",
     "test": "ng test",
     "pree2e": "webdriver-manager update",
-    "e2e": "protractor"
+    "e2e": "protractor",
+    "build": "node build-prod.js"
   },
   "private": true,
   "dependencies": {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/app/analysis-item/analysis-item.component.css	Thu Mar 30 16:03:59 2017 +0100
@@ -0,0 +1,14 @@
+md-card {
+  padding-left: 0;
+  padding-right: 0;
+  width: 100%;
+}
+
+md-card-actions {
+  width: calc(100% - 16px);
+  padding-left: 16px;
+}
+
+md-card-header {
+  margin-bottom: 8px;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/app/analysis-item/analysis-item.component.html	Thu Mar 30 16:03:59 2017 +0100
@@ -0,0 +1,16 @@
+<md-card>
+  <md-card-header>
+    <md-card-title>{{title}}</md-card-title>
+    <md-card-subtitle>{{description}}</md-card-subtitle>
+  </md-card-header>
+  <md-card-content>
+    <app-waveform
+      [timeline]="timeline"
+      [trackIdPrefix]=" id || title"
+      [isSubscribedToAudioService]="isActive && isRoot"
+      [isSubscribedToExtractionService]="isActive && !isRoot"
+      [isOneShotExtractor]="true"
+      [isSeeking]="isActive"
+    ></app-waveform>
+  </md-card-content>
+</md-card>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/app/analysis-item/analysis-item.component.ts	Thu Mar 30 16:03:59 2017 +0100
@@ -0,0 +1,29 @@
+/**
+ * Created by lucast on 21/03/2017.
+ */
+import {Component, Input} from "@angular/core";
+import Waves from 'waves-ui';
+
+export interface AnalysisItem {
+  rootAudioUri: string;
+  hasSharedTimeline: boolean;
+  isRoot: boolean;
+  extractorKey: string;
+  title?: string;
+  description?: string;
+  id?: string;
+}
+
+@Component({
+  selector: 'ugly-analysis-item',
+  templateUrl: './analysis-item.component.html',
+  styleUrls: ['./analysis-item.component.css']
+})
+export class AnalysisItemComponent {
+  @Input() timeline: Timeline;
+  @Input() title: string;
+  @Input() description: string;
+  @Input() isActive: boolean;
+  @Input() isRoot: boolean;
+  @Input() id: string;
+}
--- a/src/app/app.component.css	Tue Mar 28 13:40:56 2017 +0100
+++ b/src/app/app.component.css	Thu Mar 30 16:03:59 2017 +0100
@@ -3,17 +3,31 @@
 }
 
 md-sidenav-container {
-  height: calc(100vh - 64px); /* height of window minus the toolbar height*/
+  height: 100%;
+  width: 100%;
+  position: absolute;
 }
 
 md-sidenav {
   text-align: center;
 }
 
-md-tab-group {
-  height: calc(100vh - 64px); /* height of window minus the toolbar height*/
+.app-container {
+  height: 100%;
+  display: flex;
+  flex-direction: column;
 }
 
-md-tab-group >>> .md-tab-body-wrapper {
-  align-items: center;
+.app-header {
+  flex: 0 0 auto;
 }
+
+.app-content {
+  flex: 1 1 auto;
+  overflow-y: auto;
+  position: relative;
+}
+
+.app-footer {
+  flex: 0 0 auto;
+}
--- a/src/app/app.component.html	Tue Mar 28 13:40:56 2017 +0100
+++ b/src/app/app.component.html	Thu Mar 30 16:03:59 2017 +0100
@@ -1,33 +1,51 @@
-<md-toolbar color="primary">
-  <md-icon svgIcon="duck"></md-icon>
+<div class="app-container">
+  <div class="app-header">
+    <md-toolbar color="primary">
+      <md-icon svgIcon="duck"></md-icon>
 
-  <span class="app-toolbar-filler"></span>
+      <span class="app-toolbar-filler"></span>
 
-  <app-playback-control></app-playback-control>
-  <ugly-recording-control
-    (finishedRecording)="onFileOpened($event)"
-  ></ugly-recording-control>
+      <app-playback-control></app-playback-control>
+      <ugly-recording-control
+        (finishedRecording)="onFileOpened($event)"
+      ></ugly-recording-control>
 
-  <!-- This fills the remaining space of the current row -->
-  <span class="app-toolbar-filler"></span>
+      <!-- This fills the remaining space of the current row -->
+      <span class="app-toolbar-filler"></span>
 
 
-  <app-audio-file-open (fileOpened)="onFileOpened($event)"></app-audio-file-open>
-  <!-- menu opens when trigger button is clicked -->
-  <button md-icon-button (click)="sidenav.toggle()">
-    <md-icon>extension</md-icon>
-  </button>
-</md-toolbar>
+      <app-audio-file-open
+        (fileOpened)="onFileOpened($event)"
+      ></app-audio-file-open>
+      <!-- menu opens when trigger button is clicked -->
+      <button md-icon-button (click)="sidenav.toggle()">
+        <md-icon>extension</md-icon>
+      </button>
+    </md-toolbar>
+  </div>
 
-<md-sidenav-container>
-  <md-sidenav #sidenav align="start" mode="over">
-    <app-feature-extraction-menu
-      (requestOutput)="extractFeatures($event)"
-      [disabled]="!canExtract">
-    </app-feature-extraction-menu>
-  </md-sidenav>
-  <app-waveform
-    [audioBuffer]="audioBuffer"
-    ></app-waveform>
-  <ugly-progress-spinner [isVisible]="isProcessing"></ugly-progress-spinner>
-</md-sidenav-container>
+  <div class="app-content">
+    <md-sidenav-container>
+      <md-sidenav #sidenav align="start" mode="over">
+        <app-feature-extraction-menu
+          (requestOutput)="extractFeatures($event)"
+          [disabled]="!canExtract">
+        </app-feature-extraction-menu>
+      </md-sidenav>
+      <ugly-notebook-feed
+        [analyses]="analyses"
+        [rootAudioUri]="rootAudioUri"></ugly-notebook-feed>
+    </md-sidenav-container>
+  </div>
+
+  <div class="app-footer">
+    <md-toolbar color="primary">
+
+      <!-- menu opens when trigger button is clicked -->
+      <button md-icon-button (click)="sidenav.toggle()">
+        <md-icon>extension</md-icon>
+      </button>
+    </md-toolbar>
+
+  </div>
+</div>
--- a/src/app/app.component.ts	Tue Mar 28 13:40:56 2017 +0100
+++ b/src/app/app.component.ts	Thu Mar 30 16:03:59 2017 +0100
@@ -1,62 +1,103 @@
-import {Component} from '@angular/core';
-import {AudioPlayerService} from "./services/audio-player/audio-player.service";
+import {Component, OnDestroy} from '@angular/core';
+import {
+  AudioPlayerService,
+  AudioResourceError, AudioResource
+} from "./services/audio-player/audio-player.service";
 import {FeatureExtractionService} from "./services/feature-extraction/feature-extraction.service";
 import {ExtractorOutputInfo} from "./feature-extraction-menu/feature-extraction-menu.component";
 import {DomSanitizer} from '@angular/platform-browser';
 import {MdIconRegistry} from '@angular/material';
+import {Subscription} from "rxjs";
+import {AnalysisItem} from "./analysis-item/analysis-item.component";
 
 @Component({
   selector: 'app-root',
   templateUrl: './app.component.html',
   styleUrls: ['./app.component.css']
 })
-export class AppComponent {
+export class AppComponent implements OnDestroy {
   audioBuffer: AudioBuffer; // TODO consider revising
   canExtract: boolean;
-  isProcessing: boolean;
+  private onAudioDataSubscription: Subscription;
+  private analyses: AnalysisItem[]; // TODO some immutable state container describing entire session
+  private nRecordings: number; // TODO user control for naming a recording
+  private countingId: number; // TODO improve uniquely identifying items
+  private rootAudioUri: string;
 
   constructor(private audioService: AudioPlayerService,
               private piperService: FeatureExtractionService,
               private iconRegistry: MdIconRegistry,
               private sanitizer: DomSanitizer) {
+    this.analyses = [];
     this.canExtract = false;
-    this.isProcessing = false;
+    this.nRecordings = 0;
+    this.countingId = 1;
+
     iconRegistry.addSvgIcon(
       'duck',
       sanitizer.bypassSecurityTrustResourceUrl('assets/duck.svg')
     );
+
+    this.onAudioDataSubscription = this.audioService.audioLoaded$.subscribe(
+      resource => {
+        const wasError = (resource as AudioResourceError).message != null;
+        if (wasError) {
+          this.analyses.shift();
+          this.canExtract = false;
+        } else {
+          this.audioBuffer = (resource as AudioResource).samples;
+          if (this.audioBuffer) {
+            this.canExtract = true;
+          }
+        }
+      }
+    );
   }
 
   onFileOpened(file: File | Blob) {
     this.canExtract = false;
-    this.isProcessing = true;
-    const reader: FileReader = new FileReader();
-    const mimeType = file.type;
-    reader.onload = (event: any) => {
-      const url: string = file instanceof Blob ? URL.createObjectURL(file) :
-        URL.createObjectURL(new Blob([event.target.result], {type: mimeType}));
-      this.audioService.loadAudioFromUrl(url);
-      // TODO use a rxjs/Subject instead?
-      this.audioService.decodeAudioData(event.target.result)
-        .then(audioBuffer => {
-        this.audioBuffer = audioBuffer;
-        if (this.audioBuffer) {
-          this.canExtract = true;
-          this.isProcessing = false;
-        }
-      }).catch(error => {
-        this.canExtract = false;
-        this.isProcessing = false;
-        console.warn(error);
-      });
-    };
-    reader.readAsArrayBuffer(file);
+    const url = this.audioService.loadAudio(file);
+    this.rootAudioUri = url; // TODO this isn't going to work to id previously loaded files
+
+    // TODO is it safe to assume it is a recording?
+    const title = (file instanceof File) ?
+      (file as File).name : `Recording ${this.nRecordings++}`;
+
+    if (this.analyses.filter(item => item.title === title).length > 0) {
+      // TODO this reveals how brittle the current name / uri based id is
+      // need something more robust, and also need to notify the user
+      // in a suitable way in the actual event of a duplicate file
+      console.warn('There is already a notebook based on this audio file.');
+      return;
+    }
+
+    // TODO re-ordering of items for display
+    // , one alternative is a Angular Pipe / Filter for use in the Template
+    this.analyses.unshift({
+      rootAudioUri: url,
+      hasSharedTimeline: true,
+      extractorKey: 'not:real',
+      isRoot: true,
+      title: title,
+      description: new Date().toLocaleString(),
+      id: `${this.countingId++}`
+    });
   }
 
   extractFeatures(outputInfo: ExtractorOutputInfo): void {
     if (!this.canExtract || !outputInfo) return;
     this.canExtract = false;
-    this.isProcessing = true;
+
+    this.analyses.unshift({
+      rootAudioUri: this.rootAudioUri,
+      hasSharedTimeline: true,
+      extractorKey: outputInfo.combinedKey,
+      isRoot: false,
+      title: outputInfo.name,
+      description: outputInfo.outputId,
+      id: `${this.countingId++}`
+    });
+
     this.piperService.collect({
       audioData: [...Array(this.audioBuffer.numberOfChannels).keys()]
         .map(i => this.audioBuffer.getChannelData(i)),
@@ -68,11 +109,13 @@
       outputId: outputInfo.outputId
     }).then(() => {
       this.canExtract = true;
-      this.isProcessing = false;
     }).catch(err => {
       this.canExtract = true;
-      this.isProcessing = false;
       console.error(err)
     });
   }
+
+  ngOnDestroy(): void {
+    this.onAudioDataSubscription.unsubscribe();
+  }
 }
--- a/src/app/app.module.ts	Tue Mar 28 13:40:56 2017 +0100
+++ b/src/app/app.module.ts	Thu Mar 30 16:03:59 2017 +0100
@@ -8,7 +8,10 @@
 import { WaveformComponent } from './waveform/waveform.component';
 import { AudioFileOpenComponent } from './audio-file-open/audio-file-open.component';
 import { PlaybackControlComponent } from './playback-control/playback-control.component';
-import { AudioPlayerService } from "./services/audio-player/audio-player.service";
+import {
+  AudioPlayerService,
+  UrlResourceLifetimeManager, ResourceReader
+} from "./services/audio-player/audio-player.service";
 import { FeatureExtractionService } from "./services/feature-extraction/feature-extraction.service";
 import { FeatureExtractionMenuComponent } from "./feature-extraction-menu/feature-extraction-menu.component";
 import { ProgressSpinnerComponent } from "./progress-spinner/progress-spinner.component";
@@ -21,6 +24,8 @@
   ThrowingMediaRecorder,
 } from "./services/audio-recorder/audio-recorder.service";
 import {RecordingControlComponent} from "./recording-control/recording-control.component";
+import {NotebookFeedComponent} from "./notebook-feed/notebook-feed.component";
+import {AnalysisItemComponent} from "./analysis-item/analysis-item.component";
 
 export function createAudioContext(): AudioContext {
   return new (
@@ -59,6 +64,32 @@
   }
 }
 
+export function createUrlResourceManager(): UrlResourceLifetimeManager {
+  return {
+    createUrlToResource: (resource: File | Blob): string => {
+      return URL.createObjectURL(resource);
+    },
+    revokeUrlToResource: (url: string) => {
+      URL.revokeObjectURL(url);
+    }
+  };
+}
+
+export function createResourceReader(): ResourceReader{
+  return (resource) => {
+    return new Promise((res, rej) => {
+      const reader: FileReader = new FileReader();
+      reader.onload = (event: any) => {
+        res(event.target.result);
+      };
+      reader.onerror = (event) => {
+        rej(event.message);
+      };
+      reader.readAsArrayBuffer(resource);
+    });
+  };;
+}
+
 @NgModule({
   declarations: [
     AppComponent,
@@ -67,7 +98,9 @@
     PlaybackControlComponent,
     RecordingControlComponent,
     FeatureExtractionMenuComponent,
-    ProgressSpinnerComponent
+    ProgressSpinnerComponent,
+    AnalysisItemComponent,
+    NotebookFeedComponent
   ],
   imports: [
     BrowserModule,
@@ -83,7 +116,9 @@
     AudioRecorderService,
     FeatureExtractionService,
     {provide: 'MediaRecorderFactory', useFactory: createMediaRecorderFactory},
-    {provide: 'PiperRepoUri', useValue: 'assets/remote-plugins.json'}
+    {provide: 'PiperRepoUri', useValue: 'assets/remote-plugins.json'},
+    {provide: 'UrlResourceLifetimeManager', useFactory: createUrlResourceManager},
+    {provide: 'ResourceReader', useFactory: createResourceReader}
   ],
   bootstrap: [AppComponent]
 })
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/app/notebook-feed/notebook-feed.component.css	Thu Mar 30 16:03:59 2017 +0100
@@ -0,0 +1,3 @@
+.break {
+  margin-bottom: 32px;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/app/notebook-feed/notebook-feed.component.html	Thu Mar 30 16:03:59 2017 +0100
@@ -0,0 +1,12 @@
+
+<template ngFor let-item [ngForOf]="analyses">
+  <div [class.break]="item.isRoot">
+      <ugly-analysis-item
+        [timeline]="item.hasSharedTimeline ? sharedTimeline : undefined"
+        [title]="item.title"
+        [description]="item.description"
+        [isActive]="rootAudioUri === item.rootAudioUri"
+        [isRoot]="item.isRoot"
+        [id]="item.id"></ugly-analysis-item>
+  </div>
+</template>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/app/notebook-feed/notebook-feed.component.ts	Thu Mar 30 16:03:59 2017 +0100
@@ -0,0 +1,34 @@
+/**
+ * Created by lucast on 21/03/2017.
+ */
+import {Component, Input} from "@angular/core";
+import Waves from 'waves-ui';
+import {AnalysisItem} from "../analysis-item/analysis-item.component";
+
+@Component({
+  selector: 'ugly-notebook-feed',
+  templateUrl: './notebook-feed.component.html',
+  styleUrls: ['./notebook-feed.component.css']
+})
+export class NotebookFeedComponent {
+  sharedTimeline: Timeline;
+  @Input() analyses: AnalysisItem[];
+  @Input() set rootAudioUri(uri: string) {
+    this._rootAudioUri = uri;
+
+    // TODO is this safe? will the fact references are held elsewhere
+    // keep the previous instance alive? Or will it get garbage collected in
+    // screw previous layers up?
+    this.sharedTimeline = new Waves.core.Timeline();
+  }
+
+  get rootAudioUri(): string {
+    return this._rootAudioUri;
+  }
+  private _rootAudioUri: string;
+
+  constructor() {
+    this.sharedTimeline = new Waves.core.Timeline();
+    this.analyses = [];
+  }
+}
--- a/src/app/progress-spinner/progress-spinner.component.ts	Tue Mar 28 13:40:56 2017 +0100
+++ b/src/app/progress-spinner/progress-spinner.component.ts	Thu Mar 30 16:03:59 2017 +0100
@@ -19,8 +19,8 @@
       height: 40px;
       width: 40px;
       position: absolute;
-      top: calc(100% - 40px);
-      left: calc(100% - 40px);
+      top: calc(50% - 20px);
+      left: calc(50% - 20px);
     }
     
     .spinner {
--- a/src/app/services/audio-player/audio-player.service.ts	Tue Mar 28 13:40:56 2017 +0100
+++ b/src/app/services/audio-player/audio-player.service.ts	Thu Mar 30 16:03:59 2017 +0100
@@ -2,6 +2,25 @@
 import {Subject} from "rxjs/Subject";
 import {Observable} from "rxjs";
 
+export interface UrlResourceLifetimeManager {
+  createUrlToResource(resource: File | Blob): string;
+  revokeUrlToResource(url: string): void;
+}
+
+export type ResourceReader = (resource: File | Blob) => Promise<ArrayBuffer>;
+
+export interface AudioResource {
+  samples: AudioBuffer;
+  url: string;
+  mimeType: string;
+}
+
+export interface AudioResourceError {
+  message: string;
+}
+
+export type AudioLoadResponse = AudioResource | AudioResourceError;
+
 @Injectable()
 export class AudioPlayerService {
 
@@ -10,9 +29,15 @@
   playingStateChange$: Observable<boolean>;
   private seeked: Subject<number>;
   seeked$: Observable<number>;
+  private audioLoaded: Subject<AudioLoadResponse>;
+  audioLoaded$: Observable<AudioLoadResponse>;
 
   constructor(@Inject(HTMLAudioElement) private audioElement: HTMLAudioElement /* TODO probably shouldn't play audio this way */,
-              @Inject('AudioContext') private audioContext: AudioContext) {
+              @Inject('AudioContext') private audioContext: AudioContext,
+              @Inject('ResourceReader') private readResource: ResourceReader,
+              @Inject(
+                'UrlResourceLifetimeManager'
+              ) private resourceManager: UrlResourceLifetimeManager) {
     this.currentObjectUrl = '';
     this.playingStateChange = new Subject<boolean>();
     this.playingStateChange$ = this.playingStateChange.asObservable();
@@ -24,6 +49,8 @@
     this.audioElement.addEventListener('seeked', () => {
       this.seeked.next(this.audioElement.currentTime);
     });
+    this.audioLoaded = new Subject<AudioLoadResponse>();
+    this.audioLoaded$ = this.audioLoaded.asObservable();
   }
 
   getCurrentTime(): number {
@@ -34,17 +61,43 @@
     return !this.audioElement.paused;
   }
 
-  decodeAudioData(buffer: ArrayBuffer): Promise<AudioBuffer> {
-    return new Promise((res, rej) => this.audioContext.decodeAudioData(buffer, res, rej));
-  }
 
-  loadAudioFromUrl(url: string): void {
+  loadAudio(resource: File | Blob): string {
     if (this.currentObjectUrl)
-      URL.revokeObjectURL(this.currentObjectUrl);
+      this.resourceManager.revokeUrlToResource(this.currentObjectUrl);
+    const url: string = this.resourceManager.createUrlToResource(resource);
     this.currentObjectUrl = url;
     this.audioElement.pause();
     this.audioElement.src = url;
     this.audioElement.load();
+
+    const decode: (buffer: ArrayBuffer) => Promise<AudioBuffer> = buffer => {
+      try {
+        return this.audioContext.decodeAudioData(buffer) as Promise<AudioBuffer>;
+      } catch (e) {
+        console.warn('Falling back to callback style decodeAudioData');
+        return new Promise(
+          (res, rej) => this.audioContext.decodeAudioData(buffer, res, rej)
+        );
+      }
+    };
+
+    this.readResource(resource)
+      .then(decode)
+      .then(val => {
+        this.audioLoaded.next({
+          samples: val,
+          url: url,
+          mimeType: resource.type
+        });
+      })
+      .catch(err => {
+        const message = err && err.message ? err.message : "Read error";
+        this.audioLoaded.next({
+          message: message
+        });
+      });
+    return url;
   }
 
   togglePlaying(): void {
--- a/src/app/waveform/waveform.component.css	Tue Mar 28 13:40:56 2017 +0100
+++ b/src/app/waveform/waveform.component.css	Thu Mar 30 16:03:59 2017 +0100
@@ -1,3 +1,4 @@
 .track {
-  height: 100%;
+  height: 160px;
+  width: 100%;
 }
--- a/src/app/waveform/waveform.component.html	Tue Mar 28 13:40:56 2017 +0100
+++ b/src/app/waveform/waveform.component.html	Thu Mar 30 16:03:59 2017 +0100
@@ -2,3 +2,6 @@
   #track class="track"
   (mousedown)="seekStart()"
   (mouseup)="seekEnd($event.clientX)"></div>
+<template [ngIf]="isLoading">
+  <ugly-progress-spinner [isVisible]="true"></ugly-progress-spinner>
+</template>
--- a/src/app/waveform/waveform.component.ts	Tue Mar 28 13:40:56 2017 +0100
+++ b/src/app/waveform/waveform.component.ts	Thu Mar 30 16:03:59 2017 +0100
@@ -2,7 +2,10 @@
   Component, OnInit, ViewChild, ElementRef, Input, AfterViewInit, NgZone,
   OnDestroy
 } from '@angular/core';
-import {AudioPlayerService} from "../services/audio-player/audio-player.service";
+import {
+  AudioPlayerService, AudioResource,
+  AudioResourceError
+} from "../services/audio-player/audio-player.service";
 import wavesUI from 'waves-ui';
 import {
   FeatureExtractionService
@@ -17,7 +20,6 @@
 import * as Hammer from 'hammerjs';
 import {WavesSpectrogramLayer} from "../spectrogram/Spectrogram";
 
-type Timeline = any; // TODO what type actually is it.. start a .d.ts for waves-ui?
 type Layer = any;
 type Track = any;
 type Colour = string;
@@ -31,16 +33,119 @@
 
   @ViewChild('track') trackDiv: ElementRef;
 
-  private _audioBuffer: AudioBuffer;
-  private timeline: Timeline;
-  private cursorLayer: any;
+  @Input() timeline: Timeline;
+  @Input() trackIdPrefix: string;
+  @Input() set isSubscribedToExtractionService(isSubscribed: boolean) {
+    if (isSubscribed) {
+      if (this.featureExtractionSubscription) {
+        return;
+      }
 
-  @Input()
+      const colours = function* () {
+        const circularColours = [
+          'black',
+          'red',
+          'green',
+          'purple',
+          'orange'
+        ];
+        let index = 0;
+        const nColours = circularColours.length;
+        while (true) {
+          yield circularColours[index = ++index % nColours];
+        }
+      }();
+
+      this.featureExtractionSubscription =
+        this.piperService.featuresExtracted$.subscribe(
+          features => {
+            this.renderFeatures(features, colours.next().value);
+          });
+    } else {
+      if (this.featureExtractionSubscription) {
+        this.featureExtractionSubscription.unsubscribe();
+      }
+    }
+  }
+  @Input() set isSubscribedToAudioService(isSubscribed: boolean) {
+    this._isSubscribedToAudioService = isSubscribed;
+    if (isSubscribed) {
+      if (this.onAudioDataSubscription) {
+        return;
+      }
+
+      this.onAudioDataSubscription =
+        this.audioService.audioLoaded$.subscribe(res => {
+          const wasError = (res as AudioResourceError).message != null;
+
+          if (wasError) {
+            console.warn('No audio, display error?');
+          } else {
+            this.audioBuffer = (res as AudioResource).samples;
+          }
+        });
+    } else {
+      if (this.onAudioDataSubscription) {
+        this.onAudioDataSubscription.unsubscribe();
+      }
+    }
+  }
+
+  get isSubscribedToAudioService(): boolean {
+    return this._isSubscribedToAudioService;
+  }
+
+  @Input() set isOneShotExtractor(isOneShot: boolean) {
+    this._isOneShotExtractor = isOneShot;
+  }
+
+  get isOneShotExtractor(): boolean {
+    return this._isOneShotExtractor;
+  }
+
+  @Input() set isSeeking(isSeeking: boolean) {
+    this._isSeeking = isSeeking;
+    if (isSeeking) {
+      if (this.seekedSubscription) {
+        return;
+      }
+      if(this.playingStateSubscription) {
+        return;
+      }
+
+      this.seekedSubscription = this.audioService.seeked$.subscribe(() => {
+        if (!this.isPlaying)
+          this.animate();
+      });
+      this.playingStateSubscription =
+        this.audioService.playingStateChange$.subscribe(
+          isPlaying => {
+            this.isPlaying = isPlaying;
+            if (this.isPlaying)
+              this.animate();
+          });
+    } else {
+      if (this.isPlaying) {
+        this.isPlaying = false;
+      }
+      if (this.playingStateSubscription) {
+        this.playingStateSubscription.unsubscribe();
+      }
+      if (this.seekedSubscription) {
+        this.seekedSubscription.unsubscribe();
+      }
+    }
+  }
+
+  get isSeeking(): boolean {
+    return this._isSeeking;
+  }
+
   set audioBuffer(buffer: AudioBuffer) {
     this._audioBuffer = buffer || undefined;
     if (this.audioBuffer) {
       this.renderWaveform(this.audioBuffer);
-      this.renderSpectrogram(this.audioBuffer);
+      // this.renderSpectrogram(this.audioBuffer);
     }
   }
 
@@ -48,71 +153,197 @@
     return this._audioBuffer;
   }
 
+  private _audioBuffer: AudioBuffer;
+  private _isSubscribedToAudioService: boolean;
+  private _isOneShotExtractor: boolean;
+  private _isSeeking: boolean;
+  private cursorLayer: any;
+  private layers: Layer[];
   private featureExtractionSubscription: Subscription;
   private playingStateSubscription: Subscription;
   private seekedSubscription: Subscription;
+  private onAudioDataSubscription: Subscription;
   private isPlaying: boolean;
-  private offsetAtPanStart: number;
-  private initialZoom: number;
-  private initialDistance: number;
   private zoomOnMouseDown: number;
   private offsetOnMouseDown: number;
+  private hasShot: boolean;
+  private isLoading: boolean;
 
   constructor(private audioService: AudioPlayerService,
               private piperService: FeatureExtractionService,
               public ngZone: NgZone) {
-    this._audioBuffer = undefined;
+    this.isSubscribedToAudioService = true;
+    this.isSeeking = true;
+    this.layers = [];
+    this.audioBuffer = undefined;
     this.timeline = undefined;
     this.cursorLayer = undefined;
     this.isPlaying = false;
-    const colours = function* () {
-      const circularColours = [
-        'black',
-        'red',
-        'green',
-        'purple',
-        'orange'
-      ];
-      let index = 0;
-      const nColours = circularColours.length;
-      while (true) {
-        yield circularColours[index = ++index % nColours];
-      }
-    }();
-
-    this.featureExtractionSubscription = piperService.featuresExtracted$.subscribe(
-      features => {
-        this.renderFeatures(features, colours.next().value);
-      });
-    this.playingStateSubscription = audioService.playingStateChange$.subscribe(
-      isPlaying => {
-        this.isPlaying = isPlaying;
-        if (this.isPlaying)
-          this.animate();
-      });
-    this.seekedSubscription = audioService.seeked$.subscribe(() => {
-      if (!this.isPlaying)
-        this.animate();
-    });
+    this.isLoading = true;
   }
 
   ngOnInit() {
   }
 
   ngAfterViewInit(): void {
-    this.timeline = this.renderTimeline();
+    this.trackIdPrefix = this.trackIdPrefix || "default";
+    if (this.timeline) {
+      this.renderTimeline(null, true, true);
+    } else {
+      this.renderTimeline();
+    }
   }
 
-  renderTimeline(duration: number = 1.0): Timeline {
+  renderTimeline(duration: number = 1.0,
+                 useExistingDuration: boolean = false,
+                 isInitialRender: boolean = false): Timeline {
     const track: HTMLElement = this.trackDiv.nativeElement;
     track.innerHTML = "";
     const height: number = track.getBoundingClientRect().height;
     const width: number = track.getBoundingClientRect().width;
     const pixelsPerSecond = width / duration;
-    const timeline = new wavesUI.core.Timeline(pixelsPerSecond, width);
-    timeline.createTrack(track, height/2, 'wave');
-    timeline.createTrack(track, height/2, 'grid');
-    return timeline;
+    const hasExistingTimeline = this.timeline instanceof wavesUI.core.Timeline;
+
+    if (hasExistingTimeline) {
+      if (!useExistingDuration) {
+        this.timeline.pixelsPerSecond = pixelsPerSecond;
+        this.timeline.visibleWidth = width;
+      }
+    } else {
+      this.timeline = new wavesUI.core.Timeline(pixelsPerSecond, width);
+    }
+    const waveTrack = this.timeline.createTrack(
+      track,
+      height,
+      `wave-${this.trackIdPrefix}`
+    );
+    if (isInitialRender && hasExistingTimeline) {
+      // time axis
+      const timeAxis = new wavesUI.helpers.TimeAxisLayer({
+        height: height,
+        color: '#b0b0b0'
+      });
+      this.addLayer(timeAxis, waveTrack, this.timeline.timeContext, true);
+      this.cursorLayer = new wavesUI.helpers.CursorLayer({
+        height: height
+      });
+      this.addLayer(this.cursorLayer, waveTrack, this.timeline.timeContext);
+    }
+    if ('ontouchstart' in window) {
+      interface Point {
+        x: number;
+        y: number;
+      }
+
+      let zoomGestureJustEnded: boolean = false;
+
+      const pixelToExponent: Function = wavesUI.utils.scales.linear()
+        .domain([0, 100]) // 100px => factor 2
+        .range([0, 1]);
+
+      const calculateDistance: (p1: Point, p2: Point) => number = (p1, p2) => {
+        return Math.pow(
+          Math.pow(p2.x - p1.x, 2) +
+          Math.pow(p2.y - p1.y, 2), 0.5);
+      };
+
+      const calculateMidPoint: (p1: Point, p2: Point) => Point = (p1, p2) => {
+        return {
+          x: 0.5 * (p1.x + p2.x),
+          y: 0.5 * (p1.y + p2.y)
+        };
+      };
+
+      const hammertime = new Hammer.Manager(this.trackDiv.nativeElement, {
+        recognizers: [
+          [Hammer.Pan, { direction: Hammer.DIRECTION_HORIZONTAL }]
+        ]
+      });
+
+      // it seems HammerJs binds the event to the window?
+      // causing these events to propagate to other components?
+      const componentTimeline = this.timeline;
+      let initialZoom;
+      let initialDistance;
+      let offsetAtPanStart;
+      let startX;
+      let isZooming;
+
+      const scroll = (ev) => {
+        if (ev.center.x - startX === 0) return;
+        if (zoomGestureJustEnded) {
+          zoomGestureJustEnded = false;
+          console.log("Skip this event: likely a single touch dangling from pinch");
+          return;
+        }
+        componentTimeline.timeContext.offset = offsetAtPanStart +
+          componentTimeline.timeContext.timeToPixel.invert(ev.deltaX);
+        componentTimeline.tracks.update();
+      };
+
+      const zoom = (ev) => {
+        if (ev.touches.length < 2) return;
+        ev.preventDefault();
+        const minZoom = componentTimeline.state.minZoom;
+        const maxZoom = componentTimeline.state.maxZoom;
+        const p1: Point = {
+          x: ev.touches[0].clientX,
+          y: ev.touches[0].clientY
+        };
+        const p2: Point = {
+          x: ev.touches[1].clientX,
+          y: ev.touches[1].clientY
+        };
+        const distance = calculateDistance(p1, p2);
+        const midPoint = calculateMidPoint(p1, p2);
+
+        const lastCenterTime =
+          componentTimeline.timeContext.timeToPixel.invert(midPoint.x);
+
+        const exponent = pixelToExponent(distance - initialDistance);
+        const targetZoom = initialZoom * Math.pow(2, exponent);
+
+        componentTimeline.timeContext.zoom =
+          Math.min(Math.max(targetZoom, minZoom), maxZoom);
+
+        const newCenterTime =
+          componentTimeline.timeContext.timeToPixel.invert(midPoint.x);
+
+        componentTimeline.timeContext.offset += newCenterTime - lastCenterTime;
+        componentTimeline.tracks.update();
+      };
+      hammertime.on('panstart', (ev) => {
+        offsetAtPanStart = componentTimeline.timeContext.offset;
+        startX = ev.center.x;
+      });
+      hammertime.on('panleft', scroll);
+      hammertime.on('panright', scroll);
+
+
+      const element: HTMLElement = this.trackDiv.nativeElement;
+      element.addEventListener('touchstart', (e) => {
+        if (e.touches.length < 2) return;
+        isZooming = true;
+        initialZoom = componentTimeline.timeContext.zoom;
+
+        initialDistance = calculateDistance({
+          x: e.touches[0].clientX,
+          y: e.touches[0].clientY
+        }, {
+          x: e.touches[1].clientX,
+          y: e.touches[1].clientY
+        });
+      });
+      element.addEventListener('touchend', () => {
+        if (isZooming) {
+          isZooming = false;
+          zoomGestureJustEnded = true;
+        }
+       });
+      element.addEventListener('touchmove', zoom);
+    }
+    // this.timeline.createTrack(track, height/2, `wave-${this.trackIdPrefix}`);
+    // this.timeline.createTrack(track, height/2, `grid-${this.trackIdPrefix}`);
   }
 
   estimatePercentile(matrix, percentile) {
@@ -239,20 +470,23 @@
       const trackLayers = Array.from(track.layers);
       while (trackLayers.length) {
         let layer: Layer = trackLayers.pop();
-        track.remove(layer);
-
-        const index = timeContextChildren.indexOf(layer.timeContext);
-        if (index >= 0) {
-          timeContextChildren.splice(index, 1);
+        if (this.layers.includes(layer)) {
+          track.remove(layer);
+          this.layers.splice(this.layers.indexOf(layer), 1);
+          const index = timeContextChildren.indexOf(layer.timeContext);
+          if (index >= 0) {
+            timeContextChildren.splice(index, 1);
+          }
+          layer.destroy();
         }
-        layer.destroy();
       }
     }
   }
 
   renderWaveform(buffer: AudioBuffer): void {
-    const height: number = this.trackDiv.nativeElement.getBoundingClientRect().height / 2;
-    const waveTrack = this.timeline.getTrackById('wave');
+    // const height: number = this.trackDiv.nativeElement.getBoundingClientRect().height / 2;
+    const height: number = this.trackDiv.nativeElement.getBoundingClientRect().height;
+    const waveTrack = this.timeline.getTrackById(`wave-${this.trackIdPrefix}`);
     if (this.timeline) {
       // resize
       const width = this.trackDiv.nativeElement.getBoundingClientRect().width;
@@ -263,7 +497,7 @@
       this.timeline.pixelsPerSecond = width / buffer.duration;
       waveTrack.height = height;
     } else {
-      this.timeline = this.renderTimeline(buffer.duration)
+      this.renderTimeline(buffer.duration)
     }
     this.timeline.timeContext.offset = 0.5 * this.timeline.timeContext.visibleDuration;
 
@@ -277,7 +511,7 @@
     const nchannels = buffer.numberOfChannels;
     const totalWaveHeight = height * 0.9;
     const waveHeight = totalWaveHeight / nchannels;
-    
+
     for (let ch = 0; ch < nchannels; ++ch) {
       console.log("about to construct a waveform layer for channel " + ch);
       const waveformLayer = new wavesUI.helpers.WaveformLayer(buffer, {
@@ -297,92 +531,13 @@
     waveTrack.render();
     waveTrack.update();
 
-
-    if ('ontouchstart' in window) {
-      interface Point {
-        x: number;
-        y: number;
-      }
-
-      let zoomGestureJustEnded: boolean = false;
-
-      const pixelToExponent: Function = wavesUI.utils.scales.linear()
-        .domain([0, 100]) // 100px => factor 2
-        .range([0, 1]);
-
-      const calculateDistance: (p1: Point, p2: Point) => number = (p1, p2) => {
-        return Math.pow(
-          Math.pow(p2.x - p1.x, 2) +
-          Math.pow(p2.y - p1.y, 2), 0.5);
-      };
-
-      const hammertime = new Hammer(this.trackDiv.nativeElement);
-      const scroll = (ev) => {
-        if (zoomGestureJustEnded) {
-          zoomGestureJustEnded = false;
-          console.log("Skip this event: likely a single touch dangling from pinch");
-          return;
-        }
-        this.timeline.timeContext.offset = this.offsetAtPanStart +
-          this.timeline.timeContext.timeToPixel.invert(ev.deltaX);
-        this.timeline.tracks.update();
-      };
-
-      const zoom = (ev) => {
-        const minZoom = this.timeline.state.minZoom;
-        const maxZoom = this.timeline.state.maxZoom;
-        const distance = calculateDistance({
-          x: ev.pointers[0].clientX,
-          y: ev.pointers[0].clientY
-        }, {
-          x: ev.pointers[1].clientX,
-          y: ev.pointers[1].clientY
-        });
-
-        const lastCenterTime =
-          this.timeline.timeContext.timeToPixel.invert(ev.center.x);
-
-        const exponent = pixelToExponent(distance - this.initialDistance);
-        const targetZoom = this.initialZoom * Math.pow(2, exponent);
-
-        this.timeline.timeContext.zoom =
-          Math.min(Math.max(targetZoom, minZoom), maxZoom);
-
-        const newCenterTime =
-          this.timeline.timeContext.timeToPixel.invert(ev.center.x);
-
-        this.timeline.timeContext.offset += newCenterTime - lastCenterTime;
-        this.timeline.tracks.update();
-      };
-      hammertime.get('pinch').set({ enable: true });
-      hammertime.on('panstart', () => {
-        this.offsetAtPanStart = this.timeline.timeContext.offset;
-      });
-      hammertime.on('panleft', scroll);
-      hammertime.on('panright', scroll);
-      hammertime.on('pinchstart', (e) => {
-        this.initialZoom = this.timeline.timeContext.zoom;
-
-        this.initialDistance = calculateDistance({
-          x: e.pointers[0].clientX,
-          y: e.pointers[0].clientY
-        }, {
-          x: e.pointers[1].clientX,
-          y: e.pointers[1].clientY
-        });
-      });
-      hammertime.on('pinch', zoom);
-      hammertime.on('pinchend', () => {
-        zoomGestureJustEnded = true;
-      });
-    }
-
+    this.isLoading = false;
     this.animate();
   }
 
   renderSpectrogram(buffer: AudioBuffer): void {
     const height: number = this.trackDiv.nativeElement.getBoundingClientRect().height / 2;
-    const gridTrack = this.timeline.getTrackById('grid');
+    const gridTrack = this.timeline.getTrackById(`grid-${this.trackIdPrefix}`);
 
     const spectrogramLayer = new WavesSpectrogramLayer(buffer, {
       top: height * 0.05,
@@ -399,12 +554,18 @@
 
   // TODO refactor - this doesn't belong here
   private renderFeatures(extracted: SimpleResponse, colour: Colour): void {
+    if (this.isOneShotExtractor && !this.hasShot) {
+      this.featureExtractionSubscription.unsubscribe();
+      this.hasShot = true;
+    }
+
     if (!extracted.hasOwnProperty('features') || !extracted.hasOwnProperty('outputDescriptor')) return;
     if (!extracted.features.hasOwnProperty('shape') || !extracted.features.hasOwnProperty('data')) return;
     const features: FeatureCollection = (extracted.features as FeatureCollection);
     const outputDescriptor = extracted.outputDescriptor;
-    const height = this.trackDiv.nativeElement.getBoundingClientRect().height / 2;
-    const waveTrack = this.timeline.getTrackById('wave');
+    // const height = this.trackDiv.nativeElement.getBoundingClientRect().height / 2;
+    const height = this.trackDiv.nativeElement.getBoundingClientRect().height;
+    const waveTrack = this.timeline.getTrackById(`wave-${this.trackIdPrefix}`);
 
     // TODO refactor all of this
     switch (features.shape) {
@@ -573,10 +734,13 @@
                     features.shape + "'");
     }
 
+    this.isLoading = false;
     this.timeline.tracks.update();
   }
 
   private animate(): void {
+    if (!this.isSeeking) return;
+
     this.ngZone.runOutsideAngular(() => {
       // listen for time passing...
       const updateSeekingCursor = () => {
@@ -625,6 +789,7 @@
         timeContext : new wavesUI.core.LayerTimeContext(timeContext));
     }
     track.add(layer);
+    this.layers.push(layer);
     layer.render();
     layer.update();
     if (this.cursorLayer && track.$layout.contains(this.cursorLayer.$el)) {
@@ -646,9 +811,14 @@
   }
 
   ngOnDestroy(): void {
-    this.featureExtractionSubscription.unsubscribe();
-    this.playingStateSubscription.unsubscribe();
-    this.seekedSubscription.unsubscribe();
+    if (this.featureExtractionSubscription)
+      this.featureExtractionSubscription.unsubscribe();
+    if (this.playingStateSubscription)
+      this.playingStateSubscription.unsubscribe();
+    if (this.seekedSubscription)
+      this.seekedSubscription.unsubscribe();
+    if (this.onAudioDataSubscription)
+      this.onAudioDataSubscription.unsubscribe();
   }
 
   seekStart(): void {
@@ -669,9 +839,11 @@
   seek(x: number): void {
     if (this.timeline) {
       const timeContext: any = this.timeline.timeContext;
-      this.audioService.seekTo(
-        timeContext.timeToPixel.invert(x)- timeContext.offset
-      );
+      if (this.isSeeking) {
+        this.audioService.seekTo(
+          timeContext.timeToPixel.invert(x)- timeContext.offset
+        );
+      }
     }
   }
 }
--- a/src/app/waveform/waves-ui.d.ts	Tue Mar 28 13:40:56 2017 +0100
+++ b/src/app/waveform/waves-ui.d.ts	Thu Mar 30 16:03:59 2017 +0100
@@ -101,4 +101,22 @@
   Layer: LayerConstructor;
   LayerTimeContext: any; // TODO
   Timeline: any; // TODO
+  TimelineTimeContext: TimelineTimeContextConstructor;
 }
+
+type Timeline = any;
+
+interface TimelineTimeContext {
+  pixelsPerSecond: number;
+  readonly computedPixelsPerSecond: number;
+  offset: number;
+  zoom: number;
+  visibleWidth: number;
+  readonly visibleDuration: number;
+  maintainVisibleDuration: boolean;
+  timeToPixel: (time: number) => number;
+}
+
+interface TimelineTimeContextConstructor {
+  new(pixelsPerSecond: number, visibleWidth: number): TimelineTimeContext;
+}
--- a/src/styles.css	Tue Mar 28 13:40:56 2017 +0100
+++ b/src/styles.css	Thu Mar 30 16:03:59 2017 +0100
@@ -27,4 +27,10 @@
 body {
   margin: 0;
   font-family: Roboto, sans-serif;
+  overflow: hidden;
+  height: 100%;
 }
+
+html {
+  height: 100%;
+}
--- a/yarn.lock	Tue Mar 28 13:40:56 2017 +0100
+++ b/yarn.lock	Thu Mar 30 16:03:59 2017 +0100
@@ -1,11 +1,13 @@
 # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
 # yarn lockfile v1
+
+
 "@angular/cli@^1.0.0-rc.2":
-  version "1.0.0-rc.4"
-  resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-1.0.0-rc.4.tgz#e542f95378fd996fb0bc3b9d686bf3a457fe61b4"
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-1.0.0.tgz#7bfde1e7c5f28bf5bed4dda1352ee67ee887302f"
   dependencies:
     "@ngtools/json-schema" "1.0.5"
-    "@ngtools/webpack" "1.2.14"
+    "@ngtools/webpack" "1.3.0"
     autoprefixer "^6.5.3"
     chalk "^1.1.3"
     common-tags "^1.3.1"
@@ -54,7 +56,7 @@
     stylus "^0.54.5"
     stylus-loader "^2.4.0"
     temp "0.8.3"
-    typescript ">=2.0.0 <2.2.0"
+    typescript ">=2.0.0 <2.3.0"
     url-loader "^0.5.7"
     walk-sync "^0.3.1"
     webpack "~2.2.0"
@@ -118,9 +120,9 @@
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/@ngtools/json-schema/-/json-schema-1.0.5.tgz#ad39037c70c88b245ac7267a71777646b6063d77"
 
-"@ngtools/webpack@1.2.14":
-  version "1.2.14"
-  resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-1.2.14.tgz#ff59f504196871e69b5d01a21ea747d6f8e5e546"
+"@ngtools/webpack@1.3.0":
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-1.3.0.tgz#a1071230985358ecdf87b2fa9879ae6cc6355e83"
   dependencies:
     enhanced-resolve "^3.1.0"
     loader-utils "^1.0.2"
@@ -140,8 +142,8 @@
   resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-2.5.38.tgz#a4379124c4921d4e21de54ec74669c9e9b356717"
 
 "@types/node@^6.0.46", "@types/node@~6.0.60":
-  version "6.0.65"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.65.tgz#c00faa7ffcfc9842b5dd7bf650872562504d5670"
+  version "6.0.68"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.68.tgz#0c43b6b8b9445feb86a0fbd3457e3f4bc591e66d"
 
 "@types/q@^0.0.32":
   version "0.0.32"
@@ -151,11 +153,18 @@
   version "2.53.42"
   resolved "https://registry.yarnpkg.com/@types/selenium-webdriver/-/selenium-webdriver-2.53.42.tgz#74cb77fb6052edaff2a8984ddafd88d419f25cac"
 
+JSONStream@^1.0.3:
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.1.tgz#707f761e01dae9e16f1bcf93703b78c70966579a"
+  dependencies:
+    jsonparse "^1.2.0"
+    through ">=2.2.7 <3"
+
 abbrev@1:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.0.tgz#d0554c2256636e2f56e7c2e5ad183f859428d81f"
 
-accepts@~1.3.3, accepts@1.3.3:
+accepts@1.3.3, accepts@~1.3.3:
   version "1.3.3"
   resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.3.tgz#c3ca7434938648c3e0d9c1e328dd68b622c284ca"
   dependencies:
@@ -172,14 +181,14 @@
   version "4.0.11"
   resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.11.tgz#edcda3bd937e7556410d42ed5860f67399c794c0"
 
+adm-zip@0.4.4:
+  version "0.4.4"
+  resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.4.4.tgz#a61ed5ae6905c3aea58b3a657d25033091052736"
+
 adm-zip@^0.4.7:
   version "0.4.7"
   resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.4.7.tgz#8606c2cbf1c426ce8c8ec00174447fd49b6eafc1"
 
-adm-zip@0.4.4:
-  version "0.4.4"
-  resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.4.4.tgz#a61ed5ae6905c3aea58b3a657d25033091052736"
-
 after@0.8.2:
   version "0.8.2"
   resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f"
@@ -356,14 +365,14 @@
   version "0.2.3"
   resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86"
 
+assert-plus@1.0.0, assert-plus@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
+
 assert-plus@^0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234"
 
-assert-plus@^1.0.0, assert-plus@1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
-
 assert@^1.1.1, assert@^1.4.0:
   version "1.4.1"
   resolved "https://registry.yarnpkg.com/assert/-/assert-1.4.1.tgz#99912d591836b5a6f5b345c0f07eefc08fc65d91"
@@ -404,13 +413,13 @@
   version "0.9.2"
   resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d"
 
-async@^1.4.0, async@^1.4.2, async@^1.5.2:
+async@^1.4.0, async@^1.5.2:
   version "1.5.2"
   resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
 
 async@^2.0.1, async@^2.1.2, async@^2.1.4:
-  version "2.1.5"
-  resolved "https://registry.yarnpkg.com/async/-/async-2.1.5.tgz#e587c68580994ac67fc56ff86d3ac56bdbe810bc"
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/async/-/async-2.2.0.tgz#c324eba010a237e4fbd55a12dee86367d5c0ef32"
   dependencies:
     lodash "^4.14.0"
 
@@ -1165,9 +1174,9 @@
   version "6.0.2"
   resolved "https://registry.yarnpkg.com/browser-pack/-/browser-pack-6.0.2.tgz#f86cd6cef4f5300c8e63e07a4d512f65fbff4531"
   dependencies:
+    JSONStream "^1.0.3"
     combine-source-map "~0.7.1"
     defined "^1.0.0"
-    JSONStream "^1.0.3"
     through2 "^2.0.0"
     umd "^3.0.0"
 
@@ -1211,8 +1220,8 @@
     randombytes "^2.0.1"
 
 browserify-sign@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.0.0.tgz#10773910c3c206d5420a46aad8694f820b85968f"
+  version "4.0.4"
+  resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.0.4.tgz#aa4eb68e5d7b658baa6bf6a57e630cbd7a93d298"
   dependencies:
     bn.js "^4.1.1"
     browserify-rsa "^4.0.0"
@@ -1232,6 +1241,7 @@
   version "13.3.0"
   resolved "https://registry.yarnpkg.com/browserify/-/browserify-13.3.0.tgz#b5a9c9020243f0c70e4675bec8223bc627e415ce"
   dependencies:
+    JSONStream "^1.0.3"
     assert "^1.4.0"
     browser-pack "^6.0.1"
     browser-resolve "^1.11.0"
@@ -1253,7 +1263,6 @@
     https-browserify "~0.0.0"
     inherits "~2.0.1"
     insert-module-globals "^7.0.0"
-    JSONStream "^1.0.3"
     labeled-stream-splicer "^2.0.0"
     module-deps "^4.0.8"
     os-browserify "~0.1.1"
@@ -1284,6 +1293,7 @@
   version "14.1.0"
   resolved "https://registry.yarnpkg.com/browserify/-/browserify-14.1.0.tgz#0508cc1e7bf4c152312c2fa523e676c0b0b92311"
   dependencies:
+    JSONStream "^1.0.3"
     assert "^1.4.0"
     browser-pack "^6.0.1"
     browser-resolve "^1.11.0"
@@ -1305,7 +1315,6 @@
     https-browserify "~0.0.0"
     inherits "~2.0.1"
     insert-module-globals "^7.0.0"
-    JSONStream "^1.0.3"
     labeled-stream-splicer "^2.0.0"
     module-deps "^4.0.8"
     os-browserify "~0.1.1"
@@ -1413,8 +1422,8 @@
   resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a"
 
 camelcase@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.0.0.tgz#8b0f90d44be5e281b903b9887349b92595ef07f2"
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd"
 
 caniuse-api@^1.5.2:
   version "1.5.3"
@@ -1426,8 +1435,8 @@
     lodash.uniq "^4.3.0"
 
 caniuse-db@^1.0.30000346, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639:
-  version "1.0.30000639"
-  resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000639.tgz#5982f70a54352adaf8901a772d2c68ca24f501aa"
+  version "1.0.30000646"
+  resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000646.tgz#c724b90d61df24286e015fc528d062073c00def4"
 
 capture-stack-trace@^1.0.0:
   version "1.0.0"
@@ -1482,8 +1491,8 @@
     chalk "^1.1.3"
 
 clean-css@4.0.x:
-  version "4.0.9"
-  resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.0.9.tgz#63ff450b3f939508cc0cd2989bb9daaedc98333e"
+  version "4.0.10"
+  resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.0.10.tgz#6be448d6ba8c767654ebe11f158b97a887cb713f"
   dependencies:
     source-map "0.5.x"
 
@@ -1585,7 +1594,7 @@
     css-color-names "0.0.4"
     has "^1.0.1"
 
-colors@^1.1.0, colors@^1.1.2, colors@~1.1.2, colors@1.1.2:
+colors@1.1.2, colors@^1.1.0, colors@^1.1.2, colors@~1.1.2:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63"
 
@@ -1610,7 +1619,7 @@
   dependencies:
     delayed-stream "~1.0.0"
 
-commander@^2.5.0, commander@^2.6.0, commander@^2.8.1, commander@2.9.x:
+commander@2.9.x, commander@^2.5.0, commander@^2.6.0, commander@^2.8.1:
   version "2.9.0"
   resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4"
   dependencies:
@@ -1653,10 +1662,10 @@
   resolved "https://registry.yarnpkg.com/component-inherit/-/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143"
 
 compressible@~2.0.8:
-  version "2.0.9"
-  resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.9.tgz#6daab4e2b599c2770dd9e21e7a891b1c5a755425"
-  dependencies:
-    mime-db ">= 1.24.0 < 2"
+  version "2.0.10"
+  resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.10.tgz#feda1c7f7617912732b29bf8cf26252a20b9eecd"
+  dependencies:
+    mime-db ">= 1.27.0 < 2"
 
 compression@^1.5.2:
   version "1.6.2"
@@ -1705,7 +1714,7 @@
     parseurl "~1.3.1"
     utils-merge "1.0.0"
 
-console-browserify@^1.1.0, console-browserify@1.1.x:
+console-browserify@1.1.x, console-browserify@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.1.0.tgz#f0241c45730a9fc6323b206dbf38edc741d0bb10"
   dependencies:
@@ -1728,8 +1737,8 @@
   resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.2.tgz#b7d113aee7a8dd27bd21133c4dc2529df1721eed"
 
 convert-source-map@^1.1.0, convert-source-map@^1.3.0:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.4.0.tgz#e3dad195bf61bfe13a7a3c73e9876ec14a0268f3"
+  version "1.5.0"
+  resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.0.tgz#9acd70851c6d5dfdd93d9282e5edf94a03ff46b5"
 
 convert-source-map@~1.1.0:
   version "1.1.3"
@@ -1887,7 +1896,7 @@
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-0.1.0.tgz#c814903e45623371a0477b40109aaafbeeaddbb4"
 
-cssnano@^3.10.0, "cssnano@>=2.6.1 <4":
+"cssnano@>=2.6.1 <4", cssnano@^3.10.0:
   version "3.10.0"
   resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-3.10.0.tgz#4f38f6cea2b9b17fa01490f23f1dc68ea65c1c38"
   dependencies:
@@ -1951,13 +1960,13 @@
   version "0.1.4"
   resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
 
-debug@*, debug@^2.1.1, debug@^2.1.3, debug@^2.2.0, debug@2:
+debug@*, debug@2, debug@^2.1.1, debug@^2.1.3, debug@^2.2.0:
   version "2.6.3"
   resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.3.tgz#0f7eb8c30965ec08c72accfa0130c8b79984141d"
   dependencies:
     ms "0.7.2"
 
-debug@~2.2.0, debug@2.2.0:
+debug@2.2.0, debug@~2.2.0:
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da"
   dependencies:
@@ -2032,7 +2041,7 @@
   version "1.2.1"
   resolved "https://registry.yarnpkg.com/denodeify/-/denodeify-1.2.1.tgz#3a36287f5034e699e7577901052c2e6c94251631"
 
-depd@~1.1.0, depd@1.1.0:
+depd@1.1.0, depd@~1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.0.tgz#e1bd82c6aab6ced965b97b88b17ed3e528ca18c3"
 
@@ -2127,14 +2136,14 @@
   version "1.1.7"
   resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.1.7.tgz#867aa4b093faa05f1de08c06f4d7b21fdf8698bc"
 
+domelementtype@1:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.0.tgz#b17aed82e8ab59e52dd9c19b1756e0fc187204c2"
+
 domelementtype@~1.1.1:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b"
 
-domelementtype@1:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.0.tgz#b17aed82e8ab59e52dd9c19b1756e0fc187204c2"
-
 domhandler@2.1:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.1.0.tgz#d2646f5e57f6c3bab11cf6cb05d3c0acf7412594"
@@ -2187,8 +2196,8 @@
   resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
 
 electron-to-chromium@^1.2.7:
-  version "1.2.7"
-  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.2.7.tgz#4f748061407e478c76256d04496972b71f647407"
+  version "1.3.2"
+  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.2.tgz#b8ce5c93b308db0e92f6d0435c46ddec8f6363ab"
 
 elliptic@^6.0.0:
   version "6.4.0"
@@ -2276,14 +2285,14 @@
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/ent/-/ent-2.2.0.tgz#e964219325a21d05f44466a2f686ed6ce5f5dd1d"
 
+entities@1.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/entities/-/entities-1.0.0.tgz#b2987aa3821347fcde642b24fdfc9e4fb712bf26"
+
 entities@~1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0"
 
-entities@1.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/entities/-/entities-1.0.0.tgz#b2987aa3821347fcde642b24fdfc9e4fb712bf26"
-
 errno@^0.1.1, errno@^0.1.3:
   version "0.1.4"
   resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.4.tgz#b896e23a9e5e8ba33871fc996abd3635fc9a1c7d"
@@ -2355,7 +2364,7 @@
     path-key "^1.0.0"
     strip-eof "^1.0.0"
 
-exit@^0.1.2, exit@0.1.2, exit@0.1.x:
+exit@0.1.2, exit@0.1.x, exit@^0.1.2:
   version "0.1.2"
   resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c"
 
@@ -2426,7 +2435,7 @@
     utils-merge "1.0.0"
     vary "~1.1.0"
 
-extend@^3.0.0, extend@~3.0.0, extend@3:
+extend@3, extend@^3.0.0, extend@~3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.0.tgz#5a474353b9f3353ddd8176dfd37b91c83a46f1d4"
 
@@ -2504,7 +2513,7 @@
     repeat-element "^1.1.2"
     repeat-string "^1.5.2"
 
-finalhandler@~1.0.0, finalhandler@1.0.0:
+finalhandler@1.0.0, finalhandler@~1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.0.0.tgz#b5691c2c0912092f18ac23e9416bde5cd7dc6755"
   dependencies:
@@ -2675,19 +2684,9 @@
   dependencies:
     is-glob "^2.0.0"
 
-glob@^5.0.15, glob@^5.0.5, glob@~5.0.0:
-  version "5.0.15"
-  resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1"
-  dependencies:
-    inflight "^1.0.4"
-    inherits "2"
-    minimatch "2 || 3"
-    once "^1.3.0"
-    path-is-absolute "^1.0.0"
-
-glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.0.6, glob@^7.1.0, glob@^7.1.1, glob@~7.1.1:
-  version "7.1.1"
-  resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8"
+glob@7.0.x:
+  version "7.0.6"
+  resolved "https://registry.yarnpkg.com/glob/-/glob-7.0.6.tgz#211bafaf49e525b8cd93260d14ab136152b3f57a"
   dependencies:
     fs.realpath "^1.0.0"
     inflight "^1.0.4"
@@ -2696,9 +2695,19 @@
     once "^1.3.0"
     path-is-absolute "^1.0.0"
 
-glob@7.0.x:
-  version "7.0.6"
-  resolved "https://registry.yarnpkg.com/glob/-/glob-7.0.6.tgz#211bafaf49e525b8cd93260d14ab136152b3f57a"
+glob@^5.0.15, glob@^5.0.5, glob@~5.0.0:
+  version "5.0.15"
+  resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1"
+  dependencies:
+    inflight "^1.0.4"
+    inherits "2"
+    minimatch "2 || 3"
+    once "^1.3.0"
+    path-is-absolute "^1.0.0"
+
+glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.0.6, glob@^7.1.0, glob@^7.1.1, glob@~7.1.1:
+  version "7.1.1"
+  resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8"
   dependencies:
     fs.realpath "^1.0.0"
     inflight "^1.0.4"
@@ -2712,8 +2721,8 @@
   resolved "https://registry.yarnpkg.com/globals/-/globals-6.4.1.tgz#8498032b3b6d1cc81eebc5f79690d8fe29fabf4f"
 
 globals@^9.0.0:
-  version "9.16.0"
-  resolved "https://registry.yarnpkg.com/globals/-/globals-9.16.0.tgz#63e903658171ec2d9f51b1d31de5e2b8dc01fb80"
+  version "9.17.0"
+  resolved "https://registry.yarnpkg.com/globals/-/globals-9.17.0.tgz#0c0ca696d9b9bb694d2e5470bd37777caad50286"
 
 globby@^5.0.0:
   version "5.0.0"
@@ -2871,8 +2880,8 @@
     os-tmpdir "^1.0.1"
 
 hosted-git-info@^2.1.4:
-  version "2.3.1"
-  resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.3.1.tgz#ac439421605f0beb0ea1349de7d8bb28e50be1dd"
+  version "2.4.1"
+  resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.4.1.tgz#4b0445e41c004a8bd1337773a4ff790ca40318c8"
 
 hpack.js@^2.1.6:
   version "2.1.6"
@@ -2919,15 +2928,6 @@
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/htmlescape/-/htmlescape-1.1.1.tgz#3a03edc2214bca3b66424a3e7959349509cb0351"
 
-htmlparser2@~3.3.0:
-  version "3.3.0"
-  resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.3.0.tgz#cc70d05a59f6542e43f0e685c982e14c924a9efe"
-  dependencies:
-    domelementtype "1"
-    domhandler "2.1"
-    domutils "1.1"
-    readable-stream "1.0"
-
 htmlparser2@3.8.x:
   version "3.8.3"
   resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.8.3.tgz#996c28b191516a8be86501a7d79757e5c70c1068"
@@ -2938,6 +2938,15 @@
     entities "1.0"
     readable-stream "1.1"
 
+htmlparser2@~3.3.0:
+  version "3.3.0"
+  resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.3.0.tgz#cc70d05a59f6542e43f0e685c982e14c924a9efe"
+  dependencies:
+    domelementtype "1"
+    domhandler "2.1"
+    domutils "1.1"
+    readable-stream "1.0"
+
 http-deceiver@^1.2.4:
   version "1.2.7"
   resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87"
@@ -2983,7 +2992,7 @@
     jsprim "^1.2.2"
     sshpk "^1.7.0"
 
-https-browserify@~0.0.0, https-browserify@0.0.1:
+https-browserify@0.0.1, https-browserify@~0.0.0:
   version "0.0.1"
   resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-0.0.1.tgz#3f91365cabe60b77ed0ebba24b454e3e09d95a82"
 
@@ -2995,7 +3004,7 @@
     debug "2"
     extend "3"
 
-iconv-lite@^0.4.5, iconv-lite@0.4.15:
+iconv-lite@0.4.15, iconv-lite@^0.4.5:
   version "0.4.15"
   resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.15.tgz#fe265a218ac6a57cfe854927e9d04c19825eddeb"
 
@@ -3050,7 +3059,7 @@
     once "^1.3.0"
     wrappy "1"
 
-inherits@^2.0.1, inherits@~2.0.0, inherits@~2.0.1, inherits@2, inherits@2.0.3:
+inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@~2.0.0, inherits@~2.0.1:
   version "2.0.3"
   resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
 
@@ -3090,18 +3099,18 @@
   version "7.0.1"
   resolved "https://registry.yarnpkg.com/insert-module-globals/-/insert-module-globals-7.0.1.tgz#c03bf4e01cb086d5b5e5ace8ad0afe7889d638c3"
   dependencies:
+    JSONStream "^1.0.3"
     combine-source-map "~0.7.1"
     concat-stream "~1.5.1"
     is-buffer "^1.1.0"
-    JSONStream "^1.0.3"
     lexical-scope "^1.2.0"
     process "~0.11.0"
     through2 "^2.0.0"
     xtend "^4.0.0"
 
 interpret@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.1.tgz#d579fb7f693b858004947af39fa0db49f795602c"
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.2.tgz#f4f623f0bb7122f15f5717c8e254b8161b5c5b2d"
 
 invariant@^2.2.0:
   version "2.2.2"
@@ -3113,9 +3122,9 @@
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
 
-ipaddr.js@1.2.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.2.0.tgz#8aba49c9192799585bdd643e0ccb50e8ae777ba4"
+ipaddr.js@1.3.0:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.3.0.tgz#1e03a52fdad83a8bbb2b25cbf4998b4cffcd3dec"
 
 is-absolute-url@^2.0.0:
   version "2.1.0"
@@ -3273,21 +3282,21 @@
   version "0.2.1"
   resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72"
 
-isarray@^1.0.0, isarray@~1.0.0, isarray@1.0.0:
+isarray@0.0.1, isarray@~0.0.1:
+  version "0.0.1"
+  resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
+
+isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
 
-isarray@~0.0.1, isarray@0.0.1:
-  version "0.0.1"
-  resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
-
 isbinaryfile@^3.0.0:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-3.0.2.tgz#4a3e974ec0cba9004d3fc6cde7209ea69368a621"
 
-isexe@^1.1.1:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/isexe/-/isexe-1.1.2.tgz#36f3e22e60750920f5e7241a476a8c6a42275ad0"
+isexe@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
 
 isobject@^2.0.0:
   version "2.1.0"
@@ -3300,17 +3309,17 @@
   resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
 
 istanbul-api@^1.1.1:
-  version "1.1.5"
-  resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-1.1.5.tgz#3ddb38f11183196da8f6e5c90c938cd3917b16a7"
+  version "1.1.7"
+  resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-1.1.7.tgz#f6f37f09f8002b130f891c646b70ee4a8e7345ae"
   dependencies:
     async "^2.1.4"
     fileset "^2.0.2"
-    istanbul-lib-coverage "^1.0.0"
-    istanbul-lib-hook "^1.0.4"
-    istanbul-lib-instrument "^1.6.1"
-    istanbul-lib-report "^1.0.0-alpha.3"
-    istanbul-lib-source-maps "^1.1.0"
-    istanbul-reports "^1.0.0"
+    istanbul-lib-coverage "^1.0.2"
+    istanbul-lib-hook "^1.0.5"
+    istanbul-lib-instrument "^1.7.0"
+    istanbul-lib-report "^1.0.0"
+    istanbul-lib-source-maps "^1.1.1"
+    istanbul-reports "^1.0.2"
     js-yaml "^3.7.0"
     mkdirp "^0.5.1"
     once "^1.4.0"
@@ -3324,51 +3333,49 @@
     loader-utils "^0.2.16"
     object-assign "^4.1.0"
 
-istanbul-lib-coverage@^1.0.0, istanbul-lib-coverage@^1.0.0-alpha, istanbul-lib-coverage@^1.0.0-alpha.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.0.1.tgz#f263efb519c051c5f1f3343034fc40e7b43ff212"
-
-istanbul-lib-hook@^1.0.4:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-1.0.4.tgz#1919debbc195807880041971caf9c7e2be2144d6"
+istanbul-lib-coverage@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.0.2.tgz#87a0c015b6910651cb3b184814dfb339337e25e1"
+
+istanbul-lib-hook@^1.0.5:
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-1.0.5.tgz#6ca3d16d60c5f4082da39f7c5cd38ea8a772b88e"
   dependencies:
     append-transform "^0.4.0"
 
-istanbul-lib-instrument@^1.1.3, istanbul-lib-instrument@^1.6.1:
-  version "1.6.1"
-  resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.6.1.tgz#6c9c3191ebd5aa856d66dc2f0b2f719c3732de2d"
+istanbul-lib-instrument@^1.1.3, istanbul-lib-instrument@^1.7.0:
+  version "1.7.0"
+  resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.7.0.tgz#b8e0dc25709bb44e17336ab47b7bb5c97c23f659"
   dependencies:
     babel-generator "^6.18.0"
     babel-template "^6.16.0"
     babel-traverse "^6.18.0"
     babel-types "^6.18.0"
     babylon "^6.13.0"
-    istanbul-lib-coverage "^1.0.0"
+    istanbul-lib-coverage "^1.0.2"
     semver "^5.3.0"
 
-istanbul-lib-report@^1.0.0-alpha.3:
-  version "1.0.0-alpha.3"
-  resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-1.0.0-alpha.3.tgz#32d5f6ec7f33ca3a602209e278b2e6ff143498af"
-  dependencies:
-    async "^1.4.2"
-    istanbul-lib-coverage "^1.0.0-alpha"
+istanbul-lib-report@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-1.0.0.tgz#d83dac7f26566b521585569367fe84ccfc7aaecb"
+  dependencies:
+    istanbul-lib-coverage "^1.0.2"
     mkdirp "^0.5.1"
     path-parse "^1.0.5"
-    rimraf "^2.4.3"
     supports-color "^3.1.2"
 
-istanbul-lib-source-maps@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.1.0.tgz#9d429218f35b823560ea300a96ff0c3bbdab785f"
-  dependencies:
-    istanbul-lib-coverage "^1.0.0-alpha.0"
+istanbul-lib-source-maps@^1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.1.1.tgz#f8c8c2e8f2160d1d91526d97e5bd63b2079af71c"
+  dependencies:
+    istanbul-lib-coverage "^1.0.2"
     mkdirp "^0.5.1"
     rimraf "^2.4.4"
     source-map "^0.5.3"
 
-istanbul-reports@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.0.1.tgz#9a17176bc4a6cbebdae52b2f15961d52fa623fbc"
+istanbul-reports@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.0.2.tgz#4e8366abe6fa746cc1cd6633f108de12cc6ac6fa"
   dependencies:
     handlebars "^4.0.3"
 
@@ -3404,14 +3411,14 @@
   version "2.1.9"
   resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.1.9.tgz#f0e80ae039a4bd654b5f281fc93f04a914a7fcce"
 
+js-tokens@1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-1.0.1.tgz#cc435a5c8b94ad15acb7983140fc80182c89aeae"
+
 js-tokens@^3.0.0:
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.1.tgz#08e9f132484a2c45a30907e9dc4d5567b7f114d7"
 
-js-tokens@1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-1.0.1.tgz#cc435a5c8b94ad15acb7983140fc80182c89aeae"
-
 js-yaml@^3.7.0:
   version "3.8.2"
   resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.8.2.tgz#02d3e2c0f6beab20248d412c352203827d786721"
@@ -3475,7 +3482,7 @@
   version "5.0.1"
   resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
 
-json3@^3.3.2, json3@3.3.2:
+json3@3.3.2, json3@^3.3.2:
   version "3.3.2"
   resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1"
 
@@ -3501,13 +3508,6 @@
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.0.tgz#85fc245b1d9259acc6941960b905adf64e7de0e8"
 
-JSONStream@^1.0.3:
-  version "1.3.1"
-  resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.1.tgz#707f761e01dae9e16f1bcf93703b78c70966579a"
-  dependencies:
-    jsonparse "^1.2.0"
-    through ">=2.2.7 <3"
-
 jsprim@^1.2.2:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.0.tgz#a3b87e40298d8c380552d8cc7628a0bb95a22918"
@@ -3609,10 +3609,10 @@
     stream-splicer "^2.0.0"
 
 latest-version@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-3.0.0.tgz#3104f008c0c391084107f85a344bc61e38970649"
-  dependencies:
-    package-json "^3.0.0"
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-3.1.0.tgz#a205383fea322b33b5ae3b18abee0dc2f356ee15"
+  dependencies:
+    package-json "^4.0.0"
 
 lazy-cache@^1.0.3:
   version "1.0.4"
@@ -3716,6 +3716,10 @@
   version "4.5.0"
   resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
 
+lodash@3.7.x:
+  version "3.7.0"
+  resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.7.0.tgz#3678bd8ab995057c07ade836ed2ef087da811d45"
+
 lodash@^3.10.0, lodash@^3.2.0, lodash@^3.8.0, lodash@^3.9.3:
   version "3.10.1"
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6"
@@ -3728,10 +3732,6 @@
   version "4.16.6"
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.16.6.tgz#d22c9ac660288f3843e16ba7d2b5d06cca27d777"
 
-lodash@3.7.x:
-  version "3.7.0"
-  resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.7.0.tgz#3678bd8ab995057c07ade836ed2ef087da811d45"
-
 log4js@^0.6.31:
   version "0.6.38"
   resolved "https://registry.yarnpkg.com/log4js/-/log4js-0.6.38.tgz#2c494116695d6fb25480943d3fc872e662a522fd"
@@ -3764,6 +3764,10 @@
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306"
 
+lru-cache@2.2.x:
+  version "2.2.4"
+  resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.2.4.tgz#6c658619becf14031d0d0b594b16042ce4dc063d"
+
 lru-cache@^4.0.0, lru-cache@^4.0.1:
   version "4.0.2"
   resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.0.2.tgz#1d17679c069cda5d040991a09dbc2c0db377e55e"
@@ -3771,10 +3775,6 @@
     pseudomap "^1.0.1"
     yallist "^2.0.0"
 
-lru-cache@2.2.x:
-  version "2.2.4"
-  resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.2.4.tgz#6c658619becf14031d0d0b594b16042ce4dc063d"
-
 macaddress@^0.2.8:
   version "0.2.8"
   resolved "https://registry.yarnpkg.com/macaddress/-/macaddress-0.2.8.tgz#5904dc537c39ec6dbefeae902327135fa8511f12"
@@ -3862,21 +3862,17 @@
     bn.js "^4.0.0"
     brorand "^1.0.1"
 
-"mime-db@>= 1.24.0 < 2":
+"mime-db@>= 1.27.0 < 2", mime-db@~1.27.0:
   version "1.27.0"
   resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.27.0.tgz#820f572296bbd20ec25ed55e5b5de869e5436eb1"
 
-mime-db@~1.26.0:
-  version "1.26.0"
-  resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.26.0.tgz#eaffcd0e4fc6935cf8134da246e2e6c35305adff"
-
 mime-types@^2.1.12, mime-types@~2.1.11, mime-types@~2.1.13, mime-types@~2.1.7:
-  version "2.1.14"
-  resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.14.tgz#f7ef7d97583fcaf3b7d282b6f8b5679dab1e94ee"
-  dependencies:
-    mime-db "~1.26.0"
-
-mime@^1.2.11, mime@^1.3.4, mime@1.3.4, mime@1.3.x:
+  version "2.1.15"
+  resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.15.tgz#a4ebf5064094569237b8cf70046776d09fc92aed"
+  dependencies:
+    mime-db "~1.27.0"
+
+mime@1.3.4, mime@1.3.x, mime@^1.2.11, mime@^1.3.4:
   version "1.3.4"
   resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53"
 
@@ -3892,17 +3888,21 @@
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
 
+"minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@~3.0.2:
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.3.tgz#2a4e4090b96b2db06a9d7df01055a62a77c9b774"
+  dependencies:
+    brace-expansion "^1.0.0"
+
 minimatch@^2.0.3:
   version "2.0.10"
   resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-2.0.10.tgz#8d087c39c6b38c001b97fca7ce6d0e1e80afbac7"
   dependencies:
     brace-expansion "^1.0.0"
 
-minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@~3.0.2, "minimatch@2 || 3":
-  version "3.0.3"
-  resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.3.tgz#2a4e4090b96b2db06a9d7df01055a62a77c9b774"
-  dependencies:
-    brace-expansion "^1.0.0"
+minimist@0.0.8:
+  version "0.0.8"
+  resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
 
 minimist@^1.1.0, minimist@^1.1.3, minimist@^1.2.0:
   version "1.2.0"
@@ -3912,11 +3912,7 @@
   version "0.0.10"
   resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf"
 
-minimist@0.0.8:
-  version "0.0.8"
-  resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
-
-mkdirp@^0.5.0, mkdirp@^0.5.1, "mkdirp@>=0.5 0", mkdirp@~0.5.0, mkdirp@~0.5.1, mkdirp@0.5.x:
+mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1:
   version "0.5.1"
   resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
   dependencies:
@@ -3926,6 +3922,7 @@
   version "4.1.1"
   resolved "https://registry.yarnpkg.com/module-deps/-/module-deps-4.1.1.tgz#23215833f1da13fd606ccb8087b44852dcb821fd"
   dependencies:
+    JSONStream "^1.0.3"
     browser-resolve "^1.7.0"
     cached-path-relative "^1.0.0"
     concat-stream "~1.5.0"
@@ -3933,7 +3930,6 @@
     detective "^4.0.0"
     duplexer2 "^0.1.2"
     inherits "^2.0.1"
-    JSONStream "^1.0.3"
     parents "^1.0.0"
     readable-stream "^2.0.2"
     resolve "^1.1.3"
@@ -4039,8 +4035,8 @@
     tar-pack "^3.4.0"
 
 node-sass@^4.3.0:
-  version "4.5.0"
-  resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.5.0.tgz#532e37bad0ce587348c831535dbc98ea4289508b"
+  version "4.5.2"
+  resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.5.2.tgz#4012fa2bd129b1d6365117e88d9da0500d99da64"
   dependencies:
     async-foreach "^0.1.3"
     chalk "^1.1.1"
@@ -4057,7 +4053,7 @@
     nan "^2.3.2"
     node-gyp "^3.3.1"
     npmlog "^4.0.0"
-    request "^2.61.0"
+    request "^2.79.0"
     sass-graph "^2.1.1"
     stdout-stream "^1.4.0"
 
@@ -4065,6 +4061,12 @@
   version "0.4.1"
   resolved "https://registry.yarnpkg.com/node-watch/-/node-watch-0.4.1.tgz#d0947d54a995f91135db4056b68722c6d7c322ad"
 
+"nopt@2 || 3":
+  version "3.0.6"
+  resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9"
+  dependencies:
+    abbrev "1"
+
 nopt@^4.0.1:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d"
@@ -4072,12 +4074,6 @@
     abbrev "1"
     osenv "^0.1.4"
 
-"nopt@2 || 3":
-  version "3.0.6"
-  resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9"
-  dependencies:
-    abbrev "1"
-
 normalize-package-data@^2.3.2, normalize-package-data@^2.3.4:
   version "2.3.6"
   resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.3.6.tgz#498fa420c96401f787402ba21e600def9f981fff"
@@ -4088,8 +4084,10 @@
     validate-npm-package-license "^3.0.1"
 
 normalize-path@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.0.1.tgz#47886ac1662760d4261b7d979d241709d3ce3f7a"
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9"
+  dependencies:
+    remove-trailing-separator "^1.0.1"
 
 normalize-range@^0.1.2:
   version "0.1.2"
@@ -4110,7 +4108,7 @@
   dependencies:
     path-key "^1.0.0"
 
-npmlog@^4.0.0, npmlog@^4.0.2, "npmlog@0 || 1 || 2 || 3 || 4":
+"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.0, npmlog@^4.0.2:
   version "4.0.2"
   resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.0.2.tgz#d03950e0e78ce1527ba26d2a7592e9348ac3e75f"
   dependencies:
@@ -4141,14 +4139,14 @@
   version "0.8.2"
   resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43"
 
+object-assign@4.1.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.0.tgz#7a3b3d0e98063d43f4c03f2e8ae6cd51a86883a0"
+
 object-assign@^4.0.0, object-assign@^4.0.1, object-assign@^4.1.0:
   version "4.1.1"
   resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
 
-object-assign@4.1.0:
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.0.tgz#7a3b3d0e98063d43f4c03f2e8ae6cd51a86883a0"
-
 object-component@0.0.3:
   version "0.0.3"
   resolved "https://registry.yarnpkg.com/object-component/-/object-component-0.0.3.tgz#f0c69aa50efc95b866c186f400a33769cb2f1291"
@@ -4238,7 +4236,7 @@
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
 
-osenv@^0.1.4, osenv@0:
+osenv@0, osenv@^0.1.4:
   version "0.1.4"
   resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.4.tgz#42fe6d5953df06c8064be6f176c3d05aaaa34644"
   dependencies:
@@ -4253,9 +4251,9 @@
     mkdirp "^0.5.1"
     object-assign "^4.1.0"
 
-package-json@^3.0.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/package-json/-/package-json-3.1.0.tgz#ce281900fe8052150cc6709c6c006c18fdb2f379"
+package-json@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/package-json/-/package-json-4.0.0.tgz#f3c9dc8738f5b59304d54d2cfb3f91d08fdd7998"
   dependencies:
     got "^6.7.1"
     registry-auth-token "^3.0.1"
@@ -4325,7 +4323,7 @@
   version "1.3.1"
   resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.1.tgz#c8ab8c9223ba34888aa64a297b28853bec18da56"
 
-path-browserify@~0.0.0, path-browserify@0.0.0:
+path-browserify@0.0.0, path-browserify@~0.0.0:
   version "0.0.0"
   resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.0.tgz#a0b870729aae214005b7d5032ec2cbbb0fb4451a"
 
@@ -4726,11 +4724,11 @@
     webdriver-manager "^12.0.1"
 
 proxy-addr@~1.1.3:
-  version "1.1.3"
-  resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-1.1.3.tgz#dc97502f5722e888467b3fa2297a7b1ff47df074"
+  version "1.1.4"
+  resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-1.1.4.tgz#27e545f6960a44a627d9b44467e35c1b6b4ce2f3"
   dependencies:
     forwarded "~0.1.0"
-    ipaddr.js "1.2.0"
+    ipaddr.js "1.3.0"
 
 prr@~0.0.0:
   version "0.0.0"
@@ -4750,15 +4748,15 @@
     parse-asn1 "^5.0.0"
     randombytes "^2.0.1"
 
+punycode@1.3.2:
+  version "1.3.2"
+  resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d"
+
 punycode@^1.2.4, punycode@^1.3.2, punycode@^1.4.1:
   version "1.4.1"
   resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
 
-punycode@1.3.2:
-  version "1.3.2"
-  resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d"
-
-q@^1.1.2, q@^1.4.1, q@1.4.1:
+q@1.4.1, q@^1.1.2, q@^1.4.1:
   version "1.4.1"
   resolved "https://registry.yarnpkg.com/q/-/q-1.4.1.tgz#55705bcd93c5f3673530c2c2cbc0c2b3addc286e"
 
@@ -4766,7 +4764,7 @@
   version "1.1.5"
   resolved "https://registry.yarnpkg.com/qjobs/-/qjobs-1.1.5.tgz#659de9f2cf8dcc27a1481276f205377272382e73"
 
-qs@~6.4.0, qs@6.4.0:
+qs@6.4.0, qs@~6.4.0:
   version "6.4.0"
   resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233"
 
@@ -4817,8 +4815,8 @@
   resolved "https://registry.yarnpkg.com/raw-loader/-/raw-loader-0.5.1.tgz#0c3d0beaed8a01c966d9787bf778281252a979aa"
 
 rc@^1.0.1, rc@^1.1.6, rc@^1.1.7:
-  version "1.1.7"
-  resolved "https://registry.yarnpkg.com/rc/-/rc-1.1.7.tgz#c5ea564bb07aff9fd3a5b32e906c1d3a65940fea"
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.0.tgz#c7de973b7b46297c041366b2fd3d2363b1697c66"
   dependencies:
     deep-extend "~0.4.0"
     ini "~1.3.0"
@@ -4846,6 +4844,24 @@
     normalize-package-data "^2.3.2"
     path-type "^1.0.0"
 
+readable-stream@1.0, readable-stream@~1.0.2:
+  version "1.0.34"
+  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c"
+  dependencies:
+    core-util-is "~1.0.0"
+    inherits "~2.0.1"
+    isarray "0.0.1"
+    string_decoder "~0.10.x"
+
+readable-stream@1.1:
+  version "1.1.13"
+  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.13.tgz#f6eef764f514c89e2b9e23146a75ba106756d23e"
+  dependencies:
+    core-util-is "~1.0.0"
+    inherits "~2.0.1"
+    isarray "0.0.1"
+    string_decoder "~0.10.x"
+
 "readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.1.0, readable-stream@^2.1.4, readable-stream@^2.1.5:
   version "2.2.6"
   resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.2.6.tgz#8b43aed76e71483938d12a8d46c6cf1a00b1f816"
@@ -4858,15 +4874,6 @@
     string_decoder "~0.10.x"
     util-deprecate "~1.0.1"
 
-readable-stream@~1.0.2, readable-stream@1.0:
-  version "1.0.34"
-  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c"
-  dependencies:
-    core-util-is "~1.0.0"
-    inherits "~2.0.1"
-    isarray "0.0.1"
-    string_decoder "~0.10.x"
-
 readable-stream@~2.0.0:
   version "2.0.6"
   resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e"
@@ -4878,15 +4885,6 @@
     string_decoder "~0.10.x"
     util-deprecate "~1.0.1"
 
-readable-stream@1.1:
-  version "1.1.13"
-  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.13.tgz#f6eef764f514c89e2b9e23146a75ba106756d23e"
-  dependencies:
-    core-util-is "~1.0.0"
-    inherits "~2.0.1"
-    isarray "0.0.1"
-    string_decoder "~0.10.x"
-
 readdirp@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.1.0.tgz#4ed0ad060df3073300c48440373f72d1cc642d78"
@@ -4896,6 +4894,15 @@
     readable-stream "^2.0.2"
     set-immediate-shim "^1.0.1"
 
+recast@0.10.33:
+  version "0.10.33"
+  resolved "https://registry.yarnpkg.com/recast/-/recast-0.10.33.tgz#942808f7aa016f1fa7142c461d7e5704aaa8d697"
+  dependencies:
+    ast-types "0.8.12"
+    esprima-fb "~15001.1001.0-dev-harmony-fb"
+    private "~0.1.5"
+    source-map "~0.5.0"
+
 recast@^0.10.10:
   version "0.10.43"
   resolved "https://registry.yarnpkg.com/recast/-/recast-0.10.43.tgz#b95d50f6d60761a5f6252e15d80678168491ce7f"
@@ -4914,15 +4921,6 @@
     private "~0.1.5"
     source-map "~0.5.0"
 
-recast@0.10.33:
-  version "0.10.33"
-  resolved "https://registry.yarnpkg.com/recast/-/recast-0.10.33.tgz#942808f7aa016f1fa7142c461d7e5704aaa8d697"
-  dependencies:
-    ast-types "0.8.12"
-    esprima-fb "~15001.1001.0-dev-harmony-fb"
-    private "~0.1.5"
-    source-map "~0.5.0"
-
 redent@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde"
@@ -5034,6 +5032,10 @@
   version "0.2.7"
   resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9"
 
+remove-trailing-separator@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.0.1.tgz#615ebb96af559552d4bf4057c8436d486ab63cc4"
+
 renderkid@^2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/renderkid/-/renderkid-2.0.1.tgz#898cabfc8bede4b7b91135a3ffd323e58c0db319"
@@ -5068,7 +5070,7 @@
   dependencies:
     is-finite "^1.0.0"
 
-request@^2.61.0, request@^2.69.0, request@^2.72.0, request@^2.78.0, request@^2.81.0, request@2:
+request@2, request@^2.69.0, request@^2.72.0, request@^2.78.0, request@^2.79.0, request@^2.81.0:
   version "2.81.0"
   resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0"
   dependencies:
@@ -5111,16 +5113,16 @@
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
 
+resolve@1.1.7:
+  version "1.1.7"
+  resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
+
 resolve@^1.1.3, resolve@^1.1.4, resolve@^1.1.6, resolve@^1.1.7:
   version "1.3.2"
   resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.3.2.tgz#1f0442c9e0cbb8136e87b9305f932f46c7f28235"
   dependencies:
     path-parse "^1.0.5"
 
-resolve@1.1.7:
-  version "1.1.7"
-  resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
-
 restore-cursor@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf"
@@ -5134,7 +5136,7 @@
   dependencies:
     align-text "^0.1.1"
 
-rimraf@^2.2.8, rimraf@^2.3.3, rimraf@^2.4.3, rimraf@^2.4.4, rimraf@^2.5.1, rimraf@^2.5.2, rimraf@^2.5.3, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@2:
+rimraf@2, rimraf@^2.2.8, rimraf@^2.3.3, rimraf@^2.4.4, rimraf@^2.5.1, rimraf@^2.5.2, rimraf@^2.5.3, rimraf@^2.5.4, rimraf@^2.6.1:
   version "2.6.1"
   resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.1.tgz#c2338ec643df7a1b7fe5c54fa86f57428a55f33d"
   dependencies:
@@ -5194,18 +5196,18 @@
   dependencies:
     https-proxy-agent "^1.0.0"
 
+sax@0.5.x:
+  version "0.5.8"
+  resolved "https://registry.yarnpkg.com/sax/-/sax-0.5.8.tgz#d472db228eb331c2506b0e8c15524adb939d12c1"
+
+sax@0.6.x:
+  version "0.6.1"
+  resolved "https://registry.yarnpkg.com/sax/-/sax-0.6.1.tgz#563b19c7c1de892e09bfc4f2fc30e3c27f0952b9"
+
 sax@>=0.6.0, sax@~1.2.1:
   version "1.2.2"
   resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.2.tgz#fd8631a23bc7826bef5d871bdb87378c95647828"
 
-sax@0.5.x:
-  version "0.5.8"
-  resolved "https://registry.yarnpkg.com/sax/-/sax-0.5.8.tgz#d472db228eb331c2506b0e8c15524adb939d12c1"
-
-sax@0.6.x:
-  version "0.6.1"
-  resolved "https://registry.yarnpkg.com/sax/-/sax-0.6.1.tgz#563b19c7c1de892e09bfc4f2fc30e3c27f0952b9"
-
 script-loader@^0.7.0:
   version "0.7.0"
   resolved "https://registry.yarnpkg.com/script-loader/-/script-loader-0.7.0.tgz#685dc7e7069e0dee7a92674f0ebc5b0f55baa5ec"
@@ -5216,6 +5218,15 @@
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
 
+selenium-webdriver@3.0.1:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/selenium-webdriver/-/selenium-webdriver-3.0.1.tgz#a2dea5da4a97f6672e89e7ca7276cefa365147a7"
+  dependencies:
+    adm-zip "^0.4.7"
+    rimraf "^2.5.4"
+    tmp "0.0.30"
+    xml2js "^0.4.17"
+
 selenium-webdriver@^2.53.2:
   version "2.53.3"
   resolved "https://registry.yarnpkg.com/selenium-webdriver/-/selenium-webdriver-2.53.3.tgz#d29ff5a957dff1a1b49dc457756e4e4bfbdce085"
@@ -5226,15 +5237,6 @@
     ws "^1.0.1"
     xml2js "0.4.4"
 
-selenium-webdriver@3.0.1:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/selenium-webdriver/-/selenium-webdriver-3.0.1.tgz#a2dea5da4a97f6672e89e7ca7276cefa365147a7"
-  dependencies:
-    adm-zip "^0.4.7"
-    rimraf "^2.5.4"
-    tmp "0.0.30"
-    xml2js "^0.4.17"
-
 semver-diff@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36"
@@ -5247,7 +5249,7 @@
   dependencies:
     semver "^5.3.0"
 
-semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@~5.3.0, "semver@2 || 3 || 4 || 5":
+"semver@2 || 3 || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@~5.3.0:
   version "5.3.0"
   resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f"
 
@@ -5472,28 +5474,28 @@
   dependencies:
     source-map "^0.5.6"
 
-source-map@^0.1.41, source-map@~0.1.33, source-map@~0.1.7, source-map@0.1.x:
+source-map@0.1.32:
+  version "0.1.32"
+  resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.1.32.tgz#c8b6c167797ba4740a8ea33252162ff08591b266"
+  dependencies:
+    amdefine ">=0.0.4"
+
+source-map@0.1.x, source-map@^0.1.41, source-map@~0.1.33, source-map@~0.1.7:
   version "0.1.43"
   resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.1.43.tgz#c24bc146ca517c1471f5dacbe2571b2b7f9e3346"
   dependencies:
     amdefine ">=0.0.4"
 
+source-map@0.5.x, source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.0, source-map@~0.5.1, source-map@~0.5.3:
+  version "0.5.6"
+  resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412"
+
 source-map@^0.4.4:
   version "0.4.4"
   resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b"
   dependencies:
     amdefine ">=0.0.4"
 
-source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.0, source-map@~0.5.1, source-map@~0.5.3, source-map@0.5.x:
-  version "0.5.6"
-  resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412"
-
-source-map@0.1.32:
-  version "0.1.32"
-  resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.1.32.tgz#c8b6c167797ba4740a8ea33252162ff08591b266"
-  dependencies:
-    amdefine ">=0.0.4"
-
 spdx-correct@~1.0.0:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40"
@@ -5596,25 +5598,25 @@
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"
 
+string-width@^1.0.1, string-width@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
+  dependencies:
+    code-point-at "^1.0.0"
+    is-fullwidth-code-point "^1.0.0"
+    strip-ansi "^3.0.0"
+
+string-width@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.0.0.tgz#635c5436cc72a6e0c387ceca278d4e2eec52687e"
+  dependencies:
+    is-fullwidth-code-point "^2.0.0"
+    strip-ansi "^3.0.0"
+
 string_decoder@^0.10.25, string_decoder@~0.10.0, string_decoder@~0.10.x:
   version "0.10.31"
   resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
 
-string-width@^1.0.1, string-width@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
-  dependencies:
-    code-point-at "^1.0.0"
-    is-fullwidth-code-point "^1.0.0"
-    strip-ansi "^3.0.0"
-
-string-width@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.0.0.tgz#635c5436cc72a6e0c387ceca278d4e2eec52687e"
-  dependencies:
-    is-fullwidth-code-point "^2.0.0"
-    strip-ansi "^3.0.0"
-
 stringmap@~0.2.2:
   version "0.2.2"
   resolved "https://registry.yarnpkg.com/stringmap/-/stringmap-0.2.2.tgz#556c137b258f942b8776f5b2ef582aa069d7d1b1"
@@ -5649,14 +5651,14 @@
   dependencies:
     get-stdin "^4.0.1"
 
+strip-json-comments@1.0.x:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-1.0.4.tgz#1e15fbcac97d3ee99bf2d73b4c656b082bbafb91"
+
 strip-json-comments@^2.0.0, strip-json-comments@~2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
 
-strip-json-comments@1.0.x:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-1.0.4.tgz#1e15fbcac97d3ee99bf2d73b4c656b082bbafb91"
-
 style-loader@^0.13.1:
   version "0.13.2"
   resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.13.2.tgz#74533384cf698c7104c7951150b49717adc2f3bb"
@@ -5758,10 +5760,6 @@
   dependencies:
     execa "^0.4.0"
 
-through@^2.3.6, "through@>=2.2.7 <3", through@~2.3.8, through@X.X.X:
-  version "2.3.8"
-  resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
-
 through2@^2.0.0:
   version "2.0.3"
   resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be"
@@ -5769,6 +5767,10 @@
     readable-stream "^2.1.5"
     xtend "~4.0.1"
 
+"through@>=2.2.7 <3", through@X.X.X, through@^2.3.6, through@~2.3.8:
+  version "2.3.8"
+  resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
+
 timed-out@^4.0.0:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f"
@@ -5785,12 +5787,6 @@
   dependencies:
     setimmediate "^1.0.4"
 
-tmp@^0.0.31, tmp@0.0.x:
-  version "0.0.31"
-  resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.31.tgz#8f38ab9438e17315e5dbd8b3657e8bfb277ae4a7"
-  dependencies:
-    os-tmpdir "~1.0.1"
-
 tmp@0.0.24:
   version "0.0.24"
   resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.24.tgz#d6a5e198d14a9835cc6f2d7c3d9e302428c8cf12"
@@ -5801,12 +5797,18 @@
   dependencies:
     os-tmpdir "~1.0.1"
 
-tmp@0.0.30:
+tmp@0.0.30, tmp@0.0.x:
   version "0.0.30"
   resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.30.tgz#72419d4a8be7d6ce75148fd8b324e593a711c2ed"
   dependencies:
     os-tmpdir "~1.0.1"
 
+tmp@^0.0.31:
+  version "0.0.31"
+  resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.31.tgz#8f38ab9438e17315e5dbd8b3657e8bfb277ae4a7"
+  dependencies:
+    os-tmpdir "~1.0.1"
+
 to-array@0.1.4:
   version "0.1.4"
   resolved "https://registry.yarnpkg.com/to-array/-/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890"
@@ -5898,7 +5900,7 @@
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-1.4.0.tgz#84f8a83df9967d35bf1ff3aa48c7339593d64e19"
 
-tty-browserify@~0.0.0, tty-browserify@0.0.0:
+tty-browserify@0.0.0, tty-browserify@~0.0.0:
   version "0.0.0"
   resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6"
 
@@ -5923,21 +5925,18 @@
   version "0.0.6"
   resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
 
-"typescript@>=2.0.0 <2.2.0":
-  version "2.1.6"
-  resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.1.6.tgz#40c7e6e9e5da7961b7718b55505f9cac9487a607"
-
-typescript@2.1.5:
+typescript@2.1.5, "typescript@>=2.0.0 <2.3.0":
   version "2.1.5"
   resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.1.5.tgz#6fe9479e00e01855247cea216e7561bafcdbcd4a"
 
-uglify-js@^2.6, uglify-js@^2.7.5, uglify-js@2.8.x:
-  version "2.8.14"
-  resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.14.tgz#25b15d1af39b21752ee33703adbf432e8bc8f77d"
+uglify-js@2.8.x, uglify-js@^2.6, uglify-js@^2.7.5:
+  version "2.8.18"
+  resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.18.tgz#925d14bae48ab62d1883b41afe6e2261662adb8e"
   dependencies:
     source-map "~0.5.1"
+    yargs "~3.10.0"
+  optionalDependencies:
     uglify-to-browserify "~1.0.0"
-    yargs "~3.10.0"
 
 uglify-js@~2.3:
   version "2.3.6"
@@ -5983,7 +5982,7 @@
   dependencies:
     crypto-random-string "^1.0.0"
 
-unpipe@~1.0.0, unpipe@1.0.0:
+unpipe@1.0.0, unpipe@~1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
 
@@ -6021,6 +6020,13 @@
   dependencies:
     prepend-http "^1.0.1"
 
+url-parse@1.0.x:
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.0.5.tgz#0854860422afdcfefeb6c965c662d4800169927b"
+  dependencies:
+    querystringify "0.0.x"
+    requires-port "1.0.x"
+
 url-parse@^1.1.1:
   version "1.1.8"
   resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.1.8.tgz#7a65b3a8d57a1e86af6b4e2276e34774167c0156"
@@ -6028,13 +6034,6 @@
     querystringify "0.0.x"
     requires-port "1.0.x"
 
-url-parse@1.0.x:
-  version "1.0.5"
-  resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.0.5.tgz#0854860422afdcfefeb6c965c662d4800169927b"
-  dependencies:
-    querystringify "0.0.x"
-    requires-port "1.0.x"
-
 url@^0.11.0, url@~0.11.0:
   version "0.11.0"
   resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"
@@ -6047,8 +6046,8 @@
   resolved "https://registry.yarnpkg.com/user-home/-/user-home-1.1.1.tgz#2b5be23a32b63a7c9deb8d0f28d485724a3df190"
 
 useragent@^2.1.10:
-  version "2.1.12"
-  resolved "https://registry.yarnpkg.com/useragent/-/useragent-2.1.12.tgz#aa7da6cdc48bdc37ba86790871a7321d64edbaa2"
+  version "2.1.13"
+  resolved "https://registry.yarnpkg.com/useragent/-/useragent-2.1.13.tgz#bba43e8aa24d5ceb83c2937473e102e21df74c10"
   dependencies:
     lru-cache "2.2.x"
     tmp "0.0.x"
@@ -6057,7 +6056,7 @@
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
 
-util@^0.10.3, util@~0.10.1, util@0.10.3:
+util@0.10.3, util@^0.10.3, util@~0.10.1:
   version "0.10.3"
   resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9"
   dependencies:
@@ -6114,7 +6113,7 @@
   version "0.2.1"
   resolved "https://registry.yarnpkg.com/vlq/-/vlq-0.2.1.tgz#14439d711891e682535467f8587c5630e4222a6c"
 
-vm-browserify@~0.0.1, vm-browserify@0.0.4:
+vm-browserify@0.0.4, vm-browserify@~0.0.1:
   version "0.0.4"
   resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-0.0.4.tgz#5d7ea45bbef9e4a6ff65f95438e0a87c357d5a73"
   dependencies:
@@ -6141,7 +6140,7 @@
 
 "waves-ui@github:cannam/waves-ui":
   version "0.3.0"
-  resolved "https://codeload.github.com/cannam/waves-ui/tar.gz/83aa00d500baa4c8b463d857a4bbaa75f4678856"
+  resolved "https://codeload.github.com/cannam/waves-ui/tar.gz/a8acba51d37e42418eebfc1ba645faf956402f89"
   dependencies:
     babel-runtime "^5.8.12"
 
@@ -6158,6 +6157,21 @@
     "@types/selenium-webdriver" "^2.53.35"
     selenium-webdriver "^2.53.2"
 
+webdriver-manager@10.2.5:
+  version "10.2.5"
+  resolved "https://registry.yarnpkg.com/webdriver-manager/-/webdriver-manager-10.2.5.tgz#6433c1a64b038388c295ed0dc9daa71e5df5024e"
+  dependencies:
+    adm-zip "^0.4.7"
+    chalk "^1.1.1"
+    del "^2.2.0"
+    glob "^7.0.3"
+    ini "^1.3.4"
+    minimist "^1.2.0"
+    q "^1.4.1"
+    request "^2.69.0"
+    rimraf "^2.5.2"
+    semver "^5.3.0"
+
 webdriver-manager@^12.0.1:
   version "12.0.4"
   resolved "https://registry.yarnpkg.com/webdriver-manager/-/webdriver-manager-12.0.4.tgz#658e431c805bc3a7e6bf74bc819475884e6d4861"
@@ -6174,21 +6188,6 @@
     semver "^5.3.0"
     xml2js "^0.4.17"
 
-webdriver-manager@10.2.5:
-  version "10.2.5"
-  resolved "https://registry.yarnpkg.com/webdriver-manager/-/webdriver-manager-10.2.5.tgz#6433c1a64b038388c295ed0dc9daa71e5df5024e"
-  dependencies:
-    adm-zip "^0.4.7"
-    chalk "^1.1.1"
-    del "^2.2.0"
-    glob "^7.0.3"
-    ini "^1.3.4"
-    minimist "^1.2.0"
-    q "^1.4.1"
-    request "^2.69.0"
-    rimraf "^2.5.2"
-    semver "^5.3.0"
-
 webpack-dev-middleware@^1.0.11, webpack-dev-middleware@^1.9.0:
   version "1.10.1"
   resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-1.10.1.tgz#c6b4cf428139cf1aefbe06a0c00fdb4f8da2f893"
@@ -6280,11 +6279,11 @@
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f"
 
-which@^1.2.1, which@^1.2.8, which@^1.2.9, which@1:
-  version "1.2.12"
-  resolved "https://registry.yarnpkg.com/which/-/which-1.2.12.tgz#de67b5e450269f194909ef23ece4ebe416fa1192"
-  dependencies:
-    isexe "^1.1.1"
+which@1, which@^1.2.1, which@^1.2.8, which@^1.2.9:
+  version "1.2.14"
+  resolved "https://registry.yarnpkg.com/which/-/which-1.2.14.tgz#9a87c4378f03e827cecaf1acdf56c736c01c14e5"
+  dependencies:
+    isexe "^2.0.0"
 
 wide-align@^1.1.0:
   version "1.1.0"
@@ -6298,6 +6297,10 @@
   dependencies:
     string-width "^1.0.1"
 
+window-size@0.1.0:
+  version "0.1.0"
+  resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d"
+
 window-size@^0.1.2:
   version "0.1.4"
   resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.4.tgz#f8e1aa1ee5a53ec5bf151ffa09742a6ad7697876"
@@ -6306,18 +6309,14 @@
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.2.0.tgz#b4315bb4214a3d7058ebeee892e13fa24d98b075"
 
-window-size@0.1.0:
-  version "0.1.0"
-  resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d"
+wordwrap@0.0.2:
+  version "0.0.2"
+  resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f"
 
 wordwrap@~0.0.2:
   version "0.0.3"
   resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107"
 
-wordwrap@0.0.2:
-  version "0.0.2"
-  resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f"
-
 wrap-ansi@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85"
@@ -6337,6 +6336,13 @@
     imurmurhash "^0.1.4"
     slide "^1.1.5"
 
+ws@1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/ws/-/ws-1.1.1.tgz#082ddb6c641e85d4bb451f03d52f06eabdb1f018"
+  dependencies:
+    options ">=0.0.5"
+    ultron "1.0.x"
+
 ws@^1.0.1:
   version "1.1.4"
   resolved "https://registry.yarnpkg.com/ws/-/ws-1.1.4.tgz#57f40d036832e5f5055662a397c4de76ed66bf61"
@@ -6344,13 +6350,6 @@
     options ">=0.0.5"
     ultron "1.0.x"
 
-ws@1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/ws/-/ws-1.1.1.tgz#082ddb6c641e85d4bb451f03d52f06eabdb1f018"
-  dependencies:
-    options ">=0.0.5"
-    ultron "1.0.x"
-
 wtf-8@1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/wtf-8/-/wtf-8-1.0.0.tgz#392d8ba2d0f1c34d1ee2d630f15d0efb68e1048a"
@@ -6363,6 +6362,13 @@
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/xml-char-classes/-/xml-char-classes-1.0.0.tgz#64657848a20ffc5df583a42ad8a277b4512bbc4d"
 
+xml2js@0.4.4:
+  version "0.4.4"
+  resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.4.tgz#3111010003008ae19240eba17497b57c729c555d"
+  dependencies:
+    sax "0.6.x"
+    xmlbuilder ">=1.0.0"
+
 xml2js@^0.4.17:
   version "0.4.17"
   resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.17.tgz#17be93eaae3f3b779359c795b419705a8817e868"
@@ -6370,23 +6376,12 @@
     sax ">=0.6.0"
     xmlbuilder "^4.1.0"
 
-xml2js@0.4.4:
-  version "0.4.4"
-  resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.4.tgz#3111010003008ae19240eba17497b57c729c555d"
-  dependencies:
-    sax "0.6.x"
-    xmlbuilder ">=1.0.0"
-
-xmlbuilder@^4.1.0:
+xmlbuilder@>=1.0.0, xmlbuilder@^4.1.0:
   version "4.2.1"
   resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-4.2.1.tgz#aa58a3041a066f90eaa16c2f5389ff19f3f461a5"
   dependencies:
     lodash "^4.0.0"
 
-xmlbuilder@>=1.0.0:
-  version "8.2.2"
-  resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-8.2.2.tgz#69248673410b4ba42e1a6136551d2922335aa773"
-
 xmldom@^0.1.19:
   version "0.1.27"
   resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.27.tgz#d501f97b3bdb403af8ef9ecc20573187aadac0e9"
@@ -6488,4 +6483,3 @@
 zone.js@^0.7.2, zone.js@^0.7.6:
   version "0.7.8"
   resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.7.8.tgz#4f3fe8834d44597f2639053a0fa438df34fffded"
-