<commit_before>import React from "react";
import styled, { keyframes } from "styled-components";
import PropTypes from "prop-types";

import Page from "../components/page";

const GitHubIcon = () => {
  return (
    <svg
      xmlns="http://www.w3.org/2000/svg"
      width="20"
      height="20"
      fill="currentColor"
      viewBox="0 0 16 16"
    >
      <path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z" />
    </svg>
  );
};

const Code = ({ commits }) => {
  return (
    <Page title="Code" description="Some of my most recent coding projects.">
      <Background />
      <Heading>Code</Heading>
      <Paragraph>
        A probably not entirely up-to-date list of my current side projects...
        There is plenty more to see on{" "}
        <a
          href="https://github.com/overshard"
          rel="noopener noreferrer"
          target="_blank"
        >
          my GitHub account
        </a>{" "}
        and generally around the internet if you are interested.
      </Paragraph>
      <Projects>
        <Project>
          <ProjectHeading>Analytics</ProjectHeading>
          <ProjectParagraph>
            A self-hostable analytics service with a straightforward API to
            track events from any source.
          </ProjectParagraph>
          <ProjectCommit>
            {JSON.stringify(commits.analytics.data, null, 2)}
          </ProjectCommit>
          <ProjectButton
            href="https://www.github.com/overshard/analytics"
            rel="noopener noreferrer"
            target="_blank"
          >
            <GitHubIcon />
            GitHub
          </ProjectButton>
        </Project>
        <Project>
          <ProjectHeading>Status</ProjectHeading>
          <ProjectParagraph>
            A self-hosted status monitoring service.
          </ProjectParagraph>
          <ProjectCommit>
            {JSON.stringify(commits.status.data, null, 2)}
          </ProjectCommit>
          <ProjectButton
            href="https://www.github.com/overshard/status"
            rel="noopener noreferrer"
            target="_blank"
          >
            <GitHubIcon />
            GitHub
          </ProjectButton>
        </Project>
        <Project>
          <ProjectHeading>Blog</ProjectHeading>
          <ProjectParagraph>
            A self-hostable blog built on Wagtail targeted towards developers
            with code blocks, syntax highlighting, live search, great SEO, and a
            clean customizable UI.
          </ProjectParagraph>
          <ProjectCommit>
            {JSON.stringify(commits.blog.data, null, 2)}
          </ProjectCommit>
          <ProjectButton
            href="https://www.github.com/overshard/blog"
            rel="noopener noreferrer"
            target="_blank"
          >
            <GitHubIcon />
            GitHub
          </ProjectButton>
        </Project>
        <Project>
          <ProjectHeading>Timelite</ProjectHeading>
          <ProjectParagraph>
            A simple time tracking progressive web app. Uses local storage and
            service workers to remain accessible offline. Sometimes you just
            need the essentials when you are busy.
          </ProjectParagraph>
          <ProjectCommit>
            {JSON.stringify(commits.timelite.data, null, 2)}
          </ProjectCommit>
          <ProjectButton
            href="https://www.github.com/overshard/timelite"
            rel="noopener noreferrer"
            target="_blank"
          >
            <GitHubIcon />
            GitHub
          </ProjectButton>
        </Project>
        <Project>
          <ProjectHeading>Timestrap</ProjectHeading>
          <ProjectParagraph>
            A full feature time tracking web app. Supports multiple users and
            exporting reports in multiple formats. Makes use of websockets to
            maintain state across clients.
          </ProjectParagraph>
          <ProjectCommit>
            {JSON.stringify(commits.timestrap.data, null, 2)}
          </ProjectCommit>
          <ProjectButton
            href="https://www.github.com/overshard/timestrap"
            rel="noopener noreferrer"
            target="_blank"
          >
            <GitHubIcon />
            GitHub
          </ProjectButton>
        </Project>
        <Project>
          <ProjectHeading>isaacbythewood.com</ProjectHeading>
          <ProjectParagraph>
            The personal website of Isaac Bythewood. So this site...
          </ProjectParagraph>
          <ProjectCommit>
            {JSON.stringify(commits.isaacbythewood.data, null, 2)}
          </ProjectCommit>
          <ProjectButton
            href="https://www.github.com/overshard/isaacbythewood.com"
            rel="noopener noreferrer"
            target="_blank"
          >
            <GitHubIcon />
            GitHub
          </ProjectButton>
        </Project>
        <Project>
          <ProjectHeading>dockerfiles</ProjectHeading>
          <ProjectParagraph>
            All the Dockerfiles I use for various purposes. More detailed usage
            instructions are at the top of each Dockerfile.
          </ProjectParagraph>
          <ProjectCommit>
            {JSON.stringify(commits.dockerfiles.data, null, 2)}
          </ProjectCommit>
          <ProjectButton
            href="https://www.github.com/overshard/dockerfiles"
            rel="noopener noreferrer"
            target="_blank"
          >
            <GitHubIcon />
            GitHub
          </ProjectButton>
        </Project>
        <Project>
          <ProjectHeading>alpinefiles</ProjectHeading>
          <ProjectParagraph>
            Some of the files that I use on my Alpine Linux servers.
          </ProjectParagraph>
          <ProjectCommit>
            {JSON.stringify(commits.alpinefiles.data, null, 2)}
          </ProjectCommit>
          <ProjectButton
            href="https://www.github.com/overshard/alpinefiles"
            rel="noopener noreferrer"
            target="_blank"
          >
            <GitHubIcon />
            GitHub
          </ProjectButton>
        </Project>
        <Project>
          <ProjectHeading>dotfiles</ProjectHeading>
          <ProjectParagraph>
            A variety of config files for setting up new systems.
          </ProjectParagraph>
          <ProjectCommit>
            {JSON.stringify(commits.dotfiles.data, null, 2)}
          </ProjectCommit>
          <ProjectButton
            href="https://www.github.com/overshard/dotfiles"
            rel="noopener noreferrer"
            target="_blank"
          >
            <GitHubIcon />
            GitHub
          </ProjectButton>
        </Project>
      </Projects>
    </Page>
  );
};

export async function getServerSideProps() {
  // NOTE: We eval the require on purpose so that webpack doesn't bundle it
  const { Sequelize } = eval("require('sequelize')");

  const sequelize = new Sequelize("sqlite://db.sqlite3");

  const Commit = sequelize.define("commit", {
    repo: {
      type: Sequelize.STRING,
      allowNull: false,
    },
    createdAt: {
      type: Sequelize.DATE,
      allowNull: false,
    },
    data: {
      type: Sequelize.JSON,
      allowNull: false,
    },
  });

  const getCommit = async (repo) => {
    const storedCommit = await Commit.findOne({
      where: {
        repo,
        createdAt: {
          [Sequelize.Op.gte]: new Date(Date.now() - 24 * 60 * 60 * 1000),
        },
      },
    });
    if (storedCommit) {
      return storedCommit;
    }
    const commitsFetch = await fetch(
      `https://api.github.com/repos/overshard/${repo}/commits`
    );
    const commits = await commitsFetch.json();
    const commit = commits[0];
    const commitData = {
      repo: repo,
      createdAt: new Date(),
      data: {
        sha: commit.sha,
        commit: {
          message: commit.commit.message,
          date: commit.commit.author.date,
        },
        author: {
          name: commit.commit.author.name,
          email: commit.commit.author.email,
        },
      },
    };

    await Commit.create(commitData);
    return commitData;
  };

  const getCommits = async () => {
    const alpinefiles = await getCommit("alpinefiles");
    const analytics = await getCommit("analytics");
    const blog = await getCommit("blog");
    const dockerfiles = await getCommit("dockerfiles");
    const dotfiles = await getCommit("dotfiles");
    const isaacbythewood = await getCommit("isaacbythewood.com");
    const status = await getCommit("status");
    const timelite = await getCommit("timelite");
    const timestrap = await getCommit("timestrap");

    return {
      alpinefiles: alpinefiles,
      analytics: analytics,
      blog: blog,
      dockerfiles: dockerfiles,
      dotfiles: dotfiles,
      isaacbythewood: isaacbythewood,
      status: status,
      timelite: timelite,
      timestrap: timestrap,
    };
  };

  await Commit.sync();

  return getCommits().then((commits) => {
    return {
      props: {
        commits: JSON.parse(JSON.stringify(commits)),
      },
    };
  });
}

Code.propTypes = {
  commits: PropTypes.object,
};

export default Code;

const TransformRight = keyframes`
  from {
    transform: scaleX(0);
  }

  to {
    transform: scaleX(1);
  }
`;

const TransformLeft = keyframes`
  from {
    transform: scaleX(1);
  }

  to {
    transform: scaleX(0);
  }
`;

const Background = styled.div`
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: white;
  z-index: -2;
`;

const Heading = styled.h1`
  font-weight: 700;
  margin-top: 60px;
  margin-bottom: 20px;
  font-size: 2.5em;
  color: black;

  &::before {
    content: "";
    display: block;
    width: 50px;
    height: 5px;
    margin-bottom: 20px;
    background-color: ${(props) => props.theme.colors.blue};
  }
`;

const Paragraph = styled.p`
  font-size: 1.5em;
  margin-top: 0;
  margin-bottom: 20px;
  font-weight: 300;
  color: black;

  a {
    color: black;
    text-decoration: none;
    position: relative;
    white-space: nowrap;

    &::before {
      content: "";
      position: absolute;
      left: 0;
      right: 0;
      bottom: 0;
      height: 2px;
      background: rgba(0, 0, 0, 1);
      transform-origin: left;
      animation: ${TransformLeft} 300ms normal forwards;
    }

    &::after {
      content: "";
      position: absolute;
      left: 0;
      right: 0;
      bottom: 0;
      height: 2px;
      background: rgba(0, 0, 0, 0.2);
    }

    &:hover {
      &::before {
        animation: ${TransformRight} 300ms normal forwards;
      }
    }
  }
`;

const Projects = styled.div`
  display: flex;
  width: 100%;
  flex-wrap: wrap;
  gap: 20px;
`;

const Project = styled.div`
  width: calc(50% - 20px);
  display: flex;
  flex-direction: column;

  @media (${(props) => props.theme.breakpoints.mobile}) {
    width: 100%;
  }
`;

const ProjectHeading = styled.h1`
  font-weight: 700;
  margin-top: 60px;
  margin-bottom: 20px;
  font-size: 2em;
  color: black;
`;

const ProjectParagraph = styled.p`
  font-size: 1.5em;
  margin-top: 0;
  margin-bottom: 20px;
  font-weight: 300;
  color: black;
  flex-grow: 1;
`;

const ProjectButton = styled.a`
  padding: 10px 15px;
  font-size: 1.2em;
  text-decoration: none;
  background-color: black;
  margin-bottom: 20px;
  font-weight: 700;
  text-transform: uppercase;
  display: inline-block;
  letter-spacing: 2px;
  color: white;
  background-image: linear-gradient(
    to right,
    ${(props) => props.theme.colors.blue} 0,
    ${(props) => props.theme.colors.purple} 100%
  );
  transform: scale(1);
  transition-duration: 250ms;
  transition-property: transform;
  width: 125px;
  text-align: center;
  display: flex;
  align-items: center;

  & svg {
    margin-right: 10px;
  }

  &:hover {
    transform: scale(1.2);
  }
`;

const ProjectCommit = styled.pre`
  font-family: monospace;
  background: black;
  color: #00ff00;
  padding: 20px;
  overflow-x: hidden;
  max-width: 100%;
  text-overflow: ellipsis;
`;
<commit_msg>Add ai-art code project<commit_after>import React from "react";
import styled, { keyframes } from "styled-components";
import PropTypes from "prop-types";

import Page from "../components/page";

