<commit_before>import { createLogic } from 'redux-logic';

import { mapRefocus } from 'fm3/actions/mapActions';
import { startProgress, stopProgress, setActiveModal } from 'fm3/actions/mainActions';
import { toastsAddError } from 'fm3/actions/toastsActions';
import { gallerySetImageIds, galleryRequestImage, gallerySetImage, galleryRemoveItem, galleryUpload, galleryUploadFinished, gallerySetItemError, gallerySetTags } from 'fm3/actions/galleryActions';
import { infoPointSet } from 'fm3/actions/infoPointActions';
import { API_URL } from 'fm3/backendDefinitions';

const galleryRequestImagesLogic = createLogic({
  cancelType: ['SET_TOOL', 'MAP_RESET'],
  type: 'GALLERY_REQUEST_IMAGES',
  process({ action: { payload: { lat, lon } }, getState, cancelled$ }, dispatch, done) {
    const pid = Math.random();
    dispatch(startProgress(pid));
    cancelled$.subscribe(() => {
      dispatch(stopProgress(pid));
    });

    fetch(`${API_URL}/gallery/pictures?by=radius&lat=${lat}&lon=${lon}&distance=${5000 / 2 ** getState().map.zoom}`)
      .then((res) => {
        if (res.status !== 200) {
          throw new Error(`Server vrátil neočakávaný status: ${res.status}`);
        }
        return res.json();
      })
      .then((payload) => {
        const ids = payload.map(item => item.id);
        dispatch(gallerySetImageIds(ids));
        dispatch(galleryRequestImage(ids[0]));
      })
      .catch((e) => {
        dispatch(toastsAddError(`Nastala chyba pri načítavaní fotiek: ${e.message}`));
      })
      .then(() => {
        dispatch(stopProgress(pid));
        done();
      });
  },
});

const galleryRequestImageLogic = createLogic({
  cancelType: ['SET_TOOL', 'MAP_RESET'],
  type: 'GALLERY_REQUEST_IMAGE',
  process({ action: { payload: id }, getState, cancelled$ }, dispatch, done) {
    const pid = Math.random();
    dispatch(startProgress(pid));
    cancelled$.subscribe(() => {
      dispatch(stopProgress(pid));
    });

    fetch(`${API_URL}/gallery/pictures/${id}`, {
      headers: getState().auth.user ? {
        Authorization: `Bearer ${getState().auth.user.authToken}`,
      } : {},
    })
      .then((res) => {
        if (res.status !== 200) {
          throw new Error(`Server vrátil neočakávaný status: ${res.status}`);
        }
        return res.json();
      })
      .then((payload) => {
        dispatch(gallerySetImage({
          ...payload,
          createdAt: new Date(payload.createdAt),
          takenAt: payload.takenAt && new Date(payload.takenAt),
          comments: payload.comments.map(comment => ({ ...comment, createdAt: new Date(comment.createdAt) })),
        }));
      })
      .catch((e) => {
        dispatch(toastsAddError(`Nastala chyba pri načítavaní fotky: ${e.message}`));
      })
      .then(() => {
        dispatch(stopProgress(pid));
        done();
      });
  },
});

const galleryShowOnTheMapLogic = createLogic({
  type: 'GALLERY_SHOW_ON_THE_MAP',
  process({ getState }, dispatch, done) {
    const { image } = getState().gallery;
    if (image) {
      dispatch(infoPointSet(image.lat, image.lon, image.title));
      dispatch(mapRefocus({ lat: image.lat, lon: image.lon }));
    }
    done();
  },
});

const galleryUploadModalLogic = createLogic({
  type: 'SET_ACTIVE_MODAL',
  transform({ getState, action }, next) {
    if (action.payload === 'gallery-upload' && !getState().auth.user) {
      next(toastsAddError('Pre nahrávanie fotiek do galérie musíte byť prihlásený.'));
    } else {
      next(action);
    }
  },
  process({ action }, dispatch, done) {
    if (action.payload === 'gallery-upload') {
      const pid = Math.random();
      dispatch(startProgress(pid));

      fetch(`${API_URL}/gallery/picture-tags`)
        .then((res) => {
          if (res.status !== 200) {
            throw new Error(`Server vrátil neočakávaný status: ${res.status}`);
          }
          return res.json();
        })
        .then((payload) => {
          dispatch(gallerySetTags(payload));
        })
        .catch((e) => {
          dispatch(toastsAddError(`Nastala chyba pri načítavaní tagov: ${e.message}`));
        })
        .then(() => {
          dispatch(stopProgress(pid));
          done();
        });
    } else {
      done();
    }
  },
});

const galleryItemUploadLogic = createLogic({
  type: ['GALLERY_UPLOAD'],
  cancelType: 'SET_ACTIVE_MODAL',
  process({ getState }, dispatch, done) {
    const { items, uploadingId } = getState().gallery;

    if (uploadingId === null) {
      dispatch(galleryUploadFinished());
      if (getState().gallery.items.length === 0) {
        dispatch(setActiveModal(null));
      }
      done();
      return;
    }

    const item = items.find(({ id }) => id === uploadingId);

    const formData = new FormData();
    formData.append('image', item.file);
    formData.append('meta', JSON.stringify({
      title: item.title,
      description: item.description,
      position: item.position,
      takenAt: item.takenAt && item.takenAt.toISOString(),
      tags: item.tags,
    }));

    fetch(`${API_URL}/gallery/pictures`, {
      method: 'POST',
      headers: {
        Accept: 'application/json',
        Authorization: `Bearer ${getState().auth.user.authToken}`,
      },
      body: formData,
    }).then((res) => {
      if (res.status !== 200) {
        throw new Error(`Server vrátil neočakávaný status: ${res.status}`);
      }
      dispatch(galleryRemoveItem(item.id));
      dispatch(galleryUpload());
    }).catch((err) => {
      dispatch(gallerySetItemError(item.id, err.message));
      dispatch(galleryUpload());
    }).then(() => {
      done();
    });
  },
});

const gallerySubmitCommentLogic = createLogic({
  cancelType: ['SET_TOOL', 'MAP_RESET'],
  type: 'GALLERY_SUBMIT_COMMENT',
  process({ getState, cancelled$ }, dispatch, done) {
    const pid = Math.random();
    dispatch(startProgress(pid));
    cancelled$.subscribe(() => {
      dispatch(stopProgress(pid));
    });

    const id = getState().gallery.activeImageId;

    fetch(`${API_URL}/gallery/pictures/${id}/comments`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Accept: 'application/json',
        Authorization: `Bearer ${getState().auth.user.authToken}`,
      },
      body: JSON.stringify({
        comment: getState().gallery.comment,
      }),
    })
      .then((res) => {
        if (res.status !== 200) {
          throw new Error(`Server vrátil neočakávaný status: ${res.status}`);
        }
        return res.json();
      })
      .then(() => {
        dispatch(galleryRequestImage(id));
      })
      .catch((e) => {
        dispatch(toastsAddError(`Nastala chyba pri pridávani komentára: ${e.message}`));
      })
      .then(() => {
        dispatch(stopProgress(pid));
        done();
      });
  },
});

