@mui/icons-material#Replay TypeScript Examples

The following examples show how to use @mui/icons-material#Replay. 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: ArtifactSlotDropdown.tsx    From genshin-optimizer with MIT License 6 votes vote down vote up
export default function ArtifactSlotDropdown({ slotKey = "", onChange, hasUnselect = false, ...props }: ArtifactSlotDropdownProps) {
  const { t } = useTranslation(["artifact", "ui"]);
  return <DropdownButton
    title={slotKey ? t(`artifact:slotName:${slotKey}`) : t('artifact:slot')}
    color={slotKey ? "success" : "primary"}
    startIcon={slotKey ? artifactSlotIcon(slotKey) : undefined}
    {...props}
  >
    {hasUnselect && <MenuItem selected={slotKey === ""} disabled={slotKey === ""} onClick={() => onChange("")} >
      <ListItemIcon>
        <Replay />
      </ListItemIcon>
      <ListItemText>
        {t`ui:unselect`}
      </ListItemText>
    </MenuItem>}
    {hasUnselect && <Divider />}
    {allSlotKeys.map(key =>
      <MenuItem key={key} selected={slotKey === key} disabled={slotKey === key} onClick={() => onChange(key)} >
        <ListItemIcon>
          {artifactSlotIcon(key)}
        </ListItemIcon>
        <ListItemText>
          {t(`artifact:slotName:${key}`)}
        </ListItemText>
      </MenuItem>)}
  </DropdownButton>
}
Example #2
Source File: StatInput.tsx    From genshin-optimizer with MIT License 6 votes vote down vote up
export default function StatInput({ name, children, value, placeholder, defaultValue = 0, onValueChange, percent = false, disabled = false, onReset, ...restProps }: StatInputInput) {

  return <FlexButtonGroup {...restProps} >
    {children}
    <TextButton sx={{ whiteSpace: "nowrap" }} >
      {name}
    </TextButton>
    <CustomNumberInputButtonGroupWrapper sx={{ flexBasis: 30, flexGrow: 1 }} >
      <CustomNumberInput
        sx={{ px: 1 }}
        inputProps={{
          sx: { textAlign: "right" }
        }}
        float={percent}
        placeholder={placeholder}
        value={value}
        onChange={onValueChange}
        disabled={disabled}
        endAdornment={percent ? "%" : undefined}
      />
    </CustomNumberInputButtonGroupWrapper>
    <Button onClick={() => onReset ? onReset() : onValueChange(defaultValue)} disabled={disabled || value === defaultValue} >
      <Replay />
    </Button>
  </FlexButtonGroup>
}
Example #3
Source File: ArtifactFilter.tsx    From genshin-optimizer with MIT License 6 votes vote down vote up
export default function ArtifactFilter({ filterOption, filterOptionDispatch, filterDispatch, numShowing, total, }:
  { filterOption: FilterOption, filterOptionDispatch: (any) => void, filterDispatch: (any) => void, numShowing: number, total: number }) {
  const { t } = useTranslation(["artifact", "ui"])

  return <Suspense fallback={<Skeleton variant="rectangular" width="100%" height={300} />}>
    <CardDark  >
      <CardContent>
        <Grid container>
          <Grid item >
            <Typography variant="h6"><Trans t={t} i18nKey="artifactFilter">Artifact Filter</Trans></Typography>
          </Grid>
          <Grid item flexGrow={1} display="flex" justifyContent="center" alignItems="center">
            {numShowing !== total && <Typography>Filtered {numShowing} / {total}</Typography>}
          </Grid>
          <Grid item>
            <Button size="small" color="error" onClick={() => filterDispatch({ type: "reset" })} startIcon={<Replay />}>
              <Trans t={t} i18nKey="ui:reset" />
            </Button>
          </Grid>
        </Grid>
        <Suspense fallback={<Skeleton variant="rectangular" width="100%" height={200} />}>
          <ArtifactFilterDisplay filterOption={filterOption} filterOptionDispatch={filterOptionDispatch} />
        </Suspense>
      </CardContent>
    </CardDark>
  </Suspense>
}
Example #4
Source File: ArtifactSetConditional.tsx    From genshin-optimizer with MIT License 5 votes vote down vote up
function ArtConditionalModal({ open, onClose, artifactCondCount }: {
  open: boolean, onClose: () => void, artifactCondCount: number
}) {
  const dataContext = useContext(DataContext)
  const { character, characterDispatch } = dataContext
  const artifactSheets = usePromise(ArtifactSheet.getAll, [])
  const resetArtConds = useCallback(() => {
    const conditional = Object.fromEntries(Object.entries(character.conditional).filter(([k, v]) => !allArtifactSets.includes(k as any)))
    characterDispatch({ conditional })
  }, [character, characterDispatch]);

  if (!artifactSheets) return null
  const artSetKeyList = Object.entries(ArtifactSheet.setKeysByRarities(artifactSheets)).reverse().flatMap(([, sets]) => sets)
  return <ModalWrapper open={open} onClose={onClose} ><CardDark>
    <CardContent>
      <Grid container spacing={1}>
        <Grid item flexGrow={1}>
          <Typography variant="h6">Default Artifact Set Effects {!!artifactCondCount && <SqBadge color="success">{artifactCondCount} Selected</SqBadge>}</Typography>
        </Grid>
        <Grid item>
          <Button onClick={resetArtConds} color="error" startIcon={<Replay />}>Reset All</Button>
        </Grid>
        <Grid item>
          <CloseButton onClick={onClose} />
        </Grid>
      </Grid>
    </CardContent>
    <Divider />
    <CardContent>
      <CardLight sx={{ mb: 1 }}>
        <CardContent>
          <Typography>Some artifacts provide conditional stats. This windows allows you to select those stats, so they can take effect during build calculation, when artifact sets are not specified.</Typography>
        </CardContent>
      </CardLight>
      <Grid container spacing={1}>
        {artSetKeyList.map(setKey => {
          const sheet: ArtifactSheet = artifactSheets[setKey]
          // Don't display if no conditional in artifact
          if (!Object.values(sheet.setEffects).some(entry => entry.document && entry.document.some(d => "states" in d))) return null
          return <Grid item key={setKey} xs={6} lg={4}>
            <CardLight sx={{ height: "100%" }}>
              <Box className={`grad-${sheet.rarity[0]}star`} width="100%" sx={{ display: "flex" }} >
                <Box component="img" src={sheet.defIconSrc} sx={{ height: 100, width: "auto" }} />
                <Box sx={{ flexGrow: 1, px: 1, display: "flex", flexDirection: "column", justifyContent: "center" }}>
                  <Typography variant="h6">{sheet.name ?? ""}</Typography>
                  <Box display="flex" gap={1}>
                    <Typography variant="subtitle1">{sheet.rarity.map((ns, i) => <span key={ns}>{ns} <Stars stars={1} /> {i < (sheet.rarity.length - 1) ? "/ " : null}</span>)}</Typography>
                    {/* If there is ever a 2-set conditional, we will need to change this */}
                    <InfoTooltip title={<Typography><Translate ns={`artifact_${setKey}_gen`} key18={"setEffects.4"} /></Typography>} />
                  </Box>
                </Box>
              </Box>
              <DataContext.Provider value={fakeData(dataContext) /* TODO: Do we need to Memo this? */}>
                <CardContent sx={{ display: "flex", flexDirection: "column", gap: 2 }}>
                  {Object.keys(sheet.setEffects)
                    .filter(setNumKey => sheet.setEffects[setNumKey]?.document
                      .some(doc => "states" in doc)
                    )
                    .map(setNumKey =>
                      <SetEffectDisplay key={setNumKey} setKey={setKey} setNumKey={parseInt(setNumKey) as SetNum} hideHeader conditionalsOnly />
                    )
                  }
                </CardContent>
              </DataContext.Provider>
            </CardLight>
          </Grid>
        })}
      </Grid>
    </CardContent>
    <Divider />
    <CardContent sx={{ py: 1 }}>
      <CloseButton large onClick={onClose} />
    </CardContent>
  </CardDark></ModalWrapper>
}
Example #5
Source File: MainStatSelectionCard.tsx    From genshin-optimizer with MIT License 5 votes vote down vote up
export default function MainStatSelectionCard({ mainStatKeys, onChangeMainStatKey, disabled = false, }: {
  mainStatKeys: BuildSetting["mainStatKeys"]
  onChangeMainStatKey: (slotKey: SlotKey, mainStatKey?: MainStatKey) => void
  disabled?: boolean
}) {
  const { t } = useTranslation("artifact")
  return <Box display="flex" flexDirection="column" gap={1}>
    {artifactsSlotsToSelectMainStats.map(slotKey => {
      const numSel = mainStatKeys[slotKey].length
      return <Box key={slotKey}>
        <Divider />
        <CardContent sx={{ pt: 1 }}>
          <Box sx={{ display: "flex", gap: 1, alignItems: "center", pb: 1 }}>
            <BootstrapTooltip placement="top" title={<Typography>{t(`slotName.${slotKey}`)}</Typography>}>
              <span>{artifactSlotIcon(slotKey)}</span>
            </BootstrapTooltip>
            <Box flexGrow={1}>
              <SqBadge color="info">{numSel ? `${numSel} Selected` : `Any`}</SqBadge>
            </Box>
            <Button color="error" size="small" disabled={!mainStatKeys[slotKey].length || disabled} sx={{ mt: -1, mb: -1 }}
              onClick={() => onChangeMainStatKey(slotKey)}>
              <Replay />
            </Button>
          </Box>
          <Grid container spacing={1}>
            {Artifact.slotMainStats(slotKey).map((mainStatKey, i) => {
              const element = allElementsWithPhy.find(ele => mainStatKey.includes(ele))
              const color = mainStatKeys[slotKey].includes(mainStatKey)
                ? element ?? "success"
                : "secondary"
              return <Grid item key={mainStatKey} flexGrow={1} xs={i < 3 ? 4 : undefined} >
                <BootstrapTooltip placement="top" title={<Typography><strong>{KeyMap.getArtStr(mainStatKey)}</strong></Typography>} disableInteractive>
                  <Button fullWidth size="small" color={color} sx={{ fontSize: "1.2em", height: "100%", pointerEvents: disabled ? "none" : undefined, cursor: disabled ? "none" : undefined }}
                    onClick={() => onChangeMainStatKey(slotKey, mainStatKey)}>
                    {element ? uncoloredEleIcons[element] : StatIcon[mainStatKey]}
                  </Button>
                </BootstrapTooltip>
              </Grid>
            })}
          </Grid>
        </CardContent>
      </Box>
    })}
  </Box >
}
Example #6
Source File: UseEquipped.tsx    From genshin-optimizer with MIT License 5 votes vote down vote up
export default function UseEquipped({ useEquippedArts, buildSettingsDispatch, disabled }) {
  const { t } = useTranslation("page_character")
  const { character: { key: characterKey } } = useContext(DataContext)
  const { database } = useContext(DatabaseContext)
  const [show, onOpen, onClose] = useBoolState(false)
  const [{ equipmentPriority: tempEquipmentPriority }, setOptimizeDBState] = useOptimizeDBState()
  //Basic validate for the equipmentPrio list to remove dups and characters that doesnt exist.
  const equipmentPriority = useMemo(() => [...new Set(tempEquipmentPriority)].filter(ck => database._getChar(ck)), [database, tempEquipmentPriority])
  const setPrio = useCallback((equipmentPriority: CharacterKey[]) => setOptimizeDBState({ equipmentPriority }), [setOptimizeDBState])

  const setPrioRank = useCallback((fromIndex, toIndex) => {
    const arr = [...equipmentPriority]
    var element = arr[fromIndex];
    arr.splice(fromIndex, 1);
    arr.splice(toIndex, 0, element);
    setPrio(arr)
  }, [equipmentPriority, setPrio])
  const removePrio = useCallback((fromIndex) => {
    const arr = [...equipmentPriority]
    arr.splice(fromIndex, 1)
    setPrio(arr)
  }, [equipmentPriority, setPrio])
  const addPrio = useCallback((ck: CharacterKey) => setPrio([...equipmentPriority, ck]), [equipmentPriority, setPrio])
  const resetPrio = useCallback(() => setPrio([]), [setPrio])

  const numAbove = useMemo(() => {
    let numAbove = equipmentPriority.length
    const index = equipmentPriority.indexOf(characterKey)
    if (index >= 0) numAbove = index
    return numAbove
  }, [characterKey, equipmentPriority])
  const numUseEquippedChar = useMemo(() => {
    return database._getCharKeys().length - 1 - numAbove
  }, [numAbove, database])
  const numUnlisted = useMemo(() => {
    return database._getCharKeys().length - equipmentPriority.length
  }, [equipmentPriority, database])

  return <Box display="flex" gap={1}>
    <ModalWrapper open={show} onClose={onClose} containerProps={{ maxWidth: "sm" }}><CardDark>
      <CardContent>
        <Grid container spacing={1}>
          <Grid item flexGrow={1}>
            <Typography variant="h6"><Trans t={t} i18nKey="tabOptimize.useEquipped.modal.title">Character Priority for Equipped Artifacts</Trans></Typography>
          </Grid>
          <Grid item sx={{ mb: -1 }}>
            <CloseButton onClick={onClose} />
          </Grid>
        </Grid>
      </CardContent>
      <Divider />
      <CardContent>
        <CardLight sx={{ mb: 1 }}>
          <CardContent>
            <Typography gutterBottom><Trans t={t} i18nKey="tabOptimize.useEquipped.modal.desc1">When generating a build, the Optimizer will only consider equipped artifacts from characters below the current character or those not on the list.</Trans></Typography>
            <Typography gutterBottom><Trans t={t} i18nKey="tabOptimize.useEquipped.modal.desc2">If the current character is not on the list, the Optimizer will only consider equipped artifacts from others characters that are not on the list.</Trans></Typography>
          </CardContent>
        </CardLight>
        <Box display="flex" flexDirection="column" gap={2}>
          {equipmentPriority.map((ck, i) =>
            <SelectItem key={ck} characterKey={ck} rank={i + 1} maxRank={equipmentPriority.length} setRank={(num) => num && setPrioRank(i, num - 1)} onRemove={() => removePrio(i)} numAbove={numAbove} />)}
          <Box sx={{ display: "flex", gap: 1 }}>
            <NewItem onAdd={addPrio} list={equipmentPriority} />
            <Button color="error" onClick={resetPrio} startIcon={<Replay />}><Trans t={t} i18nKey="tabOptimize.useEquipped.modal.clearList">Clear List</Trans></Button>
          </Box>
          {!!numUseEquippedChar && <SqBadge color="success"><Typography><Trans t={t} i18nKey="tabOptimize.useEquipped.modal.usingNum" count={numUnlisted}>Using artifacts from <strong>{{ count: numUnlisted }}</strong> unlisted characters</Trans></Typography></SqBadge>}
        </Box>
      </CardContent>
    </CardDark ></ModalWrapper>
    <ButtonGroup sx={{ display: "flex", width: "100%" }}>
      <Button sx={{ flexGrow: 1 }} onClick={() => buildSettingsDispatch({ useEquippedArts: !useEquippedArts })} disabled={disabled} startIcon={useEquippedArts ? <CheckBox /> : <CheckBoxOutlineBlank />} color={useEquippedArts ? "success" : "secondary"}>
        <Box>
          <span><Trans t={t} i18nKey="tabOptimize.useEquipped.title">Use Equipped Artifacts</Trans></span>
          {useEquippedArts && <SqBadge><Trans t={t} i18nKey="tabOptimize.useEquipped.usingNum" count={numUseEquippedChar}>Using from <strong>{{ count: numUseEquippedChar }}</strong> characters</Trans></SqBadge>}
        </Box>
      </Button>
      {useEquippedArts && <Button sx={{ flexShrink: 1 }} color="info" onClick={onOpen}><Settings /></Button>}
    </ButtonGroup>
  </Box>
}
Example #7
Source File: ArtifactEditor.tsx    From genshin-optimizer with MIT License 4 votes vote down vote up
export default function ArtifactEditor({ artifactIdToEdit = "", cancelEdit, allowUpload = false, allowEmpty = false, disableEditSetSlot: disableEditSlotProp = false }:
  { artifactIdToEdit?: string, cancelEdit: () => void, allowUpload?: boolean, allowEmpty?: boolean, disableEditSetSlot?: boolean }) {
  const { t } = useTranslation("artifact")

  const artifactSheets = usePromise(ArtifactSheet.getAll, [])

  const { database } = useContext(DatabaseContext)

  const [show, setShow] = useState(false)

  const [dirtyDatabase, setDirtyDatabase] = useForceUpdate()
  useEffect(() => database.followAnyArt(setDirtyDatabase), [database, setDirtyDatabase])

  const [editorArtifact, artifactDispatch] = useReducer(artifactReducer, undefined)
  const artifact = useMemo(() => editorArtifact && parseArtifact(editorArtifact), [editorArtifact])

  const [modalShow, setModalShow] = useState(false)

  const [{ processed, outstanding }, dispatchQueue] = useReducer(queueReducer, { processed: [], outstanding: [] })
  const firstProcessed = processed[0] as ProcessedEntry | undefined
  const firstOutstanding = outstanding[0] as OutstandingEntry | undefined

  const processingImageURL = usePromise(firstOutstanding?.imageURL, [firstOutstanding?.imageURL])
  const processingResult = usePromise(firstOutstanding?.result, [firstOutstanding?.result])

  const remaining = processed.length + outstanding.length

  const image = firstProcessed?.imageURL ?? processingImageURL
  const { artifact: artifactProcessed, texts } = firstProcessed ?? {}
  // const fileName = firstProcessed?.fileName ?? firstOutstanding?.fileName ?? "Click here to upload Artifact screenshot files"

  const disableEditSetSlot = disableEditSlotProp || !!artifact?.location

  useEffect(() => {
    if (!artifact && artifactProcessed)
      artifactDispatch({ type: "overwrite", artifact: artifactProcessed })
  }, [artifact, artifactProcessed, artifactDispatch])

  useEffect(() => {
    const numProcessing = Math.min(maxProcessedCount - processed.length, maxProcessingCount, outstanding.length)
    const processingCurrent = numProcessing && !outstanding[0].result
    outstanding.slice(0, numProcessing).forEach(processEntry)
    if (processingCurrent)
      dispatchQueue({ type: "processing" })
  }, [processed.length, outstanding])

  useEffect(() => {
    if (processingResult)
      dispatchQueue({ type: "processed", ...processingResult })
  }, [processingResult, dispatchQueue])

  const uploadFiles = useCallback((files: FileList) => {
    setShow(true)
    dispatchQueue({ type: "upload", files: [...files].map(file => ({ file, fileName: file.name })) })
  }, [dispatchQueue, setShow])
  const clearQueue = useCallback(() => dispatchQueue({ type: "clear" }), [dispatchQueue])

  useEffect(() => {
    const pasteFunc = (e: any) => uploadFiles(e.clipboardData.files)
    allowUpload && window.addEventListener('paste', pasteFunc);
    return () => {
      if (allowUpload) window.removeEventListener('paste', pasteFunc)
    }
  }, [uploadFiles, allowUpload])

  const onUpload = useCallback(
    e => {
      uploadFiles(e.target.files)
      e.target.value = null // reset the value so the same file can be uploaded again...
    },
    [uploadFiles],
  )

  const { old, oldType }: { old: ICachedArtifact | undefined, oldType: "edit" | "duplicate" | "upgrade" | "" } = useMemo(() => {
    const databaseArtifact = dirtyDatabase && artifactIdToEdit && database._getArt(artifactIdToEdit)
    if (databaseArtifact) return { old: databaseArtifact, oldType: "edit" }
    if (artifact === undefined) return { old: undefined, oldType: "" }
    const { duplicated, upgraded } = dirtyDatabase && database.findDuplicates(artifact)
    return { old: duplicated[0] ?? upgraded[0], oldType: duplicated.length !== 0 ? "duplicate" : "upgrade" }
  }, [artifact, artifactIdToEdit, database, dirtyDatabase])

  const { artifact: cachedArtifact, errors } = useMemo(() => {
    if (!artifact) return { artifact: undefined, errors: [] as Displayable[] }
    const validated = validateArtifact(artifact, artifactIdToEdit)
    if (old) {
      validated.artifact.location = old.location
      validated.artifact.exclude = old.exclude
    }
    return validated
  }, [artifact, artifactIdToEdit, old])

  // Overwriting using a different function from `databaseArtifact` because `useMemo` does not
  // guarantee to trigger *only when* dependencies change, which is necessary in this case.
  useEffect(() => {
    if (artifactIdToEdit === "new") {
      setShow(true)
      artifactDispatch({ type: "reset" })
    }
    const databaseArtifact = artifactIdToEdit && dirtyDatabase && database._getArt(artifactIdToEdit)
    if (databaseArtifact) {
      setShow(true)
      artifactDispatch({ type: "overwrite", artifact: deepClone(databaseArtifact) })
    }
  }, [artifactIdToEdit, database, dirtyDatabase])

  const sheet = artifact ? artifactSheets?.[artifact.setKey] : undefined
  const reset = useCallback(() => {
    cancelEdit?.();
    dispatchQueue({ type: "pop" })
    artifactDispatch({ type: "reset" })
  }, [cancelEdit, artifactDispatch])
  const update = useCallback((newValue: Partial<IArtifact>) => {
    const newSheet = newValue.setKey ? artifactSheets![newValue.setKey] : sheet!

    function pick<T>(value: T | undefined, available: readonly T[], prefer?: T): T {
      return (value && available.includes(value)) ? value : (prefer ?? available[0])
    }

    if (newValue.setKey) {
      newValue.rarity = pick(artifact?.rarity, newSheet.rarity, Math.max(...newSheet.rarity) as ArtifactRarity)
      newValue.slotKey = pick(artifact?.slotKey, newSheet.slots)
    }
    if (newValue.rarity)
      newValue.level = artifact?.level ?? 0
    if (newValue.level)
      newValue.level = clamp(newValue.level, 0, 4 * (newValue.rarity ?? artifact!.rarity))
    if (newValue.slotKey)
      newValue.mainStatKey = pick(artifact?.mainStatKey, Artifact.slotMainStats(newValue.slotKey))

    if (newValue.mainStatKey) {
      newValue.substats = [0, 1, 2, 3].map(i =>
        (artifact && artifact.substats[i].key !== newValue.mainStatKey) ? artifact!.substats[i] : { key: "", value: 0 })
    }
    artifactDispatch({ type: "update", artifact: newValue })
  }, [artifact, artifactSheets, sheet, artifactDispatch])
  const setSubstat = useCallback((index: number, substat: ISubstat) => {
    artifactDispatch({ type: "substat", index, substat })
  }, [artifactDispatch])
  const isValid = !errors.length
  const canClearArtifact = (): boolean => window.confirm(t`editor.clearPrompt` as string)
  const { rarity = 5, level = 0, slotKey = "flower" } = artifact ?? {}
  const { currentEfficiency = 0, maxEfficiency = 0 } = cachedArtifact ? Artifact.getArtifactEfficiency(cachedArtifact, allSubstatFilter) : {}
  const preventClosing = processed.length || outstanding.length
  const onClose = useCallback(
    (e) => {
      if (preventClosing) e.preventDefault()
      setShow(false)
      cancelEdit()
    }, [preventClosing, setShow, cancelEdit])

  const theme = useTheme();
  const grmd = useMediaQuery(theme.breakpoints.up('md'));

  const element = artifact ? allElementsWithPhy.find(ele => artifact.mainStatKey.includes(ele)) : undefined
  const color = artifact
    ? element ?? "success"
    : "primary"

  return <ModalWrapper open={show} onClose={onClose} >
    <Suspense fallback={<Skeleton variant="rectangular" sx={{ width: "100%", height: show ? "100%" : 64 }} />}><CardDark >
      <UploadExplainationModal modalShow={modalShow} hide={() => setModalShow(false)} />
      <CardHeader
        title={<Trans t={t} i18nKey="editor.title" >Artifact Editor</Trans>}
        action={<CloseButton disabled={!!preventClosing} onClick={onClose} />}
      />
      <CardContent sx={{ display: "flex", flexDirection: "column", gap: 1 }}>
        <Grid container spacing={1} columns={{ xs: 1, md: 2 }} >
          {/* Left column */}
          <Grid item xs={1} display="flex" flexDirection="column" gap={1}>
            {/* set & rarity */}
            <ButtonGroup sx={{ display: "flex", mb: 1 }}>
              {/* Artifact Set */}
              <ArtifactSetSingleAutocomplete
                size="small"
                disableClearable
                artSetKey={artifact?.setKey ?? ""}
                setArtSetKey={setKey => update({ setKey: setKey as ArtifactSetKey })}
                sx={{ flexGrow: 1 }}
                disabled={disableEditSetSlot}
              />
              {/* rarity dropdown */}
              <ArtifactRarityDropdown rarity={artifact ? rarity : undefined} onChange={r => update({ rarity: r })} filter={r => !!sheet?.rarity?.includes?.(r)} disabled={disableEditSetSlot || !sheet} />
            </ButtonGroup>

            {/* level */}
            <Box component="div" display="flex">
              <CustomNumberTextField id="filled-basic" label="Level" variant="filled" sx={{ flexShrink: 1, flexGrow: 1, mr: 1, my: 0 }} margin="dense" size="small"
                value={level} disabled={!sheet} placeholder={`0~${rarity * 4}`} onChange={l => update({ level: l })}
              />
              <ButtonGroup >
                <Button onClick={() => update({ level: level - 1 })} disabled={!sheet || level === 0}>-</Button>
                {rarity ? [...Array(rarity + 1).keys()].map(i => 4 * i).map(i => <Button key={i} onClick={() => update({ level: i })} disabled={!sheet || level === i}>{i}</Button>) : null}
                <Button onClick={() => update({ level: level + 1 })} disabled={!sheet || level === (rarity * 4)}>+</Button>
              </ButtonGroup>
            </Box>

            {/* slot */}
            <Box component="div" display="flex">
              <ArtifactSlotDropdown disabled={disableEditSetSlot || !sheet} slotKey={slotKey} onChange={slotKey => update({ slotKey })} />
              <CardLight sx={{ p: 1, ml: 1, flexGrow: 1 }}>
                <Suspense fallback={<Skeleton width="60%" />}>
                  <Typography color="text.secondary">
                    {sheet?.getSlotName(artifact!.slotKey) ? <span><ImgIcon src={sheet.slotIcons[artifact!.slotKey]} /> {sheet?.getSlotName(artifact!.slotKey)}</span> : t`editor.unknownPieceName`}
                  </Typography>
                </Suspense>
              </CardLight>
            </Box>

            {/* main stat */}
            <Box component="div" display="flex">
              <DropdownButton startIcon={element ? uncoloredEleIcons[element] : (artifact?.mainStatKey ? StatIcon[artifact.mainStatKey] : undefined)}
                title={<b>{artifact ? KeyMap.getArtStr(artifact.mainStatKey) : t`mainStat`}</b>} disabled={!sheet} color={color} >
                {Artifact.slotMainStats(slotKey).map(mainStatK =>
                  <MenuItem key={mainStatK} selected={artifact?.mainStatKey === mainStatK} disabled={artifact?.mainStatKey === mainStatK} onClick={() => update({ mainStatKey: mainStatK })} >
                    <ListItemIcon>{StatIcon[mainStatK]}</ListItemIcon>
                    <ListItemText>{KeyMap.getArtStr(mainStatK)}</ListItemText>
                  </MenuItem>)}
              </DropdownButton>
              <CardLight sx={{ p: 1, ml: 1, flexGrow: 1 }}>
                <Typography color="text.secondary">
                  {artifact ? `${cacheValueString(Artifact.mainStatValue(artifact.mainStatKey, rarity, level), KeyMap.unit(artifact.mainStatKey))}${KeyMap.unit(artifact.mainStatKey)}` : t`mainStat`}
                </Typography>
              </CardLight>
            </Box>

            {/* Current/Max Substats Efficiency */}
            <SubstatEfficiencyDisplayCard valid={isValid} efficiency={currentEfficiency} t={t} />
            {currentEfficiency !== maxEfficiency && <SubstatEfficiencyDisplayCard max valid={isValid} efficiency={maxEfficiency} t={t} />}

            {/* Image OCR */}
            {allowUpload && <CardLight>
              <CardContent sx={{ display: "flex", flexDirection: "column", gap: 1 }}>
                {/* TODO: artifactDispatch not overwrite */}
                <Suspense fallback={<Skeleton width="100%" height="100" />}>
                  <Grid container spacing={1} alignItems="center">
                    <Grid item flexGrow={1}>
                      <label htmlFor="contained-button-file">
                        <InputInvis accept="image/*" id="contained-button-file" multiple type="file" onChange={onUpload} />
                        <Button component="span" startIcon={<PhotoCamera />}>
                          Upload Screenshot (or Ctrl-V)
                        </Button>
                      </label>
                    </Grid>
                    <Grid item>
                      <Button color="info" sx={{ px: 2, minWidth: 0 }} onClick={() => setModalShow(true)}><Typography><FontAwesomeIcon icon={faQuestionCircle} /></Typography></Button>
                    </Grid>
                  </Grid>
                  {image && <Box display="flex" justifyContent="center">
                    <Box component="img" src={image} width="100%" maxWidth={350} height="auto" alt="Screenshot to parse for artifact values" />
                  </Box>}
                  {remaining > 0 && <CardDark sx={{ pl: 2 }} ><Grid container spacing={1} alignItems="center" >
                    {!firstProcessed && firstOutstanding && <Grid item>
                      <CircularProgress size="1em" />
                    </Grid>}
                    <Grid item flexGrow={1}>
                      <Typography>
                        <span>
                          Screenshots in file-queue: <b>{remaining}</b>
                          {/* {process.env.NODE_ENV === "development" && ` (Debug: Processed ${processed.length}/${maxProcessedCount}, Processing: ${outstanding.filter(entry => entry.result).length}/${maxProcessingCount}, Outstanding: ${outstanding.length})`} */}
                        </span>
                      </Typography>
                    </Grid>
                    <Grid item>
                      <Button size="small" color="error" onClick={clearQueue}>Clear file-queue</Button>
                    </Grid>
                  </Grid></CardDark>}
                </Suspense>
              </CardContent>
            </CardLight>}
          </Grid>

          {/* Right column */}
          <Grid item xs={1} display="flex" flexDirection="column" gap={1}>
            {/* substat selections */}
            {[0, 1, 2, 3].map((index) => <SubstatInput key={index} index={index} artifact={cachedArtifact} setSubstat={setSubstat} />)}
            {texts && <CardLight><CardContent>
              <div>{texts.slotKey}</div>
              <div>{texts.mainStatKey}</div>
              <div>{texts.mainStatVal}</div>
              <div>{texts.rarity}</div>
              <div>{texts.level}</div>
              <div>{texts.substats}</div>
              <div>{texts.setKey}</div>
            </CardContent></CardLight>}
          </Grid>
        </Grid>

        {/* Duplicate/Updated/Edit UI */}
        {old && <Grid container sx={{ justifyContent: "space-around" }} spacing={1} >
          <Grid item xs={12} md={5.5} lg={4} ><CardLight>
            <Typography sx={{ textAlign: "center" }} py={1} variant="h6" color="text.secondary" >{oldType !== "edit" ? (oldType === "duplicate" ? t`editor.dupArt` : t`editor.upArt`) : t`editor.beforeEdit`}</Typography>
            <ArtifactCard artifactObj={old} />
          </CardLight></Grid>
          {grmd && <Grid item md={1} display="flex" alignItems="center" justifyContent="center" >
            <CardLight sx={{ display: "flex" }}><ChevronRight sx={{ fontSize: 40 }} /></CardLight>
          </Grid>}
          <Grid item xs={12} md={5.5} lg={4} ><CardLight>
            <Typography sx={{ textAlign: "center" }} py={1} variant="h6" color="text.secondary" >{t`editor.preview`}</Typography>
            <ArtifactCard artifactObj={cachedArtifact} />
          </CardLight></Grid>
        </Grid>}

        {/* Error alert */}
        {!isValid && <Alert variant="filled" severity="error" >{errors.map((e, i) => <div key={i}>{e}</div>)}</Alert>}

        {/* Buttons */}
        <Grid container spacing={2}>
          <Grid item>
            {oldType === "edit" ?
              <Button startIcon={<Add />} onClick={() => {
                database.updateArt(editorArtifact!, old!.id);
                if (allowEmpty) reset()
                else {
                  setShow(false)
                  cancelEdit()
                }
              }} disabled={!editorArtifact || !isValid} color="primary">
                {t`editor.btnSave`}
              </Button> :
              <Button startIcon={<Add />} onClick={() => {
                database.createArt(artifact!);
                if (allowEmpty) reset()
                else {
                  setShow(false)
                  cancelEdit()
                }
              }} disabled={!artifact || !isValid} color={oldType === "duplicate" ? "warning" : "primary"}>
                {t`editor.btnAdd`}
              </Button>}
          </Grid>
          <Grid item flexGrow={1}>
            {allowEmpty && <Button startIcon={<Replay />} disabled={!artifact} onClick={() => { canClearArtifact() && reset() }} color="error">{t`editor.btnClear`}</Button>}
          </Grid>
          <Grid item>
            {process.env.NODE_ENV === "development" && <Button color="info" startIcon={<Shuffle />} onClick={async () => artifactDispatch({ type: "overwrite", artifact: await randomizeArtifact() })}>{t`editor.btnRandom`}</Button>}
          </Grid>
          {old && oldType !== "edit" && <Grid item>
            <Button startIcon={<Update />} onClick={() => { database.updateArt(editorArtifact!, old.id); allowEmpty ? reset() : setShow(false) }} disabled={!editorArtifact || !isValid} color="success">{t`editor.btnUpdate`}</Button>
          </Grid>}
        </Grid>
      </CardContent>
    </CardDark ></Suspense>
  </ModalWrapper>
}
Example #8
Source File: index.tsx    From genshin-optimizer with MIT License 4 votes vote down vote up
export default function PageArtifact() {
  const [{ tcMode }] = useDBState("GlobalSettings", initGlobalSettings)
  const { t } = useTranslation(["artifact", "ui"]);
  const { database } = useContext(DatabaseContext)
  const [state, setState] = useDBState("ArtifactDisplay", initialState)
  const stateDispatch = useCallback(
    action => {
      if (action.type === "reset") setState(initialArtifactSortFilter())
      else setState(action)
    },
    [setState],
  )
  const brPt = useMediaQueryUp()
  const maxNumArtifactsToDisplay = numToShowMap[brPt]

  const { effFilter, filterOption, ascending, probabilityFilter } = state
  let { sortType } = state
  const showProbability = tcMode && sortType === "probability"
  //force the sortType back to a normal value after exiting TC mode
  if (sortType === "probability" && !tcMode) stateDispatch({ sortType: artifactSortKeys[0] })

  const [pageIdex, setpageIdex] = useState(0)
  const invScrollRef = useRef<HTMLDivElement>(null)
  const [dbDirty, forceUpdate] = useForceUpdate()
  const effFilterSet = useMemo(() => new Set(effFilter), [effFilter]) as Set<SubstatKey>
  const deleteArtifact = useCallback((id: string) => database.removeArt(id), [database])

  useEffect(() => {
    ReactGA.send({ hitType: "pageview", page: '/artifact' })
    return database.followAnyArt(forceUpdate)
  }, [database, forceUpdate])

  const filterOptionDispatch = useCallback((action) => {
    stateDispatch({
      filterOption: {
        ...filterOption,
        ...action
      }
    })
  }, [stateDispatch, filterOption])

  const setProbabilityFilter = useCallback(probabilityFilter => stateDispatch({ probabilityFilter }), [stateDispatch],)

  const noArtifact = useMemo(() => !database._getArts().length, [database])
  const sortConfigs = useMemo(() => artifactSortConfigs(effFilterSet, probabilityFilter), [effFilterSet, probabilityFilter])
  const filterConfigs = useMemo(() => artifactFilterConfigs(), [])
  const { artifactIds, totalArtNum } = useMemo(() => {
    const { sortType = artifactSortKeys[0], ascending = false, filterOption } = state
    let allArtifacts = database._getArts()
    const filterFunc = filterFunction(filterOption, filterConfigs)
    const sortFunc = sortFunction(sortType, ascending, sortConfigs)
    //in probability mode, filter out the artifacts that already reach criteria
    if (showProbability) {
      allArtifacts.forEach(art => (art as any).probability = probability(art, probabilityFilter))
      allArtifacts = allArtifacts.filter(art => (art as any).probability && (art as any).probability !== 1)
    }
    const artifactIds = allArtifacts.filter(filterFunc).sort(sortFunc).map(art => art.id)
    return { artifactIds, totalArtNum: allArtifacts.length, ...dbDirty }//use dbDirty to shoo away warnings!
  }, [state, dbDirty, database, sortConfigs, filterConfigs, probabilityFilter, showProbability])


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

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

  return <Box display="flex" flexDirection="column" gap={1} my={1}>
    <InfoComponent
      pageKey="artifactPage"
      modalTitle={t`info.title`}
      text={t("tipsOfTheDay", { returnObjects: true }) as string[]}
    >
      <InfoDisplay />
    </InfoComponent>

    {noArtifact && <Alert severity="info" variant="filled">Looks like you haven't added any artifacts yet. If you want, there are <Link color="warning.main" component={RouterLink} to="/scanner">automatic scanners</Link> that can speed up the import process!</Alert>}

    <ArtifactFilter filterOption={filterOption} filterOptionDispatch={filterOptionDispatch} filterDispatch={stateDispatch}
      numShowing={artifactIds.length} total={totalArtNum} />
    {showProbability && <ProbabilityFilter probabilityFilter={probabilityFilter} setProbabilityFilter={setProbabilityFilter} />}
    <CardDark ref={invScrollRef}>
      <CardContent>
        <Grid container sx={{ mb: 1 }}>
          <Grid item flexGrow={1}><span><Trans t={t} i18nKey="efficiencyFilter.title">Substats to use in efficiency calculation</Trans></span></Grid>
          <Grid item>
            <Button size="small" color="error" onClick={() => stateDispatch({ effFilter: [...allSubstatKeys] })} startIcon={<Replay />}><Trans t={t} i18nKey="ui:reset" /></Button>
          </Grid>
        </Grid>
        <EfficiencyFilter selectedKeys={effFilter} onChange={n => stateDispatch({ effFilter: n })} />
      </CardContent>
    </CardDark>
    <CardDark ><CardContent>
      <Grid container alignItems="center" sx={{ pb: 2 }}>
        <Grid item flexGrow={1}>
          <Pagination count={numPages} page={currentPageIndex + 1} onChange={setPage} />
        </Grid>
        <Grid item flexGrow={1}>
          <ShowingArt numShowing={artifactIdsToShow.length} total={totalShowing} t={t} />
        </Grid>
        <Grid item xs={12} sm={6} md={4} lg={4} xl={3} display="flex">
          <Box flexGrow={1} />
          <SortByButton sortKeys={[...artifactSortKeys.filter(key => (artifactSortKeysTC as unknown as string[]).includes(key) ? tcMode : true)]}
            value={sortType} onChange={sortType => stateDispatch({ sortType })}
            ascending={ascending} onChangeAsc={ascending => stateDispatch({ ascending })}
          />
        </Grid>
      </Grid>
      <ArtifactRedButtons artifactIds={artifactIds} filterOption={filterOption} />
    </CardContent></CardDark>

    <Suspense fallback={<Skeleton variant="rectangular" sx={{ width: "100%", height: "100%", minHeight: 5000 }} />}>
      <Grid container spacing={1} columns={columns} >
        <Grid item xs={1} >
          <NewArtifactCard />
        </Grid>
        {artifactIdsToShow.map(artId =>
          <Grid item key={artId} xs={1}  >
            <ArtifactCard
              artifactId={artId}
              effFilter={effFilterSet}
              onDelete={deleteArtifact}
              probabilityFilter={showProbability ? probabilityFilter : undefined}
              editor
              canExclude
              canEquip
            />
          </Grid>
        )}
      </Grid>
    </Suspense>
    {numPages > 1 && <CardDark ><CardContent>
      <Grid container>
        <Grid item flexGrow={1}>
          <Pagination count={numPages} page={currentPageIndex + 1} onChange={setPage} />
        </Grid>
        <Grid item>
          <ShowingArt numShowing={artifactIdsToShow.length} total={totalShowing} t={t} />
        </Grid>
      </Grid>
    </CardContent></CardDark>}
  </Box >
}
Example #9
Source File: random.tsx    From Search-Next with GNU General Public License v3.0 4 votes vote down vote up
Random: React.FC<RandomProps> = ({ data, onChange }) => {
  const [imgList, setImgList] = React.useState([] as BingImage[]); //图片列表
  const [loadings, setLoadings] = React.useState<boolean[]>([]); //图片加载数组
  const [checkHsh, setCheckHsh] = React.useState<string>(''); //选中图片的hsh值
  const [apiLoading, setApiLoading] = React.useState<boolean>(false);
  const demoList = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];
  const [init, setInit] = React.useState<boolean>(false);

  const formatItem = (data: BingImage): AuthBackgroundRandomData => {
    return {
      id: data._id,
      url: data.url,
      hsh: data.hsh,
      copyright: data.copyright,
      copyrightlink: data.copyrightlink,
    };
  };

  const getList = () => {
    setApiLoading(true);
    const hsh = initCheck();
    bingImg({ size: 10, hsh }).then((res) => {
      let list = res.data;
      setImgList(list);
      setLoadings(list.map(() => true));
      setApiLoading(false);
      const image = list[0];
      if (data) {
        setCheckHsh(data.hsh);
      } else {
        if (onChange) onChange(formatItem(image));
        setCheckHsh(image.hsh);
      }
    });
  };

  // 图片加载状态
  const imgLoad = (index: number) => {
    let imgLoadings = loadings;
    imgLoadings[index] = false;
    setLoadings(imgLoadings.map((i) => i));
  };

  const initCheck = (): string => {
    const check = data;
    let hsh = '';
    if (check) {
      hsh = check.hsh;
    }
    setCheckHsh(hsh);
    return hsh;
  };

  // 选择背景
  const onCheckChange = (hsh: string) => {
    setCheckHsh(hsh);
    findImgToStroage(hsh);
    const selected: any = imgList.find((i) => i.hsh === hsh);
    if (selected && onChange) {
      onChange(formatItem(selected));
    }
  };

  const findImgToStroage = (hsh: string) => {
    // 解决初始化时每个card都触发onChange事件的问题
    if (!init) return setInit(true);
    const userId = localStorage.getItem('account');
    const image = imgList.find((i) => i.hsh === hsh);
    if (image && image.hsh === data?.hsh) return;
    if (image && userId) {
      setBackground(userId, {
        check: true,
        url: image.url,
        bgId: image._id,
        hsh: image.hsh,
        copyright: image.copyright,
        copyrightlink: image.copyrightlink,
      });
    }
  };

  React.useEffect(() => {
    getList();
  }, []);

  return (
    <div className="my-0 px-4 border-t">
      <ItemHeader
        title="背景"
        desc="修改适用于主页的背景,当前版本壁纸尺寸为标准1920x1080,在更大分辨率下会存在模糊问题。"
        rightHandle={
          <Tooltip title="通过刷新获取随机背景图片">
            <Button
              startIcon={<Replay />}
              size="small"
              disableElevation
              onClick={() => getList()}
            >
              刷新
            </Button>
          </Tooltip>
        }
      />
      <div
        className={classNames(
          'p-4 justify-center flex flex-wrap mt-2 mb-4 gap-3',
          css`
            .ant-image {
              display: block;
            }
          `,
        )}
      >
        {apiLoading
          ? demoList.map((i) => (
              <OutlineCard key={i} label=" " disabled loading />
            ))
          : imgList.map((i, j) => (
              <OutlineCard
                key={i.hsh}
                id={i.hsh}
                value={checkHsh}
                label={dayjs(i.enddate).format('YYYY/MM/DD')}
                onChange={(val) => onCheckChange(val)}
                tip={i.copyright}
              >
                <Spin
                  spinning={loadings[j]}
                  indicator={<CircularProgress size={18} color="inherit" />}
                >
                  <Image
                    className="w-32 h-20 block"
                    onLoad={() => imgLoad(j)}
                    preview={false}
                    placeholder
                    src={i.url}
                    alt={i.copyright}
                  />
                </Spin>
              </OutlineCard>
            ))}
      </div>
    </div>
  );
}