const GitHubIcon = () => {
  return (
    <svg
      xmlns="http://www.w3.org/2000/svg"
      width="20"
      height="20"
      fill="currentColor"
      viewBox="0 0 16 16"
    >
      <path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z" />
    </svg>
  );
};

const Code = ({ commits }) => {
  return (
    <Page title="Code" description="Some of my most recent coding projects.">
      <Background />
      <Heading>Code</Heading>
      <Paragraph>
        A probably not entirely up-to-date list of my current side projects...
        There is plenty more to see on{" "}
        <a
          href="https://github.com/overshard"
          rel="noopener noreferrer"
          target="_blank"
        >
          my GitHub account
        </a>{" "}
        and generally around the internet if you are interested.
      </Paragraph>
      <Projects>
        <Project>
          <ProjectHeading>AI-Art</ProjectHeading>
          <ProjectParagraph>
            Art generation using VQGAN + CLIP in docker containers.
          </ProjectParagraph>
          <ProjectCommit>
            {JSON.stringify(commits.aiart.data, null, 2)}
          </ProjectCommit>
          <ProjectButton
            href="https://www.github.com/overshard/ai-art"
            rel="noopener noreferrer"
            target="_blank"
          >
            <GitHubIcon />
            GitHub
          </ProjectButton>
        </Project>
        <Project>
          <ProjectHeading>Analytics</ProjectHeading>
          <ProjectParagraph>
            A self-hostable analytics service with a straightforward API to
            track events from any source.
          </ProjectParagraph>
          <ProjectCommit>
            {JSON.stringify(commits.analytics.data, null, 2)}
          </ProjectCommit>
          <ProjectButton
            href="https://www.github.com/overshard/analytics"
            rel="noopener noreferrer"
            target="_blank"
          >
            <GitHubIcon />
            GitHub
          </ProjectButton>
        </Project>
        <Project>
          <ProjectHeading>Status</ProjectHeading>
          <ProjectParagraph>
            A self-hosted status monitoring service.
          </ProjectParagraph>
          <ProjectCommit>
            {JSON.stringify(commits.status.data, null, 2)}
          </ProjectCommit>
          <ProjectButton
            href="https://www.github.com/overshard/status"
            rel="noopener noreferrer"
            target="_blank"
          >
            <GitHubIcon />
            GitHub
          </ProjectButton>
        </Project>
        <Project>
          <ProjectHeading>Blog</ProjectHeading>
          <ProjectParagraph>
            A self-hostable blog built on Wagtail targeted towards developers
            with code blocks, syntax highlighting, live search, great SEO, and a
            clean customizable UI.
          </ProjectParagraph>
          <ProjectCommit>
            {JSON.stringify(commits.blog.data, null, 2)}
          </ProjectCommit>
          <ProjectButton
            href="https://www.github.com/overshard/blog"
            rel="noopener noreferrer"
            target="_blank"
          >
            <GitHubIcon />
            GitHub
          </ProjectButton>
        </Project>
        <Project>
          <ProjectHeading>Timelite</ProjectHeading>
          <ProjectParagraph>
            A simple time tracking progressive web app. Uses local storage and
            service workers to remain accessible offline. Sometimes you just
            need the essentials when you are busy.
          </ProjectParagraph>
          <ProjectCommit>
            {JSON.stringify(commits.timelite.data, null, 2)}
          </ProjectCommit>
          <ProjectButton
            href="https://www.github.com/overshard/timelite"
            rel="noopener noreferrer"
            target="_blank"
          >
            <GitHubIcon />
            GitHub
          </ProjectButton>
        </Project>
        <Project>
          <ProjectHeading>Timestrap</ProjectHeading>
          <ProjectParagraph>
            A full feature time tracking web app. Supports multiple users and
            exporting reports in multiple formats. Makes use of websockets to
            maintain state across clients.
          </ProjectParagraph>
          <ProjectCommit>
            {JSON.stringify(commits.timestrap.data, null, 2)}
          </ProjectCommit>
          <ProjectButton
            href="https://www.github.com/overshard/timestrap"
            rel="noopener noreferrer"
            target="_blank"
          >
            <GitHubIcon />
            GitHub
          </ProjectButton>
        </Project>
        <Project>
          <ProjectHeading>isaacbythewood.com</ProjectHeading>
          <ProjectParagraph>
            The personal website of Isaac Bythewood. So this site...
          </ProjectParagraph>
          <ProjectCommit>
            {JSON.stringify(commits.isaacbythewood.data, null, 2)}
          </ProjectCommit>
          <ProjectButton
            href="https://www.github.com/overshard/isaacbythewood.com"
            rel="noopener noreferrer"
            target="_blank"
          >
            <GitHubIcon />
            GitHub
          </ProjectButton>
        </Project>
        <Project>
          <ProjectHeading>dockerfiles</ProjectHeading>
          <ProjectParagraph>
            All the Dockerfiles I use for various purposes. More detailed usage
            instructions are at the top of each Dockerfile.
          </ProjectParagraph>
          <ProjectCommit>
            {JSON.stringify(commits.dockerfiles.data, null, 2)}
          </ProjectCommit>
          <ProjectButton
            href="https://www.github.com/overshard/dockerfiles"
            rel="noopener noreferrer"
            target="_blank"
          >
            <GitHubIcon />
            GitHub
          </ProjectButton>
        </Project>
        <Project>
          <ProjectHeading>alpinefiles</ProjectHeading>
          <ProjectParagraph>
            Some of the files that I use on my Alpine Linux servers.
          </ProjectParagraph>
          <ProjectCommit>
            {JSON.stringify(commits.alpinefiles.data, null, 2)}
          </ProjectCommit>
          <ProjectButton
            href="https://www.github.com/overshard/alpinefiles"
            rel="noopener noreferrer"
            target="_blank"
          >
            <GitHubIcon />
            GitHub
          </ProjectButton>
        </Project>
        <Project>
          <ProjectHeading>dotfiles</ProjectHeading>
          <ProjectParagraph>
            A variety of config files for setting up new systems.
          </ProjectParagraph>
          <ProjectCommit>
            {JSON.stringify(commits.dotfiles.data, null, 2)}
          </ProjectCommit>
          <ProjectButton
            href="https://www.github.com/overshard/dotfiles"
            rel="noopener noreferrer"
            target="_blank"
          >
            <GitHubIcon />
            GitHub
          </ProjectButton>
        </Project>
      </Projects>
    </Page>
  );
};

export async function getServerSideProps() {
  // NOTE: We eval the require on purpose so that webpack doesn't bundle it
  const { Sequelize } = eval("require('sequelize')");

  const sequelize = new Sequelize("sqlite://db.sqlite3");

  const Commit = sequelize.define("commit", {
    repo: {
      type: Sequelize.STRING,
      allowNull: false,
    },
    createdAt: {
      type: Sequelize.DATE,
      allowNull: false,
    },
    data: {
      type: Sequelize.JSON,
      allowNull: false,
    },
  });

  const getCommit = async (repo) => {
    const storedCommit = await Commit.findOne({
      where: {
        repo,
        createdAt: {
          [Sequelize.Op.gte]: new Date(Date.now() - 24 * 60 * 60 * 1000),
        },
      },
    });
    if (storedCommit) {
      return storedCommit;
    }
    const commitsFetch = await fetch(
      `https://api.github.com/repos/overshard/${repo}/commits`
    );
    const commits = await commitsFetch.json();
    const commit = commits[0];
    const commitData = {
      repo: repo,
      createdAt: new Date(),
      data: {
        sha: commit.sha,
        commit: {
          message: commit.commit.message,
          date: commit.commit.author.date,
        },
        author: {
          name: commit.commit.author.name,
          email: commit.commit.author.email,
        },
      },
    };

    await Commit.create(commitData);
    return commitData;
  };

  const getCommits = async () => {
    const aiart = await getCommit("ai-art");
    const alpinefiles = await getCommit("alpinefiles");
    const analytics = await getCommit("analytics");
    const blog = await getCommit("blog");
    const dockerfiles = await getCommit("dockerfiles");
    const dotfiles = await getCommit("dotfiles");
    const isaacbythewood = await getCommit("isaacbythewood.com");
    const status = await getCommit("status");
    const timelite = await getCommit("timelite");
    const timestrap = await getCommit("timestrap");

    return {
      aiart: aiart,
      alpinefiles: alpinefiles,
      analytics: analytics,
      blog: blog,
      dockerfiles: dockerfiles,
      dotfiles: dotfiles,
      isaacbythewood: isaacbythewood,
      status: status,
      timelite: timelite,
      timestrap: timestrap,
    };
  };

  await Commit.sync();

  return getCommits().then((commits) => {
    return {
      props: {
        commits: JSON.parse(JSON.stringify(commits)),
      },
    };
  });
}

Code.propTypes = {
  commits: PropTypes.object,
};

export default Code;

const TransformRight = keyframes`
  from {
    transform: scaleX(0);
  }

  to {
    transform: scaleX(1);
  }
`;

const TransformLeft = keyframes`
  from {
    transform: scaleX(1);
  }

  to {
    transform: scaleX(0);
  }
`;

const Background = styled.div`
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: white;
  z-index: -2;
`;

const Heading = styled.h1`
  font-weight: 700;
  margin-top: 60px;
  margin-bottom: 20px;
  font-size: 2.5em;
  color: black;

  &::before {
    content: "";
    display: block;
    width: 50px;
    height: 5px;
    margin-bottom: 20px;
    background-color: ${(props) => props.theme.colors.blue};
  }
`;

const Paragraph = styled.p`
  font-size: 1.5em;
  margin-top: 0;
  margin-bottom: 20px;
  font-weight: 300;
  color: black;

  a {
    color: black;
    text-decoration: none;
    position: relative;
    white-space: nowrap;

    &::before {
      content: "";
      position: absolute;
      left: 0;
      right: 0;
      bottom: 0;
      height: 2px;
      background: rgba(0, 0, 0, 1);
      transform-origin: left;
      animation: ${TransformLeft} 300ms normal forwards;
    }

    &::after {
      content: "";
      position: absolute;
      left: 0;
      right: 0;
      bottom: 0;
      height: 2px;
      background: rgba(0, 0, 0, 0.2);
    }

    &:hover {
      &::before {
        animation: ${TransformRight} 300ms normal forwards;
      }
    }
  }
`;

const Projects = styled.div`
  display: flex;
  width: 100%;
  flex-wrap: wrap;
  gap: 20px;
`;

const Project = styled.div`
  width: calc(50% - 20px);
  display: flex;
  flex-direction: column;

  @media (${(props) => props.theme.breakpoints.mobile}) {
    width: 100%;
  }
`;

const ProjectHeading = styled.h1`
  font-weight: 700;
  margin-top: 60px;
  margin-bottom: 20px;
  font-size: 2em;
  color: black;
`;

const ProjectParagraph = styled.p`
  font-size: 1.5em;
  margin-top: 0;
  margin-bottom: 20px;
  font-weight: 300;
  color: black;
  flex-grow: 1;
`;

const ProjectButton = styled.a`
  padding: 10px 15px;
  font-size: 1.2em;
  text-decoration: none;
  background-color: black;
  margin-bottom: 20px;
  font-weight: 700;
  text-transform: uppercase;
  display: inline-block;
  letter-spacing: 2px;
  color: white;
  background-image: linear-gradient(
    to right,
    ${(props) => props.theme.colors.blue} 0,
    ${(props) => props.theme.colors.purple} 100%
  );
  transform: scale(1);
  transition-duration: 250ms;
  transition-property: transform;
  width: 125px;
  text-align: center;
  display: flex;
  align-items: center;

  & svg {
    margin-right: 10px;
  }

  &:hover {
    transform: scale(1.2);
  }
`;

const ProjectCommit = styled.pre`
  font-family: monospace;
  background: black;
  color: #00ff00;
  padding: 20px;
  overflow-x: hidden;
  max-width: 100%;
  text-overflow: ellipsis;
`;
<|endoftext|>