const gallerySubmitStarsLogic = createLogic({
  cancelType: ['SET_TOOL', 'MAP_RESET'],
  type: 'GALLERY_SUBMIT_STARS',
  process({ action: { payload: stars }, getState, cancelled$ }, dispatch, done) {
    const pid = Math.random();
    dispatch(startProgress(pid));
    cancelled$.subscribe(() => {
      dispatch(stopProgress(pid));
    });

    const id = getState().gallery.activeImageId;

    fetch(`${API_URL}/gallery/pictures/${id}/rating`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${getState().auth.user.authToken}`,
      },
      body: JSON.stringify({
        stars,
      }),
    })
      .then((res) => {
        if (res.status !== 204) {
          throw new Error(`Server vrátil neočakávaný status: ${res.status}`);
        }
        dispatch(galleryRequestImage(id));
      })
      .catch((e) => {
        dispatch(toastsAddError(`Nastala chyba pri hodnotení: ${e.message}`));
      })
      .then(() => {
        dispatch(stopProgress(pid));
        done();
      });
  },
});

export default [
  galleryRequestImagesLogic,
  galleryRequestImageLogic,
  galleryShowOnTheMapLogic,
  galleryUploadModalLogic,
  galleryItemUploadLogic,
  gallerySubmitCommentLogic,
  gallerySubmitStarsLogic,
];
<commit_msg>prevented showing undefined picture if none is found<commit_after>import { createLogic } from 'redux-logic';

import { mapRefocus } from 'fm3/actions/mapActions';
import { startProgress, stopProgress, setActiveModal } from 'fm3/actions/mainActions';
import { toastsAddError } from 'fm3/actions/toastsActions';
import { gallerySetImageIds, galleryRequestImage, gallerySetImage, galleryRemoveItem, galleryUpload, galleryUploadFinished, gallerySetItemError, gallerySetTags } from 'fm3/actions/galleryActions';
import { infoPointSet } from 'fm3/actions/infoPointActions';
import { API_URL } from 'fm3/backendDefinitions';

const galleryRequestImagesLogic = createLogic({
  cancelType: ['SET_TOOL', 'MAP_RESET'],
  type: 'GALLERY_REQUEST_IMAGES',
  process({ action: { payload: { lat, lon } }, getState, cancelled$ }, dispatch, done) {
    const pid = Math.random();
    dispatch(startProgress(pid));
    cancelled$.subscribe(() => {
      dispatch(stopProgress(pid));
    });

    fetch(`${API_URL}/gallery/pictures?by=radius&lat=${lat}&lon=${lon}&distance=${5000 / 2 ** getState().map.zoom}`)
      .then((res) => {
        if (res.status !== 200) {
          throw new Error(`Server vrátil neočakávaný status: ${res.status}`);
        }
        return res.json();
      })
      .then((payload) => {
        const ids = payload.map(item => item.id);
        dispatch(gallerySetImageIds(ids));
        if (ids.length) {
          dispatch(galleryRequestImage(ids[0]));
        }
      })
      .catch((e) => {
        dispatch(toastsAddError(`Nastala chyba pri načítavaní fotiek: ${e.message}`));
      })
      .then(() => {
        dispatch(stopProgress(pid));
        done();
      });
  },
});

const galleryRequestImageLogic = createLogic({
  cancelType: ['SET_TOOL', 'MAP_RESET'],
  type: 'GALLERY_REQUEST_IMAGE',
  process({ action: { payload: id }, getState, cancelled$ }, dispatch, done) {
    const pid = Math.random();
    dispatch(startProgress(pid));
    cancelled$.subscribe(() => {
      dispatch(stopProgress(pid));
    });

    fetch(`${API_URL}/gallery/pictures/${id}`, {
      headers: getState().auth.user ? {
        Authorization: `Bearer ${getState().auth.user.authToken}`,
      } : {},
    })
      .then((res) => {
        if (res.status !== 200) {
          throw new Error(`Server vrátil neočakávaný status: ${res.status}`);
        }
        return res.json();
      })
      .then((payload) => {
        dispatch(gallerySetImage({
          ...payload,
          createdAt: new Date(payload.createdAt),
          takenAt: payload.takenAt && new Date(payload.takenAt),
          comments: payload.comments.map(comment => ({ ...comment, createdAt: new Date(comment.createdAt) })),
        }));
      })
      .catch((e) => {
        dispatch(toastsAddError(`Nastala chyba pri načítavaní fotky: ${e.message}`));
      })
      .then(() => {
        dispatch(stopProgress(pid));
        done();
      });
  },
});

const galleryShowOnTheMapLogic = createLogic({
  type: 'GALLERY_SHOW_ON_THE_MAP',
  process({ getState }, dispatch, done) {
    const { image } = getState().gallery;
    if (image) {
      dispatch(infoPointSet(image.lat, image.lon, image.title));
      dispatch(mapRefocus({ lat: image.lat, lon: image.lon }));
    }
    done();
  },
});

const galleryUploadModalLogic = createLogic({
  type: 'SET_ACTIVE_MODAL',
  transform({ getState, action }, next) {
    if (action.payload === 'gallery-upload' && !getState().auth.user) {
      next(toastsAddError('Pre nahrávanie fotiek do galérie musíte byť prihlásený.'));
    } else {
      next(action);
    }
  },
  process({ action }, dispatch, done) {
    if (action.payload === 'gallery-upload') {
      const pid = Math.random();
      dispatch(startProgress(pid));

      fetch(`${API_URL}/gallery/picture-tags`)
        .then((res) => {
          if (res.status !== 200) {
            throw new Error(`Server vrátil neočakávaný status: ${res.status}`);
          }
          return res.json();
        })
        .then((payload) => {
          dispatch(gallerySetTags(payload));
        })
        .catch((e) => {
          dispatch(toastsAddError(`Nastala chyba pri načítavaní tagov: ${e.message}`));
        })
        .then(() => {
          dispatch(stopProgress(pid));
          done();
        });
    } else {
      done();
    }
  },
});

const galleryItemUploadLogic = createLogic({
  type: ['GALLERY_UPLOAD'],
  cancelType: 'SET_ACTIVE_MODAL',
  process({ getState }, dispatch, done) {
    const { items, uploadingId } = getState().gallery;

    if (uploadingId === null) {
      dispatch(galleryUploadFinished());
      if (getState().gallery.items.length === 0) {
        dispatch(setActiveModal(null));
      }
      done();
      return;
    }

    const item = items.find(({ id }) => id === uploadingId);

    const formData = new FormData();
    formData.append('image', item.file);
    formData.append('meta', JSON.stringify({
      title: item.title,
      description: item.description,
      position: item.position,
      takenAt: item.takenAt && item.takenAt.toISOString(),
      tags: item.tags,
    }));

    fetch(`${API_URL}/gallery/pictures`, {
      method: 'POST',
      headers: {
        Accept: 'application/json',
        Authorization: `Bearer ${getState().auth.user.authToken}`,
      },
      body: formData,
    }).then((res) => {
      if (res.status !== 200) {
        throw new Error(`Server vrátil neočakávaný status: ${res.status}`);
      }
      dispatch(galleryRemoveItem(item.id));
      dispatch(galleryUpload());
    }).catch((err) => {
      dispatch(gallerySetItemError(item.id, err.message));
      dispatch(galleryUpload());
    }).then(() => {
      done();
    });
  },
});

const gallerySubmitCommentLogic = createLogic({
  cancelType: ['SET_TOOL', 'MAP_RESET'],
  type: 'GALLERY_SUBMIT_COMMENT',
  process({ getState, cancelled$ }, dispatch, done) {
    const pid = Math.random();
    dispatch(startProgress(pid));
    cancelled$.subscribe(() => {
      dispatch(stopProgress(pid));
    });

    const id = getState().gallery.activeImageId;

    fetch(`${API_URL}/gallery/pictures/${id}/comments`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Accept: 'application/json',
        Authorization: `Bearer ${getState().auth.user.authToken}`,
      },
      body: JSON.stringify({
        comment: getState().gallery.comment,
      }),
    })
      .then((res) => {
        if (res.status !== 200) {
          throw new Error(`Server vrátil neočakávaný status: ${res.status}`);
        }
        return res.json();
      })
      .then(() => {
        dispatch(galleryRequestImage(id));
      })
      .catch((e) => {
        dispatch(toastsAddError(`Nastala chyba pri pridávani komentára: ${e.message}`));
      })
      .then(() => {
        dispatch(stopProgress(pid));
        done();
      });
  },
});

const gallerySubmitStarsLogic = createLogic({
  cancelType: ['SET_TOOL', 'MAP_RESET'],
  type: 'GALLERY_SUBMIT_STARS',
  process({ action: { payload: stars }, getState, cancelled$ }, dispatch, done) {
    const pid = Math.random();
    dispatch(startProgress(pid));
    cancelled$.subscribe(() => {
      dispatch(stopProgress(pid));
    });

    const id = getState().gallery.activeImageId;

    fetch(`${API_URL}/gallery/pictures/${id}/rating`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${getState().auth.user.authToken}`,
      },
      body: JSON.stringify({
        stars,
      }),
    })
      .then((res) => {
        if (res.status !== 204) {
          throw new Error(`Server vrátil neočakávaný status: ${res.status}`);
        }
        dispatch(galleryRequestImage(id));
      })
      .catch((e) => {
        dispatch(toastsAddError(`Nastala chyba pri hodnotení: ${e.message}`));
      })
      .then(() => {
        dispatch(stopProgress(pid));
        done();
      });
  },
});

export default [
  galleryRequestImagesLogic,
  galleryRequestImageLogic,
  galleryShowOnTheMapLogic,
  galleryUploadModalLogic,
  galleryItemUploadLogic,
  gallerySubmitCommentLogic,
  gallerySubmitStarsLogic,
];
<|endoftext|>


<commit_before>/**
 * Coverage visualization of Alignment sources.
 * @flow
 */
'use strict';

import type {Alignment, AlignmentDataSource} from './Alignment';
import type * as Interval from './Interval';
import type {TwoBitSource} from './TwoBitDataSource';
import type {DataCanvasRenderingContext2D} from 'data-canvas';

var React = require('react'),
    scale = require('./scale'),
    shallowEquals = require('shallow-equals'),
    types = require('./react-types'),
    d3utils = require('./d3utils'),
    _ = require("underscore"),
    dataCanvas = require('data-canvas'),
    canvasUtils = require('./canvas-utils'),
    {getOpInfo} = require('./pileuputils'),
    TiledCanvas = require('./TiledCanvas'),
    style = require('./style'),
    ContigInterval = require('./ContigInterval');

type BinSummary = {
  count: number;
  mismatches: {[key: string]: number};
}

type BinSummaryWithLocation = {
  position: number;
  count: number;
  mismatches: {[key: string]: number};
}

// Basic setup (TODO: make this configurable by the user)
const SHOW_MISMATCHES = true;

// Only show mismatch information when there are more than this many
// reads supporting that mismatch.
const MISMATCH_THRESHOLD = 1;

// Color the reference base in the bar chart when the Variant Allele Fraction
// exceeds this amount.
const REF_COLOR_VAF_THRESHOLD = 0.2;

/**
 * Extract summary statistics from the read data.
 */
function extractSummaryStatistics(reads: Array<Alignment>,
                                  contig: string,
                                  referenceSource: TwoBitSource) {
  var binCounts = ({}: {[key: number]: BinSummary});

  // This is written in an imperative style (instead of with _.groupBy)
  // as an optimization.
  _.each(reads, read => {
    var interval = read.getInterval();

    var opInfo = getOpInfo(read, referenceSource);

    var start = interval.start(),
        stop = interval.stop();
    for (var j = start; j <= stop; j++) {
      if (!binCounts[j]) binCounts[j] = {count: 0, mismatches: {}};
      binCounts[j].count += 1;
    }

    // Capture mismatches for future use
    _.each(opInfo.mismatches, m => {
      var binCount = binCounts[m.pos + 1];
      if (binCount) {
        var mismatches = binCount.mismatches;
        mismatches[m.basePair] = 1 + (mismatches[m.basePair] || 0);
      } else {
        // do nothing, we don't info on this position yet
      }
    });
  });
  var maxCoverage = _.max(binCounts, bc => bc.count).count;

  var posCounts = _.map(binCounts,
                        (bc, position) => ({position: Number(position),
                                            count: bc.count,
                                            mismatches: bc.mismatches}));
  var sortedPosCounts = _.sortBy(posCounts, bin => bin.position);

  sortedPosCounts.forEach(({position, count, mismatches}) => {
    if (_.isEmpty(mismatches)) return;

    // If there's a high variant allele fraction at this locus, add the
    // reference in as a "mismatch" as well. This makes the locus more visually
    // distinct and gives a better indication of the proportion of base pairs.
    var ref = referenceSource.getRangeAsString(
        {contig, start: position - 1, stop: position - 1});
    var mismatchCount = _.reduce(mismatches, (x, y) => x + y);
    if (mismatchCount > REF_COLOR_VAF_THRESHOLD * count) {
      mismatches[ref] = count - mismatchCount;
    }
  });

  return {binCounts: sortedPosCounts, maxCoverage};
}

// TODO: what about matching contigs?
function binsInRange(binCounts: BinSummaryWithLocation[],
                     range: ContigInterval): BinSummaryWithLocation[] {
  var start = range.start(),
      stop = range.stop();
  return binCounts.filter(
      ({position}) => (position >= start - 1 && position <= stop + 1));
}

function renderCoverage(ctx: DataCanvasRenderingContext2D,
                        xScale: (num: number) => number,
                        yScale: (num: number) => number,
                        range: ContigInterval<string>,
                        binCounts: BinSummaryWithLocation[]) {
  var bins = binsInRange(binCounts, range);
  renderBars(ctx, xScale, yScale, bins);
}

// Draw coverage bins & mismatches
function renderBars(ctx: DataCanvasRenderingContext2D,
                    xScale: (num: number) => number,
                    yScale: (num: number) => number,
                    bins: BinSummaryWithLocation[]) {
  if (bins.length === 0) return;

  var barWidth = xScale(1) - xScale(0);
  var showPadding = (barWidth > style.COVERAGE_MIN_BAR_WIDTH_FOR_GAP);
  var padding = showPadding ? 1 : 0;

  var binPos = function(bin) {
    // Round to integer coordinates for crisp lines, without aliasing.
    var barX1 = Math.round(xScale(bin.position)),
        barX2 = Math.round(xScale(bin.position + 1)) - padding,
        barY = Math.round(yScale(bin.count));
    return {barX1, barX2, barY};
  };

  var mismatchBins = [];  // keep track of which ones have mismatches
  var vBasePosY = yScale(0);  // the very bottom of the canvas
  ctx.fillStyle = style.COVERAGE_BIN_COLOR;
  ctx.beginPath();
  var {barX1} = binPos(bins[0]);
  ctx.moveTo(barX1, vBasePosY);
  bins.forEach(bin => {
    ctx.pushObject(bin);
    var {barX1, barX2, barY} = binPos(bin);
    ctx.lineTo(barX1, barY);
    ctx.lineTo(barX2, barY);
    if (showPadding) {
      ctx.lineTo(barX2, vBasePosY);
      ctx.lineTo(barX2 + 1, vBasePosY);
    }

    if (SHOW_MISMATCHES && !_.isEmpty(bin.mismatches)) {
      mismatchBins.push(bin);
    }

    ctx.popObject();
  });
  var {barX2} = binPos(bins[bins.length - 1]);
  ctx.lineTo(barX2, vBasePosY);  // right edge of the right bar.
  ctx.closePath();
  ctx.fill();

  mismatchBins.forEach(bin => {
    var {barX1, barX2} = binPos(bin);
    ctx.pushObject(bin);
    var countSoFar = 0;
    _.chain(bin.mismatches)
      .map((count, base) => ({count, base}))  // pull base into the object
      .filter(({count}) => count > MISMATCH_THRESHOLD)
      .sortBy(({count}) => -count)  // the most common mismatch at the bottom
      .each(({count, base}) => {
        var misMatchObj = {position: bin.position, count, base};
        ctx.pushObject(misMatchObj);  // for debugging and click-tracking

        ctx.fillStyle = style.BASE_COLORS[base];
        var y = yScale(countSoFar);
        ctx.fillRect(barX1,
                     y,
                     barX2 - barX1,
                     yScale(countSoFar + count) - y);
        countSoFar += count;

        ctx.popObject();
      });
    ctx.popObject();
  });
}

