@fortawesome/free-solid-svg-icons#faArrowRight TypeScript Examples

The following examples show how to use @fortawesome/free-solid-svg-icons#faArrowRight. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: PaginationBar.tsx    From website with MIT License 6 votes vote down vote up
PaginationBar: React.FC<PaginationBarProps> = ({
  page,
  pagesCount,
  setPage,
  totalProfiles,
}) => {
  return (
    <div className="flex items-center justify-between my-4 text-primary">
      <div className="flex items-center">
        <button
          className="disabled:opacity-50 hover:text-greenFec"
          disabled={page === 1}
          onClick={() => setPage((page) => page - 1)}
        >
          <FontAwesomeIcon icon={faArrowLeft} width="18px" />
        </button>
        <span className="px-2">
          Página {page} de {pagesCount}
        </span>
        <button
          className="disabled:opacity-50 hover:text-greenFec"
          disabled={page === pagesCount}
          onClick={() => setPage((page) => page + 1)}
        >
          <FontAwesomeIcon icon={faArrowRight} width="18px" />
        </button>
      </div>
      <span>
        Total de <span className="font-semibold">{totalProfiles}</span> perfiles
      </span>
    </div>
  );
}
Example #2
Source File: import.component.ts    From 1hop with MIT License 5 votes vote down vote up
copyIcon = faArrowRight;
Example #3
Source File: migrate.component.ts    From 1hop with MIT License 5 votes vote down vote up
copyIcon = faArrowRight;
Example #4
Source File: fa-library.ts    From eth2stats-dashboard with MIT License 5 votes vote down vote up
library.add(faBell, faChevronDown, faTimes, faArrowRight, faCheck, faPlusCircle,
    faExclamationCircle, faHeart, faCodeBranch, faMap, faList, faCircle,
    faDotCircle,
    faCheckCircle, faNetworkWired, faUsers, faCube, faSortUp, faSortDown,
    faEllipsisV, faSync, faMicrochip, faCheckDouble, faLaptopCode);