<commit_before>/**
 * An AppWorker is responsible for:
 *
 * - Launching a Shiny application with the proper user/group permissions
 * - Ensuring that stderr is written to the specified path
 * - Returning a promise that resolves when the worker process exits
 */

var child_process = require('child_process');
var fs = require('fs');
var path = require('path');
var util = require('util');
var Q = require('q');
var _ = require('underscore');

var rprog = process.env.R || 'R';
var scriptPath = __dirname + '/../../R/SockJSAdapter.R';

function exists_p(path) {
  var defer = Q.defer();
  fs.exists(path, function(exists) {
    defer.resolve(exists);
  });
  return defer.promise;
}

/**
 * Begins launching the worker; returns a promise that resolves when
 * the worker is constructed (doesn't necessarily mean the process has
 * actually begun running though).
 *
 * @param {AppSpec} appSpec - Contains the basic details about the app to
 *   launch
 * @param {Number} listenPort - The port number that the Shiny app should use.
 * @param {String} logFilePath - The file path to write stderr to.
 */
function launchWorker_p(appSpec, listenPort, logFilePath) {

  if (!appSpec.runAs)
    return Q.reject(new Error("No user specified"));

  if (!appSpec.appDir)
    return Q.reject(new Error("No app directory specified"));

  return exists_p(appSpec.appDir).then(function(exists) {
    if (!exists)
      throw new Error("App directory does not exist");

    // Open the log file asynchronously, then create the worker
    return Q.nfcall(fs.open, logFilePath, 'a', 0666).then(function(logStream) {

      // Create the worker; when it exits (or fails to start), close
      // the logStream.
      var worker = new AppWorker(appSpec, listenPort, logStream);
      worker.getExit_p().fin(function() {
        logStream.end();
      });

      return worker;
    });
  })
};
exports.launchWorker_p = launchWorker_p;

/**
 * Like launchWorker_p, but the promise it returns doesn't resolve until
 * the worker process exits.
 */
function runWorker_p(appSpec, listenPort, logFilePath) {
  return launchWorker_p(appSpec, listenPort, logFilePath).invoke('getExit_p');
};
exports.runWorker_p = runWorker_p;

/**
 * An AppWorker models a single R process that is running a Shiny app.
 *
 * @constructor
 * @param {AppSpec} appSpec - Contains the basic details about the app to
 *   launch
 * @param {Number} listenPort - The port number that the Shiny app should use.
 * @param {Stream} logStream - The stream to dump stderr to.
 */
var AppWorker = function(appSpec, listenPort, logStream) {
  this.$dfEnded = Q.defer();
  var self = this;

  try {
    // Run R
    var args = [
      path.join(__dirname, 'run-as.js'),
      appSpec.runAs,
      rprog,
      "--no-save",
      "-f",
      scriptPath
    ];

    var env = _.clone(process.env);
    env.SHINY_PORT = '' + listenPort;
    env.SHINY_APP = '.';
    if (appSpec.settings.gaTrackingId)
      env.SHINY_GAID = appSpec.settings.gaTrackingId;

    this.$proc = child_process.spawn(process.execPath, args, {
      env: env,
      cwd: appSpec.appDir,
      stdio: ['ignore', 'ignore', logStream]
    });
    this.$proc.on('exit', function(code, signal) {
      self.$dfEnded.resolve({code: code, signal: signal});
    });
  }
  catch (e) {
    this.$dfEnded.reject(e);
  }
};

(function() {

  /**
   * Returns a promise that is resolved when the process exits.
   * If the process terminated normally, code is the final exit
   * code of the process, otherwise null. If the process
   * terminated due to receipt of a signal, signal is the string
   * name of the signal, otherwise null.
   */
  this.getExit_p = function() {
    return this.$dfEnded.promise;
  };

  this.kill = function(signal) {
    this.$proc.kill(signal);
  };

}).call(AppWorker.prototype);
<commit_msg>Use su instead of run-as<commit_after>/**
 * An AppWorker is responsible for:
 *
 * - Launching a Shiny application with the proper user/group permissions
 * - Ensuring that stderr is written to the specified path
 * - Returning a promise that resolves when the worker process exits
 */

var child_process = require('child_process');
var fs = require('fs');
var path = require('path');
var util = require('util');
var bash = require('bash');
var Q = require('q');
var _ = require('underscore');

var rprog = process.env.R || 'R';
var scriptPath = __dirname + '/../../R/SockJSAdapter.R';

function exists_p(path) {
  var defer = Q.defer();
  fs.exists(path, function(exists) {
    defer.resolve(exists);
  });
  return defer.promise;
}

/**
 * Begins launching the worker; returns a promise that resolves when
 * the worker is constructed (doesn't necessarily mean the process has
 * actually begun running though).
 *
 * @param {AppSpec} appSpec - Contains the basic details about the app to
 *   launch
 * @param {Number} listenPort - The port number that the Shiny app should use.
 * @param {String} logFilePath - The file path to write stderr to.
 */
function launchWorker_p(appSpec, listenPort, logFilePath) {

  if (!appSpec.runAs)
    return Q.reject(new Error("No user specified"));

  if (!appSpec.appDir)
    return Q.reject(new Error("No app directory specified"));

  return exists_p(appSpec.appDir).then(function(exists) {
    if (!exists)
      throw new Error("App directory does not exist");

    // Open the log file asynchronously, then create the worker
    return Q.nfcall(fs.open, logFilePath, 'a', 0666).then(function(logStream) {

      // Create the worker; when it exits (or fails to start), close
      // the logStream.
      var worker = new AppWorker(appSpec, listenPort, logStream);
      worker.getExit_p().fin(function() {
        logStream.end();
      });

      return worker;
    });
  })
};
exports.launchWorker_p = launchWorker_p;

/**
 * Like launchWorker_p, but the promise it returns doesn't resolve until
 * the worker process exits.
 */
function runWorker_p(appSpec, listenPort, logFilePath) {
  return launchWorker_p(appSpec, listenPort, logFilePath).invoke('getExit_p');
};
exports.runWorker_p = runWorker_p;

/**
 * An AppWorker models a single R process that is running a Shiny app.
 *
 * @constructor
 * @param {AppSpec} appSpec - Contains the basic details about the app to
 *   launch
 * @param {Number} listenPort - The port number that the Shiny app should use.
 * @param {Stream} logStream - The stream to dump stderr to.
 */
var AppWorker = function(appSpec, listenPort, logStream) {
  this.$dfEnded = Q.defer();
  var self = this;

  try {
    // Run R
    var args = [
      '-',
      appSpec.runAs,
      '-c',
      'SHINY_APP=' + bash.escape(appSpec.appDir) + ' ' +
      'SHINY_PORT=' + bash.escape(listenPort) + ' ' +
      (appSpec.settings.gaTrackingId ?
        'SHINY_GAID=' + bash.escape(appSpec.settings.gaTrackingId) + ' ' :
        '') +
      bash.escape(rprog) + " --no-save -f " + bash.escape(scriptPath)
    ];

    this.$proc = child_process.spawn('/bin/su', args, {
      stdio: ['ignore', 'ignore', logStream]
    });
    this.$proc.on('exit', function(code, signal) {
      self.$dfEnded.resolve({code: code, signal: signal});
    });
  }
  catch (e) {
    this.$dfEnded.reject(e);
  }
};

(function() {

  /**
   * Returns a promise that is resolved when the process exits.
   * If the process terminated normally, code is the final exit
   * code of the process, otherwise null. If the process
   * terminated due to receipt of a signal, signal is the string
   * name of the signal, otherwise null.
   */
  this.getExit_p = function() {
    return this.$dfEnded.promise;
  };

  this.kill = function(signal) {
    this.$proc.kill(signal);
  };

}).call(AppWorker.prototype);
<|endoftext|>


<commit_before>import { LoggingWinston } from '@google-cloud/logging-winston';
import { pickup as pickupSchema, report as reportSchema } from 'common/validation/reports.js';
import compression from 'compression';
import cors from 'cors';
import express from 'express';
import expressWinston from 'express-winston';
import helmet from 'helmet';
import Multer from 'multer';
import winston from 'winston';
import { getNewPickupHandler, getNewReportHandler } from './api/reports.js';
import { authenticate, getToken, logout } from './api/security.js';
import { getApprove, getLogin, getRegister, getReject } from './api/user.js';
import validate from './api/validation.js';
import { upload } from './services/photos.js';
import { createPickup, createReport } from './services/report_management.js';
import {
  approveUser,
  getUser,
  isExistingUser,
  loginSchema,
  registerSchema,
  registerUser,
  rejectUser,
  updateUser,
} from './services/user_management.js';

// app setup
const app = express();

// gzip
app.use(compression());

// enable x-www-form-urlencoded body format
app.use(express.urlencoded({ extended: true }));

// parse application/json
app.use(express.json());

const multer = Multer({
  storage: Multer.memoryStorage(),
  limits: {
    fileSize: 5 * 1024 * 1024, // no larger than 5mb, you can change as needed.
  },
});

// security best practices
app.use(helmet());

app.use(cors());

if (process.env.ENVIRONMENT !== 'development' && process.env.ENVIRONMENT !== 'test') {
  app.use(
    expressWinston.logger({
      transports: [new LoggingWinston()],
      metaField: null, //this causes the metadata to be stored at the root of the log entry
      responseField: null, // this prevents the response from being included in the metadata (including body and status code)
      requestWhitelist: ['url', 'method', 'httpVersion', 'originalUrl', 'query', 'body'],
      responseWhitelist: ['body'], // this populates the `res.body` so we can get the response size (not required)
      format: winston.format.json(),
      ignoreRoute: (request) => request?.route?.path === '/user/login',
      dynamicMeta: (req, res) => {
        const httpRequest = {};
        const meta = {};
        if (req) {
          meta.httpRequest = httpRequest;
          httpRequest.requestMethod = req.method;
          httpRequest.requestUrl = `${req.protocol}://${req.get('host')}${req.originalUrl}`;
          httpRequest.remoteIp = req.ip.indexOf(':') >= 0 ? req.ip.substring(req.ip.lastIndexOf(':') + 1) : req.ip; // just ipv4
          httpRequest.requestSize = req.socket.bytesRead;
          httpRequest.userAgent = req.get('User-Agent');
          httpRequest.referrer = req.get('Referrer');
        }

        if (res) {
          meta.httpRequest = httpRequest;
          httpRequest.status = res.statusCode;
          httpRequest.latency = {
            seconds: Math.floor(res.responseTime / 1000),
            nanos: (res.responseTime % 1000) * 1000000,
          };
          if (res.body) {
            if (typeof res.body === 'object') {
              httpRequest.responseSize = JSON.stringify(res.body).length;
            } else if (typeof res.body === 'string') {
              httpRequest.responseSize = res.body.length;
            }
          }
        }
        return meta;
      },
      meta: true,
    })
  );
}

function handleAsyncErrors(callback) {
  // could be replaced with https://www.npmjs.com/package/express-async-errors
  // this will be done by default at express 5.0
  return (request, response, next) => {
    callback(request, response, next).catch(next);
    // passing an error to next returns an HTML error page, maybe not ideal
  };
}

// user management
app.post('/user/token', handleAsyncErrors(getToken));
app.post(
  '/user/register',
  handleAsyncErrors(authenticate),
  validate(registerSchema),
  handleAsyncErrors(getRegister(isExistingUser, registerUser, getUser))
);
app.post(
  '/user/login',
  handleAsyncErrors(authenticate),
  validate(loginSchema),
  handleAsyncErrors(getLogin(getUser, updateUser))
);
app.post('/user/logout', handleAsyncErrors(logout));
app.get('/user/approval/:guid/:role', handleAsyncErrors(getApprove(approveUser)));
app.get('/user/reject/:guid', handleAsyncErrors(getReject(rejectUser)));
// app.delete('/user/delete/:email/:auth_provider', handleAsyncErrors(del)); // TODO for facebook delete requirements