type Props = {
  width: number;
  height: number;
  range: GenomeRange;
  source: AlignmentDataSource;
  referenceSource: TwoBitSource;
};

class CoverageTrack extends React.Component {
  props: Props;

  constructor(props: Props) {
    super(props);
    this.state = {
      width: 0,
      height: 0,
      labelSize: {weight: 0, height: 0},  // for precise padding calculations
      reads: [],
      binCounts: [],
      maxCoverage: 0
    };
  }

  render(): any {
    return <canvas ref='canvas' />;
  }

  getScale() {
    return d3utils.getTrackScale(this.props.range, this.props.width);
  }

  componentDidMount() {
    var updateState = () => {
      var ci = new ContigInterval(this.props.range.contig, 0, Number.MAX_VALUE),
          reads = this.props.source.getAlignmentsInRange(ci),
          {binCounts, maxCoverage} = extractSummaryStatistics(reads, this.props.range.contig, this.props.referenceSource);

      this.setState({
        reads,
        binCounts,
        maxCoverage
      });
    };

    this.props.source.on('newdata', updateState);
    this.props.referenceSource.on('newdata', updateState);
  }

  componentDidUpdate(prevProps: any, prevState: any) {
    if (!shallowEquals(this.props, prevProps) ||
        !shallowEquals(this.state, prevState)) {
      this.visualizeCoverage();
    }
  }

  getContext(): CanvasRenderingContext2D {
    var canvas = (this.refs.canvas : HTMLCanvasElement);
    // The typecast through `any` is because getContext could return a WebGL context.
    var ctx = ((canvas.getContext('2d') : any) : CanvasRenderingContext2D);
    return ctx;
  }

  // Draw three ticks on the left to set the scale for the user
  renderTicks(ctx, yScale) {
    var axisMax = yScale.domain()[0];
    [0, Math.round(axisMax / 2), axisMax].forEach(tick => {
      // Draw a line indicating the tick
      ctx.pushObject({value: tick, type: 'tick'});
      var tickPosY = Math.round(yScale(tick));
      ctx.strokeStyle = style.COVERAGE_FONT_COLOR;
      canvasUtils.drawLine(ctx, 0, tickPosY, style.COVERAGE_TICK_LENGTH, tickPosY);
      ctx.popObject();

      var tickLabel = tick + 'X';
      ctx.pushObject({value: tick, label: tickLabel, type: 'label'});
      // Now print the coverage information
      ctx.font = style.COVERAGE_FONT_STYLE;
      var textPosX = style.COVERAGE_TICK_LENGTH + style.COVERAGE_TEXT_PADDING,
          textPosY = tickPosY + style.COVERAGE_TEXT_Y_OFFSET;
      // The stroke creates a border around the text to make it legible over the bars.
      ctx.strokeStyle = 'white';
      ctx.lineWidth = 2;
      ctx.strokeText(tickLabel, textPosX, textPosY);
      ctx.lineWidth = 1;
      ctx.fillStyle = style.COVERAGE_FONT_COLOR;
      ctx.fillText(tickLabel, textPosX, textPosY);
      ctx.popObject();
    });
  }

  visualizeCoverage() {
    var canvas = (this.refs.canvas : HTMLCanvasElement),
        width = this.props.width,
        height = this.props.height,
        padding = 10,  // TODO: move into style
        range = ContigInterval.fromGenomeRange(this.props.range);

    // Hold off until height & width are known.
    if (width === 0) return;
    d3utils.sizeCanvas(canvas, width, height);

    var yScale = scale.linear()
      .domain([this.state.maxCoverage, 0])
      .range([padding, height - padding])
      .nice();

    var ctx = dataCanvas.getDataContext(this.getContext());
    ctx.save();
    ctx.reset();
    ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);

    renderCoverage(ctx, this.getScale(), yScale, range, this.state.binCounts);
    this.renderTicks(ctx, yScale);

    ctx.restore();
  }
}

CoverageTrack.displayName = 'coverage';

module.exports = CoverageTrack;
<commit_msg>Switch to TiledCanvas<commit_after>/**
 * Coverage visualization of Alignment sources.
 * @flow
 */
'use strict';

import type {Alignment, AlignmentDataSource} from './Alignment';
import type * as Interval from './Interval';
import type {TwoBitSource} from './TwoBitDataSource';
import type {DataCanvasRenderingContext2D} from 'data-canvas';

var React = require('react'),
    scale = require('./scale'),
    shallowEquals = require('shallow-equals'),
    d3utils = require('./d3utils'),
    _ = require("underscore"),
    dataCanvas = require('data-canvas'),
    canvasUtils = require('./canvas-utils'),
    {getOpInfo} = require('./pileuputils'),
    TiledCanvas = require('./TiledCanvas'),
    style = require('./style'),
    ContigInterval = require('./ContigInterval');

type BinSummary = {
  count: number;
  mismatches: {[key: string]: number};
}

type BinSummaryWithLocation = {
  position: number;
  count: number;
  mismatches: {[key: string]: number};
}

// Basic setup (TODO: make this configurable by the user)
const SHOW_MISMATCHES = true;

// Only show mismatch information when there are more than this many
// reads supporting that mismatch.
const MISMATCH_THRESHOLD = 1;

// Color the reference base in the bar chart when the Variant Allele Fraction
// exceeds this amount.
const REF_COLOR_VAF_THRESHOLD = 0.2;

/**
 * Extract summary statistics from the read data.
 */
function extractSummaryStatistics(reads: Array<Alignment>,
                                  contig: string,
                                  referenceSource: TwoBitSource) {
  var binCounts = ({}: {[key: number]: BinSummary});

  // This is written in an imperative style (instead of with _.groupBy)
  // as an optimization.
  _.each(reads, read => {
    var interval = read.getInterval();

    var opInfo = getOpInfo(read, referenceSource);

    var start = interval.start(),
        stop = interval.stop();
    for (var j = start; j <= stop; j++) {
      if (!binCounts[j]) binCounts[j] = {count: 0, mismatches: {}};
      binCounts[j].count += 1;
    }

    // Capture mismatches for future use
    _.each(opInfo.mismatches, m => {
      var binCount = binCounts[m.pos + 1];
      if (binCount) {
        var mismatches = binCount.mismatches;
        mismatches[m.basePair] = 1 + (mismatches[m.basePair] || 0);
      } else {
        // do nothing, we don't info on this position yet
      }
    });
  });
  var maxCoverage = _.max(binCounts, bc => bc.count).count;

  var posCounts = _.map(binCounts,
                        (bc, position) => ({position: Number(position),
                                            count: bc.count,
                                            mismatches: bc.mismatches}));
  var sortedPosCounts = _.sortBy(posCounts, bin => bin.position);

  sortedPosCounts.forEach(({position, count, mismatches}) => {
    if (_.isEmpty(mismatches)) return;

    // If there's a high variant allele fraction at this locus, add the
    // reference in as a "mismatch" as well. This makes the locus more visually
    // distinct and gives a better indication of the proportion of base pairs.
    var ref = referenceSource.getRangeAsString(
        {contig, start: position - 1, stop: position - 1});
    var mismatchCount = _.reduce(mismatches, (x, y) => x + y);
    if (mismatchCount > REF_COLOR_VAF_THRESHOLD * count) {
      mismatches[ref] = count - mismatchCount;
    }
  });

  return {binCounts: sortedPosCounts, maxCoverage};
}

class CoverageTiledCanvas extends TiledCanvas {
  height: number;
  yScale: (count: number) => number;
  binCounts: BinSummaryWithLocation[];

  constructor() {
    super();

    this.height = 0;
    this.yScale = x => x;
    this.binCounts = [];
  }

  heightForRef(ref: string): number {
    return this.height;
  }

  update(height: number, yScale: (count: number) => number, binCounts: BinSummaryWithLocation[]) {
    this.height = height;
    this.yScale = yScale;
    this.binCounts = binCounts;
    // this.invalidateAll();
  }

  render(ctx: DataCanvasRenderingContext2D,
         xScale: (x: number)=>number,
         range: ContigInterval<string>) {
    var bins = binsInRange(this.binCounts, range);
    renderBars(ctx, xScale, this.yScale, bins);
  }
}

// TODO: what about matching contigs?
function binsInRange(binCounts: BinSummaryWithLocation[],
                     range: ContigInterval): BinSummaryWithLocation[] {
  var start = range.start(),
      stop = range.stop();
  return binCounts.filter(
      ({position}) => (position >= start - 1 && position <= stop + 1));
}

// Draw coverage bins & mismatches
function renderBars(ctx: DataCanvasRenderingContext2D,
                    xScale: (num: number) => number,
                    yScale: (num: number) => number,
                    bins: BinSummaryWithLocation[]) {
  if (bins.length === 0) return;

  var barWidth = xScale(1) - xScale(0);
  var showPadding = (barWidth > style.COVERAGE_MIN_BAR_WIDTH_FOR_GAP);
  var padding = showPadding ? 1 : 0;

  var binPos = function(bin) {
    // Round to integer coordinates for crisp lines, without aliasing.
    var barX1 = Math.round(xScale(bin.position)),
        barX2 = Math.round(xScale(bin.position + 1)) - padding,
        barY = Math.round(yScale(bin.count));
    return {barX1, barX2, barY};
  };

  var mismatchBins = [];  // keep track of which ones have mismatches
  var vBasePosY = yScale(0);  // the very bottom of the canvas
  ctx.fillStyle = style.COVERAGE_BIN_COLOR;
  ctx.beginPath();
  var {barX1} = binPos(bins[0]);
  ctx.moveTo(barX1, vBasePosY);
  bins.forEach(bin => {
    ctx.pushObject(bin);
    var {barX1, barX2, barY} = binPos(bin);
    ctx.lineTo(barX1, barY);
    ctx.lineTo(barX2, barY);
    if (showPadding) {
      ctx.lineTo(barX2, vBasePosY);
      ctx.lineTo(barX2 + 1, vBasePosY);
    }

    if (SHOW_MISMATCHES && !_.isEmpty(bin.mismatches)) {
      mismatchBins.push(bin);
    }

    ctx.popObject();
  });
  var {barX2} = binPos(bins[bins.length - 1]);
  ctx.lineTo(barX2, vBasePosY);  // right edge of the right bar.
  ctx.closePath();
  ctx.fill();

  mismatchBins.forEach(bin => {
    var {barX1, barX2} = binPos(bin);
    ctx.pushObject(bin);
    var countSoFar = 0;
    _.chain(bin.mismatches)
      .map((count, base) => ({count, base}))  // pull base into the object
      .filter(({count}) => count > MISMATCH_THRESHOLD)
      .sortBy(({count}) => -count)  // the most common mismatch at the bottom
      .each(({count, base}) => {
        var misMatchObj = {position: bin.position, count, base};
        ctx.pushObject(misMatchObj);  // for debugging and click-tracking

        ctx.fillStyle = style.BASE_COLORS[base];
        var y = yScale(countSoFar);
        ctx.fillRect(barX1,
                     y,
                     barX2 - barX1,
                     yScale(countSoFar + count) - y);
        countSoFar += count;

        ctx.popObject();
      });
    ctx.popObject();
  });
}

type Props = {
  width: number;
  height: number;
  range: GenomeRange;
  source: AlignmentDataSource;
  referenceSource: TwoBitSource;
};

