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

The following examples show how to use @fortawesome/free-solid-svg-icons#faPlus. 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: index.tsx    From bad-cards-game with GNU Affero General Public License v3.0 6 votes vote down vote up
library.add(
  faDotCircle,
  faCircle,
  faBars,
  faTimes,
  faInfoCircle,
  faTrophy,
  faShareSquare,
  faHeart,
  faInstagram,
  faTwitter,
  faGithub,
  faFacebook,
  faHandPointRight,
  faEdit,
  faSave,
  faCamera,
  faPlus,
  faMinus,
  faRandom,
);
Example #2
Source File: ComponentsPanel.tsx    From MagicUI with Apache License 2.0 6 votes vote down vote up
function NewPageButton(props: { onClick: () => void }) {
  return (
    <div className={style.new_page_button} onClick={props.onClick}>
      <button>
        <FontAwesomeIcon icon={faPlus}/>
      </button>
    </div>
  );
}
Example #3
Source File: AddTask.tsx    From knboard with MIT License 6 votes vote down vote up
AddTask = ({ columnId, index }: Props) => {
  const dispatch = useDispatch();

  const handleOnClick = () => {
    dispatch(setCreateDialogColumn(columnId));
    dispatch(setCreateDialogOpen(true));
  };

  return (
    <Button
      css={css`
        text-transform: inherit;
        color: ${N80A};
        padding: 4px 0;
        margin-top: 6px;
        margin-bottom: 6px;
        &:hover {
          color: ${N900};
        }
        &:focus {
          outline: 2px solid #aaa;
        }
        .MuiButton-iconSizeMedium > *:first-of-type {
          font-size: 12px;
        }
      `}
      startIcon={<FontAwesomeIcon icon={faPlus} />}
      onClick={handleOnClick}
      fullWidth
    >
      Add card
    </Button>
  );
}
Example #4
Source File: shared.module.ts    From enterprise-ng-2020-workshop with MIT License 6 votes vote down vote up
constructor(faIconLibrary: FaIconLibrary) {
    faIconLibrary.addIcons(
      faGithub,
      faMediumM,
      faPlus,
      faEdit,
      faTrash,
      faTimes,
      faCaretUp,
      faCaretDown,
      faExclamationTriangle,
      faFilter,
      faTasks,
      faCheck,
      faSquare,
      faLanguage,
      faPaintBrush,
      faLightbulb,
      faWindowMaximize,
      faStream,
      faBook
    );
  }
Example #5
Source File: index.tsx    From genshin-optimizer with MIT License 6 votes vote down vote up
function NewArtifactCard() {
  const [show, setshow] = useState(false)
  const onShow = useCallback(() => setshow(true), [setshow])
  const onHide = useCallback(() => setshow(false), [setshow])

  return <CardDark sx={{ height: "100%", width: "100%", minHeight: 300, display: "flex", flexDirection: "column" }}>
    <Suspense fallback={false}><ArtifactEditor
      artifactIdToEdit={show ? "new" : ""}
      cancelEdit={onHide}
      allowUpload
      allowEmpty
    /></Suspense>
    <CardContent>
      <Typography sx={{ textAlign: "center" }}>Add New Artifact</Typography>
    </CardContent>
    <Box sx={{
      flexGrow: 1,
      display: "flex",
      justifyContent: "center",
      alignItems: "center"
    }}
    >
      <Button onClick={onShow} color="info" sx={{ borderRadius: "1em" }}>
        <Typography variant="h1"><FontAwesomeIcon icon={faPlus} className="fa-fw" /></Typography>
      </Button>
    </Box>
  </CardDark>
}
Example #6
Source File: index.tsx    From excalideck with MIT License 6 votes vote down vote up
export default function AddEmptySlideButton({ onClick }: Props) {
    return (
        <button
            className="AddEmptySlideButton"
            title="Add slide"
            onClick={() => onClick()}
        >
            <FontAwesomeIcon icon={faPlus} />
        </button>
    );
}
Example #7
Source File: AddGoalButton.tsx    From calculate-my-odds with MIT License 6 votes vote down vote up
render() {
        return (
            <div className="add-goal-button-component">
                <ButtonWithDropdown
                    icon={faPlus}
                    content="Add goal"
                    onClick={() => this.props.onNewGoal?.(this.createSingleCompletionGoal())}
                    dropdownItems={[
                        {
                            name: "Single completion goal",
                            onClick: () => this.props.onNewGoal?.(this.createSingleCompletionGoal())
                        },
                        {
                            name: "Full completion goal",
                            onClick: () => this.props.onNewGoal?.(this.createFullCompletionGoal())
                        },
                        {
                            name: "Partial completion goal",
                            onClick: () => this.props.onNewGoal?.(this.createPartialCompletionGoal())
                        }
                    ]}
                    dropdownWidth="11em"
                />
            </div>
        );
    }
Example #8
Source File: AddFailureButton.tsx    From calculate-my-odds with MIT License 6 votes vote down vote up
render() {
        return (
            <div className="add-failure-button-component">
                <ButtonWithDropdown
                    icon={faPlus}
                    content="Add failure condition"
                    onClick={() => this.props.onNewFailure?.(this.createSingleCompletionFailure())}
                    dropdownItems={[
                        {
                            name: "Single completion condition",
                            onClick: () => this.props.onNewFailure?.(this.createSingleCompletionFailure())
                        },
                        {
                            name: "Full completion condition",
                            onClick: () => this.props.onNewFailure?.(this.createFullCompletionFailure())
                        },
                        {
                            name: "Partial completion condition",
                            onClick: () => this.props.onNewFailure?.(this.createPartialCompletionFailure())
                        }
                    ]}
                    dropdownWidth="13em"
                />
            </div>
        );
    }