// data submission
app.post(
  '/reports/report',
  handleAsyncErrors(authenticate),
  multer.single('photo'),
  validate(reportSchema.omit(['photo'])),
  handleAsyncErrors(getNewReportHandler(upload, createReport))
);
app.post(
  '/reports/pickup',
  handleAsyncErrors(authenticate),
  multer.single('photo'),
  validate(pickupSchema.omit(['photo'])),
  handleAsyncErrors(getNewPickupHandler(upload, createPickup))
);

app.use(
  expressWinston.errorLogger({
    transports: [new winston.transports.Console()],
    format: winston.format.combine(winston.format.colorize(), winston.format.json()),
  })
);

export default app;
<commit_msg>fix[back]: increase maximum file upload size<commit_after>import { LoggingWinston } from '@google-cloud/logging-winston';
import { pickup as pickupSchema, report as reportSchema } from 'common/validation/reports.js';
import compression from 'compression';
import cors from 'cors';
import express from 'express';
import expressWinston from 'express-winston';
import helmet from 'helmet';
import Multer from 'multer';
import winston from 'winston';
import { getNewPickupHandler, getNewReportHandler } from './api/reports.js';
import { authenticate, getToken, logout } from './api/security.js';
import { getApprove, getLogin, getRegister, getReject } from './api/user.js';
import validate from './api/validation.js';
import { upload } from './services/photos.js';
import { createPickup, createReport } from './services/report_management.js';
import {
  approveUser,
  getUser,
  isExistingUser,
  loginSchema,
  registerSchema,
  registerUser,
  rejectUser,
  updateUser,
} from './services/user_management.js';

// app setup
const app = express();

// gzip
app.use(compression());

// enable x-www-form-urlencoded body format
app.use(express.urlencoded({ extended: true }));

// parse application/json
app.use(express.json());

const multer = Multer({
  storage: Multer.memoryStorage(),
  limits: {
    fileSize: 20 * 1024 * 1024, // 20mb
  },
});

// security best practices
app.use(helmet());

app.use(cors());

if (process.env.ENVIRONMENT !== 'development' && process.env.ENVIRONMENT !== 'test') {
  app.use(
    expressWinston.logger({
      transports: [new LoggingWinston()],
      metaField: null, //this causes the metadata to be stored at the root of the log entry
      responseField: null, // this prevents the response from being included in the metadata (including body and status code)
      requestWhitelist: ['url', 'method', 'httpVersion', 'originalUrl', 'query', 'body'],
      responseWhitelist: ['body'], // this populates the `res.body` so we can get the response size (not required)
      format: winston.format.json(),
      ignoreRoute: (request) => request?.route?.path === '/user/login',
      dynamicMeta: (req, res) => {
        const httpRequest = {};
        const meta = {};
        if (req) {
          meta.httpRequest = httpRequest;
          httpRequest.requestMethod = req.method;
          httpRequest.requestUrl = `${req.protocol}://${req.get('host')}${req.originalUrl}`;
          httpRequest.remoteIp = req.ip.indexOf(':') >= 0 ? req.ip.substring(req.ip.lastIndexOf(':') + 1) : req.ip; // just ipv4
          httpRequest.requestSize = req.socket.bytesRead;
          httpRequest.userAgent = req.get('User-Agent');
          httpRequest.referrer = req.get('Referrer');
        }

        if (res) {
          meta.httpRequest = httpRequest;
          httpRequest.status = res.statusCode;
          httpRequest.latency = {
            seconds: Math.floor(res.responseTime / 1000),
            nanos: (res.responseTime % 1000) * 1000000,
          };
          if (res.body) {
            if (typeof res.body === 'object') {
              httpRequest.responseSize = JSON.stringify(res.body).length;
            } else if (typeof res.body === 'string') {
              httpRequest.responseSize = res.body.length;
            }
          }
        }
        return meta;
      },
      meta: true,
    })
  );
}

function handleAsyncErrors(callback) {
  // could be replaced with https://www.npmjs.com/package/express-async-errors
  // this will be done by default at express 5.0
  return (request, response, next) => {
    callback(request, response, next).catch(next);
    // passing an error to next returns an HTML error page, maybe not ideal
  };
}

// user management
app.post('/user/token', handleAsyncErrors(getToken));
app.post(
  '/user/register',
  handleAsyncErrors(authenticate),
  validate(registerSchema),
  handleAsyncErrors(getRegister(isExistingUser, registerUser, getUser))
);
app.post(
  '/user/login',
  handleAsyncErrors(authenticate),
  validate(loginSchema),
  handleAsyncErrors(getLogin(getUser, updateUser))
);
app.post('/user/logout', handleAsyncErrors(logout));
app.get('/user/approval/:guid/:role', handleAsyncErrors(getApprove(approveUser)));
app.get('/user/reject/:guid', handleAsyncErrors(getReject(rejectUser)));
// app.delete('/user/delete/:email/:auth_provider', handleAsyncErrors(del)); // TODO for facebook delete requirements

// data submission
app.post(
  '/reports/report',
  handleAsyncErrors(authenticate),
  multer.single('photo'),
  validate(reportSchema.omit(['photo'])),
  handleAsyncErrors(getNewReportHandler(upload, createReport))
);
app.post(
  '/reports/pickup',
  handleAsyncErrors(authenticate),
  multer.single('photo'),
  validate(pickupSchema.omit(['photo'])),
  handleAsyncErrors(getNewPickupHandler(upload, createPickup))
);

app.use(
  expressWinston.errorLogger({
    transports: [new winston.transports.Console()],
    format: winston.format.combine(winston.format.colorize(), winston.format.json()),
  })
);

export default app;
<|endoftext|>