type State = {
  reads: Alignment[];
  binCounts: BinSummaryWithLocation[];
  maxCoverage: number;
};

class CoverageTrack extends React.Component {
  props: Props;
  state: State;
  tiles: CoverageTiledCanvas;

  constructor(props: Props) {
    super(props);
    this.state = {
      reads: [],
      binCounts: [],
      maxCoverage: 0
    };
  }

  render(): any {
    return <canvas ref='canvas' />;
  }

  getScale() {
    return d3utils.getTrackScale(this.props.range, this.props.width);
  }

  componentDidMount() {
    var updateState = () => {
      var ci = new ContigInterval(this.props.range.contig, 0, Number.MAX_VALUE),
          reads = this.props.source.getAlignmentsInRange(ci),
          {binCounts, maxCoverage} = extractSummaryStatistics(reads, this.props.range.contig, this.props.referenceSource);

      var padding = 10;  // TODO: move into style
      var yScale = scale.linear()
        .domain([maxCoverage, 0])
        .range([padding, this.props.height - padding])
        .nice();

      this.tiles.update(this.props.height, yScale, binCounts);
      this.tiles.invalidateAll();
      this.setState({
        reads,
        binCounts,
        maxCoverage
      });
    };

    this.tiles = new CoverageTiledCanvas();
    this.props.source.on('newdata', updateState);
    this.props.referenceSource.on('newdata', updateState);
  }

  componentDidUpdate(prevProps: any, prevState: any) {
    if (!shallowEquals(this.props, prevProps) ||
        !shallowEquals(this.state, prevState)) {
      // TODO: check for a height change.
      this.visualizeCoverage();
    }
  }

  getContext(): CanvasRenderingContext2D {
    var canvas = (this.refs.canvas : HTMLCanvasElement);
    // The typecast through `any` is because getContext could return a WebGL context.
    var ctx = ((canvas.getContext('2d') : any) : CanvasRenderingContext2D);
    return ctx;
  }

  // Draw three ticks on the left to set the scale for the user
  renderTicks(ctx: DataCanvasRenderingContext2D, yScale: (num: number)=>number) {
    var axisMax = yScale.domain()[0];
    [0, Math.round(axisMax / 2), axisMax].forEach(tick => {
      // Draw a line indicating the tick
      ctx.pushObject({value: tick, type: 'tick'});
      var tickPosY = Math.round(yScale(tick));
      ctx.strokeStyle = style.COVERAGE_FONT_COLOR;
      canvasUtils.drawLine(ctx, 0, tickPosY, style.COVERAGE_TICK_LENGTH, tickPosY);
      ctx.popObject();

      var tickLabel = tick + 'X';
      ctx.pushObject({value: tick, label: tickLabel, type: 'label'});
      // Now print the coverage information
      ctx.font = style.COVERAGE_FONT_STYLE;
      var textPosX = style.COVERAGE_TICK_LENGTH + style.COVERAGE_TEXT_PADDING,
          textPosY = tickPosY + style.COVERAGE_TEXT_Y_OFFSET;
      // The stroke creates a border around the text to make it legible over the bars.
      ctx.strokeStyle = 'white';
      ctx.lineWidth = 2;
      ctx.strokeText(tickLabel, textPosX, textPosY);
      ctx.lineWidth = 1;
      ctx.fillStyle = style.COVERAGE_FONT_COLOR;
      ctx.fillText(tickLabel, textPosX, textPosY);
      ctx.popObject();
    });
  }

  visualizeCoverage() {
    var canvas = (this.refs.canvas : HTMLCanvasElement),
        width = this.props.width,
        height = this.props.height,
        range = ContigInterval.fromGenomeRange(this.props.range);

    // Hold off until height & width are known.
    if (width === 0) return;
    d3utils.sizeCanvas(canvas, width, height);

    var ctx = dataCanvas.getDataContext(this.getContext());
    ctx.save();
    ctx.reset();
    ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);

    var yScale = this.tiles.yScale;

    this.tiles.renderToScreen(ctx, range, this.getScale());
    this.renderTicks(ctx, yScale);

    ctx.restore();
  }
}

CoverageTrack.displayName = 'coverage';

module.exports = CoverageTrack;
<|endoftext|>


<commit_before>'use strict';

// Spreadsheet view, 2019. Inserts button next to to the task name.
togglbutton.render('.SpreadsheetRow .SpreadsheetTaskName:not(.toggl)', { observe: true },
  function (taskNameCell) {
    const container = taskNameCell.closest('.SpreadsheetRow');

    if (container.querySelector('.toggl-button')) {
      // Due to the way this UI is rendered, we must check for existence of old buttons manually.
      return;
    }

    const descriptionSelector = () => taskNameCell.querySelector('textarea').textContent.trim();
    const projectHeaderSelector = () => {
      // Try to look for for page project title instead.
      const projectHeader = document.querySelector('.TopbarPageHeaderStructure.ProjectPageHeader .TopbarPageHeaderStructure-title');
      if (!projectHeader) {
        return '';
      }
      return projectHeader.textContent.trim();
    };
    const projectSelector = () => {
      const projectCell = container.querySelector('.SpreadsheetTaskRow-projectsCell');
      if (!projectCell) {
        // Try to look for for page project title instead.
        return projectHeaderSelector();
      }

      // There can be multiple projects, but we can't support trying to match multiple yet.
      const firstProject = projectCell.querySelector('.Pill');
      return firstProject ? firstProject.textContent.trim() : projectHeaderSelector();
    };

    const link = togglbutton.createTimerLink({
      className: 'asana-spreadsheet',
      description: descriptionSelector,
      projectName: projectSelector,
      buttonType: 'minimal'
    });

    taskNameCell.insertAdjacentElement('afterend', link);
  }
);

// 2019 My Tasks view, possibly other similar views.
togglbutton.render('.MyTasksTaskRow:not(.toggl)', { observe: true },
  function (elem) {
    if (elem.querySelector('.toggl-button')) {
      // Due to the way this UI is rendered, we must check for existence of old buttons manually.
      return;
    }
    const container = elem.querySelector('.ItemRowTwoColumnStructure-left');
    const description = elem.querySelector('.TaskName textarea').textContent;

    const projectSelector = () => {
      const projectElement = elem.querySelector('.TaskRow-pots .Pill');

      return projectElement ? projectElement.textContent : '';
    };

    const link = togglbutton.createTimerLink({
      className: 'asana-new-ui',
      description: description,
      projectName: projectSelector,
      buttonType: 'minimal'
    });

    container.appendChild(link);
  }
);

// Dec 2019 detail view
togglbutton.render(
  '.SingleTaskPaneSpreadsheet:not(.toggl)',
  { observe: true },
  function (elem) {
    if ($('.toggl-button', elem)) {
      return;
    }

    const descriptionSelector = () => {
      return $('.SingleTaskTitleInput-taskName textarea', elem).textContent.trim();
    };

    const projectSelector = () => {
      const projectEl = $('.TaskProjectToken-potTokenizerPill', elem);
      return projectEl ? projectEl.textContent.trim() : '';
    };

    const link = togglbutton.createTimerLink({
      className: 'asana-board',
      description: descriptionSelector,
      projectName: projectSelector,
      buttonType: 'minimal'
    });

    link.style.margin = '0 5px';

    const firstButton = elem.querySelector('.SingleTaskPaneToolbar-button');
    firstButton.parentNode.insertBefore(link, firstButton);
  }
);
<commit_msg>fix(asana): Restore old detail view until update rolled out for all users<commit_after>'use strict';

// Spreadsheet view, 2019. Inserts button next to to the task name.
togglbutton.render('.SpreadsheetRow .SpreadsheetTaskName:not(.toggl)', { observe: true },
  function (taskNameCell) {
    const container = taskNameCell.closest('.SpreadsheetRow');

    if (container.querySelector('.toggl-button')) {
      // Due to the way this UI is rendered, we must check for existence of old buttons manually.
      return;
    }

    const descriptionSelector = () => taskNameCell.querySelector('textarea').textContent.trim();
    const projectHeaderSelector = () => {
      // Try to look for for page project title instead.
      const projectHeader = document.querySelector('.TopbarPageHeaderStructure.ProjectPageHeader .TopbarPageHeaderStructure-title');
      if (!projectHeader) {
        return '';
      }
      return projectHeader.textContent.trim();
    };
    const projectSelector = () => {
      const projectCell = container.querySelector('.SpreadsheetTaskRow-projectsCell');
      if (!projectCell) {
        // Try to look for for page project title instead.
        return projectHeaderSelector();
      }

      // There can be multiple projects, but we can't support trying to match multiple yet.
      const firstProject = projectCell.querySelector('.Pill');
      return firstProject ? firstProject.textContent.trim() : projectHeaderSelector();
    };

    const link = togglbutton.createTimerLink({
      className: 'asana-spreadsheet',
      description: descriptionSelector,
      projectName: projectSelector,
      buttonType: 'minimal'
    });

    taskNameCell.insertAdjacentElement('afterend', link);
  }
);

// 2019 My Tasks view, possibly other similar views.
togglbutton.render('.MyTasksTaskRow:not(.toggl)', { observe: true },
  function (elem) {
    if (elem.querySelector('.toggl-button')) {
      // Due to the way this UI is rendered, we must check for existence of old buttons manually.
      return;
    }
    const container = elem.querySelector('.ItemRowTwoColumnStructure-left');
    const description = elem.querySelector('.TaskName textarea').textContent;

    const projectSelector = () => {
      const projectElement = elem.querySelector('.TaskRow-pots .Pill');

      return projectElement ? projectElement.textContent : '';
    };

    const link = togglbutton.createTimerLink({
      className: 'asana-new-ui',
      description: description,
      projectName: projectSelector,
      buttonType: 'minimal'
    });

    container.appendChild(link);
  }
);

// Dec 2019 detail view
togglbutton.render(
  '.SingleTaskPaneSpreadsheet:not(.toggl)',
  { observe: true },
  function (elem) {
    if ($('.toggl-button', elem)) {
      return;
    }

    const descriptionSelector = () => {
      return $('.SingleTaskTitleInput-taskName textarea', elem).textContent.trim();
    };

    const projectSelector = () => {
      const projectEl = $('.TaskProjectToken-potTokenizerPill', elem);
      return projectEl ? projectEl.textContent.trim() : '';
    };

    const link = togglbutton.createTimerLink({
      className: 'asana-board',
      description: descriptionSelector,
      projectName: projectSelector,
      buttonType: 'minimal'
    });

    link.style.margin = '0 5px';

    const firstButton = elem.querySelector('.SingleTaskPaneToolbar-button');
    firstButton.parentNode.insertBefore(link, firstButton);
  }
);

// Old and Beta UI - detail view
togglbutton.render(
  '.SingleTaskPane-titleRow:not(.toggl)',
  { observe: true },
  function (elem) {
    if ($('.toggl-button', elem)) {
      return;
    }
    const container = $('.SingleTaskPaneToolbar');

    const descriptionSelector = () => {
      return $(
        '.SingleTaskPane-titleRow .simpleTextarea',
        elem.parentNode
      ).textContent;
    };

    const projectSelector = () => {
      const projectElement = $(
        '.SingleTaskPane-projects .TaskProjectPill-projectName',
        elem.parentNode
      );

      return projectElement ? projectElement.textContent : '';
    };

    const link = togglbutton.createTimerLink({
      className: 'asana-board',
      description: descriptionSelector,
      projectName: projectSelector,
      buttonType: 'minimal'
    });

    link.style.marginRight = '5px';

    if (container) {
      const closeButton = container.lastElementChild;
      container.insertBefore(link, closeButton);
    }
  }
);
<|endoftext|>


