<commit_before>// Create a thumbnails view for a given viewer, using a URL template, and
// the number of pages in the document.
DV.Thumbnails = function(viewer){
  this.currentPage   = null;
  this.zoomLevel     = null;
  this.scrollTimer   = null;
  this.imageUrl      = viewer.schema.document.resources.page.image.replace(/\{size\}/, 'small');
  this.pageCount     = viewer.schema.document.pages;
  this.viewer        = viewer;
  this.resizeId      = _.uniqueId();
  this.sizes         = {
    "0": {w: 60, h: 75},
    "1": {w: 90, h: 112},
    "2": {w: 120, h: 150},
    "3": {w: 150, h: 188},
    "4": {w: 180, h: 225}
  };
  _.bindAll(this, 'lazyloadThumbnails', 'loadThumbnails');
};

// Render the Thumbnails from scratch.
DV.Thumbnails.prototype.render = function() {
  this.el = this.viewer.$('.DV-thumbnails');
  this.getZoom();
  this.el.empty();
  this.buildThumbnails(1, Math.min(100, this.pageCount));
  if (this.pageCount > 100) {
    // NB: This 100 millisecond delay is questionable.
    _.delay(_.bind(this.buildThumbnails, this, 101, this.pageCount), 100);
  }
  this.setZoom();
  this.viewer.elements.window.unbind('scroll.thumbnails').bind('scroll.thumbnails', this.lazyloadThumbnails);
  var resizeEvent = 'resize.thumbnails-' + this.resizeId;
  DV.jQuery(window).unbind(resizeEvent).bind(resizeEvent, this.lazyloadThumbnails);
};

DV.Thumbnails.prototype.buildThumbnails = function(startPage, endPage) {
  var thumbnailsHTML = JST.thumbnails({
    page      : startPage,
    endPage   : endPage,
    zoom      : this.zoomLevel,
    imageUrl  : this.imageUrl
  });
  this.el.html(this.el.html() + thumbnailsHTML);
  _.defer(this.loadThumbnails);
};

// Set the appropriate zoomLevel class for the thumbnails, estimating
// height change.
DV.Thumbnails.prototype.setZoom = function(zoom) {
  this.getZoom(zoom);
  var size = this.sizes[this.zoomLevel];
  this.viewer.$('.DV-hasHeight').each(function(i) {
    var ratio = size.w / this.width;
    $(this).css({height: this.height * ratio});
  });
  this.el[0].className = this.el[0].className.replace(/DV-zoom-\d\s*/, '');
  this.el.addClass('DV-zoom-' + this.zoomLevel);
};

// The thumbnails (unfortunately) have their own notion of the current zoom
// level -- specified from 0 - 4.
DV.Thumbnails.prototype.getZoom = function(zoom) {
  if (zoom != null) {
    return this.zoomLevel = _.indexOf(this.viewer.models.document.ZOOM_RANGES, zoom);
  } else {
    return this.zoomLevel = this.viewer.slider.slider('value');
  }
};

// After a thumbnail has been loaded, we know its height.
DV.Thumbnails.prototype.setImageSize = function(image, imageEl) {
  var size = this.sizes[this.zoomLevel];
  var ratio = size.w / image.width;
  var newHeight = image.height * ratio;
  if (Math.abs(size.h - newHeight) > 10) {
    if (newHeight < size.h) {
      imageEl.addClass('DV-hasHeight').css({height: newHeight});
    } else {
      var heightRatio = newHeight / size.h;
      var newWidth = size.w / heightRatio;
      imageEl.add(imageEl.prev('.DV-thumbnail-shadow')).css({width: newWidth});
    }
  }
  imageEl.attr({src: image.src});
};

// Only attempt to load the current viewport's worth of thumbnails if we've
// been sitting still for at least 1/10th of a second.
DV.Thumbnails.prototype.lazyloadThumbnails = function() {
  if (this.viewer.state != 'ViewThumbnails') return;
  if (this.scrollTimer) clearTimeout(this.scrollTimer);
  this.scrollTimer = setTimeout(this.loadThumbnails, 100);
};

// Load the currently visible thumbnails, as determined by the size and position
// of the viewport.
DV.Thumbnails.prototype.loadThumbnails = function() {
  var viewer           = this.viewer;
  var width            = viewer.$('.DV-thumbnails').width();
  var height           = viewer.elements.window.height();
  var scrollTop        = viewer.elements.window.scrollTop();
  var scrollBottom     = scrollTop + height;
  var first            = viewer.$('.DV-thumbnail:first-child');
  var firstHeight      = first.outerHeight(true);
  var firstWidth       = first.outerWidth(true);

  // Determine the top and bottom page.
  var thumbnailsPerRow = Math.floor(width / firstWidth);
  var startPage        = Math.floor(scrollTop / firstHeight * thumbnailsPerRow);
  var endPage          = Math.ceil(scrollBottom / firstHeight * thumbnailsPerRow);

  // Round to the nearest whole row.
  startPage            -= (startPage % thumbnailsPerRow);
  endPage              += thumbnailsPerRow - (endPage % thumbnailsPerRow) + 1;

  this.loadImages(startPage, endPage);
};

// Load all of the images within a range of visible thumbnails.
DV.Thumbnails.prototype.loadImages = function(startPage, endPage) {
  var self = this;
  var viewer = this.viewer;
  var gt = startPage > 0 ? ':gt(' + startPage + ')' : '';
  var lt = endPage <= this.pageCount ? ':lt(' + endPage + ')' : '';
  viewer.$('.DV-thumbnail' + lt + gt).each(function(i) {
    var el = viewer.$(this);
    if (!el.attr('src')) {
      var imageEl = viewer.$('.DV-thumbnail-image', el);
      var image = new Image();
      DV.jQuery(image).bind('load', _.bind(self.setImageSize, self, image, imageEl))
                      .attr({src: imageEl.attr('data-src')});
    }
  });
};
<commit_msg>fixing erroneous reference to jquery.<commit_after>// Create a thumbnails view for a given viewer, using a URL template, and
// the number of pages in the document.
DV.Thumbnails = function(viewer){
  this.currentPage   = null;
  this.zoomLevel     = null;
  this.scrollTimer   = null;
  this.imageUrl      = viewer.schema.document.resources.page.image.replace(/\{size\}/, 'small');
  this.pageCount     = viewer.schema.document.pages;
  this.viewer        = viewer;
  this.resizeId      = _.uniqueId();
  this.sizes         = {
    "0": {w: 60, h: 75},
    "1": {w: 90, h: 112},
    "2": {w: 120, h: 150},
    "3": {w: 150, h: 188},
    "4": {w: 180, h: 225}
  };
  _.bindAll(this, 'lazyloadThumbnails', 'loadThumbnails');
};

// Render the Thumbnails from scratch.
DV.Thumbnails.prototype.render = function() {
  this.el = this.viewer.$('.DV-thumbnails');
  this.getZoom();
  this.el.empty();
  this.buildThumbnails(1, Math.min(100, this.pageCount));
  if (this.pageCount > 100) {
    // NB: This 100 millisecond delay is questionable.
    _.delay(_.bind(this.buildThumbnails, this, 101, this.pageCount), 100);
  }
  this.setZoom();
  this.viewer.elements.window.unbind('scroll.thumbnails').bind('scroll.thumbnails', this.lazyloadThumbnails);
  var resizeEvent = 'resize.thumbnails-' + this.resizeId;
  DV.jQuery(window).unbind(resizeEvent).bind(resizeEvent, this.lazyloadThumbnails);
};

DV.Thumbnails.prototype.buildThumbnails = function(startPage, endPage) {
  var thumbnailsHTML = JST.thumbnails({
    page      : startPage,
    endPage   : endPage,
    zoom      : this.zoomLevel,
    imageUrl  : this.imageUrl
  });
  this.el.html(this.el.html() + thumbnailsHTML);
  _.defer(this.loadThumbnails);
};

// Set the appropriate zoomLevel class for the thumbnails, estimating
// height change.
DV.Thumbnails.prototype.setZoom = function(zoom) {
  this.getZoom(zoom);
  var size = this.sizes[this.zoomLevel];
  this.viewer.$('.DV-hasHeight').each(function(i) {
    var ratio = size.w / this.width;
    DV.jQuery(this).css({height: this.height * ratio});
  });
  this.el[0].className = this.el[0].className.replace(/DV-zoom-\d\s*/, '');
  this.el.addClass('DV-zoom-' + this.zoomLevel);
};

// The thumbnails (unfortunately) have their own notion of the current zoom
// level -- specified from 0 - 4.
DV.Thumbnails.prototype.getZoom = function(zoom) {
  if (zoom != null) {
    return this.zoomLevel = _.indexOf(this.viewer.models.document.ZOOM_RANGES, zoom);
  } else {
    return this.zoomLevel = this.viewer.slider.slider('value');
  }
};

// After a thumbnail has been loaded, we know its height.
DV.Thumbnails.prototype.setImageSize = function(image, imageEl) {
  var size = this.sizes[this.zoomLevel];
  var ratio = size.w / image.width;
  var newHeight = image.height * ratio;
  if (Math.abs(size.h - newHeight) > 10) {
    if (newHeight < size.h) {
      imageEl.addClass('DV-hasHeight').css({height: newHeight});
    } else {
      var heightRatio = newHeight / size.h;
      var newWidth = size.w / heightRatio;
      imageEl.add(imageEl.prev('.DV-thumbnail-shadow')).css({width: newWidth});
    }
  }
  imageEl.attr({src: image.src});
};

// Only attempt to load the current viewport's worth of thumbnails if we've
// been sitting still for at least 1/10th of a second.
DV.Thumbnails.prototype.lazyloadThumbnails = function() {
  if (this.viewer.state != 'ViewThumbnails') return;
  if (this.scrollTimer) clearTimeout(this.scrollTimer);
  this.scrollTimer = setTimeout(this.loadThumbnails, 100);
};

// Load the currently visible thumbnails, as determined by the size and position
// of the viewport.
DV.Thumbnails.prototype.loadThumbnails = function() {
  var viewer           = this.viewer;
  var width            = viewer.$('.DV-thumbnails').width();
  var height           = viewer.elements.window.height();
  var scrollTop        = viewer.elements.window.scrollTop();
  var scrollBottom     = scrollTop + height;
  var first            = viewer.$('.DV-thumbnail:first-child');
  var firstHeight      = first.outerHeight(true);
  var firstWidth       = first.outerWidth(true);

  // Determine the top and bottom page.
  var thumbnailsPerRow = Math.floor(width / firstWidth);
  var startPage        = Math.floor(scrollTop / firstHeight * thumbnailsPerRow);
  var endPage          = Math.ceil(scrollBottom / firstHeight * thumbnailsPerRow);

  // Round to the nearest whole row.
  startPage            -= (startPage % thumbnailsPerRow);
  endPage              += thumbnailsPerRow - (endPage % thumbnailsPerRow) + 1;

  this.loadImages(startPage, endPage);
};

// Load all of the images within a range of visible thumbnails.
DV.Thumbnails.prototype.loadImages = function(startPage, endPage) {
  var self = this;
  var viewer = this.viewer;
  var gt = startPage > 0 ? ':gt(' + startPage + ')' : '';
  var lt = endPage <= this.pageCount ? ':lt(' + endPage + ')' : '';
  viewer.$('.DV-thumbnail' + lt + gt).each(function(i) {
    var el = viewer.$(this);
    if (!el.attr('src')) {
      var imageEl = viewer.$('.DV-thumbnail-image', el);
      var image = new Image();
      DV.jQuery(image).bind('load', _.bind(self.setImageSize, self, image, imageEl))
                      .attr({src: imageEl.attr('data-src')});
    }
  });
};
<|endoftext|>


<commit_before>import { lex, tokenString } from "../../src/compiler/lexer/lexer";

test("lex opening tag", () => {
	expect(lex(`<div>`)).toEqual([{"attributes": {}, "closed": false, "type": "tagOpen", "value": "div"}]);
});

test("lex closing tag", () => {
	expect(lex(`</div>`)).toEqual([{"type": "tagClose", "value": "div"}]);
});

