@mui/material#ListItemIcon TypeScript Examples
The following examples show how to use
@mui/material#ListItemIcon.
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: AppNavbar.tsx From sapio-studio with Mozilla Public License 2.0 | 6 votes |
function SettingsMenuItem() {
const dispatch = useDispatch();
return (
<ListItem
disableGutters
button={false}
key={'settings'}
onClick={() => dispatch(switch_showing('Settings'))}
>
<ListItemIcon>
<SettingsIcon />
</ListItemIcon>
<ListItemText primary={'Settings'} />
</ListItem>
);
}
Example #2
Source File: OpenInv.tsx From NekoMaid with MIT License | 6 votes |
playerAction: ActionComponent = ({ onClose, player }) => {
const his = useHistory()
const globalData = useGlobalData()
return <MenuItem disabled={!player || (!globalData.hasOpenInv && !player.online)} onClick={() => {
onClose()
if (player) his.push('/NekoMaid/openInv/' + player.name)
}}>
<ListItemIcon><Backpack /></ListItemIcon>{lang.openInv.title}
</MenuItem>
}
Example #3
Source File: Vault.tsx From NekoMaid with MIT License | 6 votes |
Groups: React.FC<{ plugin: Plugin, id: string | undefined, onClose: () => void, groups: GroupInfo[] }> =
({ plugin, id, onClose, groups }) => {
const [loading, setLoading] = useState(true)
const [playerGroups, setPlayerGroups] = useState<Record<string, true>>({ })
const refresh = () => {
setLoading(true)
plugin.emit('vault:playerGroup', (res: string[]) => {
if (!res) return
const obj: Record<string, true> = { }
res.forEach(it => (obj[it] = true))
setPlayerGroups(obj)
setLoading(false)
}, id, null, 0)
}
useEffect(() => {
setPlayerGroups({})
if (!id) return
refresh()
}, [id])
return <Dialog onClose={onClose} open={!!id}>
<DialogTitle>{lang.vault.whosPermissionGroup(id!)}</DialogTitle>
<List sx={{ pt: 0 }}>
{groups.map(it => <ListItem onClick={() => { }} key={it.id}>
<ListItemIcon><Checkbox
tabIndex={-1}
disabled={loading}
checked={!!playerGroups[it.id]}
onChange={e => plugin.emit('vault:playerGroup', (res: boolean) => {
action(res)
refresh()
}, id, it.id, e.target.checked ? 1 : 2)}
/></ListItemIcon>
<ListItemText primary={it.id} />
</ListItem>)}
</List>
<DialogActions><Button onClick={onClose}>{minecraft['gui.back']}</Button></DialogActions>
</Dialog>
}
Example #4
Source File: CustomListItem.tsx From frontend with MIT License | 6 votes |
function CustomListItem({ label, icon, selected, ...props }: Props) {
return (
<ListItem disablePadding {...props}>
<ListItemButton selected={selected}>
<ListItemIcon
title={label}
sx={(theme) => ({
color: selected ? theme.palette.primary.main : theme.palette.action.active,
})}>
{icon}
</ListItemIcon>
{label && (
<ListItemText
primary={label}
primaryTypographyProps={{ color: selected ? 'primary' : undefined }}
/>
)}
</ListItemButton>
</ListItem>
)
}
Example #5
Source File: MenuItemWithImage.tsx From genshin-optimizer with MIT License | 6 votes |
export default function MenuItemWithImage({ value, image = "", text, theme, isSelected, addlElement, props }: MenuItemWithImageProps) {
return <MenuItem key={value} value={value} {...props}>
<ListItemIcon>{image}</ListItemIcon>
<ListItemText primaryTypographyProps={{ style: { fontWeight: isSelected ? theme.typography.fontWeightMedium : theme.typography.fontWeightRegular } }}>
{text}
</ListItemText>
{addlElement && addlElement}
</MenuItem>
}
Example #6
Source File: ArtifactSlotDropdown.tsx From genshin-optimizer with MIT License | 6 votes |
export default function ArtifactSlotDropdown({ slotKey = "", onChange, hasUnselect = false, ...props }: ArtifactSlotDropdownProps) {
const { t } = useTranslation(["artifact", "ui"]);
return <DropdownButton
title={slotKey ? t(`artifact:slotName:${slotKey}`) : t('artifact:slot')}
color={slotKey ? "success" : "primary"}
startIcon={slotKey ? artifactSlotIcon(slotKey) : undefined}
{...props}
>
{hasUnselect && <MenuItem selected={slotKey === ""} disabled={slotKey === ""} onClick={() => onChange("")} >
<ListItemIcon>
<Replay />
</ListItemIcon>
<ListItemText>
{t`ui:unselect`}
</ListItemText>
</MenuItem>}
{hasUnselect && <Divider />}
{allSlotKeys.map(key =>
<MenuItem key={key} selected={slotKey === key} disabled={slotKey === key} onClick={() => onChange(key)} >
<ListItemIcon>
{artifactSlotIcon(key)}
</ListItemIcon>
<ListItemText>
{t(`artifact:slotName:${key}`)}
</ListItemText>
</MenuItem>)}
</DropdownButton>
}
Example #7
Source File: list-item-link.tsx From example with MIT License | 6 votes |
export function ListItemLink(props: IListItemLinkProps) {
const { icon, primary, to } = props
const location = useLocation();
const renderLink = React.useMemo(
() =>
React.forwardRef(function Link(itemProps, ref) {
return <RouterLink to={to} ref={ref as any} {...itemProps} role={undefined} />
}),
[to],
)
return (
<li>
<ListItem
button
component={renderLink}
selected={
location.pathname === to ||
(props.default && (location.pathname === "" || location.pathname === "/"))
}>
{icon ? <ListItemIcon>{icon}</ListItemIcon> : null}
<ListItemText primary={primary} />
</ListItem>
</li>
)
}
Example #8
Source File: AppNavbar.tsx From sapio-studio with Mozilla Public License 2.0 | 6 votes |
function Simulator() {
const dispatch = useDispatch();
const simulateRef = React.useRef<HTMLLIElement>(null);
const [sim_open, setSimOpen] = React.useState(false);
return (
<div>
<ListItem
disableGutters
button={false}
key={'Simulate'}
onClick={() => setSimOpen(true)}
ref={simulateRef}
>
<ListItemIcon></ListItemIcon>
<ListItemText primary={'Simulate'} />
</ListItem>
<Menu
anchorEl={simulateRef.current}
anchorOrigin={{
vertical: 'center',
horizontal: 'right',
}}
keepMounted
open={sim_open}
onClose={() => setSimOpen(false)}
>
<MenuItem
onClick={() => {
setSimOpen(false);
dispatch(toggle_showing());
}}
>
Timing
</MenuItem>
</Menu>
</div>
);
}
Example #9
Source File: DesktopSideBar.tsx From Tachidesk-WebUI with Mozilla Public License 2.0 | 6 votes |
export default function DesktopSideBar({ navBarItems }: IProps) {
const location = useLocation();
const theme = useTheme();
const iconFor = (path: string, IconComponent: any, SelectedIconComponent: any) => {
if (location.pathname === path) return <SelectedIconComponent sx={{ color: 'primary.main' }} fontSize="large" />;
return <IconComponent sx={{ color: (theme.palette.mode === 'dark') ? 'grey.A400' : 'grey.600' }} fontSize="large" />;
};
return (
<SideNavBarContainer>
{
// eslint-disable-next-line react/destructuring-assignment
navBarItems.map(({
path, title, IconComponent, SelectedIconComponent,
}: NavbarItem) => (
<Link to={path} style={{ color: 'inherit', textDecoration: 'none' }} key={path}>
<ListItem disableRipple button key={title}>
<ListItemIcon sx={{ minWidth: '0' }}>
<Tooltip placement="right" title={title}>
{iconFor(path, IconComponent, SelectedIconComponent)}
</Tooltip>
</ListItemIcon>
</ListItem>
</Link>
))
}
</SideNavBarContainer>
);
}
Example #10
Source File: LibraryOptions.tsx From Tachidesk-WebUI with Mozilla Public License 2.0 | 6 votes |
function sortsTab(currentTab: number) {
const { options: { sorts, sortDesc }, setOption } = useLibraryOptionsContext();
const handleChange = (event: React.MouseEvent<HTMLDivElement, MouseEvent>, index: string) => {
if (sorts === index) {
setOption('sortDesc', (sortDes) => !sortDes);
} else {
setOption('sortDesc', false);
}
setOption('sorts', index);
};
return (
<>
<TabPanel index={1} currentIndex={currentTab}>
<Stack direction="column">
{
['sortToRead', 'sortAlph', 'sortID'].map((e) => {
let icon;
if (sorts === e) {
icon = !sortDesc ? (<ArrowUpwardIcon color="primary" />)
: (<ArrowDownwardIcon color="primary" />);
}
icon = icon === undefined && sortDesc === undefined && e === 'sortID' ? (<ArrowDownwardIcon color="primary" />) : icon;
return (
<ListItem disablePadding>
<ListItemButton onClick={(event) => handleChange(event, e)}>
<ListItemIcon>{icon}</ListItemIcon>
<ListItemText primary={e} />
</ListItemButton>
</ListItem>
);
})
}
</Stack>
</TabPanel>
</>
);
}
Example #11
Source File: Mobile.tsx From GTAV-NativeDB with MIT License | 6 votes |
function AppBarAction({ text, mobileIcon, buttonProps: { href, target, onClick, ...buttonProps } }: AppBarActionProps) {
const history = useHistory()
const handleClick = useCallback(() => {
if (href) {
if (href.includes('://') || target) {
window.open(href, target)
}
else {
history.push(href)
}
}
onClick && onClick()
}, [href, target, onClick, history])
return (
<MenuItem onClick={handleClick}>
{mobileIcon && <ListItemIcon>
{React.createElement(mobileIcon)}
</ListItemIcon>}
{text}
</MenuItem>
)
}
Example #12
Source File: index.tsx From Search-Next with GNU General Public License v3.0 | 6 votes |
MenuListItem: React.FC<MenuListItemProps> = ({
icon,
primary,
...props
}) => {
return (
<ListItem {...props}>
<ListItemIcon className="min-w-min mr-2">{icon}</ListItemIcon>
<ListItemText primary={primary} />
</ListItem>
);
}
Example #13
Source File: SortFilter.tsx From Tachidesk-WebUI with Mozilla Public License 2.0 | 5 votes |
export default function SortFilter(props: Props) {
const {
values,
name,
state,
position,
group,
updateFilterValue,
update,
} = props;
const [val, setval] = React.useState(state);
const [open, setOpen] = React.useState(false);
const handleClick = () => {
setOpen(!open);
};
if (values) {
const handleChange = (event:
React.MouseEvent<HTMLDivElement, MouseEvent>, index: number) => {
const tmp = val;
if (tmp.index === index) {
tmp.ascending = !tmp.ascending;
} else {
tmp.ascending = true;
}
tmp.index = index;
setval(tmp);
const upd = update.filter((e: {
position: number; group: number | undefined;
}) => !(position === e.position && group === e.group));
updateFilterValue([...upd, { position, state: JSON.stringify(tmp), group }]);
};
const ret = (
<FormControl fullWidth>
<ListItemButton onClick={handleClick}>
<ListItemText primary={name} />
{open ? <ExpandLess /> : <ExpandMore />}
</ListItemButton>
<Collapse in={open}>
<List>
{values.map((value: string, index: number) => {
let icon;
if (val.index === index) {
icon = val.ascending ? (<ArrowUpwardIcon color="primary" />)
: (<ArrowDownwardIcon color="primary" />);
}
return (
<ListItem disablePadding key={`${name} ${value}`}>
<ListItemButton
onClick={(event) => handleChange(event, index)}
>
<ListItemIcon>
{icon}
</ListItemIcon>
<ListItemText primary={value} />
</ListItemButton>
</ListItem>
);
})}
</List>
</Collapse>
</FormControl>
);
return (
<Box key={name} sx={{ display: 'flex', flexDirection: 'column', minWidth: 120 }}>
{ret}
</Box>
);
}
return (<></>);
}
Example #14
Source File: PlayBar.tsx From rewind with MIT License | 5 votes |
function MoreMenu() {
const [anchorEl, setAnchorEl] = useState(null);
const open = Boolean(anchorEl);
const handleClick = (event: any) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
const analyzer = useAnalysisApp();
const handleTakeScreenshot = () => {
analyzer.screenshotTaker.takeScreenshot();
handleClose();
};
const [helpOpen, setHelpOpen] = useState(false);
const handleOpenHelp = () => {
setHelpOpen(true);
handleClose();
};
return (
<>
<HelpModalDialog isOpen={helpOpen} onClose={() => setHelpOpen(false)} />
<IconButton
aria-label="more"
id="long-button"
aria-controls="long-menu"
// aria-expanded={open ? "true" : undefined}
aria-haspopup="true"
onClick={handleClick}
onFocus={ignoreFocus}
>
<MoreVert />
</IconButton>
<Menu
open={open}
onClose={handleClose}
anchorEl={anchorEl}
anchorOrigin={{
vertical: "top",
horizontal: "center",
}}
transformOrigin={{
vertical: "bottom",
horizontal: "center",
}}
>
<MenuItem onClick={handleTakeScreenshot}>
<ListItemIcon>
<PhotoCamera />
</ListItemIcon>
<ListItemText>Take Screenshot</ListItemText>
</MenuItem>
<MenuItem onClick={handleOpenHelp}>
<ListItemIcon>
<Help />
</ListItemIcon>
<ListItemText>Help</ListItemText>
</MenuItem>
</Menu>
</>
);
}
Example #15
Source File: AppNavbar.tsx From sapio-studio with Mozilla Public License 2.0 | 5 votes |
function MainScreens() {
const dispatch = useDispatch();
return (
<>
<ListItem
disableGutters
button={true}
key={'Wallet'}
onClick={() => dispatch(switch_showing('Wallet'))}
>
<ListItemIcon></ListItemIcon>
<ListItemText primary={'Wallet'} />
</ListItem>
<ListItem
disableGutters
button={true}
key={'Contract Creator'}
onClick={() => dispatch(switch_showing('ContractViewer'))}
>
<ListItemIcon></ListItemIcon>
<ListItemText primary={'Contract Creator'} />
</ListItem>
<ListItem
disableGutters
button
key={'Miniscript'}
onClick={async () => {
dispatch(switch_showing('MiniscriptCompiler'));
}}
>
<ListItemIcon></ListItemIcon>
<ListItemText primary={'Miniscript Testing'} />
</ListItem>
<ListItem
disableGutters
button={true}
key={'Chat'}
onClick={() => dispatch(switch_showing('Chat'))}
>
<ListItemIcon>
<Chat></Chat>
</ListItemIcon>
<ListItemText primary={'Chat'} />
</ListItem>
</>
);
}
Example #16
Source File: AppNavbar.tsx From sapio-studio with Mozilla Public License 2.0 | 5 votes |
function NodeMenu(props: { bitcoin_node_manager: BitcoinNodeManager }) {
const dispatch = useDispatch();
const nodeRef = React.useRef<HTMLLIElement>(null);
const [node_open, setNodeOpen] = React.useState(false);
const close = () => setNodeOpen(false);
return (
<>
<ListItem
disableGutters
button={false}
key={'Bitcoin Node'}
onClick={() => setNodeOpen(true)}
ref={nodeRef}
>
<ListItemIcon></ListItemIcon>
<ListItemText primary={'Bitcoin Node'} />
</ListItem>
<Menu
anchorEl={nodeRef.current}
anchorOrigin={{
vertical: 'center',
horizontal: 'right',
}}
keepMounted
open={node_open}
onClose={close}
>
<MenuItem
onClick={async () => {
close();
const addr =
await props.bitcoin_node_manager.get_new_address();
window.electron.write_clipboard(addr);
}}
>
Get New Address to Clipboard
</MenuItem>
<MenuItem
onClick={() => {
close();
props.bitcoin_node_manager
.generate_blocks(10)
.catch((err) => console.error(err));
}}
>
Generate 10 Blocks
</MenuItem>
<Divider />
<MenuItem
onClick={() => {
close();
dispatch(toggle_status_bar());
}}
>
Toggle Status
</MenuItem>
</Menu>
</>
);
}
Example #17
Source File: Header.tsx From genshin-optimizer with MIT License | 5 votes |
function MobileHeader({ anchor, currentTab }) {
const [mobileOpen, setMobileOpen] = useState(false);
const handleDrawerToggle = () => {
setMobileOpen(!mobileOpen);
};
const { t } = useTranslation("ui")
return <>
<AppBar position="fixed" sx={{ bgcolor: "#343a40" }} elevation={0} >
<Drawer
anchor="right"
variant="temporary"
open={mobileOpen}
onClose={handleDrawerToggle}
ModalProps={{
keepMounted: true, // Better open performance on mobile.
}}
>
<List>
<ListItemButton key="home" component={RouterLink} to={'/'} selected={currentTab === ""} disabled={currentTab === ""} onClick={handleDrawerToggle} >
<ListItemText>{t("pageTitle")}</ListItemText>
</ListItemButton >
{content.map(({ i18Key, value, to, svg }) =>
<ListItemButton key={value} component={RouterLink} to={to} selected={currentTab === value} disabled={currentTab === value} onClick={handleDrawerToggle} >
<ListItemIcon><FontAwesomeIcon icon={svg} /></ListItemIcon>
<ListItemText>{t(i18Key)}</ListItemText>
</ListItemButton >)}
</List>
<Divider />
<List>
{links.map(({ i18Key, href, svg, label }) =>
<ListItemButton key={label} component="a" href={href} target="_blank" onClick={e => ReactGA.outboundLink({ label }, () => { })} >
<ListItemIcon><FontAwesomeIcon icon={svg} /></ListItemIcon>
<ListItemText>{t(i18Key)}</ListItemText>
</ListItemButton >)}
</List>
</Drawer>
<Toolbar>
<Button variant="text" sx={{ color: "white" }} component={RouterLink} to="/">
<Typography variant="h6" noWrap component="div">
<Trans t={t} i18nKey="pageTitle">Genshin Optimizer</Trans>
</Typography>
</Button>
<Box flexGrow={1} />
<IconButton
color="inherit"
aria-label="open drawer"
edge="end"
onClick={handleDrawerToggle}
>
<MenuIcon />
</IconButton>
</Toolbar>
</AppBar>
{/* add a blank toolbar to keep space and provide a scroll anchor */}
<Toolbar id={anchor} />
</>
}
Example #18
Source File: HoverMenu.tsx From frontend with MIT License | 5 votes |
export default function HoverMenu({ menu, items, icon: Icon, isOpen }: Props) {
const router = useRouter()
const [anchorMenu, setAnchorMenu] = useState<null | HTMLElement>(null)
const isSelected = useMemo(
() => items.filter((item) => item.href !== '#' && router.asPath.includes(item.href)).length > 0,
[items],
)
const handleOpenMenu = (event: React.MouseEvent<HTMLElement>) => {
setAnchorMenu(event.currentTarget)
}
const handleCloseMenu = () => setAnchorMenu(null)
return (
<Root>
<ListItemButton
selected={isSelected}
onClick={handleOpenMenu}
sx={{ borderRadius: '0 25px 25px 0' }}>
<ListItemIcon
title={menu}
sx={(theme) => ({
minWidth: theme.spacing(4),
color: isSelected ? theme.palette.primary.main : theme.palette.action.active,
})}>
{<Icon />}
</ListItemIcon>
{isOpen && (
<ListItemText
primary={menu}
primaryTypographyProps={{ color: isSelected ? 'primary' : undefined }}
/>
)}
{isOpen && (
<ChevronRightIcon
color={
anchorMenu ? (isSelected ? 'primary' : 'action') : isSelected ? 'primary' : 'disabled'
}
/>
)}
</ListItemButton>
<Menu
keepMounted
id="menu-appbar"
anchorEl={anchorMenu}
onClose={handleCloseMenu}
open={Boolean(anchorMenu)}
className={isOpen ? classes.open : classes.close}
anchorOrigin={{ vertical: 'top', horizontal: 'left' }}
transformOrigin={{ vertical: 'center', horizontal: 'left' }}>
{items.map(({ label, icon: Icon, href }) => (
<CustomListItem
key={label}
sx={{ p: 0 }}
selected={href !== '#' && router.asPath.includes(href)}
icon={<Icon />}
label={label}
onClick={() => router.push(href)}
/>
))}
</Menu>
</Root>
)
}
Example #19
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 #20
Source File: AppNavbar.tsx From sapio-studio with Mozilla Public License 2.0 | 4 votes |
function ContractMenu(props: { relayout: () => void }) {
const dispatch = useDispatch();
const workspace = useSelector(selectWorkspace);
const contractRef = React.useRef<HTMLLIElement>(null);
const [contracts_open, setContractsOpen] = React.useState(false);
return (
<div>
<ListItem
disableGutters
button={false}
key={'Contract'}
onClick={() => setContractsOpen(true)}
ref={contractRef}
>
<ListItemIcon></ListItemIcon>
<ListItemText primary={'Contract'} />
</ListItem>
<Menu
anchorEl={contractRef.current}
anchorOrigin={{
vertical: 'center',
horizontal: 'right',
}}
keepMounted
open={contracts_open}
onClose={() => setContractsOpen(false)}
>
<MenuItem
onClick={() => {
setContractsOpen(false);
dispatch(open_modal('LoadHex'));
}}
>
Open Contract from Clipboard
</MenuItem>
<MenuItem
onClick={() => {
setContractsOpen(false);
dispatch(create_contract_from_file());
}}
>
Open Contract from File
</MenuItem>
<MenuItem
onClick={() => {
setContractsOpen(false);
dispatch(open_modal('SaveHex'));
}}
>
Save Contract
</MenuItem>
<MenuItem
onClick={() => {
setContractsOpen(false);
window.electron.sapio.load_wasm_plugin(workspace);
}}
>
Load WASM Plugin
</MenuItem>
<MenuItem
onClick={async () => {
setContractsOpen(false);
const apis =
await window.electron.sapio.load_contract_list(
workspace
);
if ('err' in apis) {
alert(apis.err);
return;
}
dispatch(set_apis(apis.ok));
dispatch(switch_showing('ContractCreator'));
}}
>
Create New Contract
</MenuItem>
<MenuItem
onClick={() => {
setContractsOpen(false);
dispatch(recreate_contract());
}}
>
Recreate Last Contract
</MenuItem>
<Divider />
<MenuItem
onClick={() => {
props.relayout();
}}
>
Repair Layout
</MenuItem>
</Menu>
</div>
);
}
Example #21
Source File: WalletMultiButton.tsx From wallet-adapter with Apache License 2.0 | 4 votes |
WalletMultiButton: FC<ButtonProps> = ({
color = 'primary',
variant = 'contained',
type = 'button',
children,
...props
}) => {
const { publicKey, wallet, disconnect } = useWallet();
const { setOpen } = useWalletDialog();
const [anchor, setAnchor] = useState<HTMLElement>();
const base58 = useMemo(() => publicKey?.toBase58(), [publicKey]);
const content = useMemo(() => {
if (children) return children;
if (!wallet || !base58) return null;
return base58.slice(0, 4) + '..' + base58.slice(-4);
}, [children, wallet, base58]);
if (!wallet) {
return (
<WalletDialogButton color={color} variant={variant} type={type} {...props}>
{children}
</WalletDialogButton>
);
}
if (!base58) {
return (
<WalletConnectButton color={color} variant={variant} type={type} {...props}>
{children}
</WalletConnectButton>
);
}
return (
<>
<Button
color={color}
variant={variant}
type={type}
startIcon={<WalletIcon wallet={wallet} />}
onClick={(event) => setAnchor(event.currentTarget)}
aria-controls="wallet-menu"
aria-haspopup="true"
{...props}
>
{content}
</Button>
<StyledMenu
id="wallet-menu"
anchorEl={anchor}
open={!!anchor}
onClose={() => setAnchor(undefined)}
marginThreshold={0}
TransitionComponent={Fade}
transitionDuration={250}
keepMounted
anchorOrigin={{
vertical: 'top',
horizontal: 'left',
}}
>
<WalletMenuItem onClick={() => setAnchor(undefined)}>
<Button
color={color}
variant={variant}
type={type}
startIcon={<WalletIcon wallet={wallet} />}
onClick={(event) => setAnchor(undefined)}
fullWidth
{...props}
>
{wallet.adapter.name}
</Button>
</WalletMenuItem>
<Collapse in={!!anchor}>
<WalletActionMenuItem
onClick={async () => {
setAnchor(undefined);
await navigator.clipboard.writeText(base58);
}}
>
<ListItemIcon>
<CopyIcon />
</ListItemIcon>
Copy address
</WalletActionMenuItem>
<WalletActionMenuItem
onClick={() => {
setAnchor(undefined);
setOpen(true);
}}
>
<ListItemIcon>
<SwitchIcon />
</ListItemIcon>
Change wallet
</WalletActionMenuItem>
<WalletActionMenuItem
onClick={() => {
setAnchor(undefined);
// eslint-disable-next-line @typescript-eslint/no-empty-function
disconnect().catch(() => {
// Silently catch because any errors are caught by the context `onError` handler
});
}}
>
<ListItemIcon>
<DisconnectIcon />
</ListItemIcon>
Disconnect
</WalletActionMenuItem>
</Collapse>
</StyledMenu>
</>
);
}
Example #22
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 #23
Source File: PlayerList.tsx From NekoMaid with MIT License | 4 votes |
PlayerInfo: React.FC<{ name?: string }> = React.memo(({ name }) => {
const plugin = usePlugin()
const globalData = useGlobalData()
const [open, setOpen] = useState(false)
const [info, setInfo] = useState<IPlayerInfo | undefined>()
const refresh = () => plugin.emit('playerList:query', setInfo, name)
useEffect(() => {
setInfo(undefined)
if (name) refresh()
}, [name])
return name && info
? <>
<Divider />
<List
sx={{ width: '100%' }}
component='nav'
subheader={<ListSubheader component='div' sx={{ backgroundColor: 'inherit' }}>{lang.playerList.details}</ListSubheader>}
>
<ListItem>
<ListItemIcon><AssignmentInd /></ListItemIcon>
<ListItemText primary={globalData.onlineMode
? <Link underline='hover' rel='noopener' target='_blank' href={'https://namemc.com/profile/' + info.id}>{info.id}</Link>
: info.id
} />
</ListItem>
{!info.hasPlayedBefore && <ListItem>
<ListItemIcon><ErrorOutline color='error' /></ListItemIcon>
<ListItemText primary={lang.playerList.hasNotPlayed} />
</ListItem>}
{info.ban != null && <ListItem>
<ListItemIcon><Block color='error' /></ListItemIcon>
<ListItemText primary={lang.playerList.banned + (info.ban ? ': ' + info.ban : '')} />
</ListItem>}
{info.whitelisted && <ListItem>
<ListItemIcon><Star color='warning' /></ListItemIcon>
<ListItemText primary={lang.playerList.whitelisted} />
</ListItem>}
{info.isOP && <ListItem>
<ListItemIcon><Security color='primary' /></ListItemIcon>
<ListItemText primary={lang.playerList.op} />
</ListItem>}
{info.hasPlayedBefore && <>
<ListItemButton onClick={() => setOpen(!open)}>
<ListItemIcon><Equalizer /></ListItemIcon>
<ListItemText primary={minecraft['gui.stats']} />
{open ? <ExpandLess /> : <ExpandMore />}
</ListItemButton>
<Collapse in={open} timeout="auto" unmountOnExit>
<List component='div' dense disablePadding>
{[
minecraft['stat.minecraft.play_time'] + ': ' + dayjs.duration(info.playTime / 20, 'seconds').humanize(),
lang.playerList.firstPlay + ': ' + dayjs(info.firstPlay).fromNow(),
lang.playerList.lastPlay + ': ' + dayjs(info.lastOnline).fromNow(),
minecraft['stat.minecraft.leave_game'] + ': ' + info.quit,
minecraft['stat.minecraft.deaths'] + ': ' + info.death,
minecraft['stat.minecraft.player_kills'] + ': ' + info.playerKill,
minecraft['stat.minecraft.mob_kills'] + ': ' + info.entityKill,
lang.playerList.tnt + ': ' + info.tnt
].map((it, i) => <ListItem key={i} sx={{ pl: 4 }}>
<ListItemIcon>{icons[i]}</ListItemIcon>
<ListItemText primary={it} />
</ListItem>)}
</List>
</Collapse>
</>}
</List>
<CardActions disableSpacing sx={{ justifyContent: 'flex-end' }}>
<Tooltip title={lang.playerList[info.whitelisted ? 'clickToRemoveWhitelist' : 'clickToAddWhitelist']}>
<IconButton onClick={() => whitelist(name, plugin, refresh, !info.whitelisted)}>
{info.whitelisted ? <Star color='warning' /> : <StarBorder />}
</IconButton>
</Tooltip>
<Tooltip title={lang.playerList[info.ban == null ? 'clickToBan' : 'clickToPardon']}>
<IconButton onClick={() => banPlayer(name, plugin, refresh, info.ban == null)}>
<Block color={info.ban == null ? undefined : 'error'} />
</IconButton>
</Tooltip>
</CardActions>
</>
: <></>
})
Example #24
Source File: Files.tsx From NekoMaid with MIT License | 4 votes |
Files: React.FC = () => {
const plugin = usePlugin()
const theme = useTheme()
const his = useHistory()
const loc = useLocation()
const drawerWidth = useDrawerWidth()
const tree = useRef<HTMLHRElement | null>(null)
const editor = useRef<UnControlled | null>(null)
const prevExpanded = useRef<string[]>([])
const dirs = useRef<Record<string, boolean>>({ })
// eslint-disable-next-line func-call-spacing
const loading = useRef<Record<string, () => Promise<void>> & { '!#LOADING'?: boolean }>({ })
const [id, setId] = useState(0)
const [curPath, setCurPath] = useState('')
const [progress, setProgress] = useState(-1)
const [copyPath, setCopyPath] = useState('')
const [expanded, setExpanded] = useState<string[]>([])
const [compressFile, setCompressFile] = useState<string | null>(null)
const [anchorEl, setAnchorEl] = React.useState<HTMLButtonElement | null>(null)
const isDir = !!dirs.current[curPath]
const dirPath = isDir ? curPath : curPath.substring(0, curPath.lastIndexOf('/'))
const spacing = theme.spacing(3)
const refresh = () => {
loading.current = { }
dirs.current = { }
prevExpanded.current = []
setCurPath('')
setExpanded([])
setId(id + 1)
}
useEffect(() => {
if (!tree.current) return
const resize = () => {
if (!tree.current) return
const height = tree.current.style.maxHeight = (window.innerHeight - tree.current.offsetTop - parseInt(spacing)) + 'px'
const style = (editor as any).current?.editor?.display?.wrapper?.style
if (style) style.height = height
}
resize()
window.addEventListener('resize', resize)
return window.removeEventListener('resize', resize)
}, [tree.current, spacing])
return <Box sx={{ height: '100vh', py: 3 }}>
<Toolbar />
<Container maxWidth={false}>
<Grid container spacing={3} sx={{ width: { sm: `calc(100vw - ${drawerWidth}px - ${theme.spacing(3)})` } }}>
<Grid item lg={4} md={12} xl={3} xs={12}>
<Card sx={{ minHeight: 400 }}>
<CardHeader
title={lang.files.filesList}
sx={{ position: 'relative' }}
action={<Box sx={{ position: 'absolute', right: theme.spacing(1), top: '50%', transform: 'translateY(-50%)' }}
>
<Tooltip title={lang.files.delete}><span>
<IconButton
disabled={!curPath}
size='small'
onClick={() => dialog({
okButton: { color: 'error' },
content: <>{lang.files.confirmDelete(<span className='bold'>{curPath}</span>)}
<span className='bold' style={{ color: theme.palette.error.main }}>({lang.unrecoverable})</span></>
}).then(it => it && plugin.emit('files:update', (res: boolean) => {
action(res)
if (!res) return
refresh()
if (loc.pathname.replace(/^\/NekoMaid\/files\/?/, '') === curPath) his.push('/NekoMaid/files')
}, curPath))}
><DeleteForever /></IconButton>
</span></Tooltip>
<Tooltip title={lang.files.createFile}>
<IconButton size='small' onClick={() => fileNameDialog(lang.files.createFile, curPath)
.then(it => it != null && his.push(`/NekoMaid/files/${dirPath ? dirPath + '/' : ''}${it}`))}>
<Description /></IconButton></Tooltip>
<Tooltip title={lang.files.createFolder}>
<IconButton size='small' onClick={() => fileNameDialog(lang.files.createFolder, curPath)
.then(it => it != null && plugin.emit('files:createDirectory', (res: boolean) => {
action(res)
if (res) refresh()
}, dirPath + '/' + it))}><CreateNewFolder /></IconButton></Tooltip>
<Tooltip title={lang.more}>
<IconButton size='small' onClick={e => setAnchorEl(anchorEl ? null : e.currentTarget)}><MoreHoriz /></IconButton>
</Tooltip>
</Box>} />
<Divider />
<TreeView
ref={tree}
defaultCollapseIcon={<ArrowDropDown />}
defaultExpandIcon={<ArrowRight />}
sx={{ flexGrow: 1, width: '100%', overflowY: 'auto' }}
expanded={expanded}
onNodeToggle={(_: any, it: string[]) => {
const l = loading.current
if (it.length < prevExpanded.current.length || !l[it[0]]) {
setExpanded(it)
prevExpanded.current = it
return
}
l[it[0]]().then(() => {
prevExpanded.current.unshift(it[0])
setExpanded([...prevExpanded.current])
delete l[it[0]]
})
delete l[it[0]]
}}
onNodeSelect={(_: any, it: string) => {
setCurPath(it[0] === '/' ? it.slice(1) : it)
if (dirs.current[it] || loading.current['!#LOADING']) return
if (it.startsWith('/')) it = it.slice(1)
his.push('/NekoMaid/files/' + it)
}}
>
<Item plugin={plugin} path='' loading={loading.current} dirs={dirs.current} key={id} />
</TreeView>
</Card>
</Grid>
<Grid item lg={8} md={12} xl={9} xs={12} sx={{ maxWidth: `calc(100vw - ${theme.spacing(1)})`, paddingBottom: 3 }}>
<Editor plugin={plugin} editorRef={editor} loading={loading.current} dirs={dirs.current} refresh={refresh} />
</Grid>
</Grid>
</Container>
<Menu
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onClose={() => setAnchorEl(null)}
anchorOrigin={anchorOrigin}
transformOrigin={anchorOrigin}
>
<MenuItem onClick={() => {
refresh()
setAnchorEl(null)
}}><ListItemIcon><Refresh /></ListItemIcon>{lang.refresh}</MenuItem>
<MenuItem disabled={!curPath} onClick={() => {
setAnchorEl(null)
fileNameDialog(lang.files.rename, curPath).then(it => it != null && plugin.emit('files:rename', (res: boolean) => {
action(res)
if (res) refresh()
}, curPath, dirPath + '/' + it))
}}><ListItemIcon><DriveFileRenameOutline /></ListItemIcon>{lang.files.rename}</MenuItem>
<MenuItem disabled={!curPath} onClick={() => {
setAnchorEl(null)
setCopyPath(curPath)
}}>
<ListItemIcon><FileCopy /></ListItemIcon>{lang.files.copy}
</MenuItem>
<MenuItem disabled={!copyPath} onClick={() => {
setAnchorEl(null)
toast(lang.files.pasting)
plugin.emit('files:copy', (res: boolean) => {
action(res)
refresh()
}, copyPath, dirPath)
}}>
<ListItemIcon><ContentPaste /></ListItemIcon>{lang.files.paste}
</MenuItem>
<MenuItem disabled={progress !== -1} component='label' htmlFor='NekoMaid-files-upload-input' onClick={() => setAnchorEl(null)}>
<ListItemIcon><Upload /></ListItemIcon>{progress === -1 ? lang.files.upload : `${lang.files.uploading} (${progress.toFixed(2)}%)`}
</MenuItem>
<MenuItem disabled={isDir} onClick={() => {
setAnchorEl(null)
toast(lang.files.downloading)
plugin.emit('files:download', (res: ArrayBuffer | null) => {
if (res) window.open(address! + 'Download/' + res, '_blank')
else failed()
}, curPath)
}}><ListItemIcon><Download /></ListItemIcon>{lang.files.download}</MenuItem>
<MenuItem onClick={() => {
setAnchorEl(null)
setCompressFile(curPath)
}}><ListItemIcon><Inbox /></ListItemIcon>{lang.files.compress}</MenuItem>
<MenuItem onClick={() => {
setAnchorEl(null)
toast(lang.files.uncompressing)
plugin.emit('files:compress', (res: boolean) => {
action(res)
refresh()
}, curPath)
}}><ListItemIcon><Outbox /></ListItemIcon>{lang.files.decompress}</MenuItem>
</Menu>
<Input id='NekoMaid-files-upload-input' type='file' sx={{ display: 'none' }} onChange={e => {
const elm = e.target as HTMLInputElement
const file = elm.files?.[0]
elm.value = ''
if (!file) return
const size = file.size
if (size > 128 * 1024 * 1024) return failed(lang.files.uploadTooBig)
toast(lang.files.uploading)
const name = dirPath + '/' + file.name
if (dirs.current[name] != null) return failed(lang.files.exists)
plugin.emit('files:upload', (res: string | null) => {
if (!res) return failed(lang.files.exists)
const formdata = new FormData()
formdata.append('file', file)
const xhr = new XMLHttpRequest()
setProgress(0)
xhr.open('put', address! + 'Upload/' + res)
xhr.onreadystatechange = () => {
if (xhr.readyState !== 4) return
setProgress(-1)
action(xhr.status === 200)
refresh()
}
xhr.upload.onprogress = e => e.lengthComputable && setProgress(e.loaded / e.total * 100)
xhr.send(formdata)
}, name[0] === '/' ? name.slice(1) : name)
}} />
<CompressDialog file={compressFile} path={dirPath} dirs={dirs.current} onClose={() => setCompressFile(null)} refresh={refresh} plugin={plugin} />
</Box>
}
Example #25
Source File: Config.tsx From NekoMaid with MIT License | 4 votes |
configs.push({
title: lang.config.serverConfig,
component () {
const plugin = usePlugin()
const globalData = useGlobalData()
const [flag, update] = useState(0)
const [info, setInfo] = useState<Record<string, string>>({ })
const [open, setOpen] = useState(false)
const [canGetData, setCanGetData] = useState(true)
const [loading, setLoading] = useState(false)
const setValue = (field: string, value: any, isGlobal = true) => {
plugin.emit('server:set', field, value)
success()
if (isGlobal) {
(globalData as any)[field] = value
update(flag + 1)
location.reload()
}
}
const createEditButtom = (field: string, isGlobal?: boolean, isInt = true) => <IconButton
onClick={() => dialog(
{
content: lang.inputValue,
input: isInt
? {
error: true,
type: 'number',
helperText: lang.invalidValue,
validator: (it: string) => /^\d+$/.test(it) && +it >= 0
}
: { }
}).then(res => res != null && setValue(field, isInt ? parseInt(res as any) : (res || null), isGlobal))}
><Edit /></IconButton>
const infoElm: JSX.Element[] = []
for (const key in info) {
const name = (lang.config as any)[key]
infoElm.push(<ListItem key={key} sx={{ pl: 4 }}>
<ListItemText
primary={key === 'isAikarFlags' ? <Link href='https://mcflags.emc.gs' target='_blank' rel='noopener'>{name}</Link> : name}
secondary={info[key].toString()}
/>
</ListItem>)
}
return <List>
<CircularLoading loading={loading} />
<ListItem secondaryAction={globalData.canSetMaxPlayers
? createEditButtom('maxPlayers')
: undefined}>
<ListItemText primary={lang.config.maxPlayers + ': ' + globalData.maxPlayers} />
</ListItem>
<ListItem secondaryAction={createEditButtom('spawnRadius')}>
<ListItemText primary={lang.config.spawnRadius + ': ' + globalData.spawnRadius} />
</ListItem>
<ListItem secondaryAction={createEditButtom('motd', false, false)}>
<ListItemText primary={lang.config.motd} />
</ListItem>
<ListItem secondaryAction={<Switch checked={globalData.hasWhitelist} onChange={e => setValue('hasWhitelist', e.target.checked)} />}>
<ListItemText primary={lang.config.whitelist} />
</ListItem>
{canGetData && <>
<ListItemButton onClick={() => {
if (infoElm.length) setOpen(!open)
else {
setLoading(true)
plugin.emit('server:fetchInfo', (data: any) => {
setLoading(false)
if (!data) {
failed(lang.unsupported)
setCanGetData(false)
return
}
setInfo(data)
setOpen(true)
})
}
}}>
<ListItemIcon><Equalizer /></ListItemIcon>
<ListItemText primary={lang.info} />
{open ? <ExpandLess /> : <ExpandMore />}
</ListItemButton>
<Collapse in={open} timeout='auto' unmountOnExit>
<List component='div' dense disablePadding>{infoElm}</List>
</Collapse>
</>}
</List>
}
},
{
title: lang.history,
component () {
const [cur, update] = useState(0)
const list: ServerRecord[] = JSON.parse(localStorage.getItem('NekoMaid:servers') || '[]')
return <List>
{list.sort((a, b) => b.time - a.time).map(it => {
const i = it.address.indexOf('?')
return <ListItem
disablePadding
key={it.address}
secondaryAction={<IconButton edge='end' size='small' onClick={() => {
localStorage.setItem('NekoMaid:servers', JSON.stringify(list.filter(s => s.address !== it.address)))
success()
update(cur + 1)
}}><Delete /></IconButton>}
>
<ListItemButton onClick={() => {
location.hash = ''
location.search = it.address
}} dense>
<ListItemAvatar><Avatar src={it.icon} variant='rounded'><HelpOutline /></Avatar></ListItemAvatar>
<ListItemText primary={<Tooltip title={it.address.slice(i + 1)}>
<span>{it.address.slice(0, i)}</span></Tooltip>} secondary={dayjs(it.time).fromNow()} />
</ListItemButton>
</ListItem>
})}
</List>
}
},
{
title: lang.config.theme,
component () {
const color = localStorage.getItem('NekoMaid:color') || 'blue'
return <CardContent sx={{ textAlign: 'center' }}>
<Box>
<ToggleButtonGroup exclusive value={localStorage.getItem('NekoMaid:colorMode') || ''} onChange={(_, it) => {
localStorage.setItem('NekoMaid:colorMode', it)
location.reload()
}}>
<ToggleButton value='light'><Brightness7 /> {lang.config.light}</ToggleButton>
<ToggleButton value=''><SettingsBrightness /> {lang.config.system}</ToggleButton>
<ToggleButton value='dark'><Brightness4 /> {lang.config.dark}</ToggleButton>
</ToggleButtonGroup>
</Box>
<Paper sx={{ marginTop: 2, width: '176px', overflow: 'hidden', display: 'inline-block' }}>
{Object.keys(colors).slice(1, 17).map((key, i) => {
const checked = color === key
const elm = <Box
key={key}
onClick={() => {
localStorage.setItem('NekoMaid:color', key)
location.reload()
}}
sx={{
backgroundColor: (colors as any)[key][600],
width: '44px',
height: '44px',
display: 'inline-block',
cursor: 'pointer'
}}
><Check htmlColor='white' sx={{ top: '10px', position: 'relative', opacity: checked ? 1 : 0 }} /></Box>
return (i + 1) % 4 === 0 ? <React.Fragment key={key}>{elm}<br /></React.Fragment> : elm
})}
</Paper>
</CardContent>
}
})
Example #26
Source File: App.tsx From NekoMaid with MIT License | 4 votes |
App: React.FC<{ darkMode: boolean, setDarkMode: (a: boolean) => void }> = React.memo(({ darkMode, setDarkMode }) => {
const loc = useLocation()
const his = useHistory()
const pluginRef = useRef<Plugin | null>(null)
const [mobileOpen, setMobileOpen] = useState(false)
const [globalItemsOpen, setGlobalItemsOpen] = useState(false)
const [globalData, setGlobalData] = useState<GlobalInfo>({ } as any)
const [drawerWidth, setDrawerWidth] = useState(240)
const updateF = useState(0)[1]
const create = useMemo(() => {
const io = socketIO(origin!, { path: pathname!, auth: { token } })
const map: Record<string, Plugin> = { }
const fn = (window as any).__NekoMaidAPICreate = (name: string) => map[name] || (map[name] = new Plugin(io, name))
const nekoMaid = pluginRef.current = fn('NekoMaid')
io.on('globalData', (data: GlobalInfo) => {
const his: ServerRecord[] = JSON.parse(localStorage.getItem('NekoMaid:servers') || '[]')
const curAddress = address!.replace('http://', '') + '?' + token
let cur = his.find(it => it.address === curAddress)
if (!cur) his.push((cur = { address: curAddress, time: 0 }))
cur.time = Date.now()
cur.icon = data.icon
const arr = loc.pathname.split('/')
if (!sent && arr.length > 2) io.emit('switchPage', arr[1], arr[2])
sent = true
localStorage.setItem('NekoMaid:servers', JSON.stringify(his))
new Set(Object.values(data.plugins).flat()).forEach(loadPlugin)
setGlobalData(data)
pages = { }
initPages(nekoMaid)
onGlobalDataReceived(nekoMaid, data)
update(Math.random())
if (process.env.NODE_ENV !== 'development' && data.pluginVersion !== version) toast(lang.pluginUpdate, 'warning')
}).on('!', () => {
io.close()
dialog({ content: lang.wrongToken, cancelButton: false })
// eslint-disable-next-line no-return-assign
.then(() => (location.search = location.pathname = location.hash = ''))
}).on('reconnect', () => {
toast(lang.reconnect)
setTimeout(() => location.reload(), 5000)
}).on('disconnect', () => failed(lang.disconnected)).on('connect_error', () => failed(lang.failedToConnect))
return fn
}, [])
useEffect(() => { if (!loc.pathname || loc.pathname === '/') his.replace('/NekoMaid/dashboard') }, [loc.pathname])
useEffect(() => {
update = updateF
return () => { update = undefined as any }
}, [])
const handleDrawerToggle = () => {
setDrawerWidth(240)
setMobileOpen(!mobileOpen)
}
const isExpand = drawerWidth === 240
const routes: JSX.Element[] = []
const mapToItem = (name: string, it: Page) => {
const path = Array.isArray(it.path) ? it.path[0] : it.path
const key = '/' + name + '/' + path
routes.push(<pluginCtx.Provider key={key} value={create(name)}>
<Route
path={Array.isArray(it.path) ? it.path.map(it => '/' + name + '/' + it) : key}
component={it.component}
strict={it.strict}
exact={it.exact}
sensitive={it.sensitive}
/>
</pluginCtx.Provider>)
const icon = <ListItemIcon><pluginCtx.Provider value={create(name)}>
{(typeof it.icon === 'function' ? <it.icon /> : it.icon) || <Build />}
</pluginCtx.Provider></ListItemIcon>
return it.title
? <NavLink key={key} to={'/' + name + '/' + (it.url || path)} activeClassName='actived'>
<ListItem button>
{isExpand ? icon : <Tooltip title={it.title} placement='right'>{icon}</Tooltip>}
{isExpand && <ListItemText primary={it.title} />}
</ListItem>
</NavLink>
: undefined
}
const singlePages: JSX.Element[] = []
const multiPagesPages: Array<JSX.Element | JSX.Element[]> = []
let index = 0
for (const name in pages) {
if (pages[name].length === 1) {
const elm = mapToItem(name, pages[name][0])
if (elm) singlePages.push(elm)
} else {
if (multiPagesPages.length) multiPagesPages.push(<Divider key={index++} />)
multiPagesPages.push(pages[name].map(it => mapToItem(name, it)!).filter(Boolean))
}
}
if (singlePages.length) multiPagesPages.push(<Divider key={index++} />, singlePages)
const drawer = <Box sx={{ overflowX: 'hidden' }}>
<Toolbar />
<Divider sx={{ display: { sm: 'none', xs: 'block' } }} />
<List sx={{
'& a': {
color: 'inherit',
textDecoration: 'inherit'
},
'& .actived > div': {
fontWeight: 'bold',
color: theme => theme.palette.primary.main,
backgroundColor: theme => alpha(theme.palette.primary.main, theme.palette.action.selectedOpacity) + '!important',
'& svg': { color: theme => theme.palette.primary.main + '!important' }
}
}}>{multiPagesPages.flat()}</List>
</Box>
return <Box sx={{ display: 'flex' }}>
<CssBaseline />
<AppBar position='fixed' sx={{ zIndex: theme => theme.zIndex.drawer + 1 }}>
<Toolbar>
<IconButton
color='inherit'
edge='start'
onClick={() => setDrawerWidth(isExpand ? 57 : 240)}
sx={{ mr: 1, display: { sm: 'inline-flex', xs: 'none' } }}
><ChevronLeft sx={{ transition: '.3s', transform: isExpand ? undefined : 'rotate(-180deg)' }} /></IconButton>
<IconButton color='inherit' edge='start' onClick={handleDrawerToggle} sx={{ mr: 2, display: { sm: 'none' } }}><Menu /></IconButton>
<Typography variant='h3' noWrap component='div' sx={{ flexGrow: 1 }}>NekoMaid</Typography>
{globalData.hasNBTAPI && <IconButton
color='inherit'
onClick={() => setGlobalItemsOpen(!globalItemsOpen)}
onDragOver={() => setGlobalItemsOpen(true)}
><Backpack /></IconButton>}
<LanguageSwitch />
<IconButton color='inherit' edge='end' onClick={() => setDarkMode(!darkMode)}>
{darkMode ? <Brightness7 /> : <Brightness4 />}
</IconButton>
</Toolbar>
</AppBar>
<globalCtx.Provider value={globalData}>
<Box component='nav' sx={{ width: { sm: drawerWidth }, flexShrink: { sm: 0 } }}>
<Drawer
variant='temporary'
open={mobileOpen}
onClose={handleDrawerToggle}
ModalProps={{ keepMounted: true }}
sx={{
display: { xs: 'block', sm: 'none' },
'& .MuiDrawer-paper': {
boxSizing: 'border-box',
width: drawerWidth,
backgroundImage: theme => theme.palette.mode === 'dark'
? 'linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.05))'
: undefined
}
}}
>
{drawer}
</Drawer>
<Drawer
open
variant='permanent'
sx={{
display: { xs: 'none', sm: 'block' },
'& .MuiDrawer-paper': {
boxSizing: 'border-box',
width: drawerWidth,
transition: 'width .3s',
backgroundImage: theme => theme.palette.mode === 'dark' ? 'linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.05))' : undefined
}
}}
>
{drawer}
</Drawer>
</Box>
<Box component='main' sx={{ flexGrow: 1, width: '100vw' }}>
<drawerWidthCtx.Provider value={drawerWidth}>{routes}</drawerWidthCtx.Provider>
{globalData.hasNBTAPI && <pluginCtx.Provider value={pluginRef.current}>
<GlobalItems open={globalItemsOpen} onClose={() => setGlobalItemsOpen(false)} />
</pluginCtx.Provider>}
</Box>
</globalCtx.Provider>
</Box>
})
Example #27
Source File: MenuItem.tsx From console with GNU Affero General Public License v3.0 | 4 votes |
MenuItem = ({
page,
stateClsName = "",
onExpand,
selectedMenuItem,
pathValue = "",
expandedGroup = "",
setSelectedMenuItem,
id = `${Math.random()}`,
setPreviewMenuGroup,
previewMenuGroup,
}: {
page: any;
stateClsName?: string;
setSelectedMenuItem: (value: string) => void;
selectedMenuItem?: any;
pathValue?: string;
onExpand: (id: any) => void;
expandedGroup?: string;
id?: string;
setPreviewMenuGroup: (value: string) => void;
previewMenuGroup: string;
}) => {
const childrenMenuList = page?.children?.filter(
(item: any) =>
((item.customPermissionFnc
? item.customPermissionFnc()
: hasPermission(CONSOLE_UI_RESOURCE, IAM_PAGES_PERMISSIONS[item.to])) ||
item.forceDisplay) &&
!item.fsHidden
);
let hasChildren = childrenMenuList?.length;
const expandCollapseHandler = useCallback(
(e: any) => {
e.preventDefault();
if (previewMenuGroup === page.id) {
setPreviewMenuGroup("");
} else if (page.id !== selectedMenuItem) {
setPreviewMenuGroup(page.id);
onExpand("");
}
if (page.id === selectedMenuItem && selectedMenuItem === expandedGroup) {
onExpand(null);
} else if (page.id === selectedMenuItem) {
onExpand(selectedMenuItem);
setPreviewMenuGroup("");
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[page, selectedMenuItem, previewMenuGroup, expandedGroup]
);
const selectMenuHandler = useCallback(
(e: any) => {
onExpand(page.id);
setSelectedMenuItem(page.id);
page.onClick && page.onClick(e);
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[page]
);
const onClickHandler = hasChildren
? expandCollapseHandler
: selectMenuHandler;
const isActiveGroup = expandedGroup === page.id;
const activeClsName =
pathValue.includes(selectedMenuItem) && page.id === selectedMenuItem
? "active"
: "";
return (
<React.Fragment>
<ListItem
key={page.to}
button
onClick={(e: any) => {
onClickHandler(e);
setSelectedMenuItem(selectedMenuItem);
}}
component={page.component}
to={page.to}
id={id}
className={`${activeClsName} ${stateClsName} main-menu-item `}
disableRipple
sx={{
...menuItemContainerStyles,
marginTop: "5px",
...menuItemMiniStyles,
"& .expanded-icon": {
border: "1px solid #35393c",
},
}}
>
{page.icon && (
<Tooltip title={`${page.name}`} placement="right">
<ListItemIcon
sx={{ ...menuItemIconStyles }}
className={`${
isActiveGroup && hasChildren ? "expanded-icon" : ""
}`}
>
<Suspense fallback={<div>...</div>}>
<page.icon />
</Suspense>
</ListItemIcon>
</Tooltip>
)}
{page.name && (
<ListItemText
className={stateClsName}
sx={{ ...menuItemTextStyles }}
primary={page.name}
/>
)}
{hasChildren ? (
isActiveGroup || previewMenuGroup === page.id ? (
<MenuCollapsedIcon
height={15}
width={15}
className="group-icon"
style={{ color: "white" }}
/>
) : (
<MenuExpandedIcon
height={15}
width={15}
className="group-icon"
style={{ color: "white" }}
/>
)
) : null}
</ListItem>
{(isActiveGroup || previewMenuGroup === page.id) && hasChildren ? (
<Collapse
key={page.id}
id={`${page.id}-children`}
in={true}
timeout="auto"
unmountOnExit
>
<List
component="div"
disablePadding
key={page.id}
sx={{
marginLeft: "15px",
"&.mini": {
marginLeft: "0px",
},
}}
className={`${stateClsName}`}
>
{childrenMenuList.map((item: any) => {
return (
<ListItem
key={item.to}
button
component={item.component}
to={item.to}
onClick={(e: any) => {
if (page.id) {
setPreviewMenuGroup("");
setSelectedMenuItem(page.id);
}
}}
disableRipple
sx={{
...menuItemStyle,
...menuItemMiniStyles,
}}
className={`${stateClsName}`}
>
{item.icon && (
<Tooltip title={`${item.name}`} placement="right">
<ListItemIcon
sx={{
background: "#00274D",
display: "flex",
alignItems: "center",
justifyContent: "center",
"& svg": {
fill: "#fff",
minWidth: "12px",
maxWidth: "12px",
},
}}
className="menu-icon"
>
<Suspense fallback={<div>...</div>}>
<item.icon />
</Suspense>
</ListItemIcon>
</Tooltip>
)}
{item.name && (
<ListItemText
className={stateClsName}
sx={{ ...menuItemTextStyles, marginLeft: "16px" }}
primary={item.name}
/>
)}
</ListItem>
);
})}
</List>
</Collapse>
) : null}
</React.Fragment>
);
}
Example #28
Source File: CollectionRowActions.tsx From firecms with MIT License | 4 votes |
/**
*
* @param entity
* @param isSelected
* @param selectionEnabled
* @param size
* @param toggleEntitySelection
* @param onCopyClicked
* @param onEditClicked
* @param onDeleteClicked
* @constructor
*
* @category Collection components
*/
export function CollectionRowActions<M extends { [Key: string]: any }>({
entity,
isSelected,
selectionEnabled,
size,
toggleEntitySelection,
onCopyClicked,
onEditClicked,
onDeleteClicked
}:
{
entity: Entity<M>,
size: CollectionSize,
isSelected?: boolean,
selectionEnabled?: boolean,
toggleEntitySelection?: (selectedEntity: Entity<M>) => void
onEditClicked?: (selectedEntity: Entity<M>) => void,
onCopyClicked?: (selectedEntity: Entity<M>) => void,
onDeleteClicked?: (selectedEntity: Entity<M>) => void,
}) {
const editEnabled = Boolean(onEditClicked);
const copyEnabled = Boolean(onCopyClicked);
const deleteEnabled = Boolean(onDeleteClicked);
const classes = useTableStyles();
const [anchorEl, setAnchorEl] = React.useState<any | null>(null);
const openMenu = useCallback((event: React.MouseEvent) => {
setAnchorEl(event.currentTarget);
event.stopPropagation();
}, [setAnchorEl]);
const closeMenu = useCallback(() => {
setAnchorEl(null);
}, [setAnchorEl]);
const onCheckboxChange = (event: React.ChangeEvent) => {
if (toggleEntitySelection)
toggleEntitySelection(entity);
event.stopPropagation();
};
const onDeleteClick = useCallback((event: MouseEvent) => {
event.stopPropagation();
if (onDeleteClicked)
onDeleteClicked(entity);
setAnchorEl(null);
}, [entity, onDeleteClicked, setAnchorEl]);
const onCopyClick = useCallback((event: MouseEvent) => {
event.stopPropagation();
if (onCopyClicked)
onCopyClicked(entity);
setAnchorEl(null);
}, [entity, onCopyClicked, setAnchorEl]);
return (
<div className={classes.cellButtonsWrap}>
{(editEnabled || deleteEnabled || selectionEnabled) &&
<div className={classes.cellButtons}
>
{editEnabled &&
<Tooltip title={`Edit ${entity.id}`}>
<IconButton
onClick={(event: MouseEvent) => {
event.stopPropagation();
if (onEditClicked)
onEditClicked(entity);
}}
size="large">
<KeyboardTab/>
</IconButton>
</Tooltip>
}
{selectionEnabled &&
<Tooltip title={`Select ${entity.id}`}>
<Checkbox
checked={isSelected}
onChange={onCheckboxChange}
/>
</Tooltip>}
{(copyEnabled || deleteEnabled) &&
<IconButton onClick={openMenu} size="large">
<MoreVert/>
</IconButton>
}
{(copyEnabled || deleteEnabled) && <Menu
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onClose={closeMenu}
elevation={2}
>
{deleteEnabled && <MenuItem onClick={onDeleteClick}>
<ListItemIcon>
<Delete/>
</ListItemIcon>
<ListItemText primary={"Delete"}/>
</MenuItem>}
{copyEnabled && <MenuItem onClick={onCopyClick}>
<ListItemIcon>
<FileCopy/>
</ListItemIcon>
<ListItemText primary="Copy"/>
</MenuItem>}
</Menu>}
</div>}
{size !== "xs" && (
<div className={classes.cellButtonsId}>
{entity
? <Typography
className={"mono"}
variant={"caption"}
color={"textSecondary"}> {entity.id} </Typography>
: <Skeleton variant="text"/>
}
</div>
)}
</div>
);
}
Example #29
Source File: SubstatInput.tsx From genshin-optimizer with MIT License | 4 votes |
export default function SubstatInput({ index, artifact, setSubstat }: { index: number, artifact: ICachedArtifact | undefined, setSubstat: (index: number, substat: ISubstat) => void, }) {
const { t } = useTranslation("artifact")
const { mainStatKey = "", rarity = 5 } = artifact ?? {}
const { key = "", value = 0, rolls = [], efficiency = 0 } = artifact?.substats[index] ?? {}
const accurateValue = rolls.reduce((a, b) => a + b, 0)
const unit = KeyMap.unit(key), rollNum = rolls.length
let error: string = "", rollData: readonly number[] = [], allowedRolls = 0
if (artifact) {
// Account for the rolls it will need to fill all 4 substates, +1 for its base roll
const rarity = artifact.rarity
const { numUpgrades, high } = Artifact.rollInfo(rarity)
const maxRollNum = numUpgrades + high - 3;
allowedRolls = maxRollNum - rollNum
rollData = key ? Artifact.getSubstatRollData(key, rarity) : []
}
const rollOffset = 7 - rollData.length
if (!rollNum && key && value) error = error || t`editor.substat.error.noCalc`
if (allowedRolls < 0) error = error || t("editor.substat.error.noOverRoll", { value: allowedRolls + rollNum })
return <CardLight>
<Box sx={{ display: "flex" }}>
<ButtonGroup size="small" sx={{ width: "100%", display: "flex" }}>
<DropdownButton
startIcon={key ? StatIcon[key] : undefined}
title={key ? KeyMap.getArtStr(key) : t('editor.substat.substatFormat', { value: index + 1 })}
disabled={!artifact}
color={key ? "success" : "primary"}
sx={{ whiteSpace: "nowrap" }}>
{key && <MenuItem onClick={() => setSubstat(index, { key: "", value: 0 })}>{t`editor.substat.noSubstat`}</MenuItem>}
{allSubstatKeys.filter(key => mainStatKey !== key)
.map(k => <MenuItem key={k} selected={key === k} disabled={key === k} onClick={() => setSubstat(index, { key: k, value: 0 })} >
<ListItemIcon>{StatIcon[k]}</ListItemIcon>
<ListItemText>{KeyMap.getArtStr(k)}</ListItemText>
</MenuItem>)}
</DropdownButton>
<CustomNumberInputButtonGroupWrapper sx={{ flexBasis: 30, flexGrow: 1 }} >
<CustomNumberInput
float={unit === "%"}
placeholder={t`editor.substat.selectSub`}
value={key ? value : undefined}
onChange={value => setSubstat(index, { key, value: value ?? 0 })}
disabled={!key}
error={!!error}
sx={{
px: 1,
}}
inputProps={{
sx: { textAlign: "right" }
}}
/>
</CustomNumberInputButtonGroupWrapper>
{!!rollData.length && <TextButton>{t`editor.substat.nextRolls`}</TextButton>}
{rollData.map((v, i) => {
let newValue = cacheValueString(accurateValue + v, unit)
newValue = artifactSubstatRollCorrection[rarity]?.[key]?.[newValue] ?? newValue
return <Button key={i} color={`roll${clamp(rollOffset + i, 1, 6)}` as any} disabled={(value && !rollNum) || allowedRolls <= 0} onClick={() => setSubstat(index, { key, value: parseFloat(newValue) })}>{newValue}</Button>
})}
</ButtonGroup>
</Box>
<Box sx={{ p: 1, }}>
{error ? <SqBadge color="error">{t`ui:error`}</SqBadge> : <Grid container>
<Grid item>
<SqBadge color={rollNum === 0 ? "secondary" : `roll${clamp(rollNum, 1, 6)}`}>
{rollNum ? t("editor.substat.RollCount", { count: rollNum }) : t`editor.substat.noRoll`}
</SqBadge>
</Grid>
<Grid item flexGrow={1}>
{!!rolls.length && [...rolls].sort().map((val, i) =>
<Typography component="span" key={`${i}.${val}`} color={`roll${clamp(rollOffset + rollData.indexOf(val), 1, 6)}.main`} sx={{ ml: 1 }} >{cacheValueString(val, unit)}</Typography>)}
</Grid>
<Grid item xs="auto" flexShrink={1}>
<Typography>
<Trans t={t} i18nKey="editor.substat.eff" color="text.secondary">
Efficiency: <PercentBadge valid={true} max={rollNum * 100} value={efficiency ? efficiency : t`editor.substat.noStat` as string} />
</Trans>
</Typography>
</Grid>
</Grid>}
</Box>
</CardLight >
}