<commit_before>const arrayNonindexKeys = [
    {
        arrayNonindexKeys: {
            testPlainObjects: true,
            test (x, stateObj) {
                if (Array.isArray(x)) {
                    if (
                        // By avoiding serializing arrays into objects which
                        //  have only positive-integer keys, we reduce
                        //  size and improve revival performance; arrays with
                        //  non-index keys will be larger however
                        Object.keys(x).some((k) => {
                            //  No need to check for `isNaN` or
                            //   `isNaN(parseInt())` as `NaN` will be treated
                            //   as a string.
                            //  No need to do check as `parseInt(Number())`
                            //   since scientific notation will be
                            //   pre-resolved if a number was given, and it
                            //   will otherwise be a string
                            return String(parseInt(k)) !== k;
                        })
                    ) {
                        stateObj.iterateIn = 'object';
                        stateObj.addLength = true;
                    }
                    return true;
                }
                return false;
            },
            replace (a, stateObj) {
                if (Array.isArray(a)) {
                    // Catch sparse undefined
                    stateObj.iterateUnsetNumeric = true;
                }
                return a;
            },
            revive (o) {
                if (Array.isArray(o)) {
                    return o;
                }
                const arr = [];
                // No map here as may be a sparse array (including
                //   with `length` set)
                // Todo: Reenable when Node `engines` >= 7
                // Object.entries(o).forEach(([key, val]) => {
                Object.keys(o).forEach((key) => {
                    const val = o[key];
                    arr[key] = val;
                });
                return arr;
            }
        }
    },
    {
        sparseUndefined: {
            test (x, stateObj) {
                return typeof x === 'undefined' && stateObj.ownKeys === false;
            },
            replace (n) { return 0; },
            revive (s) { return undefined; } // Will avoid adding anything
        }
    }
];

export default arrayNonindexKeys;
<commit_msg>- Refactoring: Remove redundant code from `array-nonindex-keys` (completing coverage for the file)<commit_after>const arrayNonindexKeys = [
    {
        arrayNonindexKeys: {
            testPlainObjects: true,
            test (x, stateObj) {
                if (Array.isArray(x)) {
                    if (
                        // By avoiding serializing arrays into objects which
                        //  have only positive-integer keys, we reduce
                        //  size and improve revival performance; arrays with
                        //  non-index keys will be larger however
                        Object.keys(x).some((k) => {
                            //  No need to check for `isNaN` or
                            //   `isNaN(parseInt())` as `NaN` will be treated
                            //   as a string.
                            //  No need to do check as `parseInt(Number())`
                            //   since scientific notation will be
                            //   pre-resolved if a number was given, and it
                            //   will otherwise be a string
                            return String(parseInt(k)) !== k;
                        })
                    ) {
                        stateObj.iterateIn = 'object';
                        stateObj.addLength = true;
                    }
                    return true;
                }
                return false;
            },
            replace (a, stateObj) {
                // Catch sparse undefined
                stateObj.iterateUnsetNumeric = true;
                return a;
            },
            revive (o) {
                if (Array.isArray(o)) {
                    return o;
                }
                const arr = [];
                // No map here as may be a sparse array (including
                //   with `length` set)
                // Todo: Reenable when Node `engines` >= 7
                // Object.entries(o).forEach(([key, val]) => {
                Object.keys(o).forEach((key) => {
                    const val = o[key];
                    arr[key] = val;
                });
                return arr;
            }
        }
    },
    {
        sparseUndefined: {
            test (x, stateObj) {
                return typeof x === 'undefined' && stateObj.ownKeys === false;
            },
            replace (n) { return 0; },
            revive (s) { return undefined; } // Will avoid adding anything
        }
    }
];

export default arrayNonindexKeys;
<|endoftext|>


