Skip to content

Commit

Permalink
feat: optimize frontend for mobile device
Browse files Browse the repository at this point in the history
  • Loading branch information
samuelncui committed Oct 13, 2023
1 parent baf0166 commit b0505b4
Show file tree
Hide file tree
Showing 6 changed files with 199 additions and 126 deletions.
12 changes: 9 additions & 3 deletions frontend/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ import { JobsBrowser, JobsType } from "./pages/jobs";

import "./app.less";
import { sleep } from "./tools";
import { Nullable } from "tsdef";
import { Job } from "./entity";
import { useEffect } from "react";
import { useState } from "react";

Expand Down Expand Up @@ -52,7 +50,15 @@ const App = () => {
return (
<div id="app">
<ThemeProvider theme={theme}>
<Tabs className="tabs" value={location.pathname.slice(1)} onChange={handleTabChange} indicatorColor="secondary">
<Tabs
className="tabs"
value={location.pathname.slice(1)}
onChange={handleTabChange}
indicatorColor="secondary"
variant="scrollable"
scrollButtons="auto"
allowScrollButtonsMobile
>
<Tab label="File" value={FileBrowserType} />
<Tab label="Backup" value={BackupType} />
<Tab label="Restore" value={RestoreType} />
Expand Down
164 changes: 69 additions & 95 deletions frontend/src/components/job-archive.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import format from "format-duration";

import { Virtuoso, VirtuosoHandle } from "react-virtuoso";

import { styled } from "@mui/material/styles";
import Grid from "@mui/material/Grid";
import Box from "@mui/material/Box";
import ListItem from "@mui/material/ListItem";
Expand All @@ -28,6 +29,7 @@ import { formatFilesize, sleep } from "../tools";

import { JobCard } from "./job-card";
import { RefreshContext } from "../pages/jobs";
import { FileListItem } from "./job-file-list-item";

export const ArchiveCard = ({ job, state, display }: { job: Job; state: JobArchiveState; display: JobArchiveDisplay | null }): JSX.Element => {
const [fields, progress] = useMemo(() => {
Expand Down Expand Up @@ -191,66 +193,12 @@ const ArchiveViewFilesDialog = ({ sources }: { sources: SourceState[] }) => {
<Button size="small" onClick={handleClickOpen}>
View Files
</Button>
{open && <FileList handleClose={handleClose} sources={sources} />}
{open && <FileList title="View Files" onClose={handleClose} sources={sources} />}
</Fragment>
);
};

const FileList = memo(({ handleClose, sources }: { handleClose: () => void; sources: SourceState[] }) => {
const virtuosoRef = useRef<VirtuosoHandle | null>(null);
useEffect(() => {
(async () => {
const idx = sources.findIndex((src) => src.status !== CopyStatus.SUBMITED && src.status !== CopyStatus.STAGED);
if (idx < 0) {
return;
}

await sleep(100);
console.log(idx, virtuosoRef);

if (!virtuosoRef.current) {
return;
}
virtuosoRef.current.scrollToIndex({ index: idx - 5, align: "center", behavior: "smooth" });
})();
}, [sources]);

return (
<Dialog open={true} onClose={handleClose} maxWidth={"lg"} fullWidth scroll="paper" sx={{ height: "100%" }} className="view-log-dialog">
<DialogTitle>View Files</DialogTitle>
<DialogContent dividers style={{ padding: 0 }}>
<Virtuoso
style={{ width: "100%", height: "100%" }}
totalCount={sources.length}
ref={virtuosoRef}
itemContent={(idx) => {
const src = sources[idx];
if (!src || !src.source) {
return null;
}

return (
<ListItem key={idx} component="div" disablePadding>
<ListItemText
primary={src.source.base + src.source.path.join("/")}
secondary={`Size: ${formatFilesize(src.size)} Status: ${CopyStatus[src.status]}`}
style={{ padding: 0, margin: 5 }}
/>
</ListItem>
);
}}
/>
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>Close</Button>
</DialogActions>
</Dialog>
);
});

const RollbackDialog = ({ jobID, state }: { jobID: bigint; state: JobArchiveState }) => {
const refresh = useContext(RefreshContext);

const [open, setOpen] = useState(false);
const handleClickOpen = () => {
setOpen(true);
Expand All @@ -259,6 +207,18 @@ const RollbackDialog = ({ jobID, state }: { jobID: bigint; state: JobArchiveStat
setOpen(false);
};

return (
<Fragment>
<Button size="small" onClick={handleClickOpen}>
Rollback
</Button>
{open && <RollbackFileList onClose={handleClose} jobID={jobID} state={state} />}
</Fragment>
);
};

const RollbackFileList = ({ onClose, jobID, state }: { onClose: () => void; jobID: bigint; state: JobArchiveState }) => {
const refresh = useContext(RefreshContext);
const handleClickItem = useCallback(
async (idx: number) => {
const found = state.sources[idx];
Expand All @@ -277,53 +237,67 @@ const RollbackDialog = ({ jobID, state }: { jobID: bigint; state: JobArchiveStat
}

await cli.jobEditState({ id: jobID, state: { state: { oneofKind: "archive", archive: { ...state, sources } } } });
alert(`Rollback to file '${path}' success!`);
await refresh();

alert(`Rollback to file '${path}' success!`);
},
[state, refresh],
);

return (
<Fragment>
<Button size="small" onClick={handleClickOpen}>
Rollback
</Button>
{open && (
<Dialog open={true} onClose={handleClose} maxWidth={"lg"} fullWidth scroll="paper" sx={{ height: "100%" }} className="view-log-dialog">
<DialogTitle>Click File to Rollback</DialogTitle>
<DialogContent dividers style={{ padding: 0 }}>
<Virtuoso
style={{ width: "100%", height: "100%" }}
totalCount={state.sources.length}
itemContent={(idx) => {
const src = state.sources[idx];
if (!src || !src.source) {
return null;
}

return (
<ListItem key={idx} component="div" disablePadding>
<ListItemButton style={{ padding: 0 }} onClick={() => handleClickItem(idx)}>
<ListItemText
primary={src.source.base + src.source.path.join("/")}
secondary={`Size: ${formatFilesize(src.size)} Status: ${CopyStatus[src.status]}`}
style={{ padding: 0, margin: 5 }}
/>
</ListItemButton>
</ListItem>
);
}}
/>
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>Close</Button>
</DialogActions>
</Dialog>
)}
</Fragment>
);
return <FileList title="Click Rollback Target File" onClose={onClose} onClickItem={handleClickItem} sources={state.sources} />;
};

const FileList = memo(
({ onClose, onClickItem, title, sources }: { onClose: () => void; onClickItem?: (idx: number) => void; title: string; sources: SourceState[] }) => {
const virtuosoRef = useRef<VirtuosoHandle | null>(null);

useEffect(() => {
(async () => {
const idx = sources.findIndex((src) => src.status !== CopyStatus.SUBMITED && src.status !== CopyStatus.STAGED);
if (idx < 0) {
return;
}

await sleep(100);
if (!virtuosoRef.current) {
return;
}

virtuosoRef.current.scrollToIndex({ index: idx, align: "center", behavior: "smooth" });
})();
}, [sources]);

return (
<Dialog open={true} onClose={onClose} maxWidth={"lg"} fullWidth scroll="paper" sx={{ height: "100%" }} className="view-log-dialog">
<DialogTitle>{title}</DialogTitle>
<DialogContent dividers style={{ padding: 0 }}>
<Virtuoso
style={{ width: "100%", height: "100%" }}
totalCount={sources.length}
ref={virtuosoRef}
itemContent={(idx) => {
const src = sources[idx];
if (!src || !src.source) {
return null;
}

return (
<FileListItem
src={{ path: src.source.base + src.source.path.join("/"), size: src.size, status: src.status }}
onClick={onClickItem ? () => onClickItem(idx) : undefined}
/>
);
}}
/>
</DialogContent>
<DialogActions>
<Button onClick={onClose}>Close</Button>
</DialogActions>
</Dialog>
);
},
);

function makeArchiveCopyingParam(jobID: bigint, param: JobArchiveCopyingParam): JobDispatchRequest {
return {
id: jobID,
Expand Down
7 changes: 5 additions & 2 deletions frontend/src/components/job-card.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useCallback, useContext } from "react";

import { styled } from "@mui/material/styles";
import Typography from "@mui/material/Typography";
import Card from "@mui/material/Card";
import CardActions from "@mui/material/CardActions";
Expand Down Expand Up @@ -27,6 +28,8 @@ const DeleteJobButton = ({ jobID }: { jobID: bigint }) => {
);
};

const RightButtonsContainer = styled("div")({ marginLeft: "auto !important", marginRight: 0 });

export const JobCard = ({ job, detail, buttons }: { job: Job; detail?: JSX.Element; buttons?: JSX.Element }) => {
return (
<Card sx={{ textAlign: "left" }} className="job-detail">
Expand All @@ -40,10 +43,10 @@ export const JobCard = ({ job, detail, buttons }: { job: Job; detail?: JSX.Eleme
<Divider />
<CardActions>
<div>{buttons}</div>
<div style={{ marginLeft: "auto", marginRight: 0 }}>
<RightButtonsContainer>
<ViewLogDialog key="VIEW_LOG" jobID={job.id} />
<DeleteJobButton key="DELETE_JOB" jobID={job.id} />
</div>
</RightButtonsContainer>
</CardActions>
</Card>
);
Expand Down
40 changes: 40 additions & 0 deletions frontend/src/components/job-file-list-item.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { memo } from "react";

import { styled } from "@mui/material/styles";
import ListItem from "@mui/material/ListItem";
import ListItemText from "@mui/material/ListItemText";
import ListItemButton from "@mui/material/ListItemButton";

import { CopyStatus, SourceState } from "../entity";
import { formatFilesize } from "../tools";

const FileListItemText = styled(ListItemText)({ padding: 0, margin: 5, marginLeft: 10 });
const FileListItemButton = styled(ListItemButton)({ padding: 0 });

export interface FileState {
path: string;
status: CopyStatus;
size: bigint;
// message?: string;
}

export const FileListItem = memo(({ src, onClick, className }: { src?: FileState; onClick?: () => void; className?: string }) => {
if (!src) {
return null;
}

const text = <FileListItemText primary={src.path} secondary={`Size: ${formatFilesize(src.size)} Status: ${CopyStatus[src.status]}`} />;
if (!onClick) {
return (
<ListItem component="div" className={className} disablePadding>
{text}
</ListItem>
);
}

return (
<ListItem component="div" className={className} disablePadding>
<FileListItemButton onClick={onClick}>{text}</FileListItemButton>
</ListItem>
);
});
Loading

0 comments on commit b0505b4

Please sign in to comment.