@mui/material#CardHeader TypeScript Examples

The following examples show how to use @mui/material#CardHeader. 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: item-card.tsx    From sdk with MIT License 6 votes vote down vote up
export function ItemCard({ item }: IItemCardProps) {
	return (
		<Card sx={{ width: 200 }}>
			<CardHeader
				sx={{
					display: "flex",
					overflow: "hidden",
					"& .MuiCardHeader-content": {
						overflow: "hidden"
					}
				}}
				title={<Typography noWrap gutterBottom variant="h6" component="h4">
					{item.meta ? item.meta.name : "No metadata"}
				</Typography>}
			/>
			<ItemMedia url={getMetaImageUrl(item.meta?.content)}/>
			<CardContent>
				<Typography variant="body2" color="text.secondary" sx={{ textAlign: "right" }}>
					<strong>Supply: {item.supply}</strong>
				</Typography>
			</CardContent>
			<CardActions>
				<Button
					size="small"
					component={Link}
					to={`/sell/${item.id}`}
				>
					Sell
				</Button>
				{/*<Button size="small" color={"warning"}>Burn</Button>*/}
			</CardActions>
		</Card>
	)
}
Example #2
Source File: Profiler.tsx    From NekoMaid with MIT License 6 votes vote down vote up
Pie: React.FC<{ title: string, data: any[], formatter?: any }> = React.memo(({ title, data, formatter }) => <Grid item sm={6} xs={12}>
  <Card>
    <CardHeader title={title} />
    <Divider />
    {data.length
      ? <ReactECharts style={{ height: 300 }} theme={useTheme().palette.mode === 'dark' ? 'dark' : undefined} option={{
        backgroundColor: 'rgba(0, 0, 0, 0)',
        itemStyle: {
          borderRadius: 5,
          borderColor: 'rgba(0, 0, 0, 0)',
          borderWidth: 4
        },
        tooltip: {
          trigger: 'item',
          formatter
        },
        series: [{
          type: 'pie',
          radius: '50%',
          data,
          emphasis: {
            itemStyle: {
              shadowBlur: 10,
              shadowOffsetX: 0,
              shadowColor: 'rgba(0, 0, 0, 0.5)'
            }
          }
        }]
      }} />
      : <CardContent><Empty /></CardContent>}
  </Card>
</Grid>)
Example #3
Source File: Config.tsx    From NekoMaid with MIT License 6 votes vote down vote up
Config: React.FC = () => {
  return <Box sx={{ minHeight: '100%', py: 3 }}>
    <Toolbar />
    <Container maxWidth={false}>
      <Grid container spacing={3}>
        {configs.map((it, i) => <Grid key={i} item lg={4} md={12} xl={6} xs={12}>
          <Card>
            <CardHeader title={it.title} />
            <Divider />
            <it.component />
          </Card>
        </Grid>)}
      </Grid>
    </Container>
  </Box>
}
Example #4
Source File: PrincipleCard.tsx    From frontend with MIT License 6 votes vote down vote up
export default function PrincipleCard({ Icon, heading, content }: PrincipleCardProps) {
  return (
    <StyledCard elevation={0}>
      <CardHeader
        className={classes.cardHeader}
        avatar={<Icon className={classes.icon} />}
        title={
          <Typography variant="body2" component="div" className={classes.heading}>
            {heading}
          </Typography>
        }
        classes={{
          action: classes.cardHeaderAction,
          content: classes.cardHeaderTitleRoot,
        }}
      />
      <CardContent className={classes.contentContainer}>
        <Typography className={classes.content} variant="body1">
          {content}
        </Typography>
      </CardContent>
    </StyledCard>
  )
}
Example #5
Source File: TimelineItem.tsx    From frontend with MIT License 6 votes vote down vote up
export default function TimelineItem({ children, title, lastItem, Icon }: TimelineItemProps) {
  return (
    <StyledTimelineItemMaterial className={classes.timelineItem}>
      <TimelineSeparator>
        <Icon className={classes.icon} color="primary" />
        <TimelineConnector className={classes.connector} />
        {lastItem ? <ArrowForwardIosIcon className={classes.arrowIcon} color="primary" /> : ''}
      </TimelineSeparator>
      <TimelineContent classes={{ root: classes.contentContainer }}>
        <Card variant="outlined" className={classes.contentPaper}>
          {title && <CardHeader titleTypographyProps={{ color: 'textSecondary' }} title={title} />}
          <CardContent className={classes.content}>
            <Typography variant="body2" component="div">
              {children}
            </Typography>
          </CardContent>
        </Card>
      </TimelineContent>
    </StyledTimelineItemMaterial>
  )
}
Example #6
Source File: index.tsx    From genshin-optimizer with MIT License 6 votes vote down vote up
function FeatureCard({ image, title, content, t }) {
  const [expanded, setExpanded] = useState(false);

  return <CardLight >
    <CardContent sx={{ p: 1, pb: 0 }}>
      <Box component="img" src={image} alt="test" sx={{ width: "100%", height: "auto" }} />
    </CardContent>
    <CardHeader
      action={
        <ExpandButton
          expand={expanded}
          onClick={() => setExpanded(!expanded)}
          aria-expanded={expanded}
          aria-label="show more"
        >
          <ExpandMore />
        </ExpandButton>
      }
      titleTypographyProps={{ variant: "subtitle1" }}
      title={title(t)}
    />
    <Collapse in={expanded} timeout="auto" unmountOnExit>
      <CardContent sx={{ pt: 0 }}>
        {content(t)}
      </CardContent>
    </Collapse>
  </CardLight >
}
Example #7
Source File: index.tsx    From genshin-optimizer with MIT License 6 votes vote down vote up
function FormulaCalc({ sectionKey, displayNs }: { displayNs: DisplaySub<NodeDisplay>, sectionKey: string }) {
  const { data } = useContext(DataContext)
  const header = usePromise(getDisplayHeader(data, sectionKey), [data, sectionKey])
  if (!header) return null
  if (Object.entries(displayNs).every(([_, node]) => node.isEmpty)) return null
  const { title, icon, action } = header
  return <CardDark sx={{ mb: 1 }}>
    <CardHeader avatar={icon && <ImgIcon size={2} sx={{ m: -1 }} src={icon} />} title={title} action={action} titleTypographyProps={{ variant: "subtitle1" }} />
    <Divider />
    <CardContent>
      {Object.entries(displayNs).map(([key, node]) =>
        !node.isEmpty && <Accordion sx={{ bgcolor: "contentLight.main" }} key={key}>
          <AccordionSummary expandIcon={<ExpandMore />}>
            <Typography><ColorText color={node.info.variant}>{KeyMap.get(node.info.key ?? "")}</ColorText> <strong>{valueString(node.value, node.unit)}</strong></Typography>
          </AccordionSummary>
          <AccordionDetails>
            {node.formulas.map((subform, i) => <Typography key={i}>{subform}</Typography>)}
          </AccordionDetails>
        </Accordion>)}
    </CardContent>
  </CardDark>
}
Example #8
Source File: TabTeambuffs.tsx    From genshin-optimizer with MIT License 6 votes vote down vote up
function ResonanceDisplay() {
  const { data } = useContext(DataContext)
  return <>
    {resonanceSheets.map((res, i) => {
      const icon = <InfoTooltip title={<Typography>{res.desc}</Typography>} />
      const title = <span>{res.name} {icon}</span>
      return <CardLight key={i} sx={{ opacity: res.canShow(data) ? 1 : 0.5, }}>
        <CardHeader title={title} action={res.icon} titleTypographyProps={{ variant: "subtitle2" }} />
        {res.canShow(data) && <Divider />}
        {res.canShow(data) && <CardContent>
          <DocumentDisplay sections={res.sections} teamBuffOnly hideDesc />
        </CardContent>}
      </CardLight>
    })}
  </>
}
Example #9
Source File: OptimizationTargetSelector.tsx    From genshin-optimizer with MIT License 6 votes vote down vote up
function SelectorSection({ displayNs, sectionKey, setTarget }: { displayNs: DisplaySub<NodeDisplay>, sectionKey: string, setTarget: (target: string[]) => void }) {
  const { data } = useContext(DataContext)
  const header = usePromise(getDisplayHeader(data, sectionKey), [data, sectionKey])
  return <CardLight key={sectionKey as string}>
    {header && <CardHeader avatar={header.icon && <ImgIcon size={2} sx={{ m: -1 }} src={header.icon} />} title={header.title} action={header.action} titleTypographyProps={{ variant: "subtitle1" }} />}
    <Divider />
    <MenuList>
      {Object.entries(displayNs).map(([key, n]) =>
        <TargetSelectorMenuItem key={key} node={n} onClick={() => setTarget([sectionKey, key])} />)}
    </MenuList>
  </CardLight>
}
Example #10
Source File: WhatUnitesUsCard.tsx    From frontend with MIT License 6 votes vote down vote up
export default function WhatUnitesUsCard({ info }: WhatUnitesUsCardProps) {
  return (
    <Card elevation={0} sx={{ textAlign: 'center' }}>
      <Box
        sx={{
          height: 75,
          width: 75,
          display: 'flex',
          justifyContent: 'center',
          alignItems: 'center',
          borderRadius: '50%',
          border: `2px solid ${info.iconColor}`,
          m: 'auto',
        }}>
        {info.icon ? <info.icon sx={{ height: 45, width: 45, fill: info.iconColor }} /> : null}
      </Box>
      <CardHeader title={info.title} />
      <CardContent sx={{ px: 1 }}>{info.content}</CardContent>
    </Card>
  )
}
Example #11
Source File: DocumentDisplay.tsx    From genshin-optimizer with MIT License 6 votes vote down vote up
export function HeaderDisplay({ header, hideDesc, hideDivider }: { header: IDocumentHeader, hideDesc?: boolean, hideDivider?: boolean | ((section: DocumentSection) => boolean) }) {
  const { data } = useContext(DataContext)
  let { icon, title, action } = header
  icon = evalIfFunc(icon, data)
  const description = !hideDesc && evalIfFunc(header.description, data)
  const displayTitle = hideDesc ? title : <span>{title} <InfoTooltip title={<Typography>{description}</Typography>} /></span>
  return <>
    <CardHeader avatar={icon} title={displayTitle} action={action} titleTypographyProps={{ variant: "subtitle2" }} />
    {!hideDivider && <Divider />}
  </>
}
Example #12
Source File: StatDisplayComponent.tsx    From genshin-optimizer with MIT License 6 votes vote down vote up
function Section({ displayNs, sectionKey }: { displayNs: DisplaySub<NodeDisplay>, sectionKey: string }) {
  const { data, oldData } = useContext(DataContext)
  const header = usePromise(getDisplayHeader(data, sectionKey), [data, sectionKey])
  const displayNsReads = useMemo(() => objectMap(displayNs, (n, nodeKey) => customRead(["display", sectionKey, nodeKey])), [displayNs, sectionKey]);
  if (!header) return <CardDark></CardDark>

  const { title, icon, action } = header
  return <CardDark >
    <CardHeader avatar={icon && <ImgIcon size={2} sx={{ m: -1 }} src={icon} />} title={title} action={action} titleTypographyProps={{ variant: "subtitle1" }} />
    <Divider />
    <CardContent>
      {Object.entries(displayNs).map(([nodeKey, n]) => <NodeFieldDisplay key={nodeKey} node={n} oldValue={oldData ? oldData.get(displayNsReads[nodeKey]!).value : undefined} />)}
    </CardContent>
  </CardDark>
}
Example #13
Source File: Profiler.tsx    From NekoMaid with MIT License 5 votes vote down vote up
Threads: React.FC = React.memo(() => {
  const plugin = usePlugin()
  const [id, setId] = useState(-1)
  const [threads, setThreads] = useState<GridRowData[]>([])
  useEffect(() => {
    plugin.emit('profiler:threads', (threads: any, id: number) => {
      setThreads(threads)
      setId(id)
    })
  }, [])

  const threadsColumns: GridColDef[] = [
    { field: 'id', headerName: 'PID', minWidth: 80 },
    {
      field: 'name',
      headerName: lang.profiler.threadName,
      minWidth: 200,
      flex: 1,
      renderCell: ({ value, row: { id: cid } }) => cid === id ? <Typography color='primary'>{lang.profiler.serverThread}</Typography> : value
    },
    {
      field: 'state',
      headerName: lang.profiler.state,
      width: 150,
      renderCell: ({ value, row: { lock } }) => {
        const text = (lang.profiler.threadState as any)[value as string] || value
        return lock ? <Link onClick={() => dialog({ content: lock, title: lang.profiler.lock, cancelButton: false })} underline='hover'>{text}</Link> : text
      }
    },
    {
      field: 'stack',
      headerName: lang.profiler.stack,
      width: 100,
      renderCell: ({ value: content }) => content && <IconButton
        size='small'
        onClick={() => dialog({ content, cancelButton: false, title: lang.profiler.stack })}
      ><ViewList /></IconButton>
    }
  ]

  return <Container maxWidth={false} sx={{ py: 3, '& .MuiDataGrid-root': { border: 'none' } }}>
    <Grid container spacing={3}>
      <Grid item xs={12}>
        <Card>
          <CardHeader title={lang.profiler.threads} />
          <Divider />
          <div style={{ height: '70vh', position: 'relative' }}>
            <CircularLoading loading={!threads.length} />
            <DataGrid
              disableColumnMenu
              disableSelectionOnClick
              rows={threads}
              columns={threadsColumns}
            />
          </div>
        </Card>
      </Grid>
    </Grid>
  </Container>
})
Example #14
Source File: Profiler.tsx    From NekoMaid with MIT License 5 votes vote down vote up
Heap: React.FC = React.memo(() => {
  const plugin = usePlugin()
  const [sortModel, setSortModel] = React.useState<GridSortItem[]>([{ field: 'size', sort: 'desc' }])
  const [heap, setHeap] = useState<GridRowData[]>([])
  const [fileMap, setFileMap] = useState<Record<string, string> | null>(null)
  useEffect(() => {
    plugin.emit('profiler:heap', (heap: any, map: any) => {
      const arr: GridRowData[] = []
      for (const id in heap) arr.push({ id: getClassName(id), count: heap[id][0], size: heap[id][1], display: prettyBytes(heap[id][1]) })
      setHeap(arr)
      console.log(map)
      setFileMap(map)
    })
  }, [])

  const heapColumns = useMemo(() => {
    const heapColumns: GridColDef[] = [
      { field: 'id', headerName: lang.profiler.className, minWidth: 250, flex: 0.6 },
      { field: 'count', headerName: lang.profiler.count, width: 100 },
      { field: 'size', headerName: lang.size, width: 100, valueFormatter: ({ row: { display } }) => display }
    ]
    if (fileMap) {
      heapColumns.splice(1, 0, {
        field: '_',
        headerName: lang.profiler.classLoader,
        minWidth: 150,
        flex: 0.4,
        valueFormatter: ({ row: { id } }) => fileMap[id as string] || ''
      })
    }
    return heapColumns
  }, [fileMap])

  return <Container maxWidth={false} sx={{ py: 3, '& .MuiDataGrid-root': { border: 'none' } }}>
    <Grid container spacing={3}>
      <Grid item xs={12}>
        <Card>
          <CardHeader title={lang.profiler.heap} />
          <Divider />
          <div style={{ height: '70vh', position: 'relative' }}>
            <CircularLoading loading={!heap.length} />
            <DataGrid
              disableColumnMenu
              disableSelectionOnClick
              rows={heap}
              columns={heapColumns}
              sortingOrder={['desc', 'asc']}
              sortModel={sortModel}
              onSortModelChange={setSortModel}
            />
          </div>
        </Card>
      </Grid>
    </Grid>
  </Container>
})
Example #15
Source File: Profiler.tsx    From NekoMaid with MIT License 5 votes vote down vote up
Entities: React.FC = React.memo(() => {
  const plugin = usePlugin()
  const [selectedEntityChunk, setSelectedEntityChunk] = React.useState<GridRowId | undefined>()
  const [selectedTileChunk, setSelectedTileChunk] = React.useState<GridRowId | undefined>()
  const [data, setData] = useState<[any[], any[], EntityData[], EntityData[]] | undefined>()
  useEffect(() => {
    plugin.emit('profiler:entities', (data: [Record<string, number>, Record<string, number>, EntityData[], EntityData[]]) => {
      data[2].forEach((it, i) => (it.id = i))
      data[3].forEach((it, i) => (it.id = i))
      setData([
        Object.entries(data[0]).map(([id, value]) => ({ value, name: minecraft['entity.minecraft.' + id.toLowerCase()] || id }))
          .sort((a, b) => b.value - a.value),
        Object.entries(data[1]).map(([id, value]) => ({ value, name: minecraft['block.minecraft.' + id.toLowerCase()] || id }))
          .sort((a, b) => b.value - a.value),
        data[2], data[3]
      ])
    })
  }, [])

  const selectedEntityChunkData = useMemo(() => {
    const id = selectedEntityChunk as (number | undefined)
    if (id == null || !data) return null
    return Object.entries(data[2][id].data).map(([id, value]) => ({ value, name: minecraft['entity.minecraft.' + id.toLowerCase()] || id }))
      .sort((a, b) => b.value - a.value)
  }, [selectedEntityChunk, data])

  const selectedTileChunkData = useMemo(() => {
    const id = selectedTileChunk as (number | undefined)
    if (id == null || !data) return null
    return Object.entries(data[3][id].data).map(([id, value]) => ({ value, name: minecraft['block.minecraft.' + id.toLowerCase()] || id }))
      .sort((a, b) => b.value - a.value)
  }, [selectedTileChunk, data])

  const createGrid = (data: any, title: any, selection: GridRowId | undefined, onChange: (it: GridRowId | undefined) => void) =>
    <Grid item sm={6} xs={12}>
      <Card>
        <CardHeader title={title} />
        <Divider />
        <div style={{ height: '70vh', position: 'relative' }}>
          <DataGrid
            checkboxSelection
            disableColumnMenu
            disableSelectionOnClick
            disableDensitySelector
            rows={data}
            columns={entitiesColumns}
            onSelectionModelChange={it => onChange(it.find(a => a !== selection))}
            selectionModel={selection == null ? [] : [selection]}
          />
        </div>
      </Card>
    </Grid>

  return <Container
    maxWidth={false}
    sx={{ py: 3, '& .MuiDataGrid-root': { border: 'none' }, position: 'relative', height: data ? undefined : '80vh' }}
  >
    <CircularLoading loading={!data} background={false} />
    {data && <Grid container spacing={3}>
      {createGrid(data[2], lang.profiler.crowdEntities, selectedEntityChunk, setSelectedEntityChunk)}
      {createGrid(data[3], lang.profiler.crowdTiles, selectedTileChunk, setSelectedTileChunk)}
      {selectedEntityChunkData && <Pie title={lang.worlds.entities} data={selectedEntityChunkData} />}
      {selectedTileChunkData && <Pie title={lang.worlds.tiles} data={selectedTileChunkData} />}
      <Pie title={lang.profiler.globalEntities} data={data[0]} />
      <Pie title={lang.profiler.globalTiles} data={data[1]} />
    </Grid>}
  </Container>
})
Example #16
Source File: Profiler.tsx    From NekoMaid with MIT License 5 votes vote down vote up
Plugins: React.FC = React.memo(() => {
  const plugin = usePlugin()
  const [data, setData] = useState<[JSX.Element[], any[][]] | undefined>()
  useEffect(() => {
    const off = plugin.emit('profiler:fetchPlugins').on('profiler:plugins', (data: Record<string, [Record<string | number, [number, number]>]>) => {
      const pluginsTimes: any[][] = [[], [], []]
      const tree: [number, JSX.Element][] = []
      for (const name in data) {
        let totalTypesTime = 0
        let totalTypesCount = 0
        const subTrees: JSX.Element[] = []
        ;['events', 'tasks', 'commands'].forEach((type, i) => {
          const curKey = name + '/' + type
          const subTree: [number, JSX.Element][] = []
          const cur = data[name][i]
          let totalTime = 0
          let totalCount = 0
          for (const key in cur) {
            const [count, time] = cur[key]
            totalCount += count
            totalTypesCount += count
            totalTime += time
            totalTypesTime += time
            const key2 = `${curKey}/${key}`
            subTree.push([time, <TreeItem nodeId={key2} key={key2} label={getLabel(key, time, count)} />])
          }
          if (totalTime) pluginsTimes[i].push({ name, value: totalTime })
          if (subTree.length) {
            subTrees.push(<TreeItem nodeId={curKey} key={curKey} label={getLabel((lang.profiler as any)[type], totalTime, totalCount)}>
              {subTree.sort((a, b) => b[0] - a[0]).map(it => it[1])}
            </TreeItem>)
          }
        })
        if (totalTypesTime) {
          tree.push([totalTypesTime, <TreeItem
            nodeId={name}
            label={getLabel(name, totalTypesTime, totalTypesCount)}
            key={name}
          >{subTrees}</TreeItem>])
        }
      }
      setData([
        tree.sort((a, b) => b[0] - a[0]).map(it => it[1]),
        pluginsTimes.map(it => it.sort((a, b) => b.value - a.value))
      ])
    })
    return () => { off() }
  }, [])
  return <Container maxWidth={false} sx={{ py: 3, position: 'relative', height: data ? undefined : '80vh' }}>
    <CircularLoading loading={!data} background={false} />
    {data && <Grid container spacing={3}>
      <Grid item xs={12}>
        <Card>
          <CardHeader title={lang.profiler.pluginsTitle} sx={{ position: 'relative' }} />
          <Divider />
          {data[0].length
            ? <TreeView defaultCollapseIcon={<ExpandMore />} defaultExpandIcon={<ChevronRight />}>{data[0]}</TreeView>
            : <CardContent><Empty /></CardContent>}
        </Card>
      </Grid>
      <Pie title={lang.profiler.pluginsEventsTime} data={data[1][0]} formatter={nanoSecondFormatter} />
      <Pie title={lang.profiler.pluginsTasksTime} data={data[1][1]} formatter={nanoSecondFormatter} />
      <Pie title={lang.profiler.pluginsCommandsTime} data={data[1][2]} formatter={nanoSecondFormatter} />
    </Grid>}
  </Container>
})
Example #17
Source File: OpenInv.tsx    From NekoMaid with MIT License 5 votes vote down vote up
OpenInv: React.FC = () => {
  const plugin = usePlugin()
  const [inv, setInv] = useState<Array<Item | null>>([])
  const [ender, setEnder] = useState<Array<Item | null>>([])
  const { name: player } = useParams<{ name: string }>()
  useEffect(() => {
    if (player) plugin.emit('openInv:fetchInv', setInv, player).emit('openInv:fetchEnderChest', setEnder, player)
  }, [player])

  const mapToInv = (inv: Array<Item | null>, type: InvType) => {
    const update = (res: boolean) => {
      if (!res) failed()
      if (type === InvType.PLAYER) plugin.emit('openInv:fetchInv', setInv, player)
      else plugin.emit('openInv:fetchEnderChest', setEnder, player)
    }
    const updateWithAction = (res: boolean) => {
      action(res)
      if (type === InvType.PLAYER) plugin.emit('openInv:fetchInv', setInv, player)
      else plugin.emit('openInv:fetchEnderChest', setEnder, player)
    }
    return player
      ? inv.map((it, i) => <React.Fragment key={i}><ItemViewer
        item={it}
        data={{ type, solt: i, player }}
        onDrag={() => plugin.emit('openInv:set', update, type, player, i, null, -1)}
        onDrop={(item, obj) => plugin.emit('openInv:set', update, type,
          player, i, JSON.stringify(item), obj?.type === type && obj?.player === player ? obj.solt : -1)}
        onEdit={item => item !== false && plugin.emit('openInv:set', updateWithAction, type, player, i, item && JSON.stringify(item), -1)}
      />{!((i + 1) % 9) && <br />}</React.Fragment>)
      : <Empty title={lang.openInv.notSelected} />
  }

  return <Box sx={{ minHeight: '100%', py: 3 }}>
    <Toolbar />
    <Container maxWidth={false}>
      <Grid container spacing={3}>
        <Grid item lg={6} md={12} xl={6} xs={12}>
          <Card>
            <CardHeader
              title={lang.openInv.whosBackpack(player || minecraft['entity.minecraft.player'])}
              sx={{ position: 'relative' }}
              action={<IconButton
                size='small'
                disabled={!player}
                sx={cardActionStyles}
                onClick={() => {
                  success()
                  plugin.emit('openInv:fetchInv', setInv, player)
                }}
              ><Refresh /></IconButton>}
            />
            <Divider />
            <CardContent sx={{ whiteSpace: 'nowrap', overflowX: 'auto' }}>{mapToInv(inv, InvType.PLAYER)}</CardContent>
          </Card>
        </Grid>
        <Grid item lg={6} md={12} xl={6} xs={12}>
          <Card>
            <CardHeader
              title={lang.openInv.whosEnderChest(player || minecraft['entity.minecraft.player'])}
              sx={{ position: 'relative' }}
              action={<IconButton
                size='small'
                disabled={!player}
                sx={cardActionStyles}
                onClick={() => {
                  success()
                  plugin.emit('openInv:fetchEnderChest', setEnder, player)
                }}
              ><Refresh /></IconButton>}
            />
            <Divider />
            <CardContent sx={{ whiteSpace: 'nowrap', overflowX: 'auto' }}>{mapToInv(ender, InvType.ENDER_CHEST)}</CardContent>
          </Card>
        </Grid>
      </Grid>
    </Container>
  </Box>
}
Example #18
Source File: PlayerList.tsx    From NekoMaid with MIT License 5 votes vote down vote up
PlayerActions: React.FC = () => {
  const theme = useTheme()
  const ref = useRef<HTMLCanvasElement | null>(null)
  const globalData = useGlobalData()
  const { name } = useParams<{ name: string }>()
  useEffect(() => {
    if (!ref.current || !name) return
    const viewer = new FXAASkinViewer({
      canvas: ref.current!,
      height: 350,
      width: ref.current.clientWidth,
      skin: getSkin(globalData, name)
    })
    viewer.renderer.setClearColor(theme.palette.mode === 'dark' ? 0x2c2c2c : 0xffffff)

    const resize = () => ref.current && (viewer.width = ref.current.clientWidth)
    window.addEventListener('resize', resize)
    const control = createOrbitControls(viewer)
    control.enableRotate = true
    control.enablePan = control.enableZoom = false

    viewer.animations.add(WalkingAnimation)
    viewer.animations.add(RotatingAnimation)
    return () => {
      window.removeEventListener('resize', resize)
      viewer.dispose()
    }
  }, [ref.current, name, theme.palette.mode, globalData])

  return <Card>
    <CardHeader title={name ? lang.playerList.whosDetails(name) : lang.openInv.notSelected} />
    <Divider />
    <Box sx={{ position: 'relative', '& canvas': { width: '100%!important' } }}>
      {name
        ? <canvas
          ref={ref}
          height='400'
          style={{ cursor: 'grab', display: name ? undefined : 'none' }}
        />
        : <CardContent><Empty title={null} /></CardContent>}
    </Box>
    <PlayerInfo name={name} />
  </Card>
}
Example #19
Source File: VehCard.tsx    From mojito_pdm with Creative Commons Attribution Share Alike 4.0 International 5 votes vote down vote up
VehCard: React.FC<Car> = ({
    name,
    brand,
    description,
    brandLogo,
    image,
    price,
    category,
    spawncode,
    trunkspace,
    performance
}) => {
    const theme = useTheme();
    const [open, setOpen] = useState(false)
    const testDrive = async () => {
        await fetchNui("test_drive", {vehicle: spawncode})
    }

    // Functions
    const handleOpen = () => setOpen(true)
    const handleClose = () => setOpen(false)

    return (
        <Card sx={{
            boxShadow: theme.shadows[3],
            margin: theme.spacing(2)
        }} variant="outlined">
            <CardHeader
                avatar={<img height={40} style={{maxWidth: 100, maxHeight: 40}} src={brandLogo} alt={brand}/>}
                title={name}
                subheader={category}
            />
            <CardMedia style={{height: "150px"}} image={image}/>
            <CardActions>
                <Tooltip TransitionComponent={Zoom} sx={{maxWidth: 120}} arrow title="Test drive this vehicle">
                    <Button
                        size="small"
                        variant={theme.palette.mode === "dark" ? "contained" : "outlined"}
                        color="primary"
                        startIcon={<DirectionsCarIcon/>}
                        onClick={testDrive}
                        disableElevation
                    >
                        TEST DRIVE
                    </Button>
                </Tooltip>
                <Tooltip TransitionComponent={Zoom} arrow sx={{maxWidth: 120}}
                         title="View more information about this vehicle">
                    <Button
                        size="small"
                        variant={theme.palette.mode === "dark" ? "contained" : "outlined"}
                        color="primary"
                        startIcon={<AssignmentIcon/>}
                        onClick={handleOpen}
                        disableElevation
                    >
                        MORE INFO
                    </Button>
                </Tooltip>
                <Modal
                    open={open}
                    onClose={handleClose}
                >
                    {<ModalBody
                        name={name}
                        brand={brand}
                        description={description}
                        price={price}
                        trunkspace={trunkspace}
                        setOpen={setOpen}
                        performance={performance}
                        spawncode={spawncode}
                    />}
                </Modal>
            </CardActions>
        </Card>
    )
}
Example #20
Source File: Dashboard.tsx    From NekoMaid with MIT License 5 votes vote down vote up
Charts: React.FC<{ data: Status[] }> = React.memo(props => {
  const theme = useTheme()
  const labels: string[] = []
  const data: any = config.map(it => ({ name: it[0], data: [] as number[], type: it[1], smooth: true, yAxisIndex: it[2] }))
  props.data.forEach(it => {
    const time = new Date(it.time)
    labels.push(`${time.getHours().toString().padStart(2, '0')}:${time.getMinutes().toString().padStart(2, '0')}`)
    data[0].data.push(it.players)
    data[1].data.push(it.tps.toFixed(2))
    data[2].data.push(it.chunks)
    data[3].data.push(it.entities)
  })

  return <Card>
    <CardHeader title={lang.dashboard.title} />
    <Divider />
    <CardContent>
      <Box sx={{ position: 'relative' }}>
        <ReactECharts theme={theme.palette.mode === 'dark' ? 'dark' : undefined} option={{
          backgroundColor: 'rgba(0, 0, 0, 0)',
          tooltip: { trigger: 'axis' },
          legend: { data: config.map(it => it[0]) },
          xAxis: {
            type: 'category',
            data: labels
          },
          grid: {
            top: '11%',
            left: '3%',
            right: '0%',
            bottom: '0%',
            containLabel: true
          },
          yAxis: [
            {
              type: 'value',
              name: config[0][0],
              position: 'left',
              offset: 35,
              minInterval: 1
            },
            {
              type: 'value',
              name: 'TPS',
              position: 'left',
              min: 0,
              max: 20
            },
            {
              type: 'value',
              name: config[2][0] + '/' + config[3][0],
              position: 'right',
              minInterval: 1
            }
          ],
          series: data
        }} />
      </Box>
    </CardContent>
  </Card>
})
Example #21
Source File: Dashboard.tsx    From NekoMaid with MIT License 5 votes vote down vote up
Players: React.FC<{ players?: CurrentStatus['players'] }> = React.memo(({ players }) => {
  const his = useHistory()
  const plugin = usePlugin()
  const globalData = useGlobalData()
  const [page, setPage] = useState(1)
  const [id, update] = useState(0)
  return <Card>
    <CardHeader title={lang.dashboard.onlinePlayers} />
    <Divider />
    <CardContent>
      {players?.length === 0
        ? <Empty />
        : <>
        <List sx={{ paddingTop: 0 }}>
          {players
            ? players.slice((page - 1) * 8, page * 8).map(p => {
              const name = typeof p === 'string' ? p : p.name
              return <Tooltip key={name} title={'IP: ' + ((p as any).ip || lang.unknown)}>
                <ListItem
                  secondaryAction={<>
                    <IconButton
                      edge='end'
                      size='small'
                      onClick={() => dialog(lang.dashboard.confirmKick(<span className='bold'>{name}</span>), lang.reason)
                        .then(it => it != null && plugin.emit('dashboard:kick', (res: boolean) => {
                          action(res)
                          if (!players) return
                          players.splice(players.indexOf(it!), 1)
                          update(id + 1)
                        }, name, it || null))
                      }
                    ><ExitToApp /></IconButton>
                    <IconButton edge='end' onClick={() => his.push('/NekoMaid/playerList/' + name)} size='small'><MoreHoriz /></IconButton>
                  </>
                  }
                >
                  <ListItemAvatar>
                    <Avatar
                      src={getSkin(globalData, name, true)}
                      imgProps={{ crossOrigin: 'anonymous', onClick () { his.push('/NekoMaid/playerList/' + name) }, style: { width: 40, height: 40 } }}
                      sx={{ cursor: 'pointer' }}
                      variant='rounded'
                    />
                  </ListItemAvatar>
                  <ListItemText primary={name} />
                </ListItem>
              </Tooltip>
            })
            : <LoadingList />
          }
        </List>
        {players && <Pagination
          page={page}
          onChange={(_, it) => setPage(it)}
          count={Math.max(Math.ceil(players.length / 8), 1)}
          sx={{ display: 'flex', justifyContent: 'flex-end' }}
        />}
      </>}
    </CardContent>
  </Card>
})
Example #22
Source File: Profiler.tsx    From NekoMaid with MIT License 4 votes vote down vote up
GarbageCollection: React.FC = React.memo(() => {
  const plugin = usePlugin()
  const theme = useTheme()
  const [data, setData] = useState<GCInfo[]>([])
  const [id, setId] = useState(0)
  useEffect(() => {
    const off = plugin.on('profiler:gc', (it: GCInfo) => {
      setData(old => {
        let len = old.length
        if (len > 15) old.shift()
        else len++
        it.start = getCurrentTime()
        return [it, ...old]
      })
    })
    return () => { off() }
  }, [])

  return <Container maxWidth={false} sx={{ py: 3 }}>
    <Grid container spacing={3}>
      <Grid item xs={12} lg={4}>
        <Card>
          <CardHeader title={lang.profiler.carbageCollection} />
          <Divider />
          {data.length
            ? <List dense>
              {data.map((it, i) => <ListItem key={it.id} secondaryAction={<Checkbox
                edge='start'
                checked={i === id}
                tabIndex={-1}
                onClick={() => setId(i)}
              />}>
                <ListItemText
                  primary={<>[{it.start}] <span className='bold'>{it.name}</span></>}
                  secondary={<>
                    <Typography component='span' variant='body2' color='text.primary'>{lang.profiler.costTime}: </Typography>{formatMS(it.duration)}
                    <br /><Typography component='span' variant='body2' color='text.primary'>{lang.cause}: </Typography>{it.cause}<br />
                    <Typography component='span' variant='body2' color='text.primary'>{lang.profiler.action}: </Typography>{it.action}
                  </>}
                />
              </ListItem>)}
            </List>
            : <CardContent><Empty /></CardContent>}
        </Card>
      </Grid>
      {data[id] && <Grid item xs={12} lg={8}>
        <Card>
          <CardHeader title={lang.profiler.memoryUsage} sx={{ position: 'relative' }} />
          <Divider />
          <ReactECharts style={{ height: '80vh' }} theme={theme.palette.mode === 'dark' ? 'dark' : undefined} option={{
            backgroundColor: 'rgba(0, 0, 0, 0)',
            legend: { data: lang.profiler.memoryUsageTypes },
            itemStyle: {
              borderRadius: 5,
              borderColor: 'rgba(0, 0, 0, 0)',
              borderWidth: 4
            },
            tooltip: {
              trigger: 'axis',
              formatter: bytesMap
            },
            xAxis: {
              type: 'value',
              boundaryGap: [0, 0.01]
            },
            yAxis: {
              type: 'category',
              data: Object.keys(data[id].after)
            },
            grid: {
              left: '3%',
              right: '4%',
              bottom: '3%',
              containLabel: true
            },
            series: [
              {
                name: lang.profiler.memoryUsageTypes[0],
                type: 'bar',
                data: Object.values(data[id].after).map(origin => ({ origin, value: (origin / MB).toFixed(2) }))
              },
              {
                name: lang.profiler.memoryUsageTypes[1],
                type: 'bar',
                data: Object.values(data[id].before).map(origin => ({ origin, value: (origin / MB).toFixed(2) }))
              }
            ]
          }} />
        </Card>
      </Grid>}
    </Grid>
  </Container>
})
Example #23
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 #24
Source File: WeaponEditor.tsx    From genshin-optimizer with MIT License 4 votes vote down vote up
export default function WeaponEditor({
  weaponId: propWeaponId,
  footer = false,
  onClose,
  extraButtons
}: WeaponStatsEditorCardProps) {
  const { t } = useTranslation("ui")
  const { data } = useContext(DataContext)

  const { database } = useContext(DatabaseContext)
  const weapon = useWeapon(propWeaponId)
  const { key = "", level = 0, refinement = 0, ascension = 0, lock, location = "", id } = weapon ?? {}
  const weaponSheet = usePromise(WeaponSheet.get(key), [key])

  const weaponDispatch = useCallback((newWeapon: Partial<ICachedWeapon>) => {
    database.updateWeapon(newWeapon, propWeaponId)
  }, [propWeaponId, database])

  const setLevel = useCallback(level => {
    level = clamp(level, 1, 90)
    const ascension = ascensionMaxLevel.findIndex(ascenML => level <= ascenML)
    weaponDispatch({ level, ascension })
  }, [weaponDispatch])

  const setAscension = useCallback(() => {
    const lowerAscension = ascensionMaxLevel.findIndex(ascenML => level !== 90 && level === ascenML)
    if (ascension === lowerAscension) weaponDispatch({ ascension: ascension + 1 })
    else weaponDispatch({ ascension: lowerAscension })
  }, [weaponDispatch, ascension, level])

  const characterSheet = usePromise(location ? CharacterSheet.get(location) : undefined, [location])
  const weaponFilter = characterSheet ? (ws) => ws.weaponType === characterSheet.weaponTypeKey : undefined
  const initialWeaponFilter = characterSheet && characterSheet.weaponTypeKey

  const equipOnChar = useCallback((charKey: CharacterKey | "") => id && database.setWeaponLocation(id, charKey), [database, id])
  const filter = useCallback(
    (cs: CharacterSheet) => cs.weaponTypeKey === weaponSheet?.weaponType,
    [weaponSheet],
  )

  const [showModal, setshowModal] = useState(false)
  const img = ascension < 2 ? weaponSheet?.img : weaponSheet?.imgAwaken

  //check the levels when switching from a 5* to a 1*, for example.
  useEffect(() => {
    if (!weaponSheet || !weaponDispatch || weaponSheet.key !== weapon?.key) return
    if (weaponSheet.rarity <= 2 && (level > 70 || ascension > 4)) {
      const [level, ascension] = lowRarityMilestoneLevels[0]
      weaponDispatch({ level, ascension })
    }
  }, [weaponSheet, weapon, weaponDispatch, level, ascension])


  const weaponUIData = useMemo(() => weaponSheet && weapon && computeUIData([weaponSheet.data, dataObjForWeapon(weapon)]), [weaponSheet, weapon])
  return <ModalWrapper open={!!propWeaponId} onClose={onClose} containerProps={{ maxWidth: "md" }}><CardLight>
    <WeaponSelectionModal show={showModal} onHide={() => setshowModal(false)} onSelect={k => weaponDispatch({ key: k })} filter={weaponFilter} weaponFilter={initialWeaponFilter} />
    <CardContent >
      {weaponSheet && weaponUIData && <Grid container spacing={1.5}>
        <Grid item xs={12} sm={3}>
          <Grid container spacing={1.5}>
            <Grid item xs={6} sm={12}>
              <Box component="img" src={img} className={`grad-${weaponSheet.rarity}star`} sx={{ maxWidth: 256, width: "100%", height: "auto", borderRadius: 1 }} />
            </Grid>
            <Grid item xs={6} sm={12}>
              <Typography><small>{weaponSheet.description}</small></Typography>
            </Grid>
          </Grid>
        </Grid>
        <Grid item xs={12} sm={9} sx={{ display: "flex", flexDirection: "column", gap: 1 }}>
          <Box display="flex" gap={1} flexWrap="wrap" justifyContent="space-between">
            <ButtonGroup>
              <Button onClick={() => setshowModal(true)} >{weaponSheet?.name ?? "Select a Weapon"}</Button>
              {weaponSheet?.hasRefinement && <DropdownButton title={`Refinement ${refinement}`}>
                <MenuItem>Select Weapon Refinement</MenuItem>
                <Divider />
                {[...Array(5).keys()].map(key =>
                  <MenuItem key={key} onClick={() => weaponDispatch({ refinement: key + 1 })} selected={refinement === (key + 1)} disabled={refinement === (key + 1)}>
                    {`Refinement ${key + 1}`}
                  </MenuItem>)}
              </DropdownButton>}
              {extraButtons}
            </ButtonGroup>
          </Box>
          <Box display="flex" gap={1} flexWrap="wrap" justifyContent="space-between">
            <ButtonGroup sx={{ bgcolor: t => t.palette.contentLight.main }} >
              <CustomNumberInputButtonGroupWrapper >
                <CustomNumberInput onChange={setLevel} value={level}
                  startAdornment="Lv. "
                  inputProps={{ min: 1, max: 90, sx: { textAlign: "center" } }}
                  sx={{ width: "100%", height: "100%", pl: 2 }}
                />
              </CustomNumberInputButtonGroupWrapper>
              {weaponSheet && <Button sx={{ pl: 1 }} disabled={!weaponSheet.ambiguousLevel(level)} onClick={setAscension}><strong>/ {ascensionMaxLevel[ascension]}</strong></Button>}
              {weaponSheet && <DropdownButton title={"Select Level"} >
                {weaponSheet.milestoneLevels.map(([lv, as]) => {
                  const sameLevel = lv === ascensionMaxLevel[as]
                  const lvlstr = sameLevel ? `Lv. ${lv}` : `Lv. ${lv}/${ascensionMaxLevel[as]}`
                  const selected = lv === level && as === ascension
                  return <MenuItem key={`${lv}/${as}`} selected={selected} disabled={selected} onClick={() => weaponDispatch({ level: lv, ascension: as })}>{lvlstr}</MenuItem>
                })}
              </DropdownButton>}
            </ButtonGroup>

            <Button color="error" onClick={() => id && database.updateWeapon({ lock: !lock }, id)} startIcon={lock ? <Lock /> : <LockOpen />}>
              {lock ? "Locked" : "Unlocked"}
            </Button>
          </Box>
          <Typography><Stars stars={weaponSheet.rarity} /></Typography>
          <Typography variant="subtitle1"><strong>{weaponSheet.passiveName}</strong></Typography>
          <Typography gutterBottom>{weaponSheet.passiveName && weaponSheet.passiveDescription(weaponUIData.get(input.weapon.refineIndex).value)}</Typography>
          <Box display="flex" flexDirection="column" gap={1}>
            <CardDark >
              <CardHeader title={"Main Stats"} titleTypographyProps={{ variant: "subtitle2" }} />
              <Divider />
              <FieldDisplayList>
                {[input.weapon.main, input.weapon.sub, input.weapon.sub2].map((node, i) => {
                  const n = weaponUIData.get(node)
                  if (n.isEmpty || !n.value) return null
                  return <NodeFieldDisplay key={n.info.key} node={n} component={ListItem} />
                })}
              </FieldDisplayList>
            </CardDark>
            {data && weaponSheet.document && <DocumentDisplay sections={weaponSheet.document} />}
          </Box>
        </Grid>
      </Grid>}
    </CardContent>
    {footer && id && <CardContent sx={{ py: 1 }}>
      <Grid container spacing={1}>
        <Grid item flexGrow={1}>
          <CharacterAutocomplete showDefault defaultIcon={<BusinessCenter />} defaultText={t("inventory")} value={location} onChange={equipOnChar} filter={filter} />
        </Grid>
        {!!onClose && <Grid item><CloseButton sx={{ height: "100%" }} large onClick={onClose} /></Grid>}
      </Grid>
    </CardContent>}
  </CardLight ></ModalWrapper>
}
Example #25
Source File: CommonCard.tsx    From console with GNU Affero General Public License v3.0 4 votes vote down vote up
CommonCard = ({
  title,
  metricValue,
  metricUnit,
  subMessage,
  moreLink,
  rightComponent,
  extraMargin = false,
  classes,
}: ICommonCard) => {
  const subStyles = cardSubStyles();
  const SubHeader = () => {
    return (
      <Fragment>
        <div className={classes.subHearderContainer}>
          <div className={classes.leftSide}>
            <div>
              <span className={classes.metricText}>
                {metricValue}
                <span className={classes.unitText}>{metricUnit}</span>
              </span>
            </div>
            {subMessage && (
              <div
                className={`${classes.subMessage} ${
                  subMessage.fontWeight ? subMessage.fontWeight : ""
                }`}
              >
                {subMessage.message}
              </div>
            )}
          </div>
          <div className={classes.rightSide}>{rightComponent}</div>
        </div>
      </Fragment>
    );
  };

  const Header = () => {
    return (
      <Fragment>
        <div className={classes.headerContainer}>
          <span className={classes.title}>{title}</span>
          {moreLink && (
            <Fragment>
              <span className={classes.viewAll}>
                <Link to={moreLink}>View All</Link>
              </span>
            </Fragment>
          )}
        </div>
      </Fragment>
    );
  };

  return (
    <Fragment>
      <Card
        className={`${classes.cardRoot} ${
          extraMargin ? classes.extraMargin : ""
        }`}
      >
        {metricValue !== "" && (
          <CardHeader
            title={<Header />}
            subheader={
              <Fragment>
                <SubHeader />
              </Fragment>
            }
            classes={{
              root: subStyles.root,
              title: subStyles.title,
              content: subStyles.content,
            }}
          />
        )}
      </Card>
    </Fragment>
  );
}
Example #26
Source File: Worlds.tsx    From NekoMaid with MIT License 4 votes vote down vote up
Worlds: React.FC = () => {
  const plugin = usePlugin()
  const globalData = useGlobalData()
  const [worlds, setWorlds] = useState<World[]>([])
  const [selected, setSelected] = useState('')
  const [open, setOpen] = useState(false)
  const update = () => plugin.emit('worlds:fetch', (data: World[]) => {
    setWorlds(data)
    if (data.length) setSelected(old => data.some(it => it.id === old) ? old : '')
  })
  useEffect(() => {
    const offUpdate = plugin.on('worlds:update', update)
    update()
    return () => { offUpdate() }
  }, [])
  const sw = worlds.find(it => it.id === selected)
  const getSwitch = (name: string, configId = name) => sw
    ? <ListItem
      secondaryAction={<Switch disabled={!globalData.hasMultiverse} checked={(sw as any)[name]}
      onChange={e => {
        plugin.emit('worlds:set', sw.id, configId, e.target.checked.toString())
        success()
      }}
    />}><ListItemText primary={(lang.worlds as any)[name]} /></ListItem>
    : null

  return <Box sx={{ minHeight: '100%', py: 3 }}>
    <Toolbar />
    <Container maxWidth={false}>
      <Grid container spacing={3}>
        <Grid item lg={8} md={12} xl={9} xs={12}>
        <Card>
          <CardHeader title={lang.worlds.title} />
          <Divider />
          <Box sx={{ position: 'relative' }}>
            <TableContainer>
              <Table>
                <TableHead>
                  <TableRow>
                    <TableCell padding='checkbox' />
                    <TableCell>{lang.worlds.name}</TableCell>
                    {globalData.hasMultiverse && <TableCell>{lang.worlds.alias}</TableCell>}
                    <TableCell>{lang.worlds.players}</TableCell>
                    <TableCell>{lang.worlds.chunks}</TableCell>
                    <TableCell>{lang.worlds.entities}</TableCell>
                    <TableCell>{lang.worlds.tiles}</TableCell>
                    <TableCell>{lang.worlds.time}</TableCell>
                    <TableCell>{lang.worlds.weather}</TableCell>
                  </TableRow>
                </TableHead>
                <TableBody>
                  {worlds.map(it => <TableRow key={it.id}>
                    <TableCell padding='checkbox'><Checkbox checked={selected === it.id} onClick={() => setSelected(it.id)} /></TableCell>
                    <TableCell><Tooltip title={it.id}><span>{it.name}</span></Tooltip></TableCell>
                    {globalData.hasMultiverse && <TableCell>{it.alias}
                      <IconButton size='small' onClick={() => dialog(lang.inputValue, lang.worlds.alias).then(res => {
                        if (res == null) return
                        plugin.emit('worlds:set', it.id, 'alias', res)
                        success()
                      })}><Edit fontSize='small' /></IconButton>
                      </TableCell>}
                    <TableCell>{it.players}</TableCell>
                    <TableCell>{it.chunks}</TableCell>
                    <TableCell>{it.entities}</TableCell>
                    <TableCell>{it.tiles}</TableCell>
                    <TableCell><Countdown time={it.time} max={24000} interval={50} /></TableCell>
                    <TableCell><IconButton size='small' onClick={() => {
                      plugin.emit('worlds:weather', it.id)
                      success()
                    }}>
                      {React.createElement((it.weather === 1 ? WeatherRainy : it.weather === 2 ? WeatherLightningRainy : WbSunny) as any)}
                    </IconButton></TableCell>
                  </TableRow>)}
                </TableBody>
              </Table>
            </TableContainer>
          </Box>
        </Card>
        </Grid>
        <Grid item lg={4} md={6} xl={3} xs={12}>
          <Card>
            <CardHeader
              title={lang.operations}
              sx={{ position: 'relative' }}
              action={<Tooltip title={lang.worlds.save} placement='left'>
                <IconButton
                  size='small'
                  onClick={() => {
                    if (!sw) return
                    plugin.emit('worlds:save', sw.id)
                    success()
                  }}
                  sx={cardActionStyles}
                ><Save /></IconButton>
              </Tooltip>}
            />
            <Divider />
            <Box sx={{ position: 'relative' }}>
              {sw
                ? <List sx={{ width: '100%' }} component='nav'>
                  <ListItem secondaryAction={<ToggleButtonGroup
                    exclusive
                    color='primary'
                    size='small'
                    value={sw.difficulty}
                    onChange={(_, value) => {
                      plugin.emit('worlds:difficulty', sw.id, value)
                      success()
                    }}
                  >
                    {difficulties.map(it => <ToggleButton value={it.toUpperCase()} key={it}>{minecraft['options.difficulty.' + it]}</ToggleButton>)}
                  </ToggleButtonGroup>}><ListItemText primary={minecraft['options.difficulty']} /></ListItem>
                  <ListItem secondaryAction={<Switch checked={sw.pvp} onChange={e => {
                    plugin.emit('worlds:pvp', sw.id, e.target.checked)
                    success()
                  }} />}><ListItemText primary='PVP' /></ListItem>
                  {getSwitch('allowAnimals', 'spawning.animals.spawn')}
                  {getSwitch('allowMonsters', 'spawning.monsters.spawn')}
                  {globalData.hasMultiverse && <>
                    {getSwitch('allowFlight')}
                    {getSwitch('autoHeal')}
                    {getSwitch('hunger')}
                  </>}
                  <ListItem secondaryAction={globalData.canSetViewDistance
                    ? <IconButton
                      onClick={() => dialog({
                        content: lang.inputValue,
                        input: {
                          error: true,
                          type: 'number',
                          helperText: lang.invalidValue,
                          validator: (it: string) => /^\d+$/.test(it) && +it > 1 && +it < 33
                        }
                      }).then(res => {
                        if (!res) return
                        plugin.emit('worlds:viewDistance', sw.id, parseInt(res as any))
                        success()
                      })}
                    ><Edit /></IconButton>
                    : undefined}>
                    <ListItemText primary={lang.worlds.viewDistance + ': ' + sw.viewDistance} />
                  </ListItem>
                  <ListItem><ListItemText primary={minecraft['selectWorld.enterSeed']} secondary={sw.seed} /></ListItem>
                  <ListItemButton onClick={() => setOpen(!open)}>
                    <ListItemText primary={minecraft['selectWorld.gameRules']} />
                    {open ? <ExpandLess /> : <ExpandMore />}
                  </ListItemButton>
                  <Collapse in={open} timeout="auto" unmountOnExit>
                    <List component='div' dense disablePadding>
                      {sw.rules.map(([key, value]) => {
                        const isTrue = value === 'true'
                        const isBoolean = isTrue || value === 'false'
                        const isNumber = /^\d+$/.test(value)
                        return <ListItem
                          key={key}
                          sx={{ pl: 4 }}
                          secondaryAction={isBoolean
                            ? <Switch
                              checked={isTrue}
                              onChange={e => {
                                plugin.emit('worlds:rule', sw.id, key, e.target.checked.toString())
                                success()
                              }}
                            />
                            : <IconButton
                              onClick={() => dialog({
                                content: lang.inputValue,
                                input: isNumber
                                  ? {
                                      error: true,
                                      type: 'number',
                                      helperText: lang.invalidValue,
                                      validator: (it: string) => /^\d+$/.test(it)
                                    }
                                  : { }
                              }).then(res => {
                                if (res == null) return
                                plugin.emit('worlds:rule', sw.id, key, res)
                                success()
                              })}
                            ><Edit /></IconButton>}
                        >
                          <ListItemText primary={(minecraft['gamerule.' + key] || key) + (isBoolean ? '' : ': ' + value)} />
                        </ListItem>
                      })}
                    </List>
                  </Collapse>
                </List>
                : <CardContent><Empty /></CardContent>
              }
            </Box>
          </Card>
        </Grid>
      </Grid>
    </Container>
  </Box>
}
Example #27
Source File: Vault.tsx    From NekoMaid with MIT License 4 votes vote down vote up
Vault: React.FC = () => {
  const his = useHistory()
  const plugin = usePlugin()
  const globalData = useGlobalData()
  const { hasVaultPermission, hasVaultChat, vaultEconomy, hasVaultGroups } = globalData
  const [players, setPlayers] = useState<PlayerInfo[]>([])
  const [count, setCount] = useState(-1)
  const [page, setPage] = useState(0)
  const [sortModel, setSortModel] = useState<GridSortItem[]>([])
  const [groups, setGroups] = useState<GroupInfo[]>([])
  const [selectedId, setSelectedId] = useState<string | undefined>()
  const [selectedPlayer, setSelectedPlayer] = useState<string | undefined>()
  const [isGroup, setIsGroup] = useState(false)
  const balanceSort = sortModel[0]?.sort
  const refresh = (res?: boolean) => {
    if (res != null) action(res)
    setCount(-1)
    plugin.emit('vault:fetch', (a, b) => {
      setCount(a)
      setPlayers(b)
    }, page, balanceSort)
  }
  useEffect(refresh, [page, balanceSort])
  useEffect(() => { plugin.emit('vault:fetchGroups', setGroups) }, [])

  const columns: any[] = [
    {
      field: '',
      sortable: false,
      width: 60,
      renderCell: (it: GridCellParams) => <Avatar
        src={getSkin(globalData, it.id, true)}
        imgProps={{ crossOrigin: 'anonymous', onClick () { his.push('/NekoMaid/playerList/' + it.id) }, style: { width: 40, height: 40 } }}
        variant='rounded'
        sx={{ cursor: 'pointer' }}
      />
    },
    {
      field: 'id',
      headerName: lang.username,
      sortable: false,
      width: 200
    }
  ]
  const columns2: any[] = [
    {
      field: 'id',
      headerName: lang.vault.groupName,
      sortable: false,
      width: 160
    }
  ]

  if (hasVaultGroups) {
    columns.push({
      field: 'group',
      headerName: lang.vault.defaultGroup,
      width: 110,
      sortable: false
    })
  }
  if (hasVaultChat) {
    const a = {
      field: 'prefix',
      headerName: lang.vault.prefix,
      width: 110,
      editable: true,
      sortable: false
    }
    const b = {
      field: 'suffix',
      headerName: lang.vault.suffix,
      width: 110,
      editable: true,
      sortable: false
    }
    columns.push(a, b)
    columns2.push(a, b)
  }
  if (vaultEconomy) {
    columns.push({
      field: 'balance',
      headerName: lang.vault.balance,
      editable: true,
      width: 110,
      valueFormatter: ({ value }: any) => (value === 0 || value === 1 ? vaultEconomy.singular : vaultEconomy.plural) +
        (vaultEconomy.digits === -1 ? value : value.toFixed(vaultEconomy.digits))
    })
  }
  if (hasVaultPermission) {
    columns.push({
      field: '_',
      headerName: lang.operations,
      width: 88,
      sortable: false,
      renderCell: (it: GridCellParams) => <>
        {hasVaultGroups && <Tooltip title={lang.vault.managePermissionGroup}>
          <IconButton onClick={() => setSelectedPlayer(it.id as any)} size='small'><GroupsIcon /></IconButton>
        </Tooltip>}
        <Tooltip title={lang.vault.managePermission}><IconButton onClick={() => {
          setSelectedId(it.id as any)
          setIsGroup(false)
        }} size='small'><ListIcon /></IconButton></Tooltip>
      </>
    })
    if (hasVaultGroups) {
      columns2.push({
        field: '_',
        headerName: lang.operations,
        width: 66,
        sortable: false,
        renderCell: (it: GridCellParams) => <Tooltip title={lang.vault.managePermission}><IconButton onClick={() => {
          setSelectedId(it.id as any)
          setIsGroup(true)
        }} size='small'><ListIcon /></IconButton></Tooltip>
      })
    }
  }

  const playerList = <Card>
    <CardHeader
      title={lang.playerList.title}
      action={<IconButton onClick={() => dialog(lang.playerList.nameToSearch, lang.username).then(filter => {
        if (!filter) return refresh()
        setCount(-1)
        plugin.emit('vault:fetch', (a, b) => {
          setCount(a)
          setPlayers(b)
          success()
        }, page, sortModel.find(it => it.field === 'balance'), filter.toLowerCase())
      })}
    ><Search /></IconButton>} />
    <Divider />
    <div style={{ height: 594, width: '100%' }}>
      <DataGrid
        pagination
        disableColumnMenu
        hideFooterSelectedRowCount
        rows={players}
        columns={columns}
        pageSize={10}
        rowCount={count === -1 ? 0 : count}
        loading={count === -1}
        onPageChange={setPage}
        paginationMode='server'
        sortingMode='server'
        sortModel={sortModel}
        onSortModelChange={it => !isEqual(sortModel, it) && setSortModel(it)}
        onCellEditCommit={({ field, id, value }) => {
          let flag = false
          switch (field) {
            case 'balance':
              if (isNaN(+value!)) refresh()
              else plugin.emit('vault:setBalance', refresh, id, +value!)
              break
            case 'prefix': flag = true
            // eslint-disable-next-line no-fallthrough
            case 'suffix':
              plugin.emit('vault:setChat', refresh, id, false, flag, value || null)
          }
        }}
      />
    </div>
  </Card>

  return <Box sx={{ minHeight: '100%', py: 3, '& .MuiDataGrid-root': { border: 'none' } }}>
    <Toolbar />
    <Container maxWidth={false}>
      {hasVaultGroups
        ? <Grid container spacing={3}>
        <Grid item lg={8} md={12} xl={8} xs={12}>{playerList}</Grid>
        <Grid item lg={4} md={12} xl={4} xs={12}>
          <Card>
            <CardHeader title={lang.vault.permissionGroup} />
            <Divider />
            <div style={{ height: 594, width: '100%' }}>
              <DataGrid
                hideFooter
                disableColumnMenu
                rows={groups}
                columns={columns2}
                onCellEditCommit={({ field, id, value }) => {
                  let flag = false
                  switch (field) {
                    case 'prefix': flag = true
                    // eslint-disable-next-line no-fallthrough
                    case 'suffix':
                      plugin.emit('vault:setChat', (res: boolean) => {
                        action(res)
                        plugin.emit('vault:fetchGroups', setGroups)
                      }, id, true, flag, value || null)
                  }
                }}
              />
            </div>
          </Card>
        </Grid>
        </Grid>
        : playerList}
    </Container>
    <PermissionDialog plugin={plugin} id={selectedId} onClose={() => setSelectedId(undefined)} isGroup={isGroup} />
    {hasVaultGroups && <Groups plugin={plugin} id={selectedPlayer} onClose={() => setSelectedPlayer(undefined)} groups={groups} />}
  </Box>
}
Example #28
Source File: Scheduler.tsx    From NekoMaid with MIT License 4 votes vote down vote up
Scheduler: React.FC = () => {
  const plugin = usePlugin()
  const [id, setId] = useState(-1)
  let [tasks, setTasks] = useState<Task[]>([])
  const [name, setName] = useState('')
  const [cron, setCron] = useState('')
  const [values, setValues] = useState('')
  const [whenIdle, setWhenIdle] = useState(false)
  const [cronError, setCronError] = useState('')
  const save = () => plugin.emit('scheduler:update', (res: boolean) => {
    action(res)
    plugin.emit('scheduler:fetch', setTasks)
  }, JSON.stringify(tasks))
  useEffect(() => { plugin.emit('scheduler:fetch', setTasks) }, [])

  return <Box sx={{ minHeight: '100%', py: 3 }}>
    <Toolbar />
    <Container maxWidth={false}>
      <Grid container spacing={3}>
        <Grid item lg={4} md={12} xl={4} xs={12}>
          <Card>
            <CardHeader
              title={lang.scheduler.title}
              sx={{ position: 'relative' }}
              action={<IconButton
                size='small'
                onClick={() => {
                  const task = {
                    name: lang.scheduler.newTask,
                    cron: '*/1 * * * *',
                    enabled: true,
                    whenIdle: false,
                    values: ['/say Hello, %server_tps% (PlaceholderAPI)', 'This is a chat message']
                  }
                  setTasks([...tasks, task])
                  setId(tasks.length)
                  setCronError('')
                  setCron(task.cron)
                  setName(task.name)
                  setValues(task.values.join('\n'))
                  setWhenIdle(false)
                }}
                sx={cardActionStyles}
              ><Add /></IconButton>}
            />
            <Divider />
            {tasks.length
              ? <List
                sx={{ width: '100%' }}
                component='nav'
              >
                {tasks.map((it, i) => <ListItem
                  key={i}
                  disablePadding
                  secondaryAction={<IconButton
                    edge='end'
                    onClick={() => dialog(lang.scheduler.confirmDelete)
                      .then(it => {
                        if (it == null) return
                        setTasks((tasks = tasks.filter((_, id) => i !== id)))
                        save()
                      })}
                  ><Delete /></IconButton>}
                  sx={{ position: 'relative' }}
                >
                  <ListItemIcon sx={{ paddingLeft: 2, position: 'absolute' }}>
                    <Checkbox
                      edge='start'
                      checked={it.enabled}
                      tabIndex={-1}
                    />
                  </ListItemIcon>
                  <ListItemButton onClick={() => {
                    setId(i)
                    setCronError('')
                    setCron(tasks[i].cron)
                    setName(tasks[i].name)
                    setValues(tasks[i].values.join('\n'))
                    setWhenIdle(!!tasks[i].whenIdle)
                  }}><ListItemText inset primary={it.name} /></ListItemButton >
                </ListItem>)}
              </List>
              : <CardContent><Empty /></CardContent>}
          </Card>
        </Grid>
        <Grid item lg={8} md={12} xl={8} xs={12}>
          <Card>
            <CardHeader
              title={lang.scheduler.editor}
              sx={{ position: 'relative' }}
              action={<IconButton
                size='small'
                onClick={() => {
                  tasks[id].values = values.split('\n')
                  tasks[id].cron = cron
                  tasks[id].name = name
                  tasks[id].whenIdle = whenIdle
                  save()
                }}
                sx={cardActionStyles}
                disabled={!tasks[id] || !!cronError}
              ><Save /></IconButton>}
            />
            <Divider />
            <CardContent>
              {tasks[id]
                ? <>
                  <TextField
                    required
                    fullWidth
                    variant='standard'
                    label={lang.scheduler.name}
                    value={name}
                    onChange={e => setName(e.target.value)}
                  />
                  <TextField
                    fullWidth
                    multiline
                    rows={4}
                    value={values}
                    sx={{ marginTop: 3 }}
                    label={lang.scheduler.content}
                    onChange={e => setValues(e.target.value)}
                  />
                  <FormControlLabel
                    control={<Switch checked={whenIdle} />}
                    label={lang.scheduler.whenIdle}
                    onChange={(e: any) => setWhenIdle(e.target.checked)}
                  />
                </>
                : <Empty title={lang.scheduler.notSelected} />}
            </CardContent>
            {tasks[id] && <>
              <Divider textAlign='left'>{lang.scheduler.timer}</Divider>
              <CardContent>
                <Box sx={{
                  '& .MuiTextField-root': { backgroundColor: 'inherit!important' },
                  '& .MuiOutlinedInput-input': { color: 'inherit!important' },
                  '& .MuiTypography-h6': { color: theme => theme.palette.primary.main + '!important' }
                }}>
                  <Cron cron={cron} setCron={setCron} setCronError={setCronError} locale={currentLanguage as any} isAdmin />
                </Box>
              </CardContent>
            </>}
          </Card>
        </Grid>
      </Grid>
    </Container>
  </Box>
}
Example #29
Source File: BlockEditor.tsx    From NekoMaid with MIT License 4 votes vote down vote up
BlockEditor: React.FC = () => {
  const theme = useTheme()
  const plugin = usePlugin()
  const his = useHistory()
  const loc = useLocation()
  const globalData = useGlobalData()
  const drawerWidth = useDrawerWidth()
  const [block, setBlock] = useState<Block>()
  const [types, setTypes] = useState<string[]>([])
  const [worlds, setWorlds] = useState<string[]>([])
  const params = { world: '', x: 0, y: 0, z: 0 }
  if (loc.pathname.startsWith('/NekoMaid/block/')) {
    const arr = loc.pathname.split('/')
    if (arr.length > 6) {
      params.world = arr[3]
      params.x = +arr[4]
      params.y = +arr[5]
      params.z = +arr[6]
    } else his.push('/NekoMaid/block')
  }
  useEffect(() => {
    const off = plugin.emit('item:blocks', (types: string[], worlds: string[]) => {
      setTypes(types)
      setWorlds(worlds)
    })
      .on('block:select', (world, x, y, z) => his.push(`/NekoMaid/block/${world}/${x}/${y}/${z}`))
    return () => void off()
  }, [])
  const update = () => {
    if (params.world) {
      plugin.emit('block:fetch', (block: Block) => {
        if (!block) {
          failed()
          his.push('/NekoMaid/block')
          return
        }
        if (globalData.hasNBTAPI && block.nbt) block.nbt = stringify(parse(block.nbt), { pretty: true })
        setBlock(block)
      }, params.world, params.x, params.y, params.z)
    }
  }
  const updateWithAction = (res: boolean) => {
    action(res)
    update()
  }
  useEffect(update, [params.world, params.x, params.y, params.z])
  return <Box sx={{ minHeight: '100%', py: 3 }}>
    <Toolbar />
    <Container maxWidth={false}>
      <Grid container spacing={3} sx={{ width: { sm: `calc(100vw - ${drawerWidth}px - ${theme.spacing(3)})` } }}>
        <Grid item lg={6} md={12} xl={6} xs={12}>
          <Card sx={{ '& .CodeMirror-dialog, .CodeMirror-scrollbar-filler': { backgroundColor: theme.palette.background.paper + '!important' } }}>
            <CardHeader
              title={lang.blockEditor.title}
              sx={{ position: 'relative' }}
              action={<Box sx={cardActionStyles}>
                <IconButton
                  size='small'
                  disabled={!block || (!block.data && !block.nbt)}
                  onClick={() => block && plugin.emit('block:save', (res: boolean) => {
                    action(res)
                    update()
                  }, params.world, params.x, params.y, params.z, block.nbt || null, block.data || null)}
                ><Save /></IconButton>
                <IconButton
                  size='small'
                  disabled={!block}
                  onClick={() => {
                    update()
                    success()
                  }}
                ><Refresh /></IconButton>
              </Box>}
            />
            <Divider />
            {block
              ? <>
                <CardContent sx={{ display: 'flex', width: '100%', justifyContent: 'center' }}>
                  <ItemViewer item={{ type: block.type }} />
                  <Autocomplete
                    options={types}
                    sx={{ maxWidth: 300, marginLeft: 1, flexGrow: 1 }}
                    value={block.type}
                    onChange={(_, it) => it && plugin.emit('block:type', (res: boolean) => {
                      action(res)
                      update()
                    }, params.world, params.x, params.y, params.z, (block.type = it))}
                    getOptionLabel={it => {
                      const locatedName = getName(it.toLowerCase())
                      return (locatedName ? locatedName + ' ' : '') + it
                    }}
                    renderInput={(params) => <TextField {...params} label={lang.itemEditor.itemType} size='small' variant='standard' />}
                  />
                </CardContent>
                {block.data != null && <Accordion sx={{ '&::before': { opacity: '1!important' } }} disableGutters>
                  <AccordionSummary expandIcon={<ExpandMore />}><Typography>{lang.data}</Typography></AccordionSummary>
                  <AccordionDetails sx={{ padding: 0, '& .CodeMirror': { width: '100%', height: 350 } }}>
                    <UnControlled
                      value={block.data}
                      options={{
                        mode: 'javascript',
                        phrases: lang.codeMirrorPhrases,
                        theme: theme.palette.mode === 'dark' ? 'material' : 'one-light'
                      }}
                      onChange={(_: any, __: any, data: string) => (block.data = data)}
                    />
                  </AccordionDetails>
                </Accordion>}
                {block.nbt != null && <Accordion sx={{ '&::before': { opacity: '1!important', display: '!important' } }} disableGutters>
                  <AccordionSummary expandIcon={<ExpandMore />}><Typography>NBT</Typography></AccordionSummary>
                  <AccordionDetails sx={{ padding: 0, '& .CodeMirror': { width: '100%', height: 350 } }}>
                    <UnControlled
                      value={block.nbt}
                      options={{
                        mode: 'javascript',
                        phrases: lang.codeMirrorPhrases,
                        theme: theme.palette.mode === 'dark' ? 'material' : 'one-light'
                      }}
                      onChange={(_: any, __: any, data: string) => (block.nbt = data)}
                    />
                  </AccordionDetails>
                </Accordion>}
              </>
              : <CardContent>{worlds.length ? <BlockSelector worlds={worlds} /> : <Empty />}</CardContent>}
          </Card>
        </Grid>
        {block?.inventory?.length
          ? <Grid item lg={6} md={12} xl={6} xs={12}>
            <Card>
              <CardHeader
                title={minecraft[('container.' + block.inventoryType || '').toLowerCase()] || lang.blockEditor.container}
                sx={{ position: 'relative' }}
              />
              <Divider />
              <CardContent sx={{ whiteSpace: 'nowrap', overflowX: 'auto', textAlign: 'center' }}>
                {block.inventory.map((it, i) => <React.Fragment key={i}><ItemViewer
                  item={it}
                  data={{ type: InvType.BLOCK, solt: i, ...params }}
                  onDrag={() => plugin.emit('block:setItem', update, params.world, params.x, params.y, params.z, i, null, -1)}
                  onDrop={(item, obj) => plugin.emit('block:setItem', update, params.world, params.x, params.y, params.z, i,
                    JSON.stringify(item), compare(obj, params) ? obj.solt : -1)}
                  onEdit={item => item !== false && plugin.emit('block:setItem', updateWithAction, params.world, params.x, params.y,
                    params.z, i, item && JSON.stringify(item), -1)}
                />{!((i + 1) % 9) && <br />}</React.Fragment>)}
              </CardContent>
            </Card>
          </Grid>
          : undefined}
      </Grid>
    </Container>
  </Box>
}