Example #9
Source File: WriteLessonLink.tsx    From frontend.ro with MIT License 6 votes vote down vote up
function WriteLessonLink() {
  return (
    <Link href="/intro/despre-noi#cum-pot-sa-ajut">
      <a className={`${styles['write-lesson-link']} d-flex justify-content-center align-items-center no-underline`}>
        <FontAwesomeIcon icon={faPlus} width="32" height="32" />
        <p>Contribuie și tu cu o lecție</p>
      </a>
    </Link>
  );
}
Example #10
Source File: OrganizationDropdown.tsx    From argo-react with MIT License 5 votes vote down vote up
OrganizationDropdown: React.FC<IOrganizationDropdownProps> = ({
  setShowDropdown,
}) => {
  const history = useHistory();

  const { setSelectedOrganization } = useContext<IActionModel>(ActionContext);
  const { user } = useContext(StateContext);

  return (
    <>
      <div
        className="organization-dropdown-overlay"
        onClick={(e) => setShowDropdown(false)}
      ></div>
      <div className="organization-dropdown-box">
        <label>Teams</label>
        {user?.organizations?.map((org: IOrganization, index: number) => (
          <OrganizationDropdownItem
            orgDetails={{
              name: org.profile.name,
              avatar: org.profile.image
                ? org.profile.image
                : require("../../../../../assets/png/default_icon.png"),
            }}
            key={index}
            onClick={(e: Event) => {
              setSelectedOrganization(org);
              // history.push("/dashboard");
              setShowDropdown(false);
            }}
          />
        ))}
        <div
          className="create-team-item-container"
          onClick={(e) => history.push("/org/new")}
        >
          <div className="create-team-item">
            <div className="create-team-title">Create a Organization</div>
            <div className="create-team-icon">
              <FontAwesomeIcon icon={faPlus}></FontAwesomeIcon>
            </div>
          </div>
        </div>
      </div>
    </>
  );
}
Example #11
Source File: tableColumns.tsx    From solo with MIT License 5 votes vote down vote up
createColumns: CreateColumns = () => [
  {
    Header: "Details",
    Cell: ({ row }) => (
      <span {...row.getToggleRowExpandedProps()}>
        <FontAwesomeIcon icon={row.isExpanded ? faMinus : faPlus} />
      </span>
    )
  },
  {
    Header: "SDN",
    accessor: "sdn"
  },
  {
    Header: "Service Request #",
    accessor: "serviceRequest",
    id: "service_request"
  },
  {
    Header: "Commodity",
    id: "suppadd__code",
    accessor: "commodityName"
  },
  {
    Header: "Status",
    disableSortBy: true,
    id: "currentStatus",
    accessor: ({ mostRecentStatusIdx, statuses }) =>
      statuses[mostRecentStatusIdx].dic
  },
  {
    Header: "Nomenclature",
    id: "part__nomen",
    accessor: "part.nomen"
  },
  {
    Header: "Last Updated",
    id: "statuses__status_date",
    disableSortBy: true,
    accessor: ({ mostRecentStatusIdx, statuses }) =>
      `${formatDistanceToNow(
        parseISO(statuses[mostRecentStatusIdx].status_date)
      )} ago`
  }
]
Example #12
Source File: Tabs.tsx    From calculate-my-odds with MIT License 5 votes vote down vote up
render() {
        return (
            <div className="tabs-component">
                <div className="tabs-header-effects">
                    <div className={`tab-header-fade-left ${this.state.hideLeftFade ? "tab-fade-hidden" : ""}`}></div>
                    <div className={`tab-header-fade-right ${this.state.hideRightFade ? "tab-fade-hidden" : ""}`}></div>
                </div>
                <div
                    ref={this.headerContainerRef}
                    className="tab-header-container"
                    onWheel={e => {
                        const direction = e.deltaY < 0 ? -1 : e.deltaY > 0 ? 1 : 0;
                        this.headerContainerRef.current!.scrollLeft += direction * 20;
                        if (e.cancelable) {
                            e.preventDefault();
                        }
                    }}
                >
                    {this.props.tabs.map((tab, index) => (
                        <div
                            key={tab.id}
                            className={`tab-header ${this.props.selectedIndex === index ? "tab-selected" : ""}`}
                            onClick={() => this.props.onTabSelected?.(index)}
                        >
                            <div className="tab-header-name">
                                {tab.name}
                            </div>
                            {this.props.tabs.length >= 2 && this.props.onTabRemovalRequest &&
                            <div className="tab-header-actions">
                                <div
                                    className="tab-header-remove"
                                    onClick={e => {
                                        e.stopPropagation();
                                        this.props.onTabRemovalRequest?.(index);
                                    }}
                                >
                                    <FontAwesomeIcon icon={faTimes} />
                                </div>
                            </div>
                            }
                        </div>
                    ))}
                    {this.props.onRequestNewTab &&
                    <div
                        className="tab-header"
                        onClick={() => this.props.onRequestNewTab?.()}
                    >
                        <FontAwesomeIcon icon={faPlus} className="new-tab-icon" />
                    </div>
                    }
                </div>
                <div className="tab-content-container">
                    {this.props.tabs.map((tab, index) => (
                        <div key={tab.id} className={`tab-content ${index === this.props.selectedIndex ? "" : "tab-content-hidden"}`}>
                            {tab.content}
                        </div>
                    ))}
                </div>
            </div>
        );
    }
Example #13
Source File: transactions-table.component.ts    From thorchain-explorer-singlechain with MIT License 5 votes vote down vote up
constructor() {
    this.swapIcon = faExchangeAlt;
    this.depositIcon = faLevelDownAlt;
    this.withdrawIcon = faLevelUpAlt;
    this.refundIcon = faUndoAlt;
    this.addIcon = faPlus;
    this.timesIcon = faTimes;
  }
