react-bootstrap#Popover TypeScript Examples

The following examples show how to use react-bootstrap#Popover. 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: CollectionSortOptions.tsx    From bada-frame with GNU General Public License v3.0 7 votes vote down vote up
CollectionSortOptions = (props: OptionProps) => {
    const SortByOption = SortByOptionCreator(props);

    return (
        <Popover id="collection-sort-options" style={{ borderRadius: '10px' }}>
            <Popover.Content
                style={{ padding: 0, border: 'none', width: '185px' }}>
                <ListGroup style={{ borderRadius: '8px' }}>
                    <SortByOption sortBy={COLLECTION_SORT_BY.LATEST_FILE}>
                        {constants.SORT_BY_LATEST_PHOTO}
                    </SortByOption>
                    <SortByOption sortBy={COLLECTION_SORT_BY.MODIFICATION_TIME}>
                        {constants.SORT_BY_MODIFICATION_TIME}
                    </SortByOption>
                    <SortByOption sortBy={COLLECTION_SORT_BY.NAME}>
                        {constants.SORT_BY_COLLECTION_NAME}
                    </SortByOption>
                </ListGroup>
            </Popover.Content>
        </Popover>
    );
}
Example #2
Source File: NoblePhantasmPopover.tsx    From apps with MIT License 5 votes vote down vote up
NoblePhantasmPopover = (props: { region: Region; noblePhantasm: NoblePhantasm.NoblePhantasm }) => {
    const { region, noblePhantasm } = props;

    const popOverContent = (
        <Popover id={`np-${noblePhantasm.id}`} className="skill-popover" lang={Manager.lang()}>
            <Popover.Title>[{noblePhantasm.name}]</Popover.Title>
            <Popover.Content>
                <EffectBreakdown
                    region={region}
                    funcs={noblePhantasm.functions}
                    gain={noblePhantasm.npGain}
                    levels={noblePhantasm.functions[0]?.svals.length ?? 1}
                    popOver={true}
                />
            </Popover.Content>
        </Popover>
    );

    return (
        <OverlayTrigger
            trigger="click"
            rootClose
            placement="auto"
            overlay={popOverContent}
            popperConfig={{
                modifiers: [
                    {
                        name: "offset",
                        options: {
                            offset: [0, 10],
                        },
                    },
                ],
            }}
        >
            <Button
                variant="link"
                className="move-button"
                title={`Click to view details of noble phantasm ${noblePhantasm.name}`}
            >
                [{noblePhantasm.name}]
            </Button>
        </OverlayTrigger>
    );
}
Example #3
Source File: SkillPopover.tsx    From apps with MIT License 5 votes vote down vote up
SkillPopover = (props: { region: Region; skill: Skill.Skill }) => {
    const { region, skill } = props;

    const popOverContent = (
        <Popover id={`skill-${skill.id}`} className="skill-popover" lang={Manager.lang()}>
            <Popover.Title>
                <SkillDescriptor region={region} skill={skill} />
            </Popover.Title>
            <Popover.Content>
                <EffectBreakdown
                    region={region}
                    cooldowns={skill.coolDown.length > 0 ? skill.coolDown : undefined}
                    funcs={skill.functions}
                    triggerSkillIdStack={[skill.id]}
                    levels={skill.functions[0]?.svals.length ?? 1}
                    scripts={skill.script}
                    popOver={true}
                    additionalSkillId={skill.script.additionalSkillId}
                />
            </Popover.Content>
        </Popover>
    );

    return (
        <OverlayTrigger
            trigger="click"
            rootClose
            placement="auto"
            overlay={popOverContent}
            popperConfig={{
                modifiers: [
                    {
                        name: "offset",
                        options: {
                            offset: [0, 10],
                        },
                    },
                ],
            }}
        >
            <Button variant="link" className="move-button" title={`Click to view details of skill ${skill.name}`}>
                {SkillDescriptor.renderAsString(skill)}
            </Button>
        </OverlayTrigger>
    );
}
Example #4
Source File: AddYearPopup.tsx    From peterportal-client with MIT License 5 votes vote down vote up
AddYearPopup: FC<AddYearPopupProps> = ({ placeholderYear }) => {
  const dispatch = useAppDispatch();
  const [year, setYear] = useState(placeholderYear);
  const [show, setShow] = useState(false);
  const target = useRef(null);

  useEffect(() => { setYear(placeholderYear) }, [placeholderYear]);

  const handleClick = (event: React.MouseEvent) => {
    setShow(!show);
  };

  return (
    <div>
      <Button variant="light" ref={target} className="add-year-btn" onClick={handleClick}>
        <PlusCircleFill className="add-year-icon" />
        <div className="add-year-text">Add year</div>
      </Button>
      <Overlay show={show} target={target} placement="top">
        <Popover id=''>
          <Popover.Content>
            <Form>
              <Form.Group>
                <Form.Label className="add-year-form-label">
                  Start Year
                </Form.Label>
                <Form.Control
                  type="number"
                  name="year"
                  value={year}
                  onChange={(e) => {
                    setYear(parseInt(e.target.value));
                  }}
                  onKeyDown={(e: React.KeyboardEvent) => {
                    // prevent submitting form (reloads the page)
                    if (e.key === 'Enter') {
                      e.preventDefault();
                    }
                  }}
                  min={1000}
                  max={9999}
                  placeholder={placeholderYear.toString()}
                ></Form.Control>
              </Form.Group>
              <Button
                className="popup-btn"
                onClick={() => {
                  setShow(!show);
                  dispatch(addYear(
                    {
                      yearData: {
                        startYear: year,
                        quarters: ['fall', 'winter', 'spring'].map(quarter => { return { name: quarter, courses: [] } })
                      }
                    }
                  ));
                  setYear(placeholderYear);
                }}
              >
                Add Year
              </Button>
            </Form>
          </Popover.Content>
        </Popover>
      </Overlay>
    </div >
  );
}
Example #5
Source File: Header.tsx    From peterportal-client with MIT License 5 votes vote down vote up
Header: FC<HeaderProps> = ({ courseCount, unitCount, saveRoadmap }) => {
  const dispatch = useAppDispatch();
  const [target, setTarget] = useState<any>(null!);
  const [showMenu, setShowMenu] = useState(false);

  const buttons = <>
    <Button variant={isMobile ? "primary" : 'light'} className={isMobile ? 'my-1' : "header-btn"} onClick={() => dispatch(setShowTransfer(true))}>
      Transfer Credits
      <ArrowLeftRight className="header-icon" />
    </Button>
    <Button variant={isMobile ? "primary" : 'light'} className={isMobile ? 'my-1' : "header-btn"} onClick={saveRoadmap}>
      Save
      <Save className="header-icon" />
    </Button>
    <Button variant={isMobile ? "primary" : 'light'} className={isMobile ? 'my-1' : "header-btn"} onClick={() => dispatch(clearPlanner())}>
      Clear
      <Trash className="header-icon" />
    </Button>
  </>

  const onMenuClick = (event: React.MouseEvent) => {
    setShowMenu(!showMenu);
    setTarget(event.target);
  };

  return (
    <div className="header">
      <Transfer />
      <div>
        <div id="title">
          Peter's Roadmap
        </div>
        <span id="planner-stats">
          Total: <span id="course-count">{courseCount}</span>{" "}
          {courseCount === 1 ? "course" : "courses"},{" "}
          <span id="unit-count">{unitCount}</span>{" "}
          {unitCount === 1 ? "unit" : "units"}
        </span>
      </div>
      <div>
        {
          isMobile && <>
            <Button variant="light" className="header-btn add-course" onClick={() => { dispatch(setShowSearch(true)) }}>
              <Plus className="header-icon mr-1" />
              Add Course
            </Button>
            <List className='mx-3' onClick={onMenuClick} />
            <Overlay show={showMenu} target={target} placement="left">
              <Popover id='roadmap-header-buttons'>
                <Popover.Content>
                  <div className='d-flex flex-column'>
                    {buttons}
                  </div>
                </Popover.Content>
              </Popover>
            </Overlay>
          </>
        }
        {
          isBrowser && <ButtonGroup>
            {buttons}
          </ButtonGroup>
        }
      </div>
    </div >
  )
}
Example #6
Source File: index.tsx    From nouns-monorepo with GNU General Public License v3.0 5 votes vote down vote up
ProposalTransactions = ({
  className,
  proposalTransactions,
  onRemoveProposalTransaction,
}: {
  className?: string;
  proposalTransactions: ProposalTransaction[];
  onRemoveProposalTransaction: (index: number) => void;
}) => {
  const getPopover = (tx: ProposalTransaction) => (
    <Popover className={classes.popover} id="transaction-details-popover">
      <Popover.Header as="h3">Transaction Details</Popover.Header>
      <Popover.Body>
        <Row>
          <Col sm="3">
            <b>Address</b>
          </Col>
          <Col sm="9">
            <a href={buildEtherscanAddressLink(tx.address)} target="_blank" rel="noreferrer">
              {tx.address}
            </a>
          </Col>
        </Row>
        <Row>
          <Col sm="3">
            <b>Value</b>
          </Col>
          <Col sm="9">{tx.value ? `${utils.formatEther(tx.value)} ETH` : 'None'}</Col>
        </Row>
        <Row>
          <Col sm="3">
            <b>Function</b>
          </Col>
          <Col sm="9">{tx.signature || 'None'}</Col>
        </Row>
        <Row>
          <Col sm="3">
            <b>Calldata</b>
          </Col>
          <Col sm="9">{tx.calldata === '0x' ? 'None' : tx.calldata}</Col>
        </Row>
      </Popover.Body>
    </Popover>
  );

  return (
    <div className={className}>
      {proposalTransactions.map((tx, i) => (
        <OverlayTrigger
          key={i}
          trigger={['hover', 'focus']}
          placement="top"
          overlay={getPopover(tx)}
        >
          <div
            className={`${classes.transactionDetails} d-flex justify-content-between align-items-center`}
          >
            <div>
              <span>Transaction #{i + 1} - </span>
              <span>
                <b>{tx.signature || 'transfer()'}</b>
              </span>
            </div>
            <button
              className={classes.removeTransactionButton}
              onClick={() => onRemoveProposalTransaction(i)}
            >
              <img src={xIcon} alt="Remove Transaction" />
            </button>
          </div>
        </OverlayTrigger>
      ))}
    </div>
  );
}
Example #7
Source File: VoteWidget.tsx    From 3Speak-app with GNU General Public License v3.0 4 votes vote down vote up
export function VoteWidget(props: any) {
  const reflink = useMemo(() => {
    return RefLink.parse(props.reflink)
  }, [props.reflink])
  const author = useMemo(() => {
    return reflink.root
  }, [reflink])

  const permlink = useMemo(() => {
    return reflink.permlink
  }, [])

  const [downvoters, setDownvoters] = useState([])
  const [upvoters, setUpvoters] = useState([])
  const [upvotePct, setUpvotePct] = useState(0)
  const [downvotePct, setDownvotePct] = useState(0)
  const [showModal, setShowModal] = useState(false)
  const [showDModal, setShowDModal] = useState(false)

  const setVoters = () => {
    void AccountService.permalinkToPostInfo(props.reflink).then((post) => {
      const votes = post.active_votes.sort((e, i) => {
        return i.rshares - e.rshares
      })

      setDownvoters(votes.filter((vote) => vote.percent < 0))
      setUpvoters(votes.filter((vote) => vote.percent >= 0).reverse())
    })
  }

  useEffect(() => {
    setVoters()
  }, [])

  const handleClose = () => {
    setShowModal(false)
  }

  const handleShow = () => {
    setShowModal(true)
  }

  const handleDClose = () => {
    setShowDModal(false)
  }
  const handleDShow = () => {
    setShowDModal(true)
  }

  const handleVote = async () => {
    const modalState = showModal
    if (modalState === false) {
      handleShow()
    } else {
      const profileID = localStorage.getItem('SNProfileID')

      if (profileID) {
        try {
          const profile = (await AccountService.getAccount(profileID)) as any
          const theWifObj = Finder.one.in(profile.keyring).with({
            privateKeys: {},
          })
          const wif = theWifObj.privateKeys.posting_key // posting key
          const voter = profile.nickname // voting account
          const weight = upvotePct // vote weight in percentage(between 1 - 100)
          const accountType = 'hive'

          const voteOp = {
            wif,
            voter,
            author,
            permlink,
            weight,
            accountType,
            profileID,
          }

          await AccountService.voteHandler(voteOp)
          NotificationManager.success('Vote cast')
          const voterFmt = `@${voter}`
          setUpvoters([...upvoters, voterFmt])
          setShowModal(false)
          upvoters.push()
        } catch (error) {
          NotificationManager.success('There was an error completing this operation')
        }
      } else {
        NotificationManager.success('You need to be logged in to perform this operation')
      }
    }
  }

  const handleDownVote = async () => {
    const modalState = showDModal
    if (modalState === false) {
      handleDShow()
    } else {
      const profileID = localStorage.getItem('SNProfileID')

      if (profileID) {
        try {
          const profile = (await AccountService.getAccount(profileID)) as any
          const theWifObj = Finder.one.in(profile.keyring).with({
            privateKeys: {},
          })
          const wif = theWifObj.privateKeys.posting_key // posting key
          const voter = profile.nickname // voting account
          const weight = downvotePct * -1 // vote weight in percentage(between 1 - 100)
          const accountType = 'hive'

          const voteOp = {
            wif,
            voter,
            author,
            permlink,
            weight,
            accountType,
            profileID,
          }

          await AccountService.voteHandler(voteOp)

          NotificationManager.success('Vote casted, page will reload momentarily')
          setShowDModal(false)
        } catch (error) {
          NotificationManager.success('There was an error completing this operation')
        }
      } else {
        NotificationManager.success('You need to be logged in to perform this operation')
      }
    }
  }

  return (
    <>
      <span className="ml-2 p-0">
        <span style={{ cursor: 'pointer' }}>
          <FaThumbsUp
            className="text-secondary"
            onClick={() => {
              void handleVote()
            }}
          />
        </span>
        {showModal && (
          <span>
            <RangeSlider
              value={upvotePct}
              onChange={(evt) => {
                setUpvotePct(evt.target.value)
              }}
            />
            <FontAwesomeIcon
              size={'lg'}
              icon={(<FaChevronCircleUp style={{ cursor: 'pointer' }} />) as any}
            />
            <FontAwesomeIcon
              size={'lg'}
              icon={
                (<FaTimesCircle style={{ cursor: 'pointer' }} className="text-danger" />) as any
              }
            />
          </span>
        )}
      </span>
      <OverlayTrigger
        rootClose
        trigger="click"
        placement="bottom"
        overlay={
          <Popover id="popover-basic">
            <Popover.Title as="h3">
              Upvotes for @{author}/{permlink}
            </Popover.Title>
            <Popover.Content>
              {upvoters.slice(0, 10).map((e, index) => {
                return (
                  <div key={index}>
                    @{e.voter}: {e.percent / 100}%<br />
                  </div>
                )
              })}
              <a
                onClick={() => {
                  //todo: open modal
                }}
              >
                See more...
              </a>
            </Popover.Content>
          </Popover>
        }
      >
        <b style={{ cursor: 'pointer' }}>{upvoters.length}</b>
      </OverlayTrigger>

      <span className="ml-2 p-0">
        <span style={{ cursor: 'pointer' }}>
          <FaThumbsDown
            className="text-secondary"
            onClick={() => {
              void handleDownVote()
            }}
          />
        </span>
        {showDModal && (
          <span>
            <RangeSlider
              value={downvotePct}
              onChange={(changeEvent) => {
                setDownvotePct(changeEvent.target.value)
              }}
            />
            <FontAwesomeIcon
              size={'lg'}
              icon={(<FaChevronCircleDown style={{ cursor: 'pointer' }} />) as any}
            />
            <FontAwesomeIcon
              size={'lg'}
              icon={
                (<FaTimesCircle style={{ cursor: 'pointer' }} className="text-danger" />) as any
              }
            />
          </span>
        )}
      </span>
      <OverlayTrigger
        rootClose
        trigger="click"
        placement="bottom"
        overlay={
          <Popover id="basic-popover">
            <Popover.Title as="h3">
              Downvotes for @{author}/{permlink}
            </Popover.Title>
            <Popover.Content>
              {downvoters.slice(0, 10).map((item, index) => {
                return (
                  <div key={index}>
                    @{item.voter}: {item.percent / 100}%<br />
                  </div>
                )
              })}
              <a
                onClick={() => {
                  //todo: open modal
                }}
              >
                See more...
              </a>
            </Popover.Content>
          </Popover>
        }
      >
        <b style={{ cursor: 'pointer' }}>{downvoters.length}</b>
      </OverlayTrigger>
    </>
  )
}
Example #8
Source File: CollectionOptions.tsx    From bada-frame with GNU General Public License v3.0 4 votes vote down vote up
CollectionOptions = (props: CollectionOptionsProps) => {
    const collectionRename = async (
        selectedCollection: Collection,
        newName: string
    ) => {
        if (selectedCollection.name !== newName) {
            await renameCollection(selectedCollection, newName);
            props.syncWithRemote();
        }
    };
    const showRenameCollectionModal = () => {
        props.setCollectionNamerAttributes({
            title: constants.RENAME_COLLECTION,
            buttonText: constants.RENAME,
            autoFilledName: getSelectedCollection(
                props.selectedCollectionID,
                props.collections
            )?.name,
            callback: (newName) => {
                props.startLoading();
                collectionRename(
                    getSelectedCollection(
                        props.selectedCollectionID,
                        props.collections
                    ),
                    newName
                );
            },
        });
    };
    const confirmDeleteCollection = () => {
        props.setDialogMessage({
            title: constants.CONFIRM_DELETE_COLLECTION,
            content: constants.DELETE_COLLECTION_MESSAGE(),
            staticBackdrop: true,
            proceed: {
                text: constants.DELETE_COLLECTION,
                action: () => {
                    props.startLoading();
                    deleteCollection(
                        props.selectedCollectionID,
                        props.syncWithRemote,
                        props.redirectToAll,
                        props.setDialogMessage
                    );
                },
                variant: 'danger',
            },
            close: {
                text: constants.CANCEL,
            },
        });
    };

    const archiveCollectionHelper = () => {
        changeCollectionVisibilityHelper(
            getSelectedCollection(
                props.selectedCollectionID,
                props.collections
            ),
            props.startLoading,
            props.finishLoading,
            props.setDialogMessage,
            props.syncWithRemote
        );
    };

    const confirmDownloadCollection = () => {
        props.setDialogMessage({
            title: constants.CONFIRM_DOWNLOAD_COLLECTION,
            content: constants.DOWNLOAD_COLLECTION_MESSAGE(),
            staticBackdrop: true,
            proceed: {
                text: constants.DOWNLOAD,
                action: downloadCollectionHelper,
                variant: 'success',
            },
            close: {
                text: constants.CANCEL,
            },
        });
    };

    const downloadCollectionHelper = async () => {
        props.startLoading();
        await downloadCollection(
            props.selectedCollectionID,
            props.setDialogMessage
        );
        await sleep(1000);
        props.finishLoading();
    };

    return (
        <Popover id="collection-options" style={{ borderRadius: '10px' }}>
            <Popover.Content style={{ padding: 0, border: 'none' }}>
                <ListGroup style={{ borderRadius: '8px' }}>
                    <MenuItem>
                        <MenuLink onClick={showRenameCollectionModal}>
                            {constants.RENAME}
                        </MenuLink>
                    </MenuItem>
                    <MenuItem>
                        <MenuLink onClick={props.showCollectionShareModal}>
                            {constants.SHARE}
                        </MenuLink>
                    </MenuItem>
                    <MenuItem>
                        <MenuLink onClick={confirmDownloadCollection}>
                            {constants.DOWNLOAD}
                        </MenuLink>
                    </MenuItem>
                    <MenuItem>
                        <MenuLink onClick={archiveCollectionHelper}>
                            {IsArchived(
                                getSelectedCollection(
                                    props.selectedCollectionID,
                                    props.collections
                                )
                            )
                                ? constants.UNARCHIVE
                                : constants.ARCHIVE}
                        </MenuLink>
                    </MenuItem>
                    <MenuItem>
                        <MenuLink
                            variant={ButtonVariant.danger}
                            onClick={confirmDeleteCollection}>
                            {constants.DELETE}
                        </MenuLink>
                    </MenuItem>
                </ListGroup>
            </Popover.Content>
        </Popover>
    );
}
Example #9
Source File: Quarter.tsx    From peterportal-client with MIT License 4 votes vote down vote up
Quarter: FC<QuarterProps> = ({ year, yearIndex, quarterIndex, data }) => {
  const dispatch = useAppDispatch();
  let quarterTitle = data.name.charAt(0).toUpperCase() + data.name.slice(1);
  const invalidCourses = useAppSelector(state => state.roadmap.invalidCourses);

  const [showQuarterMenu, setShowQuarterMenu] = useState(false);
  const [target, setTarget] = useState<any>(null!);

  const handleQuarterMenuClick = (event: React.MouseEvent) => {
    setShowQuarterMenu(!showQuarterMenu);
    setTarget(event.target);
  }

  const calculateQuarterStats = () => {
    let unitCount = 0;
    let courseCount = 0;
    data.courses.forEach(course => {
      unitCount += course.units[0];
      courseCount += 1;
    })
    return [unitCount, courseCount];
  };

  let unitCount = calculateQuarterStats()[0];

  const renderCourses = () => {
    return data.courses.map((course, index) => {
      return <Draggable key={`quarter-course-${index}`} draggableId={`${yearIndex}-${quarterIndex}-${course.id}-${index}`} index={index}>
        {(provided, snapshot) => {
          let requiredCourses: string[] = null!;
          // if this is an invalid course, set the required courses
          invalidCourses.forEach(ic => {
            let loc = ic.location;
            if (loc.courseIndex == index && loc.quarterIndex == quarterIndex && loc.yearIndex == yearIndex) {
              requiredCourses = ic.required;
            }
          });

          const onDelete = () => {
            dispatch(deleteCourse({
              yearIndex,
              quarterIndex,
              courseIndex: index,
            }));
          };

          return (
            <div
              ref={provided.innerRef}
              {...provided.draggableProps}
              {...provided.dragHandleProps}
              style={{
                margin: '0rem 2rem 1rem 2rem',
                ...provided.draggableProps.style
              }}
            >
              <Course key={course.id} {...course}
                requiredCourses={requiredCourses}
                onDelete={onDelete} />
            </div>
          );
        }}
      </Draggable>
    })
  }

  return <div className="quarter">
    <span className="quarter-header">
      <h2 className="quarter-title">
        {quarterTitle} {year}
      </h2>
      <ThreeDots onClick={handleQuarterMenuClick} className="edit-btn" />
      <Overlay show={showQuarterMenu} target={target} placement="bottom">
        <Popover id={`quarter-menu-${yearIndex}-${quarterIndex}`}>
          <Popover.Content>
            <div>
              <Button variant="light" className="quarter-menu-btn red-menu-btn" onClick={() => dispatch(clearQuarter({ yearIndex: yearIndex, quarterIndex: quarterIndex }))}>
                Clear
              </Button>
              <Button variant="light" className="quarter-menu-btn red-menu-btn" onClick={() => {
                dispatch(deleteQuarter({ yearIndex: yearIndex, quarterIndex: quarterIndex }));
                setShowQuarterMenu(false)
              }}>
                Delete
              </Button>
            </div>
          </Popover.Content>
        </Popover>
      </Overlay>
    </span>
    <div className="quarter-units">
      {unitCount} {unitCount === 1 ? "unit" : "units"}
    </div>
    <Droppable droppableId={yearIndex + "-" + quarterIndex} type="COURSE">
      {(provided) => {
        return (
          <div
            ref={provided.innerRef}
            {...provided.droppableProps}
            style={{ paddingBottom: '1rem' }}>
            {renderCourses()}
            {provided.placeholder}
          </div>
        );
      }}
    </Droppable>
  </div>
}
Example #10
Source File: Year.tsx    From peterportal-client with MIT License 4 votes vote down vote up
Year: FC<YearProps> = ({ yearIndex, data }) => {
  const dispatch = useAppDispatch();
  const [showContent, setShowContent] = useState(true);
  const [show, setShow] = useState(false);
  const [showAddQuarter, setShowAddQuarter] = useState(false);
  const [showEditYear, setShowEditYear] = useState(false);
  const [target, setTarget] = useState<any>(null!);
  const [addQuarterTarget, setAddQuarterTarget] = useState<any>(null!);
  const [editYearTarget, setEditYearTarget] = useState<any>(null!);
  const [placeholderYear, setPlaceholderYear] = useState(data.startYear);

  const handleEditClick = (event: React.MouseEvent) => {
    if (showAddQuarter) {
      /* hide both overlays */
      setShowAddQuarter(!showAddQuarter);
      setShow(!show);
    } else if (showEditYear) {
      setShowEditYear(!showEditYear);
      setShow(!show);
    } else {
      setShow(!show);
      setTarget(event.target);
    }
  };

  const handleShowAddQuarterClick = (event: React.MouseEvent) => {
    setShowEditYear(false); // hide any other currently displayed menu bar options
    setShowAddQuarter(!showAddQuarter);
    setAddQuarterTarget(event.target);
  }

  const handleAddQuarterClick = (year: number, quarter: string) => {
    dispatch(addQuarter({ startYear: year, quarterData: { name: quarter, courses: [] } }));
  }

  const handleEditYearClick = (event: React.MouseEvent) => {
    setShowAddQuarter(false);           // hide any other currently displayed menu bar options
    setPlaceholderYear(data.startYear); // set default year to current year
    setShowEditYear(!showEditYear);
    setEditYearTarget(event.target);
  }

  const calculateYearStats = () => {
    let unitCount = 0;
    let courseCount = 0;
    data.quarters.forEach(quarter => {
      quarter.courses.forEach(course => {
        unitCount += course.units[0];
        courseCount += 1;
      })
    })
    return { unitCount, courseCount };
  };

  let { unitCount, courseCount } = calculateYearStats();

  return (
    <div className="year">
      <div className="yearTitleBar">
        <Button
          variant="link"
          className="year-accordion"
          onClick={() => {
            setShowContent(!showContent);
          }}
        >
          <span className="year-accordion-title">
            <span id="year-title">
              {showContent ? (
                <CaretDownFill className="caret-icon" />
              ) : (
                <CaretRightFill className="caret-icon" />
              )}
              <span id="year-number">Year {yearIndex + 1} </span>
              <span id="year-range">
                ({data.startYear} - {data.startYear + 1})
              </span>
            </span>
            <span id="year-stats">
              <span id="course-count">{courseCount}</span>{" "}
              {courseCount === 1 ? "course" : "courses"},{" "}
              <span id="unit-count">{unitCount}</span>{" "}
              {unitCount === 1 ? "unit" : "units"}
            </span>
          </span>
        </Button>
        <ThreeDots onClick={handleEditClick} className="edit-btn" />
        <Overlay show={show} target={target} placement="bottom">
          <Popover id={`year-menu-${yearIndex}`}>
            <Popover.Content className="year-settings-popup">
              <div>
                <Button disabled={!(data.quarters && data.quarters.length < 6)} onClick={handleShowAddQuarterClick} variant="light" className="year-settings-btn">
                  Add Quarter
                </Button>
                <Button onClick={handleEditYearClick} variant="light" className="year-settings-btn">
                  Edit Year
                </Button>
                <Button
                  variant="light"
                  className="year-settings-btn"
                  id="clear-btn"
                  onClick={() => {
                    dispatch(clearYear({
                      yearIndex: yearIndex
                    }));
                  }}
                >
                  Clear
                </Button>
                <Button
                  variant="light"
                  className="year-settings-btn"
                  id="remove-btn"
                  onClick={() => {
                    dispatch(deleteYear({
                      yearIndex: yearIndex
                    }));
                  }}
                >
                  Remove
                </Button>
              </div>
            </Popover.Content>
          </Popover>
        </Overlay>
        <Overlay show={showAddQuarter && data.quarters && data.quarters.length < 6} target={addQuarterTarget} placement="left">
          <Popover id={`add-quarter-menu-${yearIndex}`}>
            <Popover.Content>
              <div>
                {!data.quarters.map(quarter => quarter.name).includes("fall") && <Button onClick={() => handleAddQuarterClick(data.startYear, "fall")} variant="light" className="year-settings-btn">Fall</Button>}
                {!data.quarters.map(quarter => quarter.name).includes("winter") && <Button onClick={() => handleAddQuarterClick(data.startYear, "winter")} variant="light" className="year-settings-btn">Winter</Button>}
                {!data.quarters.map(quarter => quarter.name).includes("spring") && <Button onClick={() => handleAddQuarterClick(data.startYear, "spring")} variant="light" className="year-settings-btn">Spring</Button>}
                {!data.quarters.map(quarter => quarter.name).includes("summer I") && <Button onClick={() => handleAddQuarterClick(data.startYear, "summer I")} variant="light" className="year-settings-btn">Summer I</Button>}
                {!data.quarters.map(quarter => quarter.name).includes("summer II") && <Button onClick={() => handleAddQuarterClick(data.startYear, "summer II")} variant="light" className="year-settings-btn">Summer II</Button>}
                {!data.quarters.map(quarter => quarter.name).includes("summer 10 Week") && <Button onClick={() => handleAddQuarterClick(data.startYear, "summer 10 Week")} variant="light" className="year-settings-btn">Summer 10 Week</Button>}
              </div>
            </Popover.Content>
          </Popover>
        </Overlay>
        <Overlay show={showEditYear} target={editYearTarget} placement="left">
          <Popover id={`edit-year-menu-${yearIndex}`}>
            <Popover.Content>
              <Form>
                <Form.Group>
                  <Form.Label className="edit-year-form-label">
                    Start Year
                  </Form.Label>
                  <Form.Control
                    type="number"
                    name="year"
                    value={placeholderYear}
                    onChange={(e) => {
                      setPlaceholderYear(parseInt(e.target.value));
                    }}
                    onKeyDown={(e: React.KeyboardEvent) => {
                      // prevent submitting form (reloads the page)
                      if (e.key === 'Enter') {
                        e.preventDefault();
                      }
                    }}
                    min={1000}
                    max={9999}
                    placeholder={placeholderYear.toString()}
                  ></Form.Control>
                </Form.Group>
                <Button
                  className="edit-year-popup-btn"
                  onClick={() => {
                    setShowEditYear(!showEditYear);
                    setShow(!show);
                    if (placeholderYear != data.startYear) {
                      dispatch(editYear({ startYear: placeholderYear, index: yearIndex }));
                    }
                  }}
                >
                  Confirm
                </Button>
              </Form>
            </Popover.Content>
          </Popover>
        </Overlay>
      </div>
      {showContent && (
        <div className="year-accordion-content">
          {
            data.quarters.map((quarter, quarterIndex) => {
              return <Quarter
                key={`year-quarter-${quarterIndex}`}
                year={data.startYear + (quarterIndex == 0 ? 0 : 1)}
                yearIndex={yearIndex}
                quarterIndex={quarterIndex}
                data={quarter}
              />
            })
          }

          {/* render blank, non-functional quarters to ensure there are 3 per row */}
          {data.quarters.length > 3 && data.quarters.length < 6 && (
            [undefined, undefined].slice(data.quarters.length - 4).map(() => {
              return <div className="empty-quarter"></div>
            })
          )
          }
        </div>
      )}
    </div>
  );
}
Example #11
Source File: Menu.tsx    From cftracker with MIT License 4 votes vote down vote up
Menu = (): JSX.Element => {
  const dispatch = useDispatch();

  const state: RootStateType = useSelector((state) => state) as RootStateType;

  const [handle, setHandle] = useState(
    state.userList.handles.length ? state.userList.handles.toString() : ""
  );
  console.log(state.userList.handles.toString());
  useEffect(() => {
    fetchProblemList(dispatch);
    fetchContestList(dispatch);
    fetchSharedProblemList(dispatch);
  }, []);

  // useEffect(() => {
  //   if (!state.contestList.loading && !state.problemList.loading) sync(true);
  // }, [state.userList]);

  useEffect(() => {
    if (!state.contestList.loading && !state.problemList.loading)
      sync(state.userList.handles.length > 2 ? true : false);
    // console.log(state.contestList.loading);
    // console.log(state.problemList.loading);
  }, [state.userList, state.contestList.loading, state.problemList.loading]);

  const sync = (wait = false) => {
    fetchUserSubmissions(dispatch, state.userList.handles, wait);
  };

  const submitUser = () => {
    // Notification.info({
    //   title: "User submitted!",
    //   duration: 200,
    //   description: "hh",
    // });
    // toast.error("? Wow so easy!", {
    //   position: "bottom-right",
    //   autoClose: 2001,
    //   hideProgressBar: false,
    //   closeOnClick: true,
    //   pauseOnHover: true,
    //   draggable: true,
    //   progress: undefined,
    // }); 
    fetchUsers(dispatch, handle);
  };

  return (
    <Navbar
      className={
        "navbar navbar-expand-lg p-2 ps-4 pe-4 " + state.appState.theme.navbar
      }
      expand="md"
    >
      <div className="container p-0">
        <Link to="/" className="navbar-brand" href="#">
          CFTracker
        </Link>
        <Navbar.Toggle aria-controls="basic-navbar-nav" />

        <Navbar.Collapse id="basic-navbar-nav">
          <Nav className="ms-auto mt-2 mt-lg-0">
            <li className="nav-item active">
              <Link to={Path.Issues} className="nav-link" href="#">
                {/* <span className="p-1">{<FontAwesomeIcon icon={faBars} />}</span> */}
                <span>Issues</span>
              </Link>
            </li>
            <li className="nav-item active">
              <Link to={Path.PROBLEMS} className="nav-link" href="#">
                {/* <span className="p-1">{<FontAwesomeIcon icon={faBars} />}</span> */}
                <span>Problems</span>
              </Link>
            </li>
            <li className="nav-item">
              <Link to={Path.CONTESTS} className="nav-link" href="#">
                {/*  <span className="p-1">  {<FontAwesomeIcon icon={faListAlt} />}  </span>*/}
                <span>Contests</span>
              </Link>
            </li>

            <li className="nav-item">
              <OverlayTrigger
                trigger="click"
                placement="bottom"
                key="bottom"
                overlay={
                  <Popover
                    id="popover-basic"
                    className={state.appState.theme.bgText}
                  >
                    <Popover.Header
                      as="h3"
                      className={state.appState.theme.bgText}
                    >
                      <div className="d-flex align-items-center">
                        <span className={state.appState.theme.bgText}>
                          CFTracker (Created by{" "}
                          <a
                            href="https://codeforces.com/profile/bashem"
                            className={" " + state.appState.theme.text}
                            target="__blank"
                          >
                            bashem
                          </a>
                          )
                        </span>
                      </div>
                    </Popover.Header>
                    <Popover.Body className={state.appState.theme.bgText}>
                      <ul className="list-group list-group-flush">
                        <li
                          className={
                            "list-group-item " + state.appState.theme.bgText
                          }
                        >
                          <span className="pe-2">Source Code</span>
                          <a
                            href="https://github.com/mbashem/cftracker"
                            className="text-secondary pt-1 fs-5"
                            target="__blank"
                          >
                            {<FontAwesomeIcon icon={faGithub} />}
                          </a>
                        </li>
                      </ul>
                    </Popover.Body>
                  </Popover>
                }
              >
                <a
                  href="#"
                  onClick={(e) => e.preventDefault()}
                  className="nav-link"
                  title="Created by Bashem"
                >
                  <FontAwesomeIcon icon={faInfo} />
                </a>
              </OverlayTrigger>
            </li>

            <li className="nav-item">
              <a
                className={"nav-link"}
                href="#"
                title="Change Theme"
                onClick={(e) => {
                  e.preventDefault();
                  if (state.appState.themeMod === ThemesType.DARK)
                    changeAppState(
                      dispatch,
                      AppReducerType.CHANGE_THEME,
                      ThemesType.LIGHT
                    );
                  else
                    changeAppState(
                      dispatch,
                      AppReducerType.CHANGE_THEME,
                      ThemesType.DARK
                    );
                }}
              >
                <FontAwesomeIcon
                  icon={
                    state.appState.themeMod === ThemesType.DARK ? faMoon : faSun
                  }
                />
              </a>
            </li>

            <li className="nav-item">
              <a
                className="nav-link"
                onClick={(e) => {
                  e.preventDefault();
                  sync();
                }}
                title="Refresh Submissions"
                href="#"
              >
                <FontAwesomeIcon icon={faSync} />
              </a>
            </li>

            <li className="nav-item">
              <form
                className="form-inline d-flex my-2 my-lg-0 nav-item"
                onSubmit={(e) => {
                  e.preventDefault();
                  submitUser();
                }}
              >
                <input
                  name="handle"
                  className={"form-control " + state.appState.theme.bgText}
                  type="text"
                  placeholder="handle1,handle2,.."
                  aria-label="handles"
                  value={handle}
                  onChange={(e) => setHandle(e.target.value)}
                />
              </form>
            </li>
          </Nav>
        </Navbar.Collapse>
      </div>
    </Navbar>
  );
}
Example #12
Source File: index.tsx    From nouns-monorepo with GNU General Public License v3.0 4 votes vote down vote up
Playground: React.FC = () => {
  const [nounSvgs, setNounSvgs] = useState<string[]>();
  const [traits, setTraits] = useState<Trait[]>();
  const [modSeed, setModSeed] = useState<{ [key: string]: number }>();
  const [initLoad, setInitLoad] = useState<boolean>(true);
  const [displayNoun, setDisplayNoun] = useState<boolean>(false);
  const [indexOfNounToDisplay, setIndexOfNounToDisplay] = useState<number>();
  const [selectIndexes, setSelectIndexes] = useState<Record<string, number>>({});
  const [pendingTrait, setPendingTrait] = useState<PendingCustomTrait>();
  const [isPendingTraitValid, setPendingTraitValid] = useState<boolean>();

  const customTraitFileRef = useRef<HTMLInputElement>(null);

  const generateNounSvg = React.useCallback(
    (amount: number = 1) => {
      for (let i = 0; i < amount; i++) {
        const seed = { ...getRandomNounSeed(), ...modSeed };
        const { parts, background } = getNounData(seed);
        const svg = buildSVG(parts, encoder.data.palette, background);
        setNounSvgs(prev => {
          return prev ? [svg, ...prev] : [svg];
        });
      }
    },
    // eslint-disable-next-line react-hooks/exhaustive-deps
    [pendingTrait, modSeed],
  );

  useEffect(() => {
    const traitTitles = ['background', 'body', 'accessory', 'head', 'glasses'];
    const traitNames = [
      ['cool', 'warm'],
      ...Object.values(ImageData.images).map(i => {
        return i.map(imageData => imageData.filename);
      }),
    ];
    setTraits(
      traitTitles.map((value, index) => {
        return {
          title: value,
          traitNames: traitNames[index],
        };
      }),
    );

    if (initLoad) {
      generateNounSvg(8);
      setInitLoad(false);
    }
  }, [generateNounSvg, initLoad]);

  const traitOptions = (trait: Trait) => {
    return Array.from(Array(trait.traitNames.length + 1)).map((_, index) => {
      const traitName = trait.traitNames[index - 1];
      const parsedTitle = index === 0 ? `Random` : parseTraitName(traitName);
      return (
        <option key={index} value={traitName}>
          {parsedTitle}
        </option>
      );
    });
  };

  const traitButtonHandler = (trait: Trait, traitIndex: number) => {
    setModSeed(prev => {
      // -1 traitIndex = random
      if (traitIndex < 0) {
        let state = { ...prev };
        delete state[trait.title];
        return state;
      }
      return {
        ...prev,
        [trait.title]: traitIndex,
      };
    });
  };

  const resetTraitFileUpload = () => {
    if (customTraitFileRef.current) {
      customTraitFileRef.current.value = '';
    }
  };

  let pendingTraitErrorTimeout: NodeJS.Timeout;
  const setPendingTraitInvalid = () => {
    setPendingTraitValid(false);
    resetTraitFileUpload();
    pendingTraitErrorTimeout = setTimeout(() => {
      setPendingTraitValid(undefined);
    }, 5_000);
  };

  const validateAndSetCustomTrait = (file: File | undefined) => {
    if (pendingTraitErrorTimeout) {
      clearTimeout(pendingTraitErrorTimeout);
    }
    if (!file) {
      return;
    }

    const reader = new FileReader();
    reader.onload = e => {
      try {
        const buffer = Buffer.from(e?.target?.result!);
        const png = PNG.sync.read(buffer);
        if (png.width !== 32 || png.height !== 32) {
          throw new Error('Image must be 32x32');
        }
        const filename = file.name?.replace('.png', '') || 'custom';
        const data = encoder.encodeImage(filename, {
          width: png.width,
          height: png.height,
          rgbaAt: (x: number, y: number) => {
            const idx = (png.width * y + x) << 2;
            const [r, g, b, a] = [
              png.data[idx],
              png.data[idx + 1],
              png.data[idx + 2],
              png.data[idx + 3],
            ];
            return {
              r,
              g,
              b,
              a,
            };
          },
        });
        setPendingTrait({
          data,
          filename,
          type: DEFAULT_TRAIT_TYPE,
        });
        setPendingTraitValid(true);
      } catch (error) {
        setPendingTraitInvalid();
      }
    };
    reader.readAsArrayBuffer(file);
  };

  const uploadCustomTrait = () => {
    const { type, data, filename } = pendingTrait || {};
    if (type && data && filename) {
      const images = ImageData.images as Record<string, EncodedImage[]>;
      images[type].unshift({
        filename,
        data,
      });
      const title = traitKeyToTitle[type];
      const trait = traits?.find(t => t.title === title);

      resetTraitFileUpload();
      setPendingTrait(undefined);
      setPendingTraitValid(undefined);
      traitButtonHandler(trait!, 0);
      setSelectIndexes({
        ...selectIndexes,
        [title]: 0,
      });
    }
  };

  return (
    <>
      {displayNoun && indexOfNounToDisplay !== undefined && nounSvgs && (
        <NounModal
          onDismiss={() => {
            setDisplayNoun(false);
          }}
          svg={nounSvgs[indexOfNounToDisplay]}
        />
      )}

      <Container fluid="lg">
        <Row>
          <Col lg={10} className={classes.headerRow}>
            <span>
              <Trans>Explore</Trans>
            </span>
            <h1>
              <Trans>Playground</Trans>
            </h1>
            <p>
              <Trans>
                The playground was built using the {nounsProtocolLink}. Noun's traits are determined
                by the Noun Seed. The seed was generated using {nounsAssetsLink} and rendered using
                the {nounsSDKLink}.
              </Trans>
            </p>
          </Col>
        </Row>
        <Row>
          <Col lg={3}>
            <Col lg={12}>
              <Button
                onClick={() => {
                  generateNounSvg();
                }}
                className={classes.primaryBtn}
              >
                <Trans>Generate Nouns</Trans>
              </Button>
            </Col>
            <Row>
              {traits &&
                traits.map((trait, index) => {
                  return (
                    <Col lg={12} xs={6}>
                      <Form className={classes.traitForm}>
                        <FloatingLabel
                          controlId="floatingSelect"
                          label={traitKeyToLocalizedTraitKeyFirstLetterCapitalized(trait.title)}
                          key={index}
                          className={classes.floatingLabel}
                        >
                          <Form.Select
                            aria-label="Floating label select example"
                            className={classes.traitFormBtn}
                            value={trait.traitNames[selectIndexes?.[trait.title]] ?? -1}
                            onChange={e => {
                              let index = e.currentTarget.selectedIndex;
                              traitButtonHandler(trait, index - 1); // - 1 to account for 'random'
                              setSelectIndexes({
                                ...selectIndexes,
                                [trait.title]: index - 1,
                              });
                            }}
                          >
                            {traitOptions(trait)}
                          </Form.Select>
                        </FloatingLabel>
                      </Form>
                    </Col>
                  );
                })}
            </Row>
            <label style={{ margin: '1rem 0 .25rem 0' }} htmlFor="custom-trait-upload">
              <Trans>Upload Custom Trait</Trans>
              <OverlayTrigger
                trigger="hover"
                placement="top"
                overlay={
                  <Popover>
                    <div style={{ padding: '0.25rem' }}>
                      <Trans>Only 32x32 PNG images are accepted</Trans>
                    </div>
                  </Popover>
                }
              >
                <Image
                  style={{ margin: '0 0 .25rem .25rem' }}
                  src={InfoIcon}
                  className={classes.voteIcon}
                />
              </OverlayTrigger>
            </label>
            <Form.Control
              type="file"
              id="custom-trait-upload"
              accept="image/PNG"
              isValid={isPendingTraitValid}
              isInvalid={isPendingTraitValid === false}
              ref={customTraitFileRef}
              className={classes.fileUpload}
              onChange={(e: ChangeEvent<HTMLInputElement>) =>
                validateAndSetCustomTrait(e.target.files?.[0])
              }
            />
            {pendingTrait && (
              <>
                <FloatingLabel label="Custom Trait Type" className={classes.floatingLabel}>
                  <Form.Select
                    aria-label="Custom Trait Type"
                    className={classes.traitFormBtn}
                    onChange={e => setPendingTrait({ ...pendingTrait, type: e.target.value })}
                  >
                    {Object.entries(traitKeyToTitle).map(([key, title]) => (
                      <option value={key}>{capitalizeFirstLetter(title)}</option>
                    ))}
                  </Form.Select>
                </FloatingLabel>
                <Button onClick={() => uploadCustomTrait()} className={classes.primaryBtn}>
                  <Trans>Upload</Trans>
                </Button>
              </>
            )}
            <p className={classes.nounYearsFooter}>
              <Trans>
                You've generated{' '}
                {i18n.number(parseInt(nounSvgs ? (nounSvgs.length / 365).toFixed(2) : '0'))} years
                worth of Nouns
              </Trans>
            </p>
          </Col>
          <Col lg={9}>
            <Row>
              {nounSvgs &&
                nounSvgs.map((svg, i) => {
                  return (
                    <Col xs={4} lg={3} key={i}>
                      <div
                        onClick={() => {
                          setIndexOfNounToDisplay(i);
                          setDisplayNoun(true);
                        }}
                      >
                        <Noun
                          imgPath={`data:image/svg+xml;base64,${btoa(svg)}`}
                          alt="noun"
                          className={classes.nounImg}
                          wrapperClassName={classes.nounWrapper}
                        />
                      </div>
                    </Col>
                  );
                })}
            </Row>
          </Col>
        </Row>
      </Container>
    </>
  );
}