<commit_before>var linktoFromThePage = {
  /* options of the plugin */
  /*
   * in Mirador startup file, add to windowSettings.plugins: "linktoFromThePage": true
   */
  options: {},

  /* i18next locales */

  locales: {
    'ga': {
      'transcribe-canvas-image': 'Trascríobh',
      'status': 'stádas'
    },
    'en': {
      'transcribe-canvas-image': 'Transcribe this page',
      'canvas-image-transcription': 'View transcription',
      'download-tei-xml': 'Download TEI transcription',
      'status': 'status'
    }
  },
  /* template for CSS styles */
  styleTemplate: Mirador.Handlebars.compile([
  ''].join('')),

  /* template for the Transcribe buttons */
  transcribeTemplate: Mirador.Handlebars.compile([
  '',
  '',
  '',
  '',
  '',
  '',
  '',
  '',
  ''].join('')),
  styleTemplate: Mirador.Handlebars.compile([
  ''].join('')),

  /* initializes the plugin */
  init: function () {
    i18next.on('initialized', function () {
      this.addLocalesToViewer();
    }.bind(this));
    this.injectStylesToDom();
    this.setPluginOptions();
    this.injectWorkspaceEventHandler();
    this.injectWindowEventHandler();
  },

  /* injects the button to the window menu */
  injectButtonToMenu: function (windowButtons) {
    if (this.options.showTranscribeLink && this.options.showTranscribeLink == true) {
      $(windowButtons).prepend(this.transcribeTemplate());
    }
  },

  /* inject style template */
  injectStylesToDom: function () {
    var this_ = this;
    document.body.insertAdjacentHTML('beforeend', this_.styleTemplate());
  },

  /* set local options */
  setPluginOptions: function () {
    var this_ = this;
    var origFunc = Mirador.Viewer.prototype.setupViewer;
    Mirador.Viewer.prototype.setupViewer = function () {
      origFunc.apply(this);
      var windowSettings = this.state.currentConfig.windowSettings;
      if (windowSettings.plugins && windowSettings.plugins.linktoFromThePage) {
        $.each(windowSettings.plugins.linktoFromThePage, function (index, item) {
          linktoFromThePage.options[index] = item;
        });
      }
    };
  },

  /* inject workspace event handler */
  injectWorkspaceEventHandler: function () {
    var this_ = this;
    var origFunc = Mirador.Workspace.prototype.bindEvents;
    Mirador.Workspace.prototype.bindEvents = function () {
      origFunc.apply(this);

      this.eventEmitter.subscribe('WINDOW_ELEMENT_UPDATED', function (event, data) {
        var windowButtons = data.element.find('.window-manifest-navigation');
        this_.setSavedPreferences();
        this_.injectButtonToMenu(windowButtons);
      });

      this.eventEmitter.subscribe('manifestReceived', function (event, data) {
        if (data.jsonLd !== undefined) {
          /* if collections_FtP.json data is available, initialise manifestsFromThePage with null values for collections NOT in FromThePage */
          if (data.jsonLd.within !== undefined) {
            if (collectionsFtP !== undefined) {
              var isTranscribedCollection = this_.isFtP(data.jsonLd.within[ "@id"]);
              if (isTranscribedCollection == false) {
                var manifestID = 'https:' + data.uri;
                manifestsFromThePage[manifestID] = null;
                $.each(data.jsonLd.sequences[0].canvases, function (index, item) {
                  $.each(this, function (index, val) {
                    if (index == '@id') {
                      manifestsFromThePage[val] = null;
                    }
                  });
                });
                var transcribed = false;
                if (data.jsonLd.metadata) {
                  var isText = false;
                  $.each(data.jsonLd.metadata, function (index, item) {
                    if (item.label == 'Type of resource') {
                      if (item.value[0][ "@value"] == 'dctypes:Text') {
                        isText = true;
                      }
                    }
                  });
                  if (isText == true) {
                    if (data.jsonLd.seeAlso) {
                      $.each(data.jsonLd.seeAlso, function (index, item) {
                        if (~ item.format.indexOf("application/tei+xml")) {
                          transcribed = true;
                        }
                      });
                    }
                    if (transcribed == false) {
                      var fromThePageURI = this_.linktoFromThePage(data.jsonLd[ "@id"], 'manifests', '', transcribed);
                    }
                  }
                  else {
                    manifestsFromThePage[data.jsonLd[ "@id"]] = null;
                  }
                }
              }
            }
          }
          /* duchas specific: */
          if (data.jsonLd[ "@id"].includes("duchas")) {
            var duchasURI;
            var transcribed = false;
            var manifestID = 'https:' + data.uri;
            if (data.jsonLd.seeAlso) {
              $.each(data.jsonLd.seeAlso, function (index, item) {
                var format = Mirador.JsonLd.getTextValue(item.format);
                if (~ format.indexOf("application/tei+xml")) {
                  transcribed = true;
                }
                var id = Mirador.JsonLd.getTextValue(item[ "@id"]);
                if (~ id.indexOf("http://www.duchas.ie/en/cbes/")) {
                  duchasURI = id;
                }
              });
            }
            if (duchasURI !== undefined && transcribed == false) {
              if (this.options && this.options.showTranscribeLink && this.options.showTranscribeLink == true) {
                manifestsFromThePage[manifestID] = duchasURI; }
                else {
                  manifestsFromThePage[manifestID] = duchasURI;
                }
            }
            else {
              manifestsFromThePage[manifestID] = null;
            }
            /* end duchas specific */
            /* add all canvas @ids in the manifest to the manifestsFromThePage hash */
            $.each(data.jsonLd.sequences[0].canvases, function (index, item) {
              $.each(this, function (index, val) {
                if (index == '@id') {
                  manifestsFromThePage[val] = manifestsFromThePage[manifestID];
                }
              });
            });
          }
        }
      });
    };
  },

  /* inject window event handler */
  injectWindowEventHandler: function () {
    var this_ = this;

    var origFunc = Mirador.Window.prototype.bindEvents;
    Mirador.Window.prototype.bindEvents = function () {
      origFunc.apply(this);
      var localDomain = document.location.origin;
      _this = this;
      var canvasID = this.canvasID;
      var current_slot_id = $('.highlight[data-image-id="' + canvasID + '"]').closest("div.layout-slot").attr('data-layout-slot-id');

      /* if registered with Dúchas.ie ... */

      if (canvasID.includes("duchas")) {
        /* handle dúchas records here */
        if (manifestsFromThePage[canvasID] !== undefined && manifestsFromThePage[canvasID] !== '' && manifestsFromThePage[canvasID] !== null && manifestsFromThePage[canvasID] !== false) {
          /* (Dúchas chapter objects have null manifestsFromThePage values) */
          /* check data.manifest to see if it has already been transcribed */
          this_.showTranscriptionLink(current_slot_id, manifestsFromThePage[canvasID]);
          return;
        }
      }

      var transcribed = false;

      /* if registered with FromThePage ... */

      if (manifestsFromThePage[_this.manifest.uri] !== undefined && manifestsFromThePage[_this.manifest.uri] !== null && manifestsFromThePage[_this.manifest.uri] !== false) {
        link = this_.setLinkTypeFromThePage(manifestsFromThePage[_this.manifest.uri], 'read');
        if (_this.manifest.jsonLd.seeAlso) {
          $.each(_this.manifest.jsonLd.seeAlso, function (index, item) {
            if (~ item.format.indexOf("application/tei+xml")) {
              transcribed = true;
            }
          });
        }
        if (transcribed == false) {
          this_.showTranscriptionLink(current_slot_id, link);
          var fromThePageURI = this_.linktoFromThePage(_this.manifest.uri, 'manifests', current_slot_id, transcribed);
          return;
        }
        else if (this_.options.showTranscriptionLink && this_.options.showTranscriptionLink == true) {
            this_.showDocumentLink(current_slot_id, link, 'FtP');
        }
        var fromThePageURI = this_.linktoFromThePage(_this.manifest.uri, 'manifests', current_slot_id, transcribed);
        return;
      }

      if (manifestsFromThePage[_this.manifest.uri] == undefined) {
        if (_this.manifest.jsonLd.seeAlso) {
          $.each(_this.manifest.jsonLd.seeAlso, function (index, item) {
            if (~ item.format.indexOf("application/tei+xml")) {
              transcribed = true;
              /* this is just too much ... */
              //var downloadLink = this[ "@id"];
              //this_.showDownloadLink(current_slot_id, downloadLink);
            }
          });
        }
        var retval = this_.linktoFromThePage(_this.manifest.uri, 'manifests', current_slot_id, transcribed);
        if (retval == true) { /* true signifies asynchronous ajax process in progress, so do not show renderings */
          return;
        }
      }

      /* look for PDF or HTML renderings & expose a link, regardless of whether in FromThePage */

      if (this.manifest.jsonLd.rendering && _this.options.showRendering && _this.options.showRendering == true) {
        var rendering = this.manifest.jsonLd.rendering;
        var renderingHTML, renderingPDF;
        if (Array.isArray(rendering)) {
          renderingPDF = rendering;
          rendering = renderingPDF.find(function(r) {
            return r.format === "application/pdf";
          });
          renderingHTML = rendering;
          rendering = renderingHTML.find(function(r) {
            return r.format === "text/html";
          });
        }
        /* show one or the other ... */
        if (rendering.format == 'text/html') {
          renderingHTML = rendering["@id"];
          this_.showDocumentLink(current_slot_id, renderingHTML, 'text/html');
        }
        else if (rendering.format == 'application/pdf') {
          renderingPDF = rendering["@id"];
          this_.showDocumentLink(current_slot_id, renderingPDF, 'application/pdf');
        }
        return;
      }
    };
  },

  /* add the locales to the internationalisation module (i18next) of the viewer */
  addLocalesToViewer: function () {
    for (var language in this.locales) {
      i18next.addResources(
      language, 'translation',
      this.locales[language]);
    }
  },

  linktoFromThePage: function (ID, type, slot_id, isTranscribed) {
    _this = this;
    if (this.isLocalResource(ID) == false) {
      return false;
    }
    if (ID.includes('duchas')) {
      return false;
    }
    if (manifestsFromThePage[ID] !== undefined) {
      if (manifestsFromThePage[ID] == null) {
        return manifestsFromThePage[ID];
      }
    }
    else if (manifestsFromThePage[ID] == undefined) {
      manifestsFromThePage[ID] = false;
    }
    if (typeof type === undefined) {
      type = 'manifests';
    }
    /* add protocol declaration if missing ... not appropriate for all sites */
    if (ID.substring(0, 2) == '//') {
      ID = $.trim('https:' + ID);
    }
    /* skip if previously queried */
    if (manifestsFromThePage[ID] !== undefined && manifestsFromThePage[ID] !== null && manifestsFromThePage[ID] !== false) {
      return manifestsFromThePage[ID];
    }
    var url = 'https://fromthepage.com/iiif/for/' + ID;

    $.ajax(url, {
      success: function (responseText) {
        returnResponse(responseText, isTranscribed);
      },
      error: function (xhr, ajaxOptions, errorMsg) {
        if (xhr.status == 404) {
          //console.log(errorMsg);
          manifestsFromThePage[ID] = null;
        }
        else if (xhr.status == 401) {
          /* not implemented at FromThePage */
        }
      },
      timeout: configFromThePage[ 'timeoutFromThePage']
    });

    return true;

    function returnResponse(data, isTranscribed) {
      if (data !== undefined) {
        if (manifestsFromThePage[ID] === undefined || manifestsFromThePage[ID] == false) {
          if (type == 'manifests') {
            manifestsFromThePage[ID] = data[ "@id"];
            if (isTranscribed == false) {
              _this.showTranscriptionLink(slot_id, _this.setLinkTypeFromThePage(manifestsFromThePage[ID], 'read'),'FtP');
            }
            else {
              _this.showDocumentLink(slot_id, _this.setLinkTypeFromThePage(manifestsFromThePage[ID], 'read'),'FtP');
            }
          }
          else if (type == 'canvases') {
            var response = data[ "@id"];
            response = data[ "@id"].substr(0, response.indexOf('/canvas/')) + '/manifest';
            manifestsFromThePage[ID] = $.trim(response);
            _this.showTranscriptionLink(slot_id, _this.setLinkTypeFromThePage(manifestsFromThePage[ID], 'read'));
          }
        }
      }
    }
  },

  showDownloadLink: function (slot_id, link) {
    $('div.layout-slot[data-layout-slot-id="' + slot_id + '"] .mirador-icon-canvas-tei').attr("href", link);
    if ($('div.layout-slot[data-layout-slot-id="' + slot_id + '"] .mirador-icon-canvas-tei').hasClass("hidden")) {
      $('div.layout-slot[data-layout-slot-id="' + slot_id + '"] .mirador-icon-canvas-tei').removeClass("hidden");
    }
    return true;
  },
  showDocumentLink: function (slot_id, link, format) {
    $('div.layout-slot[data-layout-slot-id="' + slot_id + '"] .mirador-icon-canvas-transcription').attr("href", link);
    if ($('div.layout-slot[data-layout-slot-id="' + slot_id + '"] .mirador-icon-canvas-transcription').hasClass("hidden")) {
      $('div.layout-slot[data-layout-slot-id="' + slot_id + '"] .mirador-icon-canvas-transcription').removeClass("hidden");
    }
    if (format) {
      if (format == 'FtP') {
        $('div.layout-slot[data-layout-slot-id="' + slot_id + '"] .mirador-icon-canvas-transcription').attr("href", link);
        $('div.layout-slot[data-layout-slot-id="' + slot_id + '"] .mirador-icon-canvas-transcription > i').addClass("fa-file-text"); /* default dodger blue */
      }
      if (format == 'application/pdf') {
        $('div.layout-slot[data-layout-slot-id="' + slot_id + '"] .mirador-icon-canvas-transcription').attr("href", link);
        $('div.layout-slot[data-layout-slot-id="' + slot_id + '"] .mirador-icon-canvas-transcription > i').addClass("fa-file-pdf-o");
        $('div.layout-slot[data-layout-slot-id="' + slot_id + '"] .mirador-icon-canvas-transcription > i').css({'color':'#ff0000','font-weight:':'bold'}); /* PDF red */
      }
      else if (format == 'text/html') {
        $('div.layout-slot[data-layout-slot-id="' + slot_id + '"] .mirador-icon-canvas-transcription').attr("href", link);
        $('div.layout-slot[data-layout-slot-id="' + slot_id + '"] .mirador-icon-canvas-transcription > i').addClass("fa-file-code-o");
        $('div.layout-slot[data-layout-slot-id="' + slot_id + '"] .mirador-icon-canvas-transcription > i').css({'color':'#449d44','font-weight':'bold'}); /* UCD green */
      }
    }
    else {
      $('div.layout-slot[data-layout-slot-id="' + slot_id + '"] .mirador-icon-canvas-transcription > i').addClass("fa-file-text"); /* default dodger blue */
    }
    return true;
  },
  showTranscriptionLink: function (slot_id, link) {
    $('div.layout-slot[data-layout-slot-id="' + slot_id + '"] .mirador-icon-canvas-transcribe').attr("href", link);
    if ($('div.layout-slot[data-layout-slot-id="' + slot_id + '"] .mirador-icon-canvas-transcribe').hasClass("hidden")) {
      $('div.layout-slot[data-layout-slot-id="' + slot_id + '"] .mirador-icon-canvas-transcribe').removeClass("hidden");
    }
    return true;
  },
  isLocalResource: function (ID) {
    if (configFromThePage[ 'local_domain'] && configFromThePage[ 'local_domain'] !== '') {
      if (ID.includes(configFromThePage[ 'local_domain'])) {
        return true;
      }
    }
    return false;
  },

  setLinkTypeFromThePage: function (uri, activity) {
    if (uri == false) {
      return;
    }
    var val = uri.substr(uri.indexOf("/iiif/") + 6);
    val = val.substr(0, val.indexOf('/'));
    var uri;
    switch (activity) {
      case 'about':
        uri = 'https://fromthepage.com/work/show?work_id=' + val;
        break;
      case 'edit':
        uri = 'https://fromthepage.com/work/edit?work_id=' + val;
        break;
      case 'read':
        uri = 'https://fromthepage.com/display/read_work?work_id=' + val;
        break;
      case 'show':
        uri = 'https://fromthepage.com/display/show?work_id=' + val;
        break;
      case 'xhtml':
        uri = 'https://fromthepage.com/export/show?work_id=' + val;
        break;
      default:
        uri = 'https://fromthepage.com/display/read_work?work_id=' + val;
    }
    return uri;
  },

  isFtP: function (collectionId) {
    /* is the manifest part of a collection declared in local collections_FtP.json? */
    var status = false;
    $.each(collectionsFtP, function (index, item) {
      $.each(item, function (key, collection) {
        if (collection.collectionIdLocal == collectionId) {
          status = true;
        }
      });
    });
    return status;
  },

  /* update menu buttons from saved settings in local storage */
  setSavedPreferences: function (context) {
    /* read saved settings from local storage, then update menu and buttons */
    var savedPreferences_buttons = localStorage.getItem('dl_settings_buttons');
    var savedPreferences_userButtons = localStorage.getItem('dl_settings_userButtons');
    //var savedPreferences_plugins = localStorage.getItem('dl_settings_plugins');

    if (savedPreferences_buttons) {
      updateSettings(savedPreferences_buttons);
    }
    if (savedPreferences_userButtons) {
      updateSettings(savedPreferences_userButtons);
    }

    function updateSettings(settings) {
      /* updates both menu settings and menu buttons */
      $.each(JSON.parse(settings), function (key, val) {
        var className = 'a.' + key;
        var mainMenuClasses = {
          'a.bookmark': 'a.bookmark-workspace', 'a.changeLayout': 'a.change-layout', 'a.fullScreen': 'a.fullscreen-viewer', 'a.canvasLink': 'a.shareButtons'
        };
        if (mainMenuClasses[className] && mainMenuClasses[className].length) {
          className = mainMenuClasses[className];
        }
        if ($.type(val) == 'boolean') {
          var counter = 0;
          var setLinks = setInterval(function () {
            if ($(className).length) {
              counter++
              if (val == true) {
                $(className).removeClass('noshow');
                $('.window-options-item[name=key]').attr('checked', '');
              }
              else {
                $(className).addClass('noshow');
                $('.mirador-container #window-options-panel .window-options-item[name="' + key + '"]').removeAttr('checked');
              }
              clearInterval(setLinks);
            }
            else {
              counter++;
              if (counter > 29) {
                clearInterval(setLinks);
              }
            }
          },
          100);
        }
        else if ($.type(val) == 'object') {
          var counter = 0;
          var irrelevantKeys =[ 'label', 'plugin'];
          $.each(val, function (objKey, objVal) {
            /* skip irrelevant keys */
            if ($.inArray(objKey, irrelevantKeys) !== -1) {
              return;
            }
            className = 'a.' + objKey;
            var setObjLinks = setInterval(function () {
              if ($(className).length) {
                if (objVal == false) {
                  $('a.shareButtons').addClass('noshow');
                  $('.mirador-container #window-options-panel .window-options-item[name="' + objKey + '"]').removeAttr('checked');
                }
                else {
                  $(className).removeClass('noshow');
                  $('.mirador-container #window-options-panel .window-options-item[name="' + objKey + '"]').attr('checked','');
                }
                clearInterval(setObjLinks);
              }
              else {
                counter++;
                if (counter > 80) {
                  clearInterval(setObjLinks);
                }
              }
            },
            100);
          });
        }
      });
    }
  }
};

