@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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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>
})