test("lex self closing tag", () => {
	expect(lex(`<input/>`)).toEqual([{"attributes": {}, "closed": true, "type": "tagOpen", "value": "input"}]);
});

test("lex text", () => {
	expect(lex(`text test`)).toEqual([{"attributes": {"": "\"text test\""}, "closed": true, "type": "tagOpen", "value": "text"}]);
});

test("lex text inside tag", () => {
	expect(lex(`<div>text</div>`)).toEqual([
		{
			"type": "tagOpen",
			"value": "div",
			"attributes": {},
			"closed": false
		},
		{
			"type": "tagOpen",
			"value": "text",
			"attributes": {
				"": "\"text\""
			},
			"closed": true
		},
		{
			"type": "tagClose",
			"value": "div"
		}
	]);
})

test("lex expression", () => {
	expect(lex(`{data + 1}`)).toEqual([{"attributes": {"": "data.data + 1"}, "closed": true, "type": "tagOpen", "value": "text"}]);
});

test("lex attributes", () => {
	expect(lex(`<div id="test-id" class='test-class' dynamic={true}>`)).toEqual([{"attributes": {"id": "\"test-id\"", "class": "'test-class'", dynamic: "true"}, "closed": false, "type": "tagOpen", "value": "div"}]);
});

test("lex comments", () => {
	expect(lex(`<!-- comment -->`)).toEqual([]);
});

test("opening tag token to string", () => {
	const input = "
";
	expect(tokenString(lex(input)[0])).toBe(input);
});

test("opening tag token with attributes to string", () => {
	const input = `<div id="test-id" class='test-class' expression={dynamic}>`;
	expect(tokenString(lex(input)[0])).toBe(input.replace("dynamic", "data.dynamic"));
});

test("self-closing tag token to string", () => {
	const input = `<input/>`;
	expect(tokenString(lex(input)[0])).toBe(input);
});

test("self-closing tag token with attributes to string", () => {
	const input = `<input id="test-id" class='test-class' expression={dynamic}/>`;
	expect(tokenString(lex(input)[0])).toBe(input.replace("dynamic", "data.dynamic"));
});

test("closing tag token to string", () => {
	const input = `</div>`;
	expect(tokenString(lex(input)[0])).toBe(input);
});

test("text token to string", () => {
	const input = `Test Text`;
	expect(tokenString(lex(input)[0])).toBe(input);
});

test("expression token to string", () => {
	const input = `{dynamic + 1}`;
	expect(tokenString(lex(input)[0])).toBe(input.replace("dynamic", "data.dynamic"));
});