var duchasTranscribed = false;
/* FromThePage may retain references to manifests that were added, deleted, then added again;
 * initialising as follows avoids receiving blind references from the FromThePage API for such cases
 */
var manifestsFromThePage = {
  "https://data.ucd.ie/api/img/manifests/ivrla:3849": "https://fromthepage.com/iiif/355/manifest",
  "https://data.ucd.ie/api/img/ivrla:3849/canvas/ivrla:3850": "https://fromthepage.com/iiif/355/manifest",
  "https://data.ucd.ie/api/img/manifests/ivrla:3827": "https://fromthepage.com/iiif/340/manifest",
  "https://data.ucd.ie/api/img/ivrla:3827/canvas/ivrla:3828": "https://fromthepage.com/iiif/340/manifest",
  "https://data.ucd.ie/api/img/ivrla:3827/canvas/ivrla:3829": "https://fromthepage.com/iiif/340/manifest",
  "https://data.ucd.ie/api/img/ivrla:3827/canvas/ivrla:3830": "https://fromthepage.com/iiif/340/manifest",
  "https://data.ucd.ie/api/img/ivrla:3827/canvas/ivrla:3831": "https://fromthepage.com/iiif/340/manifest",
  "https://data.ucd.ie/api/img/manifests/ivrla:3835": "https://fromthepage.com/iiif/344/manifest",
  "https://data.ucd.ie/api/img/ivrla:3835/canvas/ivrla:3836": "https://fromthepage.com/iiif/344/manifest",
  "https://data.ucd.ie/api/img/ivrla:3835/canvas/ivrla:3837": "https://fromthepage.com/iiif/344/manifest"
};
var localCollections;
var configFromThePage = {
  /* transpose to Mirador config? */
  "local_domain": "data.ucd.ie", // identifying the local domain prevents lookup of resources from outside that domain
  "timeoutFromThePage": 5000 // maximum tolerable latency for a FromThePage response
}

$(document).ready(function () {
  linktoFromThePage.init();
});
<commit_msg>Delete mirador-plugin-fromThePage.js<commit_after><|endoftext|>


<commit_before>'use strict';

togglbutton.render(
  '.wspace-task-view:not(.toggl)',
  { observe: true },
  function (elem) {
    const container = $('.wrike-panel-header-toolbar', elem);

    const descriptionText = function () {
      const urlString = window.location.href;
      const url = new URL(urlString);
      // We have to look for the param in a custom hash
      const hash = url.hash.substr(1);
      const hashParts = hash.split('&');
      let taskId = null;
      for (let i = 0; i < hashParts.length; i++) {
        const partSplit = hashParts[i].split('=');
        if (partSplit[0] === 'ot') {
          taskId = partSplit[1];
        }
      }

      const titleElem = $('.title-field-ghost', elem);
      const titleElemText = titleElem ? titleElem.textContent : 'not found';
      return '#' + taskId + ' ' + titleElemText.trim();
    };

    const projectText = function () {
      const projectElem = $('.wspace-tag-simple', elem);
      // We process the project element text content.
      return projectElem.textContent;
    };

    const link = togglbutton.createTimerLink({
      className: 'wrike',
      description: descriptionText,
      projectName: projectText
    });

    container.insertBefore(link, container.firstChild);
  }
);
<commit_msg>feat(wrike): Include issue number and project name in time entries<commit_after>'use strict';

togglbutton.render(
  '.wspace-task-view:not(.toggl)',
  { observe: true },
  function (elem) {
    const container = $('.wrike-panel-header-toolbar', elem);

    const descriptionText = function () {
      const urlString = window.location.href;
      const url = new URL(urlString);
      // We have to look for the param in a custom hash
      const hash = url.hash.substr(1);
      const hashParts = hash.split('&');
      let taskId = null;
      for (let i = 0; i < hashParts.length; i++) {
        const partSplit = hashParts[i].split('=');
        if (partSplit[0] === 'ot') {
          taskId = partSplit[1];
        }
      }

      const titleElem = $('.title-field-ghost', elem);
      const titleElemText = titleElem ? titleElem.textContent : 'not found';
      return `#${taskId} ${titleElemText.trim()}`;
    };

    const projectText = function () {
      const projectElem = $('.wspace-tag-simple', elem);
      // We process the project element text content.
      return projectElem.textContent;
    };

    const link = togglbutton.createTimerLink({
      className: 'wrike',
      description: descriptionText,
      projectName: projectText
    });

    container.insertBefore(link, container.firstChild);
  }
);
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import { Navbar, Nav, MenuItem, NavDropdown } from 'react-bootstrap';
import { Link } from 'react-router';
import auth from './auth';

require('../node_modules/bootstrap/dist/css/bootstrap.min.css');
require('./styles/style.sass');

export default class App extends Component {
  logout = () => {
    auth.logout(() => {
      this.props.history.push('/');
    });
  }
  render() {
    var navLinks = [];
    if (auth.loggedIn()) {
      navLinks.push(
<Link to="programs">Programs</Link>
);
      navLinks.push(
        <NavDropdown eventKey={3} title={auth.getUserEmail()}>
          <MenuItem onClick={this.logout}>Logout</MenuItem>
        </NavDropdown>
      );
    } else {
      navLinks.push(
<Link to="login">Login</Link>
);
      navLinks.push(
<Link to="register">Register</Link>
);
    }
    return (

        <Navbar staticTop>
          <Navbar.Header>
            <Navbar.Brand>
              <Link to="/">Gym Diary</Link>
            </Navbar.Brand>
            <Navbar.Toggle />
          </Navbar.Header>
          <Navbar.Collapse>

              {navLinks}

          </Navbar.Collapse>
        </Navbar>
        {this.props.children}

Copyright Henrik

    );
  }
}
<commit_msg>NavDropDown required id for screen readers.<commit_after>import React, { Component } from 'react';
import { Navbar, Nav, MenuItem, NavDropdown } from 'react-bootstrap';
import { Link } from 'react-router';
import auth from './auth';

require('../node_modules/bootstrap/dist/css/bootstrap.min.css');
require('./styles/style.sass');

export default class App extends Component {
  logout = () => {
    auth.logout(() => {
      this.props.history.push('/');
    });
  }
  render() {
    var navLinks = [];
    if (auth.loggedIn()) {
      navLinks.push(
<Link to="programs">Programs</Link>
);
      navLinks.push(
        <NavDropdown eventKey={3} title={auth.getUserEmail()} id="user-dropdown">
          <MenuItem onClick={this.logout}>Logout</MenuItem>
        </NavDropdown>
      );
    } else {
      navLinks.push(
<Link to="login">Login</Link>
);
      navLinks.push(
<Link to="register">Register</Link>
);
    }
    return (

        <Navbar staticTop>
          <Navbar.Header>
            <Navbar.Brand>
              <Link to="/">Gym Diary</Link>
            </Navbar.Brand>
            <Navbar.Toggle />
          </Navbar.Header>
          <Navbar.Collapse>

              {navLinks}

          </Navbar.Collapse>
        </Navbar>
        {this.props.children}

Copyright Henrik

    );
  }
}
<|endoftext|>


<commit_before><<<<<<< HEAD
import content from 'app/views/content.html';
import inventory from 'app/views/inventory.template.html';
=======
>>>>>>> 2ed9f8e... Removed the controller from markup.
import best from 'app/views/best.template.html';
import vendors from 'app/views/vendors.template.html';
import materialExchange from 'app/views/mats-exchange.template.html';
import debugItem from 'app/views/debugItem.template.html';
import developer from 'app/scripts/developer/developer.template.html';
import login from 'app/scripts/login/login.template.html';

function routes($stateProvider, $urlRouterProvider) {
  'ngInject';

  const states = [{
    name: 'root',
    abstract: true
  }, {
    name: 'best',
    parent: 'content',
    templateUrl: best,
    url: '/best'
  }, {
    name: 'vendors',
    parent: 'content',
    templateUrl: vendors,
    url: '/vendors'
  }, {
    name: 'materials-exchange',
    parent: 'content',
    url: '/materials-exchange',
    templateUrl: materialExchange
  }, {
    name: 'debugItem',
    parent: 'content',
    url: '/debugItem/:itemId',
    templateUrl: debugItem
  }, {
    name: 'developer',
    parent: 'content',
    url: '/developer',
    templateUrl: developer
  }, {
    name: 'login',
    parent: 'shell',
    url: '/login',
    templateUrl: login
  }];

  states.forEach((state) => {
    $stateProvider.state(state);
  });

  $urlRouterProvider.otherwise('/inventory');
}

export default routes;
<commit_msg>Fixed a forgotten merge.<commit_after>import best from 'app/views/best.template.html';
import vendors from 'app/views/vendors.template.html';
import materialExchange from 'app/views/mats-exchange.template.html';
import debugItem from 'app/views/debugItem.template.html';
import developer from 'app/scripts/developer/developer.template.html';
import login from 'app/scripts/login/login.template.html';

function routes($stateProvider, $urlRouterProvider) {
  'ngInject';

  const states = [{
    name: 'root',
    abstract: true
  }, {
    name: 'best',
    parent: 'content',
    templateUrl: best,
    url: '/best'
  }, {
    name: 'vendors',
    parent: 'content',
    templateUrl: vendors,
    url: '/vendors'
  }, {
    name: 'materials-exchange',
    parent: 'content',
    url: '/materials-exchange',
    templateUrl: materialExchange
  }, {
    name: 'debugItem',
    parent: 'content',
    url: '/debugItem/:itemId',
    templateUrl: debugItem
  }, {
    name: 'developer',
    parent: 'content',
    url: '/developer',
    templateUrl: developer
  }, {
    name: 'login',
    parent: 'shell',
    url: '/login',
    templateUrl: login
  }];

  states.forEach((state) => {
    $stateProvider.state(state);
  });

  $urlRouterProvider.otherwise('/inventory');
}

export default routes;
<|endoftext|>


<commit_before>import {remote} from 'electron';
import path from 'path';

export function inject () {
  global.manifest = remote.getGlobal('manifest');
  global.options = remote.getGlobal('options');

  const appPath = remote.app.getAppPath();
  const {addPath} = require(path.join(appPath, 'node_modules', 'app-module-path'));

  addPath(path.join(appPath, 'scripts'));
  addPath(path.join(appPath, 'node_modules'));

  // Add loggers to be used in the console
  const logger = require('common/utils/logger');
  window.log = logger.debugLogger('console:renderer');
  window.logError = logger.errorLogger('console:renderer', false);
  window.logFatal = logger.errorLogger('console:renderer', true);

  // Handle errors
  window.onerror = function (message, source, lineno, colno, error) {
    window.logError(error instanceof Error ? error : new Error(error || message));
  };
}
<commit_msg>Log scope too (browser)<commit_after>import {remote} from 'electron';
import path from 'path';

export function inject (scope) {
  global.manifest = remote.getGlobal('manifest');
  global.options = remote.getGlobal('options');

  const appPath = remote.app.getAppPath();
  const {addPath} = require(path.join(appPath, 'node_modules', 'app-module-path'));

  addPath(path.join(appPath, 'scripts'));
  addPath(path.join(appPath, 'node_modules'));

  // Add loggers to be used in the console
  const logger = require('common/utils/logger');
  window.log = logger.debugLogger('console:' + scope);
  window.logError = logger.errorLogger('console:' + scope, false);
  window.logFatal = logger.errorLogger('console:' + scope, true);

  // Handle errors
  window.onerror = function (message, source, lineno, colno, error) {
    window.logError(error instanceof Error ? error : new Error(error || message));
  };
}
<|endoftext|>


