Repository: vgteam/sequenceTubeMap
Pull request #293 (2023-06-14 05:20:46)

Integrate track picker
Integrate track picker into the mounted files section

Notable Changes:
- The track data structure has been updated to support a single file rather than an array of files
- Tracks are stored in an object rather than an array, tracks are identified by an unique trackID

Bugs:
- Read coloring only partially works. Certain strands are not updated when the color changes.
- Region area does not display autocomplete until something has been typed


Patch:
diff --git a/src/App.css b/src/App.css
index 3831062..75982e6 100644
--- a/src/App.css
+++ b/src/App.css
@@ -114,6 +114,17 @@ input[type="file"] {
   top: 5px;
 }
 
+.btn-alert {
+  background-color: #e01010;
+  border-color: #e01010;
+}
+
+.btn-alert:hover {
+  background-color: #b61515;
+  border-color: #b61515;
+}
+
+
 .btn-secondary {
   background-color: #868e96;
   border-color: #868e96;
diff --git a/src/App.js b/src/App.js
index 81ae40c..3d0165e 100644
--- a/src/App.js
+++ b/src/App.js
@@ -21,14 +21,16 @@ const EXAMPLE_TRACKS = [
   {"files": [{"type": "read", "name": "fakeReads"}]}
 ];
 
-function addTracksToColorSchemes(tracks, schemes) {
+function getColorSchemesFromTracks(tracks) {
   
-  console.log("Initializing coloring for tracks: ", tracks, "combining with:", schemes);
+  let schemes = [];
 
   for (const key in tracks) {
     if (schemes[key] === undefined) {
       // We need to adopt a color scheme
-      if (tracks[key].files[0].type === "read") {
+      if (tracks[key].trackColorSettings !== undefined) {
+        schemes[key] = tracks[key].trackColorSettings;
+      } else if (tracks[key].trackType === "read") {
         schemes[key] = {...config.defaultReadColorPalette};
       } else {
         schemes[key] = {...config.defaultHaplotypeColorPalette};
@@ -61,7 +63,7 @@ class App extends Component {
         showReads: true,
         showSoftClips: true,
         colorReadsByMappingQuality: false,
-        colorSchemes: addTracksToColorSchemes(this.defaultViewTarget.tracks, []),
+        colorSchemes: getColorSchemesFromTracks(this.defaultViewTarget.tracks),
         mappingQualityCutoff: 0,
       },
     };
@@ -97,8 +99,7 @@ class App extends Component {
 
       this.setState((state) => {
         // Make sure we have color schemes.
-        let newColorSchemes = state.visOptions.colorSchemes.slice();
-        addTracksToColorSchemes(newViewTarget.tracks, newColorSchemes);
+        let newColorSchemes = getColorSchemesFromTracks(newViewTarget.tracks);
 
 
         console.log("Adopting color schemes: ", newColorSchemes)
diff --git a/src/Types.ts b/src/Types.ts
index b87344e..9ac28e0 100644
--- a/src/Types.ts
+++ b/src/Types.ts
@@ -13,16 +13,16 @@ type DataType = "built-in" | "file-upload" | "mounted files" | "examples";
 // Files like GBZ contains graph and maybe haplotype and so can be either
 type filetype = "graph" | "haplotype" | "read" | "bed";
 
-// Describes a file via name and type(graph, haplotype)
-// e.g name: cactus.xg, type: graph
-type file = {
-  name: string;
-  type: filetype;
-};
 
 // Contains information necessary to make a track
 type track = {
-  files: Array<file>
+  trackFile: string; // Name of file
+  trackType: filetype;
+  trackColorSettings: ColorScheme;
+}
+
+type tracks = {
+  [key: number]: track;
 }
 
 // Describes something the Tube Map can look at, specifically a region and the files the region is in.
@@ -82,4 +82,6 @@ type ColorScheme = {
 
 // Stores the assigned colorschemes of all tracks
 // Entries correspond to their track counterpart, e.g colorSchemes[0] corresponds to tracks[0]
-type colorSchemes = Array<ColorScheme>;
\ No newline at end of file
+type colorSchemes = {
+  trackID: ColorScheme;
+}
\ No newline at end of file
diff --git a/src/components/CustomizationAccordion.js b/src/components/CustomizationAccordion.js
index d5f9e50..7fae971 100644
--- a/src/components/CustomizationAccordion.js
+++ b/src/components/CustomizationAccordion.js
@@ -57,7 +57,11 @@ class VisualizationOptions extends Component {
     for (let key in this.props.tracks) {
       // Generate settings controls for each track
       let track = this.props.tracks[key];
-      let type = track.files[0].type;
+      console.log(track);
+      if (!track.trackFile) {
+        continue;
+      }
+      let type = track.trackType;
       if (type === "graph") {
         trackSettingsList.push(
           <TrackSettings key={key} label="Graph Paths" fileType={type} trackColorSettings={visOptions.colorSchemes[key]} setTrackColorSetting={this.setColorWithIndex(key)} />
@@ -191,7 +195,6 @@ class VisualizationOptions extends Component {
                     </React.Fragment>
                   )}
                 </FormGroup>
-                {trackSettingsList}
               </CardBody>
             </Collapse>
           </Card>
diff --git a/src/components/DataPositionFormRow.js b/src/components/DataPositionFormRow.js
index 45b4815..8733c8f 100644
--- a/src/components/DataPositionFormRow.js
+++ b/src/components/DataPositionFormRow.js
@@ -70,7 +70,8 @@ class DataPositionFormRow extends Component {
         <HelpButton file="./help/help-tooltip.md"></HelpButton>
         
         <Button
-          color="primary"
+          color={this.props.viewTargetHasChange ? "alert" : "primary"}
+          title={this.props.viewTargetHasChange ? "Click to apply pending changes." : "No changes to apply; view is up to date."}
           id="goButton"
           onClick={this.props.handleGoButton}
           disabled={this.props.uploadInProgress}
@@ -121,6 +122,7 @@ DataPositionFormRow.propTypes = {
   handleGoRight: PropTypes.func.isRequired,
   uploadInProgress: PropTypes.bool.isRequired,
   getCurrentViewTarget: PropTypes.func.isRequired,
+  viewTargetHasChange: PropTypes.bool.isRequired,
 };
 
 export default DataPositionFormRow;
diff --git a/src/components/HeaderForm.js b/src/components/HeaderForm.js
index 1be130f..c9d985d 100644
--- a/src/components/HeaderForm.js
+++ b/src/components/HeaderForm.js
@@ -6,10 +6,11 @@ import { fetchAndParse } from "../fetchAndParse";
 // import defaultConfig from '../config.default.json';
 import config from "../config.json";
 import DataPositionFormRow from "./DataPositionFormRow";
-import MountedDataFormRow from "./MountedDataFormRow";
 import FileUploadFormRow from "./FileUploadFormRow";
 import ExampleSelectButtons from "./ExampleSelectButtons";
 import RegionInput from "./RegionInput";
+import TrackPicker from "./TrackPicker";
+import SelectionDropdown from "./SelectionDropdown";
 import { parseRegion, stringifyRegion } from "../common.mjs";
 // See src/Types.ts
 
@@ -65,10 +66,7 @@ const EMPTY_STATE = {
 
   // These ones are for selecting entire files and need to be preserved when
   // switching dataType.
-  graphSelectOptions: ["none"],
-  gbwtSelectOptions: ["none"],
-  gamSelectOptions: ["none"],
-  bedSelectOptions: ["none"],
+  fileSelectOptions: [],
 };
 
 // Creates track to be stored in ViewTarget
@@ -93,38 +91,54 @@ function tracksEqual(curr, next) {
     return false;
   }
 
-  if (curr.files.length !== next.files.length) {
-    return false;
-  }
-  //loop through file names to see if they're equal
-  for (let i = 0; i < curr.files.length; i++) {
-    const curr_file = curr.files[i].name;
-    const next_file = next.files[i].name;
-    
-    //count falsy file names as the same
-    if ((!curr_file && !next_file) || curr_file === next_file) {
-      continue;
+  const curr_file = curr.trackFile;
+  const next_file = next.trackFile;
+
+  const curr_settings = curr.trackColorSettings;
+  const next_settings = next.trackColorSettings;
+  
+  // check if color settings are equal
+  if (curr_settings && next_settings){
+    if (curr_settings.mainPalette !== next_settings.mainPalette || 
+        curr_settings.auxPalette !== next_settings.auxPalette ||
+        curr_settings.colorReadsByMappingQuality !== next_settings.colorReadsByMappingQuality) {
+          
+        return false;
     }
-    return false;
   }
-  return true;
+  //count falsy file names as the same
+  if ((!curr_file && !next_file) || curr_file === next_file) {
+    return true;
+  }
+  return false;
+
 }
 
 // Checks if two view targets are the same. They are the same if they have the
 // same tracks and the same region.
 function viewTargetsEqual(currViewTarget, nextViewTarget) {
+
   // Update if one is undefined and the other isn't
   if ((currViewTarget === undefined) !== (nextViewTarget === undefined)) {
     return false;
   }
 
   // Update if view target tracks are not equal
-  if (currViewTarget.tracks.length !== nextViewTarget.tracks.length) {
+  if (Object.keys(currViewTarget.tracks).length !== Object.keys(nextViewTarget.tracks).length) {
     // Different lengths so not equal
     return false;
   }
-  for (let i = 0; i < currViewTarget.tracks.length; i++) {
-    if (!tracksEqual(currViewTarget.tracks[i], nextViewTarget.tracks[i])){
+
+  for (const key in currViewTarget.tracks) {
+    const currTrack = currViewTarget.tracks[key];
+    const nextTrack = nextViewTarget.tracks[key];
+
+    // if the key doesn't exist in the other track
+    if (!currTrack || !nextTrack) {
+      return false;
+    }
+
+    if (!tracksEqual(currTrack, nextTrack)) {
       // Different tracks so not equal
       return false;
     }
@@ -139,6 +153,7 @@ function viewTargetsEqual(currViewTarget, nextViewTarget) {
 
 }
 
+
 class HeaderForm extends Component {
   state = EMPTY_STATE;
   componentDidMount() {
@@ -175,10 +190,10 @@ class HeaderForm extends Component {
       this.getBedRegions(bedSelect, dataPath);
     }
     for (const key in ds.tracks) {
-      if (ds.tracks[key].files[0].type === fileTypes.GRAPH) {
+      if (ds.tracks[key].trackType === fileTypes.GRAPH) {
         // Load the paths for any graph tracks
         console.log("Get path names for track: ", ds.tracks[key]);
-        this.getPathNames(ds.tracks[key].files[0].name, dataPath);
+        this.getPathNames(ds.tracks[key].trackFile, dataPath);
       }
     }
     this.setState((state) => {
@@ -210,13 +225,12 @@ class HeaderForm extends Component {
       let maxKey = -1;
       for (const key in state.tracks) {
         let track = state.tracks[key];
-        if (track.files[0].type === type) {
-          console.log("See file " + seenTracksOfType + " of right type");
+        if (track.trackType === type) {
           if (seenTracksOfType === index) {
             if (file !== "none") {
               // We want to adjust it, so keep a modified copy of it
               let newTrack = JSON.parse(JSON.stringify(track));
-              newTrack.files[0].name = file;
+              newTrack.trackFile = file;
               newTracks[key] = newTrack;
             }
             // If the file is "none" we drop the track.
@@ -226,7 +240,6 @@ class HeaderForm extends Component {
           }
           seenTracksOfType++;
         } else {
-          console.log("See file of other type");
           // We want to keep all tracks of other types as is
           newTracks[key] = track;
         }
@@ -250,16 +263,19 @@ class HeaderForm extends Component {
     });
   }
 
-  getTrackFile = (type, index) => {
+  getTrackFile = (tracks, type, index) => {
     // Get the file used in the nth track of the gicen type, or "none" if no
     // such track exists.
     let seenTracksOfType = 0;
-    for (const key in this.state.tracks) {
-      let track = this.state.tracks[key];
-      if (track.files[0].type === type) {
+    for (const key in tracks) {
+      let track = tracks[key];
+      if (track === -1) {
+        continue;
+      }
+      if (track.trackType === type) {
         if (seenTracksOfType === index) {
           // This is the one. Return its filename.
-          return track.files[0].name;
+          return track.trackFile;
         }
         seenTracksOfType++;
       } 
@@ -278,15 +294,12 @@ class HeaderForm extends Component {
           "Content-Type": "application/json",
         },
       });
-      if (json.graphFiles === undefined) {
+      if (!json.files || json.files.length === 0) {
         // We did not get back a graph, only (possibly) an error.
         const error =
           json.error || "Server did not return a list of mounted filenames.";
         this.setState({ error: error });
       } else {
-        json.graphFiles.unshift("none");
-        json.gbwtFiles.unshift("none");
-        json.gamIndices.unshift("none");
         json.bedFiles.unshift("none");
 
         if (this.state.dataPath === "mounted") {
@@ -298,17 +311,15 @@ class HeaderForm extends Component {
               this.getBedRegions(bedSelect, "mounted");
             }
             for (const key in state.tracks) {
-              if (state.tracks[key].files[0].type === fileTypes.GRAPH) {
+              if (state.tracks[key].trackType === fileTypes.GRAPH) {
                 // Load the paths for any graph tracks.
                 // TODO: Do we need to do this now?
                 console.log("Get path names for track: ", state.tracks[key]);
-                this.getPathNames(state.tracks[key].files[0].name, "mounted");
+                this.getPathNames(state.tracks[key].trackFile, "mounted");
               }
             } 
             return {
-              graphSelectOptions: json.graphFiles,
-              gbwtSelectOptions: json.gbwtFiles,
-              gamSelectOptions: json.gamIndices,
+              fileSelectOptions: json.files,
               bedSelectOptions: json.bedFiles,
               bedSelect,
             };
@@ -316,9 +327,7 @@ class HeaderForm extends Component {
         } else {
           this.setState((state) => {
             return {
-              graphSelectOptions: json.graphFiles,
-              gbwtSelectOptions: json.gbwtFiles,
-              gamSelectOptions: json.gamIndices,
+              fileSelectOptions: json.files,
               bedSelectOptions: json.bedFiles,
             };
           });
@@ -438,10 +447,10 @@ class HeaderForm extends Component {
             this.setState({ regionInfo: {} });
           }
           for (const key in ds.tracks) {
-            if (ds.tracks[key].files[0].type === fileTypes.GRAPH) {
+            if (ds.tracks[key].trackType === fileTypes.GRAPH) {
               // Load the paths for any graph tracks.
               console.log("Get path names for track: ", ds.tracks[key]);
-              this.getPathNames(ds.tracks[key].files[0].name, dataPath);
+              this.getPathNames(ds.tracks[key].trackFile, dataPath);
             }
           }
           this.setState({
@@ -482,7 +491,6 @@ class HeaderForm extends Component {
       this.props.setCurrentViewTarget(nextViewTarget);
     }
 
-   
   };
 
   getRegionCoords = (desc) => {
@@ -516,28 +524,34 @@ class HeaderForm extends Component {
     }
     this.setState({ region: coords });
   };
-  handleInputChange = (event) => {
+
+  handleInputChange = (newTracks) => {
+    this.setState((state) => {
+      let newState = Object.assign({}, state);
+      newState.tracks = newTracks;
+      console.log('Set result: ' + JSON.stringify(newTracks));
+      return newState;
+    });
+
+    // update path names
+    const graphFile = this.getTrackFile(newTracks, fileTypes.GRAPH, 0);
+    if (graphFile && graphFile !== "none"){
+      this.getPathNames(graphFile, this.state.dataPath);
+    }
+
+  };
+
+  handleBedChange = (event) => {
     const id = event.target.id;
     const value = event.target.value;
     this.setState({ [id]: value });
-    if (id === "graphSelect") {
-      if (value && value !== "none") {
-        this.getPathNames(value, this.state.dataPath);
-      }
-      this.setTrackFile(fileTypes.GRAPH, 0, value);
-    } else if (id === "gbwtSelect") {
-      this.setTrackFile(fileTypes.HAPLOTYPE, 0, value);
-    } else if (id === "gamSelect") {
-      this.setTrackFile(fileTypes.READ, 0, value);
-    } else if (id === "gam2Select") {
-      this.setTrackFile(fileTypes.READ, 1, value);
-    } else if (id === "bedSelect") {
-      if (value !== "none") {
-        this.getBedRegions(value, this.state.dataPath);
-      }
-      this.setState({ bedFile: value });
+
+    if (value !== "none") {
+      this.getBedRegions(value, this.state.dataPath);
     }
-  };
+    this.setState({ bedFile: value });
+
+  }
 
   // Budge the region left or right by the given negative or positive fraction
   // of its width.
@@ -650,10 +664,11 @@ class HeaderForm extends Component {
     const mountedFilesFlag = this.state.dataType === dataTypes.MOUNTED_FILES;
     const uploadFilesFlag = this.state.dataType === dataTypes.FILE_UPLOAD;
     const examplesFlag = this.state.dataType === dataTypes.EXAMPLES;
+    const viewTargetHasChange = !viewTargetsEqual(this.getNextViewTarget(), this.props.getCurrentViewTarget());
 
     console.log(
-      "Rendering header form with graphSelectOptions: ",
-      this.state.graphSelectOptions
+      "Rendering header form with fileSelectOptions: ",
+      this.state.fileSelectOptions
     );
 
     return (
@@ -687,18 +702,31 @@ class HeaderForm extends Component {
               </select>
               &nbsp;
               {mountedFilesFlag && (
-                <MountedDataFormRow
-                  graphSelect={this.getTrackFile(fileTypes.GRAPH, 0)}
-                  graphSelectOptions={this.state.graphSelectOptions}
-                  gbwtSelect={this.getTrackFile(fileTypes.HAPLOTYPE, 0)}
-                  gbwtSelectOptions={this.state.gbwtSelectOptions}
-                  gamSelect={this.getTrackFile(fileTypes.READ, 0)}
-                  gam2Select={this.getTrackFile(fileTypes.READ, 1)}
-                  gamSelectOptions={this.state.gamSelectOptions}
-                  bedSelect={this.state.bedSelect}
-                  bedSelectOptions={this.state.bedSelectOptions}
-                  handleInputChange={this.handleInputChange}
-                />
+                <React.Fragment>
+                  <TrackPicker
+                    tracks={this.state.tracks}
+                    availableTracks={this.state.fileSelectOptions}
+                    onChange={this.handleInputChange}
+                  />
+
+                  <Label
+                    for="bedSelectInput"
+                    className="customData tight-label mb-2 mr-sm-2 mb-sm-0 ml-2"
+                  >
+                    BED file:
+                  </Label>
+                  
+                  <SelectionDropdown
+                    className="customDataMounted dropdown mb-2 mr-sm-4 mb-sm-0"
+                    id="bedSelect"
+                    inputId="bedSelectInput"
+                    value={this.state.bedSelect}
+                    onChange={this.handleBedChange}
+                    options={this.state.bedSelectOptions}
+                  />
+                  &nbsp;
+                </React.Fragment>
+
               )}
               {!examplesFlag && (
                 <RegionInput
@@ -742,6 +770,7 @@ class HeaderForm extends Component {
                   handleGoButton={this.handleGoButton}
                   uploadInProgress={this.state.uploadInProgress}
                   getCurrentViewTarget={this.props.getCurrentViewTarget}
+                  viewTargetHasChange={viewTargetHasChange}
                 />
               )}
             </Col>
diff --git a/src/components/MountedDataFormRow.js b/src/components/MountedDataFormRow.js
deleted file mode 100644
index 881ee40..0000000
--- a/src/components/MountedDataFormRow.js
+++ /dev/null
@@ -1,100 +0,0 @@
-import React, { Component } from "react";
-import PropTypes from "prop-types";
-import { Label } from "reactstrap";
-import SelectionDropdown from "./SelectionDropdown";
-
-class MountedDataFormRow extends Component {
-  render() {
-    // We use React-Select's Select, but it can't be labeled by a label pointed at its ID, because it renders as a div with that ID and divs are non-labellable according to https://html.spec.whatwg.org/multipage/forms.html#category-label.
-    return (
-      <React.Fragment>
-        <Label
-          for="graphSelectInput"
-          className="customData tight-label mb-2 mr-sm-2 mb-sm-0 ml-2"
-        >
-          graph file:
-        </Label>
-        <SelectionDropdown
-          className="customDataMounted dropdown mb-2 mr-sm-4 mb-sm-0"
-          id="graphSelect"
-          inputId="graphSelectInput"
-          value={this.props.graphSelect}
-          onChange={this.props.handleInputChange}
-          options={this.props.graphSelectOptions}
-        />
-        <Label
-          for="gbwtSelectInput"
-          className="customData tight-label mb-2 mr-sm-2 mb-sm-0 ml-2"
-        >
-          gbwt file:
-        </Label>
-        <SelectionDropdown
-          className="customDataMounted dropdown mb-2 mr-sm-4 mb-sm-0"
-          id="gbwtSelect"
-          inputId="gbwtSelectInput"
-          value={this.props.gbwtSelect}
-          onChange={this.props.handleInputChange}
-          options={this.props.gbwtSelectOptions}
-        />
-        <Label
-          for="gamSelectInput"
-          className="customData tight-label mb-2 mr-sm-2 mb-sm-0 ml-2"
-        >
-          gam index:
-        </Label>
-        <SelectionDropdown
-          className="customDataMounted dropdown mb-2 mr-sm-4 mb-sm-0"
-          id="gamSelect"
-          inputId="gamSelectInput"
-          value={this.props.gamSelect}
-          onChange={this.props.handleInputChange}
-          options={this.props.gamSelectOptions}
-        />
-        <Label
-          for="gam2SelectInput"
-          className="customData tight-label mb-2 mr-sm-2 mb-sm-0 ml-2"
-        >
-          gam index 2:
-        </Label>
-        <SelectionDropdown
-          className="customDataMounted dropdown mb-2 mr-sm-4 mb-sm-0"
-          id="gam2Select"
-          inputId="gam2SelectInput"
-          value={this.props.gam2Select}
-          onChange={this.props.handleInputChange}
-          options={this.props.gamSelectOptions}
-        />
-
-        <Label
-          for="bedSelectInput"
-          className="customData tight-label mb-2 mr-sm-2 mb-sm-0 ml-2"
-        >
-          BED file:
-        </Label>
-        <SelectionDropdown
-          className="customDataMounted dropdown mb-2 mr-sm-4 mb-sm-0"
-          id="bedSelect"
-          inputId="bedSelectInput"
-          value={this.props.bedSelect}
-          onChange={this.props.handleInputChange}
-          options={this.props.bedSelectOptions}
-        />
-        &nbsp;
-      </React.Fragment>
-    );
-  }
-}
-
-MountedDataFormRow.propTypes = {
-  gamSelect: PropTypes.string.isRequired,
-  gamSelectOptions: PropTypes.array.isRequired,
-  bedSelect: PropTypes.string.isRequired,
-  bedSelectOptions: PropTypes.array.isRequired,
-  gbwtSelect: PropTypes.string.isRequired,
-  gbwtSelectOptions: PropTypes.array.isRequired,
-  graphSelect: PropTypes.string.isRequired,
-  graphSelectOptions: PropTypes.array.isRequired,
-  handleInputChange: PropTypes.func.isRequired,
-};
-
-export default MountedDataFormRow;
diff --git a/src/components/RadioRow.js b/src/components/RadioRow.js
index 601286f..6eb2da6 100644
--- a/src/components/RadioRow.js
+++ b/src/components/RadioRow.js
@@ -51,7 +51,7 @@ class RadioRow extends Component {
     });
     return (
       <FormGroup row className="mb-1">
-        <Col sm="2">{this.props.rowHeading}:</Col>
+        {this.props.rowHeading}:
         {colorRadios}
       </FormGroup>
     );
diff --git a/src/components/RegionInput.js b/src/components/RegionInput.js
index d79572b..58d63f4 100644
--- a/src/components/RegionInput.js
+++ b/src/components/RegionInput.js
@@ -16,6 +16,7 @@ export const RegionInput = ({
 }) => {
   // Generate autocomplete options for regions from regionInfo
   // Add : to pathNames
+  console.log("rendering with pathnames: ", pathNames);
   const pathNamesColon = pathNames.map((name) => name + ":");
   const pathsWithRegion = [];
 
diff --git a/src/components/TrackFilePicker.demo.js b/src/components/TrackFilePicker.demo.js
index d8e13ca..a13cfdc 100644
--- a/src/components/TrackFilePicker.demo.js
+++ b/src/components/TrackFilePicker.demo.js
@@ -7,13 +7,13 @@ import TrackFilePicker from "./TrackFilePicker";
 
 export default (<Demo
   props={{
-    tracks: P.json([{"files": [{"name": "fileA1.vg", "type": "graph"},
-                              {"name": "fileA2.gbwt", "type": "haplotype"}]},
-                    {"files": [{"name": "fileB1.gbwt", "type": "haplotype"},
-                              {"name": "fileB2.gam", "type": "read"}]},
-                    {"files": [{"name": "fileC1.xg", "type": "graph"}]}]),
+    tracks: P.json([{"trackFile": "fileA1.vg", "trackType": "graph"},
+                    {"trackFile": "fileA2.gbwt", "trackType": "haplotype"},
+                    {"trackFile": "fileB1.gbwt", "trackType": "haplotype"},
+                    {"trackFile": "fileB2.gam", "trackType": "read"},
+                    {"trackFile": "fileC1.xg", "trackType": "graph"}]),
 
-    value: P.json({"name": "fileA1.vg", "type": "graph"}),
+    value: P.string("fileA1.vg"),
     fileType: P.choices(["graph", "haplotype", "read"]),
     pickerType: P.choices(["dropdown", "upload"]),
   }}
diff --git a/src/components/TrackFilePicker.js b/src/components/TrackFilePicker.js
index 396a588..81d4d80 100644
--- a/src/components/TrackFilePicker.js
+++ b/src/components/TrackFilePicker.js
@@ -33,16 +33,14 @@ export const TrackFilePicker = ({
     const fileOptions = []
     // find all file options matching the specified file type
     for (const track of tracks) {
-        for (const file of track["files"]) {
-          if (file["type"] === fileType) {
-            fileOptions.push(file);
-          }
+        if (track.trackType === fileType) {
+          fileOptions.push(track.trackFile)
         }
     }
 
     // takes in an array of options and maps them into a format <Select> takes
     const dropDownOptions = fileOptions.map((option) => ({
-      label: option["name"],
+      label: option,
       value: option,
     }));
     
@@ -53,7 +51,7 @@ export const TrackFilePicker = ({
         <div data-testid={testID}>
             <Select 
               options={dropDownOptions}
-              value={{label: value["name"], value: value}}
+              value={{label: value, value: value}}
               // Identical-looking object literals will compare unequal, so we
               // need to provide a way to turn them into strings so that
               // `value` can be matched up with the corresponding item in
@@ -83,7 +81,7 @@ export const TrackFilePicker = ({
 TrackFilePicker.propTypes = {
     tracks: PropTypes.array.isRequired,
     fileType: PropTypes.string,
-    value: PropTypes.object,
+    value: PropTypes.string,
     handleInputChange: PropTypes.func.isRequired,
     pickerType: PropTypes.string,
     className: PropTypes.string,
@@ -91,7 +89,7 @@ TrackFilePicker.propTypes = {
 }
   
 TrackFilePicker.defaultProps = {
-  value: {"name": "Select a file", "type": undefined},
+  value: "Select a file",
   fileType: "graph",
   pickerType: "dropdown",
   className: undefined,
diff --git a/src/components/TrackList.demo.js b/src/components/TrackList.demo.js
index 6e19d76..974b0be 100644
--- a/src/components/TrackList.demo.js
+++ b/src/components/TrackList.demo.js
@@ -37,11 +37,11 @@ export default (<Demo
         },
     }),
     availableTracks: P.json([
-        {"files": [{"name": "fileA1.vg", "type": "graph"},
-                   {"name": "fileA2.gbwt", "type": "haplotype"}]},
-        {"files": [{"name": "fileB1.gbwt", "type": "haplotype"},
-                   {"name": "fileB2.gam", "type": "read"}]},
-        {"files": [{"name": "fileC1.xg", "type": "graph"}]}
+      [{"trackFile": "fileA1.vg", "trackType": "graph"},
+      {"trackFile": "fileA2.gbwt", "trackType": "haplotype"},
+      {"trackFile": "fileB1.gbwt", "trackType": "haplotype"},
+      {"trackFile": "fileB2.gam", "trackType": "read"},
+      {"trackFile": "fileC1.xg", "trackType": "graph"}]
       ])
   }}
 >
diff --git a/src/components/TrackListItem.demo.js b/src/components/TrackListItem.demo.js
index 57d3913..ba81e6e 100644
--- a/src/components/TrackListItem.demo.js
+++ b/src/components/TrackListItem.demo.js
@@ -16,13 +16,11 @@ export default (<Demo
         colorReadsByMappingQuality: false
       }
     }),
-    availableTracks: P.json([
-      {"files": [{"name": "fileA1.vg", "type": "graph"},
-                 {"name": "fileA2.gbwt", "type": "haplotype"}]},
-      {"files": [{"name": "fileB1.gbwt", "type": "haplotype"},
-                 {"name": "fileB2.gam", "type": "read"}]},
-      {"files": [{"name": "fileC1.xg", "type": "graph"}]}
-    ])
+    availableTracks: P.json([{"trackFile": "fileA1.vg", "trackType": "graph"},
+                            {"trackFile": "fileA2.gbwt", "trackType": "haplotype"},
+                            {"trackFile": "fileB1.gbwt", "trackType": "haplotype"},
+                            {"trackFile": "fileB2.gam", "trackType": "read"},
+                            {"trackFile": "fileC1.xg", "trackType": "graph"}]),
   }}
 >
   {
diff --git a/src/components/TrackListItem.js b/src/components/TrackListItem.js
index 8c2d4a8..ff03951 100644
--- a/src/components/TrackListItem.js
+++ b/src/components/TrackListItem.js
@@ -74,6 +74,7 @@ export const TrackListItem = ({
     // displayed elements uses propChanges(local state) first, then uses trackProps
     // Isn't just a || because propChanges can set the trackFile to be undefined
     const displayedFile = "trackFile" in propChanges ? propChanges["trackFile"] : trackProps["trackFile"];
+    console.log(displayedFile);
     return (
       <Container key={trackID} style={{ width: "800px", marginLeft: 0 }}>
         <Row className="g-0">
diff --git a/src/components/TrackPicker.demo.js b/src/components/TrackPicker.demo.js
index da2a946..f9db613 100644
--- a/src/components/TrackPicker.demo.js
+++ b/src/components/TrackPicker.demo.js
@@ -12,13 +12,11 @@ export default (<Demo
         2: config.defaultTrackProps,
         3: config.defaultTrackProps,
     }),
-    availableTracks: P.json([
-        {"files": [{"name": "fileA1.vg", "type": "graph"},
-                   {"name": "fileA2.gbwt", "type": "haplotype"}]},
-        {"files": [{"name": "fileB1.gbwt", "type": "haplotype"},
-                   {"name": "fileB2.gam", "type": "read"}]},
-        {"files": [{"name": "fileC1.xg", "type": "graph"}]}
-      ])
+    availableTracks: P.json([{"trackFile": "fileA1.vg", "trackType": "graph"},
+                            {"trackFile": "fileA2.gbwt", "trackType": "haplotype"},
+                            {"trackFile": "fileB1.gbwt", "trackType": "haplotype"},
+                            {"trackFile": "fileB2.gam", "trackType": "read"},
+                            {"trackFile": "fileC1.xg", "trackType": "graph"}])
   }}
 >
   {
diff --git a/src/components/TrackPicker.js b/src/components/TrackPicker.js
index ab118ce..4995b6a 100644
--- a/src/components/TrackPicker.js
+++ b/src/components/TrackPicker.js
@@ -5,6 +5,7 @@ import { Button } from 'reactstrap'
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { faList } from '@fortawesome/free-solid-svg-icons';
 import PopupDialog from './PopupDialog.js';
+import config from "./../config.json";
 
 export const TrackPicker = ({
     tracks, // expects a trackList, same as trackListDisplay
@@ -43,7 +44,7 @@ TrackPicker.propTypes = {
 }
 
 TrackPicker.defaultProps = {
-    availableColors: [],
+    tracks: {1: config.defaultTrackProps},
     onChange: () => {}
 }
 
diff --git a/src/components/TrackPickerDisplay.demo.js b/src/components/TrackPickerDisplay.demo.js
index c23c538..c7ea614 100644
--- a/src/components/TrackPickerDisplay.demo.js
+++ b/src/components/TrackPickerDisplay.demo.js
@@ -36,13 +36,11 @@ export default (<Demo
             }
         },
     }),
-    availableTracks: P.json([
-        {"files": [{"name": "fileA1.vg", "type": "graph"},
-                   {"name": "fileA2.gbwt", "type": "haplotype"}]},
-        {"files": [{"name": "fileB1.gbwt", "type": "haplotype"},
-                   {"name": "fileB2.gam", "type": "read"}]},
-        {"files": [{"name": "fileC1.xg", "type": "graph"}]}
-      ])
+    availableTracks: P.json([{"trackFile": "fileA1.vg", "trackType": "graph"},
+                            {"trackFile": "fileA2.gbwt", "trackType": "haplotype"},
+                            {"trackFile": "fileB1.gbwt", "trackType": "haplotype"},
+                            {"trackFile": "fileB2.gam", "trackType": "read"},
+                            {"trackFile": "fileC1.xg", "trackType": "graph"}])
   }}
 >
   {
diff --git a/src/components/TrackPickerDisplay.js b/src/components/TrackPickerDisplay.js
index 3d2e459..86ff970 100644
--- a/src/components/TrackPickerDisplay.js
+++ b/src/components/TrackPickerDisplay.js
@@ -80,8 +80,6 @@ export const TrackPickerDisplay = ({
 
         // call onChange if the track list is valid and changes have been made        
         if (validTrackList && JSON.stringify(newTrackList) !== JSON.stringify(tracks)) {
-            console.log("tracks", tracks);
-            console.log("trackChanges", trackListChanges);
             console.log("calling Track Picker Display onChange with ", newTrackList);
             onChange(newTrackList);
             // clear out pending changes
@@ -120,7 +118,6 @@ TrackPickerDisplay.propTypes = {
 }
 
 TrackPickerDisplay.defaultProps = {
-    availableColors: [],
     onChange: () => {}
 }
 
diff --git a/src/components/TubeMapContainer.js b/src/components/TubeMapContainer.js
index 1c657dd..0245171 100644
--- a/src/components/TubeMapContainer.js
+++ b/src/components/TubeMapContainer.js
@@ -147,41 +147,39 @@ class TubeMapContainer extends Component {
         let graphTrackID = null;
         // And the haplotype track number if any
         let haplotypeTrackID = null;
-        for (let i = 0; i < this.props.viewTarget.tracks.length; i++) {
+
+        console.log("getting viewTarget ", this.props.viewTarget);
+        for (const i in this.props.viewTarget.tracks) {
           const track = this.props.viewTarget.tracks[i];
-          for (const file of track.files) {
-            if (file.type === "read") {
-              //add track index to array if the track contains a gam file
-              readTrackIDs.push(i);
-              break;
-            }
-            if (file.type === "graph") {
-              // Or note if it is a graph (one allowed)
-              graphTrackID = i;
-              break;
-            }
-            if (file.type === "haplotype") {
-              // Or a collection of haplotypes (one allowed)
-              haplotypeTrackID = i;
-              break;
-            }
+          if (track.trackType === "read") {
+            //add track index to array if the track contains a gam file
+            readTrackIDs.push(i);
+          }
+          if (track.trackType === "graph") {
+            // Or note if it is a graph (one allowed)
+            graphTrackID = i;
+          }
+          if (track.trackType === "haplotype") {
+            // Or a collection of haplotypes (one allowed)
+            haplotypeTrackID = i;
           }
         }
 
         console.log("Graph track: " + graphTrackID + " Haplotype track: " + haplotypeTrackID);
 
         const nodes = tubeMap.vgExtractNodes(json.graph);
-        // TODO: For now claim haplotypes came from the graph
-        const tracks = tubeMap.vgExtractTracks(json.graph, graphTrackID, graphTrackID);
+        const tracks = tubeMap.vgExtractTracks(json.graph, graphTrackID, haplotypeTrackID);
 
         // Call vgExtractReads on each file of reads and store in readsArr
         let readsArr = [];
         // Count total reads seen so far.
         let totalReads = 0;
+        console.log("json gams", Object.values(json.gam));
         for (const gam of Object.values(json.gam)) {
           // For each returned list of reads from a file, convert all those reads to tube map format.
           // Include total read count to prevent duplicate ids.
           // Also include the source track's ID.
+          console.log("readTrackIDs", readTrackIDs);
           let newReads = tubeMap.vgExtractReads(nodes, tracks, gam, totalReads, readTrackIDs[readsArr.length]);
           readsArr.push(newReads);
           totalReads += newReads.length;
diff --git a/src/config.json b/src/config.json
index 0b1f947..a065446 100644
--- a/src/config.json
+++ b/src/config.json
@@ -4,8 +4,8 @@
     {
       "name": "snp1kg-BRCA1",
       "tracks": [
-        {"files": [{"type": "graph", "name": "snp1kg-BRCA1.vg.xg"}]},
-        {"files": [{"type": "read", "name": "NA12878-BRCA1.sorted.gam"}]}
+        {"trackFile": "snp1kg-BRCA1.vg.xg", "trackType": "graph"},
+        {"trackFile": "NA12878-BRCA1.sorted.gam", "trackType": "read"}
       ],
       "dataPath": "default",
       "region": "17:1-100",
@@ -15,8 +15,8 @@
     {
       "name": "vg \"small\" example",
       "tracks": [
-        {"files": [{"type": "graph", "name": "x.vg.xg"}]},
-        {"files": [{"type": "haplotype", "name": "x.vg.gbwt"}]}
+        {"trackFile": "x.vg.xg", "trackType": "graph"},
+        {"trackFile": "x.vg.gbwt", "trackType": "haplotype"}
       ],
       "dataPath": "mounted",
       "dataType": "built-in",
@@ -25,8 +25,8 @@
     {
       "name": "cactus",
       "tracks": [
-        {"files": [{"type": "graph", "name": "cactus.vg.xg"}]},
-        {"files": [{"type": "read", "name": "cactus-NA12879.sorted.gam"}]}
+        {"trackFile": "cactus.vg.xg", "trackType": "graph"},
+        {"trackFile": "cactus-NA12879.sorted.gam", "trackType": "read"}
       ],
       "dataPath": "mounted",
       "bedFile": "cactus.bed",
@@ -36,9 +36,9 @@
     {
       "name": "cactus multiple reads",
       "tracks": [
-        {"files": [{"type": "graph", "name": "cactus.vg.xg"}]},
-        {"files": [{"type": "read", "name": "cactus0_10.sorted.gam"}]},
-        {"files": [{"type": "read", "name": "cactus10_20.sorted.gam"}]}
+        {"trackFile": "cactus.vg.xg", "trackType": "graph"},
+        {"trackFile": "cactus0_10.sorted.gam", "trackType": "read"},
+        {"trackFile": "cactus10_20.sorted.gam", "trackType": "read"}
       ],
       "dataPath": "mounted",
       "bedFile": "cactus.bed",
diff --git a/src/server.mjs b/src/server.mjs
index cf6a342..ee7ab56 100644
--- a/src/server.mjs
+++ b/src/server.mjs
@@ -218,10 +218,8 @@ function endsWithExtensions(file, extensions) {
 // returns the file name of the specified type in that track
 // returns falsy value if file type is not found
 function getFileFromType(track, type) {
-  for (const file of track.files) {
-    if (file.type == type) {
-      return file.name;
-    }
+  if (track.trackType === type) {
+    return track.trackFile;
   }
   return "none";
 }
@@ -232,8 +230,8 @@ function getFileFromType(track, type) {
 //
 // This is a fancy ES6 generator.
 function* eachFileOfType(tracks, type) {
-  for (const track of tracks) {
-    const file = getFileFromType(track, type);
+  for (const key in tracks) {
+    const file = getFileFromType(tracks[key], type);
     if (file && file !== "none") {
       yield file;
     }
@@ -295,6 +293,11 @@ api.post("/getChunkedData", (req, res, next) => {
 
   let gamFiles = getGams(req.body.tracks);
 
+  console.log("graphFile ", graphFile);
+  console.log("gbwtFile ", gbwtFile);
+  console.log("bedFile ", bedFile);
+  console.log("gamFiles ", gamFiles);
+
   req.withGam = true;
   if (!gamFiles || !gamFiles.length) {
     req.withGam = false;
@@ -956,9 +959,7 @@ function pickDataPath(reqDataPath) {
 api.get("/getFilenames", (req, res) => {
   console.log("received request for filenames");
   const result = {
-    graphFiles: [],
-    gbwtFiles: [],
-    gamIndices: [],
+    files: [], // store a list of file object, excluding bed files, {  name: string; type: filetype;}
     bedFiles: [],
   };
 
@@ -966,13 +967,13 @@ api.get("/getFilenames", (req, res) => {
     // list files in folder
     fs.readdirSync(MOUNTED_DATA_PATH).forEach((file) => {
       if (endsWithExtensions(file, GRAPH_EXTENSIONS)) {
-        result.graphFiles.push(file);
+        result.files.push({"trackFile": file, "trackType": "graph"});
       }
       if (endsWithExtensions(file, HAPLOTYPE_EXTENSIONS)) {
-        result.gbwtFiles.push(file);
+        result.files.push({"trackFile": file, "trackType": "haplotype"});
       }
       if (file.endsWith(".sorted.gam")) {
-        result.gamIndices.push(file);
+        result.files.push({"trackFile": file, "trackType": "read"});
       }
       if (file.endsWith(".bed")) {
         result.bedFiles.push(file);
diff --git a/src/util/tubemap.js b/src/util/tubemap.js
index 36dd30f..3d868ae 100644
--- a/src/util/tubemap.js
+++ b/src/util/tubemap.js
@@ -124,9 +124,8 @@ const config = {
   nodeWidthOption: 0,
   showReads: true,
   showSoftClips: true,
-  colorSchemes: [], 
+  colorSchemes: {}, 
            // colors corresponds with tracks(input files), [haplotype, read1, read2, ...]
-           // stores haplotype color in the first slot
   exonColors: "lightColors",
   hideLegendFlag: false,
   colorReadsByMappingQuality: false,
@@ -2384,10 +2383,11 @@ function generateTrackColor(track, highlight) {
       // Don't repeat the color of the first track (reference) to highilight is better.
       // TODO: Allow using color 0 for other schemes not the same as the one for the reference path.
       // TODO: Stop reads from taking this color?
-      if (track.id === 0) {
-        trackColor = getColorSet(config.colorSchemes[sourceID].mainPalette)[0];
+
+      const colorSet = getColorSet(config.colorSchemes[sourceID].mainPalette);
+      if (track.id === 0 || colorSet.length === 1) {
+        trackColor = colorSet[0];
       } else {
-        const colorSet = getColorSet(config.colorSchemes[sourceID].mainPalette);
         trackColor = colorSet[((track.id - 1) % (colorSet.length - 1)) + 1];
       }
     } else {
@@ -3740,9 +3740,13 @@ export function vgExtractTracks(vg, pathSourceTrackId, haplotypeSourceTrackID) {
     track.id = index;
     track.sequence = sequence;
     track.isCompletelyReverse = isCompletelyReverse;
-    if (path.hasOwnProperty("freq")) {
+    // Even non-haplotype paths will be assigned a "freq" field by vg. See
+    // <https://github.com/vgteam/vg/blob/6b34cd50e851eb9a91be3a605e040c9be1d4b78e/src/haplotype_extracter.cpp#L52-L55>.
+    // We want to copy those through so that non-haplotype paths have a normal width.
+    track.freq = path.freq;
+    // But haplotypes will have names starting with "thread_".
+    if (path.name && path.name.startsWith("thread_")) {
       // This is a haplotype
-      track.freq = path.freq;
       track.sourceTrackID = haplotypeSourceTrackID;
     } else {
       // This is a path


Test patch:
diff --git a/src/components/TrackFilePicker.test.js b/src/components/TrackFilePicker.test.js
index 52b4976..fc50686 100644
--- a/src/components/TrackFilePicker.test.js
+++ b/src/components/TrackFilePicker.test.js
@@ -5,11 +5,11 @@ import {TrackFilePicker} from './TrackFilePicker';
 
 
 describe('TrackFilePicker', () => {
-    const testTracks = [{"files": [{"name": "fileA1.vg", "type": "graph"},
-                                   {"name": "fileA2.gbwt", "type": "haplotype"}]},
-                        {"files": [{"name": "fileB1.gbwt", "type": "haplotype"},
-                                   {"name": "fileB2.gam", "type": "read"}]},
-                        {"files": [{"name": "fileC1.xg", "type": "graph"}]}];
+    const testTracks = [{"trackFile": "fileA1.vg", "trackType": "graph"},
+                        {"trackFile": "fileA2.gbwt", "trackType": "haplotype"},
+                        {"trackFile": "fileB1.gbwt", "trackType": "haplotype"},
+                        {"trackFile": "fileB2.gam", "trackType": "read"},
+                        {"trackFile": "fileC1.xg", "trackType": "graph"}];
 
     it('should render without errors', async () => {
         const fakeOnChange = jest.fn();
@@ -33,7 +33,7 @@ describe('TrackFilePicker', () => {
             tracks={testTracks}
             fileType={"graph"}
             pickerType={"dropdown"}
-            value={{"name": "fileA1.vg", "type": "graph"}}
+            value={"fileA1.vg"}
             handleInputChange={fakeOnChange}/>
         );
 
@@ -47,7 +47,7 @@ describe('TrackFilePicker', () => {
             tracks={testTracks}
             fileType={"graph"}
             pickerType={"dropdown"}
-            value={{"name": "fileC1.xg", "type": "graph"}}
+            value={"fileC1.xg"}
             handleInputChange={fakeOnChange}/>
         );
         
@@ -79,7 +79,7 @@ describe('TrackFilePicker', () => {
         fireEvent.click(getByText("fileB1.gbwt"));
 
         expect(fakeOnChange).toHaveBeenCalledTimes(1);
-        expect(fakeOnChange).toHaveBeenCalledWith({"name": "fileB1.gbwt", "type": "haplotype"});
+        expect(fakeOnChange).toHaveBeenCalledWith("fileB1.gbwt");
 
         // make sure we can repeat the process
         fireEvent.keyDown(fileSelectComponent.firstChild, {key: "ArrowDown"});
@@ -87,7 +87,7 @@ describe('TrackFilePicker', () => {
         fireEvent.click(getByText("fileA2.gbwt"));
 
         expect(fakeOnChange).toHaveBeenCalledTimes(2);
-        expect(fakeOnChange).toHaveBeenCalledWith({"name": "fileA2.gbwt", "type": "haplotype"});
+        expect(fakeOnChange).toHaveBeenCalledWith("fileA2.gbwt");
 
     });
 
@@ -113,7 +113,7 @@ describe('TrackFilePicker', () => {
         fireEvent.click(getByText("fileC1.xg"));
 
         expect(fakeOnChange).toHaveBeenCalledTimes(1);
-        expect(fakeOnChange).toHaveBeenCalledWith({"name": "fileC1.xg", "type": "graph"});
+        expect(fakeOnChange).toHaveBeenCalledWith("fileC1.xg");
     })
     
 });
diff --git a/src/components/TrackList.test.js b/src/components/TrackList.test.js
index dde95f9..ebebcd7 100644
--- a/src/components/TrackList.test.js
+++ b/src/components/TrackList.test.js
@@ -35,11 +35,11 @@ describe('TrackList', () => {
         },
     }
     const availableColors = ["greys", "ygreys", "reds", "plainColors", "lightColors"];
-    const availableTracks = [{"files": [{"name": "fileA1.vg", "type": "graph"},
-                                        {"name": "fileA2.gbwt", "type": "haplotype"}]},
-                             {"files": [{"name": "fileB1.gbwt", "type": "haplotype"},
-                                        {"name": "fileB2.gam", "type": "read"}]},
-                             {"files": [{"name": "fileC1.xg", "type": "graph"}]}];
+    const availableTracks = [{"trackFile": "fileA1.vg", "trackType": "graph"},
+                            {"trackFile": "fileA2.gbwt", "trackType": "haplotype"},
+                            {"trackFile": "fileB1.gbwt", "trackType": "haplotype"},
+                            {"trackFile": "fileB2.gam", "trackType": "read"},
+                            {"trackFile": "fileC1.xg", "trackType": "graph"}];
 
     function rerenderTrackList(rerender, newTracks, fakeOnChange, fakeOnDelete) {
         rerender(
@@ -110,7 +110,8 @@ describe('TrackList', () => {
         fireEvent.click(getByText("fileB1.gbwt"));
 
         expect(fakeOnChange).toHaveBeenCalledTimes(2);
-        newTracks[1].trackFile = {"name": "fileB1.gbwt", "type": "haplotype"};
+        newTracks[1].trackFile = "fileB1.gbwt";
+        newTracks[1].trackType = "haplotype";
         expect(fakeOnChange).toHaveBeenCalledWith(newTracks);
 
         // rerender file select effect
diff --git a/src/components/TrackListItem.test.js b/src/components/TrackListItem.test.js
index ed34273..c388d25 100644
--- a/src/components/TrackListItem.test.js
+++ b/src/components/TrackListItem.test.js
@@ -7,11 +7,11 @@ describe('TrackListItem', () => {
     const trackFile = undefined;
     const trackType = "graph";
     const availableColors = ["greys", "ygreys", "reds", "plainColors", "lightColors"];
-    const availableTracks = [{"files": [{"name": "fileA1.vg", "type": "graph"},
-                                        {"name": "fileA2.gbwt", "type": "haplotype"}]},
-                             {"files": [{"name": "fileB1.gbwt", "type": "haplotype"},
-                                        {"name": "fileB2.gam", "type": "read"}]},
-                             {"files": [{"name": "fileC1.xg", "type": "graph"}]}];
+    const availableTracks = [{"trackFile": "fileA1.vg", "trackType": "graph"},
+                            {"trackFile": "fileA2.gbwt", "trackType": "haplotype"},
+                            {"trackFile": "fileB1.gbwt", "trackType": "haplotype"},
+                            {"trackFile": "fileB2.gam", "trackType": "read"},
+                            {"trackFile": "fileC1.xg", "trackType": "graph"}];
     const trackColorSettings = {    
         mainPalette: "blues",
         auxPalette: "reds",
@@ -100,7 +100,7 @@ describe('TrackListItem', () => {
 
         expect(fakeOnChange).toHaveBeenCalledTimes(2);
         expect(fakeOnChange).toHaveBeenCalledWith(1, {
-            trackFile: {"name": "fileB1.gbwt", "type": "haplotype"},
+            trackFile: "fileB1.gbwt",
             trackType: "haplotype",
             trackColorSettings: trackColorSettings
         });
@@ -108,7 +108,7 @@ describe('TrackListItem', () => {
         rerender(
             <TrackListItem 
             trackProps={{
-                trackFile: {"name": "fileB1.gbwt", "type": "haplotype"},
+                trackFile: "fileB1.gbwt",
                 trackType: "haplotype",
                 trackColorSettings: trackColorSettings
               }}
diff --git a/src/components/TrackPicker.test.js b/src/components/TrackPicker.test.js
index 8c8ded9..17e137d 100644
--- a/src/components/TrackPicker.test.js
+++ b/src/components/TrackPicker.test.js
@@ -12,11 +12,11 @@ describe('TrackPicker', () => {
         3: config.defaultTrackProps,
     }
     const availableColors = ["greys", "ygreys", "reds", "plainColors", "lightColors"];
-    const availableTracks = [{"files": [{"name": "fileA1.vg", "type": "graph"},
-                                        {"name": "fileA2.gbwt", "type": "haplotype"}]},
-                             {"files": [{"name": "fileB1.gbwt", "type": "haplotype"},
-                                        {"name": "fileB2.gam", "type": "read"}]},
-                             {"files": [{"name": "fileC1.xg", "type": "graph"}]}];
+    const availableTracks = [{"trackFile": "fileA1.vg", "trackType": "graph"},
+                            {"trackFile": "fileA2.gbwt", "trackType": "haplotype"},
+                            {"trackFile": "fileB1.gbwt", "trackType": "haplotype"},
+                            {"trackFile": "fileB2.gam", "trackType": "read"},
+                            {"trackFile": "fileC1.xg", "trackType": "graph"}];
 
     it('should render without errors', async () => {
         const fakeOnChange = jest.fn();
@@ -124,10 +124,12 @@ describe('TrackPicker', () => {
 
         let newTracks = JSON.parse(JSON.stringify(tracks));
 
-        newTracks[1].trackFile = {"name": "fileA1.vg", "type": "graph"};
+        newTracks[1].trackFile = "fileA1.vg";
+        newTracks[1].trackType = "graph";
+        newTracks[2].trackFile = "fileB1.gbwt"
         newTracks[2].trackType = "haplotype";
-        newTracks[2].trackFile = {"name": "fileB1.gbwt", "type": "haplotype"};
-        newTracks[3].trackFile = {"name": "fileC1.xg", "type": "graph"};
+        newTracks[3].trackFile = "fileC1.xg";
+        newTracks[3].trackType = "graph";
 
         expect(fakeOnChange).toHaveBeenCalledTimes(1);
         expect(fakeOnChange).toHaveBeenCalledWith(newTracks);
@@ -155,7 +157,7 @@ describe('TrackPicker', () => {
         fireEvent.click(getByText("fileB2.gam"));
 
         newTracks[4] = {
-            trackFile: {"name": "fileB2.gam", "type": "read"},
+            trackFile: "fileB2.gam",
             trackType: "read",
             trackColorSettings: {    
                 mainPalette: "blues",
diff --git a/src/components/TrackPickerDisplay.test.js b/src/components/TrackPickerDisplay.test.js
index 6d2e15b..e1231ed 100644
--- a/src/components/TrackPickerDisplay.test.js
+++ b/src/components/TrackPickerDisplay.test.js
@@ -12,11 +12,11 @@ describe('TrackPickerDisplay', () => {
         3: config.defaultTrackProps,
     }
     const availableColors = ["greys", "ygreys", "reds", "plainColors", "lightColors"];
-    const availableTracks = [{"files": [{"name": "fileA1.vg", "type": "graph"},
-                                        {"name": "fileA2.gbwt", "type": "haplotype"}]},
-                             {"files": [{"name": "fileB1.gbwt", "type": "haplotype"},
-                                        {"name": "fileB2.gam", "type": "read"}]},
-                             {"files": [{"name": "fileC1.xg", "type": "graph"}]}];
+    const availableTracks = [{"trackFile": "fileA1.vg", "trackType": "graph"},
+                            {"trackFile": "fileA2.gbwt", "trackType": "haplotype"},
+                            {"trackFile": "fileB1.gbwt", "trackType": "haplotype"},
+                            {"trackFile": "fileB2.gam", "trackType": "read"},
+                            {"trackFile": "fileC1.xg", "trackType": "graph"}];
 
     it('should render without errors', async () => {
         const fakeOnChange = jest.fn();
@@ -116,10 +116,12 @@ describe('TrackPickerDisplay', () => {
 
         let newTracks = JSON.parse(JSON.stringify(tracks));
 
-        newTracks[1].trackFile = {"name": "fileA1.vg", "type": "graph"};
+        newTracks[1].trackFile = "fileA1.vg";
+        newTracks[1].trackType = "graph";
+        newTracks[2].trackFile = "fileB1.gbwt"
         newTracks[2].trackType = "haplotype";
-        newTracks[2].trackFile = {"name": "fileB1.gbwt", "type": "haplotype"};
-        newTracks[3].trackFile = {"name": "fileC1.xg", "type": "graph"};
+        newTracks[3].trackFile = "fileC1.xg"
+        newTracks[3].trackType = "graph"
 
         expect(fakeOnChange).toHaveBeenCalledTimes(1);
         expect(fakeOnChange).toHaveBeenCalledWith(newTracks);
@@ -147,7 +149,7 @@ describe('TrackPickerDisplay', () => {
         fireEvent.click(getByText("fileB2.gam"));
 
         newTracks[4] = {
-            trackFile: {"name": "fileB2.gam", "type": "read"},
+            trackFile: "fileB2.gam",
             trackType: "read",
             trackColorSettings: {    
                 mainPalette: "blues",
diff --git a/src/end-to-end.test.js b/src/end-to-end.test.js
index fda9d9b..569e087 100644
--- a/src/end-to-end.test.js
+++ b/src/end-to-end.test.js
@@ -39,7 +39,6 @@ let fakeClipboard = undefined;
 // This needs to be called by global and per-scope beforeEach
 async function setUp() {
   setCopyCallback((value) => (fakeClipboard = value));
-
   // Create the application.
   render(<App apiUrl={serverState.getApiUrl()} />);
 }
@@ -309,7 +308,7 @@ describe("When we wait for it to load", () => {
 it("produces correct link for view before & after go is pressed", async () => {
   // First test that after pressing go, the link reflects the dat form
   const expectedLinkBRCA1 =
-    "http://localhost?name=snp1kg-BRCA1&tracks[0][files][0][type]=graph&tracks[0][files][0][name]=snp1kg-BRCA1.vg.xg&tracks[1][files][0][type]=read&tracks[1][files][0][name]=NA12878-BRCA1.sorted.gam&dataPath=default&region=17:1-100&bedFile=snp1kg-BRCA1.bed&dataType=built-in";
+    "http://localhost?name=snp1kg-BRCA1&tracks[0][trackFile]=snp1kg-BRCA1.vg.xg&tracks[0][trackType]=graph&tracks[1][trackFile]=NA12878-BRCA1.sorted.gam&tracks[1][trackType]=read&dataPath=default&region=17:1-100&bedFile=snp1kg-BRCA1.bed&dataType=built-in";
   // Set up dropdown
   await act(async () => {
     let dropdown = document.getElementById("dataSourceSelect");
@@ -343,7 +342,7 @@ it("produces correct link for view before & after go is pressed", async () => {
   await clickCopyLink();
 
   const expectedLinkCactus =
-    "http://localhost?tracks[0][files][0][type]=graph&tracks[0][files][0][name]=cactus.vg.xg&tracks[1][files][0][type]=read&tracks[1][files][0][name]=cactus-NA12879.sorted.gam&bedFile=cactus.bed&name=cactus&region=ref:1-100&dataPath=mounted&dataType=built-in";
+    "http://localhost?tracks[0][trackFile]=cactus.vg.xg&tracks[0][trackType]=graph&tracks[1][trackFile]=cactus-NA12879.sorted.gam&tracks[1][trackType]=read&bedFile=cactus.bed&name=cactus&region=ref:1-100&dataPath=mounted&dataType=built-in";
   // Make sure link has changed after pressing go
   expect(fakeClipboard).toEqual(expectedLinkCactus);
 });
@@ -362,17 +361,32 @@ it("can retrieve the list of mounted graph files", async () => {
   });
 
   // Find the select box's input
-  let graphSelectInput = screen.getByLabelText(/graph file:/i);
-  expect(graphSelectInput).toBeTruthy();
+  let trackSelectButton = screen.queryByTestId("TrackPickerButton");
+  expect(trackSelectButton).toBeTruthy();
+
+  // open track selection
+  await act(async () => {
+    //fireEvent.click(trackSelectButton);
+    userEvent.click(trackSelectButton);
+  });
+
+
+  // add a new track
+  await waitFor(() => {
+    fireEvent.click(screen.queryByTestId("track-add-button-component"));
+  });
+
 
   // We shouldn't see the option before we open the dropdown
   expect(screen.queryByText("cactus.vg.xg")).not.toBeInTheDocument();
 
+
   // Make sure the right entry eventually shows up (since we could be racing
   // the initial load from the component mounting)
   await waitFor(() => {
-    // Open the selector and see if it is there
-    selectEvent.openMenu(graphSelectInput);
-    expect(screen.getByText("cactus.vg.xg")).toBeInTheDocument();
+    // try to select a graph file
+    fireEvent.keyDown(screen.queryByTestId('file-select-component1').firstChild, {key: "ArrowDown"});
   });
+
+  expect(screen.queryByText("cactus.vg.xg")).toBeTruthy();
 });