<commit_before>//This file is generated by bin/hook.js
var _ = require('underscore');
module.exports = { 'media_control': _.template('
  <% var renderBar = function(name) { %>      <div class="bar-container" data-<%= name %>>        <div class="bar-background" data-<%= name %>>          <div class="bar-fill-1" data-<%= name %>>
          <div class="bar-fill-2" data-<%= name %>>

        <div class="bar-scrubber" data-<%= name %>>          <div class="bar-scrubber-icon" data-<%= name %>>

  <% }; %>  <% var renderDrawer = function(name, renderContent) { %>      <div class="drawer-container" data-<%= name %>>        <div class="drawer-icon-container" data-<%= name %>>          <div class="drawer-icon media-control-icon" data-<%= name %>>
          <span class="drawer-text" data-<%= name %>>
        <% renderContent(name); %>
  <% }; %>  <% var renderIndicator = function(name) { %>      <div class="media-control-indicator" data-<%= name %>>
  <% }; %>  <% var renderButton = function(name) { %>      <button class="media-control-button media-control-icon" data-<%= name %>></button>  <% }; %>  <% var render = function(settings) {      _.each(settings, function(setting) {        if(setting === "seekbar") {          renderBar(setting);        } else if (setting === "volume") {          renderDrawer(setting, renderBar);        } else if (setting === "duration" || setting === "position") {          renderIndicator(setting);        } else {          renderButton(setting);        }      });    }; %>  <% if (settings.left && settings.left.length) { %>
    <% render(settings.left); %>
  <% } %>  <% if (settings.right && settings.right.length) { %>
    <% render(settings.right); %>
  <% } %>  <% if (settings.default && settings.default.length) { %>
    <% render(settings.default); %>
  <% } %>
'),'flash_vod': _.template('  <param name="movie" value="<%= swfPath %>">  <param name="quality" value="autohigh">  <param name="swliveconnect" value="true">  <param name="allowScriptAccess" value="always">  <param name="bgcolor" value="#001122">  <param name="allowFullScreen" value="false">  <param name="wmode" value="gpu">  <param name="tabindex" value="1">  <param name=FlashVars value="playbackId=<%= playbackId %>" />  <embed    type="application/x-shockwave-flash"    disabled="disabled"    tabindex="-1"    enablecontextmenu="false"    allowScriptAccess="always"    quality="autohight"    pluginspage="http://www.macromedia.com/go/getflashplayer"    wmode="gpu"    swliveconnect="true"    type="application/x-shockwave-flash"    allowfullscreen="false"    bgcolor="#000000"    FlashVars="playbackId=<%= playbackId %>"    src="<%= swfPath %>">  </embed>'),'hls': _.template('  <param name="movie" value="<%= swfPath %>?inline=1">  <param name="quality" value="autohigh">  <param name="swliveconnect" value="true">  <param name="allowScriptAccess" value="always">  <param name="bgcolor" value="#001122">  <param name="allowFullScreen" value="false">  <param name="wmode" value="transparent">  <param name="tabindex" value="1">  <param name=FlashVars value="playbackId=<%= playbackId %>" />  <embed    type="application/x-shockwave-flash"    tabindex="1"    enablecontextmenu="false"    allowScriptAccess="always"    quality="autohigh"    pluginspage="http://www.macromedia.com/go/getflashplayer"    wmode="transparent"    swliveconnect="true"    type="application/x-shockwave-flash"    allowfullscreen="false"    bgcolor="#000000"    FlashVars="playbackId=<%= playbackId %>"    src="<%= swfPath %>">  </embed>'),'poster': _.template('

'),'spinner_loading': _.template('

'),'spinner_three_bounce': _.template('

'),'watermark': _.template('<div data-watermark data-watermark-<%=position %>><img src="<%= imageUrl %>">
'),CSS: {'container': '[data-container]{position:absolute;background-color:#000;height:100%;width:100%}','core': '[data-player] *{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;user-select:none;margin:0;padding:0;border:0;font-style:normal;font-weight:400;text-align:center;font-size:100%;color:#000;font-family:"lucida grande",tahoma,verdana,arial,sans-serif;text-shadow:0 0 0;box-sizing:border-box}[data-player] * a,[data-player] * abbr,[data-player] * acronym,[data-player] * address,[data-player] * applet,[data-player] * article,[data-player] * aside,[data-player] * audio,[data-player] * b,[data-player] * big,[data-player] * blockquote,[data-player] * canvas,[data-player] * caption,[data-player] * center,[data-player] * cite,[data-player] * code,[data-player] * dd,[data-player] * del,[data-player] * details,[data-player] * dfn,[data-player] * div,[data-player] * dl,[data-player] * dt,[data-player] * em,[data-player] * embed,[data-player] * fieldset,[data-player] * figcaption,[data-player] * figure,[data-player] * footer,[data-player] * form,[data-player] * h1,[data-player] * h2,[data-player] * h3,[data-player] * h4,[data-player] * h5,[data-player] * h6,[data-player] * header,[data-player] * hgroup,[data-player] * i,[data-player] * iframe,[data-player] * img,[data-player] * ins,[data-player] * kbd,[data-player] * label,[data-player] * legend,[data-player] * li,[data-player] * mark,[data-player] * menu,[data-player] * nav,[data-player] * object,[data-player] * ol,[data-player] * output,[data-player] * p,[data-player] * pre,[data-player] * q,[data-player] * ruby,[data-player] * s,[data-player] * samp,[data-player] * section,[data-player] * small,[data-player] * span,[data-player] * strike,[data-player] * strong,[data-player] * sub,[data-player] * summary,[data-player] * sup,[data-player] * table,[data-player] * tbody,[data-player] * td,[data-player] * tfoot,[data-player] * th,[data-player] * thead,[data-player] * time,[data-player] * tr,[data-player] * tt,[data-player] * u,[data-player] * ul,[data-player] * var,[data-player] * video{margin:0;padding:0;border:0;font:inherit;font-size:100%;vertical-align:baseline}[data-player] * table{border-collapse:collapse;border-spacing:0}[data-player] * caption,[data-player] * td,[data-player] * th{text-align:left;font-weight:400;vertical-align:middle}[data-player] * blockquote,[data-player] * q{quotes:none}[data-player] * blockquote:after,[data-player] * blockquote:before,[data-player] * q:after,[data-player] * q:before{content:"";content:none}[data-player] * a img{border:none}[data-player]{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-webkit-transform:translate3d(0,0,0);-moz-transform:translate3d(0,0,0);transform:translate3d(0,0,0);position:relative;margin:0;height:594px;width:1055px;text-align:center;overflow:hidden}[data-player].fullscreen{width:100%;height:100%}[data-player].nocursor{cursor:none}','media_control': '@font-face{font-family:Player;src:url(assets/Player-Regular.eot);src:url(assets/Player-Regular.eot?#iefix) format("embedded-opentype"),url(assets/Player-Regular.ttf) format("truetype"),url(assets/Player-Regular.svg#player) format("svg")}.media-control[data-media-control]{position:absolute;background-color:rgba(2,2,2,.5);border-radius:0;bottom:0;left:0;right:0;margin-left:auto;margin-right:auto;max-width:100%;min-width:60%;height:40px;z-index:9999;-webkit-transition:all .4s ease-out;-moz-transition:all .4s ease-out;-ms-transition:all .4s ease-out;-o-transition:all .4s ease-out;transition:all .4s ease-out}.media-control[data-media-control] .media-control-icon{font-family:Player;font-weight:400;font-style:normal;font-size:26px;line-height:32px;letter-spacing:0;speak:none;color:#fff;vertical-align:middle;text-align:left;padding:0 6px;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.media-control[data-media-control].media-control-hide{bottom:-40px}.media-control[data-media-control].media-control-hide .media-control-layer[data-controls] .bar-container[data-seekbar] .bar-scrubber[data-seekbar]{opacity:0}.media-control[data-media-control] .media-control-layer[data-controls]{position:relative;top:10%;height:80%;vertical-align:middle}.media-control[data-media-control] .media-control-layer[data-controls] .media-control-left-panel[data-media-control]{position:absolute;top:0;left:5px;height:100%}.media-control[data-media-control] .media-control-layer[data-controls] .media-control-center-panel[data-media-control]{height:100%;text-align:center;line-height:32px}.media-control[data-media-control] .media-control-layer[data-controls] .media-control-right-panel[data-media-control]{position:absolute;top:0;right:5px;height:100%}.media-control[data-media-control] .media-control-layer[data-controls] button.media-control-button{background-color:transparent;border:0;margin:0 8px;cursor:pointer;display:inline-block}.media-control[data-media-control] .media-control-layer[data-controls] button.media-control-button:focus{outline:0}.media-control[data-media-control] .media-control-layer[data-controls] button.media-control-button[data-play]{float:left;width:32px;height:100%}.media-control[data-media-control] .media-control-layer[data-controls] button.media-control-button[data-play]:before{content:"\\e001"}.media-control[data-media-control] .media-control-layer[data-controls] button.media-control-button[data-pause]{float:left;width:32px;height:100%}.media-control[data-media-control] .media-control-layer[data-controls] button.media-control-button[data-pause]:before{content:"\\e002"}.media-control[data-media-control] .media-control-layer[data-controls] button.media-control-button[data-stop]{float:left;width:32px;height:100%}.media-control[data-media-control] .media-control-layer[data-controls] button.media-control-button[data-stop]:before{content:"\\e003"}.media-control[data-media-control] .media-control-layer[data-controls] button.media-control-button[data-fullscreen]{float:right;background-color:transparent;border:0;width:32px;height:100%}.media-control[data-media-control] .media-control-layer[data-controls] button.media-control-button[data-fullscreen]:before{content:"\\e006"}.media-control[data-media-control] .media-control-layer[data-controls] button.media-control-button[data-hd-indicator]{cursor:default;float:right;background-color:transparent;border:0;width:32px;height:100%;opacity:0}.media-control[data-media-control] .media-control-layer[data-controls] button.media-control-button[data-hd-indicator]:before{content:"\\e007"}.media-control[data-media-control] .media-control-layer[data-controls] button.media-control-button[data-hd-indicator].enabled{opacity:1}.media-control[data-media-control] .media-control-layer[data-controls] button.media-control-button[data-playpause]{float:left;width:32px;height:100%}.media-control[data-media-control] .media-control-layer[data-controls] button.media-control-button[data-playpause]:before{content:"\\e001"}.media-control[data-media-control] .media-control-layer[data-controls] button.media-control-button[data-playpause].playing:before{content:"\\e002"}.media-control[data-media-control] .media-control-layer[data-controls] button.media-control-button[data-playpause].paused:before{content:"\\e001"}.media-control[data-media-control] .media-control-layer[data-controls] button.media-control-button[data-playstop]{float:left;width:32px;height:100%}.media-control[data-media-control] .media-control-layer[data-controls] button.media-control-button[data-playstop]:before{content:"\\e001"}.media-control[data-media-control] .media-control-layer[data-controls] button.media-control-button[data-playstop].playing:before{content:"\\e003"}.media-control[data-media-control] .media-control-layer[data-controls] button.media-control-button[data-playstop].stopped:before{content:"\\e001"}.media-control[data-media-control] .media-control-layer[data-controls] .media-control-indicator[data-duration],.media-control[data-media-control] .media-control-layer[data-controls] .media-control-indicator[data-position]{display:inline-block;font-size:13px;color:#fff;cursor:default;line-height:32px;position:relative}.media-control[data-media-control] .media-control-layer[data-controls] .media-control-indicator[data-duration]:before{content:"/";margin-right:3px}.media-control[data-media-control] .media-control-layer[data-controls] .bar-container[data-seekbar]{position:absolute;top:-20px;left:0;display:inline-block;vertical-align:middle;width:100%;height:32px;cursor:pointer}.media-control[data-media-control] .media-control-layer[data-controls] .bar-container[data-seekbar] .bar-background[data-seekbar]{width:100%;height:8px;position:relative;top:12px;background-color:#6f6f6f;overflow:hidden}.media-control[data-media-control] .media-control-layer[data-controls] .bar-container[data-seekbar] .bar-background[data-seekbar] .bar-fill-1[data-seekbar]{position:absolute;top:0;left:0;width:0;height:100%;background-color:#bebebe;-webkit-transition:all .1s ease-out;-moz-transition:all .1s ease-out;-ms-transition:all .1s ease-out;-o-transition:all .1s ease-out;transition:all .1s ease-out}.media-control[data-media-control] .media-control-layer[data-controls] .bar-container[data-seekbar] .bar-background[data-seekbar] .bar-fill-2[data-seekbar]{position:absolute;top:0;left:0;width:0;height:100%;background-color:#fff;-webkit-transition:all .1s ease-out;-moz-transition:all .1s ease-out;-ms-transition:all .1s ease-out;-o-transition:all .1s ease-out;transition:all .1s ease-out}.media-control[data-media-control] .media-control-layer[data-controls] .bar-container[data-seekbar] .bar-scrubber[data-seekbar]{position:absolute;top:6px;left:0;width:20px;height:20px;opacity:1;-webkit-transition:all .1s ease-out;-moz-transition:all .1s ease-out;-ms-transition:all .1s ease-out;-o-transition:all .1s ease-out;transition:all .1s ease-out}.media-control[data-media-control] .media-control-layer[data-controls] .bar-container[data-seekbar] .bar-scrubber[data-seekbar] .bar-scrubber-icon[data-seekbar]{position:absolute;left:3px;top:3px;width:14px;height:14px;border-radius:6px;border:1px solid #6f6f6f;background-color:#fff}.media-control[data-media-control] .media-control-layer[data-controls] .drawer-container[data-volume]{float:right;display:inline-block;width:32px;height:32px;cursor:pointer}.media-control[data-media-control] .media-control-layer[data-controls] .drawer-container[data-volume] .drawer-icon-container[data-volume]{position:absolute;bottom:0}.media-control[data-media-control] .media-control-layer[data-controls] .drawer-container[data-volume] .drawer-icon-container[data-volume] .drawer-icon[data-volume]{background-color:transparent;border:0;width:32px;height:32px}.media-control[data-media-control] .media-control-layer[data-controls] .drawer-container[data-volume] .drawer-icon-container[data-volume] .drawer-icon[data-volume]:before{content:"\\e004"}.media-control[data-media-control] .media-control-layer[data-controls] .drawer-container[data-volume] .drawer-icon-container[data-volume] .drawer-icon[data-volume].muted:before{content:"\\e005"}.media-control[data-media-control] .media-control-layer[data-controls] .drawer-container[data-volume] .bar-container[data-volume]{width:32px;height:88px;position:absolute;bottom:40px;background:rgba(2,2,2,.5);border-radius:4px;-webkit-transition:all .2s ease-out;-moz-transition:all .2s ease-out;-ms-transition:all .2s ease-out;-o-transition:all .2s ease-out;transition:all .2s ease-out}.media-control[data-media-control] .media-control-layer[data-controls] .drawer-container[data-volume] .bar-container[data-volume].volume-bar-hide{opacity:0}.media-control[data-media-control] .media-control-layer[data-controls] .drawer-container[data-volume] .bar-container[data-volume] .bar-background[data-volume]{margin-left:12px;background:#6f6f6f;border-radius:4px;width:8px;height:72px;position:relative;top:8px;overflow:hidden}.media-control[data-media-control] .media-control-layer[data-controls] .drawer-container[data-volume] .bar-container[data-volume] .bar-background[data-volume] .bar-fill-1[data-volume]{position:absolute;bottom:0;background:#fff;width:100%;height:0}.media-control[data-media-control] .media-control-layer[data-controls] .drawer-container[data-volume] .bar-container[data-volume] .bar-scrubber[data-volume]{position:absolute;bottom:40%;left:6px;width:20px;height:20px}.media-control[data-media-control] .media-control-layer[data-controls] .drawer-container[data-volume] .bar-container[data-volume] .bar-scrubber[data-volume] .bar-scrubber-icon[data-volume]{position:absolute;left:4px;top:4px;width:12px;height:12px;border-radius:6px;border:1px solid #6f6f6f;background-color:#fff}','flash_vod': '[data-flash-vod]{position:absolute;height:100%;width:100%;background-color:#000;display:block;pointer-events:none}','hls': '[data-hls]{position:absolute;height:100%;width:100%;background-color:#000;display:block;pointer-events:none}','html5_video': '[data-html5-video]{position:absolute;height:100%;width:100%;display:block}','pip': '.pip-loading[data-pip]{left:25px;top:15px;position:relative;float:left;z-index:3001;color:#fff}.pip-transition[data-pip]{-webkit-transition:all .4s ease-out;-moz-transition:all .4s ease-out;-ms-transition:all .4s ease-out;-o-transition:all .4s ease-out;transition:all .4s ease-out}.master-container[data-pip]{cursor:default;width:100%;height:100%;font-size:100%;position:absolute;bottom:0;right:0;border:none}.pip-container[data-pip]{cursor:pointer;width:24%;height:24%;font-size:24%;z-index:2000;position:absolute;right:23px;bottom:23px;border-width:2px;border-radius:3px;border-style:solid;border-color:rgba(255,255,255,.25);background-clip:padding-box;-webkit-background-clip:padding-box}.pip-container[data-pip].over-media-control{bottom:63px}','poster': '@font-face{font-family:Player;src:url(assets/Player-Regular.eot);src:url(assets/Player-Regular.eot?#iefix) format("embedded-opentype"),url(assets/Player-Regular.ttf) format("truetype"),url(assets/Player-Regular.svg#player) format("svg")}.player-poster[data-poster]{cursor:pointer;position:absolute;height:100%;width:100%;z-index:998;top:0}.player-poster[data-poster] .poster-background[data-poster]{width:100%;height:100%}.player-poster[data-poster] .play-wrapper[data-poster]{position:absolute;overflow:hidden;width:100%;height:20%;line-height:100%;font-size:20%;top:50%;margin-top:-5%;text-align:center}.player-poster[data-poster] .play-wrapper[data-poster] .poster-icon[data-poster]{font-family:Player;font-weight:400;font-style:normal;line-height:1;letter-spacing:0;speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;color:#fff;opacity:.75}.player-poster[data-poster] .play-wrapper[data-poster] .poster-icon[data-poster].play[data-poster]:before{content:"\\e001"}.player-poster[data-poster] .play-wrapper[data-poster] .poster-icon[data-poster]:hover{opacity:1}','spinner_loading': 'div[data-spinner]{width:50px;height:50px;position:relative;margin-left:auto;margin-right:auto;right:0;left:0;z-index:999;top:45%}.spin-container1>div,.spin-container2>div,.spin-container3>div{width:13px;height:13px;background-color:#fff;border-radius:100%;position:absolute;-webkit-animation:bouncedelay 1.2s infinite ease-in-out;animation:bouncedelay 1.2s infinite ease-in-out;-webkit-animation-fill-mode:both;animation-fill-mode:both}[data-spinner] [data-spinner-container]{position:absolute;width:100%;height:100%}.spin-container2{-webkit-transform:rotateZ(45deg);transform:rotateZ(45deg)}.spin-container3{-webkit-transform:rotateZ(90deg);transform:rotateZ(90deg)}[data-circle1]{top:0;left:0}[data-circle2]{top:0;right:0}[data-circle3]{right:0;bottom:0}[data-circle4]{left:0;bottom:0}.spin-container2 [data-circle1]{-webkit-animation-delay:-1.1s;animation-delay:-1.1s}.spin-container3 [data-circle1]{-webkit-animation-delay:-1s;animation-delay:-1s}.spin-container1 [data-circle2]{-webkit-animation-delay:-.9s;animation-delay:-.9s}.spin-container2 [data-circle2]{-webkit-animation-delay:-.8s;animation-delay:-.8s}.spin-container3 [data-circle2]{-webkit-animation-delay:-.7s;animation-delay:-.7s}.spin-container1 [data-circle3]{-webkit-animation-delay:-.6s;animation-delay:-.6s}.spin-container2 [data-circle3]{-webkit-animation-delay:-.5s;animation-delay:-.5s}.spin-container3 [data-circle3]{-webkit-animation-delay:-.4s;animation-delay:-.4s}.spin-container1 [data-circle4]{-webkit-animation-delay:-.3s;animation-delay:-.3s}.spin-container2 [data-circle4]{-webkit-animation-delay:-.2s;animation-delay:-.2s}.spin-container3 [data-circle4]{-webkit-animation-delay:-.1s;animation-delay:-.1s}@-webkit-keyframes bouncedelay{0%,100%,80%{-webkit-transform:scale(0)}40%{-webkit-transform:scale(1)}}@keyframes bouncedelay{0%,100%,80%{transform:scale(0);-webkit-transform:scale(0)}40%{transform:scale(1);-webkit-transform:scale(1)}}','spinner_three_bounce': '.spinner-three-bounce[data-spinner]{position:absolute;margin:0 auto;width:70px;text-align:center;z-index:10;top:20%;left:0;right:0}.spinner-three-bounce[data-spinner]>div{width:18px;height:18px;background-color:#FFF;border-radius:100%;display:inline-block;-webkit-animation:bouncedelay 1.4s infinite ease-in-out;-moz-animation:bouncedelay 1.4s infinite ease-in-out;-o-animation:bouncedelay 1.4s infinite ease-in-out;animation:bouncedelay 1.4s infinite ease-in-out;-webkit-animation-fill-mode:both;-moz-animation-fill-mode:both;-o-animation-fill-mode:both;animation-fill-mode:both}.spinner-three-bounce[data-spinner] [data-bounce1]{-webkit-animation-delay:-.32s;animation-delay:-.32s}.spinner-three-bounce[data-spinner] [data-bounce2]{-webkit-animation-delay:-.16s;animation-delay:-.16s}@-webkit-keyframes bouncedelay{0%,100%,80%{-webkit-transform:scale(0);-moz-transform:scale(0);transform:scale(0)}40%{-webkit-transform:scale(1);-moz-transform:scale(1);transform:scale(1)}}@-moz-keyframes bouncedelay{0%,100%,80%{-webkit-transform:scale(0);-moz-transform:scale(0);transform:scale(0)}40%{-webkit-transform:scale(1);-moz-transform:scale(1);transform:scale(1)}}@-ms-keyframes bouncedelay{0%,100%,80%{-webkit-transform:scale(0);-moz-transform:scale(0);transform:scale(0)}40%{-webkit-transform:scale(1);-moz-transform:scale(1);transform:scale(1)}}@keyframes bouncedelay{0%,100%,80%{-webkit-transform:scale(0);-moz-transform:scale(0);transform:scale(0)}40%{-webkit-transform:scale(1);-moz-transform:scale(1);transform:scale(1)}}','watermark': '[data-watermark]{position:absolute;margin:100px auto 0;width:70px;text-align:center;z-index:10}[data-watermark-bottom-left]{bottom:10px;left:10px}[data-watermark-bottom-right]{bottom:10px;right:42px}[data-watermark-top-left]{top:-95px;left:10px}[data-watermark-top-right]{top:-95px;right:37px}',}};<commit_msg>remove jst<commit_after><|endoftext|>


<commit_before>/**
 * Utility functions for working with sagas.
 *
 * @module sagaUtils
 */
import { includes } from "lodash-es";
import { push } from "connected-react-router";
import { matchPath } from "react-router-dom";
import { SET_APP_PENDING, UNSET_APP_PENDING } from "../app/actionTypes";
import { createFindURL } from "./utils";
import { put, all } from "redux-saga/effects";

/**
 * Executes an API call by calling ``apiMethod`` with ``action``.
 *
 * If the call succeeds an action with ``actionType.SUCCEEDED`` and a ``data`` property is dispatched.
 *
 * If the call fails an action with ``actionType.FAILED`` and generic error properties is dispatched.
 *
 * @generator
 * @param apiMethod {function} the function to call with ``action``
 * @param action {object} an action to pass to ``apiMethod``
 * @param actionType {object} a request-style action type
 * @param extra {object} extra properties to assign to the SUCCEEDED action
 */
export function* apiCall(apiMethod, action, actionType, extra = {}, extraFunctions) {
    try {
        const response = yield apiMethod(action);
        yield put({ type: actionType.SUCCEEDED, data: response.body, ...extra });
        if (extraFunctions) {
            yield all(extraFunctions);
        }
    } catch (error) {
        if (error.response.statusCode === 401) {
            window.location = `/login?expired=true&return_to=${window.location.pathname}`;
        } else {
            yield putGenericError(actionType, error);
        }
    }
}

/**
 * Executes an API call that uses Virtool's find implementation when the browser URL matches to ``path``.
 *
 * This generator is intended to be used in a saga triggered by ``LOCATION_CHANGE`` from ``connected-react-router``. If the
 * ``path`` matches the current browser URL and API request will be sent to the server with the search parameter
 * (if any) appended to the request URL.
 *
 * The actual API call is made using {@link apiCall}.
 *
 * @generator
 * @param path {string} the path that, when matched, will allow find calls
 * @param apiMethod {function} the API function to call
 * @param action {object} the action to pass to the ``apiMethod``
 * @param actionType {object} the request-style action type to dispatch when the call completes
 */
export function* apiFind(path, apiMethod, action, actionType) {
    const { pathname } = action.payload;

    const match = matchPath(pathname, { path, exact: true });

    if (match) {
        yield apiCall(apiMethod, {}, actionType);
    }
}

export function* pushFindTerm(term, contains) {
    const url = createFindURL(term);

    if (!contains || includes(url.pathname, contains)) {
        yield put(push(url.pathname + url.search));
    }
}

/**
 * Pushes new user-defined state to history.
 *
 * @generator
 * @param update {object} a new state object
 */
export function* pushHistoryState(update) {
    yield put(push({ ...window.location, state: update }));
}

/**
 * Should be called in the event of an HTTP error during an API call. Dispatches a ``FAILED`` request-style action
 * containing data related to the HTTP error.
 *
 * @generator
 * @param actionType {object} a request-style action type
 * @param error {object} the HTTP error from Superagent
 */
export function* putGenericError(actionType, error) {
    const { body, status } = error.response;

    yield put({
        type: actionType.FAILED,
        message: body.message,
        error,
        status
    });
}

/**
 * Dispatches actions while yielding the passed ``generator`` that result in a GitHub-style loading bar progressing
 * across the page just below the navigation bar.
 *
 * @generator
 * @param generator {generator} the generator to yield while the bar is progressing *
 */
export function* setPending(generator) {
    yield put({ type: SET_APP_PENDING });
    yield generator;
    yield put({ type: UNSET_APP_PENDING });
}
<commit_msg>Handle errors that bubble into sagas<commit_after>/**
 * Utility functions for working with sagas.
 *
 * @module sagaUtils
 */
import { get, includes } from "lodash-es";
import { push } from "connected-react-router";
import { matchPath } from "react-router-dom";
import { SET_APP_PENDING, UNSET_APP_PENDING } from "../app/actionTypes";
import { createFindURL } from "./utils";
import { put, all } from "redux-saga/effects";

/**
 * Executes an API call by calling ``apiMethod`` with ``action``.
 *
 * If the call succeeds an action with ``actionType.SUCCEEDED`` and a ``data`` property is dispatched.
 *
 * If the call fails an action with ``actionType.FAILED`` and generic error properties is dispatched.
 *
 * @generator
 * @param apiMethod {function} the function to call with ``action``
 * @param action {object} an action to pass to ``apiMethod``
 * @param actionType {object} a request-style action type
 * @param extra {object} extra properties to assign to the SUCCEEDED action
 */
export function* apiCall(apiMethod, action, actionType, extra = {}, extraFunctions) {
    try {
        const response = yield apiMethod(action);
        yield put({ type: actionType.SUCCEEDED, data: response.body, ...extra });
        if (extraFunctions) {
            yield all(extraFunctions);
        }
    } catch (error) {
        const statusCode = get(error, "response.statusCode");

        if (statusCode === 401) {
            window.location = `/login?expired=true&return_to=${window.location.pathname}`;
            return;
        }

        if (get(error, "response.body")) {
            yield putGenericError(actionType, error);
        }

        throw error;
    }
}

/**
 * Executes an API call that uses Virtool's find implementation when the browser URL matches to ``path``.
 *
 * This generator is intended to be used in a saga triggered by ``LOCATION_CHANGE`` from ``connected-react-router``. If the
 * ``path`` matches the current browser URL and API request will be sent to the server with the search parameter
 * (if any) appended to the request URL.
 *
 * The actual API call is made using {@link apiCall}.
 *
 * @generator
 * @param path {string} the path that, when matched, will allow find calls
 * @param apiMethod {function} the API function to call
 * @param action {object} the action to pass to the ``apiMethod``
 * @param actionType {object} the request-style action type to dispatch when the call completes
 */
export function* apiFind(path, apiMethod, action, actionType) {
    const { pathname } = action.payload;

    const match = matchPath(pathname, { path, exact: true });

    if (match) {
        yield apiCall(apiMethod, {}, actionType);
    }
}

export function* pushFindTerm(term, contains) {
    const url = createFindURL(term);

    if (!contains || includes(url.pathname, contains)) {
        yield put(push(url.pathname + url.search));
    }
}

/**
 * Pushes new user-defined state to history.
 *
 * @generator
 * @param update {object} a new state object
 */
export function* pushHistoryState(update) {
    yield put(push({ ...window.location, state: update }));
}

/**
 * Should be called in the event of an HTTP error during an API call. Dispatches a ``FAILED`` request-style action
 * containing data related to the HTTP error.
 *
 * @generator
 * @param actionType {object} a request-style action type
 * @param error {object} the HTTP error from Superagent
 */
export function* putGenericError(actionType, error) {
    const { body, status } = error.response;

    yield put({
        type: actionType.FAILED,
        message: body.message,
        error,
        status
    });
}

/**
 * Dispatches actions while yielding the passed ``generator`` that result in a GitHub-style loading bar progressing
 * across the page just below the navigation bar.
 *
 * @generator
 * @param generator {generator} the generator to yield while the bar is progressing *
 */
export function* setPending(generator) {
    yield put({ type: SET_APP_PENDING });
    yield generator;
    yield put({ type: UNSET_APP_PENDING });
}
<|endoftext|>


<commit_before>import Cell from './Cell';
import Color from './Color';
import {encodeToVT100} from './util/encodeToVT100';

/**
 * Cursor implements low-level API to terminal control codes.
 *
 * @see http://www.termsys.demon.co.uk/vtansi.htm
 * @see http://misc.flogisoft.com/bash/tip_colors_and_formatting
 * @see http://man7.org/linux/man-pages/man4/console_codes.4.html
 * @see http://www.x.org/docs/xterm/ctlseqs.pdf
 * @see http://wiki.bash-hackers.org/scripting/terminalcodes
 * @since 1.0.0
 */
export default class Cursor {
  /**
   * Creates cursor that writes direct to `stdout`.
   * You can override target stream with another one.
   * Also, you can specify custom width and height of viewport where cursor will render the frame.
   *
   * @constructor
   * @param {Object} [options] Object with options
   * @param {Stream} [options.stream=process.stdout] Writable stream
   * @param {Number} [options.width=stream.columns] Number of columns (width)
   * @param {Number} [options.height=stream.rows] Number of rows (height)
   */
  constructor(options = {}) {
    const {stream = process.stdout, width = stream.columns, height = stream.rows} = options;

    this._stream = stream;
    this._width = width;
    this._height = height;

    this._x = 0;
    this._y = 0;
    this._background = {r: -1, g: -1, b: -1};
    this._foreground = {r: -1, g: -1, b: -1};
    this._display = {bold: false, dim: false, underlined: false, blink: false, reverse: false, hidden: false};

    this._cells = Array.from({length: width * height}).map(() => new Cell());
  }

  /**
   * Write to the stream.
   * It doesn't applies immediately but stores in virtual terminal that represented as array of {@link Cell} instances.
   * For applying changes you need to {@link flush} changes.
   *
   * @param {String} data Data to write to the terminal
   * @returns {Cursor}
   */
  write(data) {
    data.split('').forEach(char => {
      if (0 <= this._x && this._x < this._width && 0 <= this._y && this._y < this._height) {
        this._cells[this.getPointerFromXY(this._x, this._y)]
          .setChar(char)
          .setX(this._x)
          .setY(this._y)
          .setBackground(this._background.r, this._background.g, this._background.b)
          .setForeground(this._foreground.r, this._foreground.g, this._foreground.b)
          .setDisplay(this._display.bold, this._display.dim, this._display.underlined, this._display.blink, this._display.reverse, this._display.hidden)
          .setModified();
      }

      this._x++;
    });

    return this;
  }

  /**
   * Takes only modified cells from virtual terminal and flush changes to the real terminal.
   * There is no requirements to build diff or something, we have the markers for each cell that has been modified.
   *
   * @returns {Cursor}
   */
  flush() {
    var seq = '';

    for (var i = 0; i < this._cells.length; i++) {
      if (this._cells[i].isModified()) seq += this._cells[i].setModified(false).toString();
    }

    this._stream.write(seq);

    return this;
  }

  /**
   * Get index of the virtual terminal representation from (x, y) coordinates.
   *
   * @param {Number} [x] X coordinate on the terminal
   * @param {Number} [y] Y coordinate on the terminal
   * @returns {Number} Returns index in the buffer array
   */
  getPointerFromXY(x = this._x, y = this._y) {
    return y * this._width + x;
  }

  /**
   * Get (x, y) coordinate from the virtual terminal pointer.
   *
   * @param {Number} index Index in the buffer
   * @returns {Array} Returns an array [x, y]
   */
  getXYFromPointer(index) {
    return [index - (Math.floor(index / this._width) * this._width), Math.floor(index / this._width)];
  }

  /**
   * Move the cursor up.
   *
   * @param {Number} [y=1]
   * @returns {Cursor}
   */
  up(y = 1) {
    this._y -= Math.floor(y);
    return this;
  }

  /**
   * Move the cursor down.
   *
   * @param {Number} [y=1]
   * @returns {Cursor}
   */
  down(y = 1) {
    this._y += Math.floor(y);
    return this;
  }

  /**
   * Move the cursor right.
   *
   * @param {Number} [x=1]
   * @returns {Cursor}
   */
  right(x = 1) {
    this._x += Math.floor(x);
    return this;
  }

  /**
   * Move the cursor left.
   *
   * @param {Number} [x=1]
   * @returns {Cursor}
   */
  left(x = 1) {
    this._x -= Math.floor(x);
    return this;
  }

  /**
   * Move the cursor position relative current coordinates.
   *
   * @param {Number} x Offset by X coordinate
   * @param {Number} y Offset by Y coordinate
   * @returns {Cursor}
   */
  moveBy(x, y) {
    if (x < 0) this.left(-x);
    if (x > 0) this.right(x);

    if (y < 0) this.up(-y);
    if (y > 0) this.down(y);

    return this;
  }

  /**
   * Set the cursor position by absolute coordinates.
   *
   * @param {Number} x X coordinate
   * @param {Number} y Y coordinate
   * @returns {Cursor}
   */
  moveTo(x, y) {
    this._x = Math.floor(x);
    this._y = Math.floor(y);

    return this;
  }

  /**
   * Set the foreground color.
   * This color is used when text is rendering.
   *
   * @param {String|Boolean} color Color name or false if you want to disable foreground filling
   * @returns {Cursor}
   */
  foreground(color) {
    this._foreground = color ? Color.create(color).toRgb() : {r: -1, g: -1, b: -1};
    return this;
  }

  /**
   * Set the background color.
   * This color is used for filling the whole cell in the TTY.
   *
   * @param {String|Boolean} color Color name or false if you want to disable background filling
   * @returns {Cursor}
   */
  background(color) {
    this._background = color ? Color.create(color).toRgb() : {r: -1, g: -1, b: -1};
    return this;
  }

  /**
   * Toggle bold display mode.
   *
   * @param {Boolean} [isBold=true] If false, disables bold mode
   * @returns {Cursor}
   */
  bold(isBold = true) {
    this._display.bold = isBold;
    return this;
  }

  /**
   * Toggle dim display mode.
   *
   * @param {Boolean} [isDim=true] If false, disables dim mode
   * @returns {Cursor}
   */
  dim(isDim = true) {
    this._display.dim = isDim;
    return this;
  }

  /**
   * Toggle underlined display mode.
   *
   * @param {Boolean} [isUnderlined=true] If false, disables underlined mode
   * @returns {Cursor}
   */
  underlined(isUnderlined = true) {
    this._display.underlined = isUnderlined;
    return this;
  }

  /**
   * Toggle blink display mode.
   *
   * @param {Boolean} [isBlink=true] If false, disables blink mode
   * @returns {Cursor}
   */
  blink(isBlink = true) {
    this._display.blink = isBlink;
    return this;
  }

  /**
   * Toggle reverse display mode.
   *
   * @param {Boolean} [isReverse=true] If false, disables reverse display mode
   * @returns {Cursor}
   */
  reverse(isReverse = true) {
    this._display.reverse = isReverse;
    return this;
  }

  /**
   * Toggle hidden display mode.
   *
   * @param {Boolean} [isHidden=true] If false, disables hidden display mode
   * @returns {Cursor}
   */
  hidden(isHidden = true) {
    this._display.hidden = isHidden;
    return this;
  }

  /**
   * Erase the specified region.
   * The region describes the rectangle shape which need to erase.
   *
   * @param {Number} x1
   * @param {Number} y1
   * @param {Number} x2
   * @param {Number} y2
   * @returns {Cursor}
   */
  erase(x1, y1, x2, y2) {
    for (let y = y1; y <= y2; y++) {
      for (let x = x1; x <= x2; x++) {
        this._cells[this.getPointerFromXY(x, y)].reset();
      }
    }

    return this;
  }

  /**
   * Erase from current position to end of the line.
   *
   * @returns {Cursor}
   */
  eraseToEnd() {
    return this.erase(this._x, this._y, this._width - 1, this._y);
  }

  /**
   * Erase from current position to start of the line.
   *
   * @returns {Cursor}
   */
  eraseToStart() {
    return this.erase(0, this._y, this._x, this._y);
  }

  /**
   * Erase from current line to down.
   *
   * @returns {Cursor}
   */
  eraseToDown() {
    return this.erase(0, this._y, this._width - 1, this._height - 1);
  }

  /**
   * Erase from current line to up.
   *
   * @returns {Cursor}
   */
  eraseToUp() {
    return this.erase(0, 0, this._width - 1, this._y);
  }

  /**
   * Erase current line.
   *
   * @returns {Cursor}
   */
  eraseLine() {
    return this.erase(0, this._y, this._width - 1, this._y);
  }

  /**
   * Erase the entire screen.
   *
   * @returns {Cursor}
   */
  eraseScreen() {
    return this.erase(0, 0, this._width - 1, this._height - 1);
  }

  /**
   * Save current terminal contents into the buffer.
   * Applies immediately without calling {@link flush}.
   *
   * @returns {Cursor}
   */
  saveScreen() {
    this._stream.write(encodeToVT100('[?47h'));
    return this;
  }

  /**
   * Restore terminal contents to previously saved via {@link saveScreen}.
   * Applies immediately without calling {@link flush}.
   *
   * @returns {Cursor}
   */
  restoreScreen() {
    this._stream.write(encodeToVT100('[?47l'));
    return this;
  }

  /**
   * Set the terminal cursor invisible.
   * Applies immediately without calling {@link flush}.
   *
   * @returns {Cursor}
   */
  hideCursor() {
    this._stream.write(encodeToVT100('[?25l'));
    return this;
  }

  /**
   * Set the terminal cursor visible.
   * Applies immediately without calling {@link flush}.
   *
   * @returns {Cursor}
   */
  showCursor() {
    this._stream.write(encodeToVT100('[?25h'));
    return this;
  }

  /**
   * Reset all terminal settings.
   * Applies immediately without calling {@link flush}.
   *
   * @returns {Cursor}
   */
  reset() {
    this._stream.write(encodeToVT100('c'));
    return this;
  }

  /**
   * Wrapper around `new Cursor()`.
   *
   * @static
   * @returns {Cursor}
   */
  static create(...args) {
    return new this(...args);
  }
}
<commit_msg>perf(cursor): Remove deopt on flush()<commit_after>import Cell from './Cell';
import Color from './Color';
import {encodeToVT100} from './util/encodeToVT100';

/**
 * Cursor implements low-level API to terminal control codes.
 *
 * @see http://www.termsys.demon.co.uk/vtansi.htm
 * @see http://misc.flogisoft.com/bash/tip_colors_and_formatting
 * @see http://man7.org/linux/man-pages/man4/console_codes.4.html
 * @see http://www.x.org/docs/xterm/ctlseqs.pdf
 * @see http://wiki.bash-hackers.org/scripting/terminalcodes
 * @since 1.0.0
 */
export default class Cursor {
  /**
   * Creates cursor that writes direct to `stdout`.
   * You can override target stream with another one.
   * Also, you can specify custom width and height of viewport where cursor will render the frame.
   *
   * @constructor
   * @param {Object} [options] Object with options
   * @param {Stream} [options.stream=process.stdout] Writable stream
   * @param {Number} [options.width=stream.columns] Number of columns (width)
   * @param {Number} [options.height=stream.rows] Number of rows (height)
   */
  constructor(options = {}) {
    const {stream = process.stdout, width = stream.columns, height = stream.rows} = options;

    this._stream = stream;
    this._width = width;
    this._height = height;

    this._x = 0;
    this._y = 0;
    this._background = {r: -1, g: -1, b: -1};
    this._foreground = {r: -1, g: -1, b: -1};
    this._display = {bold: false, dim: false, underlined: false, blink: false, reverse: false, hidden: false};

    this._cells = Array.from({length: width * height}).map(() => new Cell());
  }

  /**
   * Write to the stream.
   * It doesn't applies immediately but stores in virtual terminal that represented as array of {@link Cell} instances.
   * For applying changes you need to {@link flush} changes.
   *
   * @param {String} data Data to write to the terminal
   * @returns {Cursor}
   */
  write(data) {
    data.split('').forEach(char => {
      if (0 <= this._x && this._x < this._width && 0 <= this._y && this._y < this._height) {
        this._cells[this.getPointerFromXY(this._x, this._y)]
          .setChar(char)
          .setX(this._x)
          .setY(this._y)
          .setBackground(this._background.r, this._background.g, this._background.b)
          .setForeground(this._foreground.r, this._foreground.g, this._foreground.b)
          .setDisplay(this._display.bold, this._display.dim, this._display.underlined, this._display.blink, this._display.reverse, this._display.hidden)
          .setModified();
      }

      this._x++;
    });

    return this;
  }

  /**
   * Takes only modified cells from virtual terminal and flush changes to the real terminal.
   * There is no requirements to build diff or something, we have the markers for each cell that has been modified.
   *
   * @returns {Cursor}
   */
  flush() {
    for (var i = 0; i < this._cells.length; i++) {
      if (this._cells[i].isModified()) {
        this._stream.write(this._cells[i].setModified(false).toString());
      }
    }

    return this;
  }

  /**
   * Get index of the virtual terminal representation from (x, y) coordinates.
   *
   * @param {Number} [x] X coordinate on the terminal
   * @param {Number} [y] Y coordinate on the terminal
   * @returns {Number} Returns index in the buffer array
   */
  getPointerFromXY(x = this._x, y = this._y) {
    return y * this._width + x;
  }

  /**
   * Get (x, y) coordinate from the virtual terminal pointer.
   *
   * @param {Number} index Index in the buffer
   * @returns {Array} Returns an array [x, y]
   */
  getXYFromPointer(index) {
    return [index - (Math.floor(index / this._width) * this._width), Math.floor(index / this._width)];
  }

  /**
   * Move the cursor up.
   *
   * @param {Number} [y=1]
   * @returns {Cursor}
   */
  up(y = 1) {
    this._y -= Math.floor(y);
    return this;
  }

  /**
   * Move the cursor down.
   *
   * @param {Number} [y=1]
   * @returns {Cursor}
   */
  down(y = 1) {
    this._y += Math.floor(y);
    return this;
  }

  /**
   * Move the cursor right.
   *
   * @param {Number} [x=1]
   * @returns {Cursor}
   */
  right(x = 1) {
    this._x += Math.floor(x);
    return this;
  }

  /**
   * Move the cursor left.
   *
   * @param {Number} [x=1]
   * @returns {Cursor}
   */
  left(x = 1) {
    this._x -= Math.floor(x);
    return this;
  }

  /**
   * Move the cursor position relative current coordinates.
   *
   * @param {Number} x Offset by X coordinate
   * @param {Number} y Offset by Y coordinate
   * @returns {Cursor}
   */
  moveBy(x, y) {
    if (x < 0) this.left(-x);
    if (x > 0) this.right(x);

    if (y < 0) this.up(-y);
    if (y > 0) this.down(y);

    return this;
  }

  /**
   * Set the cursor position by absolute coordinates.
   *
   * @param {Number} x X coordinate
   * @param {Number} y Y coordinate
   * @returns {Cursor}
   */
  moveTo(x, y) {
    this._x = Math.floor(x);
    this._y = Math.floor(y);

    return this;
  }

  /**
   * Set the foreground color.
   * This color is used when text is rendering.
   *
   * @param {String|Boolean} color Color name or false if you want to disable foreground filling
   * @returns {Cursor}
   */
  foreground(color) {
    this._foreground = color ? Color.create(color).toRgb() : {r: -1, g: -1, b: -1};
    return this;
  }

  /**
   * Set the background color.
   * This color is used for filling the whole cell in the TTY.
   *
   * @param {String|Boolean} color Color name or false if you want to disable background filling
   * @returns {Cursor}
   */
  background(color) {
    this._background = color ? Color.create(color).toRgb() : {r: -1, g: -1, b: -1};
    return this;
  }

  /**
   * Toggle bold display mode.
   *
   * @param {Boolean} [isBold=true] If false, disables bold mode
   * @returns {Cursor}
   */
  bold(isBold = true) {
    this._display.bold = isBold;
    return this;
  }

  /**
   * Toggle dim display mode.
   *
   * @param {Boolean} [isDim=true] If false, disables dim mode
   * @returns {Cursor}
   */
  dim(isDim = true) {
    this._display.dim = isDim;
    return this;
  }

  /**
   * Toggle underlined display mode.
   *
   * @param {Boolean} [isUnderlined=true] If false, disables underlined mode
   * @returns {Cursor}
   */
  underlined(isUnderlined = true) {
    this._display.underlined = isUnderlined;
    return this;
  }

  /**
   * Toggle blink display mode.
   *
   * @param {Boolean} [isBlink=true] If false, disables blink mode
   * @returns {Cursor}
   */
  blink(isBlink = true) {
    this._display.blink = isBlink;
    return this;
  }

  /**
   * Toggle reverse display mode.
   *
   * @param {Boolean} [isReverse=true] If false, disables reverse display mode
   * @returns {Cursor}
   */
  reverse(isReverse = true) {
    this._display.reverse = isReverse;
    return this;
  }

  /**
   * Toggle hidden display mode.
   *
   * @param {Boolean} [isHidden=true] If false, disables hidden display mode
   * @returns {Cursor}
   */
  hidden(isHidden = true) {
    this._display.hidden = isHidden;
    return this;
  }

  /**
   * Erase the specified region.
   * The region describes the rectangle shape which need to erase.
   *
   * @param {Number} x1
   * @param {Number} y1
   * @param {Number} x2
   * @param {Number} y2
   * @returns {Cursor}
   */
  erase(x1, y1, x2, y2) {
    for (let y = y1; y <= y2; y++) {
      for (let x = x1; x <= x2; x++) {
        this._cells[this.getPointerFromXY(x, y)].reset();
      }
    }

    return this;
  }

  /**
   * Erase from current position to end of the line.
   *
   * @returns {Cursor}
   */
  eraseToEnd() {
    return this.erase(this._x, this._y, this._width - 1, this._y);
  }

  /**
   * Erase from current position to start of the line.
   *
   * @returns {Cursor}
   */
  eraseToStart() {
    return this.erase(0, this._y, this._x, this._y);
  }

  /**
   * Erase from current line to down.
   *
   * @returns {Cursor}
   */
  eraseToDown() {
    return this.erase(0, this._y, this._width - 1, this._height - 1);
  }

  /**
   * Erase from current line to up.
   *
   * @returns {Cursor}
   */
  eraseToUp() {
    return this.erase(0, 0, this._width - 1, this._y);
  }

  /**
   * Erase current line.
   *
   * @returns {Cursor}
   */
  eraseLine() {
    return this.erase(0, this._y, this._width - 1, this._y);
  }

  /**
   * Erase the entire screen.
   *
   * @returns {Cursor}
   */
  eraseScreen() {
    return this.erase(0, 0, this._width - 1, this._height - 1);
  }

  /**
   * Save current terminal contents into the buffer.
   * Applies immediately without calling {@link flush}.
   *
   * @returns {Cursor}
   */
  saveScreen() {
    this._stream.write(encodeToVT100('[?47h'));
    return this;
  }

  /**
   * Restore terminal contents to previously saved via {@link saveScreen}.
   * Applies immediately without calling {@link flush}.
   *
   * @returns {Cursor}
   */
  restoreScreen() {
    this._stream.write(encodeToVT100('[?47l'));
    return this;
  }

  /**
   * Set the terminal cursor invisible.
   * Applies immediately without calling {@link flush}.
   *
   * @returns {Cursor}
   */
  hideCursor() {
    this._stream.write(encodeToVT100('[?25l'));
    return this;
  }

  /**
   * Set the terminal cursor visible.
   * Applies immediately without calling {@link flush}.
   *
   * @returns {Cursor}
   */
  showCursor() {
    this._stream.write(encodeToVT100('[?25h'));
    return this;
  }

  /**
   * Reset all terminal settings.
   * Applies immediately without calling {@link flush}.
   *
   * @returns {Cursor}
   */
  reset() {
    this._stream.write(encodeToVT100('c'));
    return this;
  }

  /**
   * Wrapper around `new Cursor()`.
   *
   * @static
   * @returns {Cursor}
   */
  static create(...args) {
    return new this(...args);
  }
}
<|endoftext|>


<commit_before>import { DEFAULT_SEARCH_PROPS, PAGE_PROPS_NO_NAV } from "actions"
import API from "api"
import { gql } from "apollo-boost"
import {
  mapDispatchToProps,
  propTypes as pagePropTypes,
  useBoilerplate
} from "components/Page"
import * as GraphiQLreq from "graphiql"
import "graphiql/graphiql.css"
import React from "react"
import { connect } from "react-redux"

const GraphiQL = props => {
  useBoilerplate({
    pageProps: PAGE_PROPS_NO_NAV,
    searchProps: DEFAULT_SEARCH_PROPS,
    ...props
  })

  // TODO: fix the below hack with inlined height after layout refactoring in NCI-Agency/anet#551
  return (

      <GraphiQLreq fetcher={fetch} />

  )

  function fetch(params) {
    const { operationName, variables } = params
    const query = gql`
      ${params.query}
    `
    return API.client.query({ operationName, query, variables })
  }
}

GraphiQL.propTypes = { ...pagePropTypes }

export default connect(null, mapDispatchToProps)(GraphiQL)
<commit_msg>Fix GraphiQL page to work with updated graphiql package<commit_after>import { DEFAULT_SEARCH_PROPS, PAGE_PROPS_NO_NAV } from "actions"
import API from "api"
import { gql } from "apollo-boost"
import {
  mapDispatchToProps,
  propTypes as pagePropTypes,
  useBoilerplate
} from "components/Page"
import GraphiQL from "graphiql"
import "graphiql/graphiql.css"
import React from "react"
import { connect } from "react-redux"

const GraphiQLContainer = props => {
  useBoilerplate({
    pageProps: PAGE_PROPS_NO_NAV,
    searchProps: DEFAULT_SEARCH_PROPS,
    ...props
  })

  // TODO: fix the below hack with inlined height after layout refactoring in NCI-Agency/anet#551
  return (

      <GraphiQL fetcher={fetch} />

  )

  function fetch(params) {
    const { operationName, variables } = params
    const query = gql`
      ${params.query}
    `
    return API.client.query({ operationName, query, variables })
  }
}

GraphiQLContainer.propTypes = { ...pagePropTypes }

export default connect(null, mapDispatchToProps)(GraphiQLContainer)
<|endoftext|>