@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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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>
}