<commit_before>/*
 * Copyright 2014, Gregg Tavares.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Gregg Tavares. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*eslint no-use-before-define:0*/

"use strict";

// URL options:
//    debug=true    : contact local host, print stuff
//    verbose=true  : print stuff
//    local=true    : contact local.superhappyfuntimes.net
//    go=false      : don't go to url

// Start the main app logic.
requirejs(
  [ './cookies',
    './io',
    './iputils',
    './misc',
    './progress',
    './strings',
  ], function(Cookie, IO, IPUtils, misc, ProgressBar, Strings) {
  var $ = document.getElementById.bind(document);

  var g = misc.parseUrlQuery();
  var log = (g.debug || g.verbose) ? console.log.bind(console) : function() { };

  var getGamesUrl = window.location.origin + "/api/getgames2";
  if (g.debug) {
    getGamesUrl = "http://localhost:1337/api/getgames2";
  }
  if (g.local) {
    getGamesUrl = "http://local.happyfuntimes.net/api/getgames2";
  }
  var nameCookie = new Cookie("name");
  var timeout = 2000; // in ms
  var inApp = g.cordovaurl !== undefined;

  var totalThingsToDo = 0;
  var totalThingsDone = 0;

  var progressBar = new ProgressBar($("scan-progress"));
  progressBar.show(true);
  $("try").href = window.location.href;

  if (inApp) {
    $("about").style.display = "none";
    //$("about").addEventListener('click', function(e) {
    //  e.preventDefault();
    //  window.open(e.target.href, '_system');
    //}, false);
  }

  var handleCouldNotFind = function() {
    Array.prototype.forEach.call(document.querySelectorAll(".hidden"), function(elem) {
      elem.style.display = "block";
    });
  };

  /**
   * should call this after every request as it not only updates
   * the progress bar but also accounting of requests.
   */
  var updateProgress = function() {
    ++totalThingsDone;
    progressBar.set(totalThingsDone / totalThingsToDo);
  };

  /**
   * Deal with response from hft.net
   */
  var checkHFTResponse = function(err, obj) {
    log("hft response: " + JSON.stringify(obj));
    // It was bad
    if (err) {
      console.error(err);
      handleCouldNotFind();
      return;
    }

    checkGamesRunning(obj);
  };

  var makeUrl = function(baseUrl) {
    var name = nameCookie.get() || "";
    return baseUrl + "/enter-name.html" + misc.objectToSearchString({
      fromHFTNet: true,
      name: name,
      cordovaurl: g.cordovaurl,
      restarturl: g.restarturl,
      origin: window.location.href,
    });
  };

  var makeUrlFromHFT = function(hft) {
    return makeUrl("http://" + hft.ipAddress);
  };

  /**
   * HFT send back the list of games running on a certain network.
   * Check if they all respond. If there's only one go to it.
   * If there's more than one let the user select. If zero then
   * scan.
   */
  var checkGamesRunning = function(response) {
    // Check each ipAddress returned.
    var ipAddresses = response.gameIps;
    var runningHFTs = [];
    var names = {};
    var numChecked = 0;
    totalThingsToDo = ipAddresses.length;
    totalThingsDone = 0;

    var checkNextHFT = function() {
      if (numChecked === ipAddresses.length) {
        progressBar.show(false);
        if (runningHFTs.length === 0) {
          handleCouldNotFind();
        } else if (runningHFTs.length === 1) {
          goToUrl(makeUrlFromHFT(runningHFTs[0]));
        } else {
          askPlayerWhichHFT(runningHFTs);
        }
        return;
      } else {
        totalThingsDone = numChecked;
        updateProgress();
      }

      var ipAddress = ipAddresses[numChecked++];
      makeHFTPingRequest(ipAddress, function(ipAddress) {
        log("ping hft: " + ipAddress);
        return function(err, obj) {
          if (!err) {
            var name = obj.serverName;
            var add = true;
            if (name) {
              if (names[name]) {
                add = false;
              } else {
                names[name] = true;
              }
            }

            if (add) {
              runningHFTs.push({
                ipAddress: ipAddress,
                data: obj,
              });
            }
          }
          progressBar.set(numChecked / ipAddresses.length);
          checkNextHFT();
        };
      }(ipAddress));
    };
    checkNextHFT();
  };

  var askPlayerWhichHFT = function(runningHFTs) {
    log("ask player to choose hft: " + JSON.stringify(runningHFTs, undefined, "  "));
    $("looking").style.display = "none";
    var template = $("hft-button-template").text;
    var html = runningHFTs.map(function(hft, ndx) {
      return Strings.replaceParams(template, {
        id: "game-" + ndx,
        url: makeUrlFromHFT(hft),
        name: hft.data.serverName || "*unknown*",
        ipAddress: g.verbose ? hft.ipAddress : "",
      });
    });
    $("multiple-hfts").style.display = "block";
    $("systems").innerHTML = html.join("\n");
  };

  log("checking: " + getGamesUrl);
  IO.sendJSON(getGamesUrl, {}, checkHFTResponse, { timeout: 5000 });

  var goToUrl = function(url) {
    found = true;
    log("**GOTO** url: " + url);
    if (!g.debug && g.go !== false && g.go !== "false") {
      window.location.href = url;
    }
  };

  var makeHFTPingRequest = function(ipAndPort, fn) {
    var url = "http://" + ipAndPort;
    IO.sendJSON(url, {cmd: 'happyFunTimesPing'}, function(err, obj) {
      if (!err) {
        if (obj.version !== "0.0.0") {
          err = "bad api version: " + obj.version;
        } else if (obj.id !== "HappyFunTimes") {
          err = "bad id: " + obj.id;
        }
      }

      fn(err, obj);
    }, { timeout: timeout });
  };

});

<commit_msg>add url argument<commit_after>/*
 * Copyright 2014, Gregg Tavares.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Gregg Tavares. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*eslint no-use-before-define:0*/

"use strict";

// URL options:
//    debug=true    : contact local host, print stuff
//    verbose=true  : print stuff
//    local=true    : contact local.superhappyfuntimes.net
//    go=false      : don't go to url

// Start the main app logic.
requirejs(
  [ './cookies',
    './io',
    './iputils',
    './misc',
    './progress',
    './strings',
  ], function(Cookie, IO, IPUtils, misc, ProgressBar, Strings) {
  var $ = document.getElementById.bind(document);

  var g = misc.parseUrlQuery();
  var log = (g.debug || g.verbose) ? console.log.bind(console) : function() { };

  var getGamesUrl = window.location.origin + "/api/getgames2";
  if (g.debug) {
    getGamesUrl = "http://localhost:1337/api/getgames2";
  }
  if (g.local) {
    getGamesUrl = "http://local.happyfuntimes.net/api/getgames2";
  }
  if (g.url) {
    getGamesUrl = g.url;
  }
  var nameCookie = new Cookie("name");
  var timeout = 2000; // in ms
  var inApp = g.cordovaurl !== undefined;

  var totalThingsToDo = 0;
  var totalThingsDone = 0;

  var progressBar = new ProgressBar($("scan-progress"));
  progressBar.show(true);
  $("try").href = window.location.href;

  if (inApp) {
    $("about").style.display = "none";
    //$("about").addEventListener('click', function(e) {
    //  e.preventDefault();
    //  window.open(e.target.href, '_system');
    //}, false);
  }

  var handleCouldNotFind = function() {
    Array.prototype.forEach.call(document.querySelectorAll(".hidden"), function(elem) {
      elem.style.display = "block";
    });
  };

  /**
   * should call this after every request as it not only updates
   * the progress bar but also accounting of requests.
   */
  var updateProgress = function() {
    ++totalThingsDone;
    progressBar.set(totalThingsDone / totalThingsToDo);
  };

  /**
   * Deal with response from hft.net
   */
  var checkHFTResponse = function(err, obj) {
    log("hft response: " + JSON.stringify(obj));
    // It was bad
    if (err) {
      console.error(err);
      handleCouldNotFind();
      return;
    }

    checkGamesRunning(obj);
  };

  var makeUrl = function(baseUrl) {
    var name = nameCookie.get() || "";
    return baseUrl + "/enter-name.html" + misc.objectToSearchString({
      fromHFTNet: true,
      name: name,
      cordovaurl: g.cordovaurl,
      restarturl: g.restarturl,
      origin: window.location.href,
    });
  };

  var makeUrlFromHFT = function(hft) {
    return makeUrl("http://" + hft.ipAddress);
  };

  /**
   * HFT send back the list of games running on a certain network.
   * Check if they all respond. If there's only one go to it.
   * If there's more than one let the user select. If zero then
   * scan.
   */
  var checkGamesRunning = function(response) {
    // Check each ipAddress returned.
    var ipAddresses = response.gameIps;
    var runningHFTs = [];
    var names = {};
    var numChecked = 0;
    totalThingsToDo = ipAddresses.length;
    totalThingsDone = 0;

    var checkNextHFT = function() {
      if (numChecked === ipAddresses.length) {
        progressBar.show(false);
        if (runningHFTs.length === 0) {
          handleCouldNotFind();
        } else if (runningHFTs.length === 1) {
          goToUrl(makeUrlFromHFT(runningHFTs[0]));
        } else {
          askPlayerWhichHFT(runningHFTs);
        }
        return;
      } else {
        totalThingsDone = numChecked;
        updateProgress();
      }

      var ipAddress = ipAddresses[numChecked++];
      makeHFTPingRequest(ipAddress, function(ipAddress) {
        log("ping hft: " + ipAddress);
        return function(err, obj) {
          if (!err) {
            var name = obj.serverName;
            var add = true;
            if (name) {
              if (names[name]) {
                add = false;
              } else {
                names[name] = true;
              }
            }

            if (add) {
              runningHFTs.push({
                ipAddress: ipAddress,
                data: obj,
              });
            }
          }
          progressBar.set(numChecked / ipAddresses.length);
          checkNextHFT();
        };
      }(ipAddress));
    };
    checkNextHFT();
  };

  var askPlayerWhichHFT = function(runningHFTs) {
    log("ask player to choose hft: " + JSON.stringify(runningHFTs, undefined, "  "));
    $("looking").style.display = "none";
    var template = $("hft-button-template").text;
    var html = runningHFTs.map(function(hft, ndx) {
      return Strings.replaceParams(template, {
        id: "game-" + ndx,
        url: makeUrlFromHFT(hft),
        name: hft.data.serverName || "*unknown*",
        ipAddress: g.verbose ? hft.ipAddress : "",
      });
    });
    $("multiple-hfts").style.display = "block";
    $("systems").innerHTML = html.join("\n");
  };

  log("checking: " + getGamesUrl);
  IO.sendJSON(getGamesUrl, {}, checkHFTResponse, { timeout: 5000 });

  var goToUrl = function(url) {
    found = true;
    log("**GOTO** url: " + url);
    if (!g.debug && g.go !== false && g.go !== "false") {
      window.location.href = url;
    }
  };

  var makeHFTPingRequest = function(ipAndPort, fn) {
    var url = "http://" + ipAndPort;
    IO.sendJSON(url, {cmd: 'happyFunTimesPing'}, function(err, obj) {
      if (!err) {
        if (obj.version !== "0.0.0") {
          err = "bad api version: " + obj.version;
        } else if (obj.id !== "HappyFunTimes") {
          err = "bad id: " + obj.id;
        }
      }

      fn(err, obj);
    }, { timeout: timeout });
  };

});

<|endoftext|>