@mui/icons-material#ChevronLeft TypeScript Examples
The following examples show how to use
@mui/icons-material#ChevronLeft.
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: FileGallery.tsx From abrechnung with GNU Affero General Public License v3.0 | 4 votes |
export default function FileGallery({ transaction }) {
const [files, setFiles] = useState([]); // map of file id to object
const [active, setActive] = useState(0);
const setTransactions = useSetRecoilState(groupTransactions(transaction.group_id));
const [showUploadDialog, setShowUploadDialog] = useState(false);
const [showImage, setShowImage] = useState(false);
useEffect(() => {
const newFileIDs = new Set(transaction.files.map((file) => file.id));
const filteredFiles = files.reduce((map, file) => {
map[file.id] = file;
return map;
}, {});
for (const loadedFile of files) {
if (!newFileIDs.has(loadedFile.id)) {
URL.revokeObjectURL(loadedFile.objectUrl); // clean up memory
delete filteredFiles[loadedFile.id];
}
}
setFiles(Object.values(filteredFiles)); // TODO: maybe include placeholders
setActive(Math.max(0, Math.min(active, transaction.files.length - 1)));
const newFiles = transaction.files.filter((file) => !filteredFiles.hasOwnProperty(file.id));
Promise.all(
newFiles.map((newFile) => {
return fetchFile({
fileID: newFile.id,
blobID: newFile.blob_id,
}).then((resp) => {
const objectUrl = URL.createObjectURL(resp.data);
return {
...newFile,
objectUrl: objectUrl,
};
});
})
)
.then((newlyLoadedFiles) => {
setFiles([...Object.values(filteredFiles), ...newlyLoadedFiles]);
})
.catch((err) => {
toast.error(`Error loading file: ${err}`);
});
}, [transaction]);
const toNextImage = () => {
if (active < files.length - 1) {
setActive(active + 1);
}
};
const toPrevImage = () => {
if (active > 0) {
setActive(active - 1);
}
};
const doShowImage = (img) => {
setShowImage(true);
};
const deleteSelectedFile = () => {
if (active < files.length) {
// sanity check, should not be needed
deleteFile({ fileID: files[active].id })
.then((t) => {
updateTransactionInState(t, setTransactions);
setShowImage(false);
})
.catch((err) => {
toast.error(`Error deleting file: ${err}`);
});
}
};
// @ts-ignore
return (
<>
<Grid
container
justifyContent="center"
alignItems="center"
style={{
position: "relative",
height: "200px",
width: "100%",
}}
>
{files.length === 0 ? (
<img height="100%" src={placeholderImg} alt="placeholder" />
) : (
files.map((item, idx) => (
<Transition key={item.id} in={active === idx} timeout={duration}>
{(state) => (
<img
height="100%"
style={{
...defaultStyle,
...transitionStyles[state],
}}
onClick={() => doShowImage(item)}
src={item.objectUrl}
srcSet={item.objectUrl}
alt={item.filename.split(".")[0]}
loading="lazy"
/>
)}
</Transition>
))
)}
<Chip
sx={{ position: "absolute", top: "5px", right: "10px" }}
size="small"
label={`${Math.min(files.length, active + 1)} / ${files.length}`}
/>
{active > 0 && (
<IconButton onClick={toPrevImage} sx={{ position: "absolute", top: "40%", left: "10px" }}>
<ChevronLeft />
</IconButton>
)}
{active < files.length - 1 && (
<IconButton onClick={toNextImage} sx={{ position: "absolute", top: "40%", right: "10px" }}>
<ChevronRight />
</IconButton>
)}
{transaction.is_wip && (
<>
<IconButton
color="primary"
sx={{
position: "absolute",
top: "80%",
right: "10px",
}}
onClick={() => setShowUploadDialog(true)}
>
<AddCircle fontSize="large" />
</IconButton>
<ImageUploadDialog
transaction={transaction}
show={showUploadDialog}
onClose={() => setShowUploadDialog(false)}
/>
</>
)}
</Grid>
<Dialog open={showImage} onClose={() => setShowImage(false)} scroll="body">
{active < files.length && <DialogTitle>{files[active].filename.split(".")[0]}</DialogTitle>}
<DialogContent>
<Grid
container
justifyContent="center"
alignItems="center"
style={{
position: "relative",
}}
>
{active < files.length && (
<img
height="100%"
width="100%"
src={files[active]?.objectUrl}
srcSet={files[active]?.objectUrl}
alt={files[active]?.filename}
loading="lazy"
/>
)}
{active > 0 && (
<IconButton
onClick={toPrevImage}
sx={{
position: "absolute",
top: "40%",
left: "0px",
}}
>
<ChevronLeft />
</IconButton>
)}
{active < files.length - 1 && (
<IconButton
onClick={toNextImage}
sx={{
position: "absolute",
top: "40%",
right: "0px",
}}
>
<ChevronRight />
</IconButton>
)}
</Grid>
</DialogContent>
{transaction.is_wip && (
<DialogActions>
<Button startIcon={<Delete />} onClick={deleteSelectedFile} variant="outlined" color="error">
Delete
</Button>
</DialogActions>
)}
</Dialog>
</>
);
}
Example #2
Source File: TransactionActions.tsx From abrechnung with GNU Affero General Public License v3.0 | 4 votes |
export default function TransactionActions({ groupID, transaction }) {
const [confirmDeleteDialogOpen, setConfirmDeleteDialogOpen] = useState(false);
const history = useHistory();
const userPermissions = useRecoilValue(currUserPermissions(groupID));
const setTransactions = useSetRecoilState(groupTransactions(transaction.group_id));
const localTransactionChanges = useRecoilValue(pendingTransactionDetailChanges(transaction.id));
const localPositionChanges = useRecoilValue(pendingTransactionPositionChanges(transaction.id));
const resetLocalTransactionChanges = useResetRecoilState(pendingTransactionDetailChanges(transaction.id));
const resetLocalPositionChanges = useResetRecoilState(pendingTransactionPositionChanges(transaction.id));
const updateTransactionAndClearLocal = useRecoilTransaction_UNSTABLE(
({ get, set, reset }) =>
(transaction: TransactionBackend) => {
set(groupTransactions(transaction.group_id), (currTransactions) => {
return currTransactions.map((t) => (t.id === transaction.id ? transaction : t));
});
reset(pendingTransactionDetailChanges(transaction.id));
reset(pendingTransactionPositionChanges(transaction.id));
}
);
const edit = () => {
if (!transaction.is_wip) {
createTransactionChange({
transactionID: transaction.id,
})
.then((t) => {
updateTransactionAndClearLocal(t);
})
.catch((err) => {
toast.error(err);
});
}
};
const abortEdit = () => {
if (transaction.is_wip) {
if (transaction.has_committed_changes) {
discardTransactionChange({
transactionID: transaction.id,
})
.then((t) => {
updateTransactionAndClearLocal(t);
})
.catch((err) => {
toast.error(err);
});
} else {
history.push(`/groups/${groupID}/`);
}
}
};
const commitEdit = () => {
if (transaction.is_wip) {
// update the transaction given the currently pending changes
// find out which local changes we have and send them to da server
const positions = Object.values(localPositionChanges.modified)
.concat(
Object.values(localPositionChanges.added).map((position) => ({
...position,
id: -1,
}))
)
.map((p) => ({
id: p.id,
name: p.name,
communist_shares: p.communist_shares,
price: p.price,
usages: p.usages,
deleted: p.deleted,
}));
if (Object.keys(localTransactionChanges).length > 0) {
updateTransaction({
transactionID: transaction.id,
description: transaction.description,
value: transaction.value,
billedAt: transaction.billed_at,
currencySymbol: transaction.currency_symbol,
currencyConversionRate: transaction.currency_conversion_rate,
creditorShares: transaction.creditor_shares,
debitorShares: transaction.debitor_shares,
...localTransactionChanges,
positions: positions.length > 0 ? positions : null,
})
.then((t) => {
updateTransactionAndClearLocal(t);
})
.catch((err) => {
toast.error(err);
});
} else if (positions.length > 0) {
updateTransactionPositions({
transactionID: transaction.id,
positions: positions,
})
.then((t) => {
updateTransactionAndClearLocal(t);
})
.catch((err) => {
toast.error(err);
});
} else {
commitTransaction({ transactionID: transaction.id })
.then((t) => {
updateTransactionAndClearLocal(t);
})
.catch((err) => {
toast.error(err);
});
}
}
};
const confirmDeleteTransaction = () => {
deleteTransaction({ transactionID: transaction.id })
.then((t) => {
// TODO: use recoil transaction
updateTransactionInState(t, setTransactions);
resetLocalPositionChanges();
resetLocalTransactionChanges();
history.push(`/groups/${groupID}/`);
})
.catch((err) => {
toast.error(err);
});
};
return (
<>
<Grid container justifyContent="space-between">
<Grid item sx={{ display: "flex", alignItems: "center" }}>
<IconButton
sx={{ display: { xs: "none", md: "inline-flex" } }}
component={RouterLink}
to={`/groups/${groupID}/`}
>
<ChevronLeft />
</IconButton>
<Chip color="primary" label={transaction.type} />
</Grid>
<Grid item>
{userPermissions.can_write && (
<>
{transaction.is_wip ? (
<>
<Button color="primary" onClick={commitEdit}>
Save
</Button>
<Button color="error" onClick={abortEdit}>
Cancel
</Button>
</>
) : (
<IconButton color="primary" onClick={edit}>
<Edit />
</IconButton>
)}
<IconButton color="error" onClick={() => setConfirmDeleteDialogOpen(true)}>
<Delete />
</IconButton>
</>
)}
</Grid>
</Grid>
<Dialog maxWidth="xs" aria-labelledby="confirmation-dialog-title" open={confirmDeleteDialogOpen}>
<DialogTitle id="confirmation-dialog-title">Confirm delete transaction</DialogTitle>
<DialogContent dividers>
Are you sure you want to delete the transaction "{transaction.description}"
</DialogContent>
<DialogActions>
<Button autoFocus onClick={() => setConfirmDeleteDialogOpen(false)} color="primary">
Cancel
</Button>
<Button onClick={confirmDeleteTransaction} color="error">
Ok
</Button>
</DialogActions>
</Dialog>
</>
);
}
Example #3
Source File: MenuToggle.tsx From console with GNU Affero General Public License v3.0 | 4 votes |
MenuToggle = ({ isOpen, onToggle }: MenuToggleProps) => {
const stateClsName = isOpen ? "wide" : "mini";
const dispatch = useDispatch();
const licenseInfo = useSelector(
(state: AppState) => state?.system?.licenseInfo
);
const operatorMode = useSelector(
(state: AppState) => state.system.operatorMode
);
const [isLicenseLoading, invokeLicenseInfoApi] = useApi(
(res: any) => {
dispatch(setLicenseInfo(res));
},
() => {
dispatch(setLicenseInfo(null));
}
);
//Get License info from SUBNET
useEffect(() => {
if (!operatorMode) {
invokeLicenseInfoApi("GET", `/api/v1/subnet/info`);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const { plan = "" } = licenseInfo || {};
return (
<Box
className={`${stateClsName}`}
sx={{
marginLeft: "26px",
marginTop: "28px",
marginRight: "8px",
display: "flex",
height: "36px",
"&.mini": {
flexFlow: "column",
alignItems: "center",
margin: "auto",
marginTop: "28px",
},
"& .logo": {
background: "transparent",
"&.wide": {
flex: "1",
"& svg": {
fill: "white",
width: 120,
},
},
"&.mini": {
marginBottom: "5px",
flex: "1",
color: "#ffffff",
"& svg": {
width: 24,
fill: "rgba(255, 255, 255, 0.8)",
},
},
},
}}
>
{isOpen ? (
<div className={`logo ${stateClsName}`}>
{operatorMode ? (
<OperatorLogo />
) : (
<LicensedConsoleLogo plan={plan} isLoading={isLicenseLoading} />
)}
</div>
) : (
<div className={`logo ${stateClsName}`}>
<Suspense fallback={<div>...</div>}>
<VersionIcon />
</Suspense>
</div>
)}
<IconButton
className={`${stateClsName}`}
sx={{
height: "30px",
width: "30px",
"&.mini": {
marginBottom: "10px",
"&:hover": {
background: "#081C42",
},
},
"&:hover": {
borderRadius: "50%",
background: "#073052",
},
"& svg": {
fill: "#ffffff",
height: "18px",
width: "18px",
},
}}
onClick={() => {
if (isOpen) {
onToggle(false);
} else {
onToggle(true);
}
}}
size="small"
>
{isOpen ? <ChevronLeft /> : <MenuIcon />}
</IconButton>
</Box>
);
}
Example #4
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>
})