Example #5
Source File: BlogArticlePreview.tsx    From frontend.ro with MIT License 5 votes vote down vote up
BlogArticlePreview = ({
  title, href, cover, firstParagraph, variant = 'row', timestamp, className = '',
}: Props) => {
  return (
    <Link href={href}>
      <a className={`${className} ${styles.BlogArticlePreview} ${styles[variant]} no-underline`}>
        <article className="d-flex justify-content-between align-items-start">
          <div className="cover-wrapper overflow-hidden">
            <Image
              className={styles.cover}
              width={cover.width}
              height={cover.height}
              src={cover.src}
              alt={`${title} cover photo `}
            />
          </div>
          <div className="content-wrapper d-flex justify-content-between flex-column">
            <div>
              <h2 className={styles.title}>
                {title}
              </h2>
              <p className="text-xl font-light">
                {firstParagraph}
              </p>
            </div>
            <div className="d-flex justify-content-between">
              <time
                dateTime={format(timestamp, 'yyyy-MM-dd')}
                className={`${styles.date} text-grey uppercase`}
              >
                {timeAgo(new Date(timestamp))}
              </time>
              <span className={`${styles['read-more']} border-bottom-1px`}>
                Citește mai mult
                <FontAwesomeIcon className="ml-2" icon={faArrowRight} />
              </span>
            </div>
          </div>
        </article>
      </a>
    </Link>
  );
}
Example #6
Source File: index.tsx    From website with MIT License 5 votes vote down vote up
Card = ({ card, href, onClick }: FeaturedCardsItemProps, ref) => {
  const router = useRouter();

  const handleClick = useCallback(() => {
    if (!card.link) return;

    router.push(card.link);
  }, [card.link, router]);

  return (
    <div
      onClick={handleClick}
      className="flex justify-between p-6 transition duration-500 ease-in-out scale-100 border-2 border-zinc-500 shadow-lg cursor-pointer hover:border-zinc-50 md:hover:scale-105 rounded-xl"
    >
      <div className="relative flex flex-col items-start justify-between">
        <div className="items-start">
          <h1 className="cards-title">{card.title}</h1>
          <p className="pt-5 pb-8 text-secondary lg:text-lg">
            {card.description}
          </p>
        </div>
        {card.link && (
          <button>
            <a
              ref={ref}
              href={href}
              onClick={onClick}
              className="flex items-center text-sm font-normal normal-case lg:text-lg text-informational hover:underline"
            >
              {card.btnText}
              <span className="ml-2">
                <FontAwesomeIcon icon={faArrowRight} width="12px" />
              </span>
            </a>
          </button>
        )}
      </div>
    </div>
  );
}
Example #7
Source File: Login.tsx    From MagicUI with Apache License 2.0 5 votes vote down vote up
export default function Login() {
  const [src, setSrc] = useState('');
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [nickname, setNickname] = useState('');
  const [emailResultIcon, setEmailResultIcon] = useState(icons.empty);
  const [passwordResultIcon, setPasswordResultIcon] = useState(icons.empty);



  const handleEmailChange = (e: any) => {
    const email = e.target.value;
    setEmail(email);
    setEmailResultIcon(icons.loading as any);
    throttle(() => {
      fetchLoginEmail(email).then(v => {
        if (v.err) {
          setEmailResultIcon(icons.error as any);
          return;
        }
        setEmailResultIcon(icons.ok as any);
        setSrc(v.avatar);
      });
    })();
  };

  const handlePasswordChange = (e: any) => {
    const password = e.target.value;
    setPassword(password);
    setPasswordResultIcon(icons.loading as any);
    throttle(() => {
      fetchLoginPassword(email, password).then(v => {
        if (v.err) {
          setPasswordResultIcon(icons.error as any);
          return;
        }
        setNickname(v.nickname);
        setPasswordResultIcon(icons.ok as any);
      });
    })();
  };

  const handleLogin = () => {
    if (emailResultIcon === icons.ok && passwordResultIcon === icons.ok) {
      Bridge.login({email, password, avatar: src, nickname});
    }
  };

  return (
    <div className={style.login}>
      <div className={style.avatar_wrapper}>
        <Avatar src={src || DEFAULT_AVATAR} size={100}/>
      </div>
      <div className={style.input_wrapper}>
        <div className={cls(style.input, style.input_email)}>
          <input type="text" placeholder="Email..." value={email} onChange={handleEmailChange}/>
          <span>{emailResultIcon}</span>
        </div>
        <div className={cls(style.input, style.input_password)}>
          <input type="password" placeholder="Password..." value={password} onChange={handlePasswordChange}/>
          <span>{passwordResultIcon}</span>
        </div>
      </div>
      <div className={style.login_btn}>
        <button onClick={handleLogin}>
          LOGIN
          <span>
            <FontAwesomeIcon icon={faArrowRight}/>
          </span>
        </button>
      </div>
      <div className={style.register_btn}>
        <button onClick={() => history.push('/register')}>
          Register?
        </button>
      </div>
    </div>
  );
}
Example #8
Source File: Tidbit.tsx    From frontend.ro with MIT License 4 votes vote down vote up
Tidbit = ({ tidbit, index }: Props) => {
  const router = useRouter();
  const [nextTidbit, setNextTidbit] = useState<TidbitI>(null);
  const [previousTidbit, setPreviousTidbit] = useState<TidbitI>(null);

  const currentItem = tidbit.items[index];

  const isFirstItem = index === 0;
  const isLastItem = index === tidbit.items.length - 1;

  const increaseViewCount = async () => {
    try {
      await TidbitService.increaseTidbitView(tidbit.tidbitId);
    } catch (err) {
      // We want to silently fail this function because
      // it's not mission critical.
      console.warn(`Couldn't increase view count for tidbit ${tidbit.tidbitId}. Is the server down?`, err);
    }
  };

  useEffect(() => {
    TidbitService.getPreviousAndNextTidbit(tidbit.tidbitId)
      .then((response) => {
        setNextTidbit(response.next);
        setPreviousTidbit(response.previous);
      })
      .catch((err) => {
        console.error('[TidbitPage.useEffect] got while fetching next tidbit', err);
      });

    increaseViewCount();
  }, [tidbit.tidbitId]);

  useKeyDown('ArrowRight', () => {
    if (!isLastItem) {
      // We're doing `+2` because the index in the URL path starts from 0
      router.replace(`/tidbits/${tidbit.tidbitId}/${index + 2}`);
    }
  }, [index]);
  useKeyDown('ArrowLeft', () => {
    if (!isFirstItem) {
      router.push(`/tidbits/${tidbit.tidbitId}/${index}`);
    }
  }, [index]);

  return (
    <main className={styles.Tidbit}>
      <div className={`${styles.hero} d-flex relative justify-content-evenly align-items-center overflow-hidden`}>
        {isFirstItem && previousTidbit && (<PrevTidbitLink previousTidbit={previousTidbit} />)}

        <Link href={`/tidbits/${tidbit.tidbitId}/${index}`}>
          <a className={`
            ${styles['arrow-link']}
            ${styles['arrow-link--left']}
            ${isFirstItem ? 'invisible' : ''}
          `}
          >
            <FontAwesomeIcon width={48} icon={faArrowAltCircleLeft} />
          </a>
        </Link>
        <BaseTidbitItem
          controls
          title={tidbit.title}
          src={currentItem.imageSrc}
          className={`${styles['main-image']} d-block`}
        />
        <Link href={`/tidbits/${tidbit.tidbitId}/${index + 2}`}>
          <a className={`
              ${styles['arrow-link']}
              ${styles['arrow-link--right']}
              ${isLastItem ? 'invisible' : ''}
            `}
          >
            <FontAwesomeIcon width={48} icon={faArrowAltCircleRight} />
          </a>
        </Link>

        {isLastItem && nextTidbit && (<NextTidbitLink nextTidbit={nextTidbit} />)}
      </div>

      {currentItem.codeSnippets !== undefined && (
        <div className={`${styles['center-container']} d-flex flex-column align-items-center`}>
          {currentItem.codeSnippets.map((codeSnippet, index) => (
            <Highlight
              // eslint-disable-next-line react/no-array-index-key
              key={index}
              code={codeSnippet}
              className={`${styles['code-snippet']} mt-8 mb-8`}
              language={currentItem.language as Language}
            />
          ))}
        </div>
      )}

      <div className={styles['center-container']}>
        <div className={styles.grid}>
          {tidbit.items.map((item, itemIndex) => (
            <TidbitGalleryItem
              // eslint-disable-next-line react/no-array-index-key
              key={itemIndex}
              tidbit={tidbit}
              itemIndex={itemIndex}
              active={itemIndex === index}
            />
          ))}
        </div>
        <nav className={`${styles['footer-nav']} d-flex justify-content-between mb-8`}>
          <Link href={previousTidbit !== null ? `/tidbits/${previousTidbit.tidbitId}/1` : '/tidbits'}>
            <a className="d-flex">
              <FontAwesomeIcon className="mr-2" width={14} icon={faArrowLeft} />
              {previousTidbit !== null ? previousTidbit.title : 'Toate Tidbit\'s-urile'}
            </a>
          </Link>

          {nextTidbit !== null && (
            <Link href={`/tidbits/${nextTidbit.tidbitId}/1`}>
              <a className="d-flex justify-content-end">
                {nextTidbit.title}
                <FontAwesomeIcon className="ml-2" width={14} icon={faArrowRight} />
              </a>
            </Link>
          )}
        </nav>
      </div>
    </main>
  );
}
Example #9
Source File: SettingsArchive.tsx    From argo-react with MIT License 4 votes vote down vote up
SettingsArchive = () => {
  const { selectedOrg, orgLoading } = useContext<IStateModel>(StateContext);

  const imageUrl = (imageUrl: string | undefined) => {
    if (imageUrl) {
      return imageUrl;
    }
    return config.urls.IMAGE_NOT_FOUND;
  };

  const orgId = selectedOrg?._id;

  const [archiveList, setArchiveList] = useState<IProject[]>([]);
  const [archiveLoading, setArchiveLoading] = useState<boolean>(true);

  const getArchived = (organizationId: string) => {
    setArchiveLoading(true);
    ApiService.getArchiveProject(organizationId).subscribe(
      (result) => {
        setArchiveList(result.archivedProjects);
        setArchiveLoading(false);
      },
      (error) => {
        setArchiveLoading(false);
        // eslint-disable-next-line
        console.log(error);
      },
    );
  };

  useEffect(() => {
    if (orgId) {
      getArchived(orgId);
    }
  }, [orgId]);

  const history = useHistory();

  const openDeployment = (siteId: string) => {
    history.push(`/org/${orgId}/sites/${siteId}/overview`);
  };

  return (
    <div className="OrgSettingsGeneral">
      <div className="settings-right-container">
        <div className="settings-profile-details">
          <div className="settings-profile-header">Archived Projects</div>
          <div className="settings-profile-body">
            {!archiveLoading && !orgLoading ? (
              archiveList.length > 0 ? (
                archiveList.map((archive: IProject, index: number) => (
                  <div
                    className="settings-deployment-item"
                    onClick={(e) => {
                      openDeployment(archive?._id!);
                    }}
                  >
                    <>
                      <div className="settings-deployment-left">
                        <img
                          className="deployment-screenshot"
                          src={imageUrl(archive?.latestDeployment?.screenshot?.url)}
                          alt={"Preview not Available"}
                        />
                        <div className="deployment-left-detail">
                          <div className="deployment-publish-detail">
                            <div className="deployment-header-title">
                              {archive.name}
                            </div>
                            <div className="deployment-header-description">
                              Last updated at{" "}
                              {moment(archive.updatedAt).format(
                                "MMM DD, YYYY hh:mm a",
                              )}
                            </div>
                            <span className="archive-tag">{archive.state}</span>
                          </div>
                        </div>
                        {/* <div className="deployment-right-detail">
                          <button
                            className="unarchive-button"
                            onClick={(e) => {
                              projectMaintain(archive?._id!);
                            }}
                          >
                            Unarchive
                          </button>
                        </div> */}
                      </div>
                    </>
                  </div>
                ))
              ) : (
                <div className="settings-archive-empty">
                  <div className="archive-empty-container">
                    <span className="archive-text">
                      {" "}
                      <b>You currently do not have any archived projects.</b>
                      <br />
                      <br />
                      You can archive any project in your organization you think
                      <br />
                      which are not getting used frequently.
                      <br />
                      <br />
                    </span>
                    <span>
                      <a
                        className="archive-link"
                        href="https://docs.argoapp.live/"
                        rel="noopener noreferrer"
                        target="_blank"
                      >
                        Browse Docs <FontAwesomeIcon icon={faArrowRight} />
                      </a>
                    </span>
                  </div>
                </div>
              )
            ) : (
              <div>
                <div className="settings-deployment-item">
                  <>
                    <div className="settings-deployment-left">
                      <Skeleton height={100} width={190} duration={2} />
                      <div className="deployment-left-detail">
                        <div className="deployment-publish-detail">
                          <div className="deployment-header-title">
                            <Skeleton width={400} duration={2} />
                          </div>
                          <div className="deployment-header-description">
                            <Skeleton width={400} duration={2} />
                          </div>
                        </div>
                      </div>
                    </div>
                  </>
                </div>
              </div>
            )}
          </div>
        </div>
      </div>
    </div>
  );
}
Example #10
Source File: EnsDomainGeneral.tsx    From argo-react with MIT License 4 votes vote down vote up
EnsDomainGeneral = () => {
  const { projectLoading, selectedProject, selectedOrg } =
    useContext<IStateModel>(StateContext);
  const { fetchProject } = useContext<IActionModel>(ActionContext);
  const [domainName, setDomainName] = useState<string>("");
  const [deployedSite, setDeployedSite] = useState<string>("");
  const [domainLoading, setDomainLoading] = useState<boolean>(false);
  const sortedDeployments = projectLoading
    ? []
    : selectedProject?.deployments
        .filter((dep) => dep.sitePreview)
        .sort((a, b) => moment(b.createdAt).diff(moment(a.createdAt)));

  const addDomainDetails = () => {
    setDomainLoading(true);
    const domain = {
      orgId: selectedOrg?._id,
      projectId: selectedProject?._id,
      name: domainName,
      link: deployedSite,
      type: "ens-domain",
    };
    ApiService.addDomain(domain).subscribe((result) => {
      if (result.success) {
        setDomainName("");
        setDeployedSite("");
        fetchProject(`${selectedProject?._id}`);
      } else {
        setDomainName("");
        setDeployedSite("");
      }
      setDomainLoading(false);
    });
  };

  const setTransaction = (tx: string) => {
    setDeployedSite(tx);
  };

  return (
    <div className="DomainGeneral">
      <div className="domain-general-right-container">
        <div className="domain-general-project-details">
          <div className="domain-general-project-header">
            ENS Domains
            <span className="beta-badge">Beta</span>
          </div>
          <div className="domain-general-project-body">
            <div className="domain-general-project-item">
              <label className="domain-general-project-item-title">
                Configure your ENS Domains/Subdomains
              </label>
              <label className="domain-general-project-item-subtitle">
                By default, your site is always accessible via arweave gateway based
                on transaction hash. ENS is an open source blockchain-based naming
                protocol.
              </label>
              <label className="domain-general-project-item-subtitle label-note">
                To resolve your ENS Domains, use a gateway like eth.link or eth.limo.
                Brave & Opera browsers have direct support for ENS Domains, so no
                gateway is required.
              </label>
              <a href="https://docs.spheron.network/">
                Learn more about ens domains from our docs
                <span>
                  <FontAwesomeIcon icon={faArrowRight} />
                </span>
              </a>
              <div className="domain-general-add-domain-container">
                <input
                  type="text"
                  className="add-domain-input"
                  placeholder="eg: mywebsite.eth or dash.mywebsite.eth"
                  value={domainName}
                  onChange={(e) => setDomainName(e.target.value)}
                />
                <div className="add-domain-select-container">
                  <select
                    className="add-domain-select"
                    value={deployedSite}
                    onChange={(e) => setTransaction(e.target.value)}
                  >
                    <option value="">Select Site</option>
                    {(sortedDeployments ? sortedDeployments : []).map(
                      (dep, index) => (
                        <option value={dep.sitePreview} key={index}>
                          {dep.sitePreview}
                        </option>
                      ),
                    )}
                  </select>
                  <span className="select-down-icon">
                    <FontAwesomeIcon icon={faChevronDown} />
                  </span>
                </div>
                <button
                  className="add-domain-button"
                  disabled={!domainName || !deployedSite}
                  onClick={addDomainDetails}
                >
                  {domainLoading ? (
                    <BounceLoader size={20} color={"#fff"} loading={true} />
                  ) : (
                    "Add"
                  )}
                </button>
              </div>
              <div className="domain-general-domain-list">
                {!projectLoading ? (
                  selectedProject?.ensDomains?.length ? (
                    selectedProject?.ensDomains?.map((domain, index) => (
                      <div key={index}>
                        <DomainItem
                          index={index}
                          type="filled"
                          domainId={`${domain._id}`}
                          domain={`${domain.name}`}
                          link={`${domain.link}`}
                          isSubdomain={false}
                          autoDns={domain.isLatest}
                          uuid={`${domain.argoKey}`}
                          ownerVerified={domain.verified}
                          domainType={domain.type}
                        />
                      </div>
                    ))
                  ) : null
                ) : (
                  <>
                    <DomainItem
                      index={1}
                      type="skeleton"
                      domainId=""
                      domain=""
                      link=""
                      uuid=""
                      isSubdomain={false}
                      autoDns={false}
                      ownerVerified={true}
                      domainType=""
                    />
                  </>
                )}
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}
Example #11
Source File: HandshakeDomainGeneral.tsx    From argo-react with MIT License 4 votes vote down vote up
HandshakeDomainGeneral = () => {
  const { projectLoading, selectedProject, selectedOrg } =
    useContext<IStateModel>(StateContext);
  const { fetchProject } = useContext<IActionModel>(ActionContext);
  const [domainName, setDomainName] = useState<string>("");
  const [deployedSite, setDeployedSite] = useState<string>("");
  const [domainLoading, setDomainLoading] = useState<boolean>(false);
  const sortedDeployments = projectLoading
    ? []
    : selectedProject?.deployments
        .filter((dep) => dep.sitePreview)
        .sort((a, b) => moment(b.createdAt).diff(moment(a.createdAt)));
  const sortedResolverSkylinks = projectLoading
    ? []
    : selectedProject?.resolverSkylinks.sort((a, b) =>
        moment(b.createdAt).diff(moment(a.createdAt)),
      );

  const addDomainDetails = () => {
    setDomainLoading(true);
    const domain = {
      orgId: selectedOrg?._id,
      projectId: selectedProject?._id,
      name: domainName,
      link: deployedSite,
      type: "handshake-domain",
    };
    ApiService.addDomain(domain).subscribe((result) => {
      if (result.success) {
        setDomainName("");
        setDeployedSite("");
        fetchProject(`${selectedProject?._id}`);
      } else {
        setDomainName("");
        setDeployedSite("");
      }
      setDomainLoading(false);
    });
  };

  const setTransaction = (tx: string) => {
    setDeployedSite(tx);
  };

  return (
    <div className="DomainGeneral">
      <div className="domain-general-right-container">
        <div className="domain-general-project-details">
          <div className="domain-general-project-header">
            Handshake Domains
            <span className="beta-badge">Beta</span>
          </div>
          <div className="domain-general-project-body">
            <div className="domain-general-project-item">
              <label className="domain-general-project-item-title">
                Configure your Handshake Domains
              </label>
              <label className="domain-general-project-item-subtitle">
                By default, your site is always accessible via arweave gateway based
                on transaction hash. Handshake is decentralized naming and
                certificate authority that allow you to access your site in a
                decentralized peer-to-peer root naming system.
              </label>
              <label className="domain-general-project-item-subtitle label-note">
                To resolve your Handshake Domains, connect to{" "}
                <a href="https://hdns.io" rel="noopener noreferrer" target="_blank">
                  HDNS.io
                </a>{" "}
                or use a gateway like hns.to
              </label>
              <a
                href="https://docs.argoapp.live/custom-domains/handshake-domains"
                rel="noopener noreferrer"
                target="_blank"
              >
                Learn more about handshake domains in our docs
                <span>
                  <FontAwesomeIcon icon={faArrowRight} />
                </span>
              </a>
              <div className="domain-general-add-domain-container">
                <input
                  type="text"
                  className="add-domain-input"
                  placeholder="mywebsite.hns"
                  value={domainName}
                  onChange={(e) => setDomainName(e.target.value)}
                />
                <div className="add-domain-select-container">
                  <select
                    className="add-domain-select"
                    value={deployedSite}
                    onChange={(e) => setTransaction(e.target.value)}
                  >
                    <option value="">Select Site</option>
                    {(sortedResolverSkylinks ? sortedResolverSkylinks : []).map(
                      (dep, index) => (
                        <option
                          value={`https://siasky.net/${dep.resolverSkylink}`}
                          key={index}
                        >
                          Resolver Skylink - {dep.name}
                        </option>
                      ),
                    )}
                    {(sortedDeployments ? sortedDeployments : []).map(
                      (dep, index) => (
                        <option value={dep.sitePreview} key={index}>
                          {dep.sitePreview}
                        </option>
                      ),
                    )}
                  </select>
                  <span className="select-down-icon">
                    <FontAwesomeIcon icon={faChevronDown} />
                  </span>
                </div>
                <button
                  className="add-domain-button"
                  disabled={!domainName || !deployedSite}
                  onClick={addDomainDetails}
                >
                  {domainLoading ? (
                    <BounceLoader size={20} color={"#fff"} loading={true} />
                  ) : (
                    "Add"
                  )}
                </button>
              </div>
              <div className="domain-general-domain-list">
                {!projectLoading ? (
                  selectedProject?.handshakeDomains?.length ? (
                    selectedProject?.handshakeDomains?.map((domain, index) => (
                      <div key={index}>
                        <DomainItem
                          index={index}
                          type="filled"
                          domainId={`${domain._id}`}
                          domain={`${domain.name}`}
                          link={`${domain.link}`}
                          isSubdomain={false}
                          autoDns={domain.isLatest}
                          uuid={`${domain.argoKey}`}
                          ownerVerified={domain.verified}
                          domainType={domain.type}
                        />
                      </div>
                    ))
                  ) : null
                ) : (
                  <>
                    <DomainItem
                      index={1}
                      type="skeleton"
                      domainId=""
                      domain=""
                      link=""
                      uuid=""
                      isSubdomain={false}
                      autoDns={false}
                      ownerVerified={true}
                      domainType=""
                    />
                  </>
                )}
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}
Example #12
Source File: HandshakeSubdomainGeneral.tsx    From argo-react with MIT License 4 votes vote down vote up
HandshakeSubdomainGeneral = () => {
  const { projectLoading, selectedProject, selectedOrg } =
    useContext<IStateModel>(StateContext);
  const { fetchProject } = useContext<IActionModel>(ActionContext);
  const [domainName, setDomainName] = useState<string>("");
  const [deployedSite, setDeployedSite] = useState<string>("");
  const [domainLoading, setDomainLoading] = useState<boolean>(false);
  const sortedDeployments = projectLoading
    ? []
    : selectedProject?.deployments
        .filter((dep) => dep.sitePreview)
        .sort((a, b) => moment(b.createdAt).diff(moment(a.createdAt)));

  const sortedResolverSkylinks = projectLoading
    ? []
    : selectedProject?.resolverSkylinks.sort((a, b) =>
        moment(b.createdAt).diff(moment(a.createdAt)),
      );

  const addDomainDetails = () => {
    setDomainLoading(true);
    const domain = {
      orgId: selectedOrg?._id,
      projectId: selectedProject?._id,
      name: domainName,
      link: deployedSite,
      type: "handshake-subdomain",
    };
    ApiService.addDomain(domain).subscribe((result) => {
      if (result.success) {
        setDomainName("");
        setDeployedSite("");
        fetchProject(`${selectedProject?._id}`);
      } else {
        setDomainName("");
        setDeployedSite("");
      }
      setDomainLoading(false);
    });
  };

  const setTransaction = (tx: string) => {
    setDeployedSite(tx);
  };

  return (
    <div className="DomainGeneral">
      <div className="domain-general-right-container">
        <div className="domain-general-project-details">
          <div className="domain-general-project-header">
            Handshake Subdomains
            <span className="beta-badge">Beta</span>
          </div>
          <div className="domain-general-project-body">
            <div className="domain-general-project-item">
              <label className="domain-general-project-item-title">
                Configure your Handshake Subdomains
              </label>
              <label className="domain-general-project-item-subtitle">
                By default, your site is always accessible via arweave gateway based
                on transaction hash. Handshake is decentralized naming and
                certificate authority that allow you to access your site in a
                decentralized peer-to-peer root naming system.
              </label>
              <label className="domain-general-project-item-subtitle label-note">
                To resolve your Handshake Domains, connect to{" "}
                <a href="https://hdns.io" rel="noopener noreferrer" target="_blank">
                  HDNS.io
                </a>{" "}
                or use a gateway like hns.to
              </label>
              <a
                href="https://docs.spheron.network/domain-and-https/hns-domain/overview"
                rel="noopener noreferrer"
                target="_blank"
              >
                Learn more about handshake domains in our docs
                <span>
                  <FontAwesomeIcon icon={faArrowRight} />
                </span>
              </a>
              <div className="domain-general-add-domain-container">
                <input
                  type="text"
                  className="add-domain-input"
                  placeholder="mywebsite.hns"
                  value={domainName}
                  onChange={(e) => setDomainName(e.target.value)}
                />
                <div className="add-domain-select-container">
                  <select
                    className="add-domain-select"
                    value={deployedSite}
                    onChange={(e) => setTransaction(e.target.value)}
                  >
                    <option value="">Select Site</option>
                    {(sortedResolverSkylinks ? sortedResolverSkylinks : []).map(
                      (dep, index) => (
                        <option
                          value={`https://siasky.net/${dep.resolverSkylink}`}
                          key={index}
                        >
                          Resolver Skylink - {dep.name}
                        </option>
                      ),
                    )}
                    {(sortedDeployments ? sortedDeployments : []).map(
                      (dep, index) => (
                        <option value={dep.sitePreview} key={index}>
                          {dep.sitePreview}
                        </option>
                      ),
                    )}
                  </select>
                  <span className="select-down-icon">
                    <FontAwesomeIcon icon={faChevronDown} />
                  </span>
                </div>
                <button
                  className="add-domain-button"
                  disabled={!domainName || !deployedSite}
                  onClick={addDomainDetails}
                >
                  {domainLoading ? (
                    <BounceLoader size={20} color={"#fff"} loading={true} />
                  ) : (
                    "Add"
                  )}
                </button>
              </div>
              <div className="domain-general-domain-list">
                {!projectLoading ? (
                  selectedProject?.handshakeSubdomains?.length ? (
                    selectedProject?.handshakeSubdomains?.map((subdomain, index) => (
                      <div key={index}>
                        <DomainItem
                          index={index}
                          type="filled"
                          domainId={`${subdomain._id}`}
                          domain={`${subdomain.name}`}
                          link={`${subdomain.link}`}
                          isSubdomain={true}
                          autoDns={subdomain.isLatest}
                          uuid={`${subdomain.argoKey}`}
                          ownerVerified={subdomain.verified}
                          domainType={subdomain.type}
                        />
                      </div>
                    ))
                  ) : null
                ) : (
                  <>
                    <DomainItem
                      index={1}
                      type="skeleton"
                      domainId=""
                      domain=""
                      link=""
                      uuid=""
                      isSubdomain={true}
                      autoDns={false}
                      ownerVerified={true}
                      domainType=""
                    />
                  </>
                )}
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}
Example #13
Source File: Overview.tsx    From argo-react with MIT License 4 votes vote down vote up
Overview = () => {
  const history = useHistory();
  const params = useParams<any>();
  const componentIsMounted = useRef(true);

  useEffect(() => {
    return () => {
      componentIsMounted.current = false;
    };
  }, []);

  const [pinDetailLoading, setPinDetailLoading] = useState<boolean>(true);
  const [pinDetail, setPinDetail] = useState<any>({ cid: "N.A", isPinned: false });

  const { projectLoading, selectedProject, selectedOrg, orgLoading } =
    useContext<IStateModel>(StateContext);

  const latestDeployment: any = selectedProject?.latestDeployment;

  const lastPublishedDate = moment(latestDeployment?.createdAt).format(
    "MMM DD, YYYY hh:mm A",
  );

  const lastCreatedDate = moment(selectedProject?.createdAt).format(
    "MMM DD, YYYY hh:mm A",
  );

  let displayGithubRepo = "";
  let githubBranchLink = "";
  if (selectedProject) {
    displayGithubRepo = selectedProject.githubUrl.substring(
      19,
      selectedProject.githubUrl.length - 4,
    );

    githubBranchLink = `${selectedProject.githubUrl.substring(
      0,
      selectedProject.githubUrl.length - 4,
    )}/tree/${"master"}`;
  }

  useEffect(() => {
    if (latestDeployment?.configuration?.protocol === "ipfs-filecoin") {
      getFilecoinPinDetais();
    } else if (latestDeployment?.configuration?.protocol === "ipfs-pinata") {
      getPinataPinDetais();
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [latestDeployment?.configuration?.protocol]);

  const getFilecoinPinDetais = async () => {
    setPinDetailLoading(true);
    if (latestDeployment.sitePreview) {
      const cid = latestDeployment.sitePreview.split(
        "https://ipfs.infura.io/ipfs/",
      )[1];
      ApiService.getFilecoinPinDetails(cid).subscribe((data) => {
        if (componentIsMounted.current) {
          setPinDetail(data);
          setPinDetailLoading(false);
        }
      });
    } else {
      setPinDetailLoading(false);
    }
  };
  const getPinataPinDetais = async () => {
    setPinDetailLoading(true);
    if (latestDeployment.sitePreview) {
      const cid = latestDeployment.sitePreview.split(
        "https://ipfs.infura.io/ipfs/",
      )[1];
      ApiService.getPinataPinDetails(cid).subscribe((data) => {
        if (componentIsMounted.current) {
          setPinDetail(data);
          setPinDetailLoading(false);
        }
      });
    } else {
      setPinDetailLoading(false);
    }
  };

  return (
    <div className="SiteOverview">
      <ProjectTopCard />
      <div
        className="site-overview-card-container domain-container"
        onClick={(e) =>
          history.push(`/org/${params.orgid}/sites/${params.siteid}/domain`)
        }
      >
        <div className="domain-container-left">
          <h2>Set up a custom domain with ArGo</h2>
          <p>
            Setup a domain you already own. All domains come with a free SSL cert.
          </p>
        </div>
        {!projectLoading && (
          <div className="domain-container-right">
            {!selectedProject?.domains.length ? (
              <span className="blue-color">
                <FontAwesomeIcon icon={faArrowRight} />
              </span>
            ) : (
              <span className="green-color">
                <FontAwesomeIcon icon={faCheck} />
              </span>
            )}
          </div>
        )}
      </div>
      {latestDeployment?.configuration?.protocol === "ipfs-filecoin" && (
        <div className="site-overview-card-container deploy-container">
          <div className="site-overview-header-title">
            Latest Filecoin Deployment Pinning Details
          </div>
          <div className="deploy-summary-item">
            <div className="deploy-summary-body-item">
              <label>Filecoin CID:</label>
              <span>
                {!pinDetailLoading ? (
                  pinDetail.cid
                ) : (
                  <Skeleton width={200} duration={2} />
                )}
              </span>
            </div>
            <div className="deploy-summary-body-item">
              <label>Filecoin Pinning Status:</label>
              <span>
                {!pinDetailLoading ? (
                  pinDetail.isPinned ? (
                    "Pinned"
                  ) : (
                    "Not Pinned"
                  )
                ) : (
                  <Skeleton width={200} duration={2} />
                )}
              </span>
            </div>
            {!pinDetailLoading && pinDetail.isPinned && (
              <div className="deploy-summary-body-item">
                <label>Filecoin Pinned Date:</label>
                <span>
                  {!pinDetailLoading ? (
                    moment(pinDetail.pinnedDate).format("MMM DD, YYYY hh:mm A")
                  ) : (
                    <Skeleton width={200} duration={2} />
                  )}
                </span>
              </div>
            )}
          </div>
        </div>
      )}
      {latestDeployment?.configuration?.protocol === "ipfs-pinata" && (
        <div className="site-overview-card-container deploy-container">
          <div className="site-overview-header-title">
            Latest IPFS Deployment Pinning Details
          </div>
          <div className="deploy-summary-item">
            <div className="deploy-summary-body-item">
              <label>IPFS CID:</label>
              <span>
                {!pinDetailLoading ? (
                  pinDetail.cid
                ) : (
                  <Skeleton width={200} duration={2} />
                )}
              </span>
            </div>
            <div className="deploy-summary-body-item">
              <label>IPFS Pinning Status:</label>
              <span>
                {!pinDetailLoading ? (
                  pinDetail.isPinned ? (
                    "Pinned"
                  ) : (
                    "Not Pinned"
                  )
                ) : (
                  <Skeleton width={200} duration={2} />
                )}
              </span>
            </div>
            {!pinDetailLoading && pinDetail.isPinned && (
              <div className="deploy-summary-body-item">
                <label>IPFS Pinned Date:</label>
                <span>
                  {!pinDetailLoading ? (
                    moment(pinDetail.pinnedDate).format("MMM DD, YYYY hh:mm A")
                  ) : (
                    <Skeleton width={200} duration={2} />
                  )}
                </span>
              </div>
            )}
          </div>
        </div>
      )}
      <div className="site-overview-card-container deploy-container">
        <div className="site-overview-header-title">Project Overview</div>
        <div className="deploy-summary-item">
          <div className="deploy-summary-body-item">
            <label>Name:</label>
            <span>
              {!projectLoading ? (
                selectedProject?.name
              ) : (
                <Skeleton width={200} duration={2} />
              )}
            </span>
          </div>
          <div className="deploy-summary-body-item">
            <label>Owner:</label>
            <span>
              {!orgLoading ? (
                selectedOrg?.profile.name
              ) : (
                <Skeleton width={200} duration={2} />
              )}
            </span>
          </div>
          <div className="deploy-summary-body-item">
            <label>GitHub Repo/Branch:</label>
            <a href={githubBranchLink} target="_blank" rel="noopener noreferrer">
              {!projectLoading ? (
                `${displayGithubRepo} (branch: ${selectedProject?.latestDeployment?.configuration.branch})`
              ) : (
                <Skeleton width={200} duration={2} />
              )}
            </a>
          </div>
          <div className="deploy-summary-body-item">
            <label>Latest deploy site:</label>
            {!projectLoading ? (
              latestDeployment?.sitePreview ? (
                <a
                  href={latestDeployment?.sitePreview}
                  target="_blank"
                  rel="noopener noreferrer"
                >
                  {latestDeployment?.sitePreview}
                </a>
              ) : (
                <span>Site preview not available</span>
              )
            ) : (
              <Skeleton width={200} duration={2} />
            )}
          </div>
          <div className="deploy-summary-body-item">
            <label>Created:</label>
            <span>
              {!projectLoading ? (
                lastCreatedDate
              ) : (
                <Skeleton width={200} duration={2} />
              )}
            </span>
          </div>
          <div className="deploy-summary-body-item">
            <label>Last Published:</label>
            <span>
              {!projectLoading ? (
                lastPublishedDate
              ) : (
                <Skeleton width={200} duration={2} />
              )}
            </span>
          </div>
        </div>
      </div>
    </div>
  );
}
Example #14
Source File: Editor.tsx    From zwo-editor with MIT License 4 votes vote down vote up
Editor = ({ match }: RouteComponentProps<TParams>) => {
  const { v4: uuidv4 } = require("uuid");

  const S3_URL = "https://zwift-workout.s3-eu-west-1.amazonaws.com";

  const [id, setId] = useState(
    match.params.id === "new"
      ? localStorage.getItem("id") || generateId()
      : match.params.id
  );
  const [bars, setBars] = useState<Array<Bar>>(
    JSON.parse(localStorage.getItem("currentWorkout") || "[]")
  );
  const [actionId, setActionId] = useState<string | undefined>(undefined);
  const [ftp, setFtp] = useState(
    parseInt(localStorage.getItem("ftp") || "200")
  );
  const [weight, setWeight] = useState(
    parseInt(localStorage.getItem("weight") || "75")
  );
  const [instructions, setInstructions] = useState<Array<Instruction>>(
    JSON.parse(localStorage.getItem("instructions") || "[]")
  );
  const [tags, setTags] = useState(
    JSON.parse(localStorage.getItem("tags") || "[]")
  );

  const [name, setName] = useState(localStorage.getItem("name") || "");
  const [description, setDescription] = useState(
    localStorage.getItem("description") || ""
  );
  const [author, setAuthor] = useState(localStorage.getItem("author") || "");

  const [savePopupIsVisile, setSavePopupVisibility] = useState(false);
  const [sharePopupIsVisile, setSharePopupVisibility] = useState(false);

  const [user, setUser] = useState<firebase.User | null>(null);
  const [visibleForm, setVisibleForm] = useState("login"); // default form is login

  const canvasRef = useRef<HTMLInputElement>(null);
  const segmentsRef = useRef<HTMLInputElement>(null);
  const [segmentsWidth, setSegmentsWidth] = useState(1320);

  const [message, setMessage] = useState<Message>();

  const [showWorkouts, setShowWorkouts] = useState(false);

  // bike or run
  const [sportType, setSportType] = useState<SportType>(
    (localStorage.getItem("sportType") as SportType) || "bike"
  );

  // distance or time
  const [durationType, setDurationType] = useState<DurationType>(
    (localStorage.getItem("durationType") as DurationType) || "time"
  );

  const [runningTimes, setRunningTimes] = useState(loadRunningTimes());

  const [textEditorIsVisible, setTextEditorIsVisible] = useState(false);
  const [selectedInstruction, setSelectedInstruction] = useState<Instruction>();

  const db = firebase.database();

  const location = useLocation();

  useEffect(() => {
    const params = new URLSearchParams(location.search);
    const mode = params.get("mode");
    if (mode === "resetPassword") {
      setVisibleForm("updatePassword");
      setSavePopupVisibility(true);
    }
  }, [location]);

  useEffect(() => {
    setMessage({ visible: true, class: "loading", text: "Loading.." });

    db.ref("workouts/" + id)
      .once("value")
      .then(function (snapshot) {
        if (snapshot.val()) {
          // workout exist on server
          setAuthor(snapshot.val().author);
          setName(snapshot.val().name);
          setDescription(snapshot.val().description);
          setBars(snapshot.val().workout || []);
          setInstructions(snapshot.val().instructions || []);
          setTags(snapshot.val().tags || []);
          setDurationType(snapshot.val().durationType);
          setSportType(snapshot.val().sportType);

          localStorage.setItem("id", id);
        } else {
          // workout doesn't exist on cloud
          if (id === localStorage.getItem("id")) {
            // user refreshed the page
          } else {
            // treat this as new workout
            setBars([]);
            setInstructions([]);
            setName("");
            setDescription("");
            setAuthor("");
            setTags([]);
          }

          localStorage.setItem("id", id);
        }
        console.log("useEffect firebase");

        //finished loading
        setMessage({ visible: false });
      });

    auth.onAuthStateChanged((user) => {
      if (user) {
        setUser(user);
      }
    });

    window.history.replaceState("", "", `/editor/${id}`);

    ReactGA.initialize("UA-55073449-9");
    ReactGA.pageview(window.location.pathname + window.location.search);
  }, [id, db]);

  useEffect(() => {
    localStorage.setItem("currentWorkout", JSON.stringify(bars));
    localStorage.setItem("ftp", ftp.toString());

    localStorage.setItem("instructions", JSON.stringify(instructions));
    localStorage.setItem("weight", weight.toString());

    localStorage.setItem("name", name);
    localStorage.setItem("description", description);
    localStorage.setItem("author", author);
    localStorage.setItem("tags", JSON.stringify(tags));
    localStorage.setItem("sportType", sportType);
    localStorage.setItem("durationType", durationType);

    localStorage.setItem("runningTimes", JSON.stringify(runningTimes));

    setSegmentsWidth(segmentsRef.current?.scrollWidth || 1320);
  }, [
    segmentsRef,
    bars,
    ftp,
    instructions,
    weight,
    name,
    description,
    author,
    tags,
    sportType,
    durationType,
    runningTimes,
  ]);

  function generateId() {
    return Math.random().toString(36).substr(2, 16);
  }

  function newWorkout() {
    console.log("New workout");

    setId(generateId());
    setBars([]);
    setInstructions([]);
    setName("");
    setDescription("");
    setAuthor("");
    setTags([]);
  }

  function handleOnChange(id: string, values: Bar) {
    const index = bars.findIndex((bar) => bar.id === id);

    const updatedArray = [...bars];
    updatedArray[index] = values;

    setBars(updatedArray);
  }

  function handleOnClick(id: string) {
    if (id === actionId) {
      setActionId(undefined);
    } else {
      setActionId(id);
    }
  }

  function handleKeyPress(event: React.KeyboardEvent<HTMLDivElement>) {
    if (event.target instanceof HTMLInputElement) {
      // Ignore key presses coming from input elements
      return;
    }

    if (event.target instanceof HTMLTextAreaElement) {
      // Ignore key presses coming from textarea elements
      return;
    }

    switch (event.keyCode) {
      case 8:
        removeBar(actionId || "");
        // Prevent navigation to previous page
        event.preventDefault();
        break;
      case 37:
        // reduce time
        removeTimeToBar(actionId || "");
        break;
      case 39:
        // add time
        addTimeToBar(actionId || "");
        break;
      case 38:
        // add power
        addPowerToBar(actionId || "");
        break;
      case 40:
        // add power
        removePowerToBar(actionId || "");
        break;
      default:
        //console.log(event.keyCode);
        break;
    }
  }

  function addBar(
    zone: number,
    duration: number = 300,
    cadence: number = 0,
    pace: number = 0,
    length: number = 200
  ) {
    setBars((bars) => [
      ...bars,
      {
        time:
          durationType === "time"
            ? duration
            : helpers.round(
                helpers.calculateTime(length, calculateSpeed(pace)),
                1
              ),
        length:
          durationType === "time"
            ? helpers.round(
                helpers.calculateDistance(duration, calculateSpeed(pace)),
                1
              )
            : length,
        power: zone,
        cadence: cadence,
        type: "bar",
        id: uuidv4(),
        pace: pace,
      },
    ]);
  }

  function addTrapeze(
    zone1: number,
    zone2: number,
    duration: number = 300,
    pace: number = 0,
    length: number = 1000,
    cadence: number = 0
  ) {
    setBars((bars) => [
      ...bars,
      {
        time:
          durationType === "time"
            ? duration
            : helpers.round(
                helpers.calculateTime(length, calculateSpeed(pace)),
                1
              ),
        length:
          durationType === "time"
            ? helpers.round(
                helpers.calculateDistance(duration, calculateSpeed(pace)),
                1
              )
            : length,
        startPower: zone1,
        endPower: zone2,
        cadence: cadence,
        pace: pace,
        type: "trapeze",
        id: uuidv4(),
      },
    ]);
  }

  function addFreeRide(
    duration: number = 600,
    cadence: number = 0,
    length: number = 1000
  ) {
    setBars((bars) => [
      ...bars,
      {
        time: durationType === "time" ? duration : 0,
        length: durationType === "time" ? 0 : length,
        cadence: cadence,
        type: "freeRide",
        id: uuidv4(),
      },
    ]);
  }

  function addInterval(
    repeat: number = 3,
    onDuration: number = 30,
    offDuration: number = 120,
    onPower: number = 1,
    offPower: number = 0.5,
    cadence: number = 0,
    restingCadence: number = 0,
    pace: number = 0,
    onLength: number = 200,
    offLength: number = 200
  ) {
    setBars((bars) => [
      ...bars,
      {
        time:
          durationType === "time"
            ? (onDuration + offDuration) * repeat
            : helpers.round(
                helpers.calculateTime(
                  (onLength + offLength) * repeat,
                  calculateSpeed(pace)
                ),
                1
              ),
        length:
          durationType === "time"
            ? helpers.round(
                helpers.calculateDistance(
                  (onDuration + offDuration) * repeat,
                  calculateSpeed(pace)
                ),
                1
              )
            : (onLength + offLength) * repeat,
        id: uuidv4(),
        type: "interval",
        cadence: cadence,
        restingCadence: restingCadence,
        repeat: repeat,
        onDuration:
          durationType === "time"
            ? onDuration
            : helpers.round(
                helpers.calculateTime(
                  (onLength * 1) / onPower,
                  calculateSpeed(pace)
                ),
                1
              ),
        offDuration:
          durationType === "time"
            ? offDuration
            : helpers.round(
                helpers.calculateTime(
                  (offLength * 1) / offPower,
                  calculateSpeed(pace)
                ),
                1
              ),
        onPower: onPower,
        offPower: offPower,
        pace: pace,
        onLength:
          durationType === "time"
            ? helpers.round(
                helpers.calculateDistance(
                  (onDuration * 1) / onPower,
                  calculateSpeed(pace)
                ),
                1
              )
            : onLength,
        offLength:
          durationType === "time"
            ? helpers.round(
                helpers.calculateDistance(
                  (offDuration * 1) / offPower,
                  calculateSpeed(pace)
                ),
                1
              )
            : offLength,
      },
    ]);
  }

  function addInstruction(text = "", time = 0, length = 0) {
    setInstructions((instructions) => [
      ...instructions,
      {
        text: text,
        time: time,
        length: length,
        id: uuidv4(),
      },
    ]);
  }

  function changeInstruction(id: string, values: Instruction) {
    const index = instructions.findIndex(
      (instructions) => instructions.id === id
    );

    const updatedArray = [...instructions];
    updatedArray[index] = values;
    setInstructions(updatedArray);
  }

  function deleteInstruction(id: string) {
    const updatedArray = [...instructions];
    setInstructions(updatedArray.filter((item) => item.id !== id));
  }

  function removeBar(id: string) {
    const updatedArray = [...bars];
    setBars(updatedArray.filter((item) => item.id !== id));
    setActionId(undefined);
  }

  function addTimeToBar(id: string) {
    const updatedArray = [...bars];

    const index = updatedArray.findIndex((bar) => bar.id === id);
    const element = updatedArray[index];
    if (element && durationType === "time") {
      element.time = element.time + 5;
      element.length =
        (helpers.calculateDistance(
          element.time,
          calculateSpeed(element.pace || 0)
        ) *
          1) /
        (element.power || 1);
      setBars(updatedArray);
    }

    if (element && durationType === "distance") {
      element.length = (element.length || 0) + 200;
      element.time =
        (helpers.calculateTime(
          element.length,
          calculateSpeed(element.pace || 0)
        ) *
          1) /
        (element.power || 1);
      setBars(updatedArray);
    }
  }

  function removeTimeToBar(id: string) {
    const updatedArray = [...bars];

    const index = updatedArray.findIndex((bar) => bar.id === id);
    const element = updatedArray[index];
    if (element && element.time > 5 && durationType === "time") {
      element.time = element.time - 5;
      element.length =
        (helpers.calculateDistance(
          element.time,
          calculateSpeed(element.pace || 0)
        ) *
          1) /
        (element.power || 1);
      setBars(updatedArray);
    }

    if (element && (element.length || 0) > 200 && durationType === "distance") {
      element.length = (element.length || 0) - 200;
      element.time =
        (helpers.calculateTime(
          element.length,
          calculateSpeed(element.pace || 0)
        ) *
          1) /
        (element.power || 1);
      setBars(updatedArray);
    }
  }

  function addPowerToBar(id: string) {
    const updatedArray = [...bars];

    const index = updatedArray.findIndex((bar) => bar.id === id);
    const element = updatedArray[index];
    if (element && element.power) {
      element.power = parseFloat((element.power + 1 / ftp).toFixed(3));

      if (durationType === "time") {
        element.length =
          (helpers.calculateDistance(
            element.time,
            calculateSpeed(element.pace || 0)
          ) *
            1) /
          element.power;
      } else {
        element.time =
          (helpers.calculateTime(
            element.length,
            calculateSpeed(element.pace || 0)
          ) *
            1) /
          element.power;
      }

      setBars(updatedArray);
    }
  }

  function removePowerToBar(id: string) {
    const updatedArray = [...bars];

    const index = updatedArray.findIndex((bar) => bar.id === id);
    const element = updatedArray[index];
    if (element && element.power && element.power >= Zones.Z1.min) {
      element.power = parseFloat((element.power - 1 / ftp).toFixed(3));

      if (durationType === "time") {
        element.length =
          (helpers.calculateDistance(
            element.time,
            calculateSpeed(element.pace || 0)
          ) *
            1) /
          element.power;
      } else {
        element.time =
          (helpers.calculateTime(
            element.length,
            calculateSpeed(element.pace || 0)
          ) *
            1) /
          element.power;
      }

      setBars(updatedArray);
    }
  }

  function duplicateBar(id: string) {
    const index = bars.findIndex((bar) => bar.id === id);
    const element = [...bars][index];

    if (element.type === "bar")
      addBar(
        element.power || 80,
        element.time,
        element.cadence,
        element.pace,
        element.length
      );
    if (element.type === "freeRide")
      addFreeRide(element.time, element.cadence, element.length);
    if (element.type === "trapeze")
      addTrapeze(
        element.startPower || 80,
        element.endPower || 160,
        element.time,
        element.pace || 0,
        element.length,
        element.cadence
      );
    if (element.type === "interval")
      addInterval(
        element.repeat,
        element.onDuration,
        element.offDuration,
        element.onPower,
        element.offPower,
        element.cadence,
        element.restingCadence,
        element.pace,
        element.onLength,
        element.offLength
      );

    setActionId(undefined);
  }

  function moveLeft(id: string) {
    const index = bars.findIndex((bar) => bar.id === id);
    // not first position of array
    if (index > 0) {
      const updatedArray = [...bars];
      const element = [...bars][index];
      updatedArray.splice(index, 1);
      updatedArray.splice(index - 1, 0, element);
      setBars(updatedArray);
    }
  }

  function moveRight(id: string) {
    const index = bars.findIndex((bar) => bar.id === id);
    // not first position of array
    if (index < bars.length - 1) {
      const updatedArray = [...bars];
      const element = [...bars][index];
      updatedArray.splice(index, 1);
      updatedArray.splice(index + 1, 0, element);
      setBars(updatedArray);
    }
  }

  function saveWorkout() {
    setSavePopupVisibility(true);
  }

  function deleteWorkout() {
    // save to cloud (firebase) if logged in
    if (user) {
      const itemsRef = firebase.database().ref();

      var updates: any = {};
      updates[`users/${user.uid}/workouts/${id}`] = null;
      updates[`workouts/${id}`] = null;

      // save to firebase
      itemsRef
        .update(updates)
        .then(() => {
          newWorkout();
        })
        .catch((error) => {
          console.log(error);
          setMessage({
            visible: true,
            class: "error",
            text: "Cannot delete workout",
          });
        });
    }
  }

  function shareWorkout() {
    if (user) {
      save();
      setSharePopupVisibility(true);
    } else {
      saveWorkout();
    }
  }

  function save() {
    setMessage({ visible: true, class: "loading", text: "Saving.." });

    const xml = createWorkoutXml({
      author,
      name,
      description,
      sportType,
      durationType,
      tags,
      bars,
      instructions,
    });

    const file = new Blob([xml], { type: "application/xml" });

    // save to cloud (firebase) if logged in
    if (user) {
      const itemsRef = firebase.database().ref();

      const item = {
        id: id,
        name: name,
        description: description,
        author: author,
        workout: bars,
        tags: tags,
        instructions: instructions,
        userId: user.uid,
        updatedAt: Date(),
        sportType: sportType,
        durationType: durationType,
      };

      const item2 = {
        name: name,
        description: description,
        updatedAt: Date(),
        sportType: sportType,
        durationType: durationType,
        workoutTime: helpers.formatDuration(
          helpers.getWorkoutLength(bars, durationType)
        ),
        workoutDistance: helpers.getWorkoutDistance(bars),
      };

      var updates: any = {};
      updates[`users/${user.uid}/workouts/${id}`] = item2;
      updates[`workouts/${id}`] = item;

      // save to firebase
      itemsRef
        .update(updates)
        .then(() => {
          //upload to s3
          upload(file, false);
          setMessage({ visible: false });
        })
        .catch((error) => {
          console.log(error);
          setMessage({
            visible: true,
            class: "error",
            text: "Cannot save this",
          });
        });
    } else {
      // download workout without saving
      setMessage({ visible: false });
    }

    return file;
  }

  function logout() {
    console.log("logout");

    auth.signOut().then(() => setUser(null));
  }

  function downloadWorkout() {
    const tempFile = save();
    const url = window.URL.createObjectURL(tempFile);

    var a = document.createElement("a");
    document.body.appendChild(a);
    a.style.display = "none";
    a.href = url;
    a.download = `${id}.zwo`;
    a.click();
    window.URL.revokeObjectURL(url);
  }

  function handleUpload(file: Blob) {
    // ask user if they want to overwrite current workout first
    if (bars.length > 0) {
      if (!window.confirm("Are you sure you want to create a new workout?")) {
        return false;
      }
    }

    newWorkout();
    upload(file, true);
  }

  function upload(file: Blob, parse = false) {
    fetch(process.env.REACT_APP_UPLOAD_FUNCTION || "https://zwiftworkout.netlify.app/.netlify/functions/upload", {
      method: "POST",
      body: JSON.stringify({
        fileType: "zwo",
        fileName: `${id}.zwo`,
      }),
    })
      .then((res) => res.json())
      .then(function (data) {
        const signedUrl = data.uploadURL;

        // upload to S3
        fetch(signedUrl, {
          method: "PUT",
          headers: {
            "Content-Type": "zwo",
          },
          body: file,
        })
          .then((response) => response.text())
          .then((data) => {
            console.log("File uploaded");

            // can parse now

            if (parse) fetchAndParse(id);
          })
          .catch((error) => {
            console.error(error);
          });
      });
  }

  function fetchAndParse(id: string) {
    // remove previous workout

    // TODO fix for running distance based
    setBars([]);
    setInstructions([]);

    fetch(`${S3_URL}/${id}.zwo`)
      .then((response) => response.text())
      .then((data) => {
        // remove xml comments
        data = data.replace(/<!--(.*?)-->/gm, "");

        //now parse file
        const workout = Converter.xml2js(data);
        const workout_file = workout.elements[0];

        if (workout_file.name === "workout_file") {
          // file is valid
          const authorIndex = workout_file.elements.findIndex(
            (element: { name: string }) => element.name === "author"
          );
          if (
            authorIndex !== -1 &&
            workout_file.elements[authorIndex].elements
          ) {
            setAuthor(workout_file.elements[authorIndex].elements[0].text);
          }

          const nameIndex = workout_file.elements.findIndex(
            (element: { name: string }) => element.name === "name"
          );
          if (nameIndex !== -1 && workout_file.elements[nameIndex].elements) {
            setName(workout_file.elements[nameIndex].elements[0].text);
          }

          const descriptionIndex = workout_file.elements.findIndex(
            (element: { name: string }) => element.name === "description"
          );
          if (
            descriptionIndex !== -1 &&
            workout_file.elements[descriptionIndex].elements
          ) {
            setDescription(
              workout_file.elements[descriptionIndex].elements[0].text
            );
          }

          const workoutIndex = workout_file.elements.findIndex(
            (element: { name: string }) => element.name === "workout"
          );

          var totalTime = 0;

          workout_file.elements[workoutIndex].elements.map(
            (w: {
              name: string;
              attributes: {
                Power: any;
                PowerLow: string;
                Duration: string;
                PowerHigh: string;
                Cadence: string;
                CadenceResting: string;
                Repeat: string;
                OnDuration: string;
                OffDuration: string;
                OnPower: string;
                OffPower: string;
                Pace: string;
              };
              elements: any;
            }) => {
              let duration = parseFloat(w.attributes.Duration);

              if (w.name === "SteadyState")
                addBar(
                  parseFloat(w.attributes.Power || w.attributes.PowerLow),
                  parseFloat(w.attributes.Duration),
                  parseFloat(w.attributes.Cadence || "0"),
                  parseInt(w.attributes.Pace || "0")
                );

              if (
                w.name === "Ramp" ||
                w.name === "Warmup" ||
                w.name === "Cooldown"
              )
                addTrapeze(
                  parseFloat(w.attributes.PowerLow),
                  parseFloat(w.attributes.PowerHigh),
                  parseFloat(w.attributes.Duration),
                  parseInt(w.attributes.Pace || "0"),
                  undefined,
                  parseInt(w.attributes.Cadence)
                );

              if (w.name === "IntervalsT") {
                addInterval(
                  parseFloat(w.attributes.Repeat),
                  parseFloat(w.attributes.OnDuration),
                  parseFloat(w.attributes.OffDuration),
                  parseFloat(w.attributes.OnPower),
                  parseFloat(w.attributes.OffPower),
                  parseInt(w.attributes.Cadence || "0"),
                  parseInt(w.attributes.CadenceResting),
                  parseInt(w.attributes.Pace || "0")
                );
                duration =
                  (parseFloat(w.attributes.OnDuration) +
                    parseFloat(w.attributes.OffDuration)) *
                  parseFloat(w.attributes.Repeat);
              }

              if (w.name === "FreeRide")
                addFreeRide(
                  parseFloat(w.attributes.Duration),
                  parseInt(w.attributes.Cadence)
                );

              // check for instructions
              const textElements = w.elements;
              if (textElements && textElements.length > 0) {
                textElements.map(
                  (t: {
                    name: string;
                    attributes: {
                      message: string | undefined;
                      timeoffset: string;
                    };
                  }) => {
                    if (t.name.toLowerCase() === "textevent")
                      addInstruction(
                        t.attributes.message,
                        totalTime + parseFloat(t.attributes.timeoffset)
                      );

                    return false;
                  }
                );
              }

              totalTime = totalTime + duration;
              // map functions expect return value
              return false;
            }
          );
        }
      })
      .catch((error) => {
        console.error(error);
      });
  }

  function calculateSpeed(pace: number = 0) {
    if (sportType === "bike") {
      return 0;
    } else {
      // return speed in m/s
      // speed  = distance / time
      const distances = [1.60934, 5, 10, 21.0975, 42.195];
      const times = [
        runningTimes.oneMile,
        runningTimes.fiveKm,
        runningTimes.tenKm,
        runningTimes.halfMarathon,
        runningTimes.marathon,
      ];

      return (distances[pace] * 1000) / helpers.getTimeinSeconds(times[pace]);
    }
  }

  const renderBar = (bar: Bar) => (
    <Bar
      key={bar.id}
      id={bar.id}
      time={bar.time}
      length={bar.length || 200}
      power={bar.power || 100}
      cadence={bar.cadence}
      ftp={ftp}
      weight={weight}
      sportType={sportType}
      durationType={durationType}
      pace={bar.pace || 0}
      speed={calculateSpeed(bar.pace || 0)}
      onChange={(id: string, value: any) => handleOnChange(id, value)} // Change any to Interface Bar?
      onClick={(id: string) => handleOnClick(id)}
      selected={bar.id === actionId}
      showLabel={true}
    />
  );

  const renderTrapeze = (bar: Bar) => (
    <Trapeze
      key={bar.id}
      id={bar.id}
      time={bar.time}
      length={bar.length || 200}
      cadence={bar.cadence}
      startPower={bar.startPower || 80}
      endPower={bar.endPower || 160}
      ftp={ftp}
      sportType={sportType}
      durationType={durationType}
      pace={bar.pace || 0}
      speed={calculateSpeed(bar.pace || 0)}
      onChange={(id: string, value: any) => handleOnChange(id, value)} // Change any to Interface Bar?
      onClick={(id: string) => handleOnClick(id)}
      selected={bar.id === actionId}
    />
  );

  const renderFreeRide = (bar: Bar) => (
    <FreeRide
      key={bar.id}
      id={bar.id}
      time={bar.time}
      length={bar.length}
      cadence={bar.cadence}
      durationType={durationType}
      sportType={sportType}
      onChange={(id: string, value: any) => handleOnChange(id, value)} // Change any to Interface Bar?
      onClick={(id: string) => handleOnClick(id)}
      selected={bar.id === actionId}
    />
  );

  const renderInterval = (bar: Bar) => (
    <Interval
      key={bar.id}
      id={bar.id}
      repeat={bar.repeat || 3}
      onDuration={bar.onDuration || 10}
      offDuration={bar.offDuration || 50}
      onPower={bar.onPower || 250}
      offPower={bar.offPower || 120}
      onLength={bar.onLength || 200}
      offLength={bar.offLength || 200}
      cadence={bar.cadence}
      restingCadence={bar.restingCadence || 0}
      ftp={ftp}
      weight={weight}
      sportType={sportType}
      durationType={durationType}
      pace={bar.pace || 0}
      speed={calculateSpeed(bar.pace || 0)}
      handleIntervalChange={(id: string, value: any) =>
        handleOnChange(id, value)
      }
      handleIntervalClick={(id: string) => handleOnClick(id)}
      selected={bar.id === actionId}
    />
  );

  const renderComment = (instruction: Instruction, index: number) => (
    <Comment
      key={instruction.id}
      instruction={instruction}
      durationType={durationType}
      width={
        durationType === "distance"
          ? parseInt(helpers.getWorkoutDistance(bars)) * 100
          : helpers.getWorkoutLength(bars, durationType) / 3
      }
      onChange={(id: string, values: Instruction) =>
        changeInstruction(id, values)
      }
      onClick={(id: string) =>
        setSelectedInstruction(instructions.find((i) => i.id === id))
      }
      index={index}
    />
  );

  const renderRegistrationForm = () => {
    switch (visibleForm) {
      case "login":
        return (
          <LoginForm
            login={setUser}
            showSignup={() => setVisibleForm("signup")}
            dismiss={() => setSavePopupVisibility(false)}
            showForgotPassword={() => setVisibleForm("forgotPassword")}
          />
        );
      case "signup":
        return (
          <SignupForm
            signUp={setUser}
            showLogin={() => setVisibleForm("login")}
            dismiss={() => setSavePopupVisibility(false)}
          />
        );
      case "forgotPassword":
        return (
          <ForgotPasswordForm
            dismiss={() => setSavePopupVisibility(false)}
            workoutId={id}
          />
        );
      case "updatePassword":
        return (
          <UpdatePasswordForm
            dismiss={() => {
              setVisibleForm("login");
              setSavePopupVisibility(true);
            }}
          />
        );
      default:
        break;
    }
  };

  function setPace(value: string, id: string) {
    const index = bars.findIndex((bar) => bar.id === id);

    if (index !== -1) {
      const updatedArray = [...bars];
      const element = [...updatedArray][index];
      element.pace = parseInt(value);

      if (durationType === "time") {
        element.length =
          (helpers.calculateDistance(
            element.time,
            calculateSpeed(element.pace || 0)
          ) *
            1) /
          (element.power || 1);
      } else {
        element.time =
          (helpers.calculateTime(
            element.length,
            calculateSpeed(element.pace || 0)
          ) *
            1) /
          (element.power || 1);
      }

      setBars(updatedArray);
    }
  }

  function getPace(id: string) {
    const index = bars.findIndex((bar) => bar.id === id);

    if (index !== -1) {
      const element = [...bars][index];
      return element.pace;
    }
  }

  function switchSportType(newSportType: SportType) {
    setSportType(newSportType);
    setDurationType(newSportType === "bike" ? "time" : "distance");
  }

  function toggleTextEditor() {
    if (bars.length > 0 && !textEditorIsVisible) {
      if (
        window.confirm(
          "Editing a workout from the text editor will overwrite current workout"
        )
      )
        setTextEditorIsVisible(!textEditorIsVisible);
    } else {
      setTextEditorIsVisible(!textEditorIsVisible);
    }
  }

  function transformTextToWorkout(textValue: string) {
    // reset each time
    setBars([]);
    setInstructions([]);

    // 2 minutes block at 112% FTP
    // 2 minutes block at 330 W
    // 30 seconds block at ..

    //console.log(textValue);

    const workoutBlocks = textValue.split("\n");
    workoutBlocks.forEach((workoutBlock) => {
      if (workoutBlock.includes("steady")) {
        // generate a steady state block

        // extract watts
        const powerInWatts = workoutBlock.match(/([0-9]\d*w)/);
        const powerInWattsPerKg = workoutBlock.match(/([0-9]*.?[0-9]wkg)/);
        const powerInPercentageFtp = workoutBlock.match(/([0-9]\d*%)/);

        let power = powerInWatts ? parseInt(powerInWatts[0]) / ftp : 1;
        power = powerInWattsPerKg
          ? (parseFloat(powerInWattsPerKg[0]) * weight) / ftp
          : power;
        power = powerInPercentageFtp
          ? parseInt(powerInPercentageFtp[0]) / 100
          : power;

        // extract duration in seconds
        const durationInSeconds = workoutBlock.match(/([0-9]\d*s)/);
        const durationInMinutes = workoutBlock.match(/([0-9]*:?[0-9][0-9]*m)/);

        let duration = durationInSeconds && parseInt(durationInSeconds[0]);
        duration = durationInMinutes
          ? parseInt(durationInMinutes[0].split(":")[0]) * 60 +
            (parseInt(durationInMinutes[0].split(":")[1]) || 0)
          : duration;

        // extract cadence in rpm
        const cadence = workoutBlock.match(/([0-9]\d*rpm)/);
        const rpm = cadence ? parseInt(cadence[0]) : undefined;

        // extract multiplier
        // const multiplier = workoutBlock.match(/([0-9]\d*x)/)
        // const nTimes = multiplier ? Array(parseInt(multiplier[0])) : Array(1)
        // for (var i = 0; i < nTimes.length; i++)

        addBar(power, duration || 300, rpm);
      }

      if (
        workoutBlock.includes("ramp") ||
        workoutBlock.includes("warmup") ||
        workoutBlock.includes("cooldown")
      ) {
        // generate a steady ramp block

        // extract watts
        const startPowerInWatts = workoutBlock.match(/([0-9]\d*w)/);
        const startPowerInWattsPerKg = workoutBlock.match(/([0-9]*.?[0-9]wkg)/);
        const startPowerInPercentageFtp = workoutBlock.match(/([0-9]\d*%)/);

        let startPower = startPowerInWatts
          ? parseInt(startPowerInWatts[0]) / ftp
          : 1;
        startPower = startPowerInWattsPerKg
          ? (parseFloat(startPowerInWattsPerKg[0]) * weight) / ftp
          : startPower;
        startPower = startPowerInPercentageFtp
          ? parseInt(startPowerInPercentageFtp[0]) / 100
          : startPower;

        // extract watts
        const endPowerInWatts = workoutBlock.match(/(-[0-9]\d*w)/);
        const endPowerInWattsPerKg = workoutBlock.match(/(-[0-9]*.?[0-9]wkg)/);
        const endPowerInPercentageFtp = workoutBlock.match(/-([0-9]\d*%)/);

        let endPower = endPowerInWatts
          ? Math.abs(parseInt(endPowerInWatts[0])) / ftp
          : 1;
        endPower = endPowerInWattsPerKg
          ? (Math.abs(parseFloat(endPowerInWattsPerKg[0])) * weight) / ftp
          : endPower;
        endPower = endPowerInPercentageFtp
          ? Math.abs(parseInt(endPowerInPercentageFtp[0])) / 100
          : endPower;

        const durationInSeconds = workoutBlock.match(/([0-9]\d*s)/);
        const durationInMinutes = workoutBlock.match(/([0-9]*:?[0-9][0-9]*m)/);

        let duration = durationInSeconds && parseInt(durationInSeconds[0]);
        duration = durationInMinutes
          ? parseInt(durationInMinutes[0].split(":")[0]) * 60 +
            (parseInt(durationInMinutes[0].split(":")[1]) || 0)
          : duration;

        // extract cadence in rpm
        const cadence = workoutBlock.match(/([0-9]\d*rpm)/);
        const rpm = cadence ? parseInt(cadence[0]) : undefined;

        addTrapeze(
          startPower,
          endPower,
          duration || 300,
          undefined,
          undefined,
          rpm
        );
      }

      if (workoutBlock.includes("freeride")) {
        const durationInSeconds = workoutBlock.match(/([0-9]\d*s)/);
        const durationInMinutes = workoutBlock.match(/([0-9]*:?[0-9][0-9]*m)/);

        let duration = durationInSeconds && parseInt(durationInSeconds[0]);
        duration = durationInMinutes
          ? parseInt(durationInMinutes[0].split(":")[0]) * 60 +
            (parseInt(durationInMinutes[0].split(":")[1]) || 0)
          : duration;

        // extract cadence in rpm
        const cadence = workoutBlock.match(/([0-9]\d*rpm)/);
        const rpm = cadence ? parseInt(cadence[0]) : undefined;

        addFreeRide(duration || 600, rpm);
      }

      if (workoutBlock.includes("interval")) {
        const multiplier = workoutBlock.match(/([0-9]\d*x)/);
        const nTimes = multiplier ? parseInt(multiplier[0]) : 3;

        const durationInSeconds = workoutBlock.match(/([0-9]\d*s)/);
        const durationInMinutes = workoutBlock.match(/([0-9]*:?[0-9][0-9]*m)/);

        let duration = durationInSeconds && parseInt(durationInSeconds[0]);
        duration = durationInMinutes
          ? parseInt(durationInMinutes[0].split(":")[0]) * 60 +
            (parseInt(durationInMinutes[0].split(":")[1]) || 0)
          : duration;

        const offDurationInSeconds = workoutBlock.match(/(-[0-9]\d*s)/);
        const offDurationInMinutes = workoutBlock.match(
          /(-[0-9]*:?[0-9][0-9]*m)/
        );

        let offDuration =
          offDurationInSeconds && Math.abs(parseInt(offDurationInSeconds[0]));
        offDuration = offDurationInMinutes
          ? Math.abs(parseInt(offDurationInMinutes[0].split(":")[0])) * 60 +
            (parseInt(offDurationInMinutes[0].split(":")[1]) || 0)
          : offDuration;

        // extract watts
        const startPowerInWatts = workoutBlock.match(/([0-9]\d*w)/);
        const startPowerInWattsPerKg = workoutBlock.match(/([0-9]*.?[0-9]wkg)/);
        const startPowerInPercentageFtp = workoutBlock.match(/([0-9]\d*%)/);

        let startPower = startPowerInWatts
          ? parseInt(startPowerInWatts[0]) / ftp
          : 1;
        startPower = startPowerInWattsPerKg
          ? (parseFloat(startPowerInWattsPerKg[0]) * weight) / ftp
          : startPower;
        startPower = startPowerInPercentageFtp
          ? parseInt(startPowerInPercentageFtp[0]) / 100
          : startPower;

        // extract watts
        const endPowerInWatts = workoutBlock.match(/(-[0-9]\d*w)/);
        const endPowerInWattsPerKg = workoutBlock.match(/(-[0-9]*.?[0-9]wkg)/);
        const endPowerInPercentageFtp = workoutBlock.match(/-([0-9]\d*%)/);

        let endPower = endPowerInWatts
          ? Math.abs(parseInt(endPowerInWatts[0])) / ftp
          : 0.5;
        endPower = endPowerInWattsPerKg
          ? (Math.abs(parseFloat(endPowerInWattsPerKg[0])) * weight) / ftp
          : endPower;
        endPower = endPowerInPercentageFtp
          ? Math.abs(parseInt(endPowerInPercentageFtp[0])) / 100
          : endPower;

        // extract cadence in rpm
        const cadence = workoutBlock.match(/([0-9]\d*rpm)/);
        const rpm = cadence ? parseInt(cadence[0]) : undefined;

        const restingCadence = workoutBlock.match(/(-[0-9]\d*rpm)/);
        const restingRpm = restingCadence
          ? Math.abs(parseInt(restingCadence[0]))
          : undefined;

        addInterval(
          nTimes,
          duration || 30,
          offDuration || 120,
          startPower,
          endPower,
          rpm,
          restingRpm
        );
      }

      if (workoutBlock.includes("message")) {
        // extract message
        const message = workoutBlock.match(/["'](.*?)["']/);
        const text = message ? message[0] : "";

        const durationInSeconds = workoutBlock.match(/([0-9]\d*s)/);
        const durationInMinutes = workoutBlock.match(/([0-9]*:?[0-9][0-9]*m)/);

        let duration = durationInSeconds && parseInt(durationInSeconds[0]);
        duration = durationInMinutes
          ? parseInt(durationInMinutes[0].split(":")[0]) * 60 +
            (parseInt(durationInMinutes[0].split(":")[1]) || 0)
          : duration;

        addInstruction(text, duration || 0);
      }
    });
  }

  return (
    // Adding tabIndex allows div element to receive keyboard events
    <div className="container" onKeyDown={handleKeyPress} tabIndex={0}>
      <Helmet>
        <title>
          {name
            ? `${name} - Zwift Workout Editor`
            : "My Workout - Zwift Workout Editor"}
        </title>
        <meta name="description" content={description} />
        <meta
          property="og:title"
          content={
            name
              ? `${name} - Zwift Workout Editor`
              : "My Workout - Zwift Workout Editor"
          }
        />
        <meta property="og:description" content={description} />
        <link
          rel="canonical"
          href={`https://www.zwiftworkout.com/editor/${id}`}
        />
        <meta
          property="og:url"
          content={`https://www.zwiftworkout.com/editor/${id}`}
        />
      </Helmet>

      {message?.visible && (
        <div className={`message ${message.class}`}>
          {message.text}
          <button
            className="close"
            onClick={() => setMessage({ visible: false })}
          >
            <FontAwesomeIcon icon={faTimesCircle} size="lg" fixedWidth />
          </button>
        </div>
      )}

      {showWorkouts && (
        <Popup width="500px" dismiss={() => setShowWorkouts(false)}>
          {user ? <Workouts userId={user.uid} /> : renderRegistrationForm()}
        </Popup>
      )}

      {selectedInstruction && (
        <EditComment
          instruction={selectedInstruction}
          onChange={(id: string, values: Instruction) => {
            changeInstruction(id, values);
            setSelectedInstruction(undefined);
          }}
          dismiss={() => setSelectedInstruction(undefined)}
          onDelete={(id: string) => {
            deleteInstruction(id);
            setSelectedInstruction(undefined);
          }}
        />
      )}

      {savePopupIsVisile && (
        <Popup width="500px" dismiss={() => setSavePopupVisibility(false)}>
          {user ? (
            <SaveForm
              name={name}
              description={description}
              author={author}
              tags={tags}
              onNameChange={setName}
              onDescriptionChange={setDescription}
              onAuthorChange={setAuthor}
              onTagsChange={setTags}
              onSave={() => {
                save();
                setSavePopupVisibility(false);
              }}
              onDismiss={() => setSavePopupVisibility(false)}
              onLogout={logout}
            />
          ) : (
            renderRegistrationForm()
          )}
        </Popup>
      )}
      {sharePopupIsVisile && (
        <Popup width="500px" dismiss={() => setSharePopupVisibility(false)}>
          <ShareForm id={id} onDismiss={() => setSharePopupVisibility(false)} />
        </Popup>
      )}
      <div className="info">
        <div className="title">
          <h1>{name}</h1>
          <div className="description">{description}</div>
          <p>{author ? `by ${author}` : ""}</p>
        </div>
        <div className="workout">
          <div className="form-input">
            <label>Workout Time</label>
            <input
              className="textInput"
              value={helpers.formatDuration(
                helpers.getWorkoutLength(bars, durationType)
              )}
              disabled
            />
          </div>
          {sportType === "run" && (
            <div className="form-input">
              <label>Workout Distance</label>
              <input
                className="textInput"
                value={helpers.getWorkoutDistance(bars)}
                disabled
              />
            </div>
          )}
          {sportType === "bike" && (
            <div className="form-input">
              <label title="Training Load">Training Load</label>
              <input
                className="textInput"
                value={helpers.getStressScore(bars, ftp)}
                disabled
              />
            </div>
          )}
          {sportType === "run" && (
            <LeftRightToggle<"time", "distance">
              label="Duration Type"
              leftValue="time"
              rightValue="distance"
              leftIcon={faClock}
              rightIcon={faRuler}
              selected={durationType}
              onChange={setDurationType}
            />
          )}
          <LeftRightToggle<"bike", "run">
            label="Sport Type"
            leftValue="bike"
            rightValue="run"
            leftIcon={faBiking}
            rightIcon={faRunning}
            selected={sportType}
            onChange={switchSportType}
          />
        </div>
      </div>
      {sportType === "run" && (
        <RunningTimesEditor times={runningTimes} onChange={setRunningTimes} />
      )}
      {textEditorIsVisible && sportType === "bike" && (
        <div className="text-editor">
          <textarea
            onChange={(e) => transformTextToWorkout(e.target.value)}
            rows={10}
            spellCheck={false}
            className="text-editor-textarea"
            placeholder="Add one block per line here: &#10;steady 3.0wkg 30s"
          ></textarea>
          <div className="text-editor-instructions">
            <h2>Instructions</h2>
            <p>
              Every row correspond to a workout block. Scroll down to see some
              examples.
            </p>
            <h3>Blocks</h3>
            <p>
              <span>steady</span> <span>warmup</span> <span>cooldown</span>{" "}
              <span>ramp</span> <span>intervals</span> <span>freeride</span>{" "}
              <span>message</span>
            </p>
            <h3>Time</h3>
            <p>
              <span>30s</span> or <span>0:30m</span>
            </p>
            <h3>Power</h3>
            <p>
              <span>250w</span> or <span>3.0wkg</span> or <span>75%</span> (FTP)
            </p>
            <h3>Cadence</h3>
            <p>
              <span>120rpm</span>
            </p>
            <h2>Examples</h2>
            <h3>Steady block</h3>
            <p>
              <code>steady 3.0wkg 30s</code>
              <code>steady 120w 10m 85rpm</code>
            </p>
            <h3>Warmup / Cooldown / Ramp block</h3>
            <p>
              <code>warmup 2.0wkg-3.5wkg 10m</code>
              <code>cooldown 180w-100w 5m 110rpm</code>
            </p>
            <h3>Intervals</h3>
            <p>
              <code>interval 10x 30s-30s 4.0wkg-1.0wkg 110rpm-85rpm</code>
              <code>interval 3x 1:00m-5:00m 300w-180w</code>
            </p>
            <h3>Free Ride</h3>
            <p>
              <code>freeride 10m 85rpm</code>
            </p>
            <h3>Text Event</h3>
            <p>
              <code>message "Get ready to your first set!" 30s</code>
              <code>message "Last one!" 20:00m</code>
            </p>
          </div>
        </div>
      )}
      <div id="editor" className="editor">
        {actionId && (
          <div className="actions">
            <button onClick={() => moveLeft(actionId)} title="Move Left">
              <FontAwesomeIcon icon={faArrowLeft} size="lg" fixedWidth />
            </button>
            <button onClick={() => moveRight(actionId)} title="Move Right">
              <FontAwesomeIcon icon={faArrowRight} size="lg" fixedWidth />
            </button>
            <button onClick={() => removeBar(actionId)} title="Delete">
              <FontAwesomeIcon icon={faTrash} size="lg" fixedWidth />
            </button>
            <button onClick={() => duplicateBar(actionId)} title="Duplicate">
              <FontAwesomeIcon icon={faCopy} size="lg" fixedWidth />
            </button>
            {sportType === "run" && (
              <select
                name="pace"
                value={getPace(actionId)}
                onChange={(e) => setPace(e.target?.value, actionId)}
                className="selectInput"
              >
                <option value="0">1 Mile Pace</option>
                <option value="1">5K Pace</option>
                <option value="2">10K Pace</option>
                <option value="3">Half Marathon Pace</option>
                <option value="4">Marathon Pace</option>
              </select>
            )}
          </div>
        )}
        <div className="canvas" ref={canvasRef}>
          {actionId && (
            <div
              className="fader"
              style={{ width: canvasRef.current?.scrollWidth }}
              onClick={() => setActionId(undefined)}
            ></div>
          )}
          <div className="segments" ref={segmentsRef}>
            {bars.map((bar) => {
              if (bar.type === "bar") {
                return renderBar(bar);
              } else if (bar.type === "trapeze") {
                return renderTrapeze(bar);
              } else if (bar.type === "freeRide") {
                return renderFreeRide(bar);
              } else if (bar.type === "interval") {
                return renderInterval(bar);
              } else {
                return false;
              }
            })}
          </div>

          <div className="slider">
            {instructions.map((instruction, index) =>
              renderComment(instruction, index)
            )}
          </div>

          {durationType === "time" ? (
            <TimeAxis width={segmentsWidth} />
          ) : (
            <DistanceAxis width={segmentsWidth} />
          )}
        </div>

        <ZoneAxis />
      </div>
      <div className="cta">
        {sportType === "bike" ? (
          <div>
            <ReactTooltip effect="solid" />
            <button
              className="btn btn-square"
              onClick={() => toggleTextEditor()}
              style={{ backgroundColor: "palevioletred" }}
              data-tip="New! Workout text editor!"
            >
              <FontAwesomeIcon icon={faPen} fixedWidth />
            </button>
            <button
              className="btn btn-square"
              onClick={() => addBar(0.5)}
              style={{ backgroundColor: Colors.GRAY }}
            >
              Z1
            </button>
            <button
              className="btn btn-square"
              onClick={() => addBar(Zones.Z2.min)}
              style={{ backgroundColor: Colors.BLUE }}
            >
              Z2
            </button>
            <button
              className="btn btn-square"
              onClick={() => addBar(Zones.Z3.min)}
              style={{ backgroundColor: Colors.GREEN }}
            >
              Z3
            </button>
            <button
              className="btn btn-square"
              onClick={() => addBar(Zones.Z4.min)}
              style={{ backgroundColor: Colors.YELLOW }}
            >
              Z4
            </button>
            <button
              className="btn btn-square"
              onClick={() => addBar(Zones.Z5.min)}
              style={{ backgroundColor: Colors.ORANGE }}
            >
              Z5
            </button>
            <button
              className="btn btn-square"
              onClick={() => addBar(Zones.Z6.min)}
              style={{ backgroundColor: Colors.RED }}
            >
              Z6
            </button>
          </div>
        ) : (
          <button
            className="btn"
            onClick={() => addBar(1, 300, 0, 0, 1000)}
            style={{ backgroundColor: Colors.WHITE }}
          >
            <SteadyLogo className="btn-icon" /> Steady Pace
          </button>
        )}

        <button
          className="btn"
          onClick={() => addTrapeze(0.25, 0.75)}
          style={{ backgroundColor: Colors.WHITE }}
        >
          <WarmupLogo className="btn-icon" /> Warm up
        </button>
        <button
          className="btn"
          onClick={() => addTrapeze(0.75, 0.25)}
          style={{ backgroundColor: Colors.WHITE }}
        >
          <WarmdownLogo className="btn-icon" /> Cool down
        </button>
        <button
          className="btn"
          onClick={() => addInterval()}
          style={{ backgroundColor: Colors.WHITE }}
        >
          <IntervalLogo className="btn-icon" /> Interval
        </button>
        <button
          className="btn"
          onClick={() => addFreeRide()}
          style={{ backgroundColor: Colors.WHITE }}
        >
          <FontAwesomeIcon
            icon={sportType === "bike" ? faBicycle : faRunning}
            size="lg"
            fixedWidth
          />{" "}
          Free {sportType === "bike" ? "Ride" : "Run"}
        </button>
        <button
          className="btn"
          onClick={() => addInstruction()}
          style={{ backgroundColor: Colors.WHITE }}
        >
          <FontAwesomeIcon icon={faComment} size="lg" fixedWidth /> Text Event
        </button>
        {sportType === "bike" && (
          <div className="form-input">
            <label htmlFor="ftp">FTP (W)</label>
            <input
              className="textInput"
              type="number"
              name="ftp"
              value={ftp}
              onChange={(e) => setFtp(parseInt(e.target.value))}
            />
          </div>
        )}

        {sportType === "bike" && (
          <div className="form-input">
            <label htmlFor="weight">Body Weight (Kg)</label>
            <input
              className="textInput"
              type="number"
              name="weight"
              value={weight}
              onChange={(e) => setWeight(parseInt(e.target.value))}
            />
          </div>
        )}

        <button
          className="btn"
          onClick={() => {
            if (
              window.confirm("Are you sure you want to create a new workout?")
            )
              newWorkout();
          }}
        >
          <FontAwesomeIcon icon={faFile} size="lg" fixedWidth /> New
        </button>
        <button className="btn" onClick={() => saveWorkout()}>
          <FontAwesomeIcon icon={faSave} size="lg" fixedWidth /> Save
        </button>
        <button
          className="btn"
          onClick={() => {
            if (window.confirm("Are you sure you want to delete this workout?"))
              deleteWorkout();
          }}
        >
          <FontAwesomeIcon icon={faTrash} size="lg" fixedWidth /> Delete
        </button>
        <button className="btn" onClick={() => downloadWorkout()}>
          <FontAwesomeIcon icon={faDownload} size="lg" fixedWidth /> Download
        </button>
        <input
          accept=".xml,.zwo"
          id="contained-button-file"
          type="file"
          style={{ display: "none" }}
          onChange={(e) => handleUpload(e.target.files![0])}
        />
        <button
          className="btn"
          onClick={() =>
            document.getElementById("contained-button-file")!.click()
          }
        >
          <FontAwesomeIcon icon={faUpload} size="lg" fixedWidth /> Upload
        </button>
        <button className="btn" onClick={() => setShowWorkouts(true)}>
          <FontAwesomeIcon icon={faList} size="lg" fixedWidth /> Workouts
        </button>
        <button className="btn" onClick={() => shareWorkout()}>
          <FontAwesomeIcon icon={faShareAlt} size="lg" fixedWidth /> Share
        </button>
      </div>
      <Footer />
    </div>
  );
}
Example #15
Source File: Landing.tsx    From hacker-ui with MIT License 4 votes vote down vote up
function Landing(props: Props) {
  const { Root, styles } = useStyles(props);
  const theme = useTheme();
  const isMobile = useMediaQuery(theme.media.down('mobile'));

  return (
    <Root>
      <AppBar onOpenMobileNav={() => {}} />
      <main className={styles.main}>
        <div className={styles.container}>
          <h1 className={styles.title}>Hacker UI</h1>
          <p className={styles.subtitle}>a simple component library</p>
          <p className={styles.description}>
            ⚠ Development on Hiatus. Not ready for use.
          </p>
          <div className={styles.buttons}>
            <Button
              className={styles.button}
              color={theme.brand}
              variant="filled"
              size={isMobile ? 'standard' : 'large'}
              component="a"
              // @ts-ignore
              href="https://github.com/hui-org/hacker-ui"
            >
              Github
            </Button>
            <Button
              className={styles.button}
              component={Link}
              variant="filled"
              size={isMobile ? 'standard' : 'large'}
              // @ts-ignore
              to="/intro/what-is-hacker-ui"
            >
              <span>Get Started</span>
              <FontAwesomeIcon icon={faArrowRight} />
            </Button>
          </div>
          <ul className={styles.features}>
            <li className={styles.feature}>
              <Emoji>
                <span role="img" aria-label="">
                  ?
                </span>
              </Emoji>
              <span>Small&nbsp;&lt;&nbsp;20kB</span>
            </li>
            <li className={styles.feature}>
              <Emoji>
                <span role="img" aria-label="">
                  ?
                </span>
              </Emoji>
              <span>Tree Shakable</span>
            </li>
            <li className={styles.feature}>
              <Emoji>
                <span role="img" aria-label="">
                  ?
                </span>
              </Emoji>
              <span>Themable</span>
            </li>
            <li className={styles.feature}>
              <Emoji>
                <span role="img" aria-label="">
                  ?
                </span>
              </Emoji>
              <span>Dark Mode</span>
            </li>
            <li className={styles.feature}>
              <Emoji>
                <span role="img" aria-label="">
                  ?
                </span>
              </Emoji>
              <span>20+ Components</span>
            </li>
            <li className={styles.feature}>
              <Emoji>
                <span role="img" aria-label="">
                  ?
                </span>
              </Emoji>
              <span>25+ Examples</span>
            </li>
            {/* TODO: uncomment when tested with SSR */}
            {/* <li className={styles.feature}>
              <Emoji>
                <span role="img" aria-label="">
                  ?
                </span>
              </Emoji>
              <span>SSR Capable</span>
            </li> */}
            <li className={styles.feature}>
              <Emoji>
                <span role="img" aria-label="">
                  ✏️
                </span>
              </Emoji>
              <span>CSS-in-JS</span>
            </li>
            {/* <li className={styles.feature}>
              <Emoji>
                <span role="img" aria-label="">
                  ?
                </span>
              </Emoji>
              <span>Full TypeScript Support</span>
            </li> */}
            <li className={styles.feature}>
              <Emoji>
                <span role="img" aria-label="">
                  ?
                </span>
              </Emoji>
              <span>Built with TypeScript</span>
            </li>
          </ul>
        </div>
      </main>
    </Root>
  );
}
Example #16
Source File: views.tsx    From MagicUI with Apache License 2.0 4 votes vote down vote up
export default function Views(props: IViewsProps) {
  const [code, setCode] = useState([] as string[]);
  const [dslCode, setDslCode] = useState('');
  const [curStep, setCurStep] = useState(0);
  const [tabItem, setTabItem] = useState([] as string[]);
  const [curTabIndex, setCurTabIndex] = useState(0);
  const [tabItemArray, setTabItemArray] = useState(tabItems);
  const [codeModeArray, setCodeModeArray] = useState(codeMode);
  const [codeTypeIndex, setCodeTypeIndex] = useState(0);

  useEffect(() => {
    ipcRenderer.on('code', (event, args) => {
      if (args.type === 'json') {
        let dsl = jsonToDslCode(args.data);
        setCode([dsl]);
        setTabItem(tabItems[0]);
        cachedCode[0] = [dsl];
        setDslCode(dsl);
        return;
      }
      if (args.type === 'target') {
        setTabItemArray([tabItems[1]]);
        setTabItem(tabItems[1]);
        setCodeModeArray([codeMode[1]]);
        setDslCode(args.data);
        setCurStep(1);
        Bridge.compile('html', args.data).then((html) => {
          setCode(html);
          cachedCode[0] = html;
        });
        return;
      }
    });
  }, []);

  const handlePrevStep = () => {
    if (curStep <= 0) return;
    setCurStep(curStep => {
      setCode(cachedCode[curStep - 1]);
      setTabItem(tabItemArray[curStep - 1]);
      setCurTabIndex(0);
      setCodeTypeIndex(0);
      return curStep - 1;
    });
  };

  const handleNextStep = () => {
    if (curStep >= tabItemArray.length - 1) return;

    setCurStep(curStep => {
      if (curStep === 0) {
        Bridge.compile('html', code[0]).then((html) => {
          cachedCode[curStep + 1] = html;
          setCode(html);
        });
      }

      setTabItem(tabItemArray[curStep + 1]);
      setCurTabIndex(0);
      setCodeTypeIndex(0);
      return curStep + 1;
    });
  };

  const handleTabClick = (i: number) => {
    setCurTabIndex(i);
    setCodeTypeIndex(0);
    if (i === 1) {
      Bridge.compile('react', dslCode).then((react) => {
        cachedCode[curStep + i] = react;
        setCode(react);
      })
    }
    if (i === 0) {
      Bridge.compile('html', dslCode).then((html) => {
        cachedCode[curStep + i] = html;
        setCode(html);
      })
    }
  }

  const handleCodeChange = (edit: any, data: any, value: any) => {
    setCode(value);
  };

  const handleExport = () => {
    saveCodeFile(tabItems[curStep][curTabIndex], code).then(() => {

    });
  };

  const prevBtn = curStep !== 0 && tabItemArray.length > 1 && (
    <button disabled={curStep === 0} onClick={handlePrevStep} className={style.prev_btn}>
      <span>
        <FontAwesomeIcon icon={faArrowLeft}/>
      </span>
      Prev
    </button>
  );

  const nextBtn = (
    <button onClick={handleNextStep} className={style.next_btn}>
      Next
      <span>
        <FontAwesomeIcon icon={faArrowRight}/>
      </span>
    </button>
  );

  const exportBtn = (
    <button onClick={handleExport} className={style.export_btn}>
      Export
      <span>
        <FontAwesomeIcon icon={faFileExport}/>
      </span>
    </button>
  );

  return (
    <div className={style.views}>
      <Tab items={tabItem} onTabClick={handleTabClick} curIndex={curTabIndex}/>
      <CodeEditor code={code[codeTypeIndex]}
                  onCodeChange={handleCodeChange}
                  mode={codeMode[curStep][curTabIndex][codeTypeIndex]}
                  codeTypes={codeType[curStep][curTabIndex]}
                  curCodeType={codeTypeIndex}
                  onCodeTypeTabClick={(i) => setCodeTypeIndex(i)}/>
      <div className={style.operator_btn}>
        {prevBtn}
        {curStep === tabItems.length - 1 ? exportBtn : nextBtn}
      </div>
    </div>
  );
}