test("lex error from unclosed opening bracket", () => {
	console.error = jest.fn();

	expect(Array.isArray(lex("
<"))).toBe(true);
	expect(console.error).toBeCalled();
});

test("lex error from invalid opening tag", () => {
	console.error = jest.fn();

	expect(() => {lex("
 {
	console.error = jest.fn();

	expect(() => {lex("<input/");}).toThrow();
	expect(console.error).toBeCalled();
});

test("lex error from unclosed closing tag", () => {
	console.error = jest.fn();

	expect(Array.isArray(lex("
 {
	console.error = jest.fn();

	expect(Array.isArray(lex("`)).toEqual([]);
});

test("opening tag token to string", () => {
	const input = "
";
	expect(tokenString(lex(input)[0])).toBe(input);
});

test("opening tag token with attributes to string", () => {
	const input = `<div id="test-id" class='test-class' expression={dynamic}>`;
	expect(tokenString(lex(input)[0])).toBe(input.replace("dynamic", "data.dynamic"));
});

test("self-closing tag token to string", () => {
	const input = `<input/>`;
	expect(tokenString(lex(input)[0])).toBe(input);
});

test("self-closing tag token with attributes to string", () => {
	const input = `<input id="test-id" class='test-class' expression={dynamic}/>`;
	expect(tokenString(lex(input)[0])).toBe(input.replace("dynamic", "data.dynamic"));
});

test("closing tag token to string", () => {
	const input = `</div>`;
	expect(tokenString(lex(input)[0])).toBe(input);
});

test("text token to string", () => {
	const input = `Test Text`;
	expect(tokenString(lex(input)[0])).toBe(input);
});

test("expression token to string", () => {
	const input = `{dynamic + 1}`;
	expect(tokenString(lex(input)[0])).toBe(input.replace("dynamic", "data.dynamic"));
});

test("lex error from unclosed opening bracket", () => {
	console.error = jest.fn();

	expect(Array.isArray(lex("
<"))).toBe(true);
	expect(console.error).toBeCalled();
});

test("lex error from invalid opening tag", () => {
	console.error = jest.fn();

	expect(() => {lex("
 {
	console.error = jest.fn();

	expect(() => {lex("<input/");}).toThrow();
	expect(console.error).toBeCalled();
});

test("lex error from unclosed closing tag", () => {
	console.error = jest.fn();

	expect(Array.isArray(lex("
 {
	console.error = jest.fn();

	expect(Array.isArray(lex("


<commit_before>// @flow
import * as React from 'react'
import type {Folder} from './list'
import {Box, Button, Text, Icon, MultiAvatar, Avatar, Meta, Usernames} from '../common-adapters'
import {getStyle} from '../common-adapters/text'
import {globalStyles, globalColors, globalMargins} from '../styles'

const Avatars = ({styles, users, smallMode, ignored, isPublic, isTeam}) => {
  if (!isPublic && users.length > 1) {
    users = users.filter(({you}) => !you)
  }
  const avatarCount = Math.min(2, users.length)
  const opacity = ignored ? 0.5 : 1
  const avatarProps = users.slice(0, 2).map(({username}, idx) => ({
    borderColor: avatarCount > 1 && idx === 0 ? globalColors.white : undefined,
    loadingColor: globalColors.lightGrey,
    size: smallMode ? 24 : 32,
    username,
  }))

  let teamname = 'unknown'
  if (isTeam && users.length > 0) {
    teamname = users[0].username
  }

  return (
    <Box
      style={{
        ...globalStyles.flexBoxRow,
        alignItems: 'flex-start',
        height: smallMode ? globalMargins.large : 56,
        justifyContent: 'flex-start',
        padding: globalMargins.xtiny,
        width: smallMode ? globalMargins.large : 56,
      }}
    >
      {isTeam
        ? <Avatar
            size={smallMode ? 32 : 40}
            teamname={teamname}
            isTeam={true}
            style={{opacity, marginLeft: globalMargins.xtiny, marginTop: globalMargins.xtiny}}
          />
        : <MultiAvatar
            singleSize={smallMode ? 32 : 40}
            multiSize={smallMode ? 24 : 32}
            avatarProps={avatarProps}
            style={{opacity}}
          />}
    </Box>
  )
}

const Modified = ({smallMode, styles, modified}) => {
  const iconColor = {color: getStyle('BodySmall', styles.modifiedMode).color}
  const boltStyle = {
    fontSize: smallMode ? 10 : 10,
    alignSelf: 'center',
    ...(smallMode ? {marginTop: 2} : {marginLeft: -2, marginRight: 1, marginTop: 2}),
    ...iconColor,
  }

  return (
    <Box style={stylesModified}>
      <Icon type="iconfont-thunderbolt" style={boltStyle} hint="Modified" />
      <Text type="BodySmall" backgroundMode={styles.modifiedMode}>Modified {modified.when} by </Text>
      <Text type="BodySmallInlineLink" backgroundMode={styles.modifiedMode}>{modified.username}</Text>
    </Box>
  )
}

const RowMeta = ({ignored, meta, styles}) => {
  if (meta === 'ignored') {
    return
  }

  const metaColors = {
    new: globalColors.white,
    rekey: globalColors.white,
  }

  const metaBGColors = {
    new: globalColors.orange,
    rekey: globalColors.red,
  }

  const metaProps = {
    title: meta || '',
    style: meta ? {color: metaColors[meta], backgroundColor: metaBGColors[meta]} : {},
  }

  return <Meta {...metaProps} />
}

type RowType = {
  hasReadOnlyUsers: boolean,
  installed: boolean,
  smallMode: boolean,
  sortName: string,
  onOpen: (path: string) => void,
  onChat: (tlf: string, isTeam?: boolean) => void,
  onClick: (path: string) => void,
  onRekey: (path: string) => void,
}

const Row = ({
  users,
  isPublic,
  isTeam,
  hasReadOnlyUsers,
  ignored,
  installed,
  meta,
  modified,
  smallMode,
  onChat,
  onOpen,
  onClick,
  userAvatar,
  onRekey,
  path,
  sortName,
}: RowType & Folder) => {
  const onOpenClick = event => {
    event.preventDefault()
    event.stopPropagation()
    if (onOpen) {
      onOpen(path)
    }
  }
  const onChatClick = event => {
    event.preventDefault()
    event.stopPropagation()
    if (onChat) {
      onChat(sortName, isTeam)
    }
  }
  const styles = isPublic ? stylesPublic : stylesPrivate

  let redColor = globalColors.red

  if (ignored) {
    redColor = globalColors.red_75
  }

  const containerStyle = {
    ...styles.rowContainer,
    minHeight: smallMode ? 40 : 48,
    backgroundColor: !smallMode && !installed ? globalColors.grey : globalColors.white,
  }

  return (
    <Box style={containerStyle} className="folder-row" onClick={() => onClick && onClick(path)}>
      <Box style={globalStyles.flexBoxRow}>
        <Avatars
          users={users}
          styles={styles}
          smallMode={smallMode}
          ignored={ignored}
          isPublic={isPublic}
          isTeam={isTeam}
        />
        <Box style={stylesBodyContainer}>
          <Usernames
            users={users}
            type={smallMode ? 'BodySmallSemibold' : 'BodySemibold'}
            style={{
              color: isPublic ? globalColors.yellowGreen2 : globalColors.darkBlue,
              opacity: ignored ? 0.6 : 1,
            }}
            redColor={redColor}
          />
          {meta && !ignored && <RowMeta ignored={ignored} meta={meta} styles={styles} />}
          {!(meta || ignored) &&
            modified &&
            <Modified modified={modified} styles={styles} smallMode={smallMode} />}
        </Box>
        {!smallMode &&
          !isPublic &&
          !hasReadOnlyUsers &&
          meta !== 'rekey' &&
          <Box style={{...stylesActionContainer, width: smallMode ? undefined : 64}}>
            <Text
              type="BodySmallSecondaryLink"
              className="folder-row-hover-action"
              onClick={onChatClick}
              style={styles.action}
            >
              Chat
            </Text>
          </Box>}
        <Box
          style={{
            ...stylesActionContainer,
            width: smallMode ? undefined : 64,
            marginRight: globalMargins.small,
          }}
        >
          {!smallMode &&
            meta !== 'rekey' &&
            <Text
              type="BodySmallSecondaryLink"
              className="folder-row-hover-action"
              onClick={onOpenClick}
              style={styles.action}
            >
              Open
            </Text>}
          {meta === 'rekey' &&
            <Button
              small={true}
              type="PrimaryPrivate"
              onClick={e => {
                if (onRekey) {
                  e.stopPropagation()
                  onRekey(path)
                }
              }}
              label="Rekey"
              style={styles.action}
            />}
        </Box>
      </Box>
    </Box>
  )
}

const rowContainer = {
  ...globalStyles.flexBoxColumn,
  ...globalStyles.clickable,
  position: 'relative',
}

const stylesPrivate = {
  rowContainer: {
    ...rowContainer,
    backgroundColor: globalColors.white,
    color: globalColors.white,
  },
  ignored: {
    color: globalColors.white_40,
    backgroundColor: globalColors.white,
  },
  groupIcon: {
    small: 'icon-folder-private-group-24',
    normal: 'icon-folder-private-group-32',
  },
  nameColor: globalColors.white,
  modifiedMode: 'Terminal',
  action: {
    ...globalStyles.clickable,
    alignSelf: 'center',
    marginRight: globalMargins.tiny,
  },
}

const stylesPublic = {
  rowContainer: {
    ...rowContainer,
    backgroundColor: globalColors.white,
    color: globalColors.yellowGreen2,
  },
  ignored: {
    color: globalColors.white_75,
    backgroundColor: globalColors.white,
  },
  groupIcon: {
    small: 'icon-folder-public-group-24',
    normal: 'icon-folder-public-group-32',
  },
  nameColor: globalColors.yellowGreen2,
  modifiedMode: 'Normal',
  action: {
    ...globalStyles.clickable,
    alignSelf: 'center',
    marginRight: globalMargins.tiny,
  },
}

const stylesBodyContainer = {
  ...globalStyles.flexBoxColumn,
  flex: 1,
  justifyContent: 'center',
  padding: 8,
  marginRight: 16,
}

const stylesActionContainer = {
  ...globalStyles.flexBoxRow,
  alignItems: 'flex-start',
  justifyContent: 'center',
}

const stylesModified = {
  ...globalStyles.flexBoxRow,
  alignItems: 'center',
}

export default Row
<commit_msg>alignment<commit_after>// @flow
import * as React from 'react'
import type {Folder} from './list'
import {Box, Button, Text, Icon, MultiAvatar, Avatar, Meta, Usernames} from '../common-adapters'
import {getStyle} from '../common-adapters/text'
import {globalStyles, globalColors, globalMargins} from '../styles'

const Avatars = ({styles, users, smallMode, ignored, isPublic, isTeam}) => {
  if (!isPublic && users.length > 1) {
    users = users.filter(({you}) => !you)
  }
  const avatarCount = Math.min(2, users.length)
  const opacity = ignored ? 0.5 : 1
  const avatarProps = users.slice(0, 2).map(({username}, idx) => ({
    borderColor: avatarCount > 1 && idx === 0 ? globalColors.white : undefined,
    loadingColor: globalColors.lightGrey,
    size: smallMode ? 24 : 32,
    username,
  }))

  let teamname = 'unknown'
  if (isTeam && users.length > 0) {
    teamname = users[0].username
  }

  return (
    <Box
      style={{
        ...globalStyles.flexBoxRow,
        alignItems: 'center',
        height: smallMode ? globalMargins.large : 56,
        justifyContent: 'flex-start',
        padding: globalMargins.xtiny,
        width: smallMode ? globalMargins.large : 56,
      }}
    >
      {isTeam
        ? <Avatar
            size={smallMode ? 32 : 40}
            teamname={teamname}
            isTeam={true}
            style={{opacity, marginLeft: globalMargins.xtiny, marginTop: globalMargins.xtiny}}
          />
        : <MultiAvatar
            singleSize={smallMode ? 32 : 40}
            multiSize={smallMode ? 24 : 32}
            avatarProps={avatarProps}
            style={{opacity}}
          />}
    </Box>
  )
}

const Modified = ({smallMode, styles, modified}) => {
  const iconColor = {color: getStyle('BodySmall', styles.modifiedMode).color}
  const boltStyle = {
    fontSize: smallMode ? 10 : 10,
    alignSelf: 'center',
    ...(smallMode ? {marginTop: 2} : {marginLeft: -2, marginRight: 1, marginTop: 2}),
    ...iconColor,
  }

  return (
    <Box style={stylesModified}>
      <Icon type="iconfont-thunderbolt" style={boltStyle} hint="Modified" />
      <Text type="BodySmall" backgroundMode={styles.modifiedMode}>Modified {modified.when} by </Text>
      <Text type="BodySmallInlineLink" backgroundMode={styles.modifiedMode}>{modified.username}</Text>
    </Box>
  )
}

const RowMeta = ({ignored, meta, styles}) => {
  if (meta === 'ignored') {
    return
  }

  const metaColors = {
    new: globalColors.white,
    rekey: globalColors.white,
  }

  const metaBGColors = {
    new: globalColors.orange,
    rekey: globalColors.red,
  }

  const metaProps = {
    title: meta || '',
    style: meta ? {color: metaColors[meta], backgroundColor: metaBGColors[meta]} : {},
  }

  return <Meta {...metaProps} />
}

type RowType = {
  hasReadOnlyUsers: boolean,
  installed: boolean,
  smallMode: boolean,
  sortName: string,
  onOpen: (path: string) => void,
  onChat: (tlf: string, isTeam?: boolean) => void,
  onClick: (path: string) => void,
  onRekey: (path: string) => void,
}

const Row = ({
  users,
  isPublic,
  isTeam,
  hasReadOnlyUsers,
  ignored,
  installed,
  meta,
  modified,
  smallMode,
  onChat,
  onOpen,
  onClick,
  userAvatar,
  onRekey,
  path,
  sortName,
}: RowType & Folder) => {
  const onOpenClick = event => {
    event.preventDefault()
    event.stopPropagation()
    if (onOpen) {
      onOpen(path)
    }
  }
  const onChatClick = event => {
    event.preventDefault()
    event.stopPropagation()
    if (onChat) {
      onChat(sortName, isTeam)
    }
  }
  const styles = isPublic ? stylesPublic : stylesPrivate

  let redColor = globalColors.red

  if (ignored) {
    redColor = globalColors.red_75
  }

  const containerStyle = {
    ...styles.rowContainer,
    minHeight: smallMode ? 40 : 48,
    backgroundColor: !smallMode && !installed ? globalColors.grey : globalColors.white,
  }

  return (
    <Box style={containerStyle} className="folder-row" onClick={() => onClick && onClick(path)}>
      <Box style={{...globalStyles.flexBoxRow, alignItems: 'center'}}>
        <Avatars
          users={users}
          styles={styles}
          smallMode={smallMode}
          ignored={ignored}
          isPublic={isPublic}
          isTeam={isTeam}
        />
        <Box style={stylesBodyContainer}>
          <Usernames
            users={users}
            type={smallMode ? 'BodySmallSemibold' : 'BodySemibold'}
            style={{
              color: isPublic ? globalColors.yellowGreen2 : globalColors.darkBlue,
              opacity: ignored ? 0.6 : 1,
            }}
            redColor={redColor}
          />
          {meta && !ignored && <RowMeta ignored={ignored} meta={meta} styles={styles} />}
          {!(meta || ignored) &&
            modified &&
            <Modified modified={modified} styles={styles} smallMode={smallMode} />}
        </Box>
        {!smallMode &&
          !isPublic &&
          !hasReadOnlyUsers &&
          meta !== 'rekey' &&
          <Box style={{...stylesActionContainer, width: smallMode ? undefined : 64}}>
            <Text
              type="BodySmallSecondaryLink"
              className="folder-row-hover-action"
              onClick={onChatClick}
              style={styles.action}
            >
              Chat
            </Text>
          </Box>}
        <Box
          style={{
            ...stylesActionContainer,
            width: smallMode ? undefined : 64,
            marginRight: globalMargins.small,
          }}
        >
          {!smallMode &&
            meta !== 'rekey' &&
            <Text
              type="BodySmallSecondaryLink"
              className="folder-row-hover-action"
              onClick={onOpenClick}
              style={styles.action}
            >
              Open
            </Text>}
          {meta === 'rekey' &&
            <Button
              small={true}
              type="PrimaryPrivate"
              onClick={e => {
                if (onRekey) {
                  e.stopPropagation()
                  onRekey(path)
                }
              }}
              label="Rekey"
              style={styles.action}
            />}
        </Box>
      </Box>
    </Box>
  )
}

const rowContainer = {
  ...globalStyles.flexBoxColumn,
  ...globalStyles.clickable,
  position: 'relative',
}

const stylesPrivate = {
  rowContainer: {
    ...rowContainer,
    backgroundColor: globalColors.white,
    color: globalColors.white,
  },
  ignored: {
    color: globalColors.white_40,
    backgroundColor: globalColors.white,
  },
  groupIcon: {
    small: 'icon-folder-private-group-24',
    normal: 'icon-folder-private-group-32',
  },
  nameColor: globalColors.white,
  modifiedMode: 'Terminal',
  action: {
    ...globalStyles.clickable,
    alignSelf: 'center',
    marginRight: globalMargins.tiny,
  },
}

const stylesPublic = {
  rowContainer: {
    ...rowContainer,
    backgroundColor: globalColors.white,
    color: globalColors.yellowGreen2,
  },
  ignored: {
    color: globalColors.white_75,
    backgroundColor: globalColors.white,
  },
  groupIcon: {
    small: 'icon-folder-public-group-24',
    normal: 'icon-folder-public-group-32',
  },
  nameColor: globalColors.yellowGreen2,
  modifiedMode: 'Normal',
  action: {
    ...globalStyles.clickable,
    alignSelf: 'center',
    marginRight: globalMargins.tiny,
  },
}

const stylesBodyContainer = {
  ...globalStyles.flexBoxColumn,
  flex: 1,
  justifyContent: 'center',
  padding: 8,
  marginRight: 16,
}

const stylesActionContainer = {
  ...globalStyles.flexBoxRow,
  alignItems: 'flex-start',
  justifyContent: 'center',
}

const stylesModified = {
  ...globalStyles.flexBoxRow,
  alignItems: 'center',
}

export default Row
<|endoftext|>


<commit_before>//
// Progress Modal
//
define(['underscore', 'js/quickvm_sup', 'filesize',
       	'js/lib/text!template/imaging-modal.html'],
    function(_, sup, filesize, imagingString)
    {
	'use strict';

	var imagingTemplate = null;
	var imaging_modal_display = true;
	var imaging_modal_active  = false;
	var status_callback;
	var completion_callback;

	function ShowImagingModal()
	{
	    var laststatus = "preparing";

	    //
	    // Ask the server for information to populate the imaging modal.
	    //
	    var callback = function(json) {
		var value = json.value;
		console.log("ShowImagingModal");
		console.log(json);

		if (json.code) {
		    if (imaging_modal_active) {
			sup.HideModal("#imaging-modal");
			imaging_modal_active = false;
			$('#imaging_modal').off('hidden.bs.modal');
		    }
		    sup.SpitOops("oops", "Server says: " + json.value);
		    completion_callback(1);
		    return;
		}

		if (! imaging_modal_active && imaging_modal_display) {
		    sup.ShowModal("#imaging-modal");
		    imaging_modal_active  = true;
		    imaging_modal_display = false;

		    // Handler so we know the user closed the modal.
		    $('#imaging_modal').on('hidden.bs.modal', function (e) {
			imaging_modal_active = false;
			$('#imaging_modal').off('hidden.bs.modal');
		    })
		}

		//
		// Fill in the details.
		//
		if (! _.has(value, "node_status")) {
		    value["node_status"] = "unknown";
		}
		if (_.has(value, "image_size")) {
		    // We get KB to avoid overflow along the way.
		    value["image_size"] = filesize(value["image_size"]*1024);
		}
		else {
		    value["image_size"] = "unknown";
		}
		$('#imaging_modal_node_status').html(value["node_status"]);
		$('#imaging_modal_image_size').html(value["image_size"]);

		if (_.has(value, "image_status")) {
		    var status = value["image_status"];

		    if (status == "imaging") {
			$('#tracker-imaging').removeClass('progtrckr-todo');
			$('#tracker-imaging').addClass('progtrckr-done');
		    }
		    else if (status == "finishing") {
			$('#tracker-imaging').removeClass('progtrckr-todo');
			$('#tracker-imaging').addClass('progtrckr-done');
			$('#tracker-finishing').removeClass('progtrckr-todo');
			$('#tracker-finishing').addClass('progtrckr-done');
		    }
		    else if (status == "ready") {
			$('#tracker-imaging').removeClass('progtrckr-todo');
			$('#tracker-imaging').addClass('progtrckr-done');
			$('#tracker-finishing').removeClass('progtrckr-todo');
			$('#tracker-finishing').addClass('progtrckr-done');
			$('#tracker-ready').removeClass('progtrckr-todo');
			$('#tracker-ready').addClass('progtrckr-done');
			$('#imaging-spinner').addClass("hidden");
			$('#imaging-close').removeClass("hidden");
			completion_callback(0);
			return;
		    }
		    else if (status == "failed") {
			if (laststatus == "preparing") {
			    $('#tracker-imaging').removeClass('progtrckr-todo');
			    $('#tracker-imaging').addClass('progtrckr-failed');
			}
			if (laststatus == "imaging" || laststatus == "preparing") {
			    $('#tracker-finishing').removeClass('progtrckr-todo');
			    $('#tracker-finishing').addClass('progtrckr-failed');
			}
			$('#tracker-ready').removeClass('progtrckr-todo');
			$('#tracker-ready').addClass('progtrckr-failed');

			$('#tracker-ready').html("Failed");
			$('#imaging-spinner').addClass("invisible");
			$('#imaging-close').removeClass("hidden");
			completion_callback(1);
			return;
		    }
		    laststatus = status;
		}
		//
		// Done, we need to do something here if we exited before
		// ready or failed above.
		//
		if (_.has(value, "exited")) {
		    $('#imaging-spinner').addClass("hidden");
		    $('#imaging-close').removeClass("hidden");
		    completion_callback(0);
		    return;
		}

		// And check again in a little bit.
		setTimeout(function f() { ShowImagingModal() }, 5000);
	    }

	    var $xmlthing = status_callback();
	    $xmlthing.done(callback);
	}

	return function(s_callback, c_callback)
	{
	    status_callback = s_callback;
	    completion_callback = c_callback;

	    if (imagingTemplate == null) {
		imagingTemplate  = _.template(imagingString);
    		var imaging_html     = imagingTemplate({});
		$('#imaging_div').html(imaging_html);
	    }
	    imaging_modal_display = true;
	    ShowImagingModal();
	}
    }
);<commit_msg>Oops, missing newline at end of file, how did that happen!<commit_after>//
// Progress Modal
//
define(['underscore', 'js/quickvm_sup', 'filesize',
       	'js/lib/text!template/imaging-modal.html'],
    function(_, sup, filesize, imagingString)
    {
	'use strict';

	var imagingTemplate = null;
	var imaging_modal_display = true;
	var imaging_modal_active  = false;
	var status_callback;
	var completion_callback;

	function ShowImagingModal()
	{
	    var laststatus = "preparing";

	    //
	    // Ask the server for information to populate the imaging modal.
	    //
	    var callback = function(json) {
		var value = json.value;
		console.log("ShowImagingModal");
		console.log(json);

		if (json.code) {
		    if (imaging_modal_active) {
			sup.HideModal("#imaging-modal");
			imaging_modal_active = false;
			$('#imaging_modal').off('hidden.bs.modal');
		    }
		    sup.SpitOops("oops", "Server says: " + json.value);
		    completion_callback(1);
		    return;
		}

		if (! imaging_modal_active && imaging_modal_display) {
		    sup.ShowModal("#imaging-modal");
		    imaging_modal_active  = true;
		    imaging_modal_display = false;

		    // Handler so we know the user closed the modal.
		    $('#imaging_modal').on('hidden.bs.modal', function (e) {
			imaging_modal_active = false;
			$('#imaging_modal').off('hidden.bs.modal');
		    })
		}

		//
		// Fill in the details.
		//
		if (! _.has(value, "node_status")) {
		    value["node_status"] = "unknown";
		}
		if (_.has(value, "image_size")) {
		    // We get KB to avoid overflow along the way.
		    value["image_size"] = filesize(value["image_size"]*1024);
		}
		else {
		    value["image_size"] = "unknown";
		}
		$('#imaging_modal_node_status').html(value["node_status"]);
		$('#imaging_modal_image_size').html(value["image_size"]);

		if (_.has(value, "image_status")) {
		    var status = value["image_status"];

		    if (status == "imaging") {
			$('#tracker-imaging').removeClass('progtrckr-todo');
			$('#tracker-imaging').addClass('progtrckr-done');
		    }
		    else if (status == "finishing") {
			$('#tracker-imaging').removeClass('progtrckr-todo');
			$('#tracker-imaging').addClass('progtrckr-done');
			$('#tracker-finishing').removeClass('progtrckr-todo');
			$('#tracker-finishing').addClass('progtrckr-done');
		    }
		    else if (status == "ready") {
			$('#tracker-imaging').removeClass('progtrckr-todo');
			$('#tracker-imaging').addClass('progtrckr-done');
			$('#tracker-finishing').removeClass('progtrckr-todo');
			$('#tracker-finishing').addClass('progtrckr-done');
			$('#tracker-ready').removeClass('progtrckr-todo');
			$('#tracker-ready').addClass('progtrckr-done');
			$('#imaging-spinner').addClass("hidden");
			$('#imaging-close').removeClass("hidden");
			completion_callback(0);
			return;
		    }
		    else if (status == "failed") {
			if (laststatus == "preparing") {
			    $('#tracker-imaging').removeClass('progtrckr-todo');
			    $('#tracker-imaging').addClass('progtrckr-failed');
			}
			if (laststatus == "imaging" || laststatus == "preparing") {
			    $('#tracker-finishing').removeClass('progtrckr-todo');
			    $('#tracker-finishing').addClass('progtrckr-failed');
			}
			$('#tracker-ready').removeClass('progtrckr-todo');
			$('#tracker-ready').addClass('progtrckr-failed');

			$('#tracker-ready').html("Failed");
			$('#imaging-spinner').addClass("invisible");
			$('#imaging-close').removeClass("hidden");
			completion_callback(1);
			return;
		    }
		    laststatus = status;
		}
		//
		// Done, we need to do something here if we exited before
		// ready or failed above.
		//
		if (_.has(value, "exited")) {
		    $('#imaging-spinner').addClass("hidden");
		    $('#imaging-close').removeClass("hidden");
		    completion_callback(0);
		    return;
		}

		// And check again in a little bit.
		setTimeout(function f() { ShowImagingModal() }, 5000);
	    }

	    var $xmlthing = status_callback();
	    $xmlthing.done(callback);
	}

	return function(s_callback, c_callback)
	{
	    status_callback = s_callback;
	    completion_callback = c_callback;

	    if (imagingTemplate == null) {
		imagingTemplate  = _.template(imagingString);
    		var imaging_html     = imagingTemplate({});
		$('#imaging_div').html(imaging_html);
	    }
	    imaging_modal_display = true;
	    ShowImagingModal();
	}
    }
);
<|endoftext|>


<commit_before>// @flow

import type {
  NavigationScreenProp,
  NavigationLeafRoute,
  NavigationScene,
  NavigationTransitionProps,
} from 'react-navigation';
import {
  type Media,
  mediaPropType,
  type Dimensions,
  dimensionsPropType,
} from 'lib/types/media-types';
import type { AppState } from '../redux-setup';
import { type VerticalBounds, verticalBoundsPropType } from './vertical-bounds';

import * as React from 'react';
import {
  View,
  StyleSheet,
  TouchableWithoutFeedback,
  Animated,
  Easing,
  TouchableOpacity,
} from 'react-native';
import PropTypes from 'prop-types';
import Icon from 'react-native-vector-icons/Feather';
import {
  PinchGestureHandler,
  PanGestureHandler,
  State as GestureState,
} from 'react-native-gesture-handler';
import Orientation from 'react-native-orientation-locker';

import { connect } from 'lib/utils/redux-utils';

import {
  contentBottomOffset,
  dimensionsSelector,
  contentVerticalOffsetSelector,
} from '../selectors/dimension-selectors';
import Multimedia from './multimedia.react';
import ConnectedStatusBar from '../connected-status-bar.react';

type LayoutCoordinates = $ReadOnly<{|
  x: number,
  y: number,
  width: number,
  height: number,
|}>;
type NavProp = NavigationScreenProp<{|
  ...NavigationLeafRoute,
  params: {|
    media: Media,
    initialCoordinates: LayoutCoordinates,
    verticalBounds: VerticalBounds,
  |},
|}>;

type Props = {|
  navigation: NavProp,
  scene: NavigationScene,
  transitionProps: NavigationTransitionProps,
  // Redux state
  screenDimensions: Dimensions,
  contentVerticalOffset: number,
|};
class MultimediaModal extends React.PureComponent<Props> {

  static propTypes = {
    navigation: PropTypes.shape({
      state: PropTypes.shape({
        params: PropTypes.shape({
          media: mediaPropType.isRequired,
          initialCoordinates: PropTypes.shape({
            x: PropTypes.number.isRequired,
            y: PropTypes.number.isRequired,
            width: PropTypes.number.isRequired,
            height: PropTypes.number.isRequired,
          }).isRequired,
          verticalBounds: verticalBoundsPropType.isRequired,
        }).isRequired,
      }).isRequired,
      goBack: PropTypes.func.isRequired,
    }).isRequired,
    transitionProps: PropTypes.object.isRequired,
    scene: PropTypes.object.isRequired,
    screenDimensions: dimensionsPropType.isRequired,
    contentVerticalOffset: PropTypes.number.isRequired,
  };

  centerXNum: number;
  centerYNum: number;
  centerX = new Animated.Value(0);
  centerY = new Animated.Value(0);

  pinchScale = new Animated.Value(1);
  pinchFocalX = new Animated.Value(0);
  pinchFocalY = new Animated.Value(0);
  pinchEvent = Animated.event(
    [{
      nativeEvent: {
        scale: this.pinchScale,
        focalX: this.pinchFocalX,
        focalY: this.pinchFocalY,
      },
    }],
    { useNativeDriver: true },
  );

  panX = new Animated.Value(0);
  panY = new Animated.Value(0);
  panEvent = Animated.event(
    [{
      nativeEvent: {
        translationX: this.panX,
        translationY: this.panY,
      },
    }],
    { useNativeDriver: true },
  );

  curScaleNum = 1;
  curXNum = 0;
  curYNum = 0;
  curScale = new Animated.Value(1);
  curX = new Animated.Value(0);
  curY = new Animated.Value(0);

  progress: Animated.Value;
  scale: Animated.Value;
  pinchX: Animated.Value;
  pinchY: Animated.Value;
  x: Animated.Value;
  y: Animated.Value;
  imageContainerOpacity: Animated.Value;

  constructor(props: Props) {
    super(props);
    this.updateCenter();

    const { height, width } = this.imageDimensions;
    const { height: screenHeight, width: screenWidth } = this.screenDimensions;
    const top = (screenHeight - height) / 2 + props.contentVerticalOffset;
    const left = (screenWidth - width) / 2;

    const { initialCoordinates } = props.navigation.state.params;
    const initialScale = new Animated.Value(initialCoordinates.width / width);
    const initialTranslateX = new Animated.Value(
      (initialCoordinates.x + initialCoordinates.width / 2)
        - (left + width / 2),
    );
    const initialTranslateY = new Animated.Value(
      (initialCoordinates.y + initialCoordinates.height / 2)
        - (top + height / 2),
    );

    const { position } = props.transitionProps;
    const { index } = props.scene;
    this.progress = position.interpolate({
      inputRange: [ index - 1, index ],
      outputRange: ([ 0, 1 ]: number[]),
      extrapolate: 'clamp',
    });
    this.imageContainerOpacity = this.progress.interpolate({
      inputRange: [ 0, 0.1 ],
      outputRange: ([ 0, 1 ]: number[]),
      extrapolate: 'clamp',
    });

    const reverseProgress = Animated.subtract(1, this.progress);
    this.scale = Animated.add(
      Animated.multiply(reverseProgress, initialScale),
      Animated.multiply(
        this.progress,
        Animated.multiply(this.curScale, this.pinchScale),
      ),
    );

    this.pinchX = Animated.multiply(
      Animated.subtract(1, this.pinchScale),
      Animated.subtract(
        Animated.subtract(
          this.pinchFocalX,
          this.curX,
        ),
        this.centerX,
      ),
    );
    this.x = Animated.add(
      Animated.multiply(reverseProgress, initialTranslateX),
      Animated.multiply(
        this.progress,
        Animated.add(
          this.curX,
          Animated.add(this.pinchX, this.panX),
        ),
      ),
    );

    this.pinchY = Animated.multiply(
      Animated.subtract(1, this.pinchScale),
      Animated.subtract(
        Animated.subtract(
          this.pinchFocalY,
          this.curY,
        ),
        this.centerY,
      ),
    );
    this.y = Animated.add(
      Animated.multiply(reverseProgress, initialTranslateY),
      Animated.multiply(
        this.progress,
        Animated.add(
          this.curY,
          Animated.add(this.pinchY, this.panY),
        ),
      ),
    );
  }

  updateCenter() {
    const { height: screenHeight, width: screenWidth } = this.screenDimensions;
    this.centerXNum = screenWidth / 2;
    this.centerYNum = screenHeight / 2 + this.props.contentVerticalOffset;
    this.centerX.setValue(this.centerXNum);
    this.centerY.setValue(this.centerYNum);
  }

  componentDidMount() {
    if (MultimediaModal.isActive(this.props)) {
      Orientation.unlockAllOrientations();
    }
  }

  componentDidUpdate(prevProps: Props) {
    if (
      this.props.screenDimensions !== prevProps.screenDimensions ||
      this.props.contentVerticalOffset !== prevProps.contentVerticalOffset
    ) {
      this.updateCenter();
    }

    const isActive = MultimediaModal.isActive(this.props);
    const wasActive = MultimediaModal.isActive(prevProps);
    if (isActive && !wasActive) {
      Orientation.unlockAllOrientations();
    } else if (!isActive && wasActive) {
      Orientation.lockToPortrait();
    }
  }

  get screenDimensions(): Dimensions {
    const { screenDimensions, contentVerticalOffset } = this.props;
    if (contentVerticalOffset === 0) {
      return screenDimensions;
    }
    const { height, width } = screenDimensions;
    return { height: height - contentVerticalOffset, width };
  }

  get imageDimensions(): Dimensions {
    // Make space for the close button
    let { height: maxHeight, width: maxWidth } = this.screenDimensions;
    if (maxHeight > maxWidth) {
      maxHeight -= 100;
    } else {
      maxWidth -= 100;
    }

    const { dimensions } = this.props.navigation.state.params.media;
    if (dimensions.height < maxHeight && dimensions.width < maxWidth) {
      return dimensions;
    }

    const heightRatio = maxHeight / dimensions.height;
    const widthRatio = maxWidth / dimensions.width;
    if (heightRatio < widthRatio) {
      return {
        height: maxHeight,
        width: dimensions.width * heightRatio,
      };
    } else {
      return {
        width: maxWidth,
        height: dimensions.height * widthRatio,
      };
    }
  }

  get imageContainerStyle() {
    const { height, width } = this.imageDimensions;
    const { height: screenHeight, width: screenWidth } = this.screenDimensions;
    const top = (screenHeight - height) / 2 + this.props.contentVerticalOffset;
    const left = (screenWidth - width) / 2;
    const { verticalBounds } = this.props.navigation.state.params;
    return {
      height,
      width,
      marginTop: top - verticalBounds.y,
      marginLeft: left,
      opacity: this.imageContainerOpacity,
      transform: [
        { translateX: this.x },
        { translateY: this.y },
        { scale: this.scale },
      ],
    };
  }

  static isActive(props) {
    const { index } = props.scene;
    return index === props.transitionProps.index;
  }

  get contentContainerStyle() {
    const { verticalBounds } = this.props.navigation.state.params;
    const fullScreenHeight = this.screenDimensions.height
      + contentBottomOffset
      + this.props.contentVerticalOffset;
    const top = verticalBounds.y;
    const bottom = fullScreenHeight - verticalBounds.y - verticalBounds.height;

    // margin will clip, but padding won't
    const verticalStyle = MultimediaModal.isActive(this.props)
      ? { paddingTop: top, paddingBottom: bottom }
      : { marginTop: top, marginBottom: bottom };
    return [ styles.contentContainer, verticalStyle ];
  }

  render() {
    const { media } = this.props.navigation.state.params;
    const statusBar = MultimediaModal.isActive(this.props)
      ? <ConnectedStatusBar barStyle="light-content" />
      : null;
    const backdropStyle = { opacity: this.progress };
    const closeButtonStyle = {
      opacity: this.progress,
      top: Math.max(this.props.contentVerticalOffset, 6),
    };
    const view = (
      <Animated.View style={styles.container}>
        {statusBar}
        <Animated.View style={[ styles.backdrop, backdropStyle ]} />
        <View style={this.contentContainerStyle}>
          <TouchableWithoutFeedback onPress={this.close}>
            <View style={styles.cover} />
          </TouchableWithoutFeedback>
          <Animated.View style={this.imageContainerStyle}>
            <Multimedia media={media} spinnerColor="white" />
          </Animated.View>
        </View>
        <Animated.View style={[
          styles.closeButtonContainer,
          closeButtonStyle,
        ]}>
          <TouchableOpacity onPress={this.close}>
            <Icon name="x-circle" style={styles.closeButton} />
          </TouchableOpacity>
        </Animated.View>
      </Animated.View>
    );
    return (
      <PinchGestureHandler
        onGestureEvent={this.pinchEvent}
        onHandlerStateChange={this.onPinchHandlerStateChange}
      >
        <Animated.View style={styles.container}>
          <PanGestureHandler
            onGestureEvent={this.panEvent}
            onHandlerStateChange={this.onPanHandlerStateChange}
          >
            {view}
          </PanGestureHandler>
        </Animated.View>
      </PinchGestureHandler>
    );
  }

  close = () => {
    this.props.navigation.goBack();
  }

  onPinchHandlerStateChange = (
    event: { nativeEvent: {
      state: number,
      oldState: number,
      scale: number,
      focalX: number,
      focalY: number,
    } },
  ) => {
    const { state, oldState, scale, focalX, focalY } = event.nativeEvent;
    if (state === GestureState.ACTIVE || oldState !== GestureState.ACTIVE) {
      return;
    }

    this.curScaleNum *= scale;
    this.curScale.setValue(this.curScaleNum);

    // Keep this logic in sync with pinchX/pinchY definitions in constructor
    this.curXNum += (1 - scale) * (focalX - this.curXNum - this.centerXNum);
    this.curYNum += (1 - scale) * (focalY - this.curYNum - this.centerYNum);
    this.curX.setValue(this.curXNum);
    this.curY.setValue(this.curYNum);

    this.pinchScale.setValue(1);
    this.pinchFocalX.setValue(0);
    this.pinchFocalY.setValue(0);

    this.recenter();
  }

  onPanHandlerStateChange = (
    event: { nativeEvent: {
      state: number,
      oldState: number,
      translationX: number,
      translationY: number,
    } },
  ) => {
    const { state, oldState, translationX, translationY } = event.nativeEvent;
    if (state === GestureState.ACTIVE || oldState !== GestureState.ACTIVE) {
      return;
    }

    this.curXNum += translationX;
    this.curYNum += translationY;
    this.curX.setValue(this.curXNum);
    this.curY.setValue(this.curYNum);

    this.panX.setValue(0);
    this.panY.setValue(0);

    this.recenter();
  }

  // Figures out what we need to add to this.curX to make it "centered"
  get deltaX() {
    if (this.curXNum === 0) {
      return 0;
    }

    const nextScale = Math.max(this.curScaleNum, 1);
    const { width } = this.imageDimensions;
    const apparentWidth = nextScale * width;
    const screenWidth = this.screenDimensions.width;
    if (apparentWidth < screenWidth) {
      return this.curXNum * -1;
    }

    const horizPop = (apparentWidth - screenWidth) / 2;
    const deltaLeft = this.curXNum - horizPop;
    if (deltaLeft > 0) {
      return deltaLeft * -1;
    }

    const deltaRight = this.curXNum + horizPop;
    if (deltaRight < 0) {
      return deltaRight * -1;
    }

    return 0;
  }

  // Figures out what we need to add to this.curY to make it "centered"
  get deltaY() {
    if (this.curYNum === 0) {
      return 0;
    }

    const nextScale = Math.max(this.curScaleNum, 1);
    const { height } = this.imageDimensions;
    const apparentHeight = nextScale * height;
    const screenHeight = this.screenDimensions.height
      + this.props.contentVerticalOffset + contentBottomOffset;
    if (apparentHeight < screenHeight) {
      return this.curYNum * -1;
    }

    const vertPop = (apparentHeight - screenHeight) / 2;
    const deltaTop = this.curYNum - vertPop;
    if (deltaTop > 0) {
      return deltaTop * -1;
    }

    const deltaBottom = this.curYNum + vertPop;
    if (deltaBottom < 0) {
      return deltaBottom * -1;
    }

    return 0;
  }

  recenter() {
    const nextScale = Math.max(this.curScaleNum, 1);
    const { deltaX, deltaY } = this;

    const animations = [];
    const config = {
      duration: 250,
      useNativeDriver: true,
      easing: Easing.out(Easing.ease),
    };
    if (nextScale !== this.curScaleNum) {
      this.curScaleNum = nextScale;
      animations.push(
        Animated.timing(
          this.curScale,
          { ...config, toValue: this.curScaleNum },
        ),
      );
    }
    if (deltaX !== 0) {
      this.curXNum += deltaX;
      animations.push(
        Animated.timing(
          this.curX,
          { ...config, toValue: this.curXNum },
        ),
      );
    }
    if (deltaY !== 0) {
      this.curYNum += deltaY;
      animations.push(
        Animated.timing(
          this.curY,
          { ...config, toValue: this.curYNum },
        ),
      );
    }

    if (animations.length > 0) {
      Animated.parallel(animations).start();
    }
  }

}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  backdrop: {
    position: "absolute",
    top: 0,
    bottom: 0,
    left: 0,
    right: 0,
    backgroundColor: "black",
  },
  contentContainer: {
    flex: 1,
    overflow: "hidden",
  },
  cover: {
    position: "absolute",
    top: 0,
    bottom: 0,
    left: 0,
    right: 0,
  },
  closeButtonContainer: {
    position: "absolute",
    right: 12,
  },
  closeButton: {
    fontSize: 36,
    color: "white",
  },
});

export default connect(
  (state: AppState) => ({
    screenDimensions: dimensionsSelector(state),
    contentVerticalOffset: contentVerticalOffsetSelector(state),
  }),
)(MultimediaModal);
<commit_msg>[native] Use simultaneousHandlers in MultimediaModal<commit_after>// @flow

import type {
  NavigationScreenProp,
  NavigationLeafRoute,
  NavigationScene,
  NavigationTransitionProps,
} from 'react-navigation';
import {
  type Media,
  mediaPropType,
  type Dimensions,
  dimensionsPropType,
} from 'lib/types/media-types';
import type { AppState } from '../redux-setup';
import { type VerticalBounds, verticalBoundsPropType } from './vertical-bounds';

import * as React from 'react';
import {
  View,
  StyleSheet,
  TouchableWithoutFeedback,
  Animated,
  Easing,
  TouchableOpacity,
} from 'react-native';
import PropTypes from 'prop-types';
import Icon from 'react-native-vector-icons/Feather';
import {
  PinchGestureHandler,
  PanGestureHandler,
  State as GestureState,
} from 'react-native-gesture-handler';
import Orientation from 'react-native-orientation-locker';

import { connect } from 'lib/utils/redux-utils';

import {
  contentBottomOffset,
  dimensionsSelector,
  contentVerticalOffsetSelector,
} from '../selectors/dimension-selectors';
import Multimedia from './multimedia.react';
import ConnectedStatusBar from '../connected-status-bar.react';

type LayoutCoordinates = $ReadOnly<{|
  x: number,
  y: number,
  width: number,
  height: number,
|}>;
type NavProp = NavigationScreenProp<{|
  ...NavigationLeafRoute,
  params: {|
    media: Media,
    initialCoordinates: LayoutCoordinates,
    verticalBounds: VerticalBounds,
  |},
|}>;

type Props = {|
  navigation: NavProp,
  scene: NavigationScene,
  transitionProps: NavigationTransitionProps,
  // Redux state
  screenDimensions: Dimensions,
  contentVerticalOffset: number,
|};
class MultimediaModal extends React.PureComponent<Props> {

  static propTypes = {
    navigation: PropTypes.shape({
      state: PropTypes.shape({
        params: PropTypes.shape({
          media: mediaPropType.isRequired,
          initialCoordinates: PropTypes.shape({
            x: PropTypes.number.isRequired,
            y: PropTypes.number.isRequired,
            width: PropTypes.number.isRequired,
            height: PropTypes.number.isRequired,
          }).isRequired,
          verticalBounds: verticalBoundsPropType.isRequired,
        }).isRequired,
      }).isRequired,
      goBack: PropTypes.func.isRequired,
    }).isRequired,
    transitionProps: PropTypes.object.isRequired,
    scene: PropTypes.object.isRequired,
    screenDimensions: dimensionsPropType.isRequired,
    contentVerticalOffset: PropTypes.number.isRequired,
  };

  centerXNum: number;
  centerYNum: number;
  centerX = new Animated.Value(0);
  centerY = new Animated.Value(0);

  pinchHandler: React.Ref<PinchGestureHandler> = React.createRef();
  pinchScale = new Animated.Value(1);
  pinchFocalX = new Animated.Value(0);
  pinchFocalY = new Animated.Value(0);
  pinchEvent = Animated.event(
    [{
      nativeEvent: {
        scale: this.pinchScale,
        focalX: this.pinchFocalX,
        focalY: this.pinchFocalY,
      },
    }],
    { useNativeDriver: true },
  );

  panX = new Animated.Value(0);
  panY = new Animated.Value(0);
  panEvent = Animated.event(
    [{
      nativeEvent: {
        translationX: this.panX,
        translationY: this.panY,
      },
    }],
    { useNativeDriver: true },
  );

  curScaleNum = 1;
  curXNum = 0;
  curYNum = 0;
  curScale = new Animated.Value(1);
  curX = new Animated.Value(0);
  curY = new Animated.Value(0);

  progress: Animated.Value;
  scale: Animated.Value;
  pinchX: Animated.Value;
  pinchY: Animated.Value;
  x: Animated.Value;
  y: Animated.Value;
  imageContainerOpacity: Animated.Value;

  constructor(props: Props) {
    super(props);
    this.updateCenter();

    const { height, width } = this.imageDimensions;
    const { height: screenHeight, width: screenWidth } = this.screenDimensions;
    const top = (screenHeight - height) / 2 + props.contentVerticalOffset;
    const left = (screenWidth - width) / 2;

    const { initialCoordinates } = props.navigation.state.params;
    const initialScale = new Animated.Value(initialCoordinates.width / width);
    const initialTranslateX = new Animated.Value(
      (initialCoordinates.x + initialCoordinates.width / 2)
        - (left + width / 2),
    );
    const initialTranslateY = new Animated.Value(
      (initialCoordinates.y + initialCoordinates.height / 2)
        - (top + height / 2),
    );

    const { position } = props.transitionProps;
    const { index } = props.scene;
    this.progress = position.interpolate({
      inputRange: [ index - 1, index ],
      outputRange: ([ 0, 1 ]: number[]),
      extrapolate: 'clamp',
    });
    this.imageContainerOpacity = this.progress.interpolate({
      inputRange: [ 0, 0.1 ],
      outputRange: ([ 0, 1 ]: number[]),
      extrapolate: 'clamp',
    });

    const reverseProgress = Animated.subtract(1, this.progress);
    this.scale = Animated.add(
      Animated.multiply(reverseProgress, initialScale),
      Animated.multiply(
        this.progress,
        Animated.multiply(this.curScale, this.pinchScale),
      ),
    );

    this.pinchX = Animated.multiply(
      Animated.subtract(1, this.pinchScale),
      Animated.subtract(
        Animated.subtract(
          this.pinchFocalX,
          this.curX,
        ),
        this.centerX,
      ),
    );
    this.x = Animated.add(
      Animated.multiply(reverseProgress, initialTranslateX),
      Animated.multiply(
        this.progress,
        Animated.add(
          this.curX,
          Animated.add(this.pinchX, this.panX),
        ),
      ),
    );

    this.pinchY = Animated.multiply(
      Animated.subtract(1, this.pinchScale),
      Animated.subtract(
        Animated.subtract(
          this.pinchFocalY,
          this.curY,
        ),
        this.centerY,
      ),
    );
    this.y = Animated.add(
      Animated.multiply(reverseProgress, initialTranslateY),
      Animated.multiply(
        this.progress,
        Animated.add(
          this.curY,
          Animated.add(this.pinchY, this.panY),
        ),
      ),
    );
  }

  updateCenter() {
    const { height: screenHeight, width: screenWidth } = this.screenDimensions;
    this.centerXNum = screenWidth / 2;
    this.centerYNum = screenHeight / 2 + this.props.contentVerticalOffset;
    this.centerX.setValue(this.centerXNum);
    this.centerY.setValue(this.centerYNum);
  }

  componentDidMount() {
    if (MultimediaModal.isActive(this.props)) {
      Orientation.unlockAllOrientations();
    }
  }

  componentDidUpdate(prevProps: Props) {
    if (
      this.props.screenDimensions !== prevProps.screenDimensions ||
      this.props.contentVerticalOffset !== prevProps.contentVerticalOffset
    ) {
      this.updateCenter();
    }

    const isActive = MultimediaModal.isActive(this.props);
    const wasActive = MultimediaModal.isActive(prevProps);
    if (isActive && !wasActive) {
      Orientation.unlockAllOrientations();
    } else if (!isActive && wasActive) {
      Orientation.lockToPortrait();
    }
  }

  get screenDimensions(): Dimensions {
    const { screenDimensions, contentVerticalOffset } = this.props;
    if (contentVerticalOffset === 0) {
      return screenDimensions;
    }
    const { height, width } = screenDimensions;
    return { height: height - contentVerticalOffset, width };
  }

  get imageDimensions(): Dimensions {
    // Make space for the close button
    let { height: maxHeight, width: maxWidth } = this.screenDimensions;
    if (maxHeight > maxWidth) {
      maxHeight -= 100;
    } else {
      maxWidth -= 100;
    }

    const { dimensions } = this.props.navigation.state.params.media;
    if (dimensions.height < maxHeight && dimensions.width < maxWidth) {
      return dimensions;
    }

    const heightRatio = maxHeight / dimensions.height;
    const widthRatio = maxWidth / dimensions.width;
    if (heightRatio < widthRatio) {
      return {
        height: maxHeight,
        width: dimensions.width * heightRatio,
      };
    } else {
      return {
        width: maxWidth,
        height: dimensions.height * widthRatio,
      };
    }
  }

  get imageContainerStyle() {
    const { height, width } = this.imageDimensions;
    const { height: screenHeight, width: screenWidth } = this.screenDimensions;
    const top = (screenHeight - height) / 2 + this.props.contentVerticalOffset;
    const left = (screenWidth - width) / 2;
    const { verticalBounds } = this.props.navigation.state.params;
    return {
      height,
      width,
      marginTop: top - verticalBounds.y,
      marginLeft: left,
      opacity: this.imageContainerOpacity,
      transform: [
        { translateX: this.x },
        { translateY: this.y },
        { scale: this.scale },
      ],
    };
  }

  static isActive(props) {
    const { index } = props.scene;
    return index === props.transitionProps.index;
  }

  get contentContainerStyle() {
    const { verticalBounds } = this.props.navigation.state.params;
    const fullScreenHeight = this.screenDimensions.height
      + contentBottomOffset
      + this.props.contentVerticalOffset;
    const top = verticalBounds.y;
    const bottom = fullScreenHeight - verticalBounds.y - verticalBounds.height;

    // margin will clip, but padding won't
    const verticalStyle = MultimediaModal.isActive(this.props)
      ? { paddingTop: top, paddingBottom: bottom }
      : { marginTop: top, marginBottom: bottom };
    return [ styles.contentContainer, verticalStyle ];
  }

  render() {
    const { media } = this.props.navigation.state.params;
    const statusBar = MultimediaModal.isActive(this.props)
      ? <ConnectedStatusBar barStyle="light-content" />
      : null;
    const backdropStyle = { opacity: this.progress };
    const closeButtonStyle = {
      opacity: this.progress,
      top: Math.max(this.props.contentVerticalOffset, 6),
    };
    const view = (
      <Animated.View style={styles.container}>
        {statusBar}
        <Animated.View style={[ styles.backdrop, backdropStyle ]} />
        <View style={this.contentContainerStyle}>
          <TouchableWithoutFeedback onPress={this.close}>
            <View style={styles.cover} />
          </TouchableWithoutFeedback>
          <Animated.View style={this.imageContainerStyle}>
            <Multimedia media={media} spinnerColor="white" />
          </Animated.View>
        </View>
        <Animated.View style={[
          styles.closeButtonContainer,
          closeButtonStyle,
        ]}>
          <TouchableOpacity onPress={this.close}>
            <Icon name="x-circle" style={styles.closeButton} />
          </TouchableOpacity>
        </Animated.View>
      </Animated.View>
    );
    return (
      <PinchGestureHandler
        onGestureEvent={this.pinchEvent}
        onHandlerStateChange={this.onPinchHandlerStateChange}
        ref={this.pinchHandler}
      >
        <Animated.View style={styles.container}>
          <PanGestureHandler
            onGestureEvent={this.panEvent}
            onHandlerStateChange={this.onPanHandlerStateChange}
            simultaneousHandlers={this.pinchHandler}
            avgTouches
          >
            {view}
          </PanGestureHandler>
        </Animated.View>
      </PinchGestureHandler>
    );
  }

  close = () => {
    this.props.navigation.goBack();
  }

  onPinchHandlerStateChange = (
    event: { nativeEvent: {
      state: number,
      oldState: number,
      scale: number,
      focalX: number,
      focalY: number,
    } },
  ) => {
    const { state, oldState, scale, focalX, focalY } = event.nativeEvent;
    if (state === GestureState.ACTIVE || oldState !== GestureState.ACTIVE) {
      return;
    }

    this.pinchScale.setValue(1);
    this.pinchFocalX.setValue(0);
    this.pinchFocalY.setValue(0);

    this.curScaleNum *= scale;
    this.curScale.setValue(this.curScaleNum);

    // Keep this logic in sync with pinchX/pinchY definitions in constructor
    this.curXNum += (1 - scale) * (focalX - this.curXNum - this.centerXNum);
    this.curYNum += (1 - scale) * (focalY - this.curYNum - this.centerYNum);
    this.curX.setValue(this.curXNum);
    this.curY.setValue(this.curYNum);

    this.recenter();
  }

  onPanHandlerStateChange = (
    event: { nativeEvent: {
      state: number,
      oldState: number,
      translationX: number,
      translationY: number,
    } },
  ) => {
    const { state, oldState, translationX, translationY } = event.nativeEvent;
    if (state === GestureState.ACTIVE || oldState !== GestureState.ACTIVE) {
      return;
    }

    this.panX.setValue(0);
    this.panY.setValue(0);

    this.curXNum += translationX;
    this.curYNum += translationY;
    this.curX.setValue(this.curXNum);
    this.curY.setValue(this.curYNum);

    this.recenter();
  }

  // Figures out what we need to add to this.curX to make it "centered"
  get deltaX() {
    if (this.curXNum === 0) {
      return 0;
    }

    const nextScale = Math.max(this.curScaleNum, 1);
    const { width } = this.imageDimensions;
    const apparentWidth = nextScale * width;
    const screenWidth = this.screenDimensions.width;
    if (apparentWidth < screenWidth) {
      return this.curXNum * -1;
    }

    const horizPop = (apparentWidth - screenWidth) / 2;
    const deltaLeft = this.curXNum - horizPop;
    if (deltaLeft > 0) {
      return deltaLeft * -1;
    }

    const deltaRight = this.curXNum + horizPop;
    if (deltaRight < 0) {
      return deltaRight * -1;
    }

    return 0;
  }

  // Figures out what we need to add to this.curY to make it "centered"
  get deltaY() {
    if (this.curYNum === 0) {
      return 0;
    }

    const nextScale = Math.max(this.curScaleNum, 1);
    const { height } = this.imageDimensions;
    const apparentHeight = nextScale * height;
    const screenHeight = this.screenDimensions.height
      + this.props.contentVerticalOffset + contentBottomOffset;
    if (apparentHeight < screenHeight) {
      return this.curYNum * -1;
    }

    const vertPop = (apparentHeight - screenHeight) / 2;
    const deltaTop = this.curYNum - vertPop;
    if (deltaTop > 0) {
      return deltaTop * -1;
    }

    const deltaBottom = this.curYNum + vertPop;
    if (deltaBottom < 0) {
      return deltaBottom * -1;
    }

    return 0;
  }

  recenter() {
    const nextScale = Math.max(this.curScaleNum, 1);
    const { deltaX, deltaY } = this;

    const animations = [];
    const config = {
      duration: 250,
      useNativeDriver: true,
      easing: Easing.out(Easing.ease),
    };
    if (nextScale !== this.curScaleNum) {
      this.curScaleNum = nextScale;
      animations.push(
        Animated.timing(
          this.curScale,
          { ...config, toValue: this.curScaleNum },
        ),
      );
    }
    if (deltaX !== 0) {
      this.curXNum += deltaX;
      animations.push(
        Animated.timing(
          this.curX,
          { ...config, toValue: this.curXNum },
        ),
      );
    }
    if (deltaY !== 0) {
      this.curYNum += deltaY;
      animations.push(
        Animated.timing(
          this.curY,
          { ...config, toValue: this.curYNum },
        ),
      );
    }

    if (animations.length > 0) {
      Animated.parallel(animations).start();
    }
  }

}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  backdrop: {
    position: "absolute",
    top: 0,
    bottom: 0,
    left: 0,
    right: 0,
    backgroundColor: "black",
  },
  contentContainer: {
    flex: 1,
    overflow: "hidden",
  },
  cover: {
    position: "absolute",
    top: 0,
    bottom: 0,
    left: 0,
    right: 0,
  },
  closeButtonContainer: {
    position: "absolute",
    right: 12,
  },
  closeButton: {
    fontSize: 36,
    color: "white",
  },
});

export default connect(
  (state: AppState) => ({
    screenDimensions: dimensionsSelector(state),
    contentVerticalOffset: contentVerticalOffsetSelector(state),
  }),
)(MultimediaModal);
<|endoftext|>


<commit_before>define([
  'angular',
  'jquery',
],
function (angular, $) {
  "use strict";

  var module = angular.module('grafana.routes');

  module.controller('SoloPanelCtrl',
    function(
      $scope,
      backendSrv,
      $routeParams,
      dashboardSrv,
      timeSrv,
      $location,
      templateValuesSrv,
      contextSrv) {

    var panelId;

    $scope.init = function() {
      contextSrv.sidemenu = false;

      var params = $location.search();
      panelId = parseInt(params.panelId);

      var request;

      if ($routeParams.slug) {
        request = backendSrv.getDashboard($routeParams.slug);
      } else {
        request = backendSrv.get('/api/snapshots/' + $routeParams.key);
      }

      request.then(function(dashboard) {
        $scope.initPanelScope(dashboard);
      }).then(null, function(err) {
        $scope.appEvent('alert-error', ['Load panel error', err.message]);
      });
    };

    $scope.initPanelScope = function(dashboard) {
      $scope.dashboard = dashboardSrv.create(dashboard.model);

      $scope.row = {
        height: ($(window).height() - 10) + 'px',
      };

      $scope.test = "Hej";
      $scope.$index = 0;
      $scope.panel = $scope.getPanelById(panelId);

      if (!$scope.panel) {
        $scope.appEvent('alert-error', ['Panel not found', '']);
        return;
      }

      $scope.panel.span = 12;
      $scope.dashboardViewState = { registerPanel: function() { }, state: {}};

      timeSrv.init($scope.dashboard);
      templateValuesSrv.init($scope.dashboard, $scope.dashboardViewState);
    };

    $scope.getPanelById = function(id) {
      var rows = $scope.dashboard.rows;
      for (var i = 0; i < rows.length; i++) {
        var row = rows[i];
        for (var j = 0; j < row.panels.length; j++) {
          var panel = row.panels[j];
          if (panel.id === id) {
            return panel;
          }
        }
      }
      return null;
    };

    if (!$scope.skipAutoInit) {
      $scope.init();
    }

  });

});
<commit_msg>Fixed intendation issue<commit_after>define([
  'angular',
  'jquery',
],
function (angular, $) {
  "use strict";

  var module = angular.module('grafana.routes');

  module.controller('SoloPanelCtrl', function(
    $scope,
    backendSrv,
    $routeParams,
    dashboardSrv,
    timeSrv,
    $location,
    templateValuesSrv,
    contextSrv) {

    var panelId;

    $scope.init = function() {
      contextSrv.sidemenu = false;

      var params = $location.search();
      panelId = parseInt(params.panelId);

      var request;

      if ($routeParams.slug) {
        request = backendSrv.getDashboard($routeParams.slug);
      } else {
        request = backendSrv.get('/api/snapshots/' + $routeParams.key);
      }

      request.then(function(dashboard) {
        $scope.initPanelScope(dashboard);
      }).then(null, function(err) {
        $scope.appEvent('alert-error', ['Load panel error', err.message]);
      });
    };

    $scope.initPanelScope = function(dashboard) {
      $scope.dashboard = dashboardSrv.create(dashboard.model);

      $scope.row = {
        height: ($(window).height() - 10) + 'px',
      };

      $scope.test = "Hej";
      $scope.$index = 0;
      $scope.panel = $scope.getPanelById(panelId);

      if (!$scope.panel) {
        $scope.appEvent('alert-error', ['Panel not found', '']);
        return;
      }

      $scope.panel.span = 12;
      $scope.dashboardViewState = { registerPanel: function() { }, state: {}};

      timeSrv.init($scope.dashboard);
      templateValuesSrv.init($scope.dashboard, $scope.dashboardViewState);
    };

    $scope.getPanelById = function(id) {
      var rows = $scope.dashboard.rows;
      for (var i = 0; i < rows.length; i++) {
        var row = rows[i];
        for (var j = 0; j < row.panels.length; j++) {
          var panel = row.panels[j];
          if (panel.id === id) {
            return panel;
          }
        }
      }
      return null;
    };

    if (!$scope.skipAutoInit) {
      $scope.init();
    }

  });

});
<|endoftext|>


<commit_before>/*
 * peryòd
 * https://github.com/leny/peryod
 *
 * Copyright (c) 2013 Leny
 * Licensed under the MIT license.
 */

;( function() {
    "use strict";

    var oDateStrings = {
        "seconds": "a few seconds ago...",
        "minute": "1 minute ago...",
        "minutes": "{value} minutes ago...",
        "hour": "1 hour ago...",
        "hours": "{value} hours ago...",
        "yesterday": "yesterday, {value}",
        "past": "{date}, {time}",
        "months": [
            "january",
            "february",
            "march",
            "april",
            "may",
            "june",
            "july",
            "august",
            "september",
            "october",
            "november",
            "december"
        ]
    };

    Date.prototype.peryod = function( dNow, oGivenStrings ) {
        var iDiff, bFuture, _ref, _ref2, sHours, sMinutes, sDay, iMonth, sMonth;
        dNow = ( !!dNow && typeof dNow === "object" && Object.prototype.toString.call( dNow ) === "[object Date]" ) ? dNow : new Date();

        iDiff = ~~( ( dNow.getTime() - this.getTime() ) / 1000 );

        bFuture = iDiff < 0;

        // seconds
        if( !bFuture && iDiff < 60 ) {
            return ( _ref = oGivenStrings ) ? _ref.seconds : oDateStrings.seconds;
        }

        // 1 minutes
        if( !bFuture && iDiff < 120 ) {
            return ( _ref = oGivenStrings ) ? _ref.minute : oDateStrings.minute;
        }

        // 2 - 59 minutes
        if( !bFuture && iDiff < 3600 ) {
            return ( ( _ref = oGivenStrings ) ? _ref.minutes : oDateStrings.minutes ).replace( "{value}", Math.floor( iDiff / 60 ) );
        }

        // 1 hour
        if( !bFuture && iDiff < 7200 ) {
            return ( _ref = oGivenStrings ) ? _ref.hour : oDateStrings.hour;
        }

        // 2 - 6 hours
        if( !bFuture && iDiff < 21600 ) {
            return ( ( _ref = oGivenStrings ) ? _ref.hours : oDateStrings.hours ).replace( "{value}", Math.floor( iDiff / 3600 ) );
        }

        // 7 - 23 hours in the same day
        if( !bFuture && iDiff < 86400 && dNow.getDate() === this.getDate() ) {
            return ( ( _ref = oGivenStrings ) ? _ref.hours : oDateStrings.hours ).replace( "{value}", Math.floor( iDiff / 3600 ) );
        }

        sHours = ( _ref = this.getHours() ) < 10 ? "0" + _ref : _ref;
        sMinutes = ( _ref = this.getMinutes() ) < 10 ? "0" + _ref : _ref;

        // 7+ hours, yesterday
        if( !bFuture && iDiff < ( ( this.getHours() + 24 ) * 60 * 60 ) ) {
            return ( ( _ref = oGivenStrings ) ? _ref.yesterday : oDateStrings.yesterday ).replace( "{value}", sHours + ":" + sMinutes );
        }

        // far from yesterday
        sDay = ( _ref = this.getDate() ) < 10 ? "0" + _ref : _ref;
        iMonth = this.getMonth();
        sMonth = ( ( _ref = oGivenStrings ) ? ( ( _ref2 = _ref.months[ iMonth ] ) ? _ref2 : oDateStrings.months[ iMonth ] ) : oDateStrings.months[ iMonth ] );
        return ( ( _ref = oGivenStrings ) ? _ref.past : oDateStrings.past ).replace( "{date}", sDay + " " + sMonth + " " + this.getFullYear() ).replace( "{time}", sHours + ":" + sMinutes );
    };

} ).call( this );
<commit_msg>JSHint<commit_after>/* peryòd
 * https://github.com/leny/peryod
 *
 * Copyright (c) 2013 Leny
 * Licensed under the MIT license.
 */

;( function() {
    "use strict";

    var oDateStrings = {
        "seconds": "a few seconds ago...",
        "minute": "1 minute ago...",
        "minutes": "{value} minutes ago...",
        "hour": "1 hour ago...",
        "hours": "{value} hours ago...",
        "yesterday": "yesterday, {value}",
        "past": "{date}, {time}",
        "months": [
            "january",
            "february",
            "march",
            "april",
            "may",
            "june",
            "july",
            "august",
            "september",
            "october",
            "november",
            "december"
        ]
    };

    Date.prototype.peryod = function( dNow, oGivenStrings ) {
        var iDiff, bFuture, _ref, _ref2, sHours, sMinutes, sDay, iMonth, sMonth;
        dNow = ( !!dNow && typeof dNow === "object" && Object.prototype.toString.call( dNow ) === "[object Date]" ) ? dNow : new Date();

        iDiff = Math.round( ( dNow.getTime() - this.getTime() ) / 1000 );

        bFuture = iDiff < 0;

        // seconds
        if( !bFuture && iDiff < 60 ) {
            return ( _ref = oGivenStrings ) ? _ref.seconds : oDateStrings.seconds;
        }

        // 1 minutes
        if( !bFuture && iDiff < 120 ) {
            return ( _ref = oGivenStrings ) ? _ref.minute : oDateStrings.minute;
        }

        // 2 - 59 minutes
        if( !bFuture && iDiff < 3600 ) {
            return ( ( _ref = oGivenStrings ) ? _ref.minutes : oDateStrings.minutes ).replace( "{value}", Math.floor( iDiff / 60 ) );
        }

        // 1 hour
        if( !bFuture && iDiff < 7200 ) {
            return ( _ref = oGivenStrings ) ? _ref.hour : oDateStrings.hour;
        }

        // 2 - 6 hours
        if( !bFuture && iDiff < 21600 ) {
            return ( ( _ref = oGivenStrings ) ? _ref.hours : oDateStrings.hours ).replace( "{value}", Math.floor( iDiff / 3600 ) );
        }

        // 7 - 23 hours in the same day
        if( !bFuture && iDiff < 86400 && dNow.getDate() === this.getDate() ) {
            return ( ( _ref = oGivenStrings ) ? _ref.hours : oDateStrings.hours ).replace( "{value}", Math.floor( iDiff / 3600 ) );
        }

        sHours = ( _ref = this.getHours() ) < 10 ? "0" + _ref : _ref;
        sMinutes = ( _ref = this.getMinutes() ) < 10 ? "0" + _ref : _ref;

        // 7+ hours, yesterday
        if( !bFuture && iDiff < ( ( this.getHours() + 24 ) * 60 * 60 ) ) {
            return ( ( _ref = oGivenStrings ) ? _ref.yesterday : oDateStrings.yesterday ).replace( "{value}", sHours + ":" + sMinutes );
        }

        // far from yesterday
        sDay = ( _ref = this.getDate() ) < 10 ? "0" + _ref : _ref;
        iMonth = this.getMonth();
        sMonth = ( ( _ref = oGivenStrings ) ? ( ( _ref2 = _ref.months[ iMonth ] ) ? _ref2 : oDateStrings.months[ iMonth ] ) : oDateStrings.months[ iMonth ] );
        return ( ( _ref = oGivenStrings ) ? _ref.past : oDateStrings.past ).replace( "{date}", sDay + " " + sMonth + " " + this.getFullYear() ).replace( "{time}", sHours + ":" + sMinutes );
    };

} ).call( this );
<|endoftext|>


<commit_before>var Readability = require("../readabilitySAX"),
	http = require("http"),
	https = require("https"),
	url = require("url"),
	Parser = require("htmlparser2/lib/Parser.js"),
	parserOptions = {
		lowerCaseTags: true
	};

exports.get = function(uri, cb){
	function onErr(err){
		err = err.toString();
		cb({
			title:	"Error",
	    	text:	err,
	    	html:	"" + err + "",
	    	error: true
	    });
	}

	var link;
	if(typeof uri === "object") link = uri;
	else if(typeof uri === "string") link = url.parse(uri);
	else onErr("No URI specified!");

	var req;
	if(link.protocol === "http:") req = http;
	else if(link.protocol === "https:") req = https;
	else return onErr("Unsupported protocol: " + link.protocol);

	//fix for pre node 0.5.x
	if(!link.path) link.path = link.pathname;

	req.request(link, function(resp){
		if(resp.statusCode % 301 < 2){
			exports.get(resp.headers.location, cb);
			return;
		}
		if(resp.statusCode >= 400){
			onErr("Got error: " + http.STATUS_CODES[resp.statusCode] + ", status code " + resp.statusCode);
			return;
		}

		var settings = {pageURL: url.format(link)},
			readable = new Readability(settings),
			parser = new Parser(readable, parserOptions),
			data = "";

		resp.on("data", function(chunk){
			chunk = chunk.toString();
			data += chunk;
			parser.write(chunk);
		});

		resp.on("end", function(){
			parser.done();

			var article = readable.getArticle();
			if(article.score < 300 && article.textLength < 250){
				article = exports.process(data, settings, 1);
	    	}
	    	article.link = settings.pageURL;
	    	cb(article);
		});
	}).on("error", onErr).end();
};

exports.process = function(data, settings, skipLevel){
	if(!skipLevel) skipLevel = 0;
	if(!settings) settings = {};

	var readable = new Readability(settings),
		parser = new Parser(readable, parserOptions),
		article;

	do {
		if(skipLevel !== 0) readable.setSkipLevel(skipLevel);

	    parser.parseComplete(data);

	    article = readable.getArticle();
	    skipLevel += 1;
	} while(article.score < 300 && article.textLength < 250 && skipLevel < 4);

	/*
	if(article.textLength < 250) return {
			title:	"Error",
	    	text:	"Couldn't find content!",
	    	html:	"Couldn't find content!",
	    	error: true
	};
	*/
	return article;
};

exports.Readability = Readability;<commit_msg>small changes to whitespace<commit_after>var Readability = require("../readabilitySAX"),
	http = require("http"),
	https = require("https"),
	url = require("url"),
	Parser = require("htmlparser2/lib/Parser.js"),
	parserOptions = {
		lowerCaseTags: true
	};

exports.get = function(uri, cb){
	function onErr(err){
		err = err.toString();
		cb({
			title:	"Error",
            text:	err,
            html:	"" + err + "",
            error: true
	    });
	}

	var link;
	if(typeof uri === "object") link = uri;
	else if(typeof uri === "string") link = url.parse(uri);
	else onErr("No URI specified!");

	var req;
	if(link.protocol === "http:") req = http;
	else if(link.protocol === "https:") req = https;
	else return onErr("Unsupported protocol: " + link.protocol);

	//fix for pre node 0.5.x
	if(!link.path) link.path = link.pathname;

	req.request(link, function(resp){
		if(resp.statusCode % 301 < 2){
			exports.get(resp.headers.location, cb);
			return;
		}
		if(resp.statusCode >= 400){
			onErr("Got error: " + http.STATUS_CODES[resp.statusCode] + ", status code " + resp.statusCode);
			return;
		}

		var settings = {pageURL: url.format(link)},
			readable = new Readability(settings),
			parser = new Parser(readable, parserOptions),
			data = "";

		resp.on("data", function(chunk){
			chunk = chunk.toString();
			data += chunk;
			parser.write(chunk);
		});

		resp.on("end", function(){
			parser.done();

			var article = readable.getArticle();
			if(article.score < 300 && article.textLength < 250){
				article = exports.process(data, settings, 1);
            }
            article.link = settings.pageURL;
            cb(article);
		});
	}).on("error", onErr).end();
};

exports.process = function(data, settings, skipLevel){
	if(!skipLevel) skipLevel = 0;
	if(!settings) settings = {};

	var readable = new Readability(settings),
		parser = new Parser(readable, parserOptions),
		article;

	do {
		if(skipLevel !== 0) readable.setSkipLevel(skipLevel);

	    parser.parseComplete(data);

	    article = readable.getArticle();
	    skipLevel += 1;
	} while(article.score < 300 && article.textLength < 250 && skipLevel < 4);

	/*
	if(article.textLength < 250) return {
			title:	"Error",
            text:	"Couldn't find content!",
            html:	"Couldn't find content!",
            error: true
	};
	*/
	return article;
};

exports.Readability = Readability;<|endoftext|>


<commit_before>var heroCanvas = {
    init : function(){
        this.drawHero();
    },
    drawHero : function(){
        var hero = new Image(),
            canvas = document.getElementById('heroCanvas'),
            ctx = canvas.getContext('2d'),
            requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
            window.requestAnimationFrame = requestAnimationFrame;
        window.addEventListener("keydown", onKeyDown, false);
        window.addEventListener("keyup", onKeyUp, false);
        function onKeyDown(event) {
            var keyCode = event.keyCode;
            console.log(keyCode);
            switch (keyCode) {
                case 37: //Left
                    keyLeft = true;
                    break;
                case 38: //Top
                    keyTop = true;
                    break;
                case 39: //Right
                    keyRight = true;
                    break;
                case 40: //Bottom
                    keyBottom = true;
                    break;
            }
        }

        function onKeyUp(event) {
            var keyCode = event.keyCode;
            switch (keyCode) {
                case 37: //Left
                    keyLeft = false;
                    break;
                case 38: //Top
                    keyTop = false;
                    break;
                case 39: //Right
                    keyRight = false;
                    break;
                case 40: //Bottom
                    keyBottom = false;
                    break;
            }
        }

        //neccessary variables
        var tickX = 10,
            tickY = 10,
            keyLeft = false,
            keyTop = false,
            keyRight = false,
            keyBottom = false;
        //main animation function
        function drawStuff() {
            window.requestAnimationFrame(drawStuff);
            hero.src = "/images/character.png";
            hero.onload = function(){
                console.log("image loaded");
                   ctx.drawImage(hero, 0, 0, 16, 16, tickX, tickY, 16, 16);
            };
            if (keyRight == true) {
                tickX += 1;
                ctx.clearRect ( 0 , 0 , canvas.width, canvas.height );
                ctx.drawImage(hero, 0, 32, 16, 16, tickX, tickY, 16, 16);
            }
            if (keyBottom == true) {
                tickY += 1;
                ctx.clearRect ( 0 , 0 , canvas.width, canvas.height );
                ctx.drawImage(hero, 0, 0, 16, 16, tickX, tickY, 16, 16);
            }
            if (keyTop == true) {
                tickY--;
                ctx.clearRect ( 0 , 0 , canvas.width, canvas.height );
                ctx.drawImage(hero, 0, 16, 16, 16, tickX, tickY, 16, 16);
            }
            if (keyLeft == true) {
                tickX--;
                ctx.clearRect ( 0 , 0 , canvas.width, canvas.height );
                ctx.drawImage(hero, 0, 48, 16, 16, tickX, tickY, 16, 16);
            }
        }
        window.requestAnimationFrame(drawStuff);
    }
}

<commit_msg>Add setInterval()<commit_after>var heroCanvas = {
    init : function(){
        this.drawHero();
    },
    drawHero : function(){
        var hero = new Image(),
            canvas = document.getElementById('heroCanvas'),
            ctx = canvas.getContext('2d'),
            requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
            window.requestAnimationFrame = requestAnimationFrame;
        window.addEventListener("keydown", onKeyDown, false);
        window.addEventListener("keyup", onKeyUp, false);
        function onKeyDown(event) {
            var keyCode = event.keyCode;
            console.log(keyCode);
            switch (keyCode) {
                case 37: //Left
                    keyLeft = true;
                    break;
                case 38: //Top
                    keyTop = true;
                    break;
                case 39: //Right
                    keyRight = true;
                    break;
                case 40: //Bottom
                    keyBottom = true;
                    break;
            }
        }

        function onKeyUp(event) {
            var keyCode = event.keyCode;
            switch (keyCode) {
                case 37: //Left
                    keyLeft = false;
                    break;
                case 38: //Top
                    keyTop = false;
                    break;
                case 39: //Right
                    keyRight = false;
                    break;
                case 40: //Bottom
                    keyBottom = false;
                    break;
            }
        }
        //neccessary variables
        var tickX = 10,
            tickY = 10,
            keyLeft = false,
            keyTop = false,
            keyRight = false,
            keyBottom = false,
            switchImage = true;
        function drawMyImage(image, x, y, tickX, tickY){
            if(switchImage){
                ctx.clearRect ( 0 , 0 , canvas.width, canvas.height );
                ctx.drawImage(image, x, y, 16, 16, tickX, tickY, 16, 16);
                switchImage = false;
            }else{
                ctx.clearRect ( 0 , 0 , canvas.width, canvas.height );
                ctx.drawImage(image, x+16, y, 16, 16, tickX, tickY, 16, 16);
                switchImage = true;
            }
        }
        //main animation function
        function drawStuff() {
            window.requestAnimationFrame(drawStuff);
            hero.src = "/images/character.png";
            hero.onload = function(){
                console.log("image loaded");
                drawMyImage(hero,0,0,tickX, tickY);
            };
            if (keyRight == true) {
                tickX += 1;
                ctx.clearRect ( 0 , 0 , canvas.width, canvas.height );
                var animate = setInterval(drawMyImage(hero,0,32,tickX, tickY), 1000);
            }
            if (keyBottom == true) {
                tickY += 1;
                var animate = setInterval(drawMyImage(hero,0,0,tickX, tickY), 1000);
            }
            if (keyTop == true) {
                tickY--;
                ctx.clearRect ( 0 , 0 , canvas.width, canvas.height );
                var animate = setInterval(drawMyImage(hero,0,16,tickX, tickY), 1000);
            }
            if (keyLeft == true) {
                tickX--;
                ctx.clearRect ( 0 , 0 , canvas.width, canvas.height );
                var animate = setInterval(drawMyImage(hero,0,48,tickX, tickY), 1000);
            }
        }

        window.requestAnimationFrame(drawStuff);
    }
}

<|endoftext|>