Example #14
Source File: UserActivity.tsx    From frontend.ro with MIT License 5 votes vote down vote up
CreatedExercises = () => {
  const [createdExercises, setCreatedExercises] = useState([]);

  useEffect(() => {
    ExerciseService
      .getCreatedExercises()
      .then((exercises) => {
        setCreatedExercises(exercises);
      })
      .catch((err) => {
        console.error('[CreatedExercises.getCreatedExercises] Failed to get exercises.', err);
      });
  }, []);

  return (
    <section>
      <h2> Exerciții create </h2>
      <div className={styles['exercises-wrapper']}>
        {createdExercises.map((ex) => (
          <ExercisePreview
            key={ex._id}
            exercise={ex}
            href={`exercitii/${ex._id}`}
            isPrivate={ex.private}
            viewMode="TEACHER"
            feedbackCount={0}
            isApproved={false}
            readOnly={false}
          />
        ))}
        <Link href="/exercitii/creeaza">
          <a className="d-flex align-items-center justify-content-center no-underline text-center">
            <FontAwesomeIcon icon={faPlus} width="32" height="32" />
            <span> Creează un nou exercițiu </span>
          </a>
        </Link>
      </div>
    </section>
  );
}
Example #15
Source File: ComplexList.example.tsx    From hacker-ui with MIT License 5 votes vote down vote up
function ComplexListExample(props: Props) {
  const { Root, styles } = useStyles(props);
  const [quantities, setQuantities] = useState({} as { [id: string]: number });

  return (
    <Root>
      <List className={styles.list}>
        {products.map(({ id, imgUrl, title, price }) => {
          const quantity = quantities[id] ?? 0;

          const handleDec = () => {
            setQuantities((quantities) => ({
              ...quantities,
              [id]: Math.max(0, quantity - 1),
            }));
          };
          const handleInc = () => {
            setQuantities((quantities) => ({
              ...quantities,
              [id]: quantity + 1,
            }));
          };

          return (
            <ListItem className={styles.listItem}>
              <img className={styles.img} src={imgUrl} alt={title} />
              <div className={styles.info}>
                <h3 className={styles.title}>{title}</h3>
                <p className={styles.subtitle}>
                  ${(price / 100).toLocaleString()}
                </p>
              </div>
              <div className={styles.buttonSection}>
                <div className={styles.buttons}>
                  <Button shape="icon" size="small" onClick={handleDec}>
                    <FontAwesomeIcon icon={faMinus} />
                  </Button>
                  <div className={styles.quantityCount}>{quantity}</div>
                  <Button shape="icon" size="small" onClick={handleInc}>
                    <FontAwesomeIcon icon={faPlus} />
                  </Button>
                </div>
                <div className={styles.subLabel}>Quantity</div>
              </div>
              <div className={styles.subtotalSection}>
                <div className={styles.subtotal}>
                  ${((quantity * price) / 100).toLocaleString()}
                </div>
                <div className={styles.subLabel}>Subtotal</div>
              </div>
            </ListItem>
          );
        })}
      </List>
    </Root>
  );
}
Example #16
Source File: FileSystem.tsx    From MagicUI with Apache License 2.0 5 votes vote down vote up
function ProjectManageTools() {
  const user = useSelector((state: IStoreState) => state.user);
  const dslFile = useSelector((state: IStoreState) => state.dslFile);
  const openFileItems = useSelector((state: IStoreState) => state.openFileItems);
  const dispatch = useDispatch();
  const handleCreateFile = () => {
    modal(cancel => (
      <NewFileOrFolderModal fileType="file" cancel={cancel} email={user.email} folder={dslFile.folder}
                            dispatch={dispatch}/>
    ));
  };
  const handleCreateFolder = () => {
    modal(cancel => (
      <NewFileOrFolderModal fileType="folder" cancel={cancel} email={user.email} folder={dslFile.folder}
                            dispatch={dispatch}/>
    ));
  };

  const handleDeleteFile = () => {
    modal(cancel => <Confirm title={`Do you want to delete ${dslFile.filename}`} cancel={cancel} confirm={() => {
      deleteDslFile(user.email, dslFile.id, dslFile.fileId).then(v => {
        if (!v.err) {
          toast('delete file!');
          dispatch(localDeleteFile(dslFile.id));
          let i = 0, check = false;
          for (let item of openFileItems.items) {
            if (v.id === item.id) {
              check = true;
              break
            }
            i++;
          }
          console.log(check, i - 1);
          check && dispatch(closeFile(i - 1, dslFile.id, dslFile.fileId));
          cancel();
        }
      });
    }}/>);
  };

  return (
    <div className={style.project_manage_tools}>
      <button className={style.mk_file_btn} onClick={handleCreateFile}>
        <FontAwesomeIcon icon={faPlus}/>
      </button>
      <button className={style.mk_dir_btn} onClick={handleCreateFolder}>
        <FontAwesomeIcon icon={faFolderPlus}/>
      </button>
      <button className={style.delete_btn} onClick={handleDeleteFile}>
        <FontAwesomeIcon icon={faTrash}/>
      </button>
    </div>
  );
}
Example #17
Source File: ProbabilityTableContainer.tsx    From calculate-my-odds with MIT License 4 votes vote down vote up
render() {
        return (
            <SpaceContainer className="probability-table-component">
                {this.props.table.items.length > 0 &&
                <div>
                    {this.props.table.items.map((item, index) => (
                        <ProbabilityInput
                            key={item.id}
                            item={item}
                            onChange={item => this.updateItem(index, item)}
                            onDeleteRequest={() => this.deleteItem(item)}
                            requestTabFocus={this.props.requestTabFocus}
                            showDeleteButton={this.props.table.items.length >= 2}
                            validator={this.props.validator}
                        />
                    ))}
                </div>
                }
                {this.state.showProbabilitiesExceedOneError &&
                <div>
                    <ErrorDisplay>
                        The sum of all probabilities in the table should not exceed 100%.
                    </ErrorDisplay>
                </div>
                }
                <ButtonContainer>
                    <Button
                        icon={faPlus}
                        content="Add option"
                        onClick={() => this.addNewItem()}
                    />
                    <Button
                        icon={faList}
                        content="Properties"
                        onClick={() => this.setState({
                            showTableProperties: !this.state.showTableProperties
                        })}
                    />
                </ButtonContainer>
                {this.state.showTableProperties &&
                <SpaceContainer>
                    <div>
                        <label>Table name</label>
                        <Input
                            value={this.props.table.name}
                            onChange={e => this.props.onChange?.({
                                ...this.props.table,
                                name: e.target.value
                            })}
                        />
                    </div>
                    <div>
                        <label>Table rolls per iteration</label>
                        <TooltipContainer
                            tooltipContent="Enter a value."
                            show={this.state.showEmptyTableRollsPerIterationError}
                            side={TooltipSide.Right}
                        >
                            <IntegerInput
                                value={this.props.table.rollsPerIteration}
                                onChange={value => {
                                    this.props.onChange?.({
                                        ...this.props.table,
                                        rollsPerIteration: value
                                    });
                                    this.setState({
                                        showEmptyTableRollsPerIterationError: false
                                    });
                                }}
                                onFocus={() => this.setState({
                                    showEmptyTableRollsPerIterationError: false
                                })}
                                markError={this.state.showEmptyTableRollsPerIterationError}
                            />
                        </TooltipContainer>
                    </div>
                </SpaceContainer>
                }
            </SpaceContainer>
        );
    }
Example #18
Source File: FileSwitcher.tsx    From frontend.ro with MIT License 4 votes vote down vote up
render() {
    const {
      readOnly,
      maxHeight,
      folderStructure,
      selectedFileKey,
      feedbacks: feedbacksProp,
    } = this.props;

    const {
      ctxMenuKey,
      isCollapsed,
      ctxMenuType,
      dropdownStyle,
      isGeneratingArchive,
    } = this.state;

    let { renamedAsset } = this.state;
    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    // @ts-ignore
    renamedAsset = renamedAsset || { key: null };

    const files = folderStructure.files.map((f) => ({ ...f, icon: FileIcons.getIcon(f.name) }));
    const feedbacks = new Feedbacks(null, feedbacksProp || []).getTypesByFileKey();

    return (
      <div
        className={`
          ${styles['file-switcher']}
          ${readOnly ? styles['is--read-only'] : ''}
          ${isCollapsed ? styles['is--collapsed'] : ''}`}
        ref={this.fileSwitcherRef}
        style={{ width: `${INITIAL_WIDTH_PX}px`, minWidth: `${MIN_WIDTH_PX}px`, maxHeight: `${maxHeight}px` }}
      >
        {isCollapsed && (
          <Button onClick={this.toggleCollapse} title="Browse files" className={`${styles['toggle-button']}`}>
            <img src={FileIcons.getIconUrl('svg')} alt="File SVG icon" />
          </Button>
        )}
        <div className={styles.controls}>
          <div>
            {!readOnly && (
              <Button onClick={() => this.newFile()} title="New file">
                <FontAwesomeIcon icon={faPlus} width="18" height="18" />
              </Button>
            )}
            {!readOnly && (
              <Button onClick={() => this.newFolder()} title="New folder">
                <FontAwesomeIcon icon={faFolderPlus} width="18" height="18" />
              </Button>
            )}
            <Button
              onClick={this.onDownload}
              loading={isGeneratingArchive}
              title="Download to device"
            >
              <FontAwesomeIcon icon={faCloudDownloadAlt} width="18" height="18" />
            </Button>
          </div>
          <Button onClick={this.toggleCollapse} title="Collapse panel">
            <FontAwesomeIcon icon={faChevronLeft} width="18" height="18" />
          </Button>
        </div>
        {/* <Scroll className="is--fliped-x"> */}
        <div>
          {folderStructure.folders.map((folder, index) => (
            <FolderBrowse
              key={folder.key}
              folderKey={folder.key}
              folderStructure={folderStructure}
              // eslint-disable-next-line @typescript-eslint/ban-ts-comment
              // @ts-ignore
              feedbacks={feedbacks}
              readOnly={readOnly}
              selectFile={this.selectFile}
              selectedFileKey={selectedFileKey}
              renamedAsset={renamedAsset}
              ctxMenuKey={ctxMenuKey}
              openMenu={this.openMenu}
              enterEditMode={this.enterEditMode}
              onRename={this.onRename}
              saveAsset={this.saveAsset}
            />
          ))}
          <FilesList
            readOnly={readOnly}
            files={files}
            feedbacks={feedbacks}
            selectedFileKey={selectedFileKey}
            ctxMenuKey={ctxMenuKey}
            selectFile={this.selectFile}
            enterEditMode={this.enterEditMode}
            openMenu={this.openMenu}
            renamedAsset={renamedAsset}
            onRename={this.onRename}
            saveAsset={this.saveAsset}
          />
        </div>
        {/* </Scroll> */}
        <List className={styles['dropdown-menu']} style={dropdownStyle}>
          {ctxMenuType === 'FOLDER' && (
            <>
              <li>
                <Button onClick={() => this.newFile(ctxMenuKey)}>
                  <FontAwesomeIcon icon={faFileAlt} width="18" height="18" />
                  New file
                </Button>
              </li>
              <li>
                <Button onClick={() => this.newFolder(ctxMenuKey)}>
                  <FontAwesomeIcon icon={faFolder} width="18" height="18" />
                  New folder
                </Button>
              </li>
            </>
          )}
          <li>
            <Button onClick={() => this.enterEditMode(ctxMenuKey)}>
              <FontAwesomeIcon icon={faEdit} width="18" height="18" />
              Rename
            </Button>
          </li>
          <li>
            <Button onClick={() => this.deleteFileOrFolder(ctxMenuKey)}>
              <FontAwesomeIcon icon={faTrashAlt} width="18" height="18" />
              Delete
            </Button>
          </li>
        </List>
        <HResizable onResize={this.onResize} />
      </div>
    );
  }
