Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: optimize images and add loading skeletons #3

Merged
merged 12 commits into from
Nov 25, 2021
Next Next commit
fix: optimize images and add skeleton
  • Loading branch information
EKarton committed Nov 2, 2021
commit 9856cfaeb6e2394e57da290b9c188e3d58c5f198
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ module.exports = {
extends: ['react-app', 'react-app/jest', 'plugin:prettier/recommended'],
plugins: ['prettier'],
rules: {
'no-unused-vars': 'error',
'no-unused-vars': 'warn',
'no-console': ['error', { allow: ['warn', 'error'] }],
},
};
4 changes: 4 additions & 0 deletions src/apps/MainApp.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Switch, Route } from 'react-router-dom';
import LandingPage from 'pages/LandingPage';
import LoginPage from 'pages/LoginPage';
import AuthenticatedApp, { AuthenticatedPaths } from './AuthenticatedApp';
import LogoutPage from 'pages/LogoutPage';

export default function MainApp() {
return (
Expand All @@ -12,6 +13,9 @@ export default function MainApp() {
<Route path="/login">
<LoginPage />
</Route>
<Route path="/logout">
<LogoutPage />
</Route>
<Route paths={AuthenticatedPaths}>
<AuthenticatedApp />
</Route>
Expand Down
29 changes: 29 additions & 0 deletions src/components/FileListTableSkeleton.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Table } from 'semantic-ui-react';
import Skeleton from '@mui/material/Skeleton';

export function FileListTableSkeleton({ numRows }) {
return (
<Table striped>
<Table.Header>
<Table.Row>
<Table.HeaderCell>Name</Table.HeaderCell>
<Table.HeaderCell>Date Modified</Table.HeaderCell>
<Table.HeaderCell>File Size</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{Array.from({ length: numRows }, () => (
<Table.Row span>
<Table.Cell colspan={3}>
<Skeleton animation="wave" />
</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table>
);
}

FileListTableSkeleton.defaultProps = {
numRows: 5,
};
16 changes: 14 additions & 2 deletions src/components/GlobalAppBar.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import AppBar from '@mui/material/AppBar';
import IconButton from '@mui/material/IconButton';
import MenuIcon from '@mui/icons-material/Menu';
import Toolbar from '@mui/material/Toolbar';
import RCloneLogo from 'assets/images/rclone-icon/logo_on_light__horizontal_color_256px.png';
import SettingsIcon from '@mui/icons-material/Settings';
import LogoutIcon from '@mui/icons-material/Logout';
import './GlobalAppBar.scss';
import { Link } from 'react-router-dom';

export default function GlobalAppBar({ onDrawerButttonClicked }) {
return (
Expand All @@ -18,7 +20,17 @@ export default function GlobalAppBar({ onDrawerButttonClicked }) {
>
<MenuIcon />
</IconButton>
<img className="global-app-bar__logo" src={RCloneLogo} alt="RClone" />
<div className="global-app-bar__logo" />
<div>
<IconButton aria-label="settings">
<SettingsIcon />
</IconButton>
<Link to="/logout">
<IconButton>
<LogoutIcon />
</IconButton>
</Link>
</div>
</Toolbar>
</AppBar>
);
Expand Down
1 change: 1 addition & 0 deletions src/components/GlobalAppBar.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
background-origin: content-box;
background-position: left center;
height: 1.5rem;
flex-grow: 1;
}

&__nav-button {
Expand Down
4 changes: 3 additions & 1 deletion src/components/GlobalNavBar.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ export default function GlobalNavBar({ isExpanded }) {
root: cx('global-navbar', {
'global-navbar--expanded': isExpanded,
}),
paper: 'global-navbar__paper',
paper: cx('global-navbar__paper', {
'global-navbar__paper--expanded': isExpanded,
}),
}}
>
<List>
Expand Down
10 changes: 5 additions & 5 deletions src/components/GlobalNavBar.scss
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@ $minimized-navbar-width: 4rem;
.global-navbar {
width: $minimized-navbar-width;

&__paper {
width: $minimized-navbar-width;
}

&--expanded {
width: $maximized-navbar-width;
}

&__paper {
width: $minimized-navbar-width;

&__paper {
&--expanded {
width: $maximized-navbar-width;
}
}
Expand Down
63 changes: 63 additions & 0 deletions src/components/Image.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { useEffect, useState } from 'react';
import useRCloneClient from 'hooks/useRCloneClient';
import { Skeleton } from '@mui/material';
import cx from 'classnames';
import './Image.scss';

export default function Image({ image, width, height, imgClassName, skeletonClassName }) {
const rCloneClient = useRCloneClient();
const [imageUrl, setImageUrl] = useState();
const [error, setError] = useState();

useEffect(() => {
const fetchData = async () => {
try {
setError(undefined);
setImageUrl(undefined);

const imageBlob = await rCloneClient.fetchFileContents(
image.remote,
image.folderPath,
image.fileName
);

const imageUrl = URL.createObjectURL(new Blob([imageBlob]));
setImageUrl(imageUrl);
} catch (error) {
setError(error);
}
};

if (!imageUrl && image) {
fetchData();
}

return () => {
if (imageUrl) {
URL.revokeObjectURL(imageUrl);
}
};
}, [image, imageUrl, rCloneClient]);

if (!imageUrl) {
return (
<Skeleton
variant="rectangular"
className={cx('image', skeletonClassName)}
width={width}
height={height}
/>
);
}

return (
<img
className={cx('image', imgClassName)}
src={imageUrl}
alt={image.fileName}
loading="lazy"
width={width}
height={height}
/>
);
}
5 changes: 5 additions & 0 deletions src/components/Image.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.image {
object-fit: cover;
width: 100%;
height: 100%;
}
58 changes: 8 additions & 50 deletions src/components/LazyImage.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import { Skeleton } from '@mui/material';
import useRCloneClient from 'hooks/useRCloneClient';
import { useEffect, useState } from 'react';
import { useInView } from 'react-intersection-observer';
import cx from 'classnames';
import Image from './Image';
import './LazyImage.scss';

export default function LazyImage({ image, width, height, imgClassName }) {
Expand All @@ -11,64 +8,25 @@ export default function LazyImage({ image, width, height, imgClassName }) {
rootMargin: '0px',
});

const rCloneClient = useRCloneClient();
const [imageUrl, setImageUrl] = useState();
const [error, setError] = useState();

useEffect(() => {
const fetchData = async () => {
try {
setError(undefined);
setImageUrl(undefined);

const imageBlob = await rCloneClient.fetchFileContents(
image.remote,
image.folderPath,
image.fileName
);

const imageUrl = URL.createObjectURL(new Blob([imageBlob]));
setImageUrl(imageUrl);
} catch (error) {
setError(error);
}
};

if (inView && !imageUrl && image) {
fetchData();
}

return () => {
if (imageUrl) {
URL.revokeObjectURL(imageUrl);
}
};
}, [image, imageUrl, inView, rCloneClient]);

const renderContent = () => {
if (error) {
return <div>Error! {error.message}</div>;
}

if (!imageUrl) {
return <Skeleton variant="rectangular" className="lazy-image__skeleton" />;
if (!inView) {
return null;
}

return (
<img
className={cx('lazy-image__img', imgClassName)}
src={imageUrl}
alt={image.fileName}
loading="lazy"
<Image
image={image}
width={width}
height={height}
imgClassName="lazy-image__img"
skeletonClassName="lazy-image__skeleton"
/>
);
};

return (
<div ref={ref} data-inview={inView} style={{ width, height }}>
{inView ? renderContent() : null}
{renderContent()}
</div>
);
}
14 changes: 11 additions & 3 deletions src/components/LazyImage.scss
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
.lazy-image {
&__img {
object-fit: contain;
background-color: black;
padding: 0.25rem;
object-fit: cover;
// background-color: #121212;
padding: 0.125rem;
}

&__skeleton-wrapper {
// background-color: #121212;
width: 100% !important;
height: 100% !important;
padding: 0.125rem;
}

&__skeleton {
// background-color: #212121 !important;
width: 100% !important;
height: 100% !important;
}
Expand Down
12 changes: 12 additions & 0 deletions src/hooks/useRCloneInfo.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,24 @@ export default function useRCloneInfo() {
return state?.[key] || localStorage.getItem(key);
};

const removeInfo = (dispatchType, localStorageKey) => {
localStorage.removeItem(localStorageKey);
dispatch({ type: dispatchType, payload: undefined });
};

const clearRCloneInfo = () => {
removeInfo(actionTypes.SET_ENDPOINT, 'endpoint');
removeInfo(actionTypes.SET_ENDPOINT, 'username');
removeInfo(actionTypes.SET_ENDPOINT, 'password');
};

return {
rCloneInfo: {
endpoint: getValue('endpoint'),
username: getValue('username'),
password: getValue('password'),
},
setRCloneInfo,
clearRCloneInfo,
};
}
Loading