Example #19
Source File: index.tsx    From genshin-optimizer with MIT License 4 votes vote down vote up
export default function PageCharacter(props) {
  // TODO: #412 We shouldn't be loading all the character translation files. Should have a separate lookup file for character name.
  const { t } = useTranslation(["page_character", ...allCharacterKeys.map(k => `char_${k}_gen`)])
  const { database } = useContext(DatabaseContext)
  const [state, stateDispatch] = useDBState("CharacterDisplay", initialState)
  const [searchTerm, setSearchTerm] = useState("")
  const deferredSearchTerm = useDeferredValue(searchTerm)
  const [pageIdex, setpageIdex] = useState(0)
  const invScrollRef = useRef<HTMLDivElement>(null)
  const setPage = useCallback(
    (e, value) => {
      invScrollRef.current?.scrollIntoView({ behavior: "smooth" })
      setpageIdex(value - 1);
    },
    [setpageIdex, invScrollRef],
  )

  const brPt = useMediaQueryUp()
  const maxNumToDisplay = numToShowMap[brPt]

  const [newCharacter, setnewCharacter] = useState(false)
  const [dbDirty, forceUpdate] = useForceUpdate()
  //set follow, should run only once
  useEffect(() => {
    ReactGA.send({ hitType: "pageview", page: '/characters' })
    return database.followAnyChar(forceUpdate)
  }, [forceUpdate, database])

  const characterSheets = usePromise(CharacterSheet.getAll, [])

  const deleteCharacter = useCallback(async (cKey: CharacterKey) => {
    const chararcterSheet = await CharacterSheet.get(cKey)
    let name = chararcterSheet?.name
    //use translated string
    if (typeof name === "object")
      name = i18next.t(`char_${cKey}_gen:name`)

    if (!window.confirm(t("removeCharacter", { value: name }))) return
    database.removeChar(cKey)
  }, [database, t])

  const editCharacter = useCharSelectionCallback()

  const navigate = useNavigate()

  const { element, weaponType } = state
  const sortConfigs = useMemo(() => characterSheets && characterSortConfigs(database, characterSheets), [database, characterSheets])
  const filterConfigs = useMemo(() => characterSheets && characterFilterConfigs(database, characterSheets), [database, characterSheets])
  const { charKeyList, totalCharNum } = useMemo(() => {
    const chars = database._getCharKeys()
    const totalCharNum = chars.length
    if (!sortConfigs || !filterConfigs) return { charKeyList: [], totalCharNum }
    const charKeyList = database._getCharKeys()
      .filter(filterFunction({ element, weaponType, favorite: "yes", name: deferredSearchTerm }, filterConfigs))
      .sort(sortFunction(state.sortType, state.ascending, sortConfigs))
      .concat(
        database._getCharKeys()
          .filter(filterFunction({ element, weaponType, favorite: "no", name: deferredSearchTerm }, filterConfigs))
          .sort(sortFunction(state.sortType, state.ascending, sortConfigs)))
    return dbDirty && { charKeyList, totalCharNum }
  },
    [dbDirty, database, sortConfigs, state.sortType, state.ascending, element, filterConfigs, weaponType, deferredSearchTerm])

  const { charKeyListToShow, numPages, currentPageIndex } = useMemo(() => {
    const numPages = Math.ceil(charKeyList.length / maxNumToDisplay)
    const currentPageIndex = clamp(pageIdex, 0, numPages - 1)
    return { charKeyListToShow: charKeyList.slice(currentPageIndex * maxNumToDisplay, (currentPageIndex + 1) * maxNumToDisplay), numPages, currentPageIndex }
  }, [charKeyList, pageIdex, maxNumToDisplay])

  const totalShowing = charKeyList.length !== totalCharNum ? `${charKeyList.length}/${totalCharNum}` : `${totalCharNum}`

  return <Box my={1} display="flex" flexDirection="column" gap={1}>
    <CardDark ref={invScrollRef} ><CardContent sx={{ display: "flex", flexDirection: "column", gap: 1 }}>
      <Grid container spacing={1}>
        <Grid item>
          <WeaponToggle sx={{ height: "100%" }} onChange={weaponType => stateDispatch({ weaponType })} value={state.weaponType} size="small" />
        </Grid>
        <Grid item>
          <ElementToggle sx={{ height: "100%" }} onChange={element => stateDispatch({ element })} value={state.element} size="small" />
        </Grid>
        <Grid item flexGrow={1}>
          <TextField
            autoFocus
            value={searchTerm}
            onChange={(e: ChangeEvent<HTMLTextAreaElement>) => setSearchTerm(e.target.value)}
            label={t("characterName")}
          />
        </Grid>
        <Grid item >
          <SortByButton sx={{ height: "100%" }}
            sortKeys={characterSortKeys} value={state.sortType} onChange={sortType => stateDispatch({ sortType })}
            ascending={state.ascending} onChangeAsc={ascending => stateDispatch({ ascending })} />
        </Grid>
      </Grid>
      <Grid container alignItems="flex-end">
        <Grid item flexGrow={1}>
          <Pagination count={numPages} page={currentPageIndex + 1} onChange={setPage} />
        </Grid>
        <Grid item>
          <ShowingCharacter numShowing={charKeyListToShow.length} total={totalShowing} t={t} />
        </Grid>
      </Grid>
    </CardContent></CardDark>
    <Suspense fallback={<Skeleton variant="rectangular" sx={{ width: "100%", height: "100%", minHeight: 5000 }} />}>
      <Grid container spacing={1} columns={columns}>
        <Grid item xs={1} >
          <CardDark sx={{ height: "100%", minHeight: 400, width: "100%", display: "flex", flexDirection: "column" }}>
            <CardContent>
              <Typography sx={{ textAlign: "center" }}><Trans t={t} i18nKey="addNew" /></Typography>
            </CardContent>
            <CharacterSelectionModal newFirst show={newCharacter} onHide={() => setnewCharacter(false)} onSelect={editCharacter} />
            <Box sx={{
              flexGrow: 1,
              display: "flex",
              justifyContent: "center",
              alignItems: "center"
            }}
            >
              <Button onClick={() => setnewCharacter(true)} color="info" sx={{ borderRadius: "1em" }}>
                <Typography variant="h1"><FontAwesomeIcon icon={faPlus} className="fa-fw" /></Typography>
              </Button>
            </Box>
          </CardDark>
        </Grid>
        {charKeyListToShow.map(charKey =>
          <Grid item key={charKey} xs={1} >
            <CharacterCard
              characterKey={charKey}
              onClick={() => navigate(`${charKey}`)}
              footer={<><Divider /><Box sx={{ py: 1, px: 2, display: "flex", gap: 1, justifyContent: "space-between" }}>
                <BootstrapTooltip placement="top" title={<Typography>{t("tabs.talent")}</Typography>}>
                  <IconButton onClick={() => navigate(`${charKey}/talent`)}>
                    <FactCheck />
                  </IconButton>
                </BootstrapTooltip>
                <BootstrapTooltip placement="top" title={<Typography>{t("tabs.equip")}</Typography>}>
                  <IconButton onClick={() => navigate(`${charKey}/equip`)} >
                    <Checkroom />
                  </IconButton>
                </BootstrapTooltip>
                <BootstrapTooltip placement="top" title={<Typography>{t("tabs.teambuffs")}</Typography>}>
                  <IconButton onClick={() => navigate(`${charKey}/teambuffs`)} >
                    <Groups />
                  </IconButton>
                </BootstrapTooltip>
                <BootstrapTooltip placement="top" title={<Typography>{t("tabs.optimize")}</Typography>}>
                  <IconButton onClick={() => navigate(`${charKey}/optimize`)} >
                    <Calculate />
                  </IconButton>
                </BootstrapTooltip>
                <Divider orientation="vertical" />
                <BootstrapTooltip placement="top" title={<Typography>{t("delete")}</Typography>}>
                  <IconButton color="error" onClick={() => deleteCharacter(charKey)}>
                    <DeleteForever />
                  </IconButton>
                </BootstrapTooltip>
              </Box></>}
            />
          </Grid>)}
      </Grid>
    </Suspense>
    {numPages > 1 && <CardDark ><CardContent>
      <Grid container alignItems="flex-end">
        <Grid item flexGrow={1}>
          <Pagination count={numPages} page={currentPageIndex + 1} onChange={setPage} />
        </Grid>
        <Grid item>
          <ShowingCharacter numShowing={charKeyListToShow.length} total={totalShowing} t={t} />
        </Grid>
      </Grid>
    </CardContent></CardDark>}
  </Box>
}
Example #20
Source File: index.tsx    From genshin-optimizer with MIT License 4 votes vote down vote up
export default function PageWeapon() {
  const { t } = useTranslation(["page_weapon", "ui"]);
  const { database } = useContext(DatabaseContext)
  const [state, stateDisplatch] = useDBState("WeaponDisplay", initialState)
  const [newWeaponModalShow, setnewWeaponModalShow] = useState(false)
  const [dbDirty, forceUpdate] = useForceUpdate()
  const invScrollRef = useRef<HTMLDivElement>(null)
  const [pageIdex, setpageIdex] = useState(0)
  //set follow, should run only once
  useEffect(() => {
    ReactGA.send({ hitType: "pageview", page: '/weapon' })
    return database.followAnyWeapon(forceUpdate)
  }, [forceUpdate, database])

  const brPt = useMediaQueryUp()
  const maxNumToDisplay = numToShowMap[brPt]

  const weaponSheets = usePromise(WeaponSheet.getAll, [])

  const deleteWeapon = useCallback(async (key) => {
    const weapon = database._getWeapon(key)
    if (!weapon) return
    const name = i18next.t(`weapon_${weapon.key}_gen:name`)

    if (!window.confirm(`Are you sure you want to remove ${name}?`)) return
    database.removeWeapon(key)
    if (state.editWeaponId === key)
      stateDisplatch({ editWeaponId: "" })
  }, [state.editWeaponId, stateDisplatch, database])

  const editWeapon = useCallback(key => {
    stateDisplatch({ editWeaponId: key })
  }, [stateDisplatch])

  const newWeapon = useCallback(
    (weaponKey: WeaponKey) => {
      editWeapon(database.createWeapon(initialWeapon(weaponKey)))
    },
    [database, editWeapon])

  const { sortType, ascending, weaponType, rarity } = state
  const sortConfigs = useMemo(() => weaponSheets && weaponSortConfigs(weaponSheets), [weaponSheets])
  const filterConfigs = useMemo(() => weaponSheets && weaponFilterConfigs(weaponSheets), [weaponSheets])
  const { weaponIdList, totalWeaponNum } = useMemo(() => {
    const weapons = database._getWeapons()
    const totalWeaponNum = weapons.length
    if (!sortConfigs || !filterConfigs) return { weaponIdList: [], totalWeaponNum }
    const weaponIdList = weapons.filter(filterFunction({ weaponType, rarity }, filterConfigs))
      .sort(sortFunction(sortType, ascending, sortConfigs)).map(weapon => weapon.id);
    return dbDirty && { weaponIdList, totalWeaponNum }
  }, [dbDirty, database, sortConfigs, filterConfigs, sortType, ascending, rarity, weaponType])

  const { weaponIdsToShow, numPages, currentPageIndex } = useMemo(() => {
    const numPages = Math.ceil(weaponIdList.length / maxNumToDisplay)
    const currentPageIndex = clamp(pageIdex, 0, numPages - 1)
    return { weaponIdsToShow: weaponIdList.slice(currentPageIndex * maxNumToDisplay, (currentPageIndex + 1) * maxNumToDisplay), numPages, currentPageIndex }
  }, [weaponIdList, pageIdex, maxNumToDisplay])

  //for pagination
  const totalShowing = weaponIdList.length !== totalWeaponNum ? `${weaponIdList.length}/${totalWeaponNum}` : `${totalWeaponNum}`
  const setPage = useCallback(
    (e, value) => {
      invScrollRef.current?.scrollIntoView({ behavior: "smooth" })
      setpageIdex(value - 1);
    },
    [setpageIdex, invScrollRef],
  )

  const resetEditWeapon = useCallback(() => stateDisplatch({ editWeaponId: "" }), [stateDisplatch])

  const { editWeaponId } = state

  // Validate weaponId to be an actual weapon
  useEffect(() => {
    if (!editWeaponId) return
    if (!database._getWeapon(editWeaponId))
      resetEditWeapon()
  }, [database, editWeaponId, resetEditWeapon])

  return <Box my={1} display="flex" flexDirection="column" gap={1}>
    {/* editor/character detail display */}
    <Suspense fallback={false}>
      <WeaponEditor
        weaponId={editWeaponId}
        footer
        onClose={resetEditWeapon}
      />
    </Suspense>

    <CardDark ref={invScrollRef} sx={{ p: 2, pb: 1 }}>
      <Grid container spacing={1} sx={{ mb: 1 }}>
        <Grid item>
          <WeaponToggle sx={{ height: "100%" }} onChange={weaponType => stateDisplatch({ weaponType })} value={weaponType} size="small" />
        </Grid>
        <Grid item flexGrow={1}>
          <SolidToggleButtonGroup sx={{ height: "100%" }} onChange={(e, newVal) => stateDisplatch({ rarity: newVal })} value={rarity} size="small">
            {allRarities.map(star => <ToggleButton key={star} value={star}><Box display="flex" gap={1}><strong>{star}</strong><Stars stars={1} /></Box></ToggleButton>)}
          </SolidToggleButtonGroup>
        </Grid>
        <Grid item >
          <SortByButton sx={{ height: "100%" }} sortKeys={weaponSortKeys}
            value={sortType} onChange={sortType => stateDisplatch({ sortType })}
            ascending={ascending} onChangeAsc={ascending => stateDisplatch({ ascending })}
          />
        </Grid>
      </Grid>
      <Grid container alignItems="flex-end">
        <Grid item flexGrow={1}>
          <Pagination count={numPages} page={currentPageIndex + 1} onChange={setPage} />
        </Grid>
        <Grid item>
          <ShowingWeapon numShowing={weaponIdsToShow.length} total={totalShowing} t={t} />
        </Grid>
      </Grid>
    </CardDark>
    <Suspense fallback={<Skeleton variant="rectangular" sx={{ width: "100%", height: "100%", minHeight: 500 }} />}>
      <Grid container spacing={1} columns={columns}>
        <Grid item xs={1}>
          <CardDark sx={{ height: "100%", width: "100%", minHeight: 300, display: "flex", flexDirection: "column" }}>
            <CardContent>
              <Typography sx={{ textAlign: "center" }}>Add New Weapon</Typography>
            </CardContent>
            <WeaponSelectionModal show={newWeaponModalShow} onHide={() => setnewWeaponModalShow(false)} onSelect={newWeapon} />
            <Box sx={{
              flexGrow: 1,
              display: "flex",
              justifyContent: "center",
              alignItems: "center"
            }}
            >
              <Button onClick={() => setnewWeaponModalShow(true)} color="info" sx={{ borderRadius: "1em" }}>
                <Typography variant="h1"><FontAwesomeIcon icon={faPlus} className="fa-fw" /></Typography>
              </Button>
            </Box>
          </CardDark>
        </Grid>
        {weaponIdsToShow.map(weaponId =>
          <Grid item key={weaponId} xs={1} >
            <WeaponCard
              weaponId={weaponId}
              onDelete={deleteWeapon}
              onEdit={editWeapon}
              canEquip
            />
          </Grid>)}
      </Grid>
    </Suspense>
    {numPages > 1 && <CardDark><CardContent>
      <Grid container alignItems="flex-end">
        <Grid item flexGrow={1}>
          <Pagination count={numPages} page={currentPageIndex + 1} onChange={setPage} />
        </Grid>
        <Grid item>
          <ShowingWeapon numShowing={weaponIdsToShow.length} total={totalShowing} t={t} />
        </Grid>
      </Grid>
    </CardContent></CardDark>}
  </Box>
}
Example #21
Source File: BoardBar.tsx    From knboard with MIT License 4 votes vote down vote up
BoardBar = () => {
  const dispatch = useDispatch();
  const members = useSelector(selectAllMembers);
  const error = useSelector((state: RootState) => state.board.detailError);
  const detail = useSelector((state: RootState) => state.board.detail);
  const boardOwner = useSelector(currentBoardOwner);
  const { id } = useParams();
  const detailDataExists = detail?.id.toString() === id;

  if (!detailDataExists || error || !detail) {
    return null;
  }

  const handleAddColumn = () => {
    dispatch(addColumn(detail.id));
  };

  const handleEditLabels = () => {
    dispatch(setDialogOpen(true));
  };

  return (
    <Container data-testid="board">
      <Items>
        <Left>
          <BoardName
            id={detail.id}
            name={detail.name}
            isOwner={boardOwner}
            data-testid="board-name"
          />
          <AvatarGroup
            max={3}
            data-testid="member-group"
            css={css`
              margin-left: 1.5rem;
              & .MuiAvatarGroup-avatar {
                ${avatarStyles}
                border: none;
              }
              &:hover {
                cursor: pointer;
              }
            `}
            onClick={(e: any) => {
              if (e.target.classList.contains("MuiAvatarGroup-avatar")) {
                dispatch(setMemberListOpen(true));
              }
            }}
          >
            {members.map((member) => (
              <MemberDetail
                key={member.id}
                member={member}
                isOwner={detail.owner === member.id}
              />
            ))}
          </AvatarGroup>
          {boardOwner && <MemberInvite boardId={detail.id} />}
          <MemberFilter boardId={detail.id} />
        </Left>
        <Right>
          <Button
            size="small"
            css={css`
              ${buttonStyles}
              margin-right: 0.5rem;
              font-weight: 600;
            `}
            onClick={handleEditLabels}
            startIcon={<FontAwesomeIcon icon={faPen} />}
            data-testid="open-labels-dialog"
          >
            Edit labels
          </Button>
          <Button
            size="small"
            css={css`
              ${buttonStyles}
              font-weight: 600;
            `}
            onClick={handleAddColumn}
            startIcon={<FontAwesomeIcon icon={faPlus} />}
            data-testid="add-col"
          >
            Add Column
          </Button>
        </Right>
      </Items>
      <MemberDialog board={detail} />
      <MemberListDialog />
      <EditTaskDialog />
      <CreateTaskDialog />
      <LabelDialog />
    </Container>
  );
}
Example #22
Source File: UserActivity.tsx    From frontend.ro with MIT License 4 votes vote down vote up
function UserActivity({ profileUser, currentUser }: ConnectedProps<typeof connector> & Props) {
  const isOwnProfile = currentUser.info && currentUser.info.username === profileUser.username;
  const [didError, setDidError] = useState(false);
  const [solvedExercises, setSolvedExercises] = useState<Submission[]>(undefined);
  const [tutorialsProgress, setTutorialsProgress] = useState<TutorialProgressI[]>(undefined);

  const fetchTutorialsProgress = async () => {
    try {
      const tutorials = await TutorialService.getAll();
      const progressResponses = await Promise.all(tutorials.map((tutorial) => {
        return TutorialService.getProgress(tutorial.tutorialId);
      }));

      setTutorialsProgress(progressResponses);
    } catch (err) {
      console.error('UserActivity.fetchTutorialsProgress', err);
      setDidError(true);
    }
  };

  useEffect(() => {
    // Solved exercises
    if (isOwnProfile) {
      ExerciseService
        .getSolvedExercises()
        .then((resp) => setSolvedExercises(resp))
        .catch((err) => console.error(err));

      fetchTutorialsProgress();
    } else {
      setSolvedExercises([]);
      setTutorialsProgress([]);
    }
  }, []);

  if (didError) {
    return (
      <p className="text-red text-center">
        Oops! Nu am putut încărca profilul.
        <br />
        Încearcă din nou.
      </p>
    );
  }

  if (!solvedExercises || !tutorialsProgress) {
    // Loading
    return null;
  }

  if (!isOwnProfile) {
    return <NoActivity user={profileUser} />;
  }

  return (
    <PageContainer>
      <section className="mb-12">
        <h2> Tutoriale </h2>
        {tutorialsProgress
          .map(aggregateTutorialProgress)
          .map((aggregatedProgress, index) => (
            <div
              key={tutorialsProgress[index].name}
              className={`${styles['progress-wrapper']} p-3`}
            >
              <TutorialProgress
                title={tutorialsProgress[index].name}
                tutorialProgress={tutorialsProgress[index]}
              />

              {aggregatedProgress.done < aggregatedProgress.total && (
                <Link href={`/${tutorialsProgress[index].tutorialId}/tutorial`}>
                  <a className="btn btn--light no-underline mt-4">
                    {(
                      aggregatedProgress.done === 0
                      && aggregatedProgress.inProgress === 0
                    )
                      ? 'Începe tutorialul'
                      : 'Continuă'}
                  </a>
                </Link>
              )}

              {/* TODO: https://github.com/FrontEnd-ro/frontend.ro/issues/512 */}
              {/* {aggregatedProgress.done === aggregatedProgress.total && (
                <Link href="#">
                  <a className="btn btn--light no-underline mt-4">
                    Vezi certificarea
                  </a>
                </Link>
              )} */}
            </div>
          ))}
      </section>
      <h2>
        Exerciții rezolvate
      </h2>
      <div className={styles['exercises-wrapper']}>
        {solvedExercises.map((submission: Submission) => (
          <ExercisePreview
            key={submission._id}
            exercise={submission.exercise}
            href={`rezolva/${submission.exercise._id}`}
            viewMode="STUDENT"
            feedbackCount={submission.feedbacks.filter((f) => f.type === 'improvement').length}
            isApproved={submission.status === SubmissionStatus.DONE}
            readOnly={[
              SubmissionStatus.AWAITING_REVIEW,
              SubmissionStatus.DONE,
            ].includes(submission.status)}
          />
        ))}
        {solvedExercises.length === 0 && (
          <Link href="/exercitii">
            <a className="d-flex align-items-center justify-content-center no-underline text-center">
              <FontAwesomeIcon icon={faPlus} width="32" height="32" />
              <span> Rezolvă un exercițiu </span>
            </a>
          </Link>
        )}
      </div>
      <hr />
      {isOwnProfile && profileUser.role.includes(UserRole.ADMIN) && (
        <CreatedExercises />
      )}
    </PageContainer>
  );
}
Example #23
Source File: AddMedia.tsx    From sync-party with GNU General Public License v3.0 4 votes vote down vote up
export default function AddMedia({
    isActive,
    partyItemsSet,
    setAddMediaIsActive,
    socket,
    setPlayerFocused,
    handleItemEditSave
}: Props): JSX.Element {
    const { t } = useTranslation();

    const user = useSelector((state: RootAppState) => state.globalState.user);
    const party = useSelector((state: RootAppState) => state.globalState.party);
    const userItems = useSelector(
        (state: RootAppState) => state.globalState.userItems
    );

    const mediaItemDefault: NewMediaItem = {
        name: '',
        type: 'file',
        owner: user ? user.id : null,
        url: ''
    };

    const [activeTab, setActiveTab] = useState<'user' | 'web' | 'file'>('file');
    const [file, setFile] = useState<File | null>(null);
    const [mediaItem, setMediaItem] = useState(mediaItemDefault);
    const [uploadStartTime, setUploadStartTime] = useState(0);
    const [isUploading, setIsUploading] = useState(false);
    const [progress, setProgress] = useState(0);
    const [addedSuccessfully, setAddedSuccessfully] = useState(false);
    const [lastCreatedItem, setLastCreatedItem] = useState<NewMediaItem>();
    const [uploadError, setUploadError] = useState(false);
    const [fetchingLinkMetadata, setFetchingLinkMetadata] = useState(false);
    const [linkMetadata, setLinkMetadata] = useState<{
        videoTitle: string;
        channelTitle: string;
    } | null>(null);

    const dispatch = useDispatch();

    // Preselect user tab if there are items to add
    useEffect(() => {
        if (userItems && party)
            if (
                userItems.filter(
                    (userItem: MediaItem) => !partyItemsSet.has(userItem.id)
                ).length
            ) {
                setActiveTab('user');
            }
    }, [userItems, party, partyItemsSet]);

    const addUserItem = async (item: MediaItem): Promise<void> => {
        if (party) {
            try {
                const response = await Axios.post(
                    '/api/partyItems',
                    { mediaItem: item, partyId: party.id },
                    axiosConfig()
                );

                if (response.data.success === true) {
                    updatePartyAndUserParties();
                } else {
                    dispatch(
                        setGlobalState({
                            errorMessage: t(
                                `apiResponseMessages.${response.data.msg}`
                            )
                        })
                    );
                }
            } catch (error) {
                dispatch(
                    setGlobalState({
                        errorMessage: t(`errors.addToPartyError`)
                    })
                );
            }
        }
    };

    const addWebItem = async (event: React.MouseEvent): Promise<void> => {
        event.preventDefault();

        if (party) {
            try {
                const response = await Axios.post(
                    '/api/mediaItem',
                    { mediaItem: mediaItem, partyId: party.id },
                    axiosConfig()
                );

                if (response.data.success === true) {
                    updatePartyAndUserParties();
                    getUpdatedUserItems(dispatch, t);
                    resetUploadForm();
                    setIsUploading(false);
                    setLastCreatedItem(mediaItem);
                    setAddedSuccessfully(true);
                    hideFinishInAFewSecs();
                    toggleCollapseAddMediaMenu();
                } else {
                    dispatch(
                        setGlobalState({
                            errorMessage: t(
                                `apiResponseMessages.${response.data.msg}`
                            )
                        })
                    );
                }
            } catch (error) {
                dispatch(
                    setGlobalState({
                        errorMessage: t(`errors.addItemError`)
                    })
                );
            }
        }
    };

    const addFileItem = async (event: React.MouseEvent): Promise<void> => {
        event.preventDefault();

        if (party && file && mediaItem.owner) {
            const formData = new FormData();
            formData.append('owner', mediaItem.owner);
            formData.append('name', mediaItem.name);
            formData.append('file', file);
            formData.append('partyId', party.id);
            setIsUploading(true);
            setAddedSuccessfully(false);
            setUploadStartTime(Date.now());
            try {
                const response = await Axios.post('/api/file', formData, {
                    headers: {
                        'Content-Type': 'multipart/form-data'
                    },
                    onUploadProgress: (progressEvent) => {
                        const percentCompleted = Math.round(
                            (progressEvent.loaded * 100) / progressEvent.total
                        );
                        setProgress(percentCompleted);
                    },
                    withCredentials: true
                });

                if (response.data.success === true) {
                    updatePartyAndUserParties();
                    getUpdatedUserItems(dispatch, t);
                    resetUploadForm();
                    setLastCreatedItem(mediaItem);
                    setIsUploading(false);
                    setAddedSuccessfully(true);
                    hideFinishInAFewSecs();
                    toggleCollapseAddMediaMenu();
                } else {
                    dispatch(
                        setGlobalState({
                            errorMessage: t(
                                `apiResponseMessages.${response.data.msg}`
                            )
                        })
                    );
                }
            } catch (error) {
                dispatch(
                    setGlobalState({
                        errorMessage: t(`errors.uploadError`)
                    })
                );

                resetUploadForm();
                setIsUploading(false);
                setUploadError(true);
            }
        }
    };

    const updatePartyAndUserParties = async (): Promise<void> => {
        if (socket && party && userItems) {
            // Update userParties
            const updatedUserParties = await getUpdatedUserParties(dispatch, t);

            const updatedParty = updatedUserParties.find(
                (userParty: ClientParty) => userParty.id === party.id
            );

            // Preselect file tab if there are no items left to add
            if (
                updatedParty &&
                !userItems.filter(
                    (userItem: MediaItem) =>
                        !updatedParty.items.find(
                            (item: MediaItem) => item.id === userItem.id
                        )
                ).length
            ) {
                setActiveTab('file');
            }

            // Update current party
            dispatch(
                setGlobalState({
                    party: updatedParty
                })
            );

            // Ask other users to update their userParties
            socket.emit('partyUpdate', { partyId: party.id });
        }
    };

    const handleLinkInput = async (
        event: React.ChangeEvent<HTMLInputElement>
    ): Promise<void> => {
        let url = event.target.value;

        // YT: Remove list-related URL params
        if (
            url.indexOf('https://www.youtube.com') === 0 &&
            url.indexOf('&list=') > -1
        ) {
            url = url.slice(0, url.indexOf('&list='));
        }

        const webMediaItem: NewMediaItem = {
            ...mediaItem,
            url: url,
            type: 'web'
        };

        setMediaItem(webMediaItem);

        if (url.indexOf('https://www.youtube.com') === 0) {
            setFetchingLinkMetadata(true);

            try {
                const response = await Axios.post(
                    '/api/linkMetadata',
                    { url: url },
                    { ...axiosConfig(), timeout: 3000 }
                );

                setLinkMetadata({
                    videoTitle: response.data.videoTitle,
                    channelTitle: response.data.channelTitle
                });

                setMediaItem({
                    ...webMediaItem,
                    name: response.data.videoTitle
                });

                setFetchingLinkMetadata(false);
            } catch (error) {
                setMediaItem({ ...webMediaItem, name: '' });
                setFetchingLinkMetadata(false);
            }
        }
    };

    const toggleCollapseAddMediaMenu = (): void => {
        if (isActive) {
            setActiveTab('file');
        }
        setAddMediaIsActive(!isActive);
        setUploadError(false);
        resetUploadForm();
    };

    const changeTab = (tab: 'user' | 'web' | 'file'): void => {
        setActiveTab(tab);
        setFile(null);
        setMediaItem(mediaItemDefault);
        setUploadError(false);
    };

    const resetUploadForm = (): void => {
        setFile(null);
        setMediaItem(mediaItemDefault);
    };

    const hideFinishInAFewSecs = (): void => {
        setTimeout(() => {
            setAddedSuccessfully(false);
        }, 5000);
    };

    return (
        <div
            className={'mt-2' + (!isActive ? '' : ' flex flex-col flex-shrink')}
        >
            {isActive && (
                <>
                    <AddMediaTabBar
                        activeTab={activeTab}
                        changeTab={changeTab}
                        isUploading={isUploading}
                        toggleCollapseAddMediaMenu={toggleCollapseAddMediaMenu}
                    ></AddMediaTabBar>
                    <div className="flex flex-col">
                        {!isUploading && !uploadError && userItems && party ? (
                            <>
                                {activeTab === 'user' && (
                                    <AddMediaTabUser
                                        partyItemsSet={partyItemsSet}
                                        addUserItem={addUserItem}
                                        setPlayerFocused={(
                                            focused: boolean
                                        ): void => setPlayerFocused(focused)}
                                        handleItemEditSave={handleItemEditSave}
                                    ></AddMediaTabUser>
                                )}
                                {activeTab === 'web' && (
                                    <AddMediaTabWeb
                                        mediaItem={mediaItem}
                                        setMediaItem={(
                                            mediaItem: NewMediaItem
                                        ): void => setMediaItem(mediaItem)}
                                        addWebItem={addWebItem}
                                        handleLinkInput={handleLinkInput}
                                        setPlayerFocused={(
                                            focused: boolean
                                        ): void => setPlayerFocused(focused)}
                                        linkMetadata={linkMetadata}
                                        fetchingLinkMetadata={
                                            fetchingLinkMetadata
                                        }
                                    ></AddMediaTabWeb>
                                )}
                                {activeTab === 'file' && (
                                    <AddMediaTabFile
                                        file={file}
                                        setFile={(file: File): void =>
                                            setFile(file)
                                        }
                                        mediaItem={mediaItem}
                                        setMediaItem={(
                                            mediaItem: NewMediaItem
                                        ): void => setMediaItem(mediaItem)}
                                        addFileItem={addFileItem}
                                        resetUploadForm={resetUploadForm}
                                        setPlayerFocused={(
                                            focused: boolean
                                        ): void => setPlayerFocused(focused)}
                                    ></AddMediaTabFile>
                                )}
                            </>
                        ) : !uploadError ? (
                            <AddMediaUploadProgress
                                progress={progress}
                                uploadStartTime={uploadStartTime}
                            ></AddMediaUploadProgress>
                        ) : (
                            <div className="my-3">
                                {t('mediaMenu.uploadError')}
                            </div>
                        )}
                    </div>
                </>
            )}

            {!isActive && (
                <>
                    <Button
                        padding="p-1"
                        title={t('mediaMenu.addMediaTitle')}
                        text={
                            <>
                                <FontAwesomeIcon
                                    icon={faPlus}
                                ></FontAwesomeIcon>
                                <span>{' ' + t('mediaMenu.addMedia')}</span>
                            </>
                        }
                        onClick={toggleCollapseAddMediaMenu}
                    ></Button>
                    {addedSuccessfully && lastCreatedItem && (
                        <div className="my-3 breakLongWords">
                            <FontAwesomeIcon
                                className="text-purple-400"
                                icon={faThumbsUp}
                            ></FontAwesomeIcon>{' '}
                            {lastCreatedItem.type === 'file'
                                ? t('mediaMenu.uploadFinished')
                                : t('mediaMenu.addingFinished')}
                            {lastCreatedItem.name}
                        </div>
                    )}
                </>
            )}
        </div>
    );
}